diff --git a/Cargo.lock b/Cargo.lock index 2f9aaf29541..7c5f75baecd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6071,6 +6071,7 @@ name = "mgs-dev" version = "0.1.0" dependencies = [ "anyhow", + "camino", "clap", "futures", "gateway-messages", @@ -7680,10 +7681,12 @@ name = "omicron-dev" version = "0.1.0" dependencies = [ "anyhow", + "camino", "clap", "dropshot", "expectorate", "futures", + "gateway-test-utils", "libc", "nexus-config", "nexus-test-interface", @@ -12781,6 +12784,7 @@ dependencies = [ "gateway-types", "hex", "hubtools", + "nexus-types", "nix 0.30.1", "omicron-common", "omicron-workspace-hack", diff --git a/dev-tools/mgs-dev/Cargo.toml b/dev-tools/mgs-dev/Cargo.toml index 771e82a0b1c..8a38d9d26c7 100644 --- a/dev-tools/mgs-dev/Cargo.toml +++ b/dev-tools/mgs-dev/Cargo.toml @@ -9,6 +9,7 @@ workspace = true [dependencies] anyhow.workspace = true +camino.workspace = true clap.workspace = true futures.workspace = true gateway-messages.workspace = true diff --git a/dev-tools/mgs-dev/src/main.rs b/dev-tools/mgs-dev/src/main.rs index caed71825aa..716955b6844 100644 --- a/dev-tools/mgs-dev/src/main.rs +++ b/dev-tools/mgs-dev/src/main.rs @@ -4,8 +4,10 @@ //! Developer tool for running MGS. +use camino::Utf8PathBuf; use clap::{Args, Parser, Subcommand}; use futures::StreamExt; +use gateway_test_utils::setup::DEFAULT_SP_SIM_CONFIG; use libc::SIGINT; use signal_hook_tokio::Signals; use std::net::SocketAddr; @@ -43,6 +45,9 @@ struct MgsRunArgs { /// Oximeter producer. #[clap(long)] nexus_address: Option, + /// Override the sp-sim configuration file. + #[clap(long, default_value = DEFAULT_SP_SIM_CONFIG)] + sp_sim_config_file: Utf8PathBuf, } impl MgsRunArgs { @@ -54,7 +59,9 @@ impl MgsRunArgs { println!("mgs-dev: setting up MGS ... "); let (mut mgs_config, sp_sim_config) = - gateway_test_utils::setup::load_test_config(); + gateway_test_utils::setup::load_test_config( + self.sp_sim_config_file.clone(), + ); if let Some(addr) = self.nexus_address { mgs_config.metrics = Some(gateway_test_utils::setup::MetricsConfig { diff --git a/dev-tools/omicron-dev/Cargo.toml b/dev-tools/omicron-dev/Cargo.toml index 2c7dbcabc66..17c5b89096a 100644 --- a/dev-tools/omicron-dev/Cargo.toml +++ b/dev-tools/omicron-dev/Cargo.toml @@ -12,9 +12,11 @@ omicron-rpaths.workspace = true [dependencies] anyhow.workspace = true +camino.workspace = true clap.workspace = true dropshot.workspace = true futures.workspace = true +gateway-test-utils.workspace = true libc.workspace = true nexus-config.workspace = true nexus-test-interface.workspace = true diff --git a/dev-tools/omicron-dev/src/main.rs b/dev-tools/omicron-dev/src/main.rs index 456154243d4..7eb569ba345 100644 --- a/dev-tools/omicron-dev/src/main.rs +++ b/dev-tools/omicron-dev/src/main.rs @@ -3,8 +3,10 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use anyhow::Context; +use camino::Utf8PathBuf; use clap::{Args, Parser, Subcommand}; use futures::StreamExt; +use gateway_test_utils::setup::DEFAULT_SP_SIM_CONFIG; use libc::SIGINT; use nexus_config::NexusConfig; use nexus_test_interface::NexusServer; @@ -45,6 +47,9 @@ struct RunAllArgs { /// Nexus external API listen port. Use `0` to request any available port. #[clap(long, action)] nexus_listen_port: Option, + /// Override the gateway server configuration file. + #[clap(long, default_value = DEFAULT_SP_SIM_CONFIG)] + gateway_config: Utf8PathBuf, } impl RunAllArgs { @@ -77,7 +82,7 @@ impl RunAllArgs { println!("omicron-dev: setting up all services ... "); let cptestctx = nexus_test_utils::omicron_dev_setup_with_config::< omicron_nexus::Server, - >(&mut config, 0) + >(&mut config, 0, self.gateway_config.clone()) .await .context("error setting up services")?; diff --git a/gateway-test-utils/configs/sp_sim_config_cabooses.test.toml b/gateway-test-utils/configs/sp_sim_config_cabooses.test.toml new file mode 100644 index 00000000000..c14316f8bd0 --- /dev/null +++ b/gateway-test-utils/configs/sp_sim_config_cabooses.test.toml @@ -0,0 +1,476 @@ +# +# SP simulator: example config file with customised cabosses +# + +# +# This file contains a configuration that is different from the default +# simulated SP configuration file by setting the values for the contents of the +# cabooses for "SimSidecar0" and "SimGimlet00". This type of configuration file +# is useful for cases when testing requires specific values for the cabooses. +# + +# +# NOTE: for the test suite, all ports MUST be 0 (in order to bind to any +# available port) because the test suite will be running many servers +# concurrently. +# +[[simulated_sps.sidecar]] +serial_number = "SimSidecar0" +manufacturing_root_cert_seed = "01de01de01de01de01de01de01de01de01de01de01de01de01de01de01de01de" +device_id_cert_seed = "01de000000000000000000000000000000000000000000000000000000000000" + +[simulated_sps.sidecar.cabooses.sp_slot_0] +board = "sidecar-b" +git_commit = "437c053649220611cdffc8a8d8d4033c0ef4b89c" +name = "sidecar-b" +version = "1.0.44" + +[simulated_sps.sidecar.cabooses.sp_slot_1] +board = "sidecar-b" +git_commit = "437c053649220611cdffc8a8d8d4033c0ef4b89c" +name = "sidecar-b" +version = "1.0.44" + +[simulated_sps.sidecar.cabooses.rot_slot_a] +board = "oxide-rot-1" +git_commit = "6edf9b5e6aa5c928a5462bda1f7a4c6f3caa40ab" +name = "oxide-rot-1" +version = "1.0.35" +sign = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + +[simulated_sps.sidecar.cabooses.rot_slot_b] +board = "oxide-rot-1" +git_commit = "6edf9b5e6aa5c928a5462bda1f7a4c6f3caa40ab" +name = "oxide-rot-1" +version = "1.0.35" +sign = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + +[simulated_sps.sidecar.cabooses.stage0] +board = "oxide-rot-1" +git_commit = "bdf56dd950b934360df596ed5b2d8b8813c92168" +name = "oxide-rot-1" +version = "1.4.0" +sign = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + +[simulated_sps.sidecar.cabooses.stage0_next] +board = "oxide-rot-1" +git_commit = "bdf56dd950b934360df596ed5b2d8b8813c92168" +name = "oxide-rot-1" +version = "1.4.0" +sign = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + +[[simulated_sps.sidecar.network_config]] +[simulated_sps.sidecar.network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.sidecar.network_config]] +[simulated_sps.sidecar.network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.sidecar.ereport_network_config]] +[simulated_sps.sidecar.ereport_network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.sidecar.ereport_network_config]] +[simulated_sps.sidecar.ereport_network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.sidecar.components]] +id = "dev-0" +device = "fake-tmp-sensor" +description = "FAKE temperature sensor 1" +capabilities = 0x2 +presence = "Present" +sensors = [ + {name = "Southwest", kind = "Temperature", last_data.value = 41.7890625, last_data.timestamp = 1234 }, +] + +[[simulated_sps.sidecar.components]] +id = "dev-1" +device = "fake-tmp-sensor" +description = "FAKE temperature sensor 2" +capabilities = 0x2 +presence = "Failed" +sensors = [ + { name = "South", kind = "Temperature", last_error.value = "DeviceError", last_error.timestamp = 1234 }, +] + +[simulated_sps.sidecar.ereport_config] +restart_id = "0d3e464a-666e-4687-976f-90e31238be8b" + +[[simulated_sps.sidecar.ereport_config.ereports]] +task_name = "task_thermal_server" +task_gen = 1 +uptime = 1235 +class = "oxide.sidecar.thermal.sensor_read_error" +sensor = { id = "dev-1", device = "fake-tmp-sensor", location = "South", presence = "Failed" } +error = "DeviceError" + +[[simulated_sps.sidecar]] +serial_number = "SimSidecar1" +manufacturing_root_cert_seed = "01de01de01de01de01de01de01de01de01de01de01de01de01de01de01de01de" +device_id_cert_seed = "01de000000000000000000000000000000000000000000000000000000000001" + +[[simulated_sps.sidecar.network_config]] +[simulated_sps.sidecar.network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.sidecar.network_config]] +[simulated_sps.sidecar.network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.sidecar.ereport_network_config]] +[simulated_sps.sidecar.ereport_network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.sidecar.ereport_network_config]] +[simulated_sps.sidecar.ereport_network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.gimlet]] +serial_number = "SimGimlet00" +manufacturing_root_cert_seed = "01de01de01de01de01de01de01de01de01de01de01de01de01de01de01de01de" +device_id_cert_seed = "01de000000000000000000000000000000000000000000000000000000000002" + +[simulated_sps.gimlet.cabooses.sp_slot_0] +board = "gimlet-e" +git_commit = "437c053649220611cdffc8a8d8d4033c0ef4b89c" +name = "gimlet-e" +version = "1.0.44" + +[simulated_sps.gimlet.cabooses.sp_slot_1] +board = "gimlet-e" +git_commit = "437c053649220611cdffc8a8d8d4033c0ef4b89c" +name = "gimlet-e" +version = "1.0.44" + +[simulated_sps.gimlet.cabooses.rot_slot_a] +board = "oxide-rot-1" +git_commit = "6edf9b5e6aa5c928a5462bda1f7a4c6f3caa40ab" +name = "oxide-rot-1" +version = "1.0.35" +sign = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + +[simulated_sps.gimlet.cabooses.rot_slot_b] +board = "oxide-rot-1" +git_commit = "6edf9b5e6aa5c928a5462bda1f7a4c6f3caa40ab" +name = "oxide-rot-1" +version = "1.0.35" +sign = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + +[simulated_sps.gimlet.cabooses.stage0] +board = "oxide-rot-1" +git_commit = "bdf56dd950b934360df596ed5b2d8b8813c92168" +name = "oxide-rot-1" +version = "1.4.0" +sign = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + +[simulated_sps.gimlet.cabooses.stage0_next] +board = "oxide-rot-1" +git_commit = "bdf56dd950b934360df596ed5b2d8b8813c92168" +name = "oxide-rot-1" +version = "1.4.0" +sign = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" + +[[simulated_sps.gimlet.network_config]] +[simulated_sps.gimlet.network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.gimlet.network_config]] +[simulated_sps.gimlet.network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.gimlet.ereport_network_config]] +[simulated_sps.gimlet.ereport_network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.gimlet.ereport_network_config]] +[simulated_sps.gimlet.ereport_network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.gimlet.components]] +id = "sp3-host-cpu" +device = "sp3-host-cpu" +description = "FAKE host cpu" +capabilities = 0 +presence = "Present" +serial_console = "[::1]:0" + +[[simulated_sps.gimlet.components]] +id = "dev-0" +device = "fake-tmp-sensor" +description = "FAKE temperature sensor" +capabilities = 0x2 +presence = "Failed" +sensors = [ + { name = "Southwest", kind = "Temperature", last_error.value = "DeviceError", last_error.timestamp = 1234 }, +] +[[simulated_sps.gimlet.components]] +id = "dev-1" +device = "tmp117" +description = "FAKE temperature sensor" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "South", kind = "Temperature", last_data.value = 42.5625, last_data.timestamp = 1234 }, +] + +[[simulated_sps.gimlet.components]] +id = "dev-2" +device = "tmp117" +description = "FAKE Southeast temperature sensor" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "Southeast", kind = "Temperature", last_data.value = 41.570313, last_data.timestamp = 1234 }, +] + +[[simulated_sps.gimlet.components]] +id = "dev-6" +device = "at24csw080" +description = "FAKE U.2 Sharkfin A VPD" +capabilities = 0x0 +presence = "Present" + +[[simulated_sps.gimlet.components]] +id = "dev-7" +device = "max5970" +description = "FAKE U.2 Sharkfin A hot swap controller" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "V12_U2A_A0", kind = "Current", last_data.value = 0.45898438, last_data.timestamp = 1234 }, + { name = "V3P3_U2A_A0", kind = "Current", last_data.value = 0.024414063, last_data.timestamp = 1234 }, + { name = "V12_U2A_A0", kind = "Voltage", last_data.value = 12.03125, last_data.timestamp = 1234 }, + { name = "V3P3_U2A_A0", kind = "Voltage", last_data.value = 3.328125, last_data.timestamp = 1234 }, +] + +[[simulated_sps.gimlet.components]] +id = "dev-8" +device = "nvme_bmc" +description = "FAKE U.2 A NVMe Basic Management Command" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "U2_N0", kind = "Temperature", last_data.value = 56.0, last_data.timestamp = 1234 }, +] +[[simulated_sps.gimlet.components]] +id = "dev-39" +device = "tmp451" +description = "FAKE T6 temperature sensor" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "t6", kind = "Temperature", last_data.value = 70.625, last_data.timestamp = 1234 }, +] + +[[simulated_sps.gimlet.components]] +id = "dev-46" +device = "sbtsi" +description = "CPU temperature sensor" +capabilities = 2 +presence = "Present" +sensors = [ + { name = "CPU", kind = "Temperature", last_data.value = 64.5, last_data.timestamp = 1234 }, +] + +[[simulated_sps.gimlet.components]] +id = "dev-53" +device = "max31790" +description = "FAKE Fan controller" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "Southeast", kind = "Speed", last_data.value = 2607.0, last_data.timestamp = 1234 }, + { name = "Northeast", kind = "Speed", last_data.value = 2476.0, last_data.timestamp = 1234 }, + { name = "South", kind = "Speed", last_data.value = 2553.0, last_data.timestamp = 1234 }, + { name = "North", kind = "Speed", last_data.value = 2265.0, last_data.timestamp = 1234 }, + { name = "Southwest", kind = "Speed", last_data.value = 2649.0, last_data.timestamp = 1234 }, + { name = "Northwest", kind = "Speed", last_data.value = 2275.0, last_data.timestamp = 1234 }, +] + +[simulated_sps.gimlet.ereport_config] +restart_id = "af1ebf85-36ba-4c31-bbec-b9825d6d9d8b" + +[[simulated_sps.gimlet.ereport_config.ereports]] +task_name = "task_apollo_server" +task_gen = 13 +uptime = 1233 +class = "gov.nasa.apollo.o2_tanks.stir.begin" +message = "stirring the tanks" + +[[simulated_sps.gimlet.ereport_config.ereports]] +task_name = "drv_ae35_server" +task_gen = 1 +uptime = 1234 +class = "io.discovery.ae35.fault" +message = "i've just picked up a fault in the AE-35 unit" +de = { scheme = "fmd", authority = { product-id = "HAL-9000-series computer", server-id = "HAL 9000"}, mod-name = "ae35-diagnosis" } +hours_to_failure = 72 + +[[simulated_sps.gimlet.ereport_config.ereports]] +task_name = "task_apollo_server" +task_gen = 13 +uptime = 1237 +class = "gov.nasa.apollo.fault" +message = "houston, we have a problem" +crew = ["Lovell", "Swigert", "Haise"] + +[[simulated_sps.gimlet.ereport_config.ereports]] +task_name = "drv_thingy_server" +task_gen = 2 +uptime = 1240 +class = "flagrant_error" +computer = false + +[[simulated_sps.gimlet.ereport_config.ereports]] +task_name = "task_latex_server" +task_gen = 1 +uptime = 1245 +class = "overfull_hbox" +badness = 10000 + +[[simulated_sps.gimlet]] +serial_number = "SimGimlet01" +manufacturing_root_cert_seed = "01de01de01de01de01de01de01de01de01de01de01de01de01de01de01de01de" +device_id_cert_seed = "01de000000000000000000000000000000000000000000000000000000000003" + +[[simulated_sps.gimlet.network_config]] +[simulated_sps.gimlet.network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.gimlet.network_config]] +[simulated_sps.gimlet.network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.gimlet.ereport_network_config]] +[simulated_sps.gimlet.ereport_network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.gimlet.ereport_network_config]] +[simulated_sps.gimlet.ereport_network_config.simulated] +bind_addr = "[::1]:0" + +[[simulated_sps.gimlet.components]] +id = "sp3-host-cpu" +device = "sp3-host-cpu" +description = "FAKE host cpu" +capabilities = 0 +presence = "Present" +serial_console = "[::1]:0" + + +[[simulated_sps.gimlet.components]] +id = "dev-0" +device = "tmp117" +description = "FAKE temperature sensor" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "Southwest", kind = "Temperature", last_data.value = 41.3629, last_data.timestamp = 1234 }, +] +[[simulated_sps.gimlet.components]] +id = "dev-1" +device = "tmp117" +description = "FAKE temperature sensor" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "South", kind = "Temperature", last_data.value = 42.5625, last_data.timestamp = 1234 }, +] + +[[simulated_sps.gimlet.components]] +id = "dev-2" +device = "tmp117" +description = "FAKE Southeast temperature sensor" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "Southeast", kind = "Temperature", last_data.value = 41.570313, last_data.timestamp = 1234 }, +] + +[[simulated_sps.gimlet.components]] +id = "dev-6" +device = "at24csw080" +description = "FAKE U.2 Sharkfin A VPD" +capabilities = 0x0 +presence = "Present" + +[[simulated_sps.gimlet.components]] +id = "dev-7" +device = "max5970" +description = "FAKE U.2 Sharkfin A hot swap controller" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "V12_U2A_A0", kind = "Current", last_data.value = 0.41893438, last_data.timestamp = 1234 }, + { name = "V3P3_U2A_A0", kind = "Current", last_data.value = 0.025614603, last_data.timestamp = 1234 }, + { name = "V12_U2A_A0", kind = "Voltage", last_data.value = 12.02914, last_data.timestamp = 1234 }, + { name = "V3P3_U2A_A0", kind = "Voltage", last_data.value = 3.2618, last_data.timestamp = 1234 }, +] + +[[simulated_sps.gimlet.components]] +id = "dev-8" +device = "nvme_bmc" +description = "FAKE U.2 A NVMe Basic Management Command" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "U2_N0", kind = "Temperature", last_data.value = 56.0, last_data.timestamp = 1234 }, +] +[[simulated_sps.gimlet.components]] +id = "dev-39" +device = "tmp451" +description = "FAKE T6 temperature sensor" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "t6", kind = "Temperature", last_data.value = 70.625, last_data.timestamp = 1234 }, +] + +[[simulated_sps.gimlet.components]] +id = "dev-46" +device = "sbtsi" +description = "CPU temperature sensor" +capabilities = 2 +presence = "Present" +sensors = [ + { name = "CPU", kind = "Temperature", last_data.value = 62.6, last_data.timestamp = 1234 }, +] + +[[simulated_sps.gimlet.components]] +id = "dev-53" +device = "max31790" +description = "FAKE Fan controller" +capabilities = 0x2 +presence = "Present" +sensors = [ + { name = "Southeast", kind = "Speed", last_data.value = 2510.0, last_data.timestamp = 1234 }, + { name = "Northeast", kind = "Speed", last_data.value = 2390.0, last_data.timestamp = 1234 }, + { name = "South", kind = "Speed", last_data.value = 2467.0, last_data.timestamp = 1234 }, + { name = "North", kind = "Speed", last_data.value = 2195.0, last_data.timestamp = 1234 }, + { name = "Southwest", kind = "Speed", last_data.value = 2680.0, last_data.timestamp = 1234 }, + { name = "Northwest", kind = "Speed", last_data.value = 2212.0, last_data.timestamp = 1234 }, +] + +[simulated_sps.gimlet.ereport_config] +restart_id = "55e30cc7-a109-492f-aca9-735ed725df3c" + +[[simulated_sps.gimlet.ereport_config.ereports]] +task_name = "task_thermal_server" +task_gen = 1 +uptime = 1233 +class = "computer.oxide.gimlet.chassis_integrity.fault" +nosub_class = "chassis_integrity.cat_hair_detected" +message = "cat hair detected inside gimlet" +de = { scheme = "fmd", mod-name = "hubris-thermal-diagnosis", mod-version = "1.0", authority = { "product-id" = "oxide", server-id = "SimGimlet1" }} +certainty = 0x64 +cat_hair_amount = 10000 + +# +# NOTE: for the test suite, the [log] section is ignored; sp-sim logs are rolled +# into the gateway logfile. +# +[log] +level = "debug" +mode = "stderr-terminal" diff --git a/gateway-test-utils/src/setup.rs b/gateway-test-utils/src/setup.rs index dea606ab77c..fd5dbd29211 100644 --- a/gateway-test-utils/src/setup.rs +++ b/gateway-test-utils/src/setup.rs @@ -5,6 +5,7 @@ // Copyright 2022 Oxide Computer Company use camino::Utf8Path; +use camino::Utf8PathBuf; use dropshot::test_util::ClientTestContext; use dropshot::test_util::LogContext; use gateway_messages::SpPort; @@ -32,6 +33,8 @@ use uuid::Uuid; // TODO this exact value is copy/pasted from `nexus/test-utils` - should we // import it or have our own? const RACK_UUID: &str = "c19a698f-c6f9-4a17-ae30-20d711b8f7dc"; +pub const DEFAULT_SP_SIM_CONFIG: &str = + concat!(env!("CARGO_MANIFEST_DIR"), "/configs/sp_sim_config.test.toml"); pub struct GatewayTestContext { pub client: ClientTestContext, @@ -62,24 +65,22 @@ impl GatewayTestContext { } } -pub fn load_test_config() -> (omicron_gateway::Config, sp_sim::Config) { +pub fn load_test_config( + sp_sim_config_file: Utf8PathBuf, +) -> (omicron_gateway::Config, sp_sim::Config) { // The test configs are located relative to the directory this file is in. // TODO: embed these with include_str! instead? let manifest_dir = Utf8Path::new(env!("CARGO_MANIFEST_DIR")); - let server_config_file_path = manifest_dir.join("configs/config.test.toml"); - let server_config = - match omicron_gateway::Config::from_file(&server_config_file_path) { - Ok(config) => config, - Err(e) => panic!("failed to load MGS config: {e}"), - }; + let config_path = manifest_dir.join("configs/config.test.toml"); + let server_config = omicron_gateway::Config::from_file(&config_path) + .unwrap_or_else(|e| { + panic!("failed to load MGS config from {config_path}: {e}") + }); - let sp_sim_config_file_path = - manifest_dir.join("configs/sp_sim_config.test.toml"); - let sp_sim_config = - match sp_sim::Config::from_file(&sp_sim_config_file_path) { - Ok(config) => config, - Err(e) => panic!("failed to load SP simulator config: {e}"), - }; + let sp_sim_config = match sp_sim::Config::from_file(sp_sim_config_file) { + Ok(config) => config, + Err(e) => panic!("failed to load SP simulator config: {e}"), + }; (server_config, sp_sim_config) } @@ -87,7 +88,8 @@ pub async fn test_setup( test_name: &str, sp_port: SpPort, ) -> GatewayTestContext { - let (server_config, sp_sim_config) = load_test_config(); + let (server_config, sp_sim_config) = + load_test_config(DEFAULT_SP_SIM_CONFIG.into()); test_setup_with_config( test_name, sp_port, diff --git a/nexus/mgs-updates/tests/sp_updater.rs b/nexus/mgs-updates/tests/sp_updater.rs index ef7c4cd7978..bae4d1eaa9d 100644 --- a/nexus/mgs-updates/tests/sp_updater.rs +++ b/nexus/mgs-updates/tests/sp_updater.rs @@ -7,7 +7,7 @@ use gateway_client::SpComponent; use gateway_client::types::SpType; use gateway_messages::{SpPort, UpdateInProgressStatus, UpdateStatus}; -use gateway_test_utils::setup as mgs_setup; +use gateway_test_utils::setup::{self as mgs_setup, DEFAULT_SP_SIM_CONFIG}; use hubtools::RawHubrisArchive; use hubtools::{CabooseBuilder, HubrisArchiveBuilder}; use nexus_mgs_updates::{MgsClients, SpUpdater, UpdateProgress}; @@ -567,7 +567,8 @@ async fn test_sp_updater_switches_mgs_instances_on_failure() { async fn test_sp_updater_delivers_progress() { // Start MGS + Sim SP. let mgstestctx = { - let (mut mgs_config, sp_sim_config) = mgs_setup::load_test_config(); + let (mut mgs_config, sp_sim_config) = + mgs_setup::load_test_config(DEFAULT_SP_SIM_CONFIG.into()); // Enabling SP metrics collection makes this alread-flaky test even // flakier, so let's just turn it off. // TODO(eliza): it would be nice if we didn't have to disable metrics in diff --git a/nexus/test-utils/src/lib.rs b/nexus/test-utils/src/lib.rs index 9a76249fb12..e5aaf2474d4 100644 --- a/nexus/test-utils/src/lib.rs +++ b/nexus/test-utils/src/lib.rs @@ -8,6 +8,7 @@ use anyhow::Context; use anyhow::Result; use camino::Utf8Path; +use camino::Utf8PathBuf; use chrono::Utc; use dropshot::ConfigLogging; use dropshot::ConfigLoggingLevel; @@ -16,6 +17,7 @@ use dropshot::test_util::ClientTestContext; use dropshot::test_util::LogContext; use futures::FutureExt; use futures::future::BoxFuture; +use gateway_test_utils::setup::DEFAULT_SP_SIM_CONFIG; use gateway_test_utils::setup::GatewayTestContext; use hickory_resolver::TokioResolver; use hickory_resolver::config::NameServerConfig; @@ -327,6 +329,7 @@ pub async fn test_setup( sim::SimMode::Explicit, None, extra_sled_agents, + DEFAULT_SP_SIM_CONFIG.into(), ) .await } @@ -673,10 +676,12 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { &mut self, switch_location: SwitchLocation, port: Option, + sp_sim_config_file: Utf8PathBuf, ) { debug!(&self.logctx.log, "Starting Management Gateway"); let (mgs_config, sp_sim_config) = - gateway_test_utils::setup::load_test_config(); + gateway_test_utils::setup::load_test_config(sp_sim_config_file); + let mgs_addr = port.map(|port| SocketAddrV6::new(Ipv6Addr::LOCALHOST, port, 0, 0)); let gateway = gateway_test_utils::setup::test_setup_with_config( @@ -1583,6 +1588,7 @@ enum PopulateCrdb { pub async fn omicron_dev_setup_with_config( config: &mut NexusConfig, extra_sled_agents: u16, + gateway_config_file: Utf8PathBuf, ) -> Result> { let builder = ControlPlaneTestContextBuilder::::new("omicron-dev", config); @@ -1609,6 +1615,7 @@ pub async fn omicron_dev_setup_with_config( sim::SimMode::Auto, None, extra_sled_agents, + gateway_config_file, ) .await) } @@ -1620,6 +1627,7 @@ pub async fn test_setup_with_config( sim_mode: sim::SimMode, initial_cert: Option, extra_sled_agents: u16, + gateway_config_file: Utf8PathBuf, ) -> ControlPlaneTestContext { let builder = ControlPlaneTestContextBuilder::::new(test_name, config); setup_with_config_impl( @@ -1628,6 +1636,7 @@ pub async fn test_setup_with_config( sim_mode, initial_cert, extra_sled_agents, + gateway_config_file, ) .await } @@ -1638,6 +1647,7 @@ async fn setup_with_config_impl( sim_mode: sim::SimMode, initial_cert: Option, extra_sled_agents: u16, + gateway_config_file: Utf8PathBuf, ) -> ControlPlaneTestContext { const STEP_TIMEOUT: Duration = Duration::from_secs(60); @@ -1664,6 +1674,7 @@ async fn setup_with_config_impl( // be configured. If extra sled agents are requested, then the second sled // agent will be for switch1. + let mgs_config = gateway_config_file.clone(); builder .init_with_steps( vec![ @@ -1671,7 +1682,11 @@ async fn setup_with_config_impl( "start_gateway_switch0", Box::new(|builder| { builder - .start_gateway(SwitchLocation::Switch0, None) + .start_gateway( + SwitchLocation::Switch0, + None, + mgs_config, + ) .boxed() }), ), @@ -1711,7 +1726,11 @@ async fn setup_with_config_impl( "start_gateway_switch1", Box::new(|builder| { builder - .start_gateway(SwitchLocation::Switch1, None) + .start_gateway( + SwitchLocation::Switch1, + None, + gateway_config_file, + ) .boxed() }), ), diff --git a/nexus/tests/integration_tests/certificates.rs b/nexus/tests/integration_tests/certificates.rs index 57d90e27006..79711ea1995 100644 --- a/nexus/tests/integration_tests/certificates.rs +++ b/nexus/tests/integration_tests/certificates.rs @@ -8,6 +8,7 @@ use display_error_chain::ErrorChainExt; use dropshot::HttpErrorResponseBody; use dropshot::test_util::ClientTestContext; use futures::TryStreamExt; +use gateway_test_utils::setup::DEFAULT_SP_SIM_CONFIG; use http::StatusCode; use http::method::Method; use internal_dns_types::names::DNS_ZONE_EXTERNAL_TESTING; @@ -351,6 +352,7 @@ async fn test_silo_certificates() { omicron_sled_agent::sim::SimMode::Explicit, Some(silo1.cert.clone()), 0, + DEFAULT_SP_SIM_CONFIG.into(), ) .await }; diff --git a/nexus/tests/integration_tests/console_api.rs b/nexus/tests/integration_tests/console_api.rs index 8609474e212..f3cadbe9e9a 100644 --- a/nexus/tests/integration_tests/console_api.rs +++ b/nexus/tests/integration_tests/console_api.rs @@ -6,6 +6,7 @@ use anyhow::Context; use camino::Utf8PathBuf; use dropshot::ResultsPage; use dropshot::test_util::ClientTestContext; +use gateway_test_utils::setup::DEFAULT_SP_SIM_CONFIG; use http::{StatusCode, header, method::Method}; use nexus_auth::context::OpContext; use std::env::current_dir; @@ -393,6 +394,7 @@ async fn test_absolute_static_dir() { sim::SimMode::Explicit, None, 0, + DEFAULT_SP_SIM_CONFIG.into(), ) .await; let testctx = &cptestctx.external_client; @@ -903,6 +905,7 @@ async fn test_session_idle_timeout_deletes_session() { sim::SimMode::Explicit, None, 0, + DEFAULT_SP_SIM_CONFIG.into(), ) .await; let testctx = &cptestctx.external_client; diff --git a/nexus/tests/integration_tests/device_auth.rs b/nexus/tests/integration_tests/device_auth.rs index 60532eff4e8..78ea189a629 100644 --- a/nexus/tests/integration_tests/device_auth.rs +++ b/nexus/tests/integration_tests/device_auth.rs @@ -7,6 +7,7 @@ use std::num::NonZeroU32; use chrono::Utc; use dropshot::test_util::ClientTestContext; use dropshot::{HttpErrorResponseBody, ResultsPage}; +use gateway_test_utils::setup::DEFAULT_SP_SIM_CONFIG; use nexus_auth::authn::USER_TEST_UNPRIVILEGED; use nexus_config::NexusConfig; use nexus_db_queries::db::fixed_data::silo::DEFAULT_SILO; @@ -803,6 +804,7 @@ async fn test_session_list_with_config( sim::SimMode::Explicit, None, 0, + DEFAULT_SP_SIM_CONFIG.into(), ) .await; let testctx = &cptestctx.external_client; diff --git a/nexus/tests/integration_tests/initialization.rs b/nexus/tests/integration_tests/initialization.rs index 989029625a5..fa4511d6de1 100644 --- a/nexus/tests/integration_tests/initialization.rs +++ b/nexus/tests/integration_tests/initialization.rs @@ -2,6 +2,7 @@ // 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/. +use gateway_test_utils::setup::DEFAULT_SP_SIM_CONFIG; use nexus_config::Database; use nexus_config::InternalDns; use nexus_test_interface::NexusServer; @@ -90,8 +91,20 @@ async fn test_nexus_boots_before_dendrite() { // inside of Nexus initialization. We must use MGS_PORT here because Nexus // hardcodes it. info!(&log, "Starting MGS"); - builder.start_gateway(SwitchLocation::Switch0, Some(MGS_PORT)).await; - builder.start_gateway(SwitchLocation::Switch1, None).await; + builder + .start_gateway( + SwitchLocation::Switch0, + Some(MGS_PORT), + DEFAULT_SP_SIM_CONFIG.into(), + ) + .await; + builder + .start_gateway( + SwitchLocation::Switch1, + None, + DEFAULT_SP_SIM_CONFIG.into(), + ) + .await; info!(&log, "Started MGS"); let populate = true; diff --git a/nexus/tests/integration_tests/metrics.rs b/nexus/tests/integration_tests/metrics.rs index 7b36814cbc6..bf3551353e6 100644 --- a/nexus/tests/integration_tests/metrics.rs +++ b/nexus/tests/integration_tests/metrics.rs @@ -9,6 +9,7 @@ use crate::integration_tests::instances::{ }; use chrono::Utc; use dropshot::HttpErrorResponseBody; +use gateway_test_utils::setup::DEFAULT_SP_SIM_CONFIG; use http::{Method, StatusCode}; use nexus_auth::authn::USER_TEST_UNPRIVILEGED; use nexus_db_queries::db::identity::Asset; @@ -651,7 +652,9 @@ async fn test_mgs_metrics( ) { // Make a MGS let (mut mgs_config, sp_sim_config) = - gateway_test_utils::setup::load_test_config(); + gateway_test_utils::setup::load_test_config( + DEFAULT_SP_SIM_CONFIG.into(), + ); let mgs = { // munge the already-parsed MGS config file to point it at the test // Nexus' address. diff --git a/sp-sim/Cargo.toml b/sp-sim/Cargo.toml index 9f273d4ac69..0f453d416c8 100644 --- a/sp-sim/Cargo.toml +++ b/sp-sim/Cargo.toml @@ -18,6 +18,7 @@ gateway-messages.workspace = true gateway-types.workspace = true hex = { workspace = true, features = [ "serde" ] } hubtools.workspace = true +nexus-types.workspace = true omicron-common.workspace = true oxide-tokio-rt.workspace = true serde.workspace = true diff --git a/sp-sim/src/config.rs b/sp-sim/src/config.rs index b78d0ab58fa..d5c03d87b7c 100644 --- a/sp-sim/src/config.rs +++ b/sp-sim/src/config.rs @@ -9,6 +9,7 @@ use crate::sensors; use dropshot::ConfigLogging; use gateway_messages::DeviceCapabilities; use gateway_messages::DevicePresence; +use nexus_types::inventory::Caboose; use serde::Deserialize; use serde::Serialize; use std::net::Ipv6Addr; @@ -70,6 +71,17 @@ impl slog::KV for NetworkConfig { } } +/// Configuration for every caboose in the SP +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct SpCabooses { + pub sp_slot_0: Caboose, + pub sp_slot_1: Caboose, + pub rot_slot_a: Caboose, + pub rot_slot_b: Caboose, + pub stage0: Caboose, + pub stage0_next: Caboose, +} + /// Common configuration for all flavors of SP #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct SpCommonConfig { @@ -100,6 +112,10 @@ pub struct SpCommonConfig { /// Fake ereport configuration #[serde(default)] pub ereport_config: EreportConfig, + /// Configurable caboose values. If unset, these will be + /// populated with default values + #[serde(skip_serializing_if = "Option::is_none")] + pub cabooses: Option, } /// Configuration of a simulated SP component diff --git a/sp-sim/src/gimlet.rs b/sp-sim/src/gimlet.rs index 4250b4a7aed..c6daf61f7d6 100644 --- a/sp-sim/src/gimlet.rs +++ b/sp-sim/src/gimlet.rs @@ -296,6 +296,7 @@ impl Gimlet { BaseboardKind::Gimlet, gimlet.common.no_stage0_caboose, phase1_hash_policy, + gimlet.common.cabooses.clone(), ); let ereport_state = { let mut cfg = gimlet.common.ereport_config.clone(); diff --git a/sp-sim/src/sidecar.rs b/sp-sim/src/sidecar.rs index 341cc0994de..828049b8807 100644 --- a/sp-sim/src/sidecar.rs +++ b/sp-sim/src/sidecar.rs @@ -255,6 +255,14 @@ impl Sidecar { EreportState::new(cfg, ereport_log) }; + let update_state = SimSpUpdate::new( + BaseboardKind::Sidecar, + sidecar.common.no_stage0_caboose, + // sidecar doesn't have phase 1 flash; any policy is fine + HostFlashHashPolicy::assume_already_hashed(), + sidecar.common.cabooses.clone(), + ); + let power_state_changes = Arc::new(AtomicUsize::new(0)); let (inner, handler, responses_sent_count) = Inner::new( servers, @@ -266,7 +274,7 @@ impl Sidecar { commands_rx, log, sidecar.common.old_rot_state, - sidecar.common.no_stage0_caboose, + update_state, Arc::clone(&power_state_changes), ); let inner_task = @@ -338,7 +346,7 @@ impl Inner { commands: mpsc::UnboundedReceiver, log: Logger, old_rot_state: bool, - no_stage0_caboose: bool, + update_state: SimSpUpdate, power_state_changes: Arc, ) -> (Self, Arc>, watch::Receiver) { let [udp0, udp1] = servers; @@ -348,7 +356,7 @@ impl Inner { ignition, log, old_rot_state, - no_stage0_caboose, + update_state, power_state_changes, ))); let responses_sent_count = watch::Sender::new(0); @@ -513,7 +521,7 @@ impl Handler { ignition: FakeIgnition, log: Logger, old_rot_state: bool, - no_stage0_caboose: bool, + update_state: SimSpUpdate, power_state_changes: Arc, ) -> Self { let mut leaked_component_device_strings = @@ -542,12 +550,7 @@ impl Handler { ignition, power_state: PowerState::A2, power_state_changes, - update_state: SimSpUpdate::new( - BaseboardKind::Sidecar, - no_stage0_caboose, - // sidecar doesn't have phase 1 flash; any policy is fine - HostFlashHashPolicy::assume_already_hashed(), - ), + update_state, reset_pending: None, should_fail_to_respond_signal: None, old_rot_state, diff --git a/sp-sim/src/update.rs b/sp-sim/src/update.rs index 35e54e9d162..6defb14b8b4 100644 --- a/sp-sim/src/update.rs +++ b/sp-sim/src/update.rs @@ -12,6 +12,7 @@ use crate::SIM_GIMLET_BOARD; use crate::SIM_ROT_BOARD; use crate::SIM_ROT_STAGE0_BOARD; use crate::SIM_SIDECAR_BOARD; +use crate::config::SpCabooses; use crate::helpers::rot_slot_id_from_u16; use crate::helpers::rot_slot_id_to_u16; use gateway_messages::Fwid; @@ -24,6 +25,7 @@ use gateway_messages::UpdateChunk; use gateway_messages::UpdateId; use gateway_messages::UpdateInProgressStatus; use hubtools::RawHubrisImage; +use nexus_types::inventory::Caboose; use omicron_common::disk::M2Slot; use sha2::Sha256; use sha3::Digest; @@ -78,6 +80,7 @@ impl SimSpUpdate { baseboard_kind: BaseboardKind, no_stage0_caboose: bool, phase1_hash_policy: HostFlashHashPolicy, + cabooses: Option, ) -> Self { const SP_GITC0: &str = "ffffffff"; const SP_GITC1: &str = "fefefefe"; @@ -97,46 +100,78 @@ impl SimSpUpdate { const STAGE0_VERS0: &str = "0.0.200"; const STAGE0_VERS1: &str = "0.0.200"; - let sp_board = baseboard_kind.sp_board(); - let sp_name = baseboard_kind.sp_name(); - let rot_name = baseboard_kind.rot_name(); - - let caboose_sp_active = CabooseValue::Caboose( - hubtools::CabooseBuilder::default() - .git_commit(SP_GITC0) - .board(sp_board) - .name(sp_name) - .version(SP_VERS0) - .build(), - ); - let caboose_sp_inactive = CabooseValue::Caboose( - hubtools::CabooseBuilder::default() - .git_commit(SP_GITC1) - .board(sp_board) - .name(sp_name) - .version(SP_VERS1) - .build(), - ); - - let caboose_rot_a = CabooseValue::Caboose( - hubtools::CabooseBuilder::default() - .git_commit(ROT_GITC0) - .board(SIM_ROT_BOARD) - .name(rot_name) - .version(ROT_VERS0) - .sign(ROT_STAGING_DEVEL_SIGN) - .build(), - ); - - let caboose_rot_b = CabooseValue::Caboose( - hubtools::CabooseBuilder::default() - .git_commit(ROT_GITC1) - .board(SIM_ROT_BOARD) - .name(rot_name) - .version(ROT_VERS1) - .sign(ROT_STAGING_DEVEL_SIGN) - .build(), - ); + // If we find in the config preset cabooses we use those. Otherwise, we + // can use default values + let ( + sp_active_src, + sp_inactive_src, + rot_a_src, + rot_b_src, + stage0_src, + stage0_next_src, + ) = if let Some(c) = cabooses { + ( + c.sp_slot_0, + c.sp_slot_1, + c.rot_slot_a, + c.rot_slot_b, + c.stage0, + c.stage0_next, + ) + } else { + let sp_board = baseboard_kind.sp_board().to_string(); + let sp_name = baseboard_kind.sp_name().to_string(); + let rot_name = baseboard_kind.rot_name().to_string(); + ( + Caboose { + git_commit: SP_GITC0.to_string(), + board: sp_board.clone(), + name: sp_name.clone(), + version: SP_VERS0.to_string(), + sign: None, + }, + Caboose { + git_commit: SP_GITC1.to_string(), + board: sp_board.clone(), + name: sp_name.clone(), + version: SP_VERS1.to_string(), + sign: None, + }, + Caboose { + git_commit: ROT_GITC0.to_string(), + board: SIM_ROT_BOARD.to_string(), + name: rot_name.clone(), + version: ROT_VERS0.to_string(), + sign: Some(ROT_STAGING_DEVEL_SIGN.to_string()), + }, + Caboose { + git_commit: ROT_GITC1.to_string(), + board: SIM_ROT_BOARD.to_string(), + name: rot_name.clone(), + version: ROT_VERS1.to_string(), + sign: Some(ROT_STAGING_DEVEL_SIGN.to_string()), + }, + Caboose { + git_commit: STAGE0_GITC0.to_string(), + board: SIM_ROT_STAGE0_BOARD.to_string(), + name: rot_name.clone(), + version: STAGE0_VERS0.to_string(), + sign: Some(ROT_STAGING_DEVEL_SIGN.to_string()), + }, + Caboose { + git_commit: STAGE0_GITC1.to_string(), + board: SIM_ROT_STAGE0_BOARD.to_string(), + name: rot_name.clone(), + version: STAGE0_VERS1.to_string(), + sign: Some(ROT_STAGING_DEVEL_SIGN.to_string()), + }, + ) + }; + + let caboose_sp_active = build_caboose(sp_active_src); + let caboose_sp_inactive = build_caboose(sp_inactive_src); + let caboose_rot_a = build_caboose(rot_a_src); + let caboose_rot_b = build_caboose(rot_b_src); let (caboose_stage0, caboose_stage0next) = if no_stage0_caboose { ( @@ -144,26 +179,7 @@ impl SimSpUpdate { CabooseValue::InvalidMissingAllKeys, ) } else { - ( - CabooseValue::Caboose( - hubtools::CabooseBuilder::default() - .git_commit(STAGE0_GITC0) - .board(SIM_ROT_STAGE0_BOARD) - .name(rot_name) - .version(STAGE0_VERS0) - .sign(ROT_STAGING_DEVEL_SIGN) - .build(), - ), - CabooseValue::Caboose( - hubtools::CabooseBuilder::default() - .git_commit(STAGE0_GITC1) - .board(SIM_ROT_STAGE0_BOARD) - .name(rot_name) - .version(STAGE0_VERS1) - .sign(ROT_STAGING_DEVEL_SIGN) - .build(), - ), - ) + (build_caboose(stage0_src), build_caboose(stage0_next_src)) }; const SLOT_A_DIGEST: [u8; 32] = [0xaa; 32]; @@ -750,6 +766,21 @@ impl CabooseValue { } } +// A helper function to build a CabooseValue from Caboose +fn build_caboose(source: Caboose) -> CabooseValue { + let mut builder = hubtools::CabooseBuilder::default() + .git_commit(source.git_commit) + .board(source.board) + .name(source.name) + .version(source.version); + + if let Some(sign_str) = source.sign { + builder = builder.sign(sign_str); + } + + CabooseValue::Caboose(builder.build()) +} + enum UpdateState { NotPrepared, Prepared {