Skip to content

Commit 1c69391

Browse files
authored
reconfigurator-cli: Expose a simulation state wrapper usable in tests (#9456)
This is a second attempt at #9442, pulling in the excellent feedback from @davepacheco and @sunshowers. It's staged on top of #9455. Like #9442, this only updates a single test. Hopefully this is closer to what we really want; if so, I'll land this then work through the remaining planner tests (and add more helpers to `ReconfiguratorCliTestState` as needed).
1 parent 5d08d2f commit 1c69391

File tree

10 files changed

+507
-326
lines changed

10 files changed

+507
-326
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 162 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ use tufaceous_artifact::ArtifactVersionError;
8585
use tufaceous_lib::assemble::ArtifactManifest;
8686

8787
mod log_capture;
88+
pub mod test_utils;
8889

8990
/// REPL state
9091
#[derive(Debug)]
@@ -261,6 +262,138 @@ impl ReconfiguratorSim {
261262

262263
Ok(builder.build())
263264
}
265+
266+
fn load_example<F>(
267+
&mut self,
268+
seed: Option<String>,
269+
f: F,
270+
) -> anyhow::Result<String>
271+
where
272+
F: FnOnce(ExampleSystemBuilder) -> anyhow::Result<ExampleSystemBuilder>,
273+
{
274+
let mut s = String::new();
275+
let mut state = self.current_state().to_mut();
276+
if !state.system_mut().is_empty() {
277+
bail!(
278+
"changes made to simulated system: run `wipe system` before \
279+
loading"
280+
);
281+
}
282+
283+
// Generate the example system.
284+
match seed {
285+
Some(seed) => {
286+
// In this case, reset the RNG state to the provided seed.
287+
swriteln!(s, "setting new RNG seed: {}", seed);
288+
state.rng_mut().set_seed(seed);
289+
}
290+
None => {
291+
// In this case, use the existing RNG state.
292+
swriteln!(
293+
s,
294+
"using existing RNG state (seed: {})",
295+
state.rng_mut().seed()
296+
);
297+
}
298+
};
299+
let rng = state.rng_mut().next_example_rng();
300+
301+
let builder = f(ExampleSystemBuilder::new_with_rng(&self.log, rng)
302+
.nexus_count(
303+
state
304+
.config_mut()
305+
.num_nexus()
306+
.map_or(NEXUS_REDUNDANCY, |n| n.into()),
307+
))?;
308+
309+
let (example, blueprint) = builder.build();
310+
311+
// Generate the internal and external DNS configs based on the blueprint.
312+
let sleds_by_id = make_sleds_by_id(&example.system)?;
313+
let blueprint_nexus_generation =
314+
blueprint_active_nexus_generation(&blueprint);
315+
let internal_dns = blueprint_internal_dns_config(
316+
&blueprint,
317+
&sleds_by_id,
318+
blueprint_nexus_generation,
319+
&Default::default(),
320+
)?;
321+
let external_dns_zone_name =
322+
state.config_mut().external_dns_zone_name().to_owned();
323+
let external_dns = blueprint_external_dns_config(
324+
&blueprint,
325+
state.config_mut().silo_names(),
326+
external_dns_zone_name,
327+
blueprint_nexus_generation,
328+
);
329+
330+
let blueprint_id = blueprint.id;
331+
let collection_id = example.collection.id;
332+
333+
state
334+
.system_mut()
335+
.load_example(example, blueprint, internal_dns, external_dns)
336+
.expect("already checked non-empty state above");
337+
self.commit_and_bump(
338+
"reconfigurator-cli load-example".to_owned(),
339+
state,
340+
);
341+
342+
Ok(format!(
343+
"loaded example system with:\n\
344+
- collection: {collection_id}\n\
345+
- blueprint: {blueprint_id}",
346+
))
347+
}
348+
349+
fn run_planner(
350+
&mut self,
351+
parent_blueprint_id: BlueprintId,
352+
collection_id: CollectionId,
353+
) -> anyhow::Result<String> {
354+
let mut state = self.current_state().to_mut();
355+
let rng = state.rng_mut().next_planner_rng();
356+
let system = state.system_mut();
357+
358+
let parent_blueprint = {
359+
let resolved = system.resolve_blueprint_id(parent_blueprint_id);
360+
system.get_blueprint(&resolved)?
361+
};
362+
363+
let collection = {
364+
let resolved = system.resolve_collection_id(collection_id)?;
365+
system.get_collection(&resolved)?
366+
};
367+
368+
let creator = "reconfigurator-sim";
369+
let planning_input = self
370+
.planning_input(parent_blueprint)
371+
.context("failed to construct planning input")?;
372+
let planner = Planner::new_based_on(
373+
self.log.clone(),
374+
parent_blueprint,
375+
&planning_input,
376+
creator,
377+
collection,
378+
rng,
379+
)
380+
.context("creating planner")?;
381+
382+
let blueprint = planner.plan().context("generating blueprint")?;
383+
let rv = format!(
384+
"generated blueprint {} based on parent blueprint {}\n\
385+
blueprint source: {}",
386+
blueprint.id, parent_blueprint.id, blueprint.source,
387+
);
388+
system.add_blueprint(blueprint)?;
389+
390+
self.commit_and_bump(
391+
"reconfigurator-cli blueprint-plan".to_owned(),
392+
state,
393+
);
394+
395+
Ok(rv)
396+
}
264397
}
265398

