Skip to content

Commit

Permalink
feat(topology): add label pool code in plugin
Browse files Browse the repository at this point in the history
Signed-off-by: sinhaashish <[email protected]>
  • Loading branch information
sinhaashish committed Aug 2, 2024
1 parent c0a4373 commit 43637d9
Show file tree
Hide file tree
Showing 6 changed files with 326 additions and 117 deletions.
5 changes: 5 additions & 0 deletions control-plane/plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@ impl ExecuteOperation for LabelResources {
label,
overwrite,
} => node::Node::label(id, label.to_string(), *overwrite, &cli_args.output).await,
LabelResources::Pool {
id,
label,
overwrite,
} => pool::Pool::label(id, label.to_string(), *overwrite, &cli_args.output).await,
}
}
}
78 changes: 75 additions & 3 deletions control-plane/plugin/src/resources/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::resources::node;
use pstor::transport_api::ResourceKind;
use snafu::Snafu;

/// All errors returned when resources command fails.
Expand All @@ -25,9 +25,14 @@ pub enum Error {
source: openapi::tower::client::Error<openapi::models::RestJsonError>,
},
#[snafu(display("Invalid label format: {source}"))]
NodeLabelFormat { source: node::TopologyError },
NodeLabelFormat { source: TopologyError },
#[snafu(display("{source}"))]
NodeLabel { source: node::OpError },
NodeLabel { source: OpError },
#[snafu(display("Invalid label format: {source}"))]
PoolLabelFormat { source: TopologyError },
#[snafu(display("{source}"))]
PoolLabel { source: OpError },

/// Error when node uncordon request fails.
#[snafu(display("Failed to uncordon node {id}. Error {source}"))]
NodeUncordonError {
Expand Down Expand Up @@ -99,3 +104,70 @@ pub enum Error {
))]
LabelNodeFilter { labels: String },
}

/// Errors related to label topology formats.
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum TopologyError {
#[snafu(display("key must not be an empty string"))]
KeyIsEmpty {},
#[snafu(display("value must not be an empty string"))]
ValueIsEmpty {},
#[snafu(display("key part must not be more than 63 characters"))]
KeyTooLong {},
#[snafu(display("value part must not be more than 63 characters"))]
ValueTooLong {},
#[snafu(display("both key and value parts must start with an ascii alphanumeric character"))]
EdgesNotAlphaNum {},
#[snafu(display("key can contain at most one `/` character"))]
KeySlashCount {},
#[snafu(display(
"only ascii alphanumeric characters and (`/`,` - `, `_`,`.`) are allowed for the key part"
))]
KeyIsNotAlphaNumericPlus {},
#[snafu(display(
"only ascii alphanumeric characters and (`-`,` _ `, `.`) are allowed for the label part"
))]
ValueIsNotAlphaNumericPlus {},
#[snafu(display("only a single assignment key=value is allowed"))]
LabelMultiAssign {},
#[snafu(display(
"the supported formats are: \
key=value for adding (example: group=a) \
and key- for removing (example: group-)"
))]
LabelAssign {},
}

/// Errors related to node label topology operation execution.
#[derive(Debug, snafu::Snafu)]
#[snafu(visibility(pub))]
pub enum OpError {
#[snafu(display("{resource} {id} not unlabelled as it did not contain the label"))]
LabelNotFound { resource: String, id: String },
#[snafu(display("{resource} {id} not labelled as the same label already exists"))]
LabelExists { resource: String, id: String },
#[snafu(display("{resource} {id} not found"))]
ResourceNotFound { resource: String, id: String },
#[snafu(display(
"{resource} {id} not labelled as the label key already exists, but with a different value and --overwrite is false"
))]
LabelConflict { resource: String, id: String },
#[snafu(display("Failed to label {resource} {id}. Error {source}"))]
Generic {
resource: String,
id: String,
source: openapi::tower::client::Error<openapi::models::RestJsonError>,
},
}

impl From<TopologyError> for Error {
fn from(source: TopologyError) -> Self {
Self::NodeLabelFormat { source }
}
}
impl From<OpError> for Error {
fn from(source: OpError) -> Self {
Self::NodeLabel { source }
}
}
18 changes: 18 additions & 0 deletions control-plane/plugin/src/resources/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,24 @@ pub enum LabelResources {
#[clap(long)]
overwrite: bool,
},
/// Adds or removes a label to or from the specified pool.
Pool {
/// The id of the pool to label/unlabel.
id: PoolId,
/// The label to be added or removed from the pool.
/// To add a label, please use the following format:
/// ${key}=${value}
/// To remove a label, please use the following format:
/// ${key}-
/// A label key and value must begin with a letter or number, and may contain letters,
/// numbers, hyphens, dots, and underscores, up to 63 characters each.
/// The key may contain a single slash.
label: String,
/// Allow labels to be overwritten, otherwise reject label updates that overwrite existing
/// labels.
#[clap(long)]
overwrite: bool,
},
}

#[derive(clap::Subcommand, Debug)]
Expand Down
135 changes: 25 additions & 110 deletions control-plane/plugin/src/resources/node.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::{
operations::{Cordoning, Drain, GetWithArgs, Label, ListWithArgs, PluginResult},
resources::{
error::Error,
error::{Error, LabelAssignSnafu, OpError, TopologyError},
utils::{
self, optional_cell, print_table, CreateRow, CreateRows, GetHeaderRow, OutputFormat,
self, optional_cell, print_table, validate_topology_key, validate_topology_value,
CreateRow, CreateRows, GetHeaderRow, OutputFormat,
},
NodeId,
},
Expand Down Expand Up @@ -677,109 +678,6 @@ impl Drain for Node {
}
}

