diff --git a/src/garage/cli/remote/cluster.rs b/src/garage/cli/remote/cluster.rs index 284e3690..ff533289 100644 --- a/src/garage/cli/remote/cluster.rs +++ b/src/garage/cli/remote/cluster.rs @@ -9,6 +9,31 @@ use crate::cli::remote::*; use crate::cli::structs::*; impl Cli { + pub async fn cmd_health(&self, quiet: bool) -> Result<(), Error> { + let health = self.api_request(GetClusterHealthRequest).await?; + + if !quiet { + let table = vec![ + format!("Cluster health:\t{}", health.status.to_uppercase()), + format!("Known nodes:\t{}", health.known_nodes), + format!("Connected nodes:\t{}", health.connected_nodes), + format!("Storage nodes:\t{}", health.storage_nodes), + format!("Storage nodes up:\t{}", health.storage_nodes_up), + format!("Partitions:\t{}", health.partitions), + format!("Partitions with quorum:\t{}", health.partitions_quorum), + format!("Fully healthy partitions:\t{}", health.partitions_all_ok), + ]; + format_table(table); + } + + match health.status.as_str() { + "unavailable" => Err(Error::Message( + "Cluster is currently unavailable".to_string(), + )), + _ => Ok(()), + } + } + pub async fn cmd_status(&self) -> Result<(), Error> { let status = self.api_request(GetClusterStatusRequest).await?; let layout = self.api_request(GetClusterLayoutRequest).await?; diff --git a/src/garage/cli/remote/mod.rs b/src/garage/cli/remote/mod.rs index d1a20989..a30b5c1a 100644 --- a/src/garage/cli/remote/mod.rs +++ b/src/garage/cli/remote/mod.rs @@ -33,6 +33,7 @@ impl Cli { pub async fn handle(&self, cmd: Command) -> Result<(), Error> { match cmd { Command::Status => self.cmd_status().await, + Command::Health(opt) => self.cmd_health(opt.quiet).await, Command::Node(NodeOperation::Connect(connect_opt)) => { self.cmd_connect(connect_opt).await } diff --git a/src/garage/cli/structs.rs b/src/garage/cli/structs.rs index f623c60d..368581cb 100644 --- a/src/garage/cli/structs.rs +++ b/src/garage/cli/structs.rs @@ -10,6 +10,10 @@ pub enum Command { #[structopt(name = "server", version = garage_version())] Server(ServerOpt), + /// Check the cluster health and set the exit code to 1 if it is unavailable + #[structopt(name = "health", version = garage_version())] + Health(HealthOpt), + /// Get network status #[structopt(name = "status", version = garage_version())] Status, @@ -103,6 +107,17 @@ pub struct ServerOpt { pub(crate) default_bucket: bool, } +// ------------------------- +// ---- garage health ---- +// ------------------------- + +#[derive(StructOpt, Debug)] +pub struct HealthOpt { + /// Do not print healthyness to stdout + #[structopt(short = "q", long = "quiet")] + pub(crate) quiet: bool, +} + // ------------------------- // ---- garage node ... ---- // -------------------------