Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ rc admin heal stop local
# Pool expansion and decommission workflows
rc admin pool list local
rc admin pool status local 1 --by-id
rc admin expand start local
rc admin expand status local
rc admin expand stop local
rc admin decommission start local '/data/pool1/disk{1...4}'
rc admin decommission status local '/data/pool1/disk{1...4}'
rc admin decommission cancel local 1 --by-id
Expand Down Expand Up @@ -304,6 +307,7 @@ rc admin rebalance status local --json
| `admin info` | Display cluster information (cluster, server, disk) |
| `admin heal` | Manage cluster healing operations (status, start, stop) |
| `admin pool` | List pools and inspect expansion/decommission status |
| `admin expand` | Manage post-expansion data rebalancing (start, status, stop) |
| `admin decommission` | Manage server pool decommissioning (start, status, cancel) |
| `admin rebalance` | Manage post-expansion rebalancing (start, status, stop) |

Expand Down
56 changes: 56 additions & 0 deletions crates/cli/src/commands/admin/expand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! Expansion workflow commands for post-expansion data rebalancing.

use clap::Subcommand;

use super::rebalance;
use crate::exit_code::ExitCode;
use crate::output::Formatter;

const EXPAND_AFTER_HELP: &str = "\
Examples:
rc admin expand start local
rc admin expand status local
rc admin expand stop local

Notes:
Add server pools by updating the RustFS service configuration and restarting
the deployment first. This command manages the post-expansion rebalance step.";

/// Expansion workflow subcommands
#[derive(Subcommand, Debug)]
#[command(after_help = EXPAND_AFTER_HELP)]
pub enum ExpandCommands {
/// Start post-expansion data rebalancing
Start(rebalance::StartArgs),

/// Show post-expansion rebalance status
Status(rebalance::StatusArgs),

/// Stop a running post-expansion rebalance
Stop(rebalance::StopArgs),
}

/// Execute an expansion workflow subcommand
pub async fn execute(cmd: ExpandCommands, formatter: &Formatter) -> ExitCode {
match cmd {
ExpandCommands::Start(args) => {
rebalance::execute_start_with_messages(
args,
formatter,
"Expansion rebalance started successfully",
"Failed to start expansion rebalance",
)
.await
}
ExpandCommands::Status(args) => rebalance::execute_status(args, formatter).await,
ExpandCommands::Stop(args) => {
rebalance::execute_stop_with_messages(
args,
formatter,
"Expansion rebalance stopped successfully",
"Failed to stop expansion rebalance",
)
.await
}
}
}
48 changes: 48 additions & 0 deletions crates/cli/src/commands/admin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! service accounts, and cluster operations on RustFS/MinIO-compatible servers.

mod decommission;
mod expand;
mod group;
mod heal;
mod info;
Expand Down Expand Up @@ -35,6 +36,10 @@ pub enum AdminCommands {
#[command(subcommand)]
Pool(pool::PoolCommands),

/// Manage post-expansion data rebalancing
#[command(alias = "scale", subcommand)]
Expand(expand::ExpandCommands),
Comment on lines +39 to +41
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Document the new CLI contract in SPEC

Adding the public rc admin expand command changes the CLI contract, but docs/SPEC.md still only documents admin rebalance and has no admin expand syntax, alias, JSON-output references, or exit-code section. Since AGENTS.md identifies docs/SPEC.md as the CLI behavior contract and the PR checklist requires contract changes to be documented, users and any spec-based validation will miss this new workflow even though it is now exposed in help and README.

Useful? React with 👍 / 👎.


/// Manage server pool decommissioning
#[command(alias = "decom", subcommand)]
Decommission(decommission::DecommissionCommands),
Expand Down Expand Up @@ -68,6 +73,7 @@ pub async fn execute(cmd: AdminCommands, output_config: OutputConfig) -> ExitCod
AdminCommands::Info(info_cmd) => info::execute(info_cmd, &formatter).await,
AdminCommands::Heal(heal_cmd) => heal::execute(heal_cmd, &formatter).await,
AdminCommands::Pool(pool_cmd) => pool::execute(pool_cmd, &formatter).await,
AdminCommands::Expand(expand_cmd) => expand::execute(expand_cmd, &formatter).await,
AdminCommands::Decommission(decommission_cmd) => {
decommission::execute(decommission_cmd, &formatter).await
}
Expand Down Expand Up @@ -217,6 +223,48 @@ mod tests {
}
}

#[test]
fn test_parse_admin_expand_commands() {
let cli = TestCli::parse_from(["rc", "expand", "start", "local"]);

match cli.command {
AdminCommands::Expand(expand::ExpandCommands::Start(args)) => {
assert_eq!(args.alias, "local");
}
_ => panic!("Unexpected command parsing result"),
}

let cli = TestCli::parse_from(["rc", "expand", "status", "local"]);

match cli.command {
AdminCommands::Expand(expand::ExpandCommands::Status(args)) => {
assert_eq!(args.alias, "local");
}
_ => panic!("Unexpected command parsing result"),
}

let cli = TestCli::parse_from(["rc", "expand", "stop", "local"]);

match cli.command {
AdminCommands::Expand(expand::ExpandCommands::Stop(args)) => {
assert_eq!(args.alias, "local");
}
_ => panic!("Unexpected command parsing result"),
}
}

#[test]
fn test_parse_admin_expand_alias() {
let cli = TestCli::parse_from(["rc", "scale", "status", "local"]);

match cli.command {
AdminCommands::Expand(expand::ExpandCommands::Status(args)) => {
assert_eq!(args.alias, "local");
}
_ => panic!("Unexpected command parsing result"),
}
}

