diff --git a/Cargo.lock b/Cargo.lock index e96cddced28..3da5011b7f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4296,7 +4296,7 @@ dependencies = [ [[package]] name = "gateway-ereport-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=01f06089a97bd084bec2595d4d62e0f91338cf57#01f06089a97bd084bec2595d4d62e0f91338cf57" +source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=438cd185666d5f164548885873f0f5df9a38eed1#438cd185666d5f164548885873f0f5df9a38eed1" dependencies = [ "hubpack", "serde", @@ -4306,7 +4306,7 @@ dependencies = [ [[package]] name = "gateway-messages" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=01f06089a97bd084bec2595d4d62e0f91338cf57#01f06089a97bd084bec2595d4d62e0f91338cf57" +source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=438cd185666d5f164548885873f0f5df9a38eed1#438cd185666d5f164548885873f0f5df9a38eed1" dependencies = [ "bitflags 2.11.0", "gateway-ereport-messages", @@ -4325,7 +4325,7 @@ dependencies = [ [[package]] name = "gateway-sp-comms" version = "0.1.2" -source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=01f06089a97bd084bec2595d4d62e0f91338cf57#01f06089a97bd084bec2595d4d62e0f91338cf57" +source = "git+https://github.com/oxidecomputer/management-gateway-service?rev=438cd185666d5f164548885873f0f5df9a38eed1#438cd185666d5f164548885873f0f5df9a38eed1" dependencies = [ "async-trait", "backoff", @@ -4352,7 +4352,7 @@ dependencies = [ "socket2 0.6.3", "string_cache", "thiserror 2.0.18", - "tlvc 0.3.1 (git+https://github.com/oxidecomputer/tlvc.git?branch=main)", + "tlvc 0.4.1 (git+https://github.com/oxidecomputer/tlvc.git?branch=main)", "tokio", "usdt 0.5.0", "uuid", @@ -5213,7 +5213,7 @@ dependencies = [ "path-slash 0.1.5", "rsa 0.9.10", "thiserror 1.0.69", - "tlvc 0.3.1 (git+https://github.com/oxidecomputer/tlvc)", + "tlvc 0.3.1", "tlvc-text 0.3.0", "toml 0.7.8", "x509-cert", @@ -5234,7 +5234,7 @@ dependencies = [ "path-slash 0.1.5", "rsa 0.9.10", "thiserror 1.0.69", - "tlvc 0.3.1 (git+https://github.com/oxidecomputer/tlvc)", + "tlvc 0.3.1", "tlvc-text 0.3.0", "toml 0.7.8", "x509-cert", @@ -5255,7 +5255,7 @@ dependencies = [ "path-slash 0.2.1", "rsa 0.9.10", "thiserror 2.0.18", - "tlvc 0.4.1", + "tlvc 0.4.1 (git+https://github.com/oxidecomputer/tlvc?rev=7a96eab94c8aec9120412d8601ffd68853957da3)", "tlvc-text 0.4.0", "toml 1.1.2+spec-1.1.0", "x509-cert", @@ -10018,6 +10018,7 @@ dependencies = [ "gateway-messages", "generic-array 0.14.7", "getrandom 0.2.17", + "getrandom 0.3.4", "getrandom 0.4.3", "group 0.13.0", "hashbrown 0.15.5", @@ -15734,7 +15735,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix 1.1.4", "windows-sys 0.59.0", @@ -16039,7 +16040,7 @@ dependencies = [ [[package]] name = "tlvc" version = "0.3.1" -source = "git+https://github.com/oxidecomputer/tlvc.git?branch=main#e644a21a7ca973ed31499106ea926bd63ebccc6f" +source = "git+https://github.com/oxidecomputer/tlvc#e644a21a7ca973ed31499106ea926bd63ebccc6f" dependencies = [ "byteorder", "crc", @@ -16048,12 +16049,13 @@ dependencies = [ [[package]] name = "tlvc" -version = "0.3.1" -source = "git+https://github.com/oxidecomputer/tlvc#e644a21a7ca973ed31499106ea926bd63ebccc6f" +version = "0.4.1" +source = "git+https://github.com/oxidecomputer/tlvc.git?branch=main#7a96eab94c8aec9120412d8601ffd68853957da3" dependencies = [ - "byteorder", "crc", - "zerocopy 0.6.6", + "thiserror 2.0.18", + "zerocopy 0.8.55", + "zerocopy-derive 0.8.55", ] [[package]] @@ -16074,7 +16076,7 @@ source = "git+https://github.com/oxidecomputer/tlvc#e644a21a7ca973ed31499106ea92 dependencies = [ "ron", "serde", - "tlvc 0.3.1 (git+https://github.com/oxidecomputer/tlvc)", + "tlvc 0.3.1", "zerocopy 0.6.6", ] @@ -16085,7 +16087,7 @@ source = "git+https://github.com/oxidecomputer/tlvc?rev=7a96eab94c8aec9120412d86 dependencies = [ "ron", "serde", - "tlvc 0.4.1", + "tlvc 0.4.1 (git+https://github.com/oxidecomputer/tlvc?rev=7a96eab94c8aec9120412d8601ffd68853957da3)", "zerocopy 0.8.55", ] diff --git a/Cargo.toml b/Cargo.toml index 339882b9dea..f6327959882 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -549,9 +549,10 @@ gateway-client = { path = "clients/gateway-client" } # is "fine", because SP/MGS communication maintains forwards and backwards # compatibility, but will mean that faux-mgs might be missing new # functionality.) -gateway-ereport-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "01f06089a97bd084bec2595d4d62e0f91338cf57", default-features = false, features = ["debug-impls"] } -gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "01f06089a97bd084bec2595d4d62e0f91338cf57", default-features = false, features = ["std"] } -gateway-sp-comms = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "01f06089a97bd084bec2595d4d62e0f91338cf57" } +# +gateway-ereport-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "438cd185666d5f164548885873f0f5df9a38eed1", default-features = false, features = ["debug-impls"] } +gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "438cd185666d5f164548885873f0f5df9a38eed1", default-features = false, features = ["std"] } +gateway-sp-comms = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "438cd185666d5f164548885873f0f5df9a38eed1" } gateway-test-utils = { path = "gateway-test-utils" } gateway-types = { path = "gateway-types" } gateway-types-versions = { path = "gateway-types/versions" } diff --git a/clients/gateway-client/src/lib.rs b/clients/gateway-client/src/lib.rs index 3ffdd8e3743..9531ac803ea 100644 --- a/clients/gateway-client/src/lib.rs +++ b/clients/gateway-client/src/lib.rs @@ -75,7 +75,14 @@ progenitor::generate_api!( }, replace = { + Barcode = gateway_types::component_vpd::Barcode, + ComponentVpd = gateway_types::component_vpd::ComponentVpd, + Mpn1Barcode = gateway_types::component_vpd::Mpn1Barcode, + OxideBarcode = gateway_types::component_vpd::OxideBarcode, + PmbusDevice = gateway_types::component_vpd::PmbusDevice, RotSlot = gateway_types::rot::RotSlot, + SledFanTray = gateway_types::component_vpd::SledFanTray, + Tmp11x = gateway_types::component_vpd::Tmp11x, RotState = gateway_types::rot::RotState, RotImageError = gateway_types::rot::RotImageError, Ena = ereport_types::Ena, diff --git a/gateway-api/src/lib.rs b/gateway-api/src/lib.rs index f0e9f34f005..6d768ebd043 100644 --- a/gateway-api/src/lib.rs +++ b/gateway-api/src/lib.rs @@ -24,6 +24,7 @@ api_versions!([ // | example for the next person. // v // (next_int, IDENT), + (4, COMPONENT_VPD), (3, NEWTYPE_UUID_BUMP), (2, COSMO), (1, INITIAL), @@ -334,6 +335,21 @@ pub trait GatewayApi { body: TypedBody, ) -> Result; + /// Get the vital product data (VPD) identity of a component. + /// + /// Not all components have VPD. This endpoint will return an error if the + /// requested component does not advertise the `HAS_VPD` DeviceCapability + /// bit. + #[endpoint { + method = GET, + path = "/sp/{type}/{slot}/component/{component}/vpd", + versions = VERSION_COMPONENT_VPD.., + }] + async fn sp_component_vpd_get( + rqctx: RequestContext, + path: Path, + ) -> Result, HttpError>; + /// Read the CMPA from a root of trust. /// /// This endpoint is only valid for the `rot` component. diff --git a/gateway-types/src/component_vpd.rs b/gateway-types/src/component_vpd.rs new file mode 100644 index 00000000000..1a9748b36b1 --- /dev/null +++ b/gateway-types/src/component_vpd.rs @@ -0,0 +1,5 @@ +// 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/. + +pub use gateway_types_versions::latest::component_vpd::*; diff --git a/gateway-types/src/lib.rs b/gateway-types/src/lib.rs index 2ea2c61f92c..f45e064b34d 100644 --- a/gateway-types/src/lib.rs +++ b/gateway-types/src/lib.rs @@ -14,6 +14,7 @@ pub mod caboose; pub mod component; pub mod component_details; +pub mod component_vpd; pub mod host; pub mod ignition; pub mod rot; diff --git a/gateway-types/versions/src/component_vpd/component_vpd.rs b/gateway-types/versions/src/component_vpd/component_vpd.rs new file mode 100644 index 00000000000..5d153fce87a --- /dev/null +++ b/gateway-types/versions/src/component_vpd/component_vpd.rs @@ -0,0 +1,152 @@ +// 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/. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +#[serde(tag = "type", rename_all = "snake_case")] +#[allow(clippy::large_enum_variant)] +pub enum ComponentVpd { + OxideBarcode(OxideBarcode), + Mpn1Barcode(Mpn1Barcode), + SledFanTray(SledFanTray), + Tmp11x(Tmp11x), + Pmbus(PmbusDevice), +} + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +pub struct OxideBarcode { + pub part_number: String, + pub revision: u32, + pub serial_number: String, +} + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +pub struct Mpn1Barcode { + pub manufacturer: String, + pub part_number: String, + pub revision: String, + pub serial_number: String, +} + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum Barcode { + Oxide(OxideBarcode), + Mpn1(Mpn1Barcode), +} + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +pub struct SledFanTray { + pub identity: OxideBarcode, + pub vpd_board_identity: OxideBarcode, + pub fan0: Barcode, + pub fan1: Barcode, + pub fan2: Barcode, +} + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +pub struct Tmp11x { + pub device_id: u16, + pub eeprom1: u16, + pub eeprom2: u16, + pub eeprom3: u16, +} + +/// PMBus vital product data (VPD) read from a PMBus device. +/// +/// If the device does not support a particular VPD command, the field in this +/// struct corresponding to that command will be `None`. Otherwise, the value +/// contains the exact bytes returned by the device, including an empty value or +/// any NUL bytes returned by the device. +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +pub struct PmbusDevice { + /// `MFR_ID` (PMBus command 0x99). + pub mfr_id: Option>, + /// `MFR_MODEL` (PMBus command 0x9A). + pub mfr_model: Option>, + /// `MFR_REVISION` (PMBus command 0x9B). + pub mfr_revision: Option>, + /// `MFR_LOCATION` (PMBus command 0x9C). + pub mfr_location: Option>, + /// `MFR_DATE` (PMBus command 0x9D). + pub mfr_date: Option>, + /// `MFR_SERIAL` (PMBus command 0x9E). + pub mfr_serial: Option>, + /// `IC_DEVICE_ID` (PMBus command 0xAD). + pub ic_device_id: Option>, + /// `IC_DEVICE_REV` (PMBus command 0xAE). + pub ic_device_rev: Option>, +} diff --git a/gateway-types/versions/src/component_vpd/mod.rs b/gateway-types/versions/src/component_vpd/mod.rs new file mode 100644 index 00000000000..2348385d614 --- /dev/null +++ b/gateway-types/versions/src/component_vpd/mod.rs @@ -0,0 +1,9 @@ +// 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/. + +//! Version `COMPONENT_VPD` of the Gateway API. +//! +//! This version adds APIs for reading vital product data from an SP component. + +pub mod component_vpd; diff --git a/gateway-types/versions/src/impls/component_vpd.rs b/gateway-types/versions/src/impls/component_vpd.rs new file mode 100644 index 00000000000..21cf382a668 --- /dev/null +++ b/gateway-types/versions/src/impls/component_vpd.rs @@ -0,0 +1,546 @@ +// 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/. + +use crate::latest::component_vpd::{ + Barcode, ComponentVpd, Mpn1Barcode, OxideBarcode, PmbusDevice, SledFanTray, + Tmp11x, +}; +use gateway_messages::vpd as gw; +use std::fmt; +use std::num::ParseIntError; +use std::str::FromStr; + +#[derive(thiserror::Error, Debug, Eq, PartialEq)] +pub enum ParseBarcodeError { + #[error("barcode received from SP was not UTF-8")] + // TODO(eliza): t'would be nice to format the invalid bytes in a nice-ish + // way so you can see what the garbled message was... + NotUtf8, + #[error("missing barcode version (expected {expected})")] + MissingVersion { expected: &'static str }, + #[error("missing part number")] + MissingPartNumber, + #[error("missing revision")] + MissingRevision, + #[error("missing serial number")] + MissingSerialNumber, + #[error("missing MPN1 manufacturer string")] + MissingManufacturer, + #[error("unexpected fields")] + UnexpectedFields, + #[error("unknown barcode version {version:?} (expected {expected})")] + UnknownVersion { version: String, expected: &'static str }, + #[error("invalid Oxide barcode revision {revision}")] + BadRevision { + revision: String, + #[source] + error: ParseIntError, + }, +} + +#[derive(thiserror::Error, Debug)] +pub enum InvalidComponentVpd { + #[error(transparent)] + Barcode(#[from] ParseBarcodeError), + #[error(transparent)] + SledFanTray(#[from] InvalidAssemblyBarcode), +} + +#[derive(thiserror::Error, Debug)] +#[error("invalid fan assembly {which_barcode} barcode")] +pub struct InvalidAssemblyBarcode { + which_barcode: &'static str, + #[source] + error: ParseBarcodeError, +} + +impl InvalidAssemblyBarcode { + fn mk(which_barcode: &'static str) -> impl Fn(ParseBarcodeError) -> Self { + move |error| Self { which_barcode, error } + } +} + +impl Barcode { + const EXPECTED_VERSION: &str = + "one of '0XV1', 'OXV1', '0XV2', 'OXV2', or 'MPN1'"; + + /// Borrows the manufacturer string for this barcode, if this is an `MPN1` + /// barcode. Otherwise, if this is an Oxide-issued barcode, this returns + /// `None`. + pub fn manufacturer(&self) -> Option<&str> { + match self { + Self::Mpn1(Mpn1Barcode { manufacturer, .. }) => { + Some(manufacturer.as_str()) + } + _ => None, + } + } + + /// Borrows the serial number portion of this barcode. + pub fn serial_number(&self) -> &str { + match self { + Self::Mpn1(Mpn1Barcode { serial_number, .. }) => { + serial_number.as_str() + } + Self::Oxide(OxideBarcode { serial_number, .. }) => { + serial_number.as_str() + } + } + } + + /// Borrows the part number portion of this barcode. + pub fn part_number(&self) -> &str { + match self { + Self::Mpn1(Mpn1Barcode { part_number, .. }) => part_number.as_str(), + Self::Oxide(OxideBarcode { part_number, .. }) => { + part_number.as_str() + } + } + } +} + +impl TryFrom for Barcode { + type Error = ParseBarcodeError; + fn try_from(value: gw::Barcode) -> Result { + value.as_str().ok_or(ParseBarcodeError::NotUtf8)?.parse() + } +} + +impl FromStr for Barcode { + type Err = ParseBarcodeError; + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let version = + parts.next().ok_or(ParseBarcodeError::MissingVersion { + expected: Self::EXPECTED_VERSION, + })?; + match version { + "0XV1" | "OXV1" | "0XV2" | "OXV2" => { + OxideBarcode::from_parts(version, parts).map(Self::Oxide) + } + Mpn1Barcode::MPN1 => Mpn1Barcode::from_parts(parts).map(Self::Mpn1), + version => Err(ParseBarcodeError::UnknownVersion { + version: version.to_string(), + expected: Self::EXPECTED_VERSION, + }), + } + } +} + +impl fmt::Display for Barcode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Oxide(oxide) => oxide.fmt(f), + Self::Mpn1(mpn1) => mpn1.fmt(f), + } + } +} + +impl TryFrom for OxideBarcode { + type Error = ParseBarcodeError; + fn try_from(value: gw::Barcode) -> Result { + value.as_str().ok_or(ParseBarcodeError::NotUtf8)?.parse() + } +} + +impl FromStr for OxideBarcode { + type Err = ParseBarcodeError; + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let version = + parts.next().ok_or(ParseBarcodeError::MissingVersion { + expected: Self::EXPECTED_VERSION, + })?; + Self::from_parts(version, parts) + } +} + +impl OxideBarcode { + const EXPECTED_VERSION: &str = "one of '0XV1', 'OXV1', '0XV2', or 'OXV2'"; + + fn from_parts<'parts>( + version: &'parts str, + mut parts: impl Iterator + 'parts, + ) -> Result { + let part_number = + parts.next().ok_or(ParseBarcodeError::MissingPartNumber)?; + let revision = + parts.next().ok_or(ParseBarcodeError::MissingRevision)?; + let serial_number = + parts.next().ok_or(ParseBarcodeError::MissingSerialNumber)?; + if parts.next().is_some() { + return Err(ParseBarcodeError::UnexpectedFields); + } + let part_number = match version { + "0XV1" | "OXV1" => { + // V1 does not include the hyphen after the first three digits + // of the part number, so we need to insert it. + let pn_chars = part_number.chars(); + let mut part_number = String::with_capacity(11); + for (i, ch) in pn_chars.enumerate() { + if i == 3 && ch != '-' { + part_number.push('-'); + } + part_number.push(ch); + } + part_number + } + "0XV2" | "OXV2" => part_number.to_string(), + _ => { + return Err(ParseBarcodeError::UnknownVersion { + version: version.to_string(), + expected: Self::EXPECTED_VERSION, + }); + } + }; + let revision = revision.parse().map_err(|error| { + ParseBarcodeError::BadRevision { + revision: revision.to_string(), + error, + } + })?; + + Ok(Self { + part_number, + revision, + serial_number: serial_number.to_owned(), + }) + } +} + +impl TryFrom for Mpn1Barcode { + type Error = ParseBarcodeError; + fn try_from(value: gw::Barcode) -> Result { + value.as_str().ok_or(ParseBarcodeError::NotUtf8)?.parse() + } +} + +impl FromStr for Mpn1Barcode { + type Err = ParseBarcodeError; + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let version = + parts.next().ok_or(ParseBarcodeError::MissingVersion { + expected: Self::MPN1, + })?; + if version != Self::MPN1 { + return Err(ParseBarcodeError::UnknownVersion { + version: version.to_string(), + expected: Self::MPN1, + }); + } + Self::from_parts(parts) + } +} + +impl fmt::Display for OxideBarcode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { part_number, revision, serial_number } = self; + write!(f, "0XV2:{part_number}:{revision:03}:{serial_number}") + } +} + +impl Mpn1Barcode { + const MPN1: &str = "MPN1"; + + fn from_parts<'parts>( + mut parts: impl Iterator + 'parts, + ) -> Result { + let manufacturer = + parts.next().ok_or(ParseBarcodeError::MissingManufacturer)?; + let part_number = + parts.next().ok_or(ParseBarcodeError::MissingPartNumber)?; + let revision = + parts.next().ok_or(ParseBarcodeError::MissingRevision)?; + let serial_number = + parts.next().ok_or(ParseBarcodeError::MissingSerialNumber)?; + if parts.next().is_some() { + return Err(ParseBarcodeError::UnexpectedFields); + } + + Ok(Self { + manufacturer: manufacturer.to_owned(), + part_number: part_number.to_owned(), + revision: revision.to_owned(), + serial_number: serial_number.to_owned(), + }) + } +} + +impl fmt::Display for Mpn1Barcode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { manufacturer, part_number, revision, serial_number } = self; + write!( + f, + "{}:{manufacturer}:{part_number}:{revision:}:{serial_number}", + Self::MPN1 + ) + } +} + +impl TryFrom for ComponentVpd { + type Error = InvalidComponentVpd; + + fn try_from(value: gw::Vpd) -> Result { + match value { + gw::Vpd::Pmbus(vpd) => Ok(Self::Pmbus(vpd.into())), + gw::Vpd::Barcode(barcode) => Ok(match barcode.try_into()? { + Barcode::Oxide(barcode) => Self::OxideBarcode(barcode), + Barcode::Mpn1(barcode) => Self::Mpn1Barcode(barcode), + }), + gw::Vpd::SledFanTray(vpd) => Ok(Self::SledFanTray(vpd.try_into()?)), + gw::Vpd::Tmp11x(vpd) => Ok(Self::Tmp11x(vpd.into())), + } + } +} + +impl From for PmbusDevice { + fn from(value: gw::PmbusVpd) -> Self { + let gw::PmbusVpd { + mfr_id, + mfr_model, + mfr_revision, + mfr_location, + mfr_date, + mfr_serial, + ic_device_id, + ic_device_rev, + } = value; + Self { + mfr_id: mfr_id.as_bytes().map(<[u8]>::to_vec), + mfr_model: mfr_model.as_bytes().map(<[u8]>::to_vec), + mfr_revision: mfr_revision.as_bytes().map(<[u8]>::to_vec), + mfr_location: mfr_location.as_bytes().map(<[u8]>::to_vec), + mfr_date: mfr_date.as_bytes().map(<[u8]>::to_vec), + mfr_serial: mfr_serial.as_bytes().map(<[u8]>::to_vec), + ic_device_id: ic_device_id.as_bytes().map(<[u8]>::to_vec), + ic_device_rev: ic_device_rev.as_bytes().map(<[u8]>::to_vec), + } + } +} + +impl From for Tmp11x { + fn from(value: gw::Tmp11xVpd) -> Self { + let gw::Tmp11xVpd { id, eeprom1, eeprom2, eeprom3 } = value; + Self { device_id: id, eeprom1, eeprom2, eeprom3 } + } +} + +impl TryFrom for SledFanTray { + type Error = InvalidAssemblyBarcode; + fn try_from(value: gw::SledFanTrayVpd) -> Result { + let gw::SledFanTrayVpd { + identity, + vpd_board_identity, + fans: [fan0, fan1, fan2], + } = value; + Ok(Self { + identity: identity + .try_into() + .map_err(InvalidAssemblyBarcode::mk("fan tray"))?, + vpd_board_identity: vpd_board_identity + .try_into() + .map_err(InvalidAssemblyBarcode::mk("VPD board"))?, + fan0: fan0 + .try_into() + .map_err(InvalidAssemblyBarcode::mk("fan 0"))?, + fan1: fan1 + .try_into() + .map_err(InvalidAssemblyBarcode::mk("fan 1"))?, + fan2: fan2 + .try_into() + .map_err(InvalidAssemblyBarcode::mk("fan 2"))?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Most of the tests for barcode parsing in this module are lifted from + // the ones in Hubris' `oxide-barcode` crate. + // https://github.com/oxidecomputer/hubris/blob/0d1ba0453a5d80470f07ea9949eb82bc3c291e01/lib/oxide-barcode/src/lib.rs#L363-L614 + + #[track_caller] + fn check_parse_oxide(input: &str, expected: OxideBarcode) { + let parsed = dbg!(input).parse::(); + dbg!(&parsed); + + assert_eq!( + parsed.as_ref(), + Ok(dbg!(&expected)), + "parsing Oxide barcodestring: {input}" + ); + + let formatted = parsed.as_ref().unwrap().to_string(); + assert_eq!( + parsed, + dbg!(formatted).parse::(), + "parsed Oxide barcode for string {input} should round-trip through \ + `fmt::Display`", + ); + + // We accept barcode strings that start with both leading zero and + // leading capital-O. Permute our input from one of these to the other + // to make sure both forms parse equivalently. + let mut copy = input.to_owned(); + match copy.as_bytes()[0] { + b'0' => copy.replace_range(0..1, "O"), + b'O' => copy.replace_range(0..1, "0"), + c => panic!("unexpected leading character: {}", c as char), + } + + let parsed = dbg!(©).parse::(); + + assert_eq!( + dbg!(&parsed).as_ref(), + Ok(&expected), + "parsing Oxide barcode string: {copy}" + ); + + let formatted = parsed.as_ref().unwrap().to_string(); + assert_eq!( + parsed, + dbg!(formatted).parse::(), + "parsed Oxide barcode for string {copy} should round-trip through \ + `fmt::Display`", + ); + } + + #[test] + fn parse_oxv1() { + check_parse_oxide( + "0XV1:1230000456:023:TST01234567", + OxideBarcode { + part_number: "123-0000456".to_owned(), + revision: 23, + serial_number: "TST01234567".to_owned(), + }, + ); + } + + #[test] + fn parse_oxv2() { + check_parse_oxide( + "0XV2:123-0000456:023:TST01234567", + OxideBarcode { + part_number: "123-0000456".to_owned(), + revision: 23, + serial_number: "TST01234567".to_owned(), + }, + ); + } + + #[test] + fn parse_oxv2_shorter_serial() { + check_parse_oxide( + "0XV2:123-0000456:023:TST0123456", + OxideBarcode { + part_number: "123-0000456".to_owned(), + revision: 23, + serial_number: "TST0123456".to_owned(), + }, + ); + } + + #[test] + fn parse_oxv2_shorter_part() { + check_parse_oxide( + "0XV2:123-000045:023:TST01234567", + OxideBarcode { + part_number: "123-000045".to_owned(), + revision: 23, + serial_number: "TST01234567".to_owned(), + }, + ); + } + + #[track_caller] + fn check_parse_mpn1(input: &str, expected: Mpn1Barcode) { + let parsed = dbg!(input).parse::(); + dbg!(&parsed); + + assert_eq!( + parsed.as_ref(), + Ok(dbg!(&expected)), + "parsing MPN1 identity {input}" + ); + let formatted = parsed.as_ref().unwrap().to_string(); + assert_eq!( + dbg!(formatted).parse::().as_ref(), + Ok(&expected), + "parsed MPN1 barcode {input} should round-trip through \ + `fmt::Display`", + ); + } + + #[test] + fn parse_mpn1() { + check_parse_mpn1( + "MPN1:ABC:ASDF-1000:032:123456789", + Mpn1Barcode { + manufacturer: "ABC".to_owned(), + part_number: "ASDF-1000".to_owned(), + revision: "032".to_owned(), + serial_number: "123456789".to_owned(), + }, + ); + } + + #[test] + fn parse_mpn1_empty() { + check_parse_mpn1( + "MPN1::::", + Mpn1Barcode { + manufacturer: String::new(), + part_number: String::new(), + revision: String::new(), + serial_number: String::new(), + }, + ); + } + + #[test] + fn parse_mpn1_no_mpn_rev() { + check_parse_mpn1( + "MPN1:XYZ:::12345ABCD", + Mpn1Barcode { + manufacturer: "XYZ".to_owned(), + part_number: String::new(), + revision: String::new(), + serial_number: "12345ABCD".to_owned(), + }, + ); + } + + #[test] + fn parse_mpn1_no_serial() { + check_parse_mpn1( + "MPN1:XYZ:1234ABC:420:", + Mpn1Barcode { + manufacturer: "XYZ".to_owned(), + part_number: "1234ABC".to_owned(), + revision: "420".to_owned(), + serial_number: String::new(), + }, + ); + } + + #[test] + fn parse_mpn1_from_andy() { + check_parse_mpn1( + "MPN1:SYD:9CRA0848P8G012:C:WWYY1SSS", + Mpn1Barcode { + manufacturer: "SYD".to_owned(), + part_number: "9CRA0848P8G012".to_owned(), + revision: "C".to_owned(), + serial_number: "WWYY1SSS".to_owned(), + }, + ); + } +} diff --git a/gateway-types/versions/src/impls/mod.rs b/gateway-types/versions/src/impls/mod.rs index aa0fab64d3c..c274ba204ff 100644 --- a/gateway-types/versions/src/impls/mod.rs +++ b/gateway-types/versions/src/impls/mod.rs @@ -9,6 +9,7 @@ mod component; mod component_details; +pub(crate) mod component_vpd; mod host; mod ignition; mod rot; diff --git a/gateway-types/versions/src/latest.rs b/gateway-types/versions/src/latest.rs index 4c102e7fd22..8aa6e1f2428 100644 --- a/gateway-types/versions/src/latest.rs +++ b/gateway-types/versions/src/latest.rs @@ -46,6 +46,22 @@ pub mod component_details { pub use crate::v1::component_details::UnsupportedComponentDetails; } +pub mod component_vpd { + pub use crate::v4::component_vpd::Barcode; + pub use crate::v4::component_vpd::ComponentVpd; + pub use crate::v4::component_vpd::Mpn1Barcode; + pub use crate::v4::component_vpd::OxideBarcode; + pub use crate::v4::component_vpd::PmbusDevice; + pub use crate::v4::component_vpd::SledFanTray; + pub use crate::v4::component_vpd::Tmp11x; + + // These error types are not part of the actual API, and are therefore not + // versioned. They are returned by `TryFrom` and similar. + pub use crate::impls::component_vpd::InvalidAssemblyBarcode; + pub use crate::impls::component_vpd::InvalidComponentVpd; + pub use crate::impls::component_vpd::ParseBarcodeError; +} + pub mod host { pub use crate::v1::host::ComponentFirmwareHashStatus; pub use crate::v1::host::HostStartupOptions; diff --git a/gateway-types/versions/src/lib.rs b/gateway-types/versions/src/lib.rs index 2909130f1b3..ef36c03d856 100644 --- a/gateway-types/versions/src/lib.rs +++ b/gateway-types/versions/src/lib.rs @@ -35,3 +35,5 @@ pub mod latest; pub mod v1; #[path = "cosmo/mod.rs"] pub mod v2; +#[path = "component_vpd/mod.rs"] +pub mod v4; diff --git a/gateway/src/error.rs b/gateway/src/error.rs index a78f3599129..dbf45b0a1df 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -5,8 +5,10 @@ //! Error handling facilities for the management gateway. use crate::management_switch::SpIdentifier; +use dropshot::ErrorStatusCode; use dropshot::HttpError; use gateway_messages::SpError; +use gateway_messages::VpdError; use gateway_sp_comms::BindError; pub use gateway_sp_comms::error::CommunicationError; use gateway_sp_comms::error::UpdateError; @@ -186,6 +188,9 @@ fn comms_error_to_http( InlineErrorChain::new(&error).to_string(), ) } + CommunicationError::SpError(SpError::Vpd(vpd_error)) => { + vpd_error_to_http(error, vpd_error) + } _ => http_err_with_message( dropshot::ErrorStatusCode::SERVICE_UNAVAILABLE, "SpCommunicationFailed", @@ -194,6 +199,63 @@ fn comms_error_to_http( } } +fn vpd_error_to_http(error: &dyn Error, kind: &VpdError) -> HttpError { + let (status_code, error_code) = match kind { + VpdError::InvalidDevice => { + (ErrorStatusCode::BAD_REQUEST, "InvalidVpdDevice") + } + VpdError::NotPresent => { + (ErrorStatusCode::SERVICE_UNAVAILABLE, "VpdDeviceNotPresent") + } + VpdError::DeviceError => { + (ErrorStatusCode::INTERNAL_SERVER_ERROR, "VpdDeviceError") + } + VpdError::Unavailable => { + (ErrorStatusCode::SERVICE_UNAVAILABLE, "VpdDeviceUnavailable") + } + VpdError::DeviceTimeout => { + (ErrorStatusCode::SERVICE_UNAVAILABLE, "VpdDeviceTimeout") + } + VpdError::DeviceOff => { + (ErrorStatusCode::SERVICE_UNAVAILABLE, "VpdDeviceOff") + } + VpdError::BadAddress => { + (ErrorStatusCode::INTERNAL_SERVER_ERROR, "InvalidVpdAddress") + } + VpdError::BadBuffer => { + (ErrorStatusCode::INTERNAL_SERVER_ERROR, "InvalidVpdBuffer") + } + VpdError::BadRead => { + (ErrorStatusCode::INTERNAL_SERVER_ERROR, "VpdReadFailed") + } + VpdError::BadWrite => { + (ErrorStatusCode::INTERNAL_SERVER_ERROR, "VpdWriteFailed") + } + VpdError::BadLock => { + (ErrorStatusCode::INTERNAL_SERVER_ERROR, "VpdLockFailed") + } + VpdError::NotImplemented => { + (ErrorStatusCode::NOT_IMPLEMENTED, "VpdNotImplemented") + } + VpdError::IsLocked => (ErrorStatusCode::CONFLICT, "VpdLocked"), + VpdError::PartiallyLocked => { + (ErrorStatusCode::CONFLICT, "VpdPartiallyLocked") + } + VpdError::AlreadyLocked => { + (ErrorStatusCode::CONFLICT, "VpdAlreadyLocked") + } + VpdError::TaskRestarted => { + (ErrorStatusCode::SERVICE_UNAVAILABLE, "VpdTaskRestarted") + } + }; + + http_err_with_message( + status_code, + error_code, + InlineErrorChain::new(&error).to_string(), + ) +} + impl From for HttpError { fn from(error: SpLookupError) -> Self { match error { diff --git a/gateway/src/http_entrypoints.rs b/gateway/src/http_entrypoints.rs index da748f721be..a29e8a6625f 100644 --- a/gateway/src/http_entrypoints.rs +++ b/gateway/src/http_entrypoints.rs @@ -41,6 +41,7 @@ use gateway_types::component::SpComponentList; use gateway_types::component::SpIdentifier; use gateway_types::component::SpState; use gateway_types::component_details::SpComponentDetails; +use gateway_types::component_vpd::ComponentVpd; use gateway_types::host::ComponentFirmwareHashStatus; use gateway_types::host::HostStartupOptions; use gateway_types::ignition::PathSpIgnitionCommand; @@ -707,6 +708,33 @@ impl GatewayApi for GatewayImpl { apictx.latencies.instrument_dropshot_handler(&rqctx, handler).await } + async fn sp_component_vpd_get( + rqctx: RequestContext, + path: Path, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + + let PathSpComponent { sp, component } = path.into_inner(); + let sp_id = sp.into(); + let handler = async { + let sp = apictx.mgmt_switch.sp(sp_id)?; + let component = component_from_str(&component)?; + let vpd = sp.component_vpd(component).await.map_err(|err| { + SpCommsError::SpCommunicationFailed { sp: sp_id, err } + })?; + let vpd = vpd.try_into().map_err(|err| { + http_err_with_message( + dropshot::ErrorStatusCode::INTERNAL_SERVER_ERROR, + "InvalidComponentVpd", + format!("invalid VPD returned by SP: {err}"), + ) + })?; + + Ok(HttpResponseOk(vpd)) + }; + apictx.latencies.instrument_dropshot_handler(&rqctx, handler).await + } + async fn sp_rot_cmpa_get( rqctx: RequestContext, path: Path, diff --git a/gateway/tests/integration_tests/serial_console.rs b/gateway/tests/integration_tests/serial_console.rs index bea06137ca6..e7bee29359c 100644 --- a/gateway/tests/integration_tests/serial_console.rs +++ b/gateway/tests/integration_tests/serial_console.rs @@ -2,7 +2,6 @@ // 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 dropshot::HttpErrorResponseBody; use futures::prelude::*; use gateway_messages::SpPort; use gateway_test_utils::current_simulator_state; @@ -90,12 +89,11 @@ async fn serial_console_detach() { .sp_component_serial_console_attach(&SpType::Sled, 0, "sp3-host-cpu") .await .unwrap_err(); - let gateway_client::Error::UnexpectedResponse(response) = err else { + let gateway_client::Error::ErrorResponse(response) = err else { panic!("unexpected error"); }; assert_eq!(response.status(), StatusCode::BAD_REQUEST); - let err: HttpErrorResponseBody = response.json().await.unwrap(); - assert!(err.message.contains("serial console already attached")); + assert!(response.message.contains("serial console already attached")); // the original websocket should still work ws.send(Message::Binary(b"hello".to_vec())).await.unwrap(); diff --git a/openapi/gateway/gateway-3.0.0-dacda2.json.gitstub b/openapi/gateway/gateway-3.0.0-dacda2.json.gitstub new file mode 100644 index 00000000000..330390555d8 --- /dev/null +++ b/openapi/gateway/gateway-3.0.0-dacda2.json.gitstub @@ -0,0 +1 @@ +4d5bdc6d348b27761348d763c4085f060bcefc18:openapi/gateway/gateway-3.0.0-dacda2.json diff --git a/openapi/gateway/gateway-3.0.0-dacda2.json b/openapi/gateway/gateway-4.0.0-428501.json similarity index 91% rename from openapi/gateway/gateway-3.0.0-dacda2.json rename to openapi/gateway/gateway-4.0.0-428501.json index 7ac0310a3cc..bdc049fc70d 100644 --- a/openapi/gateway/gateway-3.0.0-dacda2.json +++ b/openapi/gateway/gateway-4.0.0-428501.json @@ -7,7 +7,7 @@ "url": "https://oxide.computer", "email": "api@oxide.computer" }, - "version": "3.0.0" + "version": "4.0.0" }, "paths": { "/ignition": { @@ -982,13 +982,14 @@ } ], "responses": { - "default": { - "description": "", - "content": { - "*/*": { - "schema": {} - } - } + "101": { + "description": "Negotiating protocol upgrade from HTTP/1.1 to WebSocket" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" } }, "x-dropshot-websocket": {} @@ -1231,6 +1232,60 @@ } } }, + "/sp/{type}/{slot}/component/{component}/vpd": { + "get": { + "summary": "Get the vital product data (VPD) identity of a component.", + "description": "Not all components have VPD. This endpoint will return an error if the requested component does not advertise the `HAS_VPD` DeviceCapability bit.", + "operationId": "sp_component_vpd_get", + "parameters": [ + { + "in": "path", + "name": "component", + "description": "ID for the component of the SP; this is the internal identifier used by the SP itself to identify its components.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "slot", + "required": true, + "schema": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + { + "in": "path", + "name": "type", + "required": true, + "schema": { + "$ref": "#/components/schemas/SpType" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComponentVpd" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/sp/{type}/{slot}/ereports": { "post": { "summary": "Request ereports from the target service processor.", @@ -1818,6 +1873,68 @@ }, "components": { "schemas": { + "Barcode": { + "oneOf": [ + { + "type": "object", + "properties": { + "part_number": { + "type": "string" + }, + "revision": { + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "serial_number": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "oxide" + ] + } + }, + "required": [ + "part_number", + "revision", + "serial_number", + "type" + ] + }, + { + "type": "object", + "properties": { + "manufacturer": { + "type": "string" + }, + "part_number": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "serial_number": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "mpn1" + ] + } + }, + "required": [ + "manufacturer", + "part_number", + "revision", + "serial_number", + "type" + ] + } + ] + }, "ComponentFirmwareHashStatus": { "oneOf": [ { @@ -1878,6 +1995,235 @@ } ] }, + "ComponentVpd": { + "oneOf": [ + { + "type": "object", + "properties": { + "part_number": { + "type": "string" + }, + "revision": { + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "serial_number": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "oxide_barcode" + ] + } + }, + "required": [ + "part_number", + "revision", + "serial_number", + "type" + ] + }, + { + "type": "object", + "properties": { + "manufacturer": { + "type": "string" + }, + "part_number": { + "type": "string" + }, + "revision": { + "type": "string" + }, + "serial_number": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "mpn1_barcode" + ] + } + }, + "required": [ + "manufacturer", + "part_number", + "revision", + "serial_number", + "type" + ] + }, + { + "type": "object", + "properties": { + "fan0": { + "$ref": "#/components/schemas/Barcode" + }, + "fan1": { + "$ref": "#/components/schemas/Barcode" + }, + "fan2": { + "$ref": "#/components/schemas/Barcode" + }, + "identity": { + "$ref": "#/components/schemas/OxideBarcode" + }, + "type": { + "type": "string", + "enum": [ + "sled_fan_tray" + ] + }, + "vpd_board_identity": { + "$ref": "#/components/schemas/OxideBarcode" + } + }, + "required": [ + "fan0", + "fan1", + "fan2", + "identity", + "type", + "vpd_board_identity" + ] + }, + { + "type": "object", + "properties": { + "device_id": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "eeprom1": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "eeprom2": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "eeprom3": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "type": { + "type": "string", + "enum": [ + "tmp11x" + ] + } + }, + "required": [ + "device_id", + "eeprom1", + "eeprom2", + "eeprom3", + "type" + ] + }, + { + "description": "PMBus vital product data (VPD) read from a PMBus device.\n\nIf the device does not support a particular VPD command, the field in this struct corresponding to that command will be `None`. Otherwise, the value contains the exact bytes returned by the device, including an empty value or any NUL bytes returned by the device.", + "type": "object", + "properties": { + "ic_device_id": { + "nullable": true, + "description": "`IC_DEVICE_ID` (PMBus command 0xAD).", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "ic_device_rev": { + "nullable": true, + "description": "`IC_DEVICE_REV` (PMBus command 0xAE).", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "mfr_date": { + "nullable": true, + "description": "`MFR_DATE` (PMBus command 0x9D).", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "mfr_id": { + "nullable": true, + "description": "`MFR_ID` (PMBus command 0x99).", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "mfr_location": { + "nullable": true, + "description": "`MFR_LOCATION` (PMBus command 0x9C).", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "mfr_model": { + "nullable": true, + "description": "`MFR_MODEL` (PMBus command 0x9A).", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "mfr_revision": { + "nullable": true, + "description": "`MFR_REVISION` (PMBus command 0x9B).", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "mfr_serial": { + "nullable": true, + "description": "`MFR_SERIAL` (PMBus command 0x9E).", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "type": { + "type": "string", + "enum": [ + "pmbus" + ] + } + }, + "required": [ + "type" + ] + } + ] + }, "Duration": { "type": "object", "properties": { @@ -2405,6 +2751,27 @@ "type": "string", "format": "uuid" }, + "OxideBarcode": { + "type": "object", + "properties": { + "part_number": { + "type": "string" + }, + "revision": { + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "serial_number": { + "type": "string" + } + }, + "required": [ + "part_number", + "revision", + "serial_number" + ] + }, "PacketCount": { "type": "object", "properties": { diff --git a/openapi/gateway/gateway-latest.json b/openapi/gateway/gateway-latest.json index 7b2c936f827..a3deb05c24c 120000 --- a/openapi/gateway/gateway-latest.json +++ b/openapi/gateway/gateway-latest.json @@ -1 +1 @@ -gateway-3.0.0-dacda2.json \ No newline at end of file +gateway-4.0.0-428501.json \ No newline at end of file diff --git a/package-manifest.toml b/package-manifest.toml index af4657d6636..e7105c67db0 100644 --- a/package-manifest.toml +++ b/package-manifest.toml @@ -605,8 +605,8 @@ source.type = "prebuilt" source.repo = "management-gateway-service" # In general, this commit should match the pinned revision of `gateway-sp-comms` # in `Cargo.toml`. -source.commit = "01f06089a97bd084bec2595d4d62e0f91338cf57" -source.sha256 = "22f71d7877e4bcbaa36005a7abe2a2ca2f93f24127a7799161de6ffb071a0ac6" +source.commit = "438cd185666d5f164548885873f0f5df9a38eed1" +source.sha256 = "176fab5f1fdd892832d42f11fdef97734184d22400d7d76835c0d83981c25c4b" output.type = "zone" output.intermediate_only = true diff --git a/sp-sim/src/gimlet.rs b/sp-sim/src/gimlet.rs index fd115780632..c1634d8f168 100644 --- a/sp-sim/src/gimlet.rs +++ b/sp-sim/src/gimlet.rs @@ -1571,6 +1571,22 @@ impl SpHandler for Handler { self.update_state.get_component_caboose_value(component, slot, key, buf) } + fn component_get_vpd( + &mut self, + component: SpComponent, + _buf: &mut [u8], + ) -> Result { + // TODO(eliza): we should allow configuring a VPD response in the sim + // config file... + warn!( + &self.log, + "asked to read VPD for component, which the simulator doesn't + implement yet"; + "component" => ?component, + ); + Err(SpError::RequestUnsupportedForComponent) + } + fn read_sensor( &mut self, request: gateway_messages::SensorRequest, diff --git a/sp-sim/src/sidecar.rs b/sp-sim/src/sidecar.rs index 062565b66fa..152e50cf2dd 100644 --- a/sp-sim/src/sidecar.rs +++ b/sp-sim/src/sidecar.rs @@ -1173,6 +1173,22 @@ impl SpHandler for Handler { self.update_state.get_component_caboose_value(component, slot, key, buf) } + fn component_get_vpd( + &mut self, + component: SpComponent, + _buf: &mut [u8], + ) -> Result { + // TODO(eliza): we should allow configuring a VPD response in the sim + // config file... + warn!( + &self.log, + "asked to read VPD for component, which the simulator doesn't + implement yet"; + "component" => ?component, + ); + Err(SpError::RequestUnsupportedForComponent) + } + fn read_sensor( &mut self, request: gateway_messages::SensorRequest, diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 95b2cd01076..465bc02762e 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -66,7 +66,7 @@ futures-core = { version = "0.3.32" } futures-sink = { version = "0.3.32" } futures-task = { version = "0.3.32", default-features = false, features = ["std"] } futures-util = { version = "0.3.32", features = ["channel", "io", "sink"] } -gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "01f06089a97bd084bec2595d4d62e0f91338cf57", features = ["std"] } +gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "438cd185666d5f164548885873f0f5df9a38eed1", features = ["std"] } generic-array = { version = "0.14.7", default-features = false, features = ["more_lengths", "zeroize"] } getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2.17", default-features = false, features = ["js", "rdrand", "std"] } getrandom-9fbad63c4bcf4a8f = { package = "getrandom", version = "0.4.3", default-features = false, features = ["std", "sys_rng", "wasm_js"] } @@ -223,7 +223,7 @@ futures-core = { version = "0.3.32" } futures-sink = { version = "0.3.32" } futures-task = { version = "0.3.32", default-features = false, features = ["std"] } futures-util = { version = "0.3.32", features = ["channel", "io", "sink"] } -gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "01f06089a97bd084bec2595d4d62e0f91338cf57", features = ["std"] } +gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "438cd185666d5f164548885873f0f5df9a38eed1", features = ["std"] } generic-array = { version = "0.14.7", default-features = false, features = ["more_lengths", "zeroize"] } getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2.17", default-features = false, features = ["js", "rdrand", "std"] } getrandom-9fbad63c4bcf4a8f = { package = "getrandom", version = "0.4.3", default-features = false, features = ["std", "sys_rng", "wasm_js"] } @@ -339,6 +339,7 @@ zip-181b0d20215bb231 = { package = "zip", version = "8.6.0", default-features = cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } dof-468e82937335b1c9 = { package = "dof", version = "0.3.0", default-features = false, features = ["des"] } dof-9fbad63c4bcf4a8f = { package = "dof", version = "0.4.0", default-features = false, features = ["des"] } +getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.4", default-features = false, features = ["std"] } hyper-rustls = { version = "0.27.7", features = ["http2"] } hyper-util = { version = "0.1.20", features = ["full"] } linux-raw-sys = { version = "0.4.15", default-features = false, features = ["elf", "errno", "general", "ioctl", "no_std", "system"] } @@ -354,6 +355,7 @@ tokio-rustls = { version = "0.26.4", default-features = false, features = ["aws- cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } dof-468e82937335b1c9 = { package = "dof", version = "0.3.0", default-features = false, features = ["des"] } dof-9fbad63c4bcf4a8f = { package = "dof", version = "0.4.0", default-features = false, features = ["des"] } +getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.4", default-features = false, features = ["std"] } hyper-rustls = { version = "0.27.7", features = ["http2"] } hyper-util = { version = "0.1.20", features = ["full"] } linux-raw-sys = { version = "0.4.15", default-features = false, features = ["elf", "errno", "general", "ioctl", "no_std", "system"] } @@ -368,6 +370,7 @@ tokio-rustls = { version = "0.26.4", default-features = false, features = ["aws- [target.x86_64-apple-darwin.dependencies] cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } errno = { version = "0.3.14" } +getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.4", default-features = false, features = ["std"] } hyper-rustls = { version = "0.27.7", features = ["http2"] } hyper-util = { version = "0.1.20", features = ["full"] } miniz_oxide = { version = "0.8.9", default-features = false, features = ["simd", "with-alloc"] } @@ -381,6 +384,7 @@ tokio-rustls = { version = "0.26.4", default-features = false, features = ["aws- [target.x86_64-apple-darwin.build-dependencies] cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } errno = { version = "0.3.14" } +getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.4", default-features = false, features = ["std"] } hyper-rustls = { version = "0.27.7", features = ["http2"] } hyper-util = { version = "0.1.20", features = ["full"] } miniz_oxide = { version = "0.8.9", default-features = false, features = ["simd", "with-alloc"] } @@ -394,6 +398,7 @@ tokio-rustls = { version = "0.26.4", default-features = false, features = ["aws- [target.aarch64-apple-darwin.dependencies] cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } errno = { version = "0.3.14" } +getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.4", default-features = false, features = ["std"] } hyper-rustls = { version = "0.27.7", features = ["http2"] } hyper-util = { version = "0.1.20", features = ["full"] } miniz_oxide = { version = "0.8.9", default-features = false, features = ["simd", "with-alloc"] } @@ -407,6 +412,7 @@ tokio-rustls = { version = "0.26.4", default-features = false, features = ["aws- [target.aarch64-apple-darwin.build-dependencies] cookie = { version = "0.18.1", default-features = false, features = ["percent-encode"] } errno = { version = "0.3.14" } +getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.4", default-features = false, features = ["std"] } hyper-rustls = { version = "0.27.7", features = ["http2"] } hyper-util = { version = "0.1.20", features = ["full"] } miniz_oxide = { version = "0.8.9", default-features = false, features = ["simd", "with-alloc"] } @@ -422,6 +428,7 @@ cookie = { version = "0.18.1", default-features = false, features = ["percent-en dof-468e82937335b1c9 = { package = "dof", version = "0.3.0", default-features = false, features = ["des"] } dof-9fbad63c4bcf4a8f = { package = "dof", version = "0.4.0", default-features = false, features = ["des"] } errno = { version = "0.3.14" } +getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.4", default-features = false, features = ["std"] } hyper-rustls = { version = "0.27.7", features = ["http2"] } hyper-util = { version = "0.1.20", features = ["full"] } miniz_oxide = { version = "0.8.9", default-features = false, features = ["simd", "with-alloc"] } @@ -439,6 +446,7 @@ cookie = { version = "0.18.1", default-features = false, features = ["percent-en dof-468e82937335b1c9 = { package = "dof", version = "0.3.0", default-features = false, features = ["des"] } dof-9fbad63c4bcf4a8f = { package = "dof", version = "0.4.0", default-features = false, features = ["des"] } errno = { version = "0.3.14" } +getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3.4", default-features = false, features = ["std"] } hyper-rustls = { version = "0.27.7", features = ["http2"] } hyper-util = { version = "0.1.20", features = ["full"] } miniz_oxide = { version = "0.8.9", default-features = false, features = ["simd", "with-alloc"] }