From 1c727799670bceffa750cbc9388c893f4e05eff6 Mon Sep 17 00:00:00 2001 From: Rain Date: Sun, 7 Dec 2025 21:28:22 +0000 Subject: [PATCH 1/2] [spr] initial version Created using spr 1.3.6-beta.1 --- Cargo.lock | 22 + Cargo.toml | 3 + gateway-api/Cargo.toml | 1 + gateway-api/src/lib.rs | 155 ++---- gateway-types/Cargo.toml | 3 +- gateway-types/migrations/Cargo.toml | 30 ++ gateway-types/migrations/src/lib.rs | 12 + gateway-types/migrations/src/v1/caboose.rs | 17 + gateway-types/migrations/src/v1/component.rs | 316 ++++++++++++ .../migrations/src/v1/component_details.rs | 445 +++++++++++++++++ gateway-types/migrations/src/v1/host.rs | 73 +++ .../v1.rs => migrations/src/v1/ignition.rs} | 29 +- gateway-types/migrations/src/v1/mod.rs | 16 + gateway-types/migrations/src/v1/params.rs | 127 +++++ gateway-types/migrations/src/v1/rot.rs | 403 +++++++++++++++ gateway-types/migrations/src/v1/sensor.rs | 74 +++ gateway-types/migrations/src/v1/task_dump.rs | 34 ++ gateway-types/migrations/src/v1/update.rs | 171 +++++++ .../v2.rs => migrations/src/v2/ignition.rs} | 12 +- gateway-types/migrations/src/v2/mod.rs | 9 + gateway-types/src/caboose.rs | 14 +- gateway-types/src/component.rs | 320 +----------- gateway-types/src/component_details.rs | 460 +----------------- gateway-types/src/host.rs | 71 +-- gateway-types/src/ignition.rs | 8 + gateway-types/src/ignition/mod.rs | 38 -- gateway-types/src/lib.rs | 7 + gateway-types/src/rot.rs | 407 +--------------- gateway-types/src/sensor.rs | 72 +-- gateway-types/src/task_dump.rs | 31 +- gateway-types/src/update.rs | 173 +------ gateway/Cargo.toml | 1 + gateway/src/http_entrypoints.rs | 36 +- 33 files changed, 1904 insertions(+), 1686 deletions(-) create mode 100644 gateway-types/migrations/Cargo.toml create mode 100644 gateway-types/migrations/src/lib.rs create mode 100644 gateway-types/migrations/src/v1/caboose.rs create mode 100644 gateway-types/migrations/src/v1/component.rs create mode 100644 gateway-types/migrations/src/v1/component_details.rs create mode 100644 gateway-types/migrations/src/v1/host.rs rename gateway-types/{src/ignition/v1.rs => migrations/src/v1/ignition.rs} (79%) create mode 100644 gateway-types/migrations/src/v1/mod.rs create mode 100644 gateway-types/migrations/src/v1/params.rs create mode 100644 gateway-types/migrations/src/v1/rot.rs create mode 100644 gateway-types/migrations/src/v1/sensor.rs create mode 100644 gateway-types/migrations/src/v1/task_dump.rs create mode 100644 gateway-types/migrations/src/v1/update.rs rename gateway-types/{src/ignition/v2.rs => migrations/src/v2/ignition.rs} (92%) create mode 100644 gateway-types/migrations/src/v2/mod.rs create mode 100644 gateway-types/src/ignition.rs delete mode 100644 gateway-types/src/ignition/mod.rs diff --git a/Cargo.lock b/Cargo.lock index af4763cdfc0..e4765823420 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3828,6 +3828,7 @@ dependencies = [ "dropshot-api-manager-types", "ereport-types", "gateway-types", + "gateway-types-migrations", "omicron-common", "omicron-uuid-kinds", "omicron-workspace-hack", @@ -3979,6 +3980,7 @@ dependencies = [ "daft", "dropshot", "gateway-messages", + "gateway-types-migrations", "hex", "omicron-common", "omicron-uuid-kinds", @@ -3992,6 +3994,25 @@ dependencies = [ "uuid", ] +[[package]] +name = "gateway-types-migrations" +version = "0.1.0" +dependencies = [ + "daft", + "dropshot", + "gateway-messages", + "hex", + "omicron-uuid-kinds", + "omicron-workspace-hack", + "proptest", + "schemars 0.8.22", + "serde", + "test-strategy", + "thiserror 2.0.17", + "tufaceous-artifact", + "uuid", +] + [[package]] name = "generator" version = "0.8.4" @@ -8153,6 +8174,7 @@ dependencies = [ "gateway-sp-comms", "gateway-test-utils", "gateway-types", + "gateway-types-migrations", "hex", "http", "hyper", diff --git a/Cargo.toml b/Cargo.toml index 4c38098a7b6..8071dd46222 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ members = [ "gateway-cli", "gateway-test-utils", "gateway-types", + "gateway-types/migrations", "illumos-utils", "installinator-api", "installinator-common", @@ -222,6 +223,7 @@ default-members = [ "gateway-cli", "gateway-test-utils", "gateway-types", + "gateway-types/migrations", "illumos-utils", "installinator-api", "installinator-common", @@ -472,6 +474,7 @@ gateway-messages = { git = "https://github.com/oxidecomputer/management-gateway- gateway-sp-comms = { git = "https://github.com/oxidecomputer/management-gateway-service", rev = "ea2f39ccdea124b5affcad0ca17bc5dacf65823a" } gateway-test-utils = { path = "gateway-test-utils" } gateway-types = { path = "gateway-types" } +gateway-types-migrations = { path = "gateway-types/migrations" } gethostname = "0.5.0" gfss = { path = "trust-quorum/gfss" } trust-quorum = { path = "trust-quorum" } diff --git a/gateway-api/Cargo.toml b/gateway-api/Cargo.toml index 4011cf6ed0d..0f876ab69c9 100644 --- a/gateway-api/Cargo.toml +++ b/gateway-api/Cargo.toml @@ -12,6 +12,7 @@ dropshot.workspace = true dropshot-api-manager-types.workspace = true ereport-types.workspace = true gateway-types.workspace = true +gateway-types-migrations.workspace = true omicron-common.workspace = true omicron-uuid-kinds.workspace = true omicron-workspace-hack.workspace = true diff --git a/gateway-api/src/lib.rs b/gateway-api/src/lib.rs index fe014f20259..4c5714e5bbf 100644 --- a/gateway-api/src/lib.rs +++ b/gateway-api/src/lib.rs @@ -10,7 +10,7 @@ use dropshot::{ WebsocketUpgrade, }; use dropshot_api_manager_types::api_versions; -use gateway_types::{ +use gateway_types_migrations::v1::{ caboose::SpComponentCaboose, component::{ PowerState, SpComponentFirmwareSlot, SpComponentList, SpIdentifier, @@ -18,9 +18,13 @@ use gateway_types::{ }, component_details::SpComponentDetails, host::{ComponentFirmwareHashStatus, HostStartupOptions}, - ignition, - ignition::{IgnitionCommand, SpIgnitionInfo}, - rot::{RotCfpa, RotCfpaSlot, RotCmpa, RotState}, + params::{ + ComponentCabooseSlot, ComponentUpdateIdSlot, GetCfpaParams, + GetRotBootInfoParams, PathSp, PathSpComponent, + PathSpComponentFirmwareSlot, PathSpIgnitionCommand, PathSpSensorId, + PathSpTaskDumpIndex, SetComponentActiveSlotParams, UpdateAbortBody, + }, + rot::{RotCfpa, RotCmpa, RotState}, sensor::SpSensorReading, task_dump::TaskDump, update::{ @@ -28,9 +32,6 @@ use gateway_types::{ SpComponentResetError, SpUpdateStatus, }, }; -use schemars::JsonSchema; -use serde::Deserialize; -use uuid::Uuid; api_versions!([ // WHEN CHANGING THE API (part 1 of 2): @@ -413,7 +414,12 @@ pub trait GatewayApi { }] async fn ignition_list_v1( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk< + Vec, + >, + HttpError, + >; /// List SPs via Ignition /// @@ -427,7 +433,12 @@ pub trait GatewayApi { }] async fn ignition_list( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk< + Vec, + >, + HttpError, + >; /// Get SP info via Ignition /// @@ -443,7 +454,10 @@ pub trait GatewayApi { async fn ignition_get_v1( rqctx: RequestContext, path: Path, - ) -> Result, HttpError>; + ) -> Result< + HttpResponseOk, + HttpError, + >; /// Get SP info via Ignition /// @@ -458,7 +472,10 @@ pub trait GatewayApi { async fn ignition_get( rqctx: RequestContext, path: Path, - ) -> Result, HttpError>; + ) -> Result< + HttpResponseOk, + HttpError, + >; /// Send an ignition command targeting a specific SP. /// @@ -611,119 +628,3 @@ pub trait GatewayApi { query: Query, ) -> Result, HttpError>; } - -#[derive(Deserialize, JsonSchema)] -pub struct PathSp { - /// ID for the SP that the gateway service translates into the appropriate - /// port for communicating with the given SP. - #[serde(flatten)] - pub sp: SpIdentifier, -} - -#[derive(Deserialize, JsonSchema)] -pub struct PathSpSensorId { - /// ID for the SP that the gateway service translates into the appropriate - /// port for communicating with the given SP. - #[serde(flatten)] - pub sp: SpIdentifier, - /// ID for the sensor on the SP. - pub sensor_id: u32, -} - -#[derive(Deserialize, JsonSchema)] -pub struct PathSpComponent { - /// ID for the SP that the gateway service translates into the appropriate - /// port for communicating with the given SP. - #[serde(flatten)] - pub sp: SpIdentifier, - /// ID for the component of the SP; this is the internal identifier used by - /// the SP itself to identify its components. - pub component: String, -} - -#[derive(Deserialize, JsonSchema)] -pub struct PathSpComponentFirmwareSlot { - /// ID for the SP that the gateway service translates into the appropriate - /// port for communicating with the given SP. - #[serde(flatten)] - pub sp: SpIdentifier, - /// ID for the component of the SP; this is the internal identifier used by - /// the SP itself to identify its components. - pub component: String, - /// Firmware slot of the component. - pub firmware_slot: u16, -} - -#[derive(Deserialize, JsonSchema)] -pub struct PathSpTaskDumpIndex { - /// ID for the SP that the gateway service translates into the appropriate - /// port for communicating with the given SP. - #[serde(flatten)] - pub sp: SpIdentifier, - /// The index of the task dump to be read. - pub task_dump_index: u32, -} - -#[derive(Deserialize, JsonSchema)] -pub struct ComponentCabooseSlot { - /// The firmware slot to for which we want to request caboose information. - pub firmware_slot: u16, -} - -#[derive(Deserialize, JsonSchema)] -pub struct SetComponentActiveSlotParams { - /// Persist this choice of active slot. - pub persist: bool, -} - -#[derive(Deserialize, JsonSchema)] -pub struct ComponentUpdateIdSlot { - /// An identifier for this update. - /// - /// This ID applies to this single instance of the API call; it is not an - /// ID of `image` itself. Multiple API calls with the same `image` should - /// use different IDs. - pub id: Uuid, - /// The update slot to apply this image to. Supply 0 if the component only - /// has one update slot. - pub firmware_slot: u16, -} - -#[derive(Deserialize, JsonSchema)] -pub struct UpdateAbortBody { - /// The ID of the update to abort. - /// - /// If the SP is currently receiving an update with this ID, it will be - /// aborted. - /// - /// If the SP is currently receiving an update with a different ID, the - /// abort request will fail. - /// - /// If the SP is not currently receiving any update, the request to abort - /// should succeed but will not have actually done anything. - pub id: Uuid, -} - -#[derive( - Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema, -)] -pub struct GetCfpaParams { - pub slot: RotCfpaSlot, -} - -#[derive( - Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema, -)] -pub struct GetRotBootInfoParams { - pub version: u8, -} - -#[derive(Deserialize, JsonSchema)] -pub struct PathSpIgnitionCommand { - /// ID for the SP that the gateway service translates into the appropriate - /// port for communicating with the given SP. - #[serde(flatten)] - pub sp: SpIdentifier, - /// Ignition command to perform on the targeted SP. - pub command: IgnitionCommand, -} diff --git a/gateway-types/Cargo.toml b/gateway-types/Cargo.toml index 77326fa19be..3058792940c 100644 --- a/gateway-types/Cargo.toml +++ b/gateway-types/Cargo.toml @@ -11,6 +11,7 @@ workspace = true daft.workspace = true dropshot.workspace = true gateway-messages.workspace = true +gateway-types-migrations.workspace = true # Avoid depending on gateway-sp-comms! It is a pretty heavy dependency and # would only be used for From impls anyway. We put those impls in # omicron-gateway instead, and don't use `From`. @@ -27,4 +28,4 @@ tufaceous-artifact.workspace = true uuid.workspace = true [features] -testing = ["dep:proptest", "dep:test-strategy"] +testing = ["dep:proptest", "dep:test-strategy", "gateway-types-migrations/testing"] diff --git a/gateway-types/migrations/Cargo.toml b/gateway-types/migrations/Cargo.toml new file mode 100644 index 00000000000..e5d0a1a34cb --- /dev/null +++ b/gateway-types/migrations/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "gateway-types-migrations" +version = "0.1.0" +edition.workspace = true +license = "MPL-2.0" + +[lints] +workspace = true + +[dependencies] +daft.workspace = true +dropshot.workspace = true +gateway-messages.workspace = true +hex.workspace = true +omicron-uuid-kinds.workspace = true +omicron-workspace-hack.workspace = true +proptest = { workspace = true, optional = true } +schemars.workspace = true +serde.workspace = true +test-strategy = { workspace = true, optional = true } +thiserror.workspace = true +tufaceous-artifact.workspace = true +uuid.workspace = true + +[dev-dependencies] +proptest.workspace = true +test-strategy.workspace = true + +[features] +testing = ["dep:proptest", "dep:test-strategy"] diff --git a/gateway-types/migrations/src/lib.rs b/gateway-types/migrations/src/lib.rs new file mode 100644 index 00000000000..c1c27077828 --- /dev/null +++ b/gateway-types/migrations/src/lib.rs @@ -0,0 +1,12 @@ +// 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/. + +//! Type migrations for the Gateway API. +//! +//! This crate contains all published types for the Gateway API, organized by +//! the first API version in which they were introduced. Types that change +//! between versions are defined in the version module where they changed. + +pub mod v1; +pub mod v2; diff --git a/gateway-types/migrations/src/v1/caboose.rs b/gateway-types/migrations/src/v1/caboose.rs new file mode 100644 index 00000000000..ef11b9903af --- /dev/null +++ b/gateway-types/migrations/src/v1/caboose.rs @@ -0,0 +1,17 @@ +// 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, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct SpComponentCaboose { + pub git_commit: String, + pub board: String, + pub name: String, + pub version: String, + pub sign: Option, + pub epoch: Option, +} diff --git a/gateway-types/migrations/src/v1/component.rs b/gateway-types/migrations/src/v1/component.rs new file mode 100644 index 00000000000..e6224409726 --- /dev/null +++ b/gateway-types/migrations/src/v1/component.rs @@ -0,0 +1,316 @@ +// 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 std::{ + fmt, + str::{self, FromStr}, +}; + +use daft::Diffable; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::v1::rot::RotState; + +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + Serialize, + Deserialize, + JsonSchema, + Diffable, +)] +#[serde(rename_all = "lowercase")] +#[cfg_attr(any(test, feature = "testing"), derive(test_strategy::Arbitrary))] +pub enum SpType { + Sled, + Power, + Switch, +} + +impl fmt::Display for SpType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SpType::Sled => write!(f, "sled"), + SpType::Power => write!(f, "power"), + SpType::Switch => write!(f, "switch"), + } + } +} + +impl FromStr for SpType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "sled" => Ok(SpType::Sled), + "power" => Ok(SpType::Power), + "switch" => Ok(SpType::Switch), + _ => Err(format!("invalid SpType: {}", s)), + } + } +} + +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + JsonSchema, +)] +pub struct SpIdentifier { + #[serde(rename = "type")] + pub typ: SpType, + #[serde(deserialize_with = "deserializer_u16_from_string")] + pub slot: u16, +} + +impl fmt::Display for SpIdentifier { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?} {}", self.typ, self.slot) + } +} + +// We can't use the default `Deserialize` derivation for `SpIdentifier::slot` +// because it's embedded in other structs via `serde(flatten)`, which does not +// play well with the way dropshot parses HTTP queries/paths. serde ends up +// trying to deserialize the flattened struct as a map of strings to strings, +// which breaks on `slot` (but not on `typ` for reasons I don't entirely +// understand). We can work around by using an enum that allows either `String` +// or `u16` (which gets us past the serde map of strings), and then parsing the +// string into a u16 ourselves (which gets us to the `slot` we want). More +// background: https://github.com/serde-rs/serde/issues/1346 +fn deserializer_u16_from_string<'de, D>( + deserializer: D, +) -> Result +where + D: serde::Deserializer<'de>, +{ + use serde::de::{self, Unexpected}; + + #[derive(Debug, Deserialize)] + #[serde(untagged)] + enum StringOrU16 { + String(String), + U16(u16), + } + + match StringOrU16::deserialize(deserializer)? { + StringOrU16::String(s) => s + .parse() + .map_err(|_| de::Error::invalid_type(Unexpected::Str(&s), &"u16")), + StringOrU16::U16(n) => Ok(n), + } +} + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub struct SpState { + pub serial_number: String, + pub model: String, + pub revision: u32, + pub hubris_archive_id: String, + pub base_mac_address: [u8; 6], + pub power_state: PowerState, + pub rot: RotState, +} + +// We expect serial and model numbers to be ASCII and 0-padded: find the first 0 +// byte and convert to a string. If that fails, hexlify the entire slice. +fn stringify_byte_string(bytes: &[u8]) -> String { + let first_zero = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len()); + + str::from_utf8(&bytes[..first_zero]) + .map(|s| s.to_string()) + .unwrap_or_else(|_err| hex::encode(bytes)) +} + +impl From<(gateway_messages::SpStateV1, RotState)> for SpState { + fn from(all: (gateway_messages::SpStateV1, RotState)) -> Self { + let (state, rot) = all; + Self { + serial_number: stringify_byte_string(&state.serial_number), + model: stringify_byte_string(&state.model), + revision: state.revision, + hubris_archive_id: hex::encode(state.hubris_archive_id), + base_mac_address: state.base_mac_address, + power_state: PowerState::from(state.power_state), + rot, + } + } +} + +impl From for SpState { + fn from(state: gateway_messages::SpStateV1) -> Self { + Self::from((state, RotState::from(state.rot))) + } +} + +impl From<(gateway_messages::SpStateV2, RotState)> for SpState { + fn from(all: (gateway_messages::SpStateV2, RotState)) -> Self { + let (state, rot) = all; + Self { + serial_number: stringify_byte_string(&state.serial_number), + model: stringify_byte_string(&state.model), + revision: state.revision, + hubris_archive_id: hex::encode(state.hubris_archive_id), + base_mac_address: state.base_mac_address, + power_state: PowerState::from(state.power_state), + rot, + } + } +} + +impl From for SpState { + fn from(state: gateway_messages::SpStateV2) -> Self { + Self::from((state, RotState::from(state.rot))) + } +} + +impl From<(gateway_messages::SpStateV3, RotState)> for SpState { + fn from(all: (gateway_messages::SpStateV3, RotState)) -> Self { + let (state, rot) = all; + Self { + serial_number: stringify_byte_string(&state.serial_number), + model: stringify_byte_string(&state.model), + revision: state.revision, + hubris_archive_id: hex::encode(state.hubris_archive_id), + base_mac_address: state.base_mac_address, + power_state: PowerState::from(state.power_state), + rot, + } + } +} + +/// See RFD 81. +/// +/// This enum only lists power states the SP is able to control; higher power +/// states are controlled by ignition. +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + JsonSchema, +)] +pub enum PowerState { + A0, + A1, + A2, +} + +impl From for PowerState { + fn from(power_state: gateway_messages::PowerState) -> Self { + match power_state { + gateway_messages::PowerState::A0 => Self::A0, + gateway_messages::PowerState::A1 => Self::A1, + gateway_messages::PowerState::A2 => Self::A2, + } + } +} + +impl From for gateway_messages::PowerState { + fn from(power_state: PowerState) -> Self { + match power_state { + PowerState::A0 => Self::A0, + PowerState::A1 => Self::A1, + PowerState::A2 => Self::A2, + } + } +} + +/// List of components from a single SP. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub struct SpComponentList { + pub components: Vec, +} + +/// Overview of a single SP component. +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub struct SpComponentInfo { + /// The unique identifier for this component. + pub component: String, + /// The name of the physical device. + pub device: String, + /// The component's serial number, if it has one. + pub serial_number: Option, + /// A human-readable description of the component. + pub description: String, + /// `capabilities` is a bitmask; interpret it via + /// [`gateway_messages::DeviceCapabilities`]. + pub capabilities: u32, + /// Whether or not the component is present, to the best of the SP's ability + /// to judge. + pub presence: SpComponentPresence, +} + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +/// Description of the presence or absence of a component. +/// +/// The presence of some components may vary based on the power state of the +/// sled (e.g., components that time out or appear unavailable if the sled is in +/// A2 may become present when the sled moves to A0). +pub enum SpComponentPresence { + /// The component is present. + Present, + /// The component is not present. + NotPresent, + /// The component is present but in a failed or faulty state. + Failed, + /// The SP is unable to determine the presence of the component. + Unavailable, + /// The SP's attempt to determine the presence of the component timed out. + Timeout, + /// The SP's attempt to determine the presence of the component failed. + Error, +} + +impl From for SpComponentPresence { + fn from(p: gateway_messages::DevicePresence) -> Self { + match p { + gateway_messages::DevicePresence::Present => Self::Present, + gateway_messages::DevicePresence::NotPresent => Self::NotPresent, + gateway_messages::DevicePresence::Failed => Self::Failed, + gateway_messages::DevicePresence::Unavailable => Self::Unavailable, + gateway_messages::DevicePresence::Timeout => Self::Timeout, + gateway_messages::DevicePresence::Error => Self::Error, + } + } +} + +/// Identifier for an SP's component's firmware slot; e.g., slots 0 and 1 for +/// the host boot flash. +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +pub struct SpComponentFirmwareSlot { + pub slot: u16, +} diff --git a/gateway-types/migrations/src/v1/component_details.rs b/gateway-types/migrations/src/v1/component_details.rs new file mode 100644 index 00000000000..bd55c236fda --- /dev/null +++ b/gateway-types/migrations/src/v1/component_details.rs @@ -0,0 +1,445 @@ +// 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 dropshot::HttpError; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; + +// -------------------------------------- +// Monorail port status-related types +// -------------------------------------- + +#[derive(Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum SpComponentDetails { + PortStatus(PortStatus), + PortStatusError(PortStatusError), + Measurement(Measurement), + MeasurementError(MeasurementError), +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub struct PortStatusError { + pub port: u32, + pub code: PortStatusErrorCode, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +#[serde(tag = "code", rename_all = "snake_case")] +pub enum PortStatusErrorCode { + Unconfigured, + Other { raw: u32 }, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +pub struct PortStatus { + pub port: u32, + pub cfg: PortConfig, + pub link_status: LinkStatus, + pub phy_status: Option, + pub counters: PortCounters, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +pub struct PortConfig { + pub mode: PortMode, + pub dev_type: PortDev, + pub dev_num: u8, + pub serdes_type: PortSerdes, + pub serdes_num: u8, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum PortDev { + Dev1g, + Dev2g5, + Dev10g, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum PortSerdes { + Serdes1g, + Serdes6g, + Serdes10g, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +#[serde(tag = "speed", rename_all = "snake_case")] +pub enum Speed { + Speed100M, + Speed1G, + Speed10G, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +#[serde(tag = "mode", rename_all = "snake_case")] +pub enum PortMode { + Sfi, + BaseKr, + Sgmii { speed: Speed }, + Qsgmii { speed: Speed }, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +pub struct PacketCount { + pub multicast: u32, + pub unicast: u32, + pub broadcast: u32, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +pub struct PortCounters { + pub rx: PacketCount, + pub tx: PacketCount, + pub link_down_sticky: bool, + pub phy_link_down_sticky: bool, +} + +#[derive( + Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, +)] +#[serde(tag = "status", rename_all = "snake_case")] +pub enum LinkStatus { + Error, + Down, + Up, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +pub struct PhyStatus { + pub ty: PhyType, + pub mac_link_up: LinkStatus, + pub media_link_up: LinkStatus, +} + +#[derive( + Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, +)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum PhyType { + Vsc8504, + Vsc8522, + Vsc8552, + Vsc8562, +} + +/// Error type for `gateway_messages::ComponentDetails` that are not supported +/// by MGS proper and are only available via `faux-mgs`. +#[derive(Debug, thiserror::Error)] +#[error("unsupported component details: {description}")] +pub struct UnsupportedComponentDetails { + pub description: String, +} + +impl From for HttpError { + fn from(value: UnsupportedComponentDetails) -> Self { + HttpError::for_bad_request( + None, + format!( + "requested component details are not yet supported: {value}" + ), + ) + } +} + +impl TryFrom for SpComponentDetails { + type Error = UnsupportedComponentDetails; + + fn try_from( + details: gateway_messages::ComponentDetails, + ) -> Result { + use gateway_messages::ComponentDetails; + match details { + ComponentDetails::PortStatus(Ok(status)) => { + Ok(Self::PortStatus(status.into())) + } + ComponentDetails::PortStatus(Err(err)) => { + Ok(Self::PortStatusError(err.into())) + } + ComponentDetails::Measurement(m) => Ok(match m.value { + Ok(value) => Self::Measurement(Measurement { + name: m.name, + kind: m.kind.into(), + value, + }), + Err(err) => Self::MeasurementError(MeasurementError { + name: m.name, + kind: m.kind.into(), + error: err.into(), + }), + }), + ComponentDetails::LastPostCode(inner) => { + Err(UnsupportedComponentDetails { + description: format!("last post code: {inner:?}"), + }) + } + ComponentDetails::GpioToggleCount(inner) => { + Err(UnsupportedComponentDetails { + description: format!("GPIO toggle count: {inner:?}"), + }) + } + } + } +} + +impl From for PortStatus { + fn from( + status: gateway_messages::monorail_port_status::PortStatus, + ) -> Self { + Self { + port: status.port, + cfg: status.cfg.into(), + link_status: status.link_status.into(), + phy_status: status.phy_status.map(Into::into), + counters: status.counters.into(), + } + } +} + +impl From for PortConfig { + fn from(cfg: gateway_messages::monorail_port_status::PortConfig) -> Self { + Self { + mode: cfg.mode.into(), + dev_type: cfg.dev.0.into(), + dev_num: cfg.dev.1, + serdes_type: cfg.serdes.0.into(), + serdes_num: cfg.serdes.1, + } + } +} + +impl From for PortMode { + fn from(mode: gateway_messages::monorail_port_status::PortMode) -> Self { + use gateway_messages::monorail_port_status::PortMode; + match mode { + PortMode::Sfi => Self::Sfi, + PortMode::BaseKr => Self::BaseKr, + PortMode::Sgmii(s) => Self::Sgmii { speed: s.into() }, + PortMode::Qsgmii(s) => Self::Qsgmii { speed: s.into() }, + } + } +} + +impl From for Speed { + fn from(speed: gateway_messages::monorail_port_status::Speed) -> Self { + use gateway_messages::monorail_port_status::Speed; + match speed { + Speed::Speed100M => Self::Speed100M, + Speed::Speed1G => Self::Speed1G, + Speed::Speed10G => Self::Speed10G, + } + } +} + +impl From for PortDev { + fn from(dev: gateway_messages::monorail_port_status::PortDev) -> Self { + use gateway_messages::monorail_port_status::PortDev; + match dev { + PortDev::Dev1g => Self::Dev1g, + PortDev::Dev2g5 => Self::Dev2g5, + PortDev::Dev10g => Self::Dev10g, + } + } +} + +impl From for PortSerdes { + fn from( + serdes: gateway_messages::monorail_port_status::PortSerdes, + ) -> Self { + use gateway_messages::monorail_port_status::PortSerdes; + match serdes { + PortSerdes::Serdes1g => Self::Serdes1g, + PortSerdes::Serdes6g => Self::Serdes6g, + PortSerdes::Serdes10g => Self::Serdes10g, + } + } +} + +impl From for LinkStatus { + fn from( + status: gateway_messages::monorail_port_status::LinkStatus, + ) -> Self { + use gateway_messages::monorail_port_status::LinkStatus; + match status { + LinkStatus::Error => Self::Error, + LinkStatus::Down => Self::Down, + LinkStatus::Up => Self::Up, + } + } +} + +impl From for PhyStatus { + fn from(status: gateway_messages::monorail_port_status::PhyStatus) -> Self { + Self { + ty: status.ty.into(), + mac_link_up: status.mac_link_up.into(), + media_link_up: status.media_link_up.into(), + } + } +} + +impl From for PhyType { + fn from(ty: gateway_messages::monorail_port_status::PhyType) -> Self { + use gateway_messages::monorail_port_status::PhyType; + match ty { + PhyType::Vsc8504 => Self::Vsc8504, + PhyType::Vsc8522 => Self::Vsc8522, + PhyType::Vsc8552 => Self::Vsc8552, + PhyType::Vsc8562 => Self::Vsc8562, + } + } +} + +impl From + for PortCounters +{ + fn from(c: gateway_messages::monorail_port_status::PortCounters) -> Self { + Self { + rx: c.rx.into(), + tx: c.tx.into(), + link_down_sticky: c.link_down_sticky, + phy_link_down_sticky: c.phy_link_down_sticky, + } + } +} + +impl From for PacketCount { + fn from(c: gateway_messages::monorail_port_status::PacketCount) -> Self { + Self { + multicast: c.multicast, + unicast: c.unicast, + broadcast: c.broadcast, + } + } +} + +impl From + for PortStatusError +{ + fn from( + err: gateway_messages::monorail_port_status::PortStatusError, + ) -> Self { + Self { port: err.port, code: err.code.into() } + } +} + +impl From + for PortStatusErrorCode +{ + fn from( + err: gateway_messages::monorail_port_status::PortStatusErrorCode, + ) -> Self { + use gateway_messages::monorail_port_status::PortStatusErrorCode; + match err { + PortStatusErrorCode::Unconfigured => Self::Unconfigured, + PortStatusErrorCode::Other(raw) => Self::Other { raw }, + } + } +} + +// -------------------------------------- +// Measurement-related types +// -------------------------------------- + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct Measurement { + pub name: String, + pub kind: MeasurementKind, + pub value: f32, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct MeasurementError { + pub name: String, + pub kind: MeasurementKind, + pub error: MeasurementErrorCode, +} + +#[derive( + Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema, +)] +#[serde(tag = "code", rename_all = "snake_case")] +pub enum MeasurementErrorCode { + InvalidSensor, + NoReading, + NotPresent, + DeviceError, + DeviceUnavailable, + DeviceTimeout, + DeviceOff, +} + +#[derive( + Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum MeasurementKind { + Temperature, + Power, + Current, + Voltage, + InputCurrent, + InputVoltage, + Speed, + CpuTctl, +} + +impl From for MeasurementKind { + fn from(kind: gateway_messages::measurement::MeasurementKind) -> Self { + use gateway_messages::measurement::MeasurementKind; + match kind { + MeasurementKind::Temperature => Self::Temperature, + MeasurementKind::Power => Self::Power, + MeasurementKind::Current => Self::Current, + MeasurementKind::Voltage => Self::Voltage, + MeasurementKind::InputCurrent => Self::InputCurrent, + MeasurementKind::InputVoltage => Self::InputVoltage, + MeasurementKind::Speed => Self::Speed, + MeasurementKind::CpuTctl => Self::CpuTctl, + } + } +} + +impl From + for MeasurementErrorCode +{ + fn from(err: gateway_messages::measurement::MeasurementError) -> Self { + use gateway_messages::measurement::MeasurementError; + match err { + MeasurementError::InvalidSensor => Self::InvalidSensor, + MeasurementError::NoReading => Self::NoReading, + MeasurementError::NotPresent => Self::NotPresent, + MeasurementError::DeviceError => Self::DeviceError, + MeasurementError::DeviceUnavailable => Self::DeviceUnavailable, + MeasurementError::DeviceTimeout => Self::DeviceTimeout, + MeasurementError::DeviceOff => Self::DeviceOff, + } + } +} diff --git a/gateway-types/migrations/src/v1/host.rs b/gateway-types/migrations/src/v1/host.rs new file mode 100644 index 00000000000..c6bf9d30920 --- /dev/null +++ b/gateway-types/migrations/src/v1/host.rs @@ -0,0 +1,73 @@ +// 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 gateway_messages::StartupOptions; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +pub struct HostStartupOptions { + pub phase2_recovery_mode: bool, + pub kbm: bool, + pub bootrd: bool, + pub prom: bool, + pub kmdb: bool, + pub kmdb_boot: bool, + pub boot_ramdisk: bool, + pub boot_net: bool, + pub verbose: bool, +} + +impl From for StartupOptions { + fn from(mgs_opt: HostStartupOptions) -> Self { + let mut opt = StartupOptions::empty(); + opt.set( + StartupOptions::PHASE2_RECOVERY_MODE, + mgs_opt.phase2_recovery_mode, + ); + opt.set(StartupOptions::STARTUP_KBM, mgs_opt.kbm); + opt.set(StartupOptions::STARTUP_BOOTRD, mgs_opt.bootrd); + opt.set(StartupOptions::STARTUP_PROM, mgs_opt.prom); + opt.set(StartupOptions::STARTUP_KMDB, mgs_opt.kmdb); + opt.set(StartupOptions::STARTUP_KMDB_BOOT, mgs_opt.kmdb_boot); + opt.set(StartupOptions::STARTUP_BOOT_RAMDISK, mgs_opt.boot_ramdisk); + opt.set(StartupOptions::STARTUP_BOOT_NET, mgs_opt.boot_net); + opt.set(StartupOptions::STARTUP_VERBOSE, mgs_opt.verbose); + opt + } +} + +impl From for HostStartupOptions { + fn from(opt: StartupOptions) -> Self { + Self { + phase2_recovery_mode: opt + .contains(StartupOptions::PHASE2_RECOVERY_MODE), + kbm: opt.contains(StartupOptions::STARTUP_KBM), + bootrd: opt.contains(StartupOptions::STARTUP_BOOTRD), + prom: opt.contains(StartupOptions::STARTUP_PROM), + kmdb: opt.contains(StartupOptions::STARTUP_KMDB), + kmdb_boot: opt.contains(StartupOptions::STARTUP_KMDB_BOOT), + boot_ramdisk: opt.contains(StartupOptions::STARTUP_BOOT_RAMDISK), + boot_net: opt.contains(StartupOptions::STARTUP_BOOT_NET), + verbose: opt.contains(StartupOptions::STARTUP_VERBOSE), + } + } +} + +#[derive(Serialize, Deserialize, JsonSchema)] +#[serde(tag = "status", rename_all = "snake_case")] +pub enum ComponentFirmwareHashStatus { + /// The hash is not available; the client must issue a separate request to + /// begin calculating the hash. + HashNotCalculated, + /// The hash is currently being calculated; the client should sleep briefly + /// then check again. + /// + /// We expect this operation to take a handful of seconds in practice. + HashInProgress, + /// The hash of the given firmware slot. + Hashed { sha256: [u8; 32] }, +} diff --git a/gateway-types/src/ignition/v1.rs b/gateway-types/migrations/src/v1/ignition.rs similarity index 79% rename from gateway-types/src/ignition/v1.rs rename to gateway-types/migrations/src/v1/ignition.rs index 2f3cd30e206..d6e413e4b6d 100644 --- a/gateway-types/src/ignition/v1.rs +++ b/gateway-types/migrations/src/v1/ignition.rs @@ -5,7 +5,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::component::SpIdentifier; +use crate::v1::component::SpIdentifier; #[derive( Debug, @@ -113,3 +113,30 @@ impl From for SpIgnitionSystemType { } } } + +/// Ignition command. +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum IgnitionCommand { + PowerOn, + PowerOff, + PowerReset, +} + +impl From for gateway_messages::IgnitionCommand { + fn from(cmd: IgnitionCommand) -> Self { + match cmd { + IgnitionCommand::PowerOn => { + gateway_messages::IgnitionCommand::PowerOn + } + IgnitionCommand::PowerOff => { + gateway_messages::IgnitionCommand::PowerOff + } + IgnitionCommand::PowerReset => { + gateway_messages::IgnitionCommand::PowerReset + } + } + } +} diff --git a/gateway-types/migrations/src/v1/mod.rs b/gateway-types/migrations/src/v1/mod.rs new file mode 100644 index 00000000000..fad4034e781 --- /dev/null +++ b/gateway-types/migrations/src/v1/mod.rs @@ -0,0 +1,16 @@ +// 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/. + +//! Types for Gateway API version 1. + +pub mod caboose; +pub mod component; +pub mod component_details; +pub mod host; +pub mod ignition; +pub mod params; +pub mod rot; +pub mod sensor; +pub mod task_dump; +pub mod update; diff --git a/gateway-types/migrations/src/v1/params.rs b/gateway-types/migrations/src/v1/params.rs new file mode 100644 index 00000000000..7e6bd225ffe --- /dev/null +++ b/gateway-types/migrations/src/v1/params.rs @@ -0,0 +1,127 @@ +// 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; +use uuid::Uuid; + +use crate::v1::component::SpIdentifier; +use crate::v1::ignition::IgnitionCommand; +use crate::v1::rot::RotCfpaSlot; + +#[derive(Deserialize, JsonSchema)] +pub struct PathSp { + /// ID for the SP that the gateway service translates into the appropriate + /// port for communicating with the given SP. + #[serde(flatten)] + pub sp: SpIdentifier, +} + +#[derive(Deserialize, JsonSchema)] +pub struct PathSpSensorId { + /// ID for the SP that the gateway service translates into the appropriate + /// port for communicating with the given SP. + #[serde(flatten)] + pub sp: SpIdentifier, + /// ID for the sensor on the SP. + pub sensor_id: u32, +} + +#[derive(Deserialize, JsonSchema)] +pub struct PathSpComponent { + /// ID for the SP that the gateway service translates into the appropriate + /// port for communicating with the given SP. + #[serde(flatten)] + pub sp: SpIdentifier, + /// ID for the component of the SP; this is the internal identifier used by + /// the SP itself to identify its components. + pub component: String, +} + +#[derive(Deserialize, JsonSchema)] +pub struct PathSpComponentFirmwareSlot { + /// ID for the SP that the gateway service translates into the appropriate + /// port for communicating with the given SP. + #[serde(flatten)] + pub sp: SpIdentifier, + /// ID for the component of the SP; this is the internal identifier used by + /// the SP itself to identify its components. + pub component: String, + /// Firmware slot of the component. + pub firmware_slot: u16, +} + +#[derive(Deserialize, JsonSchema)] +pub struct PathSpTaskDumpIndex { + /// ID for the SP that the gateway service translates into the appropriate + /// port for communicating with the given SP. + #[serde(flatten)] + pub sp: SpIdentifier, + /// The index of the task dump to be read. + pub task_dump_index: u32, +} + +#[derive(Deserialize, JsonSchema)] +pub struct ComponentCabooseSlot { + /// The firmware slot to for which we want to request caboose information. + pub firmware_slot: u16, +} + +#[derive(Deserialize, JsonSchema)] +pub struct SetComponentActiveSlotParams { + /// Persist this choice of active slot. + pub persist: bool, +} + +#[derive(Deserialize, JsonSchema)] +pub struct ComponentUpdateIdSlot { + /// An identifier for this update. + /// + /// This ID applies to this single instance of the API call; it is not an + /// ID of `image` itself. Multiple API calls with the same `image` should + /// use different IDs. + pub id: Uuid, + /// The update slot to apply this image to. Supply 0 if the component only + /// has one update slot. + pub firmware_slot: u16, +} + +#[derive(Deserialize, JsonSchema)] +pub struct UpdateAbortBody { + /// The ID of the update to abort. + /// + /// If the SP is currently receiving an update with this ID, it will be + /// aborted. + /// + /// If the SP is currently receiving an update with a different ID, the + /// abort request will fail. + /// + /// If the SP is not currently receiving any update, the request to abort + /// should succeed but will not have actually done anything. + pub id: Uuid, +} + +#[derive( + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema, +)] +pub struct GetCfpaParams { + pub slot: RotCfpaSlot, +} + +#[derive( + Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema, +)] +pub struct GetRotBootInfoParams { + pub version: u8, +} + +#[derive(Deserialize, JsonSchema)] +pub struct PathSpIgnitionCommand { + /// ID for the SP that the gateway service translates into the appropriate + /// port for communicating with the given SP. + #[serde(flatten)] + pub sp: SpIdentifier, + /// Ignition command to perform on the targeted SP. + pub command: IgnitionCommand, +} diff --git a/gateway-types/migrations/src/v1/rot.rs b/gateway-types/migrations/src/v1/rot.rs new file mode 100644 index 00000000000..faa2b8b1e5d --- /dev/null +++ b/gateway-types/migrations/src/v1/rot.rs @@ -0,0 +1,403 @@ +// 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 daft::Diffable; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; +use std::str::FromStr; + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +#[serde(tag = "state", rename_all = "snake_case")] +pub enum RotState { + V2 { + active: RotSlot, + persistent_boot_preference: RotSlot, + pending_persistent_boot_preference: Option, + transient_boot_preference: Option, + slot_a_sha3_256_digest: Option, + slot_b_sha3_256_digest: Option, + }, + CommunicationFailed { + message: String, + }, + V3 { + active: RotSlot, + persistent_boot_preference: RotSlot, + pending_persistent_boot_preference: Option, + transient_boot_preference: Option, + + slot_a_fwid: String, + slot_b_fwid: String, + stage0_fwid: String, + stage0next_fwid: String, + + slot_a_error: Option, + slot_b_error: Option, + stage0_error: Option, + stage0next_error: Option, + }, +} + +impl RotState { + fn fwid_to_string(fwid: gateway_messages::Fwid) -> String { + match fwid { + gateway_messages::Fwid::Sha3_256(digest) => hex::encode(digest), + } + } +} + +impl From> + for RotState +{ + fn from( + result: Result, + ) -> Self { + match result { + Ok(state) => Self::from(state), + Err(err) => Self::CommunicationFailed { message: err.to_string() }, + } + } +} + +impl From> + for RotState +{ + fn from( + result: Result< + gateway_messages::RotStateV2, + gateway_messages::RotError, + >, + ) -> Self { + match result { + Ok(state) => Self::from(state), + Err(err) => Self::CommunicationFailed { message: err.to_string() }, + } + } +} + +impl From for RotState { + fn from(state: gateway_messages::RotState) -> Self { + let boot_state = state.rot_updates.boot_state; + Self::V2 { + active: boot_state.active.into(), + slot_a_sha3_256_digest: boot_state + .slot_a + .map(|details| hex::encode(details.digest)), + slot_b_sha3_256_digest: boot_state + .slot_b + .map(|details| hex::encode(details.digest)), + // RotState(V1) didn't have the following fields, so we make + // it up as best we can. This RoT version is pre-shipping + // and should only exist on (not updated recently) test + // systems. + persistent_boot_preference: boot_state.active.into(), + pending_persistent_boot_preference: None, + transient_boot_preference: None, + } + } +} + +impl From for RotState { + fn from(state: gateway_messages::RotStateV2) -> Self { + Self::V2 { + active: state.active.into(), + persistent_boot_preference: state.persistent_boot_preference.into(), + pending_persistent_boot_preference: state + .pending_persistent_boot_preference + .map(Into::into), + transient_boot_preference: state + .transient_boot_preference + .map(Into::into), + slot_a_sha3_256_digest: state + .slot_a_sha3_256_digest + .map(hex::encode), + slot_b_sha3_256_digest: state + .slot_b_sha3_256_digest + .map(hex::encode), + } + } +} + +impl From for RotState { + fn from(state: gateway_messages::RotStateV3) -> Self { + Self::V3 { + active: state.active.into(), + persistent_boot_preference: state.persistent_boot_preference.into(), + pending_persistent_boot_preference: state + .pending_persistent_boot_preference + .map(Into::into), + transient_boot_preference: state + .transient_boot_preference + .map(Into::into), + slot_a_fwid: Self::fwid_to_string(state.slot_a_fwid), + slot_b_fwid: Self::fwid_to_string(state.slot_b_fwid), + + stage0_fwid: Self::fwid_to_string(state.stage0_fwid), + stage0next_fwid: Self::fwid_to_string(state.stage0next_fwid), + + slot_a_error: state.slot_a_status.err().map(From::from), + slot_b_error: state.slot_b_status.err().map(From::from), + + stage0_error: state.stage0_status.err().map(From::from), + stage0next_error: state.stage0next_status.err().map(From::from), + } + } +} + +impl From for RotState { + fn from(value: gateway_messages::RotBootInfo) -> Self { + match value { + gateway_messages::RotBootInfo::V1(s) => Self::from(s), + gateway_messages::RotBootInfo::V2(s) => Self::from(s), + gateway_messages::RotBootInfo::V3(s) => Self::from(s), + } + } +} + +#[derive( + Debug, + Diffable, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +#[serde(tag = "slot", rename_all = "snake_case")] +#[cfg_attr(any(test, feature = "testing"), derive(test_strategy::Arbitrary))] +pub enum RotSlot { + A, + B, +} + +impl RotSlot { + pub fn to_u16(&self) -> u16 { + match self { + RotSlot::A => 0, + RotSlot::B => 1, + } + } + + pub fn toggled(&self) -> Self { + match self { + RotSlot::A => RotSlot::B, + RotSlot::B => RotSlot::A, + } + } +} + +impl Display for RotSlot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + RotSlot::A => "A", + RotSlot::B => "B", + }; + write!(f, "{s}") + } +} + +impl FromStr for RotSlot { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "a" | "A" => Ok(RotSlot::A), + "b" | "B" => Ok(RotSlot::B), + _ => Err(format!( + "unrecognized value {} for RoT slot. \ + Must be one of `a`, `A`, `b`, or `B`", + s + )), + } + } +} + +impl From for RotSlot { + fn from(slot: gateway_messages::RotSlotId) -> Self { + match slot { + gateway_messages::RotSlotId::A => Self::A, + gateway_messages::RotSlotId::B => Self::B, + } + } +} + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +pub struct RotImageDetails { + pub digest: String, + pub version: ImageVersion, +} + +impl From for RotImageDetails { + fn from(details: gateway_messages::RotImageDetails) -> Self { + Self { + digest: hex::encode(details.digest), + version: details.version.into(), + } + } +} + +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + JsonSchema, +)] +pub struct ImageVersion { + pub epoch: u32, + pub version: u32, +} + +impl From for ImageVersion { + fn from(v: gateway_messages::ImageVersion) -> Self { + Self { epoch: v.epoch, version: v.version } + } +} + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum RotImageError { + Unchecked, + FirstPageErased, + PartiallyProgrammed, + InvalidLength, + HeaderNotProgrammed, + BootloaderTooSmall, + BadMagic, + HeaderImageSize, + UnalignedLength, + UnsupportedType, + ResetVectorNotThumb2, + ResetVector, + Signature, +} + +impl From for RotImageError { + fn from(error: gateway_messages::ImageError) -> Self { + match error { + gateway_messages::ImageError::Unchecked => RotImageError::Unchecked, + gateway_messages::ImageError::FirstPageErased => { + RotImageError::FirstPageErased + } + gateway_messages::ImageError::PartiallyProgrammed => { + RotImageError::PartiallyProgrammed + } + gateway_messages::ImageError::InvalidLength => { + RotImageError::InvalidLength + } + gateway_messages::ImageError::HeaderNotProgrammed => { + RotImageError::HeaderNotProgrammed + } + gateway_messages::ImageError::BootloaderTooSmall => { + RotImageError::BootloaderTooSmall + } + gateway_messages::ImageError::BadMagic => RotImageError::BadMagic, + gateway_messages::ImageError::HeaderImageSize => { + RotImageError::HeaderImageSize + } + gateway_messages::ImageError::UnalignedLength => { + RotImageError::UnalignedLength + } + gateway_messages::ImageError::UnsupportedType => { + RotImageError::UnsupportedType + } + gateway_messages::ImageError::ResetVectorNotThumb2 => { + RotImageError::ResetVectorNotThumb2 + } + gateway_messages::ImageError::ResetVector => { + RotImageError::ResetVector + } + gateway_messages::ImageError::Signature => RotImageError::Signature, + } + } +} + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +pub struct RotCmpa { + pub base64_data: String, +} + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +#[serde(tag = "slot", rename_all = "snake_case")] +pub enum RotCfpaSlot { + Active, + Inactive, + Scratch, +} + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Deserialize, + Serialize, + JsonSchema, +)] +pub struct RotCfpa { + pub base64_data: String, + pub slot: RotCfpaSlot, +} diff --git a/gateway-types/migrations/src/v1/sensor.rs b/gateway-types/migrations/src/v1/sensor.rs new file mode 100644 index 00000000000..59ae9e9542b --- /dev/null +++ b/gateway-types/migrations/src/v1/sensor.rs @@ -0,0 +1,74 @@ +// 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}; + +/// Result of reading an SP sensor. +#[derive( + Debug, + Clone, + Copy, + PartialEq, + PartialOrd, + Serialize, + Deserialize, + JsonSchema, +)] +pub struct SpSensorReading { + /// SP-centric timestamp of when `result` was recorded from this sensor. + /// + /// Currently this value represents "milliseconds since the last SP boot" + /// and is primarily useful as a delta between sensors on this SP (assuming + /// no reboot in between). The meaning could change with future SP releases. + pub timestamp: u64, + /// Value (or error) from the sensor. + pub result: SpSensorReadingResult, +} + +impl From for SpSensorReading { + fn from(value: gateway_messages::SensorReading) -> Self { + Self { + timestamp: value.timestamp, + result: match value.value { + Ok(value) => SpSensorReadingResult::Success { value }, + Err(err) => err.into(), + }, + } + } +} + +/// Single reading (or error) from an SP sensor. +#[derive( + Debug, + Clone, + Copy, + PartialEq, + PartialOrd, + Deserialize, + Serialize, + JsonSchema, +)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum SpSensorReadingResult { + Success { value: f32 }, + DeviceOff, + DeviceError, + DeviceNotPresent, + DeviceUnavailable, + DeviceTimeout, +} + +impl From for SpSensorReadingResult { + fn from(value: gateway_messages::SensorDataMissing) -> Self { + use gateway_messages::SensorDataMissing; + match value { + SensorDataMissing::DeviceOff => Self::DeviceOff, + SensorDataMissing::DeviceError => Self::DeviceError, + SensorDataMissing::DeviceNotPresent => Self::DeviceNotPresent, + SensorDataMissing::DeviceUnavailable => Self::DeviceUnavailable, + SensorDataMissing::DeviceTimeout => Self::DeviceTimeout, + } + } +} diff --git a/gateway-types/migrations/src/v1/task_dump.rs b/gateway-types/migrations/src/v1/task_dump.rs new file mode 100644 index 00000000000..ef525ed3155 --- /dev/null +++ b/gateway-types/migrations/src/v1/task_dump.rs @@ -0,0 +1,34 @@ +// 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, +)] +pub struct TaskDump { + /// Index of the crashed task. + pub task_index: u16, + /// Hubris timestamp at which the task crash occurred. + pub timestamp: u64, + /// Hex-encoded Hubris archive ID. + pub archive_id: String, + /// `BORD` field from the caboose. + pub bord: String, + /// `GITC` field from the caboose. + pub gitc: String, + /// `VERS` field from the caboose, if present. + pub vers: Option, + /// Base64-encoded zip file containing dehydrated task dump. + pub base64_zip: String, +} diff --git a/gateway-types/migrations/src/v1/update.rs b/gateway-types/migrations/src/v1/update.rs new file mode 100644 index 00000000000..62cf7ea6239 --- /dev/null +++ b/gateway-types/migrations/src/v1/update.rs @@ -0,0 +1,171 @@ +// 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 dropshot::{ErrorStatusCode, HttpError, HttpResponseError}; +use gateway_messages::UpdateStatus; +use omicron_uuid_kinds::MupdateUuid; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use tufaceous_artifact::ArtifactHash; +use uuid::Uuid; + +/// The error type returned by the `sp_component_reset()` MGS endpoint. +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, JsonSchema, thiserror::Error, +)] +#[serde(tag = "state", rename_all = "snake_case")] +pub enum SpComponentResetError { + /// MGS refuses to reset its own sled's SP. + #[error("cannot reset SP of the sled hosting MGS")] + ResetSpOfLocalSled, + + /// Other dropshot errors. + #[error("{internal_message}")] + Other { + message: String, + error_code: Option, + + // Skip serializing these fields, as they are used for the + // `fmt::Display` implementation and for determining the status code, + // respectively, rather than included in the response body: + #[serde(skip)] + internal_message: String, + #[serde(skip)] + status: ErrorStatusCode, + }, +} + +impl HttpResponseError for SpComponentResetError { + fn status_code(&self) -> ErrorStatusCode { + match self { + SpComponentResetError::ResetSpOfLocalSled => { + ErrorStatusCode::BAD_REQUEST + } + SpComponentResetError::Other { status, .. } => *status, + } + } +} + +impl From for SpComponentResetError { + fn from(err: HttpError) -> Self { + Self::Other { + message: err.external_message, + error_code: err.error_code, + internal_message: err.internal_message, + status: err.status_code, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "state", rename_all = "snake_case")] +pub enum SpUpdateStatus { + /// The SP has no update status. + None, + /// The SP is preparing to receive an update. + /// + /// May or may not include progress, depending on the capabilities of the + /// component being updated. + Preparing { id: Uuid, progress: Option }, + /// The SP is currently receiving an update. + InProgress { id: Uuid, bytes_received: u32, total_bytes: u32 }, + /// The SP has completed receiving an update. + Complete { id: Uuid }, + /// The SP has aborted an in-progress update. + Aborted { id: Uuid }, + /// The update process failed. + Failed { id: Uuid, code: u32 }, + /// The update process failed with an RoT-specific error. + RotError { id: Uuid, message: String }, +} + +impl From for SpUpdateStatus { + fn from(status: UpdateStatus) -> Self { + match status { + UpdateStatus::None => Self::None, + UpdateStatus::Preparing(status) => Self::Preparing { + id: status.id.into(), + progress: status.progress.map(Into::into), + }, + UpdateStatus::SpUpdateAuxFlashChckScan { + id, total_size, .. + } => Self::InProgress { + id: id.into(), + bytes_received: 0, + total_bytes: total_size, + }, + UpdateStatus::InProgress(status) => Self::InProgress { + id: status.id.into(), + bytes_received: status.bytes_received, + total_bytes: status.total_size, + }, + UpdateStatus::Complete(id) => Self::Complete { id: id.into() }, + UpdateStatus::Aborted(id) => Self::Aborted { id: id.into() }, + UpdateStatus::Failed { id, code } => { + Self::Failed { id: id.into(), code } + } + UpdateStatus::RotError { id, error } => { + Self::RotError { id: id.into(), message: format!("{error:?}") } + } + } + } +} + +/// Progress of an SP preparing to update. +/// +/// The units of `current` and `total` are unspecified and defined by the SP; +/// e.g., if preparing for an update requires erasing a flash device, this may +/// indicate progress of that erasure without defining units (bytes, pages, +/// sectors, etc.). +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +pub struct UpdatePreparationProgress { + pub current: u32, + pub total: u32, +} + +impl From + for UpdatePreparationProgress +{ + fn from(progress: gateway_messages::UpdatePreparationProgress) -> Self { + Self { current: progress.current, total: progress.total } + } +} + +// This type is a duplicate of the type in `ipcc`. We keep these types distinct +// to allow us to choose different representations for MGS's HTTP API (this +// type) and the wire format passed through the SP to installinator +// (`ipcc::InstallinatorImageId`), although _currently_ they happen to be +// defined identically. +// +// We don't define a conversion from `Self` to `ipcc::InstallinatorImageId` here +// to avoid a dependency on `libipcc`. Instead, callers can easily perform +// conversions themselves. +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub struct InstallinatorImageId { + pub update_id: MupdateUuid, + pub host_phase_2: ArtifactHash, + pub control_plane: ArtifactHash, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "progress", rename_all = "snake_case")] +pub enum HostPhase2Progress { + Available { + image_id: HostPhase2RecoveryImageId, + offset: u64, + total_size: u64, + age: Duration, + }, + None, +} + +/// Identity of a host phase2 recovery image. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub struct HostPhase2RecoveryImageId { + pub sha256_hash: ArtifactHash, +} diff --git a/gateway-types/src/ignition/v2.rs b/gateway-types/migrations/src/v2/ignition.rs similarity index 92% rename from gateway-types/src/ignition/v2.rs rename to gateway-types/migrations/src/v2/ignition.rs index dae87387a57..72c3f337543 100644 --- a/gateway-types/src/ignition/v2.rs +++ b/gateway-types/migrations/src/v2/ignition.rs @@ -2,10 +2,14 @@ // 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/. +//! Ignition types for Gateway API v2. +//! +//! This version added the `Cosmo` variant to `SpIgnitionSystemType`. + use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::component::SpIdentifier; +use crate::v1::component::SpIdentifier; #[derive( Debug, @@ -23,7 +27,7 @@ pub struct SpIgnitionInfo { pub details: SpIgnition, } -impl From for crate::ignition::v1::SpIgnitionInfo { +impl From for crate::v1::ignition::SpIgnitionInfo { fn from(s: SpIgnitionInfo) -> Self { Self { id: s.id, details: s.details.into() } } @@ -89,7 +93,7 @@ impl From for SpIgnition { } } -impl From for crate::ignition::v1::SpIgnition { +impl From for crate::v1::ignition::SpIgnition { fn from(state: SpIgnition) -> Self { match state { SpIgnition::Absent => Self::Absent, @@ -150,7 +154,7 @@ impl From for SpIgnitionSystemType { } } -impl From for crate::ignition::v1::SpIgnitionSystemType { +impl From for crate::v1::ignition::SpIgnitionSystemType { fn from(st: SpIgnitionSystemType) -> Self { match st { SpIgnitionSystemType::Gimlet => Self::Gimlet, diff --git a/gateway-types/migrations/src/v2/mod.rs b/gateway-types/migrations/src/v2/mod.rs new file mode 100644 index 00000000000..1a307b47baf --- /dev/null +++ b/gateway-types/migrations/src/v2/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/. + +//! Types for Gateway API version 2. +//! +//! This version added support for the Cosmo system type in ignition types. + +pub mod ignition; diff --git a/gateway-types/src/caboose.rs b/gateway-types/src/caboose.rs index ef11b9903af..ba42409e7fc 100644 --- a/gateway-types/src/caboose.rs +++ b/gateway-types/src/caboose.rs @@ -2,16 +2,4 @@ // 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, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct SpComponentCaboose { - pub git_commit: String, - pub board: String, - pub name: String, - pub version: String, - pub sign: Option, - pub epoch: Option, -} +pub use gateway_types_migrations::v1::caboose::SpComponentCaboose; diff --git a/gateway-types/src/component.rs b/gateway-types/src/component.rs index 6e3a3e31301..4fcb289b600 100644 --- a/gateway-types/src/component.rs +++ b/gateway-types/src/component.rs @@ -2,315 +2,11 @@ // 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 std::{ - fmt, - str::{self, FromStr}, -}; - -use daft::Diffable; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::rot::RotState; - -#[derive( - Debug, - Clone, - Copy, - PartialEq, - Eq, - Hash, - PartialOrd, - Ord, - Serialize, - Deserialize, - JsonSchema, - Diffable, -)] -#[serde(rename_all = "lowercase")] -#[cfg_attr(any(test, feature = "testing"), derive(test_strategy::Arbitrary))] -pub enum SpType { - Sled, - Power, - Switch, -} - -impl fmt::Display for SpType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SpType::Sled => write!(f, "sled"), - SpType::Power => write!(f, "power"), - SpType::Switch => write!(f, "switch"), - } - } -} - -impl FromStr for SpType { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "sled" => Ok(SpType::Sled), - "power" => Ok(SpType::Power), - "switch" => Ok(SpType::Switch), - _ => Err(format!("invalid SpType: {}", s)), - } - } -} - -#[derive( - Debug, - Clone, - Copy, - PartialEq, - Eq, - PartialOrd, - Ord, - Serialize, - Deserialize, - JsonSchema, -)] -pub struct SpIdentifier { - #[serde(rename = "type")] - pub typ: SpType, - #[serde(deserialize_with = "deserializer_u16_from_string")] - pub slot: u16, -} - -impl fmt::Display for SpIdentifier { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?} {}", self.typ, self.slot) - } -} - -// We can't use the default `Deserialize` derivation for `SpIdentifier::slot` -// because it's embedded in other structs via `serde(flatten)`, which does not -// play well with the way dropshot parses HTTP queries/paths. serde ends up -// trying to deserialize the flattened struct as a map of strings to strings, -// which breaks on `slot` (but not on `typ` for reasons I don't entirely -// understand). We can work around by using an enum that allows either `String` -// or `u16` (which gets us past the serde map of strings), and then parsing the -// string into a u16 ourselves (which gets us to the `slot` we want). More -// background: https://github.com/serde-rs/serde/issues/1346 -fn deserializer_u16_from_string<'de, D>( - deserializer: D, -) -> Result -where - D: serde::Deserializer<'de>, -{ - use serde::de::{self, Unexpected}; - - #[derive(Debug, Deserialize)] - #[serde(untagged)] - enum StringOrU16 { - String(String), - U16(u16), - } - - match StringOrU16::deserialize(deserializer)? { - StringOrU16::String(s) => s - .parse() - .map_err(|_| de::Error::invalid_type(Unexpected::Str(&s), &"u16")), - StringOrU16::U16(n) => Ok(n), - } -} - -#[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Deserialize, - Serialize, - JsonSchema, -)] -#[serde(rename_all = "snake_case")] -pub struct SpState { - pub serial_number: String, - pub model: String, - pub revision: u32, - pub hubris_archive_id: String, - pub base_mac_address: [u8; 6], - pub power_state: PowerState, - pub rot: RotState, -} - -// We expect serial and model numbers to be ASCII and 0-padded: find the first 0 -// byte and convert to a string. If that fails, hexlify the entire slice. -fn stringify_byte_string(bytes: &[u8]) -> String { - let first_zero = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len()); - - str::from_utf8(&bytes[..first_zero]) - .map(|s| s.to_string()) - .unwrap_or_else(|_err| hex::encode(bytes)) -} - -impl From<(gateway_messages::SpStateV1, RotState)> for SpState { - fn from(all: (gateway_messages::SpStateV1, RotState)) -> Self { - let (state, rot) = all; - Self { - serial_number: stringify_byte_string(&state.serial_number), - model: stringify_byte_string(&state.model), - revision: state.revision, - hubris_archive_id: hex::encode(state.hubris_archive_id), - base_mac_address: state.base_mac_address, - power_state: PowerState::from(state.power_state), - rot, - } - } -} - -impl From for SpState { - fn from(state: gateway_messages::SpStateV1) -> Self { - Self::from((state, RotState::from(state.rot))) - } -} - -impl From<(gateway_messages::SpStateV2, RotState)> for SpState { - fn from(all: (gateway_messages::SpStateV2, RotState)) -> Self { - let (state, rot) = all; - Self { - serial_number: stringify_byte_string(&state.serial_number), - model: stringify_byte_string(&state.model), - revision: state.revision, - hubris_archive_id: hex::encode(state.hubris_archive_id), - base_mac_address: state.base_mac_address, - power_state: PowerState::from(state.power_state), - rot, - } - } -} - -impl From for SpState { - fn from(state: gateway_messages::SpStateV2) -> Self { - Self::from((state, RotState::from(state.rot))) - } -} - -impl From<(gateway_messages::SpStateV3, RotState)> for SpState { - fn from(all: (gateway_messages::SpStateV3, RotState)) -> Self { - let (state, rot) = all; - Self { - serial_number: stringify_byte_string(&state.serial_number), - model: stringify_byte_string(&state.model), - revision: state.revision, - hubris_archive_id: hex::encode(state.hubris_archive_id), - base_mac_address: state.base_mac_address, - power_state: PowerState::from(state.power_state), - rot, - } - } -} - -/// See RFD 81. -/// -/// This enum only lists power states the SP is able to control; higher power -/// states are controlled by ignition. -#[derive( - Debug, - Clone, - Copy, - PartialEq, - Eq, - PartialOrd, - Ord, - Serialize, - Deserialize, - JsonSchema, -)] -pub enum PowerState { - A0, - A1, - A2, -} - -impl From for PowerState { - fn from(power_state: gateway_messages::PowerState) -> Self { - match power_state { - gateway_messages::PowerState::A0 => Self::A0, - gateway_messages::PowerState::A1 => Self::A1, - gateway_messages::PowerState::A2 => Self::A2, - } - } -} - -impl From for gateway_messages::PowerState { - fn from(power_state: PowerState) -> Self { - match power_state { - PowerState::A0 => Self::A0, - PowerState::A1 => Self::A1, - PowerState::A2 => Self::A2, - } - } -} - -/// List of components from a single SP. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -pub struct SpComponentList { - pub components: Vec, -} - -/// Overview of a single SP component. -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -pub struct SpComponentInfo { - /// The unique identifier for this component. - pub component: String, - /// The name of the physical device. - pub device: String, - /// The component's serial number, if it has one. - pub serial_number: Option, - /// A human-readable description of the component. - pub description: String, - /// `capabilities` is a bitmask; interpret it via - /// [`gateway_messages::DeviceCapabilities`]. - pub capabilities: u32, - /// Whether or not the component is present, to the best of the SP's ability - /// to judge. - pub presence: SpComponentPresence, -} - -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -/// Description of the presence or absence of a component. -/// -/// The presence of some components may vary based on the power state of the -/// sled (e.g., components that time out or appear unavailable if the sled is in -/// A2 may become present when the sled moves to A0). -pub enum SpComponentPresence { - /// The component is present. - Present, - /// The component is not present. - NotPresent, - /// The component is present but in a failed or faulty state. - Failed, - /// The SP is unable to determine the presence of the component. - Unavailable, - /// The SP's attempt to determine the presence of the component timed out. - Timeout, - /// The SP's attempt to determine the presence of the component failed. - Error, -} - -impl From for SpComponentPresence { - fn from(p: gateway_messages::DevicePresence) -> Self { - match p { - gateway_messages::DevicePresence::Present => Self::Present, - gateway_messages::DevicePresence::NotPresent => Self::NotPresent, - gateway_messages::DevicePresence::Failed => Self::Failed, - gateway_messages::DevicePresence::Unavailable => Self::Unavailable, - gateway_messages::DevicePresence::Timeout => Self::Timeout, - gateway_messages::DevicePresence::Error => Self::Error, - } - } -} - -/// Identifier for an SP's component's firmware slot; e.g., slots 0 and 1 for -/// the host boot flash. -#[derive( - Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -pub struct SpComponentFirmwareSlot { - pub slot: u16, -} +pub use gateway_types_migrations::v1::component::PowerState; +pub use gateway_types_migrations::v1::component::SpComponentFirmwareSlot; +pub use gateway_types_migrations::v1::component::SpComponentInfo; +pub use gateway_types_migrations::v1::component::SpComponentList; +pub use gateway_types_migrations::v1::component::SpComponentPresence; +pub use gateway_types_migrations::v1::component::SpIdentifier; +pub use gateway_types_migrations::v1::component::SpState; +pub use gateway_types_migrations::v1::component::SpType; diff --git a/gateway-types/src/component_details.rs b/gateway-types/src/component_details.rs index bd55c236fda..f20af7482c3 100644 --- a/gateway-types/src/component_details.rs +++ b/gateway-types/src/component_details.rs @@ -2,444 +2,22 @@ // 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::HttpError; -use schemars::JsonSchema; -use serde::Deserialize; -use serde::Serialize; - -// -------------------------------------- -// Monorail port status-related types -// -------------------------------------- - -#[derive(Debug, PartialEq, Serialize, Deserialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum SpComponentDetails { - PortStatus(PortStatus), - PortStatusError(PortStatusError), - Measurement(Measurement), - MeasurementError(MeasurementError), -} - -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -#[serde(rename_all = "snake_case")] -pub struct PortStatusError { - pub port: u32, - pub code: PortStatusErrorCode, -} - -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -#[serde(tag = "code", rename_all = "snake_case")] -pub enum PortStatusErrorCode { - Unconfigured, - Other { raw: u32 }, -} - -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -pub struct PortStatus { - pub port: u32, - pub cfg: PortConfig, - pub link_status: LinkStatus, - pub phy_status: Option, - pub counters: PortCounters, -} - -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -pub struct PortConfig { - pub mode: PortMode, - pub dev_type: PortDev, - pub dev_num: u8, - pub serdes_type: PortSerdes, - pub serdes_num: u8, -} - -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum PortDev { - Dev1g, - Dev2g5, - Dev10g, -} - -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum PortSerdes { - Serdes1g, - Serdes6g, - Serdes10g, -} - -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -#[serde(tag = "speed", rename_all = "snake_case")] -pub enum Speed { - Speed100M, - Speed1G, - Speed10G, -} - -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -#[serde(tag = "mode", rename_all = "snake_case")] -pub enum PortMode { - Sfi, - BaseKr, - Sgmii { speed: Speed }, - Qsgmii { speed: Speed }, -} - -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -pub struct PacketCount { - pub multicast: u32, - pub unicast: u32, - pub broadcast: u32, -} - -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -pub struct PortCounters { - pub rx: PacketCount, - pub tx: PacketCount, - pub link_down_sticky: bool, - pub phy_link_down_sticky: bool, -} - -#[derive( - Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, -)] -#[serde(tag = "status", rename_all = "snake_case")] -pub enum LinkStatus { - Error, - Down, - Up, -} - -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -pub struct PhyStatus { - pub ty: PhyType, - pub mac_link_up: LinkStatus, - pub media_link_up: LinkStatus, -} - -#[derive( - Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, -)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum PhyType { - Vsc8504, - Vsc8522, - Vsc8552, - Vsc8562, -} - -/// Error type for `gateway_messages::ComponentDetails` that are not supported -/// by MGS proper and are only available via `faux-mgs`. -#[derive(Debug, thiserror::Error)] -#[error("unsupported component details: {description}")] -pub struct UnsupportedComponentDetails { - pub description: String, -} - -impl From for HttpError { - fn from(value: UnsupportedComponentDetails) -> Self { - HttpError::for_bad_request( - None, - format!( - "requested component details are not yet supported: {value}" - ), - ) - } -} - -impl TryFrom for SpComponentDetails { - type Error = UnsupportedComponentDetails; - - fn try_from( - details: gateway_messages::ComponentDetails, - ) -> Result { - use gateway_messages::ComponentDetails; - match details { - ComponentDetails::PortStatus(Ok(status)) => { - Ok(Self::PortStatus(status.into())) - } - ComponentDetails::PortStatus(Err(err)) => { - Ok(Self::PortStatusError(err.into())) - } - ComponentDetails::Measurement(m) => Ok(match m.value { - Ok(value) => Self::Measurement(Measurement { - name: m.name, - kind: m.kind.into(), - value, - }), - Err(err) => Self::MeasurementError(MeasurementError { - name: m.name, - kind: m.kind.into(), - error: err.into(), - }), - }), - ComponentDetails::LastPostCode(inner) => { - Err(UnsupportedComponentDetails { - description: format!("last post code: {inner:?}"), - }) - } - ComponentDetails::GpioToggleCount(inner) => { - Err(UnsupportedComponentDetails { - description: format!("GPIO toggle count: {inner:?}"), - }) - } - } - } -} - -impl From for PortStatus { - fn from( - status: gateway_messages::monorail_port_status::PortStatus, - ) -> Self { - Self { - port: status.port, - cfg: status.cfg.into(), - link_status: status.link_status.into(), - phy_status: status.phy_status.map(Into::into), - counters: status.counters.into(), - } - } -} - -impl From for PortConfig { - fn from(cfg: gateway_messages::monorail_port_status::PortConfig) -> Self { - Self { - mode: cfg.mode.into(), - dev_type: cfg.dev.0.into(), - dev_num: cfg.dev.1, - serdes_type: cfg.serdes.0.into(), - serdes_num: cfg.serdes.1, - } - } -} - -impl From for PortMode { - fn from(mode: gateway_messages::monorail_port_status::PortMode) -> Self { - use gateway_messages::monorail_port_status::PortMode; - match mode { - PortMode::Sfi => Self::Sfi, - PortMode::BaseKr => Self::BaseKr, - PortMode::Sgmii(s) => Self::Sgmii { speed: s.into() }, - PortMode::Qsgmii(s) => Self::Qsgmii { speed: s.into() }, - } - } -} - -impl From for Speed { - fn from(speed: gateway_messages::monorail_port_status::Speed) -> Self { - use gateway_messages::monorail_port_status::Speed; - match speed { - Speed::Speed100M => Self::Speed100M, - Speed::Speed1G => Self::Speed1G, - Speed::Speed10G => Self::Speed10G, - } - } -} - -impl From for PortDev { - fn from(dev: gateway_messages::monorail_port_status::PortDev) -> Self { - use gateway_messages::monorail_port_status::PortDev; - match dev { - PortDev::Dev1g => Self::Dev1g, - PortDev::Dev2g5 => Self::Dev2g5, - PortDev::Dev10g => Self::Dev10g, - } - } -} - -impl From for PortSerdes { - fn from( - serdes: gateway_messages::monorail_port_status::PortSerdes, - ) -> Self { - use gateway_messages::monorail_port_status::PortSerdes; - match serdes { - PortSerdes::Serdes1g => Self::Serdes1g, - PortSerdes::Serdes6g => Self::Serdes6g, - PortSerdes::Serdes10g => Self::Serdes10g, - } - } -} - -impl From for LinkStatus { - fn from( - status: gateway_messages::monorail_port_status::LinkStatus, - ) -> Self { - use gateway_messages::monorail_port_status::LinkStatus; - match status { - LinkStatus::Error => Self::Error, - LinkStatus::Down => Self::Down, - LinkStatus::Up => Self::Up, - } - } -} - -impl From for PhyStatus { - fn from(status: gateway_messages::monorail_port_status::PhyStatus) -> Self { - Self { - ty: status.ty.into(), - mac_link_up: status.mac_link_up.into(), - media_link_up: status.media_link_up.into(), - } - } -} - -impl From for PhyType { - fn from(ty: gateway_messages::monorail_port_status::PhyType) -> Self { - use gateway_messages::monorail_port_status::PhyType; - match ty { - PhyType::Vsc8504 => Self::Vsc8504, - PhyType::Vsc8522 => Self::Vsc8522, - PhyType::Vsc8552 => Self::Vsc8552, - PhyType::Vsc8562 => Self::Vsc8562, - } - } -} - -impl From - for PortCounters -{ - fn from(c: gateway_messages::monorail_port_status::PortCounters) -> Self { - Self { - rx: c.rx.into(), - tx: c.tx.into(), - link_down_sticky: c.link_down_sticky, - phy_link_down_sticky: c.phy_link_down_sticky, - } - } -} - -impl From for PacketCount { - fn from(c: gateway_messages::monorail_port_status::PacketCount) -> Self { - Self { - multicast: c.multicast, - unicast: c.unicast, - broadcast: c.broadcast, - } - } -} - -impl From - for PortStatusError -{ - fn from( - err: gateway_messages::monorail_port_status::PortStatusError, - ) -> Self { - Self { port: err.port, code: err.code.into() } - } -} - -impl From - for PortStatusErrorCode -{ - fn from( - err: gateway_messages::monorail_port_status::PortStatusErrorCode, - ) -> Self { - use gateway_messages::monorail_port_status::PortStatusErrorCode; - match err { - PortStatusErrorCode::Unconfigured => Self::Unconfigured, - PortStatusErrorCode::Other(raw) => Self::Other { raw }, - } - } -} - -// -------------------------------------- -// Measurement-related types -// -------------------------------------- - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct Measurement { - pub name: String, - pub kind: MeasurementKind, - pub value: f32, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct MeasurementError { - pub name: String, - pub kind: MeasurementKind, - pub error: MeasurementErrorCode, -} - -#[derive( - Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema, -)] -#[serde(tag = "code", rename_all = "snake_case")] -pub enum MeasurementErrorCode { - InvalidSensor, - NoReading, - NotPresent, - DeviceError, - DeviceUnavailable, - DeviceTimeout, - DeviceOff, -} - -#[derive( - Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -#[serde(tag = "kind", rename_all = "snake_case")] -pub enum MeasurementKind { - Temperature, - Power, - Current, - Voltage, - InputCurrent, - InputVoltage, - Speed, - CpuTctl, -} - -impl From for MeasurementKind { - fn from(kind: gateway_messages::measurement::MeasurementKind) -> Self { - use gateway_messages::measurement::MeasurementKind; - match kind { - MeasurementKind::Temperature => Self::Temperature, - MeasurementKind::Power => Self::Power, - MeasurementKind::Current => Self::Current, - MeasurementKind::Voltage => Self::Voltage, - MeasurementKind::InputCurrent => Self::InputCurrent, - MeasurementKind::InputVoltage => Self::InputVoltage, - MeasurementKind::Speed => Self::Speed, - MeasurementKind::CpuTctl => Self::CpuTctl, - } - } -} - -impl From - for MeasurementErrorCode -{ - fn from(err: gateway_messages::measurement::MeasurementError) -> Self { - use gateway_messages::measurement::MeasurementError; - match err { - MeasurementError::InvalidSensor => Self::InvalidSensor, - MeasurementError::NoReading => Self::NoReading, - MeasurementError::NotPresent => Self::NotPresent, - MeasurementError::DeviceError => Self::DeviceError, - MeasurementError::DeviceUnavailable => Self::DeviceUnavailable, - MeasurementError::DeviceTimeout => Self::DeviceTimeout, - MeasurementError::DeviceOff => Self::DeviceOff, - } - } -} +pub use gateway_types_migrations::v1::component_details::LinkStatus; +pub use gateway_types_migrations::v1::component_details::Measurement; +pub use gateway_types_migrations::v1::component_details::MeasurementError; +pub use gateway_types_migrations::v1::component_details::MeasurementErrorCode; +pub use gateway_types_migrations::v1::component_details::MeasurementKind; +pub use gateway_types_migrations::v1::component_details::PacketCount; +pub use gateway_types_migrations::v1::component_details::PhyStatus; +pub use gateway_types_migrations::v1::component_details::PhyType; +pub use gateway_types_migrations::v1::component_details::PortConfig; +pub use gateway_types_migrations::v1::component_details::PortCounters; +pub use gateway_types_migrations::v1::component_details::PortDev; +pub use gateway_types_migrations::v1::component_details::PortMode; +pub use gateway_types_migrations::v1::component_details::PortSerdes; +pub use gateway_types_migrations::v1::component_details::PortStatus; +pub use gateway_types_migrations::v1::component_details::PortStatusError; +pub use gateway_types_migrations::v1::component_details::PortStatusErrorCode; +pub use gateway_types_migrations::v1::component_details::SpComponentDetails; +pub use gateway_types_migrations::v1::component_details::Speed; +pub use gateway_types_migrations::v1::component_details::UnsupportedComponentDetails; diff --git a/gateway-types/src/host.rs b/gateway-types/src/host.rs index c6bf9d30920..7308fba31bf 100644 --- a/gateway-types/src/host.rs +++ b/gateway-types/src/host.rs @@ -2,72 +2,5 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use gateway_messages::StartupOptions; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive( - Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -pub struct HostStartupOptions { - pub phase2_recovery_mode: bool, - pub kbm: bool, - pub bootrd: bool, - pub prom: bool, - pub kmdb: bool, - pub kmdb_boot: bool, - pub boot_ramdisk: bool, - pub boot_net: bool, - pub verbose: bool, -} - -impl From for StartupOptions { - fn from(mgs_opt: HostStartupOptions) -> Self { - let mut opt = StartupOptions::empty(); - opt.set( - StartupOptions::PHASE2_RECOVERY_MODE, - mgs_opt.phase2_recovery_mode, - ); - opt.set(StartupOptions::STARTUP_KBM, mgs_opt.kbm); - opt.set(StartupOptions::STARTUP_BOOTRD, mgs_opt.bootrd); - opt.set(StartupOptions::STARTUP_PROM, mgs_opt.prom); - opt.set(StartupOptions::STARTUP_KMDB, mgs_opt.kmdb); - opt.set(StartupOptions::STARTUP_KMDB_BOOT, mgs_opt.kmdb_boot); - opt.set(StartupOptions::STARTUP_BOOT_RAMDISK, mgs_opt.boot_ramdisk); - opt.set(StartupOptions::STARTUP_BOOT_NET, mgs_opt.boot_net); - opt.set(StartupOptions::STARTUP_VERBOSE, mgs_opt.verbose); - opt - } -} - -impl From for HostStartupOptions { - fn from(opt: StartupOptions) -> Self { - Self { - phase2_recovery_mode: opt - .contains(StartupOptions::PHASE2_RECOVERY_MODE), - kbm: opt.contains(StartupOptions::STARTUP_KBM), - bootrd: opt.contains(StartupOptions::STARTUP_BOOTRD), - prom: opt.contains(StartupOptions::STARTUP_PROM), - kmdb: opt.contains(StartupOptions::STARTUP_KMDB), - kmdb_boot: opt.contains(StartupOptions::STARTUP_KMDB_BOOT), - boot_ramdisk: opt.contains(StartupOptions::STARTUP_BOOT_RAMDISK), - boot_net: opt.contains(StartupOptions::STARTUP_BOOT_NET), - verbose: opt.contains(StartupOptions::STARTUP_VERBOSE), - } - } -} - -#[derive(Serialize, Deserialize, JsonSchema)] -#[serde(tag = "status", rename_all = "snake_case")] -pub enum ComponentFirmwareHashStatus { - /// The hash is not available; the client must issue a separate request to - /// begin calculating the hash. - HashNotCalculated, - /// The hash is currently being calculated; the client should sleep briefly - /// then check again. - /// - /// We expect this operation to take a handful of seconds in practice. - HashInProgress, - /// The hash of the given firmware slot. - Hashed { sha256: [u8; 32] }, -} +pub use gateway_types_migrations::v1::host::ComponentFirmwareHashStatus; +pub use gateway_types_migrations::v1::host::HostStartupOptions; diff --git a/gateway-types/src/ignition.rs b/gateway-types/src/ignition.rs new file mode 100644 index 00000000000..16e2e6e01e2 --- /dev/null +++ b/gateway-types/src/ignition.rs @@ -0,0 +1,8 @@ +// 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_migrations::v1::ignition::IgnitionCommand; +pub use gateway_types_migrations::v2::ignition::SpIgnition; +pub use gateway_types_migrations::v2::ignition::SpIgnitionInfo; +pub use gateway_types_migrations::v2::ignition::SpIgnitionSystemType; diff --git a/gateway-types/src/ignition/mod.rs b/gateway-types/src/ignition/mod.rs deleted file mode 100644 index 57b578730b2..00000000000 --- a/gateway-types/src/ignition/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -// 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}; - -pub mod v1; -pub mod v2; - -pub use v2::*; - -/// Ignition command. -#[derive( - Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema, -)] -#[serde(rename_all = "snake_case")] -pub enum IgnitionCommand { - PowerOn, - PowerOff, - PowerReset, -} - -impl From for gateway_messages::IgnitionCommand { - fn from(cmd: IgnitionCommand) -> Self { - match cmd { - IgnitionCommand::PowerOn => { - gateway_messages::IgnitionCommand::PowerOn - } - IgnitionCommand::PowerOff => { - gateway_messages::IgnitionCommand::PowerOff - } - IgnitionCommand::PowerReset => { - gateway_messages::IgnitionCommand::PowerReset - } - } - } -} diff --git a/gateway-types/src/lib.rs b/gateway-types/src/lib.rs index 149cd1d8f04..8392fc78747 100644 --- a/gateway-types/src/lib.rs +++ b/gateway-types/src/lib.rs @@ -3,6 +3,13 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. //! Common types for MGS. +//! +//! This crate re-exports the latest versions of all types from the +//! `gateway-types-migrations` crate. These are floating identifiers that should +//! be used by business logic that doesn't need to care about API versioning. +//! +//! The API crate (`gateway-api`) uses fixed identifiers from the migrations +//! crate directly. pub mod caboose; pub mod component; diff --git a/gateway-types/src/rot.rs b/gateway-types/src/rot.rs index faa2b8b1e5d..7c24a8e8d53 100644 --- a/gateway-types/src/rot.rs +++ b/gateway-types/src/rot.rs @@ -2,402 +2,11 @@ // 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 daft::Diffable; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::fmt::Display; -use std::str::FromStr; - -#[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Deserialize, - Serialize, - JsonSchema, -)] -#[serde(tag = "state", rename_all = "snake_case")] -pub enum RotState { - V2 { - active: RotSlot, - persistent_boot_preference: RotSlot, - pending_persistent_boot_preference: Option, - transient_boot_preference: Option, - slot_a_sha3_256_digest: Option, - slot_b_sha3_256_digest: Option, - }, - CommunicationFailed { - message: String, - }, - V3 { - active: RotSlot, - persistent_boot_preference: RotSlot, - pending_persistent_boot_preference: Option, - transient_boot_preference: Option, - - slot_a_fwid: String, - slot_b_fwid: String, - stage0_fwid: String, - stage0next_fwid: String, - - slot_a_error: Option, - slot_b_error: Option, - stage0_error: Option, - stage0next_error: Option, - }, -} - -impl RotState { - fn fwid_to_string(fwid: gateway_messages::Fwid) -> String { - match fwid { - gateway_messages::Fwid::Sha3_256(digest) => hex::encode(digest), - } - } -} - -impl From> - for RotState -{ - fn from( - result: Result, - ) -> Self { - match result { - Ok(state) => Self::from(state), - Err(err) => Self::CommunicationFailed { message: err.to_string() }, - } - } -} - -impl From> - for RotState -{ - fn from( - result: Result< - gateway_messages::RotStateV2, - gateway_messages::RotError, - >, - ) -> Self { - match result { - Ok(state) => Self::from(state), - Err(err) => Self::CommunicationFailed { message: err.to_string() }, - } - } -} - -impl From for RotState { - fn from(state: gateway_messages::RotState) -> Self { - let boot_state = state.rot_updates.boot_state; - Self::V2 { - active: boot_state.active.into(), - slot_a_sha3_256_digest: boot_state - .slot_a - .map(|details| hex::encode(details.digest)), - slot_b_sha3_256_digest: boot_state - .slot_b - .map(|details| hex::encode(details.digest)), - // RotState(V1) didn't have the following fields, so we make - // it up as best we can. This RoT version is pre-shipping - // and should only exist on (not updated recently) test - // systems. - persistent_boot_preference: boot_state.active.into(), - pending_persistent_boot_preference: None, - transient_boot_preference: None, - } - } -} - -impl From for RotState { - fn from(state: gateway_messages::RotStateV2) -> Self { - Self::V2 { - active: state.active.into(), - persistent_boot_preference: state.persistent_boot_preference.into(), - pending_persistent_boot_preference: state - .pending_persistent_boot_preference - .map(Into::into), - transient_boot_preference: state - .transient_boot_preference - .map(Into::into), - slot_a_sha3_256_digest: state - .slot_a_sha3_256_digest - .map(hex::encode), - slot_b_sha3_256_digest: state - .slot_b_sha3_256_digest - .map(hex::encode), - } - } -} - -impl From for RotState { - fn from(state: gateway_messages::RotStateV3) -> Self { - Self::V3 { - active: state.active.into(), - persistent_boot_preference: state.persistent_boot_preference.into(), - pending_persistent_boot_preference: state - .pending_persistent_boot_preference - .map(Into::into), - transient_boot_preference: state - .transient_boot_preference - .map(Into::into), - slot_a_fwid: Self::fwid_to_string(state.slot_a_fwid), - slot_b_fwid: Self::fwid_to_string(state.slot_b_fwid), - - stage0_fwid: Self::fwid_to_string(state.stage0_fwid), - stage0next_fwid: Self::fwid_to_string(state.stage0next_fwid), - - slot_a_error: state.slot_a_status.err().map(From::from), - slot_b_error: state.slot_b_status.err().map(From::from), - - stage0_error: state.stage0_status.err().map(From::from), - stage0next_error: state.stage0next_status.err().map(From::from), - } - } -} - -impl From for RotState { - fn from(value: gateway_messages::RotBootInfo) -> Self { - match value { - gateway_messages::RotBootInfo::V1(s) => Self::from(s), - gateway_messages::RotBootInfo::V2(s) => Self::from(s), - gateway_messages::RotBootInfo::V3(s) => Self::from(s), - } - } -} - -#[derive( - Debug, - Diffable, - Clone, - Copy, - PartialEq, - Eq, - PartialOrd, - Ord, - Deserialize, - Serialize, - JsonSchema, -)] -#[serde(tag = "slot", rename_all = "snake_case")] -#[cfg_attr(any(test, feature = "testing"), derive(test_strategy::Arbitrary))] -pub enum RotSlot { - A, - B, -} - -impl RotSlot { - pub fn to_u16(&self) -> u16 { - match self { - RotSlot::A => 0, - RotSlot::B => 1, - } - } - - pub fn toggled(&self) -> Self { - match self { - RotSlot::A => RotSlot::B, - RotSlot::B => RotSlot::A, - } - } -} - -impl Display for RotSlot { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let s = match self { - RotSlot::A => "A", - RotSlot::B => "B", - }; - write!(f, "{s}") - } -} - -impl FromStr for RotSlot { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "a" | "A" => Ok(RotSlot::A), - "b" | "B" => Ok(RotSlot::B), - _ => Err(format!( - "unrecognized value {} for RoT slot. \ - Must be one of `a`, `A`, `b`, or `B`", - s - )), - } - } -} - -impl From for RotSlot { - fn from(slot: gateway_messages::RotSlotId) -> Self { - match slot { - gateway_messages::RotSlotId::A => Self::A, - gateway_messages::RotSlotId::B => Self::B, - } - } -} - -#[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Deserialize, - Serialize, - JsonSchema, -)] -pub struct RotImageDetails { - pub digest: String, - pub version: ImageVersion, -} - -impl From for RotImageDetails { - fn from(details: gateway_messages::RotImageDetails) -> Self { - Self { - digest: hex::encode(details.digest), - version: details.version.into(), - } - } -} - -#[derive( - Debug, - Clone, - Copy, - PartialEq, - Eq, - PartialOrd, - Ord, - Serialize, - Deserialize, - JsonSchema, -)] -pub struct ImageVersion { - pub epoch: u32, - pub version: u32, -} - -impl From for ImageVersion { - fn from(v: gateway_messages::ImageVersion) -> Self { - Self { epoch: v.epoch, version: v.version } - } -} - -#[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Deserialize, - Serialize, - JsonSchema, -)] -#[serde(rename_all = "snake_case")] -pub enum RotImageError { - Unchecked, - FirstPageErased, - PartiallyProgrammed, - InvalidLength, - HeaderNotProgrammed, - BootloaderTooSmall, - BadMagic, - HeaderImageSize, - UnalignedLength, - UnsupportedType, - ResetVectorNotThumb2, - ResetVector, - Signature, -} - -impl From for RotImageError { - fn from(error: gateway_messages::ImageError) -> Self { - match error { - gateway_messages::ImageError::Unchecked => RotImageError::Unchecked, - gateway_messages::ImageError::FirstPageErased => { - RotImageError::FirstPageErased - } - gateway_messages::ImageError::PartiallyProgrammed => { - RotImageError::PartiallyProgrammed - } - gateway_messages::ImageError::InvalidLength => { - RotImageError::InvalidLength - } - gateway_messages::ImageError::HeaderNotProgrammed => { - RotImageError::HeaderNotProgrammed - } - gateway_messages::ImageError::BootloaderTooSmall => { - RotImageError::BootloaderTooSmall - } - gateway_messages::ImageError::BadMagic => RotImageError::BadMagic, - gateway_messages::ImageError::HeaderImageSize => { - RotImageError::HeaderImageSize - } - gateway_messages::ImageError::UnalignedLength => { - RotImageError::UnalignedLength - } - gateway_messages::ImageError::UnsupportedType => { - RotImageError::UnsupportedType - } - gateway_messages::ImageError::ResetVectorNotThumb2 => { - RotImageError::ResetVectorNotThumb2 - } - gateway_messages::ImageError::ResetVector => { - RotImageError::ResetVector - } - gateway_messages::ImageError::Signature => RotImageError::Signature, - } - } -} - -#[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Deserialize, - Serialize, - JsonSchema, -)] -pub struct RotCmpa { - pub base64_data: String, -} - -#[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Deserialize, - Serialize, - JsonSchema, -)] -#[serde(tag = "slot", rename_all = "snake_case")] -pub enum RotCfpaSlot { - Active, - Inactive, - Scratch, -} - -#[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Deserialize, - Serialize, - JsonSchema, -)] -pub struct RotCfpa { - pub base64_data: String, - pub slot: RotCfpaSlot, -} +pub use gateway_types_migrations::v1::rot::ImageVersion; +pub use gateway_types_migrations::v1::rot::RotCfpa; +pub use gateway_types_migrations::v1::rot::RotCfpaSlot; +pub use gateway_types_migrations::v1::rot::RotCmpa; +pub use gateway_types_migrations::v1::rot::RotImageDetails; +pub use gateway_types_migrations::v1::rot::RotImageError; +pub use gateway_types_migrations::v1::rot::RotSlot; +pub use gateway_types_migrations::v1::rot::RotState; diff --git a/gateway-types/src/sensor.rs b/gateway-types/src/sensor.rs index 59ae9e9542b..eb77d137236 100644 --- a/gateway-types/src/sensor.rs +++ b/gateway-types/src/sensor.rs @@ -2,73 +2,5 @@ // 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}; - -/// Result of reading an SP sensor. -#[derive( - Debug, - Clone, - Copy, - PartialEq, - PartialOrd, - Serialize, - Deserialize, - JsonSchema, -)] -pub struct SpSensorReading { - /// SP-centric timestamp of when `result` was recorded from this sensor. - /// - /// Currently this value represents "milliseconds since the last SP boot" - /// and is primarily useful as a delta between sensors on this SP (assuming - /// no reboot in between). The meaning could change with future SP releases. - pub timestamp: u64, - /// Value (or error) from the sensor. - pub result: SpSensorReadingResult, -} - -impl From for SpSensorReading { - fn from(value: gateway_messages::SensorReading) -> Self { - Self { - timestamp: value.timestamp, - result: match value.value { - Ok(value) => SpSensorReadingResult::Success { value }, - Err(err) => err.into(), - }, - } - } -} - -/// Single reading (or error) from an SP sensor. -#[derive( - Debug, - Clone, - Copy, - PartialEq, - PartialOrd, - Deserialize, - Serialize, - JsonSchema, -)] -#[serde(tag = "kind", rename_all = "snake_case")] -pub enum SpSensorReadingResult { - Success { value: f32 }, - DeviceOff, - DeviceError, - DeviceNotPresent, - DeviceUnavailable, - DeviceTimeout, -} - -impl From for SpSensorReadingResult { - fn from(value: gateway_messages::SensorDataMissing) -> Self { - use gateway_messages::SensorDataMissing; - match value { - SensorDataMissing::DeviceOff => Self::DeviceOff, - SensorDataMissing::DeviceError => Self::DeviceError, - SensorDataMissing::DeviceNotPresent => Self::DeviceNotPresent, - SensorDataMissing::DeviceUnavailable => Self::DeviceUnavailable, - SensorDataMissing::DeviceTimeout => Self::DeviceTimeout, - } - } -} +pub use gateway_types_migrations::v1::sensor::SpSensorReading; +pub use gateway_types_migrations::v1::sensor::SpSensorReadingResult; diff --git a/gateway-types/src/task_dump.rs b/gateway-types/src/task_dump.rs index ef525ed3155..9838614873d 100644 --- a/gateway-types/src/task_dump.rs +++ b/gateway-types/src/task_dump.rs @@ -2,33 +2,4 @@ // 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, -)] -pub struct TaskDump { - /// Index of the crashed task. - pub task_index: u16, - /// Hubris timestamp at which the task crash occurred. - pub timestamp: u64, - /// Hex-encoded Hubris archive ID. - pub archive_id: String, - /// `BORD` field from the caboose. - pub bord: String, - /// `GITC` field from the caboose. - pub gitc: String, - /// `VERS` field from the caboose, if present. - pub vers: Option, - /// Base64-encoded zip file containing dehydrated task dump. - pub base64_zip: String, -} +pub use gateway_types_migrations::v1::task_dump::TaskDump; diff --git a/gateway-types/src/update.rs b/gateway-types/src/update.rs index 62cf7ea6239..d14eef83a90 100644 --- a/gateway-types/src/update.rs +++ b/gateway-types/src/update.rs @@ -2,170 +2,9 @@ // 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::{ErrorStatusCode, HttpError, HttpResponseError}; -use gateway_messages::UpdateStatus; -use omicron_uuid_kinds::MupdateUuid; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::time::Duration; -use tufaceous_artifact::ArtifactHash; -use uuid::Uuid; - -/// The error type returned by the `sp_component_reset()` MGS endpoint. -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, JsonSchema, thiserror::Error, -)] -#[serde(tag = "state", rename_all = "snake_case")] -pub enum SpComponentResetError { - /// MGS refuses to reset its own sled's SP. - #[error("cannot reset SP of the sled hosting MGS")] - ResetSpOfLocalSled, - - /// Other dropshot errors. - #[error("{internal_message}")] - Other { - message: String, - error_code: Option, - - // Skip serializing these fields, as they are used for the - // `fmt::Display` implementation and for determining the status code, - // respectively, rather than included in the response body: - #[serde(skip)] - internal_message: String, - #[serde(skip)] - status: ErrorStatusCode, - }, -} - -impl HttpResponseError for SpComponentResetError { - fn status_code(&self) -> ErrorStatusCode { - match self { - SpComponentResetError::ResetSpOfLocalSled => { - ErrorStatusCode::BAD_REQUEST - } - SpComponentResetError::Other { status, .. } => *status, - } - } -} - -impl From for SpComponentResetError { - fn from(err: HttpError) -> Self { - Self::Other { - message: err.external_message, - error_code: err.error_code, - internal_message: err.internal_message, - status: err.status_code, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "state", rename_all = "snake_case")] -pub enum SpUpdateStatus { - /// The SP has no update status. - None, - /// The SP is preparing to receive an update. - /// - /// May or may not include progress, depending on the capabilities of the - /// component being updated. - Preparing { id: Uuid, progress: Option }, - /// The SP is currently receiving an update. - InProgress { id: Uuid, bytes_received: u32, total_bytes: u32 }, - /// The SP has completed receiving an update. - Complete { id: Uuid }, - /// The SP has aborted an in-progress update. - Aborted { id: Uuid }, - /// The update process failed. - Failed { id: Uuid, code: u32 }, - /// The update process failed with an RoT-specific error. - RotError { id: Uuid, message: String }, -} - -impl From for SpUpdateStatus { - fn from(status: UpdateStatus) -> Self { - match status { - UpdateStatus::None => Self::None, - UpdateStatus::Preparing(status) => Self::Preparing { - id: status.id.into(), - progress: status.progress.map(Into::into), - }, - UpdateStatus::SpUpdateAuxFlashChckScan { - id, total_size, .. - } => Self::InProgress { - id: id.into(), - bytes_received: 0, - total_bytes: total_size, - }, - UpdateStatus::InProgress(status) => Self::InProgress { - id: status.id.into(), - bytes_received: status.bytes_received, - total_bytes: status.total_size, - }, - UpdateStatus::Complete(id) => Self::Complete { id: id.into() }, - UpdateStatus::Aborted(id) => Self::Aborted { id: id.into() }, - UpdateStatus::Failed { id, code } => { - Self::Failed { id: id.into(), code } - } - UpdateStatus::RotError { id, error } => { - Self::RotError { id: id.into(), message: format!("{error:?}") } - } - } - } -} - -/// Progress of an SP preparing to update. -/// -/// The units of `current` and `total` are unspecified and defined by the SP; -/// e.g., if preparing for an update requires erasing a flash device, this may -/// indicate progress of that erasure without defining units (bytes, pages, -/// sectors, etc.). -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] -pub struct UpdatePreparationProgress { - pub current: u32, - pub total: u32, -} - -impl From - for UpdatePreparationProgress -{ - fn from(progress: gateway_messages::UpdatePreparationProgress) -> Self { - Self { current: progress.current, total: progress.total } - } -} - -// This type is a duplicate of the type in `ipcc`. We keep these types distinct -// to allow us to choose different representations for MGS's HTTP API (this -// type) and the wire format passed through the SP to installinator -// (`ipcc::InstallinatorImageId`), although _currently_ they happen to be -// defined identically. -// -// We don't define a conversion from `Self` to `ipcc::InstallinatorImageId` here -// to avoid a dependency on `libipcc`. Instead, callers can easily perform -// conversions themselves. -#[derive( - Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, -)] -#[serde(rename_all = "snake_case")] -pub struct InstallinatorImageId { - pub update_id: MupdateUuid, - pub host_phase_2: ArtifactHash, - pub control_plane: ArtifactHash, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -#[serde(tag = "progress", rename_all = "snake_case")] -pub enum HostPhase2Progress { - Available { - image_id: HostPhase2RecoveryImageId, - offset: u64, - total_size: u64, - age: Duration, - }, - None, -} - -/// Identity of a host phase2 recovery image. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] -pub struct HostPhase2RecoveryImageId { - pub sha256_hash: ArtifactHash, -} +pub use gateway_types_migrations::v1::update::HostPhase2Progress; +pub use gateway_types_migrations::v1::update::HostPhase2RecoveryImageId; +pub use gateway_types_migrations::v1::update::InstallinatorImageId; +pub use gateway_types_migrations::v1::update::SpComponentResetError; +pub use gateway_types_migrations::v1::update::SpUpdateStatus; +pub use gateway_types_migrations::v1::update::UpdatePreparationProgress; diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index ece16b8b734..25f5eea8862 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -20,6 +20,7 @@ gateway-api.workspace = true gateway-messages.workspace = true gateway-sp-comms.workspace = true gateway-types.workspace = true +gateway-types-migrations.workspace = true hex.workspace = true http.workspace = true hyper.workspace = true diff --git a/gateway/src/http_entrypoints.rs b/gateway/src/http_entrypoints.rs index 3fe8861d009..451e9840149 100644 --- a/gateway/src/http_entrypoints.rs +++ b/gateway/src/http_entrypoints.rs @@ -40,8 +40,6 @@ use gateway_types::component::SpState; use gateway_types::component_details::SpComponentDetails; use gateway_types::host::ComponentFirmwareHashStatus; use gateway_types::host::HostStartupOptions; -use gateway_types::ignition; -use gateway_types::ignition::SpIgnitionInfo; use gateway_types::rot::RotCfpa; use gateway_types::rot::RotCfpaSlot; use gateway_types::rot::RotCmpa; @@ -53,6 +51,20 @@ use gateway_types::update::HostPhase2RecoveryImageId; use gateway_types::update::InstallinatorImageId; use gateway_types::update::SpComponentResetError; use gateway_types::update::SpUpdateStatus; +use gateway_types_migrations::v1::ignition::SpIgnitionInfo as SpIgnitionInfoV1; +use gateway_types_migrations::v1::params::ComponentCabooseSlot; +use gateway_types_migrations::v1::params::ComponentUpdateIdSlot; +use gateway_types_migrations::v1::params::GetCfpaParams; +use gateway_types_migrations::v1::params::GetRotBootInfoParams; +use gateway_types_migrations::v1::params::PathSp; +use gateway_types_migrations::v1::params::PathSpComponent; +use gateway_types_migrations::v1::params::PathSpComponentFirmwareSlot; +use gateway_types_migrations::v1::params::PathSpIgnitionCommand; +use gateway_types_migrations::v1::params::PathSpSensorId; +use gateway_types_migrations::v1::params::PathSpTaskDumpIndex; +use gateway_types_migrations::v1::params::SetComponentActiveSlotParams; +use gateway_types_migrations::v1::params::UpdateAbortBody; +use gateway_types_migrations::v2::ignition::SpIgnitionInfo as SpIgnitionInfoV2; use omicron_uuid_kinds::GenericUuid; use std::io::Cursor; use std::num::NonZeroU8; @@ -832,27 +844,23 @@ impl GatewayApi for GatewayImpl { async fn ignition_list_v1( rqctx: RequestContext, - ) -> Result>, HttpError> - { + ) -> Result>, HttpError> { let HttpResponseOk(v2_info) = Self::ignition_list(rqctx).await?; Ok(HttpResponseOk( - v2_info - .into_iter() - .map(|x| ignition::v1::SpIgnitionInfo::from(x)) - .collect(), + v2_info.into_iter().map(|x| SpIgnitionInfoV1::from(x)).collect(), )) } async fn ignition_list( rqctx: RequestContext, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> { let apictx = rqctx.context(); let mgmt_switch = &apictx.mgmt_switch; let handler = async { let out = mgmt_switch .bulk_ignition_state() .await? - .map(|(id, state)| SpIgnitionInfo { + .map(|(id, state)| SpIgnitionInfoV2 { id: id.into(), details: state.into(), }) @@ -866,15 +874,15 @@ impl GatewayApi for GatewayImpl { async fn ignition_get_v1( rqctx: RequestContext, path: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let HttpResponseOk(v2_info) = Self::ignition_get(rqctx, path).await?; - Ok(HttpResponseOk(ignition::v1::SpIgnitionInfo::from(v2_info))) + Ok(HttpResponseOk(SpIgnitionInfoV1::from(v2_info))) } async fn ignition_get( rqctx: RequestContext, path: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let apictx = rqctx.context(); let mgmt_switch = &apictx.mgmt_switch; @@ -892,7 +900,7 @@ impl GatewayApi for GatewayImpl { })?; let info = - SpIgnitionInfo { id: sp_id.into(), details: state.into() }; + SpIgnitionInfoV2 { id: sp_id.into(), details: state.into() }; Ok(HttpResponseOk(info)) }; apictx.latencies.instrument_dropshot_handler(&rqctx, handler).await From 15563ae449b39b2ffeec7d1abaf6544bf7592ad5 Mon Sep 17 00:00:00 2001 From: Rain Date: Tue, 9 Dec 2025 05:42:38 +0000 Subject: [PATCH 2/2] rustfmt Created using spr 1.3.6-beta.1 --- gateway-api/src/lib.rs | 5 +- gateway/src/http_entrypoints.rs | 88 +++++++++++++++++++++++---------- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/gateway-api/src/lib.rs b/gateway-api/src/lib.rs index 39382a16c18..efbe1bbc277 100644 --- a/gateway-api/src/lib.rs +++ b/gateway-api/src/lib.rs @@ -125,7 +125,10 @@ pub trait GatewayApi { async fn sp_component_get( rqctx: RequestContext, path: Path, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Get the caboose of an SP component /// diff --git a/gateway/src/http_entrypoints.rs b/gateway/src/http_entrypoints.rs index 78319681ad3..e0a22fbad54 100644 --- a/gateway/src/http_entrypoints.rs +++ b/gateway/src/http_entrypoints.rs @@ -170,7 +170,10 @@ impl GatewayApi for GatewayImpl { async fn sp_component_get( rqctx: RequestContext, path: Path, - ) -> Result>, HttpError> { + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let v1::params::PathSpComponent { sp, component } = path.into_inner(); let sp_id = sp.into(); @@ -208,7 +211,8 @@ impl GatewayApi for GatewayImpl { rqctx: RequestContext, path: Path, query_params: Query, - ) -> Result, HttpError> { + ) -> Result, HttpError> + { const CABOOSE_KEY_GIT_COMMIT: [u8; 4] = *b"GITC"; const CABOOSE_KEY_BOARD: [u8; 4] = *b"BORD"; const CABOOSE_KEY_NAME: [u8; 4] = *b"NAME"; @@ -353,7 +357,8 @@ impl GatewayApi for GatewayImpl { async fn sp_component_active_slot_get( rqctx: RequestContext, path: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> + { let apictx = rqctx.context(); let v1::params::PathSpComponent { sp, component } = path.into_inner(); let sp_id = sp.into(); @@ -440,7 +445,8 @@ impl GatewayApi for GatewayImpl { // TODO-cleanup: "component" support for the serial console is half baked; // we don't use it at all to detach. - let v1::params::PathSpComponent { sp, component: _ } = path.into_inner(); + let v1::params::PathSpComponent { sp, component: _ } = + path.into_inner(); let sp_id = sp.into(); let handler = async { let sp = apictx.mgmt_switch.sp(sp_id)?; @@ -456,7 +462,8 @@ impl GatewayApi for GatewayImpl { async fn sp_component_reset( rqctx: RequestContext, path: Path, - ) -> Result { + ) -> Result + { let apictx = rqctx.context(); let v1::params::PathSpComponent { sp, component } = path.into_inner(); let sp_id = sp.into(); @@ -479,7 +486,9 @@ impl GatewayApi for GatewayImpl { .allowed_to_reset_sp(sp_id) .map_err(HttpError::from)? { - return Err(v1::update::SpComponentResetError::ResetSpOfLocalSled); + return Err( + v1::update::SpComponentResetError::ResetSpOfLocalSled, + ); } sp.reset_component_prepare(component) @@ -554,8 +563,11 @@ impl GatewayApi for GatewayImpl { ) -> Result { let apictx = rqctx.context(); - let v1::params::PathSpComponentFirmwareSlot { sp, component, firmware_slot } = - path.into_inner(); + let v1::params::PathSpComponentFirmwareSlot { + sp, + component, + firmware_slot, + } = path.into_inner(); let sp_id = sp.into(); let handler = async { let sp = apictx.mgmt_switch.sp(sp_id)?; @@ -589,11 +601,15 @@ impl GatewayApi for GatewayImpl { async fn sp_component_hash_firmware_get( rqctx: RequestContext, path: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> + { let apictx = rqctx.context(); - let v1::params::PathSpComponentFirmwareSlot { sp, component, firmware_slot } = - path.into_inner(); + let v1::params::PathSpComponentFirmwareSlot { + sp, + component, + firmware_slot, + } = path.into_inner(); let sp_id = sp.into(); let handler = async { let sp = apictx.mgmt_switch.sp(sp_id)?; @@ -608,7 +624,9 @@ impl GatewayApi for GatewayImpl { let status = match sp.get_host_flash_hash(firmware_slot).await { // success - Ok(sha256) => v1::host::ComponentFirmwareHashStatus::Hashed { sha256 }, + Ok(sha256) => { + v1::host::ComponentFirmwareHashStatus::Hashed { sha256 } + } // expected failure: hash needs to be calculated (or // recalculated; either way the client operation is the same) @@ -711,8 +729,12 @@ impl GatewayApi for GatewayImpl { let sp = apictx.mgmt_switch.sp(sp_id)?; let data = match slot { v1::rot::RotCfpaSlot::Active => sp.read_rot_active_cfpa().await, - v1::rot::RotCfpaSlot::Inactive => sp.read_rot_inactive_cfpa().await, - v1::rot::RotCfpaSlot::Scratch => sp.read_rot_scratch_cfpa().await, + v1::rot::RotCfpaSlot::Inactive => { + sp.read_rot_inactive_cfpa().await + } + v1::rot::RotCfpaSlot::Scratch => { + sp.read_rot_scratch_cfpa().await + } } .map_err(|err| { SpCommsError::SpCommunicationFailed { sp: sp_id, err } @@ -815,16 +837,21 @@ impl GatewayApi for GatewayImpl { async fn ignition_list_v1( rqctx: RequestContext, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> + { let HttpResponseOk(v2_info) = Self::ignition_list(rqctx).await?; Ok(HttpResponseOk( - v2_info.into_iter().map(|x| v1::ignition::SpIgnitionInfo::from(x)).collect(), + v2_info + .into_iter() + .map(|x| v1::ignition::SpIgnitionInfo::from(x)) + .collect(), )) } async fn ignition_list( rqctx: RequestContext, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> + { let apictx = rqctx.context(); let mgmt_switch = &apictx.mgmt_switch; let handler = async { @@ -870,8 +897,10 @@ impl GatewayApi for GatewayImpl { err, })?; - let info = - v2::ignition::SpIgnitionInfo { id: sp_id.into(), details: state.into() }; + let info = v2::ignition::SpIgnitionInfo { + id: sp_id.into(), + details: state.into(), + }; Ok(HttpResponseOk(info)) }; apictx.latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -883,7 +912,8 @@ impl GatewayApi for GatewayImpl { ) -> Result { let apictx = rqctx.context(); let mgmt_switch = &apictx.mgmt_switch; - let v1::params::PathSpIgnitionCommand { sp, command } = path.into_inner(); + let v1::params::PathSpIgnitionCommand { sp, command } = + path.into_inner(); let sp_id = sp.into(); let handler = async { @@ -1020,7 +1050,9 @@ impl GatewayApi for GatewayImpl { let Some(progress) = sp.most_recent_host_phase2_request().await else { - return Ok(HttpResponseOk(v1::update::HostPhase2Progress::None)); + return Ok(HttpResponseOk( + v1::update::HostPhase2Progress::None, + )); }; // Our `host_phase2_provider` is using an in-memory cache, so the only way @@ -1031,7 +1063,9 @@ impl GatewayApi for GatewayImpl { let Ok(total_size) = apictx.host_phase2_provider.total_size(progress.hash).await else { - return Ok(HttpResponseOk(v1::update::HostPhase2Progress::None)); + return Ok(HttpResponseOk( + v1::update::HostPhase2Progress::None, + )); }; let image_id = v1::update::HostPhase2RecoveryImageId { @@ -1070,7 +1104,8 @@ impl GatewayApi for GatewayImpl { async fn recovery_host_phase2_upload( rqctx: RequestContext, body: UntypedBody, - ) -> Result, HttpError> { + ) -> Result, HttpError> + { let apictx = rqctx.context(); let handler = async { // TODO: this makes a full copy of the host image, potentially unnecessarily @@ -1090,7 +1125,9 @@ impl GatewayApi for GatewayImpl { )?; let sha256_hash = ArtifactHash(sha256_hash); - Ok(HttpResponseOk(v1::update::HostPhase2RecoveryImageId { sha256_hash })) + Ok(HttpResponseOk(v1::update::HostPhase2RecoveryImageId { + sha256_hash, + })) }; apictx.latencies.instrument_dropshot_handler(&rqctx, handler).await } @@ -1109,7 +1146,8 @@ impl GatewayApi for GatewayImpl { async fn sp_all_ids( rqctx: RequestContext, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> + { let apictx = rqctx.context(); let handler = async { let all_ids = apictx