Skip to content

Commit 663a8a4

Browse files
authored
[reconfigurator] database support for PendingMgsUpdate for RoT bootloader (#8606)
1 parent 0ab176f commit 663a8a4

File tree

6 files changed

+308
-7
lines changed

6 files changed

+308
-7
lines changed

nexus/db-model/src/deployment.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ use nexus_db_schema::schema::{
2323
bp_clickhouse_server_zone_id_to_node_id, bp_omicron_dataset,
2424
bp_omicron_physical_disk, bp_omicron_zone, bp_omicron_zone_nic,
2525
bp_oximeter_read_policy, bp_pending_mgs_update_rot,
26-
bp_pending_mgs_update_sp, bp_sled_metadata, bp_target,
26+
bp_pending_mgs_update_rot_bootloader, bp_pending_mgs_update_sp,
27+
bp_sled_metadata, bp_target,
2728
};
2829
use nexus_sled_agent_shared::inventory::OmicronZoneDataset;
2930
use nexus_types::deployment::BlueprintHostPhase2DesiredContents;
@@ -1311,6 +1312,45 @@ pub trait BpPendingMgsUpdateComponent {
13111312
fn hw_baseboard_id(&self) -> &Uuid;
13121313
}
13131314

1315+
#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
1316+
#[diesel(table_name = bp_pending_mgs_update_rot_bootloader)]
1317+
pub struct BpPendingMgsUpdateRotBootloader {
1318+
pub blueprint_id: DbTypedUuid<BlueprintKind>,
1319+
pub hw_baseboard_id: Uuid,
1320+
pub sp_type: SpType,
1321+
pub sp_slot: SpMgsSlot,
1322+
pub artifact_sha256: ArtifactHash,
1323+
pub artifact_version: DbArtifactVersion,
1324+
pub expected_stage0_version: DbArtifactVersion,
1325+
pub expected_stage0_next_version: Option<DbArtifactVersion>,
1326+
}
1327+
1328+
impl BpPendingMgsUpdateComponent for BpPendingMgsUpdateRotBootloader {
1329+
fn hw_baseboard_id(&self) -> &Uuid {
1330+
&self.hw_baseboard_id
1331+
}
1332+
1333+
fn into_generic(self, baseboard_id: Arc<BaseboardId>) -> PendingMgsUpdate {
1334+
PendingMgsUpdate {
1335+
baseboard_id,
1336+
sp_type: self.sp_type.into(),
1337+
slot_id: **self.sp_slot,
1338+
artifact_hash: self.artifact_sha256.into(),
1339+
artifact_version: (*self.artifact_version).clone(),
1340+
details: PendingMgsUpdateDetails::RotBootloader {
1341+
expected_stage0_version: (*self.expected_stage0_version)
1342+
.clone(),
1343+
expected_stage0_next_version: match self
1344+
.expected_stage0_next_version
1345+
{
1346+
Some(v) => ExpectedVersion::Version((*v).clone()),
1347+
None => ExpectedVersion::NoValidVersion,
1348+
},
1349+
},
1350+
}
1351+
}
1352+
}
1353+
13141354
#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
13151355
#[diesel(table_name = bp_pending_mgs_update_sp)]
13161356
pub struct BpPendingMgsUpdateSp {

nexus/db-model/src/schema_versions.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::{collections::BTreeMap, sync::LazyLock};
1616
///
1717
/// This must be updated when you change the database schema. Refer to
1818
/// schema/crdb/README.adoc in the root of this repository for details.
19-
pub const SCHEMA_VERSION: Version = Version::new(169, 0, 0);
19+
pub const SCHEMA_VERSION: Version = Version::new(170, 0, 0);
2020

2121
/// List of all past database schema versions, in *reverse* order
2222
///
@@ -28,6 +28,7 @@ static KNOWN_VERSIONS: LazyLock<Vec<KnownVersion>> = LazyLock::new(|| {
2828
// | leaving the first copy as an example for the next person.
2929
// v
3030
// KnownVersion::new(next_int, "unique-dirname-with-the-sql-files"),
31+
KnownVersion::new(170, "add-pending-mgs-updates-rot-bootloader"),
3132
KnownVersion::new(169, "inv-ntp-timesync"),
3233
KnownVersion::new(168, "add-inv-host-phase-1-flash-hash"),
3334
KnownVersion::new(167, "add-pending-mgs-updates-rot"),

nexus/db-queries/src/db/datastore/deployment.rs

Lines changed: 218 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ use nexus_db_model::BpOmicronZoneNic;
5555
use nexus_db_model::BpOximeterReadPolicy;
5656
use nexus_db_model::BpPendingMgsUpdateComponent;
5757
use nexus_db_model::BpPendingMgsUpdateRot;
58+
use nexus_db_model::BpPendingMgsUpdateRotBootloader;
5859
use nexus_db_model::BpPendingMgsUpdateSp;
5960
use nexus_db_model::BpSledMetadata;
6061
use nexus_db_model::BpTarget;
@@ -807,9 +808,121 @@ impl DataStore {
807808
_expected_transient_boot_preference,
808809
) = update_dsl::bp_pending_mgs_update_rot::all_columns();
809810
},
810-
PendingMgsUpdateDetails::RotBootloader {
811-
..
812-
} => continue, // TODO: Implement.
811+
PendingMgsUpdateDetails::RotBootloader { expected_stage0_version, expected_stage0_next_version } => {
812+
let db_blueprint_id = DbTypedUuid::from(
813+
blueprint_id
814+
).into_sql::<diesel::sql_types::Uuid>();
815+
let db_sp_type =
816+
SpType::from(update.sp_type).into_sql::<SpTypeEnum>();
817+
let db_slot_id =
818+
SpMgsSlot::from(SqlU16::from(update.slot_id))
819+
.into_sql::<diesel::sql_types::Int4>();
820+
let db_artifact_hash =
821+
ArtifactHash::from(update.artifact_hash)
822+
.into_sql::<diesel::sql_types::Text>();
823+
let db_artifact_version = DbArtifactVersion::from(
824+
update.artifact_version.clone(),
825+
)
826+
.into_sql::<diesel::sql_types::Text>();
827+
let db_expected_stage0_version = DbArtifactVersion::from(
828+
expected_stage0_version.clone(),
829+
)
830+
.into_sql::<diesel::sql_types::Text>();
831+
let db_expected_stage0_next_version =
832+
match expected_stage0_next_version {
833+
ExpectedVersion::NoValidVersion => None,
834+
ExpectedVersion::Version(v) => {
835+
Some(DbArtifactVersion::from(v.clone()))
836+
}
837+
}
838+
.into_sql::<Nullable<diesel::sql_types::Text>>();
839+
840+
// Skip formatting several lines to prevent rustfmt bailing
841+
// out.
842+
#[rustfmt::skip]
843+
use nexus_db_schema::schema::hw_baseboard_id::dsl
844+
as baseboard_dsl;
845+
#[rustfmt::skip]
846+
use nexus_db_schema::schema::bp_pending_mgs_update_rot_bootloader::dsl
847+
as update_dsl;
848+
let selection =
849+
nexus_db_schema::schema::hw_baseboard_id::table
850+
.select((
851+
db_blueprint_id,
852+
baseboard_dsl::id,
853+
db_sp_type,
854+
db_slot_id,
855+
db_artifact_hash,
856+
db_artifact_version,
857+
db_expected_stage0_version,
858+
db_expected_stage0_next_version,
859+
))
860+
.filter(
861+
baseboard_dsl::part_number.eq(update
862+
.baseboard_id
863+
.part_number
864+
.clone()),
865+
)
866+
.filter(
867+
baseboard_dsl::serial_number.eq(update
868+
.baseboard_id
869+
.serial_number
870+
.clone()),
871+
);
872+
let count = diesel::insert_into(
873+
update_dsl::bp_pending_mgs_update_rot_bootloader,
874+
)
875+
.values(selection)
876+
.into_columns((
877+
update_dsl::blueprint_id,
878+
update_dsl::hw_baseboard_id,
879+
update_dsl::sp_type,
880+
update_dsl::sp_slot,
881+
update_dsl::artifact_sha256,
882+
update_dsl::artifact_version,
883+
update_dsl::expected_stage0_version,
884+
update_dsl::expected_stage0_next_version,
885+
))
886+
.execute_async(&conn)
887+
.await?;
888+
if count != 1 {
889+
// As with `PendingMgsUpdateDetails::Sp`, this
890+
// should be impossible in practice.
891+
error!(&opctx.log,
892+
"blueprint insertion: unexpectedly tried to \
893+
insert wrong number of rows into \
894+
bp_pending_mgs_update_rot_bootloader (aborting transaction)";
895+
"count" => count,
896+
&update.baseboard_id,
897+
);
898+
return Err(TxnError::BadInsertCount {
899+
table_name: "bp_pending_mgs_update_rot_bootloader",
900+
count,
901+
baseboard_id: update.baseboard_id.clone(),
902+
});
903+
}
904+
905+
// This statement is just here to force a compilation
906+
// error if the set of columns in
907+
// `bp_pending_mgs_update_rot_bootloader` changes because that
908+
// will affect the correctness of the above
909+
// statement.
910+
//
911+
// If you're here because of a compile error, you
912+
// might be changing the `bp_pending_mgs_update_rot_bootloader`
913+
// table. Update the statement below and be sure to
914+
// update the code above, too!
915+
let (
916+
_blueprint_id,
917+
_hw_baseboard_id,
918+
_sp_type,
919+
_sp_slot,
920+
_artifact_sha256,
921+
_artifact_version,
922+
_expected_stage0_version,
923+
_expected_stage0_next_version,
924+
) = update_dsl::bp_pending_mgs_update_rot_bootloader::all_columns();
925+
}
813926
};
814927
}
815928

@@ -1413,10 +1526,40 @@ impl DataStore {
14131526
}
14141527
};
14151528

