Skip to content

Add desired host phase 2 contents to OmicronSledConfig (PR 1/4) #8538

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 9, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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
19 changes: 19 additions & 0 deletions dev-tools/omdb/src/bin/omdb/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ use nexus_sled_agent_shared::inventory::BootPartitionDetails;
use nexus_sled_agent_shared::inventory::ConfigReconcilerInventory;
use nexus_sled_agent_shared::inventory::ConfigReconcilerInventoryResult;
use nexus_sled_agent_shared::inventory::ConfigReconcilerInventoryStatus;
use nexus_sled_agent_shared::inventory::HostPhase2DesiredContents;
use nexus_sled_agent_shared::inventory::OmicronSledConfig;
use nexus_sled_agent_shared::inventory::OmicronZoneImageSource;
use nexus_sled_agent_shared::inventory::OrphanedDataset;
Expand Down Expand Up @@ -7567,12 +7568,30 @@ fn inv_collection_print_sled_config(label: &str, config: &OmicronSledConfig) {
datasets,
zones,
remove_mupdate_override,
host_phase_2,
} = config;

println!("\n{label} SLED CONFIG");
println!(" generation: {}", generation);
println!(" remove_mupdate_override: {remove_mupdate_override:?}");

let display_host_phase_2_desired = |desired| match desired {
HostPhase2DesiredContents::CurrentContents => {
Cow::Borrowed("keep existing current contents")
}
HostPhase2DesiredContents::Artifact { hash } => {
Cow::Owned(format!("artifact {hash}"))
}
};
println!(
" desired host phase 2 slot a: {}",
display_host_phase_2_desired(host_phase_2.slot_a)
);
println!(
" desired host phase 2 slot b: {}",
display_host_phase_2_desired(host_phase_2.slot_b)
);

if disks.is_empty() {
println!(" disk config empty");
} else {
Expand Down
44 changes: 44 additions & 0 deletions nexus-sled-agent-shared/src/inventory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,47 @@ pub enum SledRole {
Scrimlet,
}

/// Describes the desired contents of a host phase 2 slot (i.e., the boot
/// partition on one of the internal M.2 drives).
#[derive(
Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq,
)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum HostPhase2DesiredContents {
/// Do not change the current contents.
///
/// We use this value when we've detected a sled has been mupdated (and we
/// don't want to overwrite phase 2 images until we understand how to
/// recover from that mupdate) and as the default value when reading an
/// [`OmicronSledConfig`] that was ledgered before this concept existed.
CurrentContents,

/// Set the phase 2 slot to the given artifact.
///
/// The artifact will come from an unpacked and distributed TUF repo.
Artifact { hash: ArtifactHash },
}

/// Describes the desired contents for both host phase 2 slots.
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct HostPhase2DesiredSlots {
pub slot_a: HostPhase2DesiredContents,
pub slot_b: HostPhase2DesiredContents,
}

impl HostPhase2DesiredSlots {
/// Return a `HostPhase2DesiredSlots` with both slots set to
/// [`HostPhase2DesiredContents::CurrentContents`]; i.e., "make no changes
/// to the current contents of either slot".
pub const fn current_contents() -> Self {
Self {
slot_a: HostPhase2DesiredContents::CurrentContents,
slot_b: HostPhase2DesiredContents::CurrentContents,
}
}
}

