mirror of
https://git.deuxfleurs.fr/Deuxfleurs/garage.git
synced 2025-03-28 12:45:27 +00:00
160 lines
4.4 KiB
Rust
160 lines
4.4 KiB
Rust
use format_table::format_table;
|
|
|
|
use garage_util::error::*;
|
|
|
|
use garage_api_admin::api::*;
|
|
|
|
use crate::cli::remote::layout::*;
|
|
use crate::cli::remote::*;
|
|
use crate::cli::structs::*;
|
|
|
|
impl Cli {
|
|
pub async fn cmd_status(&self) -> Result<(), Error> {
|
|
let status = self.api_request(GetClusterStatusRequest).await?;
|
|
let layout = self.api_request(GetClusterLayoutRequest).await?;
|
|
|
|
println!("==== HEALTHY NODES ====");
|
|
|
|
let mut healthy_nodes =
|
|
vec!["ID\tHostname\tAddress\tTags\tZone\tCapacity\tDataAvail\tVersion".to_string()];
|
|
|
|
for adv in status.nodes.iter().filter(|adv| adv.is_up) {
|
|
let host = adv.hostname.as_deref().unwrap_or("?");
|
|
let addr = match adv.addr {
|
|
Some(addr) => addr.to_string(),
|
|
None => "N/A".to_string(),
|
|
};
|
|
if let Some(cfg) = &adv.role {
|
|
let data_avail = match &adv.data_partition {
|
|
_ if cfg.capacity.is_none() => "N/A".into(),
|
|
Some(FreeSpaceResp { available, total }) => {
|
|
let pct = (*available as f64) / (*total as f64) * 100.;
|
|
let avail_str = bytesize::ByteSize::b(*available);
|
|
format!("{} ({:.1}%)", avail_str, pct)
|
|
}
|
|
None => "?".into(),
|
|
};
|
|
healthy_nodes.push(format!(
|
|
"{id:.16}\t{host}\t{addr}\t[{tags}]\t{zone}\t{capacity}\t{data_avail}\t{version}",
|
|
id = adv.id,
|
|
host = host,
|
|
addr = addr,
|
|
tags = cfg.tags.join(","),
|
|
zone = cfg.zone,
|
|
capacity = capacity_string(cfg.capacity),
|
|
data_avail = data_avail,
|
|
version = adv.garage_version.as_deref().unwrap_or_default(),
|
|
));
|
|
} else {
|
|
let status = match layout.staged_role_changes.iter().find(|x| x.id == adv.id) {
|
|
Some(NodeRoleChange {
|
|
action: NodeRoleChangeEnum::Update { .. },
|
|
..
|
|
}) => "pending...",
|
|
_ if adv.draining => "draining metadata..",
|
|
_ => "NO ROLE ASSIGNED",
|
|
};
|
|
healthy_nodes.push(format!(
|
|
"{id:.16}\t{h}\t{addr}\t\t\t{status}\t\t{version}",
|
|
id = adv.id,
|
|
h = host,
|
|
addr = addr,
|
|
status = status,
|
|
version = adv.garage_version.as_deref().unwrap_or_default(),
|
|
));
|
|
}
|
|
}
|
|
format_table(healthy_nodes);
|
|
|
|
let tf = timeago::Formatter::new();
|
|
let mut drain_msg = false;
|
|
let mut failed_nodes = vec!["ID\tHostname\tTags\tZone\tCapacity\tLast seen".to_string()];
|
|
for adv in status.nodes.iter().filter(|x| !x.is_up) {
|
|
let node = &adv.id;
|
|
|
|
let host = adv.hostname.as_deref().unwrap_or("?");
|
|
let last_seen = adv
|
|
.last_seen_secs_ago
|
|
.map(|s| tf.convert(Duration::from_secs(s)))
|
|
.unwrap_or_else(|| "never seen".into());
|
|
|
|
if let Some(cfg) = &adv.role {
|
|
let capacity = capacity_string(cfg.capacity);
|
|
|
|
failed_nodes.push(format!(
|
|
"{id:.16}\t{host}\t[{tags}]\t{zone}\t{capacity}\t{last_seen}",
|
|
id = node,
|
|
host = host,
|
|
tags = cfg.tags.join(","),
|
|
zone = cfg.zone,
|
|
capacity = capacity,
|
|
last_seen = last_seen,
|
|
));
|
|
} else {
|
|
let status = match layout.staged_role_changes.iter().find(|x| x.id == adv.id) {
|
|
Some(NodeRoleChange {
|
|
action: NodeRoleChangeEnum::Update { .. },
|
|
..
|
|
}) => "pending...",
|
|
_ if adv.draining => {
|
|
drain_msg = true;
|
|
"draining metadata.."
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
failed_nodes.push(format!(
|
|
"{id:.16}\t{host}\t\t\t{status}\t{last_seen}",
|
|
id = node,
|
|
host = host,
|
|
status = status,
|
|
last_seen = last_seen,
|
|
));
|
|
}
|
|
}
|
|
|
|
if failed_nodes.len() > 1 {
|
|
println!("\n==== FAILED NODES ====");
|
|
format_table(failed_nodes);
|
|
if drain_msg {
|
|
println!();
|
|
println!("Your cluster is expecting to drain data from nodes that are currently unavailable.");
|
|
println!(
|
|
"If these nodes are definitely dead, please review the layout history with"
|
|
);
|
|
println!(
|
|
"`garage layout history` and use `garage layout skip-dead-nodes` to force progress."
|
|
);
|
|
}
|
|
}
|
|
|
|
if print_staging_role_changes(&layout) {
|
|
println!();
|
|
println!(
|
|
"Please use `garage layout show` to check the proposed new layout and apply it."
|
|
);
|
|
println!();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn cmd_connect(&self, opt: ConnectNodeOpt) -> Result<(), Error> {
|
|
let res = self
|
|
.api_request(ConnectClusterNodesRequest(vec![opt.node]))
|
|
.await?;
|
|
if res.0.len() != 1 {
|
|
return Err(Error::Message(format!("unexpected response: {:?}", res)));
|
|
}
|
|
let res = res.0.into_iter().next().unwrap();
|
|
if res.success {
|
|
println!("Success.");
|
|
Ok(())
|
|
} else {
|
|
Err(Error::Message(format!(
|
|
"Failure: {}",
|
|
res.error.unwrap_or_default()
|
|
)))
|
|
}
|
|
}
|
|
}
|