Skip to content

Commit 17e4c0e

Browse files
authored
Add sp version info to omdb nexus update-status (#8517)
Fixes #8485
1 parent 94dda3c commit 17e4c0e

File tree

6 files changed

+304
-142
lines changed

6 files changed

+304
-142
lines changed

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

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! omdb commands that query or update specific Nexus instances
66
77
mod chicken_switches;
8+
mod update_status;
89

910
use crate::Omdb;
1011
use crate::check_allow_destructive::DestructiveOperationToken;
@@ -101,6 +102,7 @@ use update_engine::display::LineDisplayStyles;
101102
use update_engine::display::ProgressRatioDisplay;
102103
use update_engine::events::EventReport;
103104
use update_engine::events::StepOutcome;
105+
use update_status::cmd_nexus_update_status;
104106
use uuid::Uuid;
105107

106108
/// Arguments to the "omdb nexus" subcommand
@@ -4226,47 +4228,3 @@ async fn cmd_nexus_support_bundles_inspect(
42264228

42274229
support_bundle_viewer::run_dashboard(accessor).await
42284230
}
4229-
4230-
/// Runs `omdb nexus upgrade-status`
4231-
async fn cmd_nexus_update_status(
4232-
client: &nexus_client::Client,
4233-
) -> Result<(), anyhow::Error> {
4234-
let status = client
4235-
.update_status()
4236-
.await
4237-
.context("retrieving update status")?
4238-
.into_inner();
4239-
4240-
#[derive(Tabled)]
4241-
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
4242-
struct ZoneRow {
4243-
sled_id: String,
4244-
zone_type: String,
4245-
zone_id: String,
4246-
version: String,
4247-
}
4248-
4249-
let mut rows = Vec::new();
4250-
for (sled_id, mut statuses) in status.zones.into_iter() {
4251-
statuses.sort_unstable_by_key(|s| {
4252-
(s.zone_type.kind(), s.zone_id, s.version.clone())
4253-
});
4254-
for status in statuses {
4255-
rows.push(ZoneRow {
4256-
sled_id: sled_id.to_string(),
4257-
zone_type: status.zone_type.kind().name_prefix().into(),
4258-
zone_id: status.zone_id.to_string(),
4259-
version: status.version.to_string(),
4260-
});
4261-
}
4262-
}
4263-
4264-
let table = tabled::Table::new(rows)
4265-
.with(tabled::settings::Style::empty())
4266-
.with(tabled::settings::Padding::new(0, 1, 0, 0))
4267-
.to_string();
4268-
4269-
println!("Running Zones");
4270-
println!("{}", table);
4271-
Ok(())
4272-
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
//! omdb commands related to update status
6+
7+
use anyhow::Context;
8+
use nexus_types::internal_api::views::{SpStatus, ZoneStatus};
9+
use omicron_uuid_kinds::SledUuid;
10+
use tabled::Tabled;
11+
12+
/// Runs `omdb nexus update-status`
13+
pub async fn cmd_nexus_update_status(
14+
client: &nexus_client::Client,
15+
) -> Result<(), anyhow::Error> {
16+
let status = client
17+
.update_status()
18+
.await
19+
.context("retrieving update status")?
20+
.into_inner();
21+
22+
print_zones(status.zones.into_iter());
23+
print_sps(status.sps.into_iter());
24+
25+
Ok(())
26+
}
27+
28+
fn print_zones(zones: impl Iterator<Item = (SledUuid, Vec<ZoneStatus>)>) {
29+
#[derive(Tabled)]
30+
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
31+
struct ZoneRow {
32+
sled_id: String,
33+
zone_type: String,
34+
zone_id: String,
35+
version: String,
36+
}
37+
38+
let mut rows = Vec::new();
39+
for (sled_id, mut statuses) in zones {
40+
statuses.sort_unstable_by_key(|s| {
41+
(s.zone_type.kind(), s.zone_id, s.version.clone())
42+
});
43+
for status in statuses {
44+
rows.push(ZoneRow {
45+
sled_id: sled_id.to_string(),
46+
zone_type: status.zone_type.kind().name_prefix().into(),
47+
zone_id: status.zone_id.to_string(),
48+
version: status.version.to_string(),
49+
});
50+
}
51+
}
52+
53+
let table = tabled::Table::new(rows)
54+
.with(tabled::settings::Style::empty())
55+
.with(tabled::settings::Padding::new(0, 1, 0, 0))
56+
.to_string();
57+
58+
println!("Running Zones");
59+
println!("{}", table);
60+
}
61+
62+
fn print_sps(sps: impl Iterator<Item = (String, SpStatus)>) {
63+
#[derive(Tabled)]
64+
#[tabled(rename_all = "SCREAMING_SNAKE_CASE")]
65+
struct SpRow {
66+
baseboard_id: String,
67+
sled_id: String,
68+
slot0_version: String,
69+
slot1_version: String,
70+
}
71+
72+
let mut rows = Vec::new();
73+
for (baseboard_id, status) in sps {
74+
let SpStatus { sled_id, slot0_version, slot1_version } = status;
75+
rows.push(SpRow {
76+
baseboard_id,
77+
sled_id: sled_id.map_or("".to_string(), |id| id.to_string()),
78+
slot0_version: slot0_version.to_string(),
79+
slot1_version: slot1_version.to_string(),
80+
});
81+
}
82+
83+
let table = tabled::Table::new(rows)
84+
.with(tabled::settings::Style::empty())
85+
.with(tabled::settings::Padding::new(0, 1, 0, 0))
86+
.to_string();
87+
88+
println!("Installed SP Software");
89+
println!("{}", table);
90+
}

nexus/src/app/deployment.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -212,14 +212,7 @@ impl super::Nexus {
212212
})?;
213213
let new = planning_context.planning_input.tuf_repo().description();
214214
let old = planning_context.planning_input.old_repo().description();
215-
let status = UpdateStatus::new(
216-
old,
217-
new,
218-
inventory
219-
.sled_agents
220-
.iter()
221-
.map(|agent| (&agent.sled_id, &agent.last_reconciliation)),
222-
);
215+
let status = UpdateStatus::new(old, new, &inventory);
223216

224217
Ok(status)
225218
}

