diff options
Diffstat (limited to 'src/garage/cli')
-rw-r--r-- | src/garage/cli/cmd.rs | 280 | ||||
-rw-r--r-- | src/garage/cli/layout.rs | 225 | ||||
-rw-r--r-- | src/garage/cli/mod.rs | 12 | ||||
-rw-r--r-- | src/garage/cli/repair.rs | 47 | ||||
-rw-r--r-- | src/garage/cli/structs.rs | 64 | ||||
-rw-r--r-- | src/garage/cli/util.rs | 457 |
6 files changed, 90 insertions, 995 deletions
diff --git a/src/garage/cli/cmd.rs b/src/garage/cli/cmd.rs deleted file mode 100644 index 44d3d96c..00000000 --- a/src/garage/cli/cmd.rs +++ /dev/null @@ -1,280 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::time::Duration; - -use format_table::format_table; -use garage_util::error::*; - -use garage_rpc::layout::*; -use garage_rpc::system::*; -use garage_rpc::*; - -use garage_model::helper::error::Error as HelperError; - -use crate::admin::*; -use crate::cli::*; - -pub async fn cli_command_dispatch( - cmd: Command, - system_rpc_endpoint: &Endpoint<SystemRpc, ()>, - admin_rpc_endpoint: &Endpoint<AdminRpc, ()>, - rpc_host: NodeID, -) -> Result<(), HelperError> { - match cmd { - Command::Status => Ok(cmd_status(system_rpc_endpoint, rpc_host).await?), - Command::Node(NodeOperation::Connect(connect_opt)) => { - Ok(cmd_connect(system_rpc_endpoint, rpc_host, connect_opt).await?) - } - Command::Layout(layout_opt) => { - Ok(cli_layout_command_dispatch(layout_opt, system_rpc_endpoint, rpc_host).await?) - } - Command::Bucket(bo) => { - cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::BucketOperation(bo)).await - } - Command::Key(ko) => { - cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::KeyOperation(ko)).await - } - Command::Repair(ro) => { - cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::LaunchRepair(ro)).await - } - Command::Stats(so) => cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::Stats(so)).await, - Command::Worker(wo) => cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::Worker(wo)).await, - Command::Block(bo) => { - cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::BlockOperation(bo)).await - } - Command::Meta(mo) => { - cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::MetaOperation(mo)).await - } - _ => unreachable!(), - } -} - -pub async fn cmd_status(rpc_cli: &Endpoint<SystemRpc, ()>, rpc_host: NodeID) -> Result<(), Error> { - let status = fetch_status(rpc_cli, rpc_host).await?; - let layout = fetch_layout(rpc_cli, rpc_host).await?; - - println!("==== HEALTHY NODES ===="); - let mut healthy_nodes = - vec!["ID\tHostname\tAddress\tTags\tZone\tCapacity\tDataAvail".to_string()]; - for adv in status.iter().filter(|adv| adv.is_up) { - let host = adv.status.hostname.as_deref().unwrap_or("?"); - let addr = match adv.addr { - Some(addr) => addr.to_string(), - None => "N/A".to_string(), - }; - if let Some(NodeRoleV(Some(cfg))) = layout.current().roles.get(&adv.id) { - let data_avail = match &adv.status.data_disk_avail { - _ if cfg.capacity.is_none() => "N/A".into(), - Some((avail, total)) => { - let pct = (*avail as f64) / (*total as f64) * 100.; - let avail = bytesize::ByteSize::b(*avail); - format!("{} ({:.1}%)", avail, pct) - } - None => "?".into(), - }; - healthy_nodes.push(format!( - "{id:?}\t{host}\t{addr}\t[{tags}]\t{zone}\t{capacity}\t{data_avail}", - id = adv.id, - host = host, - addr = addr, - tags = cfg.tags.join(","), - zone = cfg.zone, - capacity = cfg.capacity_string(), - data_avail = data_avail, - )); - } else { - let prev_role = layout - .versions - .iter() - .rev() - .find_map(|x| match x.roles.get(&adv.id) { - Some(NodeRoleV(Some(cfg))) => Some(cfg), - _ => None, - }); - if let Some(cfg) = prev_role { - healthy_nodes.push(format!( - "{id:?}\t{host}\t{addr}\t[{tags}]\t{zone}\tdraining metadata...", - id = adv.id, - host = host, - addr = addr, - tags = cfg.tags.join(","), - zone = cfg.zone, - )); - } else { - let new_role = match layout.staging.get().roles.get(&adv.id) { - Some(NodeRoleV(Some(_))) => "pending...", - _ => "NO ROLE ASSIGNED", - }; - healthy_nodes.push(format!( - "{id:?}\t{h}\t{addr}\t\t\t{new_role}", - id = adv.id, - h = host, - addr = addr, - new_role = new_role, - )); - } - } - } - format_table(healthy_nodes); - - // Determine which nodes are unhealthy and print that to stdout - let status_map = status - .iter() - .map(|adv| (adv.id, adv)) - .collect::<HashMap<_, _>>(); - - let tf = timeago::Formatter::new(); - let mut drain_msg = false; - let mut failed_nodes = vec!["ID\tHostname\tTags\tZone\tCapacity\tLast seen".to_string()]; - let mut listed = HashSet::new(); - for ver in layout.versions.iter().rev() { - for (node, _, role) in ver.roles.items().iter() { - let cfg = match role { - NodeRoleV(Some(role)) if role.capacity.is_some() => role, - _ => continue, - }; - - if listed.contains(node) { - continue; - } - listed.insert(*node); - - let adv = status_map.get(node); - if adv.map(|x| x.is_up).unwrap_or(false) { - continue; - } - - // Node is in a layout version, is not a gateway node, and is not up: - // it is in a failed state, add proper line to the output - let (host, last_seen) = match adv { - Some(adv) => ( - adv.status.hostname.as_deref().unwrap_or("?"), - adv.last_seen_secs_ago - .map(|s| tf.convert(Duration::from_secs(s))) - .unwrap_or_else(|| "never seen".into()), - ), - None => ("??", "never seen".into()), - }; - let capacity = if ver.version == layout.current().version { - cfg.capacity_string() - } else { - drain_msg = true; - "draining metadata...".to_string() - }; - failed_nodes.push(format!( - "{id:?}\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, - )); - } - } - - 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( - rpc_cli: &Endpoint<SystemRpc, ()>, - rpc_host: NodeID, - args: ConnectNodeOpt, -) -> Result<(), Error> { - match rpc_cli - .call(&rpc_host, SystemRpc::Connect(args.node), PRIO_NORMAL) - .await?? - { - SystemRpc::Ok => { - println!("Success."); - Ok(()) - } - m => Err(Error::unexpected_rpc_message(m)), - } -} - -pub async fn cmd_admin( - rpc_cli: &Endpoint<AdminRpc, ()>, - rpc_host: NodeID, - args: AdminRpc, -) -> Result<(), HelperError> { - match rpc_cli.call(&rpc_host, args, PRIO_NORMAL).await?? { - AdminRpc::Ok(msg) => { - println!("{}", msg); - } - AdminRpc::BucketList(bl) => { - print_bucket_list(bl); - } - AdminRpc::BucketInfo { - bucket, - relevant_keys, - counters, - mpu_counters, - } => { - print_bucket_info(&bucket, &relevant_keys, &counters, &mpu_counters); - } - AdminRpc::KeyList(kl) => { - print_key_list(kl); - } - AdminRpc::KeyInfo(key, rb) => { - print_key_info(&key, &rb); - } - AdminRpc::WorkerList(wi, wlo) => { - print_worker_list(wi, wlo); - } - AdminRpc::WorkerVars(wv) => { - print_worker_vars(wv); - } - AdminRpc::WorkerInfo(tid, wi) => { - print_worker_info(tid, wi); - } - AdminRpc::BlockErrorList(el) => { - print_block_error_list(el); - } - AdminRpc::BlockInfo { - hash, - refcount, - versions, - uploads, - } => { - print_block_info(hash, refcount, versions, uploads); - } - r => { - error!("Unexpected response: {:?}", r); - } - } - Ok(()) -} - -// ---- utility ---- - -pub async fn fetch_status( - rpc_cli: &Endpoint<SystemRpc, ()>, - rpc_host: NodeID, -) -> Result<Vec<KnownNodeInfo>, Error> { - match rpc_cli - .call(&rpc_host, SystemRpc::GetKnownNodes, PRIO_NORMAL) - .await?? - { - SystemRpc::ReturnKnownNodes(nodes) => Ok(nodes), - resp => Err(Error::unexpected_rpc_message(resp)), - } -} diff --git a/src/garage/cli/layout.rs b/src/garage/cli/layout.rs index f053eef4..bb77cc2a 100644 --- a/src/garage/cli/layout.rs +++ b/src/garage/cli/layout.rs @@ -1,182 +1,13 @@ use bytesize::ByteSize; use format_table::format_table; -use garage_util::crdt::Crdt; use garage_util::error::*; use garage_rpc::layout::*; use garage_rpc::system::*; use garage_rpc::*; -use crate::cli::*; - -pub async fn cli_layout_command_dispatch( - cmd: LayoutOperation, - system_rpc_endpoint: &Endpoint<SystemRpc, ()>, - rpc_host: NodeID, -) -> Result<(), Error> { - match cmd { - LayoutOperation::Assign(assign_opt) => { - cmd_assign_role(system_rpc_endpoint, rpc_host, assign_opt).await - } - LayoutOperation::Remove(remove_opt) => { - cmd_remove_role(system_rpc_endpoint, rpc_host, remove_opt).await - } - LayoutOperation::Show => cmd_show_layout(system_rpc_endpoint, rpc_host).await, - LayoutOperation::Apply(apply_opt) => { - cmd_apply_layout(system_rpc_endpoint, rpc_host, apply_opt).await - } - LayoutOperation::Revert(revert_opt) => { - cmd_revert_layout(system_rpc_endpoint, rpc_host, revert_opt).await - } - LayoutOperation::Config(config_opt) => { - cmd_config_layout(system_rpc_endpoint, rpc_host, config_opt).await - } - LayoutOperation::History => cmd_layout_history(system_rpc_endpoint, rpc_host).await, - LayoutOperation::SkipDeadNodes(assume_sync_opt) => { - cmd_layout_skip_dead_nodes(system_rpc_endpoint, rpc_host, assume_sync_opt).await - } - } -} - -pub async fn cmd_assign_role( - rpc_cli: &Endpoint<SystemRpc, ()>, - rpc_host: NodeID, - args: AssignRoleOpt, -) -> Result<(), Error> { - let status = match rpc_cli - .call(&rpc_host, SystemRpc::GetKnownNodes, PRIO_NORMAL) - .await?? - { - SystemRpc::ReturnKnownNodes(nodes) => nodes, - resp => return Err(Error::Message(format!("Invalid RPC response: {:?}", resp))), - }; - - let mut layout = fetch_layout(rpc_cli, rpc_host).await?; - let all_nodes = layout.get_all_nodes(); - - let added_nodes = args - .node_ids - .iter() - .map(|node_id| { - find_matching_node( - status - .iter() - .map(|adv| adv.id) - .chain(all_nodes.iter().cloned()), - node_id, - ) - }) - .collect::<Result<Vec<_>, _>>()?; - - let mut roles = layout.current().roles.clone(); - roles.merge(&layout.staging.get().roles); - - for replaced in args.replace.iter() { - let replaced_node = find_matching_node(all_nodes.iter().cloned(), replaced)?; - match roles.get(&replaced_node) { - Some(NodeRoleV(Some(_))) => { - layout - .staging - .get_mut() - .roles - .merge(&roles.update_mutator(replaced_node, NodeRoleV(None))); - } - _ => { - return Err(Error::Message(format!( - "Cannot replace node {:?} as it is not currently in planned layout", - replaced_node - ))); - } - } - } - - if args.capacity.is_some() && args.gateway { - return Err(Error::Message( - "-c and -g are mutually exclusive, please configure node either with c>0 to act as a storage node or with -g to act as a gateway node".into())); - } - if args.capacity == Some(ByteSize::b(0)) { - return Err(Error::Message("Invalid capacity value: 0".into())); - } - - for added_node in added_nodes { - let new_entry = match roles.get(&added_node) { - Some(NodeRoleV(Some(old))) => { - let capacity = match args.capacity { - Some(c) => Some(c.as_u64()), - None if args.gateway => None, - None => old.capacity, - }; - let tags = if args.tags.is_empty() { - old.tags.clone() - } else { - args.tags.clone() - }; - NodeRole { - zone: args.zone.clone().unwrap_or_else(|| old.zone.to_string()), - capacity, - tags, - } - } - _ => { - let capacity = match args.capacity { - Some(c) => Some(c.as_u64()), - None if args.gateway => None, - None => return Err(Error::Message( - "Please specify a capacity with the -c flag, or set node explicitly as gateway with -g".into())), - }; - NodeRole { - zone: args - .zone - .clone() - .ok_or("Please specify a zone with the -z flag")?, - capacity, - tags: args.tags.clone(), - } - } - }; - - layout - .staging - .get_mut() - .roles - .merge(&roles.update_mutator(added_node, NodeRoleV(Some(new_entry)))); - } - - send_layout(rpc_cli, rpc_host, layout).await?; - - println!("Role changes are staged but not yet committed."); - println!("Use `garage layout show` to view staged role changes,"); - println!("and `garage layout apply` to enact staged changes."); - Ok(()) -} - -pub async fn cmd_remove_role( - rpc_cli: &Endpoint<SystemRpc, ()>, - rpc_host: NodeID, - args: RemoveRoleOpt, -) -> Result<(), Error> { - let mut layout = fetch_layout(rpc_cli, rpc_host).await?; - - let mut roles = layout.current().roles.clone(); - roles.merge(&layout.staging.get().roles); - - let deleted_node = - find_matching_node(roles.items().iter().map(|(id, _, _)| *id), &args.node_id)?; - - layout - .staging - .get_mut() - .roles - .merge(&roles.update_mutator(deleted_node, NodeRoleV(None))); - - send_layout(rpc_cli, rpc_host, layout).await?; - - println!("Role removal is staged but not yet committed."); - println!("Use `garage layout show` to view staged role changes,"); - println!("and `garage layout apply` to enact staged changes."); - Ok(()) -} +use crate::cli::structs::*; pub async fn cmd_show_layout( rpc_cli: &Endpoint<SystemRpc, ()>, @@ -226,47 +57,6 @@ pub async fn cmd_show_layout( Ok(()) } -pub async fn cmd_apply_layout( - rpc_cli: &Endpoint<SystemRpc, ()>, - rpc_host: NodeID, - apply_opt: ApplyLayoutOpt, -) -> Result<(), Error> { - let layout = fetch_layout(rpc_cli, rpc_host).await?; - - let (layout, msg) = layout.apply_staged_changes(apply_opt.version)?; - for line in msg.iter() { - println!("{}", line); - } - - send_layout(rpc_cli, rpc_host, layout).await?; - - println!("New cluster layout with updated role assignment has been applied in cluster."); - println!("Data will now be moved around between nodes accordingly."); - - Ok(()) -} - -pub async fn cmd_revert_layout( - rpc_cli: &Endpoint<SystemRpc, ()>, - rpc_host: NodeID, - revert_opt: RevertLayoutOpt, -) -> Result<(), Error> { - if !revert_opt.yes { - return Err(Error::Message( - "Please add the --yes flag to run the layout revert operation".into(), - )); - } - - let layout = fetch_layout(rpc_cli, rpc_host).await?; - - let layout = layout.revert_staged_changes()?; - - send_layout(rpc_cli, rpc_host, layout).await?; - - println!("All proposed role changes in cluster layout have been canceled."); - Ok(()) -} - pub async fn cmd_config_layout( rpc_cli: &Endpoint<SystemRpc, ()>, rpc_host: NodeID, @@ -470,6 +260,19 @@ pub async fn cmd_layout_skip_dead_nodes( // --- utility --- +pub async fn fetch_status( + rpc_cli: &Endpoint<SystemRpc, ()>, + rpc_host: NodeID, +) -> Result<Vec<KnownNodeInfo>, Error> { + match rpc_cli + .call(&rpc_host, SystemRpc::GetKnownNodes, PRIO_NORMAL) + .await?? + { + SystemRpc::ReturnKnownNodes(nodes) => Ok(nodes), + resp => Err(Error::unexpected_rpc_message(resp)), + } +} + pub async fn fetch_layout( rpc_cli: &Endpoint<SystemRpc, ()>, rpc_host: NodeID, diff --git a/src/garage/cli/mod.rs b/src/garage/cli/mod.rs index e131f62c..e007808b 100644 --- a/src/garage/cli/mod.rs +++ b/src/garage/cli/mod.rs @@ -1,13 +1,7 @@ -pub(crate) mod cmd; -pub(crate) mod init; -pub(crate) mod layout; pub(crate) mod structs; -pub(crate) mod util; pub(crate) mod convert_db; +pub(crate) mod init; +pub(crate) mod repair; -pub(crate) use cmd::*; -pub(crate) use init::*; -pub(crate) use layout::*; -pub(crate) use structs::*; -pub(crate) use util::*; +pub(crate) mod layout; diff --git a/src/garage/cli/repair.rs b/src/garage/cli/repair.rs new file mode 100644 index 00000000..45024e71 --- /dev/null +++ b/src/garage/cli/repair.rs @@ -0,0 +1,47 @@ +use std::path::PathBuf; + +use garage_util::config::*; +use garage_util::error::*; + +use garage_model::garage::Garage; + +use crate::cli::structs::*; +use crate::secrets::{fill_secrets, Secrets}; + +pub async fn offline_repair( + config_file: PathBuf, + secrets: Secrets, + opt: OfflineRepairOpt, +) -> Result<(), Error> { + if !opt.yes { + return Err(Error::Message( + "Please add the --yes flag to launch repair operation".into(), + )); + } + + info!("Loading configuration..."); + let config = fill_secrets(read_config(config_file)?, secrets)?; + + info!("Initializing Garage main data store..."); + let garage = Garage::new(config)?; + + info!("Launching repair operation..."); + match opt.what { + #[cfg(feature = "k2v")] + OfflineRepairWhat::K2VItemCounters => { + garage + .k2v + .counter_table + .offline_recount_all(&garage.k2v.item_table)?; + } + OfflineRepairWhat::ObjectCounters => { + garage + .object_counter_table + .offline_recount_all(&garage.object_table)?; + } + } + + info!("Repair operation finished, shutting down..."); + + Ok(()) +} diff --git a/src/garage/cli/structs.rs b/src/garage/cli/structs.rs index 4ec35e68..c6471515 100644 --- a/src/garage/cli/structs.rs +++ b/src/garage/cli/structs.rs @@ -1,4 +1,3 @@ -use serde::{Deserialize, Serialize}; use structopt::StructOpt; use garage_util::version::garage_version; @@ -190,7 +189,7 @@ pub struct SkipDeadNodesOpt { pub(crate) allow_missing_data: bool, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub enum BucketOperation { /// List buckets #[structopt(name = "list", version = garage_version())] @@ -237,7 +236,7 @@ pub enum BucketOperation { CleanupIncompleteUploads(CleanupIncompleteUploadsOpt), } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct WebsiteOpt { /// Create #[structopt(long = "allow")] @@ -259,13 +258,13 @@ pub struct WebsiteOpt { pub error_document: Option<String>, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct BucketOpt { /// Bucket name pub name: String, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct DeleteBucketOpt { /// Bucket name pub name: String, @@ -275,7 +274,7 @@ pub struct DeleteBucketOpt { pub yes: bool, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct AliasBucketOpt { /// Existing bucket name (its alias in global namespace or its full hex uuid) pub existing_bucket: String, @@ -288,7 +287,7 @@ pub struct AliasBucketOpt { pub local: Option<String>, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct UnaliasBucketOpt { /// Bucket name pub name: String, @@ -298,7 +297,7 @@ pub struct UnaliasBucketOpt { pub local: Option<String>, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct PermBucketOpt { /// Access key name or ID #[structopt(long = "key")] @@ -321,7 +320,7 @@ pub struct PermBucketOpt { pub bucket: String, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct SetQuotasOpt { /// Bucket name pub bucket: String, @@ -336,7 +335,7 @@ pub struct SetQuotasOpt { pub max_objects: Option<String>, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct CleanupIncompleteUploadsOpt { /// Abort multipart uploads older than this value #[structopt(long = "older-than", default_value = "1d")] @@ -347,7 +346,7 @@ pub struct CleanupIncompleteUploadsOpt { pub buckets: Vec<String>, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub enum KeyOperation { /// List keys #[structopt(name = "list", version = garage_version())] @@ -382,7 +381,7 @@ pub enum KeyOperation { Import(KeyImportOpt), } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct KeyInfoOpt { /// ID or name of the key pub key_pattern: String, @@ -391,14 +390,14 @@ pub struct KeyInfoOpt { pub show_secret: bool, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct KeyNewOpt { /// Name of the key #[structopt(default_value = "Unnamed key")] pub name: String, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct KeyRenameOpt { /// ID or name of the key pub key_pattern: String, @@ -407,7 +406,7 @@ pub struct KeyRenameOpt { pub new_name: String, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct KeyDeleteOpt { /// ID or name of the key pub key_pattern: String, @@ -417,7 +416,7 @@ pub struct KeyDeleteOpt { pub yes: bool, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct KeyPermOpt { /// ID or name of the key pub key_pattern: String, @@ -427,7 +426,7 @@ pub struct KeyPermOpt { pub create_bucket: bool, } -#[derive(Serialize, Deserialize, StructOpt, Debug)] +#[derive(StructOpt, Debug)] pub struct KeyImportOpt { /// Access key ID pub key_id: String, @@ -444,7 +443,7 @@ pub struct KeyImportOpt { pub yes: bool, } -#[derive(Serialize, Deserialize, StructOpt, Debug, Clone)] +#[derive(StructOpt, Debug, Clone)] pub struct RepairOpt { /// Launch repair operation on all nodes #[structopt(short = "a", long = "all-nodes")] @@ -458,7 +457,7 @@ pub struct RepairOpt { pub what: RepairWhat, } -#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)] +#[derive(StructOpt, Debug, Eq, PartialEq, Clone)] pub enum RepairWhat { /// Do a full sync of metadata tables #[structopt(name = "tables", version = garage_version())] @@ -489,7 +488,7 @@ pub enum RepairWhat { Rebalance, } -#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)] +#[derive(StructOpt, Debug, Eq, PartialEq, Clone)] pub enum ScrubCmd { /// Start scrub #[structopt(name = "start", version = garage_version())] @@ -503,15 +502,9 @@ pub enum ScrubCmd { /// Cancel scrub in progress #[structopt(name = "cancel", version = garage_version())] Cancel, - /// Set tranquility level for in-progress and future scrubs - #[structopt(name = "set-tranquility", version = garage_version())] - SetTranquility { - #[structopt()] - tranquility: u32, - }, } -#[derive(Serialize, Deserialize, StructOpt, Debug, Clone)] +#[derive(StructOpt, Debug, Clone)] pub struct OfflineRepairOpt { /// Confirm the launch of the repair operation #[structopt(long = "yes")] @@ -521,7 +514,7 @@ pub struct OfflineRepairOpt { pub what: OfflineRepairWhat, } -#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)] +#[derive(StructOpt, Debug, Eq, PartialEq, Clone)] pub enum OfflineRepairWhat { /// Repair K2V item counters #[cfg(feature = "k2v")] @@ -532,19 +525,14 @@ pub enum OfflineRepairWhat { ObjectCounters, } -#[derive(Serialize, Deserialize, StructOpt, Debug, Clone)] +#[derive(StructOpt, Debug, Clone)] pub struct StatsOpt { /// Gather statistics from all nodes #[structopt(short = "a", long = "all-nodes")] pub all_nodes: bool, - - /// Don't show global cluster stats (internal use in RPC) - #[structopt(skip)] - #[serde(default)] - pub skip_global: bool, } -#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)] +#[derive(StructOpt, Debug, Eq, PartialEq, Clone)] pub enum WorkerOperation { /// List all workers on Garage node #[structopt(name = "list", version = garage_version())] @@ -577,7 +565,7 @@ pub enum WorkerOperation { }, } -#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone, Copy)] +#[derive(StructOpt, Debug, Eq, PartialEq, Clone, Copy)] pub struct WorkerListOpt { /// Show only busy workers #[structopt(short = "b", long = "busy")] @@ -587,7 +575,7 @@ pub struct WorkerListOpt { pub errors: bool, } -#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone)] +#[derive(StructOpt, Debug, Eq, PartialEq, Clone)] pub enum BlockOperation { /// List all blocks that currently have a resync error #[structopt(name = "list-errors", version = garage_version())] @@ -619,7 +607,7 @@ pub enum BlockOperation { }, } -#[derive(Serialize, Deserialize, StructOpt, Debug, Eq, PartialEq, Clone, Copy)] +#[derive(StructOpt, Debug, Eq, PartialEq, Clone, Copy)] pub enum MetaOperation { /// Save a snapshot of the metadata db file #[structopt(name = "snapshot", version = garage_version())] diff --git a/src/garage/cli/util.rs b/src/garage/cli/util.rs deleted file mode 100644 index 21c14f42..00000000 --- a/src/garage/cli/util.rs +++ /dev/null @@ -1,457 +0,0 @@ -use std::collections::HashMap; -use std::time::Duration; - -use format_table::format_table; -use garage_util::background::*; -use garage_util::crdt::*; -use garage_util::data::*; -use garage_util::error::*; -use garage_util::time::*; - -use garage_block::manager::BlockResyncErrorInfo; - -use garage_model::bucket_table::*; -use garage_model::key_table::*; -use garage_model::s3::mpu_table::{self, MultipartUpload}; -use garage_model::s3::object_table; -use garage_model::s3::version_table::*; - -use crate::cli::structs::WorkerListOpt; - -pub fn print_bucket_list(bl: Vec<Bucket>) { - println!("List of buckets:"); - - let mut table = vec![]; - for bucket in bl { - let aliases = bucket - .aliases() - .iter() - .filter(|(_, _, active)| *active) - .map(|(name, _, _)| name.to_string()) - .collect::<Vec<_>>(); - let local_aliases_n = match &bucket - .local_aliases() - .iter() - .filter(|(_, _, active)| *active) - .collect::<Vec<_>>()[..] - { - [] => "".into(), - [((k, n), _, _)] => format!("{}:{}", k, n), - s => format!("[{} local aliases]", s.len()), - }; - - table.push(format!( - "\t{}\t{}\t{}", - aliases.join(","), - local_aliases_n, - hex::encode(bucket.id), - )); - } - format_table(table); -} - -pub fn print_key_list(kl: Vec<(String, String)>) { - println!("List of keys:"); - let mut table = vec![]; - for key in kl { - table.push(format!("\t{}\t{}", key.0, key.1)); - } - format_table(table); -} - -pub fn print_key_info(key: &Key, relevant_buckets: &HashMap<Uuid, Bucket>) { - let bucket_global_aliases = |b: &Uuid| { - if let Some(bucket) = relevant_buckets.get(b) { - if let Some(p) = bucket.state.as_option() { - return p - .aliases - .items() - .iter() - .filter(|(_, _, active)| *active) - .map(|(a, _, _)| a.clone()) - .collect::<Vec<_>>() - .join(", "); - } - } - - "".to_string() - }; - - match &key.state { - Deletable::Present(p) => { - println!("Key name: {}", p.name.get()); - println!("Key ID: {}", key.key_id); - println!("Secret key: {}", p.secret_key); - println!("Can create buckets: {}", p.allow_create_bucket.get()); - println!("\nKey-specific bucket aliases:"); - let mut table = vec![]; - for (alias_name, _, alias) in p.local_aliases.items().iter() { - if let Some(bucket_id) = alias { - table.push(format!( - "\t{}\t{}\t{}", - alias_name, - bucket_global_aliases(bucket_id), - hex::encode(bucket_id) - )); - } - } - format_table(table); - - println!("\nAuthorized buckets:"); - let mut table = vec![]; - for (bucket_id, perm) in p.authorized_buckets.items().iter() { - if !perm.is_any() { - continue; - } - let rflag = if perm.allow_read { "R" } else { " " }; - let wflag = if perm.allow_write { "W" } else { " " }; - let oflag = if perm.allow_owner { "O" } else { " " }; - let local_aliases = p - .local_aliases - .items() - .iter() - .filter(|(_, _, a)| *a == Some(*bucket_id)) - .map(|(a, _, _)| a.clone()) - .collect::<Vec<_>>() - .join(", "); - table.push(format!( - "\t{}{}{}\t{}\t{}\t{:?}", - rflag, - wflag, - oflag, - bucket_global_aliases(bucket_id), - local_aliases, - bucket_id - )); - } - format_table(table); - } - Deletable::Deleted => { - println!("Key {} is deleted.", key.key_id); - } - } -} - -pub fn print_bucket_info( - bucket: &Bucket, - relevant_keys: &HashMap<String, Key>, - counters: &HashMap<String, i64>, - mpu_counters: &HashMap<String, i64>, -) { - let key_name = |k| { - relevant_keys - .get(k) - .map(|k| k.params().unwrap().name.get().as_str()) - .unwrap_or("<deleted>") - }; - - println!("Bucket: {}", hex::encode(bucket.id)); - match &bucket.state { - Deletable::Deleted => println!("Bucket is deleted."), - Deletable::Present(p) => { - let size = - bytesize::ByteSize::b(*counters.get(object_table::BYTES).unwrap_or(&0) as u64); - println!( - "\nSize: {} ({})", - size.to_string_as(true), - size.to_string_as(false) - ); - println!( - "Objects: {}", - *counters.get(object_table::OBJECTS).unwrap_or(&0) - ); - println!( - "Unfinished uploads (multipart and non-multipart): {}", - *counters.get(object_table::UNFINISHED_UPLOADS).unwrap_or(&0) - ); - println!( - "Unfinished multipart uploads: {}", - *mpu_counters.get(mpu_table::UPLOADS).unwrap_or(&0) - ); - let mpu_size = - bytesize::ByteSize::b(*mpu_counters.get(mpu_table::BYTES).unwrap_or(&0) as u64); - println!( - "Size of unfinished multipart uploads: {} ({})", - mpu_size.to_string_as(true), - mpu_size.to_string_as(false), - ); - - println!("\nWebsite access: {}", p.website_config.get().is_some()); - - let quotas = p.quotas.get(); - if quotas.max_size.is_some() || quotas.max_objects.is_some() { - println!("\nQuotas:"); - if let Some(ms) = quotas.max_size { - let ms = bytesize::ByteSize::b(ms); - println!( - " maximum size: {} ({})", - ms.to_string_as(true), - ms.to_string_as(false) - ); - } - if let Some(mo) = quotas.max_objects { - println!(" maximum number of objects: {}", mo); - } - } - - println!("\nGlobal aliases:"); - for (alias, _, active) in p.aliases.items().iter() { - if *active { - println!(" {}", alias); - } - } - - println!("\nKey-specific aliases:"); - let mut table = vec![]; - for ((key_id, alias), _, active) in p.local_aliases.items().iter() { - if *active { - table.push(format!("\t{} ({})\t{}", key_id, key_name(key_id), alias)); - } - } - format_table(table); - - println!("\nAuthorized keys:"); - let mut table = vec![]; - for (k, perm) in p.authorized_keys.items().iter() { - if !perm.is_any() { - continue; - } - let rflag = if perm.allow_read { "R" } else { " " }; - let wflag = if perm.allow_write { "W" } else { " " }; - let oflag = if perm.allow_owner { "O" } else { " " }; - table.push(format!( - "\t{}{}{}\t{}\t{}", - rflag, - wflag, - oflag, - k, - key_name(k) - )); - } - format_table(table); - } - }; -} - -pub fn find_matching_node( - cand: impl std::iter::Iterator<Item = Uuid>, - pattern: &str, -) -> Result<Uuid, Error> { - let mut candidates = vec![]; - for c in cand { - if hex::encode(c).starts_with(pattern) && !candidates.contains(&c) { - candidates.push(c); - } - } - if candidates.len() != 1 { - Err(Error::Message(format!( - "{} nodes match '{}'", - candidates.len(), - pattern, - ))) - } else { - Ok(candidates[0]) - } -} - -pub fn print_worker_list(wi: HashMap<usize, WorkerInfo>, wlo: WorkerListOpt) { - let mut wi = wi.into_iter().collect::<Vec<_>>(); - wi.sort_by_key(|(tid, info)| { - ( - match info.state { - WorkerState::Busy | WorkerState::Throttled(_) => 0, - WorkerState::Idle => 1, - WorkerState::Done => 2, - }, - *tid, - ) - }); - - let mut table = vec!["TID\tState\tName\tTranq\tDone\tQueue\tErrors\tConsec\tLast".to_string()]; - for (tid, info) in wi.iter() { - if wlo.busy && !matches!(info.state, WorkerState::Busy | WorkerState::Throttled(_)) { - continue; - } - if wlo.errors && info.errors == 0 { - continue; - } - - let tf = timeago::Formatter::new(); - let err_ago = info - .last_error - .as_ref() - .map(|(_, t)| tf.convert(Duration::from_millis(now_msec() - t))) - .unwrap_or_default(); - let (total_err, consec_err) = if info.errors > 0 { - (info.errors.to_string(), info.consecutive_errors.to_string()) - } else { - ("-".into(), "-".into()) - }; - - table.push(format!( - "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", - tid, - info.state, - info.name, - info.status - .tranquility - .as_ref() - .map(ToString::to_string) - .unwrap_or_else(|| "-".into()), - info.status.progress.as_deref().unwrap_or("-"), - info.status - .queue_length - .as_ref() - .map(ToString::to_string) - .unwrap_or_else(|| "-".into()), - total_err, - consec_err, - err_ago, - )); - } - format_table(table); -} - -pub fn print_worker_info(tid: usize, info: WorkerInfo) { - let mut table = vec![]; - table.push(format!("Task id:\t{}", tid)); - table.push(format!("Worker name:\t{}", info.name)); - match info.state { - WorkerState::Throttled(t) => { - table.push(format!( - "Worker state:\tBusy (throttled, paused for {:.3}s)", - t - )); - } - s => { - table.push(format!("Worker state:\t{}", s)); - } - }; - if let Some(tql) = info.status.tranquility { - table.push(format!("Tranquility:\t{}", tql)); - } - - table.push("".into()); - table.push(format!("Total errors:\t{}", info.errors)); - table.push(format!("Consecutive errs:\t{}", info.consecutive_errors)); - if let Some((s, t)) = info.last_error { - table.push(format!("Last error:\t{}", s)); - let tf = timeago::Formatter::new(); - table.push(format!( - "Last error time:\t{}", - tf.convert(Duration::from_millis(now_msec() - t)) - )); - } - - table.push("".into()); - if let Some(p) = info.status.progress { - table.push(format!("Progress:\t{}", p)); - } - if let Some(ql) = info.status.queue_length { - table.push(format!("Queue length:\t{}", ql)); - } - if let Some(pe) = info.status.persistent_errors { - table.push(format!("Persistent errors:\t{}", pe)); - } - - for (i, s) in info.status.freeform.iter().enumerate() { - if i == 0 { - if table.last() != Some(&"".into()) { - table.push("".into()); - } - table.push(format!("Message:\t{}", s)); - } else { - table.push(format!("\t{}", s)); - } - } - format_table(table); -} - -pub fn print_worker_vars(wv: Vec<(Uuid, String, String)>) { - let table = wv - .into_iter() - .map(|(n, k, v)| format!("{:?}\t{}\t{}", n, k, v)) - .collect::<Vec<_>>(); - format_table(table); -} - -pub fn print_block_error_list(el: Vec<BlockResyncErrorInfo>) { - let now = now_msec(); - let tf = timeago::Formatter::new(); - let mut tf2 = timeago::Formatter::new(); - tf2.ago(""); - - let mut table = vec!["Hash\tRC\tErrors\tLast error\tNext try".into()]; - for e in el { - let next_try = if e.next_try > now { - tf2.convert(Duration::from_millis(e.next_try - now)) - } else { - "asap".to_string() - }; - table.push(format!( - "{}\t{}\t{}\t{}\tin {}", - hex::encode(e.hash.as_slice()), - e.refcount, - e.error_count, - tf.convert(Duration::from_millis(now - e.last_try)), - next_try - )); - } - format_table(table); -} - -pub fn print_block_info( - hash: Hash, - refcount: u64, - versions: Vec<Result<Version, Uuid>>, - uploads: Vec<MultipartUpload>, -) { - println!("Block hash: {}", hex::encode(hash.as_slice())); - println!("Refcount: {}", refcount); - println!(); - - let mut table = vec!["Version\tBucket\tKey\tMPU\tDeleted".into()]; - let mut nondeleted_count = 0; - for v in versions.iter() { - match v { - Ok(ver) => { - match &ver.backlink { - VersionBacklink::Object { bucket_id, key } => { - table.push(format!( - "{:?}\t{:?}\t{}\t\t{:?}", - ver.uuid, - bucket_id, - key, - ver.deleted.get() - )); - } - VersionBacklink::MultipartUpload { upload_id } => { - let upload = uploads.iter().find(|x| x.upload_id == *upload_id); - table.push(format!( - "{:?}\t{:?}\t{}\t{:?}\t{:?}", - ver.uuid, - upload.map(|u| u.bucket_id).unwrap_or_default(), - upload.map(|u| u.key.as_str()).unwrap_or_default(), - upload_id, - ver.deleted.get() - )); - } - } - if !ver.deleted.get() { - nondeleted_count += 1; - } - } - Err(vh) => { - table.push(format!("{:?}\t\t\t\tyes", vh)); - } - } - } - format_table(table); - - if refcount != nondeleted_count { - println!(); - println!( - "Warning: refcount does not match number of non-deleted versions, you should try `garage repair block-rc`." - ); - } -} |