Skip to content

Commit 2e658cc

Browse files
authored
Add parent_blueprint to PlanningInput (#9481)
There are two main changes in this PR: * `PlanningInput` now has a `parent_blueprint` field * The `Planner` no longer takes a separate parent blueprint argument (since it comes with the planning input) There are a bunch of other mechanical changes, mostly related to trafficking in `Arc<Blueprint>`s in various places. The rationale for change this is to ensure correctness as we get into #5552; I wrote this up in the new doc comment on `PlanningInputFromDb::assemble()`; feedback welcome! This is staged on top of #9473. I had tried to make this change before doing that work, and updating the planner tests was a true nightmare. Happy to report updating the tests after that change was quite easy! (Didn't touch the planner tests at all, which only left a few that constructed `PlanningInput`s outside the simulator.)
1 parent 99c3f3e commit 2e658cc

File tree

22 files changed

+247
-159
lines changed

22 files changed

+247
-159
lines changed

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ use std::io::IsTerminal;
7777
use std::net::IpAddr;
7878
use std::num::ParseIntError;
7979
use std::str::FromStr;
80+
use std::sync::Arc;
8081
use swrite::{SWrite, swrite, swriteln};
8182
use tabled::Tabled;
8283
use tufaceous_artifact::ArtifactHash;
@@ -120,13 +121,13 @@ impl ReconfiguratorSim {
120121

121122
fn planning_input(
122123
&self,
123-
parent_blueprint: &Blueprint,
124+
parent_blueprint: &Arc<Blueprint>,
124125
) -> anyhow::Result<PlanningInput> {
125126
let state = self.current_state();
126127
let mut builder = state
127128
.system()
128129
.description()
129-
.to_planning_input_builder()
130+
.to_planning_input_builder(Arc::clone(parent_blueprint))
130131
.context("generating planning input builder")?;
131132

132133
// The internal and external DNS numbers that go here are supposed to be
@@ -371,7 +372,6 @@ impl ReconfiguratorSim {
371372
.context("failed to construct planning input")?;
372373
let planner = Planner::new_based_on(
373374
self.log.clone(),
374-
parent_blueprint,
375375
&planning_input,
376376
creator,
377377
collection,
@@ -1735,10 +1735,14 @@ fn cmd_sled_list(
17351735
}
17361736

17371737
let state = sim.current_state();
1738+
let parent_blueprint = state
1739+
.system()
1740+
.resolve_and_get_blueprint(BlueprintId::Target)
1741+
.expect("target blueprint is always present");
17381742
let planning_input = state
17391743
.system()
17401744
.description()
1741-
.to_planning_input_builder()
1745+
.to_planning_input_builder(Arc::clone(parent_blueprint))
17421746
.context("failed to generate planning input")?
17431747
.build();
17441748
let rows = planning_input.all_sleds(SledFilter::Commissioned).map(
@@ -1823,8 +1827,12 @@ fn cmd_sled_show(
18231827
description.sled_rot_pending_persistent_boot_preference(sled_id)?;
18241828
let rot_transient_boot_preference =
18251829
description.sled_rot_transient_boot_preference(sled_id)?;
1830+
let parent_blueprint = state
1831+
.system()
1832+
.resolve_and_get_blueprint(BlueprintId::Target)
1833+
.expect("target blueprint is always present");
18261834
let planning_input = description
1827-
.to_planning_input_builder()
1835+
.to_planning_input_builder(Arc::clone(parent_blueprint))
18281836
.context("failed to generate planning_input builder")?
18291837
.build();
18301838
let sled = planning_input.sled_lookup(args.filter, sled_id)?;

live-tests/tests/test_nexus_add_remove.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use omicron_test_utils::dev::poll::CondCheckError;
3030
use omicron_test_utils::dev::poll::wait_for_condition;
3131
use slog::{debug, info};
3232
use std::net::SocketAddrV6;
33+
use std::sync::Arc;
3334
use std::time::Duration;
3435

3536
// TODO-coverage This test could check other stuff:
@@ -49,15 +50,23 @@ async fn test_nexus_add_remove(lc: &LiveTestContext) {
4950
let opctx = lc.opctx();
5051
let datastore = lc.datastore();
5152

53+
let (_, parent_blueprint) = datastore
54+
.blueprint_target_get_current_full(opctx)
55+
.await
56+
.expect("obtained current target blueprint");
5257
let planner_config = datastore
5358
.reconfigurator_config_get_latest(opctx)
5459
.await
5560
.expect("obtained latest reconfigurator config")
5661
.map_or_else(PlannerConfig::default, |c| c.config.planner_config);
57-
let planning_input =
58-
PlanningInputFromDb::assemble(&opctx, &datastore, planner_config)
59-
.await
60-
.expect("planning input");
62+
let planning_input = PlanningInputFromDb::assemble(
63+
&opctx,
64+
&datastore,
65+
planner_config,
66+
Arc::new(parent_blueprint),
67+
)
68+
.await
69+
.expect("planning input");
6170
let initial_nexus_clients = lc.all_internal_nexus_clients().await.unwrap();
6271
let nexus = initial_nexus_clients.first().expect("internal Nexus client");
6372

@@ -271,17 +280,20 @@ async fn test_nexus_add_remove(lc: &LiveTestContext) {
271280

272281
// Now run through the planner.
273282
info!(log, "running through planner");
274-
let planning_input =
275-
PlanningInputFromDb::assemble(&opctx, &datastore, planner_config)
276-
.await
277-
.expect("planning input");
278283
let (_, parent_blueprint) = datastore
279284
.blueprint_target_get_current_full(opctx)
280285
.await
281286
.expect("getting latest target blueprint");
287+
let planning_input = PlanningInputFromDb::assemble(
288+
&opctx,
289+
&datastore,
290+
planner_config,
291+
Arc::new(parent_blueprint),
292+
)
293+
.await
294+
.expect("planning input");
282295
let planner = Planner::new_based_on(
283296
log.clone(),
284-
&parent_blueprint,
285297
&planning_input,
286298
"live test suite",
287299
&latest_collection,

live-tests/tests/test_nexus_handoff.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use std::collections::BTreeMap;
3636
use std::collections::BTreeSet;
3737
use std::net::IpAddr;
3838
use std::net::SocketAddr;
39+
use std::sync::Arc;
3940
use std::time::Duration;
4041

4142
#[live_test]
@@ -163,10 +164,18 @@ async fn test_nexus_handoff(lc: &LiveTestContext) {
163164
.await
164165
.expect("obtained latest reconfigurator config")
165166
.map_or_else(PlannerConfig::default, |cs| cs.config.planner_config);
166-
let planning_input =
167-
PlanningInputFromDb::assemble(opctx, datastore, planner_config)
168-
.await
169-
.expect("planning input");
167+
let (_, parent_blueprint) = datastore
168+
.blueprint_target_get_current_full(opctx)
169+
.await
170+
.expect("getting latest target blueprint");
171+
let planning_input = PlanningInputFromDb::assemble(
172+
opctx,
173+
datastore,
174+
planner_config,
175+
Arc::new(parent_blueprint),
176+
)
177+
.await
178+
.expect("planning input");
170179
let (_blueprint_initial, blueprint_new_nexus) =
171180
blueprint_edit_current_target(
172181
log,

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,12 +1203,15 @@ mod test {
12031203
system: &SystemDescription,
12041204
test_name: &str,
12051205
) -> BlueprintBuilder<'static> {
1206-
static EMPTY_BLUEPRINT: LazyLock<Blueprint> = LazyLock::new(|| {
1207-
BlueprintBuilder::build_empty("EMPTY_BLUEPRINT static")
1208-
});
1206+
static EMPTY_BLUEPRINT: LazyLock<Arc<Blueprint>> =
1207+
LazyLock::new(|| {
1208+
Arc::new(BlueprintBuilder::build_empty(
1209+
"EMPTY_BLUEPRINT static",
1210+
))
1211+
});
12091212

12101213
let planning_input = system
1211-
.to_planning_input_builder()
1214+
.to_planning_input_builder(Arc::clone(&EMPTY_BLUEPRINT))
12121215
.expect("created planning input builder")
12131216
.build();
12141217

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3003,6 +3003,7 @@ mod tests {
30033003
use slog::info;
30043004
use std::net::Ipv4Addr;
30053005
use std::net::Ipv6Addr;
3006+
use std::sync::Arc;
30063007

30073008
// Test that we detect the right error condition and return None when we
30083009
// fail to insert a VPC due to VNI exhaustion.
@@ -3283,10 +3284,6 @@ mod tests {
32833284
datastore.sled_upsert(sled_update).await.expect("upserting sled");
32843285
}
32853286
sled_ids.sort_unstable();
3286-
let planning_input = system
3287-
.to_planning_input_builder()
3288-
.expect("creating planning builder")
3289-
.build();
32903287

32913288
// Helper to convert a zone's nic into an insertable nic.
32923289
let db_nic_from_zone = |zone_config: &BlueprintZoneConfig| {
@@ -3314,9 +3311,14 @@ mod tests {
33143311
};
33153312

33163313
// Create an initial, empty blueprint, and make it the target.
3317-
let bp0 = BlueprintBuilder::build_empty("test");
3314+
let bp0 = Arc::new(BlueprintBuilder::build_empty("test"));
33183315
bp_insert_and_make_target(&opctx, &datastore, &bp0).await;
33193316

3317+
let planning_input = system
3318+
.to_planning_input_builder(Arc::clone(&bp0))
3319+
.expect("creating planning builder")
3320+
.build();
3321+
33203322
// Our blueprint doesn't describe any services, so we shouldn't find any
33213323
// sled IDs running services.
33223324
assert_service_sled_ids(&datastore, &[]).await;

nexus/reconfigurator/planning/src/blueprint_builder/builder.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2641,7 +2641,7 @@ pub mod test {
26412641
}
26422642
}
26432643

2644-
let blueprint2 = builder.build(BlueprintSource::Test);
2644+
let blueprint2 = Arc::new(builder.build(BlueprintSource::Test));
26452645
verify_blueprint(&blueprint2, &example.input);
26462646
let summary = blueprint2.diff_since_blueprint(&blueprint1);
26472647
println!(
@@ -2658,7 +2658,11 @@ pub mod test {
26582658

26592659
let _ =
26602660
example.system.sled(SledBuilder::new().id(new_sled_id)).unwrap();
2661-
let input = example.system.to_planning_input_builder().unwrap().build();
2661+
let input = example
2662+
.system
2663+
.to_planning_input_builder(Arc::clone(&blueprint2))
2664+
.unwrap()
2665+
.build();
26622666
let mut builder = BlueprintBuilder::new_based_on(
26632667
&logctx.log,
26642668
&blueprint2,

nexus/reconfigurator/planning/src/example.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::fmt;
1010
use std::hash::Hash;
1111
use std::net::IpAddr;
1212
use std::net::Ipv4Addr;
13+
use std::sync::Arc;
1314

1415
use crate::blueprint_builder::BlueprintBuilder;
1516
use crate::blueprint_editor::ExternalNetworkingAllocator;
@@ -182,7 +183,7 @@ pub struct ExampleSystem {
182183
pub input: PlanningInput,
183184
pub collection: Collection,
184185
/// The initial blueprint that was used to describe the system.
185-
pub initial_blueprint: Blueprint,
186+
pub initial_blueprint: Arc<Blueprint>,
186187
}
187188

188189
/// Returns a collection, planning input, and blueprint describing a pretty
@@ -531,16 +532,17 @@ impl ExampleSystemBuilder {
531532
}
532533

533534
let mut input_builder = system
534-
.to_planning_input_builder()
535+
.to_planning_input_builder(Arc::new(
536+
// Start with an empty blueprint.
537+
BlueprintBuilder::build_empty_seeded(
538+
"test suite",
539+
rng.blueprint1_rng,
540+
),
541+
))
535542
.expect("failed to make planning input builder");
536543

537544
let base_input = input_builder.clone().build();
538-
539-
// Start with an empty blueprint.
540-
let initial_blueprint = BlueprintBuilder::build_empty_seeded(
541-
"test suite",
542-
rng.blueprint1_rng,
543-
);
545+
let initial_blueprint = Arc::clone(&base_input.parent_blueprint());
544546

545547
// Now make a blueprint and collection with some zones on each sled.
546548
let mut builder = BlueprintBuilder::new_based_on(

nexus/reconfigurator/planning/src/planner.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,6 @@ pub struct Planner<'a> {
175175
impl<'a> Planner<'a> {
176176
pub fn new_based_on(
177177
log: Logger,
178-
parent_blueprint: &'a Blueprint,
179178
input: &'a PlanningInput,
180179
creator: &str,
181180
// NOTE: Right now, we just assume that this is the latest inventory
@@ -185,7 +184,7 @@ impl<'a> Planner<'a> {
185184
) -> anyhow::Result<Planner<'a>> {
186185
let mut blueprint = BlueprintBuilder::new_based_on(
187186
&log,
188-
parent_blueprint,
187+
input.parent_blueprint(),
189188
creator,
190189
rng,
191190
)?;

nexus/reconfigurator/planning/src/system.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use nexus_sled_agent_shared::inventory::SledRole;
3030
use nexus_sled_agent_shared::inventory::ZoneImageResolverInventory;
3131
use nexus_sled_agent_shared::inventory::ZoneKind;
3232
use nexus_sled_agent_shared::inventory::ZoneManifestBootInventory;
33+
use nexus_types::deployment::Blueprint;
3334
use nexus_types::deployment::ClickhousePolicy;
3435
use nexus_types::deployment::CockroachDbClusterVersion;
3536
use nexus_types::deployment::CockroachDbSettings;
@@ -1140,6 +1141,7 @@ impl SystemDescription {
11401141
/// NICs.
11411142
pub fn to_planning_input_builder(
11421143
&self,
1144+
parent_blueprint: Arc<Blueprint>,
11431145
) -> anyhow::Result<PlanningInputBuilder> {
11441146
let policy = Policy {
11451147
external_ips: self.external_ip_policy.clone(),
@@ -1159,6 +1161,7 @@ impl SystemDescription {
11591161
planner_config: self.planner_config,
11601162
};
11611163
let mut builder = PlanningInputBuilder::new(
1164+
parent_blueprint,
11621165
policy,
11631166
self.internal_dns_version,
11641167
self.external_dns_version,

0 commit comments

Comments
 (0)