aboutsummaryrefslogtreecommitdiff
path: root/src/garage/cli
diff options
context:
space:
mode:
Diffstat (limited to 'src/garage/cli')
-rw-r--r--src/garage/cli/cmd.rs60
-rw-r--r--src/garage/cli/layout.rs15
-rw-r--r--src/garage/cli/mod.rs11
-rw-r--r--src/garage/cli/repair.rs47
-rw-r--r--src/garage/cli/structs.rs64
-rw-r--r--src/garage/cli/util.rs216
6 files changed, 90 insertions, 323 deletions
diff --git a/src/garage/cli/cmd.rs b/src/garage/cli/cmd.rs
deleted file mode 100644
index a6540c65..00000000
--- a/src/garage/cli/cmd.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-use garage_util::error::*;
-
-use garage_rpc::system::*;
-use garage_rpc::*;
-
-use garage_model::helper::error::Error as HelperError;
-
-use crate::admin::*;
-use crate::cli::*;
-
-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::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 bb81d144..bb77cc2a 100644
--- a/src/garage/cli/layout.rs
+++ b/src/garage/cli/layout.rs
@@ -7,7 +7,7 @@ use garage_rpc::layout::*;
use garage_rpc::system::*;
use garage_rpc::*;
-use crate::cli::*;
+use crate::cli::structs::*;
pub async fn cmd_show_layout(
rpc_cli: &Endpoint<SystemRpc, ()>,
@@ -260,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 30f566e2..e007808b 100644
--- a/src/garage/cli/mod.rs
+++ b/src/garage/cli/mod.rs
@@ -1,12 +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 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 a3a1480e..00000000
--- a/src/garage/cli/util.rs
+++ /dev/null
@@ -1,216 +0,0 @@
-use std::collections::HashMap;
-use std::time::Duration;
-
-use format_table::format_table;
-use garage_util::background::*;
-use garage_util::data::*;
-use garage_util::time::*;
-
-use garage_block::manager::BlockResyncErrorInfo;
-
-use garage_model::s3::mpu_table::MultipartUpload;
-use garage_model::s3::version_table::*;
-
-use crate::cli::structs::WorkerListOpt;
-
-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`."
- );
- }
-}