1416-
// Load all pending RoT updates.
1529+
// Load all pending RoT bootloader updates.
14171530
//
14181531
// Pagination is a little silly here because we will only allow one at a
14191532
// time in practice for a while, but it's easy enough to do.
1533+
let mut pending_updates_rot_bootloader = Vec::new();
1534+
{
1535+
use nexus_db_schema::schema::bp_pending_mgs_update_rot_bootloader::dsl;
1536+
1537+
let mut paginator = Paginator::new(
1538+
SQL_BATCH_SIZE,
1539+
dropshot::PaginationOrder::Ascending,
1540+
);
1541+
while let Some(p) = paginator.next() {
1542+
let batch = paginated(
1543+
dsl::bp_pending_mgs_update_rot_bootloader,
1544+
dsl::hw_baseboard_id,
1545+
&p.current_pagparams(),
1546+
)
1547+
.filter(dsl::blueprint_id.eq(to_db_typed_uuid(blueprint_id)))
1548+
.select(BpPendingMgsUpdateRotBootloader::as_select())
1549+
.load_async(&*conn)
1550+
.await
1551+
.map_err(|e| {
1552+
public_error_from_diesel(e, ErrorHandler::Server)
1553+
})?;
1554+
1555+
paginator = p.found_batch(&batch, &|d| d.hw_baseboard_id);
1556+
for row in batch {
1557+
pending_updates_rot_bootloader.push(row);
1558+
}
1559+
}
1560+
}
1561+
1562+
// Load all pending RoT updates.
14201563
let mut pending_updates_rot = Vec::new();
14211564
{
14221565
use nexus_db_schema::schema::bp_pending_mgs_update_rot::dsl;
@@ -1515,6 +1658,14 @@ impl DataStore {
15151658

15161659
// Combine this information to assemble the set of pending MGS updates.
15171660
let mut pending_mgs_updates = PendingMgsUpdates::new();
1661+
for row in pending_updates_rot_bootloader {
1662+
process_update_row(
1663+
row,
1664+
&baseboards_by_id,
1665+
&mut pending_mgs_updates,
1666+
&blueprint_id,
1667+
)?;
1668+
}
15181669
for row in pending_updates_rot {
15191670
process_update_row(
15201671
row,
@@ -1583,6 +1734,7 @@ impl DataStore {
15831734
noximeter_policy,
15841735
npending_mgs_updates_sp,
15851736
npending_mgs_updates_rot,
1737+
npending_mgs_updates_rot_bootloader,
15861738
) = self
15871739
.transaction_retry_wrapper("blueprint_delete")
15881740
.transaction(&conn, |conn| {
@@ -1795,6 +1947,21 @@ impl DataStore {
17951947
.await?
17961948
};
17971949

1950+
let npending_mgs_updates_rot_bootloader = {
1951+
// Skip rustfmt because it bails out on this long line.
1952+
#[rustfmt::skip]
1953+
use nexus_db_schema::schema::
1954+
bp_pending_mgs_update_rot_bootloader::dsl;
1955+
diesel::delete(
1956+
dsl::bp_pending_mgs_update_rot_bootloader.filter(
1957+
dsl::blueprint_id
1958+
.eq(to_db_typed_uuid(blueprint_id)),
1959+
),
1960+
)
1961+
.execute_async(&conn)
1962+
.await?
1963+
};
1964+
17981965
Ok((
17991966
nblueprints,
18001967
nsled_metadata,
@@ -1808,6 +1975,7 @@ impl DataStore {
18081975
noximeter_policy,
18091976
npending_mgs_updates_sp,
18101977
npending_mgs_updates_rot,
1978+
npending_mgs_updates_rot_bootloader,
18111979
))
18121980
}
18131981
})
@@ -1831,6 +1999,8 @@ impl DataStore {
18311999
"noximeter_policy" => noximeter_policy,
18322000
"npending_mgs_updates_sp" => npending_mgs_updates_sp,
18332001
"npending_mgs_updates_rot" => npending_mgs_updates_rot,
2002+
"npending_mgs_updates_rot_bootloader" =>
2003+
npending_mgs_updates_rot_bootloader,
18342004
);
18352005

18362006
Ok(())
@@ -2706,6 +2876,7 @@ mod tests {
27062876
query_count!(bp_oximeter_read_policy, blueprint_id),
27072877
query_count!(bp_pending_mgs_update_sp, blueprint_id),
27082878
query_count!(bp_pending_mgs_update_rot, blueprint_id),
2879+
query_count!(bp_pending_mgs_update_rot_bootloader, blueprint_id),
27092880
] {
27102881
let count: i64 = result.unwrap();
27112882
assert_eq!(
@@ -3343,6 +3514,7 @@ mod tests {
33433514
artifact_version: "2.0.0".parse().unwrap(),
33443515
});
33453516
let blueprint3 = builder.build();
3517+
let authz_blueprint3 = authz_blueprint_from_id(blueprint3.id);
33463518
datastore
33473519
.blueprint_insert(&opctx, &blueprint3)
33483520
.await
@@ -3359,6 +3531,48 @@ mod tests {
33593531
datastore.blueprint_delete(&opctx, &authz_blueprint2).await.unwrap();
33603532
ensure_blueprint_fully_deleted(&datastore, blueprint2.id).await;
33613533

3534+
// We now make sure we can build and insert a blueprint containing an
3535+
// RoT bootloader Pending MGS update
3536+
let mut builder = BlueprintBuilder::new_based_on(
3537+
&logctx.log,
3538+
&blueprint3,
3539+
&planning_input,
3540+
&collection,
3541+
"dummy",
3542+
)
3543+
.expect("failed to create builder");
3544+
3545+
// Configure an RoT bootloader update
3546+
let (baseboard_id, sp) =
3547+
collection.sps.iter().next().expect("at least one SP");
3548+
builder.pending_mgs_update_insert(PendingMgsUpdate {
3549+
baseboard_id: baseboard_id.clone(),
3550+
sp_type: sp.sp_type,
3551+
slot_id: sp.sp_slot,
3552+
details: PendingMgsUpdateDetails::RotBootloader {
3553+
expected_stage0_version: "1.0.0".parse().unwrap(),
3554+
expected_stage0_next_version: ExpectedVersion::NoValidVersion,
3555+
},
3556+
artifact_hash: ArtifactHash([72; 32]),
3557+
artifact_version: "2.0.0".parse().unwrap(),
3558+
});
3559+
let blueprint4 = builder.build();
3560+
datastore
3561+
.blueprint_insert(&opctx, &blueprint4)
3562+
.await
3563+
.expect("failed to insert blueprint");
3564+
let bp4_target = BlueprintTarget {
3565+
target_id: blueprint4.id,
3566+
enabled: true,
3567+
time_made_target: now_db_precision(),
3568+
};
3569+
datastore
3570+
.blueprint_target_set_current(&opctx, bp4_target)
3571+
.await
3572+
.unwrap();
3573+
datastore.blueprint_delete(&opctx, &authz_blueprint3).await.unwrap();
3574+
ensure_blueprint_fully_deleted(&datastore, blueprint3.id).await;
3575+
33623576
// Clean up.
33633577
db.terminate().await;
33643578
logctx.cleanup_successful();

nexus/db-schema/src/schema.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,6 +2101,19 @@ table! {
21012101
}
21022102
}
21032103

2104+
table! {
2105+
bp_pending_mgs_update_rot_bootloader (blueprint_id, hw_baseboard_id) {
2106+
blueprint_id -> Uuid,
2107+
hw_baseboard_id -> Uuid,
2108+
sp_type -> crate::enums::SpTypeEnum,
2109+
sp_slot -> Int4,
2110+
artifact_sha256 -> Text,
2111+
artifact_version -> Text,
2112+
expected_stage0_version -> Text,
2113+
expected_stage0_next_version -> Nullable<Text>,
2114+
}
2115+
}
2116+
21042117
table! {
21052118
bp_pending_mgs_update_sp (blueprint_id, hw_baseboard_id) {
21062119
blueprint_id -> Uuid,

0 commit comments

Comments
 (0)