From 8f30bd5bf42c09c51dd4aa509f0e36e1f188643e Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Tue, 8 Jul 2025 13:41:55 -0600 Subject: [PATCH 01/10] Planning reports --- nexus/reconfigurator/planning/src/lib.rs | 1 + nexus/reconfigurator/planning/src/planner.rs | 490 +++++++------ nexus/reconfigurator/planning/src/reports.rs | 60 ++ nexus/types/src/deployment.rs | 14 + nexus/types/src/deployment/planning_report.rs | 679 ++++++++++++++++++ 5 files changed, 1016 insertions(+), 228 deletions(-) create mode 100644 nexus/reconfigurator/planning/src/reports.rs create mode 100644 nexus/types/src/deployment/planning_report.rs diff --git a/nexus/reconfigurator/planning/src/lib.rs b/nexus/reconfigurator/planning/src/lib.rs index a11c5e4132c..bdb02c88dbf 100644 --- a/nexus/reconfigurator/planning/src/lib.rs +++ b/nexus/reconfigurator/planning/src/lib.rs @@ -11,4 +11,5 @@ pub mod blueprint_editor; pub mod example; pub mod mgs_updates; pub mod planner; +pub mod reports; pub mod system; diff --git a/nexus/reconfigurator/planning/src/planner.rs b/nexus/reconfigurator/planning/src/planner.rs index 9b13b6323b5..027347308e8 100644 --- a/nexus/reconfigurator/planning/src/planner.rs +++ b/nexus/reconfigurator/planning/src/planner.rs @@ -18,6 +18,7 @@ use crate::planner::image_source::NoopConvertInfo; use crate::planner::image_source::NoopConvertSledStatus; use crate::planner::image_source::NoopConvertZoneStatus; use crate::planner::omicron_zone_placement::PlacementError; +use crate::reports::InterimPlanningReport; use gateway_client::types::SpType; use nexus_sled_agent_shared::inventory::ConfigReconcilerInventoryResult; use nexus_sled_agent_shared::inventory::OmicronZoneImageSource; @@ -37,6 +38,13 @@ use nexus_types::deployment::SledDetails; use nexus_types::deployment::SledFilter; use nexus_types::deployment::TufRepoContentsError; use nexus_types::deployment::ZpoolFilter; +use nexus_types::deployment::{ + CockroachdbUnsafeToShutdown, PlanningAddStepReport, + PlanningCockroachdbSettingsStepReport, PlanningDecommissionStepReport, + PlanningExpungeStepReport, PlanningMgsUpdatesStepReport, + PlanningNoopImageSourceStepReport, PlanningReport, + PlanningZoneUpdatesStepReport, ZoneUnsafeToShutdown, ZoneUpdatesWaitingOn, +}; use nexus_types::external_api::views::PhysicalDiskPolicy; use nexus_types::external_api::views::SledPolicy; use nexus_types::external_api::views::SledState; @@ -46,8 +54,6 @@ use omicron_common::policy::INTERNAL_DNS_REDUNDANCY; use omicron_uuid_kinds::OmicronZoneUuid; 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; @@ -90,10 +96,8 @@ pub(crate) mod rng; /// services, etc.). const NUM_CONCURRENT_MGS_UPDATES: usize = 1; -enum UpdateStepResult { - ContinueToNextStep, - Waiting, -} +/// A receipt that `check_input_validity` has been run prior to planning. +struct InputChecked; pub struct Planner<'a> { log: Logger, @@ -143,38 +147,57 @@ impl<'a> Planner<'a> { } pub fn plan(mut self) -> Result { - self.check_input_validity()?; - self.do_plan()?; + let checked = self.check_input_validity()?; + self.do_plan(checked)?; Ok(self.blueprint.build()) } - fn check_input_validity(&self) -> Result<(), Error> { + pub fn plan_and_report( + mut self, + ) -> Result<(Blueprint, PlanningReport), Error> { + let checked = self.check_input_validity()?; + let report = self.do_plan(checked)?; + let blueprint = self.blueprint.build(); + let report = report.finalize(blueprint.id); + Ok((blueprint, report)) + } + + fn check_input_validity(&self) -> Result { if self.input.target_internal_dns_zone_count() > INTERNAL_DNS_REDUNDANCY { return Err(Error::PolicySpecifiesTooManyInternalDnsServers); } - Ok(()) + Ok(InputChecked) } - fn do_plan(&mut self) -> Result<(), Error> { - self.do_plan_expunge()?; - self.do_plan_decommission()?; - - let noop_info = - NoopConvertInfo::new(self.input, self.inventory, &self.blueprint)?; - noop_info.log_to(&self.log); - - self.do_plan_noop_image_source(noop_info)?; - self.do_plan_add()?; - if let UpdateStepResult::ContinueToNextStep = self.do_plan_mgs_updates() - { - self.do_plan_zone_updates()?; - } - self.do_plan_cockroachdb_settings(); - Ok(()) + fn do_plan( + &mut self, + _checked: InputChecked, + ) -> Result { + // Run the planning steps, recording their step reports as we go. + let expunge = self.do_plan_expunge()?; + let decommission = self.do_plan_decommission()?; + let noop_image_source = self.do_plan_noop_image_source()?; + let mgs_updates = self.do_plan_mgs_updates(); + let add = self.do_plan_add(&mgs_updates)?; + let zone_updates = self.do_plan_zone_updates(&add, &mgs_updates)?; + let cockroachdb_settings = self.do_plan_cockroachdb_settings(); + Ok(InterimPlanningReport { + expunge, + decommission, + noop_image_source, + add, + mgs_updates, + zone_updates, + cockroachdb_settings, + }) } - fn do_plan_decommission(&mut self) -> Result<(), Error> { + fn do_plan_decommission( + &mut self, + ) -> Result { + let mut report = PlanningDecommissionStepReport::new(); + // Check for any sleds that are currently commissioned but can be // decommissioned. Our gates for decommissioning are: // @@ -209,15 +232,10 @@ impl<'a> Planner<'a> { continue; } // If the sled is already decommissioned it... why is it showing - // up when we ask for commissioned sleds? Warn, but don't try to + // up when we ask for commissioned sleds? Report, but don't try to // decommission it again. (SledPolicy::Expunged, SledState::Decommissioned) => { - error!( - self.log, - "decommissioned sled returned by \ - SledFilter::Commissioned"; - "sled_id" => %sled_id, - ); + report.zombie_sleds.push(sled_id); continue; } // The sled is expunged but not yet decommissioned; fall through @@ -257,7 +275,7 @@ impl<'a> Planner<'a> { } } - Ok(()) + Ok(report) } fn do_plan_decommission_expunged_disks_for_in_service_sled( @@ -307,17 +325,22 @@ impl<'a> Planner<'a> { self.blueprint.sled_decommission_disks(sled_id, disks_to_decommission) } - fn do_plan_expunge(&mut self) -> Result<(), Error> { - let mut commissioned_sled_ids = BTreeSet::new(); + fn do_plan_expunge(&mut self) -> Result { + let mut report = PlanningExpungeStepReport::new(); // Remove services from sleds marked expunged. We use // `SledFilter::Commissioned` and have a custom `needs_zone_expungement` // function that allows us to produce better errors. + let mut commissioned_sled_ids = BTreeSet::new(); for (sled_id, sled_details) in self.input.all_sleds(SledFilter::Commissioned) { commissioned_sled_ids.insert(sled_id); - self.do_plan_expunge_for_commissioned_sled(sled_id, sled_details)?; + self.do_plan_expunge_for_commissioned_sled( + sled_id, + sled_details, + &mut report, + )?; } // Check for any decommissioned sleds (i.e., sleds for which our @@ -348,13 +371,14 @@ impl<'a> Planner<'a> { } } - Ok(()) + Ok(report) } fn do_plan_expunge_for_commissioned_sled( &mut self, sled_id: SledUuid, sled_details: &SledDetails, + report: &mut PlanningExpungeStepReport, ) -> Result<(), Error> { match sled_details.policy { SledPolicy::InService { .. } => { @@ -391,14 +415,8 @@ impl<'a> Planner<'a> { // isn't in the blueprint at all (e.g., a disk could // have been added and then expunged since our // parent blueprint was created). We don't want to - // fail in this case, but will issue a warning. - warn!( - self.log, - "planning input contained expunged disk not \ - present in parent blueprint"; - "sled_id" => %sled_id, - "disk" => ?disk, - ); + // fail in this case, but will report it. + report.orphan_disks.insert(sled_id, disk.disk_id); } Err(err) => return Err(err), } @@ -513,11 +531,17 @@ impl<'a> Planner<'a> { fn do_plan_noop_image_source( &mut self, - noop_info: NoopConvertInfo, - ) -> Result<(), Error> { + ) -> Result { + use nexus_types::deployment::PlanningNoopImageSourceSkipSledReason as SkipSledReason; + let mut report = PlanningNoopImageSourceStepReport::new(); + + let noop_info = + NoopConvertInfo::new(self.input, self.inventory, &self.blueprint)?; + noop_info.log_to(&self.log); + let sleds = match noop_info { NoopConvertInfo::GlobalEligible { sleds } => sleds, - NoopConvertInfo::GlobalIneligible { .. } => return Ok(()), + NoopConvertInfo::GlobalIneligible { .. } => return Ok(report), }; for sled in sleds { let eligible = match &sled.status { @@ -527,23 +551,19 @@ impl<'a> Planner<'a> { let zone_counts = eligible.zone_counts(); if zone_counts.num_install_dataset() == 0 { - debug!( - self.log, - "all zones are already Artifact, so \ - no noop image source action required"; - "num_total" => zone_counts.num_total, + report.skip_sled( + sled.sled_id, + SkipSledReason::AllZonesAlreadyArtifact( + zone_counts.num_total, + ), ); continue; } if zone_counts.num_eligible > 0 { - info!( - self.log, - "noop converting {}/{} install-dataset zones to artifact store", + report.converted_zones( + sled.sled_id, zone_counts.num_eligible, - zone_counts.num_install_dataset(); - "sled_id" => %sled.sled_id, - "num_total" => zone_counts.num_total, - "num_already_artifact" => zone_counts.num_already_artifact, + zone_counts.num_install_dataset(), ); } @@ -571,10 +591,15 @@ impl<'a> Planner<'a> { } } - Ok(()) + Ok(report) } - fn do_plan_add(&mut self) -> Result<(), Error> { + fn do_plan_add( + &mut self, + mgs_updates: &PlanningMgsUpdatesStepReport, + ) -> Result { + let mut report = PlanningAddStepReport::new(); + // Internal DNS is a prerequisite for bringing up all other zones. At // this point, we assume that internal DNS (as a service) is already // functioning. @@ -588,8 +613,6 @@ impl<'a> Planner<'a> { // We will not mark sleds getting Crucible zones as ineligible; other // control plane service zones starting concurrently with Crucible zones // is fine. - let mut sleds_waiting_for_ntp_zone = BTreeSet::new(); - for (sled_id, sled_resources) in self.input.all_sled_resources(SledFilter::InService) { @@ -636,12 +659,7 @@ impl<'a> Planner<'a> { .next() .is_none() { - info!( - self.log, - "skipping sled (no zpools in service)"; - "sled_id" => %sled_id, - ); - sleds_waiting_for_ntp_zone.insert(sled_id); + report.sleds_with_no_zpools_for_ntp_zone.insert(sled_id); continue; } @@ -651,14 +669,13 @@ impl<'a> Planner<'a> { // provision anything else. if self.blueprint.sled_ensure_zone_ntp( sled_id, - self.image_source_for_new_zone(ZoneKind::InternalNtp)?, + self.image_source_for_new_zone( + ZoneKind::InternalNtp, + mgs_updates, + )?, )? == Ensure::Added { - info!( - &self.log, - "found sled missing NTP zone (will add one)"; - "sled_id" => %sled_id - ); + report.sleds_missing_ntp_zone.insert(sled_id); self.blueprint.record_operation(Operation::AddZone { sled_id, kind: ZoneKind::InternalNtp, @@ -686,14 +703,11 @@ impl<'a> Planner<'a> { .requires_timesync() }) { - info!( - &self.log, - "sled getting NTP zone has other services already; \ - considering it eligible for discretionary zones"; - "sled_id" => %sled_id, - ); + report + .sleds_getting_ntp_and_discretionary_zones + .insert(sled_id); } else { - sleds_waiting_for_ntp_zone.insert(sled_id); + report.sleds_waiting_for_ntp_zone.insert(sled_id); continue; } } @@ -738,12 +752,7 @@ impl<'a> Planner<'a> { }) .unwrap_or(false); if !has_ntp_inventory { - info!( - &self.log, - "parent blueprint contains NTP zone, but it's not in \ - inventory yet"; - "sled_id" => %sled_id, - ); + report.sleds_waiting_for_ntp_zone.insert(sled_id); continue; } @@ -754,15 +763,15 @@ impl<'a> Planner<'a> { if self.blueprint.sled_ensure_zone_crucible( sled_id, *zpool_id, - self.image_source_for_new_zone(ZoneKind::Crucible)?, + self.image_source_for_new_zone( + ZoneKind::Crucible, + mgs_updates, + )?, )? == Ensure::Added { - info!( - &self.log, - "found sled zpool missing Crucible zone (will add one)"; - "sled_id" => ?sled_id, - "zpool_id" => ?zpool_id, - ); + report + .sleds_missing_crucible_zone + .insert((sled_id, *zpool_id)); ncrucibles_added += 1; } } @@ -782,16 +791,19 @@ impl<'a> Planner<'a> { } } - self.do_plan_add_discretionary_zones(&sleds_waiting_for_ntp_zone)?; + self.do_plan_add_discretionary_zones(mgs_updates, &mut report)?; // Now that we've added all the disks and zones we plan on adding, // ensure that all sleds have the datasets they need to have. - self.do_plan_datasets()?; + self.do_plan_datasets(&mut report)?; - Ok(()) + Ok(report) } - fn do_plan_datasets(&mut self) -> Result<(), Error> { + fn do_plan_datasets( + &mut self, + _report: &mut PlanningAddStepReport, + ) -> Result<(), Error> { for sled_id in self.input.all_sled_ids(SledFilter::InService) { if let EnsureMultiple::Changed { added, @@ -823,7 +835,8 @@ impl<'a> Planner<'a> { fn do_plan_add_discretionary_zones( &mut self, - sleds_waiting_for_ntp_zone: &BTreeSet, + mgs_updates: &PlanningMgsUpdatesStepReport, + report: &mut PlanningAddStepReport, ) -> Result<(), Error> { // We usually don't need to construct an `OmicronZonePlacement` to add // discretionary zones, so defer its creation until it's needed. @@ -841,7 +854,8 @@ impl<'a> Planner<'a> { DiscretionaryOmicronZone::Nexus, DiscretionaryOmicronZone::Oximeter, ] { - let num_zones_to_add = self.num_additional_zones_needed(zone_kind); + let num_zones_to_add = + self.num_additional_zones_needed(zone_kind, report); if num_zones_to_add == 0 { continue; } @@ -858,7 +872,7 @@ impl<'a> Planner<'a> { .input .all_sled_resources(SledFilter::Discretionary) .filter(|(sled_id, _)| { - !sleds_waiting_for_ntp_zone.contains(&sled_id) + !report.sleds_waiting_for_ntp_zone.contains(&sled_id) }) .map(|(sled_id, sled_resources)| { OmicronZonePlacementSledState { @@ -886,17 +900,20 @@ impl<'a> Planner<'a> { zone_placement, zone_kind, num_zones_to_add, + mgs_updates, + report, )?; } Ok(()) } - // Given the current blueprint state and policy, returns the number of - // additional zones needed of the given `zone_kind` to satisfy the policy. + /// Given the current blueprint state and policy, returns the number of + /// additional zones needed of the given `zone_kind` to satisfy the policy. fn num_additional_zones_needed( &mut self, zone_kind: DiscretionaryOmicronZone, + report: &mut PlanningAddStepReport, ) -> usize { // Count the number of `kind` zones on all in-service sleds. This // will include sleds that are in service but not eligible for new @@ -959,30 +976,31 @@ impl<'a> Planner<'a> { }; // TODO-correctness What should we do if we have _too many_ - // `zone_kind` zones? For now, just log it the number of zones any - // time we have at least the minimum number. + // `zone_kind` zones? For now, just report the number of zones + // any time we have at least the minimum number. let num_zones_to_add = target_count.saturating_sub(num_existing_kind_zones); if num_zones_to_add == 0 { - info!( - self.log, "sufficient {zone_kind:?} zones exist in plan"; - "desired_count" => target_count, - "current_count" => num_existing_kind_zones, + report.sufficient_zones_exist.insert( + ZoneKind::from(zone_kind).report_str().to_owned(), + (target_count, num_existing_kind_zones), ); } num_zones_to_add } - // Attempts to place `num_zones_to_add` new zones of `kind`. - // - // It is not an error if there are too few eligible sleds to start a - // sufficient number of zones; instead, we'll log a warning and start as - // many as we can (up to `num_zones_to_add`). + /// Attempts to place `num_zones_to_add` new zones of `kind`. + /// + /// It is not an error if there are too few eligible sleds to start a + /// sufficient number of zones; instead, we'll log a warning and start as + /// many as we can (up to `num_zones_to_add`). fn add_discretionary_zones( &mut self, zone_placement: &mut OmicronZonePlacement, kind: DiscretionaryOmicronZone, num_zones_to_add: usize, + mgs_updates: &PlanningMgsUpdatesStepReport, + report: &mut PlanningAddStepReport, ) -> Result<(), Error> { for i in 0..num_zones_to_add { let sled_id = match zone_placement.place_zone(kind) { @@ -992,18 +1010,16 @@ impl<'a> Planner<'a> { // (albeit unlikely?) we're in a weird state where we need // more sleds or disks to come online, and we may need to be // able to produce blueprints to achieve that status. - warn!( - self.log, - "failed to place all new desired {kind:?} zones"; - "placed" => i, - "wanted_to_place" => num_zones_to_add, + report.out_of_eligible_sleds.insert( + ZoneKind::from(kind).report_str().to_owned(), + (i, num_zones_to_add), ); - break; } }; - let image_source = self.image_source_for_new_zone(kind.into())?; + let image_source = + self.image_source_for_new_zone(kind.into(), mgs_updates)?; match kind { DiscretionaryOmicronZone::BoundaryNtp => { self.blueprint.sled_promote_internal_ntp_to_boundary_ntp( @@ -1039,11 +1055,9 @@ impl<'a> Planner<'a> { .blueprint .sled_add_zone_oximeter(sled_id, image_source)?, }; - info!( - self.log, "added zone to sled"; - "sled_id" => %sled_id, - "kind" => ?kind, - ); + report + .discretionary_zones_placed + .push((sled_id, ZoneKind::from(kind).report_str().to_owned())); } Ok(()) @@ -1051,7 +1065,7 @@ impl<'a> Planner<'a> { /// Update at most one MGS-managed device (SP, RoT, etc.), if any are out of /// date. - fn do_plan_mgs_updates(&mut self) -> UpdateStepResult { + fn do_plan_mgs_updates(&mut self) -> PlanningMgsUpdatesStepReport { // Determine which baseboards we will consider updating. // // Sleds may be present but not adopted as part of the control plane. @@ -1095,24 +1109,45 @@ impl<'a> Planner<'a> { current_artifacts, NUM_CONCURRENT_MGS_UPDATES, ); + self.blueprint.pending_mgs_updates_replace_all(next.clone()); - // TODO This is not quite right. See oxidecomputer/omicron#8285. - let rv = if next.is_empty() { - UpdateStepResult::ContinueToNextStep - } else { - UpdateStepResult::Waiting - }; - self.blueprint.pending_mgs_updates_replace_all(next); - rv + PlanningMgsUpdatesStepReport::new(next) } /// Update at most one existing zone to use a new image source. - fn do_plan_zone_updates(&mut self) -> Result<(), Error> { - // We are only interested in non-decommissioned sleds. + fn do_plan_zone_updates( + &mut self, + add: &PlanningAddStepReport, + mgs_updates: &PlanningMgsUpdatesStepReport, + ) -> Result { + let mut report = PlanningZoneUpdatesStepReport::new(); + + // Do not update any zones if we've added any discretionary zones + // (e.g., in response to policy changes) ... + if add.any_discretionary_zones_placed() { + report.waiting_on(ZoneUpdatesWaitingOn::DiscretionaryZones); + return Ok(report); + } + + // ... or if there are still pending updates for the RoT / SP / + // Host OS / etc. + if mgs_updates.any_updates_pending() { + report.waiting_on(ZoneUpdatesWaitingOn::PendingMgsUpdates); + return Ok(report); + } + + // We are only interested in non-decommissioned sleds with + // running NTP zones (TODO: check time sync). let sleds = self .input .all_sleds(SledFilter::Commissioned) - .map(|(id, _details)| id) + .filter_map(|(sled_id, _details)| { + if add.sleds_waiting_for_ntp_zone.contains(&sled_id) { + None + } else { + Some(sled_id) + } + }) .collect::>(); // Wait for zones to appear up-to-date in the inventory. @@ -1223,14 +1258,14 @@ impl<'a> Planner<'a> { "sled_id" => %sled_id, "zones_currently_updating" => ?zones_currently_updating, ); - return Ok(()); + return Ok(report); } } // Find out of date zones, as defined by zones whose image source does // not match what it should be based on our current target release. let target_release = self.input.tuf_repo().description(); - let mut out_of_date_zones = sleds + let out_of_date_zones = sleds .into_iter() .flat_map(|sled_id| { let log = &self.log; @@ -1258,28 +1293,27 @@ impl<'a> Planner<'a> { } }; if zone.image_source != desired_image_source { - Some((sled_id, zone, desired_image_source)) + Some((sled_id, zone.clone(), desired_image_source)) } else { None } }) }) - .peekable(); - - // Before we filter out zones that can't be updated, do we have any out - // of date zones at all? We need this to explain why we didn't update - // any zones below, if we don't. - let have_out_of_date_zones = out_of_date_zones.peek().is_some(); + .collect::>(); + report.out_of_date_zones.extend(out_of_date_zones.iter().cloned()); // Of the out-of-date zones, filter out zones that can't be updated yet, // either because they're not ready or because it wouldn't be safe to // bounce them. - let mut updateable_zones = - out_of_date_zones.filter(|(_sled_id, zone, _new_image_source)| { - if !self.can_zone_be_shut_down_safely(zone) { + let mut updateable_zones = out_of_date_zones.iter().filter( + |(_sled_id, zone, _new_image_source)| { + if !self.can_zone_be_shut_down_safely(zone, &mut report) { return false; } - match self.is_zone_ready_for_update(zone.zone_type.kind()) { + match self.is_zone_ready_for_update( + zone.zone_type.kind(), + mgs_updates, + ) { Ok(true) => true, Ok(false) => false, Err(err) => { @@ -1294,35 +1328,22 @@ impl<'a> Planner<'a> { false } } - }); + }, + ); - // Update the first out-of-date zone. if let Some((sled_id, zone, new_image_source)) = updateable_zones.next() { - // Borrow check workaround: `self.update_or_expunge_zone` needs - // `&mut self`, but `self` is borrowed in the `updateable_zones` - // iterator. Clone the one zone we want to update, then drop the - // iterator; now we can call `&mut self` methods. - let zone = zone.clone(); - std::mem::drop(updateable_zones); - - return self.update_or_expunge_zone( - sled_id, - &zone, - new_image_source, - ); - } - - if have_out_of_date_zones { - info!( - self.log, - "not all zones up-to-date, but no zones can be updated now" - ); + // Update the first out-of-date zone. + self.update_or_expunge_zone( + *sled_id, + zone, + new_image_source.clone(), + report, + ) } else { - info!(self.log, "all zones up-to-date"); + // No zones to update. + Ok(report) } - - Ok(()) } /// Update a zone to use a new image source, either in-place or by @@ -1332,7 +1353,8 @@ impl<'a> Planner<'a> { sled_id: SledUuid, zone: &BlueprintZoneConfig, new_image_source: BlueprintZoneImageSource, - ) -> Result<(), Error> { + mut report: PlanningZoneUpdatesStepReport, + ) -> Result { let zone_kind = zone.zone_type.kind(); // We're called by `do_plan_zone_updates()`, which guarantees the @@ -1345,18 +1367,12 @@ impl<'a> Planner<'a> { | ZoneKind::ClickhouseKeeper | ZoneKind::ClickhouseServer | ZoneKind::CockroachDb => { - info!( - self.log, "updating zone image source in-place"; - "sled_id" => %sled_id, - "zone_id" => %zone.id, - "kind" => ?zone.zone_type.kind(), - "image_source" => %new_image_source, - ); self.blueprint.comment(format!( "updating {:?} zone {} in-place", zone.zone_type.kind(), zone.id )); + report.updated_zones.push((sled_id, zone.clone())); self.blueprint.sled_set_zone_source( sled_id, zone.id, @@ -1370,25 +1386,24 @@ impl<'a> Planner<'a> { | ZoneKind::InternalNtp | ZoneKind::Nexus | ZoneKind::Oximeter => { - info!( - self.log, "expunging out-of-date zone"; - "sled_id" => %sled_id, - "zone_id" => %zone.id, - "kind" => ?zone.zone_type.kind(), - ); self.blueprint.comment(format!( "expunge {:?} zone {} for update", zone.zone_type.kind(), zone.id )); + report.expunged_zones.push((sled_id, zone.clone())); self.blueprint.sled_expunge_zone(sled_id, zone.id)?; } } - Ok(()) + Ok(report) } - fn do_plan_cockroachdb_settings(&mut self) { + fn do_plan_cockroachdb_settings( + &mut self, + ) -> PlanningCockroachdbSettingsStepReport { + let mut report = PlanningCockroachdbSettingsStepReport::new(); + // Figure out what we should set the CockroachDB "preserve downgrade // option" setting to based on the planning input. // @@ -1466,12 +1481,8 @@ impl<'a> Planner<'a> { Err(_) => CockroachDbPreserveDowngrade::DoNotModify, }; self.blueprint.cockroachdb_preserve_downgrade(value); - info!( - &self.log, - "will ensure cockroachdb setting"; - "setting" => "cluster.preserve_downgrade_option", - "value" => ?value, - ); + report.preserve_downgrade = value; + report // Hey! Listen! // @@ -1486,12 +1497,14 @@ impl<'a> Planner<'a> { fn image_source_for_new_zone( &self, zone_kind: ZoneKind, + mgs_updates: &PlanningMgsUpdatesStepReport, ) -> Result { - let source_repo = if self.is_zone_ready_for_update(zone_kind)? { - self.input.tuf_repo().description() - } else { - self.input.old_repo().description() - }; + let source_repo = + if self.is_zone_ready_for_update(zone_kind, mgs_updates)? { + self.input.tuf_repo().description() + } else { + self.input.old_repo().description() + }; source_repo.zone_image_source(zone_kind) } @@ -1500,10 +1513,14 @@ impl<'a> Planner<'a> { fn is_zone_ready_for_update( &self, zone_kind: ZoneKind, + mgs_updates: &PlanningMgsUpdatesStepReport, ) -> Result { - // TODO-correctness: We should return false regardless of `zone_kind` if - // there are still pending updates for components earlier in the update - // ordering than zones: RoT bootloader / RoT / SP / Host OS. + // We return false regardless of `zone_kind` if there are still + // pending updates for components earlier in the update ordering + // than zones: RoT bootloader / RoT / SP / Host OS. + if mgs_updates.any_updates_pending() { + return Ok(false); + } match zone_kind { ZoneKind::Nexus => { @@ -1552,46 +1569,58 @@ impl<'a> Planner<'a> { /// because the underlying disk / sled has been expunged" case. In this /// case, we have no choice but to reconcile with the fact that the zone is /// now gone. - fn can_zone_be_shut_down_safely(&self, zone: &BlueprintZoneConfig) -> bool { + fn can_zone_be_shut_down_safely( + &self, + zone: &BlueprintZoneConfig, + report: &mut PlanningZoneUpdatesStepReport, + ) -> bool { match zone.zone_type.kind() { ZoneKind::CockroachDb => { - debug!(self.log, "Checking if Cockroach node can shut down"); + use CockroachdbUnsafeToShutdown::*; + use ZoneUnsafeToShutdown::*; + // We must hear from all nodes let all_statuses = &self.inventory.cockroach_status; if all_statuses.len() < COCKROACHDB_REDUNDANCY { - warn!(self.log, "Not enough nodes"); + report.unsafe_zone(zone, Cockroachdb(NotEnoughNodes)); return false; } // All nodes must report: "We have the necessary redundancy, and // have observed no underreplicated ranges". - for (node_id, status) in all_statuses { - let log = self.log.new(slog::o!( - "operation" => "Checking Cockroach node status for shutdown safety", - "node_id" => node_id.to_string() - )); + for (_node_id, status) in all_statuses { let Some(ranges_underreplicated) = status.ranges_underreplicated else { - warn!(log, "Missing underreplicated stat"); + report.unsafe_zone( + zone, + Cockroachdb(MissingUnderreplicatedStat), + ); return false; }; if ranges_underreplicated != 0 { - warn!(log, "Underreplicated ranges != 0"; "ranges_underreplicated" => ranges_underreplicated); + report.unsafe_zone( + zone, + Cockroachdb(UnderreplicatedRanges( + ranges_underreplicated, + )), + ); return false; } let Some(live_nodes) = status.liveness_live_nodes else { - warn!(log, "Missing live_nodes"); + report.unsafe_zone( + zone, + Cockroachdb(MissingLiveNodesStat), + ); return false; }; if live_nodes < COCKROACHDB_REDUNDANCY as u64 { - warn!(log, "Live nodes < COCKROACHDB_REDUNDANCY"; "live_nodes" => live_nodes); + report.unsafe_zone( + zone, + Cockroachdb(NotEnoughLiveNodes(live_nodes)), + ); return false; } - info!( - log, - "CockroachDB Node status looks ready for shutdown" - ); } true } @@ -5758,7 +5787,7 @@ pub(crate) mod test { /// If incidental planner work changes this value occasionally, /// that's fine; but if we find we're changing it all the time, /// we should probably drop it and keep just the maximum below. - const EXP_PLANNING_ITERATIONS: usize = 57; + const EXP_PLANNING_ITERATIONS: usize = 55; /// Planning must not take more than this number of iterations. const MAX_PLANNING_ITERATIONS: usize = 100; @@ -5769,7 +5798,7 @@ pub(crate) mod test { update_collection_from_blueprint(&mut example, &parent); let blueprint_name = format!("blueprint{i}"); - let blueprint = Planner::new_based_on( + let (blueprint, report) = Planner::new_based_on( log.clone(), &parent, &input, @@ -5778,10 +5807,15 @@ pub(crate) mod test { ) .expect("can't create planner") .with_rng(PlannerRng::from_seed((TEST_NAME, &blueprint_name))) - .plan() + .plan_and_report() .unwrap_or_else(|_| panic!("can't re-plan after {i} iterations")); + eprintln!("{report}\n"); + assert_eq!(report.blueprint_id, blueprint.id); + // TODO: more report testing + let summary = blueprint.diff_since_blueprint(&parent); + eprintln!("diff to {blueprint_name}: {}", summary.display()); if summary.total_zones_added() == 0 && summary.total_zones_removed() == 0 && summary.total_zones_modified() == 0 diff --git a/nexus/reconfigurator/planning/src/reports.rs b/nexus/reconfigurator/planning/src/reports.rs new file mode 100644 index 00000000000..e72b63f76af --- /dev/null +++ b/nexus/reconfigurator/planning/src/reports.rs @@ -0,0 +1,60 @@ +// 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/. + +//! Utilities for structured reports on planning, i.e., Blueprint generation. +//! +//! Most of the important structures (e.g., `PlanningReport`, the step reports) +//! are defined in [`nexus_types::deployment`] so that they may be shared with +//! the `blueprint_planner` background task and `omdb`. + +use nexus_types::deployment::{ + PlanningAddStepReport, PlanningCockroachdbSettingsStepReport, + PlanningDecommissionStepReport, PlanningExpungeStepReport, + PlanningMgsUpdatesStepReport, PlanningNoopImageSourceStepReport, + PlanningReport, PlanningZoneUpdatesStepReport, +}; +use omicron_uuid_kinds::BlueprintUuid; + +/// A blueprint planning report minus the blueprint ID that the +/// report is for. Returned by [`crate::planner::Planner::do_plan`] +/// when all planning steps are complete, but before the blueprint +/// has been built (and so we don't yet know its ID). +#[derive(Debug)] +pub(crate) struct InterimPlanningReport { + pub expunge: PlanningExpungeStepReport, + pub decommission: PlanningDecommissionStepReport, + pub noop_image_source: PlanningNoopImageSourceStepReport, + pub mgs_updates: PlanningMgsUpdatesStepReport, + pub add: PlanningAddStepReport, + pub zone_updates: PlanningZoneUpdatesStepReport, + pub cockroachdb_settings: PlanningCockroachdbSettingsStepReport, +} + +impl InterimPlanningReport { + /// Attach a blueprint ID to an interim planning report. + pub(crate) fn finalize( + self, + blueprint_id: BlueprintUuid, + ) -> PlanningReport { + let Self { + expunge, + decommission, + noop_image_source, + mgs_updates, + add, + zone_updates, + cockroachdb_settings, + } = self; + PlanningReport { + blueprint_id, + expunge, + decommission, + noop_image_source, + mgs_updates, + add, + zone_updates, + cockroachdb_settings, + } + } +} diff --git a/nexus/types/src/deployment.rs b/nexus/types/src/deployment.rs index b6e46b26a13..8aa4d95ce6c 100644 --- a/nexus/types/src/deployment.rs +++ b/nexus/types/src/deployment.rs @@ -67,6 +67,7 @@ mod clickhouse; pub mod execution; mod network_resources; mod planning_input; +mod planning_report; mod zone_type; use crate::inventory::BaseboardId; @@ -109,6 +110,19 @@ pub use planning_input::TargetReleaseDescription; pub use planning_input::TufRepoContentsError; pub use planning_input::TufRepoPolicy; pub use planning_input::ZpoolFilter; +pub use planning_report::CockroachdbUnsafeToShutdown; +pub use planning_report::PlanningAddStepReport; +pub use planning_report::PlanningCockroachdbSettingsStepReport; +pub use planning_report::PlanningDecommissionStepReport; +pub use planning_report::PlanningExpungeStepReport; +pub use planning_report::PlanningMgsUpdatesStepReport; +pub use planning_report::PlanningNoopImageSourceSkipSledReason; +pub use planning_report::PlanningNoopImageSourceSkipZoneReason; +pub use planning_report::PlanningNoopImageSourceStepReport; +pub use planning_report::PlanningReport; +pub use planning_report::PlanningZoneUpdatesStepReport; +pub use planning_report::ZoneUnsafeToShutdown; +pub use planning_report::ZoneUpdatesWaitingOn; use std::sync::Arc; pub use zone_type::BlueprintZoneType; pub use zone_type::DurableDataset; diff --git a/nexus/types/src/deployment/planning_report.rs b/nexus/types/src/deployment/planning_report.rs new file mode 100644 index 00000000000..c38af74f4d3 --- /dev/null +++ b/nexus/types/src/deployment/planning_report.rs @@ -0,0 +1,679 @@ +// 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/. + +//! Types representing a report on a planning run that produced a blueprint. + +use super::ArtifactHash; +use super::BlueprintZoneConfig; +use super::BlueprintZoneImageSource; +use super::CockroachDbPreserveDowngrade; +use super::PendingMgsUpdates; + +use omicron_common::policy::COCKROACHDB_REDUNDANCY; +use omicron_uuid_kinds::BlueprintUuid; +use omicron_uuid_kinds::MupdateOverrideUuid; +use omicron_uuid_kinds::OmicronZoneUuid; +use omicron_uuid_kinds::PhysicalDiskUuid; +use omicron_uuid_kinds::SledUuid; +use omicron_uuid_kinds::ZpoolUuid; +use serde::Deserialize; +use serde::Serialize; + +use std::collections::BTreeMap; +use std::collections::BTreeSet; +use std::fmt; + +/// A full blueprint planning report. Other than the blueprint ID, each +/// field corresponds to a step in the update planner, i.e., a subroutine +/// of `omicron_nexus::reconfigurator::planning::Planner::do_plan`. +/// +/// The intent of a planning report is to capture information useful to an +/// operator or developer about the planning process itself, especially if +/// it has become "stuck" (unable to proceed with an update). It is *not* a +/// summary of the plan (blueprint), but rather a description of non-fatal +/// conditions the planner is waiting on, unexpected or invalid +/// configurations encountered during planning, etc. The planner may make +/// internal decisions based on the step reports; the intent is that an +/// operator may make administrative decisions based on the full report. +/// +/// Only successful planning runs are currently covered by this report. +/// Failures to plan (i.e., to generate a valid blueprint) are represented +/// by `nexus-reconfigurator-planning::blueprint_builder::Error`. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[must_use = "an unread report is not actionable"] +pub struct PlanningReport { + /// The blueprint produced by the planning run this report describes. + pub blueprint_id: BlueprintUuid, + + // Step reports. + pub expunge: PlanningExpungeStepReport, + pub decommission: PlanningDecommissionStepReport, + pub noop_image_source: PlanningNoopImageSourceStepReport, + pub mgs_updates: PlanningMgsUpdatesStepReport, + pub add: PlanningAddStepReport, + pub zone_updates: PlanningZoneUpdatesStepReport, + pub cockroachdb_settings: PlanningCockroachdbSettingsStepReport, +} + +impl fmt::Display for PlanningReport { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { + blueprint_id, + expunge, + decommission, + noop_image_source, + mgs_updates, + add, + zone_updates, + cockroachdb_settings, + } = self; + writeln!(f, "Report on planning run for blueprint {blueprint_id}:")?; + expunge.fmt(f)?; + decommission.fmt(f)?; + noop_image_source.fmt(f)?; + mgs_updates.fmt(f)?; + add.fmt(f)?; + zone_updates.fmt(f)?; + cockroachdb_settings.fmt(f)?; + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct PlanningExpungeStepReport { + /// Expunged disks not present in the parent blueprint. + pub orphan_disks: BTreeMap, +} + +impl PlanningExpungeStepReport { + pub fn new() -> Self { + Self { orphan_disks: BTreeMap::new() } + } +} + +impl fmt::Display for PlanningExpungeStepReport { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { orphan_disks } = self; + if !orphan_disks.is_empty() { + writeln!( + f, + "* planning input contained expunged disks \ + not present in parent blueprint:", + )?; + for (sled, disk) in orphan_disks.iter() { + writeln!(f, " * sled {sled}, disk {disk}",)?; + } + } + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct PlanningDecommissionStepReport { + /// Decommissioned sleds that unexpectedly appeared as commissioned. + pub zombie_sleds: Vec, +} + +impl PlanningDecommissionStepReport { + pub fn new() -> Self { + Self { zombie_sleds: Vec::new() } + } +} + +impl fmt::Display for PlanningDecommissionStepReport { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { zombie_sleds } = self; + if !zombie_sleds.is_empty() { + let n = zombie_sleds.len(); + let s = if n == 1 { "" } else { "s" }; + writeln!( + f, + "* decommissioned sled{s} returned by `SledFilter::Commissioned`: {}", + zombie_sleds + .iter() + .map(|sled_id| format!("{sled_id}")) + .collect::>() + .join(", ") + )?; + } + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct PlanningNoopImageSourceStepReport { + pub no_target_release: bool, + pub skipped_sleds: + BTreeMap, + pub skipped_zones: + BTreeMap, + pub converted_zones: BTreeMap, +} + +impl PlanningNoopImageSourceStepReport { + pub fn new() -> Self { + Self { + no_target_release: false, + skipped_sleds: BTreeMap::new(), + skipped_zones: BTreeMap::new(), + converted_zones: BTreeMap::new(), + } + } + + pub fn skip_sled( + &mut self, + sled_id: SledUuid, + reason: PlanningNoopImageSourceSkipSledReason, + ) { + self.skipped_sleds.insert(sled_id, reason); + } + + pub fn skip_zone( + &mut self, + zone_id: OmicronZoneUuid, + reason: PlanningNoopImageSourceSkipZoneReason, + ) { + self.skipped_zones.insert(zone_id, reason); + } + + pub fn converted_zones( + &mut self, + sled_id: SledUuid, + num_eligible: usize, + num_dataset: usize, + ) { + self.converted_zones.insert(sled_id, (num_eligible, num_dataset)); + } +} + +impl fmt::Display for PlanningNoopImageSourceStepReport { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { + no_target_release, + skipped_sleds, + skipped_zones: _, + converted_zones, + } = self; + + if *no_target_release { + return writeln!( + f, + "* Skipping noop image source check for all sleds (no current TUF repo)", + ); + } + + for (sled_id, reason) in skipped_sleds.iter() { + writeln!( + f, + "* Skipping noop image source check on sled {sled_id}: {reason}" + )?; + } + + // Very noisy in tests. + // for (zone_id, reason) in skipped_zones.iter() { + // writeln!( + // f, + // "* Skipping noop image source check for zone {zone_id}: {reason}" + // )?; + // } + + for (sled_id, (m, n)) in converted_zones.iter() { + if *m > 0 && *n > 0 { + writeln!( + f, + "* Noop converting {m}/{n} install-dataset zones to artifact store \ + on sled {sled_id}", + )?; + } + } + + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub enum PlanningNoopImageSourceSkipSledReason { + AllZonesAlreadyArtifact(usize), + SledNotInInventory, + ErrorRetrievingZoneManifest(String), + RemoveMupdateOverride(MupdateOverrideUuid), +} + +impl fmt::Display for PlanningNoopImageSourceSkipSledReason { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::AllZonesAlreadyArtifact(n) => { + write!(f, "all {n} zones are already from artifacts") + } + Self::SledNotInInventory => { + write!(f, "sled not present in latest inventory collection") + } + Self::ErrorRetrievingZoneManifest(error) => { + write!( + f, + "sled-agent encountered error retrieving zone manifest \ + (this is abnormal): {error}" + ) + } + Self::RemoveMupdateOverride(id) => { + write!( + f, + "blueprint has get_remove_mupdate_override set for sled: {id}", + ) + } + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub enum PlanningNoopImageSourceSkipZoneReason { + ZoneNotInManifest { + zone_kind: String, + file_name: String, + }, + InvalidArtifact { + zone_kind: String, + file_name: String, + error: String, + }, + ArtifactNotInRepo { + artifact_hash: ArtifactHash, + zone_kind: String, + file_name: String, + }, +} + +impl fmt::Display for PlanningNoopImageSourceSkipZoneReason { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::ZoneNotInManifest { file_name, .. } => { + write!(f, "artifact not found in zone manifest: {file_name}") + } + Self::InvalidArtifact { error, .. } => { + write!( + f, + "zone manifest inventory indicated install dataset artifact \ + is invalid, not using artifact (this is abnormal): {error}" + ) + } + Self::ArtifactNotInRepo { .. } => { + write!(f, "install dataset artifact hash not found in TUF repo") + } + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct PlanningMgsUpdatesStepReport { + pub pending_mgs_updates: PendingMgsUpdates, +} + +impl PlanningMgsUpdatesStepReport { + pub fn new(pending_mgs_updates: PendingMgsUpdates) -> Self { + Self { pending_mgs_updates } + } + + // TODO This is not quite right. See oxidecomputer/omicron#8285. + pub fn any_updates_pending(&self) -> bool { + !self.pending_mgs_updates.is_empty() + } +} + +impl fmt::Display for PlanningMgsUpdatesStepReport { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { pending_mgs_updates } = self; + if !pending_mgs_updates.is_empty() { + let n = pending_mgs_updates.len(); + let s = if n == 1 { "" } else { "s" }; + writeln!(f, "* {n} pending MGS update{s}:")?; + for update in pending_mgs_updates.iter() { + writeln!( + f, + " * {}: {:?}", + update.baseboard_id, update.details + )?; + } + } + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct PlanningAddStepReport { + pub sleds_with_no_zpools_for_ntp_zone: BTreeSet, + pub sleds_waiting_for_ntp_zone: BTreeSet, + pub sleds_getting_ntp_and_discretionary_zones: BTreeSet, + pub sleds_missing_ntp_zone: BTreeSet, + pub sleds_missing_crucible_zone: BTreeSet<(SledUuid, ZpoolUuid)>, + + /// Discretionary zone kind → (placed, wanted to place) + pub out_of_eligible_sleds: BTreeMap, + + /// Discretionary zone kind → (wanted to place, num existing) + pub sufficient_zones_exist: BTreeMap, + + /// List of (Sled ID, kind of discretionary zone placed there) pairs. + // TODO: make `sled_add_zone_*` methods return the added zone config + // so that we can report it here. + pub discretionary_zones_placed: Vec<(SledUuid, String)>, +} + +impl PlanningAddStepReport { + pub fn new() -> Self { + Self { + sleds_with_no_zpools_for_ntp_zone: BTreeSet::new(), + sleds_waiting_for_ntp_zone: BTreeSet::new(), + sleds_getting_ntp_and_discretionary_zones: BTreeSet::new(), + sleds_missing_ntp_zone: BTreeSet::new(), + sleds_missing_crucible_zone: BTreeSet::new(), + out_of_eligible_sleds: BTreeMap::new(), + sufficient_zones_exist: BTreeMap::new(), + discretionary_zones_placed: Vec::new(), + } + } + + pub fn any_discretionary_zones_placed(&self) -> bool { + !self.discretionary_zones_placed.is_empty() + } +} + +impl fmt::Display for PlanningAddStepReport { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { + sleds_with_no_zpools_for_ntp_zone, + sleds_waiting_for_ntp_zone, + sleds_getting_ntp_and_discretionary_zones, + sleds_missing_ntp_zone, + sleds_missing_crucible_zone, + out_of_eligible_sleds, + sufficient_zones_exist: _, + discretionary_zones_placed, + } = self; + + if !sleds_with_no_zpools_for_ntp_zone.is_empty() { + writeln!( + f, + "* No zpools in service for NTP zones on sleds: {}", + sleds_with_no_zpools_for_ntp_zone + .iter() + .map(|sled_id| format!("{sled_id}")) + .collect::>() + .join(", ") + )?; + } + + if !sleds_waiting_for_ntp_zone.is_empty() { + writeln!( + f, + "* Discretionary zone placement waiting for NTP zones on sleds: {}", + sleds_waiting_for_ntp_zone + .iter() + .map(|sled_id| format!("{sled_id}")) + .collect::>() + .join(", ") + )?; + } + + if !sleds_getting_ntp_and_discretionary_zones.is_empty() { + writeln!( + f, + "* Sleds getting NTP zones and which have other services already, \ + making them eligible for discretionary zones: {}", + sleds_getting_ntp_and_discretionary_zones + .iter() + .map(|sled_id| format!("{sled_id}")) + .collect::>() + .join(", ") + )?; + } + + for sled_id in sleds_missing_ntp_zone { + writeln!(f, "* Missing NTP zone on sled {sled_id}",)?; + } + + for (sled_id, zpool_id) in sleds_missing_crucible_zone { + writeln!( + f, + "* Missing Crucible zone for sled {sled_id}, zpool {zpool_id}", + )?; + } + + for (kind, (placed, desired)) in out_of_eligible_sleds.iter() { + writeln!( + f, + "* Only placed {placed}/{desired} desired {kind} zones" + )?; + } + + // Noisy in tests. + // for (kind, (desired, existing)) in sufficient_zones_exist.iter() { + // writeln!( + // f, + // "* Sufficient {kind} zones exist in plan: {desired}/{existing}" + // )?; + // } + + if !discretionary_zones_placed.is_empty() { + writeln!(f, "* Discretionary zones placed:")?; + for (sled_id, kind) in discretionary_zones_placed.iter() { + writeln!(f, " * a {kind} zone on sled {sled_id}")?; + } + } + + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct PlanningZoneUpdatesStepReport { + /// What are we waiting on to start zone updates? + pub waiting_on: Option, + + /// (Sled ID, zone, desired image) + pub out_of_date_zones: + Vec<(SledUuid, BlueprintZoneConfig, BlueprintZoneImageSource)>, + + pub expunged_zones: Vec<(SledUuid, BlueprintZoneConfig)>, + pub updated_zones: Vec<(SledUuid, BlueprintZoneConfig)>, + pub unsafe_zones: Vec<(BlueprintZoneConfig, ZoneUnsafeToShutdown)>, +} + +impl PlanningZoneUpdatesStepReport { + pub fn new() -> Self { + Self { + waiting_on: None, + out_of_date_zones: Vec::new(), + expunged_zones: Vec::new(), + updated_zones: Vec::new(), + unsafe_zones: Vec::new(), + } + } + + pub fn waiting_on(&mut self, waiting_on: ZoneUpdatesWaitingOn) { + self.waiting_on = Some(waiting_on); + } + + pub fn unsafe_zone( + &mut self, + zone: &BlueprintZoneConfig, + reason: ZoneUnsafeToShutdown, + ) { + self.unsafe_zones.push((zone.clone(), reason)) + } +} + +impl fmt::Display for PlanningZoneUpdatesStepReport { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { + waiting_on, + out_of_date_zones, + expunged_zones, + updated_zones, + unsafe_zones, + } = self; + + if let Some(waiting_on) = waiting_on { + writeln!(f, "* Zone updates waiting on {}", waiting_on.as_str())?; + } + + if !expunged_zones.is_empty() { + let n = out_of_date_zones.len(); + let s = if n == 1 { "" } else { "s" }; + writeln!(f, "* Out-of-date zone{s} expunged:")?; + for (sled_id, zone) in expunged_zones.iter() { + writeln!( + f, + " * sled {}, zone {} ({})", + sled_id, + zone.id, + zone.zone_type.kind().report_str(), + )?; + } + } + + if !updated_zones.is_empty() { + let n = out_of_date_zones.len(); + let s = if n == 1 { "" } else { "s" }; + writeln!(f, "* Out-of-date zone{s} updated in-place:")?; + for (sled_id, zone) in updated_zones.iter() { + writeln!( + f, + " * sled {}, zone {} ({})", + sled_id, + zone.id, + zone.zone_type.kind().report_str(), + )?; + } + } + + if !out_of_date_zones.is_empty() { + let n = out_of_date_zones.len(); + let s = if n == 1 { "" } else { "s" }; + writeln!(f, "* {n} out-of-date zone{s}:")?; + for (sled, zone, _image_source) in out_of_date_zones.iter() { + writeln!( + f, + " * sled {}, zone {} ({})", // TODO: current → desired image source + sled, + zone.id, + zone.zone_type.kind().report_str(), + )?; + } + } + + if !unsafe_zones.is_empty() { + let n = unsafe_zones.len(); + let s = if n == 1 { "" } else { "s" }; + writeln!(f, "* {n} zone{s} not ready to shut down safely:")?; + for (zone, reason) in unsafe_zones.iter() { + writeln!( + f, + " * zone {} ({}): {}", + zone.id, + zone.zone_type.kind().report_str(), + reason, + )?; + } + } + + Ok(()) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub enum ZoneUpdatesWaitingOn { + /// Waiting on discretionary zone placement. + DiscretionaryZones, + + /// Waiting on updates to RoT / SP / Host OS / etc. + PendingMgsUpdates, +} + +impl ZoneUpdatesWaitingOn { + pub fn as_str(&self) -> &'static str { + match self { + Self::DiscretionaryZones => "discretionary zones", + Self::PendingMgsUpdates => { + "pending MGS updates (RoT / SP / Host OS / etc.)" + } + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub enum ZoneUnsafeToShutdown { + Cockroachdb(CockroachdbUnsafeToShutdown), +} + +impl fmt::Display for ZoneUnsafeToShutdown { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Cockroachdb(reason) => write!(f, "{reason}"), + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub enum CockroachdbUnsafeToShutdown { + MissingLiveNodesStat, + MissingUnderreplicatedStat, + NotEnoughLiveNodes(u64), + NotEnoughNodes, + UnderreplicatedRanges(u64), +} + +impl fmt::Display for CockroachdbUnsafeToShutdown { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::MissingLiveNodesStat => write!(f, "missing live_nodes stat"), + Self::MissingUnderreplicatedStat => { + write!(f, "missing ranges_underreplicated stat") + } + Self::NotEnoughLiveNodes(n) => { + write!( + f, + "not enough live nodes: {n} < {COCKROACHDB_REDUNDANCY}" + ) + } + Self::NotEnoughNodes => write!(f, "not enough nodes"), + Self::UnderreplicatedRanges(n) => { + if *n > 0 { + write!(f, "{n} > 0 underreplicated ranges") + } else { + write!( + f, + "no underreplicated ranges (this shouldn't happen)" + ) + } + } + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct PlanningCockroachdbSettingsStepReport { + pub preserve_downgrade: CockroachDbPreserveDowngrade, +} + +impl PlanningCockroachdbSettingsStepReport { + pub fn new() -> Self { + Self { preserve_downgrade: CockroachDbPreserveDowngrade::DoNotModify } + } +} + +impl fmt::Display for PlanningCockroachdbSettingsStepReport { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let PlanningCockroachdbSettingsStepReport { preserve_downgrade } = self; + if !matches!( + preserve_downgrade, + CockroachDbPreserveDowngrade::DoNotModify, + ) { + writeln!( + f, + "* Will ensure cockroachdb setting: {preserve_downgrade}" + )?; + } + Ok(()) + } +} From e30070752f5fc5970fec0ce4ef9e66eda3330237 Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Tue, 15 Jul 2025 13:12:06 -0600 Subject: [PATCH 02/10] omdb support for planning reports --- dev-tools/omdb/src/bin/omdb/nexus.rs | 3 ++- nexus/src/app/background/tasks/blueprint_planner.rs | 12 +++++++++--- nexus/types/src/internal_api/background.rs | 8 +++++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/dev-tools/omdb/src/bin/omdb/nexus.rs b/dev-tools/omdb/src/bin/omdb/nexus.rs index fca3415534e..0641966ecf9 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus.rs @@ -1258,11 +1258,12 @@ fn print_task_blueprint_planner(details: &serde_json::Value) { but could not make it the target: {error}" ); } - BlueprintPlannerStatus::Targeted { blueprint_id, .. } => { + BlueprintPlannerStatus::Targeted { blueprint_id, report, .. } => { println!( " planned new blueprint {blueprint_id}, \ and made it the current target" ); + println!("{report}"); } } } diff --git a/nexus/src/app/background/tasks/blueprint_planner.rs b/nexus/src/app/background/tasks/blueprint_planner.rs index 66a33f6e419..7aac2228145 100644 --- a/nexus/src/app/background/tasks/blueprint_planner.rs +++ b/nexus/src/app/background/tasks/blueprint_planner.rs @@ -150,7 +150,7 @@ impl BlueprintPlanner { )); } }; - let blueprint = match planner.plan() { + let (blueprint, report) = match planner.plan_and_report() { Ok(blueprint) => blueprint, Err(error) => { error!(&opctx.log, "can't plan: {error}"); @@ -241,7 +241,11 @@ impl BlueprintPlanner { // We have a new target! self.tx_blueprint.send_replace(Some(Arc::new((target, blueprint)))); - BlueprintPlannerStatus::Targeted { parent_blueprint_id, blueprint_id } + BlueprintPlannerStatus::Targeted { + parent_blueprint_id, + blueprint_id, + report, + } } } @@ -332,8 +336,10 @@ mod test { BlueprintPlannerStatus::Targeted { parent_blueprint_id, blueprint_id, + report, } if parent_blueprint_id == initial_blueprint.id - && blueprint_id != initial_blueprint.id => + && blueprint_id != initial_blueprint.id + && blueprint_id == report.blueprint_id => { blueprint_id } diff --git a/nexus/types/src/internal_api/background.rs b/nexus/types/src/internal_api/background.rs index ca97f5f8924..f6a4d385305 100644 --- a/nexus/types/src/internal_api/background.rs +++ b/nexus/types/src/internal_api/background.rs @@ -2,6 +2,7 @@ // 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 crate::deployment::PlanningReport; use crate::external_api::views; use chrono::DateTime; use chrono::Utc; @@ -460,6 +461,7 @@ impl slog::KV for DebugDatasetsRendezvousStats { } /// The status of a `blueprint_planner` background task activation. +#[allow(clippy::large_enum_variant)] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub enum BlueprintPlannerStatus { /// Automatic blueprint planning has been explicitly disabled @@ -479,7 +481,11 @@ pub enum BlueprintPlannerStatus { /// Planing succeeded, and we saved and made the new blueprint the /// current target. - Targeted { parent_blueprint_id: BlueprintUuid, blueprint_id: BlueprintUuid }, + Targeted { + parent_blueprint_id: BlueprintUuid, + blueprint_id: BlueprintUuid, + report: PlanningReport, + }, } /// The status of a `alert_dispatcher` background task activation. From 2feacef3dc3484a6ad585ce9719412781ef8b333 Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Fri, 18 Jul 2025 16:47:05 +0000 Subject: [PATCH 03/10] expectorate --- .../output/cmds-add-sled-no-disks-stdout | 13 ---- .../tests/output/cmds-example-stdout | 25 ------- ...ds-expunge-newly-added-external-dns-stdout | 12 ---- ...ds-expunge-newly-added-internal-dns-stdout | 12 ---- .../output/cmds-noop-image-source-stdout | 26 ------- .../tests/output/cmds-target-release-stdout | 67 ------------------- 6 files changed, 155 deletions(-) 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 4feccb11e4c..1c0922f8df1 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 @@ -37,20 +37,7 @@ generated inventory collection eb0796d5-ab8a-4f7b-a884-b4aeacb8ab51 from configu > # 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, reason: no target release is currently set -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 -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 WARN cannot issue more SP updates (no current artifacts) -INFO all zones up-to-date -INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify generated blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 based on parent blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 > blueprint-show 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout index 4b2c4f5e167..ef82f4056ad 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout @@ -526,20 +526,7 @@ T ENA ID PARENT > blueprint-plan ade5749d-bdf3-4fab-a8ae-00bea01b3a5a INFO skipping noop image source check for all sleds, reason: no target release is currently set -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 -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 -WARN failed to place all new desired CruciblePantry zones, placed: 0, wanted_to_place: 3 -WARN failed to place all new desired InternalDns zones, placed: 0, wanted_to_place: 3 -INFO sufficient ExternalDns zones exist in plan, desired_count: 0, current_count: 0 -WARN failed to place all new desired Nexus zones, placed: 0, wanted_to_place: 3 -INFO sufficient Oximeter zones exist in plan, desired_count: 0, current_count: 0 WARN cannot issue more SP updates (no current artifacts) -INFO some zones not yet up-to-date, sled_id: 89d02b1b-478c-401a-8e28-7a26f74fa41b, zones_currently_updating: [ZoneCurrentlyUpdating { zone_id: b3c9c041-d2f0-4767-bdaf-0e52e9d7a013 (service), zone_kind: InternalNtp, reason: MissingInInventory { bp_image_source: InstallDataset } }] -INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify generated blueprint 86db3308-f817-4626-8838-4085949a6a41 based on parent blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a > blueprint-list @@ -1018,19 +1005,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 > # sled to be expunged. > blueprint-plan latest INFO skipping noop image source check for all sleds, reason: no target release is currently set -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 WARN cannot issue more SP updates (no current artifacts) -INFO all zones up-to-date -INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify generated blueprint 86db3308-f817-4626-8838-4085949a6a41 based on parent blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a > blueprint-diff ade5749d-bdf3-4fab-a8ae-00bea01b3a5a latest 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 a1876121bed..6caf1afc88f 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 @@ -1026,19 +1026,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, reason: no target release is currently set -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 added zone to sled, sled_id: 711ac7f8-d19e-4572-bdb9-e9b50f6e362a, kind: ExternalDns -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 WARN cannot issue more SP updates (no current artifacts) -INFO some zones not yet up-to-date, sled_id: 711ac7f8-d19e-4572-bdb9-e9b50f6e362a, zones_currently_updating: [ZoneCurrentlyUpdating { zone_id: fe2d5287-24e3-4071-b214-2640b097a759 (service), zone_kind: ExternalDns, reason: MissingInInventory { bp_image_source: InstallDataset } }] -INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify generated blueprint 9c998c1d-1a7b-440a-ae0c-40f781dea6e2 based on parent blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0 > blueprint-diff 366b0b68-d80e-4bc1-abd3-dc69837847e0 9c998c1d-1a7b-440a-ae0c-40f781dea6e2 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 ade6db21966..d391a5a657d 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 @@ -1043,19 +1043,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, reason: no target release is currently set -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 added zone to sled, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, kind: InternalDns -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 WARN cannot issue more SP updates (no current artifacts) -INFO some zones not yet up-to-date, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zones_currently_updating: [ZoneCurrentlyUpdating { zone_id: e375dd21-320b-43b7-bc92-a2c3dac9d9e1 (service), zone_kind: InternalDns, reason: MissingInInventory { bp_image_source: InstallDataset } }] -INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify generated blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 based on parent blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 > blueprint-diff 58d5e830-0884-47d8-a7cd-b2b3751adeb4 af934083-59b5-4bf6-8966-6fb5292c29e1 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 index 95a4c206dd7..dbcfe2d5720 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout @@ -165,22 +165,8 @@ INFO install dataset artifact hash not found in TUF repo, ignoring for noop chec INFO install dataset artifact hash not found in TUF repo, ignoring for noop checks, sled_id: b82ede02-399c-48c6-a1de-411df4fa49a7, zone_id: ecbe0b3d-1acc-44b2-b6d4-f4d2770516e4, kind: crucible, file_name: crucible.tar.gz, expected_hash: 866f6a7c2e51c056fb722b5113e80181cc9cd8b712a0d3dbf1edc4ce29e5229e INFO skipped noop image source check on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, reason: remove_mupdate_override is set in the blueprint (ffffffff-ffff-ffff-ffff-ffffffffffff) INFO skipped noop image source check on sled, sled_id: e96e226f-4ed9-4c01-91b9-69a9cd076c9e, reason: sled not found in inventory -INFO noop converting 6/6 install-dataset zones to artifact store, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, num_total: 6, num_already_artifact: 0 -INFO noop converting 5/6 install-dataset zones to artifact store, sled_id: aff6c093-197d-42c5-ad80-9f10ba051a34, num_total: 6, num_already_artifact: 0 -INFO parent blueprint contains NTP zone, but it's not in inventory yet, sled_id: e96e226f-4ed9-4c01-91b9-69a9cd076c9e -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 @@ -537,20 +523,8 @@ INFO install dataset artifact hash not found in TUF repo, ignoring for noop chec INFO install dataset artifact hash not found in TUF repo, ignoring for noop checks, sled_id: b82ede02-399c-48c6-a1de-411df4fa49a7, zone_id: ecbe0b3d-1acc-44b2-b6d4-f4d2770516e4, kind: crucible, file_name: crucible.tar.gz, expected_hash: 866f6a7c2e51c056fb722b5113e80181cc9cd8b712a0d3dbf1edc4ce29e5229e INFO skipped noop image source check on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, reason: remove_mupdate_override is set in the blueprint (ffffffff-ffff-ffff-ffff-ffffffffffff) INFO performed noop image source checks on sled, sled_id: e96e226f-4ed9-4c01-91b9-69a9cd076c9e, num_total: 2, num_already_artifact: 0, num_eligible: 2, num_ineligible: 0 -INFO noop converting 2/2 install-dataset zones to artifact store, sled_id: e96e226f-4ed9-4c01-91b9-69a9cd076c9e, num_total: 2, num_already_artifact: 0 -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 SP update not yet completed (will keep it), 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 af934083-59b5-4bf6-8966-6fb5292c29e1 based on parent blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 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 a994234a81a..0f8163333e8 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout @@ -195,19 +195,8 @@ f45ba181-4b56-42cc-a762-874d90184a43 0 INFO performed noop image source checks on sled, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, num_total: 9, num_already_artifact: 0, num_eligible: 0, num_ineligible: 9 INFO performed noop image source checks on sled, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, num_total: 8, num_already_artifact: 0, num_eligible: 0, num_ineligible: 8 INFO performed noop image source checks on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, num_total: 8, num_already_artifact: 0, num_eligible: 0, num_ineligible: 8 -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: 0, 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 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 based on parent blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 > blueprint-diff dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 @@ -380,19 +369,8 @@ external DNS: INFO performed noop image source checks on sled, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, num_total: 9, num_already_artifact: 0, num_eligible: 0, num_ineligible: 9 INFO performed noop image source checks on sled, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, num_total: 8, num_already_artifact: 0, num_eligible: 0, num_ineligible: 8 INFO performed noop image source checks on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, num_total: 8, num_already_artifact: 0, num_eligible: 0, num_ineligible: 8 -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: 0, 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 SP update not yet completed (will keep it), 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 > blueprint-diff 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 58d5e830-0884-47d8-a7cd-b2b3751adeb4 @@ -566,21 +544,10 @@ generated inventory collection eb0796d5-ab8a-4f7b-a884-b4aeacb8ab51 from configu INFO performed noop image source checks on sled, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, num_total: 9, num_already_artifact: 0, num_eligible: 0, num_ineligible: 9 INFO performed noop image source checks on sled, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, num_total: 8, num_already_artifact: 0, num_eligible: 0, num_ineligible: 8 INFO performed noop image source checks on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, num_total: 8, num_already_artifact: 0, num_eligible: 0, num_ineligible: 8 -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: 0, 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 SP update completed (will remove it and re-evaluate board), 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 skipping board for SP update, serial_number: serial0, part_number: model0 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: 1, sp_type: Sled, serial_number: serial1, part_number: model1 INFO reached maximum number of pending SP updates, max: 1 -INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify generated blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 based on parent blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 > blueprint-diff 58d5e830-0884-47d8-a7cd-b2b3751adeb4 af934083-59b5-4bf6-8966-6fb5292c29e1 @@ -762,20 +729,9 @@ generated inventory collection 61f451b3-2121-4ed6-91c7-a550054f6c21 from configu INFO performed noop image source checks on sled, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, num_total: 9, num_already_artifact: 0, num_eligible: 0, num_ineligible: 9 INFO performed noop image source checks on sled, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, num_total: 8, num_already_artifact: 0, num_eligible: 0, num_ineligible: 8 INFO performed noop image source checks on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, num_total: 8, num_already_artifact: 0, num_eligible: 0, num_ineligible: 8 -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: 0, 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 SP update impossible (will remove it and re-evaluate board), artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: NoValidVersion, expected_active_version: 0.0.1, component: sp, sp_slot: 1, sp_type: Sled, serial_number: serial1, part_number: model1 INFO configuring SP update, artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: Version(ArtifactVersion("0.5.0")), expected_active_version: 0.0.1, component: sp, sp_slot: 1, sp_type: Sled, serial_number: serial1, part_number: model1 INFO reached maximum number of pending SP updates, max: 1 -INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify generated blueprint df06bb57-ad42-4431-9206-abff322896c7 based on parent blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 > blueprint-diff af934083-59b5-4bf6-8966-6fb5292c29e1 df06bb57-ad42-4431-9206-abff322896c7 @@ -956,22 +912,11 @@ generated inventory collection b1bda47d-2c19-4fba-96e3-d9df28db7436 from configu INFO performed noop image source checks on sled, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, num_total: 9, num_already_artifact: 0, num_eligible: 0, num_ineligible: 9 INFO performed noop image source checks on sled, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, num_total: 8, num_already_artifact: 0, num_eligible: 0, num_ineligible: 8 INFO performed noop image source checks on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, num_total: 8, num_already_artifact: 0, num_eligible: 0, num_ineligible: 8 -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: 0, 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 SP update completed (will remove it and re-evaluate board), artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: Version(ArtifactVersion("0.5.0")), expected_active_version: 0.0.1, component: sp, sp_slot: 1, sp_type: Sled, serial_number: serial1, part_number: model1 INFO skipping board for SP update, serial_number: serial1, part_number: model1 INFO skipping board for SP update, serial_number: serial0, part_number: model0 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: 2, sp_type: Sled, serial_number: serial2, part_number: model2 INFO ran out of boards for SP update -INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify generated blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba based on parent blueprint df06bb57-ad42-4431-9206-abff322896c7 > blueprint-diff df06bb57-ad42-4431-9206-abff322896c7 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba @@ -1152,23 +1097,11 @@ generated inventory collection a71f7a73-35a6-45e8-acbe-f1c5925eed69 from configu INFO performed noop image source checks on sled, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, num_total: 9, num_already_artifact: 0, num_eligible: 0, num_ineligible: 9 INFO performed noop image source checks on sled, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, num_total: 8, num_already_artifact: 0, num_eligible: 0, num_ineligible: 8 INFO performed noop image source checks on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, num_total: 8, num_already_artifact: 0, num_eligible: 0, num_ineligible: 8 -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: 0, 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 SP update completed (will remove it and re-evaluate board), artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: NoValidVersion, expected_active_version: 0.0.1, component: sp, sp_slot: 2, sp_type: Sled, serial_number: serial2, part_number: model2 INFO skipping board for SP update, serial_number: serial2, part_number: model2 INFO skipping board for SP update, serial_number: serial0, part_number: model0 INFO skipping board for SP update, serial_number: serial1, part_number: model1 INFO ran out of boards for SP update -INFO updating zone image source in-place, sled_id: 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zone_id: 353b3b65-20f7-48c3-88f7-495bd5d31545, kind: Clickhouse, image_source: artifact: version 1.0.0 -INFO will ensure cockroachdb setting, setting: cluster.preserve_downgrade_option, value: DoNotModify generated blueprint 9034c710-3e57-45f3-99e5-4316145e87ac based on parent blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba > blueprint-diff 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba 9034c710-3e57-45f3-99e5-4316145e87ac From 48dce2a626dcad2e259294e002d177fa8895489e Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Fri, 25 Jul 2025 10:24:02 -0600 Subject: [PATCH 04/10] Kill InterimPlanningReport Compute the new blueprint ID up front instead. Requires passing a PlannerRng to the initializers. --- dev-tools/reconfigurator-cli/src/lib.rs | 6 +- live-tests/tests/common/reconfigurator.rs | 2 + live-tests/tests/test_nexus_add_remove.rs | 2 + .../db-queries/src/db/datastore/deployment.rs | 8 + nexus/db-queries/src/db/datastore/vpc.rs | 3 + nexus/reconfigurator/execution/src/dns.rs | 2 + .../planning/src/blueprint_builder/builder.rs | 47 ++++-- nexus/reconfigurator/planning/src/example.rs | 2 +- nexus/reconfigurator/planning/src/lib.rs | 1 - nexus/reconfigurator/planning/src/planner.rs | 138 +++++++++--------- nexus/reconfigurator/planning/src/reports.rs | 60 -------- .../app/background/tasks/blueprint_planner.rs | 2 + nexus/src/app/deployment.rs | 2 + 13 files changed, 128 insertions(+), 147 deletions(-) delete mode 100644 nexus/reconfigurator/planning/src/reports.rs diff --git a/dev-tools/reconfigurator-cli/src/lib.rs b/dev-tools/reconfigurator-cli/src/lib.rs index 928a64793e9..69bca2fdea2 100644 --- a/dev-tools/reconfigurator-cli/src/lib.rs +++ b/dev-tools/reconfigurator-cli/src/lib.rs @@ -1565,9 +1565,9 @@ fn cmd_blueprint_plan( &planning_input, creator, collection, + rng, ) - .context("creating planner")? - .with_rng(rng); + .context("creating planner")?; let blueprint = planner.plan().context("generating blueprint")?; let rv = format!( @@ -1610,9 +1610,9 @@ fn cmd_blueprint_edit( &planning_input, &latest_collection, creator, + rng, ) .context("creating blueprint builder")?; - builder.set_rng(rng); if let Some(comment) = args.comment { builder.comment(comment); diff --git a/live-tests/tests/common/reconfigurator.rs b/live-tests/tests/common/reconfigurator.rs index 040bbec81f8..c34ebf6124f 100644 --- a/live-tests/tests/common/reconfigurator.rs +++ b/live-tests/tests/common/reconfigurator.rs @@ -7,6 +7,7 @@ use anyhow::{Context, ensure}; use nexus_client::types::BlueprintTargetSet; use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; +use nexus_reconfigurator_planning::planner::PlannerRng; use nexus_types::deployment::{Blueprint, PlanningInput}; use nexus_types::inventory::Collection; use omicron_uuid_kinds::GenericUuid; @@ -73,6 +74,7 @@ pub async fn blueprint_edit_current_target( &planning_input, &collection, "test-suite", + PlannerRng::from_entropy(), ) .context("creating BlueprintBuilder")?; diff --git a/live-tests/tests/test_nexus_add_remove.rs b/live-tests/tests/test_nexus_add_remove.rs index 5fe9cc58530..c0e5a7ac6f6 100644 --- a/live-tests/tests/test_nexus_add_remove.rs +++ b/live-tests/tests/test_nexus_add_remove.rs @@ -16,6 +16,7 @@ use nexus_client::types::SagaState; use nexus_inventory::CollectionBuilder; use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; use nexus_reconfigurator_planning::planner::Planner; +use nexus_reconfigurator_planning::planner::PlannerRng; use nexus_reconfigurator_preparation::PlanningInputFromDb; use nexus_sled_agent_shared::inventory::ZoneKind; use nexus_types::deployment::BlueprintZoneDisposition; @@ -256,6 +257,7 @@ async fn test_nexus_add_remove(lc: &LiveTestContext) { &planning_input, "live test suite", &latest_collection, + PlannerRng::from_entropy(), ) .expect("constructing planner"); let new_blueprint = planner.plan().expect("creating blueprint"); diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index 19889503a65..75bc0c030b4 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -2354,6 +2354,7 @@ mod tests { use nexus_reconfigurator_planning::blueprint_builder::EnsureMultiple; use nexus_reconfigurator_planning::example::ExampleSystemBuilder; use nexus_reconfigurator_planning::example::example; + use nexus_reconfigurator_planning::planner::PlannerRng; use nexus_types::deployment::BlueprintArtifactVersion; use nexus_types::deployment::BlueprintHostPhase2DesiredContents; use nexus_types::deployment::BlueprintHostPhase2DesiredSlots; @@ -2742,6 +2743,7 @@ mod tests { &planning_input, &collection, "test", + PlannerRng::from_entropy(), ) .expect("failed to create builder"); @@ -3083,6 +3085,7 @@ mod tests { &planning_input, &collection, "dummy", + PlannerRng::from_entropy(), ) .expect("failed to create builder") .build(); @@ -3160,6 +3163,7 @@ mod tests { &EMPTY_PLANNING_INPUT, &collection, "test2", + PlannerRng::from_entropy(), ) .expect("failed to create builder") .build(); @@ -3169,6 +3173,7 @@ mod tests { &EMPTY_PLANNING_INPUT, &collection, "test3", + PlannerRng::from_entropy(), ) .expect("failed to create builder") .build(); @@ -3269,6 +3274,7 @@ mod tests { &EMPTY_PLANNING_INPUT, &collection, "test3", + PlannerRng::from_entropy(), ) .expect("failed to create builder") .build(); @@ -3314,6 +3320,7 @@ mod tests { &EMPTY_PLANNING_INPUT, &collection, "test2", + PlannerRng::from_entropy(), ) .expect("failed to create builder") .build(); @@ -3546,6 +3553,7 @@ mod tests { &example_system.input, &example_system.collection, &format!("{test_name}-2"), + PlannerRng::from_entropy(), ) .expect("failed to create builder") .build(); diff --git a/nexus/db-queries/src/db/datastore/vpc.rs b/nexus/db-queries/src/db/datastore/vpc.rs index 6e3a1bef151..44f24fa942f 100644 --- a/nexus/db-queries/src/db/datastore/vpc.rs +++ b/nexus/db-queries/src/db/datastore/vpc.rs @@ -2967,6 +2967,7 @@ mod tests { use nexus_db_fixed_data::vpc_subnet::NEXUS_VPC_SUBNET; use nexus_db_model::IncompleteNetworkInterface; use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; + use nexus_reconfigurator_planning::planner::PlannerRng; use nexus_reconfigurator_planning::system::SledBuilder; use nexus_reconfigurator_planning::system::SystemDescription; use nexus_types::deployment::Blueprint; @@ -3315,6 +3316,7 @@ mod tests { &planning_input, &collection, "test", + PlannerRng::from_entropy(), ) .expect("created blueprint builder"); for &sled_id in &sled_ids { @@ -3399,6 +3401,7 @@ mod tests { &planning_input, &collection, "test", + PlannerRng::from_entropy(), ) .expect("created blueprint builder"); for &sled_id in &sled_ids { diff --git a/nexus/reconfigurator/execution/src/dns.rs b/nexus/reconfigurator/execution/src/dns.rs index 74c5b82198d..9b9bcdf0546 100644 --- a/nexus/reconfigurator/execution/src/dns.rs +++ b/nexus/reconfigurator/execution/src/dns.rs @@ -323,6 +323,7 @@ mod test { use nexus_inventory::now_db_precision; use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; use nexus_reconfigurator_planning::example::ExampleSystemBuilder; + use nexus_reconfigurator_planning::planner::PlannerRng; use nexus_reconfigurator_preparation::PlanningInputFromDb; use nexus_sled_agent_shared::inventory::OmicronZoneConfig; use nexus_sled_agent_shared::inventory::OmicronZoneImageSource; @@ -1522,6 +1523,7 @@ mod test { &planning_input, &collection, "test suite", + PlannerRng::from_entropy(), ) .unwrap(); let sled_id = diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs index 3ec4aef2ef7..cac379307e0 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs @@ -64,6 +64,7 @@ use omicron_common::api::internal::shared::NetworkInterface; use omicron_common::api::internal::shared::NetworkInterfaceKind; use omicron_common::disk::M2Slot; use omicron_common::policy::INTERNAL_DNS_REDUNDANCY; +use omicron_uuid_kinds::BlueprintUuid; use omicron_uuid_kinds::GenericUuid; use omicron_uuid_kinds::MupdateOverrideUuid; use omicron_uuid_kinds::OmicronZoneUuid; @@ -411,6 +412,9 @@ pub struct BlueprintBuilder<'a> { /// The latest inventory collection collection: &'a Collection, + /// The ID that the completed blueprint will have + new_blueprint_id: BlueprintUuid, + // These fields are used to allocate resources for sleds. input: &'a PlanningInput, @@ -514,6 +518,7 @@ impl<'a> BlueprintBuilder<'a> { input: &'a PlanningInput, inventory: &'a Collection, creator: &str, + mut rng: PlannerRng, ) -> anyhow::Result> { let log = log.new(o!( "component" => "BlueprintBuilder", @@ -564,6 +569,7 @@ impl<'a> BlueprintBuilder<'a> { log, parent_blueprint, collection: inventory, + new_blueprint_id: rng.next_blueprint(), input, resource_allocator: OnceCell::new(), sled_editors, @@ -575,7 +581,7 @@ impl<'a> BlueprintBuilder<'a> { creator: creator.to_owned(), operations: Vec::new(), comments: Vec::new(), - rng: PlannerRng::from_entropy(), + rng, }) } @@ -583,6 +589,10 @@ impl<'a> BlueprintBuilder<'a> { &self.parent_blueprint } + pub fn new_blueprint_id(&self) -> BlueprintUuid { + self.new_blueprint_id + } + fn resource_allocator( &mut self, ) -> Result<&mut BlueprintResourceAllocator, Error> { @@ -646,7 +656,7 @@ impl<'a> BlueprintBuilder<'a> { /// Assemble a final [`Blueprint`] based on the contents of the builder pub fn build(mut self) -> Blueprint { - let blueprint_id = self.rng.next_blueprint(); + let blueprint_id = self.new_blueprint_id(); // Collect the Omicron zones config for all sleds, including sleds that // are no longer in service and need expungement work. @@ -791,12 +801,9 @@ impl<'a> BlueprintBuilder<'a> { .map_err(|err| Error::SledEditError { sled_id, err }) } - /// Within tests, set an RNG for deterministic results. - /// - /// This will ensure that tests that use this builder will produce the same - /// results each time they are run. - pub fn set_rng(&mut self, rng: PlannerRng) -> &mut Self { - self.rng = rng; + /// Set the planning report for this blueprint. + pub fn set_report(&mut self, report: PlanningReport) -> &mut Self { + self.report = Some(report); self } @@ -2283,7 +2290,6 @@ pub mod test { fn test_basic() { static TEST_NAME: &str = "blueprint_builder_test_basic"; let logctx = test_setup_log(TEST_NAME); - let mut rng = SimRngState::from_seed(TEST_NAME); let (mut example, blueprint1) = ExampleSystemBuilder::new_with_rng( &logctx.log, @@ -2298,6 +2304,7 @@ pub mod test { &example.input, &example.collection, "test_basic", + rng.next_planner_rng(), ) .expect("failed to create builder"); @@ -2349,6 +2356,7 @@ pub mod test { &input, &example.collection, "test_basic", + rng.next_planner_rng(), ) .expect("failed to create builder"); let new_sled_resources = &input @@ -2466,6 +2474,7 @@ pub mod test { fn test_decommissioned_sleds() { static TEST_NAME: &str = "blueprint_builder_test_decommissioned_sleds"; let logctx = test_setup_log(TEST_NAME); + let mut rng = SimRngState::from_seed(TEST_NAME); let (collection, input, mut blueprint1) = example(&logctx.log, TEST_NAME); verify_blueprint(&blueprint1); @@ -2516,6 +2525,7 @@ pub mod test { &input, &collection, "test_decommissioned_sleds", + rng.next_planner_rng(), ) .expect("created builder") .build(); @@ -2553,6 +2563,7 @@ pub mod test { &input, &collection, "test_decommissioned_sleds", + rng.next_planner_rng(), ) .expect("created builder") .build(); @@ -2569,6 +2580,7 @@ pub mod test { fn test_add_physical_disks() { static TEST_NAME: &str = "blueprint_builder_test_add_physical_disks"; let logctx = test_setup_log(TEST_NAME); + let mut rng = SimRngState::from_seed(TEST_NAME); // Start with an empty system (sleds with no zones). However, we leave // the disks around so that `sled_add_disks` can add them. @@ -2588,6 +2600,7 @@ pub mod test { &input, &collection, "test", + rng.next_planner_rng(), ) .expect("failed to create builder"); @@ -2673,6 +2686,7 @@ pub mod test { fn test_datasets_for_zpools_and_zones() { static TEST_NAME: &str = "test_datasets_for_zpools_and_zones"; let logctx = test_setup_log(TEST_NAME); + let mut rng = SimRngState::from_seed(TEST_NAME); let (collection, input, blueprint) = example(&logctx.log, TEST_NAME); // Creating the "example" blueprint should already invoke @@ -2687,6 +2701,7 @@ pub mod test { &input, &collection, "test", + rng.next_planner_rng(), ) .expect("failed to create builder"); @@ -2741,6 +2756,7 @@ pub mod test { &input, &collection, "test", + rng.next_planner_rng(), ) .expect("failed to create builder"); @@ -2780,6 +2796,7 @@ pub mod test { &input, &collection, "test", + rng.next_planner_rng(), ) .expect("failed to create builder"); @@ -2802,6 +2819,7 @@ pub mod test { static TEST_NAME: &str = "blueprint_builder_test_add_nexus_with_no_existing_nexus_zones"; let logctx = test_setup_log(TEST_NAME); + let mut rng = SimRngState::from_seed(TEST_NAME); // Start with an empty system (sleds with no zones). let (example, parent) = @@ -2820,6 +2838,7 @@ pub mod test { &input, &collection, "test", + rng.next_planner_rng(), ) .expect("failed to create builder"); @@ -2847,6 +2866,7 @@ pub mod test { fn test_add_nexus_error_cases() { static TEST_NAME: &str = "blueprint_builder_test_add_nexus_error_cases"; let logctx = test_setup_log(TEST_NAME); + let mut rng = SimRngState::from_seed(TEST_NAME); let (mut collection, mut input, mut parent) = example(&logctx.log, TEST_NAME); @@ -2926,6 +2946,7 @@ pub mod test { &input, &collection, "test", + rng.next_planner_rng(), ) .expect("failed to create builder"); builder @@ -2946,6 +2967,7 @@ pub mod test { &input, &collection, "test", + rng.next_planner_rng(), ) .expect("failed to create builder"); for _ in 0..3 { @@ -2985,6 +3007,7 @@ pub mod test { &input, &collection, "test", + rng.next_planner_rng(), ) .expect("failed to create builder"); let err = builder @@ -3020,6 +3043,7 @@ pub mod test { fn test_ensure_cockroachdb() { static TEST_NAME: &str = "blueprint_builder_test_ensure_cockroachdb"; let logctx = test_setup_log(TEST_NAME); + let mut rng = SimRngState::from_seed(TEST_NAME); // Start with an example system (no CRDB zones). let (example, parent) = @@ -3060,6 +3084,7 @@ pub mod test { &input, &collection, "test", + rng.next_planner_rng(), ) .expect("constructed builder"); for _ in 0..num_sled_zpools { @@ -3102,6 +3127,7 @@ pub mod test { &input, &collection, "test", + rng.next_planner_rng(), ) .expect("constructed builder"); for _ in 0..num_sled_zpools { @@ -3135,6 +3161,7 @@ pub mod test { static TEST_NAME: &str = "builder_zone_image_source_change_diff"; let logctx = test_setup_log(TEST_NAME); let log = logctx.log.clone(); + let mut rng = SimRngState::from_seed(TEST_NAME); // Use our example system. let (system, blueprint1) = @@ -3147,9 +3174,9 @@ pub mod test { &system.input, &system.collection, TEST_NAME, + rng.next_planner_rng(), ) .expect("built blueprint builder"); - blueprint_builder.set_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))); let sled_id = system .input diff --git a/nexus/reconfigurator/planning/src/example.rs b/nexus/reconfigurator/planning/src/example.rs index 237702f47ba..ce793980b2c 100644 --- a/nexus/reconfigurator/planning/src/example.rs +++ b/nexus/reconfigurator/planning/src/example.rs @@ -433,9 +433,9 @@ impl ExampleSystemBuilder { &base_input, &collection, "test suite", + rng.blueprint2_rng, ) .unwrap(); - builder.set_rng(rng.blueprint2_rng); // Add as many external IPs as is necessary for external DNS zones. We // pick addresses in the TEST-NET-2 (RFC 5737) range. diff --git a/nexus/reconfigurator/planning/src/lib.rs b/nexus/reconfigurator/planning/src/lib.rs index bdb02c88dbf..a11c5e4132c 100644 --- a/nexus/reconfigurator/planning/src/lib.rs +++ b/nexus/reconfigurator/planning/src/lib.rs @@ -11,5 +11,4 @@ pub mod blueprint_editor; pub mod example; pub mod mgs_updates; pub mod planner; -pub mod reports; pub mod system; diff --git a/nexus/reconfigurator/planning/src/planner.rs b/nexus/reconfigurator/planning/src/planner.rs index 027347308e8..bbcc415f33e 100644 --- a/nexus/reconfigurator/planning/src/planner.rs +++ b/nexus/reconfigurator/planning/src/planner.rs @@ -18,7 +18,6 @@ use crate::planner::image_source::NoopConvertInfo; use crate::planner::image_source::NoopConvertSledStatus; use crate::planner::image_source::NoopConvertZoneStatus; use crate::planner::omicron_zone_placement::PlacementError; -use crate::reports::InterimPlanningReport; use gateway_client::types::SpType; use nexus_sled_agent_shared::inventory::ConfigReconcilerInventoryResult; use nexus_sled_agent_shared::inventory::OmicronZoneImageSource; @@ -124,6 +123,7 @@ impl<'a> Planner<'a> { // NOTE: Right now, we just assume that this is the latest inventory // collection. See the comment on the corresponding field in `Planner`. inventory: &'a Collection, + rng: PlannerRng, ) -> anyhow::Result> { let blueprint = BlueprintBuilder::new_based_on( &log, @@ -131,24 +131,14 @@ impl<'a> Planner<'a> { input, inventory, creator, + rng, )?; Ok(Planner { log, input, blueprint, inventory }) } - /// Within tests, set a seeded RNG for deterministic results. - /// - /// This will ensure that tests that use this builder will produce the same - /// results each time they are run. - pub fn with_rng(mut self, rng: PlannerRng) -> Self { - // This is an owned builder (self rather than &mut self) because it is - // almost never going to be conditional. - self.blueprint.set_rng(rng); - self - } - pub fn plan(mut self) -> Result { let checked = self.check_input_validity()?; - self.do_plan(checked)?; + let _ = self.do_plan(checked)?; Ok(self.blueprint.build()) } @@ -158,7 +148,6 @@ impl<'a> Planner<'a> { let checked = self.check_input_validity()?; let report = self.do_plan(checked)?; let blueprint = self.blueprint.build(); - let report = report.finalize(blueprint.id); Ok((blueprint, report)) } @@ -173,7 +162,7 @@ impl<'a> Planner<'a> { fn do_plan( &mut self, _checked: InputChecked, - ) -> Result { + ) -> Result { // Run the planning steps, recording their step reports as we go. let expunge = self.do_plan_expunge()?; let decommission = self.do_plan_decommission()?; @@ -182,7 +171,8 @@ impl<'a> Planner<'a> { let add = self.do_plan_add(&mgs_updates)?; let zone_updates = self.do_plan_zone_updates(&add, &mgs_updates)?; let cockroachdb_settings = self.do_plan_cockroachdb_settings(); - Ok(InterimPlanningReport { + Ok(PlanningReport { + blueprint_id: self.blueprint.new_blueprint_id(), expunge, decommission, noop_image_source, @@ -1716,6 +1706,7 @@ pub(crate) mod test { &input, test_name, &collection, + PlannerRng::from_entropy(), ) .expect("created planner"); let child_blueprint = planner.plan().expect("planning succeeded"); @@ -1756,9 +1747,9 @@ pub(crate) mod test { &example.input, "no-op?", &example.collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -1793,9 +1784,9 @@ pub(crate) mod test { &input, "test: add NTP?", &example.collection, + PlannerRng::from_seed((TEST_NAME, "bp3")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp3"))) .plan() .expect("failed to plan"); @@ -1837,9 +1828,9 @@ pub(crate) mod test { &input, "test: add nothing more", &example.collection, + PlannerRng::from_seed((TEST_NAME, "bp4")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp4"))) .plan() .expect("failed to plan"); let summary = blueprint4.diff_since_blueprint(&blueprint3); @@ -1875,9 +1866,9 @@ pub(crate) mod test { &input, "test: add Crucible zones?", &collection, + PlannerRng::from_seed((TEST_NAME, "bp5")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp5"))) .plan() .expect("failed to plan"); @@ -1978,9 +1969,9 @@ pub(crate) mod test { &input, "test_blueprint2", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -2059,9 +2050,9 @@ pub(crate) mod test { &input, "test_blueprint2", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -2147,6 +2138,7 @@ pub(crate) mod test { &builder.build(), "test_blueprint2", &collection, + PlannerRng::from_entropy(), ) .expect("created planner") .plan() @@ -2187,9 +2179,9 @@ pub(crate) mod test { &input, "test_blueprint2", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -2266,9 +2258,9 @@ pub(crate) mod test { &input, "test_blueprint2", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -2307,9 +2299,9 @@ pub(crate) mod test { &input, "test_blueprint3", &collection, + PlannerRng::from_seed((TEST_NAME, "bp3")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp3"))) .plan() .expect("failed to plan"); @@ -2367,6 +2359,7 @@ pub(crate) mod test { &input, &collection, TEST_NAME, + PlannerRng::from_entropy(), ) .expect("failed to build blueprint builder"); let sled_id = builder.sled_ids_with_zones().next().expect("no sleds"); @@ -2385,6 +2378,7 @@ pub(crate) mod test { &input, &collection, TEST_NAME, + PlannerRng::from_entropy(), ) .expect("failed to build blueprint builder"); @@ -2447,9 +2441,9 @@ pub(crate) mod test { &input, "test_blueprint2", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -2478,9 +2472,9 @@ pub(crate) mod test { &input, "test_blueprint3", &collection, + PlannerRng::from_seed((TEST_NAME, "bp3")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp3"))) .plan() .expect("failed to re-plan"); @@ -2596,9 +2590,9 @@ pub(crate) mod test { &input, "test: some new disks", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -2682,9 +2676,9 @@ pub(crate) mod test { &input, "test: fix a dataset", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -2763,9 +2757,9 @@ pub(crate) mod test { &input, "test: expunge a disk", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -2823,9 +2817,9 @@ pub(crate) mod test { &input, "test: decommission a disk", &collection, + PlannerRng::from_seed((TEST_NAME, "bp3")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp3"))) .plan() .expect("failed to plan"); @@ -2879,9 +2873,9 @@ pub(crate) mod test { &input, "test: expunge and decommission all disks", &collection, + PlannerRng::from_seed((TEST_NAME, "bp4")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp4"))) .plan() .expect("failed to plan"); @@ -2969,9 +2963,9 @@ pub(crate) mod test { &input, "test: expunge a disk", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -3139,9 +3133,9 @@ pub(crate) mod test { &input, "test: expunge a disk with a zone on top", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -3316,9 +3310,9 @@ pub(crate) mod test { &input, "test_blueprint2", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -3563,9 +3557,9 @@ pub(crate) mod test { &input, "test_blueprint2", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); @@ -3607,9 +3601,9 @@ pub(crate) mod test { &input, "test_blueprint3", &collection, + PlannerRng::from_seed((TEST_NAME, "bp3")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp3"))) .plan() .expect("succeeded in planner"); @@ -3658,9 +3652,9 @@ pub(crate) mod test { &input, "test_blueprint4", &collection, + PlannerRng::from_seed((TEST_NAME, "bp4")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp4"))) .plan() .expect("succeeded in planner"); @@ -3718,9 +3712,9 @@ pub(crate) mod test { &builder.clone().build(), "initial settings", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to plan"); assert_eq!(bp2.cockroachdb_fingerprint, "bp2"); @@ -3745,9 +3739,9 @@ pub(crate) mod test { &builder.clone().build(), "initial settings", &collection, + PlannerRng::from_seed((TEST_NAME, "bp3")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp3"))) .plan() .expect("failed to plan"); assert_eq!(bp3.cockroachdb_fingerprint, "bp3"); @@ -3770,9 +3764,9 @@ pub(crate) mod test { &builder.clone().build(), "after ensure", &collection, + PlannerRng::from_seed((TEST_NAME, "bp4")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp4"))) .plan() .expect("failed to plan"); assert_eq!(bp4.cockroachdb_fingerprint, "bp4"); @@ -3799,12 +3793,12 @@ pub(crate) mod test { &builder.clone().build(), "unknown version", &collection, + PlannerRng::from_seed(( + TEST_NAME, + format!("bp5-{}", preserve_downgrade), + )), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed(( - TEST_NAME, - format!("bp5-{}", preserve_downgrade), - ))) .plan() .expect("failed to plan"); assert_eq!(bp5.cockroachdb_fingerprint, "bp5"); @@ -3858,9 +3852,9 @@ pub(crate) mod test { &input, "test_blueprint2", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to re-plan"); @@ -3928,9 +3922,9 @@ pub(crate) mod test { &input, "test_blueprint2", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("failed to create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("failed to re-plan"); @@ -3992,9 +3986,9 @@ pub(crate) mod test { &input, "test_blueprint2", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("plan"); @@ -4066,9 +4060,9 @@ pub(crate) mod test { &input, "test_blueprint3", &collection, + PlannerRng::from_seed((TEST_NAME, "bp3")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp3"))) .plan() .expect("plan"); @@ -4107,9 +4101,9 @@ pub(crate) mod test { &input, "test_blueprint4", &collection, + PlannerRng::from_seed((TEST_NAME, "bp4")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp4"))) .plan() .expect("plan"); @@ -4155,9 +4149,9 @@ pub(crate) mod test { &input, "test_blueprint5", &collection, + PlannerRng::from_seed((TEST_NAME, "bp5")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp5"))) .plan() .expect("plan"); @@ -4202,9 +4196,9 @@ pub(crate) mod test { &input, "test_blueprint6", &collection, + PlannerRng::from_seed((TEST_NAME, "bp6")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp6"))) .plan() .expect("plan"); @@ -4239,9 +4233,9 @@ pub(crate) mod test { &input, "test_blueprint7", &collection, + PlannerRng::from_seed((TEST_NAME, "bp7")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp7"))) .plan() .expect("plan"); @@ -4282,9 +4276,9 @@ pub(crate) mod test { &input, "test_blueprint8", &collection, + PlannerRng::from_seed((TEST_NAME, "bp8")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp8"))) .plan() .expect("plan"); @@ -4335,9 +4329,9 @@ pub(crate) mod test { &input, "test_blueprint2", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("plan"); @@ -4393,9 +4387,9 @@ pub(crate) mod test { &input, "test_blueprint3", &collection, + PlannerRng::from_seed((TEST_NAME, "bp3")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp3"))) .plan() .expect("plan"); @@ -4430,9 +4424,9 @@ pub(crate) mod test { &input, "test_blueprint4", &collection, + PlannerRng::from_seed((TEST_NAME, "bp4")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp4"))) .plan() .expect("plan"); @@ -4461,9 +4455,9 @@ pub(crate) mod test { &input, "test_blueprint5", &collection, + PlannerRng::from_seed((TEST_NAME, "bp5")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp5"))) .plan() .expect("plan"); @@ -4494,9 +4488,9 @@ pub(crate) mod test { &input, "test_blueprint6", &collection, + PlannerRng::from_seed((TEST_NAME, "bp6")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp6"))) .plan() .expect("plan"); @@ -4554,9 +4548,9 @@ pub(crate) mod test { &input, "test_blueprint2", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("plan"); @@ -4601,9 +4595,9 @@ pub(crate) mod test { &input, "test_blueprint3", &collection, + PlannerRng::from_seed((TEST_NAME, "bp3")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp3"))) .plan() .expect("plan"); @@ -4629,9 +4623,9 @@ pub(crate) mod test { &input, "test_blueprint4", &collection, + PlannerRng::from_seed((TEST_NAME, "bp4")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp4"))) .plan() .expect("plan"); @@ -4730,9 +4724,9 @@ pub(crate) mod test { &input, "expunge disk", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("planned"); @@ -4877,9 +4871,9 @@ pub(crate) mod test { &input, "removed Nexus zone from inventory", &collection, + PlannerRng::from_seed((TEST_NAME, "bp3")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp3"))) .plan() .expect("planned"); @@ -4957,9 +4951,9 @@ pub(crate) mod test { &input, "expunge disk", &collection, + PlannerRng::from_seed((TEST_NAME, "bp2")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp2"))) .plan() .expect("planned"); @@ -5030,9 +5024,9 @@ pub(crate) mod test { &input, "removed Nexus zone from inventory", &collection, + PlannerRng::from_seed((TEST_NAME, "bp3")), ) .expect("created planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp3"))) .plan() .expect("planned"); @@ -5252,9 +5246,9 @@ pub(crate) mod test { &input, "test_blueprint3", &example.collection, + PlannerRng::from_seed((TEST_NAME, "bp3")), ) .expect("can't create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp3"))) .plan() .expect("can't re-plan for new Nexus zone"); { @@ -5286,9 +5280,9 @@ pub(crate) mod test { &input, &blueprint_name, &example.collection, + PlannerRng::from_seed((TEST_NAME, &blueprint_name)), ) .expect("can't create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, &blueprint_name))) .plan() .unwrap_or_else(|_| panic!("can't re-plan after {i} iterations")); @@ -5366,9 +5360,9 @@ pub(crate) mod test { &input, &blueprint_name, &example.collection, + PlannerRng::from_seed((TEST_NAME, &blueprint_name)), ) .expect("can't create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, &blueprint_name))) .plan() .unwrap_or_else(|_| panic!("can't re-plan after {i} iterations")); @@ -5445,9 +5439,9 @@ pub(crate) mod test { &example.input, &blueprint_name, &example.collection, + PlannerRng::from_seed((TEST_NAME, &blueprint_name)), ) .expect("can't create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, &blueprint_name))) .plan() .unwrap_or_else(|_| panic!("can't plan to include Cockroach nodes")); @@ -5651,9 +5645,9 @@ pub(crate) mod test { &example.input, &format!("test_blueprint_cockroach_{i}"), &example.collection, + PlannerRng::from_seed((TEST_NAME, "bp_crdb")), ) .expect("can't create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, "bp_crdb"))) .plan() .expect("plan for trivial TUF repo"); @@ -5804,9 +5798,9 @@ pub(crate) mod test { &input, &blueprint_name, &example.collection, + PlannerRng::from_seed((TEST_NAME, &blueprint_name)), ) .expect("can't create planner") - .with_rng(PlannerRng::from_seed((TEST_NAME, &blueprint_name))) .plan_and_report() .unwrap_or_else(|_| panic!("can't re-plan after {i} iterations")); diff --git a/nexus/reconfigurator/planning/src/reports.rs b/nexus/reconfigurator/planning/src/reports.rs deleted file mode 100644 index e72b63f76af..00000000000 --- a/nexus/reconfigurator/planning/src/reports.rs +++ /dev/null @@ -1,60 +0,0 @@ -// 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/. - -//! Utilities for structured reports on planning, i.e., Blueprint generation. -//! -//! Most of the important structures (e.g., `PlanningReport`, the step reports) -//! are defined in [`nexus_types::deployment`] so that they may be shared with -//! the `blueprint_planner` background task and `omdb`. - -use nexus_types::deployment::{ - PlanningAddStepReport, PlanningCockroachdbSettingsStepReport, - PlanningDecommissionStepReport, PlanningExpungeStepReport, - PlanningMgsUpdatesStepReport, PlanningNoopImageSourceStepReport, - PlanningReport, PlanningZoneUpdatesStepReport, -}; -use omicron_uuid_kinds::BlueprintUuid; - -/// A blueprint planning report minus the blueprint ID that the -/// report is for. Returned by [`crate::planner::Planner::do_plan`] -/// when all planning steps are complete, but before the blueprint -/// has been built (and so we don't yet know its ID). -#[derive(Debug)] -pub(crate) struct InterimPlanningReport { - pub expunge: PlanningExpungeStepReport, - pub decommission: PlanningDecommissionStepReport, - pub noop_image_source: PlanningNoopImageSourceStepReport, - pub mgs_updates: PlanningMgsUpdatesStepReport, - pub add: PlanningAddStepReport, - pub zone_updates: PlanningZoneUpdatesStepReport, - pub cockroachdb_settings: PlanningCockroachdbSettingsStepReport, -} - -impl InterimPlanningReport { - /// Attach a blueprint ID to an interim planning report. - pub(crate) fn finalize( - self, - blueprint_id: BlueprintUuid, - ) -> PlanningReport { - let Self { - expunge, - decommission, - noop_image_source, - mgs_updates, - add, - zone_updates, - cockroachdb_settings, - } = self; - PlanningReport { - blueprint_id, - expunge, - decommission, - noop_image_source, - mgs_updates, - add, - zone_updates, - cockroachdb_settings, - } - } -} diff --git a/nexus/src/app/background/tasks/blueprint_planner.rs b/nexus/src/app/background/tasks/blueprint_planner.rs index 7aac2228145..edbadc0a696 100644 --- a/nexus/src/app/background/tasks/blueprint_planner.rs +++ b/nexus/src/app/background/tasks/blueprint_planner.rs @@ -11,6 +11,7 @@ use nexus_auth::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::DataStore; use nexus_reconfigurator_planning::planner::Planner; +use nexus_reconfigurator_planning::planner::PlannerRng; use nexus_reconfigurator_preparation::PlanningInputFromDb; use nexus_types::deployment::ReconfiguratorChickenSwitches; use nexus_types::deployment::{Blueprint, BlueprintTarget}; @@ -135,6 +136,7 @@ impl BlueprintPlanner { &input, "blueprint_planner", &collection, + PlannerRng::from_entropy(), ) { Ok(planner) => planner, Err(error) => { diff --git a/nexus/src/app/deployment.rs b/nexus/src/app/deployment.rs index d2c968054ae..1f47c2223af 100644 --- a/nexus/src/app/deployment.rs +++ b/nexus/src/app/deployment.rs @@ -7,6 +7,7 @@ use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_reconfigurator_planning::planner::Planner; +use nexus_reconfigurator_planning::planner::PlannerRng; use nexus_reconfigurator_preparation::PlanningInputFromDb; use nexus_types::deployment::Blueprint; use nexus_types::deployment::BlueprintMetadata; @@ -176,6 +177,7 @@ impl super::Nexus { &planning_context.planning_input, &planning_context.creator, &inventory, + PlannerRng::from_entropy(), ) .map_err(|error| { Error::internal_error(&format!( From f0aabe55b940f9eccf1468501d16d0dff0376362 Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Fri, 25 Jul 2025 14:13:28 -0600 Subject: [PATCH 05/10] Store planning reports in blueprints --- dev-tools/omdb/tests/successes.out | 6 ++ dev-tools/reconfigurator-cli/src/lib.rs | 4 +- .../output/cmds-add-sled-no-disks-stdout | 4 ++ .../tests/output/cmds-example-stdout | 9 +++ ...ds-expunge-newly-added-external-dns-stdout | 12 ++++ ...ds-expunge-newly-added-internal-dns-stdout | 3 + .../output/cmds-host-phase-2-source-stdout | 6 ++ .../tests/output/cmds-set-mgs-updates-stdout | 15 ++++ .../cmds-set-remove-mupdate-override-stdout | 6 ++ .../tests/output/cmds-set-zone-images-stdout | 9 +++ .../db-queries/src/db/datastore/deployment.rs | 5 ++ nexus/db-queries/src/db/datastore/rack.rs | 22 ++++-- nexus/reconfigurator/execution/src/dns.rs | 5 +- .../planning/src/blueprint_builder/builder.rs | 13 +++- nexus/reconfigurator/planning/src/planner.rs | 20 ++---- .../example_builder_zone_counts_blueprint.txt | 3 + .../planner_decommissions_sleds_bp2.txt | 7 ++ .../output/planner_nonprovisionable_bp2.txt | 7 ++ .../output/zone_image_source_change_1.txt | 2 +- .../background/tasks/blueprint_execution.rs | 3 +- .../app/background/tasks/blueprint_load.rs | 3 +- .../app/background/tasks/blueprint_planner.rs | 3 +- nexus/test-utils/src/lib.rs | 5 +- nexus/types/src/deployment.rs | 8 +++ nexus/types/src/deployment/blueprint_diff.rs | 1 + nexus/types/src/deployment/planning_report.rs | 71 +++++++++++++++---- sled-agent/src/rack_setup/service.rs | 12 ++-- 27 files changed, 215 insertions(+), 49 deletions(-) diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index c6322c090d6..e5dc8153e8f 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -1568,6 +1568,9 @@ parent: PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint .............: + + --------------------------------------------- stderr: note: using Nexus URL http://127.0.0.1:REDACTED_PORT/ @@ -1688,6 +1691,9 @@ parent: PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint .............: + + --------------------------------------------- stderr: note: using Nexus URL http://127.0.0.1:REDACTED_PORT/ diff --git a/dev-tools/reconfigurator-cli/src/lib.rs b/dev-tools/reconfigurator-cli/src/lib.rs index 69bca2fdea2..569bb60c32c 100644 --- a/dev-tools/reconfigurator-cli/src/lib.rs +++ b/dev-tools/reconfigurator-cli/src/lib.rs @@ -1571,8 +1571,8 @@ fn cmd_blueprint_plan( let blueprint = planner.plan().context("generating blueprint")?; let rv = format!( - "generated blueprint {} based on parent blueprint {}", - blueprint.id, parent_blueprint.id, + "generated blueprint {} based on parent blueprint {}\n{}", + blueprint.id, parent_blueprint.id, blueprint.report, ); system.add_blueprint(blueprint)?; 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 1c0922f8df1..78d2f470200 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 @@ -270,4 +270,8 @@ parent: dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1: +* No zpools in service for NTP zones on sleds: 00320471-945d-413c-85e7-03e091a70b3c + + diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout index ef82f4056ad..d772cc70f97 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout @@ -392,6 +392,9 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a: + + > inventory-generate @@ -500,6 +503,9 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a: + + > # Exercise `blueprint-diff` arguments. @@ -999,6 +1005,9 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a: + + > # Plan a blueprint run -- this will cause zones and disks on the expunged 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 6caf1afc88f..08448e4a76d 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 @@ -334,6 +334,9 @@ parent: 06c88262-f435-410e-ba98-101bed41ec27 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint 3f00b694-1b16-4aaa-8f78-e6b3a527b434: + + > blueprint-edit 3f00b694-1b16-4aaa-8f78-e6b3a527b434 expunge-zone 8429c772-07e8-40a6-acde-2ed47d16cf84 blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0 created from blueprint 3f00b694-1b16-4aaa-8f78-e6b3a527b434: expunged zone 8429c772-07e8-40a6-acde-2ed47d16cf84 from sled 711ac7f8-d19e-4572-bdb9-e9b50f6e362a @@ -1022,6 +1025,9 @@ parent: 3f00b694-1b16-4aaa-8f78-e6b3a527b434 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0: + + > # 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 @@ -1714,6 +1720,12 @@ parent: 366b0b68-d80e-4bc1-abd3-dc69837847e0 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint 9c998c1d-1a7b-440a-ae0c-40f781dea6e2: +* Discretionary zones placed: + * on sled 711ac7f8-d19e-4572-bdb9-e9b50f6e362a: external_dns zone +* Zone updates waiting on discretionary zones + + > # expunging the new zone should work, then diff again to see the new zone also have its DNS records removed. > blueprint-edit 9c998c1d-1a7b-440a-ae0c-40f781dea6e2 expunge-zone 8c0a1969-15b6-4165-ba6d-a27c24151037 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 d391a5a657d..95a49f01e1b 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 @@ -332,6 +332,9 @@ parent: 184f10b3-61cb-41ef-9b93-3489b2bac559 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21: + + > # Expunge an internal DNS zone > blueprint-edit dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 expunge-zone 99e2f30b-3174-40bf-a78a-90da8abba8ca diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout index 2490e7f32d7..15e011a7d10 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout @@ -712,6 +712,9 @@ parent: 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4: + + > # Restore A to "current contents" @@ -1419,4 +1422,7 @@ parent: af934083-59b5-4bf6-8966-6fb5292c29e1 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint df06bb57-ad42-4431-9206-abff322896c7: + + diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout index c524f6e980f..60bc48aaaeb 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout @@ -208,6 +208,9 @@ parent: 6ccc786b-17f1-4562-958f-5a7d9a5a15fd PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint ad97e762-7bf1-45a6-a98f-60afb7e491c0: + + > # Configure an MGS-managed update to one of the SPs. @@ -422,6 +425,9 @@ parent: ad97e762-7bf1-45a6-a98f-60afb7e491c0 sled 2 model2 serial2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 1.1.0 Sp { expected_active_version: ArtifactVersion("1.0.0"), expected_inactive_version: Version(ArtifactVersion("1.0.1")) } +Report on planning run for blueprint cca24b71-09b5-4042-9185-b33e9f2ebba0: + + > blueprint-diff ad97e762-7bf1-45a6-a98f-60afb7e491c0 cca24b71-09b5-4042-9185-b33e9f2ebba0 from: blueprint ad97e762-7bf1-45a6-a98f-60afb7e491c0 @@ -966,6 +972,9 @@ parent: cca24b71-09b5-4042-9185-b33e9f2ebba0 sled 2 model2 serial2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 newest Sp { expected_active_version: ArtifactVersion("newer"), expected_inactive_version: Version(ArtifactVersion("older")) } +Report on planning run for blueprint 5bf974f3-81f9-455b-b24e-3099f765664c: + + > blueprint-diff cca24b71-09b5-4042-9185-b33e9f2ebba0 5bf974f3-81f9-455b-b24e-3099f765664c from: blueprint cca24b71-09b5-4042-9185-b33e9f2ebba0 @@ -1514,6 +1523,9 @@ parent: 5bf974f3-81f9-455b-b24e-3099f765664c sled 2 model2 serial2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 newest Sp { expected_active_version: ArtifactVersion("newer"), expected_inactive_version: Version(ArtifactVersion("older")) } +Report on planning run for blueprint 1b837a27-3be1-4fcb-8499-a921c839e1d0: + + > blueprint-diff 5bf974f3-81f9-455b-b24e-3099f765664c 1b837a27-3be1-4fcb-8499-a921c839e1d0 from: blueprint 5bf974f3-81f9-455b-b24e-3099f765664c @@ -1892,6 +1904,9 @@ parent: 1b837a27-3be1-4fcb-8499-a921c839e1d0 sled 0 model0 serial0 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 three Sp { expected_active_version: ArtifactVersion("two"), expected_inactive_version: NoValidVersion } +Report on planning run for blueprint 3682a71b-c6ca-4b7e-8f84-16df80c85960: + + > blueprint-diff 1b837a27-3be1-4fcb-8499-a921c839e1d0 3682a71b-c6ca-4b7e-8f84-16df80c85960 from: blueprint 1b837a27-3be1-4fcb-8499-a921c839e1d0 diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout index 149410242ab..35fb4525436 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout @@ -277,6 +277,9 @@ parent: df06bb57-ad42-4431-9206-abff322896c7 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba: + + > # Now make another blueprint, starting by adding a new sled and removing sled 6. @@ -665,6 +668,9 @@ parent: afb09faf-a586-4483-9289-04d4f1d8ba23 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700: + + > blueprint-diff afb09faf-a586-4483-9289-04d4f1d8ba23 latest from: blueprint afb09faf-a586-4483-9289-04d4f1d8ba23 diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout index 0054dcc9852..d657d0c1a9f 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout @@ -110,6 +110,9 @@ parent: 1b013011-2062-4b48-b544-a32b23bce83a PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint 971eeb12-1830-4fa0-a699-98ea0164505c: + + > # Set a couple zones' image sources to specific artifacts. @@ -225,6 +228,9 @@ parent: 9766ca20-38d4-4380-b005-e7c43c797e7c PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176: + + > blueprint-diff 971eeb12-1830-4fa0-a699-98ea0164505c f714e6ea-e85a-4d7d-93c2-a018744fe176 from: blueprint 971eeb12-1830-4fa0-a699-98ea0164505c @@ -544,6 +550,9 @@ parent: bb128f06-a2e1-44c1-8874-4f789d0ff896 PENDING MGS-MANAGED UPDATES: 0 +Report on planning run for blueprint d9c572a1-a68c-4945-b1ec-5389bd588fe9: + + > blueprint-diff f714e6ea-e85a-4d7d-93c2-a018744fe176 d9c572a1-a68c-4945-b1ec-5389bd588fe9 from: blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176 diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index 75bc0c030b4..cd4e5293d5a 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -75,6 +75,7 @@ use nexus_types::deployment::ExpectedVersion; use nexus_types::deployment::OximeterReadMode; use nexus_types::deployment::PendingMgsUpdateDetails; use nexus_types::deployment::PendingMgsUpdates; +use nexus_types::deployment::PlanningReport; use nexus_types::inventory::BaseboardId; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::Error; @@ -1349,6 +1350,9 @@ impl DataStore { } } + // FIXME: Once reports are stored in the database, read them out here. + let report = PlanningReport::new(blueprint_id); + Ok(Blueprint { id: blueprint_id, pending_mgs_updates, @@ -1365,6 +1369,7 @@ impl DataStore { time_created, creator, comment, + report, }) } diff --git a/nexus/db-queries/src/db/datastore/rack.rs b/nexus/db-queries/src/db/datastore/rack.rs index 816b132aedd..ce0e4f72244 100644 --- a/nexus/db-queries/src/db/datastore/rack.rs +++ b/nexus/db-queries/src/db/datastore/rack.rs @@ -1020,7 +1020,7 @@ mod test { }; use nexus_types::deployment::{ BlueprintZoneDisposition, BlueprintZoneImageSource, - OmicronZoneExternalSnatIp, OximeterReadMode, + OmicronZoneExternalSnatIp, OximeterReadMode, PlanningReport, }; use nexus_types::external_api::shared::SiloIdentityMode; use nexus_types::external_api::views::SledState; @@ -1050,11 +1050,12 @@ mod test { // easily specify just the parts that they want. impl Default for RackInit { fn default() -> Self { + let blueprint_id = BlueprintUuid::new_v4(); RackInit { rack_id: Uuid::parse_str(nexus_test_utils::RACK_UUID).unwrap(), rack_subnet: nexus_test_utils::RACK_SUBNET.parse().unwrap(), blueprint: Blueprint { - id: BlueprintUuid::new_v4(), + id: blueprint_id, sleds: BTreeMap::new(), pending_mgs_updates: PendingMgsUpdates::new(), cockroachdb_setting_preserve_downgrade: @@ -1070,6 +1071,7 @@ mod test { time_created: Utc::now(), creator: "test suite".to_string(), comment: "test suite".to_string(), + report: PlanningReport::new(blueprint_id), }, physical_disks: vec![], zpools: vec![], @@ -1545,8 +1547,9 @@ mod test { .into_iter() .collect(), ); + let blueprint_id = BlueprintUuid::new_v4(); let blueprint = Blueprint { - id: BlueprintUuid::new_v4(), + id: blueprint_id, sleds: make_sled_config_only_zones(blueprint_zones), pending_mgs_updates: PendingMgsUpdates::new(), cockroachdb_setting_preserve_downgrade: @@ -1562,6 +1565,7 @@ mod test { time_created: now_db_precision(), creator: "test suite".to_string(), comment: "test blueprint".to_string(), + report: PlanningReport::new(blueprint_id), }; let rack = datastore @@ -1806,8 +1810,9 @@ mod test { HashMap::from([("api.sys".to_string(), external_records.clone())]), ); + let blueprint_id = BlueprintUuid::new_v4(); let blueprint = Blueprint { - id: BlueprintUuid::new_v4(), + id: blueprint_id, sleds: make_sled_config_only_zones(blueprint_zones), pending_mgs_updates: PendingMgsUpdates::new(), cockroachdb_setting_preserve_downgrade: @@ -1823,6 +1828,7 @@ mod test { time_created: now_db_precision(), creator: "test suite".to_string(), comment: "test blueprint".to_string(), + report: PlanningReport::new(blueprint_id), }; let rack = datastore @@ -2016,8 +2022,9 @@ mod test { .into_iter() .collect::>(), ); + let blueprint_id = BlueprintUuid::new_v4(); let blueprint = Blueprint { - id: BlueprintUuid::new_v4(), + id: blueprint_id, sleds: make_sled_config_only_zones(blueprint_zones), pending_mgs_updates: PendingMgsUpdates::new(), cockroachdb_setting_preserve_downgrade: @@ -2033,6 +2040,7 @@ mod test { time_created: now_db_precision(), creator: "test suite".to_string(), comment: "test blueprint".to_string(), + report: PlanningReport::new(blueprint_id), }; let result = datastore @@ -2156,8 +2164,9 @@ mod test { .collect::>(), ); + let blueprint_id = BlueprintUuid::new_v4(); let blueprint = Blueprint { - id: BlueprintUuid::new_v4(), + id: blueprint_id, sleds: make_sled_config_only_zones(blueprint_zones), pending_mgs_updates: PendingMgsUpdates::new(), cockroachdb_setting_preserve_downgrade: @@ -2173,6 +2182,7 @@ mod test { time_created: now_db_precision(), creator: "test suite".to_string(), comment: "test blueprint".to_string(), + report: PlanningReport::new(blueprint_id), }; let result = datastore diff --git a/nexus/reconfigurator/execution/src/dns.rs b/nexus/reconfigurator/execution/src/dns.rs index 9b9bcdf0546..46a54e4b762 100644 --- a/nexus/reconfigurator/execution/src/dns.rs +++ b/nexus/reconfigurator/execution/src/dns.rs @@ -350,6 +350,7 @@ mod test { use nexus_types::deployment::OximeterReadMode; use nexus_types::deployment::OximeterReadPolicy; use nexus_types::deployment::PendingMgsUpdates; + use nexus_types::deployment::PlanningReport; use nexus_types::deployment::SledFilter; use nexus_types::deployment::TufRepoPolicy; use nexus_types::deployment::blueprint_zone_type; @@ -707,8 +708,9 @@ mod test { let dns_empty = dns_config_empty(); let initial_dns_generation = dns_empty.generation; + let blueprint_id = BlueprintUuid::new_v4(); let mut blueprint = Blueprint { - id: BlueprintUuid::new_v4(), + id: blueprint_id, sleds: blueprint_sleds, pending_mgs_updates: PendingMgsUpdates::new(), cockroachdb_setting_preserve_downgrade: @@ -724,6 +726,7 @@ mod test { time_created: now_db_precision(), creator: "test-suite".to_string(), comment: "test blueprint".to_string(), + report: PlanningReport::new(blueprint_id), }; // To make things slightly more interesting, let's add a zone that's diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs index cac379307e0..9b30cee3539 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs @@ -45,6 +45,7 @@ use nexus_types::deployment::OmicronZoneExternalSnatIp; use nexus_types::deployment::OximeterReadMode; use nexus_types::deployment::PendingMgsUpdates; use nexus_types::deployment::PlanningInput; +use nexus_types::deployment::PlanningReport; use nexus_types::deployment::SledFilter; use nexus_types::deployment::SledResources; use nexus_types::deployment::TufRepoContentsError; @@ -434,13 +435,14 @@ pub struct BlueprintBuilder<'a> { sled_editors: BTreeMap, cockroachdb_setting_preserve_downgrade: CockroachDbPreserveDowngrade, target_release_minimum_generation: Generation, + report: Option, creator: String, operations: Vec, comments: Vec, pending_mgs_updates: PendingMgsUpdates, - // Random number generator for new UUIDs + /// Random number generator for new UUIDs rng: PlannerRng, } @@ -490,8 +492,10 @@ impl<'a> BlueprintBuilder<'a> { .collect::>(); let num_sleds = sleds.len(); + let id = rng.next_blueprint(); + let report = PlanningReport::new(id); Blueprint { - id: rng.next_blueprint(), + id, sleds, pending_mgs_updates: PendingMgsUpdates::new(), parent_blueprint_id: None, @@ -507,6 +511,7 @@ impl<'a> BlueprintBuilder<'a> { time_created: now_db_precision(), creator: creator.to_owned(), comment: format!("starting blueprint with {num_sleds} empty sleds"), + report, } } @@ -578,6 +583,7 @@ impl<'a> BlueprintBuilder<'a> { pending_mgs_updates: parent_blueprint.pending_mgs_updates.clone(), target_release_minimum_generation: parent_blueprint .target_release_minimum_generation, + report: None, creator: creator.to_owned(), operations: Vec::new(), comments: Vec::new(), @@ -771,6 +777,9 @@ impl<'a> BlueprintBuilder<'a> { .chain(self.operations.iter().map(|op| op.to_string())) .collect::>() .join(", "), + report: self + .report + .unwrap_or_else(|| PlanningReport::new(blueprint_id)), } } diff --git a/nexus/reconfigurator/planning/src/planner.rs b/nexus/reconfigurator/planning/src/planner.rs index bbcc415f33e..e21405ceef6 100644 --- a/nexus/reconfigurator/planning/src/planner.rs +++ b/nexus/reconfigurator/planning/src/planner.rs @@ -137,18 +137,10 @@ impl<'a> Planner<'a> { } pub fn plan(mut self) -> Result { - let checked = self.check_input_validity()?; - let _ = self.do_plan(checked)?; - Ok(self.blueprint.build()) - } - - pub fn plan_and_report( - mut self, - ) -> Result<(Blueprint, PlanningReport), Error> { let checked = self.check_input_validity()?; let report = self.do_plan(checked)?; - let blueprint = self.blueprint.build(); - Ok((blueprint, report)) + self.blueprint.set_report(report); + Ok(self.blueprint.build()) } fn check_input_validity(&self) -> Result { @@ -5792,7 +5784,7 @@ pub(crate) mod test { update_collection_from_blueprint(&mut example, &parent); let blueprint_name = format!("blueprint{i}"); - let (blueprint, report) = Planner::new_based_on( + let blueprint = Planner::new_based_on( log.clone(), &parent, &input, @@ -5801,11 +5793,11 @@ pub(crate) mod test { PlannerRng::from_seed((TEST_NAME, &blueprint_name)), ) .expect("can't create planner") - .plan_and_report() + .plan() .unwrap_or_else(|_| panic!("can't re-plan after {i} iterations")); - eprintln!("{report}\n"); - assert_eq!(report.blueprint_id, blueprint.id); + assert_eq!(blueprint.report.blueprint_id, blueprint.id); + eprintln!("{}\n", blueprint.report); // TODO: more report testing let summary = blueprint.diff_since_blueprint(&parent); diff --git a/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt b/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt index 262bdeabd5d..3497e8742b4 100644 --- a/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt +++ b/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt @@ -533,3 +533,6 @@ parent: e35b2fdd-354d-48d9-acb5-703b2c269a54 target release min gen: 1 PENDING MGS-MANAGED UPDATES: 0 + +Report on planning run for blueprint 4a0b8410-b14f-41e7-85e7-3c0fe7050ccc: + diff --git a/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt b/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt index aaefd04e153..ea245c1a115 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt @@ -324,3 +324,10 @@ parent: 516e80a3-b362-4fac-bd3c-4559717120dd target release min gen: 1 PENDING MGS-MANAGED UPDATES: 0 + +Report on planning run for blueprint 1ac2d88f-27dd-4506-8585-6b2be832528e: +* Discretionary zones placed: + * on sled d67ce8f0-a691-4010-b414-420d82e80527: crucible_pantry, nexus zones + * on sled fefcf4cf-f7e7-46b3-b629-058526ce440e: clickhouse, internal_dns zones +* Zone updates waiting on discretionary zones + diff --git a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt index 1295f3ff2ea..3424e652cca 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt @@ -512,3 +512,10 @@ parent: 4d4e6c38-cd95-4c4e-8f45-6af4d686964b target release min gen: 1 PENDING MGS-MANAGED UPDATES: 0 + +Report on planning run for blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6: +* Discretionary zones placed: + * on sled 75bc286f-2b4b-482c-9431-59272af529da: nexus, nexus, nexus zones + * on sled affab35f-600a-4109-8ea0-34a067a4e0bc: nexus, nexus, nexus zones +* Zone updates waiting on discretionary zones + diff --git a/nexus/reconfigurator/planning/tests/output/zone_image_source_change_1.txt b/nexus/reconfigurator/planning/tests/output/zone_image_source_change_1.txt index 57cf92543a6..440e7e28e51 100644 --- a/nexus/reconfigurator/planning/tests/output/zone_image_source_change_1.txt +++ b/nexus/reconfigurator/planning/tests/output/zone_image_source_change_1.txt @@ -1,5 +1,5 @@ from: blueprint 11d7ef8b-adcd-4a37-9b4e-69faa3c242b1 -to: blueprint 665dc34a-dbf2-4d13-9ceb-9542d434ab0e +to: blueprint 1481141d-a5cf-4103-8344-738967e0f110 MODIFIED SLEDS: diff --git a/nexus/src/app/background/tasks/blueprint_execution.rs b/nexus/src/app/background/tasks/blueprint_execution.rs index 88fea70e7a1..c8c16456a96 100644 --- a/nexus/src/app/background/tasks/blueprint_execution.rs +++ b/nexus/src/app/background/tasks/blueprint_execution.rs @@ -196,7 +196,7 @@ mod test { BlueprintTarget, BlueprintZoneConfig, BlueprintZoneDisposition, BlueprintZoneImageSource, BlueprintZoneType, CockroachDbPreserveDowngrade, OximeterReadMode, PendingMgsUpdates, - blueprint_zone_type, + PlanningReport, blueprint_zone_type, }; use nexus_types::external_api::views::SledState; use omicron_common::api::external; @@ -276,6 +276,7 @@ mod test { time_created: chrono::Utc::now(), creator: "test".to_string(), comment: "test blueprint".to_string(), + report: PlanningReport::new(id), }; datastore diff --git a/nexus/src/app/background/tasks/blueprint_load.rs b/nexus/src/app/background/tasks/blueprint_load.rs index 7f6f0aa5ba4..d2d9c7c380e 100644 --- a/nexus/src/app/background/tasks/blueprint_load.rs +++ b/nexus/src/app/background/tasks/blueprint_load.rs @@ -195,7 +195,7 @@ mod test { use nexus_test_utils_macros::nexus_test; use nexus_types::deployment::{ Blueprint, BlueprintTarget, CockroachDbPreserveDowngrade, - OximeterReadMode, PendingMgsUpdates, + OximeterReadMode, PendingMgsUpdates, PlanningReport, }; use omicron_common::api::external::Generation; use omicron_uuid_kinds::BlueprintUuid; @@ -232,6 +232,7 @@ mod test { time_created: now_db_precision(), creator: "test".to_string(), comment: "test blueprint".to_string(), + report: PlanningReport::new(id), }, ) } diff --git a/nexus/src/app/background/tasks/blueprint_planner.rs b/nexus/src/app/background/tasks/blueprint_planner.rs index edbadc0a696..453a3f4bad4 100644 --- a/nexus/src/app/background/tasks/blueprint_planner.rs +++ b/nexus/src/app/background/tasks/blueprint_planner.rs @@ -152,7 +152,7 @@ impl BlueprintPlanner { )); } }; - let (blueprint, report) = match planner.plan_and_report() { + let blueprint = match planner.plan() { Ok(blueprint) => blueprint, Err(error) => { error!(&opctx.log, "can't plan: {error}"); @@ -242,6 +242,7 @@ impl BlueprintPlanner { } // We have a new target! + let report = blueprint.report.clone(); self.tx_blueprint.send_replace(Some(Arc::new((target, blueprint)))); BlueprintPlannerStatus::Targeted { parent_blueprint_id, diff --git a/nexus/test-utils/src/lib.rs b/nexus/test-utils/src/lib.rs index d92c056cb01..c9b196d4d68 100644 --- a/nexus/test-utils/src/lib.rs +++ b/nexus/test-utils/src/lib.rs @@ -55,6 +55,7 @@ use nexus_types::deployment::OmicronZoneExternalFloatingAddr; use nexus_types::deployment::OmicronZoneExternalFloatingIp; use nexus_types::deployment::OmicronZoneExternalSnatIp; use nexus_types::deployment::OximeterReadMode; +use nexus_types::deployment::PlanningReport; use nexus_types::deployment::blueprint_zone_type; use nexus_types::external_api::views::SledState; use nexus_types::internal_api::params::DnsConfigParams; @@ -913,8 +914,9 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { .blueprint_sleds .take() .expect("should have already made blueprint sled configs"); + let id = BlueprintUuid::new_v4(); let blueprint = Blueprint { - id: BlueprintUuid::new_v4(), + id, sleds, pending_mgs_updates: PendingMgsUpdates::new(), parent_blueprint_id: None, @@ -932,6 +934,7 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { time_created: Utc::now(), creator: "nexus-test-utils".to_string(), comment: "initial test blueprint".to_string(), + report: PlanningReport::new(id), }; self.initial_blueprint_id = Some(blueprint.id); diff --git a/nexus/types/src/deployment.rs b/nexus/types/src/deployment.rs index 8aa4d95ce6c..c2a6dbc61d5 100644 --- a/nexus/types/src/deployment.rs +++ b/nexus/types/src/deployment.rs @@ -239,12 +239,17 @@ pub struct Blueprint { /// when this blueprint was generated (for debugging) #[daft(ignore)] pub time_created: chrono::DateTime, + /// identity of the component that generated the blueprint (for debugging) /// This would generally be the Uuid of a Nexus instance. pub creator: String, + /// human-readable string describing why this blueprint was created /// (for debugging) pub comment: String, + + /// Report on the planning session that resulted in this blueprint + pub report: PlanningReport, } impl Blueprint { @@ -638,6 +643,7 @@ impl fmt::Display for BlueprintDisplay<'_> { time_created: _, creator: _, comment: _, + report, } = self.blueprint; writeln!(f, "blueprint {}", id)?; @@ -750,6 +756,8 @@ impl fmt::Display for BlueprintDisplay<'_> { )?; } + writeln!(f, "\n{report}")?; + Ok(()) } } diff --git a/nexus/types/src/deployment/blueprint_diff.rs b/nexus/types/src/deployment/blueprint_diff.rs index 22dd9932dc4..957395053a9 100644 --- a/nexus/types/src/deployment/blueprint_diff.rs +++ b/nexus/types/src/deployment/blueprint_diff.rs @@ -76,6 +76,7 @@ impl<'a> BlueprintDiffSummary<'a> { oximeter_read_mode, creator: _, comment: _, + report: _, } = &self.diff; // Did we modify, add, or remove any sleds? diff --git a/nexus/types/src/deployment/planning_report.rs b/nexus/types/src/deployment/planning_report.rs index c38af74f4d3..69c199646fb 100644 --- a/nexus/types/src/deployment/planning_report.rs +++ b/nexus/types/src/deployment/planning_report.rs @@ -10,6 +10,7 @@ use super::BlueprintZoneImageSource; use super::CockroachDbPreserveDowngrade; use super::PendingMgsUpdates; +use daft::Diffable; use omicron_common::policy::COCKROACHDB_REDUNDANCY; use omicron_uuid_kinds::BlueprintUuid; use omicron_uuid_kinds::MupdateOverrideUuid; @@ -17,6 +18,7 @@ use omicron_uuid_kinds::OmicronZoneUuid; use omicron_uuid_kinds::PhysicalDiskUuid; use omicron_uuid_kinds::SledUuid; use omicron_uuid_kinds::ZpoolUuid; +use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -40,7 +42,9 @@ use std::fmt; /// Only successful planning runs are currently covered by this report. /// Failures to plan (i.e., to generate a valid blueprint) are represented /// by `nexus-reconfigurator-planning::blueprint_builder::Error`. -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] #[must_use = "an unread report is not actionable"] pub struct PlanningReport { /// The blueprint produced by the planning run this report describes. @@ -56,6 +60,23 @@ pub struct PlanningReport { pub cockroachdb_settings: PlanningCockroachdbSettingsStepReport, } +impl PlanningReport { + pub fn new(blueprint_id: BlueprintUuid) -> Self { + Self { + blueprint_id, + expunge: PlanningExpungeStepReport::new(), + decommission: PlanningDecommissionStepReport::new(), + noop_image_source: PlanningNoopImageSourceStepReport::new(), + mgs_updates: PlanningMgsUpdatesStepReport::new( + PendingMgsUpdates::new(), + ), + add: PlanningAddStepReport::new(), + zone_updates: PlanningZoneUpdatesStepReport::new(), + cockroachdb_settings: PlanningCockroachdbSettingsStepReport::new(), + } + } +} + impl fmt::Display for PlanningReport { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let Self { @@ -80,7 +101,9 @@ impl fmt::Display for PlanningReport { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] pub struct PlanningExpungeStepReport { /// Expunged disks not present in the parent blueprint. pub orphan_disks: BTreeMap, @@ -109,7 +132,9 @@ impl fmt::Display for PlanningExpungeStepReport { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] pub struct PlanningDecommissionStepReport { /// Decommissioned sleds that unexpectedly appeared as commissioned. pub zombie_sleds: Vec, @@ -141,7 +166,9 @@ impl fmt::Display for PlanningDecommissionStepReport { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] pub struct PlanningNoopImageSourceStepReport { pub no_target_release: bool, pub skipped_sleds: @@ -232,7 +259,9 @@ impl fmt::Display for PlanningNoopImageSourceStepReport { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] pub enum PlanningNoopImageSourceSkipSledReason { AllZonesAlreadyArtifact(usize), SledNotInInventory, @@ -266,7 +295,9 @@ impl fmt::Display for PlanningNoopImageSourceSkipSledReason { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] pub enum PlanningNoopImageSourceSkipZoneReason { ZoneNotInManifest { zone_kind: String, @@ -304,7 +335,9 @@ impl fmt::Display for PlanningNoopImageSourceSkipZoneReason { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] pub struct PlanningMgsUpdatesStepReport { pub pending_mgs_updates: PendingMgsUpdates, } @@ -339,7 +372,9 @@ impl fmt::Display for PlanningMgsUpdatesStepReport { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] pub struct PlanningAddStepReport { pub sleds_with_no_zpools_for_ntp_zone: BTreeSet, pub sleds_waiting_for_ntp_zone: BTreeSet, @@ -465,7 +500,9 @@ impl fmt::Display for PlanningAddStepReport { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] pub struct PlanningZoneUpdatesStepReport { /// What are we waiting on to start zone updates? pub waiting_on: Option, @@ -581,7 +618,9 @@ impl fmt::Display for PlanningZoneUpdatesStepReport { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] pub enum ZoneUpdatesWaitingOn { /// Waiting on discretionary zone placement. DiscretionaryZones, @@ -601,7 +640,9 @@ impl ZoneUpdatesWaitingOn { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] pub enum ZoneUnsafeToShutdown { Cockroachdb(CockroachdbUnsafeToShutdown), } @@ -614,7 +655,9 @@ impl fmt::Display for ZoneUnsafeToShutdown { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] pub enum CockroachdbUnsafeToShutdown { MissingLiveNodesStat, MissingUnderreplicatedStat, @@ -651,7 +694,9 @@ impl fmt::Display for CockroachdbUnsafeToShutdown { } } -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] pub struct PlanningCockroachdbSettingsStepReport { pub preserve_downgrade: CockroachDbPreserveDowngrade, } diff --git a/sled-agent/src/rack_setup/service.rs b/sled-agent/src/rack_setup/service.rs index f985a1b53f1..2d1bfb4d17e 100644 --- a/sled-agent/src/rack_setup/service.rs +++ b/sled-agent/src/rack_setup/service.rs @@ -94,11 +94,9 @@ use nexus_sled_agent_shared::inventory::{ }; use nexus_types::deployment::{ Blueprint, BlueprintDatasetConfig, BlueprintDatasetDisposition, - BlueprintHostPhase2DesiredSlots, BlueprintZoneType, - CockroachDbPreserveDowngrade, blueprint_zone_type, -}; -use nexus_types::deployment::{ - BlueprintSledConfig, OximeterReadMode, PendingMgsUpdates, + BlueprintHostPhase2DesiredSlots, BlueprintSledConfig, BlueprintZoneType, + CockroachDbPreserveDowngrade, OximeterReadMode, PendingMgsUpdates, + PlanningReport, blueprint_zone_type, }; use nexus_types::external_api::views::SledState; use omicron_common::address::{COCKROACH_ADMIN_PORT, get_sled_address}; @@ -1589,8 +1587,9 @@ pub(crate) fn build_initial_blueprint_from_sled_configs( ); } + let id = BlueprintUuid::new_v4(); Ok(Blueprint { - id: BlueprintUuid::new_v4(), + id, sleds: blueprint_sleds, pending_mgs_updates: PendingMgsUpdates::new(), parent_blueprint_id: None, @@ -1614,6 +1613,7 @@ pub(crate) fn build_initial_blueprint_from_sled_configs( time_created: Utc::now(), creator: "RSS".to_string(), comment: "initial blueprint from rack setup".to_string(), + report: PlanningReport::new(id), }) } From 3fa1a21e5f13d85557cb91e0175abaa3d08bb480 Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Mon, 28 Jul 2025 12:08:58 -0600 Subject: [PATCH 06/10] OpenAPI compatible planning reports --- dev-tools/omdb/tests/successes.out | 4 +- .../output/cmds-add-sled-no-disks-stdout | 5 +- .../tests/output/cmds-example-stdout | 16 +- ...ds-expunge-newly-added-external-dns-stdout | 13 +- ...ds-expunge-newly-added-internal-dns-stdout | 7 +- .../output/cmds-host-phase-2-source-stdout | 4 +- .../output/cmds-noop-image-source-stdout | 15 + .../tests/output/cmds-set-mgs-updates-stdout | 10 +- .../cmds-set-remove-mupdate-override-stdout | 4 +- .../tests/output/cmds-set-zone-images-stdout | 6 +- .../tests/output/cmds-target-release-stdout | 55 ++ nexus/reconfigurator/planning/src/planner.rs | 66 +- .../example_builder_zone_counts_blueprint.txt | 2 +- .../planner_decommissions_sleds_bp2.txt | 6 +- .../output/planner_nonprovisionable_bp2.txt | 6 +- nexus/types/src/deployment/planning_report.rs | 462 +++++++++---- openapi/nexus-internal.json | 638 ++++++++++++++++++ 17 files changed, 1133 insertions(+), 186 deletions(-) diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index e5dc8153e8f..fe0b7fdd033 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -1568,7 +1568,7 @@ parent: PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint .............: +Nothing to report on planning for blueprint .............. --------------------------------------------- @@ -1691,7 +1691,7 @@ parent: PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint .............: +Nothing to report on planning for blueprint .............. --------------------------------------------- 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 78d2f470200..bb1b994fa98 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 @@ -39,6 +39,9 @@ generated inventory collection eb0796d5-ab8a-4f7b-a884-b4aeacb8ab51 from configu INFO skipping noop image source check for all sleds, reason: no target release is currently set WARN cannot issue more SP updates (no current artifacts) generated blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 based on parent blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 +Planning report for blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1: +* No zpools in service for NTP zones on sleds: 00320471-945d-413c-85e7-03e091a70b3c + > blueprint-show 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 @@ -270,7 +273,7 @@ parent: dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1: +Planning report for blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1: * No zpools in service for NTP zones on sleds: 00320471-945d-413c-85e7-03e091a70b3c diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout index d772cc70f97..0223fb9f5b5 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout @@ -392,7 +392,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a: +Nothing to report on planning for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. @@ -503,7 +503,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a: +Nothing to report on planning for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. @@ -534,6 +534,14 @@ T ENA ID PARENT INFO skipping noop image source check for all sleds, reason: no target release is currently set WARN cannot issue more SP updates (no current artifacts) generated blueprint 86db3308-f817-4626-8838-4085949a6a41 based on parent blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a +Planning report for blueprint 86db3308-f817-4626-8838-4085949a6a41: +* Discretionary zone placement waiting for NTP zones on sleds: 89d02b1b-478c-401a-8e28-7a26f74fa41b +* Missing NTP zone on sled 89d02b1b-478c-401a-8e28-7a26f74fa41b +* Only placed 0/1 desired clickhouse zones +* Only placed 0/3 desired crucible_pantry zones +* Only placed 0/3 desired internal_dns zones +* Only placed 0/3 desired nexus zones + > blueprint-list T ENA ID PARENT TIME_CREATED @@ -1005,7 +1013,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a: +Nothing to report on planning for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. @@ -1016,6 +1024,8 @@ Report on planning run for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a: INFO skipping noop image source check for all sleds, reason: no target release is currently set WARN cannot issue more SP updates (no current artifacts) generated blueprint 86db3308-f817-4626-8838-4085949a6a41 based on parent blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a +Nothing to report on planning for blueprint 86db3308-f817-4626-8838-4085949a6a41. + > blueprint-diff ade5749d-bdf3-4fab-a8ae-00bea01b3a5a latest from: blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a 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 08448e4a76d..f8cb6823d86 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 @@ -334,7 +334,7 @@ parent: 06c88262-f435-410e-ba98-101bed41ec27 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint 3f00b694-1b16-4aaa-8f78-e6b3a527b434: +Nothing to report on planning for blueprint 3f00b694-1b16-4aaa-8f78-e6b3a527b434. @@ -1025,7 +1025,7 @@ parent: 3f00b694-1b16-4aaa-8f78-e6b3a527b434 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0: +Nothing to report on planning for blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0. @@ -1034,6 +1034,11 @@ Report on planning run for blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0: INFO skipping noop image source check for all sleds, reason: no target release is currently set WARN cannot issue more SP updates (no current artifacts) generated blueprint 9c998c1d-1a7b-440a-ae0c-40f781dea6e2 based on parent blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0 +Planning report for blueprint 9c998c1d-1a7b-440a-ae0c-40f781dea6e2: +* Discretionary zones placed: + * 1 zone on sled 711ac7f8-d19e-4572-bdb9-e9b50f6e362a: external_dns +* Zone updates waiting on discretionary zones + > blueprint-diff 366b0b68-d80e-4bc1-abd3-dc69837847e0 9c998c1d-1a7b-440a-ae0c-40f781dea6e2 from: blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0 @@ -1720,9 +1725,9 @@ parent: 366b0b68-d80e-4bc1-abd3-dc69837847e0 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint 9c998c1d-1a7b-440a-ae0c-40f781dea6e2: +Planning report for blueprint 9c998c1d-1a7b-440a-ae0c-40f781dea6e2: * Discretionary zones placed: - * on sled 711ac7f8-d19e-4572-bdb9-e9b50f6e362a: external_dns zone + * 1 zone on sled 711ac7f8-d19e-4572-bdb9-e9b50f6e362a: external_dns * Zone updates waiting on discretionary zones 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 95a49f01e1b..0999e5e5116 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 @@ -332,7 +332,7 @@ parent: 184f10b3-61cb-41ef-9b93-3489b2bac559 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21: +Nothing to report on planning for blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21. @@ -1048,6 +1048,11 @@ external DNS: INFO skipping noop image source check for all sleds, reason: no target release is currently set WARN cannot issue more SP updates (no current artifacts) generated blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 based on parent blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 +Planning report for blueprint af934083-59b5-4bf6-8966-6fb5292c29e1: +* Discretionary zones placed: + * 1 zone on sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c: internal_dns +* Zone updates waiting on discretionary zones + > blueprint-diff 58d5e830-0884-47d8-a7cd-b2b3751adeb4 af934083-59b5-4bf6-8966-6fb5292c29e1 from: blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout index 15e011a7d10..e5215838f7d 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout @@ -712,7 +712,7 @@ parent: 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4: +Nothing to report on planning for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4. @@ -1422,7 +1422,7 @@ parent: af934083-59b5-4bf6-8966-6fb5292c29e1 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint df06bb57-ad42-4431-9206-abff322896c7: +Nothing to report on planning for blueprint df06bb57-ad42-4431-9206-abff322896c7. 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 index dbcfe2d5720..6e4602f0253 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout @@ -168,6 +168,14 @@ INFO skipped noop image source check on sled, sled_id: e96e226f-4ed9-4c01-91b9-6 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 generated blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 based on parent blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 +Planning report for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4: +* Noop converting 6/6 install-dataset zones to artifact store on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 +* Noop converting 5/6 install-dataset zones to artifact store on sled aff6c093-197d-42c5-ad80-9f10ba051a34 +* 1 pending MGS update: + * model0:serial0: Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: NoValidVersion } +* Discretionary zone placement waiting for NTP zones on sleds: e96e226f-4ed9-4c01-91b9-69a9cd076c9e +* Zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) + > # This diff should show expected changes to the blueprint. @@ -526,6 +534,13 @@ INFO performed noop image source checks on sled, sled_id: e96e226f-4ed9-4c01-91b INFO SP update not yet completed (will keep it), 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 generated blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 based on parent blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 +Planning report for blueprint af934083-59b5-4bf6-8966-6fb5292c29e1: +* Skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts +* Noop converting 2/2 install-dataset zones to artifact store on sled e96e226f-4ed9-4c01-91b9-69a9cd076c9e +* 1 pending MGS update: + * model0:serial0: Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: NoValidVersion } +* Zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) + > # This diff should show changes to the sled that's back in inventory. diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout index 60bc48aaaeb..70ed671898f 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout @@ -208,7 +208,7 @@ parent: 6ccc786b-17f1-4562-958f-5a7d9a5a15fd PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint ad97e762-7bf1-45a6-a98f-60afb7e491c0: +Nothing to report on planning for blueprint ad97e762-7bf1-45a6-a98f-60afb7e491c0. @@ -425,7 +425,7 @@ parent: ad97e762-7bf1-45a6-a98f-60afb7e491c0 sled 2 model2 serial2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 1.1.0 Sp { expected_active_version: ArtifactVersion("1.0.0"), expected_inactive_version: Version(ArtifactVersion("1.0.1")) } -Report on planning run for blueprint cca24b71-09b5-4042-9185-b33e9f2ebba0: +Nothing to report on planning for blueprint cca24b71-09b5-4042-9185-b33e9f2ebba0. @@ -972,7 +972,7 @@ parent: cca24b71-09b5-4042-9185-b33e9f2ebba0 sled 2 model2 serial2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 newest Sp { expected_active_version: ArtifactVersion("newer"), expected_inactive_version: Version(ArtifactVersion("older")) } -Report on planning run for blueprint 5bf974f3-81f9-455b-b24e-3099f765664c: +Nothing to report on planning for blueprint 5bf974f3-81f9-455b-b24e-3099f765664c. @@ -1523,7 +1523,7 @@ parent: 5bf974f3-81f9-455b-b24e-3099f765664c sled 2 model2 serial2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 newest Sp { expected_active_version: ArtifactVersion("newer"), expected_inactive_version: Version(ArtifactVersion("older")) } -Report on planning run for blueprint 1b837a27-3be1-4fcb-8499-a921c839e1d0: +Nothing to report on planning for blueprint 1b837a27-3be1-4fcb-8499-a921c839e1d0. @@ -1904,7 +1904,7 @@ parent: 1b837a27-3be1-4fcb-8499-a921c839e1d0 sled 0 model0 serial0 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 three Sp { expected_active_version: ArtifactVersion("two"), expected_inactive_version: NoValidVersion } -Report on planning run for blueprint 3682a71b-c6ca-4b7e-8f84-16df80c85960: +Nothing to report on planning for blueprint 3682a71b-c6ca-4b7e-8f84-16df80c85960. diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout index 35fb4525436..7f49b91c6e1 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout @@ -277,7 +277,7 @@ parent: df06bb57-ad42-4431-9206-abff322896c7 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba: +Nothing to report on planning for blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba. @@ -668,7 +668,7 @@ parent: afb09faf-a586-4483-9289-04d4f1d8ba23 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700: +Nothing to report on planning for blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700. diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout index d657d0c1a9f..bc716c9f26b 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout @@ -110,7 +110,7 @@ parent: 1b013011-2062-4b48-b544-a32b23bce83a PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint 971eeb12-1830-4fa0-a699-98ea0164505c: +Nothing to report on planning for blueprint 971eeb12-1830-4fa0-a699-98ea0164505c. @@ -228,7 +228,7 @@ parent: 9766ca20-38d4-4380-b005-e7c43c797e7c PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176: +Nothing to report on planning for blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176. @@ -550,7 +550,7 @@ parent: bb128f06-a2e1-44c1-8874-4f789d0ff896 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint d9c572a1-a68c-4945-b1ec-5389bd588fe9: +Nothing to report on planning for blueprint d9c572a1-a68c-4945-b1ec-5389bd588fe9. 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 0f8163333e8..fe64cebe661 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout @@ -198,6 +198,11 @@ INFO performed noop image source checks on sled, sled_id: d81c6a84-79b8-4958-ae4 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 generated blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 based on parent blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 +Planning report for blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1: +* 1 pending MGS update: + * model0:serial0: Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: NoValidVersion } +* Zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) + > blueprint-diff dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 from: blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 @@ -372,6 +377,11 @@ INFO performed noop image source checks on sled, sled_id: d81c6a84-79b8-4958-ae4 INFO SP update not yet completed (will keep it), 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 generated blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 based on parent blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 +Planning report for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4: +* 1 pending MGS update: + * model0:serial0: Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: NoValidVersion } +* Zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) + > blueprint-diff 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 58d5e830-0884-47d8-a7cd-b2b3751adeb4 from: blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 @@ -549,6 +559,11 @@ INFO skipping board for SP update, serial_number: serial0, part_number: model0 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: 1, sp_type: Sled, serial_number: serial1, part_number: model1 INFO reached maximum number of pending SP updates, max: 1 generated blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 based on parent blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 +Planning report for blueprint af934083-59b5-4bf6-8966-6fb5292c29e1: +* 1 pending MGS update: + * model1:serial1: Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: NoValidVersion } +* Zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) + > blueprint-diff 58d5e830-0884-47d8-a7cd-b2b3751adeb4 af934083-59b5-4bf6-8966-6fb5292c29e1 from: blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 @@ -733,6 +748,11 @@ INFO SP update impossible (will remove it and re-evaluate board), artifact_versi INFO configuring SP update, artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: Version(ArtifactVersion("0.5.0")), expected_active_version: 0.0.1, component: sp, sp_slot: 1, sp_type: Sled, serial_number: serial1, part_number: model1 INFO reached maximum number of pending SP updates, max: 1 generated blueprint df06bb57-ad42-4431-9206-abff322896c7 based on parent blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 +Planning report for blueprint df06bb57-ad42-4431-9206-abff322896c7: +* 1 pending MGS update: + * model1:serial1: Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: Version(ArtifactVersion("0.5.0")) } +* Zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) + > blueprint-diff af934083-59b5-4bf6-8966-6fb5292c29e1 df06bb57-ad42-4431-9206-abff322896c7 from: blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 @@ -918,6 +938,11 @@ INFO skipping board for SP update, serial_number: serial0, part_number: model0 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: 2, sp_type: Sled, serial_number: serial2, part_number: model2 INFO ran out of boards for SP update generated blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba based on parent blueprint df06bb57-ad42-4431-9206-abff322896c7 +Planning report for blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba: +* 1 pending MGS update: + * model2:serial2: Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: NoValidVersion } +* Zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) + > blueprint-diff df06bb57-ad42-4431-9206-abff322896c7 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba from: blueprint df06bb57-ad42-4431-9206-abff322896c7 @@ -1103,6 +1128,36 @@ INFO skipping board for SP update, serial_number: serial0, part_number: model0 INFO skipping board for SP update, serial_number: serial1, part_number: model1 INFO ran out of boards for SP update generated blueprint 9034c710-3e57-45f3-99e5-4316145e87ac based on parent blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba +Planning report for blueprint 9034c710-3e57-45f3-99e5-4316145e87ac: +* 1 out-of-date zone updated in-place: + * sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zone 353b3b65-20f7-48c3-88f7-495bd5d31545 (clickhouse) +* 25 remaining out-of-date zones: + * sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zone 353b3b65-20f7-48c3-88f7-495bd5d31545 (clickhouse): install dataset → artifact: version 1.0.0 + * sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zone 466a9f29-62bf-4e63-924a-b9efdb86afec (nexus): install dataset → artifact: version 1.0.0 + * sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zone 62620961-fc4a-481e-968b-f5acbac0dc63 (internal_ntp): install dataset → artifact: version 1.0.0 + * sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zone 6c3ae381-04f7-41ea-b0ac-74db387dbc3a (external_dns): install dataset → artifact: version 1.0.0 + * sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zone 86a22a56-0168-453d-9df1-cb2a7c64b5d3 (crucible): install dataset → artifact: version 1.0.0 + * sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zone 99e2f30b-3174-40bf-a78a-90da8abba8ca (internal_dns): install dataset → artifact: version 1.0.0 + * sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zone ad6a3a03-8d0f-4504-99a4-cbf73d69b973 (crucible_pantry): install dataset → artifact: version 1.0.0 + * sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zone bd354eef-d8a6-4165-9124-283fb5e46d77 (crucible): install dataset → artifact: version 1.0.0 + * sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c, zone e2fdefe7-95b2-4fd2-ae37-56929a06d58c (crucible): install dataset → artifact: version 1.0.0 + * sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, zone 058fd5f9-60a8-4e11-9302-15172782e17d (crucible): install dataset → artifact: version 1.0.0 + * sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, zone 0c71b3b2-6ceb-4e8f-b020-b08675e83038 (nexus): install dataset → artifact: version 1.0.0 + * sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, zone 427ec88f-f467-42fa-9bbb-66a91a36103c (internal_dns): install dataset → artifact: version 1.0.0 + * sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, zone 5199c033-4cf9-4ab6-8ae7-566bd7606363 (crucible): install dataset → artifact: version 1.0.0 + * sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, zone 6444f8a5-6465-4f0b-a549-1993c113569c (internal_ntp): install dataset → artifact: version 1.0.0 + * sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, zone 803bfb63-c246-41db-b0da-d3b87ddfc63d (external_dns): install dataset → artifact: version 1.0.0 + * sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, zone ba4994a8-23f9-4b1a-a84f-a08d74591389 (crucible_pantry): install dataset → artifact: version 1.0.0 + * sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, zone dfac80b4-a887-430a-ae87-a4e065dba787 (crucible): install dataset → artifact: version 1.0.0 + * sled d81c6a84-79b8-4958-ae41-ea46c9b19763, zone 3eeb8d49-eb1a-43f8-bb64-c2338421c2c6 (nexus): install dataset → artifact: version 1.0.0 + * sled d81c6a84-79b8-4958-ae41-ea46c9b19763, zone 694bd14f-cb24-4be4-bb19-876e79cda2c8 (crucible): install dataset → artifact: version 1.0.0 + * sled d81c6a84-79b8-4958-ae41-ea46c9b19763, zone 75b220ba-a0f4-4872-8202-dc7c87f062d0 (crucible_pantry): install dataset → artifact: version 1.0.0 + * sled d81c6a84-79b8-4958-ae41-ea46c9b19763, zone 7c252b64-c5af-4ec1-989e-9a03f3b0f111 (crucible): install dataset → artifact: version 1.0.0 + * sled d81c6a84-79b8-4958-ae41-ea46c9b19763, zone ea5b4030-b52f-44b2-8d70-45f15f987d01 (internal_dns): install dataset → artifact: version 1.0.0 + * sled d81c6a84-79b8-4958-ae41-ea46c9b19763, zone f10a4fb9-759f-4a65-b25e-5794ad2d07d8 (internal_ntp): install dataset → artifact: version 1.0.0 + * sled d81c6a84-79b8-4958-ae41-ea46c9b19763, zone f55647d4-5500-4ad3-893a-df45bd50d622 (crucible): install dataset → artifact: version 1.0.0 + * sled d81c6a84-79b8-4958-ae41-ea46c9b19763, zone f6ec9c67-946a-4da3-98d5-581f72ce8bf0 (external_dns): install dataset → artifact: version 1.0.0 + > blueprint-diff 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba 9034c710-3e57-45f3-99e5-4316145e87ac from: blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba diff --git a/nexus/reconfigurator/planning/src/planner.rs b/nexus/reconfigurator/planning/src/planner.rs index e21405ceef6..c2b548f2770 100644 --- a/nexus/reconfigurator/planning/src/planner.rs +++ b/nexus/reconfigurator/planning/src/planner.rs @@ -535,9 +535,9 @@ impl<'a> Planner<'a> { if zone_counts.num_install_dataset() == 0 { report.skip_sled( sled.sled_id, - SkipSledReason::AllZonesAlreadyArtifact( - zone_counts.num_total, - ), + SkipSledReason::AllZonesAlreadyArtifact { + num_total: zone_counts.num_total, + }, ); continue; } @@ -751,9 +751,7 @@ impl<'a> Planner<'a> { )?, )? == Ensure::Added { - report - .sleds_missing_crucible_zone - .insert((sled_id, *zpool_id)); + report.missing_crucible_zone(sled_id, *zpool_id); ncrucibles_added += 1; } } @@ -963,9 +961,10 @@ impl<'a> Planner<'a> { let num_zones_to_add = target_count.saturating_sub(num_existing_kind_zones); if num_zones_to_add == 0 { - report.sufficient_zones_exist.insert( - ZoneKind::from(zone_kind).report_str().to_owned(), - (target_count, num_existing_kind_zones), + report.sufficient_zones_exist( + ZoneKind::from(zone_kind).report_str(), + target_count, + num_existing_kind_zones, ); } num_zones_to_add @@ -992,9 +991,10 @@ impl<'a> Planner<'a> { // (albeit unlikely?) we're in a weird state where we need // more sleds or disks to come online, and we may need to be // able to produce blueprints to achieve that status. - report.out_of_eligible_sleds.insert( - ZoneKind::from(kind).report_str().to_owned(), - (i, num_zones_to_add), + report.out_of_eligible_sleds( + ZoneKind::from(kind).report_str(), + i, + num_zones_to_add, ); break; } @@ -1037,9 +1037,10 @@ impl<'a> Planner<'a> { .blueprint .sled_add_zone_oximeter(sled_id, image_source)?, }; - report - .discretionary_zones_placed - .push((sled_id, ZoneKind::from(kind).report_str().to_owned())); + report.discretionary_zone_placed( + sled_id, + ZoneKind::from(kind).report_str(), + ); } Ok(()) @@ -1113,7 +1114,8 @@ impl<'a> Planner<'a> { // ... or if there are still pending updates for the RoT / SP / // Host OS / etc. - if mgs_updates.any_updates_pending() { + // TODO This is not quite right. See oxidecomputer/omicron#8285. + if !mgs_updates.is_empty() { report.waiting_on(ZoneUpdatesWaitingOn::PendingMgsUpdates); return Ok(report); } @@ -1282,7 +1284,10 @@ impl<'a> Planner<'a> { }) }) .collect::>(); - report.out_of_date_zones.extend(out_of_date_zones.iter().cloned()); + + for (sled_id, zone, desired_image) in out_of_date_zones.iter() { + report.out_of_date_zone(*sled_id, zone, desired_image.clone()); + } // Of the out-of-date zones, filter out zones that can't be updated yet, // either because they're not ready or because it wouldn't be safe to @@ -1354,7 +1359,7 @@ impl<'a> Planner<'a> { zone.zone_type.kind(), zone.id )); - report.updated_zones.push((sled_id, zone.clone())); + report.updated_zone(sled_id, &zone); self.blueprint.sled_set_zone_source( sled_id, zone.id, @@ -1373,7 +1378,7 @@ impl<'a> Planner<'a> { zone.zone_type.kind(), zone.id )); - report.expunged_zones.push((sled_id, zone.clone())); + report.expunged_zone(sled_id, zone); self.blueprint.sled_expunge_zone(sled_id, zone.id)?; } } @@ -1500,7 +1505,7 @@ impl<'a> Planner<'a> { // We return false regardless of `zone_kind` if there are still // pending updates for components earlier in the update ordering // than zones: RoT bootloader / RoT / SP / Host OS. - if mgs_updates.any_updates_pending() { + if !mgs_updates.is_empty() { return Ok(false); } @@ -1564,7 +1569,10 @@ impl<'a> Planner<'a> { // We must hear from all nodes let all_statuses = &self.inventory.cockroach_status; if all_statuses.len() < COCKROACHDB_REDUNDANCY { - report.unsafe_zone(zone, Cockroachdb(NotEnoughNodes)); + report.unsafe_zone( + zone, + Cockroachdb { reason: NotEnoughNodes }, + ); return false; } @@ -1576,30 +1584,34 @@ impl<'a> Planner<'a> { else { report.unsafe_zone( zone, - Cockroachdb(MissingUnderreplicatedStat), + Cockroachdb { reason: MissingUnderreplicatedStat }, ); return false; }; if ranges_underreplicated != 0 { report.unsafe_zone( zone, - Cockroachdb(UnderreplicatedRanges( - ranges_underreplicated, - )), + Cockroachdb { + reason: UnderreplicatedRanges { + n: ranges_underreplicated, + }, + }, ); return false; } let Some(live_nodes) = status.liveness_live_nodes else { report.unsafe_zone( zone, - Cockroachdb(MissingLiveNodesStat), + Cockroachdb { reason: MissingLiveNodesStat }, ); return false; }; if live_nodes < COCKROACHDB_REDUNDANCY as u64 { report.unsafe_zone( zone, - Cockroachdb(NotEnoughLiveNodes(live_nodes)), + Cockroachdb { + reason: NotEnoughLiveNodes { live_nodes }, + }, ); return false; } diff --git a/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt b/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt index 3497e8742b4..9123be5312c 100644 --- a/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt +++ b/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt @@ -534,5 +534,5 @@ parent: e35b2fdd-354d-48d9-acb5-703b2c269a54 PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint 4a0b8410-b14f-41e7-85e7-3c0fe7050ccc: +Nothing to report on planning for blueprint 4a0b8410-b14f-41e7-85e7-3c0fe7050ccc. diff --git a/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt b/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt index ea245c1a115..97769f457d9 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt @@ -325,9 +325,9 @@ parent: 516e80a3-b362-4fac-bd3c-4559717120dd PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint 1ac2d88f-27dd-4506-8585-6b2be832528e: +Planning report for blueprint 1ac2d88f-27dd-4506-8585-6b2be832528e: * Discretionary zones placed: - * on sled d67ce8f0-a691-4010-b414-420d82e80527: crucible_pantry, nexus zones - * on sled fefcf4cf-f7e7-46b3-b629-058526ce440e: clickhouse, internal_dns zones + * 2 zones on sled d67ce8f0-a691-4010-b414-420d82e80527: crucible_pantry, nexus + * 2 zones on sled fefcf4cf-f7e7-46b3-b629-058526ce440e: clickhouse, internal_dns * Zone updates waiting on discretionary zones diff --git a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt index 3424e652cca..8d56025ea5f 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt @@ -513,9 +513,9 @@ parent: 4d4e6c38-cd95-4c4e-8f45-6af4d686964b PENDING MGS-MANAGED UPDATES: 0 -Report on planning run for blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6: +Planning report for blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6: * Discretionary zones placed: - * on sled 75bc286f-2b4b-482c-9431-59272af529da: nexus, nexus, nexus zones - * on sled affab35f-600a-4109-8ea0-34a067a4e0bc: nexus, nexus, nexus zones + * 3 zones on sled 75bc286f-2b4b-482c-9431-59272af529da: nexus, nexus, nexus + * 3 zones on sled affab35f-600a-4109-8ea0-34a067a4e0bc: nexus, nexus, nexus * Zone updates waiting on discretionary zones diff --git a/nexus/types/src/deployment/planning_report.rs b/nexus/types/src/deployment/planning_report.rs index 69c199646fb..b94412619a8 100644 --- a/nexus/types/src/deployment/planning_report.rs +++ b/nexus/types/src/deployment/planning_report.rs @@ -75,28 +75,46 @@ impl PlanningReport { cockroachdb_settings: PlanningCockroachdbSettingsStepReport::new(), } } + + pub fn is_empty(&self) -> bool { + self.expunge.is_empty() + && self.decommission.is_empty() + && self.noop_image_source.is_empty() + && self.mgs_updates.is_empty() + && self.add.is_empty() + && self.zone_updates.is_empty() + && self.cockroachdb_settings.is_empty() + } } impl fmt::Display for PlanningReport { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let Self { - blueprint_id, - expunge, - decommission, - noop_image_source, - mgs_updates, - add, - zone_updates, - cockroachdb_settings, - } = self; - writeln!(f, "Report on planning run for blueprint {blueprint_id}:")?; - expunge.fmt(f)?; - decommission.fmt(f)?; - noop_image_source.fmt(f)?; - mgs_updates.fmt(f)?; - add.fmt(f)?; - zone_updates.fmt(f)?; - cockroachdb_settings.fmt(f)?; + if self.is_empty() { + writeln!( + f, + "Nothing to report on planning for blueprint {}.", + self.blueprint_id, + )?; + } else { + let Self { + blueprint_id, + expunge, + decommission, + noop_image_source, + mgs_updates, + add, + zone_updates, + cockroachdb_settings, + } = self; + writeln!(f, "Planning report for blueprint {blueprint_id}:")?; + expunge.fmt(f)?; + decommission.fmt(f)?; + noop_image_source.fmt(f)?; + mgs_updates.fmt(f)?; + add.fmt(f)?; + zone_updates.fmt(f)?; + cockroachdb_settings.fmt(f)?; + } Ok(()) } } @@ -113,6 +131,10 @@ impl PlanningExpungeStepReport { pub fn new() -> Self { Self { orphan_disks: BTreeMap::new() } } + + pub fn is_empty(&self) -> bool { + self.orphan_disks.is_empty() + } } impl fmt::Display for PlanningExpungeStepReport { @@ -144,17 +166,20 @@ impl PlanningDecommissionStepReport { pub fn new() -> Self { Self { zombie_sleds: Vec::new() } } + + pub fn is_empty(&self) -> bool { + self.zombie_sleds.is_empty() + } } impl fmt::Display for PlanningDecommissionStepReport { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let Self { zombie_sleds } = self; if !zombie_sleds.is_empty() { - let n = zombie_sleds.len(); - let s = if n == 1 { "" } else { "s" }; + let (n, s) = plural_vec(zombie_sleds); writeln!( f, - "* decommissioned sled{s} returned by `SledFilter::Commissioned`: {}", + "* {n} decommissioned sled{s} returned by `SledFilter::Commissioned`: {}", zombie_sleds .iter() .map(|sled_id| format!("{sled_id}")) @@ -166,6 +191,16 @@ impl fmt::Display for PlanningDecommissionStepReport { } } +/// How many of the total install-dataset zones were noop-converted to use +/// the artifact store on a particular sled. +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] +pub struct PlanningNoopImageSourceConvertedZones { + pub num_eligible: usize, + pub num_dataset: usize, +} + #[derive( Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, )] @@ -175,7 +210,8 @@ pub struct PlanningNoopImageSourceStepReport { BTreeMap, pub skipped_zones: BTreeMap, - pub converted_zones: BTreeMap, + pub converted_zones: + BTreeMap, } impl PlanningNoopImageSourceStepReport { @@ -188,6 +224,13 @@ impl PlanningNoopImageSourceStepReport { } } + pub fn is_empty(&self) -> bool { + !self.no_target_release + && self.skipped_sleds.is_empty() + && self.skipped_zones.is_empty() + && self.converted_zones.is_empty() + } + pub fn skip_sled( &mut self, sled_id: SledUuid, @@ -210,7 +253,10 @@ impl PlanningNoopImageSourceStepReport { num_eligible: usize, num_dataset: usize, ) { - self.converted_zones.insert(sled_id, (num_eligible, num_dataset)); + self.converted_zones.insert( + sled_id, + PlanningNoopImageSourceConvertedZones { num_eligible, num_dataset }, + ); } } @@ -237,20 +283,16 @@ impl fmt::Display for PlanningNoopImageSourceStepReport { )?; } - // Very noisy in tests. - // for (zone_id, reason) in skipped_zones.iter() { - // writeln!( - // f, - // "* Skipping noop image source check for zone {zone_id}: {reason}" - // )?; - // } - - for (sled_id, (m, n)) in converted_zones.iter() { - if *m > 0 && *n > 0 { + for ( + sled_id, + PlanningNoopImageSourceConvertedZones { num_eligible, num_dataset }, + ) in converted_zones.iter() + { + if *num_eligible > 0 && *num_dataset > 0 { writeln!( f, - "* Noop converting {m}/{n} install-dataset zones to artifact store \ - on sled {sled_id}", + "* Noop converting {num_eligible}/{num_dataset} install-dataset zones \ + to artifact store on sled {sled_id}", )?; } } @@ -262,30 +304,31 @@ impl fmt::Display for PlanningNoopImageSourceStepReport { #[derive( Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, )] +#[serde(rename_all = "snake_case", tag = "type")] pub enum PlanningNoopImageSourceSkipSledReason { - AllZonesAlreadyArtifact(usize), + AllZonesAlreadyArtifact { num_total: usize }, SledNotInInventory, - ErrorRetrievingZoneManifest(String), - RemoveMupdateOverride(MupdateOverrideUuid), + ErrorRetrievingZoneManifest { error: String }, + RemoveMupdateOverride { id: MupdateOverrideUuid }, } impl fmt::Display for PlanningNoopImageSourceSkipSledReason { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Self::AllZonesAlreadyArtifact(n) => { - write!(f, "all {n} zones are already from artifacts") + Self::AllZonesAlreadyArtifact { num_total } => { + write!(f, "all {num_total} zones are already from artifacts") } Self::SledNotInInventory => { write!(f, "sled not present in latest inventory collection") } - Self::ErrorRetrievingZoneManifest(error) => { + Self::ErrorRetrievingZoneManifest { error } => { write!( f, "sled-agent encountered error retrieving zone manifest \ (this is abnormal): {error}" ) } - Self::RemoveMupdateOverride(id) => { + Self::RemoveMupdateOverride { id } => { write!( f, "blueprint has get_remove_mupdate_override set for sled: {id}", @@ -298,6 +341,7 @@ impl fmt::Display for PlanningNoopImageSourceSkipSledReason { #[derive( Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, )] +#[serde(rename_all = "snake_case", tag = "type")] pub enum PlanningNoopImageSourceSkipZoneReason { ZoneNotInManifest { zone_kind: String, @@ -347,9 +391,8 @@ impl PlanningMgsUpdatesStepReport { Self { pending_mgs_updates } } - // TODO This is not quite right. See oxidecomputer/omicron#8285. - pub fn any_updates_pending(&self) -> bool { - !self.pending_mgs_updates.is_empty() + pub fn is_empty(&self) -> bool { + self.pending_mgs_updates.is_empty() } } @@ -358,7 +401,7 @@ impl fmt::Display for PlanningMgsUpdatesStepReport { let Self { pending_mgs_updates } = self; if !pending_mgs_updates.is_empty() { let n = pending_mgs_updates.len(); - let s = if n == 1 { "" } else { "s" }; + let s = plural(n); writeln!(f, "* {n} pending MGS update{s}:")?; for update in pending_mgs_updates.iter() { writeln!( @@ -372,6 +415,25 @@ impl fmt::Display for PlanningMgsUpdatesStepReport { } } +/// How many discretionary zones we actually placed out of how many we +/// wanted to place. +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] +pub struct PlanningAddOutOfEligibleSleds { + pub placed: usize, + pub wanted_to_place: usize, +} + +/// We have at least the minimum required number of zones of a given kind. +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] +pub struct PlanningAddSufficientZonesExist { + pub target_count: usize, + pub num_existing: usize, +} + #[derive( Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, )] @@ -380,18 +442,19 @@ pub struct PlanningAddStepReport { pub sleds_waiting_for_ntp_zone: BTreeSet, pub sleds_getting_ntp_and_discretionary_zones: BTreeSet, pub sleds_missing_ntp_zone: BTreeSet, - pub sleds_missing_crucible_zone: BTreeSet<(SledUuid, ZpoolUuid)>, + pub sleds_missing_crucible_zone: BTreeMap>, /// Discretionary zone kind → (placed, wanted to place) - pub out_of_eligible_sleds: BTreeMap, + pub out_of_eligible_sleds: BTreeMap, /// Discretionary zone kind → (wanted to place, num existing) - pub sufficient_zones_exist: BTreeMap, + pub sufficient_zones_exist: + BTreeMap, - /// List of (Sled ID, kind of discretionary zone placed there) pairs. + /// Sled ID → kinds of discretionary zones placed there // TODO: make `sled_add_zone_*` methods return the added zone config // so that we can report it here. - pub discretionary_zones_placed: Vec<(SledUuid, String)>, + pub discretionary_zones_placed: BTreeMap>, } impl PlanningAddStepReport { @@ -401,16 +464,72 @@ impl PlanningAddStepReport { sleds_waiting_for_ntp_zone: BTreeSet::new(), sleds_getting_ntp_and_discretionary_zones: BTreeSet::new(), sleds_missing_ntp_zone: BTreeSet::new(), - sleds_missing_crucible_zone: BTreeSet::new(), + sleds_missing_crucible_zone: BTreeMap::new(), out_of_eligible_sleds: BTreeMap::new(), sufficient_zones_exist: BTreeMap::new(), - discretionary_zones_placed: Vec::new(), + discretionary_zones_placed: BTreeMap::new(), } } + pub fn is_empty(&self) -> bool { + self.sleds_with_no_zpools_for_ntp_zone.is_empty() + && self.sleds_waiting_for_ntp_zone.is_empty() + && self.sleds_getting_ntp_and_discretionary_zones.is_empty() + && self.sleds_missing_ntp_zone.is_empty() + && self.sleds_missing_crucible_zone.is_empty() + && self.out_of_eligible_sleds.is_empty() + && self.discretionary_zones_placed.is_empty() + } + pub fn any_discretionary_zones_placed(&self) -> bool { !self.discretionary_zones_placed.is_empty() } + + pub fn missing_crucible_zone( + &mut self, + sled_id: SledUuid, + zpool_id: ZpoolUuid, + ) { + self.sleds_missing_crucible_zone + .entry(sled_id) + .and_modify(|pools| pools.push(zpool_id)) + .or_insert_with(|| vec![zpool_id]); + } + + pub fn out_of_eligible_sleds( + &mut self, + zone_kind: &str, + placed: usize, + wanted_to_place: usize, + ) { + self.out_of_eligible_sleds.insert( + zone_kind.to_owned(), + PlanningAddOutOfEligibleSleds { placed, wanted_to_place }, + ); + } + + pub fn sufficient_zones_exist( + &mut self, + zone_kind: &str, + target_count: usize, + num_existing: usize, + ) { + self.sufficient_zones_exist.insert( + zone_kind.to_owned(), + PlanningAddSufficientZonesExist { target_count, num_existing }, + ); + } + + pub fn discretionary_zone_placed( + &mut self, + sled_id: SledUuid, + zone_kind: &str, + ) { + self.discretionary_zones_placed + .entry(sled_id) + .and_modify(|kinds| kinds.push(zone_kind.to_owned())) + .or_insert_with(|| vec![zone_kind.to_owned()]); + } } impl fmt::Display for PlanningAddStepReport { @@ -467,32 +586,33 @@ impl fmt::Display for PlanningAddStepReport { writeln!(f, "* Missing NTP zone on sled {sled_id}",)?; } - for (sled_id, zpool_id) in sleds_missing_crucible_zone { - writeln!( - f, - "* Missing Crucible zone for sled {sled_id}, zpool {zpool_id}", - )?; + for (sled_id, zpools) in sleds_missing_crucible_zone { + for zpool_id in zpools { + writeln!( + f, + "* Missing Crucible zone for sled {sled_id}, zpool {zpool_id}", + )?; + } } - for (kind, (placed, desired)) in out_of_eligible_sleds.iter() { + for (kind, PlanningAddOutOfEligibleSleds { placed, wanted_to_place }) in + out_of_eligible_sleds.iter() + { writeln!( f, - "* Only placed {placed}/{desired} desired {kind} zones" + "* Only placed {placed}/{wanted_to_place} desired {kind} zones" )?; } - // Noisy in tests. - // for (kind, (desired, existing)) in sufficient_zones_exist.iter() { - // writeln!( - // f, - // "* Sufficient {kind} zones exist in plan: {desired}/{existing}" - // )?; - // } - if !discretionary_zones_placed.is_empty() { writeln!(f, "* Discretionary zones placed:")?; - for (sled_id, kind) in discretionary_zones_placed.iter() { - writeln!(f, " * a {kind} zone on sled {sled_id}")?; + for (sled_id, kinds) in discretionary_zones_placed.iter() { + let (n, s) = plural_vec(kinds); + writeln!( + f, + " * {n} zone{s} on sled {sled_id}: {}", + kinds.join(", ") + )?; } } @@ -500,6 +620,15 @@ impl fmt::Display for PlanningAddStepReport { } } +/// We have at least the minimum required number of zones of a given kind. +#[derive( + Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, +)] +pub struct PlanningOutOfDateZone { + pub zone_config: BlueprintZoneConfig, + pub desired_image_source: BlueprintZoneImageSource, +} + #[derive( Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, )] @@ -507,36 +636,79 @@ pub struct PlanningZoneUpdatesStepReport { /// What are we waiting on to start zone updates? pub waiting_on: Option, - /// (Sled ID, zone, desired image) - pub out_of_date_zones: - Vec<(SledUuid, BlueprintZoneConfig, BlueprintZoneImageSource)>, - - pub expunged_zones: Vec<(SledUuid, BlueprintZoneConfig)>, - pub updated_zones: Vec<(SledUuid, BlueprintZoneConfig)>, - pub unsafe_zones: Vec<(BlueprintZoneConfig, ZoneUnsafeToShutdown)>, + pub out_of_date_zones: BTreeMap>, + pub expunged_zones: BTreeMap>, + pub updated_zones: BTreeMap>, + pub unsafe_zones: BTreeMap, } impl PlanningZoneUpdatesStepReport { pub fn new() -> Self { Self { waiting_on: None, - out_of_date_zones: Vec::new(), - expunged_zones: Vec::new(), - updated_zones: Vec::new(), - unsafe_zones: Vec::new(), + out_of_date_zones: BTreeMap::new(), + expunged_zones: BTreeMap::new(), + updated_zones: BTreeMap::new(), + unsafe_zones: BTreeMap::new(), } } + pub fn is_empty(&self) -> bool { + self.waiting_on.is_none() + && self.out_of_date_zones.is_empty() + && self.expunged_zones.is_empty() + && self.updated_zones.is_empty() + && self.unsafe_zones.is_empty() + } + pub fn waiting_on(&mut self, waiting_on: ZoneUpdatesWaitingOn) { self.waiting_on = Some(waiting_on); } + pub fn out_of_date_zone( + &mut self, + sled_id: SledUuid, + zone_config: &BlueprintZoneConfig, + desired_image_source: BlueprintZoneImageSource, + ) { + let out_of_date = PlanningOutOfDateZone { + zone_config: zone_config.to_owned(), + desired_image_source, + }; + self.out_of_date_zones + .entry(sled_id) + .and_modify(|zones| zones.push(out_of_date.clone())) + .or_insert_with(|| vec![out_of_date]); + } + + pub fn expunged_zone( + &mut self, + sled_id: SledUuid, + zone_config: &BlueprintZoneConfig, + ) { + self.expunged_zones + .entry(sled_id) + .and_modify(|zones| zones.push(zone_config.to_owned())) + .or_insert_with(|| vec![zone_config.to_owned()]); + } + + pub fn updated_zone( + &mut self, + sled_id: SledUuid, + zone_config: &BlueprintZoneConfig, + ) { + self.updated_zones + .entry(sled_id) + .and_modify(|zones| zones.push(zone_config.to_owned())) + .or_insert_with(|| vec![zone_config.to_owned()]); + } + pub fn unsafe_zone( &mut self, zone: &BlueprintZoneConfig, reason: ZoneUnsafeToShutdown, ) { - self.unsafe_zones.push((zone.clone(), reason)) + self.unsafe_zones.insert(zone.clone(), reason); } } @@ -555,53 +727,61 @@ impl fmt::Display for PlanningZoneUpdatesStepReport { } if !expunged_zones.is_empty() { - let n = out_of_date_zones.len(); - let s = if n == 1 { "" } else { "s" }; - writeln!(f, "* Out-of-date zone{s} expunged:")?; - for (sled_id, zone) in expunged_zones.iter() { - writeln!( - f, - " * sled {}, zone {} ({})", - sled_id, - zone.id, - zone.zone_type.kind().report_str(), - )?; + let (n, s) = plural_map_of_vec(expunged_zones); + writeln!(f, "* {n} out-of-date zone{s} expunged:")?; + for (sled_id, zones) in expunged_zones.iter() { + for zone in zones { + writeln!( + f, + " * sled {}, zone {} ({})", + sled_id, + zone.id, + zone.zone_type.kind().report_str(), + )?; + } } } if !updated_zones.is_empty() { - let n = out_of_date_zones.len(); - let s = if n == 1 { "" } else { "s" }; - writeln!(f, "* Out-of-date zone{s} updated in-place:")?; - for (sled_id, zone) in updated_zones.iter() { - writeln!( - f, - " * sled {}, zone {} ({})", - sled_id, - zone.id, - zone.zone_type.kind().report_str(), - )?; + let (n, s) = plural_map_of_vec(updated_zones); + writeln!(f, "* {n} out-of-date zone{s} updated in-place:")?; + for (sled_id, zones) in updated_zones.iter() { + for zone in zones { + writeln!( + f, + " * sled {}, zone {} ({})", + sled_id, + zone.id, + zone.zone_type.kind().report_str(), + )?; + } } } if !out_of_date_zones.is_empty() { - let n = out_of_date_zones.len(); - let s = if n == 1 { "" } else { "s" }; - writeln!(f, "* {n} out-of-date zone{s}:")?; - for (sled, zone, _image_source) in out_of_date_zones.iter() { - writeln!( - f, - " * sled {}, zone {} ({})", // TODO: current → desired image source - sled, - zone.id, - zone.zone_type.kind().report_str(), - )?; + let (n, s) = plural_map_of_vec(out_of_date_zones); + writeln!(f, "* {n} remaining out-of-date zone{s}:")?; + for (sled_id, zones) in out_of_date_zones.iter() { + for PlanningOutOfDateZone { + zone_config, + desired_image_source, + } in zones + { + writeln!( + f, + " * sled {}, zone {} ({}): {} → {}", + sled_id, + zone_config.id, + zone_config.zone_type.kind().report_str(), + zone_config.image_source, + desired_image_source, + )?; + } } } if !unsafe_zones.is_empty() { - let n = unsafe_zones.len(); - let s = if n == 1 { "" } else { "s" }; + let (n, s) = plural_map(unsafe_zones); writeln!(f, "* {n} zone{s} not ready to shut down safely:")?; for (zone, reason) in unsafe_zones.iter() { writeln!( @@ -621,6 +801,7 @@ impl fmt::Display for PlanningZoneUpdatesStepReport { #[derive( Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, )] +#[serde(rename_all = "snake_case", tag = "type")] pub enum ZoneUpdatesWaitingOn { /// Waiting on discretionary zone placement. DiscretionaryZones, @@ -643,14 +824,15 @@ impl ZoneUpdatesWaitingOn { #[derive( Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, )] +#[serde(rename_all = "snake_case", tag = "type")] pub enum ZoneUnsafeToShutdown { - Cockroachdb(CockroachdbUnsafeToShutdown), + Cockroachdb { reason: CockroachdbUnsafeToShutdown }, } impl fmt::Display for ZoneUnsafeToShutdown { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Self::Cockroachdb(reason) => write!(f, "{reason}"), + Self::Cockroachdb { reason } => write!(f, "{reason}"), } } } @@ -658,12 +840,13 @@ impl fmt::Display for ZoneUnsafeToShutdown { #[derive( Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Diffable, JsonSchema, )] +#[serde(rename_all = "snake_case", tag = "type")] pub enum CockroachdbUnsafeToShutdown { MissingLiveNodesStat, MissingUnderreplicatedStat, - NotEnoughLiveNodes(u64), + NotEnoughLiveNodes { live_nodes: u64 }, NotEnoughNodes, - UnderreplicatedRanges(u64), + UnderreplicatedRanges { n: u64 }, } impl fmt::Display for CockroachdbUnsafeToShutdown { @@ -673,14 +856,14 @@ impl fmt::Display for CockroachdbUnsafeToShutdown { Self::MissingUnderreplicatedStat => { write!(f, "missing ranges_underreplicated stat") } - Self::NotEnoughLiveNodes(n) => { + Self::NotEnoughLiveNodes { live_nodes } => { write!( f, - "not enough live nodes: {n} < {COCKROACHDB_REDUNDANCY}" + "not enough live nodes: {live_nodes} < {COCKROACHDB_REDUNDANCY}" ) } Self::NotEnoughNodes => write!(f, "not enough nodes"), - Self::UnderreplicatedRanges(n) => { + Self::UnderreplicatedRanges { n } => { if *n > 0 { write!(f, "{n} > 0 underreplicated ranges") } else { @@ -705,15 +888,17 @@ impl PlanningCockroachdbSettingsStepReport { pub fn new() -> Self { Self { preserve_downgrade: CockroachDbPreserveDowngrade::DoNotModify } } + + pub fn is_empty(&self) -> bool { + self.preserve_downgrade == CockroachDbPreserveDowngrade::DoNotModify + } } impl fmt::Display for PlanningCockroachdbSettingsStepReport { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let PlanningCockroachdbSettingsStepReport { preserve_downgrade } = self; - if !matches!( - preserve_downgrade, - CockroachDbPreserveDowngrade::DoNotModify, - ) { + if !self.is_empty() { + let PlanningCockroachdbSettingsStepReport { preserve_downgrade } = + self; writeln!( f, "* Will ensure cockroachdb setting: {preserve_downgrade}" @@ -722,3 +907,22 @@ impl fmt::Display for PlanningCockroachdbSettingsStepReport { Ok(()) } } + +fn plural(n: usize) -> &'static str { + if n == 1 { "" } else { "s" } +} + +fn plural_vec(vec: &Vec) -> (usize, &'static str) { + let n = vec.len(); + (n, plural(n)) +} + +fn plural_map(map: &BTreeMap) -> (usize, &'static str) { + let n = map.len(); + (n, plural(n)) +} + +fn plural_map_of_vec(map: &BTreeMap>) -> (usize, &'static str) { + let n = map.values().map(|v| v.len()).sum(); + (n, plural(n)) +} diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index 68a0f0b13d1..999d8f3d7db 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -2507,6 +2507,14 @@ } ] }, + "report": { + "description": "Report on the planning session that resulted in this blueprint", + "allOf": [ + { + "$ref": "#/components/schemas/PlanningReport" + } + ] + }, "sleds": { "description": "A map of sled id -> desired configuration of the sled.", "type": "object", @@ -2539,6 +2547,7 @@ "oximeter_read_mode", "oximeter_read_version", "pending_mgs_updates", + "report", "sleds", "target_release_minimum_generation", "time_created" @@ -3717,6 +3726,92 @@ } ] }, + "CockroachdbUnsafeToShutdown": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "missing_live_nodes_stat" + ] + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "missing_underreplicated_stat" + ] + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "properties": { + "live_nodes": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "type": { + "type": "string", + "enum": [ + "not_enough_live_nodes" + ] + } + }, + "required": [ + "live_nodes", + "type" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "not_enough_nodes" + ] + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "properties": { + "n": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "type": { + "type": "string", + "enum": [ + "underreplicated_ranges" + ] + } + }, + "required": [ + "n", + "type" + ] + } + ] + }, "CompletedAttempt": { "description": "externally-exposed status for a completed attempt", "type": "object", @@ -6219,6 +6314,493 @@ "ok" ] }, + "PlanningAddOutOfEligibleSleds": { + "description": "How many discretionary zones we actually placed out of how many we wanted to place.", + "type": "object", + "properties": { + "placed": { + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "wanted_to_place": { + "type": "integer", + "format": "uint", + "minimum": 0 + } + }, + "required": [ + "placed", + "wanted_to_place" + ] + }, + "PlanningAddStepReport": { + "type": "object", + "properties": { + "discretionary_zones_placed": { + "description": "Sled ID → kinds of discretionary zones placed there", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "out_of_eligible_sleds": { + "description": "Discretionary zone kind → (placed, wanted to place)", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/PlanningAddOutOfEligibleSleds" + } + }, + "sleds_getting_ntp_and_discretionary_zones": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true + }, + "sleds_missing_crucible_zone": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForZpoolKind" + } + } + }, + "sleds_missing_ntp_zone": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true + }, + "sleds_waiting_for_ntp_zone": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true + }, + "sleds_with_no_zpools_for_ntp_zone": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true + }, + "sufficient_zones_exist": { + "description": "Discretionary zone kind → (wanted to place, num existing)", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/PlanningAddSufficientZonesExist" + } + } + }, + "required": [ + "discretionary_zones_placed", + "out_of_eligible_sleds", + "sleds_getting_ntp_and_discretionary_zones", + "sleds_missing_crucible_zone", + "sleds_missing_ntp_zone", + "sleds_waiting_for_ntp_zone", + "sleds_with_no_zpools_for_ntp_zone", + "sufficient_zones_exist" + ] + }, + "PlanningAddSufficientZonesExist": { + "description": "We have at least the minimum required number of zones of a given kind.", + "type": "object", + "properties": { + "num_existing": { + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "target_count": { + "type": "integer", + "format": "uint", + "minimum": 0 + } + }, + "required": [ + "num_existing", + "target_count" + ] + }, + "PlanningCockroachdbSettingsStepReport": { + "type": "object", + "properties": { + "preserve_downgrade": { + "$ref": "#/components/schemas/CockroachDbPreserveDowngrade" + } + }, + "required": [ + "preserve_downgrade" + ] + }, + "PlanningDecommissionStepReport": { + "type": "object", + "properties": { + "zombie_sleds": { + "description": "Decommissioned sleds that unexpectedly appeared as commissioned.", + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + } + } + }, + "required": [ + "zombie_sleds" + ] + }, + "PlanningExpungeStepReport": { + "type": "object", + "properties": { + "orphan_disks": { + "description": "Expunged disks not present in the parent blueprint.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/TypedUuidForPhysicalDiskKind" + } + } + }, + "required": [ + "orphan_disks" + ] + }, + "PlanningMgsUpdatesStepReport": { + "type": "object", + "properties": { + "pending_mgs_updates": { + "$ref": "#/components/schemas/PendingMgsUpdates" + } + }, + "required": [ + "pending_mgs_updates" + ] + }, + "PlanningNoopImageSourceConvertedZones": { + "description": "How many of the total install-dataset zones were noop-converted to use the artifact store on a particular sled.", + "type": "object", + "properties": { + "num_dataset": { + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "num_eligible": { + "type": "integer", + "format": "uint", + "minimum": 0 + } + }, + "required": [ + "num_dataset", + "num_eligible" + ] + }, + "PlanningNoopImageSourceSkipSledReason": { + "oneOf": [ + { + "type": "object", + "properties": { + "num_total": { + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "type": { + "type": "string", + "enum": [ + "all_zones_already_artifact" + ] + } + }, + "required": [ + "num_total", + "type" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "sled_not_in_inventory" + ] + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "error_retrieving_zone_manifest" + ] + } + }, + "required": [ + "error", + "type" + ] + }, + { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/TypedUuidForMupdateOverrideKind" + }, + "type": { + "type": "string", + "enum": [ + "remove_mupdate_override" + ] + } + }, + "required": [ + "id", + "type" + ] + } + ] + }, + "PlanningNoopImageSourceSkipZoneReason": { + "oneOf": [ + { + "type": "object", + "properties": { + "file_name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "zone_not_in_manifest" + ] + }, + "zone_kind": { + "type": "string" + } + }, + "required": [ + "file_name", + "type", + "zone_kind" + ] + }, + { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "file_name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "invalid_artifact" + ] + }, + "zone_kind": { + "type": "string" + } + }, + "required": [ + "error", + "file_name", + "type", + "zone_kind" + ] + }, + { + "type": "object", + "properties": { + "artifact_hash": { + "type": "string", + "format": "hex string (32 bytes)" + }, + "file_name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "artifact_not_in_repo" + ] + }, + "zone_kind": { + "type": "string" + } + }, + "required": [ + "artifact_hash", + "file_name", + "type", + "zone_kind" + ] + } + ] + }, + "PlanningNoopImageSourceStepReport": { + "type": "object", + "properties": { + "converted_zones": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/PlanningNoopImageSourceConvertedZones" + } + }, + "no_target_release": { + "type": "boolean" + }, + "skipped_sleds": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/PlanningNoopImageSourceSkipSledReason" + } + }, + "skipped_zones": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/PlanningNoopImageSourceSkipZoneReason" + } + } + }, + "required": [ + "converted_zones", + "no_target_release", + "skipped_sleds", + "skipped_zones" + ] + }, + "PlanningOutOfDateZone": { + "description": "We have at least the minimum required number of zones of a given kind.", + "type": "object", + "properties": { + "desired_image_source": { + "$ref": "#/components/schemas/BlueprintZoneImageSource" + }, + "zone_config": { + "$ref": "#/components/schemas/BlueprintZoneConfig" + } + }, + "required": [ + "desired_image_source", + "zone_config" + ] + }, + "PlanningReport": { + "description": "A full blueprint planning report. Other than the blueprint ID, each field corresponds to a step in the update planner, i.e., a subroutine of `omicron_nexus::reconfigurator::planning::Planner::do_plan`.\n\nThe intent of a planning report is to capture information useful to an operator or developer about the planning process itself, especially if it has become \"stuck\" (unable to proceed with an update). It is *not* a summary of the plan (blueprint), but rather a description of non-fatal conditions the planner is waiting on, unexpected or invalid configurations encountered during planning, etc. The planner may make internal decisions based on the step reports; the intent is that an operator may make administrative decisions based on the full report.\n\nOnly successful planning runs are currently covered by this report. Failures to plan (i.e., to generate a valid blueprint) are represented by `nexus-reconfigurator-planning::blueprint_builder::Error`.", + "type": "object", + "properties": { + "add": { + "$ref": "#/components/schemas/PlanningAddStepReport" + }, + "blueprint_id": { + "description": "The blueprint produced by the planning run this report describes.", + "allOf": [ + { + "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + } + ] + }, + "cockroachdb_settings": { + "$ref": "#/components/schemas/PlanningCockroachdbSettingsStepReport" + }, + "decommission": { + "$ref": "#/components/schemas/PlanningDecommissionStepReport" + }, + "expunge": { + "$ref": "#/components/schemas/PlanningExpungeStepReport" + }, + "mgs_updates": { + "$ref": "#/components/schemas/PlanningMgsUpdatesStepReport" + }, + "noop_image_source": { + "$ref": "#/components/schemas/PlanningNoopImageSourceStepReport" + }, + "zone_updates": { + "$ref": "#/components/schemas/PlanningZoneUpdatesStepReport" + } + }, + "required": [ + "add", + "blueprint_id", + "cockroachdb_settings", + "decommission", + "expunge", + "mgs_updates", + "noop_image_source", + "zone_updates" + ] + }, + "PlanningZoneUpdatesStepReport": { + "type": "object", + "properties": { + "expunged_zones": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlueprintZoneConfig" + } + } + }, + "out_of_date_zones": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlanningOutOfDateZone" + } + } + }, + "unsafe_zones": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/ZoneUnsafeToShutdown" + } + }, + "updated_zones": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlueprintZoneConfig" + } + } + }, + "waiting_on": { + "nullable": true, + "description": "What are we waiting on to start zone updates?", + "allOf": [ + { + "$ref": "#/components/schemas/ZoneUpdatesWaitingOn" + } + ] + } + }, + "required": [ + "expunged_zones", + "out_of_date_zones", + "unsafe_zones", + "updated_zones" + ] + }, "PortConfigV2": { "type": "object", "properties": { @@ -7977,6 +8559,62 @@ "zone_type" ] }, + "ZoneUnsafeToShutdown": { + "oneOf": [ + { + "type": "object", + "properties": { + "reason": { + "$ref": "#/components/schemas/CockroachdbUnsafeToShutdown" + }, + "type": { + "type": "string", + "enum": [ + "cockroachdb" + ] + } + }, + "required": [ + "reason", + "type" + ] + } + ] + }, + "ZoneUpdatesWaitingOn": { + "oneOf": [ + { + "description": "Waiting on discretionary zone placement.", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "discretionary_zones" + ] + } + }, + "required": [ + "type" + ] + }, + { + "description": "Waiting on updates to RoT / SP / Host OS / etc.", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "pending_mgs_updates" + ] + } + }, + "required": [ + "type" + ] + } + ] + }, "ZpoolName": { "title": "The name of a Zpool", "description": "Zpool names are of the format ox{i,p}_. They are either Internal or External, and should be unique", From 6d5b56487c603dd9bf8e10514f656903850dbe61 Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Wed, 30 Jul 2025 15:00:21 -0600 Subject: [PATCH 07/10] Hoist zone update skipping logic into do_plan --- nexus/reconfigurator/planning/src/planner.rs | 46 ++++++++----------- nexus/types/src/deployment/planning_report.rs | 10 ++-- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/nexus/reconfigurator/planning/src/planner.rs b/nexus/reconfigurator/planning/src/planner.rs index c2b548f2770..5361be9e0ac 100644 --- a/nexus/reconfigurator/planning/src/planner.rs +++ b/nexus/reconfigurator/planning/src/planner.rs @@ -161,7 +161,22 @@ impl<'a> Planner<'a> { let noop_image_source = self.do_plan_noop_image_source()?; let mgs_updates = self.do_plan_mgs_updates(); let add = self.do_plan_add(&mgs_updates)?; - let zone_updates = self.do_plan_zone_updates(&add, &mgs_updates)?; + let zone_updates = if add.any_discretionary_zones_placed() { + // Do not update any zones if we've added any discretionary zones + // (e.g., in response to policy changes) ... + PlanningZoneUpdatesStepReport::waiting_on( + ZoneUpdatesWaitingOn::DiscretionaryZones, + ) + } else if !mgs_updates.is_empty() { + // ... or if there are still pending updates for the RoT / SP / + // Host OS / etc. + // TODO This is not quite right. See oxidecomputer/omicron#8285. + PlanningZoneUpdatesStepReport::waiting_on( + ZoneUpdatesWaitingOn::PendingMgsUpdates, + ) + } else { + self.do_plan_zone_updates(&mgs_updates)? + }; let cockroachdb_settings = self.do_plan_cockroachdb_settings(); Ok(PlanningReport { blueprint_id: self.blueprint.new_blueprint_id(), @@ -973,7 +988,7 @@ impl<'a> Planner<'a> { /// Attempts to place `num_zones_to_add` new zones of `kind`. /// /// It is not an error if there are too few eligible sleds to start a - /// sufficient number of zones; instead, we'll log a warning and start as + /// sufficient number of zones; instead, we'll report it and start as /// many as we can (up to `num_zones_to_add`). fn add_discretionary_zones( &mut self, @@ -1100,38 +1115,15 @@ impl<'a> Planner<'a> { /// Update at most one existing zone to use a new image source. fn do_plan_zone_updates( &mut self, - add: &PlanningAddStepReport, mgs_updates: &PlanningMgsUpdatesStepReport, ) -> Result { let mut report = PlanningZoneUpdatesStepReport::new(); - // Do not update any zones if we've added any discretionary zones - // (e.g., in response to policy changes) ... - if add.any_discretionary_zones_placed() { - report.waiting_on(ZoneUpdatesWaitingOn::DiscretionaryZones); - return Ok(report); - } - - // ... or if there are still pending updates for the RoT / SP / - // Host OS / etc. - // TODO This is not quite right. See oxidecomputer/omicron#8285. - if !mgs_updates.is_empty() { - report.waiting_on(ZoneUpdatesWaitingOn::PendingMgsUpdates); - return Ok(report); - } - - // We are only interested in non-decommissioned sleds with - // running NTP zones (TODO: check time sync). + // We are only interested in non-decommissioned sleds. let sleds = self .input .all_sleds(SledFilter::Commissioned) - .filter_map(|(sled_id, _details)| { - if add.sleds_waiting_for_ntp_zone.contains(&sled_id) { - None - } else { - Some(sled_id) - } - }) + .map(|(id, _details)| id) .collect::>(); // Wait for zones to appear up-to-date in the inventory. diff --git a/nexus/types/src/deployment/planning_report.rs b/nexus/types/src/deployment/planning_report.rs index b94412619a8..d3509cfda43 100644 --- a/nexus/types/src/deployment/planning_report.rs +++ b/nexus/types/src/deployment/planning_report.rs @@ -653,6 +653,12 @@ impl PlanningZoneUpdatesStepReport { } } + pub fn waiting_on(waiting_on: ZoneUpdatesWaitingOn) -> Self { + let mut new = Self::new(); + new.waiting_on = Some(waiting_on); + new + } + pub fn is_empty(&self) -> bool { self.waiting_on.is_none() && self.out_of_date_zones.is_empty() @@ -661,10 +667,6 @@ impl PlanningZoneUpdatesStepReport { && self.unsafe_zones.is_empty() } - pub fn waiting_on(&mut self, waiting_on: ZoneUpdatesWaitingOn) { - self.waiting_on = Some(waiting_on); - } - pub fn out_of_date_zone( &mut self, sled_id: SledUuid, From dcac3dd692277a1b1d85ffc1484824717b92018a Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Wed, 6 Aug 2025 11:42:28 -0600 Subject: [PATCH 08/10] "Empty planning report ..." --- dev-tools/omdb/tests/successes.out | 4 ++-- .../tests/output/cmds-example-stdout | 8 ++++---- .../cmds-expunge-newly-added-external-dns-stdout | 4 ++-- .../cmds-expunge-newly-added-internal-dns-stdout | 2 +- .../tests/output/cmds-host-phase-2-source-stdout | 4 ++-- .../tests/output/cmds-set-mgs-updates-stdout | 10 +++++----- .../output/cmds-set-remove-mupdate-override-stdout | 4 ++-- .../tests/output/cmds-set-zone-images-stdout | 6 +++--- .../output/example_builder_zone_counts_blueprint.txt | 2 +- nexus/types/src/deployment/planning_report.rs | 2 +- 10 files changed, 23 insertions(+), 23 deletions(-) diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index 49a4163d9e0..b816998c1fe 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -1615,7 +1615,7 @@ parent: PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint .............. +Empty planning report for blueprint .............. --------------------------------------------- @@ -1738,7 +1738,7 @@ parent: PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint .............. +Empty planning report for blueprint .............. --------------------------------------------- diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout index d6aa9f36e59..db296744e4e 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout @@ -396,7 +396,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. +Empty planning report for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. @@ -509,7 +509,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. +Empty planning report for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. @@ -1023,7 +1023,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. +Empty planning report for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. @@ -1541,7 +1541,7 @@ COCKROACH STATUS INFO skipping noop image source check for all sleds, reason: no target release is currently set WARN cannot issue more SP updates (no current artifacts) generated blueprint 86db3308-f817-4626-8838-4085949a6a41 based on parent blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a -Nothing to report on planning for blueprint 86db3308-f817-4626-8838-4085949a6a41. +Empty planning report for blueprint 86db3308-f817-4626-8838-4085949a6a41. > blueprint-diff ade5749d-bdf3-4fab-a8ae-00bea01b3a5a latest 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 c38933dede6..d7f64c9343b 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 @@ -334,7 +334,7 @@ parent: 06c88262-f435-410e-ba98-101bed41ec27 PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint 3f00b694-1b16-4aaa-8f78-e6b3a527b434. +Empty planning report for blueprint 3f00b694-1b16-4aaa-8f78-e6b3a527b434. @@ -1025,7 +1025,7 @@ parent: 3f00b694-1b16-4aaa-8f78-e6b3a527b434 PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0. +Empty planning report for blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0. 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 9f7081101e6..725721be0f3 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 @@ -332,7 +332,7 @@ parent: 184f10b3-61cb-41ef-9b93-3489b2bac559 PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21. +Empty planning report for blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21. diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout index e5215838f7d..53c6612b927 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout @@ -712,7 +712,7 @@ parent: 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4. +Empty planning report for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4. @@ -1422,7 +1422,7 @@ parent: af934083-59b5-4bf6-8966-6fb5292c29e1 PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint df06bb57-ad42-4431-9206-abff322896c7. +Empty planning report for blueprint df06bb57-ad42-4431-9206-abff322896c7. diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout index 70ed671898f..d860e9d13a9 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout @@ -208,7 +208,7 @@ parent: 6ccc786b-17f1-4562-958f-5a7d9a5a15fd PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint ad97e762-7bf1-45a6-a98f-60afb7e491c0. +Empty planning report for blueprint ad97e762-7bf1-45a6-a98f-60afb7e491c0. @@ -425,7 +425,7 @@ parent: ad97e762-7bf1-45a6-a98f-60afb7e491c0 sled 2 model2 serial2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 1.1.0 Sp { expected_active_version: ArtifactVersion("1.0.0"), expected_inactive_version: Version(ArtifactVersion("1.0.1")) } -Nothing to report on planning for blueprint cca24b71-09b5-4042-9185-b33e9f2ebba0. +Empty planning report for blueprint cca24b71-09b5-4042-9185-b33e9f2ebba0. @@ -972,7 +972,7 @@ parent: cca24b71-09b5-4042-9185-b33e9f2ebba0 sled 2 model2 serial2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 newest Sp { expected_active_version: ArtifactVersion("newer"), expected_inactive_version: Version(ArtifactVersion("older")) } -Nothing to report on planning for blueprint 5bf974f3-81f9-455b-b24e-3099f765664c. +Empty planning report for blueprint 5bf974f3-81f9-455b-b24e-3099f765664c. @@ -1523,7 +1523,7 @@ parent: 5bf974f3-81f9-455b-b24e-3099f765664c sled 2 model2 serial2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 newest Sp { expected_active_version: ArtifactVersion("newer"), expected_inactive_version: Version(ArtifactVersion("older")) } -Nothing to report on planning for blueprint 1b837a27-3be1-4fcb-8499-a921c839e1d0. +Empty planning report for blueprint 1b837a27-3be1-4fcb-8499-a921c839e1d0. @@ -1904,7 +1904,7 @@ parent: 1b837a27-3be1-4fcb-8499-a921c839e1d0 sled 0 model0 serial0 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 three Sp { expected_active_version: ArtifactVersion("two"), expected_inactive_version: NoValidVersion } -Nothing to report on planning for blueprint 3682a71b-c6ca-4b7e-8f84-16df80c85960. +Empty planning report for blueprint 3682a71b-c6ca-4b7e-8f84-16df80c85960. diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout index 7f49b91c6e1..491458aa9fa 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout @@ -277,7 +277,7 @@ parent: df06bb57-ad42-4431-9206-abff322896c7 PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba. +Empty planning report for blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba. @@ -668,7 +668,7 @@ parent: afb09faf-a586-4483-9289-04d4f1d8ba23 PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700. +Empty planning report for blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700. diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout index bc716c9f26b..5cbfeaf1c21 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout @@ -110,7 +110,7 @@ parent: 1b013011-2062-4b48-b544-a32b23bce83a PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint 971eeb12-1830-4fa0-a699-98ea0164505c. +Empty planning report for blueprint 971eeb12-1830-4fa0-a699-98ea0164505c. @@ -228,7 +228,7 @@ parent: 9766ca20-38d4-4380-b005-e7c43c797e7c PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176. +Empty planning report for blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176. @@ -550,7 +550,7 @@ parent: bb128f06-a2e1-44c1-8874-4f789d0ff896 PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint d9c572a1-a68c-4945-b1ec-5389bd588fe9. +Empty planning report for blueprint d9c572a1-a68c-4945-b1ec-5389bd588fe9. diff --git a/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt b/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt index 9123be5312c..ad348404f72 100644 --- a/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt +++ b/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt @@ -534,5 +534,5 @@ parent: e35b2fdd-354d-48d9-acb5-703b2c269a54 PENDING MGS-MANAGED UPDATES: 0 -Nothing to report on planning for blueprint 4a0b8410-b14f-41e7-85e7-3c0fe7050ccc. +Empty planning report for blueprint 4a0b8410-b14f-41e7-85e7-3c0fe7050ccc. diff --git a/nexus/types/src/deployment/planning_report.rs b/nexus/types/src/deployment/planning_report.rs index 48bcadd2951..c3285e093a8 100644 --- a/nexus/types/src/deployment/planning_report.rs +++ b/nexus/types/src/deployment/planning_report.rs @@ -97,7 +97,7 @@ impl fmt::Display for PlanningReport { if self.is_empty() { writeln!( f, - "Nothing to report on planning for blueprint {}.", + "Empty planning report for blueprint {}.", self.blueprint_id, )?; } else { From 7822a53c28ce6deb6a97eb5b0827fcf5cd672328 Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Wed, 13 Aug 2025 14:43:06 -0600 Subject: [PATCH 09/10] lowercase --- dev-tools/omdb/tests/successes.out | 4 +- .../output/cmds-add-sled-no-disks-stdout | 16 +-- .../tests/output/cmds-example-stdout | 24 ++-- ...ds-expunge-newly-added-external-dns-stdout | 20 ++-- ...ds-expunge-newly-added-internal-dns-stdout | 10 +- .../output/cmds-host-phase-2-source-stdout | 4 +- .../output/cmds-mupdate-update-flow-stdout | 110 +++++++++--------- .../output/cmds-noop-image-source-stdout | 24 ++-- .../tests/output/cmds-set-mgs-updates-stdout | 10 +- .../cmds-set-remove-mupdate-override-stdout | 4 +- .../tests/output/cmds-set-zone-images-stdout | 6 +- .../tests/output/cmds-target-release-stdout | 34 +++--- .../example_builder_zone_counts_blueprint.txt | 2 +- .../planner_decommissions_sleds_bp2.txt | 8 +- .../output/planner_nonprovisionable_bp2.txt | 8 +- nexus/types/src/deployment/planning_report.rs | 34 +++--- 16 files changed, 159 insertions(+), 159 deletions(-) diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index 979afbe4bfc..119880e8574 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -1615,7 +1615,7 @@ parent: PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint .............. +empty planning report for blueprint .............. --------------------------------------------- @@ -1738,7 +1738,7 @@ parent: PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint .............. +empty planning report for blueprint .............. --------------------------------------------- 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 527aeb60923..9591408f905 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 @@ -39,12 +39,12 @@ generated inventory collection eb0796d5-ab8a-4f7b-a884-b4aeacb8ab51 from configu INFO skipping noop image source check for all sleds, reason: no target release is currently set WARN cannot issue more MGS-driven updates (no current artifacts) generated blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 based on parent blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 -Planning report for blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1: -Chicken switches: +planning report for blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1: +chicken switches: add zones with mupdate override: false -* No zpools in service for NTP zones on sleds: 00320471-945d-413c-85e7-03e091a70b3c -* Discretionary zone placement waiting for NTP zones on sleds: 00320471-945d-413c-85e7-03e091a70b3c +* no zpools in service for NTP zones on sleds: 00320471-945d-413c-85e7-03e091a70b3c +* discretionary zone placement waiting for NTP zones on sleds: 00320471-945d-413c-85e7-03e091a70b3c > blueprint-show 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 @@ -277,12 +277,12 @@ parent: dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 PENDING MGS-MANAGED UPDATES: 0 -Planning report for blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1: -Chicken switches: +planning report for blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1: +chicken switches: add zones with mupdate override: false -* No zpools in service for NTP zones on sleds: 00320471-945d-413c-85e7-03e091a70b3c -* Discretionary zone placement waiting for NTP zones on sleds: 00320471-945d-413c-85e7-03e091a70b3c +* no zpools in service for NTP zones on sleds: 00320471-945d-413c-85e7-03e091a70b3c +* discretionary zone placement waiting for NTP zones on sleds: 00320471-945d-413c-85e7-03e091a70b3c diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout index 715829099e7..9d31dfaea39 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-example-stdout @@ -402,7 +402,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. +empty planning report for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. @@ -521,7 +521,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. +empty planning report for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. @@ -553,16 +553,16 @@ INFO skipping noop image source check for all sleds, reason: no target release i WARN cannot issue more MGS-driven updates (no current artifacts) INFO some zones not yet up-to-date, sled_id: 89d02b1b-478c-401a-8e28-7a26f74fa41b, zones_currently_updating: [ZoneCurrentlyUpdating { zone_id: b3c9c041-d2f0-4767-bdaf-0e52e9d7a013 (service), zone_kind: InternalNtp, reason: MissingInInventory { bp_image_source: InstallDataset } }] generated blueprint 86db3308-f817-4626-8838-4085949a6a41 based on parent blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a -Planning report for blueprint 86db3308-f817-4626-8838-4085949a6a41: -Chicken switches: +planning report for blueprint 86db3308-f817-4626-8838-4085949a6a41: +chicken switches: add zones with mupdate override: false -* Discretionary zone placement waiting for NTP zones on sleds: 89d02b1b-478c-401a-8e28-7a26f74fa41b -* Missing NTP zone on sled 89d02b1b-478c-401a-8e28-7a26f74fa41b -* Only placed 0/1 desired clickhouse zones -* Only placed 0/3 desired crucible_pantry zones -* Only placed 0/3 desired internal_dns zones -* Only placed 0/3 desired nexus zones +* discretionary zone placement waiting for NTP zones on sleds: 89d02b1b-478c-401a-8e28-7a26f74fa41b +* missing NTP zone on sled 89d02b1b-478c-401a-8e28-7a26f74fa41b +* only placed 0/1 desired clickhouse zones +* only placed 0/3 desired crucible_pantry zones +* only placed 0/3 desired internal_dns zones +* only placed 0/3 desired nexus zones > blueprint-list @@ -1035,7 +1035,7 @@ parent: 02697f74-b14a-4418-90f0-c28b2a3a6aa9 PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. +empty planning report for blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a. @@ -1582,7 +1582,7 @@ INTERNAL DNS STATUS INFO skipping noop image source check for all sleds, reason: no target release is currently set WARN cannot issue more MGS-driven updates (no current artifacts) generated blueprint 86db3308-f817-4626-8838-4085949a6a41 based on parent blueprint ade5749d-bdf3-4fab-a8ae-00bea01b3a5a -Empty planning report for blueprint 86db3308-f817-4626-8838-4085949a6a41. +empty planning report for blueprint 86db3308-f817-4626-8838-4085949a6a41. > blueprint-diff ade5749d-bdf3-4fab-a8ae-00bea01b3a5a latest 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 abaaa783a9d..fea4459e751 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 @@ -334,7 +334,7 @@ parent: 06c88262-f435-410e-ba98-101bed41ec27 PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint 3f00b694-1b16-4aaa-8f78-e6b3a527b434. +empty planning report for blueprint 3f00b694-1b16-4aaa-8f78-e6b3a527b434. @@ -1025,7 +1025,7 @@ parent: 3f00b694-1b16-4aaa-8f78-e6b3a527b434 PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0. +empty planning report for blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0. @@ -1034,13 +1034,13 @@ Empty planning report for blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0. INFO skipping noop image source check for all sleds, reason: no target release is currently set WARN cannot issue more MGS-driven updates (no current artifacts) generated blueprint 9c998c1d-1a7b-440a-ae0c-40f781dea6e2 based on parent blueprint 366b0b68-d80e-4bc1-abd3-dc69837847e0 -Planning report for blueprint 9c998c1d-1a7b-440a-ae0c-40f781dea6e2: -Chicken switches: +planning report for blueprint 9c998c1d-1a7b-440a-ae0c-40f781dea6e2: +chicken switches: add zones with mupdate override: false -* Discretionary zones placed: +* discretionary zones placed: * 1 zone on sled 711ac7f8-d19e-4572-bdb9-e9b50f6e362a: external_dns -* Zone updates waiting on discretionary zones +* zone updates waiting on discretionary zones > blueprint-diff 366b0b68-d80e-4bc1-abd3-dc69837847e0 9c998c1d-1a7b-440a-ae0c-40f781dea6e2 @@ -1728,13 +1728,13 @@ parent: 366b0b68-d80e-4bc1-abd3-dc69837847e0 PENDING MGS-MANAGED UPDATES: 0 -Planning report for blueprint 9c998c1d-1a7b-440a-ae0c-40f781dea6e2: -Chicken switches: +planning report for blueprint 9c998c1d-1a7b-440a-ae0c-40f781dea6e2: +chicken switches: add zones with mupdate override: false -* Discretionary zones placed: +* discretionary zones placed: * 1 zone on sled 711ac7f8-d19e-4572-bdb9-e9b50f6e362a: external_dns -* Zone updates waiting on discretionary zones +* zone updates waiting on discretionary zones 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 34f8fa181e9..8f759dc2f04 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 @@ -332,7 +332,7 @@ parent: 184f10b3-61cb-41ef-9b93-3489b2bac559 PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21. +empty planning report for blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21. @@ -1048,13 +1048,13 @@ external DNS: INFO skipping noop image source check for all sleds, reason: no target release is currently set WARN cannot issue more MGS-driven updates (no current artifacts) generated blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 based on parent blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 -Planning report for blueprint af934083-59b5-4bf6-8966-6fb5292c29e1: -Chicken switches: +planning report for blueprint af934083-59b5-4bf6-8966-6fb5292c29e1: +chicken switches: add zones with mupdate override: false -* Discretionary zones placed: +* discretionary zones placed: * 1 zone on sled 2b8f0cb3-0295-4b3c-bc58-4fe88b57112c: internal_dns -* Zone updates waiting on discretionary zones +* zone updates waiting on discretionary zones > blueprint-diff 58d5e830-0884-47d8-a7cd-b2b3751adeb4 af934083-59b5-4bf6-8966-6fb5292c29e1 diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout index 53c6612b927..174b44d1c93 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-host-phase-2-source-stdout @@ -712,7 +712,7 @@ parent: 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4. +empty planning report for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4. @@ -1422,7 +1422,7 @@ parent: af934083-59b5-4bf6-8966-6fb5292c29e1 PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint df06bb57-ad42-4431-9206-abff322896c7. +empty planning report for blueprint df06bb57-ad42-4431-9206-abff322896c7. diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout index 495974ed41d..b537180fad3 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout @@ -498,12 +498,12 @@ WARN skipped noop image source check on sled, sled_id: 2b8f0cb3-0295-4b3c-bc58-4 INFO skipped noop image source check on sled, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, reason: remove_mupdate_override is set in the blueprint (6123eac1-ec5b-42ba-b73f-9845105a9971) INFO skipped noop image source check on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, reason: remove_mupdate_override is set in the blueprint (203fa72c-85c1-466a-8ed3-338ee029530d) generated blueprint a5a8f242-ffa5-473c-8efd-2acf2dc0b736 based on parent blueprint d60afc57-f15d-476c-bd0f-b1071e2bb976 -Planning report for blueprint a5a8f242-ffa5-473c-8efd-2acf2dc0b736: -Chicken switches: +planning report for blueprint a5a8f242-ffa5-473c-8efd-2acf2dc0b736: +chicken switches: add zones with mupdate override: false -* Waiting on MUPdate overrides -* Zone updates waiting on MUPdate overrides +* waiting on MUPdate overrides +* zone updates waiting on MUPdate overrides @@ -872,12 +872,12 @@ WARN skipped noop image source check on sled, sled_id: 2b8f0cb3-0295-4b3c-bc58-4 INFO skipped noop image source check on sled, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, reason: sled not found in inventory INFO skipped noop image source check on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, reason: remove_mupdate_override is set in the blueprint (203fa72c-85c1-466a-8ed3-338ee029530d) generated blueprint 626487fa-7139-45ec-8416-902271fc730b based on parent blueprint a5a8f242-ffa5-473c-8efd-2acf2dc0b736 -Planning report for blueprint 626487fa-7139-45ec-8416-902271fc730b: -Chicken switches: +planning report for blueprint 626487fa-7139-45ec-8416-902271fc730b: +chicken switches: add zones with mupdate override: false -* Waiting on MUPdate overrides -* Zone updates waiting on MUPdate overrides +* waiting on MUPdate overrides +* zone updates waiting on MUPdate overrides > blueprint-diff latest @@ -1092,13 +1092,13 @@ WARN skipped noop image source check on sled, sled_id: 2b8f0cb3-0295-4b3c-bc58-4 INFO performed noop image source checks on sled, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, num_total: 6, num_already_artifact: 0, num_eligible: 6, num_ineligible: 0 INFO skipped noop image source check on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, reason: remove_mupdate_override is set in the blueprint (1c0ce176-6dc8-4a90-adea-d4a8000751da) generated blueprint c1a0d242-9160-40f4-96ae-61f8f40a0b1b based on parent blueprint 626487fa-7139-45ec-8416-902271fc730b -Planning report for blueprint c1a0d242-9160-40f4-96ae-61f8f40a0b1b: -Chicken switches: +planning report for blueprint c1a0d242-9160-40f4-96ae-61f8f40a0b1b: +chicken switches: add zones with mupdate override: false -* Noop converting 6/6 install-dataset zones to artifact store on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 -* Waiting on MUPdate overrides -* Zone updates waiting on MUPdate overrides +* noop converting 6/6 install-dataset zones to artifact store on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 +* waiting on MUPdate overrides +* zone updates waiting on MUPdate overrides @@ -1376,13 +1376,13 @@ INFO install dataset artifact hash not found in TUF repo, ignoring for noop chec INFO install dataset artifact hash not found in TUF repo, ignoring for noop checks, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, zone_id: f55647d4-5500-4ad3-893a-df45bd50d622, kind: crucible, file_name: crucible.tar.gz, expected_hash: 866f6a7c2e51c056fb722b5113e80181cc9cd8b712a0d3dbf1edc4ce29e5229e INFO install dataset artifact hash not found in TUF repo, ignoring for noop checks, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, zone_id: f6ec9c67-946a-4da3-98d5-581f72ce8bf0, kind: external_dns, file_name: external_dns.tar.gz, expected_hash: f282c45771429f7bebf71f0cc668521066db57c6bb07fcfccdfb44825d3d930f generated blueprint afb09faf-a586-4483-9289-04d4f1d8ba23 based on parent blueprint c1a0d242-9160-40f4-96ae-61f8f40a0b1b -Planning report for blueprint afb09faf-a586-4483-9289-04d4f1d8ba23: -Chicken switches: +planning report for blueprint afb09faf-a586-4483-9289-04d4f1d8ba23: +chicken switches: add zones with mupdate override: false -* Skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts -* Waiting on MUPdate overrides -* Zone updates waiting on MUPdate overrides +* skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts +* waiting on MUPdate overrides +* zone updates waiting on MUPdate overrides > blueprint-show latest @@ -1550,13 +1550,13 @@ parent: c1a0d242-9160-40f4-96ae-61f8f40a0b1b PENDING MGS-MANAGED UPDATES: 0 -Planning report for blueprint afb09faf-a586-4483-9289-04d4f1d8ba23: -Chicken switches: +planning report for blueprint afb09faf-a586-4483-9289-04d4f1d8ba23: +chicken switches: add zones with mupdate override: false -* Skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts -* Waiting on MUPdate overrides -* Zone updates waiting on MUPdate overrides +* skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts +* waiting on MUPdate overrides +* zone updates waiting on MUPdate overrides @@ -1787,14 +1787,14 @@ WARN skipped noop image source check on sled, sled_id: 2b8f0cb3-0295-4b3c-bc58-4 INFO performed noop image source checks on sled, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, num_total: 6, num_already_artifact: 6, num_eligible: 0, num_ineligible: 0 INFO performed noop image source checks on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, num_total: 6, num_already_artifact: 0, num_eligible: 6, num_ineligible: 0 generated blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700 based on parent blueprint afb09faf-a586-4483-9289-04d4f1d8ba23 -Planning report for blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700: -Chicken switches: +planning report for blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700: +chicken switches: add zones with mupdate override: false -* Skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts -* Noop converting 6/6 install-dataset zones to artifact store on sled d81c6a84-79b8-4958-ae41-ea46c9b19763 -* Waiting on MUPdate overrides -* Zone updates waiting on MUPdate overrides +* skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts +* noop converting 6/6 install-dataset zones to artifact store on sled d81c6a84-79b8-4958-ae41-ea46c9b19763 +* waiting on MUPdate overrides +* zone updates waiting on MUPdate overrides > blueprint-show latest @@ -1962,14 +1962,14 @@ parent: afb09faf-a586-4483-9289-04d4f1d8ba23 PENDING MGS-MANAGED UPDATES: 0 -Planning report for blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700: -Chicken switches: +planning report for blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700: +chicken switches: add zones with mupdate override: false -* Skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts -* Noop converting 6/6 install-dataset zones to artifact store on sled d81c6a84-79b8-4958-ae41-ea46c9b19763 -* Waiting on MUPdate overrides -* Zone updates waiting on MUPdate overrides +* skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts +* noop converting 6/6 install-dataset zones to artifact store on sled d81c6a84-79b8-4958-ae41-ea46c9b19763 +* waiting on MUPdate overrides +* zone updates waiting on MUPdate overrides @@ -2184,12 +2184,12 @@ INFO skipping board for MGS-driven update, serial_number: serial2, part_number: INFO ran out of boards for MGS-driven update INFO some zones not yet up-to-date, sled_id: 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6, zones_currently_updating: [ZoneCurrentlyUpdating { zone_id: 0c71b3b2-6ceb-4e8f-b020-b08675e83038 (service), zone_kind: Nexus, reason: ImageSourceMismatch { bp_image_source: Artifact { version: Available { version: ArtifactVersion("1.0.0") }, hash: ArtifactHash("0e32b4a3e5d3668bb1d6a16fb06b74dc60b973fa479dcee0aae3adbb52bf1388") }, inv_image_source: InstallDataset } }, ZoneCurrentlyUpdating { zone_id: 427ec88f-f467-42fa-9bbb-66a91a36103c (service), zone_kind: InternalDns, reason: ImageSourceMismatch { bp_image_source: Artifact { version: Available { version: ArtifactVersion("1.0.0") }, hash: ArtifactHash("ffbf1373f7ee08dddd74c53ed2a94e7c4c572a982d3a9bc94000c6956b700c6a") }, inv_image_source: InstallDataset } }, ZoneCurrentlyUpdating { zone_id: 5199c033-4cf9-4ab6-8ae7-566bd7606363 (service), zone_kind: Crucible, reason: ImageSourceMismatch { bp_image_source: Artifact { version: Available { version: ArtifactVersion("1.0.0") }, hash: ArtifactHash("6f17cf65fb5a5bec5542dd07c03cd0acc01e59130f02c532c8d848ecae810047") }, inv_image_source: InstallDataset } }, ZoneCurrentlyUpdating { zone_id: 6444f8a5-6465-4f0b-a549-1993c113569c (service), zone_kind: InternalNtp, reason: ImageSourceMismatch { bp_image_source: Artifact { version: Available { version: ArtifactVersion("1.0.0") }, hash: ArtifactHash("67593d686ed04a1709f93972b71f4ebc148a9362120f65d239943e814a9a7439") }, inv_image_source: InstallDataset } }, ZoneCurrentlyUpdating { zone_id: 803bfb63-c246-41db-b0da-d3b87ddfc63d (service), zone_kind: ExternalDns, reason: ImageSourceMismatch { bp_image_source: Artifact { version: Available { version: ArtifactVersion("1.0.0") }, hash: ArtifactHash("ccca13ed19b8731f9adaf0d6203b02ea3b9ede4fa426b9fac0a07ce95440046d") }, inv_image_source: InstallDataset } }, ZoneCurrentlyUpdating { zone_id: ba4994a8-23f9-4b1a-a84f-a08d74591389 (service), zone_kind: CruciblePantry, reason: ImageSourceMismatch { bp_image_source: Artifact { version: Available { version: ArtifactVersion("1.0.0") }, hash: ArtifactHash("21f0ada306859c23917361f2e0b9235806c32607ec689c7e8cf16bb898bc5a02") }, inv_image_source: InstallDataset } }] generated blueprint 8f2d1f39-7c88-4701-aa43-56bf281b28c1 based on parent blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700 -Planning report for blueprint 8f2d1f39-7c88-4701-aa43-56bf281b28c1: -Chicken switches: +planning report for blueprint 8f2d1f39-7c88-4701-aa43-56bf281b28c1: +chicken switches: add zones with mupdate override: false -* Skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts -* Skipping noop image source check on sled d81c6a84-79b8-4958-ae41-ea46c9b19763: all 6 zones are already from artifacts +* skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts +* skipping noop image source check on sled d81c6a84-79b8-4958-ae41-ea46c9b19763: all 6 zones are already from artifacts > blueprint-show latest @@ -2357,12 +2357,12 @@ parent: ce365dff-2cdb-4f35-a186-b15e20e1e700 PENDING MGS-MANAGED UPDATES: 0 -Planning report for blueprint 8f2d1f39-7c88-4701-aa43-56bf281b28c1: -Chicken switches: +planning report for blueprint 8f2d1f39-7c88-4701-aa43-56bf281b28c1: +chicken switches: add zones with mupdate override: false -* Skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts -* Skipping noop image source check on sled d81c6a84-79b8-4958-ae41-ea46c9b19763: all 6 zones are already from artifacts +* skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts +* skipping noop image source check on sled d81c6a84-79b8-4958-ae41-ea46c9b19763: all 6 zones are already from artifacts @@ -2538,14 +2538,14 @@ INFO skipped noop image source check on sled, sled_id: 98e6b7c2-2efa-41ca-b20a-0 INFO performed noop image source checks on sled, sled_id: c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b, num_total: 0, num_already_artifact: 0, num_eligible: 0, num_ineligible: 0 INFO performed noop image source checks on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, num_total: 6, num_already_artifact: 6, num_eligible: 0, num_ineligible: 0 generated blueprint 12d602a6-5ab4-487a-b94e-eb30cdf30300 based on parent blueprint 8f2d1f39-7c88-4701-aa43-56bf281b28c1 -Planning report for blueprint 12d602a6-5ab4-487a-b94e-eb30cdf30300: -Chicken switches: +planning report for blueprint 12d602a6-5ab4-487a-b94e-eb30cdf30300: +chicken switches: add zones with mupdate override: false -* Skipping noop image source check on sled c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b: all 0 zones are already from artifacts -* Skipping noop image source check on sled d81c6a84-79b8-4958-ae41-ea46c9b19763: all 6 zones are already from artifacts -* Waiting on MUPdate overrides -* Zone updates waiting on MUPdate overrides +* skipping noop image source check on sled c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b: all 0 zones are already from artifacts +* skipping noop image source check on sled d81c6a84-79b8-4958-ae41-ea46c9b19763: all 6 zones are already from artifacts +* waiting on MUPdate overrides +* zone updates waiting on MUPdate overrides > blueprint-diff latest @@ -2764,12 +2764,12 @@ INFO performed noop image source checks on sled, sled_id: c3bc4c6d-fdde-4fc4-849 INFO performed noop image source checks on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, num_total: 6, num_already_artifact: 6, num_eligible: 0, num_ineligible: 0 INFO altered physical disks, sled_id: c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b, sled_edits: SledEditCounts { disks: EditCounts { added: 10, updated: 0, expunged: 0, removed: 0 }, datasets: EditCounts { added: 20, updated: 0, expunged: 0, removed: 0 }, zones: EditCounts { added: 0, updated: 0, expunged: 0, removed: 0 } } generated blueprint 61a93ea3-c872-48e0-aace-e86b0c52b839 based on parent blueprint 12d602a6-5ab4-487a-b94e-eb30cdf30300 -Planning report for blueprint 61a93ea3-c872-48e0-aace-e86b0c52b839: -* Skipping noop image source check on sled c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b: all 0 zones are already from artifacts -* Skipping noop image source check on sled d81c6a84-79b8-4958-ae41-ea46c9b19763: all 6 zones are already from artifacts -* Discretionary zone placement waiting for NTP zones on sleds: c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b -* Missing NTP zone on sled c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b -* Zone updates waiting on MUPdate overrides +planning report for blueprint 61a93ea3-c872-48e0-aace-e86b0c52b839: +* skipping noop image source check on sled c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b: all 0 zones are already from artifacts +* skipping noop image source check on sled d81c6a84-79b8-4958-ae41-ea46c9b19763: all 6 zones are already from artifacts +* discretionary zone placement waiting for NTP zones on sleds: c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b +* missing NTP zone on sled c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b +* zone updates waiting on MUPdate overrides > blueprint-diff latest 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 index 405e0211762..b6a09e0dd58 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout @@ -171,14 +171,14 @@ INFO install dataset artifact hash not found in TUF repo, ignoring for noop chec INFO skipped noop image source check on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, reason: remove_mupdate_override is set in the blueprint (ffffffff-ffff-ffff-ffff-ffffffffffff) INFO skipped noop image source check on sled, sled_id: e96e226f-4ed9-4c01-91b9-69a9cd076c9e, reason: sled not found in inventory generated blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 based on parent blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 -Planning report for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4: -Chicken switches: +planning report for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4: +chicken switches: add zones with mupdate override: false -* Noop converting 6/6 install-dataset zones to artifact store on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 -* Noop converting 5/6 install-dataset zones to artifact store on sled aff6c093-197d-42c5-ad80-9f10ba051a34 -* Waiting on MUPdate overrides -* Zone updates waiting on MUPdate overrides +* noop converting 6/6 install-dataset zones to artifact store on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 +* noop converting 5/6 install-dataset zones to artifact store on sled aff6c093-197d-42c5-ad80-9f10ba051a34 +* waiting on MUPdate overrides +* zone updates waiting on MUPdate overrides @@ -528,14 +528,14 @@ INFO install dataset artifact hash not found in TUF repo, ignoring for noop chec INFO skipped noop image source check on sled, sled_id: d81c6a84-79b8-4958-ae41-ea46c9b19763, reason: remove_mupdate_override is set in the blueprint (ffffffff-ffff-ffff-ffff-ffffffffffff) INFO performed noop image source checks on sled, sled_id: e96e226f-4ed9-4c01-91b9-69a9cd076c9e, num_total: 2, num_already_artifact: 0, num_eligible: 2, num_ineligible: 0 generated blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 based on parent blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 -Planning report for blueprint af934083-59b5-4bf6-8966-6fb5292c29e1: -Chicken switches: +planning report for blueprint af934083-59b5-4bf6-8966-6fb5292c29e1: +chicken switches: add zones with mupdate override: false -* Skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts -* Noop converting 2/2 install-dataset zones to artifact store on sled e96e226f-4ed9-4c01-91b9-69a9cd076c9e -* Waiting on MUPdate overrides -* Zone updates waiting on MUPdate overrides +* skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts +* noop converting 2/2 install-dataset zones to artifact store on sled e96e226f-4ed9-4c01-91b9-69a9cd076c9e +* waiting on MUPdate overrides +* zone updates waiting on MUPdate overrides diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout index d860e9d13a9..2d0265cb002 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-mgs-updates-stdout @@ -208,7 +208,7 @@ parent: 6ccc786b-17f1-4562-958f-5a7d9a5a15fd PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint ad97e762-7bf1-45a6-a98f-60afb7e491c0. +empty planning report for blueprint ad97e762-7bf1-45a6-a98f-60afb7e491c0. @@ -425,7 +425,7 @@ parent: ad97e762-7bf1-45a6-a98f-60afb7e491c0 sled 2 model2 serial2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 1.1.0 Sp { expected_active_version: ArtifactVersion("1.0.0"), expected_inactive_version: Version(ArtifactVersion("1.0.1")) } -Empty planning report for blueprint cca24b71-09b5-4042-9185-b33e9f2ebba0. +empty planning report for blueprint cca24b71-09b5-4042-9185-b33e9f2ebba0. @@ -972,7 +972,7 @@ parent: cca24b71-09b5-4042-9185-b33e9f2ebba0 sled 2 model2 serial2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 newest Sp { expected_active_version: ArtifactVersion("newer"), expected_inactive_version: Version(ArtifactVersion("older")) } -Empty planning report for blueprint 5bf974f3-81f9-455b-b24e-3099f765664c. +empty planning report for blueprint 5bf974f3-81f9-455b-b24e-3099f765664c. @@ -1523,7 +1523,7 @@ parent: 5bf974f3-81f9-455b-b24e-3099f765664c sled 2 model2 serial2 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 newest Sp { expected_active_version: ArtifactVersion("newer"), expected_inactive_version: Version(ArtifactVersion("older")) } -Empty planning report for blueprint 1b837a27-3be1-4fcb-8499-a921c839e1d0. +empty planning report for blueprint 1b837a27-3be1-4fcb-8499-a921c839e1d0. @@ -1904,7 +1904,7 @@ parent: 1b837a27-3be1-4fcb-8499-a921c839e1d0 sled 0 model0 serial0 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 three Sp { expected_active_version: ArtifactVersion("two"), expected_inactive_version: NoValidVersion } -Empty planning report for blueprint 3682a71b-c6ca-4b7e-8f84-16df80c85960. +empty planning report for blueprint 3682a71b-c6ca-4b7e-8f84-16df80c85960. diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout index db2c3523493..01fb9438c11 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-remove-mupdate-override-stdout @@ -277,7 +277,7 @@ parent: df06bb57-ad42-4431-9206-abff322896c7 PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba. +empty planning report for blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba. @@ -668,7 +668,7 @@ parent: afb09faf-a586-4483-9289-04d4f1d8ba23 PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700. +empty planning report for blueprint ce365dff-2cdb-4f35-a186-b15e20e1e700. diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout index 5cbfeaf1c21..f704a5c0af3 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-set-zone-images-stdout @@ -110,7 +110,7 @@ parent: 1b013011-2062-4b48-b544-a32b23bce83a PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint 971eeb12-1830-4fa0-a699-98ea0164505c. +empty planning report for blueprint 971eeb12-1830-4fa0-a699-98ea0164505c. @@ -228,7 +228,7 @@ parent: 9766ca20-38d4-4380-b005-e7c43c797e7c PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176. +empty planning report for blueprint f714e6ea-e85a-4d7d-93c2-a018744fe176. @@ -550,7 +550,7 @@ parent: bb128f06-a2e1-44c1-8874-4f789d0ff896 PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint d9c572a1-a68c-4945-b1ec-5389bd588fe9. +empty planning report for blueprint d9c572a1-a68c-4945-b1ec-5389bd588fe9. 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 4af0d90e822..c545d943bfd 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-target-release-stdout @@ -210,13 +210,13 @@ WARN cannot configure RoT update for board (missing sign in caboose from invento INFO configuring MGS-driven 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 MGS-driven updates, max: 1 generated blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 based on parent blueprint dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 -Planning report for blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1: -Chicken switches: +planning report for blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1: +chicken switches: add zones with mupdate override: false * 1 pending MGS update: * model0:serial0: Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: NoValidVersion } -* Zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) +* zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) > blueprint-diff dbcbd3d6-41ff-48ae-ac0b-1becc9b2fd21 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 @@ -392,13 +392,13 @@ INFO performed noop image source checks on sled, sled_id: d81c6a84-79b8-4958-ae4 INFO MGS-driven update not yet completed (will keep it), 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 MGS-driven updates, max: 1 generated blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 based on parent blueprint 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 -Planning report for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4: -Chicken switches: +planning report for blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4: +chicken switches: add zones with mupdate override: false * 1 pending MGS update: * model0:serial0: Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: NoValidVersion } -* Zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) +* zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) > blueprint-diff 8da82a8e-bf97-4fbd-8ddd-9f6462732cf1 58d5e830-0884-47d8-a7cd-b2b3751adeb4 @@ -579,13 +579,13 @@ WARN cannot configure RoT update for board (missing sign in caboose from invento INFO configuring MGS-driven update, artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: NoValidVersion, expected_active_version: 0.0.1, component: sp, sp_slot: 1, sp_type: Sled, serial_number: serial1, part_number: model1 INFO reached maximum number of pending MGS-driven updates, max: 1 generated blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 based on parent blueprint 58d5e830-0884-47d8-a7cd-b2b3751adeb4 -Planning report for blueprint af934083-59b5-4bf6-8966-6fb5292c29e1: -Chicken switches: +planning report for blueprint af934083-59b5-4bf6-8966-6fb5292c29e1: +chicken switches: add zones with mupdate override: false * 1 pending MGS update: * model1:serial1: Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: NoValidVersion } -* Zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) +* zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) > blueprint-diff 58d5e830-0884-47d8-a7cd-b2b3751adeb4 af934083-59b5-4bf6-8966-6fb5292c29e1 @@ -776,13 +776,13 @@ WARN cannot configure RoT update for board (missing sign in caboose from invento INFO configuring MGS-driven update, artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: Version(ArtifactVersion("0.5.0")), expected_active_version: 0.0.1, component: sp, sp_slot: 1, sp_type: Sled, serial_number: serial1, part_number: model1 INFO reached maximum number of pending MGS-driven updates, max: 1 generated blueprint df06bb57-ad42-4431-9206-abff322896c7 based on parent blueprint af934083-59b5-4bf6-8966-6fb5292c29e1 -Planning report for blueprint df06bb57-ad42-4431-9206-abff322896c7: -Chicken switches: +planning report for blueprint df06bb57-ad42-4431-9206-abff322896c7: +chicken switches: add zones with mupdate override: false * 1 pending MGS update: * model1:serial1: Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: Version(ArtifactVersion("0.5.0")) } -* Zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) +* zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) > blueprint-diff af934083-59b5-4bf6-8966-6fb5292c29e1 df06bb57-ad42-4431-9206-abff322896c7 @@ -972,13 +972,13 @@ WARN cannot configure RoT update for board (missing sign in caboose from invento INFO configuring MGS-driven update, artifact_version: 1.0.0, artifact_hash: 7e6667e646ad001b54c8365a3d309c03f89c59102723d38d01697ee8079fe670, expected_inactive_version: NoValidVersion, expected_active_version: 0.0.1, component: sp, sp_slot: 2, sp_type: Sled, serial_number: serial2, part_number: model2 INFO ran out of boards for MGS-driven update generated blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba based on parent blueprint df06bb57-ad42-4431-9206-abff322896c7 -Planning report for blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba: -Chicken switches: +planning report for blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba: +chicken switches: add zones with mupdate override: false * 1 pending MGS update: * model2:serial2: Sp { expected_active_version: ArtifactVersion("0.0.1"), expected_inactive_version: NoValidVersion } -* Zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) +* zone updates waiting on pending MGS updates (RoT / SP / Host OS / etc.) > blueprint-diff df06bb57-ad42-4431-9206-abff322896c7 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba @@ -1168,8 +1168,8 @@ WARN cannot configure RoT update for board (missing sign in caboose from invento INFO skipping board for MGS-driven update, serial_number: serial1, part_number: model1 INFO ran out of boards for MGS-driven update generated blueprint 9034c710-3e57-45f3-99e5-4316145e87ac based on parent blueprint 7f976e0d-d2a5-4eeb-9e82-c82bc2824aba -Planning report for blueprint 9034c710-3e57-45f3-99e5-4316145e87ac: -Chicken switches: +planning report for blueprint 9034c710-3e57-45f3-99e5-4316145e87ac: +chicken switches: add zones with mupdate override: false * 1 out-of-date zone updated in-place: diff --git a/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt b/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt index ad348404f72..15d2c2d6a77 100644 --- a/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt +++ b/nexus/reconfigurator/planning/tests/output/example_builder_zone_counts_blueprint.txt @@ -534,5 +534,5 @@ parent: e35b2fdd-354d-48d9-acb5-703b2c269a54 PENDING MGS-MANAGED UPDATES: 0 -Empty planning report for blueprint 4a0b8410-b14f-41e7-85e7-3c0fe7050ccc. +empty planning report for blueprint 4a0b8410-b14f-41e7-85e7-3c0fe7050ccc. diff --git a/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt b/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt index 2fd2c97679d..93d346b3170 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_decommissions_sleds_bp2.txt @@ -325,12 +325,12 @@ parent: 516e80a3-b362-4fac-bd3c-4559717120dd PENDING MGS-MANAGED UPDATES: 0 -Planning report for blueprint 1ac2d88f-27dd-4506-8585-6b2be832528e: -Chicken switches: +planning report for blueprint 1ac2d88f-27dd-4506-8585-6b2be832528e: +chicken switches: add zones with mupdate override: false -* Discretionary zones placed: +* discretionary zones placed: * 2 zones on sled d67ce8f0-a691-4010-b414-420d82e80527: crucible_pantry, nexus * 2 zones on sled fefcf4cf-f7e7-46b3-b629-058526ce440e: clickhouse, internal_dns -* Zone updates waiting on discretionary zones +* zone updates waiting on discretionary zones diff --git a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt index a82490611cb..77c19780bed 100644 --- a/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt +++ b/nexus/reconfigurator/planning/tests/output/planner_nonprovisionable_bp2.txt @@ -513,12 +513,12 @@ parent: 4d4e6c38-cd95-4c4e-8f45-6af4d686964b PENDING MGS-MANAGED UPDATES: 0 -Planning report for blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6: -Chicken switches: +planning report for blueprint 9f71f5d3-a272-4382-9154-6ea2e171a6c6: +chicken switches: add zones with mupdate override: false -* Discretionary zones placed: +* discretionary zones placed: * 3 zones on sled 75bc286f-2b4b-482c-9431-59272af529da: nexus, nexus, nexus * 3 zones on sled affab35f-600a-4109-8ea0-34a067a4e0bc: nexus, nexus, nexus -* Zone updates waiting on discretionary zones +* zone updates waiting on discretionary zones diff --git a/nexus/types/src/deployment/planning_report.rs b/nexus/types/src/deployment/planning_report.rs index 0e290cf394e..64780350569 100644 --- a/nexus/types/src/deployment/planning_report.rs +++ b/nexus/types/src/deployment/planning_report.rs @@ -97,7 +97,7 @@ impl fmt::Display for PlanningReport { if self.is_empty() { writeln!( f, - "Empty planning report for blueprint {}.", + "empty planning report for blueprint {}.", self.blueprint_id, )?; } else { @@ -112,11 +112,11 @@ impl fmt::Display for PlanningReport { zone_updates, cockroachdb_settings, } = self; - writeln!(f, "Planning report for blueprint {blueprint_id}:")?; + writeln!(f, "planning report for blueprint {blueprint_id}:")?; if *chicken_switches != PlannerChickenSwitches::default() { writeln!( f, - "Chicken switches:\n{}", + "chicken switches:\n{}", chicken_switches.display() )?; } @@ -285,14 +285,14 @@ impl fmt::Display for PlanningNoopImageSourceStepReport { if *no_target_release { return writeln!( f, - "* Skipping noop image source check for all sleds (no current TUF repo)", + "* skipping noop image source check for all sleds (no current TUF repo)", ); } for (sled_id, reason) in skipped_sleds.iter() { writeln!( f, - "* Skipping noop image source check on sled {sled_id}: {reason}" + "* skipping noop image source check on sled {sled_id}: {reason}" )?; } @@ -304,7 +304,7 @@ impl fmt::Display for PlanningNoopImageSourceStepReport { if *num_eligible > 0 && *num_dataset > 0 { writeln!( f, - "* Noop converting {num_eligible}/{num_dataset} install-dataset zones \ + "* noop converting {num_eligible}/{num_dataset} install-dataset zones \ to artifact store on sled {sled_id}", )?; } @@ -605,13 +605,13 @@ impl fmt::Display for PlanningAddStepReport { } = self; if let Some(waiting_on) = waiting_on { - writeln!(f, "* Waiting on {}", waiting_on.as_str())?; + writeln!(f, "* waiting on {}", waiting_on.as_str())?; } if !sleds_without_ntp_zones_in_inventory.is_empty() { writeln!( f, - "* Waiting for NTP zones to appear in inventory on sleds: {}", + "* waiting for NTP zones to appear in inventory on sleds: {}", sleds_without_ntp_zones_in_inventory .iter() .map(|sled_id| format!("{sled_id}")) @@ -623,7 +623,7 @@ impl fmt::Display for PlanningAddStepReport { if !sleds_without_zpools_for_ntp_zones.is_empty() { writeln!( f, - "* No zpools in service for NTP zones on sleds: {}", + "* no zpools in service for NTP zones on sleds: {}", sleds_without_zpools_for_ntp_zones .iter() .map(|sled_id| format!("{sled_id}")) @@ -635,7 +635,7 @@ impl fmt::Display for PlanningAddStepReport { if !sleds_waiting_for_ntp_zone.is_empty() { writeln!( f, - "* Discretionary zone placement waiting for NTP zones on sleds: {}", + "* discretionary zone placement waiting for NTP zones on sleds: {}", sleds_waiting_for_ntp_zone .iter() .map(|sled_id| format!("{sled_id}")) @@ -647,7 +647,7 @@ impl fmt::Display for PlanningAddStepReport { if !sleds_getting_ntp_and_discretionary_zones.is_empty() { writeln!( f, - "* Sleds getting NTP zones and which have other services already, \ + "* sleds getting NTP zones and which have other services already, \ making them eligible for discretionary zones: {}", sleds_getting_ntp_and_discretionary_zones .iter() @@ -658,14 +658,14 @@ impl fmt::Display for PlanningAddStepReport { } for sled_id in sleds_missing_ntp_zone { - writeln!(f, "* Missing NTP zone on sled {sled_id}",)?; + writeln!(f, "* missing NTP zone on sled {sled_id}",)?; } for (sled_id, zpools) in sleds_missing_crucible_zone { for zpool_id in zpools { writeln!( f, - "* Missing Crucible zone for sled {sled_id}, zpool {zpool_id}", + "* missing Crucible zone for sled {sled_id}, zpool {zpool_id}", )?; } } @@ -675,12 +675,12 @@ impl fmt::Display for PlanningAddStepReport { { writeln!( f, - "* Only placed {placed}/{wanted_to_place} desired {kind} zones" + "* only placed {placed}/{wanted_to_place} desired {kind} zones" )?; } if !discretionary_zones_placed.is_empty() { - writeln!(f, "* Discretionary zones placed:")?; + writeln!(f, "* discretionary zones placed:")?; for (sled_id, kinds) in discretionary_zones_placed.iter() { let (n, s) = plural_vec(kinds); writeln!( @@ -800,7 +800,7 @@ impl fmt::Display for PlanningZoneUpdatesStepReport { } = self; if let Some(waiting_on) = waiting_on { - writeln!(f, "* Zone updates waiting on {}", waiting_on.as_str())?; + writeln!(f, "* zone updates waiting on {}", waiting_on.as_str())?; } if !expunged_zones.is_empty() { @@ -975,7 +975,7 @@ impl fmt::Display for PlanningCockroachdbSettingsStepReport { self; writeln!( f, - "* Will ensure cockroachdb setting: {preserve_downgrade}" + "* will ensure cockroachdb setting: {preserve_downgrade}" )?; } Ok(()) From d02feafefdbc532673989984e906a9f3a236017d Mon Sep 17 00:00:00 2001 From: Alex Plotnick Date: Wed, 13 Aug 2025 15:08:27 -0600 Subject: [PATCH 10/10] More MUPdate info in `add` step report --- .../output/cmds-mupdate-update-flow-stdout | 10 ++++++++ .../output/cmds-noop-image-source-stdout | 2 ++ nexus/reconfigurator/planning/src/planner.rs | 22 ++++++++++-------- nexus/types/src/deployment/planning_report.rs | 23 +++++++++++++++++++ openapi/nexus-internal.json | 10 ++++++++ 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout b/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout index b537180fad3..a52e4f53fb1 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-mupdate-update-flow-stdout @@ -503,6 +503,7 @@ chicken switches: add zones with mupdate override: false * waiting on MUPdate overrides +* MUPdate overrides exist * zone updates waiting on MUPdate overrides @@ -877,6 +878,7 @@ chicken switches: add zones with mupdate override: false * waiting on MUPdate overrides +* MUPdate overrides exist * zone updates waiting on MUPdate overrides @@ -1098,6 +1100,7 @@ chicken switches: * noop converting 6/6 install-dataset zones to artifact store on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 * waiting on MUPdate overrides +* MUPdate overrides exist * zone updates waiting on MUPdate overrides @@ -1382,6 +1385,7 @@ chicken switches: * skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts * waiting on MUPdate overrides +* MUPdate overrides exist * zone updates waiting on MUPdate overrides @@ -1556,6 +1560,7 @@ chicken switches: * skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts * waiting on MUPdate overrides +* MUPdate overrides exist * zone updates waiting on MUPdate overrides @@ -1794,6 +1799,7 @@ chicken switches: * skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts * noop converting 6/6 install-dataset zones to artifact store on sled d81c6a84-79b8-4958-ae41-ea46c9b19763 * waiting on MUPdate overrides +* MUPdate overrides exist * zone updates waiting on MUPdate overrides @@ -1969,6 +1975,7 @@ chicken switches: * skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts * noop converting 6/6 install-dataset zones to artifact store on sled d81c6a84-79b8-4958-ae41-ea46c9b19763 * waiting on MUPdate overrides +* MUPdate overrides exist * zone updates waiting on MUPdate overrides @@ -2545,6 +2552,7 @@ chicken switches: * skipping noop image source check on sled c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b: all 0 zones are already from artifacts * skipping noop image source check on sled d81c6a84-79b8-4958-ae41-ea46c9b19763: all 6 zones are already from artifacts * waiting on MUPdate overrides +* MUPdate overrides exist * zone updates waiting on MUPdate overrides @@ -2767,6 +2775,8 @@ generated blueprint 61a93ea3-c872-48e0-aace-e86b0c52b839 based on parent bluepri planning report for blueprint 61a93ea3-c872-48e0-aace-e86b0c52b839: * skipping noop image source check on sled c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b: all 0 zones are already from artifacts * skipping noop image source check on sled d81c6a84-79b8-4958-ae41-ea46c9b19763: all 6 zones are already from artifacts +* MUPdate overrides exist +* adding zones despite MUPdate override, as specified by the `add_zones_with_mupdate_override` chicken switch * discretionary zone placement waiting for NTP zones on sleds: c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b * missing NTP zone on sled c3bc4c6d-fdde-4fc4-8493-89d2a1e5ee6b * zone updates waiting on MUPdate overrides 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 index b6a09e0dd58..08e6ad39d07 100644 --- a/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout +++ b/dev-tools/reconfigurator-cli/tests/output/cmds-noop-image-source-stdout @@ -178,6 +178,7 @@ chicken switches: * noop converting 6/6 install-dataset zones to artifact store on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6 * noop converting 5/6 install-dataset zones to artifact store on sled aff6c093-197d-42c5-ad80-9f10ba051a34 * waiting on MUPdate overrides +* MUPdate overrides exist * zone updates waiting on MUPdate overrides @@ -535,6 +536,7 @@ chicken switches: * skipping noop image source check on sled 98e6b7c2-2efa-41ca-b20a-0a4d61102fe6: all 6 zones are already from artifacts * noop converting 2/2 install-dataset zones to artifact store on sled e96e226f-4ed9-4c01-91b9-69a9cd076c9e * waiting on MUPdate overrides +* MUPdate overrides exist * zone updates waiting on MUPdate overrides diff --git a/nexus/reconfigurator/planning/src/planner.rs b/nexus/reconfigurator/planning/src/planner.rs index c73bf8ef89c..4a38be0ce0c 100644 --- a/nexus/reconfigurator/planning/src/planner.rs +++ b/nexus/reconfigurator/planning/src/planner.rs @@ -193,15 +193,19 @@ impl<'a> Planner<'a> { }; // Likewise for zone additions, unless overridden with the chicken switch. - let add = if plan_mupdate_override_res.is_empty() - || self.input.chicken_switches().add_zones_with_mupdate_override - { - self.do_plan_add(&mgs_updates)? - } else { - PlanningAddStepReport::waiting_on( - ZoneAddWaitingOn::MupdateOverrides, - ) - }; + let has_mupdate_override = !plan_mupdate_override_res.is_empty(); + let add_zones_with_mupdate_override = + self.input.chicken_switches().add_zones_with_mupdate_override; + let mut add = + if !has_mupdate_override || add_zones_with_mupdate_override { + self.do_plan_add(&mgs_updates)? + } else { + PlanningAddStepReport::waiting_on( + ZoneAddWaitingOn::MupdateOverrides, + ) + }; + add.has_mupdate_override = has_mupdate_override; + add.add_zones_with_mupdate_override = add_zones_with_mupdate_override; let zone_updates = if add.any_discretionary_zones_placed() { // Do not update any zones if we've added any discretionary zones diff --git a/nexus/types/src/deployment/planning_report.rs b/nexus/types/src/deployment/planning_report.rs index 64780350569..9511b97b631 100644 --- a/nexus/types/src/deployment/planning_report.rs +++ b/nexus/types/src/deployment/planning_report.rs @@ -484,6 +484,12 @@ pub struct PlanningAddStepReport { /// What are we waiting on to start zone additions? pub waiting_on: Option, + /// Are there any outstanding MUPdate overrides? + pub has_mupdate_override: bool, + + /// The value of the homonymous chicken switch. + pub add_zones_with_mupdate_override: bool, + pub sleds_without_ntp_zones_in_inventory: BTreeSet, pub sleds_without_zpools_for_ntp_zones: BTreeSet, pub sleds_waiting_for_ntp_zone: BTreeSet, @@ -508,6 +514,8 @@ impl PlanningAddStepReport { pub fn new() -> Self { Self { waiting_on: None, + has_mupdate_override: true, + add_zones_with_mupdate_override: false, sleds_without_ntp_zones_in_inventory: BTreeSet::new(), sleds_without_zpools_for_ntp_zones: BTreeSet::new(), sleds_waiting_for_ntp_zone: BTreeSet::new(), @@ -593,6 +601,8 @@ impl fmt::Display for PlanningAddStepReport { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let Self { waiting_on, + has_mupdate_override, + add_zones_with_mupdate_override, sleds_without_ntp_zones_in_inventory, sleds_without_zpools_for_ntp_zones, sleds_waiting_for_ntp_zone, @@ -608,6 +618,19 @@ impl fmt::Display for PlanningAddStepReport { writeln!(f, "* waiting on {}", waiting_on.as_str())?; } + if *has_mupdate_override { + writeln!(f, "* MUPdate overrides exist")?; + } + + if *add_zones_with_mupdate_override { + writeln!( + f, + "* adding zones despite MUPdate override, \ + as specified by the `add_zones_with_mupdate_override` \ + chicken switch" + )?; + } + if !sleds_without_ntp_zones_in_inventory.is_empty() { writeln!( f, diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index aba6433b863..18eb840a169 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -6412,6 +6412,10 @@ "PlanningAddStepReport": { "type": "object", "properties": { + "add_zones_with_mupdate_override": { + "description": "The value of the homonymous chicken switch.", + "type": "boolean" + }, "discretionary_zones_placed": { "description": "Sled ID → kinds of discretionary zones placed there", "type": "object", @@ -6422,6 +6426,10 @@ } } }, + "has_mupdate_override": { + "description": "Are there any outstanding MUPdate overrides?", + "type": "boolean" + }, "out_of_eligible_sleds": { "description": "Discretionary zone kind → (placed, wanted to place)", "type": "object", @@ -6491,7 +6499,9 @@ } }, "required": [ + "add_zones_with_mupdate_override", "discretionary_zones_placed", + "has_mupdate_override", "out_of_eligible_sleds", "sleds_getting_ntp_and_discretionary_zones", "sleds_missing_crucible_zone",