Skip to content

Commit ab0cded

Browse files
authored
feat(admin): add decommission clear command (#240)
1 parent f22ca70 commit ab0cded

7 files changed

Lines changed: 147 additions & 1 deletion

File tree

crates/cli/src/commands/admin/decommission.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ pub enum DecommissionCommands {
1919

2020
/// Cancel decommissioning a pool
2121
Cancel(CancelArgs),
22+
23+
/// Clear failed or canceled decommissioning metadata for a pool
24+
Clear(ClearArgs),
2225
}
2326

2427
#[derive(clap::Args, Debug)]
@@ -60,6 +63,19 @@ pub struct CancelArgs {
6063
pub by_id: bool,
6164
}
6265

66+
#[derive(clap::Args, Debug)]
67+
pub struct ClearArgs {
68+
/// Alias name of the server
69+
pub alias: String,
70+
71+
/// Pool command line, or zero-based pool ID with --by-id
72+
pub pool: String,
73+
74+
/// Interpret POOL as a zero-based pool ID
75+
#[arg(long)]
76+
pub by_id: bool,
77+
}
78+
6379
#[derive(Serialize)]
6480
struct DecommissionOperationOutput {
6581
success: bool,
@@ -78,6 +94,7 @@ pub async fn execute(cmd: DecommissionCommands, formatter: &Formatter) -> ExitCo
7894
DecommissionCommands::Start(args) => execute_start(args, formatter).await,
7995
DecommissionCommands::Status(args) => execute_status(args, formatter).await,
8096
DecommissionCommands::Cancel(args) => execute_cancel(args, formatter).await,
97+
DecommissionCommands::Clear(args) => execute_clear(args, formatter).await,
8198
}
8299
}
83100

@@ -191,3 +208,37 @@ async fn execute_cancel(args: CancelArgs, formatter: &Formatter) -> ExitCode {
191208
}
192209
}
193210
}
211+
212+
async fn execute_clear(args: ClearArgs, formatter: &Formatter) -> ExitCode {
213+
let client = match get_admin_client(&args.alias, formatter) {
214+
Ok(c) => c,
215+
Err(code) => return code,
216+
};
217+
218+
let target = PoolTarget {
219+
pool: args.pool,
220+
by_id: args.by_id,
221+
};
222+
223+
match client.decommission_clear(target.clone()).await {
224+
Ok(()) => {
225+
if formatter.is_json() {
226+
formatter.json(&DecommissionOperationOutput {
227+
success: true,
228+
message: "Decommission cleared successfully".to_string(),
229+
pool: target.pool,
230+
});
231+
} else {
232+
formatter.success(&format!(
233+
"Decommission cleared successfully for `{}`.",
234+
target.pool
235+
));
236+
}
237+
ExitCode::Success
238+
}
239+
Err(e) => {
240+
formatter.error(&format!("Failed to clear decommission: {e}"));
241+
ExitCode::GeneralError
242+
}
243+
}
244+
}

crates/cli/src/commands/admin/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,20 @@ mod tests {
340340
}
341341
}
342342

