From 8db3a5c38d7fb8c102ad0fc9ed1accf7c5d536db Mon Sep 17 00:00:00 2001 From: karencfv Date: Mon, 14 Jul 2025 20:15:51 +1200 Subject: [PATCH 1/7] [reconfigurator] database support for PendingMgsUpdate for RoT --- nexus/db-model/src/deployment.rs | 61 ++- nexus/db-model/src/schema_versions.rs | 3 +- .../db-queries/src/db/datastore/deployment.rs | 494 +++++++++++++----- nexus/db-schema/src/schema.rs | 17 + .../crdb/add-pending-mgs-updates-rot/up.sql | 17 + schema/crdb/dbinit.sql | 27 +- 6 files changed, 478 insertions(+), 141 deletions(-) create mode 100644 schema/crdb/add-pending-mgs-updates-rot/up.sql diff --git a/nexus/db-model/src/deployment.rs b/nexus/db-model/src/deployment.rs index e41916bafe3..9c8be7ab292 100644 --- a/nexus/db-model/src/deployment.rs +++ b/nexus/db-model/src/deployment.rs @@ -5,7 +5,7 @@ //! Types for representing the deployed software and configuration in the //! database -use crate::inventory::{SpMgsSlot, SpType, ZoneType}; +use crate::inventory::{HwRotSlot, SpMgsSlot, SpType, ZoneType}; use crate::omicron_zone_config::{self, OmicronZoneNic}; use crate::typed_uuid::DbTypedUuid; use crate::{ @@ -22,11 +22,10 @@ use nexus_db_schema::schema::{ bp_clickhouse_keeper_zone_id_to_node_id, bp_clickhouse_server_zone_id_to_node_id, bp_omicron_dataset, bp_omicron_physical_disk, bp_omicron_zone, bp_omicron_zone_nic, - bp_oximeter_read_policy, bp_pending_mgs_update_sp, bp_sled_metadata, - bp_target, + bp_oximeter_read_policy, bp_pending_mgs_update_rot, + bp_pending_mgs_update_sp, bp_sled_metadata, bp_target, }; use nexus_sled_agent_shared::inventory::OmicronZoneDataset; -use nexus_types::deployment::BlueprintPhysicalDiskConfig; use nexus_types::deployment::BlueprintPhysicalDiskDisposition; use nexus_types::deployment::BlueprintTarget; use nexus_types::deployment::BlueprintZoneConfig; @@ -40,6 +39,9 @@ use nexus_types::deployment::{ BlueprintDatasetConfig, BlueprintZoneImageVersion, OximeterReadMode, }; use nexus_types::deployment::{BlueprintDatasetDisposition, ExpectedVersion}; +use nexus_types::deployment::{ + BlueprintPhysicalDiskConfig, ExpectedActiveRotSlot, +}; use nexus_types::deployment::{BlueprintZoneImageSource, blueprint_zone_type}; use nexus_types::deployment::{ OmicronZoneExternalFloatingAddr, OmicronZoneExternalFloatingIp, @@ -1288,3 +1290,54 @@ impl BpPendingMgsUpdateSp { } } } + +#[derive(Queryable, Clone, Debug, Selectable, Insertable)] +#[diesel(table_name = bp_pending_mgs_update_rot)] +pub struct BpPendingMgsUpdateRot { + pub blueprint_id: DbTypedUuid, + pub hw_baseboard_id: Uuid, + pub sp_type: SpType, + pub sp_slot: SpMgsSlot, + pub artifact_sha256: ArtifactHash, + pub artifact_version: DbArtifactVersion, + pub expected_active_slot: HwRotSlot, + pub expected_active_version: DbArtifactVersion, + pub expected_inactive_version: Option, + pub expected_persistent_boot_preference: HwRotSlot, + pub expected_pending_persistent_boot_preference: Option, + pub expected_transient_boot_preference: Option, +} + +impl BpPendingMgsUpdateRot { + pub fn into_generic( + self, + baseboard_id: Arc, + ) -> PendingMgsUpdate { + PendingMgsUpdate { + baseboard_id, + sp_type: self.sp_type.into(), + slot_id: **self.sp_slot, + artifact_hash: self.artifact_sha256.into(), + artifact_version: (*self.artifact_version).clone(), + details: PendingMgsUpdateDetails::Rot { + expected_active_slot: ExpectedActiveRotSlot { + slot: self.expected_active_slot.into(), + version: (*self.expected_active_version).clone(), + }, + expected_inactive_version: self + .expected_inactive_version + .map(|v| ExpectedVersion::Version(v.into())) + .unwrap_or(ExpectedVersion::NoValidVersion), + expected_persistent_boot_preference: self + .expected_persistent_boot_preference + .into(), + expected_pending_persistent_boot_preference: self + .expected_pending_persistent_boot_preference + .map(|s| s.into()), + expected_transient_boot_preference: self + .expected_transient_boot_preference + .map(|s| s.into()), + }, + } + } +} diff --git a/nexus/db-model/src/schema_versions.rs b/nexus/db-model/src/schema_versions.rs index 7a67315c41e..742e0c62436 100644 --- a/nexus/db-model/src/schema_versions.rs +++ b/nexus/db-model/src/schema_versions.rs @@ -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(161, 0, 0); +pub const SCHEMA_VERSION: Version = Version::new(162, 0, 0); /// List of all past database schema versions, in *reverse* order /// @@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock> = 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(162, "add-pending-mgs-updates-rot"), KnownVersion::new(161, "inv_cockroachdb_status"), KnownVersion::new(160, "tuf-trust-root"), KnownVersion::new(159, "sled-config-desired-host-phase-2"), diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index 9d82e159624..6d28de12fa5 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -53,17 +53,20 @@ use nexus_db_model::BpOmicronPhysicalDisk; use nexus_db_model::BpOmicronZone; use nexus_db_model::BpOmicronZoneNic; use nexus_db_model::BpOximeterReadPolicy; +use nexus_db_model::BpPendingMgsUpdateRot; use nexus_db_model::BpPendingMgsUpdateSp; use nexus_db_model::BpSledMetadata; use nexus_db_model::BpTarget; use nexus_db_model::DbArtifactVersion; use nexus_db_model::DbTypedUuid; use nexus_db_model::HwBaseboardId; +use nexus_db_model::HwRotSlot; use nexus_db_model::SpMgsSlot; use nexus_db_model::SpType; use nexus_db_model::SqlU16; use nexus_db_model::TufArtifact; use nexus_db_model::to_db_typed_uuid; +use nexus_db_schema::enums::HwRotSlotEnum; use nexus_db_schema::enums::SpTypeEnum; use nexus_types::deployment::Blueprint; use nexus_types::deployment::BlueprintMetadata; @@ -498,150 +501,302 @@ impl DataStore { // // This way, we don't need to know the id. The database looks // it up for us as it does the INSERT. + // + // For each SP component (SP, RoT, RoT bootloader) we will be + // inserting to a different table: bp_pending_mgs_update_sp, + // bp_pending_mgs_update_rot, or + // bp_pending_mgs_update_rot_bootloader. for update in &blueprint.pending_mgs_updates { // Right now, we only implement support for storing SP // updates. - let (expected_active_version, expected_inactive_version) = - match &update.details { - PendingMgsUpdateDetails::Sp { - expected_active_version, - expected_inactive_version, - } => ( - expected_active_version, - expected_inactive_version, - ), - PendingMgsUpdateDetails::Rot { .. } - | PendingMgsUpdateDetails::RotBootloader { - .. - } => continue, - }; - - let db_blueprint_id = DbTypedUuid::from(blueprint_id) - .into_sql::( - ); - let db_sp_type = - SpType::from(update.sp_type).into_sql::(); - let db_slot_id = - SpMgsSlot::from(SqlU16::from(update.slot_id)) - .into_sql::(); - let db_artifact_hash = - ArtifactHash::from(update.artifact_hash) + match &update.details { + PendingMgsUpdateDetails::Sp { + expected_active_version, + expected_inactive_version, + } => { + let db_blueprint_id = DbTypedUuid::from( + blueprint_id + ).into_sql::(); + let db_sp_type = + SpType::from(update.sp_type).into_sql::(); + let db_slot_id = + SpMgsSlot::from(SqlU16::from(update.slot_id)) + .into_sql::(); + let db_artifact_hash = + ArtifactHash::from(update.artifact_hash) + .into_sql::(); + let db_artifact_version = DbArtifactVersion::from( + update.artifact_version.clone(), + ) .into_sql::(); - let db_artifact_version = DbArtifactVersion::from( - update.artifact_version.clone(), - ) - .into_sql::(); - let db_expected_version = DbArtifactVersion::from( - expected_active_version.clone(), - ) - .into_sql::(); - let db_expected_inactive_version = - match expected_inactive_version { - ExpectedVersion::NoValidVersion => None, - ExpectedVersion::Version(v) => { - Some(DbArtifactVersion::from(v.clone())) + let db_expected_version = DbArtifactVersion::from( + expected_active_version.clone(), + ) + .into_sql::(); + let db_expected_inactive_version = + match expected_inactive_version { + ExpectedVersion::NoValidVersion => None, + ExpectedVersion::Version(v) => { + Some(DbArtifactVersion::from(v.clone())) + } + } + .into_sql::>(); + + // Skip formatting several lines to prevent rustfmt bailing + // out. + #[rustfmt::skip] + use nexus_db_schema::schema::hw_baseboard_id::dsl + as baseboard_dsl; + #[rustfmt::skip] + use nexus_db_schema::schema::bp_pending_mgs_update_sp::dsl + as update_dsl; + let selection = + nexus_db_schema::schema::hw_baseboard_id::table + .select(( + db_blueprint_id, + baseboard_dsl::id, + db_sp_type, + db_slot_id, + db_artifact_hash, + db_artifact_version, + db_expected_version, + db_expected_inactive_version, + )) + .filter( + baseboard_dsl::part_number.eq(update + .baseboard_id + .part_number + .clone()), + ) + .filter( + baseboard_dsl::serial_number.eq(update + .baseboard_id + .serial_number + .clone()), + ); + let count = diesel::insert_into( + update_dsl::bp_pending_mgs_update_sp, + ) + .values(selection) + .into_columns(( + update_dsl::blueprint_id, + update_dsl::hw_baseboard_id, + update_dsl::sp_type, + update_dsl::sp_slot, + update_dsl::artifact_sha256, + update_dsl::artifact_version, + update_dsl::expected_active_version, + update_dsl::expected_inactive_version, + )) + .execute_async(&conn) + .await?; + if count != 1 { + // This should be impossible in practice. We + // will insert however many rows matched the + // `baseboard_id` parts of the query above. It + // can't be more than one 1 because we've + // filtered on a pair of columns that are unique + // together. It could only be 0 if the baseboard + // id had never been seen before in an inventory + // collection. But in that case, how did we + // manage to construct a blueprint with it? + // + // This could happen in the test suite or with + // `reconfigurator-cli`, which both let you + // create any blueprint you like. In the test + // suite, the test just has to deal with this + // behaviour (e.g., by inserting an inventory + // collection containing this SP). With + // `reconfigurator-cli`, this amounts to user + // error. + error!(&opctx.log, + "blueprint insertion: unexpectedly tried to \ + insert wrong number of rows into \ + bp_pending_mgs_update_sp (aborting transaction)"; + "count" => count, + &update.baseboard_id, + ); + return Err(TxnError::BadInsertCount { + table_name: "bp_pending_mgs_update_sp", + count, + baseboard_id: update.baseboard_id.clone(), + }); } - } - .into_sql::>(); - // Skip formatting several lines to prevent rustfmt bailing - // out. - #[rustfmt::skip] - use nexus_db_schema::schema::hw_baseboard_id::dsl - as baseboard_dsl; - #[rustfmt::skip] - use nexus_db_schema::schema::bp_pending_mgs_update_sp::dsl - as update_dsl; - let selection = - nexus_db_schema::schema::hw_baseboard_id::table - .select(( - db_blueprint_id, - baseboard_dsl::id, - db_sp_type, - db_slot_id, - db_artifact_hash, - db_artifact_version, - db_expected_version, - db_expected_inactive_version, - )) - .filter( - baseboard_dsl::part_number.eq(update - .baseboard_id - .part_number - .clone()), + // This statement is just here to force a compilation + // error if the set of columns in + // `bp_pending_mgs_update_sp` changes because that + // will affect the correctness of the above + // statement. + // + // If you're here because of a compile error, you + // might be changing the `bp_pending_mgs_update_sp` + // table. Update the statement below and be sure to + // update the code above, too! + let ( + _blueprint_id, + _hw_baseboard_id, + _sp_type, + _sp_slot, + _artifact_sha256, + _artifact_version, + _expected_active_version, + _expected_inactive_version, + ) = update_dsl::bp_pending_mgs_update_sp::all_columns(); + }, + PendingMgsUpdateDetails::Rot { + expected_active_slot, + expected_inactive_version, + expected_persistent_boot_preference, + expected_pending_persistent_boot_preference, + expected_transient_boot_preference, + } => { + let db_blueprint_id = DbTypedUuid::from( + blueprint_id + ).into_sql::(); + let db_sp_type = + SpType::from(update.sp_type).into_sql::(); + let db_slot_id = + SpMgsSlot::from(SqlU16::from(update.slot_id)) + .into_sql::(); + let db_artifact_hash = + ArtifactHash::from(update.artifact_hash) + .into_sql::(); + let db_artifact_version = DbArtifactVersion::from( + update.artifact_version.clone(), ) - .filter( - baseboard_dsl::serial_number.eq(update - .baseboard_id - .serial_number - .clone()), - ); - let count = diesel::insert_into( - update_dsl::bp_pending_mgs_update_sp, - ) - .values(selection) - .into_columns(( - update_dsl::blueprint_id, - update_dsl::hw_baseboard_id, - update_dsl::sp_type, - update_dsl::sp_slot, - update_dsl::artifact_sha256, - update_dsl::artifact_version, - update_dsl::expected_active_version, - update_dsl::expected_inactive_version, - )) - .execute_async(&conn) - .await?; - if count != 1 { - // This should be impossible in practice. We will - // insert however many rows matched the `baseboard_id` - // parts of the query above. It can't be more than one - // 1 because we've filtered on a pair of columns that - // are unique together. It could only be 0 if the - // baseboard id had never been seen before in an - // inventory collection. But in that case, how did we - // manage to construct a blueprint with it? - // - // This could happen in the test suite or with - // `reconfigurator-cli`, which both let you create any - // blueprint you like. In the test suite, the test just - // has to deal with this behavior (e.g., by inserting an - // inventory collection containing this SP). With - // `reconfigurator-cli`, this amounts to user error. - error!(&opctx.log, - "blueprint insertion: unexpectedly tried to insert \ - wrong number of rows into \ - bp_pending_mgs_update_sp (aborting transaction)"; - "count" => count, - &update.baseboard_id, - ); - return Err(TxnError::BadInsertCount { - table_name: "bp_pending_mgs_update_sp", - count, - baseboard_id: update.baseboard_id.clone(), - }); - } + .into_sql::(); + let db_expected_active_slot = HwRotSlot::from( + *expected_active_slot.slot(), + ) + .into_sql::(); + let db_expected_active_version = DbArtifactVersion::from( + expected_active_slot.version(), + ) + .into_sql::(); + let db_expected_inactive_version = + match expected_inactive_version { + ExpectedVersion::NoValidVersion => None, + ExpectedVersion::Version(v) => { + Some(DbArtifactVersion::from(v.clone())) + } + } + .into_sql::>(); + let db_expected_persistent_boot_preference = HwRotSlot::from( + *expected_persistent_boot_preference + ).into_sql::(); + let db_expected_pending_persistent_boot_preference = + expected_pending_persistent_boot_preference.map( + |p| HwRotSlot::from(p) + ).into_sql::>(); + let db_expected_transient_boot_preference = + expected_transient_boot_preference.map( + |p| HwRotSlot::from(p) + ).into_sql::>(); + + // Skip formatting several lines to prevent rustfmt bailing + // out. + #[rustfmt::skip] + use nexus_db_schema::schema::hw_baseboard_id::dsl + as baseboard_dsl; + #[rustfmt::skip] + use nexus_db_schema::schema::bp_pending_mgs_update_rot::dsl + as update_dsl; + let selection = + nexus_db_schema::schema::hw_baseboard_id::table + .select(( + db_blueprint_id, + baseboard_dsl::id, + db_sp_type, + db_slot_id, + db_artifact_hash, + db_artifact_version, + db_expected_active_slot, + db_expected_active_version, + db_expected_inactive_version, + db_expected_persistent_boot_preference, + db_expected_pending_persistent_boot_preference, + db_expected_transient_boot_preference, + )) + .filter( + baseboard_dsl::part_number.eq(update + .baseboard_id + .part_number + .clone()), + ) + .filter( + baseboard_dsl::serial_number.eq(update + .baseboard_id + .serial_number + .clone()), + ); + let count = diesel::insert_into( + update_dsl::bp_pending_mgs_update_rot, + ) + .values(selection) + .into_columns(( + update_dsl::blueprint_id, + update_dsl::hw_baseboard_id, + update_dsl::sp_type, + update_dsl::sp_slot, + update_dsl::artifact_sha256, + update_dsl::artifact_version, + update_dsl::expected_active_slot, + update_dsl::expected_active_version, + update_dsl::expected_inactive_version, + update_dsl::expected_persistent_boot_preference, + update_dsl::expected_pending_persistent_boot_preference, + update_dsl::expected_transient_boot_preference, + )) + .execute_async(&conn) + .await?; + if count != 1 { + // As with `PendingMgsUpdateDetails::Sp`, this + // should be impossible in practice. + error!(&opctx.log, + "blueprint insertion: unexpectedly tried to \ + insert wrong number of rows into \ + bp_pending_mgs_update_rot (aborting transaction)"; + "count" => count, + &update.baseboard_id, + ); + return Err(TxnError::BadInsertCount { + table_name: "bp_pending_mgs_update_rot", + count, + baseboard_id: update.baseboard_id.clone(), + }); + } - // This statement is just here to force a compilation error - // if the set of columns in `bp_pending_mgs_update_sp` - // changes because that will affect the correctness of the - // above statement. - // - // If you're here because of a compile error, you might be - // changing the `bp_pending_mgs_update_sp` table. Update - // the statement below and be sure to update the code above, - // too! - let ( - _blueprint_id, - _hw_baseboard_id, - _sp_type, - _sp_slot, - _artifact_sha256, - _artifact_version, - _expected_active_version, - _expected_inactive_version, - ) = update_dsl::bp_pending_mgs_update_sp::all_columns(); + // This statement is just here to force a compilation + // error if the set of columns in + // `bp_pending_mgs_update_rot` changes because that + // will affect the correctness of the above + // statement. + // + // If you're here because of a compile error, you + // might be changing the `bp_pending_mgs_update_rot` + // table. Update the statement below and be sure to + // update the code above, too! + let ( + _blueprint_id, + _hw_baseboard_id, + _sp_type, + _sp_slot, + _artifact_sha256, + _artifact_version, + _expected_active_slot, + _expected_active_version, + _expected_inactive_version, + _expected_persistent_boot_preference, + _expected_pending_persistent_boot_preference, + _expected_transient_boot_preference, + ) = update_dsl::bp_pending_mgs_update_rot::all_columns(); + }, + PendingMgsUpdateDetails::RotBootloader { + .. + } => continue, // TODO: Implement. + }; } Ok(()) @@ -1238,6 +1393,36 @@ impl DataStore { } } + // Load all pending RoT updates. + let mut pending_updates_rot = Vec::new(); + { + use nexus_db_schema::schema::bp_pending_mgs_update_rot::dsl; + + let mut paginator = Paginator::new( + SQL_BATCH_SIZE, + dropshot::PaginationOrder::Ascending, + ); + while let Some(p) = paginator.next() { + let batch = paginated( + dsl::bp_pending_mgs_update_rot, + dsl::hw_baseboard_id, + &p.current_pagparams(), + ) + .filter(dsl::blueprint_id.eq(to_db_typed_uuid(blueprint_id))) + .select(BpPendingMgsUpdateRot::as_select()) + .load_async(&*conn) + .await + .map_err(|e| { + public_error_from_diesel(e, ErrorHandler::Server) + })?; + + paginator = p.found_batch(&batch, &|d| d.hw_baseboard_id); + for row in batch { + pending_updates_rot.push(row); + } + } + } + // Collect the unique baseboard ids referenced by pending updates. let baseboard_id_ids: BTreeSet<_> = pending_updates_sp.iter().map(|s| s.hw_baseboard_id).collect(); @@ -1277,6 +1462,27 @@ impl DataStore { // Combine this information to assemble the set of pending MGS updates. let mut pending_mgs_updates = PendingMgsUpdates::new(); + for row in pending_updates_rot { + let Some(baseboard) = baseboards_by_id.get(&row.hw_baseboard_id) + else { + // This should be impossible. + return Err(Error::internal_error(&format!( + "loading blueprint {}: missing baseboard that we should \ + have fetched: {}", + blueprint_id, row.hw_baseboard_id + ))); + }; + + let update = row.into_generic(baseboard.clone()); + if let Some(previous) = pending_mgs_updates.insert(update) { + // This should be impossible. + return Err(Error::internal_error(&format!( + "blueprint {}: found multiple pending updates for \ + baseboard {:?}", + blueprint_id, previous.baseboard_id + ))); + } + } for row in pending_updates_sp { let Some(baseboard) = baseboards_by_id.get(&row.hw_baseboard_id) else { @@ -1349,6 +1555,7 @@ impl DataStore { nclickhouse_servers, noximeter_policy, npending_mgs_updates_sp, + npending_mgs_updates_rot, ) = self .transaction_retry_wrapper("blueprint_delete") .transaction(&conn, |conn| { @@ -1546,6 +1753,21 @@ impl DataStore { .await? }; + let npending_mgs_updates_rot = { + // Skip rustfmt because it bails out on this long line. + #[rustfmt::skip] + use nexus_db_schema::schema:: + bp_pending_mgs_update_rot::dsl; + diesel::delete( + dsl::bp_pending_mgs_update_rot.filter( + dsl::blueprint_id + .eq(to_db_typed_uuid(blueprint_id)), + ), + ) + .execute_async(&conn) + .await? + }; + Ok(( nblueprints, nsled_metadata, @@ -1558,6 +1780,7 @@ impl DataStore { nclickhouse_servers, noximeter_policy, npending_mgs_updates_sp, + npending_mgs_updates_rot, )) } }) @@ -1580,6 +1803,7 @@ impl DataStore { "nclickhouse_servers" => nclickhouse_servers, "noximeter_policy" => noximeter_policy, "npending_mgs_updates_sp" => npending_mgs_updates_sp, + "npending_mgs_updates_rot" => npending_mgs_updates_rot, ); Ok(()) diff --git a/nexus/db-schema/src/schema.rs b/nexus/db-schema/src/schema.rs index 99285a291f3..1787a1f2e16 100644 --- a/nexus/db-schema/src/schema.rs +++ b/nexus/db-schema/src/schema.rs @@ -2088,6 +2088,23 @@ table! { } } +table! { + bp_pending_mgs_update_rot (blueprint_id, hw_baseboard_id) { + blueprint_id -> Uuid, + hw_baseboard_id -> Uuid, + sp_type -> crate::enums::SpTypeEnum, + sp_slot -> Int4, + artifact_sha256 -> Text, + artifact_version -> Text, + expected_active_slot -> crate::enums::HwRotSlotEnum, + expected_active_version -> Text, + expected_inactive_version -> Nullable, + expected_persistent_boot_preference -> crate::enums::HwRotSlotEnum, + expected_pending_persistent_boot_preference -> Nullable, + expected_transient_boot_preference -> Nullable, + } +} + table! { bootstore_keys (key, generation) { key -> Text, diff --git a/schema/crdb/add-pending-mgs-updates-rot/up.sql b/schema/crdb/add-pending-mgs-updates-rot/up.sql new file mode 100644 index 00000000000..2e0b818e402 --- /dev/null +++ b/schema/crdb/add-pending-mgs-updates-rot/up.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS omicron.public.bp_pending_mgs_update_rot ( + blueprint_id UUID, + hw_baseboard_id UUID NOT NULL, + sp_type omicron.public.sp_type NOT NULL, + sp_slot INT4 NOT NULL, + artifact_sha256 STRING(64) NOT NULL, + artifact_version STRING(64) NOT NULL, + + expected_active_slot omicron.public.hw_rot_slot NOT NULL, + expected_active_version STRING NOT NULL, + expected_inactive_version STRING, + expected_persistent_boot_preference: omicron.public.hw_rot_slot NOT NULL, + expected_pending_persistent_boot_preference: omicron.public.hw_rot_slot, + expected_transient_boot_preference: omicron.public.hw_rot_slot, + + PRIMARY KEY(blueprint_id, hw_baseboard_id) +); diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index 39ea3574f13..44d5e6cec7d 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -4693,6 +4693,31 @@ CREATE TABLE IF NOT EXISTS omicron.public.bp_pending_mgs_update_sp ( PRIMARY KEY(blueprint_id, hw_baseboard_id) ); +-- Blueprint information related to pending RoT upgrades. +CREATE TABLE IF NOT EXISTS omicron.public.bp_pending_mgs_update_rot ( + -- Foreign key into the `blueprint` table + blueprint_id UUID, + -- identify of the device to be updated + -- (foreign key into the `hw_baseboard_id` table) + hw_baseboard_id UUID NOT NULL, + -- location of this device according to MGS + sp_type omicron.public.sp_type NOT NULL, + sp_slot INT4 NOT NULL, + -- artifact to be deployed to this device + artifact_sha256 STRING(64) NOT NULL, + artifact_version STRING(64) NOT NULL, + + -- RoT-specific details + expected_active_slot omicron.public.hw_rot_slot NOT NULL, + expected_active_version STRING NOT NULL, + expected_inactive_version STRING, -- NULL means invalid (no version expected) + expected_persistent_boot_preference: omicron.public.hw_rot_slot NOT NULL, + expected_pending_persistent_boot_preference: omicron.public.hw_rot_slot, + expected_transient_boot_preference: omicron.public.hw_rot_slot, + + PRIMARY KEY(blueprint_id, hw_baseboard_id) +); + -- Mapping of Omicron zone ID to CockroachDB node ID. This isn't directly used -- by the blueprint tables above, but is used by the more general Reconfigurator -- system along with them (e.g., to decommission expunged CRDB nodes). @@ -6198,7 +6223,7 @@ INSERT INTO omicron.public.db_metadata ( version, target_version ) VALUES - (TRUE, NOW(), NOW(), '161.0.0', NULL) + (TRUE, NOW(), NOW(), '162.0.0', NULL) ON CONFLICT DO NOTHING; COMMIT; From 47b24fda6cd4b188878e7e7145105d9a4b8c7281 Mon Sep 17 00:00:00 2001 From: karencfv Date: Tue, 15 Jul 2025 17:20:40 +1200 Subject: [PATCH 2/7] Fix SQL --- schema/crdb/add-pending-mgs-updates-rot/up.sql | 6 +++--- schema/crdb/dbinit.sql | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/schema/crdb/add-pending-mgs-updates-rot/up.sql b/schema/crdb/add-pending-mgs-updates-rot/up.sql index 2e0b818e402..dba08cf1529 100644 --- a/schema/crdb/add-pending-mgs-updates-rot/up.sql +++ b/schema/crdb/add-pending-mgs-updates-rot/up.sql @@ -9,9 +9,9 @@ CREATE TABLE IF NOT EXISTS omicron.public.bp_pending_mgs_update_rot ( expected_active_slot omicron.public.hw_rot_slot NOT NULL, expected_active_version STRING NOT NULL, expected_inactive_version STRING, - expected_persistent_boot_preference: omicron.public.hw_rot_slot NOT NULL, - expected_pending_persistent_boot_preference: omicron.public.hw_rot_slot, - expected_transient_boot_preference: omicron.public.hw_rot_slot, + expected_persistent_boot_preference omicron.public.hw_rot_slot NOT NULL, + expected_pending_persistent_boot_preference omicron.public.hw_rot_slot, + expected_transient_boot_preference omicron.public.hw_rot_slot, PRIMARY KEY(blueprint_id, hw_baseboard_id) ); diff --git a/schema/crdb/dbinit.sql b/schema/crdb/dbinit.sql index 5382313ca30..19648987dde 100644 --- a/schema/crdb/dbinit.sql +++ b/schema/crdb/dbinit.sql @@ -4715,9 +4715,9 @@ CREATE TABLE IF NOT EXISTS omicron.public.bp_pending_mgs_update_rot ( expected_active_slot omicron.public.hw_rot_slot NOT NULL, expected_active_version STRING NOT NULL, expected_inactive_version STRING, -- NULL means invalid (no version expected) - expected_persistent_boot_preference: omicron.public.hw_rot_slot NOT NULL, - expected_pending_persistent_boot_preference: omicron.public.hw_rot_slot, - expected_transient_boot_preference: omicron.public.hw_rot_slot, + expected_persistent_boot_preference omicron.public.hw_rot_slot NOT NULL, + expected_pending_persistent_boot_preference omicron.public.hw_rot_slot, + expected_transient_boot_preference omicron.public.hw_rot_slot, PRIMARY KEY(blueprint_id, hw_baseboard_id) ); From 3c479e5ab86198854895155a2e24da54c7b77cec Mon Sep 17 00:00:00 2001 From: karencfv Date: Tue, 15 Jul 2025 18:17:38 +1200 Subject: [PATCH 3/7] simplify row to PendingMgsUpdate conversion --- nexus/db-model/src/deployment.rs | 28 ++++- .../db-queries/src/db/datastore/deployment.rs | 101 +++++++++--------- 2 files changed, 74 insertions(+), 55 deletions(-) diff --git a/nexus/db-model/src/deployment.rs b/nexus/db-model/src/deployment.rs index 9c8be7ab292..e43d19346f1 100644 --- a/nexus/db-model/src/deployment.rs +++ b/nexus/db-model/src/deployment.rs @@ -1254,6 +1254,18 @@ impl BpOximeterReadPolicy { } } +pub trait BpPendingMgsUpdateComponent { + /// Converts a BpMgsUpdate into a PendingMgsUpdate + fn into_generic( + self, + baseboard_id: Arc< + BaseboardId>, + ) -> PendingMgsUpdate; + + /// Retrieves the baseboard ID + fn hw_baseboard_id(&self) -> &Uuid; +} + #[derive(Queryable, Clone, Debug, Selectable, Insertable)] #[diesel(table_name = bp_pending_mgs_update_sp)] pub struct BpPendingMgsUpdateSp { @@ -1267,8 +1279,12 @@ pub struct BpPendingMgsUpdateSp { pub expected_inactive_version: Option, } -impl BpPendingMgsUpdateSp { - pub fn into_generic( +impl BpPendingMgsUpdateComponent for BpPendingMgsUpdateSp { + fn hw_baseboard_id(&self) -> &Uuid { + &self.hw_baseboard_id + } + + fn into_generic( self, baseboard_id: Arc, ) -> PendingMgsUpdate { @@ -1308,8 +1324,12 @@ pub struct BpPendingMgsUpdateRot { pub expected_transient_boot_preference: Option, } -impl BpPendingMgsUpdateRot { - pub fn into_generic( +impl BpPendingMgsUpdateComponent for BpPendingMgsUpdateRot { + fn hw_baseboard_id(&self) -> &Uuid { + &self.hw_baseboard_id + } + + fn into_generic( self, baseboard_id: Arc, ) -> PendingMgsUpdate { diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index 6d28de12fa5..b70858300f4 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -14,6 +14,7 @@ use async_bb8_diesel::AsyncRunQueryDsl; use chrono::DateTime; use chrono::Utc; use clickhouse_admin_types::{KeeperId, ServerId}; +use nexus_db_model::BpPendingMgsUpdateComponent; use core::future::Future; use core::pin::Pin; use diesel::BoolExpressionMethods; @@ -87,9 +88,11 @@ use omicron_common::api::external::LookupType; use omicron_common::api::external::ResourceType; use omicron_common::bail_unless; use omicron_uuid_kinds::BlueprintUuid; +use omicron_uuid_kinds::BlueprintKind; use omicron_uuid_kinds::GenericUuid; use omicron_uuid_kinds::OmicronZoneUuid; use omicron_uuid_kinds::SledUuid; +use omicron_uuid_kinds::TypedUuid; use std::collections::BTreeMap; use std::collections::BTreeSet; use std::sync::Arc; @@ -1360,13 +1363,13 @@ impl DataStore { } }; - // Load all pending SP updates. + // Load all pending RoT updates. // // Pagination is a little silly here because we will only allow one at a // time in practice for a while, but it's easy enough to do. - let mut pending_updates_sp = Vec::new(); + let mut pending_updates_rot = Vec::new(); { - use nexus_db_schema::schema::bp_pending_mgs_update_sp::dsl; + use nexus_db_schema::schema::bp_pending_mgs_update_rot::dsl; let mut paginator = Paginator::new( SQL_BATCH_SIZE, @@ -1374,12 +1377,12 @@ impl DataStore { ); while let Some(p) = paginator.next() { let batch = paginated( - dsl::bp_pending_mgs_update_sp, + dsl::bp_pending_mgs_update_rot, dsl::hw_baseboard_id, &p.current_pagparams(), ) .filter(dsl::blueprint_id.eq(to_db_typed_uuid(blueprint_id))) - .select(BpPendingMgsUpdateSp::as_select()) + .select(BpPendingMgsUpdateRot::as_select()) .load_async(&*conn) .await .map_err(|e| { @@ -1388,15 +1391,15 @@ impl DataStore { paginator = p.found_batch(&batch, &|d| d.hw_baseboard_id); for row in batch { - pending_updates_sp.push(row); + pending_updates_rot.push(row); } } } - // Load all pending RoT updates. - let mut pending_updates_rot = Vec::new(); + // Load all pending SP updates. + let mut pending_updates_sp = Vec::new(); { - use nexus_db_schema::schema::bp_pending_mgs_update_rot::dsl; + use nexus_db_schema::schema::bp_pending_mgs_update_sp::dsl; let mut paginator = Paginator::new( SQL_BATCH_SIZE, @@ -1404,12 +1407,12 @@ impl DataStore { ); while let Some(p) = paginator.next() { let batch = paginated( - dsl::bp_pending_mgs_update_rot, + dsl::bp_pending_mgs_update_sp, dsl::hw_baseboard_id, &p.current_pagparams(), ) .filter(dsl::blueprint_id.eq(to_db_typed_uuid(blueprint_id))) - .select(BpPendingMgsUpdateRot::as_select()) + .select(BpPendingMgsUpdateSp::as_select()) .load_async(&*conn) .await .map_err(|e| { @@ -1418,10 +1421,10 @@ impl DataStore { paginator = p.found_batch(&batch, &|d| d.hw_baseboard_id); for row in batch { - pending_updates_rot.push(row); + pending_updates_sp.push(row); } } - } + } // Collect the unique baseboard ids referenced by pending updates. let baseboard_id_ids: BTreeSet<_> = @@ -1463,46 +1466,10 @@ impl DataStore { // Combine this information to assemble the set of pending MGS updates. let mut pending_mgs_updates = PendingMgsUpdates::new(); for row in pending_updates_rot { - let Some(baseboard) = baseboards_by_id.get(&row.hw_baseboard_id) - else { - // This should be impossible. - return Err(Error::internal_error(&format!( - "loading blueprint {}: missing baseboard that we should \ - have fetched: {}", - blueprint_id, row.hw_baseboard_id - ))); - }; - - let update = row.into_generic(baseboard.clone()); - if let Some(previous) = pending_mgs_updates.insert(update) { - // This should be impossible. - return Err(Error::internal_error(&format!( - "blueprint {}: found multiple pending updates for \ - baseboard {:?}", - blueprint_id, previous.baseboard_id - ))); - } + process_update_row(row, &baseboards_by_id, &mut pending_mgs_updates, &blueprint_id)?; } for row in pending_updates_sp { - let Some(baseboard) = baseboards_by_id.get(&row.hw_baseboard_id) - else { - // This should be impossible. - return Err(Error::internal_error(&format!( - "loading blueprint {}: missing baseboard that we should \ - have fetched: {}", - blueprint_id, row.hw_baseboard_id - ))); - }; - - let update = row.into_generic(baseboard.clone()); - if let Some(previous) = pending_mgs_updates.insert(update) { - // This should be impossible. - return Err(Error::internal_error(&format!( - "blueprint {}: found multiple pending updates for \ - baseboard {:?}", - blueprint_id, previous.baseboard_id - ))); - } + process_update_row(row, &baseboards_by_id, &mut pending_mgs_updates, &blueprint_id)?; } Ok(Blueprint { @@ -2147,6 +2114,37 @@ impl DataStore { } } +// Helper to process BpPendingMgsUpdateComponent rows +fn process_update_row( + row: T, + baseboards_by_id: &BTreeMap>, + pending_mgs_updates: &mut PendingMgsUpdates, + blueprint_id: &TypedUuid, +) -> Result<(), Error> +where + T: BpPendingMgsUpdateComponent, +{ + let Some(baseboard) = baseboards_by_id.get(row.hw_baseboard_id()) else { + // This should be impossible. + return Err(Error::internal_error(&format!( + "loading blueprint {}: missing baseboard that we should \ + have fetched: {}", + blueprint_id, row.hw_baseboard_id() + ))); + }; + + let update = row.into_generic(baseboard.clone()); + if let Some(previous) = pending_mgs_updates.insert(update) { + // This should be impossible. + return Err(Error::internal_error(&format!( + "blueprint {}: found multiple pending updates for \ + baseboard {:?}", + blueprint_id, previous.baseboard_id + ))); + } + Ok(()) +} + // Helper to create an `authz::Blueprint` for a specific blueprint ID fn authz_blueprint_from_id(blueprint_id: BlueprintUuid) -> authz::Blueprint { let blueprint_id = blueprint_id.into_untyped_uuid(); @@ -2642,6 +2640,7 @@ mod tests { query_count!(bp_clickhouse_server_zone_id_to_node_id, blueprint_id), query_count!(bp_oximeter_read_policy, blueprint_id), query_count!(bp_pending_mgs_update_sp, blueprint_id), + query_count!(bp_pending_mgs_update_rot, blueprint_id), ] { let count: i64 = result.unwrap(); assert_eq!( From e5bf99994e8f13583e3759c364833fa40efaed7e Mon Sep 17 00:00:00 2001 From: karencfv Date: Tue, 15 Jul 2025 18:18:52 +1200 Subject: [PATCH 4/7] fmt --- nexus/db-model/src/deployment.rs | 20 ++++------------ .../db-queries/src/db/datastore/deployment.rs | 23 ++++++++++++++----- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/nexus/db-model/src/deployment.rs b/nexus/db-model/src/deployment.rs index e43d19346f1..bfe2e3c7917 100644 --- a/nexus/db-model/src/deployment.rs +++ b/nexus/db-model/src/deployment.rs @@ -1256,11 +1256,7 @@ impl BpOximeterReadPolicy { pub trait BpPendingMgsUpdateComponent { /// Converts a BpMgsUpdate into a PendingMgsUpdate - fn into_generic( - self, - baseboard_id: Arc< - BaseboardId>, - ) -> PendingMgsUpdate; + fn into_generic(self, baseboard_id: Arc) -> PendingMgsUpdate; /// Retrieves the baseboard ID fn hw_baseboard_id(&self) -> &Uuid; @@ -1281,13 +1277,10 @@ pub struct BpPendingMgsUpdateSp { impl BpPendingMgsUpdateComponent for BpPendingMgsUpdateSp { fn hw_baseboard_id(&self) -> &Uuid { - &self.hw_baseboard_id + &self.hw_baseboard_id } - fn into_generic( - self, - baseboard_id: Arc, - ) -> PendingMgsUpdate { + fn into_generic(self, baseboard_id: Arc) -> PendingMgsUpdate { PendingMgsUpdate { baseboard_id, sp_type: self.sp_type.into(), @@ -1326,13 +1319,10 @@ pub struct BpPendingMgsUpdateRot { impl BpPendingMgsUpdateComponent for BpPendingMgsUpdateRot { fn hw_baseboard_id(&self) -> &Uuid { - &self.hw_baseboard_id + &self.hw_baseboard_id } - fn into_generic( - self, - baseboard_id: Arc, - ) -> PendingMgsUpdate { + fn into_generic(self, baseboard_id: Arc) -> PendingMgsUpdate { PendingMgsUpdate { baseboard_id, sp_type: self.sp_type.into(), diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index b70858300f4..fcb7302d905 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -14,7 +14,6 @@ use async_bb8_diesel::AsyncRunQueryDsl; use chrono::DateTime; use chrono::Utc; use clickhouse_admin_types::{KeeperId, ServerId}; -use nexus_db_model::BpPendingMgsUpdateComponent; use core::future::Future; use core::pin::Pin; use diesel::BoolExpressionMethods; @@ -54,6 +53,7 @@ use nexus_db_model::BpOmicronPhysicalDisk; use nexus_db_model::BpOmicronZone; use nexus_db_model::BpOmicronZoneNic; use nexus_db_model::BpOximeterReadPolicy; +use nexus_db_model::BpPendingMgsUpdateComponent; use nexus_db_model::BpPendingMgsUpdateRot; use nexus_db_model::BpPendingMgsUpdateSp; use nexus_db_model::BpSledMetadata; @@ -87,8 +87,8 @@ use omicron_common::api::external::ListResultVec; use omicron_common::api::external::LookupType; use omicron_common::api::external::ResourceType; use omicron_common::bail_unless; -use omicron_uuid_kinds::BlueprintUuid; use omicron_uuid_kinds::BlueprintKind; +use omicron_uuid_kinds::BlueprintUuid; use omicron_uuid_kinds::GenericUuid; use omicron_uuid_kinds::OmicronZoneUuid; use omicron_uuid_kinds::SledUuid; @@ -1424,7 +1424,7 @@ impl DataStore { pending_updates_sp.push(row); } } - } + } // Collect the unique baseboard ids referenced by pending updates. let baseboard_id_ids: BTreeSet<_> = @@ -1466,10 +1466,20 @@ impl DataStore { // Combine this information to assemble the set of pending MGS updates. let mut pending_mgs_updates = PendingMgsUpdates::new(); for row in pending_updates_rot { - process_update_row(row, &baseboards_by_id, &mut pending_mgs_updates, &blueprint_id)?; + process_update_row( + row, + &baseboards_by_id, + &mut pending_mgs_updates, + &blueprint_id, + )?; } for row in pending_updates_sp { - process_update_row(row, &baseboards_by_id, &mut pending_mgs_updates, &blueprint_id)?; + process_update_row( + row, + &baseboards_by_id, + &mut pending_mgs_updates, + &blueprint_id, + )?; } Ok(Blueprint { @@ -2129,7 +2139,8 @@ where return Err(Error::internal_error(&format!( "loading blueprint {}: missing baseboard that we should \ have fetched: {}", - blueprint_id, row.hw_baseboard_id() + blueprint_id, + row.hw_baseboard_id() ))); }; From b4443fff88400b8d91c8deb88afbd4ddbe982198 Mon Sep 17 00:00:00 2001 From: karencfv Date: Wed, 16 Jul 2025 13:20:47 +1200 Subject: [PATCH 5/7] update representative blueprint test to include RoT --- Cargo.lock | 1 + nexus/db-queries/Cargo.toml | 1 + .../db-queries/src/db/datastore/deployment.rs | 30 +++++++++++++++++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a28ac69b4f..537c0d2dfda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6288,6 +6288,7 @@ dependencies = [ "expectorate", "futures", "gateway-client", + "gateway-types", "hyper-rustls", "id-map", "iddqd", diff --git a/nexus/db-queries/Cargo.toml b/nexus/db-queries/Cargo.toml index 471ab2351b4..b9c3e5098a0 100644 --- a/nexus/db-queries/Cargo.toml +++ b/nexus/db-queries/Cargo.toml @@ -88,6 +88,7 @@ criterion.workspace = true expectorate.workspace = true hyper-rustls.workspace = true gateway-client.workspace = true +gateway-types.workspace = true illumos-utils.workspace = true internal-dns-resolver.workspace = true itertools.workspace = true diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index fcb7302d905..850edbb0376 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -2530,6 +2530,7 @@ mod tests { use crate::db::pub_test_utils::TestDatabase; use crate::db::raw_query_builder::QueryBuilder; + use gateway_types::rot::RotSlot; use nexus_inventory::CollectionBuilder; use nexus_inventory::now_db_precision; use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; @@ -2543,6 +2544,7 @@ mod tests { use nexus_types::deployment::BlueprintZoneImageSource; use nexus_types::deployment::BlueprintZoneImageVersion; use nexus_types::deployment::BlueprintZoneType; + use nexus_types::deployment::ExpectedActiveRotSlot; use nexus_types::deployment::OmicronZoneExternalFloatingIp; use nexus_types::deployment::PendingMgsUpdate; use nexus_types::deployment::PlanningInput; @@ -2918,6 +2920,7 @@ mod tests { .zpools; // Create a builder for a child blueprint. + // TODO-K: This is what I want let mut builder = BlueprintBuilder::new_based_on( &logctx.log, &blueprint1, @@ -3167,15 +3170,36 @@ mod tests { // blueprint2 is more interesting in terms of containing a variety of // different blueprint structures. We want to try deleting that. To do // that, we have to create a new blueprint and make that one the target. - let blueprint3 = BlueprintBuilder::new_based_on( + let mut builder = BlueprintBuilder::new_based_on( &logctx.log, &blueprint2, &planning_input, &collection, "dummy", ) - .expect("failed to create builder") - .build(); + .expect("failed to create builder"); + + // Configure an RoT update + let (baseboard_id, sp) = + collection.sps.iter().next().expect("at least one SP"); + builder.pending_mgs_update_insert(PendingMgsUpdate { + baseboard_id: baseboard_id.clone(), + sp_type: sp.sp_type, + slot_id: sp.sp_slot, + details: PendingMgsUpdateDetails::Rot { + expected_active_slot: ExpectedActiveRotSlot { + slot: RotSlot::A, + version: "1.0.0".parse().unwrap(), + }, + expected_inactive_version: ExpectedVersion::NoValidVersion, + expected_persistent_boot_preference: RotSlot::A, + expected_pending_persistent_boot_preference: None, + expected_transient_boot_preference: None, + }, + artifact_hash: ArtifactHash([72; 32]), + artifact_version: "2.0.0".parse().unwrap(), + }); + let blueprint3 = builder.build(); datastore .blueprint_insert(&opctx, &blueprint3) .await From 3180eae24c220f3cb9e2eb710b179599eb580bf3 Mon Sep 17 00:00:00 2001 From: karencfv Date: Wed, 16 Jul 2025 13:35:57 +1200 Subject: [PATCH 6/7] fmt --- nexus/db-model/src/deployment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nexus/db-model/src/deployment.rs b/nexus/db-model/src/deployment.rs index b865d66d5c6..8daf9cb9ede 100644 --- a/nexus/db-model/src/deployment.rs +++ b/nexus/db-model/src/deployment.rs @@ -36,13 +36,13 @@ use nexus_types::deployment::BlueprintZoneDisposition; use nexus_types::deployment::BlueprintZoneType; use nexus_types::deployment::ClickhouseClusterConfig; use nexus_types::deployment::CockroachDbPreserveDowngrade; +use nexus_types::deployment::ExpectedActiveRotSlot; use nexus_types::deployment::PendingMgsUpdate; use nexus_types::deployment::PendingMgsUpdateDetails; use nexus_types::deployment::{ BlueprintArtifactVersion, BlueprintDatasetConfig, OximeterReadMode, }; use nexus_types::deployment::{BlueprintDatasetDisposition, ExpectedVersion}; -use nexus_types::deployment::ExpectedActiveRotSlot; use nexus_types::deployment::{BlueprintZoneImageSource, blueprint_zone_type}; use nexus_types::deployment::{ OmicronZoneExternalFloatingAddr, OmicronZoneExternalFloatingIp, From 5afeeca55dc7403717970a514c4d40de6f202c25 Mon Sep 17 00:00:00 2001 From: karencfv Date: Wed, 16 Jul 2025 16:52:08 +1200 Subject: [PATCH 7/7] clean up --- nexus/db-queries/src/db/datastore/deployment.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index e879abb3e45..905b37c11d9 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -2972,7 +2972,6 @@ mod tests { .zpools; // Create a builder for a child blueprint. - // TODO-K: This is what I want let mut builder = BlueprintBuilder::new_based_on( &logctx.log, &blueprint1,