From d568738a54a7f2011a1ec4f437449156e0ae5e67 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 13 Jan 2026 11:04:49 -0500 Subject: [PATCH 1/2] blueprint builder: track decommissioned sleds separately --- .../planning/src/blueprint_builder/builder.rs | 77 ++++++++++++++----- .../src/blueprint_editor/sled_editor.rs | 37 ++++----- 2 files changed, 73 insertions(+), 41 deletions(-) diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs index 895626882cd..4ad71a8491b 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs @@ -507,9 +507,16 @@ pub struct BlueprintBuilder<'a> { /// The ID that the completed blueprint will have new_blueprint_id: BlueprintUuid, + // `sled_editors` contains only commissioned sleds. We still carry forward + // any decommissioned sleds (that were either already decommissioned in + // `parent_blueprint`, or that we ourselves decommissioned), but they're + // stored separately in `decommissioned_sleds` to avoid the possibility that + // we try to make additional changes after decommissioning. + sled_editors: BTreeMap, + decommissioned_sleds: BTreeMap, + // These fields will become part of the final blueprint. See the // corresponding fields in `Blueprint`. - sled_editors: BTreeMap, cockroachdb_setting_preserve_downgrade: CockroachDbPreserveDowngrade, cockroachdb_fingerprint: String, target_release_minimum_generation: Generation, @@ -575,14 +582,28 @@ impl<'a> BlueprintBuilder<'a> { "parent_id" => parent_blueprint.id.to_string(), )); - // Convert our parent blueprint's sled configs into `SledEditor`s. + // Convert our parent blueprint's sled configs into `SledEditor` (for + // commissioned sleds) or `EditedSled`s (with zero edits made, for + // decommissioned sleds). let mut sled_editors = BTreeMap::new(); + let mut decommissioned_sleds = BTreeMap::new(); for (sled_id, sled_cfg) in &parent_blueprint.sleds { - let editor = SledEditor::for_existing(sled_cfg.clone()) - .with_context(|| { - format!("failed to construct SledEditor for sled {sled_id}") - })?; - sled_editors.insert(*sled_id, editor); + match sled_cfg.state { + SledState::Active => { + let editor = SledEditor::for_existing(sled_cfg.clone()) + .with_context(|| { + format!( + "failed to construct SledEditor \ + for sled {sled_id}" + ) + })?; + sled_editors.insert(*sled_id, editor); + } + SledState::Decommissioned => { + let edited = EditedSled::new(sled_cfg.clone()); + decommissioned_sleds.insert(*sled_id, edited); + } + } } // Copy the Oximeter read policy from our parent blueprint so we can @@ -601,6 +622,7 @@ impl<'a> BlueprintBuilder<'a> { oximeter_read_policy, new_blueprint_id: rng.next_blueprint(), sled_editors, + decommissioned_sleds, cockroachdb_setting_preserve_downgrade: parent_blueprint .cockroachdb_setting_preserve_downgrade, cockroachdb_fingerprint: parent_blueprint @@ -873,12 +895,17 @@ impl<'a> BlueprintBuilder<'a> { pub fn build(self, source: BlueprintSource) -> 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. + // Collect the Omicron zones config for all sleds, including any + // decommissioned sleds, which we continue to carry forward. let mut sleds = BTreeMap::new(); - for (sled_id, editor) in self.sled_editors { - let EditedSled { config, edit_counts, scalar_edits } = - editor.finalize(); + let commissioned_sleds = self + .sled_editors + .into_iter() + .map(|(id, editor)| (id, editor.finalize())); + let all_sleds = commissioned_sleds.chain(self.decommissioned_sleds); + + for (sled_id, edited_sled) in all_sleds { + let EditedSled { config, edit_counts, scalar_edits } = edited_sled; sleds.insert(sled_id, config); if edit_counts.has_nonzero_counts() || scalar_edits.has_edits() { let EditedSledScalarEdits { @@ -892,8 +919,10 @@ impl<'a> BlueprintBuilder<'a> { "disk_edits" => ?edit_counts.disks, "dataset_edits" => ?edit_counts.datasets, "zone_edits" => ?edit_counts.zones, - "debug_force_generation_bump" => debug_force_generation_bump, - "remove_mupdate_override_modified" => remove_mupdate_override, + "debug_force_generation_bump" => + debug_force_generation_bump, + "remove_mupdate_override_modified" => + remove_mupdate_override, ); } else { debug!( @@ -945,18 +974,30 @@ impl<'a> BlueprintBuilder<'a> { } /// Set the desired state of the given sled. + /// + /// Fails if the sled is not in the current set of commissioned sleds (i.e., + /// the sled doesn't exist or exists but is already decommissioned). pub fn set_sled_decommissioned( &mut self, sled_id: SledUuid, ) -> Result<(), Error> { - let editor = self.sled_editors.get_mut(&sled_id).ok_or_else(|| { + let editor = self.sled_editors.remove(&sled_id).ok_or_else(|| { Error::Planner(anyhow!( "tried to set sled state for unknown sled {sled_id}" )) })?; - editor - .decommission() - .map_err(|err| Error::SledEditError { sled_id, err }) + match editor.decommission() { + Ok(edited) => { + self.decommissioned_sleds.insert(sled_id, edited); + Ok(()) + } + Err((editor, err)) => { + // We failed to decommission the sled but already removed it + // from `sled_editors`; put it back before returning. + self.sled_editors.insert(sled_id, editor); + Err(Error::SledEditError { sled_id, err }) + } + } } /// This is a short human-readable string summarizing the changes reflected diff --git a/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs b/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs index 93c54f10a2b..cdd9c6ae2b1 100644 --- a/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs +++ b/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs @@ -210,8 +210,8 @@ impl SledEditor { } } - pub fn decommission(&mut self) -> Result<(), SledEditError> { - match &mut self.0 { + pub fn decommission(self) -> Result { + match self.0 { InnerSledEditor::Active(editor) => { // Decommissioning a sled is a one-way trip that has many // preconditions. We can't check all of them here (e.g., we @@ -219,29 +219,20 @@ impl SledEditor { // decommissioning, which is entirely outside the realm of // `SledEditor`. But we can do some basic checks: all of the // disks, datasets, and zones for this sled should be expunged. - editor.validate_decommisionable()?; - - // We can't take ownership of `editor` from the `&mut self` - // reference we have, and we need ownership to call - // `finalize()`. Steal the contents via `mem::swap()` with an - // empty editor. This isn't panic safe (i.e., if we panic - // between the `mem::swap()` and the reassignment to `self.0` - // below, we'll be left in the active state with an empty sled - // editor), but omicron in general is not panic safe and aborts - // on panic. Plus `finalize()` should never panic. - let mut stolen = ActiveSledEditor::new_empty(Ipv6Subnet::new( - Ipv6Addr::LOCALHOST, - )); - mem::swap(editor, &mut stolen); - - let mut finalized = stolen.finalize(); - finalized.config.state = SledState::Decommissioned; - self.0 = InnerSledEditor::Decommissioned(finalized); + match editor.validate_decommisionable() { + Ok(()) => { + let mut finalized = editor.finalize(); + finalized.config.state = SledState::Decommissioned; + Ok(finalized) + } + Err(err) => { + Err((Self(InnerSledEditor::Active(editor)), err)) + } + } } // If we're already decommissioned, there's nothing to do. - InnerSledEditor::Decommissioned(_) => (), + InnerSledEditor::Decommissioned(edited) => Ok(edited), } - Ok(()) } pub fn alloc_underlay_ip(&mut self) -> Result { @@ -530,7 +521,7 @@ pub(crate) struct EditedSled { } impl EditedSled { - fn new(config: BlueprintSledConfig) -> Self { + pub fn new(config: BlueprintSledConfig) -> Self { Self { config, edit_counts: SledEditCounts::zeroes(), From f6fbe536e4f51d7ed9f684ffbf0c04c56d1c4677 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Tue, 13 Jan 2026 11:38:30 -0500 Subject: [PATCH 2/2] remove InnerSledEditor; we only construct SledEditors for commissioned sleds --- .../planning/src/blueprint_builder/builder.rs | 43 +- .../src/blueprint_editor/sled_editor.rs | 397 ++---------------- 2 files changed, 54 insertions(+), 386 deletions(-) diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs index 4ad71a8491b..3b767634712 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs @@ -717,7 +717,7 @@ impl<'a> BlueprintBuilder<'a> { let any_sled_subnet = self .sled_editors .values() - .filter_map(|editor| editor.subnet()) + .map(|editor| editor.subnet()) .next() .ok_or(Error::RackSubnetUnknownNoSleds)?; let rack_subnet = ReservedRackSubnet::from_subnet(any_sled_subnet); @@ -753,12 +753,7 @@ impl<'a> BlueprintBuilder<'a> { pub fn current_commissioned_sleds( &self, ) -> impl Iterator + '_ { - self.sled_editors.iter().filter_map(|(sled_id, editor)| { - match editor.state() { - SledState::Active => Some(*sled_id), - SledState::Decommissioned => None, - } - }) + self.sled_editors.keys().copied() } /// Iterate over the in-service [`BlueprintZoneConfig`] instances on a @@ -965,12 +960,15 @@ impl<'a> BlueprintBuilder<'a> { &self, sled_id: SledUuid, ) -> Result { - let editor = self.sled_editors.get(&sled_id).ok_or_else(|| { - Error::Planner(anyhow!( + if self.sled_editors.contains_key(&sled_id) { + Ok(SledState::Active) + } else if self.decommissioned_sleds.contains_key(&sled_id) { + Ok(SledState::Decommissioned) + } else { + Err(Error::Planner(anyhow!( "tried to get sled state for unknown sled {sled_id}" - )) - })?; - Ok(editor.state()) + ))) + } } /// Set the desired state of the given sled. @@ -2115,9 +2113,8 @@ impl<'a> BlueprintBuilder<'a> { "tried to change image of zone on unknown sled {sled_id}" )) })?; - editor - .set_host_phase_2(host_phase_2) - .map_err(|err| Error::SledEditError { sled_id, err }) + editor.set_host_phase_2(host_phase_2); + Ok(()) } pub fn sled_set_host_phase_2_slot( @@ -2131,9 +2128,8 @@ impl<'a> BlueprintBuilder<'a> { "tried to change image of zone on unknown sled {sled_id}" )) })?; - editor - .set_host_phase_2_slot(slot, host_phase_2) - .map_err(|err| Error::SledEditError { sled_id, err }) + editor.set_host_phase_2_slot(slot, host_phase_2); + Ok(()) } /// Set the `remove_mupdate_override` field of the given sled. @@ -2147,9 +2143,8 @@ impl<'a> BlueprintBuilder<'a> { "tried to set sled state for unknown sled {sled_id}" )) })?; - editor - .set_remove_mupdate_override(remove_mupdate_override) - .map_err(|err| Error::SledEditError { sled_id, err }) + editor.set_remove_mupdate_override(remove_mupdate_override); + Ok(()) } fn sled_add_zone( @@ -2177,6 +2172,7 @@ impl<'a> BlueprintBuilder<'a> { })?; editor .alloc_underlay_ip() + .ok_or(SledEditError::OutOfUnderlayIps) .map_err(|err| Error::SledEditError { sled_id, err }) } @@ -2320,9 +2316,8 @@ impl<'a> BlueprintBuilder<'a> { "tried to force generation bump for unknown sled {sled_id}" )) })?; - editor - .debug_force_generation_bump() - .map_err(|err| Error::SledEditError { sled_id, err }) + editor.debug_force_generation_bump(); + Ok(()) } } diff --git a/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs b/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs index cdd9c6ae2b1..3e05e587a40 100644 --- a/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs +++ b/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs @@ -19,7 +19,6 @@ use host_phase_2::HostPhase2Editor; use iddqd::IdOrdMap; use iddqd::id_ord_map::Entry; use illumos_utils::zpool::ZpoolName; -use itertools::Either; use nexus_types::deployment::BlueprintDatasetConfig; use nexus_types::deployment::BlueprintDatasetDisposition; use nexus_types::deployment::BlueprintExpungedZoneAccessReason; @@ -48,7 +47,6 @@ use omicron_uuid_kinds::ZpoolUuid; use scalar::ScalarEditor; use sled_agent_types::inventory::MupdateOverrideBootInventory; use sled_agent_types::inventory::ZoneKind; -use std::iter; use std::mem; use std::net::Ipv6Addr; use underlay_ip_allocator::SledUnderlayIpAllocator; @@ -72,6 +70,8 @@ use self::zones::ZonesEditor; #[derive(Debug, thiserror::Error)] pub enum SledInputError { + #[error("attempted to construct SledEditor for decommissioned sled")] + DecommissionedSled, #[error(transparent)] MultipleDatasetsOfKind(#[from] MultipleDatasetsOfKind), } @@ -146,363 +146,7 @@ pub enum SledEditError { } #[derive(Debug)] -pub(crate) struct SledEditor(InnerSledEditor); - -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -enum InnerSledEditor { - // Internally, `SledEditor` has a variant for each variant of `SledState`, - // as the operations allowed in different states are substantially different - // (i.e., an active sled allows any edit; a decommissioned sled allows - // none). - Active(ActiveSledEditor), - Decommissioned(EditedSled), -} - -impl SledEditor { - pub fn for_existing( - config: BlueprintSledConfig, - ) -> Result { - let inner = match config.state { - SledState::Active => { - InnerSledEditor::Active(ActiveSledEditor::new(config)?) - } - SledState::Decommissioned => { - InnerSledEditor::Decommissioned(EditedSled::new(config)) - } - }; - Ok(Self(inner)) - } - - pub fn for_new_active(subnet: Ipv6Subnet) -> Self { - Self(InnerSledEditor::Active(ActiveSledEditor::new_empty(subnet))) - } - - pub fn finalize(self) -> EditedSled { - match self.0 { - InnerSledEditor::Active(editor) => editor.finalize(), - InnerSledEditor::Decommissioned(edited) => edited, - } - } - - pub fn state(&self) -> SledState { - match &self.0 { - InnerSledEditor::Active(_) => SledState::Active, - InnerSledEditor::Decommissioned(_) => SledState::Decommissioned, - } - } - - /// Returns the subnet of this sled if it is active, or `None` if it is - /// decommissioned. - pub fn subnet(&self) -> Option> { - match &self.0 { - InnerSledEditor::Active(active) => { - Some(active.underlay_ip_allocator.subnet()) - } - InnerSledEditor::Decommissioned(_) => None, - } - } - - pub fn edit_counts(&self) -> SledEditCounts { - match &self.0 { - InnerSledEditor::Active(editor) => editor.edit_counts(), - InnerSledEditor::Decommissioned(edited) => edited.edit_counts, - } - } - - pub fn decommission(self) -> Result { - match self.0 { - InnerSledEditor::Active(editor) => { - // Decommissioning a sled is a one-way trip that has many - // preconditions. We can't check all of them here (e.g., we - // should kick the sled out of trust quorum before - // decommissioning, which is entirely outside the realm of - // `SledEditor`. But we can do some basic checks: all of the - // disks, datasets, and zones for this sled should be expunged. - match editor.validate_decommisionable() { - Ok(()) => { - let mut finalized = editor.finalize(); - finalized.config.state = SledState::Decommissioned; - Ok(finalized) - } - Err(err) => { - Err((Self(InnerSledEditor::Active(editor)), err)) - } - } - } - // If we're already decommissioned, there's nothing to do. - InnerSledEditor::Decommissioned(edited) => Ok(edited), - } - } - - pub fn alloc_underlay_ip(&mut self) -> Result { - self.as_active_mut()? - .alloc_underlay_ip() - .ok_or(SledEditError::OutOfUnderlayIps) - } - - pub fn disks( - &self, - mut filter: F, - ) -> impl Iterator - where - F: FnMut(BlueprintPhysicalDiskDisposition) -> bool, - { - match &self.0 { - InnerSledEditor::Active(editor) => { - Either::Left(editor.disks(filter)) - } - InnerSledEditor::Decommissioned(edited) => Either::Right( - edited - .config - .disks - .iter() - .filter(move |disk| filter(disk.disposition)), - ), - } - } - - pub fn datasets( - &self, - mut filter: F, - ) -> impl Iterator - where - F: FnMut(BlueprintDatasetDisposition) -> bool, - { - match &self.0 { - InnerSledEditor::Active(editor) => { - Either::Left(editor.datasets(filter)) - } - InnerSledEditor::Decommissioned(edited) => Either::Right( - edited - .config - .datasets - .iter() - .filter(move |disk| filter(disk.disposition)), - ), - } - } - - pub fn in_service_zones( - &self, - ) -> impl Iterator { - match &self.0 { - InnerSledEditor::Active(editor) => { - Either::Left(editor.in_service_zones()) - } - InnerSledEditor::Decommissioned(_) => { - // A decommissioned sled cannot have any in-service zones! - Either::Right(iter::empty()) - } - } - } - - pub fn could_be_running_zones( - &self, - ) -> impl Iterator { - match &self.0 { - InnerSledEditor::Active(editor) => { - Either::Left(editor.could_be_running_zones()) - } - InnerSledEditor::Decommissioned(_) => { - // A decommissioned sled cannot have any running zones! - Either::Right(iter::empty()) - } - } - } - - pub fn expunged_zones( - &self, - reason: BlueprintExpungedZoneAccessReason, - ) -> impl Iterator { - match &self.0 { - InnerSledEditor::Active(editor) => { - Either::Left(editor.expunged_zones(reason)) - } - InnerSledEditor::Decommissioned(edited) => Either::Right( - edited - .config - .zones - .iter() - .filter(move |zone| zone.disposition.is_expunged()), - ), - } - } - - pub fn all_in_service_and_expunged_zones( - &self, - reason: BlueprintExpungedZoneAccessReason, - ) -> impl Iterator { - match &self.0 { - InnerSledEditor::Active(editor) => { - Either::Left(editor.all_in_service_and_expunged_zones(reason)) - } - InnerSledEditor::Decommissioned(edited) => { - Either::Right(edited.config.zones.iter()) - } - } - } - - pub fn host_phase_2(&self) -> BlueprintHostPhase2DesiredSlots { - match &self.0 { - InnerSledEditor::Active(editor) => editor.host_phase_2(), - InnerSledEditor::Decommissioned(edited) => { - edited.config.host_phase_2.clone() - } - } - } - - /// Returns the remove_mupdate_override field for this sled. - pub fn get_remove_mupdate_override(&self) -> Option { - match &self.0 { - InnerSledEditor::Active(editor) => { - *editor.remove_mupdate_override.value() - } - InnerSledEditor::Decommissioned(sled) => { - sled.config.remove_mupdate_override - } - } - } - - fn as_active_mut( - &mut self, - ) -> Result<&mut ActiveSledEditor, SledEditError> { - match &mut self.0 { - InnerSledEditor::Active(editor) => Ok(editor), - InnerSledEditor::Decommissioned(_) => { - Err(SledEditError::EditDecommissioned) - } - } - } - - pub fn ensure_disk( - &mut self, - disk: BlueprintPhysicalDiskConfig, - rng: &mut SledPlannerRng, - ) -> Result<(), SledEditError> { - self.as_active_mut()?.ensure_disk(disk, rng) - } - - pub fn expunge_disk( - &mut self, - disk_id: &PhysicalDiskUuid, - ) -> Result { - self.as_active_mut()?.expunge_disk(disk_id) - } - - pub fn decommission_disk( - &mut self, - disk_id: &PhysicalDiskUuid, - ) -> Result<(), SledEditError> { - self.as_active_mut()?.decommission_disk(disk_id)?; - Ok(()) - } - - pub fn add_zone( - &mut self, - zone: BlueprintZoneConfig, - rng: &mut SledPlannerRng, - ) -> Result<(), SledEditError> { - self.as_active_mut()?.add_zone(zone, rng) - } - - pub fn expunge_zone( - &mut self, - zone_id: &OmicronZoneUuid, - ) -> Result { - self.as_active_mut()?.expunge_zone(zone_id) - } - - pub fn mark_expunged_zone_ready_for_cleanup( - &mut self, - zone_id: &OmicronZoneUuid, - ) -> Result { - self.as_active_mut()?.mark_expunged_zone_ready_for_cleanup(zone_id) - } - - /// Sets the image source for a zone, returning the old image source. - pub fn set_zone_image_source( - &mut self, - zone_id: &OmicronZoneUuid, - image_source: BlueprintZoneImageSource, - ) -> Result { - self.as_active_mut()?.set_zone_image_source(zone_id, image_source) - } - - // Sets the desired host phase 2 contents. - pub fn set_host_phase_2( - &mut self, - host_phase_2: BlueprintHostPhase2DesiredSlots, - ) -> Result<(), SledEditError> { - self.as_active_mut()?.set_host_phase_2(host_phase_2); - Ok(()) - } - - // Sets the desired host phase 2 contents of a particular slot. - pub fn set_host_phase_2_slot( - &mut self, - slot: M2Slot, - host_phase_2: BlueprintHostPhase2DesiredContents, - ) -> Result<(), SledEditError> { - self.as_active_mut()?.set_host_phase_2_slot(slot, host_phase_2); - Ok(()) - } - - /// Updates a sled's mupdate override field based on the mupdate override - /// provided by inventory. - pub fn ensure_mupdate_override( - &mut self, - // inv_mupdate_override_info has a weird type (not Option<&T>, not &str) - // because this is what `Result::as_ref` returns. - inv_mupdate_override_info: Result< - &Option, - &String, - >, - pending_mgs_update: Entry<'_, PendingMgsUpdate>, - noop_sled_info: NoopConvertSledInfoMut<'_>, - ) -> Result { - self.as_active_mut()?.ensure_mupdate_override( - inv_mupdate_override_info, - pending_mgs_update, - noop_sled_info, - ) - } - - /// Sets remove-mupdate-override configuration for this sled. - /// - /// Currently only used in test code. - pub fn set_remove_mupdate_override( - &mut self, - remove_mupdate_override: Option, - ) -> Result<(), SledEditError> { - self.as_active_mut()? - .set_remove_mupdate_override(remove_mupdate_override); - Ok(()) - } - - /// Backwards compatibility / test helper: If we're given a blueprint that - /// has zones but wasn't created via `SledEditor`, it might not have - /// datasets for all its zones. This method backfills them. - pub fn ensure_datasets_for_running_zones( - &mut self, - rng: &mut SledPlannerRng, - ) -> Result<(), SledEditError> { - self.as_active_mut()?.ensure_datasets_for_running_zones(rng) - } - - /// Debug method to force a sled agent generation number to be bumped, even - /// if there are no changes to the sled. - /// - /// Do not use in production. Instead, update the logic that decides if the - /// generation number should be bumped. - pub fn debug_force_generation_bump(&mut self) -> Result<(), SledEditError> { - self.as_active_mut()?.debug_force_generation_bump(); - Ok(()) - } -} - -#[derive(Debug)] -struct ActiveSledEditor { +pub struct SledEditor { underlay_ip_allocator: SledUnderlayIpAllocator, incoming_sled_agent_generation: Generation, zones: ZonesEditor, @@ -530,8 +174,18 @@ impl EditedSled { } } -impl ActiveSledEditor { - pub fn new(config: BlueprintSledConfig) -> Result { +impl SledEditor { + pub fn for_existing( + config: BlueprintSledConfig, + ) -> Result { + // We should only attempt to wrap active sleds in `SledEditor`s. + match config.state { + SledState::Active => (), // fallthrough + SledState::Decommissioned => { + return Err(SledInputError::DecommissionedSled); + } + } + let zones = ZonesEditor::new(config.sled_agent_generation, config.zones); @@ -552,7 +206,7 @@ impl ActiveSledEditor { }) } - pub fn new_empty(subnet: Ipv6Subnet) -> Self { + pub fn for_new_active(subnet: Ipv6Subnet) -> Self { Self { underlay_ip_allocator: SledUnderlayIpAllocator::new( subnet, @@ -620,6 +274,16 @@ impl ActiveSledEditor { } } + pub fn decommission(self) -> Result { + if let Err(err) = self.validate_decommisionable() { + return Err((self, err)); + } + + let mut finalized = self.finalize(); + finalized.config.state = SledState::Decommissioned; + Ok(finalized) + } + fn validate_decommisionable(&self) -> Result<(), SledEditError> { // A sled is only decommissionable if all its zones have been expunged // (i.e., there are no zones left with an in-service disposition). @@ -641,6 +305,10 @@ impl ActiveSledEditor { } } + pub fn subnet(&self) -> Ipv6Subnet { + self.underlay_ip_allocator.subnet() + } + pub fn alloc_underlay_ip(&mut self) -> Option { self.underlay_ip_allocator.alloc() } @@ -665,6 +333,11 @@ impl ActiveSledEditor { self.datasets.datasets(filter) } + /// Returns the remove_mupdate_override field for this sled. + pub fn get_remove_mupdate_override(&self) -> Option { + *self.remove_mupdate_override.value() + } + pub fn in_service_zones( &self, ) -> impl Iterator {