@@ -55,6 +55,7 @@ use nexus_db_model::BpOmicronZoneNic;
55
55
use nexus_db_model:: BpOximeterReadPolicy ;
56
56
use nexus_db_model:: BpPendingMgsUpdateComponent ;
57
57
use nexus_db_model:: BpPendingMgsUpdateRot ;
58
+ use nexus_db_model:: BpPendingMgsUpdateRotBootloader ;
58
59
use nexus_db_model:: BpPendingMgsUpdateSp ;
59
60
use nexus_db_model:: BpSledMetadata ;
60
61
use nexus_db_model:: BpTarget ;
@@ -807,9 +808,121 @@ impl DataStore {
807
808
_expected_transient_boot_preference,
808
809
) = update_dsl:: bp_pending_mgs_update_rot:: all_columns ( ) ;
809
810
} ,
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
+ }
813
926
} ;
814
927
}
815
928
@@ -1413,10 +1526,40 @@ impl DataStore {
1413
1526
}
1414
1527
} ;
1415
1528
1416
- // Load all pending RoT updates.
1529
+ // Load all pending RoT bootloader updates.
1417
1530
//
1418
1531
// Pagination is a little silly here because we will only allow one at a
1419
1532
// 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.
1420
1563
let mut pending_updates_rot = Vec :: new ( ) ;
1421
1564
{
1422
1565
use nexus_db_schema:: schema:: bp_pending_mgs_update_rot:: dsl;
@@ -1515,6 +1658,14 @@ impl DataStore {
1515
1658
1516
1659
// Combine this information to assemble the set of pending MGS updates.
1517
1660
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
+ }
1518
1669
for row in pending_updates_rot {
1519
1670
process_update_row (
1520
1671
row,
@@ -1583,6 +1734,7 @@ impl DataStore {
1583
1734
noximeter_policy,
1584
1735
npending_mgs_updates_sp,
1585
1736
npending_mgs_updates_rot,
1737
+ npending_mgs_updates_rot_bootloader,
1586
1738
) = self
1587
1739
. transaction_retry_wrapper ( "blueprint_delete" )
1588
1740
. transaction ( & conn, |conn| {
@@ -1795,6 +1947,21 @@ impl DataStore {
1795
1947
. await ?
1796
1948
} ;
1797
1949
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
+
1798
1965
Ok ( (
1799
1966
nblueprints,
1800
1967
nsled_metadata,
@@ -1808,6 +1975,7 @@ impl DataStore {
1808
1975
noximeter_policy,
1809
1976
npending_mgs_updates_sp,
1810
1977
npending_mgs_updates_rot,
1978
+ npending_mgs_updates_rot_bootloader,
1811
1979
) )
1812
1980
}
1813
1981
} )
@@ -1831,6 +1999,8 @@ impl DataStore {
1831
1999
"noximeter_policy" => noximeter_policy,
1832
2000
"npending_mgs_updates_sp" => npending_mgs_updates_sp,
1833
2001
"npending_mgs_updates_rot" => npending_mgs_updates_rot,
2002
+ "npending_mgs_updates_rot_bootloader" =>
2003
+ npending_mgs_updates_rot_bootloader,
1834
2004
) ;
1835
2005
1836
2006
Ok ( ( ) )
@@ -2706,6 +2876,7 @@ mod tests {
2706
2876
query_count ! ( bp_oximeter_read_policy, blueprint_id) ,
2707
2877
query_count ! ( bp_pending_mgs_update_sp, blueprint_id) ,
2708
2878
query_count ! ( bp_pending_mgs_update_rot, blueprint_id) ,
2879
+ query_count ! ( bp_pending_mgs_update_rot_bootloader, blueprint_id) ,
2709
2880
] {
2710
2881
let count: i64 = result. unwrap ( ) ;
2711
2882
assert_eq ! (
@@ -3343,6 +3514,7 @@ mod tests {
3343
3514
artifact_version : "2.0.0" . parse ( ) . unwrap ( ) ,
3344
3515
} ) ;
3345
3516
let blueprint3 = builder. build ( ) ;
3517
+ let authz_blueprint3 = authz_blueprint_from_id ( blueprint3. id ) ;
3346
3518
datastore
3347
3519
. blueprint_insert ( & opctx, & blueprint3)
3348
3520
. await
@@ -3359,6 +3531,48 @@ mod tests {
3359
3531
datastore. blueprint_delete ( & opctx, & authz_blueprint2) . await . unwrap ( ) ;
3360
3532
ensure_blueprint_fully_deleted ( & datastore, blueprint2. id ) . await ;
3361
3533
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
+
3362
3576
// Clean up.
3363
3577
db. terminate ( ) . await ;
3364
3578
logctx. cleanup_successful ( ) ;
0 commit comments