1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
use format_table::format_table;
use garage_util::error::*;
use garage_api::admin::api::*;
use crate::cli::structs::*;
use crate::cli_v2::layout::*;
use crate::cli_v2::*;
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".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}",
id = adv.id,
host = host,
addr = addr,
tags = cfg.tags.join(","),
zone = cfg.zone,
capacity = capacity_string(cfg.capacity),
data_avail = data_avail,
));
} 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}",
id = adv.id,
h = host,
addr = addr,
status = status,
));
}
}
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()
)))
}
}
}
|