Skip to content

Commit 8baffac

Browse files
authored
Make omdb nexus update-status show a summary by default (#9205)
The original output is still available via `omdb nexus update-status --details`.
1 parent bea91a2 commit 8baffac

File tree

5 files changed

+249
-7
lines changed

5 files changed

+249
-7
lines changed

clients/nexus-lockstep-client/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ progenitor::generate_api!(
6363
ReconfiguratorConfigParam = nexus_types::deployment::ReconfiguratorConfigParam,
6464
ReconfiguratorConfigView = nexus_types::deployment::ReconfiguratorConfigView,
6565
RecoverySiloConfig = nexus_sled_agent_shared::recovery_silo::RecoverySiloConfig,
66+
SledAgentUpdateStatus = nexus_types::internal_api::views::SledAgentUpdateStatus,
6667
UpdateStatus = nexus_types::internal_api::views::UpdateStatus,
6768
ZoneStatus = nexus_types::internal_api::views::ZoneStatus,
6869
ZpoolName = omicron_common::zpool_name::ZpoolName,

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ enum NexusCommands {
158158
#[command(visible_alias = "sb")]
159159
SupportBundles(SupportBundleArgs),
160160
/// show running artifact versions
161-
UpdateStatus,
161+
UpdateStatus(UpdateStatusArgs),
162162
}
163163

164164
#[derive(Debug, Args)]
@@ -612,6 +612,13 @@ struct SupportBundleInspectArgs {
612612
path: Option<Utf8PathBuf>,
613613
}
614614

615+
#[derive(Debug, Args)]
616+
struct UpdateStatusArgs {
617+
/// Show full details of all updateable components.
618+
#[arg(long)]
619+
details: bool,
620+
}
621+
615622
impl NexusArgs {
616623
/// Run a `omdb nexus` subcommand.
617624
pub(crate) async fn run_cmd(
@@ -845,8 +852,8 @@ impl NexusArgs {
845852
NexusCommands::SupportBundles(SupportBundleArgs {
846853
command: SupportBundleCommands::Inspect(args),
847854
}) => cmd_nexus_support_bundles_inspect(&client, args).await,
848-
NexusCommands::UpdateStatus => {
849-
cmd_nexus_update_status(&client).await
855+
NexusCommands::UpdateStatus(args) => {
856+
cmd_nexus_update_status(&client, args).await
850857
}
851858
}
852859
}

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

Lines changed: 168 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,192 @@
44

55
//! omdb commands related to update status
66
7+
use std::{
8+
borrow::Cow,
9+
collections::{BTreeMap, BTreeSet},
10+
iter,
11+
};
12+
13+
use super::UpdateStatusArgs;
714
use anyhow::Context;
815
use gateway_types::rot::RotSlot;
916
use nexus_types::internal_api::views::{
10-
HostPhase1Status, HostPhase2Status, RotBootloaderStatus, RotStatus,
11-
SpStatus, ZoneStatus,
17+
HostPhase1Status, HostPhase2Status, MgsDrivenUpdateStatus,
18+
RotBootloaderStatus, RotStatus, SledAgentUpdateStatus, SpStatus,
19+
TufRepoVersion, UpdateStatus, ZoneStatus,
1220
};
1321
use omicron_common::disk::M2Slot;
1422
use omicron_uuid_kinds::SledUuid;
23+
use strum::IntoEnumIterator;
1524
use tabled::Tabled;
1625

1726
/// Runs `omdb nexus update-status`
1827
pub async fn cmd_nexus_update_status(
1928
client: &nexus_lockstep_client::Client,
29+
args: &UpdateStatusArgs,
2030
) -> Result<(), anyhow::Error> {
31+
let UpdateStatusArgs { details } = args;
32+
2133
let status = client
2234
.update_status()
2335
.await
2436
.context("retrieving update status")?
2537
.into_inner();
2638

39+
if *details {
40+
print_status_details(status);
41+
} else {
42+
print_status_summary(status);
43+
}
44+
45+
Ok(())
46+
}
47+
48+
#[derive(
49+
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, strum::EnumIter,
50+
)]
51+
enum Component {
52+
RotBootloader,
53+
Rot,
54+
Sp,
55+
HostPhase1,
56+
HostPhase2,
57+
Zone,
58+
}
59+
60+
#[derive(Debug, Default)]
61+
struct UpdateStatusSummaryBuilder {
62+
all_versions: BTreeSet<Cow<'static, str>>,
63+
counts: BTreeMap<Component, BTreeMap<Cow<'static, str>, usize>>,
64+
}
65+
66+
impl UpdateStatusSummaryBuilder {
67+
fn insert(&mut self, component: Component, version: TufRepoVersion) {
68+
let version = match version {
69+
TufRepoVersion::Unknown => Cow::Borrowed("unknown"),
70+
TufRepoVersion::InstallDataset => Cow::Borrowed("install-dataset"),
71+
TufRepoVersion::Error(_) => Cow::Borrowed("error"),
72+
TufRepoVersion::Version(v) => Cow::Owned(v.to_string()),
73+
};
74+
75+
self.all_versions.insert(version.clone());
76+
*self
77+
.counts
78+
.entry(component)
79+
.or_default()
80+
.entry(version)
81+
.or_default() += 1;
82+
}
83+
}
84+
85+
fn print_status_summary(status: UpdateStatus) {
86+
let mut builder = UpdateStatusSummaryBuilder::default();
87+
88+
let UpdateStatus { mgs_driven, sleds } = status;
89+
90+
for sled in sleds {
91+
let SledAgentUpdateStatus { host_phase_2, sled_id: _, zones } = sled;
92+
93+
// Check the boot disk to know which slot to count.
94+
builder.insert(
95+
Component::HostPhase2,
96+
match host_phase_2.boot_disk {
97+
Ok(M2Slot::A) => host_phase_2.slot_a_version,
98+
Ok(M2Slot::B) => host_phase_2.slot_b_version,
99+
Err(err) => TufRepoVersion::Error(err),
100+
},
101+
);
102+
for z in zones {
103+
builder.insert(Component::Zone, z.version);
104+
}
105+
}
106+
107+
for mgs in mgs_driven {
108+
let MgsDrivenUpdateStatus {
109+
baseboard_description: _,
110+
rot_bootloader,
111+
rot,
112+
sp,
113+
host_os_phase_1,
114+
} = mgs;
115+
116+
// Bootloader and SP always identify the active slot statically.
117+
builder.insert(Component::RotBootloader, rot_bootloader.stage0_version);
118+
builder.insert(Component::Sp, sp.slot0_version);
119+
120+
// RoT and host phase 1 need to check the active slot.
121+
builder.insert(
122+
Component::Rot,
123+
match rot.active_slot {
124+
Some(RotSlot::A) => rot.slot_a_version,
125+
Some(RotSlot::B) => rot.slot_b_version,
126+
None => TufRepoVersion::Unknown,
127+
},
128+
);
129+
130+
match host_os_phase_1 {
131+
HostPhase1Status::NotASled => (),
132+
HostPhase1Status::Sled {
133+
active_slot,
134+
slot_a_version,
135+
slot_b_version,
136+
..
137+
} => {
138+
builder.insert(
139+
Component::HostPhase1,
140+
match active_slot {
141+
Some(M2Slot::A) => slot_a_version,
142+
Some(M2Slot::B) => slot_b_version,
143+
None => TufRepoVersion::Unknown,
144+
},
145+
);
146+
}
147+
}
148+
}
149+
150+
let rows = Component::iter().map(|c| {
151+
let name = match c {
152+
Component::RotBootloader => "RoT bootloader",
153+
Component::Rot => "RoT",
154+
Component::Sp => "SP",
155+
Component::HostPhase1 => "Host OS (phase 1)",
156+
Component::HostPhase2 => "Host OS (phase 2)",
157+
Component::Zone => "Zone",
158+
};
159+
let builder = &builder;
160+
iter::once(Cow::Borrowed(name)).chain(builder.all_versions.iter().map(
161+
move |v| {
162+
let count = builder
163+
.counts
164+
.get(&c)
165+
.and_then(|by_version| by_version.get(v))
166+
.unwrap_or(&0);
167+
Cow::Owned(count.to_string())
168+
},
169+
))
170+
});
171+
let columns =
172+
iter::once(Cow::Borrowed("")).chain(builder.all_versions.clone());
173+
174+
let mut table = tabled::builder::Builder::new();
175+
table.push_record(columns);
176+
for row in rows {
177+
table.push_record(row);
178+
}
179+
let table = table
180+
.build()
181+
.with(tabled::settings::Style::psql())
182+
.with(tabled::settings::Padding::new(0, 1, 0, 0))
183+
.to_string();
184+
185+
println!("Count of each component type by system version:");
186+
println!();
187+
println!("{table}");
188+
println!();
189+
println!("To see each individual component, rerun with `--details`.");
190+
}
191+
192+
fn print_status_details(status: UpdateStatus) {
27193
print_rot_bootloaders(
28194
status
29195
.mgs_driven
@@ -62,8 +228,6 @@ pub async fn cmd_nexus_update_status(
62228
.iter()
63229
.map(|s| (s.sled_id, s.zones.iter().cloned().collect())),
64230
);
65-
66-
Ok(())
67231
}
68232

69233
fn print_zones(zones: impl Iterator<Item = (SledUuid, Vec<ZoneStatus>)>) {

dev-tools/omdb/tests/successes.out

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,6 +1759,74 @@ Reconfigurator config:
17591759
stderr:
17601760
note: using Nexus URL http://127.0.0.1:REDACTED_PORT/
17611761
=============================================
1762+
EXECUTING COMMAND: omdb ["nexus", "update-status"]
1763+
termination: Exited(0)
1764+
---------------------------------------------
1765+
stdout:
1766+
Count of each component type by system version:
1767+
1768+
|install-dataset |unknown
1769+
------------------+----------------+--------
1770+
RoT bootloader |0 |4
1771+
RoT |0 |4
1772+
SP |0 |4
1773+
Host OS (phase 1) |0 |2
1774+
Host OS (phase 2) |0 |2
1775+
Zone |7 |0
1776+
1777+
To see each individual component, rerun with `--details`.
1778+
---------------------------------------------
1779+
stderr:
1780+
note: using Nexus URL http://127.0.0.1:REDACTED_PORT/
1781+
=============================================
1782+
EXECUTING COMMAND: omdb ["nexus", "update-status", "--details"]
1783+
termination: Exited(0)
1784+
---------------------------------------------
1785+
stdout:
1786+
Installed RoT Bootloader Software
1787+
BASEBOARD_ID STAGE0_VERSION STAGE0_NEXT_VERSION
1788+
FAKE_SIM_SIDECAR:SimSidecar0 unknown unknown
1789+
FAKE_SIM_SIDECAR:SimSidecar1 unknown unknown
1790+
i86pc:SimGimlet00 unknown unknown
1791+
i86pc:SimGimlet01 unknown unknown
1792+
1793+
Installed RoT Software
1794+
BASEBOARD_ID SLOT_A_VERSION SLOT_B_VERSION
1795+
FAKE_SIM_SIDECAR:SimSidecar0 unknown (active) unknown
1796+
FAKE_SIM_SIDECAR:SimSidecar1 unknown (active) unknown
1797+
i86pc:SimGimlet00 unknown (active) unknown
1798+
i86pc:SimGimlet01 unknown (active) unknown
1799+
1800+
Installed SP Software
1801+
BASEBOARD_ID SLOT0_VERSION SLOT1_VERSION
1802+
FAKE_SIM_SIDECAR:SimSidecar0 unknown unknown
1803+
FAKE_SIM_SIDECAR:SimSidecar1 unknown unknown
1804+
i86pc:SimGimlet00 unknown unknown
1805+
i86pc:SimGimlet01 unknown unknown
1806+
1807+
Installed Host Phase 1 Software
1808+
BASEBOARD_ID SLED_ID SLOT_A_VERSION SLOT_B_VERSION
1809+
i86pc:SimGimlet00 unknown (active) unknown
1810+
i86pc:SimGimlet01 unknown (active) unknown
1811+
1812+
Installed Host Phase 2 Software
1813+
SLED_ID SLOT_A_VERSION SLOT_B_VERSION
1814+
..........<REDACTED_UUID>........... unknown (boot disk) unknown
1815+
..........<REDACTED_UUID>........... unknown (boot disk) unknown
1816+
1817+
Running Zones
1818+
SLED_ID ZONE_TYPE ZONE_ID VERSION
1819+
..........<REDACTED_UUID>........... ntp ..........<REDACTED_UUID>........... install dataset
1820+
..........<REDACTED_UUID>........... clickhouse ..........<REDACTED_UUID>........... install dataset
1821+
..........<REDACTED_UUID>........... cockroach ..........<REDACTED_UUID>........... install dataset
1822+
..........<REDACTED_UUID>........... crucible-pantry ..........<REDACTED_UUID>........... install dataset
1823+
..........<REDACTED_UUID>........... external-dns ..........<REDACTED_UUID>........... install dataset
1824+
..........<REDACTED_UUID>........... internal-dns ..........<REDACTED_UUID>........... install dataset
1825+
..........<REDACTED_UUID>........... nexus ..........<REDACTED_UUID>........... install dataset
1826+
---------------------------------------------
1827+
stderr:
1828+
note: using Nexus URL http://127.0.0.1:REDACTED_PORT/
1829+
=============================================
17621830
EXECUTING COMMAND: omdb ["-w", "nexus", "reconfigurator-config", "set", "--planner-enabled", "true"]
17631831
termination: Exited(0)
17641832
---------------------------------------------

dev-tools/omdb/tests/test_all_output.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,8 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) {
263263
&["nexus", "blueprints", "diff", &initial_blueprint_id],
264264
// reconfigurator config: show and set
265265
&["nexus", "reconfigurator-config", "show", "current"],
266+
&["nexus", "update-status"],
267+
&["nexus", "update-status", "--details"],
266268
// NOTE: Enabling the planner here _may_ cause Nexus to start creating
267269
// new blueprints; any commands whose output is only stable if the set
268270
// of blueprints is stable must come before this command to avoid being

0 commit comments

Comments
 (0)