/// Describes the set of Reconfigurator-managed configuration elements of a sled
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
pub struct OmicronSledConfig {
Expand All @@ -584,6 +625,8 @@ pub struct OmicronSledConfig {
pub datasets: IdMap<DatasetConfig>,
pub zones: IdMap<OmicronZoneConfig>,
pub remove_mupdate_override: Option<MupdateOverrideUuid>,
#[serde(default = "HostPhase2DesiredSlots::current_contents")]
pub host_phase_2: HostPhase2DesiredSlots,
}

impl Default for OmicronSledConfig {
Expand All @@ -594,6 +637,7 @@ impl Default for OmicronSledConfig {
datasets: IdMap::default(),
zones: IdMap::default(),
remove_mupdate_override: None,
host_phase_2: HostPhase2DesiredSlots::current_contents(),
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions nexus/db-model/src/inventory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ use nexus_db_schema::schema::{
use nexus_sled_agent_shared::inventory::BootImageHeader;
use nexus_sled_agent_shared::inventory::BootPartitionDetails;
use nexus_sled_agent_shared::inventory::ConfigReconcilerInventoryStatus;
use nexus_sled_agent_shared::inventory::HostPhase2DesiredContents;
use nexus_sled_agent_shared::inventory::HostPhase2DesiredSlots;
use nexus_sled_agent_shared::inventory::MupdateOverrideBootInventory;
use nexus_sled_agent_shared::inventory::MupdateOverrideInventory;
use nexus_sled_agent_shared::inventory::MupdateOverrideNonBootInventory;
Expand Down Expand Up @@ -1944,6 +1946,9 @@ pub struct InvOmicronSledConfig {
pub id: DbTypedUuid<OmicronSledConfigKind>,
pub generation: Generation,
pub remove_mupdate_override: Option<DbTypedUuid<MupdateOverrideKind>>,

#[diesel(embed)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL

pub host_phase_2: DbHostPhase2DesiredSlots,
}

impl InvOmicronSledConfig {
Expand All @@ -1952,12 +1957,51 @@ impl InvOmicronSledConfig {
id: OmicronSledConfigUuid,
generation: external::Generation,
remove_mupdate_override: Option<MupdateOverrideUuid>,
host_phase_2: HostPhase2DesiredSlots,
) -> Self {
Self {
inv_collection_id: inv_collection_id.into(),
id: id.into(),
generation: Generation(generation),
remove_mupdate_override: remove_mupdate_override.map(From::from),
host_phase_2: host_phase_2.into(),
}
}
}

#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
#[diesel(table_name = inv_omicron_sled_config)]
pub struct DbHostPhase2DesiredSlots {
pub host_phase_2_desired_slot_a: Option<ArtifactHash>,
pub host_phase_2_desired_slot_b: Option<ArtifactHash>,
}

impl From<HostPhase2DesiredSlots> for DbHostPhase2DesiredSlots {
fn from(value: HostPhase2DesiredSlots) -> Self {
let remap = |desired| match desired {
HostPhase2DesiredContents::CurrentContents => None,
HostPhase2DesiredContents::Artifact { hash } => {
Some(ArtifactHash(hash))
}
};
Self {
host_phase_2_desired_slot_a: remap(value.slot_a),
host_phase_2_desired_slot_b: remap(value.slot_b),
}
}
}

impl From<DbHostPhase2DesiredSlots> for HostPhase2DesiredSlots {
fn from(value: DbHostPhase2DesiredSlots) -> Self {
let remap = |maybe_artifact| match maybe_artifact {
None => HostPhase2DesiredContents::CurrentContents,
Some(ArtifactHash(hash)) => {
HostPhase2DesiredContents::Artifact { hash }
}
};
Self {
slot_a: remap(value.host_phase_2_desired_slot_a),
slot_b: remap(value.host_phase_2_desired_slot_b),
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion nexus/db-model/src/schema_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock};
///
/// This must be updated when you change the database schema. Refer to
/// schema/crdb/README.adoc in the root of this repository for details.
pub const SCHEMA_VERSION: Version = Version::new(157, 0, 0);
pub const SCHEMA_VERSION: Version = Version::new(158, 0, 0);

/// List of all past database schema versions, in *reverse* order
///
Expand All @@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock<Vec<KnownVersion>> = LazyLock::new(|| {
// | leaving the first copy as an example for the next person.
// v
// KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"),
KnownVersion::new(158, "sled-config-desired-host-phase-2"),
KnownVersion::new(157, "user-data-export"),
KnownVersion::new(156, "boot-partitions-inventory"),
KnownVersion::new(155, "vpc-firewall-icmp"),
Expand Down
2 changes: 2 additions & 0 deletions nexus/db-queries/src/db/datastore/inventory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2774,6 +2774,7 @@ impl DataStore {
disks: IdMap::default(),
datasets: IdMap::default(),
zones: IdMap::default(),
host_phase_2: sled_config.host_phase_2.into(),
},
});
}
Expand Down Expand Up @@ -3871,6 +3872,7 @@ impl ConfigReconcilerRows {
sled_config_id,
config.generation,
config.remove_mupdate_override,
config.host_phase_2.clone(),
));
self.disks.extend(config.disks.iter().map(|disk| {
InvOmicronSledConfigDisk::new(
Expand Down
2 changes: 2 additions & 0 deletions nexus/db-schema/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1788,6 +1788,8 @@ table! {

generation -> Int8,
remove_mupdate_override -> Nullable<Uuid>,
host_phase_2_desired_slot_a -> Nullable<Text>,
host_phase_2_desired_slot_b -> Nullable<Text>,
}
}

Expand Down
8 changes: 8 additions & 0 deletions nexus/inventory/src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ mod test {
use gateway_messages::SpPort;
use id_map::IdMap;
use nexus_sled_agent_shared::inventory::ConfigReconcilerInventoryStatus;
use nexus_sled_agent_shared::inventory::HostPhase2DesiredSlots;
use nexus_sled_agent_shared::inventory::OmicronSledConfig;
use nexus_sled_agent_shared::inventory::OmicronZoneConfig;
use nexus_sled_agent_shared::inventory::OmicronZoneImageSource;
Expand All @@ -434,6 +435,7 @@ mod test {
datasets,
zones,
remove_mupdate_override,
host_phase_2,
} = config;

writeln!(s, " generation: {generation}").unwrap();
Expand All @@ -442,6 +444,11 @@ mod test {
" remove_mupdate_override: {remove_mupdate_override:?}"
)
.unwrap();
{
let HostPhase2DesiredSlots { slot_a, slot_b } = host_phase_2;
writeln!(s, " host_phase_2.slot_a: {slot_a:?}").unwrap();
writeln!(s, " host_phase_2.slot_b: {slot_b:?}").unwrap();
}
for disk in disks {
writeln!(
s,
Expand Down Expand Up @@ -687,6 +694,7 @@ mod test {
.into_iter()
.collect(),
remove_mupdate_override: None,
host_phase_2: HostPhase2DesiredSlots::current_contents(),
})
.await
.expect("failed to write initial zone version to fake sled agent");
Expand Down
4 changes: 4 additions & 0 deletions nexus/inventory/src/examples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use nexus_sled_agent_shared::inventory::BootImageHeader;
use nexus_sled_agent_shared::inventory::BootPartitionDetails;
use nexus_sled_agent_shared::inventory::ConfigReconcilerInventory;
use nexus_sled_agent_shared::inventory::ConfigReconcilerInventoryStatus;
use nexus_sled_agent_shared::inventory::HostPhase2DesiredSlots;
use nexus_sled_agent_shared::inventory::Inventory;
use nexus_sled_agent_shared::inventory::InventoryDataset;
use nexus_sled_agent_shared::inventory::InventoryDisk;
Expand Down Expand Up @@ -345,20 +346,23 @@ pub fn representative() -> Representative {
datasets: Default::default(),
zones: sled14.zones.into_iter().collect(),
remove_mupdate_override: None,
host_phase_2: HostPhase2DesiredSlots::current_contents(),
};
let sled16 = OmicronSledConfig {
generation: sled16.generation,
disks: Default::default(),
datasets: Default::default(),
zones: sled16.zones.into_iter().collect(),
remove_mupdate_override: None,
host_phase_2: HostPhase2DesiredSlots::current_contents(),
};
let sled17 = OmicronSledConfig {
generation: sled17.generation,
disks: Default::default(),
datasets: Default::default(),
zones: sled17.zones.into_iter().collect(),
remove_mupdate_override: None,
host_phase_2: HostPhase2DesiredSlots::current_contents(),
};

// Create iterator producing fixed IDs.
Expand Down
3 changes: 3 additions & 0 deletions nexus/test-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use nexus_config::MgdConfig;
use nexus_config::NUM_INITIAL_RESERVED_IP_ADDRESSES;
use nexus_config::NexusConfig;
use nexus_db_queries::db::pub_test_utils::crdb;
use nexus_sled_agent_shared::inventory::HostPhase2DesiredSlots;
use nexus_sled_agent_shared::inventory::OmicronSledConfig;
use nexus_sled_agent_shared::inventory::OmicronZoneDataset;
use nexus_sled_agent_shared::recovery_silo::RecoverySiloConfig;
Expand Down Expand Up @@ -1147,6 +1148,7 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> {
.map(From::from)
.collect(),
remove_mupdate_override: None,
host_phase_2: HostPhase2DesiredSlots::current_contents(),
})
.await
.expect("Failed to configure sled agent with our zones");
Expand Down Expand Up @@ -1186,6 +1188,7 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> {
datasets: IdMap::default(),
zones: IdMap::default(),
remove_mupdate_override: None,
host_phase_2: HostPhase2DesiredSlots::current_contents(),
})
.await
.expect("Failed to configure sled agent with our zones");
Expand Down
4 changes: 4 additions & 0 deletions nexus/types/src/deployment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub use crate::inventory::ZpoolName;
use blueprint_diff::ClickhouseClusterConfigDiffTablesForSingleBlueprint;
use blueprint_display::BpDatasetsTableSchema;
use daft::Diffable;
use nexus_sled_agent_shared::inventory::HostPhase2DesiredSlots;
use nexus_sled_agent_shared::inventory::OmicronSledConfig;
use nexus_sled_agent_shared::inventory::OmicronZoneConfig;
use nexus_sled_agent_shared::inventory::OmicronZoneImageSource;
Expand Down Expand Up @@ -743,6 +744,9 @@ impl BlueprintSledConfig {
})
.collect(),
remove_mupdate_override: self.remove_mupdate_override,
// TODO BlueprintSledConfig should have a corresponding field.
// https://github.com/oxidecomputer/omicron/issues/8542
host_phase_2: HostPhase2DesiredSlots::current_contents(),
}
}

Expand Down
Loading
Loading