Skip to content

Commit

Permalink
add test for updated a switch sp
Browse files Browse the repository at this point in the history
  • Loading branch information
jgallagher committed Oct 26, 2023
1 parent b2efd52 commit 9557b5f
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 35 deletions.
67 changes: 64 additions & 3 deletions nexus/tests/integration_tests/sp_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,12 +32,12 @@ fn make_fake_sp_image(board: &str) -> Vec<u8> {
}

#[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,
Expand All @@ -44,7 +46,7 @@ async fn test_sp_updater_updates_gimlet() {
.await;

let cptestctx =
test_setup::<omicron_nexus::Server>("test_sp_updater_drives_update")
test_setup::<omicron_nexus::Server>("test_sp_updater_updates_sled")
.await;

let mgs_listen_addr = mgs
Expand Down Expand Up @@ -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::<omicron_nexus::Server>("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()
);
}
9 changes: 4 additions & 5 deletions sp-sim/src/gimlet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,15 @@ impl SimulatedSp for Gimlet {
) -> Result<RotResponseV1, RotSprocketError> {
self.rot.lock().unwrap().handle_deserialized(request)
}
}

impl Gimlet {
// TODO move to `SimulatedSp`
pub async fn last_update_data(&self) -> Option<Box<[u8]>> {
async fn last_update_data(&self) -> Option<Box<[u8]>> {
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<Self> {
info!(log, "setting up simulated gimlet");

Expand Down Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions sp-sim/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,6 +53,11 @@ pub trait SimulatedSp {
&self,
request: RotRequestV1,
) -> Result<RotResponseV1, RotSprocketError>;

/// Get the last completed update delivered to this simulated SP.
///
/// Only returns data after a simulated reset.
async fn last_update_data(&self) -> Option<Box<[u8]>>;
}

// Helper function to pad a simulated serial number (stored as a `String`) to
Expand Down
105 changes: 78 additions & 27 deletions sp-sim/src/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<RotSprocket>,
manufacturing_public_key: Ed25519PublicKey,
Expand Down Expand Up @@ -111,6 +114,12 @@ impl SimulatedSp for Sidecar {
) -> Result<RotResponseV1, RotSprocketError> {
self.rot.lock().unwrap().handle_deserialized(request)
}

async fn last_update_data(&self) -> Option<Box<[u8]>> {
let handler = self.handler.as_ref()?;
let handler = handler.lock().await;
handler.update_state.last_update_data()
}
}

impl Sidecar {
Expand Down Expand Up @@ -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<Box<dyn FnOnce() + Send>>,
}

impl Handler {
Expand Down Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -661,50 +691,50 @@ impl SpHandler for Handler {
port: SpPort,
component: SpComponent,
) -> Result<gateway_messages::UpdateStatus, SpError> {
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(
&mut self,
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(
&mut self,
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(
Expand Down Expand Up @@ -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(
Expand All @@ -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 {
Expand Down Expand Up @@ -978,7 +1029,7 @@ impl SpHandler for Handler {
buf: &mut [u8],
) -> std::result::Result<usize, SpError> {
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";

Expand Down Expand Up @@ -1026,9 +1077,9 @@ impl SpHandler for Handler {
impl SimSpHandler for Handler {
fn set_sp_should_fail_to_respond_signal(
&mut self,
_signal: Box<dyn FnOnce() + Send>,
signal: Box<dyn FnOnce() + Send>,
) {
// we don't yet implement simulated reset; ignore `signal`
self.should_fail_to_respond_signal = Some(signal);
}
}

Expand Down

0 comments on commit 9557b5f

Please sign in to comment.