diff --git a/installinator/src/hardware.rs b/installinator/src/hardware.rs index dea966712fe..314df567283 100644 --- a/installinator/src/hardware.rs +++ b/installinator/src/hardware.rs @@ -21,9 +21,9 @@ pub struct Hardware { impl Hardware { pub async fn scan(log: &Logger) -> Result { - let is_gimlet = sled_hardware::is_gimlet() - .context("failed to detect whether host is a gimlet")?; - ensure!(is_gimlet, "hardware scan only supported on gimlets"); + let is_oxide_sled = sled_hardware::is_oxide_sled() + .context("failed to detect whether host is an oxide sled")?; + ensure!(is_oxide_sled, "hardware scan only supported on oxide sleds"); let hardware = HardwareManager::new(log, SledMode::Auto, vec![]) .map_err(|err| { @@ -34,7 +34,7 @@ impl Hardware { hardware.disks().into_values().map(|disk| disk.into()).collect(); info!( - log, "found gimlet hardware"; + log, "found oxide sled hardware"; "baseboard" => ?hardware.baseboard(), "is_scrimlet" => hardware.is_scrimlet(), "num_disks" => disks.len(), diff --git a/nexus/inventory/src/builder.rs b/nexus/inventory/src/builder.rs index 44bc6360a9c..463e073e20d 100644 --- a/nexus/inventory/src/builder.rs +++ b/nexus/inventory/src/builder.rs @@ -627,7 +627,7 @@ impl CollectionBuilder { let baseboard_id = match inventory.baseboard { Baseboard::Pc { .. } => None, - Baseboard::Gimlet { identifier, model, revision: _ } => { + Baseboard::Gimlet { identifier, model, .. } => { Some(Self::normalize_item( &mut self.baseboards, BaseboardId { diff --git a/sled-agent/src/bin/sled-agent.rs b/sled-agent/src/bin/sled-agent.rs index 252c0b25cb1..27e4059bd09 100644 --- a/sled-agent/src/bin/sled-agent.rs +++ b/sled-agent/src/bin/sled-agent.rs @@ -12,7 +12,9 @@ use omicron_common::cmd::fatal; use omicron_sled_agent::bootstrap::RssAccessError; use omicron_sled_agent::bootstrap::server as bootstrap_server; use omicron_sled_agent::config::Config as SledConfig; -use sled_agent_types::rack_init::RackInitializeRequest; +use sled_agent_types::rack_init::{ + RackInitializeRequest, RackInitializeRequestParams, +}; #[derive(Debug, Parser)] #[clap( @@ -60,14 +62,13 @@ async fn do_run() -> Result<(), CmdError> { rss_config_path.push("config-rss.toml"); rss_config_path }; - let rss_config = if rss_config_path.exists() { - Some( + let rss_config = rss_config_path.exists().then_some({ + let rss_config = RackInitializeRequest::from_file(rss_config_path) - .map_err(|e| CmdError::Failure(anyhow!(e)))?, - ) - } else { - None - }; + .map_err(|e| CmdError::Failure(anyhow!(e)))?; + let skip_timesync = config.skip_timesync.unwrap_or(false); + RackInitializeRequestParams::new(rss_config, skip_timesync) + }); let server = bootstrap_server::Server::start(config) .await diff --git a/sled-agent/src/bootstrap/http_entrypoints.rs b/sled-agent/src/bootstrap/http_entrypoints.rs index 12a986cf2fb..3ad94f0799a 100644 --- a/sled-agent/src/bootstrap/http_entrypoints.rs +++ b/sled-agent/src/bootstrap/http_entrypoints.rs @@ -24,7 +24,9 @@ use omicron_common::api::external::Error; use omicron_uuid_kinds::RackInitUuid; use omicron_uuid_kinds::RackResetUuid; use sled_agent_config_reconciler::InternalDisksReceiver; -use sled_agent_types::rack_init::RackInitializeRequest; +use sled_agent_types::rack_init::{ + RackInitializeRequest, RackInitializeRequestParams, +}; use sled_agent_types::rack_ops::RackOperationStatus; use sled_hardware_types::Baseboard; use slog::Logger; @@ -50,7 +52,7 @@ pub(crate) struct BootstrapServerContext { impl BootstrapServerContext { pub(super) fn start_rack_initialize( &self, - request: RackInitializeRequest, + request: RackInitializeRequestParams, ) -> Result { self.rss_access.start_initializing( &self.base_log, @@ -106,8 +108,13 @@ impl BootstrapAgentApi for BootstrapAgentImpl { rqctx: RequestContext, body: TypedBody, ) -> Result, HttpError> { + // Note that if we are performing rack initialization in + // response to an external request, we assume we are not + // skipping timesync. + const SKIP_TIMESYNC: bool = false; let ctx = rqctx.context(); - let request = body.into_inner(); + let request = + RackInitializeRequestParams::new(body.into_inner(), SKIP_TIMESYNC); let id = ctx .start_rack_initialize(request) .map_err(|err| HttpError::for_bad_request(None, err.to_string()))?; diff --git a/sled-agent/src/bootstrap/pre_server.rs b/sled-agent/src/bootstrap/pre_server.rs index b7cd30482c5..bd689156f88 100644 --- a/sled-agent/src/bootstrap/pre_server.rs +++ b/sled-agent/src/bootstrap/pre_server.rs @@ -303,7 +303,7 @@ fn sled_mode_from_config(config: &Config) -> Result { } SledMode::Auto } - SledModeConfig::Gimlet => SledMode::Gimlet, + SledModeConfig::Sled => SledMode::Sled, SledModeConfig::Scrimlet => { let asic = if cfg!(feature = "switch-asic") { DendriteAsic::TofinoAsic diff --git a/sled-agent/src/bootstrap/rack_ops.rs b/sled-agent/src/bootstrap/rack_ops.rs index db2e79d3ec9..ad72f829b4e 100644 --- a/sled-agent/src/bootstrap/rack_ops.rs +++ b/sled-agent/src/bootstrap/rack_ops.rs @@ -10,7 +10,7 @@ use bootstore::schemes::v0 as bootstore; use omicron_uuid_kinds::RackInitUuid; use omicron_uuid_kinds::RackResetUuid; use sled_agent_config_reconciler::InternalDisksReceiver; -use sled_agent_types::rack_init::RackInitializeRequest; +use sled_agent_types::rack_init::RackInitializeRequestParams; use sled_agent_types::rack_ops::{RackOperationStatus, RssStep}; use slog::Logger; use sprockets_tls::keys::SprocketsConfig; @@ -148,7 +148,7 @@ impl RssAccess { global_zone_bootstrap_ip: Ipv6Addr, internal_disks_rx: &InternalDisksReceiver, bootstore_node_handle: &bootstore::NodeHandle, - request: RackInitializeRequest, + request: RackInitializeRequestParams, ) -> Result { let mut status = self.status.lock().unwrap(); @@ -330,7 +330,7 @@ async fn rack_initialize( global_zone_bootstrap_ip: Ipv6Addr, internal_disks_rx: InternalDisksReceiver, bootstore_node_handle: bootstore::NodeHandle, - request: RackInitializeRequest, + request: RackInitializeRequestParams, step_tx: watch::Sender, ) -> Result<(), SetupServiceError> { RssHandle::run_rss( diff --git a/sled-agent/src/bootstrap/rss_handle.rs b/sled-agent/src/bootstrap/rss_handle.rs index f3872b90feb..b3862a8ca58 100644 --- a/sled-agent/src/bootstrap/rss_handle.rs +++ b/sled-agent/src/bootstrap/rss_handle.rs @@ -15,7 +15,7 @@ use omicron_common::backoff::BackoffError; use omicron_common::backoff::retry_notify; use omicron_common::backoff::retry_policy_local; use sled_agent_config_reconciler::InternalDisksReceiver; -use sled_agent_types::rack_init::RackInitializeRequest; +use sled_agent_types::rack_init::RackInitializeRequestParams; use sled_agent_types::rack_ops::RssStep; use sled_agent_types::sled::StartSledAgentRequest; use slog::Logger; @@ -48,7 +48,7 @@ impl RssHandle { pub(super) async fn run_rss( log: &Logger, sprockets: SprocketsConfig, - config: RackInitializeRequest, + config: RackInitializeRequestParams, our_bootstrap_address: Ipv6Addr, internal_disks_rx: InternalDisksReceiver, bootstore: bootstore::NodeHandle, diff --git a/sled-agent/src/bootstrap/server.rs b/sled-agent/src/bootstrap/server.rs index d18b42a3466..6b9ca4b890d 100644 --- a/sled-agent/src/bootstrap/server.rs +++ b/sled-agent/src/bootstrap/server.rs @@ -41,7 +41,7 @@ use omicron_uuid_kinds::GenericUuid; use omicron_uuid_kinds::RackInitUuid; use sled_agent_config_reconciler::ConfigReconcilerSpawnToken; use sled_agent_config_reconciler::InternalDisksReceiver; -use sled_agent_types::rack_init::RackInitializeRequest; +use sled_agent_types::rack_init::RackInitializeRequestParams; use sled_agent_types::sled::StartSledAgentRequest; use sled_hardware::underlay; use sled_storage::dataset::CONFIG_DATASET; @@ -290,7 +290,7 @@ impl Server { pub fn start_rack_initialize( &self, - request: RackInitializeRequest, + request: RackInitializeRequestParams, ) -> Result { self.bootstrap_http_server.app_private().start_rack_initialize(request) } @@ -594,8 +594,8 @@ impl Inner { let initial = server.sled_agent().start_request(); let response = if initial != &request { Err(format!( - "Sled Agent already running: - initital request = {:?}, + "Sled Agent already running: + initital request = {:?}, current request: {:?}", initial, request )) diff --git a/sled-agent/src/config.rs b/sled-agent/src/config.rs index 1c6acb1b8a5..5e617721ce8 100644 --- a/sled-agent/src/config.rs +++ b/sled-agent/src/config.rs @@ -15,14 +15,15 @@ use illumos_utils::dladm::PhysicalLink; use omicron_common::vlan::VlanID; use serde::Deserialize; use sled_hardware::UnparsedDisk; -use sled_hardware::is_gimlet; +use sled_hardware::is_oxide_sled; use sprockets_tls::keys::SprocketsConfig; #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "lowercase")] pub enum SledMode { Auto, - Gimlet, + #[serde(alias = "gimlet")] + Sled, Scrimlet, } @@ -137,7 +138,7 @@ pub enum ConfigError { }, #[error("Loading certificate: {0}")] Certificate(#[source] anyhow::Error), - #[error("Could not determine if host is a Gimlet: {0}")] + #[error("Could not determine if host is an Oxide sled: {0}")] SystemDetection(#[source] anyhow::Error), #[error("Could not enumerate physical links")] FindLinks(#[from] FindPhysicalLinkError), @@ -158,7 +159,7 @@ impl Config { if let Some(link) = self.data_link.as_ref() { Ok(link.clone()) } else { - if is_gimlet().map_err(ConfigError::SystemDetection)? { + if is_oxide_sled().map_err(ConfigError::SystemDetection)? { Dladm::list_physical() .await .map_err(ConfigError::FindLinks)? diff --git a/sled-agent/src/rack_setup/service.rs b/sled-agent/src/rack_setup/service.rs index f18e205b6f7..235f924d798 100644 --- a/sled-agent/src/rack_setup/service.rs +++ b/sled-agent/src/rack_setup/service.rs @@ -129,6 +129,7 @@ use sled_agent_types::early_networking::{ }; use sled_agent_types::rack_init::{ BootstrapAddressDiscovery, RackInitializeRequest as Config, + RackInitializeRequestParams, }; use sled_agent_types::rack_ops::RssStep; use sled_agent_types::sled::StartSledAgentRequest; @@ -273,7 +274,7 @@ impl RackSetupService { /// - `bootstore` - A handle to call bootstore APIs pub(crate) fn new( log: Logger, - config: Config, + request: RackInitializeRequestParams, internal_disks_rx: InternalDisksReceiver, local_bootstrap_agent: BootstrapAgentHandle, bootstore: bootstore::NodeHandle, @@ -283,7 +284,7 @@ impl RackSetupService { let svc = ServiceInner::new(log.clone()); if let Err(e) = svc .run( - &config, + &request, &internal_disks_rx, local_bootstrap_agent, bootstore, @@ -1170,14 +1171,16 @@ impl ServiceInner { // remaining is to handoff to Nexus. async fn run( &self, - config: &Config, + request: &RackInitializeRequestParams, internal_disks_rx: &InternalDisksReceiver, local_bootstrap_agent: BootstrapAgentHandle, bootstore: bootstore::NodeHandle, step_tx: watch::Sender, ) -> Result<(), SetupServiceError> { - info!(self.log, "Injecting RSS configuration: {:#?}", config); + info!(self.log, "Injecting RSS configuration: {:#?}", request); let mut rss_step = RssProgress::new(step_tx); + let config = &request.rack_initialize_request; + let skip_timesync = request.skip_timesync; let resolver = DnsResolver::new_from_subnet( self.log.new(o!("component" => "DnsResolver")), @@ -1384,24 +1387,27 @@ impl ServiceInner { }) .collect(); - let ntp_clients = ntp_addresses - .into_iter() - .map(|address| { - let dur = std::time::Duration::from_secs(60); - let client = reqwest::ClientBuilder::new() - .connect_timeout(dur) - .timeout(dur) - .build() - .map_err(SetupServiceError::HttpClient)?; - let client = NtpAdminClient::new_with_client( - &format!("http://{}", address), - client, - self.log.new(o!("NtpAdminClient" => address.to_string())), - ); - Ok(client) - }) - .collect::, SetupServiceError>>()?; - self.wait_for_timesync(&ntp_clients).await?; + if !skip_timesync { + let ntp_clients = ntp_addresses + .into_iter() + .map(|address| { + let dur = std::time::Duration::from_secs(60); + let client = reqwest::ClientBuilder::new() + .connect_timeout(dur) + .timeout(dur) + .build() + .map_err(SetupServiceError::HttpClient)?; + let client = NtpAdminClient::new_with_client( + &format!("http://{}", address), + client, + self.log + .new(o!("NtpAdminClient" => address.to_string())), + ); + Ok(client) + }) + .collect::, SetupServiceError>>()?; + self.wait_for_timesync(&ntp_clients).await?; + } info!(self.log, "Finished setting up Internal DNS and NTP"); diff --git a/sled-agent/src/services.rs b/sled-agent/src/services.rs index 5912c741c11..564b4a88ef8 100644 --- a/sled-agent/src/services.rs +++ b/sled-agent/src/services.rs @@ -99,7 +99,7 @@ use sled_agent_types::zone_images::{ use sled_agent_zone_images::{ZoneImageSourceResolver, ramdisk_file_source}; use sled_hardware::DendriteAsic; use sled_hardware::SledMode; -use sled_hardware::is_gimlet; +use sled_hardware::is_oxide_sled; use sled_hardware::underlay; use sled_hardware_types::Baseboard; use slog::Logger; @@ -942,7 +942,7 @@ impl ServiceManager { ) -> Result, Error> { let mut links: Vec<(Link, bool)> = Vec::new(); - let is_gimlet = is_gimlet().map_err(|e| { + let is_oxide_sled = is_oxide_sled().map_err(|e| { Error::Underlay(underlay::Error::SystemDetection(e)) })?; @@ -965,7 +965,7 @@ impl ServiceManager { links.push((link, false)); } Err(_) => { - if is_gimlet { + if is_oxide_sled { return Err(Error::MissingDevice { device: pkt_source.to_string(), }); @@ -2773,11 +2773,11 @@ impl ServiceManager { ); } - let is_gimlet = is_gimlet().map_err(|e| { + let is_oxide_sled = is_oxide_sled().map_err(|e| { Error::Underlay(underlay::Error::SystemDetection(e)) })?; - if is_gimlet { + if is_oxide_sled { // Collect the prefixes for each techport. let nameaddr = bootstrap_name_and_address.as_ref(); let techport_prefixes = match nameaddr { @@ -2806,7 +2806,7 @@ impl ServiceManager { } }; - if is_gimlet + if is_oxide_sled || asic == &DendriteAsic::SoftNpuPropolisDevice || asic == &DendriteAsic::TofinoAsic { @@ -2974,11 +2974,12 @@ impl ServiceManager { } } - let is_gimlet = is_gimlet().map_err(|e| { + let is_oxide_sled = is_oxide_sled().map_err(|e| { Error::Underlay(underlay::Error::SystemDetection(e)) })?; - let maghemite_interfaces: Vec = if is_gimlet { + let maghemite_interfaces: Vec = if is_oxide_sled + { (0..32) .map(|i| { // See the `tfport_name` function @@ -3022,7 +3023,7 @@ impl ServiceManager { ); } - if is_gimlet { + if is_oxide_sled { mg_ddm_config = mg_ddm_config .add_property("dpd_host", "astring", "[::1]") .add_property( @@ -3159,9 +3160,9 @@ impl ServiceManager { let mut data_links: Vec = vec![]; let services = match self.inner.sled_mode { - // A pure gimlet sled should not be trying to activate a switch - // zone. - SledMode::Gimlet => { + // A sled that is not a scrimlet should not try to activate a + // switch zone. + SledMode::Sled => { return Err(Error::SwitchZone(anyhow::anyhow!( "attempted to activate switch zone on non-scrimlet sled" ))); diff --git a/sled-agent/types/src/rack_init.rs b/sled-agent/types/src/rack_init.rs index 0f047734c10..cb2e2547271 100644 --- a/sled-agent/types/src/rack_init.rs +++ b/sled-agent/types/src/rack_init.rs @@ -95,6 +95,7 @@ pub mod back_compat { }) } } + impl From for RackInitializeRequest { fn from(v1: RackInitializeRequestV1) -> Self { RackInitializeRequest { @@ -298,19 +299,18 @@ impl RackInitializeRequest { pub fn from_toml_with_fallback( data: &str, ) -> Result { - let v2_err = match toml::from_str::(&data) { - Ok(req) => return Ok(req), - Err(e) => e, - }; - if let Ok(v1) = - toml::from_str::(&data) - { - return Ok(v1.into()); - } - - // If we fail to parse the request as any known version, we return the - // error corresponding to the parse failure of the newest schema. - Err(v2_err.into()) + // Note that if we fail to parse the request as any known + // version, we return the error corresponding to the parse + // failure for the newest schema. + toml::from_str::(&data).or_else( + |latest_version_err| match toml::from_str::< + back_compat::RackInitializeRequestV1, + >(&data) + { + Ok(v1) => Ok(v1.into()), + Err(_v1_err) => Err(latest_version_err.into()), + }, + ) } /// Return a configuration suitable for testing. @@ -375,7 +375,7 @@ impl std::fmt::Debug for RackInitializeRequest { // If you find a compiler error here, and you just added a field to this // struct, be sure to add it to the Debug impl below! let RackInitializeRequest { - trust_quorum_peers: trust_qurorum_peers, + trust_quorum_peers, bootstrap_discovery, ntp_servers, dns_servers, @@ -389,7 +389,7 @@ impl std::fmt::Debug for RackInitializeRequest { } = &self; f.debug_struct("RackInitializeRequest") - .field("trust_quorum_peers", trust_qurorum_peers) + .field("trust_quorum_peers", trust_quorum_peers) .field("bootstrap_discovery", bootstrap_discovery) .field("ntp_servers", ntp_servers) .field("dns_servers", dns_servers) @@ -407,6 +407,21 @@ impl std::fmt::Debug for RackInitializeRequest { } } +#[derive(Debug, Clone)] +pub struct RackInitializeRequestParams { + pub rack_initialize_request: RackInitializeRequest, + pub skip_timesync: bool, +} + +impl RackInitializeRequestParams { + pub fn new( + rack_initialize_request: RackInitializeRequest, + skip_timesync: bool, + ) -> RackInitializeRequestParams { + RackInitializeRequestParams { rack_initialize_request, skip_timesync } + } +} + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case", tag = "type")] pub enum BootstrapAddressDiscovery { diff --git a/sled-hardware/src/illumos/mod.rs b/sled-hardware/src/illumos/mod.rs index 3f673e0b4ca..3670423bfb7 100644 --- a/sled-hardware/src/illumos/mod.rs +++ b/sled-hardware/src/illumos/mod.rs @@ -9,7 +9,7 @@ use gethostname::gethostname; use illumos_devinfo::{DevInfo, DevLinkType, DevLinks, Node, Property}; use libnvme::{Nvme, controller::Controller}; use omicron_common::disk::{DiskIdentity, DiskVariant}; -use sled_hardware_types::{Baseboard, SledCpuFamily}; +use sled_hardware_types::{Baseboard, OxideSled, SledCpuFamily}; use slog::Logger; use slog::debug; use slog::error; @@ -33,8 +33,8 @@ enum Error { #[error("Failed to access devinfo: {0}")] DevInfo(anyhow::Error), - #[error("Device does not appear to be an Oxide Gimlet: {0}")] - NotAGimlet(String), + #[error("Device does not appear to be an Oxide sled: {0}")] + NotAnOxideSled(String), #[error("Invalid Utf8 path: {0}")] FromPathBuf(#[from] camino::FromPathBufError), @@ -76,16 +76,14 @@ enum Error { FirmwareLogPage(#[from] libnvme::firmware::FirmwareLogPageError), } -const GIMLET_ROOT_NODE_NAME: &str = "Oxide,Gimlet"; - -/// Return true if the host system is an Oxide Gimlet. -pub fn is_gimlet() -> anyhow::Result { +/// Return true if the host system is an Oxide Sled. +pub fn is_oxide_sled() -> anyhow::Result { let mut device_info = DevInfo::new()?; let mut node_walker = device_info.walk_node(); let Some(root) = node_walker.next().transpose()? else { anyhow::bail!("No nodes in device tree"); }; - Ok(root.node_name() == GIMLET_ROOT_NODE_NAME) + Ok(OxideSled::try_from_root_node_name(&root.node_name()).is_some()) } // A snapshot of information about the underlying Tofino device @@ -135,7 +133,8 @@ impl HardwareSnapshot { let mut node_walker = device_info.walk_node(); - // First, check the root node. If we aren't running on a Gimlet, bail. + // First, check the root node. If we aren't running on an Oxide sled, + // bail. let Some(root) = node_walker.next().transpose().map_err(Error::DevInfo)? else { @@ -144,9 +143,10 @@ impl HardwareSnapshot { ))); }; let root_node = root.node_name(); - if root_node != GIMLET_ROOT_NODE_NAME { - return Err(Error::NotAGimlet(root_node)); - } + let Some(sled_type) = OxideSled::try_from_root_node_name(&root_node) + else { + return Err(Error::NotAnOxideSled(root_node)); + }; let properties = find_properties( &root, @@ -157,7 +157,8 @@ impl HardwareSnapshot { "boot-storage-unit", ], )?; - let baseboard = Baseboard::new_gimlet( + let baseboard = Baseboard::new_oxide_sled( + sled_type, string_from_property(&properties[0])?, string_from_property(&properties[1])?, u32_from_property(&properties[2])?, @@ -174,7 +175,13 @@ impl HardwareSnapshot { while let Some(node) = node_walker.next().transpose().map_err(Error::DevInfo)? { - poll_blkdev_node(&log, &mut disks, node, boot_storage_unit)?; + poll_blkdev_node( + &log, + sled_type, + &mut disks, + node, + boot_storage_unit, + )?; } Ok(Self { tofino, disks, baseboard }) @@ -294,7 +301,7 @@ impl HardwareView { } } - use HardwareUpdate::*; + use HardwareUpdate::{DiskAdded, DiskRemoved, DiskUpdated}; for disk in removed { updates.push(DiskRemoved(disk)); } @@ -309,22 +316,27 @@ impl HardwareView { } } -fn slot_to_disk_variant(slot: i64) -> Option { - match slot { - // For the source of these values, refer to: - // - // https://github.com/oxidecomputer/illumos-gate/blob/87a8bbb8edfb89ad5012beb17fa6f685c7795416/usr/src/uts/oxide/milan/milan_dxio_data.c#L823-L847 - 0x00..=0x09 => Some(DiskVariant::U2), - 0x11..=0x12 => Some(DiskVariant::M2), - _ => None, +fn slot_to_disk_variant(sled: OxideSled, slot: i64) -> Option { + let u2_slots = sled.u2_disk_slots(); + let m2_slots = sled.m2_disk_slots(); + if u2_slots.contains(&slot) { + Some(DiskVariant::U2) + } else if m2_slots.contains(&slot) { + Some(DiskVariant::M2) + } else { + None } } -fn slot_is_boot_disk(slot: i64, boot_storage_unit: BootStorageUnit) -> bool { - match (boot_storage_unit, slot) { - // See reference for these values in `slot_to_disk_variant` above. - (BootStorageUnit::A, 0x11) | (BootStorageUnit::B, 0x12) => true, - _ => false, +fn slot_is_boot_disk( + sled: OxideSled, + slot: i64, + boot_storage_unit: BootStorageUnit, +) -> bool { + let slots = sled.bootdisk_slots(); + match boot_storage_unit { + BootStorageUnit::A => slots[0] == slot, + BootStorageUnit::B => slots[1] == slot, } } @@ -481,6 +493,7 @@ fn find_properties<'a, const N: usize>( fn poll_blkdev_node( log: &Logger, + sled: OxideSled, disks: &mut HashMap, node: Node<'_>, boot_storage_unit: BootStorageUnit, @@ -551,7 +564,7 @@ fn poll_blkdev_node( let slot = i64_from_property( &find_properties(&pcieb_node, ["physical-slot#"])?[0], )?; - let Some(variant) = slot_to_disk_variant(slot) else { + let Some(variant) = slot_to_disk_variant(sled, slot) else { warn!(log, "Slot# {slot} is not recognized as a disk: {devfs_path}"); return Err(Error::UnrecognizedSlot { slot }); }; @@ -589,7 +602,7 @@ fn poll_blkdev_node( slot, variant, device_id.clone(), - slot_is_boot_disk(slot, boot_storage_unit), + slot_is_boot_disk(sled, slot, boot_storage_unit), firmware.clone(), ); disks.insert(device_id, disk); @@ -601,7 +614,7 @@ fn poll_blkdev_node( fn poll_device_tree( log: &Logger, inner: &Arc>, - nongimlet_observed_disks: &[UnparsedDisk], + nonsled_observed_disks: &[UnparsedDisk], tx: &broadcast::Sender, ) -> Result<(), Error> { // Construct a view of hardware by walking the device tree. @@ -609,13 +622,13 @@ fn poll_device_tree( Ok(polled_hw) => polled_hw, Err(e) => { - if let Error::NotAGimlet(root_node) = &e { + if let Error::NotAnOxideSled(root_node) = &e { let mut inner = inner.lock().unwrap(); if root_node.as_str() == "i86pc" { // If on i86pc, generate some baseboard information before // returning this error. Each sled agent has to be uniquely - // identified for multiple non-gimlets to work. + // identified for multiple non-sleds to work. if inner.baseboard.is_none() { let pc_baseboard = Baseboard::new_pc( gethostname().into_string().unwrap_or_else(|_| { @@ -636,8 +649,8 @@ fn poll_device_tree( // For platforms that don't support the HardwareSnapshot // functionality, sled-agent can be supplied a fixed list of // UnparsedDisks. Add those to the HardwareSnapshot here if they - // are missing (which they will be for non-gimlets). - for observed_disk in nongimlet_observed_disks { + // are missing (which they will be for non-sleds). + for observed_disk in nonsled_observed_disks { let identity = observed_disk.identity(); if !inner.disks.contains_key(identity) { inner @@ -676,14 +689,14 @@ fn poll_device_tree( async fn hardware_tracking_task( log: Logger, inner: Arc>, - nongimlet_observed_disks: Vec, + nonsled_observed_disks: Vec, tx: broadcast::Sender, ) { loop { - match poll_device_tree(&log, &inner, &nongimlet_observed_disks, &tx) { - // We've already warned about `NotAGimlet` by this point, + match poll_device_tree(&log, &inner, &nonsled_observed_disks, &tx) { + // We've already warned about `NotAnOxideSled` by this point, // so let's not spam the logs. - Ok(_) | Err(Error::NotAGimlet(_)) => (), + Ok(_) | Err(Error::NotAnOxideSled(_)) => (), Err(err) => { warn!(log, "Failed to query device tree: {err}"); } @@ -709,12 +722,12 @@ impl HardwareManager { /// /// Arguments: /// - `sled_mode`: The sled's mode of operation (auto detect or force gimlet/scrimlet). - /// - `nongimlet_observed_disks`: For non-gimlets, inject these disks into + /// - `nonsled_observed_disks`: For non-sleds, inject these disks into /// HardwareSnapshot objects. pub fn new( log: &Logger, sled_mode: SledMode, - nongimlet_observed_disks: Vec, + nonsled_observed_disks: Vec, ) -> Result { let log = log.new(o!("component" => "HardwareManager")); info!(log, "Creating HardwareManager"); @@ -733,7 +746,7 @@ impl HardwareManager { } // Treat sled as gimlet and ignore any attached Tofino device. - SledMode::Gimlet => HardwareView::new_stub_tofino( + SledMode::Sled => HardwareView::new_stub_tofino( // active= false, ), @@ -761,14 +774,14 @@ impl HardwareManager { // This mitigates issues where the Sled Agent could try to propagate // an "empty" view of hardware to other consumers before the first // query. - match poll_device_tree(&log, &inner, &nongimlet_observed_disks, &tx) { + match poll_device_tree(&log, &inner, &nonsled_observed_disks, &tx) { Ok(_) => (), - // Allow non-gimlet devices to proceed with a "null" view of + // Allow non-sled devices to proceed with a "null" view of // hardware, otherwise they won't be able to start. - Err(Error::NotAGimlet(root)) => { + Err(Error::NotAnOxideSled(root)) => { warn!( log, - "Device is not a Gimlet ({root}), proceeding with null hardware view" + "Device is not an Oxide sled ({root}), proceeding with null hardware view" ); } Err(err) => { @@ -780,7 +793,7 @@ impl HardwareManager { let inner2 = inner.clone(); let tx2 = tx.clone(); tokio::task::spawn(async move { - hardware_tracking_task(log2, inner2, nongimlet_observed_disks, tx2) + hardware_tracking_task(log2, inner2, nonsled_observed_disks, tx2) .await }); diff --git a/sled-hardware/src/lib.rs b/sled-hardware/src/lib.rs index 582c13f4053..cff03ee869f 100644 --- a/sled-hardware/src/lib.rs +++ b/sled-hardware/src/lib.rs @@ -66,13 +66,13 @@ impl std::fmt::Display for DendriteAsic { } } -/// Configuration for forcing a sled to run as a Scrimlet or Gimlet +/// Configuration for forcing a sled to run as a Scrimlet or compute Sled #[derive(Copy, Clone, Debug)] pub enum SledMode { - /// Automatically detect whether to run as a Gimlet or Scrimlet (w/ real Tofino ASIC) + /// Automatically detect whether to run as a compute sled or Scrimlet (w/ real Tofino ASIC) Auto, /// Force sled to run as a Gimlet - Gimlet, + Sled, /// Force sled to run as a Scrimlet Scrimlet { asic: DendriteAsic }, } diff --git a/sled-hardware/src/non_illumos/mod.rs b/sled-hardware/src/non_illumos/mod.rs index fa660ad0caa..e4385019843 100644 --- a/sled-hardware/src/non_illumos/mod.rs +++ b/sled-hardware/src/non_illumos/mod.rs @@ -32,7 +32,7 @@ impl HardwareManager { pub fn new( _log: &Logger, _sled_mode: SledMode, - _nongimlet_observed_disks: Vec, + _nonsled_observed_disks: Vec, ) -> Result { unimplemented!("Accessing hardware unsupported on non-illumos"); } @@ -84,7 +84,7 @@ pub async fn ensure_partition_layout( unimplemented!("Accessing hardware unsupported on non-illumos"); } -/// Return true if the host system is an Oxide Gimlet. -pub fn is_gimlet() -> anyhow::Result { +/// Return true if the host system is an Oxide sled. +pub fn is_oxide_sled() -> anyhow::Result { Ok(false) } diff --git a/sled-hardware/src/underlay.rs b/sled-hardware/src/underlay.rs index 8b3d375176f..3aaf30b96bc 100644 --- a/sled-hardware/src/underlay.rs +++ b/sled-hardware/src/underlay.rs @@ -4,7 +4,7 @@ //! Finding the underlay network physical links and address objects. -use crate::is_gimlet; +use crate::is_oxide_sled; use illumos_utils::addrobj; use illumos_utils::addrobj::AddrObject; use illumos_utils::dladm::CHELSIO_LINK_PREFIX; @@ -25,7 +25,7 @@ pub enum Error { #[error(transparent)] BadAddrObj(#[from] addrobj::ParseError), - #[error("Could not determine if host is a Gimlet: {0}")] + #[error("Could not determine if host is an Oxide sled: {0}")] SystemDetection(#[source] anyhow::Error), #[error("Could not enumerate physical links: {0}")] @@ -63,13 +63,13 @@ pub async fn find_nics( /// Return the Chelsio links on the system. /// -/// For a real Gimlet, this should return the devices like `cxgbeN`. For a -/// developer machine, or generally a non-Gimlet, this will return the +/// For a real Oxide sled, this should return the devices like `cxgbeN`. For a +/// developer machine, or generally a non-sled, this will return the /// VNICs we use to emulate those Chelsio links. pub async fn find_chelsio_links( config_data_links: &[String; 2], ) -> Result, Error> { - if is_gimlet().map_err(Error::SystemDetection)? { + if is_oxide_sled().map_err(Error::SystemDetection)? { Dladm::list_physical().await.map_err(Error::FindLinks).map(|links| { links .into_iter() diff --git a/sled-hardware/types/src/lib.rs b/sled-hardware/types/src/lib.rs index ce4a29da4c0..86881a57680 100644 --- a/sled-hardware/types/src/lib.rs +++ b/sled-hardware/types/src/lib.rs @@ -4,9 +4,77 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::ops::RangeInclusive; pub mod underlay; +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum OxideSled { + Gimlet, + Cosmo, +} + +impl OxideSled { + pub fn try_from_root_node_name(root_node_name: &str) -> Option { + const GIMLET_ROOT_NODE_NAME: &str = "Oxide,Gimlet"; + const COSMO_ROOT_NODE_NAME: &str = "Oxide,Cosmo"; + match root_node_name { + GIMLET_ROOT_NODE_NAME => Some(Self::Gimlet), + COSMO_ROOT_NODE_NAME => Some(Self::Cosmo), + _ => None, + } + } + + pub fn try_from_model(model: &str) -> Option { + match model { + "913-0000023" => Some(Self::Cosmo), + "913-0000019" | "913-0000006" => Some(Self::Gimlet), + _ => None, + } + } + + pub fn name(&self) -> &'static str { + match self { + Self::Gimlet => "gimlet", + Self::Cosmo => "cosmo", + } + } + + pub fn m2_disk_slots(&self) -> RangeInclusive { + match self { + Self::Gimlet | Self::Cosmo => 0x11..=0x12, + } + } + + pub fn u2_disk_slots(&self) -> RangeInclusive { + match self { + Self::Gimlet => 0x00..=0x09, + Self::Cosmo => 0x20..=0x29, + } + } + + pub fn bootdisk_slots(&self) -> [i64; 2] { + match self { + Self::Gimlet | Self::Cosmo => [0x11, 0x12], + } + } +} + +// Note that, as part of a larger effort to generalize Omicron +// for sled types other than gimlets, we would like to change +// the Baseboard type to refer to an `OxideSled` variant instead +// of Gimlets, specifically. Similarly, the doc comment on the +// type should be changed to read: +// +// Describes properties that should uniquely identify a system. +// +// However, this requires introducing a new revision into the +// sled-agent API, and the Baseboard type has leaked into surprising +// places, so that work has been deferred to a change subsequent +// to the one that interprets the "model" string to differentiate +// between Gimlet and Cosmo. (This comment should be removed once +// that work is completed). + /// Describes properties that should uniquely identify a Gimlet. #[derive( Clone, @@ -23,13 +91,24 @@ pub mod underlay; #[serde(tag = "type", rename_all = "snake_case")] pub enum Baseboard { Gimlet { identifier: String, model: String, revision: u32 }, - Unknown, Pc { identifier: String, model: String }, } impl Baseboard { + pub fn new_oxide_sled( + kind: OxideSled, + identifier: String, + model: String, + revision: u32, + ) -> Self { + match kind { + OxideSled::Gimlet => Self::new_gimlet(identifier, model, revision), + OxideSled::Cosmo => Self::new_cosmo(identifier, model, revision), + } + } + #[allow(dead_code)] pub fn new_gimlet( identifier: String, @@ -39,6 +118,10 @@ impl Baseboard { Self::Gimlet { identifier, model, revision } } + pub fn new_cosmo(identifier: String, model: String, revision: u32) -> Self { + Self::Gimlet { identifier, model, revision } + } + pub fn new_pc(identifier: String, model: String) -> Self { Self::Pc { identifier, model } } @@ -51,7 +134,9 @@ impl Baseboard { pub fn type_string(&self) -> &str { match &self { - Self::Gimlet { .. } => "gimlet", + Self::Gimlet { .. } => OxideSled::try_from_model(self.model()) + .map(|sled| sled.name()) + .unwrap_or("oxide"), Self::Pc { .. } => "pc", Self::Unknown => "unknown", } @@ -86,7 +171,8 @@ impl std::fmt::Display for Baseboard { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Baseboard::Gimlet { identifier, model, revision } => { - write!(f, "gimlet-{identifier}-{model}-{revision}") + let oxide_sled_type = self.type_string(); + write!(f, "{oxide_sled_type}-{identifier}-{model}-{revision}") } Baseboard::Unknown => write!(f, "unknown"), Baseboard::Pc { identifier, model } => { diff --git a/wicket/src/cli/inventory.rs b/wicket/src/cli/inventory.rs index 54bfa304c22..17b141a40e9 100644 --- a/wicket/src/cli/inventory.rs +++ b/wicket/src/cli/inventory.rs @@ -10,7 +10,6 @@ use anyhow::Context; use anyhow::Result; use clap::{Subcommand, ValueEnum}; use owo_colors::OwoColorize; -use sled_hardware_types::Baseboard; use slog::Logger; use std::fmt; use std::net::SocketAddrV6; @@ -103,12 +102,7 @@ fn print_bootstrap_sled_data( ) { let slot = desc.id.slot; - let identifier = match &desc.baseboard { - Baseboard::Gimlet { identifier, .. } => identifier.clone(), - Baseboard::Pc { identifier, .. } => identifier.clone(), - Baseboard::Unknown => "unknown".to_string(), - }; - + let identifier = desc.baseboard.identifier(); let address = desc.bootstrap_ip; // Create status indicators diff --git a/wicket/src/cli/rack_setup/config_toml.rs b/wicket/src/cli/rack_setup/config_toml.rs index 191a8cb30fd..7ff481b46b9 100644 --- a/wicket/src/cli/rack_setup/config_toml.rs +++ b/wicket/src/cli/rack_setup/config_toml.rs @@ -197,7 +197,7 @@ fn build_sleds_array(sleds: &BTreeSet) -> Array { format!(" # UNKNOWN SLED ({ip}){end}") } Baseboard::Pc { identifier, model } => { - format!(" # NON-GIMLET {identifier} (model {model}, {ip}){end}") + format!(" # NON-OXIDE {identifier} (model {model}, {ip}){end}") } } } diff --git a/wicket/src/ui/panes/rack_setup.rs b/wicket/src/ui/panes/rack_setup.rs index fcc4acf5609..6dfeaf86f6a 100644 --- a/wicket/src/ui/panes/rack_setup.rs +++ b/wicket/src/ui/panes/rack_setup.rs @@ -35,7 +35,6 @@ use ratatui::widgets::Block; use ratatui::widgets::BorderType; use ratatui::widgets::Borders; use ratatui::widgets::Paragraph; -use sled_hardware_types::Baseboard; use std::borrow::Cow; use wicket_common::rack_setup::BgpAuthKeyInfo; use wicket_common::rack_setup::BgpAuthKeyStatus; @@ -1278,11 +1277,7 @@ fn rss_config_text<'a>( bootstrap_sleds .iter() .map(|desc| { - let identifier = match &desc.baseboard { - Baseboard::Gimlet { identifier, .. } => identifier, - Baseboard::Pc { identifier, .. } => identifier, - Baseboard::Unknown => "unknown", - }; + let identifier = desc.baseboard.identifier(); let mut spans = vec![ Span::styled(" • ", label_style), Span::styled(format!("Cubby {}", desc.id.slot), ok_style),