nexus/types/src/internal_api/views.rs

Lines changed: 105 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
use crate::deployment::PendingMgsUpdate;
66
use crate::deployment::TargetReleaseDescription;
77
use crate::inventory::BaseboardId;
8+
use crate::inventory::Caboose;
9+
use crate::inventory::CabooseWhich;
10+
use crate::inventory::Collection;
811
use chrono::DateTime;
912
use chrono::SecondsFormat;
1013
use chrono::Utc;
@@ -13,12 +16,12 @@ use futures::stream::StreamExt;
1316
use iddqd::IdOrdItem;
1417
use iddqd::IdOrdMap;
1518
use iddqd::id_upcast;
16-
use nexus_sled_agent_shared::inventory::ConfigReconcilerInventory;
1719
use nexus_sled_agent_shared::inventory::ConfigReconcilerInventoryResult;
1820
use nexus_sled_agent_shared::inventory::OmicronZoneImageSource;
1921
use nexus_sled_agent_shared::inventory::OmicronZoneType;
2022
use omicron_common::api::external::MacAddr;
2123
use omicron_common::api::external::ObjectStream;
24+
use omicron_common::api::external::TufArtifactMeta;
2225
use omicron_common::api::external::Vni;
2326
use omicron_common::snake_case_result;
2427
use omicron_common::snake_case_result::SnakeCaseResult;
@@ -38,6 +41,7 @@ use std::time::Duration;
3841
use std::time::Instant;
3942
use steno::SagaResultErr;
4043
use steno::UndoActionError;
44+
use tufaceous_artifact::KnownArtifactKind;
4145
use uuid::Uuid;
4246

