2021-10-19 16:16:10 +02:00
|
|
|
use std::collections::HashSet;
|
|
|
|
|
|
|
|
use garage_util::error::*;
|
|
|
|
|
2021-11-09 12:24:04 +01:00
|
|
|
use garage_rpc::layout::*;
|
2021-10-19 16:16:10 +02:00
|
|
|
use garage_rpc::system::*;
|
|
|
|
use garage_rpc::*;
|
|
|
|
|
2022-01-03 13:58:05 +01:00
|
|
|
use garage_model::helper::error::Error as HelperError;
|
|
|
|
|
2021-10-19 16:16:10 +02:00
|
|
|
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,
|
2022-01-03 13:58:05 +01:00
|
|
|
) -> Result<(), HelperError> {
|
2021-10-19 16:16:10 +02:00
|
|
|
match cmd {
|
2022-01-03 13:58:05 +01:00
|
|
|
Command::Status => Ok(cmd_status(system_rpc_endpoint, rpc_host).await?),
|
2021-10-19 16:16:10 +02:00
|
|
|
Command::Node(NodeOperation::Connect(connect_opt)) => {
|
2022-01-03 13:58:05 +01:00
|
|
|
Ok(cmd_connect(system_rpc_endpoint, rpc_host, connect_opt).await?)
|
2021-10-19 16:16:10 +02:00
|
|
|
}
|
2021-11-09 12:24:04 +01:00
|
|
|
Command::Layout(layout_opt) => {
|
2022-01-03 13:58:05 +01:00
|
|
|
Ok(cli_layout_command_dispatch(layout_opt, system_rpc_endpoint, rpc_host).await?)
|
2021-10-19 16:16:10 +02:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
2021-12-16 13:17:09 +01:00
|
|
|
Command::Migrate(mo) => {
|
|
|
|
cmd_admin(admin_rpc_endpoint, rpc_host, AdminRpc::Migrate(mo)).await
|
|
|
|
}
|
2021-10-19 16:16:10 +02:00
|
|
|
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,
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn cmd_status(rpc_cli: &Endpoint<SystemRpc, ()>, rpc_host: NodeID) -> 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))),
|
|
|
|
};
|
2021-11-09 12:24:04 +01:00
|
|
|
let layout = fetch_layout(rpc_cli, rpc_host).await?;
|
2021-10-19 16:16:10 +02:00
|
|
|
|
|
|
|
println!("==== HEALTHY NODES ====");
|
2021-11-09 12:24:04 +01:00
|
|
|
let mut healthy_nodes = vec!["ID\tHostname\tAddress\tTags\tZone\tCapacity".to_string()];
|
2021-10-19 16:16:10 +02:00
|
|
|
for adv in status.iter().filter(|adv| adv.is_up) {
|
2021-11-09 12:24:04 +01:00
|
|
|
match layout.roles.get(&adv.id) {
|
|
|
|
Some(NodeRoleV(Some(cfg))) => {
|
|
|
|
healthy_nodes.push(format!(
|
|
|
|
"{id:?}\t{host}\t{addr}\t[{tags}]\t{zone}\t{capacity}",
|
|
|
|
id = adv.id,
|
|
|
|
host = adv.status.hostname,
|
|
|
|
addr = adv.addr,
|
|
|
|
tags = cfg.tags.join(","),
|
|
|
|
zone = cfg.zone,
|
|
|
|
capacity = cfg.capacity_string(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
let new_role = match layout.staging.get(&adv.id) {
|
|
|
|
Some(NodeRoleV(Some(_))) => "(pending)",
|
|
|
|
_ => "NO ROLE ASSIGNED",
|
|
|
|
};
|
|
|
|
healthy_nodes.push(format!(
|
|
|
|
"{id:?}\t{h}\t{addr}\t{new_role}",
|
|
|
|
id = adv.id,
|
|
|
|
h = adv.status.hostname,
|
|
|
|
addr = adv.addr,
|
|
|
|
new_role = new_role,
|
|
|
|
));
|
|
|
|
}
|
2021-10-19 16:16:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
format_table(healthy_nodes);
|
|
|
|
|
|
|
|
let status_keys = status.iter().map(|adv| adv.id).collect::<HashSet<_>>();
|
|
|
|
let failure_case_1 = status.iter().any(|adv| !adv.is_up);
|
2021-11-09 12:24:04 +01:00
|
|
|
let failure_case_2 = layout
|
|
|
|
.roles
|
|
|
|
.items()
|
2021-10-19 16:16:10 +02:00
|
|
|
.iter()
|
2021-11-09 12:24:04 +01:00
|
|
|
.filter(|(_, _, v)| v.0.is_some())
|
|
|
|
.any(|(id, _, _)| !status_keys.contains(id));
|
2021-10-19 16:16:10 +02:00
|
|
|
if failure_case_1 || failure_case_2 {
|
|
|
|
println!("\n==== FAILED NODES ====");
|
|
|
|
let mut failed_nodes =
|
2021-11-09 12:24:04 +01:00
|
|
|
vec!["ID\tHostname\tAddress\tTags\tZone\tCapacity\tLast seen".to_string()];
|
2021-10-19 16:16:10 +02:00
|
|
|
for adv in status.iter().filter(|adv| !adv.is_up) {
|
2021-11-09 12:24:04 +01:00
|
|
|
if let Some(NodeRoleV(Some(cfg))) = layout.roles.get(&adv.id) {
|
2021-10-19 16:16:10 +02:00
|
|
|
failed_nodes.push(format!(
|
2021-11-09 12:24:04 +01:00
|
|
|
"{id:?}\t{host}\t{addr}\t[{tags}]\t{zone}\t{capacity}\t{last_seen}",
|
2021-10-19 16:16:10 +02:00
|
|
|
id = adv.id,
|
|
|
|
host = adv.status.hostname,
|
|
|
|
addr = adv.addr,
|
2021-11-09 12:24:04 +01:00
|
|
|
tags = cfg.tags.join(","),
|
2021-10-19 16:16:10 +02:00
|
|
|
zone = cfg.zone,
|
|
|
|
capacity = cfg.capacity_string(),
|
|
|
|
last_seen = adv
|
|
|
|
.last_seen_secs_ago
|
|
|
|
.map(|s| format!("{}s ago", s))
|
|
|
|
.unwrap_or_else(|| "never seen".into()),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
2021-11-09 12:24:04 +01:00
|
|
|
for (id, _, role_v) in layout.roles.items().iter() {
|
|
|
|
if let NodeRoleV(Some(cfg)) = role_v {
|
|
|
|
if !status_keys.contains(id) {
|
|
|
|
failed_nodes.push(format!(
|
|
|
|
"{id:?}\t??\t??\t[{tags}]\t{zone}\t{capacity}\tnever seen",
|
|
|
|
id = id,
|
|
|
|
tags = cfg.tags.join(","),
|
|
|
|
zone = cfg.zone,
|
|
|
|
capacity = cfg.capacity_string(),
|
|
|
|
));
|
|
|
|
}
|
2021-10-19 16:16:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
format_table(failed_nodes);
|
|
|
|
}
|
|
|
|
|
2021-11-09 12:24:04 +01:00
|
|
|
if print_staging_role_changes(&layout) {
|
|
|
|
println!();
|
|
|
|
println!("Please use `garage layout show` to check the proposed new layout and apply it.");
|
|
|
|
println!();
|
|
|
|
}
|
|
|
|
|
2021-10-19 16:16:10 +02:00
|
|
|
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(())
|
|
|
|
}
|
2022-01-03 13:58:05 +01:00
|
|
|
m => Err(Error::unexpected_rpc_message(m)),
|
2021-10-19 16:16:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn cmd_admin(
|
|
|
|
rpc_cli: &Endpoint<AdminRpc, ()>,
|
|
|
|
rpc_host: NodeID,
|
|
|
|
args: AdminRpc,
|
2022-01-03 13:58:05 +01:00
|
|
|
) -> Result<(), HelperError> {
|
2021-10-19 16:16:10 +02:00
|
|
|
match rpc_cli.call(&rpc_host, &args, PRIO_NORMAL).await?? {
|
|
|
|
AdminRpc::Ok(msg) => {
|
|
|
|
println!("{}", msg);
|
|
|
|
}
|
|
|
|
AdminRpc::BucketList(bl) => {
|
|
|
|
println!("List of buckets:");
|
2021-12-15 18:36:15 +01:00
|
|
|
let mut table = vec![];
|
2021-12-14 13:55:11 +01:00
|
|
|
for alias in bl {
|
|
|
|
if let Some(p) = alias.state.get().as_option() {
|
2021-12-17 11:53:13 +01:00
|
|
|
table.push(format!("\t{}\t{:?}", alias.name(), p.bucket_id));
|
2021-12-14 13:55:11 +01:00
|
|
|
}
|
2021-10-19 16:16:10 +02:00
|
|
|
}
|
2021-12-15 18:36:15 +01:00
|
|
|
format_table(table);
|
|
|
|
println!("Buckets that don't have a global alias (i.e. that only exist in the namespace of an access key) are not shown.");
|
2021-10-19 16:16:10 +02:00
|
|
|
}
|
2021-12-16 16:17:51 +01:00
|
|
|
AdminRpc::BucketInfo(bucket, rk) => {
|
|
|
|
print_bucket_info(&bucket, &rk);
|
2021-10-19 16:16:10 +02:00
|
|
|
}
|
|
|
|
AdminRpc::KeyList(kl) => {
|
|
|
|
println!("List of keys:");
|
|
|
|
for key in kl {
|
|
|
|
println!("{}\t{}", key.0, key.1);
|
|
|
|
}
|
|
|
|
}
|
2021-12-16 16:17:51 +01:00
|
|
|
AdminRpc::KeyInfo(key, rb) => {
|
|
|
|
print_key_info(&key, &rb);
|
2021-10-19 16:16:10 +02:00
|
|
|
}
|
|
|
|
r => {
|
|
|
|
error!("Unexpected response: {:?}", r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|