#[test]
fn test_parse_admin_decommission_start_by_id() {
let cli = TestCli::parse_from(["rc", "decommission", "start", "local", "1", "--by-id"]);
Expand Down
44 changes: 37 additions & 7 deletions crates/cli/src/commands/admin/rebalance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ pub async fn execute(cmd: RebalanceCommands, formatter: &Formatter) -> ExitCode
}

async fn execute_start(args: StartArgs, formatter: &Formatter) -> ExitCode {
execute_start_with_messages(
args,
formatter,
"Rebalance started successfully",
"Failed to start rebalance",
)
.await
}

pub(super) async fn execute_start_with_messages(
args: StartArgs,
formatter: &Formatter,
success_message: &str,
failure_prefix: &str,
) -> ExitCode {
let client = match get_admin_client(&args.alias, formatter) {
Ok(c) => c,
Err(code) => return code,
Expand All @@ -69,26 +84,26 @@ async fn execute_start(args: StartArgs, formatter: &Formatter) -> ExitCode {
if formatter.is_json() {
formatter.json(&RebalanceOperationOutput {
success: true,
message: "Rebalance started successfully".to_string(),
message: success_message.to_string(),
target: args.alias,
id: Some(result.id),
});
} else {
formatter.success("Rebalance started successfully.");
formatter.success(&format!("{success_message}."));
if !result.id.is_empty() {
formatter.println(&format!(" ID: {}", result.id));
}
}
ExitCode::Success
}
Err(e) => {
formatter.error(&format!("Failed to start rebalance: {e}"));
formatter.error(&format!("{failure_prefix}: {e}"));
ExitCode::GeneralError
}
}
}

async fn execute_status(args: StatusArgs, formatter: &Formatter) -> ExitCode {
pub(super) async fn execute_status(args: StatusArgs, formatter: &Formatter) -> ExitCode {
let client = match get_admin_client(&args.alias, formatter) {
Ok(c) => c,
Err(code) => return code,
Expand All @@ -111,6 +126,21 @@ async fn execute_status(args: StatusArgs, formatter: &Formatter) -> ExitCode {
}

async fn execute_stop(args: StopArgs, formatter: &Formatter) -> ExitCode {
execute_stop_with_messages(
args,
formatter,
"Rebalance stopped successfully",
"Failed to stop rebalance",
)
.await
}

pub(super) async fn execute_stop_with_messages(
args: StopArgs,
formatter: &Formatter,
success_message: &str,
failure_prefix: &str,
) -> ExitCode {
let client = match get_admin_client(&args.alias, formatter) {
Ok(c) => c,
Err(code) => return code,
Expand All @@ -121,17 +151,17 @@ async fn execute_stop(args: StopArgs, formatter: &Formatter) -> ExitCode {
if formatter.is_json() {
formatter.json(&RebalanceOperationOutput {
success: true,
message: "Rebalance stopped successfully".to_string(),
message: success_message.to_string(),
target: args.alias,
id: None,
});
} else {
formatter.success("Rebalance stopped successfully.");
formatter.success(&format!("{success_message}."));
}
ExitCode::Success
}
Err(e) => {
formatter.error(&format!("Failed to stop rebalance: {e}"));
formatter.error(&format!("{failure_prefix}: {e}"));
ExitCode::GeneralError
}
}
Expand Down
27 changes: 27 additions & 0 deletions crates/cli/tests/help_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ fn top_level_command_help_contract() {
"info",
"heal",
"pool",
"expand",
"decommission",
"rebalance",
"user",
Expand Down Expand Up @@ -457,6 +458,32 @@ fn nested_subcommand_help_contract() {
usage: "Usage: rc admin pool status [OPTIONS] <ALIAS> [POOL]",
expected_tokens: &["--by-id"],
},
HelpCase {
args: &["admin", "expand"],
usage: "Usage: rc admin expand [OPTIONS] <COMMAND>",
expected_tokens: &[
"start",
"status",
"stop",
"Examples:",
"rc admin expand start local",
],
},
HelpCase {
args: &["admin", "expand", "start"],
usage: "Usage: rc admin expand start [OPTIONS] <ALIAS>",
expected_tokens: &[],
},
HelpCase {
args: &["admin", "expand", "status"],
usage: "Usage: rc admin expand status [OPTIONS] <ALIAS>",
expected_tokens: &[],
},
HelpCase {
args: &["admin", "expand", "stop"],
usage: "Usage: rc admin expand stop [OPTIONS] <ALIAS>",
expected_tokens: &[],
},
HelpCase {
args: &["admin", "decommission", "start"],
usage: "Usage: rc admin decommission start [OPTIONS] <ALIAS> <POOL>",
Expand Down
30 changes: 30 additions & 0 deletions schemas/output_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,36 @@
}
}
},
{
"title": "admin expand status",
"description": "Post-expansion rebalance status output",
"$ref": "#/definitions/rebalanceStatus"
},
{
Comment on lines +758 to +762
"title": "admin expand operation",
"description": "Post-expansion rebalance start/stop output",
"type": "object",
"required": [
"success",
"message",
"target"
],
"properties": {
"success": {
"type": "boolean"
},
"message": {
"type": "string"
},
"target": {
"type": "string"
},
"id": {
"type": "string",
"description": "Rebalance operation ID returned by start"
}
}
},
{
Comment on lines +763 to 787
"title": "error",
"description": "Error response",
Expand Down
Loading