From e37874b7a7a5fa2c97dad34a55e9879269cc1879 Mon Sep 17 00:00:00 2001 From: karencfv Date: Tue, 12 Aug 2025 13:16:50 +1200 Subject: [PATCH] [reconfigurator] Extract SP related planning code into its own submodule --- .../planning/src/mgs_updates/mod.rs | 174 +--------------- .../planning/src/mgs_updates/sp.rs | 188 ++++++++++++++++++ 2 files changed, 192 insertions(+), 170 deletions(-) create mode 100644 nexus/reconfigurator/planning/src/mgs_updates/sp.rs diff --git a/nexus/reconfigurator/planning/src/mgs_updates/mod.rs b/nexus/reconfigurator/planning/src/mgs_updates/mod.rs index 885e302dd00..e68e0215651 100644 --- a/nexus/reconfigurator/planning/src/mgs_updates/mod.rs +++ b/nexus/reconfigurator/planning/src/mgs_updates/mod.rs @@ -5,10 +5,13 @@ //! Facilities for making choices about MGS-managed updates mod rot; +mod sp; use crate::mgs_updates::rot::RotUpdateState; use crate::mgs_updates::rot::mgs_update_status_rot; use crate::mgs_updates::rot::try_make_update_rot; +use crate::mgs_updates::sp::mgs_update_status_sp; +use crate::mgs_updates::sp::try_make_update_sp; use gateway_types::rot::RotSlot; use nexus_types::deployment::ExpectedActiveRotSlot; @@ -21,14 +24,13 @@ use nexus_types::inventory::BaseboardId; use nexus_types::inventory::CabooseWhich; use nexus_types::inventory::Collection; use omicron_common::api::external::TufRepoDescription; -use slog::{debug, error, info, warn}; +use slog::{error, info, warn}; use slog_error_chain::InlineErrorChain; use std::collections::BTreeSet; use std::sync::Arc; use thiserror::Error; use tufaceous_artifact::ArtifactVersion; use tufaceous_artifact::ArtifactVersionError; -use tufaceous_artifact::KnownArtifactKind; /// How to handle an MGS-driven update that has become impossible due to /// unsatisfied preconditions. @@ -364,39 +366,6 @@ fn mgs_update_status( } } -/// Compares a configured SP update with information from inventory and -/// determines the current status of the update. See `MgsUpdateStatus`. -fn mgs_update_status_sp( - desired_version: &ArtifactVersion, - expected_active_version: &ArtifactVersion, - expected_inactive_version: &ExpectedVersion, - found_active_version: &str, - found_inactive_version: Option<&str>, -) -> MgsUpdateStatus { - if found_active_version == desired_version.as_str() { - // If we find the desired version in the active slot, we're done. - return MgsUpdateStatus::Done; - } - - // The update hasn't completed. - // - // Check to make sure the contents of the active slot are still what they - // were when we configured this update. If not, then this update cannot - // proceed as currently configured. It will fail its precondition check. - if found_active_version != expected_active_version.as_str() { - return MgsUpdateStatus::Impossible; - } - - // Similarly, check the contents of the inactive slot to determine if it - // still matches what we saw when we configured this update. If not, then - // this update cannot proceed as currently configured. It will fail its - // precondition check. - mgs_update_status_inactive_versions( - found_inactive_version, - expected_inactive_version, - ) -} - fn mgs_update_status_inactive_versions( found_inactive_version: Option<&str>, expected_inactive_version: &ExpectedVersion, @@ -464,141 +433,6 @@ fn try_make_update( }) } -/// Determine if the given baseboard needs an SP update and, if so, returns it. -fn try_make_update_sp( - log: &slog::Logger, - baseboard_id: &Arc, - inventory: &Collection, - current_artifacts: &TufRepoDescription, -) -> Option { - let Some(sp_info) = inventory.sps.get(baseboard_id) else { - warn!( - log, - "cannot configure SP update for board \ - (missing SP info from inventory)"; - baseboard_id - ); - return None; - }; - - let Some(active_caboose) = - inventory.caboose_for(CabooseWhich::SpSlot0, baseboard_id) - else { - warn!( - log, - "cannot configure SP update for board \ - (missing active caboose from inventory)"; - baseboard_id, - ); - return None; - }; - - let Ok(expected_active_version) = active_caboose.caboose.version.parse() - else { - warn!( - log, - "cannot configure SP update for board \ - (cannot parse current active version as an ArtifactVersion)"; - baseboard_id, - "found_version" => &active_caboose.caboose.version, - ); - return None; - }; - - let board = &active_caboose.caboose.board; - let matching_artifacts: Vec<_> = current_artifacts - .artifacts - .iter() - .filter(|a| { - // A matching SP artifact will have: - // - // - "name" matching the board name (found above from caboose) - // - "kind" matching one of the known SP kinds - - if a.id.name != *board { - return false; - } - - match a.id.kind.to_known() { - None => false, - Some( - KnownArtifactKind::GimletSp - | KnownArtifactKind::PscSp - | KnownArtifactKind::SwitchSp, - ) => true, - Some( - KnownArtifactKind::GimletRot - | KnownArtifactKind::Host - | KnownArtifactKind::Trampoline - | KnownArtifactKind::InstallinatorDocument - | KnownArtifactKind::ControlPlane - | KnownArtifactKind::Zone - | KnownArtifactKind::PscRot - | KnownArtifactKind::SwitchRot - | KnownArtifactKind::GimletRotBootloader - | KnownArtifactKind::PscRotBootloader - | KnownArtifactKind::SwitchRotBootloader, - ) => false, - } - }) - .collect(); - if matching_artifacts.is_empty() { - warn!( - log, - "cannot configure SP update for board (no matching artifact)"; - baseboard_id, - ); - return None; - } - - if matching_artifacts.len() > 1 { - // This should be impossible unless we shipped a TUF repo with multiple - // artifacts for the same board. But it doesn't prevent us from picking - // one and proceeding. Make a note and proceed. - warn!(log, "found more than one matching artifact for SP update"); - } - - let artifact = matching_artifacts[0]; - - // If the artifact's version matches what's deployed, then no update is - // needed. - if artifact.id.version == expected_active_version { - debug!(log, "no SP update needed for board"; baseboard_id); - return None; - } - - // Begin configuring an update. - let expected_inactive_version = match inventory - .caboose_for(CabooseWhich::SpSlot1, baseboard_id) - .map(|c| c.caboose.version.parse::()) - .transpose() - { - Ok(None) => ExpectedVersion::NoValidVersion, - Ok(Some(v)) => ExpectedVersion::Version(v), - Err(_) => { - warn!( - log, - "cannot configure SP update for board \ - (found inactive slot contents but version was not valid)"; - baseboard_id - ); - return None; - } - }; - - Some(PendingMgsUpdate { - baseboard_id: baseboard_id.clone(), - sp_type: sp_info.sp_type, - slot_id: sp_info.sp_slot, - details: PendingMgsUpdateDetails::Sp { - expected_active_version, - expected_inactive_version, - }, - artifact_hash: artifact.hash, - artifact_version: artifact.id.version.clone(), - }) -} - #[cfg(test)] mod test { use super::ImpossibleUpdatePolicy; diff --git a/nexus/reconfigurator/planning/src/mgs_updates/sp.rs b/nexus/reconfigurator/planning/src/mgs_updates/sp.rs new file mode 100644 index 00000000000..8fb1cae173a --- /dev/null +++ b/nexus/reconfigurator/planning/src/mgs_updates/sp.rs @@ -0,0 +1,188 @@ +// 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/. + +//! Facilities for making choices about SP updates + +use super::MgsUpdateStatus; +use super::mgs_update_status_inactive_versions; + +use nexus_types::deployment::ExpectedVersion; +use nexus_types::deployment::PendingMgsUpdate; +use nexus_types::deployment::PendingMgsUpdateDetails; +use nexus_types::inventory::BaseboardId; +use nexus_types::inventory::CabooseWhich; +use nexus_types::inventory::Collection; +use omicron_common::api::external::TufRepoDescription; +use slog::{debug, warn}; +use std::sync::Arc; +use tufaceous_artifact::ArtifactVersion; +use tufaceous_artifact::KnownArtifactKind; + +/// Compares a configured SP update with information from inventory and +/// determines the current status of the update. See `MgsUpdateStatus`. +pub fn mgs_update_status_sp( + desired_version: &ArtifactVersion, + expected_active_version: &ArtifactVersion, + expected_inactive_version: &ExpectedVersion, + found_active_version: &str, + found_inactive_version: Option<&str>, +) -> MgsUpdateStatus { + if found_active_version == desired_version.as_str() { + // If we find the desired version in the active slot, we're done. + return MgsUpdateStatus::Done; + } + + // The update hasn't completed. + // + // Check to make sure the contents of the active slot are still what they + // were when we configured this update. If not, then this update cannot + // proceed as currently configured. It will fail its precondition check. + if found_active_version != expected_active_version.as_str() { + return MgsUpdateStatus::Impossible; + } + + // Similarly, check the contents of the inactive slot to determine if it + // still matches what we saw when we configured this update. If not, then + // this update cannot proceed as currently configured. It will fail its + // precondition check. + mgs_update_status_inactive_versions( + found_inactive_version, + expected_inactive_version, + ) +} + +/// Determine if the given baseboard needs an SP update and, if so, returns it. +pub fn try_make_update_sp( + log: &slog::Logger, + baseboard_id: &Arc, + inventory: &Collection, + current_artifacts: &TufRepoDescription, +) -> Option { + let Some(sp_info) = inventory.sps.get(baseboard_id) else { + warn!( + log, + "cannot configure SP update for board \ + (missing SP info from inventory)"; + baseboard_id + ); + return None; + }; + + let Some(active_caboose) = + inventory.caboose_for(CabooseWhich::SpSlot0, baseboard_id) + else { + warn!( + log, + "cannot configure SP update for board \ + (missing active caboose from inventory)"; + baseboard_id, + ); + return None; + }; + + let Ok(expected_active_version) = active_caboose.caboose.version.parse() + else { + warn!( + log, + "cannot configure SP update for board \ + (cannot parse current active version as an ArtifactVersion)"; + baseboard_id, + "found_version" => &active_caboose.caboose.version, + ); + return None; + }; + + let board = &active_caboose.caboose.board; + let matching_artifacts: Vec<_> = current_artifacts + .artifacts + .iter() + .filter(|a| { + // A matching SP artifact will have: + // + // - "name" matching the board name (found above from caboose) + // - "kind" matching one of the known SP kinds + + if a.id.name != *board { + return false; + } + + match a.id.kind.to_known() { + None => false, + Some( + KnownArtifactKind::GimletSp + | KnownArtifactKind::PscSp + | KnownArtifactKind::SwitchSp, + ) => true, + Some( + KnownArtifactKind::GimletRot + | KnownArtifactKind::Host + | KnownArtifactKind::Trampoline + | KnownArtifactKind::InstallinatorDocument + | KnownArtifactKind::ControlPlane + | KnownArtifactKind::Zone + | KnownArtifactKind::PscRot + | KnownArtifactKind::SwitchRot + | KnownArtifactKind::GimletRotBootloader + | KnownArtifactKind::PscRotBootloader + | KnownArtifactKind::SwitchRotBootloader, + ) => false, + } + }) + .collect(); + if matching_artifacts.is_empty() { + warn!( + log, + "cannot configure SP update for board (no matching artifact)"; + baseboard_id, + ); + return None; + } + + if matching_artifacts.len() > 1 { + // This should be impossible unless we shipped a TUF repo with multiple + // artifacts for the same board. But it doesn't prevent us from picking + // one and proceeding. Make a note and proceed. + warn!(log, "found more than one matching artifact for SP update"); + } + + let artifact = matching_artifacts[0]; + + // If the artifact's version matches what's deployed, then no update is + // needed. + if artifact.id.version == expected_active_version { + debug!(log, "no SP update needed for board"; baseboard_id); + return None; + } + + // Begin configuring an update. + let expected_inactive_version = match inventory + .caboose_for(CabooseWhich::SpSlot1, baseboard_id) + .map(|c| c.caboose.version.parse::()) + .transpose() + { + Ok(None) => ExpectedVersion::NoValidVersion, + Ok(Some(v)) => ExpectedVersion::Version(v), + Err(_) => { + warn!( + log, + "cannot configure SP update for board \ + (found inactive slot contents but version was not valid)"; + baseboard_id + ); + return None; + } + }; + + Some(PendingMgsUpdate { + baseboard_id: baseboard_id.clone(), + sp_type: sp_info.sp_type, + slot_id: sp_info.sp_slot, + details: PendingMgsUpdateDetails::Sp { + expected_active_version, + expected_inactive_version, + }, + artifact_hash: artifact.hash, + artifact_version: artifact.id.version.clone(), + }) +}