@@ -43,7 +43,9 @@ use nexus_db_model::BpTarget;
4343use nexus_types:: deployment:: Blueprint ;
4444use nexus_types:: deployment:: BlueprintMetadata ;
4545use nexus_types:: deployment:: BlueprintTarget ;
46- use nexus_types:: deployment:: OmicronZonesConfig ;
46+ use nexus_types:: deployment:: BlueprintZoneDisposition ;
47+ use nexus_types:: deployment:: BlueprintZoneFilter ;
48+ use nexus_types:: deployment:: BlueprintZonesConfig ;
4749use omicron_common:: api:: external:: DataPageParams ;
4850use omicron_common:: api:: external:: Error ;
4951use omicron_common:: api:: external:: ListResultVec ;
@@ -105,52 +107,67 @@ impl DataStore {
105107 // so that we can produce the `Error` type that we want here.
106108 let row_blueprint = DbBlueprint :: from ( blueprint) ;
107109 let blueprint_id = row_blueprint. id ;
110+
111+ // `Blueprint` stores the policy for each zone next to the zone itself.
112+ // This would ideally be represented as a simple column in
113+ // bp_omicron_zone.
114+ //
115+ // But historically, `Blueprint` used to store the set of zones in
116+ // service in a BTreeSet. Since most zones are expected to be in
117+ // service, we store the set of zones NOT in service (which we expect
118+ // to be much smaller, often empty). Build that inverted set here.
119+ //
120+ // This will soon be replaced with an extra column in the
121+ // `bp_omicron_zone` table, coupled with other data migrations.
122+ let omicron_zones_not_in_service = blueprint
123+ . all_blueprint_zones ( BlueprintZoneFilter :: All )
124+ . filter_map ( |( _, zone) | {
125+ // This is going to go away soon when we change the database
126+ // representation to store the zone disposition enum next to
127+ // each zone. For now, do an exhaustive match so that this
128+ // fails if we add a new variant.
129+ match zone. disposition {
130+ BlueprintZoneDisposition :: InService => None ,
131+ BlueprintZoneDisposition :: Quiesced => {
132+ Some ( BpOmicronZoneNotInService {
133+ blueprint_id,
134+ bp_omicron_zone_id : zone. config . id ,
135+ } )
136+ }
137+ }
138+ } )
139+ . collect :: < Vec < _ > > ( ) ;
140+
108141 let sled_omicron_zones = blueprint
109- . omicron_zones
142+ . blueprint_zones
110143 . iter ( )
111144 . map ( |( sled_id, zones_config) | {
112145 BpSledOmicronZones :: new ( blueprint_id, * sled_id, zones_config)
113146 } )
114147 . collect :: < Vec < _ > > ( ) ;
115148 let omicron_zones = blueprint
116- . omicron_zones
149+ . blueprint_zones
117150 . iter ( )
118151 . flat_map ( |( sled_id, zones_config) | {
119- zones_config. zones . iter ( ) . map ( |zone| {
152+ zones_config. zones . iter ( ) . map ( move |zone| {
120153 BpOmicronZone :: new ( blueprint_id, * sled_id, zone)
121154 . map_err ( |e| Error :: internal_error ( & format ! ( "{:#}" , e) ) )
122155 } )
123156 } )
124157 . collect :: < Result < Vec < _ > , Error > > ( ) ?;
125158 let omicron_zone_nics = blueprint
126- . omicron_zones
159+ . blueprint_zones
127160 . values ( )
128161 . flat_map ( |zones_config| {
129162 zones_config. zones . iter ( ) . filter_map ( |zone| {
130163 BpOmicronZoneNic :: new ( blueprint_id, zone)
131- . with_context ( || format ! ( "zone {:?}" , zone. id) )
164+ . with_context ( || format ! ( "zone {:?}" , zone. config . id) )
132165 . map_err ( |e| Error :: internal_error ( & format ! ( "{:#}" , e) ) )
133166 . transpose ( )
134167 } )
135168 } )
136169 . collect :: < Result < Vec < BpOmicronZoneNic > , _ > > ( ) ?;
137170
138- // `Blueprint` stores a set of zones in service, but in the database we
139- // store the set of zones NOT in service (which we expect to be much
140- // smaller, often empty). Build that inverted set here.
141- let omicron_zones_not_in_service = {
142- let mut zones_not_in_service = Vec :: new ( ) ;
143- for zone in & omicron_zones {
144- if !blueprint. zones_in_service . contains ( & zone. id ) {
145- zones_not_in_service. push ( BpOmicronZoneNotInService {
146- blueprint_id,
147- bp_omicron_zone_id : zone. id ,
148- } ) ;
149- }
150- }
151- zones_not_in_service
152- } ;
153-
154171 // This implementation inserts all records associated with the
155172 // blueprint in one transaction. This is required: we don't want
156173 // any planner or executor to see a half-inserted blueprint, nor do we
@@ -273,10 +290,10 @@ impl DataStore {
273290 // the `OmicronZonesConfig` generation number for each sled that is a
274291 // part of this blueprint. Construct the BTreeMap we ultimately need,
275292 // but all the `zones` vecs will be empty until our next query below.
276- let mut omicron_zones : BTreeMap < Uuid , OmicronZonesConfig > = {
293+ let mut blueprint_zones : BTreeMap < Uuid , BlueprintZonesConfig > = {
277294 use db:: schema:: bp_sled_omicron_zones:: dsl;
278295
279- let mut omicron_zones = BTreeMap :: new ( ) ;
296+ let mut blueprint_zones = BTreeMap :: new ( ) ;
280297 let mut paginator = Paginator :: new ( SQL_BATCH_SIZE ) ;
281298 while let Some ( p) = paginator. next ( ) {
282299 let batch = paginated (
@@ -295,9 +312,9 @@ impl DataStore {
295312 paginator = p. found_batch ( & batch, & |s| s. sled_id ) ;
296313
297314 for s in batch {
298- let old = omicron_zones . insert (
315+ let old = blueprint_zones . insert (
299316 s. sled_id ,
300- OmicronZonesConfig {
317+ BlueprintZonesConfig {
301318 generation : * s. generation ,
302319 zones : Vec :: new ( ) ,
303320 } ,
@@ -310,7 +327,7 @@ impl DataStore {
310327 }
311328 }
312329
313- omicron_zones
330+ blueprint_zones
314331 } ;
315332
316333 // Assemble a mutable map of all the NICs found, by NIC id. As we
@@ -391,11 +408,6 @@ impl DataStore {
391408 omicron_zones_not_in_service
392409 } ;
393410
394- // Create the in-memory list of zones _in_ service, which we'll
395- // calculate below as we load zones. (Any zone that isn't present in
396- // `omicron_zones_not_in_service` is considered in service.)
397- let mut zones_in_service = BTreeSet :: new ( ) ;
398-
399411 // Load all the zones for each sled.
400412 {
401413 use db:: schema:: bp_omicron_zone:: dsl;
@@ -438,8 +450,9 @@ impl DataStore {
438450 } )
439451 } )
440452 . transpose ( ) ?;
441- let sled_zones =
442- omicron_zones. get_mut ( & z. sled_id ) . ok_or_else ( || {
453+ let sled_zones = blueprint_zones
454+ . get_mut ( & z. sled_id )
455+ . ok_or_else ( || {
443456 // This error means that we found a row in
444457 // bp_omicron_zone with no associated record in
445458 // bp_sled_omicron_zones. This should be
@@ -451,8 +464,14 @@ impl DataStore {
451464 ) )
452465 } ) ?;
453466 let zone_id = z. id ;
467+ let disposition =
468+ if omicron_zones_not_in_service. remove ( & zone_id) {
469+ BlueprintZoneDisposition :: Quiesced
470+ } else {
471+ BlueprintZoneDisposition :: InService
472+ } ;
454473 let zone = z
455- . into_omicron_zone_config ( nic_row)
474+ . into_blueprint_zone_config ( nic_row, disposition )
456475 . with_context ( || {
457476 format ! ( "zone {:?}: parse from database" , zone_id)
458477 } )
@@ -463,14 +482,6 @@ impl DataStore {
463482 ) )
464483 } ) ?;
465484 sled_zones. zones . push ( zone) ;
466-
467- // If we can remove `zone_id` from
468- // `omicron_zones_not_in_service`, then the zone is not in
469- // service. Otherwise, add it to the list of in-service
470- // zones.
471- if !omicron_zones_not_in_service. remove ( & zone_id) {
472- zones_in_service. insert ( zone_id) ;
473- }
474485 }
475486 }
476487 }
@@ -488,8 +499,7 @@ impl DataStore {
488499
489500 Ok ( Blueprint {
490501 id : blueprint_id,
491- omicron_zones,
492- zones_in_service,
502+ blueprint_zones,
493503 parent_blueprint_id,
494504 internal_dns_version,
495505 external_dns_version,
@@ -1359,10 +1369,8 @@ mod tests {
13591369 [ blueprint1. id]
13601370 ) ;
13611371
1362- // There ought to be no sleds or zones in service, and no parent
1363- // blueprint.
1364- assert_eq ! ( blueprint1. omicron_zones. len( ) , 0 ) ;
1365- assert_eq ! ( blueprint1. zones_in_service. len( ) , 0 ) ;
1372+ // There ought to be no sleds or zones, and no parent blueprint.
1373+ assert_eq ! ( blueprint1. blueprint_zones. len( ) , 0 ) ;
13661374 assert_eq ! ( blueprint1. parent_blueprint_id, None ) ;
13671375
13681376 // Trying to insert the same blueprint again should fail.
@@ -1407,20 +1415,17 @@ mod tests {
14071415 ) ;
14081416
14091417 // Check the number of blueprint elements against our collection.
1410- assert_eq ! ( blueprint1. omicron_zones . len( ) , policy. sleds. len( ) ) ;
1418+ assert_eq ! ( blueprint1. blueprint_zones . len( ) , policy. sleds. len( ) ) ;
14111419 assert_eq ! (
1412- blueprint1. omicron_zones . len( ) ,
1420+ blueprint1. blueprint_zones . len( ) ,
14131421 collection. omicron_zones. len( )
14141422 ) ;
14151423 assert_eq ! (
14161424 blueprint1. all_omicron_zones( ) . count( ) ,
14171425 collection. all_omicron_zones( ) . count( )
14181426 ) ;
14191427 // All zones should be in service.
1420- assert_eq ! (
1421- blueprint1. zones_in_service. len( ) ,
1422- blueprint1. all_omicron_zones( ) . count( )
1423- ) ;
1428+ assert_all_zones_in_service ( & blueprint1) ;
14241429 assert_eq ! ( blueprint1. parent_blueprint_id, None ) ;
14251430
14261431 // Set blueprint1 as the current target, and ensure that we cannot
@@ -1489,19 +1494,16 @@ mod tests {
14891494
14901495 // Check that we added the new sled and its zones.
14911496 assert_eq ! (
1492- blueprint1. omicron_zones . len( ) + 1 ,
1493- blueprint2. omicron_zones . len( )
1497+ blueprint1. blueprint_zones . len( ) + 1 ,
1498+ blueprint2. blueprint_zones . len( )
14941499 ) ;
14951500 assert_eq ! (
14961501 blueprint1. all_omicron_zones( ) . count( ) + num_new_sled_zones,
14971502 blueprint2. all_omicron_zones( ) . count( )
14981503 ) ;
14991504
15001505 // All zones should be in service.
1501- assert_eq ! (
1502- blueprint2. zones_in_service. len( ) ,
1503- blueprint2. all_omicron_zones( ) . count( )
1504- ) ;
1506+ assert_all_zones_in_service ( & blueprint2) ;
15051507 assert_eq ! ( blueprint2. parent_blueprint_id, Some ( blueprint1. id) ) ;
15061508
15071509 // Check that we can write it to the DB and read it back.
@@ -1869,4 +1871,18 @@ mod tests {
18691871 db. cleanup ( ) . await . unwrap ( ) ;
18701872 logctx. cleanup_successful ( ) ;
18711873 }
1874+
1875+ fn assert_all_zones_in_service ( blueprint : & Blueprint ) {
1876+ let not_in_service = blueprint
1877+ . all_blueprint_zones ( BlueprintZoneFilter :: All )
1878+ . filter ( |( _, z) | {
1879+ z. disposition != BlueprintZoneDisposition :: InService
1880+ } )
1881+ . collect :: < Vec < _ > > ( ) ;
1882+ assert ! (
1883+ not_in_service. is_empty( ) ,
1884+ "expected all zones to be in service, \
1885+ found these zones not in service: {not_in_service:?}"
1886+ ) ;
1887+ }
18721888}
0 commit comments