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 5, 2024
1 parent 9d3c484 commit 2c1cbcb
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 0 deletions.
19 changes: 19 additions & 0 deletions control-plane/agents/src/bin/core/volume/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,29 @@ impl Service {
})
}

/// Determine the total volume size on the system.
#[tracing::instrument(level = "info", skip(self), err, fields(volume.uuid))]
async fn total_volume_size(&self) -> Result<u64, SvcError> {
let volumes = self.registry.volumes().await;
let mut total: u64 = 0;
for vol in volumes.iter() {
total += vol.spec().size;
}
Ok(total)
}

/// Create a volume using the given parameters.
#[tracing::instrument(level = "info", skip(self), err, fields(volume.uuid = %request.uuid))]
pub(super) async fn create_volume(&self, request: &CreateVolume) -> Result<Volume, SvcError> {
let _permit = self.create_volume_permit().await?;
let capacity_limit = request.capacity_limit();
if let Some(limit) = capacity_limit {
let total_volume_size = self.total_volume_size().await?;
let this_volume_size = request.size();
if total_volume_size + this_volume_size > limit {
return Err(SvcError::VolWouldExceedCapacity {});
}
}
OperationGuardArc::<VolumeSpec>::create(&self.registry, request).await?;
self.registry.volume(&request.uuid).await
}
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"))]
VolWouldExceedCapacity {},
}

impl SvcError {
Expand Down Expand Up @@ -934,6 +936,12 @@ impl From<SvcError> for ReplyError {
source,
extra,
},
SvcError::VolWouldExceedCapacity {} => ReplyError {
kind: ReplyErrorKind::OutOfRange,
resource: ResourceKind::Volume,
source,
extra,
},
}
}
}
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
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
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
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
22 changes: 22 additions & 0 deletions tests/bdd/features/volume/capacity_limit/creation.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Feature: Volume creation capacity limit

Background:
Given a control plane, Io-Engine instances and a pool

Scenario: attempted creation exceeding the capacity limit
Given a gRPC request to create a volume
When the request includes a capacity limit
And the volume creation would result in the capacity limit being exceeded
Then volume creation should fail with an out-of-range error

Scenario: attempted creation within the capacity limit
Given a gRPC request to create a volume
When the request includes a capacity limit
And the volume creation would not result in the capacity limit being exceeded
Then volume creation should succeed

Scenario: attempted creation with no capacity limit
Given a gRPC request to create a volume
When the request does not include a capacity limit
Then volume creation should succeed

22 changes: 22 additions & 0 deletions tests/bdd/features/volume/capacity_limit/resize.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Feature: Volume resize capacity limit

Background:
Given a control plane, Io-Engine instances and a pool

Scenario: attempted volume resize exceeding the capacity limit
Given a gRPC request to resize a volume
When the request includes a capacity limit
And the volume resize would result in the capacity limit being exceeded
Then volume resize should fail with an out-of-range error

Scenario: attempted resize within the capacity limit
Given a gRPC request to resize a volume
When the request includes a capacity limit
And the volume resize would not result in the capacity limit being exceeded
Then volume resize should succeed

Scenario: attempted creation with no capacity limit
Given a gRPC request to resize a volume
When the request does not include a capacity limit
Then volume resize should succeed

0 comments on commit 2c1cbcb

Please sign in to comment.