From 9557b5f15366eab3fb40ec4e278eea8006b8d729 Mon Sep 17 00:00:00 2001 From: John Gallagher Date: Thu, 26 Oct 2023 17:45:50 -0400 Subject: [PATCH] add test for updated a switch sp --- nexus/tests/integration_tests/sp_updater.rs | 67 ++++++++++++- sp-sim/src/gimlet.rs | 9 +- sp-sim/src/lib.rs | 6 ++ sp-sim/src/sidecar.rs | 105 +++++++++++++++----- 4 files changed, 152 insertions(+), 35 deletions(-) diff --git a/nexus/tests/integration_tests/sp_updater.rs b/nexus/tests/integration_tests/sp_updater.rs index 224361f7cd5..0f7849bc571 100644 --- a/nexus/tests/integration_tests/sp_updater.rs +++ b/nexus/tests/integration_tests/sp_updater.rs @@ -10,7 +10,9 @@ use hubtools::RawHubrisArchive; use hubtools::{CabooseBuilder, HubrisArchiveBuilder}; use nexus_test_utils::test_setup; use omicron_nexus::TestInterfaces; +use sp_sim::SimulatedSp; use sp_sim::SIM_GIMLET_BOARD; +use sp_sim::SIM_SIDECAR_BOARD; use std::net::Ipv6Addr; use std::net::SocketAddrV6; use std::sync::Arc; @@ -30,12 +32,12 @@ fn make_fake_sp_image(board: &str) -> Vec { } #[tokio::test] -async fn test_sp_updater_updates_gimlet() { +async fn test_sp_updater_updates_sled() { // Start MGS + Sim SP. let (mgs_config, sp_sim_config) = mgs_setup::load_test_config(); let mgs_addr = SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0); let mgs = mgs_setup::test_setup_with_config( - "test_sp_updater_drives_update", + "test_sp_updater_updates_sled", SpPort::One, mgs_config, &sp_sim_config, @@ -44,7 +46,7 @@ async fn test_sp_updater_updates_gimlet() { .await; let cptestctx = - test_setup::("test_sp_updater_drives_update") + test_setup::("test_sp_updater_updates_sled") .await; let mgs_listen_addr = mgs @@ -87,3 +89,62 @@ async fn test_sp_updater_updates_gimlet() { hubris_archive.image.data.len() ); } + +#[tokio::test] +async fn test_sp_updater_updates_switch() { + // Start MGS + Sim SP. + let (mgs_config, sp_sim_config) = mgs_setup::load_test_config(); + let mgs_addr = SocketAddrV6::new(Ipv6Addr::LOCALHOST, 0, 0, 0); + let mgs = mgs_setup::test_setup_with_config( + "test_sp_updater_updates_switch", + SpPort::One, + mgs_config, + &sp_sim_config, + Some(mgs_addr), + ) + .await; + + let cptestctx = + test_setup::("test_sp_updater_updates_switch") + .await; + + let mgs_listen_addr = mgs + .server + .dropshot_server_for_address(mgs_addr) + .expect("missing dropshot server for localhost address") + .local_addr(); + let mgs_client = Arc::new(gateway_client::Client::new( + &format!("http://{mgs_listen_addr}"), + cptestctx.logctx.log.new(slog::o!("component" => "MgsClient")), + )); + + let sp_type = SpType::Switch; + let sp_slot = 0; + let update_id = Uuid::new_v4(); + let hubris_archive = make_fake_sp_image(SIM_SIDECAR_BOARD); + + let sp_updater = cptestctx.server.apictx().nexus.sp_updater( + sp_type, + sp_slot, + update_id, + hubris_archive.clone(), + ); + + sp_updater.update([mgs_client]).await.expect("update failed"); + + let last_update_image = mgs.simrack.sidecars[sp_slot as usize] + .last_update_data() + .await + .expect("simulated SP did not receive an update"); + + let hubris_archive = RawHubrisArchive::from_vec(hubris_archive).unwrap(); + + assert_eq!( + hubris_archive.image.data.as_slice(), + &*last_update_image, + "simulated SP update contents (len {}) \ + do not match test generated fake image (len {})", + last_update_image.len(), + hubris_archive.image.data.len() + ); +} diff --git a/sp-sim/src/gimlet.rs b/sp-sim/src/gimlet.rs index 2884384dc6d..cfa9ea14b1e 100644 --- a/sp-sim/src/gimlet.rs +++ b/sp-sim/src/gimlet.rs @@ -104,16 +104,15 @@ impl SimulatedSp for Gimlet { ) -> Result { self.rot.lock().unwrap().handle_deserialized(request) } -} -impl Gimlet { - // TODO move to `SimulatedSp` - pub async fn last_update_data(&self) -> Option> { + async fn last_update_data(&self) -> Option> { let handler = self.handler.as_ref()?; let handler = handler.lock().await; handler.update_state.last_update_data() } +} +impl Gimlet { pub async fn spawn(gimlet: &GimletConfig, log: Logger) -> Result { info!(log, "setting up simulated gimlet"); @@ -872,7 +871,7 @@ impl SpHandler for Handler { port: SpPort, update: gateway_messages::SpUpdatePrepare, ) -> Result<(), SpError> { - warn!( + debug!( &self.log, "received SP update prepare request"; "sender" => %sender, diff --git a/sp-sim/src/lib.rs b/sp-sim/src/lib.rs index 23f2a86fbd6..dee2bb5f4b5 100644 --- a/sp-sim/src/lib.rs +++ b/sp-sim/src/lib.rs @@ -18,6 +18,7 @@ pub use gimlet::Gimlet; pub use gimlet::SIM_GIMLET_BOARD; pub use server::logger; pub use sidecar::Sidecar; +pub use sidecar::SIM_SIDECAR_BOARD; pub use slog::Logger; pub use sprockets_rot::common::msgs::RotRequestV1; pub use sprockets_rot::common::msgs::RotResponseV1; @@ -52,6 +53,11 @@ pub trait SimulatedSp { &self, request: RotRequestV1, ) -> Result; + + /// Get the last completed update delivered to this simulated SP. + /// + /// Only returns data after a simulated reset. + async fn last_update_data(&self) -> Option>; } // Helper function to pad a simulated serial number (stored as a `String`) to diff --git a/sp-sim/src/sidecar.rs b/sp-sim/src/sidecar.rs index 45151666461..e268d386c2b 100644 --- a/sp-sim/src/sidecar.rs +++ b/sp-sim/src/sidecar.rs @@ -13,6 +13,7 @@ use crate::serial_number_padded; use crate::server; use crate::server::SimSpHandler; use crate::server::UdpServer; +use crate::update::SimSpUpdate; use crate::Responsiveness; use crate::SimulatedSp; use anyhow::Result; @@ -57,6 +58,8 @@ use tokio::sync::Mutex as TokioMutex; use tokio::task; use tokio::task::JoinHandle; +pub const SIM_SIDECAR_BOARD: &str = "SimSidecarSp"; + pub struct Sidecar { rot: Mutex, manufacturing_public_key: Ed25519PublicKey, @@ -111,6 +114,12 @@ impl SimulatedSp for Sidecar { ) -> Result { self.rot.lock().unwrap().handle_deserialized(request) } + + async fn last_update_data(&self) -> Option> { + let handler = self.handler.as_ref()?; + let handler = handler.lock().await; + handler.update_state.last_update_data() + } } impl Sidecar { @@ -302,6 +311,16 @@ struct Handler { ignition: FakeIgnition, rot_active_slot: RotSlotId, power_state: PowerState, + + update_state: SimSpUpdate, + reset_pending: bool, + + // To simulate an SP reset, we should (after doing whatever housekeeping we + // need to track the reset) intentionally _fail_ to respond to the request, + // simulating a `-> !` function on the SP that triggers a reset. To provide + // this, our caller will pass us a function to call if they should ignore + // whatever result we return and fail to respond at all. + should_fail_to_respond_signal: Option>, } impl Handler { @@ -332,6 +351,9 @@ impl Handler { ignition, rot_active_slot: RotSlotId::A, power_state: PowerState::A2, + update_state: SimSpUpdate::default(), + reset_pending: false, + should_fail_to_respond_signal: None, } } @@ -629,14 +651,18 @@ impl SpHandler for Handler { port: SpPort, update: gateway_messages::SpUpdatePrepare, ) -> Result<(), SpError> { - warn!( + debug!( &self.log, - "received update prepare request; not supported by simulated sidecar"; + "received update prepare request"; "sender" => %sender, "port" => ?port, "update" => ?update, ); - Err(SpError::RequestUnsupportedForSp) + self.update_state.prepare( + SpComponent::SP_ITSELF, + update.id, + update.sp_image_size.try_into().unwrap(), + ) } fn component_update_prepare( @@ -645,14 +671,18 @@ impl SpHandler for Handler { port: SpPort, update: gateway_messages::ComponentUpdatePrepare, ) -> Result<(), SpError> { - warn!( + debug!( &self.log, - "received update prepare request; not supported by simulated sidecar"; + "received update prepare request"; "sender" => %sender, "port" => ?port, "update" => ?update, ); - Err(SpError::RequestUnsupportedForSp) + self.update_state.prepare( + update.component, + update.id, + update.total_size.try_into().unwrap(), + ) } fn update_status( @@ -661,14 +691,14 @@ impl SpHandler for Handler { port: SpPort, component: SpComponent, ) -> Result { - warn!( + debug!( &self.log, - "received update status request; not supported by simulated sidecar"; + "received update status request"; "sender" => %sender, "port" => ?port, "component" => ?component, ); - Err(SpError::RequestUnsupportedForSp) + Ok(self.update_state.status()) } fn update_chunk( @@ -676,17 +706,17 @@ impl SpHandler for Handler { sender: SocketAddrV6, port: SpPort, chunk: gateway_messages::UpdateChunk, - data: &[u8], + chunk_data: &[u8], ) -> Result<(), SpError> { - warn!( + debug!( &self.log, - "received update chunk; not supported by simulated sidecar"; + "received update chunk"; "sender" => %sender, "port" => ?port, "offset" => chunk.offset, - "length" => data.len(), + "length" => chunk_data.len(), ); - Err(SpError::RequestUnsupportedForSp) + self.update_state.ingest_chunk(chunk, chunk_data) } fn update_abort( @@ -694,17 +724,17 @@ impl SpHandler for Handler { sender: SocketAddrV6, port: SpPort, component: SpComponent, - id: gateway_messages::UpdateId, + update_id: gateway_messages::UpdateId, ) -> Result<(), SpError> { - warn!( + debug!( &self.log, "received update abort; not supported by simulated sidecar"; "sender" => %sender, "port" => ?port, "component" => ?component, - "id" => ?id, + "id" => ?update_id, ); - Err(SpError::RequestUnsupportedForSp) + self.update_state.abort(update_id) } fn power_state( @@ -743,13 +773,18 @@ impl SpHandler for Handler { port: SpPort, component: SpComponent, ) -> Result<(), SpError> { - warn!( - &self.log, "received reset prepare request; not supported by simulated sidecar"; + debug!( + &self.log, "received reset prepare request"; "sender" => %sender, "port" => ?port, "component" => ?component, ); - Err(SpError::RequestUnsupportedForSp) + if component == SpComponent::SP_ITSELF { + self.reset_pending = true; + Ok(()) + } else { + Err(SpError::RequestUnsupportedForComponent) + } } fn reset_component_trigger( @@ -758,13 +793,29 @@ impl SpHandler for Handler { port: SpPort, component: SpComponent, ) -> Result<(), SpError> { - warn!( - &self.log, "received sys-reset trigger request; not supported by simulated sidecar"; + debug!( + &self.log, "received sys-reset trigger request"; "sender" => %sender, "port" => ?port, "component" => ?component, ); - Err(SpError::RequestUnsupportedForSp) + if component == SpComponent::SP_ITSELF { + if self.reset_pending { + self.update_state.sp_reset(); + self.reset_pending = false; + if let Some(signal) = self.should_fail_to_respond_signal.take() + { + // Instruct `server::handle_request()` to _not_ respond to + // this request at all, simulating an SP actually resetting. + signal(); + } + Ok(()) + } else { + Err(SpError::ResetComponentTriggerWithoutPrepare) + } + } else { + Err(SpError::RequestUnsupportedForComponent) + } } fn num_devices(&mut self, _: SocketAddrV6, _: SpPort) -> u32 { @@ -978,7 +1029,7 @@ impl SpHandler for Handler { buf: &mut [u8], ) -> std::result::Result { static SP_GITC: &[u8] = b"ffffffff"; - static SP_BORD: &[u8] = b"SimSidecarSp"; + static SP_BORD: &[u8] = SIM_SIDECAR_BOARD.as_bytes(); static SP_NAME: &[u8] = b"SimSidecar"; static SP_VERS: &[u8] = b"0.0.1"; @@ -1026,9 +1077,9 @@ impl SpHandler for Handler { impl SimSpHandler for Handler { fn set_sp_should_fail_to_respond_signal( &mut self, - _signal: Box, + signal: Box, ) { - // we don't yet implement simulated reset; ignore `signal` + self.should_fail_to_respond_signal = Some(signal); } }