Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
adaa952
add test-utils to reconfigurator-cli
jgallagher Dec 2, 2025
0b080cd
use new test-utils in one planner test
jgallagher Dec 2, 2025
e01ec44
minor api update
jgallagher Dec 2, 2025
a3b7180
test_add_multiple_nexus_to_one_sled
jgallagher Dec 2, 2025
1eb2ce7
test_spread_additional_nexus_zones_across_sleds
jgallagher Dec 2, 2025
a1a75a5
test_spread_internal_dns_zones_across_sleds
jgallagher Dec 2, 2025
003e701
test_reuse_external_ips_from_expunged_zones
jgallagher Dec 2, 2025
6b047fd
test_reuse_external_dns_ips_from_expunged_zones
jgallagher Dec 2, 2025
e753555
test_crucible_allocation_skips_nonprovisionable_disks
jgallagher Dec 2, 2025
f4c694e
test_dataset_settings_modified_in_place
jgallagher Dec 2, 2025
748f379
test_disk_add_expunge_decommission
jgallagher Dec 2, 2025
2a85cf4
test_disk_expungement_removes_zones_durable_zpool
jgallagher Dec 2, 2025
2d853af
test_disk_expungement_removes_zones_transient_filesystem
jgallagher Dec 3, 2025
00b6f6e
test_nexus_allocation_skips_nonprovisionable_sleds
jgallagher Dec 3, 2025
a9da62d
planner_decommissions_sleds
jgallagher Dec 3, 2025
5df71f8
add change_description()
jgallagher Dec 3, 2025
ad347df
test_ensure_preserve_downgrade_option
jgallagher Dec 3, 2025
ed4911b
test_crucible_pantry
jgallagher Dec 3, 2025
d92a680
test_plan_deploy_all_clickhouse_cluster_nodes
jgallagher Dec 3, 2025
cec96c4
test_expunge_clickhouse_clusters
jgallagher Dec 3, 2025
46b36ac
test_expunge_clickhouse_zones_after_policy_is_changed
jgallagher Dec 3, 2025
2e8ad12
test_zones_marked_ready_for_cleanup_based_on_inventory
jgallagher Dec 3, 2025
8de3d89
test_internal_dns_zone_replaced_after_marked_for_cleanup
jgallagher Dec 3, 2025
58f2056
test_update_crucible_pantry_before_nexus
jgallagher Dec 4, 2025
dde4f06
test_update_cockroach
jgallagher Dec 4, 2025
2645eba
test_update_boundary_ntp
jgallagher Dec 4, 2025
c99ffe6
test_update_internal_dns
jgallagher Dec 4, 2025
5cf8090
test_update_all_zones
jgallagher Dec 4, 2025
fb9bb88
dead code removal
jgallagher Dec 4, 2025
6ed8425
cargo fmt
jgallagher Dec 4, 2025
dbd8bb2
Merge branch 'main' into john/reconfigurator-cli-test-api-2
jgallagher Dec 10, 2025
63f1c68
move bulk of `cmd_blueprint_plan()` into `ReconfiguratorSim::run_plan…
jgallagher Dec 10, 2025
e02fd4d
more thorough blueprint diff check
jgallagher Dec 10, 2025
bd5f0cd
Merge branch 'john/reconfigurator-cli-test-api-2' into john/planner-t…
jgallagher Dec 10, 2025
70505be
rename *edit_latest_low_level() -> *edit_latest_raw()
jgallagher Dec 10, 2025
22c95d1
add comment about using oplog once it lands
jgallagher Dec 10, 2025
fe5f900
Merge branch 'main' into john/planner-tests-use-reconfigurator-sim
jgallagher Dec 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dev-tools/reconfigurator-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ mod log_capture;
pub mod test_utils;

/// REPL state
#[derive(Debug)]
#[derive(Clone, Debug)]
struct ReconfiguratorSim {
// The simulator currently being used.
sim: Simulator,
Expand Down
198 changes: 191 additions & 7 deletions dev-tools/reconfigurator-cli/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,30 @@

use crate::ReconfiguratorSim;
use anyhow::Context;
use nexus_inventory::CollectionBuilder;
use nexus_inventory::now_db_precision;
use nexus_reconfigurator_blippy::Blippy;
use nexus_reconfigurator_blippy::BlippyReportSortKey;
use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder;
use nexus_reconfigurator_planning::example::ExampleSystemBuilder;
use nexus_reconfigurator_planning::system::SledBuilder;
use nexus_reconfigurator_planning::system::SystemDescription;
use nexus_reconfigurator_simulation::BlueprintId;
use nexus_reconfigurator_simulation::CollectionId;
use nexus_reconfigurator_simulation::SimState;
use nexus_reconfigurator_simulation::SimStateBuilder;
use nexus_reconfigurator_simulation::errors::KeyError;
use nexus_types::deployment::Blueprint;
use nexus_types::deployment::BlueprintSource;
use nexus_types::deployment::ClickhousePolicy;
use nexus_types::deployment::PlanningInput;
use nexus_types::external_api::views::SledPolicy;
use nexus_types::inventory::Collection;
use omicron_uuid_kinds::SledUuid;
use slog::Logger;
use std::sync::Arc;

#[derive(Debug, Clone)]
pub struct ReconfiguratorCliTestState {
sim: ReconfiguratorSim,
}
Expand Down Expand Up @@ -53,11 +64,21 @@ impl ReconfiguratorCliTestState {
}

/// Get the specified blueprint.
pub fn blueprint(&self, id: BlueprintId) -> Result<&Blueprint, KeyError> {
pub fn blueprint(
&self,
id: BlueprintId,
) -> Result<&Arc<Blueprint>, KeyError> {
let state = self.sim.current_state();
state.system().resolve_and_get_blueprint(id)
}

/// Get the specified inventory collection.
pub fn inventory(&self, id: CollectionId) -> Result<&Collection, KeyError> {
let system = self.current_state().system();
let id = system.resolve_collection_id(id)?;
system.get_collection(&id)
}

/// Run the blueprint planner, using the latest blueprint and inventory
/// collection as input.
///
Expand All @@ -74,7 +95,7 @@ impl ReconfiguratorCliTestState {
/// "blippy clean" blueprints, and since this is used throughout the
/// planner's tests, we want to assert that any blueprint we plan is indeed
/// blippy clean.
pub fn run_planner(&mut self) -> anyhow::Result<Blueprint> {
pub fn run_planner(&mut self) -> anyhow::Result<Arc<Blueprint>> {
let output =
self.sim.run_planner(BlueprintId::Latest, CollectionId::Latest)?;
println!("{output}");
Expand Down Expand Up @@ -103,7 +124,7 @@ impl ReconfiguratorCliTestState {
///
/// Panics if the latest blueprint and current planning input have any
/// blippy notes.
pub fn assert_latest_blueprint_is_blippy_clean(&self) -> Blueprint {
pub fn assert_latest_blueprint_is_blippy_clean(&self) -> Arc<Blueprint> {
let blueprint = self
.blueprint(BlueprintId::Latest)
.expect("always have a latest blueprint");
Expand All @@ -120,7 +141,17 @@ impl ReconfiguratorCliTestState {
panic!("expected blippy report for blueprint to have no notes");
}

blueprint.clone()
Arc::clone(blueprint)
}

/// Read the current internal simulator state.
pub fn current_state(&self) -> &SimState {
self.sim.current_state()
}

/// Read the current system description.
pub fn current_description(&self) -> &SystemDescription {
self.current_state().system().description()
}

/// Change the internal simulator state.
Expand All @@ -138,6 +169,20 @@ impl ReconfiguratorCliTestState {
Ok(ret)
}

/// State change helper: change only the system description.
pub fn change_description<F, T>(
&mut self,
description: &str,
f: F,
) -> anyhow::Result<T>
where
F: FnOnce(&mut SystemDescription) -> anyhow::Result<T>,
{
self.change_state(description, |state| {
f(state.system_mut().description_mut())
})
}

/// State change helper: generate a new inventory collection from the
/// current simulator state.
pub fn generate_inventory(
Expand All @@ -151,15 +196,139 @@ impl ReconfiguratorCliTestState {
})
}

/// State change helper: generate a new inventory collection from the
/// current simulator state.
///
/// `f` provides an opportunity to customize the collection.
pub fn generate_inventory_customized<F, T>(
&mut self,
description: &str,
f: F,
) -> anyhow::Result<T>
where
F: FnOnce(&mut CollectionBuilder) -> anyhow::Result<T>,
{
self.change_state(description, |state| {
let mut builder = state.to_collection_builder()?;
let result = f(&mut builder)?;
state.system_mut().add_collection(builder.build())?;
Ok(result)
})
}

/// State change helper: create a new latest inventory collection, then pass
/// it to `f` which is allowed to edit it in abnormal ways that cannot be
/// done via `CollectionBuilder`.
pub fn inventory_edit_latest_raw<F, T>(
&mut self,
description: &str,
f: F,
) -> anyhow::Result<T>
where
F: FnOnce(&mut Collection) -> anyhow::Result<T>,
{
self.change_state(description, |state| {
let mut collection = state.to_collection_builder()?.build();
let result = f(&mut collection)?;
state.system_mut().add_collection(collection)?;
Ok(result)
})
}

/// State change helper: set the Clickhouse cluster policy.
pub fn set_clickhouse_policy(
&mut self,
description: &str,
policy: ClickhousePolicy,
) {
self.change_description(description, |desc| {
desc.clickhouse_policy(policy);
Ok(())
})
.expect("closure can't fail");
}

/// State change helper: edit the latest blueprint, inserting a new latest
/// blueprint.
pub fn blueprint_edit_latest<F>(
&mut self,
description: &str,
f: F,
) -> anyhow::Result<Arc<Blueprint>>
where
F: FnOnce(&mut BlueprintBuilder<'_>) -> anyhow::Result<()>,
{
let log = self.sim.log.clone();
self.change_state(description, |state| {
let rng = state.rng_mut().next_planner_rng();
let system = state.system_mut();
let blueprint = system
.get_blueprint(
&system.resolve_blueprint_id(BlueprintId::Latest),
)
.expect("always have a latest blueprint");
let mut builder = BlueprintBuilder::new_based_on(
&log,
blueprint,
"ReconfiguratorCliTestState",
rng,
)?;
f(&mut builder)?;
let blueprint = Arc::new(builder.build(BlueprintSource::Test));
system.add_blueprint(Arc::clone(&blueprint))?;
Ok(blueprint)
})
}

/// State change helper: create a new latest blueprint that is a clone of
/// the current latest blueprint (but with a new ID and correct parent ID),
/// then pass it to `f` which is allowed to edit it in abnormal ways that
/// cannot be done via `BlueprintBuilder`.
pub fn blueprint_edit_latest_raw<F>(
&mut self,
description: &str,
f: F,
) -> anyhow::Result<Arc<Blueprint>>
where
F: FnOnce(&mut Blueprint) -> anyhow::Result<()>,
{
self.change_state(description, |state| {
let mut rng = state.rng_mut().next_planner_rng();
let system = state.system_mut();
let parent_blueprint = system
.get_blueprint(
&system.resolve_blueprint_id(BlueprintId::Latest),
)
.expect("always have a latest blueprint");
let mut blueprint = Arc::clone(parent_blueprint);

{
let blueprint = Arc::make_mut(&mut blueprint);

// Update metadata fields to make this a new blueprint.
blueprint.id = rng.next_blueprint();
blueprint.parent_blueprint_id = Some(parent_blueprint.id);
blueprint.time_created = now_db_precision();

// Perform whatever modifications the caller wants.
f(blueprint)?;
}

system.add_blueprint(Arc::clone(&blueprint))?;

Ok(blueprint)
})
}

/// State change helper: add a new sled, returning its ID.
pub fn add_sled(&mut self, description: &str) -> anyhow::Result<SledUuid> {
self.add_sled_customized(description, |sled| sled)
pub fn sled_add(&mut self, description: &str) -> anyhow::Result<SledUuid> {
self.sled_add_customized(description, |sled| sled)
}

/// State change helper: add a new sled, returning its ID.
///
/// `f` provides an opportunity to customize the sled.
pub fn add_sled_customized<F>(
pub fn sled_add_customized<F>(
&mut self,
description: &str,
f: F,
Expand All @@ -175,6 +344,21 @@ impl ReconfiguratorCliTestState {
})
}

/// State change helper: expunge a sled.
pub fn sled_expunge(
&mut self,
description: &str,
sled_id: SledUuid,
) -> anyhow::Result<()> {
self.change_state(description, |state| {
state
.system_mut()
.description_mut()
.sled_set_policy(sled_id, SledPolicy::Expunged)?;
Ok(())
})
}

/// State change helper: for each active sled described by `blueprint`,
/// update the simulate sled's config to match.
///
Expand Down
11 changes: 7 additions & 4 deletions nexus/reconfigurator/blippy/src/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2253,10 +2253,13 @@ fn check_planning_input_network_records_appear_in_blueprint(
}
}
_ => {
blippy.push_planning_input_note(
Severity::Fatal,
PlanningInputKind::NicWithUnknownOpteSubnet(nic_entry),
);
// Ignore localhost (used by the test suite).
if !nic_entry.nic.ip.is_loopback() {
blippy.push_planning_input_note(
Severity::Fatal,
PlanningInputKind::NicWithUnknownOpteSubnet(nic_entry),
);
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions nexus/reconfigurator/planning/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ uuid.workspace = true
omicron-workspace-hack.workspace = true

[dev-dependencies]
assert_matches.workspace = true
dns-server.workspace = true
dropshot.workspace = true
expectorate.workspace = true
hex-literal.workspace = true
hickory-resolver.workspace = true
internal-dns-types.workspace = true
maplit.workspace = true
nexus-reconfigurator-simulation.workspace = true
omicron-common = { workspace = true, features = ["testing"] }
omicron-test-utils.workspace = true
proptest.workspace = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -919,10 +919,7 @@ impl<'a> BlueprintBuilder<'a> {
}

/// Expunge everything on a sled.
pub(crate) fn expunge_sled(
&mut self,
sled_id: SledUuid,
) -> Result<(), Error> {
pub fn expunge_sled(&mut self, sled_id: SledUuid) -> Result<(), Error> {
let editor = self.sled_editors.get_mut(&sled_id).ok_or_else(|| {
Error::Planner(anyhow!("tried to expunge unknown sled {sled_id}"))
})?;
Expand Down
Loading
Loading