diff --git a/Cargo.lock b/Cargo.lock index 77c63a42080..73509b03201 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6725,10 +6725,13 @@ name = "nexus-reconfigurator-simulation" version = "0.1.0" dependencies = [ "anyhow", + "camino", "chrono", "indexmap 2.9.0", + "itertools 0.14.0", "nexus-inventory", "nexus-reconfigurator-planning", + "nexus-sled-agent-shared", "nexus-types", "omicron-common", "omicron-uuid-kinds", @@ -6738,6 +6741,7 @@ dependencies = [ "swrite", "sync-ptr", "thiserror 2.0.12", + "tufaceous-artifact", "typed-rng", "uuid", ] diff --git a/dev-tools/reconfigurator-cli/src/lib.rs b/dev-tools/reconfigurator-cli/src/lib.rs index cb1fc0c74d9..5345d74c6b9 100644 --- a/dev-tools/reconfigurator-cli/src/lib.rs +++ b/dev-tools/reconfigurator-cli/src/lib.rs @@ -5,7 +5,7 @@ //! developer REPL for driving blueprint planning use anyhow::{Context, anyhow, bail}; -use camino::Utf8PathBuf; +use camino::{Utf8Path, Utf8PathBuf}; use clap::ValueEnum; use clap::{Args, Parser, Subcommand}; use iddqd::IdOrdMap; @@ -20,9 +20,9 @@ use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; use nexus_reconfigurator_planning::example::ExampleSystemBuilder; use nexus_reconfigurator_planning::planner::Planner; use nexus_reconfigurator_planning::system::{SledBuilder, SystemDescription}; -use nexus_reconfigurator_simulation::SimStateBuilder; -use nexus_reconfigurator_simulation::Simulator; use nexus_reconfigurator_simulation::{BlueprintId, SimState}; +use nexus_reconfigurator_simulation::{SimStateBuilder, SimTufRepoSource}; +use nexus_reconfigurator_simulation::{SimTufRepoDescription, Simulator}; use nexus_sled_agent_shared::inventory::ZoneKind; use nexus_types::deployment::PlanningInput; use nexus_types::deployment::SledFilter; @@ -39,18 +39,19 @@ use nexus_types::deployment::{OmicronZoneNic, TargetReleaseDescription}; use nexus_types::external_api::views::SledPolicy; use nexus_types::external_api::views::SledProvisionPolicy; use omicron_common::address::REPO_DEPOT_PORT; -use omicron_common::api::external::Generation; use omicron_common::api::external::Name; +use omicron_common::api::external::{Generation, TufRepoDescription}; use omicron_common::policy::NEXUS_REDUNDANCY; +use omicron_common::update::OmicronZoneManifestSource; use omicron_repl_utils::run_repl_from_file; use omicron_repl_utils::run_repl_on_stdin; -use omicron_uuid_kinds::CollectionUuid; use omicron_uuid_kinds::GenericUuid; use omicron_uuid_kinds::OmicronZoneUuid; use omicron_uuid_kinds::ReconfiguratorSimUuid; use omicron_uuid_kinds::SledUuid; use omicron_uuid_kinds::VnicUuid; use omicron_uuid_kinds::{BlueprintUuid, MupdateOverrideUuid}; +use omicron_uuid_kinds::{CollectionUuid, MupdateUuid}; use std::borrow::Cow; use std::convert::Infallible; use std::fmt::{self, Write}; @@ -220,6 +221,9 @@ fn process_command( Commands::SledRemove(args) => cmd_sled_remove(sim, args), Commands::SledShow(args) => cmd_sled_show(sim, args), Commands::SledSetPolicy(args) => cmd_sled_set_policy(sim, args), + Commands::SledUpdateInstallDataset(args) => { + cmd_sled_update_install_dataset(sim, args) + } Commands::SledUpdateSp(args) => cmd_sled_update_sp(sim, args), Commands::SiloList => cmd_silo_list(sim), Commands::SiloAdd(args) => cmd_silo_add(sim, args), @@ -275,6 +279,8 @@ enum Commands { SledShow(SledArgs), /// set a sled's policy SledSetPolicy(SledSetPolicyArgs), + /// update the install dataset on a sled, simulating a mupdate + SledUpdateInstallDataset(SledUpdateInstallDatasetArgs), /// simulate updating the sled's SP versions SledUpdateSp(SledUpdateSpArgs), @@ -395,6 +401,52 @@ impl From for SledPolicy { } } +#[derive(Debug, Args)] +struct SledUpdateInstallDatasetArgs { + /// id of the sled + sled_id: SledOpt, + + #[clap(flatten)] + source: SledMupdateSource, +} + +#[derive(Debug, Args)] +// This makes it so that only one source can be specified. +struct SledMupdateSource { + #[clap(flatten)] + valid: SledMupdateValidSource, + + /// set the mupdate source to Installinator with the given ID + #[clap(long, requires = "sled-mupdate-valid-source")] + mupdate_id: Option, + + /// simulate an error reading the zone manifest + #[clap(long, conflicts_with = "sled-mupdate-valid-source")] + with_manifest_error: bool, + + /// simulate an error validating zones by this artifact ID name + /// + /// This uses the `artifact_id_name` representation of a zone kind. + #[clap( + long, + value_name = "ARTIFACT_ID_NAME", + requires = "sled-mupdate-valid-source" + )] + with_zone_error: Vec, +} + +#[derive(Debug, Args)] +#[group(id = "sled-mupdate-valid-source", multiple = false)] +struct SledMupdateValidSource { + /// the TUF repo.zip to simulate the mupdate from + #[clap(long)] + from_repo: Option, + + /// simulate a mupdate to the target release + #[clap(long)] + to_target_release: bool, +} + #[derive(Debug, Args)] struct SledUpdateSpArgs { /// id of the sled @@ -879,6 +931,10 @@ struct TufAssembleArgs { /// The tufaceous manifest path (relative to this crate's root) manifest_path: Utf8PathBuf, + /// Allow non-semver artifact versions. + #[clap(long)] + allow_non_semver: bool, + #[clap( long, // Use help here rather than a doc comment because rustdoc doesn't like @@ -1156,6 +1212,32 @@ fn cmd_sled_set_policy( Ok(Some(format!("set sled {} policy to {}", sled_id, args.policy))) } +fn cmd_sled_update_install_dataset( + sim: &mut ReconfiguratorSim, + args: SledUpdateInstallDatasetArgs, +) -> anyhow::Result> { + let description = mupdate_source_to_description(sim, &args.source)?; + + let mut state = sim.current_state().to_mut(); + let system = state.system_mut(); + let sled_id = args.sled_id.to_sled_id(system.description())?; + system + .description_mut() + .sled_set_zone_manifest(sled_id, description.to_boot_inventory())?; + + sim.commit_and_bump( + format!( + "reconfigurator-cli sled-update-install-dataset: {}", + description.message, + ), + state, + ); + Ok(Some(format!( + "sled {}: install dataset updated: {}", + sled_id, description.message + ))) +} + fn cmd_sled_update_sp( sim: &mut ReconfiguratorSim, args: SledUpdateSpArgs, @@ -1955,26 +2037,8 @@ fn cmd_set( rv } SetArgs::TargetRelease { filename } => { - let file = std::fs::File::open(&filename) - .with_context(|| format!("open {:?}", filename))?; - let buf = std::io::BufReader::new(file); - let rt = tokio::runtime::Runtime::new() - .context("creating tokio runtime")?; - // We're not using the repo hash here. Make one up. - let repo_hash = ArtifactHash([0; 32]); - let artifacts_with_plan = rt.block_on(async { - ArtifactsWithPlan::from_zip( - buf, - None, - repo_hash, - ControlPlaneZonesMode::Split, - &sim.log, - ) - .await - .with_context(|| format!("unpacking {:?}", filename)) - })?; - let description = artifacts_with_plan.description().clone(); - drop(artifacts_with_plan); + let description = + extract_tuf_repo_description(&sim.log, &filename)?; state.system_mut().description_mut().set_target_release( TargetReleaseDescription::TufRepo(description), ); @@ -1986,6 +2050,84 @@ fn cmd_set( Ok(Some(rv)) } +/// Converts a mupdate source to a TUF repo description. +fn mupdate_source_to_description( + sim: &ReconfiguratorSim, + source: &SledMupdateSource, +) -> anyhow::Result { + let manifest_source = match source.mupdate_id { + Some(mupdate_id) => { + OmicronZoneManifestSource::Installinator { mupdate_id } + } + None => OmicronZoneManifestSource::SledAgent, + }; + if let Some(repo_path) = &source.valid.from_repo { + let description = extract_tuf_repo_description(&sim.log, repo_path)?; + let mut sim_source = SimTufRepoSource::new( + description, + manifest_source, + format!("from repo at {repo_path}"), + )?; + sim_source.simulate_zone_errors(&source.with_zone_error)?; + Ok(SimTufRepoDescription::new(sim_source)) + } else if source.valid.to_target_release { + let description = sim + .current_state() + .system() + .description() + .target_release() + .description(); + match description { + TargetReleaseDescription::Initial => { + bail!( + "cannot mupdate zones without a target release \ + (use `set target-release` or --from-repo)" + ) + } + TargetReleaseDescription::TufRepo(desc) => { + let mut sim_source = SimTufRepoSource::new( + desc.clone(), + manifest_source, + "to target release".to_owned(), + )?; + sim_source.simulate_zone_errors(&source.with_zone_error)?; + Ok(SimTufRepoDescription::new(sim_source)) + } + } + } else if source.with_manifest_error { + Ok(SimTufRepoDescription::new_error( + "simulated error obtaining zone manifest".to_owned(), + )) + } else { + bail!("an update source must be specified") + } +} + +fn extract_tuf_repo_description( + log: &slog::Logger, + filename: &Utf8Path, +) -> anyhow::Result { + let file = std::fs::File::open(filename) + .with_context(|| format!("open {:?}", filename))?; + let buf = std::io::BufReader::new(file); + let rt = + tokio::runtime::Runtime::new().context("creating tokio runtime")?; + let repo_hash = ArtifactHash([0; 32]); + let artifacts_with_plan = rt.block_on(async { + ArtifactsWithPlan::from_zip( + buf, + None, + repo_hash, + ControlPlaneZonesMode::Split, + log, + ) + .await + .with_context(|| format!("unpacking {:?}", filename)) + })?; + let description = artifacts_with_plan.description().clone(); + Ok(description) +} + fn cmd_tuf_assemble( sim: &ReconfiguratorSim, args: TufAssembleArgs, @@ -2016,18 +2158,26 @@ fn cmd_tuf_assemble( Utf8PathBuf::from(format!("repo-{}.zip", manifest.system_version)) }; + if output_path.exists() { + bail!("output path `{output_path}` already exists"); + } + // Just use a fixed key for now. // // In the future we may want to test changing the TUF key. - let args = tufaceous::Args::try_parse_from([ + let mut tufaceous_args = vec![ "tufaceous", "--key", DEFAULT_TUFACEOUS_KEY, "assemble", manifest_path.as_str(), output_path.as_str(), - ]) - .expect("args are valid so this shouldn't fail"); + ]; + if args.allow_non_semver { + tufaceous_args.push("--allow-non-semver"); + } + let args = tufaceous::Args::try_parse_from(tufaceous_args) + .expect("args are valid so this shouldn't fail"); let rt = tokio::runtime::Runtime::new().context("creating tokio runtime")?; rt.block_on(async move { args.exec(&sim.log).await }) diff --git a/dev-tools/reconfigurator-cli/tests/input/cmds-noop-image-source.txt b/dev-tools/reconfigurator-cli/tests/input/cmds-noop-image-source.txt new file mode 100644 index 00000000000..f945938d882 --- /dev/null +++ b/dev-tools/reconfigurator-cli/tests/input/cmds-noop-image-source.txt @@ -0,0 +1,50 @@ +# Load an example system. The sled with serial5 is marked non-provisionable +# so that discretionary zones don't make their way onto it. (We're going to +# expunge it below to test that we don't try and update zone image sources +# on expunged sleds.) +load-example --nsleds 6 --ndisks-per-sled 1 --sled-policy 5:non-provisionable + +sled-list + +# Create a TUF repository from a fake manifest. (The output TUF repo is +# written to a temporary directory that this invocation of `reconfigurator-cli` +# is running out of as its working directory.) +tuf-assemble ../../update-common/manifests/fake.toml +# Create a second TUF repository from a different fake manifest. +tuf-assemble ../../update-common/manifests/fake-non-semver.toml --allow-non-semver + +# Load the target release from the first TUF repository. +set target-release repo-1.0.0.zip + +# On one sled, update the install dataset. +sled-update-install-dataset serial0 --to-target-release + +# On another sled, simulate an error reading the zone manifest. +sled-update-install-dataset serial1 --with-manifest-error + +# On a third sled, update the install dataset and simulate a mupdate override. +# (Currently we do this in the blueprint, but with +# https://github.com/oxidecomputer/omicron/pull/8456 we should update this test and +# set a mupdate-override on the sled directly.) +sled-update-install-dataset serial2 --to-target-release +blueprint-edit latest set-remove-mupdate-override serial2 ffffffff-ffff-ffff-ffff-ffffffffffff + +# On a fourth sled, simulate an error validating the install dataset image on one zone. +# We pick ntp because internal-ntp is non-discretionary. +sled-update-install-dataset serial3 --to-target-release --with-zone-error ntp + +# On a fifth sled, set the install dataset to the repo-2.0.0.zip generated by the +# second TUF repository. +sled-update-install-dataset serial4 --from-repo repo-2.0.0.zip + +# On the sixth sled, update to the target release (so it shows up in inventory). +# Then, mark the sled expunged (in the planning input). +sled-update-install-dataset serial5 --to-target-release +sled-set-policy serial5 expunged + +# Generate an inventory and run a blueprint planning step. +inventory-generate +blueprint-plan latest eb0796d5-ab8a-4f7b-a884-b4aeacb8ab51 + +# This diff should show expected changes to the blueprint. +blueprint-diff 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 latest diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-add-sled-no-disks-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-add-sled-no-disks-stdout index c8f44364945..065cb018b31 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-add-sled-no-disks-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-add-sled-no-disks-stdout @@ -36,6 +36,7 @@ generated inventory collection eb0796d5-ab8a-4f7b-a884-b4aeacb8ab51 from configu > # Try to plan a new blueprint; this should be okay even though the sled > # we added has no disks. > blueprint-plan dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 eb0796d5-ab8a-4f7b-a884-b4aeacb8ab51 +INFO skipping noop image source check for all sleds (no current TUF repo) INFO skipping sled (no zpools in service), sled_id: 00320471-945d-413c-85e7-03e091a70b3c INFO sufficient BoundaryNtp zones exist in plan, desired_count: 0, current_count: 0 INFO sufficient Clickhouse zones exist in plan, desired_count: 1, current_count: 1 diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout index e6da83d20ba..e6bd81ca525 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout @@ -493,6 +493,7 @@ T ENA ID PARENT * yes ade5749d-bdf3-4fab-a8ae-00bea01b3a5a 02697f74-b14a-4418-90f0-c28b2a3a6aa9 > blueprint-plan ade5749d-bdf3-4fab-a8ae-00bea01b3a5a +INFO skipping noop image source check for all sleds (no current TUF repo) INFO found sled missing NTP zone (will add one), sled_id: 89d02b1b-478c-401a-8e28-7a26f74fa41b INFO sufficient BoundaryNtp zones exist in plan, desired_count: 0, current_count: 0 WARN failed to place all new desired Clickhouse zones, placed: 0, wanted_to_place: 1 @@ -936,6 +937,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 > # Plan a blueprint run -- this will cause zones and disks on the expunged > # sled to be expunged. > blueprint-plan latest +INFO skipping noop image source check for all sleds (no current TUF repo) INFO sufficient BoundaryNtp zones exist in plan, desired_count: 0, current_count: 0 INFO sufficient Clickhouse zones exist in plan, desired_count: 1, current_count: 1 INFO sufficient ClickhouseKeeper zones exist in plan, desired_count: 0, current_count: 0 diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-external-dns-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-external-dns-stdout index 96d99da31ee..8732ed80b6e 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-external-dns-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-external-dns-stdout @@ -969,6 +969,7 @@ parent: 3f00b694-1b16-4aaa-8f78-e6b3a527b434 > # blueprint-plan will place a new external DNS zone, diff DNS to see the new zone has `ns` and NS records. > blueprint-plan 366b0b68-d80e-4bc1-abd3-dc69837847e0 +INFO skipping noop image source check for all sleds (no current TUF repo) INFO sufficient BoundaryNtp zones exist in plan, desired_count: 0, current_count: 0 INFO sufficient Clickhouse zones exist in plan, desired_count: 1, current_count: 1 INFO sufficient ClickhouseKeeper zones exist in plan, desired_count: 0, current_count: 0 diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-internal-dns-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-internal-dns-stdout index a2125c4f88e..132fdeb474f 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-internal-dns-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-expunge-newly-added-internal-dns-stdout @@ -1002,6 +1002,7 @@ external DNS: > # Planning a new blueprint will now replace the expunged zone, with new records for its replacement. > blueprint-plan 58d5e830-0884-47d8-a7cd-b2b3751adeb4 +INFO skipping noop image source check for all sleds (no current TUF repo) INFO sufficient BoundaryNtp zones exist in plan, desired_count: 0, current_count: 0 INFO sufficient Clickhouse zones exist in plan, desired_count: 1, current_count: 1 INFO sufficient ClickhouseKeeper zones exist in plan, desired_count: 0, current_count: 0 diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stderr b/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stderr new file mode 100644 index 00000000000..e69de29bb2d diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout new file mode 100644 index 00000000000..11fd49b79ad --- /dev/null +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout @@ -0,0 +1,480 @@ +using provided RNG seed: reconfigurator-cli-test +> # Load an example system. The sled with serial5 is marked non-provisionable +> # so that discretionary zones don't make their way onto it. (We're going to +> # expunge it below to test that we don't try and update zone image sources +> # on expunged sleds.) +> load-example --nsleds 6 --ndisks-per-sled 1 --sled-policy 5:non-provisionable +loaded example system with: +- collection: f45ba181-4b56-42cc-a762-874d90184a43 +- blueprint: dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 + + +> sled-list +ID SERIAL NZPOOLS SUBNET +2b8f0cb3-0295-4b3c-bc58-4fe88b57112c serial1 1 fd00:1122:3344:102::/64 +98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 serial0 1 fd00:1122:3344:101::/64 +9a867dc9-d505-427f-9eff-cdb1d4d9bd73 serial5 1 fd00:1122:3344:106::/64 +aff6c093-197d-42c5-ad80-9f10ba051a34 serial3 1 fd00:1122:3344:104::/64 +b82ede02-399c-48c6-a1de-411df4fa49a7 serial4 1 fd00:1122:3344:105::/64 +d81c6a84-79b8-4958-ae41-ea46c9b19763 serial2 1 fd00:1122:3344:103::/64 + + +> # Create a TUF repository from a fake manifest. (The output TUF repo is +> # written to a temporary directory that this invocation of `reconfigurator-cli` +> # is running out of as its working directory.) +> tuf-assemble ../../update-common/manifests/fake.toml +INFO assembling repository in +INFO artifacts assembled and archived to `repo-1.0.0.zip`, component: OmicronRepoAssembler +created repo-1.0.0.zip for system version 1.0.0 + +> # Create a second TUF repository from a different fake manifest. +> tuf-assemble ../../update-common/manifests/fake-non-semver.toml --allow-non-semver +INFO assembling repository in +INFO artifacts assembled and archived to `repo-2.0.0.zip`, component: OmicronRepoAssembler +created repo-2.0.0.zip for system version 2.0.0 + + +> # Load the target release from the first TUF repository. +> set target-release repo-1.0.0.zip +INFO extracting uploaded archive to +INFO created directory to store extracted artifacts, path: +INFO added artifact, name: SimGimletSp, kind: gimlet_sp, version: 1.0.0, hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, length: 747 +INFO added artifact, name: fake-gimlet-rot, kind: gimlet_rot_image_a, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 +INFO added artifact, name: fake-gimlet-rot, kind: gimlet_rot_image_b, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 +INFO added artifact, name: fake-gimlet-rot-bootloader, kind: gimlet_rot_bootloader, version: 1.0.0, hash: 005ea358f1cd316df42465b1e3a0334ea22cc0c0442cf9ddf9b42fbf49780236, length: 750 +INFO added artifact, name: fake-host, kind: host_phase_1, version: 1.0.0, hash: 2053f8594971bbf0a7326c833e2ffc12b065b9d823b9c0b967d275fa595e4e89, length: 524288 +INFO added artifact, name: fake-host, kind: host_phase_2, version: 1.0.0, hash: f3dd0c7a1bd4500ea0d8bcf67581f576d47752b2f1998a4cb0f0c3155c483008, length: 1048576 +INFO added artifact, name: fake-trampoline, kind: trampoline_phase_1, version: 1.0.0, hash: 9b7575cad720f017e936fe5994fc4e21fe040acaaf83c2edd86132aa3d667c7b, length: 524288 +INFO added artifact, name: fake-trampoline, kind: trampoline_phase_2, version: 1.0.0, hash: f355fb8429a7e0f0716dad035f9a06c799168d6c0ffcde85b1a96fef21d4b53e, length: 1048576 +INFO added artifact, name: clickhouse, kind: zone, version: 1.0.0, hash: 52b1eb4daff6f9140491d547b11248392920230db3db0eef5f5fa5333fe9e659, length: 1686 +INFO added artifact, name: clickhouse_keeper, kind: zone, version: 1.0.0, hash: cda702919449d86663be97295043aeca0ead69ae5db3bbdb20053972254a27a3, length: 1690 +INFO added artifact, name: clickhouse_server, kind: zone, version: 1.0.0, hash: 5f9ae6a9821bbe8ff0bf60feddf8b167902fe5f3e2c98bd21edd1ec9d969a001, length: 1690 +INFO added artifact, name: cockroachdb, kind: zone, version: 1.0.0, hash: f3a1a3c0b3469367b005ee78665d982059d5e14e93a479412426bf941c4ed291, length: 1689 +INFO added artifact, name: crucible-zone, kind: zone, version: 1.0.0, hash: 6f17cf65fb5a5bec5542dd07c03cd0acc01e59130f02c532c8d848ecae810047, length: 1690 +INFO added artifact, name: crucible-pantry-zone, kind: zone, version: 1.0.0, hash: 21f0ada306859c23917361f2e0b9235806c32607ec689c7e8cf16bb898bc5a02, length: 1695 +INFO added artifact, name: external-dns, kind: zone, version: 1.0.0, hash: ccca13ed19b8731f9adaf0d6203b02ea3b9ede4fa426b9fac0a07ce95440046d, length: 1689 +INFO added artifact, name: internal-dns, kind: zone, version: 1.0.0, hash: ffbf1373f7ee08dddd74c53ed2a94e7c4c572a982d3a9bc94000c6956b700c6a, length: 1689 +INFO added artifact, name: ntp, kind: zone, version: 1.0.0, hash: 67593d686ed04a1709f93972b71f4ebc148a9362120f65d239943e814a9a7439, length: 1681 +INFO added artifact, name: nexus, kind: zone, version: 1.0.0, hash: 0e32b4a3e5d3668bb1d6a16fb06b74dc60b973fa479dcee0aae3adbb52bf1388, length: 1682 +INFO added artifact, name: oximeter, kind: zone, version: 1.0.0, hash: 048d8fe8cdef5b175aad714d0f148aa80ce36c9114ac15ce9d02ed3d37877a77, length: 1682 +INFO added artifact, name: fake-psc-sp, kind: psc_sp, version: 1.0.0, hash: f896cf5b19ca85864d470ad8587f980218bff3954e7f52bbd999699cd0f9635b, length: 744 +INFO added artifact, name: fake-psc-rot, kind: psc_rot_image_a, version: 1.0.0, hash: 179eb660ebc92e28b6748b6af03d9f998d6131319edd4654a1e948454c62551b, length: 750 +INFO added artifact, name: fake-psc-rot, kind: psc_rot_image_b, version: 1.0.0, hash: 179eb660ebc92e28b6748b6af03d9f998d6131319edd4654a1e948454c62551b, length: 750 +INFO added artifact, name: fake-psc-rot-bootloader, kind: psc_rot_bootloader, version: 1.0.0, hash: 005ea358f1cd316df42465b1e3a0334ea22cc0c0442cf9ddf9b42fbf49780236, length: 750 +INFO added artifact, name: fake-switch-sp, kind: switch_sp, version: 1.0.0, hash: ab32ec86e942e1a16c8d43ea143cd80dd05a9639529d3569b1c24dfa2587ee74, length: 740 +INFO added artifact, name: fake-switch-rot, kind: switch_rot_image_a, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 +INFO added artifact, name: fake-switch-rot, kind: switch_rot_image_b, version: 1.0.0, hash: 04e4a7fdb84acca92c8fd3235e26d64ea61bef8a5f98202589fd346989c5720a, length: 735 +INFO added artifact, name: fake-switch-rot-bootloader, kind: switch_rot_bootloader, version: 1.0.0, hash: 005ea358f1cd316df42465b1e3a0334ea22cc0c0442cf9ddf9b42fbf49780236, length: 750 +set target release based on repo-1.0.0.zip + + +> # On one sled, update the install dataset. +> sled-update-install-dataset serial0 --to-target-release +sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: install dataset updated: to target release (system version 1.0.0) + + +> # On another sled, simulate an error reading the zone manifest. +> sled-update-install-dataset serial1 --with-manifest-error +sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c: install dataset updated: simulated error obtaining zone manifest + + +> # On a third sled, update the install dataset and simulate a mupdate override. +> # (Currently we do this in the blueprint, but with +> # https://github.com/oxidecomputer/omicron/pull/8456 we should update this test and +> # set a mupdate-override on the sled directly.) +> sled-update-install-dataset serial2 --to-target-release +sled d81c6a84-79b8-4958-ae41-ea46c9b19763: install dataset updated: to target release (system version 1.0.0) + +> blueprint-edit latest set-remove-mupdate-override serial2 ffffffff-ffff-ffff-ffff-ffffffffffff +blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 created from latest blueprint (dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21): set remove_mupdate_override to ffffffff-ffff-ffff-ffff-ffffffffffff + + +> # On a fourth sled, simulate an error validating the install dataset image on one zone. +> # We pick ntp because internal-ntp is non-discretionary. +> sled-update-install-dataset serial3 --to-target-release --with-zone-error ntp +sled aff6c093-197d-42c5-ad80-9f10ba051a34: install dataset updated: to target release (system version 1.0.0, 1 zone errors) + + +> # On a fifth sled, set the install dataset to the repo-2.0.0.zip generated by the +> # second TUF repository. +> sled-update-install-dataset serial4 --from-repo repo-2.0.0.zip +INFO extracting uploaded archive to +INFO created directory to store extracted artifacts, path: +INFO added artifact, name: fake-gimlet-sp, kind: gimlet_sp, version: 2.0.0, hash: ce1e98a8a9ae541654508f101d59a3ddeba3d28177f1d42d5614248eef0b820b, length: 751 +INFO added artifact, name: fake-gimlet-rot, kind: gimlet_rot_image_a, version: 2.0.0, hash: e7047f500a5391e22cd8e6a8d3ae66c9d9de7a8d021e6e9a10e05bb6d554da77, length: 743 +INFO added artifact, name: fake-gimlet-rot, kind: gimlet_rot_image_b, version: 2.0.0, hash: e7047f500a5391e22cd8e6a8d3ae66c9d9de7a8d021e6e9a10e05bb6d554da77, length: 743 +INFO added artifact, name: fake-gimlet-rot-bootloader, kind: gimlet_rot_bootloader, version: 2.0.0, hash: 238a9bfc87f02141c7555ff5ebb7a22ec37bc24d6f724ce3af05ed7c412cd115, length: 750 +INFO added artifact, name: fake-host, kind: host_phase_1, version: 2.0.0, hash: 44714733af7600b30a50bfd2cbaf707ff7ee9724073ff70a6732e55a88864cf6, length: 524288 +INFO added artifact, name: fake-host, kind: host_phase_2, version: 2.0.0, hash: 0c0362b640cece5b9a5e86d8fa683bd2eb84c3e7f90731f597197d604ffa76e3, length: 1048576 +INFO added artifact, name: fake-trampoline, kind: trampoline_phase_1, version: non-semver, hash: 24f8ca0d52da5238644b11964c6feda854c7530820713efefa7ac91683b3fc76, length: 524288 +INFO added artifact, name: fake-trampoline, kind: trampoline_phase_2, version: non-semver, hash: 5fceee33d358aacb8a34ca93a30e28354bd8f341f6e3e895a2cafe83904f3d80, length: 1048576 +INFO added artifact, name: clickhouse, kind: zone, version: 2.0.0, hash: bb2d1ff02d11f72bc9049ae57f27536207519a1859d29f8d7a90ab3b44d56b08, length: 1687 +INFO added artifact, name: clickhouse_keeper, kind: zone, version: 2.0.0, hash: 1eb9f24be68f13c274aa0ac9b863cec520dbfe762620c328431728d75bfd2198, length: 1691 +INFO added artifact, name: clickhouse_server, kind: zone, version: 2.0.0, hash: 50fe271948672a9af1ba5f96c9d87ff2736fa72d78dfef598a79fa0cc8a00474, length: 1691 +INFO added artifact, name: cockroachdb, kind: zone, version: 2.0.0, hash: ebc82bf181db864b78cb7e3ddedf7ab1dd8fe7b377b02846f3c27cf0387bb387, length: 1690 +INFO added artifact, name: crucible-zone, kind: zone, version: 2.0.0, hash: 866f6a7c2e51c056fb722b5113e80181cc9cd8b712a0d3dbf1edc4ce29e5229e, length: 1691 +INFO added artifact, name: crucible-pantry-zone, kind: zone, version: 2.0.0, hash: 3ff26dad96faa8f67251f5de40458b4f809d536bfe8572134da0e42c2fa12674, length: 1696 +INFO added artifact, name: external-dns, kind: zone, version: 2.0.0, hash: f282c45771429f7bebf71f0cc668521066db57c6bb07fcfccdfb44825d3d930f, length: 1690 +INFO added artifact, name: internal-dns, kind: zone, version: 2.0.0, hash: de30657a72b066b8ef1f56351a0a5d4d7000da0a62c4be9b2e949a107ca8a389, length: 1690 +INFO added artifact, name: ntp, kind: zone, version: 2.0.0, hash: d76e26198daed69cdae04490d7477f8c842e0dbe37d463eac0d0a8d3fb803095, length: 1682 +INFO added artifact, name: nexus, kind: zone, version: 2.0.0, hash: e9b7035f41848a987a798c15ac424cc91dd662b1af0920d58d8aa1ebad7467b6, length: 1683 +INFO added artifact, name: oximeter, kind: zone, version: 2.0.0, hash: 9f4bc56a15d5fd943fdac94309994b8fd73aa2be1ec61faf44bfcf2356c9dc23, length: 1683 +INFO added artifact, name: fake-psc-sp, kind: psc_sp, version: 2.0.0, hash: 7adf04de523865003dbf120cebddd5fcf5bad650640281b294197e6ca7016e47, length: 748 +INFO added artifact, name: fake-psc-rot, kind: psc_rot_image_a, version: 2.0.0, hash: 6d1c432647e9b9e4cf846ff5d17932d75cba49c0d3f23d24243238bc40bcfef5, length: 746 +INFO added artifact, name: fake-psc-rot, kind: psc_rot_image_b, version: 2.0.0, hash: 6d1c432647e9b9e4cf846ff5d17932d75cba49c0d3f23d24243238bc40bcfef5, length: 746 +INFO added artifact, name: fake-psc-rot-bootloader, kind: psc_rot_bootloader, version: 2.0.0, hash: 238a9bfc87f02141c7555ff5ebb7a22ec37bc24d6f724ce3af05ed7c412cd115, length: 750 +INFO added artifact, name: fake-switch-sp, kind: switch_sp, version: 2.0.0, hash: 5a9019c484c051edfab4903a7a5e1817c89bd555eea3e48f6b92c6e67442e13e, length: 746 +INFO added artifact, name: fake-switch-rot, kind: switch_rot_image_a, version: 2.0.0, hash: e7047f500a5391e22cd8e6a8d3ae66c9d9de7a8d021e6e9a10e05bb6d554da77, length: 743 +INFO added artifact, name: fake-switch-rot, kind: switch_rot_image_b, version: 2.0.0, hash: e7047f500a5391e22cd8e6a8d3ae66c9d9de7a8d021e6e9a10e05bb6d554da77, length: 743 +INFO added artifact, name: fake-switch-rot-bootloader, kind: switch_rot_bootloader, version: non-semver-2, hash: a0d6df68e6112edcf62c035947563d2a58d06e11443b95b90bf087da710550a5, length: 758 +sled b82ede02-399c-48c6-a1de-411df4fa49a7: install dataset updated: from repo at repo-2.0.0.zip (system version 2.0.0) + + +> # On the sixth sled, update to the target release (so it shows up in inventory). +> # Then, mark the sled expunged (in the planning input). +> sled-update-install-dataset serial5 --to-target-release +sled 9a867dc9-d505-427f-9eff-cdb1d4d9bd73: install dataset updated: to target release (system version 1.0.0) + +> sled-set-policy serial5 expunged +set sled 9a867dc9-d505-427f-9eff-cdb1d4d9bd73 policy to expunged + + +> # Generate an inventory and run a blueprint planning step. +> inventory-generate +generated inventory collection eb0796d5-ab8a-4f7b-a884-b4aeacb8ab51 from configured sleds + +> blueprint-plan latest eb0796d5-ab8a-4f7b-a884-b4aeacb8ab51 +WARN skipping noop image source check since sled-agent encountered error retrieving zone manifest (this is abnormal), sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, error: reconfigurator-sim simulated error: simulated error obtaining zone manifest +INFO install dataset artifact hash matches TUF repo, switching out the zone image source to Artifact, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, tuf_artifact_id: nexus v1.0.0 (zone) +INFO install dataset artifact hash matches TUF repo, switching out the zone image source to Artifact, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, tuf_artifact_id: internal-dns v1.0.0 (zone) +INFO install dataset artifact hash matches TUF repo, switching out the zone image source to Artifact, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, tuf_artifact_id: crucible-zone v1.0.0 (zone) +INFO install dataset artifact hash matches TUF repo, switching out the zone image source to Artifact, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, tuf_artifact_id: ntp v1.0.0 (zone) +INFO install dataset artifact hash matches TUF repo, switching out the zone image source to Artifact, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, tuf_artifact_id: external-dns v1.0.0 (zone) +INFO install dataset artifact hash matches TUF repo, switching out the zone image source to Artifact, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, tuf_artifact_id: crucible-pantry-zone v1.0.0 (zone) +INFO noop converting 6/6 install-dataset zones to artifact store, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 +INFO install dataset artifact hash matches TUF repo, switching out the zone image source to Artifact, sled_id: aff6c093-197d-42c5-ad80-9f10ba051a34, tuf_artifact_id: nexus v1.0.0 (zone) +INFO install dataset artifact hash matches TUF repo, switching out the zone image source to Artifact, sled_id: aff6c093-197d-42c5-ad80-9f10ba051a34, tuf_artifact_id: external-dns v1.0.0 (zone) +INFO install dataset artifact hash matches TUF repo, switching out the zone image source to Artifact, sled_id: aff6c093-197d-42c5-ad80-9f10ba051a34, tuf_artifact_id: crucible-zone v1.0.0 (zone) +INFO install dataset artifact hash matches TUF repo, switching out the zone image source to Artifact, sled_id: aff6c093-197d-42c5-ad80-9f10ba051a34, tuf_artifact_id: internal-dns v1.0.0 (zone) +INFO install dataset artifact hash matches TUF repo, switching out the zone image source to Artifact, sled_id: aff6c093-197d-42c5-ad80-9f10ba051a34, tuf_artifact_id: crucible-pantry-zone v1.0.0 (zone) +WARN zone manifest inventory indicated install dataset artifact is invalid, not using artifact (this is abnormal), sled_id: aff6c093-197d-42c5-ad80-9f10ba051a34, zone_id: e8fe709c-725f-4bb2-b714-ffcda13a9e54, kind: internal_ntp, file_name: ntp.tar.gz, error: reconfigurator-sim: simulated error validating zone image +INFO noop converting 5/6 install-dataset zones to artifact store, sled_id: aff6c093-197d-42c5-ad80-9f10ba051a34 +INFO noop converting 0/2 install-dataset zones to artifact store, sled_id: b82ede02-399c-48c6-a1de-411df4fa49a7 +INFO skipping noop image source check on sled (blueprint has get_remove_mupdate_override set for sled), sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, bp_remove_mupdate_override_id: ffffffff-ffff-ffff-ffff-ffffffffffff +INFO sufficient BoundaryNtp zones exist in plan, desired_count: 0, current_count: 0 +INFO sufficient Clickhouse zones exist in plan, desired_count: 1, current_count: 1 +INFO sufficient ClickhouseKeeper zones exist in plan, desired_count: 0, current_count: 0 +INFO sufficient ClickhouseServer zones exist in plan, desired_count: 0, current_count: 0 +INFO sufficient CockroachDb zones exist in plan, desired_count: 0, current_count: 0 +INFO sufficient CruciblePantry zones exist in plan, desired_count: 3, current_count: 3 +INFO sufficient InternalDns zones exist in plan, desired_count: 3, current_count: 3 +INFO sufficient ExternalDns zones exist in plan, desired_count: 3, current_count: 3 +INFO sufficient Nexus zones exist in plan, desired_count: 3, current_count: 3 +INFO sufficient Oximeter zones exist in plan, desired_count: 0, current_count: 0 +INFO configuring SP update, artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: NoValidVersion, expected_active_version: 0.0.1, component: sp, sp_slot: 0, sp_type: Sled, serial_number: serial0, part_number: model0 +INFO reached maximum number of pending SP updates, max: 1 +INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify +generated blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 based on parent blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 + + +> # This diff should show expected changes to the blueprint. +> blueprint-diff 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 latest +from: blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 +to: blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 + + MODIFIED SLEDS: + + sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 (active, config generation 2 -> 3): + + physical disks: + ------------------------------------------------------------------------------------ + vendor model serial disposition + ------------------------------------------------------------------------------------ + fake-vendor fake-model serial-c6d33b64-fb96-4129-bab1-7878a06a5f9b in service + + + datasets: + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset id disposition quota reservation compression + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_c6d33b64-fb96-4129-bab1-7878a06a5f9b/crucible 43931274-7fe8-4077-825d-dff2bc8efa58 in service none none off + oxp_c6d33b64-fb96-4129-bab1-7878a06a5f9b/crypt/external_dns a4c3032e-21fa-4d4a-b040-a7e3c572cf3c in service none none off + oxp_c6d33b64-fb96-4129-bab1-7878a06a5f9b/crypt/internal_dns 4f60b534-eaa3-40a1-b60f-bfdf147af478 in service none none off + oxp_c6d33b64-fb96-4129-bab1-7878a06a5f9b/crypt/zone 4617d206-4330-4dfa-b9f3-f63a3db834f9 in service none none off + oxp_c6d33b64-fb96-4129-bab1-7878a06a5f9b/crypt/zone/oxz_crucible_5199c033-4cf9-4ab6-8ae7-566bd7606363 ad41be71-6c15-4428-b510-20ceacde4fa6 in service none none off + oxp_c6d33b64-fb96-4129-bab1-7878a06a5f9b/crypt/zone/oxz_crucible_pantry_ba4994a8-23f9-4b1a-a84f-a08d74591389 1bca7f71-5e42-4749-91ec-fa40793a3a9a in service none none off + oxp_c6d33b64-fb96-4129-bab1-7878a06a5f9b/crypt/zone/oxz_external_dns_803bfb63-c246-41db-b0da-d3b87ddfc63d 3ac089c9-9dec-465b-863a-188e80d71fb4 in service none none off + oxp_c6d33b64-fb96-4129-bab1-7878a06a5f9b/crypt/zone/oxz_internal_dns_427ec88f-f467-42fa-9bbb-66a91a36103c 686c19cf-a0d7-45f6-866f-c564612b2664 in service none none off + oxp_c6d33b64-fb96-4129-bab1-7878a06a5f9b/crypt/zone/oxz_nexus_0c71b3b2-6ceb-4e8f-b020-b08675e83038 793ac181-1b01-403c-850d-7f5c54bda6c9 in service none none off + oxp_c6d33b64-fb96-4129-bab1-7878a06a5f9b/crypt/zone/oxz_ntp_6444f8a5-6465-4f0b-a549-1993c113569c cdf3684f-a6cf-4449-b9ec-e696b2c663e2 in service none none off + oxp_c6d33b64-fb96-4129-bab1-7878a06a5f9b/crypt/debug 248c6c10-1ac6-45de-bb55-ede36ca56bbd in service 100 GiB none gzip-9 + + + omicron zones: + ------------------------------------------------------------------------------------------------------------------------- + zone type zone id image source disposition underlay IP + ------------------------------------------------------------------------------------------------------------------------- +* crucible 5199c033-4cf9-4ab6-8ae7-566bd7606363 - install dataset in service fd00:1122:3344:101::25 + └─ + artifact: version 1.0.0 +* crucible_pantry ba4994a8-23f9-4b1a-a84f-a08d74591389 - install dataset in service fd00:1122:3344:101::24 + └─ + artifact: version 1.0.0 +* external_dns 803bfb63-c246-41db-b0da-d3b87ddfc63d - install dataset in service fd00:1122:3344:101::23 + └─ + artifact: version 1.0.0 +* internal_dns 427ec88f-f467-42fa-9bbb-66a91a36103c - install dataset in service fd00:1122:3344:2::1 + └─ + artifact: version 1.0.0 +* internal_ntp 6444f8a5-6465-4f0b-a549-1993c113569c - install dataset in service fd00:1122:3344:101::21 + └─ + artifact: version 1.0.0 +* nexus 0c71b3b2-6ceb-4e8f-b020-b08675e83038 - install dataset in service fd00:1122:3344:101::22 + └─ + artifact: version 1.0.0 + + + sled 9a867dc9-d505-427f-9eff-cdb1d4d9bd73 (active -> decommissioned, config generation 2 -> 3): + + physical disks: + --------------------------------------------------------------------------------------- + vendor model serial disposition + --------------------------------------------------------------------------------------- +* fake-vendor fake-model serial-5ad21c90-792e-445b-b25e-285723c44243 - in service + └─ + expunged ✓ + + + datasets: + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset id disposition quota reservation compression + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +* oxp_5ad21c90-792e-445b-b25e-285723c44243/crucible 7fd50c89-afc0-4a37-ac93-ba5273120d32 - in service none none off + └─ + expunged +* oxp_5ad21c90-792e-445b-b25e-285723c44243/crypt/zone f9c25bcc-cd19-4003-a23a-807ae76e35aa - in service none none off + └─ + expunged +* oxp_5ad21c90-792e-445b-b25e-285723c44243/crypt/zone/oxz_crucible_824150c0-3fa4-4bac-9d14-c47ad04c9f3a e36ae01d-7975-429c-b802-f1bbc3b032b3 - in service none none off + └─ + expunged +* oxp_5ad21c90-792e-445b-b25e-285723c44243/crypt/zone/oxz_ntp_db288a1e-c33c-44ca-8c79-9a8978afa34d 51086d20-09ec-454c-9127-435beba2b8f0 - in service none none off + └─ + expunged +* oxp_5ad21c90-792e-445b-b25e-285723c44243/crypt/debug 639da9cf-2ce6-4e90-b95d-a0d0fc57d0f2 - in service 100 GiB none gzip-9 + └─ + expunged + + + omicron zones: + --------------------------------------------------------------------------------------------------------------- + zone type zone id image source disposition underlay IP + --------------------------------------------------------------------------------------------------------------- +* crucible 824150c0-3fa4-4bac-9d14-c47ad04c9f3a install dataset - in service fd00:1122:3344:106::22 + └─ + expunged ✓ +* internal_ntp db288a1e-c33c-44ca-8c79-9a8978afa34d install dataset - in service fd00:1122:3344:106::21 + └─ + expunged ✓ + + + sled aff6c093-197d-42c5-ad80-9f10ba051a34 (active, config generation 2 -> 3): + + physical disks: + ------------------------------------------------------------------------------------ + vendor model serial disposition + ------------------------------------------------------------------------------------ + fake-vendor fake-model serial-f3e1cbbc-5682-46ce-93a2-00a4a603bb63 in service + + + datasets: + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + dataset name dataset id disposition quota reservation compression + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + oxp_f3e1cbbc-5682-46ce-93a2-00a4a603bb63/crucible aa71e3e0-8b92-402e-89a9-4f3252af2863 in service none none off + oxp_f3e1cbbc-5682-46ce-93a2-00a4a603bb63/crypt/external_dns 0e924845-9e93-4313-bcce-23d534fcc633 in service none none off + oxp_f3e1cbbc-5682-46ce-93a2-00a4a603bb63/crypt/internal_dns 2f73afe1-14e7-4625-85a6-645d9efaa370 in service none none off + oxp_f3e1cbbc-5682-46ce-93a2-00a4a603bb63/crypt/zone cbb667a8-71f0-4ef3-a8ec-3149eaab45e0 in service none none off + oxp_f3e1cbbc-5682-46ce-93a2-00a4a603bb63/crypt/zone/oxz_crucible_8e3dd7a4-75a3-4917-a6f4-0991bbdef7ea 24bc6a37-a936-4453-96fe-61f15e9535d7 in service none none off + oxp_f3e1cbbc-5682-46ce-93a2-00a4a603bb63/crypt/zone/oxz_crucible_pantry_d07a1fed-4235-4821-a1e5-f7eb2646ff33 a2807343-8c57-45f2-877a-ca064e2e28e2 in service none none off + oxp_f3e1cbbc-5682-46ce-93a2-00a4a603bb63/crypt/zone/oxz_external_dns_43a0588f-5b57-469b-a173-db6cb6105e4c 27521fba-e5e3-418f-975b-a499010bf840 in service none none off + oxp_f3e1cbbc-5682-46ce-93a2-00a4a603bb63/crypt/zone/oxz_internal_dns_97753dbd-5a0f-4273-b1be-db6bb2b69381 f41fb7c9-c08d-463b-9095-dd7b7a39bd70 in service none none off + oxp_f3e1cbbc-5682-46ce-93a2-00a4a603bb63/crypt/zone/oxz_nexus_33862f97-2897-4d53-a9a6-78a80f7eb13f b8457107-50ca-4454-a17d-f9c0d4f94cde in service none none off + oxp_f3e1cbbc-5682-46ce-93a2-00a4a603bb63/crypt/zone/oxz_ntp_e8fe709c-725f-4bb2-b714-ffcda13a9e54 febe87b8-c8ac-4401-b9ee-bbc3be700946 in service none none off + oxp_f3e1cbbc-5682-46ce-93a2-00a4a603bb63/crypt/debug d6f0564e-e4a8-4f6f-b122-0314ff473b20 in service 100 GiB none gzip-9 + + + omicron zones: + ------------------------------------------------------------------------------------------------------------------------- + zone type zone id image source disposition underlay IP + ------------------------------------------------------------------------------------------------------------------------- + internal_ntp e8fe709c-725f-4bb2-b714-ffcda13a9e54 install dataset in service fd00:1122:3344:104::21 +* crucible 8e3dd7a4-75a3-4917-a6f4-0991bbdef7ea - install dataset in service fd00:1122:3344:104::25 + └─ + artifact: version 1.0.0 +* crucible_pantry d07a1fed-4235-4821-a1e5-f7eb2646ff33 - install dataset in service fd00:1122:3344:104::24 + └─ + artifact: version 1.0.0 +* external_dns 43a0588f-5b57-469b-a173-db6cb6105e4c - install dataset in service fd00:1122:3344:104::23 + └─ + artifact: version 1.0.0 +* internal_dns 97753dbd-5a0f-4273-b1be-db6bb2b69381 - install dataset in service fd00:1122:3344:3::1 + └─ + artifact: version 1.0.0 +* nexus 33862f97-2897-4d53-a9a6-78a80f7eb13f - install dataset in service fd00:1122:3344:104::22 + └─ + artifact: version 1.0.0 + + + COCKROACHDB SETTINGS: + state fingerprint::::::::::::::::: (none) (unchanged) + cluster.preserve_downgrade_option: (do not modify) (unchanged) + + METADATA: + internal DNS version::: 1 (unchanged) + external DNS version::: 1 (unchanged) + target release min gen: 1 (unchanged) + + OXIMETER SETTINGS: + generation: 1 (unchanged) + read from:: SingleNode (unchanged) + + PENDING MGS UPDATES: + + Pending MGS-managed updates (all baseboards): + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + sp_type slot part_number serial_number artifact_hash artifact_version details + ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ++ sled 0 model0 serial0 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670 1.0.0 Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: NoValidVersion } + + +internal DNS: +* DNS zone: "control-plane.oxide.internal": + name: 0c71b3b2-6ceb-4e8f-b020-b08675e83038.host (records: 1) + AAAA fd00:1122:3344:101::22 + name: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c.sled (records: 1) + AAAA fd00:1122:3344:102::1 + name: 33862f97-2897-4d53-a9a6-78a80f7eb13f.host (records: 1) + AAAA fd00:1122:3344:104::22 + name: 353b3b65-20f7-48c3-88f7-495bd5d31545.host (records: 1) + AAAA fd00:1122:3344:102::23 + name: 3a7c2683-58bc-479c-9c16-2f9dfc102e29.host (records: 1) + AAAA fd00:1122:3344:103::22 + name: 427ec88f-f467-42fa-9bbb-66a91a36103c.host (records: 1) + AAAA fd00:1122:3344:2::1 + name: 43a0588f-5b57-469b-a173-db6cb6105e4c.host (records: 1) + AAAA fd00:1122:3344:104::23 + name: 466a9f29-62bf-4e63-924a-b9efdb86afec.host (records: 1) + AAAA fd00:1122:3344:102::22 + name: 5199c033-4cf9-4ab6-8ae7-566bd7606363.host (records: 1) + AAAA fd00:1122:3344:101::25 + name: 62620961-fc4a-481e-968b-f5acbac0dc63.host (records: 1) + AAAA fd00:1122:3344:102::21 + name: 6444f8a5-6465-4f0b-a549-1993c113569c.host (records: 1) + AAAA fd00:1122:3344:101::21 + name: 6c3ae381-04f7-41ea-b0ac-74db387dbc3a.host (records: 1) + AAAA fd00:1122:3344:102::24 + name: 803bfb63-c246-41db-b0da-d3b87ddfc63d.host (records: 1) + AAAA fd00:1122:3344:101::23 +- name: 824150c0-3fa4-4bac-9d14-c47ad04c9f3a.host (records: 1) +- AAAA fd00:1122:3344:106::22 + name: 8e3dd7a4-75a3-4917-a6f4-0991bbdef7ea.host (records: 1) + AAAA fd00:1122:3344:104::25 + name: 97753dbd-5a0f-4273-b1be-db6bb2b69381.host (records: 1) + AAAA fd00:1122:3344:3::1 + name: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6.sled (records: 1) + AAAA fd00:1122:3344:101::1 + name: 99e2f30b-3174-40bf-a78a-90da8abba8ca.host (records: 1) + AAAA fd00:1122:3344:1::1 + name: 9a867dc9-d505-427f-9eff-cdb1d4d9bd73.sled (records: 1) + AAAA fd00:1122:3344:106::1 + name: @ (records: 3) + NS ns1.control-plane.oxide.internal + NS ns2.control-plane.oxide.internal + NS ns3.control-plane.oxide.internal + name: _clickhouse-admin-single-server._tcp (records: 1) + SRV port 8888 353b3b65-20f7-48c3-88f7-495bd5d31545.host.control-plane.oxide.internal + name: _clickhouse-native._tcp (records: 1) + SRV port 9000 353b3b65-20f7-48c3-88f7-495bd5d31545.host.control-plane.oxide.internal + name: _clickhouse._tcp (records: 1) + SRV port 8123 353b3b65-20f7-48c3-88f7-495bd5d31545.host.control-plane.oxide.internal + name: _crucible-pantry._tcp (records: 3) + SRV port 17000 ad6a3a03-8d0f-4504-99a4-cbf73d69b973.host.control-plane.oxide.internal + SRV port 17000 ba4994a8-23f9-4b1a-a84f-a08d74591389.host.control-plane.oxide.internal + SRV port 17000 d07a1fed-4235-4821-a1e5-f7eb2646ff33.host.control-plane.oxide.internal + name: _crucible._tcp.3a7c2683-58bc-479c-9c16-2f9dfc102e29 (records: 1) + SRV port 32345 3a7c2683-58bc-479c-9c16-2f9dfc102e29.host.control-plane.oxide.internal + name: _crucible._tcp.5199c033-4cf9-4ab6-8ae7-566bd7606363 (records: 1) + SRV port 32345 5199c033-4cf9-4ab6-8ae7-566bd7606363.host.control-plane.oxide.internal +- name: _crucible._tcp.824150c0-3fa4-4bac-9d14-c47ad04c9f3a (records: 1) +- SRV port 32345 824150c0-3fa4-4bac-9d14-c47ad04c9f3a.host.control-plane.oxide.internal + name: _crucible._tcp.8e3dd7a4-75a3-4917-a6f4-0991bbdef7ea (records: 1) + SRV port 32345 8e3dd7a4-75a3-4917-a6f4-0991bbdef7ea.host.control-plane.oxide.internal + name: _crucible._tcp.bd354eef-d8a6-4165-9124-283fb5e46d77 (records: 1) + SRV port 32345 bd354eef-d8a6-4165-9124-283fb5e46d77.host.control-plane.oxide.internal + name: _crucible._tcp.ecbe0b3d-1acc-44b2-b6d4-f4d2770516e4 (records: 1) + SRV port 32345 ecbe0b3d-1acc-44b2-b6d4-f4d2770516e4.host.control-plane.oxide.internal + name: _external-dns._tcp (records: 3) + SRV port 5353 43a0588f-5b57-469b-a173-db6cb6105e4c.host.control-plane.oxide.internal + SRV port 5353 6c3ae381-04f7-41ea-b0ac-74db387dbc3a.host.control-plane.oxide.internal + SRV port 5353 803bfb63-c246-41db-b0da-d3b87ddfc63d.host.control-plane.oxide.internal +* name: _internal-ntp._tcp (records: 6 -> 5) +- SRV port 123 62620961-fc4a-481e-968b-f5acbac0dc63.host.control-plane.oxide.internal +- SRV port 123 6444f8a5-6465-4f0b-a549-1993c113569c.host.control-plane.oxide.internal +- SRV port 123 b910534b-2a53-4335-a3d9-5311d2f3186a.host.control-plane.oxide.internal +- SRV port 123 db288a1e-c33c-44ca-8c79-9a8978afa34d.host.control-plane.oxide.internal +- SRV port 123 dd66f033-4fe8-438e-afb4-29d3561d4c3e.host.control-plane.oxide.internal +- SRV port 123 e8fe709c-725f-4bb2-b714-ffcda13a9e54.host.control-plane.oxide.internal ++ SRV port 123 62620961-fc4a-481e-968b-f5acbac0dc63.host.control-plane.oxide.internal ++ SRV port 123 6444f8a5-6465-4f0b-a549-1993c113569c.host.control-plane.oxide.internal ++ SRV port 123 b910534b-2a53-4335-a3d9-5311d2f3186a.host.control-plane.oxide.internal ++ SRV port 123 dd66f033-4fe8-438e-afb4-29d3561d4c3e.host.control-plane.oxide.internal ++ SRV port 123 e8fe709c-725f-4bb2-b714-ffcda13a9e54.host.control-plane.oxide.internal + name: _nameservice._tcp (records: 3) + SRV port 5353 427ec88f-f467-42fa-9bbb-66a91a36103c.host.control-plane.oxide.internal + SRV port 5353 97753dbd-5a0f-4273-b1be-db6bb2b69381.host.control-plane.oxide.internal + SRV port 5353 99e2f30b-3174-40bf-a78a-90da8abba8ca.host.control-plane.oxide.internal + name: _nexus._tcp (records: 3) + SRV port 12221 0c71b3b2-6ceb-4e8f-b020-b08675e83038.host.control-plane.oxide.internal + SRV port 12221 33862f97-2897-4d53-a9a6-78a80f7eb13f.host.control-plane.oxide.internal + SRV port 12221 466a9f29-62bf-4e63-924a-b9efdb86afec.host.control-plane.oxide.internal + name: _oximeter-reader._tcp (records: 1) + SRV port 9000 353b3b65-20f7-48c3-88f7-495bd5d31545.host.control-plane.oxide.internal + name: _repo-depot._tcp (records: 6) + SRV port 12348 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c.sled.control-plane.oxide.internal + SRV port 12348 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6.sled.control-plane.oxide.internal + SRV port 12348 9a867dc9-d505-427f-9eff-cdb1d4d9bd73.sled.control-plane.oxide.internal + SRV port 12348 aff6c093-197d-42c5-ad80-9f10ba051a34.sled.control-plane.oxide.internal + SRV port 12348 b82ede02-399c-48c6-a1de-411df4fa49a7.sled.control-plane.oxide.internal + SRV port 12348 d81c6a84-79b8-4958-ae41-ea46c9b19763.sled.control-plane.oxide.internal + name: ad6a3a03-8d0f-4504-99a4-cbf73d69b973.host (records: 1) + AAAA fd00:1122:3344:102::25 + name: aff6c093-197d-42c5-ad80-9f10ba051a34.sled (records: 1) + AAAA fd00:1122:3344:104::1 + name: b82ede02-399c-48c6-a1de-411df4fa49a7.sled (records: 1) + AAAA fd00:1122:3344:105::1 + name: b910534b-2a53-4335-a3d9-5311d2f3186a.host (records: 1) + AAAA fd00:1122:3344:105::21 + name: ba4994a8-23f9-4b1a-a84f-a08d74591389.host (records: 1) + AAAA fd00:1122:3344:101::24 + name: bd354eef-d8a6-4165-9124-283fb5e46d77.host (records: 1) + AAAA fd00:1122:3344:102::26 + name: d07a1fed-4235-4821-a1e5-f7eb2646ff33.host (records: 1) + AAAA fd00:1122:3344:104::24 + name: d81c6a84-79b8-4958-ae41-ea46c9b19763.sled (records: 1) + AAAA fd00:1122:3344:103::1 +- name: db288a1e-c33c-44ca-8c79-9a8978afa34d.host (records: 1) +- AAAA fd00:1122:3344:106::21 + name: dd66f033-4fe8-438e-afb4-29d3561d4c3e.host (records: 1) + AAAA fd00:1122:3344:103::21 + name: e8fe709c-725f-4bb2-b714-ffcda13a9e54.host (records: 1) + AAAA fd00:1122:3344:104::21 + name: ecbe0b3d-1acc-44b2-b6d4-f4d2770516e4.host (records: 1) + AAAA fd00:1122:3344:105::22 + name: ns1 (records: 1) + AAAA fd00:1122:3344:1::1 + name: ns2 (records: 1) + AAAA fd00:1122:3344:2::1 + name: ns3 (records: 1) + AAAA fd00:1122:3344:3::1 + +external DNS: + DNS zone: "oxide.example" (unchanged) + name: @ (records: 3) + NS ns1.oxide.example + NS ns2.oxide.example + NS ns3.oxide.example + name: example-silo.sys (records: 3) + A 192.0.2.2 + A 192.0.2.3 + A 192.0.2.4 + name: ns1 (records: 1) + A 198.51.100.1 + name: ns2 (records: 1) + A 198.51.100.2 + name: ns3 (records: 1) + A 198.51.100.3 + + + diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout index 8218fd338b9..0f253e5c83a 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout @@ -192,6 +192,9 @@ f45ba181-4b56-42cc-a762-874d90184a43 0 > # First step: upgrade one SP. > blueprint-plan dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 f45ba181-4b56-42cc-a762-874d90184a43 +INFO noop converting 0/9 install-dataset zones to artifact store, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c +INFO noop converting 0/8 install-dataset zones to artifact store, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 +INFO noop converting 0/8 install-dataset zones to artifact store, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763 INFO sufficient BoundaryNtp zones exist in plan, desired_count: 0, current_count: 0 INFO sufficient Clickhouse zones exist in plan, desired_count: 1, current_count: 1 INFO sufficient ClickhouseKeeper zones exist in plan, desired_count: 0, current_count: 0 @@ -374,6 +377,9 @@ external DNS: > # If we generate another plan, there should be no change. > blueprint-plan 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 f45ba181-4b56-42cc-a762-874d90184a43 +INFO noop converting 0/9 install-dataset zones to artifact store, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c +INFO noop converting 0/8 install-dataset zones to artifact store, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 +INFO noop converting 0/8 install-dataset zones to artifact store, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763 INFO sufficient BoundaryNtp zones exist in plan, desired_count: 0, current_count: 0 INFO sufficient Clickhouse zones exist in plan, desired_count: 1, current_count: 1 INFO sufficient ClickhouseKeeper zones exist in plan, desired_count: 0, current_count: 0 @@ -557,6 +563,9 @@ set sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 SP versions: active -> 1.0.0 generated inventory collection eb0796d5-ab8a-4f7b-a884-b4aeacb8ab51 from configured sleds > blueprint-plan 58d5e830-0884-47d8-a7cd-b2b3751adeb4 eb0796d5-ab8a-4f7b-a884-b4aeacb8ab51 +INFO noop converting 0/9 install-dataset zones to artifact store, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c +INFO noop converting 0/8 install-dataset zones to artifact store, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 +INFO noop converting 0/8 install-dataset zones to artifact store, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763 INFO sufficient BoundaryNtp zones exist in plan, desired_count: 0, current_count: 0 INFO sufficient Clickhouse zones exist in plan, desired_count: 1, current_count: 1 INFO sufficient ClickhouseKeeper zones exist in plan, desired_count: 0, current_count: 0 @@ -750,6 +759,9 @@ set sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c SP versions: inactive -> 0.5.0 generated inventory collection 61f451b3-2121-4ed6-91c7-a550054f6c21 from configured sleds > blueprint-plan af934083-59b5-4bf6-8966-6fb5292c29e1 61f451b3-2121-4ed6-91c7-a550054f6c21 +INFO noop converting 0/9 install-dataset zones to artifact store, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c +INFO noop converting 0/8 install-dataset zones to artifact store, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 +INFO noop converting 0/8 install-dataset zones to artifact store, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763 INFO sufficient BoundaryNtp zones exist in plan, desired_count: 0, current_count: 0 INFO sufficient Clickhouse zones exist in plan, desired_count: 1, current_count: 1 INFO sufficient ClickhouseKeeper zones exist in plan, desired_count: 0, current_count: 0 @@ -941,6 +953,9 @@ set sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c SP versions: active -> 1.0.0 generated inventory collection b1bda47d-2c19-4fba-96e3-d9df28db7436 from configured sleds > blueprint-plan df06bb57-ad42-4431-9206-abff322896c7 b1bda47d-2c19-4fba-96e3-d9df28db7436 +INFO noop converting 0/9 install-dataset zones to artifact store, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c +INFO noop converting 0/8 install-dataset zones to artifact store, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 +INFO noop converting 0/8 install-dataset zones to artifact store, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763 INFO sufficient BoundaryNtp zones exist in plan, desired_count: 0, current_count: 0 INFO sufficient Clickhouse zones exist in plan, desired_count: 1, current_count: 1 INFO sufficient ClickhouseKeeper zones exist in plan, desired_count: 0, current_count: 0 @@ -1134,6 +1149,9 @@ set sled d81c6a84-79b8-4958-ae41-ea46c9b19763 SP versions: active -> 1.0.0 generated inventory collection a71f7a73-35a6-45e8-acbe-f1c5925eed69 from configured sleds > blueprint-plan 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba a71f7a73-35a6-45e8-acbe-f1c5925eed69 +INFO noop converting 0/9 install-dataset zones to artifact store, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c +INFO noop converting 0/8 install-dataset zones to artifact store, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 +INFO noop converting 0/8 install-dataset zones to artifact store, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763 INFO sufficient BoundaryNtp zones exist in plan, desired_count: 0, current_count: 0 INFO sufficient Clickhouse zones exist in plan, desired_count: 1, current_count: 1 INFO sufficient ClickhouseKeeper zones exist in plan, desired_count: 0, current_count: 0 diff --git a/nexus-sled-agent-shared/src/inventory.rs b/nexus-sled-agent-shared/src/inventory.rs index d08f91b7dbc..b77afbc1342 100644 --- a/nexus-sled-agent-shared/src/inventory.rs +++ b/nexus-sled-agent-shared/src/inventory.rs @@ -1139,6 +1139,35 @@ impl ZoneKind { } } + /// Map an artifact ID name to the corresponding file name in the install + /// dataset. + /// + /// We don't allow mapping artifact ID names to `ZoneKind` because the map + /// isn't bijective -- both internal and boundary NTP zones use the same + /// `ntp` artifact. But the artifact ID name and the name in the install + /// dataset do form a bijective map. + pub fn artifact_id_name_to_install_dataset_file( + artifact_id_name: &str, + ) -> Option<&'static str> { + let zone_kind = match artifact_id_name { + // We arbitrarily select BoundaryNtp to perform the mapping with. + "ntp" => ZoneKind::BoundaryNtp, + "clickhouse" => ZoneKind::Clickhouse, + "clickhouse_keeper" => ZoneKind::ClickhouseKeeper, + "clickhouse_server" => ZoneKind::ClickhouseServer, + "cockroachdb" => ZoneKind::CockroachDb, + "crucible-zone" => ZoneKind::Crucible, + "crucible-pantry-zone" => ZoneKind::CruciblePantry, + "external-dns" => ZoneKind::ExternalDns, + "internal-dns" => ZoneKind::InternalDns, + "nexus" => ZoneKind::Nexus, + "oximeter" => ZoneKind::Oximeter, + _ => return None, + }; + + Some(zone_kind.artifact_in_install_dataset()) + } + /// Return true if an artifact represents a control plane zone image /// of this kind. pub fn is_control_plane_zone_artifact( @@ -1238,6 +1267,20 @@ mod tests { ); } } + + #[test] + fn test_artifact_id_to_install_dataset_file() { + for zone_kind in ZoneKind::iter() { + let artifact_id_name = zone_kind.artifact_id_name(); + let expected_file = zone_kind.artifact_in_install_dataset(); + assert_eq!( + Some(expected_file), + ZoneKind::artifact_id_name_to_install_dataset_file( + artifact_id_name + ) + ); + } + } } // Used for schemars to be able to be used with camino: diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs index 8e928346376..767c953652b 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs @@ -305,6 +305,10 @@ pub(crate) enum Operation { num_datasets_expunged: usize, num_zones_expunged: usize, }, + SledNoopZoneImageSourcesUpdated { + sled_id: SledUuid, + count: usize, + }, } impl fmt::Display for Operation { @@ -369,6 +373,13 @@ impl fmt::Display for Operation { {num_zones_expunged} zones)" ) } + Self::SledNoopZoneImageSourcesUpdated { sled_id, count } => { + write!( + f, + "sled {sled_id}: performed {count} noop \ + zone image source updates" + ) + } } } } @@ -1135,6 +1146,20 @@ impl<'a> BlueprintBuilder<'a> { Ok(datasets.into()) } + /// Returns the remove_mupdate_override field for a sled. + pub(crate) fn sled_get_remove_mupdate_override( + &self, + sled_id: SledUuid, + ) -> Result, Error> { + let editor = self.sled_editors.get(&sled_id).ok_or_else(|| { + Error::Planner(anyhow!( + "tried to get remove_mupdate_override for \ + unknown sled {sled_id}" + )) + })?; + Ok(editor.get_remove_mupdate_override()) + } + fn next_internal_dns_gz_address_index(&self, sled_id: SledUuid) -> u32 { let used_internal_dns_gz_address_indices = self .current_sled_zones( diff --git a/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs b/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs index edb84d97d42..202584c93e0 100644 --- a/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs +++ b/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs @@ -286,6 +286,18 @@ impl SledEditor { } } + /// Returns the remove_mupdate_override field for this sled. + pub fn get_remove_mupdate_override(&self) -> Option { + match &self.0 { + InnerSledEditor::Active(editor) => { + *editor.remove_mupdate_override.value() + } + InnerSledEditor::Decommissioned(sled) => { + sled.config.remove_mupdate_override + } + } + } + fn as_active_mut( &mut self, ) -> Result<&mut ActiveSledEditor, SledEditError> { @@ -343,8 +355,6 @@ impl SledEditor { } /// Sets the image source for a zone. - /// - /// Currently only used by test code. pub fn set_zone_image_source( &mut self, zone_id: &OmicronZoneUuid, diff --git a/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor/scalar.rs b/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor/scalar.rs index 74eea138f86..ceafd15606d 100644 --- a/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor/scalar.rs +++ b/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor/scalar.rs @@ -16,7 +16,6 @@ impl ScalarEditor { ScalarEditor { original, value: EditValue::Original } } - #[expect(dead_code)] pub(crate) fn value(&self) -> &T { match &self.value { EditValue::Original => &self.original, diff --git a/nexus/reconfigurator/planning/src/planner.rs b/nexus/reconfigurator/planning/src/planner.rs index b4da2ffd21f..7f7fcd7880a 100644 --- a/nexus/reconfigurator/planning/src/planner.rs +++ b/nexus/reconfigurator/planning/src/planner.rs @@ -30,6 +30,7 @@ use nexus_types::deployment::DiskFilter; use nexus_types::deployment::PlanningInput; use nexus_types::deployment::SledDetails; use nexus_types::deployment::SledFilter; +use nexus_types::deployment::TargetReleaseDescription; use nexus_types::deployment::TufRepoContentsError; use nexus_types::deployment::ZpoolFilter; use nexus_types::external_api::views::PhysicalDiskPolicy; @@ -39,11 +40,13 @@ use nexus_types::inventory::Collection; use omicron_common::policy::INTERNAL_DNS_REDUNDANCY; use omicron_uuid_kinds::PhysicalDiskUuid; use omicron_uuid_kinds::SledUuid; +use slog::debug; use slog::error; use slog::{Logger, info, warn}; use slog_error_chain::InlineErrorChain; use std::collections::BTreeMap; use std::collections::BTreeSet; +use std::collections::HashMap; use std::str::FromStr; pub(crate) use self::omicron_zone_placement::DiscretionaryOmicronZone; @@ -150,6 +153,7 @@ impl<'a> Planner<'a> { fn do_plan(&mut self) -> Result<(), Error> { self.do_plan_expunge()?; self.do_plan_decommission()?; + self.do_plan_noop_image_source()?; self.do_plan_add()?; if let UpdateStepResult::ContinueToNextStep = self.do_plan_mgs_updates() { @@ -496,6 +500,185 @@ impl<'a> Planner<'a> { Ok(()) } + fn do_plan_noop_image_source(&mut self) -> Result<(), Error> { + let TargetReleaseDescription::TufRepo(current_artifacts) = + self.input.tuf_repo().description() + else { + info!( + self.log, + "skipping noop image source check for all sleds \ + (no current TUF repo)", + ); + return Ok(()); + }; + let artifacts_by_hash: HashMap<_, _> = current_artifacts + .artifacts + .iter() + .map(|artifact| (artifact.hash, artifact)) + .collect(); + + for sled_id in self.input.all_sled_ids(SledFilter::InService) { + let Some(inv_sled) = self.inventory.sled_agents.get(&sled_id) + else { + info!( + self.log, + "skipping noop image source check \ + (sled not present in latest inventory collection)"; + "sled_id" => %sled_id, + ); + continue; + }; + + let zone_manifest = match &inv_sled + .zone_image_resolver + .zone_manifest + .boot_inventory + { + Ok(zm) => zm, + Err(message) => { + // This is a string so we don't use InlineErrorChain::new. + let message: &str = message; + warn!( + self.log, + "skipping noop image source check since \ + sled-agent encountered error retrieving zone manifest \ + (this is abnormal)"; + "sled_id" => %sled_id, + "error" => %message, + ); + continue; + } + }; + + // Does the blueprint have the remove_mupdate_override field set for + // this sled? If it does, we don't want to touch the zones on this + // sled (they should all be InstallDataset until the + // remove_mupdate_override field is cleared). + if let Some(id) = + self.blueprint.sled_get_remove_mupdate_override(sled_id)? + { + info!( + self.log, + "skipping noop image source check on sled \ + (blueprint has get_remove_mupdate_override set for sled)"; + "sled_id" => %sled_id, + "bp_remove_mupdate_override_id" => %id, + ); + continue; + } + + // Which zones have image sources set to InstallDataset? + let install_dataset_zones = self + .blueprint + .current_sled_zones( + sled_id, + BlueprintZoneDisposition::is_in_service, + ) + .filter(|z| { + z.image_source == BlueprintZoneImageSource::InstallDataset + }); + + // Out of these, which zones' hashes (as reported in the zone + // manifest) match the corresponding ones in the TUF repo? + let mut install_dataset_zone_count = 0; + let matching_zones: Vec<_> = install_dataset_zones + .inspect(|_| { + install_dataset_zone_count += 1; + }) + .filter_map(|z| { + let file_name = z.kind().artifact_in_install_dataset(); + let Some(artifact) = zone_manifest.artifacts.get(file_name) + else { + // The blueprint indicates that a zone should be present + // that isn't in the install dataset. This might be an old + // install dataset with a zone kind known to this version of + // Nexus that isn't present in it. Not normally a cause for + // concern. + debug!( + self.log, + "blueprint zone not found in zone manifest, \ + ignoring for noop checks"; + "sled_id" => %sled_id, + "zone_id" => %z.id, + "kind" => z.kind().report_str(), + "file_name" => file_name, + ); + return None; + }; + if let Err(message) = &artifact.status { + // The artifact is somehow invalid and corrupt -- definitely + // something to warn about and not proceed. + warn!( + self.log, + "zone manifest inventory indicated install dataset \ + artifact is invalid, not using artifact (this is \ + abnormal)"; + "sled_id" => %sled_id, + "zone_id" => %z.id, + "kind" => z.kind().report_str(), + "file_name" => file_name, + "error" => %message, + ); + return None; + } + + // Does the hash match what's in the TUF repo? + let Some(tuf_artifact) = + artifacts_by_hash.get(&artifact.expected_hash) + else { + debug!( + self.log, + "install dataset artifact hash not found in TUF repo, \ + ignoring for noop checks"; + "sled_id" => %sled_id, + "zone_id" => %z.id, + "kind" => z.kind().report_str(), + "file_name" => file_name, + ); + return None; + }; + + info!( + self.log, + "install dataset artifact hash matches TUF repo, \ + switching out the zone image source to Artifact"; + "sled_id" => %sled_id, + "tuf_artifact_id" => %tuf_artifact.id, + ); + Some((z.id, tuf_artifact)) + }) + .collect(); + + info!( + self.log, + "noop converting {}/{} install-dataset zones to artifact store", + matching_zones.len(), + install_dataset_zone_count; + "sled_id" => %sled_id, + ); + + // Set all these zones' image sources to the corresponding + // blueprint. + for (zone_id, tuf_artifact) in &matching_zones { + self.blueprint.sled_set_zone_source( + sled_id, + *zone_id, + BlueprintZoneImageSource::from_available_artifact( + tuf_artifact, + ), + )?; + self.blueprint.record_operation( + Operation::SledNoopZoneImageSourcesUpdated { + sled_id, + count: matching_zones.len(), + }, + ); + } + } + + Ok(()) + } + fn do_plan_add(&mut self) -> Result<(), Error> { // Internal DNS is a prerequisite for bringing up all other zones. At // this point, we assume that internal DNS (as a service) is already diff --git a/nexus/reconfigurator/planning/src/system.rs b/nexus/reconfigurator/planning/src/system.rs index 75498f49cfa..4a9d0d8ed0d 100644 --- a/nexus/reconfigurator/planning/src/system.rs +++ b/nexus/reconfigurator/planning/src/system.rs @@ -24,6 +24,7 @@ use nexus_sled_agent_shared::inventory::InventoryZpool; use nexus_sled_agent_shared::inventory::OmicronSledConfig; use nexus_sled_agent_shared::inventory::SledRole; use nexus_sled_agent_shared::inventory::ZoneImageResolverInventory; +use nexus_sled_agent_shared::inventory::ZoneManifestBootInventory; use nexus_types::deployment::ClickhousePolicy; use nexus_types::deployment::CockroachDbClusterVersion; use nexus_types::deployment::CockroachDbSettings; @@ -483,6 +484,20 @@ impl SystemDescription { Ok(self) } + /// Set the zone manifest for a sled from a provided `TufRepoDescription`. + pub fn sled_set_zone_manifest( + &mut self, + sled_id: SledUuid, + boot_inventory: Result, + ) -> anyhow::Result<&mut Self> { + let sled = self.sleds.get_mut(&sled_id).with_context(|| { + format!("attempted to access sled {} not found in system", sled_id) + })?; + let sled = Arc::make_mut(sled); + sled.set_zone_manifest(boot_inventory); + Ok(self) + } + pub fn sled_sp_active_version( &self, sled_id: SledUuid, @@ -1120,6 +1135,16 @@ impl Sled { self.sp_inactive_caboose.as_deref() } + fn set_zone_manifest( + &mut self, + boot_inventory: Result, + ) { + self.inventory_sled_agent + .zone_image_resolver + .zone_manifest + .boot_inventory = boot_inventory; + } + /// Update the reported SP versions /// /// If either field is `None`, that field is _unchanged_. diff --git a/nexus/reconfigurator/simulation/Cargo.toml b/nexus/reconfigurator/simulation/Cargo.toml index 0f65c31c58d..c2bb4945b70 100644 --- a/nexus/reconfigurator/simulation/Cargo.toml +++ b/nexus/reconfigurator/simulation/Cargo.toml @@ -8,10 +8,13 @@ workspace = true [dependencies] anyhow.workspace = true +camino.workspace = true chrono.workspace = true indexmap.workspace = true +itertools.workspace = true nexus-inventory.workspace = true nexus-reconfigurator-planning.workspace = true +nexus-sled-agent-shared.workspace = true nexus-types.workspace = true omicron-common.workspace = true omicron-uuid-kinds.workspace = true @@ -21,5 +24,6 @@ slog.workspace = true swrite.workspace = true sync-ptr.workspace = true thiserror.workspace = true +tufaceous-artifact.workspace = true typed-rng.workspace = true uuid.workspace = true diff --git a/nexus/reconfigurator/simulation/src/errors.rs b/nexus/reconfigurator/simulation/src/errors.rs index 26b91cf70f8..4dbb3bc76cc 100644 --- a/nexus/reconfigurator/simulation/src/errors.rs +++ b/nexus/reconfigurator/simulation/src/errors.rs @@ -2,6 +2,9 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use std::collections::BTreeSet; + +use itertools::Itertools; use omicron_common::api::external::{Generation, Name}; use omicron_uuid_kinds::CollectionUuid; use thiserror::Error; @@ -128,3 +131,24 @@ impl NonEmptySystemError { Self {} } } + +/// Unknown zone names were provided to `SimTufRepoSource::simulate_zone_error`. +#[derive(Clone, Debug, Error)] +#[error( + "unknown zone names `{}` (valid zone names: {})", + self.unknown.join(", "), + self.known.iter().join(", "), +)] +pub struct UnknownZoneNamesError { + /// The names of the unknown zones. + pub unknown: Vec, + + /// The set of known zone names. + pub known: BTreeSet, +} + +impl UnknownZoneNamesError { + pub(crate) fn new(unknown: Vec, known: BTreeSet) -> Self { + Self { unknown, known } + } +} diff --git a/nexus/reconfigurator/simulation/src/lib.rs b/nexus/reconfigurator/simulation/src/lib.rs index 8f8c77e09f1..30ba3fdc80c 100644 --- a/nexus/reconfigurator/simulation/src/lib.rs +++ b/nexus/reconfigurator/simulation/src/lib.rs @@ -54,9 +54,11 @@ mod sim; mod state; mod system; mod utils; +mod zone_images; pub use config::*; pub use rng::*; pub use sim::*; pub use state::*; pub use system::*; +pub use zone_images::*; diff --git a/nexus/reconfigurator/simulation/src/zone_images.rs b/nexus/reconfigurator/simulation/src/zone_images.rs new file mode 100644 index 00000000000..15b263d6d3f --- /dev/null +++ b/nexus/reconfigurator/simulation/src/zone_images.rs @@ -0,0 +1,206 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Simulation of TUF repos, zone images, and thereabouts. + +use std::collections::BTreeSet; + +use anyhow::bail; +use camino::Utf8Path; +use itertools::Itertools; +use nexus_sled_agent_shared::inventory::{ + ZoneArtifactInventory, ZoneKind, ZoneManifestBootInventory, +}; +use omicron_common::{ + api::external::TufRepoDescription, update::OmicronZoneManifestSource, +}; +use swrite::{SWrite, swrite}; +use tufaceous_artifact::KnownArtifactKind; + +use crate::errors::UnknownZoneNamesError; + +/// The reconfigurator simulator's notion of a TUF repository. +#[derive(Clone, Debug)] +pub struct SimTufRepoDescription { + /// The description and manifest source, or a simulated error. + pub source: Result, + + /// A message describing the operation. + pub message: String, +} + +impl SimTufRepoDescription { + /// Creates a new `SimTufRepoDescription`. + pub fn new(source: SimTufRepoSource) -> Self { + let message = source.full_message(); + Self { source: Ok(source), message } + } + + /// Creates a new description with a simulated error reading the zone + /// manifest. + pub fn new_error(message: String) -> Self { + Self { source: Err(message.clone()), message } + } + + /// Generates a simulated [`ZoneManifestBootInventory`] or an error. + pub fn to_boot_inventory( + &self, + ) -> Result { + match &self.source { + Ok(source) => Ok(source.to_boot_inventory()), + Err(error) => { + Err(format!("reconfigurator-sim simulated error: {error}")) + } + } + } +} + +/// The reconfigurator simulator's notion of a TUF repository where there wasn't +/// an error reading the zone manifest. +#[derive(Clone, Debug)] +pub struct SimTufRepoSource { + description: TufRepoDescription, + manifest_source: OmicronZoneManifestSource, + message: String, + known_artifact_id_names: BTreeSet, + error_artifact_id_names: BTreeSet, +} + +impl SimTufRepoSource { + /// Creates a new `SimTufRepoSource`. + /// + /// The message should be of the form "from repo at ..." or "to target release". + pub fn new( + description: TufRepoDescription, + manifest_source: OmicronZoneManifestSource, + message: String, + ) -> anyhow::Result { + let mut unknown = BTreeSet::new(); + let known = description + .artifacts + .iter() + .filter_map(|artifact| { + if artifact.id.kind.to_known() != Some(KnownArtifactKind::Zone) + { + return None; + } + + // Check that the zone name is known to ZoneKind. + if ZoneKind::artifact_id_name_to_install_dataset_file( + &artifact.id.name, + ) + .is_some() + { + Some(artifact.id.name.clone()) + } else { + unknown.insert(artifact.id.name.clone()); + None + } + }) + .collect(); + if !unknown.is_empty() { + bail!( + "unknown zone artifact ID names in provided description \ + ({message}): {}", + unknown.iter().join(", "), + ); + } + Ok(Self { + description, + manifest_source, + message, + known_artifact_id_names: known, + error_artifact_id_names: BTreeSet::new(), + }) + } + + /// Simulates errors validating zones by the given artifact ID name. + /// + /// Returns an error if any of the provided zone names weren't found in the + /// description. + pub fn simulate_zone_errors( + &mut self, + artifact_id_names: I, + ) -> Result<(), UnknownZoneNamesError> + where + I: IntoIterator, + S: AsRef, + { + let (known, unknown): (Vec<_>, Vec<_>) = artifact_id_names + .into_iter() + .map(|zone_name| zone_name.as_ref().to_owned()) + .partition(|zone_name| { + self.known_artifact_id_names.contains(zone_name) + }); + if !unknown.is_empty() { + return Err(UnknownZoneNamesError::new( + unknown, + self.known_artifact_id_names.clone(), + )); + } + self.error_artifact_id_names.extend(known); + Ok(()) + } + + /// Generates a simulated [`ZoneManifestBootInventory`]. + pub fn to_boot_inventory(&self) -> ZoneManifestBootInventory { + let artifacts = self + .description + .artifacts + .iter() + .filter_map(|artifact| { + if artifact.id.kind.to_known() != Some(KnownArtifactKind::Zone) + { + return None; + } + + let file_name = + ZoneKind::artifact_id_name_to_install_dataset_file( + &artifact.id.name, + ) + .expect("we checked this was Some at construction time") + .to_owned(); + let path = Utf8Path::new("/fake/path/install").join(&file_name); + let status = + if self.error_artifact_id_names.contains(&artifact.id.name) + { + Err("reconfigurator-sim: simulated error \ + validating zone image" + .to_owned()) + } else { + Ok(()) + }; + Some(ZoneArtifactInventory { + file_name, + path, + expected_size: artifact.size, + expected_hash: artifact.hash, + status, + }) + }) + .collect(); + ZoneManifestBootInventory { source: self.manifest_source, artifacts } + } + + /// Returns a message including the system version and the number of zone + /// errors. + pub fn full_message(&self) -> String { + let mut message = self.message.clone(); + swrite!( + message, + " (system version {}", + self.description.repo.system_version + ); + if !self.error_artifact_id_names.is_empty() { + swrite!( + message, + ", {} zone errors", + self.error_artifact_id_names.len() + ); + } + message.push(')'); + + message + } +}