@@ -20,6 +20,7 @@ use nexus_db_model::DbTypedUuid;
2020use nexus_db_model:: HwBaseboardId ;
2121use nexus_db_model:: TrustQuorumConfiguration as DbTrustQuorumConfiguration ;
2222use nexus_db_model:: TrustQuorumMember as DbTrustQuorumMember ;
23+ use nexus_types:: trust_quorum:: TrustQuorumConfigState ;
2324use nexus_types:: trust_quorum:: { TrustQuorumConfig , TrustQuorumMemberData } ;
2425use omicron_common:: api:: external:: Error ;
2526use omicron_common:: api:: external:: ListResultVec ;
@@ -75,7 +76,7 @@ impl DataStore {
7576 pub async fn tq_get_all_rack_id_and_latest_epoch (
7677 & self ,
7778 opctx : & OpContext ,
78- ) -> ListResultVec < ( RackUuid , Epoch ) > {
79+ ) -> Result < BTreeMap < RackUuid , Epoch > , Error > {
7980 opctx. authorize ( authz:: Action :: ListChildren , & authz:: FLEET ) . await ?;
8081 let conn = & * self . pool_connection_authorized ( opctx) . await ?;
8182
@@ -92,10 +93,9 @@ impl DataStore {
9293 public_error_from_diesel ( e, ErrorHandler :: Server )
9394 } ) ?;
9495
95- let mut output = Vec :: with_capacity ( values. len ( ) ) ;
96-
96+ let mut output = BTreeMap :: new ( ) ;
9797 for ( rack_id, epoch) in values {
98- output. push ( ( rack_id. into ( ) , i64_to_epoch ( epoch) ?) ) ;
98+ output. insert ( rack_id. into ( ) , i64_to_epoch ( epoch) ?) ;
9999 }
100100
101101 Ok ( output)
@@ -274,14 +274,71 @@ impl DataStore {
274274 . await
275275 . map_err ( |txn_error| txn_error. into_diesel ( & err) ) ?;
276276
277+ // Some light sanity checking
278+ match config. state {
279+ TrustQuorumConfigState :: Preparing
280+ | TrustQuorumConfigState :: PreparingLrtqUpgrade => { }
281+ TrustQuorumConfigState :: Committing
282+ | TrustQuorumConfigState :: Committed
283+ | TrustQuorumConfigState :: Aborted => {
284+ let state = config. state ;
285+ bail_txn ! (
286+ err,
287+ "Cannot insert configuration in state={:?}" ,
288+ state
289+ ) ;
290+ }
291+ }
292+
277293 let is_insertable = if let Some ( epoch) = current {
278294 // Only insert if what is in the DB is immediately prior to
279295 // this configuration.
280296 Some ( epoch) == config. epoch . previous ( )
281297 } else {
282- // Unconditional update is fine here, since a config
283- // doesn't exist TODO: Should we ensure that epoch == 1
284- // || epoch == 2 ?
298+ // We perform an unconditional insert here since
299+ // no existing configuration exists. However, the
300+ // configuration to be inserted is still subject to
301+ // some constraints.
302+ //
303+ // If there is no existing configuration, then the epoch
304+ // to be inserted must be either 1 or 2. It will be 1 if
305+ // this is a new initialization and 2 if this is an LRTQ
306+ // upgrade. Let's check both conditions here and return
307+ // an error if unmet.
308+ match config. state {
309+ TrustQuorumConfigState :: Preparing => {
310+ let actual = config. epoch ;
311+ let expected = Epoch ( 1 ) ;
312+ if actual != expected {
313+ bail_txn ! (
314+ err,
315+ "Failed to insert first TQ
316+ configuration: invalid epoch for \
317+ state=preparing: Expected {}, Got {}",
318+ expected,
319+ actual
320+ ) ;
321+ }
322+ }
323+ TrustQuorumConfigState :: PreparingLrtqUpgrade => {
324+ let actual = config. epoch ;
325+ let expected = Epoch ( 2 ) ;
326+ if actual != expected {
327+ bail_txn ! (
328+ err,
329+ "Failed to insert first TQ
330+ configuration: invalid epoch for \
331+ state=preparing-lrtq-upgrade: \
332+ Expected {}, Got {}",
333+ expected,
334+ actual
335+ ) ;
336+ }
337+ }
338+ _ => {
339+ // Already checked abbove
340+ }
341+ }
285342 true
286343 } ;
287344
@@ -1127,6 +1184,40 @@ mod tests {
11271184 . collect ( ) ,
11281185 } ;
11291186
1187+ // Create a couple of invalid configs andd try to insert them.
1188+ // They should return distinct errors.
1189+ let bad_config =
1190+ TrustQuorumConfig { epoch : Epoch ( 2 ) , ..config. clone ( ) } ;
1191+ let e1 = datastore
1192+ . tq_insert_latest_config ( opctx, bad_config)
1193+ . await
1194+ . unwrap_err ( ) ;
1195+
1196+ let bad_config = TrustQuorumConfig {
1197+ epoch : Epoch ( 3 ) ,
1198+ state : TrustQuorumConfigState :: PreparingLrtqUpgrade ,
1199+ ..config. clone ( )
1200+ } ;
1201+ let e2 = datastore
1202+ . tq_insert_latest_config ( opctx, bad_config)
1203+ . await
1204+ . unwrap_err ( ) ;
1205+
1206+ let bad_config = TrustQuorumConfig {
1207+ state : TrustQuorumConfigState :: Committing ,
1208+ ..config. clone ( )
1209+ } ;
1210+ let e3 = datastore
1211+ . tq_insert_latest_config ( opctx, bad_config)
1212+ . await
1213+ . unwrap_err ( ) ;
1214+
1215+ assert_ne ! ( e1, e2) ;
1216+ assert_ne ! ( e1, e3) ;
1217+ assert_ne ! ( e2, e3) ;
1218+
1219+ // Insert a valid config and watch it succeed
1220+
11301221 datastore. tq_insert_latest_config ( opctx, config. clone ( ) ) . await . unwrap ( ) ;
11311222
11321223 let read_config = datastore
@@ -1612,10 +1703,6 @@ mod tests {
16121703
16131704 // We should have retrieved one epoch per rack_id
16141705 assert_eq ! ( values. len( ) , 3 ) ;
1615- // Ensure all rack_ids are unique
1616- let rack_ids: BTreeSet < _ > =
1617- values. iter ( ) . map ( |( rack_id, _) | rack_id) . collect ( ) ;
1618- assert_eq ! ( rack_ids. len( ) , 3 ) ;
16191706
16201707 // The epoch should be the latest that exists
16211708 for ( rack_id, epoch) in values {
0 commit comments