Skip to content

Commit 2349feb

Browse files
authored
[reconfigurator] replace Blueprint::zones_in_service with a disposition enum next to each zone (#5238)
This greatly simplifies the handling of the expunged disposition in #5211. It also is more understandable, and less prone to making mistakes. This PR only changes the in-memory representation of what is currently `zones_in_service`. I've skipped doing the DB migration in this PR because it'll be easier to just do it wholesale in #5211. There are some small differences in diff output that I think make sense. But I took a bigger swing at it in #5270, which depends on this.
1 parent 26fad69 commit 2349feb

File tree

24 files changed

+1262
-1253
lines changed

24 files changed

+1262
-1253
lines changed

clients/sled-agent-client/src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,24 @@ impl types::OmicronZoneType {
114114
}
115115
}
116116

117+
/// Identifies whether this a Crucible (not Crucible pantry) zone
118+
pub fn is_crucible(&self) -> bool {
119+
match self {
120+
types::OmicronZoneType::Crucible { .. } => true,
121+
122+
types::OmicronZoneType::BoundaryNtp { .. }
123+
| types::OmicronZoneType::InternalNtp { .. }
124+
| types::OmicronZoneType::Clickhouse { .. }
125+
| types::OmicronZoneType::ClickhouseKeeper { .. }
126+
| types::OmicronZoneType::CockroachDb { .. }
127+
| types::OmicronZoneType::CruciblePantry { .. }
128+
| types::OmicronZoneType::ExternalDns { .. }
129+
| types::OmicronZoneType::InternalDns { .. }
130+
| types::OmicronZoneType::Nexus { .. }
131+
| types::OmicronZoneType::Oximeter { .. } => false,
132+
}
133+
}
134+
117135
/// This zone's external IP
118136
pub fn external_ip(&self) -> anyhow::Result<Option<IpAddr>> {
119137
match self {

dev-tools/reconfigurator-cli/src/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -547,8 +547,7 @@ fn cmd_blueprint_diff_inventory(
547547
.get(&blueprint_id)
548548
.ok_or_else(|| anyhow!("no such blueprint: {}", blueprint_id))?;
549549

550-
let zones = collection.all_omicron_zones().map(|z| z.id).collect();
551-
let diff = blueprint.diff_sleds_from_collection(&collection, &zones);
550+
let diff = blueprint.diff_sleds_from_collection(&collection);
552551
Ok(Some(diff.display().to_string()))
553552
}
554553

dev-tools/reconfigurator-cli/tests/output/cmd-complex-stdout

Lines changed: 0 additions & 313 deletions
This file was deleted.

nexus/db-model/src/deployment.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ use crate::{ipv6, Generation, MacAddr, Name, SqlU16, SqlU32, SqlU8};
1515
use chrono::{DateTime, Utc};
1616
use ipnetwork::IpNetwork;
1717
use nexus_types::deployment::BlueprintTarget;
18+
use nexus_types::deployment::BlueprintZoneConfig;
19+
use nexus_types::deployment::BlueprintZoneDisposition;
20+
use nexus_types::deployment::BlueprintZonesConfig;
1821
use omicron_common::api::internal::shared::NetworkInterface;
1922
use uuid::Uuid;
2023

@@ -103,7 +106,7 @@ impl BpSledOmicronZones {
103106
pub fn new(
104107
blueprint_id: Uuid,
105108
sled_id: Uuid,
106-
zones_config: &nexus_types::deployment::OmicronZonesConfig,
109+
zones_config: &BlueprintZonesConfig,
107110
) -> Self {
108111
Self {
109112
blueprint_id,
@@ -144,9 +147,9 @@ impl BpOmicronZone {
144147
pub fn new(
145148
blueprint_id: Uuid,
146149
sled_id: Uuid,
147-
zone: &nexus_types::inventory::OmicronZoneConfig,
150+
zone: &BlueprintZoneConfig,
148151
) -> Result<Self, anyhow::Error> {
149-
let zone = OmicronZone::new(sled_id, zone)?;
152+
let zone = OmicronZone::new(sled_id, &zone.config)?;
150153
Ok(Self {
151154
blueprint_id,
152155
sled_id: zone.sled_id,
@@ -172,10 +175,11 @@ impl BpOmicronZone {
172175
})
173176
}
174177

175-
pub fn into_omicron_zone_config(
178+
pub fn into_blueprint_zone_config(
176179
self,
177180
nic_row: Option<BpOmicronZoneNic>,
178-
) -> Result<nexus_types::inventory::OmicronZoneConfig, anyhow::Error> {
181+
disposition: BlueprintZoneDisposition,
182+
) -> Result<BlueprintZoneConfig, anyhow::Error> {
179183
let zone = OmicronZone {
180184
sled_id: self.sled_id,
181185
id: self.id,
@@ -198,7 +202,9 @@ impl BpOmicronZone {
198202
snat_first_port: self.snat_first_port,
199203
snat_last_port: self.snat_last_port,
200204
};
201-
zone.into_omicron_zone_config(nic_row.map(OmicronZoneNic::from))
205+
let config =
206+
zone.into_omicron_zone_config(nic_row.map(OmicronZoneNic::from))?;
207+
Ok(BlueprintZoneConfig { config, disposition })
202208
}
203209
}
204210

@@ -234,9 +240,9 @@ impl From<BpOmicronZoneNic> for OmicronZoneNic {
234240
impl BpOmicronZoneNic {
235241
pub fn new(
236242
blueprint_id: Uuid,
237-
zone: &nexus_types::inventory::OmicronZoneConfig,
243+
zone: &BlueprintZoneConfig,
238244
) -> Result<Option<BpOmicronZoneNic>, anyhow::Error> {
239-
let zone_nic = OmicronZoneNic::new(zone)?;
245+
let zone_nic = OmicronZoneNic::new(&zone.config)?;
240246
Ok(zone_nic.map(|nic| Self {
241247
blueprint_id,
242248
id: nic.id,

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

Lines changed: 77 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ use nexus_db_model::BpTarget;
4343
use nexus_types::deployment::Blueprint;
4444
use nexus_types::deployment::BlueprintMetadata;
4545
use 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;
4749
use omicron_common::api::external::DataPageParams;
4850
use omicron_common::api::external::Error;
4951
use 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
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -952,7 +952,7 @@ mod test {
952952
};
953953
use omicron_common::api::internal::shared::SourceNatConfig;
954954
use omicron_test_utils::dev;
955-
use std::collections::{BTreeMap, BTreeSet, HashMap};
955+
use std::collections::{BTreeMap, HashMap};
956956
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV6};
957957
use std::num::NonZeroU32;
958958

@@ -965,8 +965,7 @@ mod test {
965965
rack_subnet: nexus_test_utils::RACK_SUBNET.parse().unwrap(),
966966
blueprint: Blueprint {
967967
id: Uuid::new_v4(),
968-
omicron_zones: BTreeMap::new(),
969-
zones_in_service: BTreeSet::new(),
968+
blueprint_zones: BTreeMap::new(),
970969
parent_blueprint_id: None,
971970
internal_dns_version: *Generation::new(),
972971
external_dns_version: *Generation::new(),

0 commit comments

Comments
 (0)