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 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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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 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(158, 0, 0);
pub const SCHEMA_VERSION: Version = Version::new(159, 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(159, "sled-config-desired-host-phase-2"),
KnownVersion::new(158, "drop-builtin-roles"),
KnownVersion::new(157, "user-data-export"),
KnownVersion::new(156, "boot-partitions-inventory"),
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 @@ -1780,6 +1780,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
1 change: 1 addition & 0 deletions nexus/inventory/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ sled-agent-types.workspace = true
sled-agent-zone-images-examples.workspace = true
slog.workspace = true
strum.workspace = true
swrite.workspace = true
thiserror.workspace = true
tufaceous-artifact.workspace = true
typed-rng.workspace = true
Expand Down
Loading
Loading