Skip to content

[reconfigurator] database support for PendingMgsUpdate for RoT bootloader #8606

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -393,4 +393,8 @@ KEEPER MEMBERSHIP
no membership retrieved


COCKROACH STATUS
no cockroach status retrieved



42 changes: 41 additions & 1 deletion nexus/db-model/src/deployment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ use nexus_db_schema::schema::{
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_rot,
bp_pending_mgs_update_sp, bp_sled_metadata, bp_target,
bp_pending_mgs_update_rot_bootloader, bp_pending_mgs_update_sp,
bp_sled_metadata, bp_target,
};
use nexus_sled_agent_shared::inventory::OmicronZoneDataset;
use nexus_types::deployment::BlueprintHostPhase2DesiredContents;
Expand Down Expand Up @@ -1311,6 +1312,45 @@ pub trait BpPendingMgsUpdateComponent {
fn hw_baseboard_id(&self) -> &Uuid;
}

#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
#[diesel(table_name = bp_pending_mgs_update_rot_bootloader)]
pub struct BpPendingMgsUpdateRotBootloader {
pub blueprint_id: DbTypedUuid<BlueprintKind>,
pub hw_baseboard_id: Uuid,
pub sp_type: SpType,
pub sp_slot: SpMgsSlot,
pub artifact_sha256: ArtifactHash,
pub artifact_version: DbArtifactVersion,
pub expected_stage0_version: DbArtifactVersion,
pub expected_stage0_next_version: Option<DbArtifactVersion>,
}

impl BpPendingMgsUpdateComponent for BpPendingMgsUpdateRotBootloader {
fn hw_baseboard_id(&self) -> &Uuid {
&self.hw_baseboard_id
}

fn into_generic(self, baseboard_id: Arc<BaseboardId>) -> 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::RotBootloader {
expected_stage0_version: (*self.expected_stage0_version)
.clone(),
expected_stage0_next_version: match self
.expected_stage0_next_version
{
Some(v) => ExpectedVersion::Version((*v).clone()),
None => ExpectedVersion::NoValidVersion,
},
},
}
}
}

#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
#[diesel(table_name = bp_pending_mgs_update_sp)]
pub struct BpPendingMgsUpdateSp {
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(168, 0, 0);
pub const SCHEMA_VERSION: Version = Version::new(169, 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(169, "add-pending-mgs-updates-rot-bootloader"),
KnownVersion::new(168, "add-inv-host-phase-1-flash-hash"),
KnownVersion::new(167, "add-pending-mgs-updates-rot"),
KnownVersion::new(166, "bundle-user-comment"),
Expand Down
222 changes: 218 additions & 4 deletions nexus/db-queries/src/db/datastore/deployment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ use nexus_db_model::BpOmicronZoneNic;
use nexus_db_model::BpOximeterReadPolicy;
use nexus_db_model::BpPendingMgsUpdateComponent;
use nexus_db_model::BpPendingMgsUpdateRot;
use nexus_db_model::BpPendingMgsUpdateRotBootloader;
use nexus_db_model::BpPendingMgsUpdateSp;
use nexus_db_model::BpSledMetadata;
use nexus_db_model::BpTarget;
Expand Down Expand Up @@ -807,9 +808,121 @@ impl DataStore {
_expected_transient_boot_preference,
) = update_dsl::bp_pending_mgs_update_rot::all_columns();
},
PendingMgsUpdateDetails::RotBootloader {
..
} => continue, // TODO: Implement.
PendingMgsUpdateDetails::RotBootloader { expected_stage0_version, expected_stage0_next_version } => {
let db_blueprint_id = DbTypedUuid::from(
blueprint_id
).into_sql::<diesel::sql_types::Uuid>();
let db_sp_type =
SpType::from(update.sp_type).into_sql::<SpTypeEnum>();
let db_slot_id =
SpMgsSlot::from(SqlU16::from(update.slot_id))
.into_sql::<diesel::sql_types::Int4>();
let db_artifact_hash =
ArtifactHash::from(update.artifact_hash)
.into_sql::<diesel::sql_types::Text>();
let db_artifact_version = DbArtifactVersion::from(
update.artifact_version.clone(),
)
.into_sql::<diesel::sql_types::Text>();
let db_expected_stage0_version = DbArtifactVersion::from(
expected_stage0_version.clone(),
)
.into_sql::<diesel::sql_types::Text>();
let db_expected_stage0_next_version =
match expected_stage0_next_version {
ExpectedVersion::NoValidVersion => None,
ExpectedVersion::Version(v) => {
Some(DbArtifactVersion::from(v.clone()))
}
}
.into_sql::<Nullable<diesel::sql_types::Text>>();

// 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_bootloader::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_stage0_version,
db_expected_stage0_next_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_rot_bootloader,
)
.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_stage0_version,
update_dsl::expected_stage0_next_version,
))
.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_bootloader (aborting transaction)";
"count" => count,
&update.baseboard_id,
);
return Err(TxnError::BadInsertCount {
table_name: "bp_pending_mgs_update_rot_bootloader",
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_rot_bootloader` 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_bootloader`
// 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_stage0_version,
_expected_stage0_next_version,
) = update_dsl::bp_pending_mgs_update_rot_bootloader::all_columns();
}
};
}

