diff --git a/Cargo.lock b/Cargo.lock index 3b1d2a1c21..306e953049 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -496,7 +496,7 @@ dependencies = [ [[package]] name = "bhyve_api" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=42c878b71a58d430dfc306126af5d40ca816d70f#42c878b71a58d430dfc306126af5d40ca816d70f" +source = "git+https://github.com/oxidecomputer/propolis?rev=901b710b6e5bd05a94a323693c2b971e7e7b240e#901b710b6e5bd05a94a323693c2b971e7e7b240e" dependencies = [ "bhyve_api_sys", "libc", @@ -506,7 +506,7 @@ dependencies = [ [[package]] name = "bhyve_api_sys" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=42c878b71a58d430dfc306126af5d40ca816d70f#42c878b71a58d430dfc306126af5d40ca816d70f" +source = "git+https://github.com/oxidecomputer/propolis?rev=901b710b6e5bd05a94a323693c2b971e7e7b240e#901b710b6e5bd05a94a323693c2b971e7e7b240e" dependencies = [ "libc", "strum", @@ -1236,7 +1236,7 @@ dependencies = [ [[package]] name = "cpuid_profile_config" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=42c878b71a58d430dfc306126af5d40ca816d70f#42c878b71a58d430dfc306126af5d40ca816d70f" +source = "git+https://github.com/oxidecomputer/propolis?rev=901b710b6e5bd05a94a323693c2b971e7e7b240e#901b710b6e5bd05a94a323693c2b971e7e7b240e" dependencies = [ "propolis", "serde", @@ -2024,7 +2024,7 @@ checksum = "7e1a8646b2c125eeb9a84ef0faa6d2d102ea0d5da60b824ade2743263117b848" [[package]] name = "dladm" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=42c878b71a58d430dfc306126af5d40ca816d70f#42c878b71a58d430dfc306126af5d40ca816d70f" +source = "git+https://github.com/oxidecomputer/propolis?rev=901b710b6e5bd05a94a323693c2b971e7e7b240e#901b710b6e5bd05a94a323693c2b971e7e7b240e" dependencies = [ "libc", "strum", @@ -3398,7 +3398,7 @@ dependencies = [ [[package]] name = "illumos-sys-hdrs" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=98d33125413f01722947e322f82caf9d22209434#98d33125413f01722947e322f82caf9d22209434" +source = "git+https://github.com/oxidecomputer/opte?rev=631c2017f19cafb1535f621e9e5aa9198ccad869#631c2017f19cafb1535f621e9e5aa9198ccad869" [[package]] name = "illumos-utils" @@ -3819,7 +3819,7 @@ dependencies = [ [[package]] name = "kstat-macro" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=98d33125413f01722947e322f82caf9d22209434#98d33125413f01722947e322f82caf9d22209434" +source = "git+https://github.com/oxidecomputer/opte?rev=631c2017f19cafb1535f621e9e5aa9198ccad869#631c2017f19cafb1535f621e9e5aa9198ccad869" dependencies = [ "quote", "syn 1.0.109", @@ -4959,6 +4959,8 @@ dependencies = [ "dropshot", "expectorate", "futures", + "gateway-messages", + "gateway-test-utils", "libc", "nexus-test-interface", "nexus-test-utils", @@ -5148,6 +5150,9 @@ dependencies = [ "dropshot", "expectorate", "futures", + "gateway-client", + "gateway-messages", + "gateway-test-utils", "humantime", "internal-dns 0.1.0", "ipnetwork", @@ -5346,6 +5351,7 @@ dependencies = [ "camino-tempfile", "dropshot", "expectorate", + "filetime", "futures", "headers", "hex", @@ -5592,7 +5598,7 @@ dependencies = [ [[package]] name = "opte" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=98d33125413f01722947e322f82caf9d22209434#98d33125413f01722947e322f82caf9d22209434" +source = "git+https://github.com/oxidecomputer/opte?rev=631c2017f19cafb1535f621e9e5aa9198ccad869#631c2017f19cafb1535f621e9e5aa9198ccad869" dependencies = [ "cfg-if 0.1.10", "dyn-clone", @@ -5609,7 +5615,7 @@ dependencies = [ [[package]] name = "opte-api" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=98d33125413f01722947e322f82caf9d22209434#98d33125413f01722947e322f82caf9d22209434" +source = "git+https://github.com/oxidecomputer/opte?rev=631c2017f19cafb1535f621e9e5aa9198ccad869#631c2017f19cafb1535f621e9e5aa9198ccad869" dependencies = [ "cfg-if 0.1.10", "illumos-sys-hdrs", @@ -5622,7 +5628,7 @@ dependencies = [ [[package]] name = "opte-ioctl" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=98d33125413f01722947e322f82caf9d22209434#98d33125413f01722947e322f82caf9d22209434" +source = "git+https://github.com/oxidecomputer/opte?rev=631c2017f19cafb1535f621e9e5aa9198ccad869#631c2017f19cafb1535f621e9e5aa9198ccad869" dependencies = [ "libc", "libnet", @@ -5702,7 +5708,7 @@ dependencies = [ [[package]] name = "oxide-vpc" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/opte?rev=98d33125413f01722947e322f82caf9d22209434#98d33125413f01722947e322f82caf9d22209434" +source = "git+https://github.com/oxidecomputer/opte?rev=631c2017f19cafb1535f621e9e5aa9198ccad869#631c2017f19cafb1535f621e9e5aa9198ccad869" dependencies = [ "cfg-if 0.1.10", "illumos-sys-hdrs", @@ -6602,7 +6608,7 @@ dependencies = [ [[package]] name = "propolis" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=42c878b71a58d430dfc306126af5d40ca816d70f#42c878b71a58d430dfc306126af5d40ca816d70f" +source = "git+https://github.com/oxidecomputer/propolis?rev=901b710b6e5bd05a94a323693c2b971e7e7b240e#901b710b6e5bd05a94a323693c2b971e7e7b240e" dependencies = [ "anyhow", "bhyve_api", @@ -6635,7 +6641,7 @@ dependencies = [ [[package]] name = "propolis-client" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=42c878b71a58d430dfc306126af5d40ca816d70f#42c878b71a58d430dfc306126af5d40ca816d70f" +source = "git+https://github.com/oxidecomputer/propolis?rev=901b710b6e5bd05a94a323693c2b971e7e7b240e#901b710b6e5bd05a94a323693c2b971e7e7b240e" dependencies = [ "async-trait", "base64 0.21.4", @@ -6659,7 +6665,7 @@ dependencies = [ [[package]] name = "propolis-server" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=42c878b71a58d430dfc306126af5d40ca816d70f#42c878b71a58d430dfc306126af5d40ca816d70f" +source = "git+https://github.com/oxidecomputer/propolis?rev=901b710b6e5bd05a94a323693c2b971e7e7b240e#901b710b6e5bd05a94a323693c2b971e7e7b240e" dependencies = [ "anyhow", "async-trait", @@ -6711,7 +6717,7 @@ dependencies = [ [[package]] name = "propolis-server-config" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=42c878b71a58d430dfc306126af5d40ca816d70f#42c878b71a58d430dfc306126af5d40ca816d70f" +source = "git+https://github.com/oxidecomputer/propolis?rev=901b710b6e5bd05a94a323693c2b971e7e7b240e#901b710b6e5bd05a94a323693c2b971e7e7b240e" dependencies = [ "cpuid_profile_config", "serde", @@ -6723,7 +6729,7 @@ dependencies = [ [[package]] name = "propolis_types" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=42c878b71a58d430dfc306126af5d40ca816d70f#42c878b71a58d430dfc306126af5d40ca816d70f" +source = "git+https://github.com/oxidecomputer/propolis?rev=901b710b6e5bd05a94a323693c2b971e7e7b240e#901b710b6e5bd05a94a323693c2b971e7e7b240e" dependencies = [ "schemars", "serde", @@ -9770,7 +9776,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "viona_api" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=42c878b71a58d430dfc306126af5d40ca816d70f#42c878b71a58d430dfc306126af5d40ca816d70f" +source = "git+https://github.com/oxidecomputer/propolis?rev=901b710b6e5bd05a94a323693c2b971e7e7b240e#901b710b6e5bd05a94a323693c2b971e7e7b240e" dependencies = [ "libc", "viona_api_sys", @@ -9779,7 +9785,7 @@ dependencies = [ [[package]] name = "viona_api_sys" version = "0.0.0" -source = "git+https://github.com/oxidecomputer/propolis?rev=42c878b71a58d430dfc306126af5d40ca816d70f#42c878b71a58d430dfc306126af5d40ca816d70f" +source = "git+https://github.com/oxidecomputer/propolis?rev=901b710b6e5bd05a94a323693c2b971e7e7b240e#901b710b6e5bd05a94a323693c2b971e7e7b240e" dependencies = [ "libc", ] diff --git a/Cargo.toml b/Cargo.toml index 185dc2bc35..da7b582fe3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -184,6 +184,7 @@ dropshot = { git = "https://github.com/oxidecomputer/dropshot", branch = "main", either = "1.9.0" expectorate = "1.1.0" fatfs = "0.3.6" +filetime = "0.2.22" flate2 = "1.0.27" flume = "0.11.0" foreign-types = "0.3.2" @@ -247,7 +248,7 @@ omicron-sled-agent = { path = "sled-agent" } omicron-test-utils = { path = "test-utils" } omicron-zone-package = "0.8.3" oxide-client = { path = "clients/oxide-client" } -oxide-vpc = { git = "https://github.com/oxidecomputer/opte", rev = "98d33125413f01722947e322f82caf9d22209434", features = [ "api", "std" ] } +oxide-vpc = { git = "https://github.com/oxidecomputer/opte", rev = "631c2017f19cafb1535f621e9e5aa9198ccad869", features = [ "api", "std" ] } once_cell = "1.18.0" openapi-lint = { git = "https://github.com/oxidecomputer/openapi-lint", branch = "main" } openapiv3 = "1.0" @@ -255,7 +256,7 @@ openapiv3 = "1.0" openssl = "0.10" openssl-sys = "0.9" openssl-probe = "0.1.2" -opte-ioctl = { git = "https://github.com/oxidecomputer/opte", rev = "98d33125413f01722947e322f82caf9d22209434" } +opte-ioctl = { git = "https://github.com/oxidecomputer/opte", rev = "631c2017f19cafb1535f621e9e5aa9198ccad869" } oso = "0.26" owo-colors = "3.5.0" oximeter = { path = "oximeter/oximeter" } @@ -279,9 +280,9 @@ pretty-hex = "0.3.0" proc-macro2 = "1.0" progenitor = { git = "https://github.com/oxidecomputer/progenitor", branch = "main" } progenitor-client = { git = "https://github.com/oxidecomputer/progenitor", branch = "main" } -bhyve_api = { git = "https://github.com/oxidecomputer/propolis", rev = "42c878b71a58d430dfc306126af5d40ca816d70f" } -propolis-client = { git = "https://github.com/oxidecomputer/propolis", rev = "42c878b71a58d430dfc306126af5d40ca816d70f", features = [ "generated-migration" ] } -propolis-server = { git = "https://github.com/oxidecomputer/propolis", rev = "42c878b71a58d430dfc306126af5d40ca816d70f", default-features = false, features = ["mock-only"] } +bhyve_api = { git = "https://github.com/oxidecomputer/propolis", rev = "901b710b6e5bd05a94a323693c2b971e7e7b240e" } +propolis-client = { git = "https://github.com/oxidecomputer/propolis", rev = "901b710b6e5bd05a94a323693c2b971e7e7b240e", features = [ "generated-migration" ] } +propolis-server = { git = "https://github.com/oxidecomputer/propolis", rev = "901b710b6e5bd05a94a323693c2b971e7e7b240e", default-features = false, features = ["mock-only"] } proptest = "1.2.0" quote = "1.0" rand = "0.8.5" diff --git a/clients/gateway-client/src/lib.rs b/clients/gateway-client/src/lib.rs index 800254b197..b071d34975 100644 --- a/clients/gateway-client/src/lib.rs +++ b/clients/gateway-client/src/lib.rs @@ -48,7 +48,7 @@ progenitor::generate_api!( }), derives = [schemars::JsonSchema], patch = { - SpIdentifier = { derives = [Copy, PartialEq, Hash, Eq, PartialOrd, Ord, Serialize, Deserialize] }, + SpIdentifier = { derives = [Copy, PartialEq, Hash, Eq, Serialize, Deserialize] }, SpIgnition = { derives = [PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize] }, SpIgnitionSystemType = { derives = [Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize] }, SpState = { derives = [ PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize] }, @@ -59,3 +59,17 @@ progenitor::generate_api!( HostPhase2RecoveryImageId = { derives = [ PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize] }, }, ); + +// Override the impl of Ord for SpIdentifier because the default one orders the +// fields in a different order than people are likely to want. +impl Ord for crate::types::SpIdentifier { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.type_.cmp(&other.type_).then(self.slot.cmp(&other.slot)) + } +} + +impl PartialOrd for crate::types::SpIdentifier { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} diff --git a/dev-tools/crdb-seed/Cargo.toml b/dev-tools/crdb-seed/Cargo.toml index 4e7d01a835..aff26995dc 100644 --- a/dev-tools/crdb-seed/Cargo.toml +++ b/dev-tools/crdb-seed/Cargo.toml @@ -8,7 +8,7 @@ readme = "README.md" [dependencies] anyhow.workspace = true dropshot.workspace = true -omicron-test-utils.workspace = true +omicron-test-utils = { workspace = true, features = ["seed-gen"] } slog.workspace = true tokio.workspace = true omicron-workspace-hack.workspace = true diff --git a/dev-tools/crdb-seed/src/main.rs b/dev-tools/crdb-seed/src/main.rs index 190375a6da..26b0e19410 100644 --- a/dev-tools/crdb-seed/src/main.rs +++ b/dev-tools/crdb-seed/src/main.rs @@ -5,8 +5,9 @@ use anyhow::{Context, Result}; use dropshot::{test_util::LogContext, ConfigLogging, ConfigLoggingLevel}; use omicron_test_utils::dev::seed::{ - ensure_seed_tarball_exists, should_invalidate_seed, CRDB_SEED_TAR_ENV, + ensure_seed_tarball_exists, should_invalidate_seed, }; +use omicron_test_utils::dev::CRDB_SEED_TAR_ENV; use std::io::Write; #[tokio::main] diff --git a/dev-tools/omdb/Cargo.toml b/dev-tools/omdb/Cargo.toml index cd4af6e947..ff3c650d6d 100644 --- a/dev-tools/omdb/Cargo.toml +++ b/dev-tools/omdb/Cargo.toml @@ -14,9 +14,12 @@ chrono.workspace = true clap.workspace = true diesel.workspace = true dropshot.workspace = true +futures.workspace = true +gateway-client.workspace = true +gateway-messages.workspace = true +gateway-test-utils.workspace = true humantime.workspace = true internal-dns.workspace = true -futures.workspace = true nexus-client.workspace = true nexus-db-model.workspace = true nexus-db-queries.workspace = true diff --git a/dev-tools/omdb/src/bin/omdb/main.rs b/dev-tools/omdb/src/bin/omdb/main.rs index d1a56e1d80..32141d2809 100644 --- a/dev-tools/omdb/src/bin/omdb/main.rs +++ b/dev-tools/omdb/src/bin/omdb/main.rs @@ -41,6 +41,7 @@ use std::net::SocketAddr; use std::net::SocketAddrV6; mod db; +mod mgs; mod nexus; mod oximeter; mod sled_agent; @@ -57,6 +58,7 @@ async fn main() -> Result<(), anyhow::Error> { match &args.command { OmdbCommands::Db(db) => db.run_cmd(&args, &log).await, + OmdbCommands::Mgs(mgs) => mgs.run_cmd(&args, &log).await, OmdbCommands::Nexus(nexus) => nexus.run_cmd(&args, &log).await, OmdbCommands::Oximeter(oximeter) => oximeter.run_cmd(&log).await, OmdbCommands::SledAgent(sled) => sled.run_cmd(&args, &log).await, @@ -155,6 +157,8 @@ impl Omdb { enum OmdbCommands { /// Query the control plane database (CockroachDB) Db(db::DbArgs), + /// Debug a specific Management Gateway Service instance + Mgs(mgs::MgsArgs), /// Debug a specific Nexus instance Nexus(nexus::NexusArgs), /// Query oximeter collector state diff --git a/dev-tools/omdb/src/bin/omdb/mgs.rs b/dev-tools/omdb/src/bin/omdb/mgs.rs new file mode 100644 index 0000000000..d2938418e1 --- /dev/null +++ b/dev-tools/omdb/src/bin/omdb/mgs.rs @@ -0,0 +1,488 @@ +// 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/. + +//! Prototype code for collecting information from systems in the rack + +use crate::Omdb; +use anyhow::Context; +use clap::Args; +use clap::Subcommand; +use futures::StreamExt; +use gateway_client::types::PowerState; +use gateway_client::types::RotSlot; +use gateway_client::types::RotState; +use gateway_client::types::SpComponentCaboose; +use gateway_client::types::SpComponentInfo; +use gateway_client::types::SpIdentifier; +use gateway_client::types::SpIgnition; +use gateway_client::types::SpIgnitionInfo; +use gateway_client::types::SpIgnitionSystemType; +use gateway_client::types::SpState; +use gateway_client::types::SpType; +use tabled::Tabled; + +/// Arguments to the "omdb mgs" subcommand +#[derive(Debug, Args)] +pub struct MgsArgs { + /// URL of an MGS instance to query + #[clap(long, env("OMDB_MGS_URL"))] + mgs_url: Option, + + #[command(subcommand)] + command: MgsCommands, +} + +#[derive(Debug, Subcommand)] +enum MgsCommands { + /// Show information about devices and components visible to MGS + Inventory(InventoryArgs), +} + +#[derive(Debug, Args)] +struct InventoryArgs {} + +impl MgsArgs { + pub(crate) async fn run_cmd( + &self, + omdb: &Omdb, + log: &slog::Logger, + ) -> Result<(), anyhow::Error> { + let mgs_url = match &self.mgs_url { + Some(cli_or_env_url) => cli_or_env_url.clone(), + None => { + eprintln!( + "note: MGS URL not specified. Will pick one from DNS." + ); + let addrs = omdb + .dns_lookup_all( + log.clone(), + internal_dns::ServiceName::ManagementGatewayService, + ) + .await?; + let addr = addrs.into_iter().next().expect( + "expected at least one MGS address from \ + successful DNS lookup", + ); + format!("http://{}", addr) + } + }; + eprintln!("note: using MGS URL {}", &mgs_url); + let mgs_client = gateway_client::Client::new(&mgs_url, log.clone()); + + match &self.command { + MgsCommands::Inventory(inventory_args) => { + cmd_mgs_inventory(&mgs_client, inventory_args).await + } + } + } +} + +/// Runs `omdb mgs inventory` +/// +/// Shows devices and components that are visible to an MGS instance. +async fn cmd_mgs_inventory( + mgs_client: &gateway_client::Client, + _args: &InventoryArgs, +) -> Result<(), anyhow::Error> { + // Report all the SP identifiers that MGS is configured to talk to. + println!("ALL CONFIGURED SPs\n"); + let mut sp_ids = mgs_client + .sp_all_ids() + .await + .context("listing SP identifiers")? + .into_inner(); + sp_ids.sort(); + show_sp_ids(&sp_ids)?; + println!(""); + + // Report which SPs are visible via Ignition. + println!("SPs FOUND THROUGH IGNITION\n"); + let mut sp_list_ignition = mgs_client + .ignition_list() + .await + .context("listing ignition")? + .into_inner(); + sp_list_ignition.sort_by(|a, b| a.id.cmp(&b.id)); + show_sps_from_ignition(&sp_list_ignition)?; + println!(""); + + // Print basic state about each SP that's visible to ignition. + println!("SERVICE PROCESSOR STATES\n"); + let mgs_client = std::sync::Arc::new(mgs_client); + let c = &mgs_client; + let mut sp_infos = + futures::stream::iter(sp_list_ignition.iter().filter_map(|ignition| { + if matches!(ignition.details, SpIgnition::Yes { .. }) { + Some(ignition.id) + } else { + None + } + })) + .then(|sp_id| async move { + c.sp_get(sp_id.type_, sp_id.slot) + .await + .with_context(|| format!("fetching info about SP {:?}", sp_id)) + .map(|s| (sp_id, s)) + }) + .collect::>>() + .await + .into_iter() + .filter_map(|r| match r { + Ok((sp_id, v)) => Some((sp_id, v.into_inner())), + Err(error) => { + eprintln!("error: {:?}", error); + None + } + }) + .collect::>(); + sp_infos.sort(); + show_sp_states(&sp_infos)?; + println!(""); + + // Print detailed information about each SP that we've found so far. + for (sp_id, sp_state) in &sp_infos { + show_sp_details(&mgs_client, sp_id, sp_state).await?; + } + + Ok(()) +} + +fn sp_type_to_str(s: &SpType) -> &'static str { + match s { + SpType::Sled => "Sled", + SpType::Power => "Power", + SpType::Switch => "Switch", + } +} + +fn show_sp_ids(sp_ids: &[SpIdentifier]) -> Result<(), anyhow::Error> { + #[derive(Tabled)] + #[tabled(rename_all = "SCREAMING_SNAKE_CASE")] + struct SpIdRow { + #[tabled(rename = "TYPE")] + type_: &'static str, + slot: u32, + } + + impl<'a> From<&'a SpIdentifier> for SpIdRow { + fn from(id: &SpIdentifier) -> Self { + SpIdRow { type_: sp_type_to_str(&id.type_), slot: id.slot } + } + } + + let table_rows = sp_ids.iter().map(SpIdRow::from); + let table = tabled::Table::new(table_rows) + .with(tabled::settings::Style::empty()) + .with(tabled::settings::Padding::new(0, 1, 0, 0)) + .to_string(); + println!("{}", textwrap::indent(&table.to_string(), " ")); + Ok(()) +} + +fn show_sps_from_ignition( + sp_list_ignition: &[SpIgnitionInfo], +) -> Result<(), anyhow::Error> { + #[derive(Tabled)] + #[tabled(rename_all = "SCREAMING_SNAKE_CASE")] + struct IgnitionRow { + #[tabled(rename = "TYPE")] + type_: &'static str, + slot: u32, + system_type: String, + } + + impl<'a> From<&'a SpIgnitionInfo> for IgnitionRow { + fn from(value: &SpIgnitionInfo) -> Self { + IgnitionRow { + type_: sp_type_to_str(&value.id.type_), + slot: value.id.slot, + system_type: match value.details { + SpIgnition::No => "-".to_string(), + SpIgnition::Yes { + id: SpIgnitionSystemType::Gimlet, + .. + } => "Gimlet".to_string(), + SpIgnition::Yes { + id: SpIgnitionSystemType::Sidecar, + .. + } => "Sidecar".to_string(), + SpIgnition::Yes { + id: SpIgnitionSystemType::Psc, .. + } => "PSC".to_string(), + SpIgnition::Yes { + id: SpIgnitionSystemType::Unknown(v), + .. + } => format!("unknown: type {}", v), + }, + } + } + } + + let table_rows = sp_list_ignition.iter().map(IgnitionRow::from); + let table = tabled::Table::new(table_rows) + .with(tabled::settings::Style::empty()) + .with(tabled::settings::Padding::new(0, 1, 0, 0)) + .to_string(); + println!("{}", textwrap::indent(&table.to_string(), " ")); + Ok(()) +} + +fn show_sp_states( + sp_states: &[(SpIdentifier, SpState)], +) -> Result<(), anyhow::Error> { + #[derive(Tabled)] + #[tabled(rename_all = "SCREAMING_SNAKE_CASE")] + struct SpStateRow<'a> { + #[tabled(rename = "TYPE")] + type_: &'static str, + slot: u32, + model: String, + serial: String, + rev: u32, + hubris: &'a str, + pwr: &'static str, + rot_active: String, + } + + impl<'a> From<&'a (SpIdentifier, SpState)> for SpStateRow<'a> { + fn from((id, v): &'a (SpIdentifier, SpState)) -> Self { + SpStateRow { + type_: sp_type_to_str(&id.type_), + slot: id.slot, + model: v.model.clone(), + serial: v.serial_number.clone(), + rev: v.revision, + hubris: &v.hubris_archive_id, + pwr: match v.power_state { + PowerState::A0 => "A0", + PowerState::A1 => "A1", + PowerState::A2 => "A2", + }, + rot_active: match &v.rot { + RotState::CommunicationFailed { message } => { + format!("error: {}", message) + } + RotState::Enabled { active: RotSlot::A, .. } => { + "slot A".to_string() + } + RotState::Enabled { active: RotSlot::B, .. } => { + "slot B".to_string() + } + }, + } + } + } + + let table_rows = sp_states.iter().map(SpStateRow::from); + let table = tabled::Table::new(table_rows) + .with(tabled::settings::Style::empty()) + .with(tabled::settings::Padding::new(0, 1, 0, 0)) + .to_string(); + println!("{}", textwrap::indent(&table.to_string(), " ")); + Ok(()) +} + +const COMPONENTS_WITH_CABOOSES: &'static [&'static str] = &["sp", "rot"]; + +async fn show_sp_details( + mgs_client: &gateway_client::Client, + sp_id: &SpIdentifier, + sp_state: &SpState, +) -> Result<(), anyhow::Error> { + println!( + "SP DETAILS: type {:?} slot {}\n", + sp_type_to_str(&sp_id.type_), + sp_id.slot + ); + + println!(" ROOT OF TRUST\n"); + match &sp_state.rot { + RotState::CommunicationFailed { message } => { + println!(" error: {}", message); + } + RotState::Enabled { + active, + pending_persistent_boot_preference, + persistent_boot_preference, + slot_a_sha3_256_digest, + slot_b_sha3_256_digest, + transient_boot_preference, + } => { + #[derive(Tabled)] + #[tabled(rename_all = "SCREAMING_SNAKE_CASE")] + struct Row { + name: &'static str, + value: String, + } + + let rows = vec![ + Row { + name: "active slot", + value: format!("slot {:?}", active), + }, + Row { + name: "persistent boot preference", + value: format!("slot {:?}", persistent_boot_preference), + }, + Row { + name: "pending persistent boot preference", + value: pending_persistent_boot_preference + .map(|s| format!("slot {:?}", s)) + .unwrap_or_else(|| "-".to_string()), + }, + Row { + name: "transient boot preference", + value: transient_boot_preference + .map(|s| format!("slot {:?}", s)) + .unwrap_or_else(|| "-".to_string()), + }, + Row { + name: "slot A SHA3 256 digest", + value: slot_a_sha3_256_digest + .clone() + .unwrap_or_else(|| "-".to_string()), + }, + Row { + name: "slot B SHA3 256 digest", + value: slot_b_sha3_256_digest + .clone() + .unwrap_or_else(|| "-".to_string()), + }, + ]; + + let table = tabled::Table::new(rows) + .with(tabled::settings::Style::empty()) + .with(tabled::settings::Padding::new(0, 1, 0, 0)) + .to_string(); + println!("{}", textwrap::indent(&table.to_string(), " ")); + println!(""); + } + } + + let component_list = mgs_client + .sp_component_list(sp_id.type_, sp_id.slot) + .await + .with_context(|| format!("fetching components for SP {:?}", sp_id)); + let list = match component_list { + Ok(l) => l.into_inner(), + Err(e) => { + eprintln!("error: {:#}", e); + return Ok(()); + } + }; + + #[derive(Tabled)] + #[tabled(rename_all = "SCREAMING_SNAKE_CASE")] + struct SpComponentRow<'a> { + name: &'a str, + description: &'a str, + device: &'a str, + presence: String, + serial: String, + } + + impl<'a> From<&'a SpComponentInfo> for SpComponentRow<'a> { + fn from(v: &'a SpComponentInfo) -> Self { + SpComponentRow { + name: &v.component, + description: &v.description, + device: &v.device, + presence: format!("{:?}", v.presence), + serial: format!("{:?}", v.serial_number), + } + } + } + + if list.components.is_empty() { + println!(" COMPONENTS: none found\n"); + return Ok(()); + } + + let table_rows = list.components.iter().map(SpComponentRow::from); + let table = tabled::Table::new(table_rows) + .with(tabled::settings::Style::empty()) + .with(tabled::settings::Padding::new(0, 1, 0, 0)) + .to_string(); + println!(" COMPONENTS\n"); + println!("{}", textwrap::indent(&table.to_string(), " ")); + println!(""); + + #[derive(Tabled)] + #[tabled(rename_all = "SCREAMING_SNAKE_CASE")] + struct CabooseRow { + component: String, + board: String, + git_commit: String, + name: String, + version: String, + } + + impl<'a> From<(&'a SpIdentifier, &'a SpComponentInfo, SpComponentCaboose)> + for CabooseRow + { + fn from( + (_sp_id, component, caboose): ( + &'a SpIdentifier, + &'a SpComponentInfo, + SpComponentCaboose, + ), + ) -> Self { + CabooseRow { + component: component.component.clone(), + board: caboose.board, + git_commit: caboose.git_commit, + name: caboose.name, + version: caboose.version.unwrap_or_else(|| "-".to_string()), + } + } + } + + let mut cabooses = Vec::new(); + for c in &list.components { + if !COMPONENTS_WITH_CABOOSES.contains(&c.component.as_str()) { + continue; + } + + for i in 0..1 { + let r = mgs_client + .sp_component_caboose_get( + sp_id.type_, + sp_id.slot, + &c.component, + i, + ) + .await + .with_context(|| { + format!( + "get caboose for sp type {:?} sp slot {} \ + component {:?} slot {}", + sp_id.type_, sp_id.slot, &c.component, i + ) + }); + match r { + Ok(v) => { + cabooses.push(CabooseRow::from((sp_id, c, v.into_inner()))) + } + Err(error) => { + eprintln!("warn: {:#}", error); + } + } + } + } + + if cabooses.is_empty() { + println!(" CABOOSES: none found\n"); + return Ok(()); + } + + let table = tabled::Table::new(cabooses) + .with(tabled::settings::Style::empty()) + .with(tabled::settings::Padding::new(0, 1, 0, 0)) + .to_string(); + println!(" COMPONENT CABOOSES\n"); + println!("{}", textwrap::indent(&table.to_string(), " ")); + println!(""); + + Ok(()) +} diff --git a/dev-tools/omdb/tests/successes.out b/dev-tools/omdb/tests/successes.out index b1464cb824..eb075a84ea 100644 --- a/dev-tools/omdb/tests/successes.out +++ b/dev-tools/omdb/tests/successes.out @@ -84,6 +84,111 @@ stderr: note: using database URL postgresql://root@[::1]:REDACTED_PORT/omicron?sslmode=disable note: database schema version matches expected (5.0.0) ============================================= +EXECUTING COMMAND: omdb ["mgs", "inventory"] +termination: Exited(0) +--------------------------------------------- +stdout: +ALL CONFIGURED SPs + + TYPE SLOT + Sled 0 + Sled 1 + Switch 0 + Switch 1 + +SPs FOUND THROUGH IGNITION + + TYPE SLOT SYSTEM_TYPE + Sled 0 Gimlet + Sled 1 Gimlet + Switch 0 Sidecar + Switch 1 Sidecar + +SERVICE PROCESSOR STATES + + TYPE SLOT MODEL SERIAL REV HUBRIS PWR ROT_ACTIVE + Sled 0 FAKE_SIM_GIMLET SimGimlet00 0 0000000000000000 A2 slot A + Sled 1 FAKE_SIM_GIMLET SimGimlet01 0 0000000000000000 A2 slot A + Switch 0 FAKE_SIM_SIDECAR SimSidecar0 0 0000000000000000 A2 slot A + Switch 1 FAKE_SIM_SIDECAR SimSidecar1 0 0000000000000000 A2 slot A + +SP DETAILS: type "Sled" slot 0 + + ROOT OF TRUST + + NAME VALUE + active slot slot A + persistent boot preference slot A + pending persistent boot preference - + transient boot preference - + slot A SHA3 256 digest - + slot B SHA3 256 digest - + + COMPONENTS + + NAME DESCRIPTION DEVICE PRESENCE SERIAL + sp3-host-cpu FAKE host cpu sp3-host-cpu Present None + dev-0 FAKE temperature sensor fake-tmp-sensor Failed None + + CABOOSES: none found + +SP DETAILS: type "Sled" slot 1 + + ROOT OF TRUST + + NAME VALUE + active slot slot A + persistent boot preference slot A + pending persistent boot preference - + transient boot preference - + slot A SHA3 256 digest - + slot B SHA3 256 digest - + + COMPONENTS + + NAME DESCRIPTION DEVICE PRESENCE SERIAL + sp3-host-cpu FAKE host cpu sp3-host-cpu Present None + + CABOOSES: none found + +SP DETAILS: type "Switch" slot 0 + + ROOT OF TRUST + + NAME VALUE + active slot slot A + persistent boot preference slot A + pending persistent boot preference - + transient boot preference - + slot A SHA3 256 digest - + slot B SHA3 256 digest - + + COMPONENTS + + NAME DESCRIPTION DEVICE PRESENCE SERIAL + dev-0 FAKE temperature sensor 1 fake-tmp-sensor Present None + dev-1 FAKE temperature sensor 2 fake-tmp-sensor Failed None + + CABOOSES: none found + +SP DETAILS: type "Switch" slot 1 + + ROOT OF TRUST + + NAME VALUE + active slot slot A + persistent boot preference slot A + pending persistent boot preference - + transient boot preference - + slot A SHA3 256 digest - + slot B SHA3 256 digest - + + COMPONENTS: none found + +--------------------------------------------- +stderr: +note: using MGS URL http://[::1]:REDACTED_PORT/ +============================================= EXECUTING COMMAND: omdb ["nexus", "background-tasks", "doc"] termination: Exited(0) --------------------------------------------- diff --git a/dev-tools/omdb/tests/test_all_output.rs b/dev-tools/omdb/tests/test_all_output.rs index d757369ead..90e93ee429 100644 --- a/dev-tools/omdb/tests/test_all_output.rs +++ b/dev-tools/omdb/tests/test_all_output.rs @@ -42,6 +42,7 @@ async fn test_omdb_usage_errors() { &["db", "dns", "names"], &["db", "services"], &["db", "network"], + &["mgs"], &["nexus"], &["nexus", "background-tasks"], &["sled-agent"], @@ -58,10 +59,16 @@ async fn test_omdb_usage_errors() { #[nexus_test] async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { + let gwtestctx = gateway_test_utils::setup::test_setup( + "test_omdb_success_case", + gateway_messages::SpPort::One, + ) + .await; let cmd_path = path_to_executable(CMD_OMDB); let postgres_url = cptestctx.database.listen_url(); let nexus_internal_url = format!("http://{}/", cptestctx.internal_client.bind_address); + let mgs_url = format!("http://{}/", gwtestctx.client.bind_address); let mut output = String::new(); let invocations: &[&[&'static str]] = &[ &["db", "dns", "show"], @@ -70,6 +77,7 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { &["db", "services", "list-instances"], &["db", "services", "list-by-sled"], &["db", "sleds"], + &["mgs", "inventory"], &["nexus", "background-tasks", "doc"], &["nexus", "background-tasks", "show"], // We can't easily test the sled agent output because that's only @@ -81,9 +89,14 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { println!("running commands with args: {:?}", args); let p = postgres_url.to_string(); let u = nexus_internal_url.clone(); + let g = mgs_url.clone(); do_run( &mut output, - move |exec| exec.env("OMDB_DB_URL", &p).env("OMDB_NEXUS_URL", &u), + move |exec| { + exec.env("OMDB_DB_URL", &p) + .env("OMDB_NEXUS_URL", &u) + .env("OMDB_MGS_URL", &g) + }, &cmd_path, args, ) @@ -91,6 +104,7 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { } assert_contents("tests/successes.out", &output); + gwtestctx.teardown().await; } /// Verify that we properly deal with cases where: diff --git a/dev-tools/omdb/tests/usage_errors.out b/dev-tools/omdb/tests/usage_errors.out index dc2a16bc47..7bedc3ecbc 100644 --- a/dev-tools/omdb/tests/usage_errors.out +++ b/dev-tools/omdb/tests/usage_errors.out @@ -10,6 +10,7 @@ Usage: omdb [OPTIONS] Commands: db Query the control plane database (CockroachDB) + mgs Debug a specific Management Gateway Service instance nexus Debug a specific Nexus instance oximeter Query oximeter collector state sled-agent Debug a specific Sled @@ -33,6 +34,7 @@ Usage: omdb [OPTIONS] Commands: db Query the control plane database (CockroachDB) + mgs Debug a specific Management Gateway Service instance nexus Debug a specific Nexus instance oximeter Query oximeter collector state sled-agent Debug a specific Sled @@ -208,6 +210,24 @@ Options: --verbose Print out raw data structures from the data store -h, --help Print help ============================================= +EXECUTING COMMAND: omdb ["mgs"] +termination: Exited(2) +--------------------------------------------- +stdout: +--------------------------------------------- +stderr: +Debug a specific Management Gateway Service instance + +Usage: omdb mgs [OPTIONS] + +Commands: + inventory Show information about devices and components visible to MGS + help Print this message or the help of the given subcommand(s) + +Options: + --mgs-url URL of an MGS instance to query [env: OMDB_MGS_URL=] + -h, --help Print help +============================================= EXECUTING COMMAND: omdb ["nexus"] termination: Exited(2) --------------------------------------------- diff --git a/dev-tools/omicron-dev/Cargo.toml b/dev-tools/omicron-dev/Cargo.toml index 5439b69c76..ec7cafb559 100644 --- a/dev-tools/omicron-dev/Cargo.toml +++ b/dev-tools/omicron-dev/Cargo.toml @@ -13,8 +13,10 @@ camino.workspace = true clap.workspace = true dropshot.workspace = true futures.workspace = true +gateway-messages.workspace = true +gateway-test-utils.workspace = true libc.workspace = true -nexus-test-utils.workspace = true +nexus-test-utils = { workspace = true, features = ["omicron-dev"] } nexus-test-interface.workspace = true omicron-common.workspace = true omicron-nexus.workspace = true diff --git a/dev-tools/omicron-dev/src/bin/omicron-dev.rs b/dev-tools/omicron-dev/src/bin/omicron-dev.rs index 811517a101..e79184f7e5 100644 --- a/dev-tools/omicron-dev/src/bin/omicron-dev.rs +++ b/dev-tools/omicron-dev/src/bin/omicron-dev.rs @@ -29,6 +29,7 @@ async fn main() -> Result<(), anyhow::Error> { OmicronDb::DbPopulate { ref args } => cmd_db_populate(args).await, OmicronDb::DbWipe { ref args } => cmd_db_wipe(args).await, OmicronDb::ChRun { ref args } => cmd_clickhouse_run(args).await, + OmicronDb::MgsRun { ref args } => cmd_mgs_run(args).await, OmicronDb::RunAll { ref args } => cmd_run_all(args).await, OmicronDb::CertCreate { ref args } => cmd_cert_create(args).await, }; @@ -67,6 +68,12 @@ enum OmicronDb { args: ChRunArgs, }, + /// Run a simulated Management Gateway Service for development + MgsRun { + #[clap(flatten)] + args: MgsRunArgs, + }, + /// Run a full simulated control plane RunAll { #[clap(flatten)] @@ -463,3 +470,34 @@ fn write_private_file( .with_context(|| format!("open {:?} for writing", path))?; file.write_all(contents).with_context(|| format!("write to {:?}", path)) } + +#[derive(Clone, Debug, Args)] +struct MgsRunArgs {} + +async fn cmd_mgs_run(_args: &MgsRunArgs) -> Result<(), anyhow::Error> { + // Start a stream listening for SIGINT + let signals = Signals::new(&[SIGINT]).expect("failed to wait for SIGINT"); + let mut signal_stream = signals.fuse(); + + println!("omicron-dev: setting up MGS ... "); + let gwtestctx = gateway_test_utils::setup::test_setup( + "omicron-dev", + gateway_messages::SpPort::One, + ) + .await; + println!("omicron-dev: MGS is running."); + + let addr = gwtestctx.client.bind_address; + println!("omicron-dev: MGS API: http://{:?}", addr); + + // Wait for a signal. + let caught_signal = signal_stream.next().await; + assert_eq!(caught_signal.unwrap(), SIGINT); + eprintln!( + "omicron-dev: caught signal, shutting down and removing \ + temporary directory" + ); + + gwtestctx.teardown().await; + Ok(()) +} diff --git a/dev-tools/omicron-dev/tests/output/cmd-omicron-dev-noargs-stderr b/dev-tools/omicron-dev/tests/output/cmd-omicron-dev-noargs-stderr index f3c28e1ab9..ac1c87e165 100644 --- a/dev-tools/omicron-dev/tests/output/cmd-omicron-dev-noargs-stderr +++ b/dev-tools/omicron-dev/tests/output/cmd-omicron-dev-noargs-stderr @@ -7,6 +7,7 @@ Commands: db-populate Populate an existing CockroachDB cluster with the Omicron schema db-wipe Wipe the Omicron schema (and all data) from an existing CockroachDB cluster ch-run Run a ClickHouse database server for development + mgs-run Run a simulated Management Gateway Service for development run-all Run a full simulated control plane cert-create Create a self-signed certificate for use with Omicron help Print this message or the help of the given subcommand(s) diff --git a/nexus/src/app/sagas/switch_port_settings_apply.rs b/nexus/src/app/sagas/switch_port_settings_apply.rs index 07d4dd17fb..687613f0cc 100644 --- a/nexus/src/app/sagas/switch_port_settings_apply.rs +++ b/nexus/src/app/sagas/switch_port_settings_apply.rs @@ -175,7 +175,7 @@ pub(crate) fn api_to_dpd_port_settings( .to_string(), RouteSettingsV4 { link_id: link_id.0, - nexthop: Some(gw), + nexthop: gw, vid: r.vid.map(Into::into), }, ); @@ -194,7 +194,7 @@ pub(crate) fn api_to_dpd_port_settings( .to_string(), RouteSettingsV6 { link_id: link_id.0, - nexthop: Some(gw), + nexthop: gw, vid: r.vid.map(Into::into), }, ); diff --git a/nexus/test-utils/Cargo.toml b/nexus/test-utils/Cargo.toml index 8eb8df4a5b..8cd25582be 100644 --- a/nexus/test-utils/Cargo.toml +++ b/nexus/test-utils/Cargo.toml @@ -39,3 +39,6 @@ trust-dns-proto.workspace = true trust-dns-resolver.workspace = true uuid.workspace = true omicron-workspace-hack.workspace = true + +[features] +omicron-dev = ["omicron-test-utils/seed-gen"] diff --git a/nexus/test-utils/src/db.rs b/nexus/test-utils/src/db.rs index 412f412ec6..ff23f35df0 100644 --- a/nexus/test-utils/src/db.rs +++ b/nexus/test-utils/src/db.rs @@ -18,14 +18,13 @@ use slog::Logger; /// to copy the database from this seed location. fn seed_tar() -> Utf8PathBuf { // The setup script should set this environment variable. - let seed_dir = - std::env::var(dev::seed::CRDB_SEED_TAR_ENV).unwrap_or_else(|_| { - panic!( - "{} missing -- are you running this test \ + let seed_dir = std::env::var(dev::CRDB_SEED_TAR_ENV).unwrap_or_else(|_| { + panic!( + "{} missing -- are you running this test \ with `cargo nextest run`?", - dev::seed::CRDB_SEED_TAR_ENV, - ) - }); + dev::CRDB_SEED_TAR_ENV, + ) + }); seed_dir.into() } @@ -42,6 +41,7 @@ pub async fn test_setup_database(log: &Logger) -> dev::db::CockroachInstance { /// Wrapper around [`dev::test_setup_database`] which uses a seed tarball /// provided as an argument. +#[cfg(feature = "omicron-dev")] pub async fn test_setup_database_from_seed( log: &Logger, input_tar: Utf8PathBuf, diff --git a/nexus/test-utils/src/lib.rs b/nexus/test-utils/src/lib.rs index 068a4c36df..34c218b3e2 100644 --- a/nexus/test-utils/src/lib.rs +++ b/nexus/test-utils/src/lib.rs @@ -7,7 +7,6 @@ use anyhow::Context; use anyhow::Result; use camino::Utf8Path; -use camino::Utf8PathBuf; use dns_service_client::types::DnsConfigParams; use dropshot::test_util::ClientTestContext; use dropshot::test_util::LogContext; @@ -305,6 +304,7 @@ impl<'a, N: NexusServer> ControlPlaneTestContextBuilder<'a, N> { PopulateCrdb::FromEnvironmentSeed => { db::test_setup_database(log).await } + #[cfg(feature = "omicron-dev")] PopulateCrdb::FromSeed { input_tar } => { db::test_setup_database_from_seed(log, input_tar).await } @@ -790,7 +790,8 @@ enum PopulateCrdb { FromEnvironmentSeed, /// Populate Cockroach from the seed located at this path. - FromSeed { input_tar: Utf8PathBuf }, + #[cfg(feature = "omicron-dev")] + FromSeed { input_tar: camino::Utf8PathBuf }, /// Do not populate Cockroach. Empty, @@ -802,6 +803,7 @@ enum PopulateCrdb { /// The main difference from tests is that this routine ensures the seed tarball /// exists (or creates a seed tarball if it doesn't exist). For tests, this /// should be done in the `crdb-seed` setup script. +#[cfg(feature = "omicron-dev")] pub async fn omicron_dev_setup_with_config( config: &mut omicron_common::nexus_config::Config, ) -> Result> { diff --git a/package-manifest.toml b/package-manifest.toml index ff229e5def..7cf235c24a 100644 --- a/package-manifest.toml +++ b/package-manifest.toml @@ -406,10 +406,10 @@ service_name = "propolis-server" only_for_targets.image = "standard" source.type = "prebuilt" source.repo = "propolis" -source.commit = "42c878b71a58d430dfc306126af5d40ca816d70f" +source.commit = "901b710b6e5bd05a94a323693c2b971e7e7b240e" # The SHA256 digest is automatically posted to: # https://buildomat.eng.oxide.computer/public/file/oxidecomputer/propolis/image//propolis-server.sha256.txt -source.sha256 = "dce4d82bb936e990262abcaa279eee7e33a19930880b23f49fa3851cded18567" +source.sha256 = "0f681cdbe7312f66fd3c99fe033b379e49c59fa4ad04d307f68b12514307e976" output.type = "zone" [package.maghemite] @@ -458,8 +458,8 @@ only_for_targets.image = "standard" # 2. Copy dendrite.tar.gz from dendrite/out to omicron/out source.type = "prebuilt" source.repo = "dendrite" -source.commit = "363e365135cfa46d7f7558d8670f35aa8fe412e9" -source.sha256 = "2dc34eaac7eb9d320594f3ac125df6a601fe020e0b3c7f16eb0a5ebddc8e18b9" +source.commit = "7712104585266a2898da38c1345210ad26f9e71d" +source.sha256 = "486b0b016c0df06947810b90f3a3dd40423f0ee6f255ed079dc8e5618c9a7281" output.type = "zone" output.intermediate_only = true @@ -483,8 +483,8 @@ only_for_targets.image = "standard" # 2. Copy the output zone image from dendrite/out to omicron/out source.type = "prebuilt" source.repo = "dendrite" -source.commit = "363e365135cfa46d7f7558d8670f35aa8fe412e9" -source.sha256 = "1616eb25ab3d3a8b678b6cf3675af7ba61d455c3e6c2ba2a2d35a663861bc8e8" +source.commit = "7712104585266a2898da38c1345210ad26f9e71d" +source.sha256 = "76ff76d3526323c3fcbe2351cf9fbda4840e0dc11cd0eb6b71a3e0bd36c5e5e8" output.type = "zone" output.intermediate_only = true @@ -501,8 +501,8 @@ only_for_targets.image = "standard" # 2. Copy dendrite.tar.gz from dendrite/out to omicron/out/dendrite-softnpu.tar.gz source.type = "prebuilt" source.repo = "dendrite" -source.commit = "363e365135cfa46d7f7558d8670f35aa8fe412e9" -source.sha256 = "a045e6dbb84dbceaf3a8a7dc33d283449fbeaf081442d0ae14ce8d8ffcdda4e9" +source.commit = "7712104585266a2898da38c1345210ad26f9e71d" +source.sha256 = "b8e5c176070f9bc9ea0028de1999c77d66ea3438913664163975964effe4481b" output.type = "zone" output.intermediate_only = true diff --git a/sled-agent/src/bootstrap/early_networking.rs b/sled-agent/src/bootstrap/early_networking.rs index 78e54b3db4..61d4c84af3 100644 --- a/sled-agent/src/bootstrap/early_networking.rs +++ b/sled-agent/src/bootstrap/early_networking.rs @@ -495,14 +495,13 @@ impl<'a> EarlyNetworkSetup<'a> { e )) })?; - let nexthop = Some(uplink_config.gateway_ip); dpd_port_settings.v4_routes.insert( Ipv4Cidr { prefix: "0.0.0.0".parse().unwrap(), prefix_len: 0 } .to_string(), RouteSettingsV4 { link_id: link_id.0, vid: uplink_config.uplink_vid, - nexthop, + nexthop: uplink_config.gateway_ip, }, ); Ok((ipv6_entry, dpd_port_settings, port_id)) diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index 9d4e1ecda9..7b1f70c79e 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -10,6 +10,7 @@ atomicwrites.workspace = true camino.workspace = true camino-tempfile.workspace = true dropshot.workspace = true +filetime = { workspace = true, optional = true } futures.workspace = true headers.workspace = true hex.workspace = true @@ -24,7 +25,7 @@ subprocess.workspace = true tempfile.workspace = true thiserror.workspace = true tar.workspace = true -tokio = { workspace = true, features = [ "full" ] } +tokio = { workspace = true, features = ["full"] } tokio-postgres.workspace = true usdt.workspace = true rcgen.workspace = true @@ -34,3 +35,6 @@ omicron-workspace-hack.workspace = true [dev-dependencies] expectorate.workspace = true + +[features] +seed-gen = ["dep:filetime"] diff --git a/test-utils/src/dev/mod.rs b/test-utils/src/dev/mod.rs index 7d08ef4728..dbd66fe1f8 100644 --- a/test-utils/src/dev/mod.rs +++ b/test-utils/src/dev/mod.rs @@ -9,6 +9,7 @@ pub mod clickhouse; pub mod db; pub mod dendrite; pub mod poll; +#[cfg(feature = "seed-gen")] pub mod seed; pub mod test_cmds; @@ -21,6 +22,9 @@ use dropshot::ConfigLoggingLevel; use slog::Logger; use std::io::BufReader; +/// The environment variable via which the path to the seed tarball is passed. +pub static CRDB_SEED_TAR_ENV: &str = "CRDB_SEED_TAR"; + /// Set up a [`dropshot::test_util::LogContext`] appropriate for a test named /// `test_name` /// diff --git a/test-utils/src/dev/seed.rs b/test-utils/src/dev/seed.rs index 0bd791eb52..841ecd5f35 100644 --- a/test-utils/src/dev/seed.rs +++ b/test-utils/src/dev/seed.rs @@ -6,10 +6,10 @@ use std::io::{BufWriter, Write}; use anyhow::{ensure, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; +use filetime::FileTime; use slog::Logger; -/// The environment variable via which the path to the seed tarball is passed. -pub static CRDB_SEED_TAR_ENV: &str = "CRDB_SEED_TAR"; +use super::CRDB_SEED_TAR_ENV; /// Creates a string identifier for the current DB schema and version. // @@ -63,6 +63,10 @@ impl SeedTarballStatus { /// Ensures that a seed tarball corresponding to the schema returned by /// [`digest_unique_to_schema`] exists, recreating it if necessary. /// +/// This used to create a directory rather than a tarball, but that was changed +/// due to [Omicron issue +/// #4193](https://github.com/oxidecomputer/omicron/issues/4193). +/// /// If `why_invalidate` is `Some`, then if the seed tarball exists, it will be /// deleted before being recreated. /// @@ -75,6 +79,11 @@ pub async fn ensure_seed_tarball_exists( why_invalidate: Option<&str>, ) -> Result<(Utf8PathBuf, SeedTarballStatus)> { // If the CRDB_SEED_TAR_ENV variable is set, return an error. + // + // Even though this module is gated behind a feature flag, omicron-dev needs + // this function -- and so, if you're doing a top-level `cargo nextest run` + // like CI does, feature unification would mean this gets included in test + // binaries anyway. So this acts as a belt-and-suspenders check. if let Ok(val) = std::env::var(CRDB_SEED_TAR_ENV) { anyhow::bail!( "{CRDB_SEED_TAR_ENV} is set to `{val}` -- implying that a test called \ @@ -84,7 +93,8 @@ pub async fn ensure_seed_tarball_exists( } // XXX: we aren't considering cross-user permissions for this file. Might be - // worth setting more restrictive permissions on it. + // worth setting more restrictive permissions on it, or using a per-user + // cache dir. let base_seed_dir = Utf8PathBuf::from_path_buf(std::env::temp_dir()) .expect("Not a UTF-8 path") .join("crdb-base"); @@ -103,12 +113,11 @@ pub async fn ensure_seed_tarball_exists( true } (true, None) => { - // The tarball exists. Update its mtime (i.e. `touch` it) to ensure - // that it doesn't get deleted by a /tmp cleaner like on macOS. - std::fs::OpenOptions::new() - .append(true) - .open(&desired_seed_tar) - .context("failed to touch seed tarball")?; + // The tarball exists. Update its atime and mtime (i.e. `touch` it) + // to ensure that it doesn't get deleted by a /tmp cleaner. + let now = FileTime::now(); + filetime::set_file_times(&desired_seed_tar, now, now) + .context("failed to update seed tarball atime and mtime")?; return Ok((desired_seed_tar, SeedTarballStatus::Existing)); } (false, Some(why)) => { @@ -124,12 +133,14 @@ pub async fn ensure_seed_tarball_exists( } }; - // The tarball didn't exist when we started, so try to create it. + // At this point the tarball does not exist (either because it didn't exist + // in the first place or because it was deleted above), so try to create it. // - // Nextest will execute it just once, but it is possible for a user to start - // up multiple nextest processes to be running at the same time. So we - // should consider it possible for another caller to create this seed - // tarball before we finish setting it up ourselves. + // Nextest will execute this function just once via the `crdb-seed` binary, + // but it is possible for a user to start up multiple nextest processes to + // be running at the same time. So we should consider it possible for + // another caller to create this seed tarball before we finish setting it up + // ourselves. test_setup_database_seed(log, &desired_seed_tar) .await .context("failed to setup seed tarball")?; diff --git a/tools/ci_download_softnpu_machinery b/tools/ci_download_softnpu_machinery index 2575d6a186..d37d428476 100755 --- a/tools/ci_download_softnpu_machinery +++ b/tools/ci_download_softnpu_machinery @@ -15,7 +15,7 @@ OUT_DIR="out/npuzone" # Pinned commit for softnpu ASIC simulator SOFTNPU_REPO="softnpu" -SOFTNPU_COMMIT="64beaff129b7f63a04a53dd5ed0ec09f012f5756" +SOFTNPU_COMMIT="41b3a67b3d44f51528816ff8e539b4001df48305" # This is the softnpu ASIC simulator echo "fetching npuzone" diff --git a/tools/dendrite_openapi_version b/tools/dendrite_openapi_version index cbdbca7662..b1f210a647 100644 --- a/tools/dendrite_openapi_version +++ b/tools/dendrite_openapi_version @@ -1,2 +1,2 @@ -COMMIT="363e365135cfa46d7f7558d8670f35aa8fe412e9" -SHA2="4da5edf1571a550a90aa8679a25c1535d2b02154dfb6034f170e421c2633bc31" +COMMIT="7712104585266a2898da38c1345210ad26f9e71d" +SHA2="cb3f0cfbe6216d2441d34e0470252e0fb142332e47b33b65c24ef7368a694b6d" diff --git a/tools/dendrite_stub_checksums b/tools/dendrite_stub_checksums index acff400104..9538bc0d00 100644 --- a/tools/dendrite_stub_checksums +++ b/tools/dendrite_stub_checksums @@ -1,3 +1,3 @@ -CIDL_SHA256_ILLUMOS="2dc34eaac7eb9d320594f3ac125df6a601fe020e0b3c7f16eb0a5ebddc8e18b9" -CIDL_SHA256_LINUX_DPD="5a976d1e43031f4790d1cd2f42d226b47c1be9c998917666f21cfaa3a7b13939" -CIDL_SHA256_LINUX_SWADM="38680e69364ffbfc43fea524786580d151ff45ce2f1802bd5179599f7c80e5f8" +CIDL_SHA256_ILLUMOS="486b0b016c0df06947810b90f3a3dd40423f0ee6f255ed079dc8e5618c9a7281" +CIDL_SHA256_LINUX_DPD="af97aaf7e1046a5c651d316c384171df6387b4c54c8ae4a3ef498e532eaa5a4c" +CIDL_SHA256_LINUX_SWADM="909e400dcc9880720222c6dc3919404d83687f773f668160f66f38b51a81c188" diff --git a/tools/dvt_dock_version b/tools/dvt_dock_version index e2151b846f..790bd3ec26 100644 --- a/tools/dvt_dock_version +++ b/tools/dvt_dock_version @@ -1 +1 @@ -COMMIT=65f1979c1d3f4d0874a64144941cc41b46a70c80 +COMMIT=7cbfa19bad077a3c42976357a317d18291533ba2 diff --git a/tools/opte_version b/tools/opte_version index 83a91f78b4..2dbaeb7154 100644 --- a/tools/opte_version +++ b/tools/opte_version @@ -1 +1 @@ -0.23.173 +0.23.181 diff --git a/wicketd/src/preflight_check/uplink.rs b/wicketd/src/preflight_check/uplink.rs index c0f5d0c6bb..58955d04d6 100644 --- a/wicketd/src/preflight_check/uplink.rs +++ b/wicketd/src/preflight_check/uplink.rs @@ -777,7 +777,7 @@ fn build_port_settings( DPD_DEFAULT_IPV4_CIDR.parse().unwrap(), RouteSettingsV4 { link_id: link_id.0, - nexthop: Some(uplink.gateway_ip), + nexthop: uplink.gateway_ip, vid: uplink.uplink_vid, }, );