343+
#[test]
344+
fn test_parse_admin_decommission_clear_by_id() {
345+
let cli = TestCli::parse_from(["rc", "decommission", "clear", "local", "3", "--by-id"]);
346+
347+
match cli.command {
348+
AdminCommands::Decommission(decommission::DecommissionCommands::Clear(args)) => {
349+
assert_eq!(args.alias, "local");
350+
assert_eq!(args.pool, "3");
351+
assert!(args.by_id);
352+
}
353+
_ => panic!("Unexpected command parsing result"),
354+
}
355+
}
356+
343357
#[test]
344358
fn test_parse_admin_rebalance_start() {
345359
let cli = TestCli::parse_from(["rc", "rebalance", "start", "local"]);

crates/cli/tests/admin_decommission.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,47 @@ fn decom_cancel_dispatches_by_id_pool_json() {
124124

125125
handle.join().expect("admin test server finished");
126126
}
127+
128+
#[test]
129+
fn decommission_clear_dispatches_by_id_pool_json() {
130+
let config_dir = tempfile::tempdir().expect("create config dir");
131+
let (endpoint, receiver, handle) = start_admin_test_server("");
132+
133+
let output = Command::new(rc_binary())
134+
.args([
135+
"--json",
136+
"admin",
137+
"decommission",
138+
"clear",
139+
"myalias",
140+
"3",
141+
"--by-id",
142+
])
143+
.env("RC_CONFIG_DIR", config_dir.path())
144+
.env("RC_HOST_myalias", rc_host_alias(&endpoint))
145+
.output()
146+
.expect("run rc command");
147+
148+
assert!(
149+
output.status.success(),
150+
"stderr: {}",
151+
String::from_utf8_lossy(&output.stderr)
152+
);
153+
154+
let stdout = String::from_utf8(output.stdout).expect("stdout should be UTF-8");
155+
let payload: serde_json::Value = serde_json::from_str(&stdout).expect("JSON output");
156+
assert_eq!(payload["success"], true);
157+
assert_eq!(payload["message"], "Decommission cleared successfully");
158+
assert_eq!(payload["pool"], "3");
159+
160+
let request = receiver
161+
.recv_timeout(Duration::from_secs(5))
162+
.expect("captured admin request");
163+
assert_eq!(request.method, "POST");
164+
assert_eq!(
165+
request.target,
166+
"/rustfs/admin/v3/pools/clear?pool=3&by-id=true"
167+
);
168+
169+
handle.join().expect("admin test server finished");
170+
}

crates/cli/tests/admin_support/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ pub fn start_admin_test_server(
6969
let (sender, receiver) = mpsc::channel();
7070

7171
let handle = thread::spawn(move || {
72-
let deadline = Instant::now() + Duration::from_secs(10);
72+
let deadline = Instant::now() + Duration::from_secs(120);
7373
let (mut stream, _) = loop {
7474
match listener.accept() {
7575
Ok(accepted) => break accepted,

crates/cli/tests/help_contract.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,11 @@ fn nested_subcommand_help_contract() {
546546
usage: "Usage: rc admin decommission cancel [OPTIONS] <ALIAS> <POOL>",
547547
expected_tokens: &["--by-id"],
548548
},
549+
HelpCase {
550+
args: &["admin", "decommission", "clear"],
551+
usage: "Usage: rc admin decommission clear [OPTIONS] <ALIAS> <POOL>",
552+
expected_tokens: &["--by-id"],
553+
},
549554
HelpCase {
550555
args: &["admin", "rebalance", "start"],
551556
usage: "Usage: rc admin rebalance start [OPTIONS] <ALIAS>",

crates/core/src/admin/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ pub trait AdminApi: Send + Sync {
6262
/// Cancel decommissioning a storage pool
6363
async fn decommission_cancel(&self, target: PoolTarget) -> Result<()>;
6464

65+
/// Clear failed or canceled decommissioning metadata for a storage pool
66+
async fn decommission_clear(&self, target: PoolTarget) -> Result<()>;
67+
6568
/// Start a rebalance operation
6669
async fn rebalance_start(&self) -> Result<RebalanceStartResult>;
6770

crates/s3/src/admin.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,12 @@ impl AdminApi for AdminClient {
565565
.await
566566
}
567567

568+
async fn decommission_clear(&self, target: PoolTarget) -> Result<()> {
569+
let query = pool_target_query(&target);
570+
self.request_no_response(Method::POST, "/pools/clear", Some(&query), None)
571+
.await
572+
}
573+
568574
async fn rebalance_start(&self) -> Result<RebalanceStartResult> {
569575
self.request(Method::POST, "/rebalance/start", None, None)
570576
.await
@@ -1597,6 +1603,29 @@ mod tests {
15971603
handle.join().expect("server thread should finish");
15981604
}
15991605

1606+
#[tokio::test]
1607+
async fn test_decommission_clear_posts_pool_clear_route_with_by_id_query() {
1608+
let (endpoint, receiver, handle) = start_admin_test_server("200 OK", "");
1609+
let client = admin_client_for_endpoint(&endpoint);
1610+
1611+
client
1612+
.decommission_clear(PoolTarget {
1613+
pool: "3".to_string(),
1614+
by_id: true,
1615+
})
1616+
.await
1617+
.expect("decommission clear request");
1618+
1619+
let request = receiver.recv().expect("captured request");
1620+
assert_eq!(request.method, "POST");
1621+
assert_eq!(
1622+
request.target,
1623+
"/rustfs/admin/v3/pools/clear?pool=3&by-id=true"
1624+
);
1625+
assert!(request.body.is_empty());
1626+
handle.join().expect("server thread should finish");
1627+
}
1628+
16001629
#[tokio::test]
16011630
async fn test_rebalance_start_posts_rebalance_start_route() {
16021631
let (endpoint, receiver, handle) =

0 commit comments

Comments
 (0)