diff --git a/Cargo.lock b/Cargo.lock index 9cbb3b518e1..c98ab715998 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6394,10 +6394,8 @@ dependencies = [ "chrono", "futures", "iddqd", - "nexus-sled-agent-shared", "nexus-types", "omicron-common", - "omicron-passwords", "omicron-uuid-kinds", "omicron-workspace-hack", "oxnet", @@ -6763,8 +6761,10 @@ dependencies = [ "chrono", "futures", "iddqd", + "nexus-sled-agent-shared", "nexus-types", "omicron-common", + "omicron-passwords", "omicron-uuid-kinds", "omicron-workspace-hack", "oxnet", @@ -8559,6 +8559,7 @@ dependencies = [ "mg-admin-client", "nexus-client", "nexus-config", + "nexus-lockstep-client", "nexus-reconfigurator-blippy", "nexus-sled-agent-shared", "nexus-types", diff --git a/clients/nexus-client/Cargo.toml b/clients/nexus-client/Cargo.toml index 869f6b2b4ae..27feec25b73 100644 --- a/clients/nexus-client/Cargo.toml +++ b/clients/nexus-client/Cargo.toml @@ -12,9 +12,9 @@ chrono.workspace = true futures.workspace = true iddqd.workspace = true nexus-types.workspace = true -nexus-sled-agent-shared.workspace = true omicron-common.workspace = true -omicron-passwords.workspace = true +omicron-uuid-kinds.workspace = true +omicron-workspace-hack.workspace = true oxnet.workspace = true progenitor.workspace = true regress.workspace = true @@ -24,5 +24,3 @@ serde.workspace = true serde_json.workspace = true slog.workspace = true uuid.workspace = true -omicron-workspace-hack.workspace = true -omicron-uuid-kinds.workspace = true diff --git a/clients/nexus-client/src/lib.rs b/clients/nexus-client/src/lib.rs index bf9fec6c719..9751231a688 100644 --- a/clients/nexus-client/src/lib.rs +++ b/clients/nexus-client/src/lib.rs @@ -5,8 +5,6 @@ //! Interface for making API requests to the Oxide control plane at large //! from within the control plane -use std::collections::HashMap; - progenitor::generate_api!( spec = "../../openapi/nexus-internal.json", interface = Positional, @@ -28,31 +26,11 @@ progenitor::generate_api!( "oxnet" = "0.1.0", }, replace = { - // It's kind of unfortunate to pull in such a complex and unstable type - // as "blueprint" this way, but we have really useful functionality - // (e.g., diff'ing) that's implemented on our local type. - Blueprint = nexus_types::deployment::Blueprint, - BlueprintPhysicalDiskConfig = nexus_types::deployment::BlueprintPhysicalDiskConfig, - BlueprintPhysicalDiskDisposition = nexus_types::deployment::BlueprintPhysicalDiskDisposition, - BlueprintZoneImageSource = nexus_types::deployment::BlueprintZoneImageSource, - Certificate = omicron_common::api::internal::nexus::Certificate, - DatasetKind = omicron_common::api::internal::shared::DatasetKind, - DnsConfigParams = nexus_types::internal_api::params::DnsConfigParams, - DnsConfigZone = nexus_types::internal_api::params::DnsConfigZone, - DnsRecord = nexus_types::internal_api::params::DnsRecord, Generation = omicron_common::api::external::Generation, - ImportExportPolicy = omicron_common::api::external::ImportExportPolicy, MacAddr = omicron_common::api::external::MacAddr, Name = omicron_common::api::external::Name, NetworkInterface = omicron_common::api::internal::shared::NetworkInterface, NetworkInterfaceKind = omicron_common::api::internal::shared::NetworkInterfaceKind, - NewPasswordHash = omicron_passwords::NewPasswordHash, - OximeterReadMode = nexus_types::deployment::OximeterReadMode, - PendingMgsUpdate = nexus_types::deployment::PendingMgsUpdate, - PlannerConfig = nexus_types::deployment::PlannerConfig, - RecoverySiloConfig = nexus_sled_agent_shared::recovery_silo::RecoverySiloConfig, - Srv = nexus_types::internal_api::params::Srv, - ZpoolName = omicron_common::zpool_name::ZpoolName, }, patch = { SledAgentInfo = { derives = [PartialEq, Eq] }, @@ -250,131 +228,6 @@ impl From for std::time::Duration { } } -impl From for types::IpRange { - fn from(r: omicron_common::address::IpRange) -> Self { - use omicron_common::address::IpRange; - match r { - IpRange::V4(r) => types::IpRange::V4(r.into()), - IpRange::V6(r) => types::IpRange::V6(r.into()), - } - } -} - -impl From for types::Ipv4Range { - fn from(r: omicron_common::address::Ipv4Range) -> Self { - Self { first: r.first, last: r.last } - } -} - -impl From for types::Ipv6Range { - fn from(r: omicron_common::address::Ipv6Range) -> Self { - Self { first: r.first, last: r.last } - } -} - -impl From<&omicron_common::api::internal::shared::SourceNatConfig> - for types::SourceNatConfig -{ - fn from( - r: &omicron_common::api::internal::shared::SourceNatConfig, - ) -> Self { - let (first_port, last_port) = r.port_range_raw(); - Self { ip: r.ip, first_port, last_port } - } -} - -impl From - for types::PortSpeed -{ - fn from(value: omicron_common::api::internal::shared::PortSpeed) -> Self { - match value { - omicron_common::api::internal::shared::PortSpeed::Speed0G => { - types::PortSpeed::Speed0G - } - omicron_common::api::internal::shared::PortSpeed::Speed1G => { - types::PortSpeed::Speed1G - } - omicron_common::api::internal::shared::PortSpeed::Speed10G => { - types::PortSpeed::Speed10G - } - omicron_common::api::internal::shared::PortSpeed::Speed25G => { - types::PortSpeed::Speed25G - } - omicron_common::api::internal::shared::PortSpeed::Speed40G => { - types::PortSpeed::Speed40G - } - omicron_common::api::internal::shared::PortSpeed::Speed50G => { - types::PortSpeed::Speed50G - } - omicron_common::api::internal::shared::PortSpeed::Speed100G => { - types::PortSpeed::Speed100G - } - omicron_common::api::internal::shared::PortSpeed::Speed200G => { - types::PortSpeed::Speed200G - } - omicron_common::api::internal::shared::PortSpeed::Speed400G => { - types::PortSpeed::Speed400G - } - } - } -} - -impl From for types::PortFec { - fn from(value: omicron_common::api::internal::shared::PortFec) -> Self { - match value { - omicron_common::api::internal::shared::PortFec::Firecode => { - types::PortFec::Firecode - } - omicron_common::api::internal::shared::PortFec::None => { - types::PortFec::None - } - omicron_common::api::internal::shared::PortFec::Rs => { - types::PortFec::Rs - } - } - } -} - -impl From - for types::SwitchLocation -{ - fn from( - value: omicron_common::api::internal::shared::SwitchLocation, - ) -> Self { - match value { - omicron_common::api::internal::shared::SwitchLocation::Switch0 => { - types::SwitchLocation::Switch0 - } - omicron_common::api::internal::shared::SwitchLocation::Switch1 => { - types::SwitchLocation::Switch1 - } - } - } -} - -impl From - for types::ExternalPortDiscovery -{ - fn from( - value: omicron_common::api::internal::shared::ExternalPortDiscovery, - ) -> Self { - match value { - omicron_common::api::internal::shared::ExternalPortDiscovery::Auto(val) => { - let new: HashMap<_, _> = val.iter().map(|(slot, addr)| { - (slot.to_string(), *addr) - }).collect(); - types::ExternalPortDiscovery::Auto(new) - }, - omicron_common::api::internal::shared::ExternalPortDiscovery::Static(val) => { - let new: HashMap<_, _> = val.iter().map(|(slot, ports)| { - (slot.to_string(), ports.clone()) - }).collect(); - types::ExternalPortDiscovery::Static(new) - }, - } - } -} - impl From for omicron_common::api::internal::nexus::ProducerKind { @@ -408,17 +261,3 @@ impl TryFrom }) } } - -impl From<&omicron_common::api::external::AllowedSourceIps> - for types::AllowedSourceIps -{ - fn from(ips: &omicron_common::api::external::AllowedSourceIps) -> Self { - use omicron_common::api::external::AllowedSourceIps; - match ips { - AllowedSourceIps::Any => types::AllowedSourceIps::Any, - AllowedSourceIps::List(list) => { - types::AllowedSourceIps::List(list.iter().cloned().collect()) - } - } - } -} diff --git a/clients/nexus-lockstep-client/Cargo.toml b/clients/nexus-lockstep-client/Cargo.toml index c64acbf1d94..0b7620dcc28 100644 --- a/clients/nexus-lockstep-client/Cargo.toml +++ b/clients/nexus-lockstep-client/Cargo.toml @@ -11,8 +11,10 @@ workspace = true chrono.workspace = true futures.workspace = true iddqd.workspace = true +nexus-sled-agent-shared.workspace = true nexus-types.workspace = true omicron-common.workspace = true +omicron-passwords.workspace = true omicron-uuid-kinds.workspace = true omicron-workspace-hack.workspace = true oxnet.workspace = true diff --git a/clients/nexus-lockstep-client/src/lib.rs b/clients/nexus-lockstep-client/src/lib.rs index 3942cacdb3a..c28ac9bbd8f 100644 --- a/clients/nexus-lockstep-client/src/lib.rs +++ b/clients/nexus-lockstep-client/src/lib.rs @@ -6,6 +6,8 @@ //! callers that update in lockstep with Nexus itself (e.g. rack initialization, //! tests and debugging) +use std::collections::HashMap; + use iddqd::IdOrdItem; use iddqd::id_upcast; use uuid::Uuid; @@ -38,26 +40,31 @@ progenitor::generate_api!( BlueprintPhysicalDiskConfig = nexus_types::deployment::BlueprintPhysicalDiskConfig, BlueprintPhysicalDiskDisposition = nexus_types::deployment::BlueprintPhysicalDiskDisposition, BlueprintZoneImageSource = nexus_types::deployment::BlueprintZoneImageSource, + Certificate = omicron_common::api::internal::nexus::Certificate, ClickhouseMode = nexus_types::deployment::ClickhouseMode, ClickhousePolicy = nexus_types::deployment::ClickhousePolicy, DatasetKind = omicron_common::api::internal::shared::DatasetKind, + DnsConfigParams = nexus_types::internal_api::params::DnsConfigParams, + DnsConfigZone = nexus_types::internal_api::params::DnsConfigZone, + DnsRecord = nexus_types::internal_api::params::DnsRecord, Generation = omicron_common::api::external::Generation, + ImportExportPolicy = omicron_common::api::external::ImportExportPolicy, MacAddr = omicron_common::api::external::MacAddr, MgsUpdateDriverStatus = nexus_types::internal_api::views::MgsUpdateDriverStatus, Name = omicron_common::api::external::Name, NetworkInterface = omicron_common::api::internal::shared::NetworkInterface, NetworkInterfaceKind = omicron_common::api::internal::shared::NetworkInterfaceKind, - OmicronPhysicalDiskConfig = omicron_common::disk::OmicronPhysicalDiskConfig, - OmicronPhysicalDisksConfig = omicron_common::disk::OmicronPhysicalDisksConfig, + NewPasswordHash = omicron_passwords::NewPasswordHash, OximeterReadMode = nexus_types::deployment::OximeterReadMode, OximeterReadPolicy = nexus_types::deployment::OximeterReadPolicy, PendingMgsUpdate = nexus_types::deployment::PendingMgsUpdate, + PlannerConfig = nexus_types::deployment::PlannerConfig, ReconfiguratorConfig = nexus_types::deployment::ReconfiguratorConfig, ReconfiguratorConfigParam = nexus_types::deployment::ReconfiguratorConfigParam, ReconfiguratorConfigView = nexus_types::deployment::ReconfiguratorConfigView, + RecoverySiloConfig = nexus_sled_agent_shared::recovery_silo::RecoverySiloConfig, UpdateStatus = nexus_types::internal_api::views::UpdateStatus, ZoneStatus = nexus_types::internal_api::views::ZoneStatus, - ZoneStatusVersion = nexus_types::internal_api::views::ZoneStatusVersion, ZpoolName = omicron_common::zpool_name::ZpoolName, }, patch = { @@ -99,3 +106,142 @@ impl From for std::time::Duration { ) } } + +impl From for types::IpRange { + fn from(r: omicron_common::address::IpRange) -> Self { + use omicron_common::address::IpRange; + match r { + IpRange::V4(r) => types::IpRange::V4(r.into()), + IpRange::V6(r) => types::IpRange::V6(r.into()), + } + } +} + +impl From for types::Ipv4Range { + fn from(r: omicron_common::address::Ipv4Range) -> Self { + Self { first: r.first, last: r.last } + } +} + +impl From for types::Ipv6Range { + fn from(r: omicron_common::address::Ipv6Range) -> Self { + Self { first: r.first, last: r.last } + } +} + +impl From<&omicron_common::api::internal::shared::SourceNatConfig> + for types::SourceNatConfig +{ + fn from( + r: &omicron_common::api::internal::shared::SourceNatConfig, + ) -> Self { + let (first_port, last_port) = r.port_range_raw(); + Self { ip: r.ip, first_port, last_port } + } +} + +impl From + for types::PortSpeed +{ + fn from(value: omicron_common::api::internal::shared::PortSpeed) -> Self { + match value { + omicron_common::api::internal::shared::PortSpeed::Speed0G => { + types::PortSpeed::Speed0G + } + omicron_common::api::internal::shared::PortSpeed::Speed1G => { + types::PortSpeed::Speed1G + } + omicron_common::api::internal::shared::PortSpeed::Speed10G => { + types::PortSpeed::Speed10G + } + omicron_common::api::internal::shared::PortSpeed::Speed25G => { + types::PortSpeed::Speed25G + } + omicron_common::api::internal::shared::PortSpeed::Speed40G => { + types::PortSpeed::Speed40G + } + omicron_common::api::internal::shared::PortSpeed::Speed50G => { + types::PortSpeed::Speed50G + } + omicron_common::api::internal::shared::PortSpeed::Speed100G => { + types::PortSpeed::Speed100G + } + omicron_common::api::internal::shared::PortSpeed::Speed200G => { + types::PortSpeed::Speed200G + } + omicron_common::api::internal::shared::PortSpeed::Speed400G => { + types::PortSpeed::Speed400G + } + } + } +} + +impl From for types::PortFec { + fn from(value: omicron_common::api::internal::shared::PortFec) -> Self { + match value { + omicron_common::api::internal::shared::PortFec::Firecode => { + types::PortFec::Firecode + } + omicron_common::api::internal::shared::PortFec::None => { + types::PortFec::None + } + omicron_common::api::internal::shared::PortFec::Rs => { + types::PortFec::Rs + } + } + } +} + +impl From + for types::SwitchLocation +{ + fn from( + value: omicron_common::api::internal::shared::SwitchLocation, + ) -> Self { + match value { + omicron_common::api::internal::shared::SwitchLocation::Switch0 => { + types::SwitchLocation::Switch0 + } + omicron_common::api::internal::shared::SwitchLocation::Switch1 => { + types::SwitchLocation::Switch1 + } + } + } +} + +impl From + for types::ExternalPortDiscovery +{ + fn from( + value: omicron_common::api::internal::shared::ExternalPortDiscovery, + ) -> Self { + match value { + omicron_common::api::internal::shared::ExternalPortDiscovery::Auto(val) => { + let new: HashMap<_, _> = val.iter().map(|(slot, addr)| { + (slot.to_string(), *addr) + }).collect(); + types::ExternalPortDiscovery::Auto(new) + }, + omicron_common::api::internal::shared::ExternalPortDiscovery::Static(val) => { + let new: HashMap<_, _> = val.iter().map(|(slot, ports)| { + (slot.to_string(), ports.clone()) + }).collect(); + types::ExternalPortDiscovery::Static(new) + }, + } + } +} + +impl From<&omicron_common::api::external::AllowedSourceIps> + for types::AllowedSourceIps +{ + fn from(ips: &omicron_common::api::external::AllowedSourceIps) -> Self { + use omicron_common::api::external::AllowedSourceIps; + match ips { + AllowedSourceIps::Any => types::AllowedSourceIps::Any, + AllowedSourceIps::List(list) => { + types::AllowedSourceIps::List(list.iter().cloned().collect()) + } + } + } +} diff --git a/dev-tools/ls-apis/tests/api_dependencies.out b/dev-tools/ls-apis/tests/api_dependencies.out index ab3f0e1eadb..ac6ae2d8b9e 100644 --- a/dev-tools/ls-apis/tests/api_dependencies.out +++ b/dev-tools/ls-apis/tests/api_dependencies.out @@ -69,6 +69,7 @@ Nexus Internal API (client: nexus-client) consumed by: propolis-server (propolis/bin/propolis-server) via 3 paths Nexus Internal Lockstep API (client: nexus-lockstep-client) + consumed by: omicron-sled-agent (omicron/sled-agent) via 1 path NTP Admin (client: ntp-admin-client) consumed by: omicron-nexus (omicron/nexus) via 2 paths diff --git a/nexus/internal-api/src/lib.rs b/nexus/internal-api/src/lib.rs index 67b1f96c1f6..1b48d81d025 100644 --- a/nexus/internal-api/src/lib.rs +++ b/nexus/internal-api/src/lib.rs @@ -16,8 +16,7 @@ use nexus_types::{ }, internal_api::{ params::{ - OximeterInfo, RackInitializationRequest, SledAgentInfo, - SwitchPutRequest, SwitchPutResponse, + OximeterInfo, SledAgentInfo, SwitchPutRequest, SwitchPutResponse, }, views::NatEntryView, }, @@ -35,8 +34,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use uuid::Uuid; -const RACK_INITIALIZATION_REQUEST_MAX_BYTES: usize = 10 * 1024 * 1024; - #[dropshot::api_description] pub trait NexusInternalApi { type Context; @@ -75,20 +72,6 @@ pub trait NexusInternalApi { sled_info: TypedBody, ) -> Result; - /// Report that the Rack Setup Service initialization is complete - /// - /// See RFD 278 for more details. - #[endpoint { - method = PUT, - path = "/racks/{rack_id}/initialization-complete", - request_body_max_bytes = RACK_INITIALIZATION_REQUEST_MAX_BYTES, - }] - async fn rack_initialization_complete( - rqctx: RequestContext, - path_params: Path, - info: TypedBody, - ) -> Result; - #[endpoint { method = PUT, path = "/switch/{switch_id}", @@ -305,12 +288,6 @@ pub struct VolumePathParam { pub volume_id: VolumeUuid, } -/// Path parameters for Rack requests. -#[derive(Deserialize, JsonSchema)] -pub struct RackPathParam { - pub rack_id: Uuid, -} - /// Path parameters for Switch requests. #[derive(Deserialize, JsonSchema)] pub struct SwitchPathParam { diff --git a/nexus/lockstep-api/src/lib.rs b/nexus/lockstep-api/src/lib.rs index 43b54ac9260..1e145995b0f 100644 --- a/nexus/lockstep-api/src/lib.rs +++ b/nexus/lockstep-api/src/lib.rs @@ -37,6 +37,7 @@ use nexus_types::external_api::views::Ping; use nexus_types::external_api::views::PingStatus; use nexus_types::external_api::views::SledPolicy; use nexus_types::internal_api::params::InstanceMigrateRequest; +use nexus_types::internal_api::params::RackInitializationRequest; use nexus_types::internal_api::views::BackgroundTask; use nexus_types::internal_api::views::DemoSaga; use nexus_types::internal_api::views::MgsUpdateDriverStatus; @@ -52,6 +53,8 @@ use serde::Deserialize; use serde::Serialize; use uuid::Uuid; +const RACK_INITIALIZATION_REQUEST_MAX_BYTES: usize = 10 * 1024 * 1024; + #[dropshot::api_description] pub trait NexusLockstepApi { type Context; @@ -69,6 +72,20 @@ pub trait NexusLockstepApi { Ok(HttpResponseOk(Ping { status: PingStatus::Ok })) } + /// Report that the Rack Setup Service initialization is complete + /// + /// See RFD 278 for more details. + #[endpoint { + method = PUT, + path = "/racks/{rack_id}/initialization-complete", + request_body_max_bytes = RACK_INITIALIZATION_REQUEST_MAX_BYTES, + }] + async fn rack_initialization_complete( + rqctx: RequestContext, + path_params: Path, + info: TypedBody, + ) -> Result; + #[endpoint { method = POST, path = "/instances/{instance_id}/migrate", @@ -530,6 +547,12 @@ pub trait NexusLockstepApi { ) -> Result, HttpError>; } +/// Path parameters for Rack requests. +#[derive(Deserialize, JsonSchema)] +pub struct RackPathParam { + pub rack_id: Uuid, +} + /// Path parameters for Instance requests (internal API) #[derive(Deserialize, JsonSchema)] pub struct InstancePathParam { diff --git a/nexus/src/internal_api/http_entrypoints.rs b/nexus/src/internal_api/http_entrypoints.rs index c54de3b7440..930e1ec20db 100644 --- a/nexus/src/internal_api/http_entrypoints.rs +++ b/nexus/src/internal_api/http_entrypoints.rs @@ -4,7 +4,7 @@ //! Handler functions (entrypoints) for HTTP APIs internal to the control plane -use super::params::{OximeterInfo, RackInitializationRequest}; +use super::params::OximeterInfo; use crate::context::ApiContext; use dropshot::ApiDescription; use dropshot::HttpError; @@ -90,29 +90,6 @@ impl NexusInternalApi for NexusInternalApiImpl { .await } - async fn rack_initialization_complete( - rqctx: RequestContext, - path_params: Path, - info: TypedBody, - ) -> Result { - let apictx = &rqctx.context().context; - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let request = info.into_inner(); - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - - nexus - .rack_initialize( - &opctx, - path.rack_id, - request, - true, // blueprint_execution_enabled - ) - .await?; - - Ok(HttpResponseUpdatedNoContent()) - } - async fn switch_put( rqctx: RequestContext, path_params: Path, diff --git a/nexus/src/lockstep_api/http_entrypoints.rs b/nexus/src/lockstep_api/http_entrypoints.rs index 463b0883deb..8cf6a983f38 100644 --- a/nexus/src/lockstep_api/http_entrypoints.rs +++ b/nexus/src/lockstep_api/http_entrypoints.rs @@ -41,6 +41,7 @@ use nexus_types::external_api::shared; use nexus_types::external_api::shared::UninitializedSled; use nexus_types::external_api::views::SledPolicy; use nexus_types::internal_api::params::InstanceMigrateRequest; +use nexus_types::internal_api::params::RackInitializationRequest; use nexus_types::internal_api::views::BackgroundTask; use nexus_types::internal_api::views::DemoSaga; use nexus_types::internal_api::views::MgsUpdateDriverStatus; @@ -74,6 +75,29 @@ enum NexusLockstepApiImpl {} impl NexusLockstepApi for NexusLockstepApiImpl { type Context = ApiContext; + async fn rack_initialization_complete( + rqctx: RequestContext, + path_params: Path, + info: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let request = info.into_inner(); + let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + + nexus + .rack_initialize( + &opctx, + path.rack_id, + request, + true, // blueprint_execution_enabled + ) + .await?; + + Ok(HttpResponseUpdatedNoContent()) + } + async fn instance_migrate( rqctx: RequestContext, path_params: Path, diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index ffb35fd5739..8a448691ea2 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -563,45 +563,6 @@ } } }, - "/racks/{rack_id}/initialization-complete": { - "put": { - "summary": "Report that the Rack Setup Service initialization is complete", - "description": "See RFD 278 for more details.", - "operationId": "rack_initialization_complete", - "parameters": [ - { - "in": "path", - "name": "rack_id", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RackInitializationRequest" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, "/sled-agents/{sled_id}": { "get": { "summary": "Return information about the given sled agent", @@ -808,53 +769,6 @@ }, "components": { "schemas": { - "AllowedSourceIps": { - "description": "Description of source IPs allowed to reach rack services.", - "oneOf": [ - { - "description": "Allow traffic from any external IP address.", - "type": "object", - "properties": { - "allow": { - "type": "string", - "enum": [ - "any" - ] - } - }, - "required": [ - "allow" - ] - }, - { - "description": "Restrict access to a specific set of source IP addresses or subnets.\n\nAll others are prevented from reaching rack services.", - "type": "object", - "properties": { - "allow": { - "type": "string", - "enum": [ - "list" - ] - }, - "ips": { - "type": "array", - "items": { - "$ref": "#/components/schemas/IpNet" - } - } - }, - "required": [ - "allow", - "ips" - ] - } - ] - }, - "ArtifactVersion": { - "description": "An artifact version.\n\nThis is a freeform identifier with some basic validation. It may be the serialized form of a semver version, or a custom identifier that uses the same character set as a semver, plus `_`.\n\nThe exact pattern accepted is `^[a-zA-Z0-9._+-]{1,63}$`.\n\n# Ord implementation\n\n`ArtifactVersion`s are not intended to be sorted, just compared for equality. `ArtifactVersion` implements `Ord` only for storage within sorted collections.", - "type": "string", - "pattern": "^[a-zA-Z0-9._+-]{1,63}$" - }, "BackgroundTasksActivateRequest": { "description": "Query parameters for Background Task activation requests.", "type": "object", @@ -893,4422 +807,655 @@ "serial" ] }, - "BaseboardId": { - "description": "A unique baseboard id found during a collection\n\nBaseboard ids are the keys used to link up information from disparate sources (like a service processor and a sled agent).\n\nThese are normalized in the database. Each distinct baseboard id is assigned a uuid and shared across the many possible collections that reference it.\n\nUsually, the part number and serial number are combined with a revision number. We do not include that here. If we ever did find a baseboard with the same part number and serial number but a new revision number, we'd want to treat that as the same baseboard as one with a different revision number.", - "type": "object", - "properties": { - "part_number": { - "description": "Oxide Part Number", - "type": "string" - }, - "serial_number": { - "description": "Serial number (unique for a given part number)", - "type": "string" - } - }, - "required": [ - "part_number", - "serial_number" - ] - }, - "BfdMode": { - "description": "BFD connection mode.", - "type": "string", - "enum": [ - "single_hop", - "multi_hop" - ] - }, - "BfdPeerConfig": { - "type": "object", - "properties": { - "detection_threshold": { - "type": "integer", - "format": "uint8", - "minimum": 0 - }, - "local": { - "nullable": true, - "type": "string", - "format": "ip" - }, - "mode": { - "$ref": "#/components/schemas/BfdMode" - }, - "remote": { - "type": "string", - "format": "ip" - }, - "required_rx": { - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "switch": { - "$ref": "#/components/schemas/SwitchLocation" - } - }, - "required": [ - "detection_threshold", - "mode", - "remote", - "required_rx", - "switch" - ] - }, - "BgpConfig": { - "type": "object", - "properties": { - "asn": { - "description": "The autonomous system number for the BGP configuration.", - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "checker": { - "nullable": true, - "description": "Checker to apply to incoming messages.", - "default": null, - "type": "string" - }, - "originate": { - "description": "The set of prefixes for the BGP router to originate.", - "type": "array", - "items": { - "$ref": "#/components/schemas/Ipv4Net" - } - }, - "shaper": { - "nullable": true, - "description": "Shaper to apply to outgoing messages.", - "default": null, - "type": "string" - } - }, - "required": [ - "asn", - "originate" - ] + "ByteCount": { + "description": "Byte count to express memory or storage capacity.", + "type": "integer", + "format": "uint64", + "minimum": 0 }, - "BgpPeerConfig": { + "DiskRuntimeState": { + "description": "Runtime state of the Disk, which includes its attach state and some minimal metadata", "type": "object", "properties": { - "addr": { - "description": "Address of the peer.", - "type": "string", - "format": "ipv4" - }, - "allowed_export": { - "description": "Define export policy for a peer.", - "default": { - "type": "no_filtering" - }, + "disk_state": { + "description": "runtime state of the Disk", "allOf": [ { - "$ref": "#/components/schemas/ImportExportPolicy" + "$ref": "#/components/schemas/DiskState" } ] }, - "allowed_import": { - "description": "Define import policy for a peer.", - "default": { - "type": "no_filtering" - }, + "gen": { + "description": "generation number for this state", "allOf": [ { - "$ref": "#/components/schemas/ImportExportPolicy" + "$ref": "#/components/schemas/Generation" } ] }, - "asn": { - "description": "The autonomous system number of the router the peer belongs to.", - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "communities": { - "description": "Include the provided communities in updates sent to the peer.", - "default": [], - "type": "array", - "items": { - "type": "integer", - "format": "uint32", - "minimum": 0 - } - }, - "connect_retry": { - "nullable": true, - "description": "The interval in seconds between peer connection retry attempts.", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "delay_open": { - "nullable": true, - "description": "How long to delay sending open messages to a peer. In seconds.", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "enforce_first_as": { - "description": "Enforce that the first AS in paths received from this peer is the peer's AS.", - "default": false, - "type": "boolean" - }, - "hold_time": { - "nullable": true, - "description": "How long to keep a session alive without a keepalive in seconds. Defaults to 6.", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "idle_hold_time": { - "nullable": true, - "description": "How long to keep a peer in idle after a state machine reset in seconds.", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "keepalive": { - "nullable": true, - "description": "The interval to send keepalive messages at.", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "local_pref": { - "nullable": true, - "description": "Apply a local preference to routes received from this peer.", - "default": null, - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "md5_auth_key": { - "nullable": true, - "description": "Use the given key for TCP-MD5 authentication with the peer.", - "default": null, - "type": "string" - }, - "min_ttl": { - "nullable": true, - "description": "Require messages from a peer have a minimum IP time to live field.", - "default": null, - "type": "integer", - "format": "uint8", - "minimum": 0 - }, - "multi_exit_discriminator": { - "nullable": true, - "description": "Apply the provided multi-exit discriminator (MED) updates sent to the peer.", - "default": null, - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "port": { - "description": "Switch port the peer is reachable on.", - "type": "string" - }, - "remote_asn": { - "nullable": true, - "description": "Require that a peer has a specified ASN.", - "default": null, - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "vlan_id": { - "nullable": true, - "description": "Associate a VLAN ID with a BGP peer session.", - "default": null, - "type": "integer", - "format": "uint16", - "minimum": 0 + "time_updated": { + "description": "timestamp for this information", + "type": "string", + "format": "date-time" } }, "required": [ - "addr", - "asn", - "port" + "disk_state", + "gen", + "time_updated" ] }, - "BlockedMgsUpdate": { - "type": "object", - "properties": { - "baseboard_id": { - "description": "id of the baseboard that we attempted to update", - "allOf": [ - { - "$ref": "#/components/schemas/BaseboardId" + "DiskState": { + "description": "State of a Disk", + "oneOf": [ + { + "description": "Disk is being initialized", + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "creating" + ] } + }, + "required": [ + "state" ] }, - "reason": { - "description": "reason why the update failed", - "allOf": [ - { - "$ref": "#/components/schemas/FailedMgsUpdateReason" - } - ] - } - }, - "required": [ - "baseboard_id", - "reason" - ] - }, - "Blueprint": { - "description": "Describes a complete set of software and configuration for the system", - "type": "object", - "properties": { - "clickhouse_cluster_config": { - "nullable": true, - "description": "Allocation of Clickhouse Servers and Keepers for replicated clickhouse setups. This is set to `None` if replicated clickhouse is not in use.", - "allOf": [ - { - "$ref": "#/components/schemas/ClickhouseClusterConfig" + { + "description": "Disk is ready but detached from any Instance", + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "detached" + ] } + }, + "required": [ + "state" ] }, - "cockroachdb_fingerprint": { - "description": "CockroachDB state fingerprint when this blueprint was created", - "type": "string" - }, - "cockroachdb_setting_preserve_downgrade": { - "description": "Whether to set `cluster.preserve_downgrade_option` and what to set it to", - "allOf": [ - { - "$ref": "#/components/schemas/CockroachDbPreserveDowngrade" - } - ] - }, - "comment": { - "description": "human-readable string describing why this blueprint was created (for debugging)", - "type": "string" - }, - "creator": { - "description": "identity of the component that generated the blueprint (for debugging) This would generally be the Uuid of a Nexus instance.", - "type": "string" - }, - "external_dns_version": { - "description": "external DNS version when this blueprint was created", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "id": { - "description": "unique identifier for this blueprint", - "allOf": [ - { - "$ref": "#/components/schemas/BlueprintUuid" - } - ] - }, - "internal_dns_version": { - "description": "internal DNS version when this blueprint was created", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "nexus_generation": { - "description": "The generation of the active group of Nexuses\n\nIf a Nexus instance notices it has a nexus_generation less than this value, it will start to quiesce in preparation for handing off control to the newer generation (see: RFD 588).", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "oximeter_read_mode": { - "description": "Whether oximeter should read from a single node or a cluster", - "allOf": [ - { - "$ref": "#/components/schemas/OximeterReadMode" - } - ] - }, - "oximeter_read_version": { - "description": "Oximeter read policy version when this blueprint was created", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "parent_blueprint_id": { - "nullable": true, - "description": "which blueprint this blueprint is based on", - "allOf": [ - { - "$ref": "#/components/schemas/BlueprintUuid" - } - ] - }, - "pending_mgs_updates": { - "description": "List of pending MGS-mediated updates", - "allOf": [ - { - "$ref": "#/components/schemas/PendingMgsUpdates" - } - ] - }, - "sleds": { - "description": "A map of sled id -> desired configuration of the sled.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/BlueprintSledConfig" - } - }, - "source": { - "description": "Source of this blueprint (can include planning report)", - "allOf": [ - { - "$ref": "#/components/schemas/BlueprintSource" - } - ] - }, - "target_release_minimum_generation": { - "description": "The minimum release generation to accept for target release configuration. Target release configuration with a generation less than this number will be ignored.\n\nFor example, let's say that the current target release generation is 5. Then, when reconfigurator detects a MUPdate:\n\n* the target release is ignored in favor of the install dataset * this field is set to 6\n\nOnce an operator sets a new target release, its generation will be 6 or higher. Reconfigurator will then know that it is back in charge of driving the system to the target release.", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "time_created": { - "description": "when this blueprint was generated (for debugging)", - "type": "string", - "format": "date-time" - } - }, - "required": [ - "cockroachdb_fingerprint", - "cockroachdb_setting_preserve_downgrade", - "comment", - "creator", - "external_dns_version", - "id", - "internal_dns_version", - "nexus_generation", - "oximeter_read_mode", - "oximeter_read_version", - "pending_mgs_updates", - "sleds", - "source", - "target_release_minimum_generation", - "time_created" - ] - }, - "BlueprintArtifactVersion": { - "description": "The version of an artifact in a blueprint.\n\nThis is used for debugging output.", - "oneOf": [ { - "description": "A specific version of the image is available.", + "description": "Disk is ready to receive blocks from an external source", "type": "object", "properties": { - "artifact_version": { + "state": { "type": "string", "enum": [ - "available" + "import_ready" ] - }, - "version": { - "$ref": "#/components/schemas/ArtifactVersion" } }, "required": [ - "artifact_version", - "version" + "state" ] }, { - "description": "The version could not be determined. This is non-fatal.", + "description": "Disk is importing blocks from a URL", "type": "object", "properties": { - "artifact_version": { + "state": { "type": "string", "enum": [ - "unknown" + "importing_from_url" ] } }, "required": [ - "artifact_version" - ] - } - ] - }, - "BlueprintDatasetConfig": { - "description": "Information about a dataset as recorded in a blueprint", - "type": "object", - "properties": { - "address": { - "nullable": true, - "type": "string" - }, - "compression": { - "$ref": "#/components/schemas/CompressionAlgorithm" - }, - "disposition": { - "$ref": "#/components/schemas/BlueprintDatasetDisposition" - }, - "id": { - "$ref": "#/components/schemas/DatasetUuid" - }, - "kind": { - "$ref": "#/components/schemas/DatasetKind" - }, - "pool": { - "$ref": "#/components/schemas/ZpoolName" - }, - "quota": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/ByteCount" - } - ] - }, - "reservation": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/ByteCount" - } - ] - } - }, - "required": [ - "compression", - "disposition", - "id", - "kind", - "pool" - ] - }, - "BlueprintDatasetDisposition": { - "description": "The desired state of an Omicron-managed dataset in a blueprint.\n\nPart of [`BlueprintDatasetConfig`].", - "oneOf": [ - { - "description": "The dataset is in-service.", - "type": "string", - "enum": [ - "in_service" + "state" ] }, { - "description": "The dataset is permanently gone.", - "type": "string", - "enum": [ - "expunged" - ] - } - ] - }, - "BlueprintHostPhase2DesiredContents": { - "description": "Describes the desired contents of a host phase 2 slot (i.e., the boot partition on one of the internal M.2 drives).\n\nThis is the blueprint version of [`HostPhase2DesiredContents`].", - "oneOf": [ - { - "description": "Do not change the current contents.\n\nWe use this value when we've detected a sled has been mupdated (and we don't want to overwrite phase 2 images until we understand how to recover from that mupdate) and as the default value when reading a blueprint that was ledgered before this concept existed.", + "description": "Disk is importing blocks from bulk writes", "type": "object", "properties": { - "type": { + "state": { "type": "string", "enum": [ - "current_contents" + "importing_from_bulk_writes" ] } }, "required": [ - "type" + "state" ] }, { - "description": "Set the phase 2 slot to the given artifact.\n\nThe artifact will come from an unpacked and distributed TUF repo.", + "description": "Disk is being finalized to state Detached", "type": "object", "properties": { - "hash": { - "type": "string", - "format": "hex string (32 bytes)" - }, - "type": { + "state": { "type": "string", "enum": [ - "artifact" + "finalizing" ] - }, - "version": { - "$ref": "#/components/schemas/BlueprintArtifactVersion" } }, "required": [ - "hash", - "type", - "version" + "state" ] - } - ] - }, - "BlueprintHostPhase2DesiredSlots": { - "description": "Describes the desired contents for both host phase 2 slots.\n\nThis is the blueprint version of [`HostPhase2DesiredSlots`].", - "type": "object", - "properties": { - "slot_a": { - "$ref": "#/components/schemas/BlueprintHostPhase2DesiredContents" - }, - "slot_b": { - "$ref": "#/components/schemas/BlueprintHostPhase2DesiredContents" - } - }, - "required": [ - "slot_a", - "slot_b" - ] - }, - "BlueprintPhysicalDiskConfig": { - "description": "Information about an Omicron physical disk as recorded in a bluerprint.", - "type": "object", - "properties": { - "disposition": { - "$ref": "#/components/schemas/BlueprintPhysicalDiskDisposition" - }, - "id": { - "$ref": "#/components/schemas/PhysicalDiskUuid" - }, - "identity": { - "$ref": "#/components/schemas/DiskIdentity" }, - "pool_id": { - "$ref": "#/components/schemas/ZpoolUuid" - } - }, - "required": [ - "disposition", - "id", - "identity", - "pool_id" - ] - }, - "BlueprintPhysicalDiskDisposition": { - "description": "The desired state of an Omicron-managed physical disk in a blueprint.", - "oneOf": [ { - "description": "The physical disk is in-service.", + "description": "Disk is undergoing maintenance", "type": "object", "properties": { - "kind": { + "state": { "type": "string", "enum": [ - "in_service" + "maintenance" ] } }, "required": [ - "kind" + "state" ] }, { - "description": "The physical disk is permanently gone.", + "description": "Disk is being attached to the given Instance", "type": "object", "properties": { - "as_of_generation": { - "description": "Generation of the parent config in which this disk became expunged.", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] + "instance": { + "type": "string", + "format": "uuid" }, - "kind": { + "state": { "type": "string", "enum": [ - "expunged" + "attaching" ] - }, - "ready_for_cleanup": { - "description": "True if Reconfiguration knows that this disk has been expunged.\n\nIn the current implementation, this means either:\n\na) the sled where the disk was residing has been expunged.\n\nb) the planner has observed an inventory collection where the disk expungement was seen by the sled agent on the sled where the disk was previously in service. This is indicated by the inventory reporting a disk generation at least as high as `as_of_generation`.", - "type": "boolean" } }, "required": [ - "as_of_generation", - "kind", - "ready_for_cleanup" - ] - } - ] - }, - "BlueprintSledConfig": { - "description": "Information about the configuration of a sled as recorded in a blueprint.\n\nPart of [`Blueprint`].", - "type": "object", - "properties": { - "datasets": { - "$ref": "#/components/schemas/IdMapBlueprintDatasetConfig" - }, - "disks": { - "$ref": "#/components/schemas/IdMapBlueprintPhysicalDiskConfig" - }, - "host_phase_2": { - "$ref": "#/components/schemas/BlueprintHostPhase2DesiredSlots" - }, - "remove_mupdate_override": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/MupdateOverrideUuid" - } - ] - }, - "sled_agent_generation": { - "description": "Generation number used when this type is converted into an `OmicronSledConfig` for use by sled-agent.\n\nThis field is explicitly named `sled_agent_generation` to indicate that it is only required to cover information that changes what Reconfigurator sends to sled agent. For example, changing the sled `state` from `Active` to `Decommissioned` would not require a bump to `sled_agent_generation`, because a `Decommissioned` sled will never be sent an `OmicronSledConfig`.", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } + "instance", + "state" ] }, - "state": { - "$ref": "#/components/schemas/SledState" - }, - "zones": { - "$ref": "#/components/schemas/IdMapBlueprintZoneConfig" - } - }, - "required": [ - "datasets", - "disks", - "host_phase_2", - "sled_agent_generation", - "state", - "zones" - ] - }, - "BlueprintSource": { - "description": "Description of the source of a blueprint.", - "oneOf": [ { - "description": "The initial blueprint created by the rack setup service.", + "description": "Disk is attached to the given Instance", "type": "object", "properties": { - "source": { + "instance": { + "type": "string", + "format": "uuid" + }, + "state": { "type": "string", "enum": [ - "rss" + "attached" ] } }, "required": [ - "source" + "instance", + "state" ] }, { - "description": "A blueprint created by the planner, and we still have the associated planning report.", + "description": "Disk is being detached from the given Instance", "type": "object", "properties": { - "add": { - "$ref": "#/components/schemas/PlanningAddStepReport" - }, - "cockroachdb_settings": { - "$ref": "#/components/schemas/PlanningCockroachdbSettingsStepReport" - }, - "decommission": { - "$ref": "#/components/schemas/PlanningDecommissionStepReport" - }, - "expunge": { - "$ref": "#/components/schemas/PlanningExpungeStepReport" - }, - "mgs_updates": { - "$ref": "#/components/schemas/PlanningMgsUpdatesStepReport" - }, - "nexus_generation_bump": { - "$ref": "#/components/schemas/PlanningNexusGenerationBumpReport" - }, - "noop_image_source": { - "$ref": "#/components/schemas/PlanningNoopImageSourceStepReport" - }, - "planner_config": { - "description": "The configuration in effect for this planning run.", - "allOf": [ - { - "$ref": "#/components/schemas/PlannerConfig" - } - ] + "instance": { + "type": "string", + "format": "uuid" }, - "source": { + "state": { "type": "string", "enum": [ - "planner" + "detaching" ] - }, - "zone_updates": { - "$ref": "#/components/schemas/PlanningZoneUpdatesStepReport" } }, "required": [ - "add", - "cockroachdb_settings", - "decommission", - "expunge", - "mgs_updates", - "nexus_generation_bump", - "noop_image_source", - "planner_config", - "source", - "zone_updates" + "instance", + "state" ] }, { - "description": "A blueprint created by the planner but loaded from the database, so we no longer have the associated planning report.", + "description": "Disk has been destroyed", "type": "object", "properties": { - "source": { + "state": { "type": "string", "enum": [ - "planner_loaded_from_database" + "destroyed" ] } }, "required": [ - "source" + "state" ] }, { - "description": "This blueprint was created by one of `reconfigurator-cli`'s blueprint editing subcommands.", + "description": "Disk is unavailable", "type": "object", "properties": { - "source": { + "state": { "type": "string", "enum": [ - "reconfigurator_cli_edit" + "faulted" ] } }, "required": [ - "source" + "state" ] - }, - { - "description": "This blueprint was constructed by hand by an automated test.", - "type": "object", - "properties": { - "source": { - "type": "string", - "enum": [ - "test" - ] - } - }, - "required": [ - "source" - ] - } - ] - }, - "BlueprintUuid": { - "x-rust-type": { - "crate": "omicron-uuid-kinds", - "path": "omicron_uuid_kinds::BlueprintUuid", - "version": "*" - }, - "type": "string", - "format": "uuid" - }, - "BlueprintZoneConfig": { - "description": "Describes one Omicron-managed zone in a blueprint.\n\nPart of [`BlueprintSledConfig`].", - "type": "object", - "properties": { - "disposition": { - "description": "The disposition (desired state) of this zone recorded in the blueprint.", - "allOf": [ - { - "$ref": "#/components/schemas/BlueprintZoneDisposition" - } - ] - }, - "filesystem_pool": { - "description": "zpool used for the zone's (transient) root filesystem", - "allOf": [ - { - "$ref": "#/components/schemas/ZpoolName" - } - ] - }, - "id": { - "$ref": "#/components/schemas/OmicronZoneUuid" - }, - "image_source": { - "$ref": "#/components/schemas/BlueprintZoneImageSource" - }, - "zone_type": { - "$ref": "#/components/schemas/BlueprintZoneType" - } - }, - "required": [ - "disposition", - "filesystem_pool", - "id", - "image_source", - "zone_type" - ] - }, - "BlueprintZoneDisposition": { - "description": "The desired state of an Omicron-managed zone in a blueprint.\n\nPart of [`BlueprintZoneConfig`].", - "oneOf": [ - { - "description": "The zone is in-service.", - "type": "object", - "properties": { - "kind": { - "type": "string", - "enum": [ - "in_service" - ] - } - }, - "required": [ - "kind" - ] - }, - { - "description": "The zone is permanently gone.", - "type": "object", - "properties": { - "as_of_generation": { - "description": "Generation of the parent config in which this zone became expunged.", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "kind": { - "type": "string", - "enum": [ - "expunged" - ] - }, - "ready_for_cleanup": { - "description": "True if Reconfiguration knows that this zone has been shut down and will not be restarted.\n\nIn the current implementation, this means the planner has observed an inventory collection where the sled on which this zone was running (a) is no longer running the zone and (b) has a config generation at least as high as `as_of_generation`, indicating it will not try to start the zone on a cold boot based on an older config.", - "type": "boolean" - } - }, - "required": [ - "as_of_generation", - "kind", - "ready_for_cleanup" - ] - } - ] - }, - "BlueprintZoneImageSource": { - "description": "Where the zone's image source is located.\n\nThis is the blueprint version of [`OmicronZoneImageSource`].", - "oneOf": [ - { - "description": "This zone's image source is whatever happens to be on the sled's \"install\" dataset.\n\nThis is whatever was put in place at the factory or by the latest MUPdate. The image used here can vary by sled and even over time (if the sled gets MUPdated again).\n\nHistorically, this was the only source for zone images. In an system with automated control-plane-driven update we expect to only use this variant in emergencies where the system had to be recovered via MUPdate.", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "install_dataset" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "This zone's image source is the artifact matching this hash from the TUF artifact store (aka \"TUF repo depot\").\n\nThis originates from TUF repos uploaded to Nexus which are then replicated out to all sleds.", - "type": "object", - "properties": { - "hash": { - "type": "string", - "format": "hex string (32 bytes)" - }, - "type": { - "type": "string", - "enum": [ - "artifact" - ] - }, - "version": { - "$ref": "#/components/schemas/BlueprintArtifactVersion" - } - }, - "required": [ - "hash", - "type", - "version" - ] - } - ] - }, - "BlueprintZoneType": { - "oneOf": [ - { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "dns_servers": { - "type": "array", - "items": { - "type": "string", - "format": "ip" - } - }, - "domain": { - "nullable": true, - "type": "string" - }, - "external_ip": { - "$ref": "#/components/schemas/OmicronZoneExternalSnatIp" - }, - "nic": { - "description": "The service vNIC providing outbound connectivity using OPTE.", - "allOf": [ - { - "$ref": "#/components/schemas/NetworkInterface" - } - ] - }, - "ntp_servers": { - "type": "array", - "items": { - "type": "string" - } - }, - "type": { - "type": "string", - "enum": [ - "boundary_ntp" - ] - } - }, - "required": [ - "address", - "dns_servers", - "external_ip", - "nic", - "ntp_servers", - "type" - ] - }, - { - "description": "Used in single-node clickhouse setups", - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" - }, - "type": { - "type": "string", - "enum": [ - "clickhouse" - ] - } - }, - "required": [ - "address", - "dataset", - "type" - ] - }, - { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" - }, - "type": { - "type": "string", - "enum": [ - "clickhouse_keeper" - ] - } - }, - "required": [ - "address", - "dataset", - "type" - ] - }, - { - "description": "Used in replicated clickhouse setups", - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" - }, - "type": { - "type": "string", - "enum": [ - "clickhouse_server" - ] - } - }, - "required": [ - "address", - "dataset", - "type" - ] - }, - { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" - }, - "type": { - "type": "string", - "enum": [ - "cockroach_db" - ] - } - }, - "required": [ - "address", - "dataset", - "type" - ] - }, - { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" - }, - "type": { - "type": "string", - "enum": [ - "crucible" - ] - } - }, - "required": [ - "address", - "dataset", - "type" - ] - }, - { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "crucible_pantry" - ] - } - }, - "required": [ - "address", - "type" - ] - }, - { - "type": "object", - "properties": { - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" - }, - "dns_address": { - "description": "The address at which the external DNS server is reachable.", - "allOf": [ - { - "$ref": "#/components/schemas/OmicronZoneExternalFloatingAddr" - } - ] - }, - "http_address": { - "description": "The address at which the external DNS server API is reachable.", - "type": "string" - }, - "nic": { - "description": "The service vNIC providing external connectivity using OPTE.", - "allOf": [ - { - "$ref": "#/components/schemas/NetworkInterface" - } - ] - }, - "type": { - "type": "string", - "enum": [ - "external_dns" - ] - } - }, - "required": [ - "dataset", - "dns_address", - "http_address", - "nic", - "type" - ] - }, - { - "type": "object", - "properties": { - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" - }, - "dns_address": { - "type": "string" - }, - "gz_address": { - "description": "The addresses in the global zone which should be created\n\nFor the DNS service, which exists outside the sleds's typical subnet - adding an address in the GZ is necessary to allow inter-zone traffic routing.", - "type": "string", - "format": "ipv6" - }, - "gz_address_index": { - "description": "The address is also identified with an auxiliary bit of information to ensure that the created global zone address can have a unique name.", - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "http_address": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "internal_dns" - ] - } - }, - "required": [ - "dataset", - "dns_address", - "gz_address", - "gz_address_index", - "http_address", - "type" - ] - }, - { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "internal_ntp" - ] - } - }, - "required": [ - "address", - "type" - ] - }, - { - "type": "object", - "properties": { - "external_dns_servers": { - "description": "External DNS servers Nexus can use to resolve external hosts.", - "type": "array", - "items": { - "type": "string", - "format": "ip" - } - }, - "external_ip": { - "description": "The address at which the external nexus server is reachable.", - "allOf": [ - { - "$ref": "#/components/schemas/OmicronZoneExternalFloatingIp" - } - ] - }, - "external_tls": { - "description": "Whether Nexus's external endpoint should use TLS", - "type": "boolean" - }, - "internal_address": { - "description": "The address at which the internal nexus server is reachable.", - "type": "string" - }, - "lockstep_port": { - "description": "The port at which the lockstep server is reachable. This shares the same IP address with `internal_address`.", - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "nexus_generation": { - "description": "Generation number for this Nexus zone. This is used to coordinate handoff between old and new Nexus instances during updates. See RFD 588.", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "nic": { - "description": "The service vNIC providing external connectivity using OPTE.", - "allOf": [ - { - "$ref": "#/components/schemas/NetworkInterface" - } - ] - }, - "type": { - "type": "string", - "enum": [ - "nexus" - ] - } - }, - "required": [ - "external_dns_servers", - "external_ip", - "external_tls", - "internal_address", - "lockstep_port", - "nexus_generation", - "nic", - "type" - ] - }, - { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "oximeter" - ] - } - }, - "required": [ - "address", - "type" - ] - } - ] - }, - "ByteCount": { - "description": "Byte count to express memory or storage capacity.", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "CabooseWhich": { - "description": "Describes which caboose this is (which component, which slot)", - "type": "string", - "enum": [ - "sp_slot0", - "sp_slot1", - "rot_slot_a", - "rot_slot_b", - "stage0", - "stage0_next" - ] - }, - "Certificate": { - "type": "object", - "properties": { - "cert": { - "type": "string" - }, - "key": { - "type": "string" - } - }, - "required": [ - "cert", - "key" - ] - }, - "ClickhouseClusterConfig": { - "description": "Global configuration for all clickhouse servers (replicas) and keepers", - "type": "object", - "properties": { - "cluster_name": { - "description": "An arbitrary name for the Clickhouse cluster shared by all nodes", - "type": "string" - }, - "cluster_secret": { - "description": "An arbitrary string shared by all nodes used at runtime to determine whether nodes are part of the same cluster.", - "type": "string" - }, - "generation": { - "description": "The last update to the clickhouse cluster configuration\n\nThis is used by `clickhouse-admin` in the clickhouse server and keeper zones to discard old configurations.", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "highest_seen_keeper_leader_committed_log_index": { - "description": "This is used as a marker to tell if the raft configuration in a new inventory collection is newer than the last collection. This serves as a surrogate for the log index of the last committed configuration, which clickhouse keeper doesn't expose.\n\nThis is necesssary because during inventory collection we poll multiple keeper nodes, and each returns their local knowledge of the configuration. But we may reach different nodes in different attempts, and some nodes in a following attempt may reflect stale configuration. Due to timing, we can always query old information. That is just normal polling. However, we never want to use old configuration if we have already seen and acted on newer configuration.", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "keepers": { - "description": "The desired state of the clickhouse keeper cluster\n\nWe decouple deployment of zones that should contain clickhouse keeper processes from actually starting or stopping those processes, adding or removing them to/from the keeper cluster, and reconfiguring other keeper and clickhouse server nodes to reflect the new configuration.\n\nAs part of this decoupling, we keep track of the intended zone deployment in the blueprint, but that is not enough to track the desired state of the keeper cluster. We are only allowed to add or remove one keeper node at a time, and therefore we must track the desired state of the keeper cluster which may change multiple times until the keepers in the cluster match the deployed zones. An example may help:\n\n1. We start with 3 keeper nodes in 3 deployed keeper zones and need to add two to reach our desired policy of 5 keepers 2. The planner adds 2 new keeper zones to the blueprint 3. The planner will also add **one** new keeper to the `keepers` field below that matches one of the deployed zones. 4. The executor will start the new keeper process that was added to the `keepers` field, attempt to add it to the keeper cluster by pushing configuration updates to the other keepers, and then updating the clickhouse server configurations to know about the new keeper. 5. If the keeper is successfully added, as reflected in inventory, then steps 3 and 4 above will be repeated for the next keeper process. 6. If the keeper is not successfully added by the executor it will continue to retry indefinitely. 7. If the zone is expunged while the planner has it as part of its desired state in `keepers`, and the executor is trying to add it, the keeper will be removed from `keepers` in the next blueprint. If it has been added to the actual cluster by an executor in the meantime it will be removed on the next iteration of an executor.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/KeeperId" - } - }, - "max_used_keeper_id": { - "description": "Clickhouse Keeper IDs must be unique and are handed out monotonically. Keep track of the last used one.", - "allOf": [ - { - "$ref": "#/components/schemas/KeeperId" - } - ] - }, - "max_used_server_id": { - "description": "Clickhouse Server IDs must be unique and are handed out monotonically. Keep track of the last used one.", - "allOf": [ - { - "$ref": "#/components/schemas/ServerId" - } - ] - }, - "servers": { - "description": "The desired state of clickhouse server processes on the rack\n\nClickhouse servers do not have the same limitations as keepers and can be deployed all at once.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/ServerId" - } - } - }, - "required": [ - "cluster_name", - "cluster_secret", - "generation", - "highest_seen_keeper_leader_committed_log_index", - "keepers", - "max_used_keeper_id", - "max_used_server_id", - "servers" - ] - }, - "CockroachDbClusterVersion": { - "description": "CockroachDB cluster versions we are aware of.\n\nCockroachDB can be upgraded from one major version to the next, e.g. v22.1 -> v22.2. Each major version introduces changes in how it stores data on disk to support new features, and each major version has support for reading the previous version's data so that it can perform an upgrade. The version of the data format is called the \"cluster version\", which is distinct from but related to the software version that's being run.\n\nWhile software version v22.2 is using cluster version v22.1, it's possible to downgrade back to v22.1. Once the cluster version is upgraded, there's no going back.\n\nTo give us some time to evaluate new versions of the software while retaining a downgrade path, we currently deploy new versions of CockroachDB across two releases of the Oxide software, in a \"tick-tock\" model:\n\n- In \"tick\" releases, we upgrade the version of the CockroachDB software to a new major version, and update `CockroachDbClusterVersion::NEWLY_INITIALIZED`. On upgraded racks, the new version is running with the previous cluster version; on newly-initialized racks, the new version is running with the new cluser version. - In \"tock\" releases, we change `CockroachDbClusterVersion::POLICY` to the major version we upgraded to in the last \"tick\" release. This results in a new blueprint that upgrades the cluster version, destroying the downgrade path but allowing us to eventually upgrade to the next release.\n\nThese presently describe major versions of CockroachDB. The order of these must be maintained in the correct order (the first variant must be the earliest version).", - "type": "string", - "enum": [ - "V22_1" - ] - }, - "CockroachDbPreserveDowngrade": { - "description": "Whether to set `cluster.preserve_downgrade_option` and what to set it to.", - "oneOf": [ - { - "description": "Do not modify the setting.", - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": [ - "do_not_modify" - ] - } - }, - "required": [ - "action" - ] - }, - { - "description": "Ensure the setting is set to an empty string.", - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": [ - "allow_upgrade" - ] - } - }, - "required": [ - "action" - ] - }, - { - "description": "Ensure the setting is set to a given cluster version.", - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": [ - "set" - ] - }, - "data": { - "$ref": "#/components/schemas/CockroachDbClusterVersion" - } - }, - "required": [ - "action", - "data" - ] - } - ] - }, - "CockroachdbUnsafeToShutdown": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "missing_live_nodes_stat" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "missing_underreplicated_stat" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "live_nodes": { - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "type": { - "type": "string", - "enum": [ - "not_enough_live_nodes" - ] - } - }, - "required": [ - "live_nodes", - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "not_enough_nodes" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "n": { - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "type": { - "type": "string", - "enum": [ - "underreplicated_ranges" - ] - } - }, - "required": [ - "n", - "type" - ] - } - ] - }, - "CompressionAlgorithm": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "on" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "off" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "gzip" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "level": { - "$ref": "#/components/schemas/GzipLevel" - }, - "type": { - "type": "string", - "enum": [ - "gzip_n" - ] - } - }, - "required": [ - "level", - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "lz4" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "lzjb" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "zle" - ] - } - }, - "required": [ - "type" - ] - } - ] - }, - "CrucibleDatasetCreateRequest": { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "dataset_id": { - "$ref": "#/components/schemas/DatasetUuid" - }, - "zpool_id": { - "$ref": "#/components/schemas/ZpoolUuid" - } - }, - "required": [ - "address", - "dataset_id", - "zpool_id" - ] - }, - "DatasetKind": { - "description": "The kind of dataset. See the `DatasetKind` enum in omicron-common for possible values.", - "type": "string" - }, - "DatasetUuid": { - "x-rust-type": { - "crate": "omicron-uuid-kinds", - "path": "omicron_uuid_kinds::DatasetUuid", - "version": "*" - }, - "type": "string", - "format": "uuid" - }, - "DiscretionaryZonePlacement": { - "type": "object", - "properties": { - "kind": { - "type": "string" - }, - "source": { - "type": "string" - } - }, - "required": [ - "kind", - "source" - ] - }, - "DiskIdentity": { - "description": "Uniquely identifies a disk.", - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "serial": { - "type": "string" - }, - "vendor": { - "type": "string" - } - }, - "required": [ - "model", - "serial", - "vendor" - ] - }, - "DiskRuntimeState": { - "description": "Runtime state of the Disk, which includes its attach state and some minimal metadata", - "type": "object", - "properties": { - "disk_state": { - "description": "runtime state of the Disk", - "allOf": [ - { - "$ref": "#/components/schemas/DiskState" - } - ] - }, - "gen": { - "description": "generation number for this state", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "time_updated": { - "description": "timestamp for this information", - "type": "string", - "format": "date-time" - } - }, - "required": [ - "disk_state", - "gen", - "time_updated" - ] - }, - "DiskState": { - "description": "State of a Disk", - "oneOf": [ - { - "description": "Disk is being initialized", - "type": "object", - "properties": { - "state": { - "type": "string", - "enum": [ - "creating" - ] - } - }, - "required": [ - "state" - ] - }, - { - "description": "Disk is ready but detached from any Instance", - "type": "object", - "properties": { - "state": { - "type": "string", - "enum": [ - "detached" - ] - } - }, - "required": [ - "state" - ] - }, - { - "description": "Disk is ready to receive blocks from an external source", - "type": "object", - "properties": { - "state": { - "type": "string", - "enum": [ - "import_ready" - ] - } - }, - "required": [ - "state" - ] - }, - { - "description": "Disk is importing blocks from a URL", - "type": "object", - "properties": { - "state": { - "type": "string", - "enum": [ - "importing_from_url" - ] - } - }, - "required": [ - "state" - ] - }, - { - "description": "Disk is importing blocks from bulk writes", - "type": "object", - "properties": { - "state": { - "type": "string", - "enum": [ - "importing_from_bulk_writes" - ] - } - }, - "required": [ - "state" - ] - }, - { - "description": "Disk is being finalized to state Detached", - "type": "object", - "properties": { - "state": { - "type": "string", - "enum": [ - "finalizing" - ] - } - }, - "required": [ - "state" - ] - }, - { - "description": "Disk is undergoing maintenance", - "type": "object", - "properties": { - "state": { - "type": "string", - "enum": [ - "maintenance" - ] - } - }, - "required": [ - "state" - ] - }, - { - "description": "Disk is being attached to the given Instance", - "type": "object", - "properties": { - "instance": { - "type": "string", - "format": "uuid" - }, - "state": { - "type": "string", - "enum": [ - "attaching" - ] - } - }, - "required": [ - "instance", - "state" - ] - }, - { - "description": "Disk is attached to the given Instance", - "type": "object", - "properties": { - "instance": { - "type": "string", - "format": "uuid" - }, - "state": { - "type": "string", - "enum": [ - "attached" - ] - } - }, - "required": [ - "instance", - "state" - ] - }, - { - "description": "Disk is being detached from the given Instance", - "type": "object", - "properties": { - "instance": { - "type": "string", - "format": "uuid" - }, - "state": { - "type": "string", - "enum": [ - "detaching" - ] - } - }, - "required": [ - "instance", - "state" - ] - }, - { - "description": "Disk has been destroyed", - "type": "object", - "properties": { - "state": { - "type": "string", - "enum": [ - "destroyed" - ] - } - }, - "required": [ - "state" - ] - }, - { - "description": "Disk is unavailable", - "type": "object", - "properties": { - "state": { - "type": "string", - "enum": [ - "faulted" - ] - } - }, - "required": [ - "state" - ] - } - ] - }, - "DnsConfigParams": { - "type": "object", - "properties": { - "generation": { - "$ref": "#/components/schemas/Generation" - }, - "serial": { - "description": "See [`DnsConfig`]'s `serial` field for how this is different from `generation`", - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "time_created": { - "type": "string", - "format": "date-time" - }, - "zones": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DnsConfigZone" - } - } - }, - "required": [ - "generation", - "serial", - "time_created", - "zones" - ] - }, - "DnsConfigZone": { - "description": "Configuration for a specific DNS zone, as opposed to illumos zones in which the services described by these records run.\n\nThe name `@` is special: it describes records that should be provided for queries about `zone_name`. This is used in favor of the empty string as `@` is the name used for this purpose in zone files for most DNS configurations. It also avoids potentially-confusing debug output from naively printing out records and their names - if you've seen an `@` record and tools are unclear about what that means, hopefully you've arrived here!", - "type": "object", - "properties": { - "records": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DnsRecord" - } - } - }, - "zone_name": { - "type": "string" - } - }, - "required": [ - "records", - "zone_name" - ] - }, - "DnsRecord": { - "oneOf": [ - { - "type": "object", - "properties": { - "data": { - "type": "string", - "format": "ipv4" - }, - "type": { - "type": "string", - "enum": [ - "A" - ] - } - }, - "required": [ - "data", - "type" - ] - }, - { - "type": "object", - "properties": { - "data": { - "type": "string", - "format": "ipv6" - }, - "type": { - "type": "string", - "enum": [ - "AAAA" - ] - } - }, - "required": [ - "data", - "type" - ] - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/components/schemas/Srv" - }, - "type": { - "type": "string", - "enum": [ - "SRV" - ] - } - }, - "required": [ - "data", - "type" - ] - }, - { - "type": "object", - "properties": { - "data": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "NS" - ] - } - }, - "required": [ - "data", - "type" - ] - } - ] - }, - "DownstairsClientStopRequest": { - "type": "object", - "properties": { - "reason": { - "$ref": "#/components/schemas/DownstairsClientStopRequestReason" - }, - "time": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "reason", - "time" - ] - }, - "DownstairsClientStopRequestReason": { - "type": "string", - "enum": [ - "replacing", - "disabled", - "failed_reconcile", - "i_o_error", - "bad_negotiation_order", - "incompatible", - "failed_live_repair", - "too_many_outstanding_jobs", - "deactivated" - ] - }, - "DownstairsClientStopped": { - "type": "object", - "properties": { - "reason": { - "$ref": "#/components/schemas/DownstairsClientStoppedReason" - }, - "time": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "reason", - "time" - ] - }, - "DownstairsClientStoppedReason": { - "type": "string", - "enum": [ - "connection_timeout", - "connection_failed", - "timeout", - "write_failed", - "read_failed", - "requested_stop", - "finished", - "queue_closed", - "receive_task_cancelled" - ] - }, - "DownstairsRegionUuid": { - "x-rust-type": { - "crate": "omicron-uuid-kinds", - "path": "omicron_uuid_kinds::DownstairsRegionUuid", - "version": "*" - }, - "type": "string", - "format": "uuid" - }, - "DownstairsUnderRepair": { - "type": "object", - "properties": { - "region_uuid": { - "$ref": "#/components/schemas/DownstairsRegionUuid" - }, - "target_addr": { - "type": "string" - } - }, - "required": [ - "region_uuid", - "target_addr" - ] - }, - "Duration": { - "type": "object", - "properties": { - "nanos": { - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "secs": { - "type": "integer", - "format": "uint64", - "minimum": 0 - } - }, - "required": [ - "nanos", - "secs" - ] - }, - "Error": { - "description": "Error information from a response.", - "type": "object", - "properties": { - "error_code": { - "type": "string" - }, - "message": { - "type": "string" - }, - "request_id": { - "type": "string" - } - }, - "required": [ - "message", - "request_id" - ] - }, - "ExpectedActiveRotSlot": { - "description": "Describes the expected active RoT slot, and the version we expect to find for it", - "type": "object", - "properties": { - "slot": { - "$ref": "#/components/schemas/RotSlot" - }, - "version": { - "$ref": "#/components/schemas/ArtifactVersion" - } - }, - "required": [ - "slot", - "version" - ] - }, - "ExpectedVersion": { - "description": "Describes the version that we expect to find in some firmware slot", - "oneOf": [ - { - "description": "We expect to find _no_ valid caboose in this slot", - "type": "object", - "properties": { - "kind": { - "type": "string", - "enum": [ - "no_valid_version" - ] - } - }, - "required": [ - "kind" - ] - }, - { - "description": "We expect to find the specified version in this slot", - "type": "object", - "properties": { - "kind": { - "type": "string", - "enum": [ - "version" - ] - }, - "version": { - "$ref": "#/components/schemas/ArtifactVersion" - } - }, - "required": [ - "kind", - "version" - ] - } - ] - }, - "ExternalIpUuid": { - "x-rust-type": { - "crate": "omicron-uuid-kinds", - "path": "omicron_uuid_kinds::ExternalIpUuid", - "version": "*" - }, - "type": "string", - "format": "uuid" - }, - "ExternalPortDiscovery": { - "oneOf": [ - { - "type": "object", - "properties": { - "auto": { - "type": "object", - "additionalProperties": { - "type": "string", - "format": "ipv6" - } - } - }, - "required": [ - "auto" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "static": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Name" - } - } - } - }, - "required": [ - "static" - ], - "additionalProperties": false - } - ] - }, - "FailedHostOsUpdateReason": { - "description": "Describes the reason why a Host OS failed to update", - "oneOf": [ - { - "description": "The active host phase 1 slot does not match the boot disk", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "active_host_phase1_slot_boot_disk_mismatch" - ] - }, - "value": { - "$ref": "#/components/schemas/M2Slot" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "The active host phase 1 hash was not found in inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "active_host_phase1_hash_not_in_inventory" - ] - }, - "value": { - "$ref": "#/components/schemas/M2Slot" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "The active host phase 1 slot was not found in inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "active_host_phase1_slot_not_in_inventory" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "The component's caboose was not found in the inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "caboose_not_in_inventory" - ] - }, - "value": { - "$ref": "#/components/schemas/CabooseWhich" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "The version in the caboose or artifact was not able to be parsed", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "failed_version_parse" - ] - }, - "value": { - "type": "object", - "properties": { - "caboose": { - "$ref": "#/components/schemas/CabooseWhich" - }, - "err": { - "type": "string" - } - }, - "required": [ - "caboose", - "err" - ] - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "The inactive host phase 1 hash was not found in inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "inactive_host_phase1_hash_not_in_inventory" - ] - }, - "value": { - "$ref": "#/components/schemas/M2Slot" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "Last reconciliation details were not found in inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "last_reconciliation_not_in_inventory" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "No artifacts with the required conditions for the component were found", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "no_matching_artifacts_found" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "No artifact with the required conditions for phase 1 was found", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "no_matching_phase1_artifact_found" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "No artifact with the required conditions for phase 2 was found", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "no_matching_phase2_artifact_found" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "Sled agent info was not found in inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "sled_agent_info_not_in_inventory" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "The component's corresponding SP was not found in the inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "sp_not_in_inventory" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "Too many artifacts with the required conditions for the component were found", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "too_many_matching_artifacts" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "The sled agent reported an error determining the boot disk", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "unable_to_determine_boot_disk" - ] - }, - "value": { - "type": "string" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "The sled agent reported an error retrieving boot disk phase 2 image details", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "unable_to_retrieve_boot_disk_phase2_image" - ] - }, - "value": { - "type": "string" - } - }, - "required": [ - "type", - "value" - ] - } - ] - }, - "FailedMgsUpdateReason": { - "description": "Describes the reason why an SP component failed to update", - "oneOf": [ - { - "description": "There was a failed attempt to plan a Host OS update", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "host_os" - ] - }, - "value": { - "$ref": "#/components/schemas/FailedHostOsUpdateReason" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "There was a failed attempt to plan an RoT update", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "rot" - ] - }, - "value": { - "$ref": "#/components/schemas/FailedRotUpdateReason" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "There was a failed attempt to plan an RoT bootloader update", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "rot_bootloader" - ] - }, - "value": { - "$ref": "#/components/schemas/FailedRotBootloaderUpdateReason" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "There was a failed attempt to plan an SP update", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "sp" - ] - }, - "value": { - "$ref": "#/components/schemas/FailedSpUpdateReason" - } - }, - "required": [ - "type", - "value" - ] - } - ] - }, - "FailedRotBootloaderUpdateReason": { - "description": "Describes the reason why an RoT bootloader failed to update", - "oneOf": [ - { - "description": "The component's caboose was missing a value for \"sign\"", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "caboose_missing_sign" - ] - }, - "value": { - "$ref": "#/components/schemas/CabooseWhich" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "The component's caboose was not found in the inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "caboose_not_in_inventory" - ] - }, - "value": { - "$ref": "#/components/schemas/CabooseWhich" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "The version in the caboose or artifact was not able to be parsed", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "failed_version_parse" - ] - }, - "value": { - "type": "object", - "properties": { - "caboose": { - "$ref": "#/components/schemas/CabooseWhich" - }, - "err": { - "type": "string" - } - }, - "required": [ - "caboose", - "err" - ] - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "No artifact with the required conditions for the component was found", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "no_matching_artifact_found" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "The component's corresponding SP was not found in the inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "sp_not_in_inventory" - ] - } - }, - "required": [ - "type" - ] - } - ] - }, - "FailedRotUpdateReason": { - "description": "Describes the reason why an RoT failed to update", - "oneOf": [ - { - "description": "The component's caboose was missing a value for \"sign\"", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "caboose_missing_sign" - ] - }, - "value": { - "$ref": "#/components/schemas/CabooseWhich" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "The component's caboose was not found in the inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "caboose_not_in_inventory" - ] - }, - "value": { - "$ref": "#/components/schemas/CabooseWhich" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "The version in the caboose or artifact was not able to be parsed", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "failed_version_parse" - ] - }, - "value": { - "type": "object", - "properties": { - "caboose": { - "$ref": "#/components/schemas/CabooseWhich" - }, - "err": { - "type": "string" - } - }, - "required": [ - "caboose", - "err" - ] - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "No artifact with the required conditions for the component was found", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "no_matching_artifact_found" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "RoT state was not found in inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "rot_state_not_in_inventory" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "The component's corresponding SP was not found in the inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "sp_not_in_inventory" - ] - } - }, - "required": [ - "type" - ] - } - ] - }, - "FailedSpUpdateReason": { - "description": "Describes the reason why an SP failed to update", - "oneOf": [ - { - "description": "The component's caboose was not found in the inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "caboose_not_in_inventory" - ] - }, - "value": { - "$ref": "#/components/schemas/CabooseWhich" - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "The version in the caboose or artifact was not able to be parsed", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "failed_version_parse" - ] - }, - "value": { - "type": "object", - "properties": { - "caboose": { - "$ref": "#/components/schemas/CabooseWhich" - }, - "err": { - "type": "string" - } - }, - "required": [ - "caboose", - "err" - ] - } - }, - "required": [ - "type", - "value" - ] - }, - { - "description": "No artifact with the required conditions for the component was found", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "no_matching_artifact_found" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "The component's corresponding SP was not found in the inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "sp_not_in_inventory" - ] - } - }, - "required": [ - "type" - ] - } - ] - }, - "Generation": { - "description": "Generation numbers stored in the database, used for optimistic concurrency control", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "GzipLevel": { - "type": "integer", - "format": "uint8", - "minimum": 0 - }, - "IdMapBlueprintDatasetConfig": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/BlueprintDatasetConfig" - } - }, - "IdMapBlueprintPhysicalDiskConfig": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/BlueprintPhysicalDiskConfig" - } - }, - "IdMapBlueprintZoneConfig": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/BlueprintZoneConfig" - } - }, - "ImportExportPolicy": { - "description": "Define policy relating to the import and export of prefixes from a BGP peer.", - "oneOf": [ - { - "description": "Do not perform any filtering.", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "no_filtering" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "allow" - ] - }, - "value": { - "type": "array", - "items": { - "$ref": "#/components/schemas/IpNet" - } - } - }, - "required": [ - "type", - "value" - ] - } - ] - }, - "IpNet": { - "x-rust-type": { - "crate": "oxnet", - "path": "oxnet::IpNet", - "version": "0.1.0" - }, - "oneOf": [ - { - "title": "v4", - "allOf": [ - { - "$ref": "#/components/schemas/Ipv4Net" - } - ] - }, - { - "title": "v6", - "allOf": [ - { - "$ref": "#/components/schemas/Ipv6Net" - } - ] - } - ] - }, - "IpRange": { - "oneOf": [ - { - "title": "v4", - "allOf": [ - { - "$ref": "#/components/schemas/Ipv4Range" - } - ] - }, - { - "title": "v6", - "allOf": [ - { - "$ref": "#/components/schemas/Ipv6Range" - } - ] - } - ] - }, - "Ipv4Net": { - "example": "192.168.1.0/24", - "title": "An IPv4 subnet", - "description": "An IPv4 subnet, including prefix and prefix length", - "x-rust-type": { - "crate": "oxnet", - "path": "oxnet::Ipv4Net", - "version": "0.1.0" - }, - "type": "string", - "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" - }, - "Ipv4Range": { - "description": "A non-decreasing IPv4 address range, inclusive of both ends.\n\nThe first address must be less than or equal to the last address.", - "type": "object", - "properties": { - "first": { - "type": "string", - "format": "ipv4" - }, - "last": { - "type": "string", - "format": "ipv4" - } - }, - "required": [ - "first", - "last" - ] - }, - "Ipv6Net": { - "example": "fd12:3456::/64", - "title": "An IPv6 subnet", - "description": "An IPv6 subnet, including prefix and subnet mask", - "x-rust-type": { - "crate": "oxnet", - "path": "oxnet::Ipv6Net", - "version": "0.1.0" - }, - "type": "string", - "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" - }, - "Ipv6Range": { - "description": "A non-decreasing IPv6 address range, inclusive of both ends.\n\nThe first address must be less than or equal to the last address.", - "type": "object", - "properties": { - "first": { - "type": "string", - "format": "ipv6" - }, - "last": { - "type": "string", - "format": "ipv6" - } - }, - "required": [ - "first", - "last" - ] - }, - "KeeperId": { - "description": "A unique ID for a ClickHouse Keeper", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "LldpAdminStatus": { - "description": "To what extent should this port participate in LLDP", - "type": "string", - "enum": [ - "enabled", - "disabled", - "rx_only", - "tx_only" - ] - }, - "LldpPortConfig": { - "description": "Per-port LLDP configuration settings. Only the \"status\" setting is mandatory. All other fields have natural defaults or may be inherited from the switch.", - "type": "object", - "properties": { - "chassis_id": { - "nullable": true, - "description": "Chassis ID to advertise. If this is set, it will be advertised as a LocallyAssigned ID type. If this is not set, it will be inherited from the switch-level settings.", - "type": "string" - }, - "management_addrs": { - "nullable": true, - "description": "Management IP addresses to advertise. If this is not set, it will be inherited from the switch-level settings.", - "type": "array", - "items": { - "type": "string", - "format": "ip" - } - }, - "port_description": { - "nullable": true, - "description": "Port description to advertise. If this is not set, no description will be advertised.", - "type": "string" - }, - "port_id": { - "nullable": true, - "description": "Port ID to advertise. If this is set, it will be advertised as a LocallyAssigned ID type. If this is not set, it will be set to the port name. e.g., qsfp0/0.", - "type": "string" - }, - "status": { - "description": "To what extent should this port participate in LLDP", - "allOf": [ - { - "$ref": "#/components/schemas/LldpAdminStatus" - } - ] - }, - "system_description": { - "nullable": true, - "description": "System description to advertise. If this is not set, it will be inherited from the switch-level settings.", - "type": "string" - }, - "system_name": { - "nullable": true, - "description": "System name to advertise. If this is not set, it will be inherited from the switch-level settings.", - "type": "string" - } - }, - "required": [ - "status" - ] - }, - "M2Slot": { - "description": "Describes an M.2 slot, often in the context of writing a system image to it.", - "type": "string", - "enum": [ - "A", - "B" - ] - }, - "MacAddr": { - "example": "ff:ff:ff:ff:ff:ff", - "title": "A MAC address", - "description": "A Media Access Control address, in EUI-48 format", - "type": "string", - "pattern": "^([0-9a-fA-F]{0,2}:){5}[0-9a-fA-F]{0,2}$", - "minLength": 5, - "maxLength": 17 - }, - "MigrationRuntimeState": { - "description": "An update from a sled regarding the state of a migration, indicating the role of the VMM whose migration state was updated.", - "type": "object", - "properties": { - "gen": { - "$ref": "#/components/schemas/Generation" - }, - "migration_id": { - "type": "string", - "format": "uuid" - }, - "state": { - "$ref": "#/components/schemas/MigrationState" - }, - "time_updated": { - "description": "Timestamp for the migration state update.", - "type": "string", - "format": "date-time" - } - }, - "required": [ - "gen", - "migration_id", - "state", - "time_updated" - ] - }, - "MigrationState": { - "description": "The state of an instance's live migration.", - "oneOf": [ - { - "description": "The migration has not started for this VMM.", - "type": "string", - "enum": [ - "pending" - ] - }, - { - "description": "The migration is in progress.", - "type": "string", - "enum": [ - "in_progress" - ] - }, - { - "description": "The migration has failed.", - "type": "string", - "enum": [ - "failed" - ] - }, - { - "description": "The migration has completed.", - "type": "string", - "enum": [ - "completed" - ] - } - ] - }, - "MupdateOverrideUuid": { - "x-rust-type": { - "crate": "omicron-uuid-kinds", - "path": "omicron_uuid_kinds::MupdateOverrideUuid", - "version": "*" - }, - "type": "string", - "format": "uuid" - }, - "Name": { - "title": "A name unique within the parent collection", - "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID, but they may contain a UUID. They can be at most 63 characters long.", - "type": "string", - "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z]([a-zA-Z0-9-]*[a-zA-Z0-9]+)?$", - "minLength": 1, - "maxLength": 63 - }, - "NatEntryView": { - "description": "NAT Record\n\nA NAT record maps an external IP address, used by an instance or externally-facing service like Nexus, to the hosting sled.", - "type": "object", - "properties": { - "deleted": { - "type": "boolean" - }, - "external_address": { - "type": "string", - "format": "ip" - }, - "first_port": { - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "gen": { - "type": "integer", - "format": "int64" - }, - "last_port": { - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "mac": { - "$ref": "#/components/schemas/MacAddr" - }, - "sled_address": { - "type": "string", - "format": "ipv6" - }, - "vni": { - "$ref": "#/components/schemas/Vni" - } - }, - "required": [ - "deleted", - "external_address", - "first_port", - "gen", - "last_port", - "mac", - "sled_address", - "vni" - ] - }, - "NetworkInterface": { - "description": "Information required to construct a virtual network interface", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "ip": { - "type": "string", - "format": "ip" - }, - "kind": { - "$ref": "#/components/schemas/NetworkInterfaceKind" - }, - "mac": { - "$ref": "#/components/schemas/MacAddr" - }, - "name": { - "$ref": "#/components/schemas/Name" - }, - "primary": { - "type": "boolean" - }, - "slot": { - "type": "integer", - "format": "uint8", - "minimum": 0 - }, - "subnet": { - "$ref": "#/components/schemas/IpNet" - }, - "transit_ips": { - "default": [], - "type": "array", - "items": { - "$ref": "#/components/schemas/IpNet" - } - }, - "vni": { - "$ref": "#/components/schemas/Vni" - } - }, - "required": [ - "id", - "ip", - "kind", - "mac", - "name", - "primary", - "slot", - "subnet", - "vni" - ] - }, - "NetworkInterfaceKind": { - "description": "The type of network interface", - "oneOf": [ - { - "description": "A vNIC attached to a guest instance", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "instance" - ] - } - }, - "required": [ - "id", - "type" - ] - }, - { - "description": "A vNIC associated with an internal service", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "service" - ] - } - }, - "required": [ - "id", - "type" - ] - }, - { - "description": "A vNIC associated with a probe", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { - "type": "string", - "enum": [ - "probe" - ] - } - }, - "required": [ - "id", - "type" - ] - } - ] - }, - "NewPasswordHash": { - "title": "A password hash in PHC string format", - "description": "Password hashes must be in PHC (Password Hashing Competition) string format. Passwords must be hashed with Argon2id. Password hashes may be rejected if the parameters appear not to be secure enough.", - "type": "string" - }, - "NexusGenerationBumpWaitingOn": { - "oneOf": [ - { - "description": "Waiting for the planner to finish updating all non-Nexus zones", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "found_old_non_nexus_zones" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "Waiting for the planner to deploy new-generation Nexus zones", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "missing_new_nexus_in_blueprint" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "Waiting for `db_metadata_nexus` records to be deployed for new-generation Nexus zones", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "missing_nexus_database_access_records" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "Waiting for newly deployed Nexus zones to appear to inventory", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "missing_new_nexus_in_inventory" - ] - } - }, - "required": [ - "type" - ] - } - ] - }, - "OmicronZoneDataset": { - "description": "Describes a persistent ZFS dataset associated with an Omicron zone", - "type": "object", - "properties": { - "pool_name": { - "$ref": "#/components/schemas/ZpoolName" - } - }, - "required": [ - "pool_name" - ] - }, - "OmicronZoneExternalFloatingAddr": { - "description": "Floating external address with port allocated to an Omicron-managed zone.", - "type": "object", - "properties": { - "addr": { - "type": "string" - }, - "id": { - "$ref": "#/components/schemas/ExternalIpUuid" - } - }, - "required": [ - "addr", - "id" - ] - }, - "OmicronZoneExternalFloatingIp": { - "description": "Floating external IP allocated to an Omicron-managed zone.\n\nThis is a slimmer `nexus_db_model::ExternalIp` that only stores the fields necessary for blueprint planning, and requires that the zone have a single IP.", - "type": "object", - "properties": { - "id": { - "$ref": "#/components/schemas/ExternalIpUuid" - }, - "ip": { - "type": "string", - "format": "ip" - } - }, - "required": [ - "id", - "ip" - ] - }, - "OmicronZoneExternalSnatIp": { - "description": "SNAT (outbound) external IP allocated to an Omicron-managed zone.\n\nThis is a slimmer `nexus_db_model::ExternalIp` that only stores the fields necessary for blueprint planning, and requires that the zone have a single IP.", - "type": "object", - "properties": { - "id": { - "$ref": "#/components/schemas/ExternalIpUuid" - }, - "snat_cfg": { - "$ref": "#/components/schemas/SourceNatConfig" - } - }, - "required": [ - "id", - "snat_cfg" - ] - }, - "OmicronZoneUuid": { - "x-rust-type": { - "crate": "omicron-uuid-kinds", - "path": "omicron_uuid_kinds::OmicronZoneUuid", - "version": "*" - }, - "type": "string", - "format": "uuid" - }, - "OximeterInfo": { - "description": "Message used to notify Nexus that this oximeter instance is up and running.", - "type": "object", - "properties": { - "address": { - "description": "The address on which this oximeter instance listens for requests", - "type": "string" - }, - "collector_id": { - "description": "The ID for this oximeter instance.", - "type": "string", - "format": "uuid" - } - }, - "required": [ - "address", - "collector_id" - ] - }, - "OximeterReadMode": { - "description": "Where oximeter should read from", - "oneOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "single_node" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "cluster" - ] - } - }, - "required": [ - "type" - ] - } - ] - }, - "PendingMgsUpdate": { - "type": "object", - "properties": { - "artifact_hash": { - "description": "which artifact to apply to this device", - "type": "string", - "format": "hex string (32 bytes)" - }, - "artifact_version": { - "$ref": "#/components/schemas/ArtifactVersion" - }, - "baseboard_id": { - "description": "id of the baseboard that we're going to update", - "allOf": [ - { - "$ref": "#/components/schemas/BaseboardId" - } - ] - }, - "details": { - "description": "component-specific details of the pending update", - "allOf": [ - { - "$ref": "#/components/schemas/PendingMgsUpdateDetails" - } - ] - }, - "slot_id": { - "description": "last known MGS slot (cubby number) of the baseboard", - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "sp_type": { - "description": "what type of baseboard this is", - "allOf": [ - { - "$ref": "#/components/schemas/SpType" - } - ] - } - }, - "required": [ - "artifact_hash", - "artifact_version", - "baseboard_id", - "details", - "slot_id", - "sp_type" - ] - }, - "PendingMgsUpdateDetails": { - "description": "Describes the component-specific details of a PendingMgsUpdate", - "oneOf": [ - { - "description": "the SP itself is being updated", - "type": "object", - "properties": { - "component": { - "type": "string", - "enum": [ - "sp" - ] - }, - "expected_active_version": { - "description": "expected contents of the active slot", - "allOf": [ - { - "$ref": "#/components/schemas/ArtifactVersion" - } - ] - }, - "expected_inactive_version": { - "description": "expected contents of the inactive slot", - "allOf": [ - { - "$ref": "#/components/schemas/ExpectedVersion" - } - ] - } - }, - "required": [ - "component", - "expected_active_version", - "expected_inactive_version" - ] - }, - { - "description": "the RoT is being updated", - "type": "object", - "properties": { - "component": { - "type": "string", - "enum": [ - "rot" - ] - }, - "expected_active_slot": { - "$ref": "#/components/schemas/ExpectedActiveRotSlot" - }, - "expected_inactive_version": { - "$ref": "#/components/schemas/ExpectedVersion" - }, - "expected_pending_persistent_boot_preference": { - "nullable": true, - "description": "the persistent boot preference written into the CFPA scratch page that will become the persistent boot preference in the authoritative CFPA page upon reboot, unless CFPA update of the authoritative page fails for some reason.", - "allOf": [ - { - "$ref": "#/components/schemas/RotSlot" - } - ] - }, - "expected_persistent_boot_preference": { - "description": "the persistent boot preference written into the current authoritative CFPA page (ping or pong)", - "allOf": [ - { - "$ref": "#/components/schemas/RotSlot" - } - ] - }, - "expected_transient_boot_preference": { - "nullable": true, - "description": "override persistent preference selection for a single boot", - "allOf": [ - { - "$ref": "#/components/schemas/RotSlot" - } - ] - } - }, - "required": [ - "component", - "expected_active_slot", - "expected_inactive_version", - "expected_persistent_boot_preference" - ] - }, - { - "description": "the RoT bootloader is being updated", - "type": "object", - "properties": { - "component": { - "type": "string", - "enum": [ - "rot_bootloader" - ] - }, - "expected_stage0_next_version": { - "description": "expected contents of the stage 0 next", - "allOf": [ - { - "$ref": "#/components/schemas/ExpectedVersion" - } - ] - }, - "expected_stage0_version": { - "description": "expected contents of the stage 0", - "allOf": [ - { - "$ref": "#/components/schemas/ArtifactVersion" - } - ] - } - }, - "required": [ - "component", - "expected_stage0_next_version", - "expected_stage0_version" - ] - }, - { - "description": "the host OS is being updated\n\nWe write the phase 1 via MGS, and have a precheck condition that sled-agent has already written the matching phase 2.", - "type": "object", - "properties": { - "component": { - "type": "string", - "enum": [ - "host_phase1" - ] - }, - "expected_active_phase_1_hash": { - "description": "The hash of the phase 1 slot specified by `expected_active_phase_1_hash`.\n\nWe should always be able to fetch this. Even if the phase 1 contents themselves have been corrupted (very scary for the active slot!), the SP can still hash those contents.", - "type": "string", - "format": "hex string (32 bytes)" - }, - "expected_active_phase_1_slot": { - "description": "Which slot is currently active according to the SP.\n\nThis controls which slot will be used the next time the sled boots; it will _usually_ match `boot_disk`, but differs in the window of time between telling the SP to change which slot to use and the host OS rebooting to actually use that slot.", - "allOf": [ - { - "$ref": "#/components/schemas/M2Slot" - } - ] - }, - "expected_active_phase_2_hash": { - "description": "The hash of the currently-active phase 2 artifact.\n\nIt's possible sled-agent won't be able to report this value, but that would indicate that we don't know the version currently running. The planner wouldn't stage an update without knowing the current version, so if something has gone wrong in the meantime we won't proceede either.", - "type": "string", - "format": "hex string (32 bytes)" - }, - "expected_boot_disk": { - "description": "Which slot the host OS most recently booted from.", - "allOf": [ - { - "$ref": "#/components/schemas/M2Slot" - } - ] - }, - "expected_inactive_phase_1_hash": { - "description": "The hash of the phase 1 slot specified by toggling `expected_active_phase_1_slot` to the other slot.\n\nWe should always be able to fetch this. Even if the phase 1 contents of the inactive slot are entirely bogus, the SP can still hash those contents.", - "type": "string", - "format": "hex string (32 bytes)" - }, - "expected_inactive_phase_2_hash": { - "description": "The hash of the currently-inactive phase 2 artifact.\n\nIt's entirely possible that a sled needing a host OS update has no valid artifact in its inactive slot. However, a precondition for us performing a phase 1 update is that `sled-agent` on the target sled has already written the paired phase 2 artifact to the inactive slot; therefore, we don't need to be able to represent an invalid inactive slot.", - "type": "string", - "format": "hex string (32 bytes)" - }, - "sled_agent_address": { - "description": "Address for contacting sled-agent to check phase 2 contents.", - "type": "string" - } - }, - "required": [ - "component", - "expected_active_phase_1_hash", - "expected_active_phase_1_slot", - "expected_active_phase_2_hash", - "expected_boot_disk", - "expected_inactive_phase_1_hash", - "expected_inactive_phase_2_hash", - "sled_agent_address" - ] - } - ] - }, - "PendingMgsUpdates": { - "type": "object", - "properties": { - "by_baseboard": { - "title": "IdOrdMap", - "x-rust-type": { - "crate": "iddqd", - "parameters": [ - { - "$ref": "#/components/schemas/PendingMgsUpdate" - } - ], - "path": "iddqd::IdOrdMap", - "version": "*" - }, - "type": "array", - "items": { - "$ref": "#/components/schemas/PendingMgsUpdate" - }, - "uniqueItems": true } - }, - "required": [ - "by_baseboard" - ] - }, - "PhysicalDiskKind": { - "description": "Describes the form factor of physical disks.", - "type": "string", - "enum": [ - "m2", - "u2" ] }, - "PhysicalDiskPutRequest": { + "DownstairsClientStopRequest": { "type": "object", "properties": { - "id": { - "$ref": "#/components/schemas/PhysicalDiskUuid" - }, - "model": { - "type": "string" - }, - "serial": { - "type": "string" + "reason": { + "$ref": "#/components/schemas/DownstairsClientStopRequestReason" }, - "sled_id": { + "time": { "type": "string", - "format": "uuid" - }, - "variant": { - "$ref": "#/components/schemas/PhysicalDiskKind" - }, - "vendor": { - "type": "string" - } - }, - "required": [ - "id", - "model", - "serial", - "sled_id", - "variant", - "vendor" - ] - }, - "PhysicalDiskUuid": { - "x-rust-type": { - "crate": "omicron-uuid-kinds", - "path": "omicron_uuid_kinds::PhysicalDiskUuid", - "version": "*" - }, - "type": "string", - "format": "uuid" - }, - "Ping": { - "type": "object", - "properties": { - "status": { - "description": "Whether the external API is reachable. Will always be Ok if the endpoint returns anything at all.", - "allOf": [ - { - "$ref": "#/components/schemas/PingStatus" - } - ] + "format": "date-time" } }, "required": [ - "status" + "reason", + "time" ] }, - "PingStatus": { + "DownstairsClientStopRequestReason": { "type": "string", "enum": [ - "ok" - ] - }, - "PlannerConfig": { - "type": "object", - "properties": { - "add_zones_with_mupdate_override": { - "description": "Whether to add zones even if a mupdate override is present.\n\nOnce Nexus-driven update is active on a customer system, we must not add new zones while the system is recovering from a MUPdate.\n\nThis setting, which is off by default, allows us to add zones even if we've detected a recent MUPdate on the system.", - "type": "boolean" - } - }, - "required": [ - "add_zones_with_mupdate_override" + "replacing", + "disabled", + "failed_reconcile", + "i_o_error", + "bad_negotiation_order", + "incompatible", + "failed_live_repair", + "too_many_outstanding_jobs", + "deactivated" ] }, - "PlanningAddOutOfEligibleSleds": { - "description": "How many discretionary zones we actually placed out of how many we wanted to place.", + "DownstairsClientStopped": { "type": "object", "properties": { - "placed": { - "type": "integer", - "format": "uint", - "minimum": 0 + "reason": { + "$ref": "#/components/schemas/DownstairsClientStoppedReason" }, - "wanted_to_place": { - "type": "integer", - "format": "uint", - "minimum": 0 + "time": { + "type": "string", + "format": "date-time" } }, "required": [ - "placed", - "wanted_to_place" + "reason", + "time" ] }, - "PlanningAddStepReport": { - "type": "object", - "properties": { - "add_update_blocked_reasons": { - "description": "Reasons why zone adds and any updates are blocked.\n\nThis is typically a list of MUPdate-related reasons.", - "type": "array", - "items": { - "type": "string" - } - }, - "add_zones_with_mupdate_override": { - "description": "The value of the homonymous planner config. (What this really means is that zone adds happen despite being blocked by one or more MUPdate-related reasons.)", - "type": "boolean" - }, - "discretionary_zones_placed": { - "description": "Sled ID → kinds of discretionary zones placed there", - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DiscretionaryZonePlacement" - } - } - }, - "out_of_eligible_sleds": { - "description": "Discretionary zone kind → (placed, wanted to place)", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/PlanningAddOutOfEligibleSleds" - } - }, - "sleds_getting_ntp_and_discretionary_zones": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SledUuid" - }, - "uniqueItems": true - }, - "sleds_missing_crucible_zone": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ZpoolUuid" - } - } - }, - "sleds_missing_ntp_zone": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SledUuid" - }, - "uniqueItems": true - }, - "sleds_waiting_for_ntp_zone": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SledUuid" - }, - "uniqueItems": true - }, - "sleds_without_ntp_zones_in_inventory": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SledUuid" - }, - "uniqueItems": true - }, - "sleds_without_zpools_for_ntp_zones": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SledUuid" - }, - "uniqueItems": true - }, - "sufficient_zones_exist": { - "description": "Discretionary zone kind → (wanted to place, num existing)", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/PlanningAddSufficientZonesExist" - } - }, - "target_release_generation_is_one": { - "description": "Set to true if the target release generation is 1, which would allow zones to be added.", - "type": "boolean" + "DownstairsClientStoppedReason": { + "type": "string", + "enum": [ + "connection_timeout", + "connection_failed", + "timeout", + "write_failed", + "read_failed", + "requested_stop", + "finished", + "queue_closed", + "receive_task_cancelled" + ] + }, + "DownstairsRegionUuid": { + "x-rust-type": { + "crate": "omicron-uuid-kinds", + "path": "omicron_uuid_kinds::DownstairsRegionUuid", + "version": "*" + }, + "type": "string", + "format": "uuid" + }, + "DownstairsUnderRepair": { + "type": "object", + "properties": { + "region_uuid": { + "$ref": "#/components/schemas/DownstairsRegionUuid" }, - "waiting_on": { - "nullable": true, - "description": "What are we waiting on to start zone additions?", - "allOf": [ - { - "$ref": "#/components/schemas/ZoneAddWaitingOn" - } - ] + "target_addr": { + "type": "string" } }, "required": [ - "add_update_blocked_reasons", - "add_zones_with_mupdate_override", - "discretionary_zones_placed", - "out_of_eligible_sleds", - "sleds_getting_ntp_and_discretionary_zones", - "sleds_missing_crucible_zone", - "sleds_missing_ntp_zone", - "sleds_waiting_for_ntp_zone", - "sleds_without_ntp_zones_in_inventory", - "sleds_without_zpools_for_ntp_zones", - "sufficient_zones_exist", - "target_release_generation_is_one" + "region_uuid", + "target_addr" ] }, - "PlanningAddSufficientZonesExist": { - "description": "We have at least the minimum required number of zones of a given kind.", + "Duration": { "type": "object", "properties": { - "num_existing": { + "nanos": { "type": "integer", - "format": "uint", + "format": "uint32", "minimum": 0 }, - "target_count": { + "secs": { "type": "integer", - "format": "uint", + "format": "uint64", "minimum": 0 } }, "required": [ - "num_existing", - "target_count" + "nanos", + "secs" ] }, - "PlanningCockroachdbSettingsStepReport": { + "Error": { + "description": "Error information from a response.", "type": "object", "properties": { - "preserve_downgrade": { - "$ref": "#/components/schemas/CockroachDbPreserveDowngrade" + "error_code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "request_id": { + "type": "string" } }, "required": [ - "preserve_downgrade" + "message", + "request_id" ] }, - "PlanningDecommissionStepReport": { - "type": "object", - "properties": { - "zombie_sleds": { - "description": "Decommissioned sleds that unexpectedly appeared as commissioned.", - "type": "array", - "items": { - "$ref": "#/components/schemas/SledUuid" - } - } + "Generation": { + "description": "Generation numbers stored in the database, used for optimistic concurrency control", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "IpNet": { + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" }, - "required": [ - "zombie_sleds" + "oneOf": [ + { + "title": "v4", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv4Net" + } + ] + }, + { + "title": "v6", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv6Net" + } + ] + } ] }, - "PlanningExpungeStepReport": { - "type": "object", - "properties": { - "orphan_disks": { - "description": "Expunged disks not present in the parent blueprint.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/PhysicalDiskUuid" - } - } + "Ipv4Net": { + "example": "192.168.1.0/24", + "title": "An IPv4 subnet", + "description": "An IPv4 subnet, including prefix and prefix length", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv4Net", + "version": "0.1.0" }, - "required": [ - "orphan_disks" - ] + "type": "string", + "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" + }, + "Ipv6Net": { + "example": "fd12:3456::/64", + "title": "An IPv6 subnet", + "description": "An IPv6 subnet, including prefix and subnet mask", + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::Ipv6Net", + "version": "0.1.0" + }, + "type": "string", + "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" + }, + "MacAddr": { + "example": "ff:ff:ff:ff:ff:ff", + "title": "A MAC address", + "description": "A Media Access Control address, in EUI-48 format", + "type": "string", + "pattern": "^([0-9a-fA-F]{0,2}:){5}[0-9a-fA-F]{0,2}$", + "minLength": 5, + "maxLength": 17 }, - "PlanningMgsUpdatesStepReport": { + "MigrationRuntimeState": { + "description": "An update from a sled regarding the state of a migration, indicating the role of the VMM whose migration state was updated.", "type": "object", "properties": { - "blocked_mgs_updates": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BlockedMgsUpdate" - } + "gen": { + "$ref": "#/components/schemas/Generation" }, - "pending_mgs_updates": { - "$ref": "#/components/schemas/PendingMgsUpdates" + "migration_id": { + "type": "string", + "format": "uuid" + }, + "state": { + "$ref": "#/components/schemas/MigrationState" + }, + "time_updated": { + "description": "Timestamp for the migration state update.", + "type": "string", + "format": "date-time" } }, "required": [ - "blocked_mgs_updates", - "pending_mgs_updates" + "gen", + "migration_id", + "state", + "time_updated" ] }, - "PlanningNexusGenerationBumpReport": { + "MigrationState": { + "description": "The state of an instance's live migration.", "oneOf": [ { - "description": "We have no reason to bump the Nexus generation number.", - "type": "object", - "properties": { - "component": { - "type": "string", - "enum": [ - "nothing_to_report" - ] - } - }, - "required": [ - "component" + "description": "The migration has not started for this VMM.", + "type": "string", + "enum": [ + "pending" ] }, { - "description": "We are waiting on some condition before we can bump the Nexus generation.", - "type": "object", - "properties": { - "component": { - "type": "string", - "enum": [ - "waiting_on" - ] - }, - "value": { - "$ref": "#/components/schemas/NexusGenerationBumpWaitingOn" - } - }, - "required": [ - "component", - "value" + "description": "The migration is in progress.", + "type": "string", + "enum": [ + "in_progress" ] }, { - "description": "We are bumping the Nexus generation number to this value.", - "type": "object", - "properties": { - "component": { - "type": "string", - "enum": [ - "bumping_generation" - ] - }, - "value": { - "$ref": "#/components/schemas/Generation" - } - }, - "required": [ - "component", - "value" + "description": "The migration has failed.", + "type": "string", + "enum": [ + "failed" + ] + }, + { + "description": "The migration has completed.", + "type": "string", + "enum": [ + "completed" ] } ] }, - "PlanningNoopImageSourceConverted": { - "description": "How many of the total install-dataset zones and/or host phase 2 slots were noop-converted to use the artifact store on a particular sled.", + "Name": { + "title": "A name unique within the parent collection", + "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID, but they may contain a UUID. They can be at most 63 characters long.", + "type": "string", + "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z]([a-zA-Z0-9-]*[a-zA-Z0-9]+)?$", + "minLength": 1, + "maxLength": 63 + }, + "NatEntryView": { + "description": "NAT Record\n\nA NAT record maps an external IP address, used by an instance or externally-facing service like Nexus, to the hosting sled.", "type": "object", "properties": { - "host_phase_2_slot_a_eligible": { + "deleted": { "type": "boolean" }, - "host_phase_2_slot_b_eligible": { + "external_address": { + "type": "string", + "format": "ip" + }, + "first_port": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "gen": { + "type": "integer", + "format": "int64" + }, + "last_port": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "mac": { + "$ref": "#/components/schemas/MacAddr" + }, + "sled_address": { + "type": "string", + "format": "ipv6" + }, + "vni": { + "$ref": "#/components/schemas/Vni" + } + }, + "required": [ + "deleted", + "external_address", + "first_port", + "gen", + "last_port", + "mac", + "sled_address", + "vni" + ] + }, + "NetworkInterface": { + "description": "Information required to construct a virtual network interface", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "ip": { + "type": "string", + "format": "ip" + }, + "kind": { + "$ref": "#/components/schemas/NetworkInterfaceKind" + }, + "mac": { + "$ref": "#/components/schemas/MacAddr" + }, + "name": { + "$ref": "#/components/schemas/Name" + }, + "primary": { "type": "boolean" }, - "num_dataset": { + "slot": { "type": "integer", - "format": "uint", + "format": "uint8", "minimum": 0 }, - "num_eligible": { - "type": "integer", - "format": "uint", - "minimum": 0 + "subnet": { + "$ref": "#/components/schemas/IpNet" + }, + "transit_ips": { + "default": [], + "type": "array", + "items": { + "$ref": "#/components/schemas/IpNet" + } + }, + "vni": { + "$ref": "#/components/schemas/Vni" } }, "required": [ - "host_phase_2_slot_a_eligible", - "host_phase_2_slot_b_eligible", - "num_dataset", - "num_eligible" + "id", + "ip", + "kind", + "mac", + "name", + "primary", + "slot", + "subnet", + "vni" ] }, - "PlanningNoopImageSourceSkipSledHostPhase2Reason": { + "NetworkInterfaceKind": { + "description": "The type of network interface", "oneOf": [ { + "description": "A vNIC attached to a guest instance", "type": "object", "properties": { - "type": { - "type": "string", - "enum": [ - "both_slots_already_artifact" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "type": { + "id": { "type": "string", - "enum": [ - "sled_not_in_inventory" - ] - } - }, - "required": [ - "type" - ] - } - ] - }, - "PlanningNoopImageSourceSkipSledZonesReason": { - "oneOf": [ - { - "type": "object", - "properties": { - "num_total": { - "type": "integer", - "format": "uint", - "minimum": 0 + "format": "uuid" }, "type": { "type": "string", "enum": [ - "all_zones_already_artifact" + "instance" ] } }, "required": [ - "num_total", + "id", "type" ] }, { + "description": "A vNIC associated with an internal service", "type": "object", "properties": { - "type": { + "id": { "type": "string", - "enum": [ - "sled_not_in_inventory" - ] - } - }, - "required": [ - "type" - ] - }, - { - "type": "object", - "properties": { - "error": { - "type": "string" + "format": "uuid" }, "type": { "type": "string", "enum": [ - "error_retrieving_zone_manifest" + "service" ] } }, "required": [ - "error", + "id", "type" ] }, { + "description": "A vNIC associated with a probe", "type": "object", "properties": { "id": { - "$ref": "#/components/schemas/MupdateOverrideUuid" + "type": "string", + "format": "uuid" }, "type": { "type": "string", "enum": [ - "remove_mupdate_override" + "probe" ] } }, @@ -5319,324 +1466,45 @@ } ] }, - "PlanningNoopImageSourceSkipZoneReason": { - "oneOf": [ - { - "type": "object", - "properties": { - "file_name": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "zone_not_in_manifest" - ] - }, - "zone_kind": { - "type": "string" - } - }, - "required": [ - "file_name", - "type", - "zone_kind" - ] - }, - { - "type": "object", - "properties": { - "error": { - "type": "string" - }, - "file_name": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "invalid_artifact" - ] - }, - "zone_kind": { - "type": "string" - } - }, - "required": [ - "error", - "file_name", - "type", - "zone_kind" - ] - }, - { - "type": "object", - "properties": { - "artifact_hash": { - "type": "string", - "format": "hex string (32 bytes)" - }, - "file_name": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "artifact_not_in_repo" - ] - }, - "zone_kind": { - "type": "string" - } - }, - "required": [ - "artifact_hash", - "file_name", - "type", - "zone_kind" - ] - } - ] - }, - "PlanningNoopImageSourceStepReport": { - "type": "object", - "properties": { - "converted": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/PlanningNoopImageSourceConverted" - } - }, - "no_target_release": { - "type": "boolean" - }, - "skipped_sled_host_phase_2": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/PlanningNoopImageSourceSkipSledHostPhase2Reason" - } - }, - "skipped_sled_zones": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/PlanningNoopImageSourceSkipSledZonesReason" - } - }, - "skipped_zones": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/PlanningNoopImageSourceSkipZoneReason" - } - } - }, - "required": [ - "converted", - "no_target_release", - "skipped_sled_host_phase_2", - "skipped_sled_zones", - "skipped_zones" - ] - }, - "PlanningOutOfDateZone": { - "description": "We have at least the minimum required number of zones of a given kind.", - "type": "object", - "properties": { - "desired_image_source": { - "$ref": "#/components/schemas/BlueprintZoneImageSource" - }, - "zone": { - "$ref": "#/components/schemas/PlanningReportBlueprintZone" - } - }, - "required": [ - "desired_image_source", - "zone" - ] - }, - "PlanningReportBlueprintZone": { - "description": "Reduced form of a `BlueprintZoneConfig` stored in a [`PlanningReport`].", - "type": "object", - "properties": { - "id": { - "$ref": "#/components/schemas/OmicronZoneUuid" - }, - "kind": { - "$ref": "#/components/schemas/ZoneKind" - } - }, - "required": [ - "id", - "kind" - ] - }, - "PlanningZoneUpdatesStepReport": { - "type": "object", - "properties": { - "expunged_zones": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PlanningReportBlueprintZone" - } - } - }, - "out_of_date_zones": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PlanningOutOfDateZone" - } - } - }, - "unsafe_zones": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/ZoneUnsafeToShutdown" - } - }, - "updated_zones": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PlanningReportBlueprintZone" - } - } - }, - "waiting_on": { - "nullable": true, - "description": "What are we waiting on to start zone updates?", - "allOf": [ - { - "$ref": "#/components/schemas/ZoneUpdatesWaitingOn" - } - ] - }, - "waiting_zones": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/ZoneWaitingToExpunge" - } - } - }, - "required": [ - "expunged_zones", - "out_of_date_zones", - "unsafe_zones", - "updated_zones", - "waiting_zones" - ] - }, - "PortConfigV2": { + "OximeterInfo": { + "description": "Message used to notify Nexus that this oximeter instance is up and running.", "type": "object", "properties": { - "addresses": { - "description": "This port's addresses and optional vlan IDs", - "type": "array", - "items": { - "$ref": "#/components/schemas/UplinkAddressConfig" - } - }, - "autoneg": { - "description": "Whether or not to set autonegotiation", - "default": false, - "type": "boolean" - }, - "bgp_peers": { - "description": "BGP peers on this port", - "type": "array", - "items": { - "$ref": "#/components/schemas/BgpPeerConfig" - } - }, - "lldp": { - "nullable": true, - "description": "LLDP configuration for this port", - "allOf": [ - { - "$ref": "#/components/schemas/LldpPortConfig" - } - ] - }, - "port": { - "description": "Nmae of the port this config applies to.", - "type": "string" - }, - "routes": { - "description": "The set of routes associated with this port.", - "type": "array", - "items": { - "$ref": "#/components/schemas/RouteConfig" - } - }, - "switch": { - "description": "Switch the port belongs to.", - "allOf": [ - { - "$ref": "#/components/schemas/SwitchLocation" - } - ] - }, - "tx_eq": { - "nullable": true, - "description": "TX-EQ configuration for this port", - "allOf": [ - { - "$ref": "#/components/schemas/TxEqConfig" - } - ] - }, - "uplink_port_fec": { - "nullable": true, - "description": "Port forward error correction type.", - "allOf": [ - { - "$ref": "#/components/schemas/PortFec" - } - ] + "address": { + "description": "The address on which this oximeter instance listens for requests", + "type": "string" }, - "uplink_port_speed": { - "description": "Port speed.", + "collector_id": { + "description": "The ID for this oximeter instance.", + "type": "string", + "format": "uuid" + } + }, + "required": [ + "address", + "collector_id" + ] + }, + "Ping": { + "type": "object", + "properties": { + "status": { + "description": "Whether the external API is reachable. Will always be Ok if the endpoint returns anything at all.", "allOf": [ { - "$ref": "#/components/schemas/PortSpeed" + "$ref": "#/components/schemas/PingStatus" } ] } }, "required": [ - "addresses", - "bgp_peers", - "port", - "routes", - "switch", - "uplink_port_speed" - ] - }, - "PortFec": { - "description": "Switchport FEC options", - "type": "string", - "enum": [ - "firecode", - "none", - "rs" + "status" ] }, - "PortSpeed": { - "description": "Switchport Speed options", + "PingStatus": { "type": "string", "enum": [ - "speed0_g", - "speed1_g", - "speed10_g", - "speed25_g", - "speed40_g", - "speed50_g", - "speed100_g", - "speed200_g", - "speed400_g" + "ok" ] }, "ProbeExternalIp": { @@ -5815,179 +1683,6 @@ "lease_duration" ] }, - "RackInitializationRequest": { - "type": "object", - "properties": { - "allowed_source_ips": { - "description": "IPs or subnets allowed to make requests to user-facing services", - "allOf": [ - { - "$ref": "#/components/schemas/AllowedSourceIps" - } - ] - }, - "blueprint": { - "description": "Blueprint describing services initialized by RSS.", - "allOf": [ - { - "$ref": "#/components/schemas/Blueprint" - } - ] - }, - "certs": { - "description": "x.509 Certificates used to encrypt communication with the external API.", - "type": "array", - "items": { - "$ref": "#/components/schemas/Certificate" - } - }, - "crucible_datasets": { - "description": "Crucible datasets on the rack which have been provisioned by RSS.", - "type": "array", - "items": { - "$ref": "#/components/schemas/CrucibleDatasetCreateRequest" - } - }, - "external_dns_zone_name": { - "description": "delegated DNS name for external DNS", - "type": "string" - }, - "external_port_count": { - "description": "The external qsfp ports per sidecar", - "allOf": [ - { - "$ref": "#/components/schemas/ExternalPortDiscovery" - } - ] - }, - "internal_dns_zone_config": { - "description": "initial internal DNS config", - "allOf": [ - { - "$ref": "#/components/schemas/DnsConfigParams" - } - ] - }, - "internal_services_ip_pool_ranges": { - "description": "Ranges of the service IP pool which may be used for internal services, such as Nexus.", - "type": "array", - "items": { - "$ref": "#/components/schemas/IpRange" - } - }, - "physical_disks": { - "description": "\"Managed\" physical disks owned by the control plane", - "type": "array", - "items": { - "$ref": "#/components/schemas/PhysicalDiskPutRequest" - } - }, - "rack_network_config": { - "description": "Initial rack network configuration", - "allOf": [ - { - "$ref": "#/components/schemas/RackNetworkConfigV2" - } - ] - }, - "recovery_silo": { - "description": "configuration for the initial (recovery) Silo", - "allOf": [ - { - "$ref": "#/components/schemas/RecoverySiloConfig" - } - ] - }, - "zpools": { - "description": "Zpools created within the physical disks created by the control plane.", - "type": "array", - "items": { - "$ref": "#/components/schemas/ZpoolPutRequest" - } - } - }, - "required": [ - "allowed_source_ips", - "blueprint", - "certs", - "crucible_datasets", - "external_dns_zone_name", - "external_port_count", - "internal_dns_zone_config", - "internal_services_ip_pool_ranges", - "physical_disks", - "rack_network_config", - "recovery_silo", - "zpools" - ] - }, - "RackNetworkConfigV2": { - "description": "Initial network configuration", - "type": "object", - "properties": { - "bfd": { - "description": "BFD configuration for connecting the rack to external networks", - "default": [], - "type": "array", - "items": { - "$ref": "#/components/schemas/BfdPeerConfig" - } - }, - "bgp": { - "description": "BGP configurations for connecting the rack to external networks", - "type": "array", - "items": { - "$ref": "#/components/schemas/BgpConfig" - } - }, - "infra_ip_first": { - "description": "First ip address to be used for configuring network infrastructure", - "type": "string", - "format": "ipv4" - }, - "infra_ip_last": { - "description": "Last ip address to be used for configuring network infrastructure", - "type": "string", - "format": "ipv4" - }, - "ports": { - "description": "Uplinks for connecting the rack to external networks", - "type": "array", - "items": { - "$ref": "#/components/schemas/PortConfigV2" - } - }, - "rack_subnet": { - "$ref": "#/components/schemas/Ipv6Net" - } - }, - "required": [ - "bgp", - "infra_ip_first", - "infra_ip_last", - "ports", - "rack_subnet" - ] - }, - "RecoverySiloConfig": { - "type": "object", - "properties": { - "silo_name": { - "$ref": "#/components/schemas/Name" - }, - "user_name": { - "$ref": "#/components/schemas/UserId" - }, - "user_password_hash": { - "$ref": "#/components/schemas/NewPasswordHash" - } - }, - "required": [ - "silo_name", - "user_name", - "user_password_hash" - ] - }, "RepairFinishInfo": { "type": "object", "properties": { @@ -6076,82 +1771,6 @@ "time" ] }, - "RotSlot": { - "oneOf": [ - { - "type": "object", - "properties": { - "slot": { - "type": "string", - "enum": [ - "a" - ] - } - }, - "required": [ - "slot" - ] - }, - { - "type": "object", - "properties": { - "slot": { - "type": "string", - "enum": [ - "b" - ] - } - }, - "required": [ - "slot" - ] - } - ] - }, - "RouteConfig": { - "type": "object", - "properties": { - "destination": { - "description": "The destination of the route.", - "allOf": [ - { - "$ref": "#/components/schemas/IpNet" - } - ] - }, - "nexthop": { - "description": "The nexthop/gateway address.", - "type": "string", - "format": "ip" - }, - "rib_priority": { - "nullable": true, - "description": "The RIB priority (i.e. Admin Distance) associated with this route.", - "default": null, - "type": "integer", - "format": "uint8", - "minimum": 0 - }, - "vlan_id": { - "nullable": true, - "description": "The VLAN id associated with this route.", - "default": null, - "type": "integer", - "format": "uint16", - "minimum": 0 - } - }, - "required": [ - "destination", - "nexthop" - ] - }, - "ServerId": { - "description": "A unique ID for a Clickhouse Server", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, "SledAgentInfo": { "description": "Sent by a sled agent to Nexus to inform about resources", "type": "object", @@ -6290,34 +1909,6 @@ } ] }, - "SledState": { - "description": "The current state of the sled.", - "oneOf": [ - { - "description": "The sled is currently active, and has resources allocated on it.", - "type": "string", - "enum": [ - "active" - ] - }, - { - "description": "The sled has been permanently removed from service.\n\nThis is a terminal state: once a particular sled ID is decommissioned, it will never return to service. (The actual hardware may be reused, but it will be treated as a brand-new sled.)", - "type": "string", - "enum": [ - "decommissioned" - ] - } - ] - }, - "SledUuid": { - "x-rust-type": { - "crate": "omicron-uuid-kinds", - "path": "omicron_uuid_kinds::SledUuid", - "version": "*" - }, - "type": "string", - "format": "uuid" - }, "SledVmmState": { "description": "A wrapper type containing a sled's total knowledge of the state of a VMM.", "type": "object", @@ -6328,113 +1919,29 @@ "allOf": [ { "$ref": "#/components/schemas/MigrationRuntimeState" - } - ] - }, - "migration_out": { - "nullable": true, - "description": "The state of any outbound migration from this VMM.", - "allOf": [ - { - "$ref": "#/components/schemas/MigrationRuntimeState" - } - ] - }, - "vmm_state": { - "description": "The most recent state of the sled's VMM process.", - "allOf": [ - { - "$ref": "#/components/schemas/VmmRuntimeState" - } - ] - } - }, - "required": [ - "vmm_state" - ] - }, - "SourceNatConfig": { - "description": "An IP address and port range used for source NAT, i.e., making outbound network connections from guests or services.", - "type": "object", - "properties": { - "first_port": { - "description": "The first port used for source NAT, inclusive.", - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "ip": { - "description": "The external address provided to the instance or service.", - "type": "string", - "format": "ip" - }, - "last_port": { - "description": "The last port used for source NAT, also inclusive.", - "type": "integer", - "format": "uint16", - "minimum": 0 - } - }, - "required": [ - "first_port", - "ip", - "last_port" - ] - }, - "SpType": { - "type": "string", - "enum": [ - "sled", - "power", - "switch" - ] - }, - "Srv": { - "type": "object", - "properties": { - "port": { - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "prio": { - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "target": { - "type": "string" + } + ] }, - "weight": { - "type": "integer", - "format": "uint16", - "minimum": 0 - } - }, - "required": [ - "port", - "prio", - "target", - "weight" - ] - }, - "SwitchLocation": { - "description": "Identifies switch physical location", - "oneOf": [ - { - "description": "Switch in upper slot", - "type": "string", - "enum": [ - "switch0" + "migration_out": { + "nullable": true, + "description": "The state of any outbound migration from this VMM.", + "allOf": [ + { + "$ref": "#/components/schemas/MigrationRuntimeState" + } ] }, - { - "description": "Switch in lower slot", - "type": "string", - "enum": [ - "switch1" + "vmm_state": { + "description": "The most recent state of the sled's VMM process.", + "allOf": [ + { + "$ref": "#/components/schemas/VmmRuntimeState" + } ] } + }, + "required": [ + "vmm_state" ] }, "SwitchPutRequest": { @@ -6456,61 +1963,6 @@ "SwitchPutResponse": { "type": "object" }, - "TxEqConfig": { - "description": "Per-port tx-eq overrides. This can be used to fine-tune the transceiver equalization settings to improve signal integrity.", - "type": "object", - "properties": { - "main": { - "nullable": true, - "description": "Main tap", - "type": "integer", - "format": "int32" - }, - "post1": { - "nullable": true, - "description": "Post-cursor tap1", - "type": "integer", - "format": "int32" - }, - "post2": { - "nullable": true, - "description": "Post-cursor tap2", - "type": "integer", - "format": "int32" - }, - "pre1": { - "nullable": true, - "description": "Pre-cursor tap1", - "type": "integer", - "format": "int32" - }, - "pre2": { - "nullable": true, - "description": "Pre-cursor tap2", - "type": "integer", - "format": "int32" - } - } - }, - "UplinkAddressConfig": { - "type": "object", - "properties": { - "address": { - "$ref": "#/components/schemas/IpNet" - }, - "vlan_id": { - "nullable": true, - "description": "The VLAN id (if any) associated with this address.", - "default": null, - "type": "integer", - "format": "uint16", - "minimum": 0 - } - }, - "required": [ - "address" - ] - }, "UpstairsRepairType": { "type": "string", "enum": [ @@ -6536,14 +1988,6 @@ "type": "string", "format": "uuid" }, - "UserId": { - "title": "A username for a local-only user", - "description": "Usernames must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Usernames cannot be a UUID, but they may contain a UUID. They can be at most 63 characters long.", - "type": "string", - "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z]([a-zA-Z0-9-]*[a-zA-Z0-9]+)?$", - "minLength": 1, - "maxLength": 63 - }, "VmmRuntimeState": { "description": "The dynamic runtime properties of an individual VMM process.", "type": "object", @@ -6643,257 +2087,6 @@ "format": "uint32", "minimum": 0 }, - "ZoneAddWaitingOn": { - "oneOf": [ - { - "description": "Waiting on one or more blockers (typically MUPdate-related reasons) to clear.", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "blockers" - ] - } - }, - "required": [ - "type" - ] - } - ] - }, - "ZoneKind": { - "description": "Like [`OmicronZoneType`], but without any associated data.\n\nThis enum is meant to correspond exactly 1:1 with `OmicronZoneType`.\n\n# String representations of this type\n\nThere are no fewer than six string representations for this type, all slightly different from each other.\n\n1. [`Self::zone_prefix`]: Used to construct zone names. 2. [`Self::service_prefix`]: Used to construct SMF service names. 3. [`Self::name_prefix`]: Used to construct `Name` instances. 4. [`Self::report_str`]: Used for reporting and testing. 5. [`Self::artifact_id_name`]: Used to match TUF artifact IDs. 6. [`Self::artifact_in_install_dataset`]: Used to match zone image tarballs in the install dataset. (This method is equivalent to appending `.tar.gz` to the result of [`Self::zone_prefix`].)\n\nThere is no `Display` impl to ensure that users explicitly choose the representation they want. (Please play close attention to this! The functions are all similar but different, and we don't currently have great type safety around the choice.)\n\n## Adding new representations\n\nIf you have a new use case for a string representation, please reuse one of the six representations if at all possible. If you must add a new one, please add it here rather than doing something ad-hoc in the calling code so it's more legible.", - "type": "string", - "enum": [ - "boundary_ntp", - "clickhouse", - "clickhouse_keeper", - "clickhouse_server", - "cockroach_db", - "crucible", - "crucible_pantry", - "external_dns", - "internal_dns", - "internal_ntp", - "nexus", - "oximeter" - ] - }, - "ZoneUnsafeToShutdown": { - "description": "Zones which should not be shut down, because their lack of availability could be problematic for the successful functioning of the deployed system.", - "oneOf": [ - { - "type": "object", - "properties": { - "reason": { - "$ref": "#/components/schemas/CockroachdbUnsafeToShutdown" - }, - "type": { - "type": "string", - "enum": [ - "cockroachdb" - ] - } - }, - "required": [ - "reason", - "type" - ] - }, - { - "type": "object", - "properties": { - "synchronized_count": { - "type": "integer", - "format": "uint", - "minimum": 0 - }, - "total_boundary_ntp_zones": { - "type": "integer", - "format": "uint", - "minimum": 0 - }, - "type": { - "type": "string", - "enum": [ - "boundary_ntp" - ] - } - }, - "required": [ - "synchronized_count", - "total_boundary_ntp_zones", - "type" - ] - }, - { - "type": "object", - "properties": { - "synchronized_count": { - "type": "integer", - "format": "uint", - "minimum": 0 - }, - "total_internal_dns_zones": { - "type": "integer", - "format": "uint", - "minimum": 0 - }, - "type": { - "type": "string", - "enum": [ - "internal_dns" - ] - } - }, - "required": [ - "synchronized_count", - "total_internal_dns_zones", - "type" - ] - } - ] - }, - "ZoneUpdatesWaitingOn": { - "oneOf": [ - { - "description": "Waiting on blocked updates to RoT bootloader / RoT / SP / Host OS.", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "blocked_mgs_updates" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "Waiting on discretionary zone placement.", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "discretionary_zones" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "Waiting on zones to propagate to inventory.", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "inventory_propagation" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "Waiting on updates to RoT bootloader / RoT / SP / Host OS.", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "pending_mgs_updates" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "Waiting on the same set of blockers zone adds are waiting on.", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "zone_add_blockers" - ] - } - }, - "required": [ - "type" - ] - } - ] - }, - "ZoneWaitingToExpunge": { - "description": "Out-of-date zones which are not yet ready to be expunged.\n\nFor example, out-of-date Nexus zones should not be expunged until handoff has completed.", - "oneOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "nexus" - ] - }, - "zone_generation": { - "$ref": "#/components/schemas/Generation" - } - }, - "required": [ - "type", - "zone_generation" - ] - } - ] - }, - "ZpoolName": { - "title": "The name of a Zpool", - "description": "Zpool names are of the format ox{i,p}_. They are either Internal or External, and should be unique", - "type": "string", - "pattern": "^ox[ip]_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" - }, - "ZpoolPutRequest": { - "description": "Identifies information about a Zpool that should be part of the control plane.", - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "physical_disk_id": { - "$ref": "#/components/schemas/PhysicalDiskUuid" - }, - "sled_id": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "id", - "physical_disk_id", - "sled_id" - ] - }, - "ZpoolUuid": { - "x-rust-type": { - "crate": "omicron-uuid-kinds", - "path": "omicron_uuid_kinds::ZpoolUuid", - "version": "*" - }, - "type": "string", - "format": "uuid" - }, "DownstairsUuid": { "x-rust-type": { "crate": "omicron-uuid-kinds", @@ -6924,6 +2117,15 @@ } ] }, + "SledUuid": { + "x-rust-type": { + "crate": "omicron-uuid-kinds", + "path": "omicron_uuid_kinds::SledUuid", + "version": "*" + }, + "type": "string", + "format": "uuid" + }, "PropolisUuid": { "x-rust-type": { "crate": "omicron-uuid-kinds", diff --git a/openapi/nexus-lockstep.json b/openapi/nexus-lockstep.json index 9136d7bc60f..35df80ae18e 100644 --- a/openapi/nexus-lockstep.json +++ b/openapi/nexus-lockstep.json @@ -1149,6 +1149,45 @@ } } }, + "/racks/{rack_id}/initialization-complete": { + "put": { + "summary": "Report that the Rack Setup Service initialization is complete", + "description": "See RFD 278 for more details.", + "operationId": "rack_initialization_complete", + "parameters": [ + { + "in": "path", + "name": "rack_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RackInitializationRequest" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, "/sagas": { "get": { "summary": "List sagas", @@ -1370,6 +1409,48 @@ "dependency" ] }, + "AllowedSourceIps": { + "description": "Description of source IPs allowed to reach rack services.", + "oneOf": [ + { + "description": "Allow traffic from any external IP address.", + "type": "object", + "properties": { + "allow": { + "type": "string", + "enum": [ + "any" + ] + } + }, + "required": [ + "allow" + ] + }, + { + "description": "Restrict access to a specific set of source IP addresses or subnets.\n\nAll others are prevented from reaching rack services.", + "type": "object", + "properties": { + "allow": { + "type": "string", + "enum": [ + "list" + ] + }, + "ips": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IpNet" + } + } + }, + "required": [ + "allow", + "ips" + ] + } + ] + }, "ArtifactVersion": { "description": "An artifact version.\n\nThis is a freeform identifier with some basic validation. It may be the serialized form of a semver version, or a custom identifier that uses the same character set as a semver, plus `_`.\n\nThe exact pattern accepted is `^[a-zA-Z0-9._+-]{1,63}$`.\n\n# Ord implementation\n\n`ArtifactVersion`s are not intended to be sorted, just compared for equality. `ArtifactVersion` implements `Ord` only for storage within sorted collections.", "type": "string", @@ -1476,6 +1557,228 @@ "serial_number" ] }, + "BfdMode": { + "description": "BFD connection mode.", + "type": "string", + "enum": [ + "single_hop", + "multi_hop" + ] + }, + "BfdPeerConfig": { + "type": "object", + "properties": { + "detection_threshold": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "local": { + "nullable": true, + "type": "string", + "format": "ip" + }, + "mode": { + "$ref": "#/components/schemas/BfdMode" + }, + "remote": { + "type": "string", + "format": "ip" + }, + "required_rx": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "switch": { + "$ref": "#/components/schemas/SwitchLocation" + } + }, + "required": [ + "detection_threshold", + "mode", + "remote", + "required_rx", + "switch" + ] + }, + "BgpConfig": { + "type": "object", + "properties": { + "asn": { + "description": "The autonomous system number for the BGP configuration.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "checker": { + "nullable": true, + "description": "Checker to apply to incoming messages.", + "default": null, + "type": "string" + }, + "originate": { + "description": "The set of prefixes for the BGP router to originate.", + "type": "array", + "items": { + "$ref": "#/components/schemas/Ipv4Net" + } + }, + "shaper": { + "nullable": true, + "description": "Shaper to apply to outgoing messages.", + "default": null, + "type": "string" + } + }, + "required": [ + "asn", + "originate" + ] + }, + "BgpPeerConfig": { + "type": "object", + "properties": { + "addr": { + "description": "Address of the peer.", + "type": "string", + "format": "ipv4" + }, + "allowed_export": { + "description": "Define export policy for a peer.", + "default": { + "type": "no_filtering" + }, + "allOf": [ + { + "$ref": "#/components/schemas/ImportExportPolicy" + } + ] + }, + "allowed_import": { + "description": "Define import policy for a peer.", + "default": { + "type": "no_filtering" + }, + "allOf": [ + { + "$ref": "#/components/schemas/ImportExportPolicy" + } + ] + }, + "asn": { + "description": "The autonomous system number of the router the peer belongs to.", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "communities": { + "description": "Include the provided communities in updates sent to the peer.", + "default": [], + "type": "array", + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "connect_retry": { + "nullable": true, + "description": "The interval in seconds between peer connection retry attempts.", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "delay_open": { + "nullable": true, + "description": "How long to delay sending open messages to a peer. In seconds.", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "enforce_first_as": { + "description": "Enforce that the first AS in paths received from this peer is the peer's AS.", + "default": false, + "type": "boolean" + }, + "hold_time": { + "nullable": true, + "description": "How long to keep a session alive without a keepalive in seconds. Defaults to 6.", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "idle_hold_time": { + "nullable": true, + "description": "How long to keep a peer in idle after a state machine reset in seconds.", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "keepalive": { + "nullable": true, + "description": "The interval to send keepalive messages at.", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "local_pref": { + "nullable": true, + "description": "Apply a local preference to routes received from this peer.", + "default": null, + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "md5_auth_key": { + "nullable": true, + "description": "Use the given key for TCP-MD5 authentication with the peer.", + "default": null, + "type": "string" + }, + "min_ttl": { + "nullable": true, + "description": "Require messages from a peer have a minimum IP time to live field.", + "default": null, + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "multi_exit_discriminator": { + "nullable": true, + "description": "Apply the provided multi-exit discriminator (MED) updates sent to the peer.", + "default": null, + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "port": { + "description": "Switch port the peer is reachable on.", + "type": "string" + }, + "remote_asn": { + "nullable": true, + "description": "Require that a peer has a specified ASN.", + "default": null, + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "vlan_id": { + "nullable": true, + "description": "Associate a VLAN ID with a BGP peer session.", + "default": null, + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "addr", + "asn", + "port" + ] + }, "BlockedMgsUpdate": { "type": "object", "properties": { @@ -2742,6 +3045,21 @@ "stage0_next" ] }, + "Certificate": { + "type": "object", + "properties": { + "cert": { + "type": "string" + }, + "key": { + "type": "string" + } + }, + "required": [ + "cert", + "key" + ] + }, "ClickhouseClusterConfig": { "description": "Global configuration for all clickhouse servers (replicas) and keepers", "type": "object", @@ -3244,6 +3562,25 @@ } ] }, + "CrucibleDatasetCreateRequest": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "dataset_id": { + "$ref": "#/components/schemas/DatasetUuid" + }, + "zpool_id": { + "$ref": "#/components/schemas/ZpoolUuid" + } + }, + "required": [ + "address", + "dataset_id", + "zpool_id" + ] + }, "CurrentStatus": { "description": "Describes the current status of a background task", "oneOf": [ @@ -3386,34 +3723,164 @@ "vendor" ] }, - "Duration": { + "DnsConfigParams": { "type": "object", "properties": { - "nanos": { + "generation": { + "$ref": "#/components/schemas/Generation" + }, + "serial": { + "description": "See [`DnsConfig`]'s `serial` field for how this is different from `generation`", "type": "integer", "format": "uint32", "minimum": 0 }, - "secs": { - "type": "integer", - "format": "uint64", - "minimum": 0 + "time_created": { + "type": "string", + "format": "date-time" + }, + "zones": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DnsConfigZone" + } } }, "required": [ - "nanos", - "secs" + "generation", + "serial", + "time_created", + "zones" ] }, - "Error": { - "description": "Error information from a response.", + "DnsConfigZone": { + "description": "Configuration for a specific DNS zone, as opposed to illumos zones in which the services described by these records run.\n\nThe name `@` is special: it describes records that should be provided for queries about `zone_name`. This is used in favor of the empty string as `@` is the name used for this purpose in zone files for most DNS configurations. It also avoids potentially-confusing debug output from naively printing out records and their names - if you've seen an `@` record and tools are unclear about what that means, hopefully you've arrived here!", "type": "object", "properties": { - "error_code": { - "type": "string" - }, - "message": { - "type": "string" + "records": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DnsRecord" + } + } + }, + "zone_name": { + "type": "string" + } + }, + "required": [ + "records", + "zone_name" + ] + }, + "DnsRecord": { + "oneOf": [ + { + "type": "object", + "properties": { + "data": { + "type": "string", + "format": "ipv4" + }, + "type": { + "type": "string", + "enum": [ + "A" + ] + } + }, + "required": [ + "data", + "type" + ] + }, + { + "type": "object", + "properties": { + "data": { + "type": "string", + "format": "ipv6" + }, + "type": { + "type": "string", + "enum": [ + "AAAA" + ] + } + }, + "required": [ + "data", + "type" + ] + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Srv" + }, + "type": { + "type": "string", + "enum": [ + "SRV" + ] + } + }, + "required": [ + "data", + "type" + ] + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "NS" + ] + } + }, + "required": [ + "data", + "type" + ] + } + ] + }, + "Duration": { + "type": "object", + "properties": { + "nanos": { + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "secs": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + }, + "required": [ + "nanos", + "secs" + ] + }, + "Error": { + "description": "Error information from a response.", + "type": "object", + "properties": { + "error_code": { + "type": "string" + }, + "message": { + "type": "string" }, "request_id": { "type": "string" @@ -3488,6 +3955,44 @@ "type": "string", "format": "uuid" }, + "ExternalPortDiscovery": { + "oneOf": [ + { + "type": "object", + "properties": { + "auto": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "ipv6" + } + } + }, + "required": [ + "auto" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "static": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Name" + } + } + } + }, + "required": [ + "static" + ], + "additionalProperties": false + } + ] + }, "FailedHostOsUpdateReason": { "description": "Describes the reason why a Host OS failed to update", "oneOf": [ @@ -4311,6 +4816,47 @@ "$ref": "#/components/schemas/BlueprintZoneConfig" } }, + "ImportExportPolicy": { + "description": "Define policy relating to the import and export of prefixes from a BGP peer.", + "oneOf": [ + { + "description": "Do not perform any filtering.", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "no_filtering" + ] + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "allow" + ] + }, + "value": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IpNet" + } + } + }, + "required": [ + "type", + "value" + ] + } + ] + }, "InProgressUpdateStatus": { "description": "externally-exposed status for each in-progress update", "type": "object", @@ -4615,6 +5161,26 @@ } ] }, + "IpRange": { + "oneOf": [ + { + "title": "v4", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv4Range" + } + ] + }, + { + "title": "v6", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv6Range" + } + ] + } + ] + }, "Ipv4Net": { "example": "192.168.1.0/24", "title": "An IPv4 subnet", @@ -4627,6 +5193,24 @@ "type": "string", "pattern": "^(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$" }, + "Ipv4Range": { + "description": "A non-decreasing IPv4 address range, inclusive of both ends.\n\nThe first address must be less than or equal to the last address.", + "type": "object", + "properties": { + "first": { + "type": "string", + "format": "ipv4" + }, + "last": { + "type": "string", + "format": "ipv4" + } + }, + "required": [ + "first", + "last" + ] + }, "Ipv6Net": { "example": "fd12:3456::/64", "title": "An IPv6 subnet", @@ -4639,6 +5223,24 @@ "type": "string", "pattern": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$" }, + "Ipv6Range": { + "description": "A non-decreasing IPv6 address range, inclusive of both ends.\n\nThe first address must be less than or equal to the last address.", + "type": "object", + "properties": { + "first": { + "type": "string", + "format": "ipv6" + }, + "last": { + "type": "string", + "format": "ipv6" + } + }, + "required": [ + "first", + "last" + ] + }, "KeeperId": { "description": "A unique ID for a ClickHouse Keeper", "type": "integer", @@ -4725,6 +5327,67 @@ "start_time" ] }, + "LldpAdminStatus": { + "description": "To what extent should this port participate in LLDP", + "type": "string", + "enum": [ + "enabled", + "disabled", + "rx_only", + "tx_only" + ] + }, + "LldpPortConfig": { + "description": "Per-port LLDP configuration settings. Only the \"status\" setting is mandatory. All other fields have natural defaults or may be inherited from the switch.", + "type": "object", + "properties": { + "chassis_id": { + "nullable": true, + "description": "Chassis ID to advertise. If this is set, it will be advertised as a LocallyAssigned ID type. If this is not set, it will be inherited from the switch-level settings.", + "type": "string" + }, + "management_addrs": { + "nullable": true, + "description": "Management IP addresses to advertise. If this is not set, it will be inherited from the switch-level settings.", + "type": "array", + "items": { + "type": "string", + "format": "ip" + } + }, + "port_description": { + "nullable": true, + "description": "Port description to advertise. If this is not set, no description will be advertised.", + "type": "string" + }, + "port_id": { + "nullable": true, + "description": "Port ID to advertise. If this is set, it will be advertised as a LocallyAssigned ID type. If this is not set, it will be set to the port name. e.g., qsfp0/0.", + "type": "string" + }, + "status": { + "description": "To what extent should this port participate in LLDP", + "allOf": [ + { + "$ref": "#/components/schemas/LldpAdminStatus" + } + ] + }, + "system_description": { + "nullable": true, + "description": "System description to advertise. If this is not set, it will be inherited from the switch-level settings.", + "type": "string" + }, + "system_name": { + "nullable": true, + "description": "System name to advertise. If this is not set, it will be inherited from the switch-level settings.", + "type": "string" + } + }, + "required": [ + "status" + ] + }, "M2Slot": { "description": "Describes an M.2 slot, often in the context of writing a system image to it.", "type": "string", @@ -4959,6 +5622,11 @@ } ] }, + "NewPasswordHash": { + "title": "A password hash in PHC string format", + "description": "Password hashes must be in PHC (Password Hashing Competition) string format. Passwords must be hashed with Argon2id. Password hashes may be rejected if the parameters appear not to be secure enough.", + "type": "string" + }, "NexusGenerationBumpWaitingOn": { "oneOf": [ { @@ -5823,6 +6491,14 @@ "time_pending" ] }, + "PhysicalDiskKind": { + "description": "Describes the form factor of physical disks.", + "type": "string", + "enum": [ + "m2", + "u2" + ] + }, "PhysicalDiskPath": { "type": "object", "properties": { @@ -5836,12 +6512,44 @@ "disk_id" ] }, - "PhysicalDiskUuid": { - "x-rust-type": { - "crate": "omicron-uuid-kinds", - "path": "omicron_uuid_kinds::PhysicalDiskUuid", - "version": "*" - }, + "PhysicalDiskPutRequest": { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/PhysicalDiskUuid" + }, + "model": { + "type": "string" + }, + "serial": { + "type": "string" + }, + "sled_id": { + "type": "string", + "format": "uuid" + }, + "variant": { + "$ref": "#/components/schemas/PhysicalDiskKind" + }, + "vendor": { + "type": "string" + } + }, + "required": [ + "id", + "model", + "serial", + "sled_id", + "variant", + "vendor" + ] + }, + "PhysicalDiskUuid": { + "x-rust-type": { + "crate": "omicron-uuid-kinds", + "path": "omicron_uuid_kinds::PhysicalDiskUuid", + "version": "*" + }, "type": "string", "format": "uuid" }, @@ -6490,6 +7198,116 @@ "waiting_zones" ] }, + "PortConfigV2": { + "type": "object", + "properties": { + "addresses": { + "description": "This port's addresses and optional vlan IDs", + "type": "array", + "items": { + "$ref": "#/components/schemas/UplinkAddressConfig" + } + }, + "autoneg": { + "description": "Whether or not to set autonegotiation", + "default": false, + "type": "boolean" + }, + "bgp_peers": { + "description": "BGP peers on this port", + "type": "array", + "items": { + "$ref": "#/components/schemas/BgpPeerConfig" + } + }, + "lldp": { + "nullable": true, + "description": "LLDP configuration for this port", + "allOf": [ + { + "$ref": "#/components/schemas/LldpPortConfig" + } + ] + }, + "port": { + "description": "Nmae of the port this config applies to.", + "type": "string" + }, + "routes": { + "description": "The set of routes associated with this port.", + "type": "array", + "items": { + "$ref": "#/components/schemas/RouteConfig" + } + }, + "switch": { + "description": "Switch the port belongs to.", + "allOf": [ + { + "$ref": "#/components/schemas/SwitchLocation" + } + ] + }, + "tx_eq": { + "nullable": true, + "description": "TX-EQ configuration for this port", + "allOf": [ + { + "$ref": "#/components/schemas/TxEqConfig" + } + ] + }, + "uplink_port_fec": { + "nullable": true, + "description": "Port forward error correction type.", + "allOf": [ + { + "$ref": "#/components/schemas/PortFec" + } + ] + }, + "uplink_port_speed": { + "description": "Port speed.", + "allOf": [ + { + "$ref": "#/components/schemas/PortSpeed" + } + ] + } + }, + "required": [ + "addresses", + "bgp_peers", + "port", + "routes", + "switch", + "uplink_port_speed" + ] + }, + "PortFec": { + "description": "Switchport FEC options", + "type": "string", + "enum": [ + "firecode", + "none", + "rs" + ] + }, + "PortSpeed": { + "description": "Switchport Speed options", + "type": "string", + "enum": [ + "speed0_g", + "speed1_g", + "speed10_g", + "speed25_g", + "speed40_g", + "speed50_g", + "speed100_g", + "speed200_g", + "speed400_g" + ] + }, "QuiesceState": { "description": "See [`QuiesceStatus`] for more on Nexus quiescing.\n\nAt any given time, Nexus is always in one of these states:\n\n```text Undetermined (have not loaded persistent state; don't know yet) | | load persistent state and find we're not quiescing v Running (normal operation) | | quiesce starts v DrainingSagas (no new sagas are allowed, but some are still running) | | no more sagas running v DrainingDb (no sagas running; no new db connections may be | acquired by Nexus at-large, but some are still held) | | no more database connections held v RecordingQuiesce (everything is quiesced aside from one connection being | used to record our final quiesced state) | | finish recording quiesce state in database v Quiesced (no sagas running, no database connections in use) ```\n\nQuiescing is (currently) a one-way trip: once a Nexus process starts quiescing, it will never go back to normal operation. It will never go back to an earlier stage, either.", "oneOf": [ @@ -6716,6 +7534,160 @@ "state" ] }, + "RackInitializationRequest": { + "type": "object", + "properties": { + "allowed_source_ips": { + "description": "IPs or subnets allowed to make requests to user-facing services", + "allOf": [ + { + "$ref": "#/components/schemas/AllowedSourceIps" + } + ] + }, + "blueprint": { + "description": "Blueprint describing services initialized by RSS.", + "allOf": [ + { + "$ref": "#/components/schemas/Blueprint" + } + ] + }, + "certs": { + "description": "x.509 Certificates used to encrypt communication with the external API.", + "type": "array", + "items": { + "$ref": "#/components/schemas/Certificate" + } + }, + "crucible_datasets": { + "description": "Crucible datasets on the rack which have been provisioned by RSS.", + "type": "array", + "items": { + "$ref": "#/components/schemas/CrucibleDatasetCreateRequest" + } + }, + "external_dns_zone_name": { + "description": "delegated DNS name for external DNS", + "type": "string" + }, + "external_port_count": { + "description": "The external qsfp ports per sidecar", + "allOf": [ + { + "$ref": "#/components/schemas/ExternalPortDiscovery" + } + ] + }, + "internal_dns_zone_config": { + "description": "initial internal DNS config", + "allOf": [ + { + "$ref": "#/components/schemas/DnsConfigParams" + } + ] + }, + "internal_services_ip_pool_ranges": { + "description": "Ranges of the service IP pool which may be used for internal services, such as Nexus.", + "type": "array", + "items": { + "$ref": "#/components/schemas/IpRange" + } + }, + "physical_disks": { + "description": "\"Managed\" physical disks owned by the control plane", + "type": "array", + "items": { + "$ref": "#/components/schemas/PhysicalDiskPutRequest" + } + }, + "rack_network_config": { + "description": "Initial rack network configuration", + "allOf": [ + { + "$ref": "#/components/schemas/RackNetworkConfigV2" + } + ] + }, + "recovery_silo": { + "description": "configuration for the initial (recovery) Silo", + "allOf": [ + { + "$ref": "#/components/schemas/RecoverySiloConfig" + } + ] + }, + "zpools": { + "description": "Zpools created within the physical disks created by the control plane.", + "type": "array", + "items": { + "$ref": "#/components/schemas/ZpoolPutRequest" + } + } + }, + "required": [ + "allowed_source_ips", + "blueprint", + "certs", + "crucible_datasets", + "external_dns_zone_name", + "external_port_count", + "internal_dns_zone_config", + "internal_services_ip_pool_ranges", + "physical_disks", + "rack_network_config", + "recovery_silo", + "zpools" + ] + }, + "RackNetworkConfigV2": { + "description": "Initial network configuration", + "type": "object", + "properties": { + "bfd": { + "description": "BFD configuration for connecting the rack to external networks", + "default": [], + "type": "array", + "items": { + "$ref": "#/components/schemas/BfdPeerConfig" + } + }, + "bgp": { + "description": "BGP configurations for connecting the rack to external networks", + "type": "array", + "items": { + "$ref": "#/components/schemas/BgpConfig" + } + }, + "infra_ip_first": { + "description": "First ip address to be used for configuring network infrastructure", + "type": "string", + "format": "ipv4" + }, + "infra_ip_last": { + "description": "Last ip address to be used for configuring network infrastructure", + "type": "string", + "format": "ipv4" + }, + "ports": { + "description": "Uplinks for connecting the rack to external networks", + "type": "array", + "items": { + "$ref": "#/components/schemas/PortConfigV2" + } + }, + "rack_subnet": { + "$ref": "#/components/schemas/Ipv6Net" + } + }, + "required": [ + "bgp", + "infra_ip_first", + "infra_ip_last", + "ports", + "rack_subnet" + ] + }, "ReconfiguratorConfig": { "type": "object", "properties": { @@ -6770,6 +7742,25 @@ "version" ] }, + "RecoverySiloConfig": { + "type": "object", + "properties": { + "silo_name": { + "$ref": "#/components/schemas/Name" + }, + "user_name": { + "$ref": "#/components/schemas/UserId" + }, + "user_password_hash": { + "$ref": "#/components/schemas/NewPasswordHash" + } + }, + "required": [ + "silo_name", + "user_name", + "user_password_hash" + ] + }, "RotBootloaderStatus": { "type": "object", "properties": { @@ -6840,6 +7831,44 @@ "slot_b_version" ] }, + "RouteConfig": { + "type": "object", + "properties": { + "destination": { + "description": "The destination of the route.", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + }, + "nexthop": { + "description": "The nexthop/gateway address.", + "type": "string", + "format": "ip" + }, + "rib_priority": { + "nullable": true, + "description": "The RIB priority (i.e. Admin Distance) associated with this route.", + "default": null, + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "vlan_id": { + "nullable": true, + "description": "The VLAN id associated with this route.", + "default": null, + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "destination", + "nexthop" + ] + }, "Saga": { "description": "Sagas\n\nThese are currently only intended for observability by developers. We will eventually want to flesh this out into something more observable for end users.", "type": "object", @@ -7384,6 +8413,35 @@ "switch" ] }, + "Srv": { + "type": "object", + "properties": { + "port": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "prio": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "target": { + "type": "string" + }, + "weight": { + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "port", + "prio", + "target", + "weight" + ] + }, "SupportBundleCreate": { "type": "object", "properties": { @@ -7490,6 +8548,25 @@ } } }, + "SwitchLocation": { + "description": "Identifies switch physical location", + "oneOf": [ + { + "description": "Switch in upper slot", + "type": "string", + "enum": [ + "switch0" + ] + }, + { + "description": "Switch in lower slot", + "type": "string", + "enum": [ + "switch1" + ] + } + ] + }, "TufRepoVersion": { "oneOf": [ { @@ -7559,6 +8636,42 @@ } ] }, + "TxEqConfig": { + "description": "Per-port tx-eq overrides. This can be used to fine-tune the transceiver equalization settings to improve signal integrity.", + "type": "object", + "properties": { + "main": { + "nullable": true, + "description": "Main tap", + "type": "integer", + "format": "int32" + }, + "post1": { + "nullable": true, + "description": "Post-cursor tap1", + "type": "integer", + "format": "int32" + }, + "post2": { + "nullable": true, + "description": "Post-cursor tap2", + "type": "integer", + "format": "int32" + }, + "pre1": { + "nullable": true, + "description": "Pre-cursor tap1", + "type": "integer", + "format": "int32" + }, + "pre2": { + "nullable": true, + "description": "Pre-cursor tap2", + "type": "integer", + "format": "int32" + } + } + }, "UninitializedSled": { "description": "A sled that has not been added to an initialized rack yet", "type": "object", @@ -7687,6 +8800,33 @@ "sleds" ] }, + "UplinkAddressConfig": { + "type": "object", + "properties": { + "address": { + "$ref": "#/components/schemas/IpNet" + }, + "vlan_id": { + "nullable": true, + "description": "The VLAN id (if any) associated with this address.", + "default": null, + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "address" + ] + }, + "UserId": { + "title": "A username for a local-only user", + "description": "Usernames must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Usernames cannot be a UUID, but they may contain a UUID. They can be at most 63 characters long.", + "type": "string", + "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z]([a-zA-Z0-9-]*[a-zA-Z0-9]+)?$", + "minLength": 1, + "maxLength": 63 + }, "Vni": { "description": "A Geneve Virtual Network Identifier", "type": "integer", @@ -7955,6 +9095,28 @@ "type": "string", "pattern": "^ox[ip]_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }, + "ZpoolPutRequest": { + "description": "Identifies information about a Zpool that should be part of the control plane.", + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "physical_disk_id": { + "$ref": "#/components/schemas/PhysicalDiskUuid" + }, + "sled_id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "id", + "physical_disk_id", + "sled_id" + ] + }, "ZpoolUuid": { "x-rust-type": { "crate": "omicron-uuid-kinds", diff --git a/sled-agent/Cargo.toml b/sled-agent/Cargo.toml index cfa202b4d1f..9bb2197f41d 100644 --- a/sled-agent/Cargo.toml +++ b/sled-agent/Cargo.toml @@ -28,14 +28,15 @@ cockroach-admin-client.workspace = true # Only used by the simulated sled agent. crucible-agent-client.workspace = true derive_more.workspace = true +display-error-chain.workspace = true dns-server.workspace = true dns-service-client.workspace = true dpd-client.workspace = true -display-error-chain.workspace = true dropshot.workspace = true flate2.workspace = true flume.workspace = true futures.workspace = true +gateway-client.workspace = true glob.workspace = true hex.workspace = true http.workspace = true @@ -43,7 +44,6 @@ http-body-util.workspace = true http-range.workspace = true hyper.workspace = true hyper-staticfile.workspace = true -gateway-client.workspace = true id-map.workspace = true illumos-utils.workspace = true installinator-common.workspace = true @@ -57,20 +57,22 @@ macaddr.workspace = true mg-admin-client.workspace = true nexus-client.workspace = true nexus-config.workspace = true +nexus-lockstep-client.workspace = true nexus-sled-agent-shared.workspace = true nexus-types.workspace = true ntp-admin-client.workspace = true omicron-common.workspace = true omicron-ddm-admin-client.workspace = true omicron-uuid-kinds.workspace = true +omicron-workspace-hack.workspace = true oxide-tokio-rt.workspace = true oximeter.workspace = true oximeter-instruments.workspace = true oximeter-producer.workspace = true oxnet.workspace = true -propolis_api_types.workspace = true propolis-client.workspace = true propolis-mock-server.workspace = true # Only used by the simulated sled agent +propolis_api_types.workspace = true rand = { workspace = true, features = ["os_rng"] } range-requests.workspace = true repo-depot-api.workspace = true @@ -94,9 +96,11 @@ sled-storage.workspace = true slog.workspace = true slog-async.workspace = true slog-dtrace.workspace = true +slog-error-chain.workspace = true slog-term.workspace = true smf.workspace = true sprockets-tls.workspace = true +static_assertions.workspace = true strum.workspace = true tar.workspace = true thiserror.workspace = true @@ -109,13 +113,10 @@ tufaceous-artifact.workspace = true tufaceous-brand-metadata.workspace = true usdt.workspace = true uuid.workspace = true -zeroize.workspace = true -zone.workspace = true -static_assertions.workspace = true -omicron-workspace-hack.workspace = true -slog-error-chain.workspace = true walkdir.workspace = true +zeroize.workspace = true zip.workspace = true +zone.workspace = true [target.'cfg(target_os = "illumos")'.dependencies] opte-ioctl.workspace = true diff --git a/sled-agent/src/bin/sled-agent-sim.rs b/sled-agent/src/bin/sled-agent-sim.rs index 88ca421c555..675b5eb77d8 100644 --- a/sled-agent/src/bin/sled-agent-sim.rs +++ b/sled-agent/src/bin/sled-agent-sim.rs @@ -53,6 +53,9 @@ struct Args { #[clap(name = "NEXUS_IP:PORT", action)] nexus_addr: SocketAddr, + #[clap(action)] + nexus_lockstep_port: u16, + #[clap(long, name = "NEXUS_EXTERNAL_IP:PORT", action)] /// If specified, when the simulated sled agent initializes the rack, it /// will record the Nexus service running with the specified external IP @@ -154,7 +157,12 @@ async fn do_run() -> Result<(), CmdError> { let config_logging = ConfigLogging::StderrTerminal { level: ConfigLoggingLevel::Info }; - run_standalone_server(&config, &config_logging, &rss_args) - .await - .map_err(CmdError::Failure) + run_standalone_server( + &config, + args.nexus_lockstep_port, + &config_logging, + &rss_args, + ) + .await + .map_err(CmdError::Failure) } diff --git a/sled-agent/src/nexus.rs b/sled-agent/src/nexus.rs index e9e28b5c606..fa7ab74e3d5 100644 --- a/sled-agent/src/nexus.rs +++ b/sled-agent/src/nexus.rs @@ -54,9 +54,11 @@ pub(crate) trait ConvertInto: Sized { fn convert(self) -> T; } -impl ConvertInto for DiskVariant { - fn convert(self) -> nexus_client::types::PhysicalDiskKind { - use nexus_client::types::PhysicalDiskKind; +impl ConvertInto + for DiskVariant +{ + fn convert(self) -> nexus_lockstep_client::types::PhysicalDiskKind { + use nexus_lockstep_client::types::PhysicalDiskKind; match self { DiskVariant::U2 => PhysicalDiskKind::U2, diff --git a/sled-agent/src/rack_setup/service.rs b/sled-agent/src/rack_setup/service.rs index ff4d5d975f8..5506159553e 100644 --- a/sled-agent/src/rack_setup/service.rs +++ b/sled-agent/src/rack_setup/service.rs @@ -86,7 +86,7 @@ use id_map::IdMap; use internal_dns_resolver::Resolver as DnsResolver; use internal_dns_types::names::ServiceName; use itertools::Itertools; -use nexus_client::{ +use nexus_lockstep_client::{ Client as NexusClient, Error as NexusError, types as NexusTypes, }; use nexus_sled_agent_shared::inventory::{ @@ -774,7 +774,7 @@ impl ServiceInner { sled_plan: &SledPlan, service_plan: &ServicePlan, port_discovery_mode: ExternalPortDiscovery, - nexus_address: SocketAddrV6, + nexus_lockstep_address: SocketAddrV6, ) -> Result<(), SetupServiceError> { info!(self.log, "Handing off control to Nexus"); @@ -789,7 +789,11 @@ impl ServiceInner { ) .map_err(SetupServiceError::ConvertPlanToBlueprint)?; - info!(self.log, "Nexus address: {}", nexus_address.to_string()); + info!( + self.log, + "Nexus lockstep address: {}", + nexus_lockstep_address.to_string() + ); const CLIENT_TIMEOUT: Duration = Duration::from_secs(60); let client = reqwest::Client::builder() @@ -799,7 +803,7 @@ impl ServiceInner { .map_err(SetupServiceError::HttpClient)?; let nexus_client = NexusClient::new_with_client( - &format!("http://{}", nexus_address), + &format!("http://{}", nexus_lockstep_address), client, self.log.new(o!("component" => "NexusClient")), ); @@ -975,10 +979,10 @@ impl ServiceInner { local: spec.local, mode: match spec.mode { omicron_common::api::external::BfdMode::SingleHop => { - nexus_client::types::BfdMode::SingleHop + NexusTypes::BfdMode::SingleHop } omicron_common::api::external::BfdMode::MultiHop => { - nexus_client::types::BfdMode::MultiHop + NexusTypes::BfdMode::MultiHop } }, remote: spec.remote, @@ -1442,8 +1446,8 @@ impl ServiceInner { info!(self.log, "Finished setting up services"); - let nexus_address = - resolver.lookup_socket_v6(ServiceName::Nexus).await?; + let nexus_lockstep_address = + resolver.lookup_socket_v6(ServiceName::NexusLockstep).await?; rss_step.update(RssStep::NexusHandoff); // At this point, even if we reboot, we must not try to manage sleds, @@ -1453,7 +1457,7 @@ impl ServiceInner { &sled_plan, &service_plan, ExternalPortDiscovery::Auto(switch_mgmt_addrs), - nexus_address, + nexus_lockstep_address, ) .await?; diff --git a/sled-agent/src/sim/server.rs b/sled-agent/src/sim/server.rs index d417ec902e6..dca83cbd4c0 100644 --- a/sled-agent/src/sim/server.rs +++ b/sled-agent/src/sim/server.rs @@ -24,8 +24,12 @@ use internal_dns_types::config::DnsConfigBuilder; use internal_dns_types::names::DNS_ZONE_EXTERNAL_TESTING; use internal_dns_types::names::ServiceName; use nexus_client::types as NexusTypes; -use nexus_client::types::{IpRange, Ipv4Range, Ipv6Range}; use nexus_config::NUM_INITIAL_RESERVED_IP_ADDRESSES; +use nexus_lockstep_client::types::{ + AllowedSourceIps, CrucibleDatasetCreateRequest, ExternalPortDiscovery, + IpRange, Ipv4Range, Ipv6Range, RackInitializationRequest, + RackNetworkConfigV2, +}; use nexus_sled_agent_shared::inventory::OmicronZoneDataset; use nexus_types::deployment::{ BlueprintPhysicalDiskConfig, BlueprintPhysicalDiskDisposition, @@ -222,7 +226,7 @@ impl Server { let address = sled_agent.create_crucible_dataset(zpool_id, dataset_id); - datasets.push(NexusTypes::CrucibleDatasetCreateRequest { + datasets.push(CrucibleDatasetCreateRequest { zpool_id, dataset_id, address: address.to_string(), @@ -269,12 +273,12 @@ impl Server { async fn handoff_to_nexus( log: &Logger, - config: &Config, - request: &NexusTypes::RackInitializationRequest, + nexus_lockstep_address: SocketAddr, + request: &RackInitializationRequest, ) -> Result<(), anyhow::Error> { - let nexus_client = NexusClient::new( - &format!("http://{}", config.nexus_address), - log.new(o!("component" => "NexusClient")), + let nexus_client = nexus_lockstep_client::Client::new( + &format!("http://{}", nexus_lockstep_address), + log.new(o!("component" => "NexusLockstepClient")), ); let rack_id = uuid::uuid!("c19a698f-c6f9-4a17-ae30-20d711b8f7dc"); @@ -324,6 +328,7 @@ pub struct RssArgs { /// - Performs handoff to Nexus pub async fn run_standalone_server( config: &Config, + nexus_lockstep_port: u16, logging: &dropshot::ConfigLogging, rss_args: &RssArgs, ) -> Result<(), anyhow::Error> { @@ -433,7 +438,7 @@ pub async fn run_standalone_server( SocketAddr::V4(_) => panic!("did not expect v4 address"), SocketAddr::V6(a) => a, }, - lockstep_port: 0, + lockstep_port: nexus_lockstep_port, external_ip: from_ipaddr_to_external_floating_ip(ip), nic: nexus_types::inventory::NetworkInterface { id: Uuid::new_v4(), @@ -543,7 +548,7 @@ pub async fn run_standalone_server( for (dataset_id, address) in server.sled_agent.get_crucible_datasets(zpool_id) { - crucible_datasets.push(NexusTypes::CrucibleDatasetCreateRequest { + crucible_datasets.push(CrucibleDatasetCreateRequest { zpool_id, dataset_id, address: address.to_string(), @@ -582,7 +587,7 @@ pub async fn run_standalone_server( internal_dns_version, ) .context("could not construct initial blueprint")?; - let rack_init_request = NexusTypes::RackInitializationRequest { + let rack_init_request = RackInitializationRequest { blueprint, physical_disks, zpools, @@ -592,10 +597,8 @@ pub async fn run_standalone_server( internal_dns_zone_config: dns_config, external_dns_zone_name: DNS_ZONE_EXTERNAL_TESTING.to_owned(), recovery_silo, - external_port_count: NexusTypes::ExternalPortDiscovery::Static( - HashMap::new(), - ), - rack_network_config: NexusTypes::RackNetworkConfigV2 { + external_port_count: ExternalPortDiscovery::Static(HashMap::new()), + rack_network_config: RackNetworkConfigV2 { rack_subnet: Ipv6Net::host_net(Ipv6Addr::LOCALHOST), infra_ip_first: Ipv4Addr::LOCALHOST, infra_ip_last: Ipv4Addr::LOCALHOST, @@ -603,10 +606,12 @@ pub async fn run_standalone_server( bgp: Vec::new(), bfd: Vec::new(), }, - allowed_source_ips: NexusTypes::AllowedSourceIps::Any, + allowed_source_ips: AllowedSourceIps::Any, }; - handoff_to_nexus(&log, &config, &rack_init_request).await?; + let mut nexus_lockstep_address = config.nexus_address; + nexus_lockstep_address.set_port(nexus_lockstep_port); + handoff_to_nexus(&log, nexus_lockstep_address, &rack_init_request).await?; info!(log, "Handoff to Nexus is complete"); server.wait_for_finish().await diff --git a/sled-agent/src/sim/sled_agent.rs b/sled-agent/src/sim/sled_agent.rs index c75d6944b8b..4648490b2a0 100644 --- a/sled-agent/src/sim/sled_agent.rs +++ b/sled-agent/src/sim/sled_agent.rs @@ -539,11 +539,13 @@ impl SledAgent { pub fn get_all_physical_disks( &self, - ) -> Vec { + ) -> Vec { self.storage.lock().get_all_physical_disks() } - pub fn get_zpools(&self) -> Vec { + pub fn get_zpools( + &self, + ) -> Vec { self.storage.lock().get_all_zpools() } diff --git a/sled-agent/src/sim/storage.rs b/sled-agent/src/sim/storage.rs index 09ec86cf62b..d8bea8409e8 100644 --- a/sled-agent/src/sim/storage.rs +++ b/sled-agent/src/sim/storage.rs @@ -1732,20 +1732,20 @@ impl StorageInner { pub fn get_all_physical_disks( &self, - ) -> Vec { + ) -> Vec { self.physical_disks .iter() .map(|(id, disk)| { let variant = match disk.variant { DiskVariant::U2 => { - nexus_client::types::PhysicalDiskKind::U2 + nexus_lockstep_client::types::PhysicalDiskKind::U2 } DiskVariant::M2 => { - nexus_client::types::PhysicalDiskKind::M2 + nexus_lockstep_client::types::PhysicalDiskKind::M2 } }; - nexus_client::types::PhysicalDiskPutRequest { + nexus_lockstep_client::types::PhysicalDiskPutRequest { id: *id, vendor: disk.identity.vendor.clone(), serial: disk.identity.serial.clone(), @@ -1757,10 +1757,12 @@ impl StorageInner { .collect() } - pub fn get_all_zpools(&self) -> Vec { + pub fn get_all_zpools( + &self, + ) -> Vec { self.zpools .values() - .map(|pool| nexus_client::types::ZpoolPutRequest { + .map(|pool| nexus_lockstep_client::types::ZpoolPutRequest { id: pool.id.into_untyped_uuid(), sled_id: self.sled_id, physical_disk_id: pool.physical_disk_id, diff --git a/sled-agent/tests/output/cmd-sled-agent-sim-noargs-stderr b/sled-agent/tests/output/cmd-sled-agent-sim-noargs-stderr index 9aee5c4e5ad..22d0f48e7f6 100644 --- a/sled-agent/tests/output/cmd-sled-agent-sim-noargs-stderr +++ b/sled-agent/tests/output/cmd-sled-agent-sim-noargs-stderr @@ -2,7 +2,8 @@ error: the following required arguments were not provided: + -Usage: sled-agent-sim +Usage: sled-agent-sim For more information, try '--help'.