Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 196 additions & 7 deletions dev-tools/omdb/src/bin/omdb/nexus/update_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
//! omdb commands related to update status

use anyhow::Context;
use nexus_types::internal_api::views::{SpStatus, ZoneStatus};
use gateway_types::rot::RotSlot;
use nexus_types::internal_api::views::{
HostPhase1Status, HostPhase2Status, RotBootloaderStatus, RotStatus,
SpStatus, ZoneStatus,
};
use omicron_common::disk::M2Slot;
use omicron_uuid_kinds::SledUuid;
use tabled::Tabled;

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

print_zones(status.zones.into_iter());
print_sps(status.sps.into_iter());
print_rot_bootloaders(
status
.mgs_driven
.iter()
.map(|s| (s.baseboard_description.clone(), &s.rot_bootloader)),
);
println!();
print_rots(
status
.mgs_driven
.iter()
.map(|s| (s.baseboard_description.clone(), &s.rot)),
);
println!();
print_sps(
status
.mgs_driven
.iter()
.map(|s| (s.baseboard_description.clone(), &s.sp)),
);
println!();
print_host_phase_1s(
status
.mgs_driven
.iter()
.map(|s| (s.baseboard_description.clone(), &s.host_os_phase_1)),
);
println!();
print_host_phase_2s(
status.sleds.iter().map(|s| (s.sled_id, &s.host_phase_2)),
);
println!();
print_zones(
status
.sleds
.iter()
.map(|s| (s.sled_id, s.zones.iter().cloned().collect())),
);

Ok(())
}
Expand Down Expand Up @@ -59,22 +100,85 @@ fn print_zones(zones: impl Iterator<Item = (SledUuid, Vec<ZoneStatus>)>) {
println!("{}", table);
}

fn print_sps(sps: impl Iterator<Item = (String, SpStatus)>) {
fn print_rot_bootloaders<'a>(
bootloaders: impl Iterator<Item = (String, &'a RotBootloaderStatus)>,
) {
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct BootloaderRow {
baseboard_id: String,
stage0_version: String,
stage0_next_version: String,
}

let mut rows = Vec::new();
for (baseboard_id, status) in bootloaders {
let RotBootloaderStatus { stage0_version, stage0_next_version } =
status;
rows.push(BootloaderRow {
baseboard_id,
stage0_version: stage0_version.to_string(),
stage0_next_version: stage0_next_version.to_string(),
});
}

let table = tabled::Table::new(rows)
.with(tabled::settings::Style::empty())
.with(tabled::settings::Padding::new(0, 1, 0, 0))
.to_string();

println!("Installed RoT Bootloader Software");
println!("{}", table);
}

fn print_rots<'a>(rots: impl Iterator<Item = (String, &'a RotStatus)>) {
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct RotRow {
baseboard_id: String,
slot_a_version: String,
slot_b_version: String,
}

let mut rows = Vec::new();
for (baseboard_id, status) in rots {
let RotStatus { active_slot, slot_a_version, slot_b_version } = status;
let (slot_a_suffix, slot_b_suffix) = match active_slot {
Some(RotSlot::A) => (" (active)", ""),
Some(RotSlot::B) => ("", " (active)"),
// This is not expected! Be louder.
None => ("", " (ACTIVE SLOT UNKNOWN)"),
};
rows.push(RotRow {
baseboard_id,
slot_a_version: format!("{slot_a_version}{slot_a_suffix}"),
slot_b_version: format!("{slot_b_version}{slot_b_suffix}"),
});
}

let table = tabled::Table::new(rows)
.with(tabled::settings::Style::empty())
.with(tabled::settings::Padding::new(0, 1, 0, 0))
.to_string();

println!("Installed RoT Software");
println!("{}", table);
}

fn print_sps<'a>(sps: impl Iterator<Item = (String, &'a SpStatus)>) {
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct SpRow {
baseboard_id: String,
sled_id: String,
slot0_version: String,
slot1_version: String,
}

let mut rows = Vec::new();
for (baseboard_id, status) in sps {
let SpStatus { sled_id, slot0_version, slot1_version } = status;
let SpStatus { slot0_version, slot1_version } = status;
rows.push(SpRow {
baseboard_id,
sled_id: sled_id.map_or("".to_string(), |id| id.to_string()),
slot0_version: slot0_version.to_string(),
slot1_version: slot1_version.to_string(),
});
Expand All @@ -88,3 +192,88 @@ fn print_sps(sps: impl Iterator<Item = (String, SpStatus)>) {
println!("Installed SP Software");
println!("{}", table);
}

fn print_host_phase_1s<'a>(
phase_1s: impl Iterator<Item = (String, &'a HostPhase1Status)>,
) {
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct HostPhase1Row {
baseboard_id: String,
sled_id: String,
slot_a_version: String,
slot_b_version: String,
}

let mut rows = Vec::new();
for (baseboard_id, status) in phase_1s {
match status {
HostPhase1Status::NotASled => continue,
HostPhase1Status::Sled {
sled_id,
active_slot,
slot_a_version,
slot_b_version,
} => {
let (slot_a_suffix, slot_b_suffix) = match active_slot {
Some(M2Slot::A) => (" (active)", ""),
Some(M2Slot::B) => ("", " (active)"),
// This is not expected! Be louder.
None => ("", " (ACTIVE SLOT UNKNOWN)"),
};
rows.push(HostPhase1Row {
baseboard_id,
sled_id: sled_id
.map_or("".to_string(), |id| id.to_string()),
slot_a_version: format!("{slot_a_version}{slot_a_suffix}"),
slot_b_version: format!("{slot_b_version}{slot_b_suffix}"),
});
}
}
}

let table = tabled::Table::new(rows)
.with(tabled::settings::Style::empty())
.with(tabled::settings::Padding::new(0, 1, 0, 0))
.to_string();

println!("Installed Host Phase 1 Software");
println!("{}", table);
}

fn print_host_phase_2s<'a>(
sleds: impl Iterator<Item = (SledUuid, &'a HostPhase2Status)>,
) {
#[derive(Tabled)]
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
struct HostPhase2Row {
sled_id: String,
slot_a_version: String,
slot_b_version: String,
}

let mut rows = Vec::new();
for (sled_id, status) in sleds {
let HostPhase2Status { boot_disk, slot_a_version, slot_b_version } =
status;
let (slot_a_suffix, slot_b_suffix) = match boot_disk {
Ok(M2Slot::A) => (" (boot disk)", "".to_string()),
Ok(M2Slot::B) => ("", " (boot disk)".to_string()),
// This is not expected! Be louder.
Err(err) => ("", format!(" (BOOT DISK UNKNOWN: {err})")),
};
rows.push(HostPhase2Row {
sled_id: sled_id.to_string(),
slot_a_version: format!("{slot_a_version}{slot_a_suffix}"),
slot_b_version: format!("{slot_b_version}{slot_b_suffix}"),
});
}

let table = tabled::Table::new(rows)
.with(tabled::settings::Style::empty())
.with(tabled::settings::Padding::new(0, 1, 0, 0))
.to_string();

println!("Installed Host Phase 2 Software");
println!("{}", table);
}
Loading
Loading