Skip to content

Commit

Permalink
feat: capacity limit for volumes
Browse files Browse the repository at this point in the history
Add mechanism for limiting the total volume size on the system
by rejecting volume creation requests from REST.
Includes BDDs, although the resize limit is not yet implemented.

Signed-off-by: chriswldenyer <christopher.denyer@mayadata.io>
  • Loading branch information
chriswldenyer committed Jan 12, 2024
1 parent d28c0ea commit be60dae
Show file tree
Hide file tree
Showing 12 changed files with 55 additions and 7 deletions.
4 changes: 2 additions & 2 deletions control-plane/agents/src/bin/core/volume/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ impl ResourceLifecycleExt<CreateVolume> for OperationGuardArc<VolumeSpec> {
) -> Result<Self::CreateOutput, SvcError> {
let specs = registry.specs();
let mut volume = specs
.get_or_create_volume(&CreateVolumeSource::None(request))
.get_or_create_volume(&CreateVolumeSource::None(request))?
.operation_guard_wait()
.await?;
let volume_clone = volume.start_create(registry, request).await?;
Expand Down Expand Up @@ -790,7 +790,7 @@ impl ResourceLifecycleExt<CreateVolumeSource<'_>> for OperationGuardArc<VolumeSp

let specs = registry.specs();
let mut volume = specs
.get_or_create_volume(request_src)
.get_or_create_volume(request_src)?
.operation_guard_wait()
.await?;
let volume_clone = volume.start_create_update(registry, request).await?;
Expand Down
22 changes: 18 additions & 4 deletions control-plane/agents/src/bin/core/volume/specs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -748,19 +748,33 @@ impl ResourceSpecsLocked {
pub(crate) fn get_or_create_volume(
&self,
request: &CreateVolumeSource,
) -> ResourceMutex<VolumeSpec> {
) -> Result<ResourceMutex<VolumeSpec>, SvcError> {
let mut specs = self.write();
if let Some(volume) = specs.volumes.get(&request.source().uuid) {
volume.clone()
Ok(volume.clone())
} else {
// if request has a capacity limit, add up the volumes and reject
// if the capacity limit would be exceeded
match request.source().capacity_limit {
None => {} // no limit, no check needed
Some(limit) => {
let mut total: u64 = request.source().size;
for vol in specs.volumes.to_vec() {
total += vol.lock().size;
if total > limit {
return Err(SvcError::CapacityLimitExceeded {});
}
}
}
}
match request {
CreateVolumeSource::None(_) => {
specs.volumes.insert(VolumeSpec::from(request.source()))
Ok(specs.volumes.insert(VolumeSpec::from(request.source())))
}
CreateVolumeSource::Snapshot(create_from_snap) => {
let mut spec = VolumeSpec::from(request.source());
spec.set_content_source(Some(create_from_snap.to_snapshot_source()));
specs.volumes.insert(spec)
Ok(specs.volumes.insert(spec))
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions control-plane/agents/src/common/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@ pub enum SvcError {
DrainNotAllowedWhenHAisDisabled {},
#[snafu(display("Target switchover is not allowed without HA"))]
SwitchoverNotAllowedWhenHAisDisabled {},
#[snafu(display("The volume would exceed the capacity limit"))]
CapacityLimitExceeded {},
}

impl SvcError {
Expand Down Expand Up @@ -934,6 +936,12 @@ impl From<SvcError> for ReplyError {
source,
extra,
},
SvcError::CapacityLimitExceeded {} => ReplyError {
kind: ReplyErrorKind::CapacityLimitExceeded,
resource: ResourceKind::Volume,
source,
extra,
},
}
}
}
Expand Down
1 change: 1 addition & 0 deletions control-plane/grpc/proto/v1/misc/common.proto
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ enum ReplyErrorKind {
ReplicaCreateNumber = 27;
VolumeNoReplicas = 28;
InUse = 29;
CapacityLimitExceeded = 30;
}

// ResourceKind for the resource which has undergone this error
Expand Down
2 changes: 2 additions & 0 deletions control-plane/grpc/proto/v1/volume/volume.proto
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ message CreateVolumeRequest {
bool thin = 8;
// Affinity Group related information.
optional AffinityGroup affinity_group = 9;
// maximum total volume size
optional uint64 capacity_limit = 10;
}

// Publish a volume on a node
Expand Down
2 changes: 2 additions & 0 deletions control-plane/grpc/src/misc/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ impl From<ReplyErrorKind> for common::ReplyErrorKind {
ReplyErrorKind::ReplicaCreateNumber => Self::ReplicaCreateNumber,
ReplyErrorKind::VolumeNoReplicas => Self::VolumeNoReplicas,
ReplyErrorKind::InUse => Self::InUse,
ReplyErrorKind::CapacityLimitExceeded => Self::CapacityLimitExceeded,
}
}
}
Expand Down Expand Up @@ -135,6 +136,7 @@ impl From<common::ReplyErrorKind> for ReplyErrorKind {
common::ReplyErrorKind::ReplicaCreateNumber => Self::ReplicaCreateNumber,
common::ReplyErrorKind::VolumeNoReplicas => Self::VolumeNoReplicas,
common::ReplyErrorKind::InUse => Self::InUse,
common::ReplyErrorKind::CapacityLimitExceeded => Self::CapacityLimitExceeded,
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions control-plane/grpc/src/operations/volume/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,8 @@ pub trait CreateVolumeInfo: Send + Sync + std::fmt::Debug {
fn thin(&self) -> bool;
/// Affinity Group related information.
fn affinity_group(&self) -> Option<AffinityGroup>;
/// Capacity Limit
fn capacity_limit(&self) -> Option<u64>;
}

impl CreateVolumeInfo for CreateVolume {
Expand Down Expand Up @@ -924,6 +926,10 @@ impl CreateVolumeInfo for CreateVolume {
fn affinity_group(&self) -> Option<AffinityGroup> {
self.affinity_group.clone()
}

fn capacity_limit(&self) -> Option<u64> {
self.capacity_limit
}
}

/// Intermediate structure that validates the conversion to CreateVolumeRequest type.
Expand Down Expand Up @@ -972,6 +978,10 @@ impl CreateVolumeInfo for ValidatedCreateVolumeRequest {
fn affinity_group(&self) -> Option<AffinityGroup> {
self.inner.affinity_group.clone().map(|ag| ag.into())
}

fn capacity_limit(&self) -> Option<u64> {
self.inner.capacity_limit
}
}

impl ValidateRequestTypes for CreateVolumeRequest {
Expand Down Expand Up @@ -1008,6 +1018,7 @@ impl From<&dyn CreateVolumeInfo> for CreateVolume {
labels: data.labels(),
thin: data.thin(),
affinity_group: data.affinity_group(),
capacity_limit: data.capacity_limit(),
}
}
}
Expand All @@ -1025,6 +1036,7 @@ impl From<&dyn CreateVolumeInfo> for CreateVolumeRequest {
.map(|labels| crate::common::StringMapValue { value: labels }),
thin: data.thin(),
affinity_group: data.affinity_group().map(|ag| ag.into()),
capacity_limit: data.capacity_limit(),
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion control-plane/rest/openapi-specs/v0_api_spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2911,6 +2911,7 @@ components:
- FailedPersist
- Deleting
- InUse
- CapacityLimitExceeded
required:
- details
- kind
Expand Down Expand Up @@ -3826,4 +3827,4 @@ components:
content:
application/json:
schema:
$ref: '#/components/schemas/RestJsonError'
$ref: '#/components/schemas/RestJsonError'
1 change: 1 addition & 0 deletions control-plane/rest/src/versions/v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ impl CreateVolumeBody {
labels: self.labels.clone(),
thin: self.thin,
affinity_group: self.affinity_group.clone(),
capacity_limit: None,
}
}
/// Convert into rpc request type.
Expand Down
1 change: 1 addition & 0 deletions control-plane/stor-port/src/transport_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ pub enum ReplyErrorKind {
ReplicaCreateNumber,
VolumeNoReplicas,
InUse,
CapacityLimitExceeded,
}

impl From<tonic::Code> for ReplyErrorKind {
Expand Down
4 changes: 4 additions & 0 deletions control-plane/stor-port/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ impl From<ReplyError> for RestError<RestJsonError> {
let error = RestJsonError::new(details, message, Kind::FailedPrecondition);
(StatusCode::PRECONDITION_FAILED, error)
}
ReplyErrorKind::CapacityLimitExceeded => {
let error = RestJsonError::new(details, message, Kind::ResourceExhausted);
(StatusCode::INSUFFICIENT_STORAGE, error)
}
};

RestError::new(status, error)
Expand Down
2 changes: 2 additions & 0 deletions control-plane/stor-port/src/types/v0/transport/volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,8 @@ pub struct CreateVolume {
pub thin: bool,
/// Affinity Group related information.
pub affinity_group: Option<AffinityGroup>,
/// Maximum total system volume size.
pub capacity_limit: Option<u64>,
}

/// Create volume request.
Expand Down

0 comments on commit be60dae

Please sign in to comment.