/// Errors related to node label topology formats.
#[derive(Debug, snafu::Snafu)]
pub enum TopologyError {
#[snafu(display("key must not be an empty string"))]
KeyIsEmpty {},
#[snafu(display("value must not be an empty string"))]
ValueIsEmpty {},
#[snafu(display("key part must no more than 63 characters"))]
KeyTooLong {},
#[snafu(display("value part must no more than 63 characters"))]
ValueTooLong {},
#[snafu(display("both key and value parts must start with an ascii alphanumeric character"))]
EdgesNotAlphaNum {},
#[snafu(display("key can contain at most one / character"))]
KeySlashCount {},
#[snafu(display(
"only ascii alphanumeric characters and (/ - _ .) are allowed for the key part"
))]
KeyIsNotAlphaNumericPlus {},
#[snafu(display(
"only ascii alphanumeric characters and (- _ .) are allowed for the label part"
))]
ValueIsNotAlphaNumericPlus {},
#[snafu(display("only a single assignment key=value is allowed"))]
LabelMultiAssign {},
#[snafu(display(
"the supported formats are: \
key=value for adding (example: group=a) \
and key- for removing (example: group-)"
))]
LabelAssign {},
}

/// Errors related to node label topology operation execution.
#[derive(Debug, snafu::Snafu)]
pub enum OpError {
#[snafu(display("Node {id} not unlabelled as it did not contain the label"))]
LabelNotFound { id: String },
#[snafu(display("Node {id} not labelled as the same label already exists"))]
LabelExists { id: String },
#[snafu(display("Node {id} not found"))]
NodeNotFound { id: String },
#[snafu(display(
"Node {id} not labelled as the label key already exists, but with a different value and --overwrite is false"
))]
LabelConflict { id: String },
#[snafu(display("Failed to label node {id}. Error {source}"))]
Generic {
id: String,
source: openapi::tower::client::Error<openapi::models::RestJsonError>,
},
}

impl From<TopologyError> for Error {
fn from(source: TopologyError) -> Self {
Self::NodeLabelFormat { source }
}
}
impl From<OpError> for Error {
fn from(source: OpError) -> Self {
Self::NodeLabel { source }
}
}

fn allowed_topology_chars(key: char) -> bool {
key.is_ascii_alphanumeric() || matches!(key, '_' | '-' | '.')
}
fn allowed_topology_tips(label: &str) -> bool {
fn allowed_topology_tips_chars(char: Option<char>) -> bool {
char.map(|c| c.is_ascii_alphanumeric()).unwrap_or(true)
}

allowed_topology_tips_chars(label.chars().next())
&& allowed_topology_tips_chars(label.chars().last())
}
fn validate_topology_key(key: &str) -> Result<(), TopologyError> {
snafu::ensure!(!key.is_empty(), KeyIsEmptySnafu);
snafu::ensure!(key.len() <= 63, KeyTooLongSnafu);
snafu::ensure!(allowed_topology_tips(key), EdgesNotAlphaNumSnafu);

snafu::ensure!(
key.chars().filter(|c| c == &'/').count() <= 1,
KeySlashCountSnafu
);

snafu::ensure!(
key.chars().all(|c| allowed_topology_chars(c) || c == '/'),
KeyIsNotAlphaNumericPlusSnafu
);

Ok(())
}
fn validate_topology_value(value: &str) -> Result<(), TopologyError> {
snafu::ensure!(!value.is_empty(), ValueIsEmptySnafu);
snafu::ensure!(value.len() <= 63, ValueTooLongSnafu);
snafu::ensure!(allowed_topology_tips(value), EdgesNotAlphaNumSnafu);
snafu::ensure!(
value.chars().all(allowed_topology_chars),
ValueIsNotAlphaNumericPlusSnafu
);
Ok(())
}

#[async_trait(?Send)]
impl Label for Node {
type ID = NodeId;
Expand All @@ -803,15 +701,25 @@ impl Label for Node {
{
Err(source) => match source.status() {
Some(StatusCode::UNPROCESSABLE_ENTITY) if output.none() => {
Err(OpError::LabelExists { id: id.to_string() })
Err(OpError::LabelExists {
resource: "Node".to_string(),
id: id.to_string(),
})
}
Some(StatusCode::PRECONDITION_FAILED) if output.none() => {
Err(OpError::LabelConflict { id: id.to_string() })
Err(OpError::LabelConflict {
resource: "Node".to_string(),
id: id.to_string(),
})
}
Some(StatusCode::NOT_FOUND) if output.none() => {
Err(OpError::NodeNotFound { id: id.to_string() })
Err(OpError::ResourceNotFound {
resource: "Node".to_string(),
id: id.to_string(),
})
}
_ => Err(OpError::Generic {
resource: "Node".to_string(),
id: id.to_string(),
source,
}),
Expand All @@ -829,12 +737,19 @@ impl Label for Node {
{
Err(source) => match source.status() {
Some(StatusCode::PRECONDITION_FAILED) if output.none() => {
Err(OpError::LabelNotFound { id: id.to_string() })
Err(OpError::LabelNotFound {
resource: "Node".to_string(),
id: id.to_string(),
})
}
Some(StatusCode::NOT_FOUND) if output.none() => {
Err(OpError::NodeNotFound { id: id.to_string() })
Err(OpError::ResourceNotFound {
resource: "Node".to_string(),
id: id.to_string(),
})
}
_ => Err(OpError::Generic {
resource: "Node".to_string(),
id: id.to_string(),
source,
}),
Expand Down
Loading

0 comments on commit 43637d9

Please sign in to comment.