266399
/// interactive REPL for exploring the planner
@@ -1756,7 +1889,7 @@ fn cmd_sled_set(
17561889
}
17571890
SledSetCommand::OmicronConfig(command) => {
17581891
let resolved_id =
1759-
system.resolve_blueprint_id(command.blueprint.into())?;
1892+
system.resolve_blueprint_id(command.blueprint.into());
17601893
let blueprint = system.get_blueprint(&resolved_id)?;
17611894
let sled_cfg =
17621895
blueprint.sleds.get(&sled_id).with_context(|| {
@@ -2236,7 +2369,7 @@ fn cmd_blueprint_blippy(
22362369
) -> anyhow::Result<Option<String>> {
22372370
let state = sim.current_state();
22382371
let resolved_id =
2239-
state.system().resolve_blueprint_id(args.blueprint_id.into())?;
2372+
state.system().resolve_blueprint_id(args.blueprint_id.into());
22402373
let blueprint = state.system().get_blueprint(&resolved_id)?;
22412374
let planning_input = sim
22422375
.planning_input(blueprint)
@@ -2250,24 +2383,19 @@ fn cmd_blueprint_plan(
22502383
sim: &mut ReconfiguratorSim,
22512384
args: BlueprintPlanArgs,
22522385
) -> anyhow::Result<Option<String>> {
2253-
let mut state = sim.current_state().to_mut();
2254-
let rng = state.rng_mut().next_planner_rng();
2255-
let system = state.system_mut();
2386+
let state = sim.current_state();
2387+
let system = state.system();
22562388

2257-
let parent_blueprint_id =
2258-
system.resolve_blueprint_id(args.parent_blueprint_id.into())?;
2259-
let parent_blueprint = system.get_blueprint(&parent_blueprint_id)?;
2260-
let collection = match args.collection_id {
2261-
Some(collection_id) => {
2262-
let resolved =
2263-
system.resolve_collection_id(collection_id.into())?;
2264-
system.get_collection(&resolved)?
2265-
}
2389+
let parent_blueprint_id = args.parent_blueprint_id.into();
2390+
let collection_id = match args.collection_id {
2391+
Some(collection_id) => collection_id.into(),
22662392
None => {
22672393
let mut all_collections_iter = system.all_collections();
22682394
match all_collections_iter.len() {
22692395
0 => bail!("cannot plan blueprint with no loaded collections"),
2270-
1 => all_collections_iter.next().expect("iter length is 1"),
2396+
1 => CollectionId::Id(
2397+
all_collections_iter.next().expect("iter length is 1").id,
2398+
),
22712399
_ => bail!(
22722400
"blueprint-plan: must specify collection ID (one of {:?})",
22732401
all_collections_iter.map(|c| c.id).join(", ")
@@ -2276,31 +2404,7 @@ fn cmd_blueprint_plan(
22762404
}
22772405
};
22782406

2279-
let creator = "reconfigurator-sim";
2280-
let planning_input = sim
2281-
.planning_input(parent_blueprint)
2282-
.context("failed to construct planning input")?;
2283-
let planner = Planner::new_based_on(
2284-
sim.log.clone(),
2285-
parent_blueprint,
2286-
&planning_input,
2287-
creator,
2288-
collection,
2289-
rng,
2290-
)
2291-
.context("creating planner")?;
2292-
2293-
let blueprint = planner.plan().context("generating blueprint")?;
2294-
let rv = format!(
2295-
"generated blueprint {} based on parent blueprint {}\n\
2296-
blueprint source: {}",
2297-
blueprint.id, parent_blueprint.id, blueprint.source,
2298-
);
2299-
system.add_blueprint(blueprint)?;
2300-
2301-
sim.commit_and_bump("reconfigurator-cli blueprint-plan".to_owned(), state);
2302-
2303-
Ok(Some(rv))
2407+
sim.run_planner(parent_blueprint_id, collection_id).map(Some)
23042408
}
23052409

23062410
fn cmd_blueprint_edit(
@@ -2311,7 +2415,7 @@ fn cmd_blueprint_edit(
23112415
let rng = state.rng_mut().next_planner_rng();
23122416
let system = state.system_mut();
23132417

2314-
let resolved_id = system.resolve_blueprint_id(args.blueprint_id.into())?;
2418+
let resolved_id = system.resolve_blueprint_id(args.blueprint_id.into());
23152419
let blueprint = system.get_blueprint(&resolved_id)?;
23162420
let creator = args.creator.as_deref().unwrap_or("reconfigurator-cli");
23172421
let planning_input = sim
@@ -2779,7 +2883,7 @@ fn cmd_blueprint_history(
27792883

27802884
let state = sim.current_state();
27812885
let system = state.system();
2782-
let resolved_id = system.resolve_blueprint_id(blueprint_id.into())?;
2886+
let resolved_id = system.resolve_blueprint_id(blueprint_id.into());
27832887
let mut blueprint = system.get_blueprint(&resolved_id)?;
27842888

27852889
// We want to print the output in logical order, but in order to construct
@@ -2852,8 +2956,7 @@ fn cmd_blueprint_save(
28522956
let blueprint_id = args.blueprint_id;
28532957

28542958
let state = sim.current_state();
2855-
let resolved_id =
2856-
state.system().resolve_blueprint_id(blueprint_id.into())?;
2959+
let resolved_id = state.system().resolve_blueprint_id(blueprint_id.into());
28572960
let blueprint = state.system().get_blueprint(&resolved_id)?;
28582961

28592962
let output_path = &args.filename;
@@ -3362,86 +3465,21 @@ fn cmd_load_example(
33623465
sim: &mut ReconfiguratorSim,
33633466
args: LoadExampleArgs,
33643467
) -> anyhow::Result<Option<String>> {
3365-
let mut s = String::new();
3366-
let mut state = sim.current_state().to_mut();
3367-
if !state.system_mut().is_empty() {
3368-
bail!(
3369-
"changes made to simulated system: run `wipe system` before \
3370-
loading"
3371-
);
3372-
}
3373-
3374-
// Generate the example system.
3375-
match args.seed {
3376-
Some(seed) => {
3377-
// In this case, reset the RNG state to the provided seed.
3378-
swriteln!(s, "setting new RNG seed: {}", seed);
3379-
state.rng_mut().set_seed(seed);
3380-
}
3381-
None => {
3382-
// In this case, use the existing RNG state.
3383-
swriteln!(
3384-
s,
3385-
"using existing RNG state (seed: {})",
3386-
state.rng_mut().seed()
3387-
);
3388-
}
3389-
};
3390-
let rng = state.rng_mut().next_example_rng();
3391-
3392-
let mut builder = ExampleSystemBuilder::new_with_rng(&sim.log, rng)
3393-
.nsleds(args.nsleds)
3394-
.ndisks_per_sled(args.ndisks_per_sled)
3395-
.nexus_count(
3396-
state
3397-
.config_mut()
3398-
.num_nexus()
3399-
.map_or(NEXUS_REDUNDANCY, |n| n.into()),
3400-
)
3401-
.external_dns_count(3)
3402-
.context("invalid external DNS zone count")?
3403-
.create_disks_in_blueprint(!args.no_disks_in_blueprint);
3404-
for sled_policy in args.sled_policy {
3405-
builder = builder
3406-
.with_sled_policy(sled_policy.index, sled_policy.policy)
3407-
.context("setting sled policy")?;
3408-
}
3409-
3410-
let (example, blueprint) = builder.build();
3411-
3412-
// Generate the internal and external DNS configs based on the blueprint.
3413-
let sleds_by_id = make_sleds_by_id(&example.system)?;
3414-
let blueprint_nexus_generation =
3415-
blueprint_active_nexus_generation(&blueprint);
3416-
let internal_dns = blueprint_internal_dns_config(
3417-
&blueprint,
3418-
&sleds_by_id,
3419-
blueprint_nexus_generation,
3420-
&Default::default(),
3421-
)?;
3422-
let external_dns_zone_name =
3423-
state.config_mut().external_dns_zone_name().to_owned();
3424-
let external_dns = blueprint_external_dns_config(
3425-
&blueprint,
3426-
state.config_mut().silo_names(),
3427-
external_dns_zone_name,
3428-
blueprint_nexus_generation,
3429-
);
3430-
3431-
let blueprint_id = blueprint.id;
3432-
let collection_id = example.collection.id;
3433-
3434-
state
3435-
.system_mut()
3436-
.load_example(example, blueprint, internal_dns, external_dns)
3437-
.expect("already checked non-empty state above");
3438-
sim.commit_and_bump("reconfigurator-cli load-example".to_owned(), state);
3439-
3440-
Ok(Some(format!(
3441-
"loaded example system with:\n\
3442-
- collection: {collection_id}\n\
3443-
- blueprint: {blueprint_id}",
3444-
)))
3468+
sim.load_example(args.seed, |builder| {
3469+
let mut builder = builder
3470+
.nsleds(args.nsleds)
3471+
.ndisks_per_sled(args.ndisks_per_sled)
3472+
.external_dns_count(3)
3473+
.context("invalid external DNS zone count")?
3474+
.create_disks_in_blueprint(!args.no_disks_in_blueprint);
3475+
for sled_policy in args.sled_policy {
3476+
builder = builder
3477+
.with_sled_policy(sled_policy.index, sled_policy.policy)
3478+
.context("setting sled policy")?;
3479+
}
3480+
Ok(builder)
3481+
})
3482+
.map(Some)
34453483
}
34463484

34473485
fn cmd_file_contents(args: FileContentsArgs) -> anyhow::Result<Option<String>> {

0 commit comments

Comments
 (0)