Expand Down Expand Up @@ -1413,10 +1526,40 @@ impl DataStore {
}
};

// Load all pending RoT updates.
// Load all pending RoT bootloader 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_rot_bootloader = Vec::new();
{
use nexus_db_schema::schema::bp_pending_mgs_update_rot_bootloader::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_bootloader,
dsl::hw_baseboard_id,
&p.current_pagparams(),
)
.filter(dsl::blueprint_id.eq(to_db_typed_uuid(blueprint_id)))
.select(BpPendingMgsUpdateRotBootloader::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_bootloader.push(row);
}
}
}

// Load all pending RoT updates.
let mut pending_updates_rot = Vec::new();
{
use nexus_db_schema::schema::bp_pending_mgs_update_rot::dsl;
Expand Down Expand Up @@ -1515,6 +1658,14 @@ 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_bootloader {
process_update_row(
row,
&baseboards_by_id,
&mut pending_mgs_updates,
&blueprint_id,
)?;
}
for row in pending_updates_rot {
process_update_row(
row,
Expand Down Expand Up @@ -1583,6 +1734,7 @@ impl DataStore {
noximeter_policy,
npending_mgs_updates_sp,
npending_mgs_updates_rot,
npending_mgs_updates_rot_bootloader,
) = self
.transaction_retry_wrapper("blueprint_delete")
.transaction(&conn, |conn| {
Expand Down Expand Up @@ -1795,6 +1947,21 @@ impl DataStore {
.await?
};

let npending_mgs_updates_rot_bootloader = {
// Skip rustfmt because it bails out on this long line.
#[rustfmt::skip]
use nexus_db_schema::schema::
bp_pending_mgs_update_rot_bootloader::dsl;
diesel::delete(
dsl::bp_pending_mgs_update_rot_bootloader.filter(
dsl::blueprint_id
.eq(to_db_typed_uuid(blueprint_id)),
),
)
.execute_async(&conn)
.await?
};

Ok((
nblueprints,
nsled_metadata,
Expand All @@ -1808,6 +1975,7 @@ impl DataStore {
noximeter_policy,
npending_mgs_updates_sp,
npending_mgs_updates_rot,
npending_mgs_updates_rot_bootloader,
))
}
})
Expand All @@ -1831,6 +1999,8 @@ impl DataStore {
"noximeter_policy" => noximeter_policy,
"npending_mgs_updates_sp" => npending_mgs_updates_sp,
"npending_mgs_updates_rot" => npending_mgs_updates_rot,
"npending_mgs_updates_rot_bootloader" =>
npending_mgs_updates_rot_bootloader,
);

Ok(())
Expand Down Expand Up @@ -2706,6 +2876,7 @@ mod tests {
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),
query_count!(bp_pending_mgs_update_rot_bootloader, blueprint_id),
] {
let count: i64 = result.unwrap();
assert_eq!(
Expand Down Expand Up @@ -3343,6 +3514,7 @@ mod tests {
artifact_version: "2.0.0".parse().unwrap(),
});
let blueprint3 = builder.build();
let authz_blueprint3 = authz_blueprint_from_id(blueprint3.id);
datastore
.blueprint_insert(&opctx, &blueprint3)
.await
Expand All @@ -3359,6 +3531,48 @@ mod tests {
datastore.blueprint_delete(&opctx, &authz_blueprint2).await.unwrap();
ensure_blueprint_fully_deleted(&datastore, blueprint2.id).await;

// We now make sure we can build and insert a blueprint containing an
// RoT bootloader Pending MGS update
let mut builder = BlueprintBuilder::new_based_on(
&logctx.log,
&blueprint3,
&planning_input,
&collection,
"dummy",
)
.expect("failed to create builder");

// Configure an RoT bootloader 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::RotBootloader {
expected_stage0_version: "1.0.0".parse().unwrap(),
expected_stage0_next_version: ExpectedVersion::NoValidVersion,
},
artifact_hash: ArtifactHash([72; 32]),
artifact_version: "2.0.0".parse().unwrap(),
});
let blueprint4 = builder.build();
datastore
.blueprint_insert(&opctx, &blueprint4)
.await
.expect("failed to insert blueprint");
let bp4_target = BlueprintTarget {
target_id: blueprint4.id,
enabled: true,
time_made_target: now_db_precision(),
};
datastore
.blueprint_target_set_current(&opctx, bp4_target)
.await
.unwrap();
datastore.blueprint_delete(&opctx, &authz_blueprint3).await.unwrap();
ensure_blueprint_fully_deleted(&datastore, blueprint3.id).await;

// Clean up.
db.terminate().await;
logctx.cleanup_successful();
Expand Down
Loading
Loading