Skip to content

Commit e6961bc

Browse files
authored
Blueprint sled editor: don't track decommissioned sleds (#9631)
This is the small cleanup I mentioned in #9608 (comment). Prior to this PR, `SledEditor` wrapped an `InnerSledEditor` enum with active / decommissioned variants, and `SledEditor`'s methods were mostly just passthroughs to the two inner variants, except for a handful of methods that were only valid for commissioned sleds. After this PR, `SledEditor` only handles active sleds, and `BlueprintBuilder` keeps a separate set of all the configs for decommissioned sleds. Most of the other changes to the buildprint builder were minor fallout from slight changes (e.g., some `SledEditor` methods were fallible and now are infallible).
1 parent ce35f50 commit e6961bc

2 files changed

Lines changed: 114 additions & 414 deletions

File tree

nexus/reconfigurator/planning/src/blueprint_builder/builder.rs

Lines changed: 78 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -507,9 +507,16 @@ pub struct BlueprintBuilder<'a> {
507507
/// The ID that the completed blueprint will have
508508
new_blueprint_id: BlueprintUuid,
509509

510+
// `sled_editors` contains only commissioned sleds. We still carry forward
511+
// any decommissioned sleds (that were either already decommissioned in
512+
// `parent_blueprint`, or that we ourselves decommissioned), but they're
513+
// stored separately in `decommissioned_sleds` to avoid the possibility that
514+
// we try to make additional changes after decommissioning.
515+
sled_editors: BTreeMap<SledUuid, SledEditor>,
516+
decommissioned_sleds: BTreeMap<SledUuid, EditedSled>,
517+
510518
// These fields will become part of the final blueprint. See the
511519
// corresponding fields in `Blueprint`.
512-
sled_editors: BTreeMap<SledUuid, SledEditor>,
513520
cockroachdb_setting_preserve_downgrade: CockroachDbPreserveDowngrade,
514521
cockroachdb_fingerprint: String,
515522
target_release_minimum_generation: Generation,
@@ -575,14 +582,28 @@ impl<'a> BlueprintBuilder<'a> {
575582
"parent_id" => parent_blueprint.id.to_string(),
576583
));
577584

578-
// Convert our parent blueprint's sled configs into `SledEditor`s.
585+
// Convert our parent blueprint's sled configs into `SledEditor` (for
586+
// commissioned sleds) or `EditedSled`s (with zero edits made, for
587+
// decommissioned sleds).
579588
let mut sled_editors = BTreeMap::new();
589+
let mut decommissioned_sleds = BTreeMap::new();
580590
for (sled_id, sled_cfg) in &parent_blueprint.sleds {
581-
let editor = SledEditor::for_existing(sled_cfg.clone())
582-
.with_context(|| {
583-
format!("failed to construct SledEditor for sled {sled_id}")
584-
})?;
585-
sled_editors.insert(*sled_id, editor);
591+
match sled_cfg.state {
592+
SledState::Active => {
593+
let editor = SledEditor::for_existing(sled_cfg.clone())
594+
.with_context(|| {
595+
format!(
596+
"failed to construct SledEditor \
597+
for sled {sled_id}"
598+
)
599+
})?;
600+
sled_editors.insert(*sled_id, editor);
601+
}
602+
SledState::Decommissioned => {
603+
let edited = EditedSled::new(sled_cfg.clone());
604+
decommissioned_sleds.insert(*sled_id, edited);
605+
}
606+
}
586607
}
587608