4347
pub async fn to_list<T, U>(object_stream: ObjectStream<T>) -> Vec<U>
@@ -520,22 +524,24 @@ impl IdOrdItem for WaitingStatus {
520524
tag = "zone_status_version",
521525
content = "details"
522526
)]
523-
pub enum ZoneStatusVersion {
527+
pub enum TufRepoVersion {
524528
Unknown,
525529
InstallDataset,
526530
Version(Version),
527531
Error(String),
528532
}
529533

530-
impl Display for ZoneStatusVersion {
534+
impl Display for TufRepoVersion {
531535
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
532536
match self {
533-
ZoneStatusVersion::Unknown => write!(f, "unknown"),
534-
ZoneStatusVersion::InstallDataset => write!(f, "install dataset"),
535-
ZoneStatusVersion::Version(version) => {
537+
TufRepoVersion::Unknown => write!(f, "unknown"),
538+
TufRepoVersion::InstallDataset => {
539+
write!(f, "install dataset")
540+
}
541+
TufRepoVersion::Version(version) => {
536542
write!(f, "{}", version)
537543
}
538-
ZoneStatusVersion::Error(s) => {
544+
TufRepoVersion::Error(s) => {
539545
write!(f, "{}", s)
540546
}
541547
}
@@ -546,22 +552,32 @@ impl Display for ZoneStatusVersion {
546552
pub struct ZoneStatus {
547553
pub zone_id: OmicronZoneUuid,
548554
pub zone_type: OmicronZoneType,
549-
pub version: ZoneStatusVersion,
555+
pub version: TufRepoVersion,
556+
}
557+
558+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
559+
pub struct SpStatus {
560+
pub sled_id: Option<SledUuid>,
561+
pub slot0_version: TufRepoVersion,
562+
pub slot1_version: TufRepoVersion,
550563
}
551564

552565
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
553566
pub struct UpdateStatus {
554567
pub zones: BTreeMap<SledUuid, Vec<ZoneStatus>>,
568+
pub sps: BTreeMap<String, SpStatus>,
555569
}
556570

557571
impl UpdateStatus {
558-
pub fn new<'a>(
572+
pub fn new(
559573
old: &TargetReleaseDescription,
560574
new: &TargetReleaseDescription,
561-
sleds: impl Iterator<
562-
Item = (&'a SledUuid, &'a Option<ConfigReconcilerInventory>),
563-
>,
575+
inventory: &Collection,
564576
) -> UpdateStatus {
577+
let sleds = inventory
578+
.sled_agents
579+
.iter()
580+
.map(|agent| (&agent.sled_id, &agent.last_reconciliation));
565581
let zones = sleds
566582
.map(|(sled_id, inv)| {
567583
(
@@ -583,40 +599,109 @@ impl UpdateStatus {
583599
)
584600
})
585601
.collect();
586-
UpdateStatus { zones }
602+
let baseboard_ids: Vec<_> = inventory.sps.keys().cloned().collect();
603+
604+
// Find all SP versions and git commits via cabooses
605+
let mut sps: BTreeMap<BaseboardId, SpStatus> = baseboard_ids
606+
.into_iter()
607+
.map(|baseboard_id| {
608+
let slot0_version = inventory
609+
.caboose_for(CabooseWhich::SpSlot0, &baseboard_id)
610+
.map_or(TufRepoVersion::Unknown, |c| {
611+
Self::caboose_to_version(old, new, &c.caboose)
612+
});
613+
let slot1_version = inventory
614+
.caboose_for(CabooseWhich::SpSlot1, &baseboard_id)
615+
.map_or(TufRepoVersion::Unknown, |c| {
616+
Self::caboose_to_version(old, new, &c.caboose)
617+
});
618+
(
619+
(*baseboard_id).clone(),
620+
SpStatus { sled_id: None, slot0_version, slot1_version },
621+
)
622+
})
623+
.collect();
624+
625+
// Fill in the sled_id for the sp if known
626+
for sa in inventory.sled_agents.iter() {
627+
if let Some(baseboard_id) = &sa.baseboard_id {
628+
if let Some(sp) = sps.get_mut(baseboard_id) {
629+
sp.sled_id = Some(sa.sled_id);
630+
}
631+
}
632+
}
633+
634+
let sps = sps.into_iter().map(|(k, v)| (k.to_string(), v)).collect();
635+
636+
UpdateStatus { zones, sps }
637+
}
638+
639+
fn caboose_to_version(
640+
old: &TargetReleaseDescription,
641+
new: &TargetReleaseDescription,
642+
caboose: &Caboose,
643+
) -> TufRepoVersion {
644+
let matching_caboose = |a: &TufArtifactMeta| {
645+
caboose.board == a.id.name
646+
&& matches!(
647+
a.id.kind.to_known(),
648+
Some(
649+
KnownArtifactKind::GimletSp
650+
| KnownArtifactKind::PscSp
651+
| KnownArtifactKind::SwitchSp
652+
)
653+
)
654+
&& caboose.version == a.id.version.to_string()
655+
};
656+
if let Some(new) = new.tuf_repo() {
657+
if new.artifacts.iter().any(matching_caboose) {
658+
return TufRepoVersion::Version(
659+
new.repo.system_version.clone(),
660+
);
661+
}
662+
}
663+
if let Some(old) = old.tuf_repo() {
664+
if old.artifacts.iter().any(matching_caboose) {
665+
return TufRepoVersion::Version(
666+
old.repo.system_version.clone(),
667+
);
668+
}
669+
}
670+
671+
TufRepoVersion::Unknown
587672
}
588673

589-
pub fn zone_image_source_to_version(
674+
fn zone_image_source_to_version(
590675
old: &TargetReleaseDescription,
591676
new: &TargetReleaseDescription,
592677
source: &OmicronZoneImageSource,
593678
res: &ConfigReconcilerInventoryResult,
594-
) -> ZoneStatusVersion {
679+
) -> TufRepoVersion {
595680
if let ConfigReconcilerInventoryResult::Err { message } = res {
596-
return ZoneStatusVersion::Error(message.clone());
681+
return TufRepoVersion::Error(message.clone());
597682
}
598683

599684
let &OmicronZoneImageSource::Artifact { hash } = source else {
600-
return ZoneStatusVersion::InstallDataset;
685+
return TufRepoVersion::InstallDataset;
601686
};
602687

603688
if let Some(old) = old.tuf_repo() {
604689
if old.artifacts.iter().any(|meta| meta.hash == hash) {
605-
return ZoneStatusVersion::Version(
690+
return TufRepoVersion::Version(
606691
old.repo.system_version.clone(),
607692
);
608693
}
609694
}
610695

611696
if let Some(new) = new.tuf_repo() {
612697
if new.artifacts.iter().any(|meta| meta.hash == hash) {
613-
return ZoneStatusVersion::Version(
698+
return TufRepoVersion::Version(
614699
new.repo.system_version.clone(),
615700
);
616701
}
617702
}
618703

619-
ZoneStatusVersion::Unknown
704+
TufRepoVersion::Unknown
620705
}
621706
}
622707

0 commit comments

Comments
 (0)