Skip to content

Commit 1ad3d57

Browse files
authored
[omdb] Add RoT bootloader, RoT, and host OS information to omdb nexus update-status (#8901)
I'll give this a spin on a racklette and report back before merging, but I think this is straightforward enough to go ahead and review without that. Closes #8883.
1 parent 93c7540 commit 1ad3d57

File tree

4 files changed

+789
-112
lines changed

4 files changed

+789
-112
lines changed

dev-tools/omdb/src/bin/omdb/nexus/update_status.rs

Lines changed: 196 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
//! omdb commands related to update status
66
77
use anyhow::Context;
8-
use nexus_types::internal_api::views::{SpStatus, ZoneStatus};
8+
use gateway_types::rot::RotSlot;
9+
use nexus_types::internal_api::views::{
10+
HostPhase1Status, HostPhase2Status, RotBootloaderStatus, RotStatus,
11+
SpStatus, ZoneStatus,
12+
};
13+
use omicron_common::disk::M2Slot;
914
use omicron_uuid_kinds::SledUuid;
1015
use tabled::Tabled;
1116

@@ -19,8 +24,44 @@ pub async fn cmd_nexus_update_status(
1924
.context("retrieving update status")?
2025
.into_inner();
2126

22-
print_zones(status.zones.into_iter());
23-
print_sps(status.sps.into_iter());
27+
print_rot_bootloaders(
28+
status
29+
.mgs_driven
30+
.iter()
31+
.map(|s| (s.baseboard_description.clone(), &s.rot_bootloader)),
32+
);
33+
println!();
34+
print_rots(
35+
status
36+
.mgs_driven
37+
.iter()
38+
.map(|s| (s.baseboard_description.clone(), &s.rot)),
39+
);
40+
println!();
41+
print_sps(
42+
status
43+
.mgs_driven
44+
.iter()
45+
.map(|s| (s.baseboard_description.clone(), &s.sp)),
46+
);
47+
println!();
48+
print_host_phase_1s(
49+
status
50+
.mgs_driven
51+
.iter()
52+
.map(|s| (s.baseboard_description.clone(), &s.host_os_phase_1)),
53+
);
54+
println!();
55+
print_host_phase_2s(
56+
status.sleds.iter().map(|s| (s.sled_id, &s.host_phase_2)),
57+
);
58+
println!();
59+
print_zones(
60+
status
61+
.sleds
62+
.iter()
63+
.map(|s| (s.sled_id, s.zones.iter().cloned().collect())),
64+
);
2465

2566
Ok(())
2667
}
@@ -59,22 +100,85 @@ fn print_zones(zones: impl Iterator<Item = (SledUuid, Vec<ZoneStatus>)>) {
59100
println!("{}", table);
60101
}
61102

62-
fn print_sps(sps: impl Iterator<Item = (String, SpStatus)>) {
103+
fn print_rot_bootloaders<'a>(
104+
bootloaders: impl Iterator<Item = (String, &'a RotBootloaderStatus)>,
105+
) {
106+
#[derive(Tabled)]
107+
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
108+
struct BootloaderRow {
109+
baseboard_id: String,
110+
stage0_version: String,
111+
stage0_next_version: String,
112+
}
113+
114+
let mut rows = Vec::new();
115+
for (baseboard_id, status) in bootloaders {
116+
let RotBootloaderStatus { stage0_version, stage0_next_version } =
117+
status;
118+
rows.push(BootloaderRow {
119+
baseboard_id,
120+
stage0_version: stage0_version.to_string(),
121+
stage0_next_version: stage0_next_version.to_string(),
122+
});
123+
}
124+
125+
let table = tabled::Table::new(rows)
126+
.with(tabled::settings::Style::empty())
127+
.with(tabled::settings::Padding::new(0, 1, 0, 0))
128+
.to_string();
129+
130+
println!("Installed RoT Bootloader Software");
131+
println!("{}", table);
132+
}
133+
134+
fn print_rots<'a>(rots: impl Iterator<Item = (String, &'a RotStatus)>) {
135+
#[derive(Tabled)]
136+
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
137+
struct RotRow {
138+
baseboard_id: String,
139+
slot_a_version: String,
140+
slot_b_version: String,
141+
}
142+
143+
let mut rows = Vec::new();
144+
for (baseboard_id, status) in rots {
145+
let RotStatus { active_slot, slot_a_version, slot_b_version } = status;
146+
let (slot_a_suffix, slot_b_suffix) = match active_slot {
147+
Some(RotSlot::A) => (" (active)", ""),
148+
Some(RotSlot::B) => ("", " (active)"),
149+
// This is not expected! Be louder.
150+
None => ("", " (ACTIVE SLOT UNKNOWN)"),
151+
};
152+
rows.push(RotRow {
153+
baseboard_id,
154+
slot_a_version: format!("{slot_a_version}{slot_a_suffix}"),
155+
slot_b_version: format!("{slot_b_version}{slot_b_suffix}"),
156+
});
157+
}
158+
159+
let table = tabled::Table::new(rows)
160+
.with(tabled::settings::Style::empty())
161+
.with(tabled::settings::Padding::new(0, 1, 0, 0))
162+
.to_string();
163+
164+
println!("Installed RoT Software");
165+
println!("{}", table);
166+
}
167+
168+
fn print_sps<'a>(sps: impl Iterator<Item = (String, &'a SpStatus)>) {
63169
#[derive(Tabled)]
64170
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
65171
struct SpRow {
66172
baseboard_id: String,
67-
sled_id: String,
68173
slot0_version: String,
69174
slot1_version: String,
70175
}
71176

72177
let mut rows = Vec::new();
73178
for (baseboard_id, status) in sps {
74-
let SpStatus { sled_id, slot0_version, slot1_version } = status;
179+
let SpStatus { slot0_version, slot1_version } = status;
75180
rows.push(SpRow {
76181
baseboard_id,
77-
sled_id: sled_id.map_or("".to_string(), |id| id.to_string()),
78182
slot0_version: slot0_version.to_string(),
79183
slot1_version: slot1_version.to_string(),
80184
});
@@ -88,3 +192,88 @@ fn print_sps(sps: impl Iterator<Item = (String, SpStatus)>) {
88192
println!("Installed SP Software");
89193
println!("{}", table);
90194
}
195+
196+
fn print_host_phase_1s<'a>(
197+
phase_1s: impl Iterator<Item = (String, &'a HostPhase1Status)>,
198+
) {
199+
#[derive(Tabled)]
200+
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
201+
struct HostPhase1Row {
202+
baseboard_id: String,
203+
sled_id: String,
204+
slot_a_version: String,
205+
slot_b_version: String,
206+
}
207+
208+
let mut rows = Vec::new();
209+
for (baseboard_id, status) in phase_1s {
210+
match status {
211+
HostPhase1Status::NotASled => continue,
212+
HostPhase1Status::Sled {
213+
sled_id,
214+
active_slot,
215+
slot_a_version,
216+
slot_b_version,
217+
} => {
218+
let (slot_a_suffix, slot_b_suffix) = match active_slot {
219+
Some(M2Slot::A) => (" (active)", ""),
220+
Some(M2Slot::B) => ("", " (active)"),
221+
// This is not expected! Be louder.
222+
None => ("", " (ACTIVE SLOT UNKNOWN)"),
223+
};
224+
rows.push(HostPhase1Row {
225+
baseboard_id,
226+
sled_id: sled_id
227+
.map_or("".to_string(), |id| id.to_string()),
228+
slot_a_version: format!("{slot_a_version}{slot_a_suffix}"),
229+
slot_b_version: format!("{slot_b_version}{slot_b_suffix}"),
230+
});
231+
}
232+
}
233+
}
234+
235+
let table = tabled::Table::new(rows)
236+
.with(tabled::settings::Style::empty())
237+
.with(tabled::settings::Padding::new(0, 1, 0, 0))
238+
.to_string();
239+
240+
println!("Installed Host Phase 1 Software");
241+
println!("{}", table);
242+
}
243+
244+
fn print_host_phase_2s<'a>(
245+
sleds: impl Iterator<Item = (SledUuid, &'a HostPhase2Status)>,
246+
) {
247+
#[derive(Tabled)]
248+
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
249+
struct HostPhase2Row {
250+
sled_id: String,
251+
slot_a_version: String,
252+
slot_b_version: String,
253+
}
254+
255+
let mut rows = Vec::new();
256+
for (sled_id, status) in sleds {
257+
let HostPhase2Status { boot_disk, slot_a_version, slot_b_version } =
258+
status;
259+
let (slot_a_suffix, slot_b_suffix) = match boot_disk {
260+
Ok(M2Slot::A) => (" (boot disk)", "".to_string()),
261+
Ok(M2Slot::B) => ("", " (boot disk)".to_string()),
262+
// This is not expected! Be louder.
263+
Err(err) => ("", format!(" (BOOT DISK UNKNOWN: {err})")),
264+
};
265+
rows.push(HostPhase2Row {
266+
sled_id: sled_id.to_string(),
267+
slot_a_version: format!("{slot_a_version}{slot_a_suffix}"),
268+
slot_b_version: format!("{slot_b_version}{slot_b_suffix}"),
269+
});
270+
}
271+
272+
let table = tabled::Table::new(rows)
273+
.with(tabled::settings::Style::empty())
274+
.with(tabled::settings::Padding::new(0, 1, 0, 0))
275+
.to_string();
276+
277+
println!("Installed Host Phase 2 Software");
278+
println!("{}", table);
279+
}

0 commit comments

Comments
 (0)