588609
// Copy the Oximeter read policy from our parent blueprint so we can
@@ -601,6 +622,7 @@ impl<'a> BlueprintBuilder<'a> {
601622
oximeter_read_policy,
602623
new_blueprint_id: rng.next_blueprint(),
603624
sled_editors,
625+
decommissioned_sleds,
604626
cockroachdb_setting_preserve_downgrade: parent_blueprint
605627
.cockroachdb_setting_preserve_downgrade,
606628
cockroachdb_fingerprint: parent_blueprint
@@ -695,7 +717,7 @@ impl<'a> BlueprintBuilder<'a> {
695717
let any_sled_subnet = self
696718
.sled_editors
697719
.values()
698-
.filter_map(|editor| editor.subnet())
720+
.map(|editor| editor.subnet())
699721
.next()
700722
.ok_or(Error::RackSubnetUnknownNoSleds)?;
701723
let rack_subnet = ReservedRackSubnet::from_subnet(any_sled_subnet);
@@ -731,12 +753,7 @@ impl<'a> BlueprintBuilder<'a> {
731753
pub fn current_commissioned_sleds(
732754
&self,
733755
) -> impl Iterator<Item = SledUuid> + '_ {
734-
self.sled_editors.iter().filter_map(|(sled_id, editor)| {
735-
match editor.state() {
736-
SledState::Active => Some(*sled_id),
737-
SledState::Decommissioned => None,
738-
}
739-
})
756+
self.sled_editors.keys().copied()
740757
}
741758

742759
/// Iterate over the in-service [`BlueprintZoneConfig`] instances on a
@@ -873,12 +890,17 @@ impl<'a> BlueprintBuilder<'a> {
873890
pub fn build(self, source: BlueprintSource) -> Blueprint {
874891
let blueprint_id = self.new_blueprint_id();
875892

876-
// Collect the Omicron zones config for all sleds, including sleds that
877-
// are no longer in service and need expungement work.
893+
// Collect the Omicron zones config for all sleds, including any
894+
// decommissioned sleds, which we continue to carry forward.
878895
let mut sleds = BTreeMap::new();
879-
for (sled_id, editor) in self.sled_editors {
880-
let EditedSled { config, edit_counts, scalar_edits } =
881-
editor.finalize();
896+
let commissioned_sleds = self
897+
.sled_editors
898+
.into_iter()
899+
.map(|(id, editor)| (id, editor.finalize()));
900+
let all_sleds = commissioned_sleds.chain(self.decommissioned_sleds);
901+
902+
for (sled_id, edited_sled) in all_sleds {
903+
let EditedSled { config, edit_counts, scalar_edits } = edited_sled;
882904
sleds.insert(sled_id, config);
883905
if edit_counts.has_nonzero_counts() || scalar_edits.has_edits() {
884906
let EditedSledScalarEdits {
@@ -892,8 +914,10 @@ impl<'a> BlueprintBuilder<'a> {
892914
"disk_edits" => ?edit_counts.disks,
893915
"dataset_edits" => ?edit_counts.datasets,
894916
"zone_edits" => ?edit_counts.zones,
895-
"debug_force_generation_bump" => debug_force_generation_bump,
896-
"remove_mupdate_override_modified" => remove_mupdate_override,
917+
"debug_force_generation_bump" =>
918+
debug_force_generation_bump,
919+
"remove_mupdate_override_modified" =>
920+
remove_mupdate_override,
897921
);
898922
} else {
899923
debug!(
@@ -936,27 +960,42 @@ impl<'a> BlueprintBuilder<'a> {
936960
&self,
937961
sled_id: SledUuid,
938962
) -> Result<SledState, Error> {
939-
let editor = self.sled_editors.get(&sled_id).ok_or_else(|| {
940-
Error::Planner(anyhow!(
963+
if self.sled_editors.contains_key(&sled_id) {
964+
Ok(SledState::Active)
965+
} else if self.decommissioned_sleds.contains_key(&sled_id) {
966+
Ok(SledState::Decommissioned)
967+
} else {
968+
Err(Error::Planner(anyhow!(
941969
"tried to get sled state for unknown sled {sled_id}"
942-
))
943-
})?;
944-
Ok(editor.state())
970+
)))
971+
}
945972
}
946973

947974
/// Set the desired state of the given sled.
975+
///
976+
/// Fails if the sled is not in the current set of commissioned sleds (i.e.,
977+
/// the sled doesn't exist or exists but is already decommissioned).
948978
pub fn set_sled_decommissioned(
949979
&mut self,
950980
sled_id: SledUuid,
951981
) -> Result<(), Error> {
952-
let editor = self.sled_editors.get_mut(&sled_id).ok_or_else(|| {
982+
let editor = self.sled_editors.remove(&sled_id).ok_or_else(|| {
953983
Error::Planner(anyhow!(
954984
"tried to set sled state for unknown sled {sled_id}"
955985
))
956986
})?;
957-
editor
958-
.decommission()
959-
.map_err(|err| Error::SledEditError { sled_id, err })
987+
match editor.decommission() {
988+
Ok(edited) => {
989+
self.decommissioned_sleds.insert(sled_id, edited);
990+
Ok(())
991+
}
992+
Err((editor, err)) => {
993+
// We failed to decommission the sled but already removed it
994+
// from `sled_editors`; put it back before returning.
995+
self.sled_editors.insert(sled_id, editor);
996+
Err(Error::SledEditError { sled_id, err })
997+
}
998+
}
960999
}
9611000

9621001
/// This is a short human-readable string summarizing the changes reflected
@@ -2074,9 +2113,8 @@ impl<'a> BlueprintBuilder<'a> {
20742113
"tried to change image of zone on unknown sled {sled_id}"
20752114
))
20762115
})?;
2077-
editor
2078-
.set_host_phase_2(host_phase_2)
2079-
.map_err(|err| Error::SledEditError { sled_id, err })
2116+
editor.set_host_phase_2(host_phase_2);
2117+
Ok(())
20802118
}
20812119

20822120
pub fn sled_set_host_phase_2_slot(
@@ -2090,9 +2128,8 @@ impl<'a> BlueprintBuilder<'a> {
20902128
"tried to change image of zone on unknown sled {sled_id}"
20912129
))
20922130
})?;
2093-
editor
2094-
.set_host_phase_2_slot(slot, host_phase_2)
2095-
.map_err(|err| Error::SledEditError { sled_id, err })
2131+
editor.set_host_phase_2_slot(slot, host_phase_2);
2132+
Ok(())
20962133
}
20972134

20982135
/// Set the `remove_mupdate_override` field of the given sled.
@@ -2106,9 +2143,8 @@ impl<'a> BlueprintBuilder<'a> {
21062143
"tried to set sled state for unknown sled {sled_id}"
21072144
))
21082145
})?;
2109-
editor
2110-
.set_remove_mupdate_override(remove_mupdate_override)
2111-
.map_err(|err| Error::SledEditError { sled_id, err })
2146+
editor.set_remove_mupdate_override(remove_mupdate_override);
2147+
Ok(())
21122148
}
21132149

21142150
fn sled_add_zone(
@@ -2136,6 +2172,7 @@ impl<'a> BlueprintBuilder<'a> {
21362172
})?;
21372173
editor
21382174
.alloc_underlay_ip()
2175+
.ok_or(SledEditError::OutOfUnderlayIps)
21392176
.map_err(|err| Error::SledEditError { sled_id, err })
21402177
}
21412178

@@ -2279,9 +2316,8 @@ impl<'a> BlueprintBuilder<'a> {
22792316
"tried to force generation bump for unknown sled {sled_id}"
22802317
))
22812318
})?;
2282-
editor
2283-
.debug_force_generation_bump()
2284-
.map_err(|err| Error::SledEditError { sled_id, err })
2319+
editor.debug_force_generation_bump();
2320+
Ok(())
22852321
}
22862322
}
22872323

0 commit comments

Comments
 (0)