From e69498a4e9a601f3dc990f36f4b9aea7a3e1dd2b Mon Sep 17 00:00:00 2001 From: Abhinandan Purkait Date: Wed, 9 Aug 2023 09:16:13 +0000 Subject: [PATCH] feat(snapshots, restores): store restores as a part of snap meta, expose counts on rest Signed-off-by: Abhinandan Purkait --- .../core/controller/resources/operations.rs | 12 ++- .../src/bin/core/volume/clone_operations.rs | 46 ++++++++-- .../agents/src/bin/core/volume/operations.rs | 11 +++ .../agents/src/bin/core/volume/service.rs | 30 ++++++- .../grpc/proto/v1/volume/volume.proto | 4 + .../grpc/src/operations/volume/traits.rs | 5 ++ .../src/operations/volume/traits_snapshots.rs | 9 ++ .../plugin/src/bin/rest-plugin/main.rs | 12 +-- control-plane/plugin/src/operations.rs | 13 ++- control-plane/plugin/src/resources/mod.rs | 5 +- .../plugin/src/resources/snapshot.rs | 3 +- control-plane/plugin/src/resources/utils.rs | 7 +- control-plane/plugin/src/resources/volume.rs | 47 +++++++--- .../rest/openapi-specs/v0_api_spec.yaml | 12 +++ .../rest/service/src/v0/snapshots.rs | 1 + .../src/types/v0/store/snapshots/volume.rs | 90 ++++++++++++++++++- .../stor-port/src/types/v0/store/volume.rs | 15 +++- 17 files changed, 290 insertions(+), 32 deletions(-) diff --git a/control-plane/agents/src/bin/core/controller/resources/operations.rs b/control-plane/agents/src/bin/core/controller/resources/operations.rs index a3b4263b9..e17ad0120 100644 --- a/control-plane/agents/src/bin/core/controller/resources/operations.rs +++ b/control-plane/agents/src/bin/core/controller/resources/operations.rs @@ -1,5 +1,6 @@ -use crate::controller::registry::Registry; +use crate::controller::{registry::Registry, resources::OperationGuardArc}; use agents::errors::SvcError; +use stor_port::types::v0::store::volume::VolumeSpec; /// Resource Cordon Operations. #[async_trait::async_trait] @@ -265,6 +266,7 @@ pub(crate) trait ResourcePruning { pub(crate) trait ResourceCloning { type Create: Sync + Send; type CreateOutput: Sync + Send + Sized; + type Destroy: Sync + Send; /// Create a clone for the `Self` resource. async fn create_clone( @@ -272,4 +274,12 @@ pub(crate) trait ResourceCloning { registry: &Registry, request: &Self::Create, ) -> Result; + + /// Destroy a clone for the `Self` resource. + async fn destroy_clone( + &mut self, + registry: &Registry, + request: &Self::Destroy, + volume: OperationGuardArc, + ) -> Result<(), SvcError>; } diff --git a/control-plane/agents/src/bin/core/volume/clone_operations.rs b/control-plane/agents/src/bin/core/volume/clone_operations.rs index a78beee8f..93aec8ee6 100644 --- a/control-plane/agents/src/bin/core/volume/clone_operations.rs +++ b/control-plane/agents/src/bin/core/volume/clone_operations.rs @@ -2,8 +2,8 @@ use crate::{ controller::{ registry::Registry, resources::{ - operations::{ResourceCloning, ResourceLifecycleExt}, - operations_helper::SpecOperationsHelper, + operations::{ResourceCloning, ResourceLifecycle, ResourceLifecycleExt}, + operations_helper::{GuardedOperationsHelper, SpecOperationsHelper}, OperationGuardArc, TraceStrLog, }, scheduling::{volume::CloneVolumeSnapshot, ResourceFilter}, @@ -16,11 +16,13 @@ use stor_port::{ types::v0::{ store::{ replica::ReplicaSpec, - snapshots::volume::VolumeSnapshot, + snapshots::volume::{ + CreateRestoreInfo, DestroyRestoreInfo, VolumeSnapshot, VolumeSnapshotOperation, + }, volume::{VolumeContentSource, VolumeSpec}, }, transport::{ - CreateSnapshotVolume, Replica, SnapshotCloneId, SnapshotCloneParameters, + CreateSnapshotVolume, DestroyVolume, Replica, SnapshotCloneId, SnapshotCloneParameters, SnapshotCloneSpecParams, }, }, @@ -45,14 +47,48 @@ impl SnapshotCloneOp<'_> { impl ResourceCloning for OperationGuardArc { type Create = CreateSnapshotVolume; type CreateOutput = OperationGuardArc; + type Destroy = DestroyVolume; async fn create_clone( &mut self, registry: &Registry, request: &Self::Create, ) -> Result { + let spec_clone = self + .start_update( + registry, + self.as_ref(), + VolumeSnapshotOperation::CreateRestore(CreateRestoreInfo::new( + request.params().uuid.clone(), + )), + ) + .await?; let request = CreateVolumeSource::Snapshot(SnapshotCloneOp(request, self)); - OperationGuardArc::::create_ext(registry, &request).await + let create_result = OperationGuardArc::::create_ext(registry, &request).await; + self.complete_update(registry, create_result, spec_clone) + .await + } + + async fn destroy_clone( + &mut self, + registry: &Registry, + request: &Self::Destroy, + mut volume: OperationGuardArc, + ) -> Result<(), SvcError> { + let spec_clone = self + .start_update( + registry, + self.as_ref(), + VolumeSnapshotOperation::DestroyRestore(DestroyRestoreInfo::new( + request.uuid.clone(), + )), + ) + .await?; + + let destroy_result = volume.destroy(registry, request).await; + + self.complete_update(registry, destroy_result, spec_clone) + .await } } diff --git a/control-plane/agents/src/bin/core/volume/operations.rs b/control-plane/agents/src/bin/core/volume/operations.rs index d468d40cf..56cffc2b5 100644 --- a/control-plane/agents/src/bin/core/volume/operations.rs +++ b/control-plane/agents/src/bin/core/volume/operations.rs @@ -43,7 +43,9 @@ use stor_port::{ }, }; +use crate::controller::resources::UpdateInnerValue; use std::{fmt::Debug, ops::Deref}; +use stor_port::types::v0::store::volume::VolumeContentSource; #[async_trait::async_trait] impl ResourceLifecycle for OperationGuardArc { @@ -174,6 +176,14 @@ impl ResourceLifecycle for OperationGuardArc { } } + let spec = self.lock().clone(); + if let Some(VolumeContentSource::Snapshot(snap_id, _)) = spec.content_source { + if let Ok(mut snap_guard) = specs.volume_snapshot(&snap_id).await { + snap_guard.lock().remove_restore(self.uuid()); + snap_guard.update(); + } + } + self.complete_destroy(Ok(()), registry).await } } @@ -791,6 +801,7 @@ impl ResourceLifecycleExt> for OperationGuardArc Result<(), SvcError> { let mut volume = self.specs().volume(&request.uuid).await?; - volume.destroy(&self.registry, request).await?; + let content_source = volume.as_ref().content_source.clone(); + let snap_guard = match content_source { + None => None, + Some(VolumeContentSource::Snapshot(snap_uuid, _)) => { + match self.specs().volume_snapshot(&snap_uuid).await { + Ok(snap_guard) => Some(snap_guard), + Err(SvcError::VolSnapshotNotFound { .. }) => None, + Err(error) => return Err(error), + } + } + }; + + match snap_guard { + None => { + volume.destroy(&self.registry, request).await?; + } + Some(mut snap_guard) => { + snap_guard + .destroy_clone(&self.registry, request, volume) + .await?; + } + } Ok(()) } diff --git a/control-plane/grpc/proto/v1/volume/volume.proto b/control-plane/grpc/proto/v1/volume/volume.proto index ac3985b30..8d92d4c1e 100644 --- a/control-plane/grpc/proto/v1/volume/volume.proto +++ b/control-plane/grpc/proto/v1/volume/volume.proto @@ -59,6 +59,8 @@ message VolumeSpec { optional AffinityGroup affinity_group = 10; // Volume Content Source i.e the snapshot or a volume. optional VolumeContentSource content_source = 11; + // Number of snapshots taken on this volume. + uint32 num_snapshots = 12; // Volume Content Source i.e the snapshot or a volume. message VolumeContentSource { @@ -507,6 +509,8 @@ message VolumeSnapshotMeta { uint64 spec_size = 6; // Size taken by the snapshot and its predecessors. uint64 total_allocated_size = 7; + // Number of restores done from this snapshot. + uint32 num_restore = 8; message ReplicaSnapshots { repeated ReplicaSnapshot snapshots = 1; diff --git a/control-plane/grpc/src/operations/volume/traits.rs b/control-plane/grpc/src/operations/volume/traits.rs index 9cd2ea542..6bb517b5b 100644 --- a/control-plane/grpc/src/operations/volume/traits.rs +++ b/control-plane/grpc/src/operations/volume/traits.rs @@ -156,6 +156,7 @@ impl From for volume::VolumeDefinition { thin: volume_spec.thin, affinity_group: volume_spec.affinity_group.into_opt(), content_source: volume_spec.content_source.into_opt(), + num_snapshots: volume_spec.metadata.num_snapshots() as u32, }), metadata: Some(volume::Metadata { spec_status: spec_status as i32, @@ -318,6 +319,10 @@ impl TryFrom for VolumeSpec { affinity_group: volume_spec.affinity_group.into_opt(), metadata: VolumeMetadata::new(volume_meta.as_thin), content_source: volume_spec.content_source.try_into_opt()?, + // Reviewer: Should we rather maintain the list of snapshots in the grpc layer and now + // return the count in rest api. Would that make the grpc layer slow if + // there are lots of snap or is the below good enough? + num_snapshots: volume_spec.num_snapshots as usize, }; Ok(volume_spec) } diff --git a/control-plane/grpc/src/operations/volume/traits_snapshots.rs b/control-plane/grpc/src/operations/volume/traits_snapshots.rs index 0c85e8968..5c2a29bef 100644 --- a/control-plane/grpc/src/operations/volume/traits_snapshots.rs +++ b/control-plane/grpc/src/operations/volume/traits_snapshots.rs @@ -107,6 +107,7 @@ impl From<&stor_port::types::v0::store::snapshots::volume::VolumeSnapshot> for V total_allocated_size: value.metadata().total_allocated_size(), txn_id: value.metadata().txn_id().clone(), transactions, + num_restores: value.num_restores(), }, } } @@ -137,6 +138,8 @@ pub struct VolumeSnapshotMeta { /// The "actual" snapshots can be accessed by the key `txn_id`. /// Failed transactions are any other key. transactions: HashMap>, + /// Volume metadata information. + num_restores: usize, } impl VolumeSnapshotMeta { /// Get the volume snapshot status. @@ -167,6 +170,10 @@ impl VolumeSnapshotMeta { pub fn total_allocated_size(&self) -> u64 { self.total_allocated_size } + /// + pub fn num_restores(&self) -> usize { + self.num_restores + } } /// Volume replica snapshot information. @@ -468,6 +475,7 @@ impl TryFrom for VolumeSnapshot { snapshots.map(|s| (k, s)) }) .collect::, _>>()?, + num_restores: meta.num_restore as usize, }, state: VolumeSnapshotState { info, @@ -624,6 +632,7 @@ impl TryFrom for volume::VolumeSnapshot { size: value.meta.size, spec_size: value.meta.spec_size, total_allocated_size: value.meta.total_allocated_size, + num_restore: value.meta.num_restores as u32, }), state: Some(volume::VolumeSnapshotState { state: Some(snapshot::SnapshotState { diff --git a/control-plane/plugin/src/bin/rest-plugin/main.rs b/control-plane/plugin/src/bin/rest-plugin/main.rs index ce42389aa..e308c5734 100644 --- a/control-plane/plugin/src/bin/rest-plugin/main.rs +++ b/control-plane/plugin/src/bin/rest-plugin/main.rs @@ -2,8 +2,8 @@ use clap::Parser; use openapi::tower::client::Url; use plugin::{ operations::{ - Cordoning, Drain, Get, GetBlockDevices, GetSnapshots, List, Operations, RebuildHistory, - ReplicaTopology, Scale, + Cordoning, Drain, Get, GetBlockDevices, GetSnapshots, List, ListVolumes, Operations, + RebuildHistory, ReplicaTopology, Scale, }, resources::{ blockdevice, cordon, drain, node, pool, snapshot, volume, CordonResources, DrainResources, @@ -83,13 +83,15 @@ async fn execute(cli_args: CliArgs) { } GetDrainArgs::Nodes => drain::NodeDrains::list(&cli_args.output).await, }, - GetResources::Volumes => volume::Volumes::list(&cli_args.output).await, + GetResources::Volumes(vol_args) => { + volume::Volumes::list(&cli_args.output, vol_args).await + } GetResources::Volume { id } => volume::Volume::get(id, &cli_args.output).await, GetResources::RebuildHistory { id } => { volume::Volume::rebuild_history(id, &cli_args.output).await } - GetResources::VolumeReplicaTopologies => { - volume::Volume::topologies(&cli_args.output).await + GetResources::VolumeReplicaTopologies(vol_args) => { + volume::Volume::topologies(&cli_args.output, vol_args).await } GetResources::VolumeReplicaTopology { id } => { volume::Volume::topology(id, &cli_args.output).await diff --git a/control-plane/plugin/src/operations.rs b/control-plane/plugin/src/operations.rs index ba39aee17..26cfb368e 100644 --- a/control-plane/plugin/src/operations.rs +++ b/control-plane/plugin/src/operations.rs @@ -1,4 +1,6 @@ -use crate::resources::{utils, CordonResources, DrainResources, GetResources, ScaleResources}; +use crate::resources::{ + utils, volume::VolumesArgs, CordonResources, DrainResources, GetResources, ScaleResources, +}; use async_trait::async_trait; /// The types of operations that are supported. @@ -41,6 +43,13 @@ pub trait List { async fn list(output: &utils::OutputFormat); } +/// List trait. +/// To be implemented by resources which support the 'list' operation. +#[async_trait(?Send)] +pub trait ListVolumes { + async fn list(output: &utils::OutputFormat, volume_args: &VolumesArgs); +} + /// Get trait. /// To be implemented by resources which support the 'get' operation. #[async_trait(?Send)] @@ -62,7 +71,7 @@ pub trait Scale { #[async_trait(?Send)] pub trait ReplicaTopology { type ID; - async fn topologies(output: &utils::OutputFormat); + async fn topologies(output: &utils::OutputFormat, volume_args: &VolumesArgs); async fn topology(id: &Self::ID, output: &utils::OutputFormat); } diff --git a/control-plane/plugin/src/resources/mod.rs b/control-plane/plugin/src/resources/mod.rs index c79a05f32..5784629dd 100644 --- a/control-plane/plugin/src/resources/mod.rs +++ b/control-plane/plugin/src/resources/mod.rs @@ -2,6 +2,7 @@ use crate::resources::{ blockdevice::BlockDeviceArgs, node::{DrainNodeArgs, GetNodeArgs}, snapshot::VolumeSnapshotArgs, + volume::VolumesArgs, }; pub mod blockdevice; @@ -29,13 +30,13 @@ pub enum GetResources { #[clap(subcommand)] Drain(GetDrainArgs), /// Get all volumes. - Volumes, + Volumes(VolumesArgs), /// Get volume with the given ID. Volume { id: VolumeId }, /// Get Rebuild history for the volume with the given ID. RebuildHistory { id: VolumeId }, /// Get the replica topology for all volumes. - VolumeReplicaTopologies, + VolumeReplicaTopologies(VolumesArgs), /// Get the replica topology for the volume with the given ID. VolumeReplicaTopology { id: VolumeId }, /// Get volume snapshots based on input args. diff --git a/control-plane/plugin/src/resources/snapshot.rs b/control-plane/plugin/src/resources/snapshot.rs index 847363e93..328b6d0c8 100644 --- a/control-plane/plugin/src/resources/snapshot.rs +++ b/control-plane/plugin/src/resources/snapshot.rs @@ -56,7 +56,8 @@ impl CreateRow for openapi::models::VolumeSnapshot { ::utils::bytes::into_human(meta.spec_size), ::utils::bytes::into_human(state.allocated_size), ::utils::bytes::into_human(meta.total_allocated_size), - state.source_volume + state.source_volume, + self.definition.metadata.num_restores ] } } diff --git a/control-plane/plugin/src/resources/utils.rs b/control-plane/plugin/src/resources/utils.rs index e18a8a327..5811aeebb 100644 --- a/control-plane/plugin/src/resources/utils.rs +++ b/control-plane/plugin/src/resources/utils.rs @@ -20,7 +20,9 @@ lazy_static! { "STATUS", "SIZE", "THIN-PROVISIONED", - "ALLOCATED" + "ALLOCATED", + "SNAPSHOTS", + "SOURCE" ]; pub static ref SNAPSHOT_HEADERS: Row = row![ "ID", @@ -28,7 +30,8 @@ lazy_static! { "SOURCE-SIZE", "ALLOCATED-SIZE", "TOTAL-ALLOCATED-SIZE", - "SOURCE-VOL" + "SOURCE-VOL", + "RESTORES" ]; pub static ref POOLS_HEADERS: Row = row![ "ID", diff --git a/control-plane/plugin/src/resources/volume.rs b/control-plane/plugin/src/resources/volume.rs index 335510bfa..fd495d6ce 100644 --- a/control-plane/plugin/src/resources/volume.rs +++ b/control-plane/plugin/src/resources/volume.rs @@ -1,23 +1,36 @@ use crate::{ - operations::{Get, List, Scale}, + operations::{Get, Scale}, resources::{utils, VolumeId}, rest_wrapper::RestClient, }; use async_trait::async_trait; use crate::{ - operations::{RebuildHistory, ReplicaTopology}, + operations::{ListVolumes, RebuildHistory, ReplicaTopology}, resources::utils::{optional_cell, CreateRow, CreateRows, GetHeaderRow, OutputFormat}, }; use chrono::prelude::*; -use openapi::tower::client::Url; +use openapi::{models::VolumeContentSource, tower::client::Url}; use prettytable::Row; +use serde::Serialize; use std::{collections::HashMap, str::FromStr}; /// Volumes resource. #[derive(clap::Args, Debug)] pub struct Volumes {} +/// New type to wrap BD returned from REST call with all set to false +#[derive(Clone, Debug, Serialize)] +struct Restores(openapi::models::Volume); + +#[derive(Debug, Clone, clap::Args)] +/// BlockDevice args +pub struct VolumesArgs { + #[clap(long)] + /// Shows all devices if invoked, otherwise shows usable disks. + restores: bool, +} + impl CreateRow for openapi::models::Volume { fn row(&self) -> Row { let state = &self.state; @@ -38,7 +51,13 @@ impl CreateRow for openapi::models::Volume { .usage .as_ref() .map(|u| ::utils::bytes::into_human(u.allocated)) - ) + ), + self.spec.num_snapshots, + optional_cell(self.spec.content_source.as_ref().map(|source| { + match source { + VolumeContentSource::snapshot(_) => "Snapshot", + } + })), ] } } @@ -58,9 +77,9 @@ impl GetHeaderRow for openapi::models::Volume { } #[async_trait(?Send)] -impl List for Volumes { - async fn list(output: &utils::OutputFormat) { - if let Some(volumes) = get_paginated_volumes().await { +impl ListVolumes for Volumes { + async fn list(output: &utils::OutputFormat, volume_args: &VolumesArgs) { + if let Some(volumes) = get_paginated_volumes(volume_args).await { // Print table, json or yaml based on output format. utils::print_table(output, volumes); } @@ -70,7 +89,7 @@ impl List for Volumes { /// Get the list of volumes over multiple paginated requests if necessary. /// If any `get_volumes` request fails, `None` will be returned. This prevents the user from getting /// a partial list when they expect a complete list. -async fn get_paginated_volumes() -> Option> { +async fn get_paginated_volumes(volume_args: &VolumesArgs) -> Option> { // The number of volumes to get per request. let max_entries = 200; let mut starting_token = Some(0); @@ -95,6 +114,13 @@ async fn get_paginated_volumes() -> Option> { } } + if volume_args.restores { + volumes.retain(|vol| match vol.spec.content_source { + None => false, + Some(VolumeContentSource::snapshot(_)) => true, + }); + } + Some(volumes) } @@ -147,8 +173,9 @@ impl Scale for Volume { #[async_trait(?Send)] impl ReplicaTopology for Volume { type ID = VolumeId; - async fn topologies(output: &OutputFormat) { - let volumes = VolumeTopologies(get_paginated_volumes().await.unwrap_or_default()); + async fn topologies(output: &OutputFormat, volume_args: &VolumesArgs) { + let volumes = + VolumeTopologies(get_paginated_volumes(volume_args).await.unwrap_or_default()); utils::print_table(output, volumes); } async fn topology(id: &Self::ID, output: &OutputFormat) { diff --git a/control-plane/rest/openapi-specs/v0_api_spec.yaml b/control-plane/rest/openapi-specs/v0_api_spec.yaml index 6de048734..afec5bad8 100644 --- a/control-plane/rest/openapi-specs/v0_api_spec.yaml +++ b/control-plane/rest/openapi-specs/v0_api_spec.yaml @@ -3209,6 +3209,11 @@ components: $ref: '#/components/schemas/AffinityGroup' content_source: $ref: '#/components/schemas/VolumeContentSource' + num_snapshots: + description: Number of snapshots taken on this volume. + type: integer + format: int32 + minimum: 0 required: - num_paths - num_replicas @@ -3218,6 +3223,7 @@ components: - uuid - policy - thin + - num_snapshots VolumeTarget: example: node: io-engine-1 @@ -3499,6 +3505,11 @@ components: type: array items: $ref: '#/components/schemas/ReplicaSnapshot' + num_restores: + description: Number of restores done from this snapshot. + type: integer + format: int32 + minimum: 0 required: - status - size @@ -3506,6 +3517,7 @@ components: - total_allocated_size - txn_id - transactions + - num_restores VolumeSnapshotSpec: description: |- Volume Snapshot Spec information. diff --git a/control-plane/rest/service/src/v0/snapshots.rs b/control-plane/rest/service/src/v0/snapshots.rs index f13723bac..57f38874a 100644 --- a/control-plane/rest/service/src/v0/snapshots.rs +++ b/control-plane/rest/service/src/v0/snapshots.rs @@ -184,6 +184,7 @@ fn to_models_volume_snapshot(snap: &VolumeSnapshot) -> models::VolumeSnapshot { ) }) .collect::>(), + snap.meta().num_restores() as u32, ), models::VolumeSnapshotSpec::new_all(snap.spec().snap_id(), snap.spec().source_id()), ), diff --git a/control-plane/stor-port/src/types/v0/store/snapshots/volume.rs b/control-plane/stor-port/src/types/v0/store/snapshots/volume.rs index f653e7e19..a501e44f8 100644 --- a/control-plane/stor-port/src/types/v0/store/snapshots/volume.rs +++ b/control-plane/stor-port/src/types/v0/store/snapshots/volume.rs @@ -82,6 +82,18 @@ impl VolumeSnapshot { .retain(|key, _| key == &self.metadata.txn_id); self.metadata.transactions.extend(transactions) } + /// + pub fn insert_restore(&mut self, restored_volume: VolumeId) { + self.metadata.runtime_meta.restores.insert(restored_volume) + } + /// + pub fn remove_restore(&mut self, restored_volume: &VolumeId) { + self.metadata.runtime_meta.restores.remove(restored_volume) + } + /// + pub fn num_restores(&self) -> usize { + self.metadata.runtime_meta.restores.len() + } } impl From<&VolumeSnapshotUserSpec> for VolumeSnapshot { fn from(value: &VolumeSnapshotUserSpec) -> Self { @@ -115,6 +127,9 @@ pub struct VolumeSnapshotMeta { /// The "actual" snapshots can be accessed by the key `txn_id`. /// Failed transactions are any other key. transactions: HashMap>, + /// Volume metadata information. + #[serde(skip)] + runtime_meta: VolumeSnapshotRuntimeMetadata, } impl VolumeSnapshotMeta { /// Get the snapshot operation state. @@ -170,6 +185,37 @@ impl VolumeSnapshotMeta { } } +/// List of all volume snapshots and related information. +#[derive(Debug, Clone, PartialEq, Default)] +struct VolumeSnapshotRuntimeMetadata { + /// Runtime list of all volume snapshot restores. + restores: VolumeRestoreList, +} + +/// List of all volume snapshot restore and related information. +#[derive(Debug, Clone, PartialEq, Default)] +pub struct VolumeRestoreList { + pub(crate) restores: HashSet, +} +impl VolumeRestoreList { + /// Insert restored volume id into the list. + pub fn insert(&mut self, restored_volume: VolumeId) { + self.restores.insert(restored_volume); + } + /// Remove restored volume id from the list. + pub fn remove(&mut self, restored_volume: &VolumeId) { + self.restores.remove(restored_volume); + } + /// Check if there's any restored volume id. + pub fn is_empty(&self) -> bool { + self.restores.is_empty() + } + /// Number of restores. + pub fn len(&self) -> usize { + self.restores.len() + } +} + /// Operation State for a VolumeSnapshot resource. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct VolumeSnapshotOperationState { @@ -186,6 +232,8 @@ pub enum VolumeSnapshotOperation { Create(VolumeSnapshotCreateInfo), Destroy, CleanupStaleTransactions, + CreateRestore(CreateRestoreInfo), + DestroyRestore(DestroyRestoreInfo), } /// Completion info for volume snapshot create operation. @@ -234,6 +282,34 @@ impl PartialEq for VolumeSnapshot { } } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct CreateRestoreInfo { + volume_uuid: VolumeId, +} + +impl CreateRestoreInfo { + pub fn new(volume_uuid: VolumeId) -> Self { + Self { volume_uuid } + } + pub fn volume_uuid(&self) -> &VolumeId { + &self.volume_uuid + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct DestroyRestoreInfo { + volume_uuid: VolumeId, +} + +impl DestroyRestoreInfo { + pub fn new(volume_uuid: VolumeId) -> Self { + Self { volume_uuid } + } + pub fn volume_uuid(&self) -> &VolumeId { + &self.volume_uuid + } +} + /// The replica snapshot created from the creation operation. #[derive(Debug, Clone, PartialEq)] pub struct VolumeSnapshotCreateResult { @@ -318,6 +394,12 @@ impl SpecTransaction for VolumeSnapshot { } } VolumeSnapshotOperation::CleanupStaleTransactions => {} + VolumeSnapshotOperation::CreateRestore(info) => { + self.insert_restore(info.volume_uuid().clone()) + } + VolumeSnapshotOperation::DestroyRestore(info) => { + self.remove_restore(info.volume_uuid()) + } } } @@ -344,6 +426,8 @@ impl SpecTransaction for VolumeSnapshot { } VolumeSnapshotOperation::Destroy => {} VolumeSnapshotOperation::CleanupStaleTransactions => {} + VolumeSnapshotOperation::CreateRestore(_) => {} + VolumeSnapshotOperation::DestroyRestore(_) => {} } } @@ -411,7 +495,7 @@ impl StorableObject for VolumeSnapshot { /// List of all volume snapshots and related information. #[derive(Debug, Clone, PartialEq, Default)] pub struct VolumeSnapshotList { - snapshots: HashSet, + pub(crate) snapshots: HashSet, } impl VolumeSnapshotList { /// Insert snapshot into the list. @@ -426,6 +510,10 @@ impl VolumeSnapshotList { pub fn is_empty(&self) -> bool { self.snapshots.is_empty() } + /// Number of snapshots. + pub fn len(&self) -> usize { + self.snapshots.len() + } } impl PartialEq<()> for VolumeSnapshot { diff --git a/control-plane/stor-port/src/types/v0/store/volume.rs b/control-plane/stor-port/src/types/v0/store/volume.rs index 57cb7ff92..09d8debeb 100644 --- a/control-plane/stor-port/src/types/v0/store/volume.rs +++ b/control-plane/stor-port/src/types/v0/store/volume.rs @@ -175,6 +175,8 @@ pub struct VolumeSpec { /// Affinity Group related information. #[serde(default)] pub affinity_group: Option, + /// Number of snapshots. + pub num_snapshots: usize, /// Volume metadata information. #[serde(default, skip_serializing_if = "super::is_default")] pub metadata: VolumeMetadata, @@ -191,6 +193,12 @@ impl VolumeContentSource { pub fn new_snapshot_source(snapshot: SnapshotId, snap_source_vol: VolumeId) -> Self { Self::Snapshot(snapshot, snap_source_vol) } + /// + pub fn snap_source_volume(&self) -> &VolumeId { + match self { + VolumeContentSource::Snapshot(_, vol) => vol, + } + } } /// Volume meta information. @@ -210,7 +218,7 @@ impl VolumeMetadata { persisted: VolumePersistedMetadata { snapshot_as_thin: as_thin, }, - runtime: Default::default(), + runtime: VolumeRuntimeMetadata::default(), } } /// Insert snapshot in the list. @@ -219,6 +227,10 @@ impl VolumeMetadata { // we become thin provisioned! self.persisted.snapshot_as_thin = Some(true); } + /// + pub fn num_snapshots(&self) -> usize { + self.runtime.snapshots.len() + } } /// Volume meta information. @@ -744,6 +756,7 @@ impl From for models::VolumeSpec { src.metadata.persisted.snapshot_as_thin, src.affinity_group.into_opt(), src.content_source.into_opt(), + src.num_snapshots as u32, ) } }