diff --git a/Cargo.lock b/Cargo.lock index 1ffba7cd297..461544bb336 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3143,7 +3143,7 @@ dependencies = [ "internet-checksum", "ispf", "macaddr", - "nexus-client", + "nexus-lockstep-client", "omicron-sled-agent", "omicron-test-utils", "omicron-uuid-kinds", @@ -6674,8 +6674,14 @@ name = "nexus-lockstep-api" version = "0.1.0" dependencies = [ "dropshot", + "http", "nexus-types", + "omicron-common", + "omicron-uuid-kinds", "omicron-workspace-hack", + "schemars", + "serde", + "uuid", ] [[package]] @@ -6684,12 +6690,18 @@ version = "0.1.0" dependencies = [ "chrono", "futures", + "iddqd", + "nexus-types", + "omicron-common", + "omicron-uuid-kinds", "omicron-workspace-hack", + "oxnet", "progenitor 0.10.0", "regress", "reqwest", "schemars", "serde", + "serde_json", "slog", "uuid", ] @@ -6819,8 +6831,8 @@ dependencies = [ "camino", "camino-tempfile", "clap", - "nexus-client", "nexus-db-queries", + "nexus-lockstep-client", "nexus-reconfigurator-preparation", "nexus-test-utils", "nexus-test-utils-macros", @@ -7115,9 +7127,9 @@ dependencies = [ "illumos-utils", "internal-dns-resolver", "internal-dns-types", - "nexus-client", "nexus-config", "nexus-db-queries", + "nexus-lockstep-client", "nexus-sled-agent-shared", "nexus-test-interface", "nexus-types", @@ -7865,11 +7877,11 @@ dependencies = [ "internal-dns-resolver", "internal-dns-types", "live-tests-macros", - "nexus-client", "nexus-config", "nexus-db-model", "nexus-db-queries", "nexus-inventory", + "nexus-lockstep-client", "nexus-reconfigurator-planning", "nexus-reconfigurator-preparation", "nexus-sled-agent-shared", @@ -7970,7 +7982,6 @@ dependencies = [ "mg-admin-client", "nexus-auth", "nexus-background-task-interface", - "nexus-client", "nexus-config", "nexus-db-lookup", "nexus-db-model", @@ -7981,6 +7992,7 @@ dependencies = [ "nexus-internal-api", "nexus-inventory", "nexus-lockstep-api", + "nexus-lockstep-client", "nexus-metrics-producer-gc", "nexus-mgs-updates", "nexus-networking", @@ -8152,7 +8164,6 @@ dependencies = [ "ipnetwork", "itertools 0.14.0", "multimap", - "nexus-client", "nexus-config", "nexus-db-errors", "nexus-db-lookup", @@ -8160,6 +8171,7 @@ dependencies = [ "nexus-db-queries", "nexus-db-schema", "nexus-inventory", + "nexus-lockstep-client", "nexus-reconfigurator-preparation", "nexus-saga-recovery", "nexus-sled-agent-shared", diff --git a/clients/nexus-client/src/lib.rs b/clients/nexus-client/src/lib.rs index ada5aa718c3..9d596230ac5 100644 --- a/clients/nexus-client/src/lib.rs +++ b/clients/nexus-client/src/lib.rs @@ -5,10 +5,7 @@ //! Interface for making API requests to the Oxide control plane at large //! from within the control plane -use iddqd::IdOrdItem; -use iddqd::id_upcast; use std::collections::HashMap; -use uuid::Uuid; progenitor::generate_api!( spec = "../../openapi/nexus-internal.json", @@ -38,8 +35,6 @@ progenitor::generate_api!( 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, @@ -47,26 +42,17 @@ progenitor::generate_api!( 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, NewPasswordHash = omicron_passwords::NewPasswordHash, - OmicronPhysicalDiskConfig = omicron_common::disk::OmicronPhysicalDiskConfig, - OmicronPhysicalDisksConfig = omicron_common::disk::OmicronPhysicalDisksConfig, 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, Srv = nexus_types::internal_api::params::Srv, TypedUuidForBlueprintKind = omicron_uuid_kinds::BlueprintUuid, - TypedUuidForCollectionKind = omicron_uuid_kinds::CollectionUuid, TypedUuidForDatasetKind = omicron_uuid_kinds::TypedUuid, - TypedUuidForDemoSagaKind = omicron_uuid_kinds::DemoSagaUuid, TypedUuidForDownstairsKind = omicron_uuid_kinds::TypedUuid, TypedUuidForPhysicalDiskKind = omicron_uuid_kinds::TypedUuid, TypedUuidForPropolisKind = omicron_uuid_kinds::TypedUuid, @@ -76,9 +62,6 @@ progenitor::generate_api!( TypedUuidForUpstairsSessionKind = omicron_uuid_kinds::TypedUuid, TypedUuidForVolumeKind = omicron_uuid_kinds::TypedUuid, TypedUuidForZpoolKind = omicron_uuid_kinds::TypedUuid, - 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 = { @@ -88,26 +71,6 @@ progenitor::generate_api!( } ); -impl IdOrdItem for types::PendingSagaInfo { - type Key<'a> = Uuid; - - fn key(&self) -> Self::Key<'_> { - self.saga_id - } - - id_upcast!(); -} - -impl IdOrdItem for types::HeldDbClaimInfo { - type Key<'a> = u64; - - fn key(&self) -> Self::Key<'_> { - self.id - } - - id_upcast!(); -} - impl omicron_common::api::external::ClientError for types::Error { fn message(&self) -> String { self.message.clone() diff --git a/clients/nexus-lockstep-client/Cargo.toml b/clients/nexus-lockstep-client/Cargo.toml index 94bf5afffe4..c64acbf1d94 100644 --- a/clients/nexus-lockstep-client/Cargo.toml +++ b/clients/nexus-lockstep-client/Cargo.toml @@ -10,11 +10,17 @@ workspace = true [dependencies] chrono.workspace = true futures.workspace = true +iddqd.workspace = true +nexus-types.workspace = true +omicron-common.workspace = true +omicron-uuid-kinds.workspace = true omicron-workspace-hack.workspace = true +oxnet.workspace = true progenitor.workspace = true regress.workspace = true reqwest.workspace = true schemars.workspace = true serde.workspace = true +serde_json.workspace = true slog.workspace = true uuid.workspace = true diff --git a/clients/nexus-lockstep-client/src/lib.rs b/clients/nexus-lockstep-client/src/lib.rs index c6ce38dc372..eb965b796fe 100644 --- a/clients/nexus-lockstep-client/src/lib.rs +++ b/clients/nexus-lockstep-client/src/lib.rs @@ -6,10 +6,14 @@ //! callers that update in lockstep with Nexus itself (e.g. rack initialization, //! tests and debugging) +use iddqd::IdOrdItem; +use iddqd::id_upcast; +use uuid::Uuid; + progenitor::generate_api!( spec = "../../openapi/nexus-lockstep.json", interface = Positional, - derives = [schemars::JsonSchema], + derives = [schemars::JsonSchema, PartialEq], inner_type = slog::Logger, pre_hook = (|log: &slog::Logger, request: &reqwest::Request| { slog::debug!(log, "client request"; @@ -21,4 +25,82 @@ progenitor::generate_api!( post_hook = (|log: &slog::Logger, result: &Result<_, _>| { slog::debug!(log, "client response"; "result" => ?result); }), + crates = { + "iddqd" = "*", + "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, + ClickhouseMode = nexus_types::deployment::ClickhouseMode, + ClickhousePolicy = nexus_types::deployment::ClickhousePolicy, + DatasetKind = omicron_common::api::internal::shared::DatasetKind, + Generation = omicron_common::api::external::Generation, + 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, + OximeterReadMode = nexus_types::deployment::OximeterReadMode, + OximeterReadPolicy = nexus_types::deployment::OximeterReadPolicy, + PendingMgsUpdate = nexus_types::deployment::PendingMgsUpdate, + ReconfiguratorConfig = nexus_types::deployment::ReconfiguratorConfig, + ReconfiguratorConfigParam = nexus_types::deployment::ReconfiguratorConfigParam, + ReconfiguratorConfigView = nexus_types::deployment::ReconfiguratorConfigView, + TypedUuidForBlueprintKind = omicron_uuid_kinds::BlueprintUuid, + TypedUuidForDatasetKind = omicron_uuid_kinds::TypedUuid, + TypedUuidForDemoSagaKind = omicron_uuid_kinds::DemoSagaUuid, + TypedUuidForPhysicalDiskKind = omicron_uuid_kinds::TypedUuid, + TypedUuidForSledKind = omicron_uuid_kinds::TypedUuid, + TypedUuidForZpoolKind = omicron_uuid_kinds::TypedUuid, + 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 = { + ByteCount = { derives = [PartialEq, Eq] }, + Baseboard = { derives = [PartialEq, Eq] } + } ); + +impl IdOrdItem for types::PendingSagaInfo { + type Key<'a> = Uuid; + + fn key(&self) -> Self::Key<'_> { + self.saga_id + } + + id_upcast!(); +} + +impl IdOrdItem for types::HeldDbClaimInfo { + type Key<'a> = u64; + + fn key(&self) -> Self::Key<'_> { + self.id + } + + id_upcast!(); +} + +impl From for types::Duration { + fn from(s: std::time::Duration) -> Self { + Self { secs: s.as_secs(), nanos: s.subsec_nanos() } + } +} + +impl From for std::time::Duration { + fn from(s: types::Duration) -> Self { + std::time::Duration::from_nanos( + s.secs * 1000000000 + u64::from(s.nanos), + ) + } +} diff --git a/dev-tools/omdb/Cargo.toml b/dev-tools/omdb/Cargo.toml index e4586238011..ca4123438c6 100644 --- a/dev-tools/omdb/Cargo.toml +++ b/dev-tools/omdb/Cargo.toml @@ -37,10 +37,12 @@ http.workspace = true humantime.workspace = true iddqd.workspace = true indent_write.workspace = true +indicatif.workspace = true internal-dns-resolver.workspace = true internal-dns-types.workspace = true +ipnetwork.workspace = true itertools.workspace = true -nexus-client.workspace = true +multimap.workspace = true nexus-config.workspace = true nexus-db-errors.workspace = true nexus-db-lookup.workspace = true @@ -48,15 +50,20 @@ nexus-db-model.workspace = true nexus-db-queries.workspace = true nexus-db-schema.workspace = true nexus-inventory.workspace = true +nexus-lockstep-client.workspace = true nexus-reconfigurator-preparation.workspace = true nexus-saga-recovery.workspace = true nexus-sled-agent-shared.workspace = true nexus-types.workspace = true omicron-common.workspace = true omicron-uuid-kinds.workspace = true +omicron-workspace-hack.workspace = true +owo-colors.workspace = true oxide-tokio-rt.workspace = true oximeter-client.workspace = true oximeter-db = { workspace = true, default-features = false, features = [ "oxql" ] } +oxnet.workspace = true +petgraph.workspace = true # See omicron-rpaths for more about the "pq-sys" dependency. pq-sys = "*" ratatui.workspace = true @@ -79,13 +86,6 @@ unicode-width.workspace = true update-engine.workspace = true url.workspace = true uuid.workspace = true -ipnetwork.workspace = true -omicron-workspace-hack.workspace = true -multimap.workspace = true -indicatif.workspace = true -petgraph.workspace = true -oxnet.workspace = true -owo-colors.workspace = true [dev-dependencies] camino-tempfile.workspace = true diff --git a/dev-tools/omdb/src/bin/omdb/db/saga.rs b/dev-tools/omdb/src/bin/omdb/db/saga.rs index 5113a4ad3fa..91ca84ca79e 100644 --- a/dev-tools/omdb/src/bin/omdb/db/saga.rs +++ b/dev-tools/omdb/src/bin/omdb/db/saga.rs @@ -607,7 +607,7 @@ async fn get_saga_sec_status( } }; - let client = nexus_client::Client::new( + let client = nexus_lockstep_client::Client::new( &format!("http://[{addr}]:{port}/"), opctx.log.clone(), ); @@ -618,21 +618,21 @@ async fn get_saga_sec_status( } Err(e) => match e { - nexus_client::Error::InvalidRequest(_) - | nexus_client::Error::InvalidUpgrade(_) - | nexus_client::Error::ErrorResponse(_) - | nexus_client::Error::ResponseBodyError(_) - | nexus_client::Error::InvalidResponsePayload(_, _) - | nexus_client::Error::UnexpectedResponse(_) - | nexus_client::Error::PreHookError(_) - | nexus_client::Error::PostHookError(_) => { + nexus_lockstep_client::Error::InvalidRequest(_) + | nexus_lockstep_client::Error::InvalidUpgrade(_) + | nexus_lockstep_client::Error::ErrorResponse(_) + | nexus_lockstep_client::Error::ResponseBodyError(_) + | nexus_lockstep_client::Error::InvalidResponsePayload(_, _) + | nexus_lockstep_client::Error::UnexpectedResponse(_) + | nexus_lockstep_client::Error::PreHookError(_) + | nexus_lockstep_client::Error::PostHookError(_) => { return SagaSecStatus::SecPingError { sec_id: current_sec, observed_error: e.to_string(), }; } - nexus_client::Error::CommunicationError(_) => { + nexus_lockstep_client::Error::CommunicationError(_) => { // Assume communication error means that it could not be // contacted. // diff --git a/dev-tools/omdb/src/bin/omdb/nexus.rs b/dev-tools/omdb/src/bin/omdb/nexus.rs index fbaedc02d8a..a1e006fd2e0 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus.rs @@ -31,19 +31,19 @@ use futures::TryStreamExt; use http::StatusCode; use internal_dns_types::names::ServiceName; use itertools::Itertools; -use nexus_client::types::ActivationReason; -use nexus_client::types::BackgroundTask; -use nexus_client::types::BackgroundTasksActivateRequest; -use nexus_client::types::CurrentStatus; -use nexus_client::types::LastResult; -use nexus_client::types::PhysicalDiskPath; -use nexus_client::types::SagaState; -use nexus_client::types::SledSelector; -use nexus_client::types::UninitializedSledId; use nexus_db_lookup::LookupPath; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::DataStore; use nexus_inventory::now_db_precision; +use nexus_lockstep_client::types::ActivationReason; +use nexus_lockstep_client::types::BackgroundTask; +use nexus_lockstep_client::types::BackgroundTasksActivateRequest; +use nexus_lockstep_client::types::CurrentStatus; +use nexus_lockstep_client::types::LastResult; +use nexus_lockstep_client::types::PhysicalDiskPath; +use nexus_lockstep_client::types::SagaState; +use nexus_lockstep_client::types::SledSelector; +use nexus_lockstep_client::types::UninitializedSledId; use nexus_saga_recovery::LastPass; use nexus_types::deployment::Blueprint; use nexus_types::deployment::ClickhouseMode; @@ -113,7 +113,7 @@ use uuid::Uuid; /// Arguments to the "omdb nexus" subcommand #[derive(Debug, Args)] pub struct NexusArgs { - /// URL of the Nexus internal API + /// URL of the Nexus internal lockstep API #[clap( long, env = "OMDB_NEXUS_URL", @@ -268,7 +268,7 @@ impl BlueprintIdOrCurrentTarget { async fn resolve_to_id_via_nexus( &self, - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> anyhow::Result { match self { Self::CurrentTarget => { @@ -284,7 +284,7 @@ impl BlueprintIdOrCurrentTarget { async fn resolve_to_blueprint( &self, - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> anyhow::Result { let id = self.resolve_to_id_via_nexus(client).await?; let response = client @@ -614,13 +614,14 @@ impl NexusArgs { "note: Nexus URL not specified. Will pick one from DNS." ); let addr = omdb - .dns_lookup_one(log.clone(), ServiceName::Nexus) + .dns_lookup_one(log.clone(), ServiceName::NexusLockstep) .await?; format!("http://{}", addr) } }; eprintln!("note: using Nexus URL {}", &nexus_url); - let client = nexus_client::Client::new(&nexus_url, log.clone()); + let client = + nexus_lockstep_client::Client::new(&nexus_url, log.clone()); match &self.command { NexusCommands::BackgroundTasks(BackgroundTasksArgs { @@ -837,7 +838,7 @@ impl NexusArgs { /// Runs `omdb nexus background-tasks doc` async fn cmd_nexus_background_tasks_doc( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> Result<(), anyhow::Error> { let response = client.bgtask_list().await.context("listing background tasks")?; @@ -863,7 +864,7 @@ async fn cmd_nexus_background_tasks_doc( /// Runs `omdb nexus background-tasks list` async fn cmd_nexus_background_tasks_list( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> Result<(), anyhow::Error> { let response = client.bgtask_list().await.context("listing background tasks")?; @@ -881,7 +882,7 @@ async fn cmd_nexus_background_tasks_list( /// Runs `omdb nexus background-tasks show` async fn cmd_nexus_background_tasks_show( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &BackgroundTasksShowArgs, ) -> Result<(), anyhow::Error> { let response = @@ -966,7 +967,7 @@ async fn cmd_nexus_background_tasks_show( /// Runs `omdb nexus background-tasks print-report` async fn cmd_nexus_background_tasks_print_report( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &BackgroundTasksPrintReportArgs, color: ColorChoice, ) -> Result<(), anyhow::Error> { @@ -1009,7 +1010,7 @@ async fn cmd_nexus_background_tasks_print_report( /// Runs `omdb nexus background-tasks activate` async fn cmd_nexus_background_tasks_activate( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &BackgroundTasksActivateArgs, // This isn't quite "destructive" in the sense that of it being potentially // dangerous, but it does modify the system rather than being a read-only @@ -3240,7 +3241,7 @@ fn reason_code(reason: ActivationReason) -> char { } async fn cmd_nexus_blueprints_list( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> Result<(), anyhow::Error> { #[derive(Tabled)] #[tabled(rename_all = "SCREAMING_SNAKE_CASE")] @@ -3312,7 +3313,7 @@ async fn cmd_nexus_blueprints_list( } async fn cmd_nexus_blueprints_show( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &BlueprintIdArgs, ) -> Result<(), anyhow::Error> { let blueprint = args.blueprint_id.resolve_to_blueprint(client).await?; @@ -3321,7 +3322,7 @@ async fn cmd_nexus_blueprints_show( } async fn cmd_nexus_blueprints_diff( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &BlueprintDiffArgs, ) -> Result<(), anyhow::Error> { let blueprint = args.blueprint1_id.resolve_to_blueprint(client).await?; @@ -3348,7 +3349,7 @@ async fn cmd_nexus_blueprints_diff( } async fn cmd_nexus_blueprints_delete( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &BlueprintIdArgs, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { @@ -3363,7 +3364,7 @@ async fn cmd_nexus_blueprints_delete( } async fn cmd_nexus_blueprints_target_show( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> Result<(), anyhow::Error> { let target = client .blueprint_target_view() @@ -3376,7 +3377,7 @@ async fn cmd_nexus_blueprints_target_show( } async fn cmd_nexus_blueprints_target_set( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &BlueprintTargetSetArgs, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { @@ -3432,10 +3433,12 @@ async fn cmd_nexus_blueprints_target_set( }; client - .blueprint_target_set(&nexus_client::types::BlueprintTargetSet { - target_id: args.blueprint_id, - enabled, - }) + .blueprint_target_set( + &nexus_lockstep_client::types::BlueprintTargetSet { + target_id: args.blueprint_id, + enabled, + }, + ) .await .with_context(|| { format!("setting target to blueprint {}", args.blueprint_id) @@ -3445,7 +3448,7 @@ async fn cmd_nexus_blueprints_target_set( } async fn cmd_nexus_blueprints_target_set_enabled( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &BlueprintIdArgs, enabled: bool, _destruction_token: DestructiveOperationToken, @@ -3455,7 +3458,7 @@ async fn cmd_nexus_blueprints_target_set_enabled( let description = if enabled { "enabled" } else { "disabled" }; client .blueprint_target_set_enabled( - &nexus_client::types::BlueprintTargetSet { + &nexus_lockstep_client::types::BlueprintTargetSet { target_id: blueprint_id, enabled, }, @@ -3469,7 +3472,7 @@ async fn cmd_nexus_blueprints_target_set_enabled( } async fn cmd_nexus_blueprints_regenerate( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { let blueprint = @@ -3479,7 +3482,7 @@ async fn cmd_nexus_blueprints_regenerate( } async fn cmd_nexus_blueprints_import( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, _destruction_token: DestructiveOperationToken, args: &BlueprintImportArgs, ) -> Result<(), anyhow::Error> { @@ -3497,7 +3500,7 @@ async fn cmd_nexus_blueprints_import( } async fn cmd_nexus_clickhouse_policy_get( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> Result<(), anyhow::Error> { let res = client.clickhouse_policy_get().await; @@ -3541,7 +3544,7 @@ async fn cmd_nexus_clickhouse_policy_get( } async fn cmd_nexus_mgs_updates( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> Result<(), anyhow::Error> { let response = client .mgs_updates() @@ -3553,7 +3556,7 @@ async fn cmd_nexus_mgs_updates( } async fn cmd_nexus_clickhouse_policy_set( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &ClickhousePolicySetArgs, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { @@ -3603,7 +3606,7 @@ async fn cmd_nexus_clickhouse_policy_set( } async fn cmd_nexus_oximeter_read_policy_get( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> Result<(), anyhow::Error> { let res = client.oximeter_read_policy_get().await; @@ -3637,7 +3640,7 @@ async fn cmd_nexus_oximeter_read_policy_get( } async fn cmd_nexus_oximeter_read_policy_set( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &OximeterReadPolicySetArgs, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { @@ -3681,7 +3684,7 @@ async fn cmd_nexus_oximeter_read_policy_set( /// Runs `omdb nexus sagas list` async fn cmd_nexus_sagas_list( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> Result<(), anyhow::Error> { // We don't want users to confuse this with a general way to list all sagas. // Such a command would read database state and it would go under "omdb db". @@ -3726,7 +3729,7 @@ async fn cmd_nexus_sagas_list( /// Runs `omdb nexus sagas demo-create` async fn cmd_nexus_sagas_demo_create( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { let demo_saga = @@ -3741,7 +3744,7 @@ async fn cmd_nexus_sagas_demo_create( /// Runs `omdb nexus sagas demo-complete` async fn cmd_nexus_sagas_demo_complete( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &DemoSagaIdArgs, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { @@ -3766,7 +3769,7 @@ async fn cmd_nexus_sagas_demo_complete( /// Runs `omdb nexus sleds list-uninitialized` async fn cmd_nexus_sleds_list_uninitialized( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> Result<(), anyhow::Error> { let response = client .sled_list_uninitialized() @@ -3808,7 +3811,7 @@ async fn cmd_nexus_sleds_list_uninitialized( /// Runs `omdb nexus sleds add` async fn cmd_nexus_sled_add( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &SledAddArgs, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { @@ -3827,7 +3830,7 @@ async fn cmd_nexus_sled_add( /// Runs `omdb nexus sleds expunge` async fn cmd_nexus_sled_expunge( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &SledExpungeArgs, omdb: &Omdb, log: &slog::Logger, @@ -3849,7 +3852,7 @@ async fn cmd_nexus_sled_expunge( // `omdb nexus sleds expunge`, but borrowing a datastore async fn cmd_nexus_sled_expunge_with_datastore( datastore: &Arc, - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &SledExpungeArgs, log: &slog::Logger, _destruction_token: DestructiveOperationToken, @@ -3938,7 +3941,7 @@ async fn cmd_nexus_sled_expunge_with_datastore( /// Runs `omdb nexus sleds expunge-disk` async fn cmd_nexus_sled_expunge_disk( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &DiskExpungeArgs, omdb: &Omdb, log: &slog::Logger, @@ -3959,7 +3962,7 @@ async fn cmd_nexus_sled_expunge_disk( async fn cmd_nexus_sled_expunge_disk_with_datastore( datastore: &Arc, - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &DiskExpungeArgs, log: &slog::Logger, _destruction_token: DestructiveOperationToken, @@ -4072,7 +4075,7 @@ async fn cmd_nexus_sled_expunge_disk_with_datastore( /// Runs `omdb nexus support-bundles list` async fn cmd_nexus_support_bundles_list( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> Result<(), anyhow::Error> { let support_bundle_stream = client.support_bundle_list_stream(None, None); @@ -4111,13 +4114,15 @@ async fn cmd_nexus_support_bundles_list( /// Runs `omdb nexus support-bundles create` async fn cmd_nexus_support_bundles_create( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { let support_bundle_id = client - .support_bundle_create(&nexus_client::types::SupportBundleCreate { - user_comment: None, - }) + .support_bundle_create( + &nexus_lockstep_client::types::SupportBundleCreate { + user_comment: None, + }, + ) .await .context("creating support bundle")? .into_inner() @@ -4128,7 +4133,7 @@ async fn cmd_nexus_support_bundles_create( /// Runs `omdb nexus support-bundles delete` async fn cmd_nexus_support_bundles_delete( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &SupportBundleDeleteArgs, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { @@ -4158,7 +4163,7 @@ async fn write_stream_to_sink( // // "range" is in bytes, and is inclusive on both sides. async fn support_bundle_download_range( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, id: SupportBundleUuid, range: (u64, u64), ) -> anyhow::Result>> { @@ -4177,7 +4182,7 @@ async fn support_bundle_download_range( // Starts the download at "start" bytes (inclusive) and continues up to "end" // bytes (exclusive). fn support_bundle_download_ranges( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, id: SupportBundleUuid, start: u64, end: u64, @@ -4206,7 +4211,7 @@ fn support_bundle_download_ranges( /// Runs `omdb nexus support-bundles download` async fn cmd_nexus_support_bundles_download( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &SupportBundleDownloadArgs, ) -> Result<(), anyhow::Error> { let total_length = client @@ -4244,7 +4249,7 @@ async fn cmd_nexus_support_bundles_download( /// Runs `omdb nexus support-bundles get-index` async fn cmd_nexus_support_bundles_get_index( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &SupportBundleIndexArgs, ) -> Result<(), anyhow::Error> { let stream = client @@ -4264,7 +4269,7 @@ async fn cmd_nexus_support_bundles_get_index( /// Runs `omdb nexus support-bundles get-file` async fn cmd_nexus_support_bundles_get_file( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &SupportBundleFileArgs, ) -> Result<(), anyhow::Error> { let stream = client @@ -4296,7 +4301,7 @@ async fn cmd_nexus_support_bundles_get_file( /// Runs `omdb nexus support-bundles inspect` async fn cmd_nexus_support_bundles_inspect( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &SupportBundleInspectArgs, ) -> Result<(), anyhow::Error> { let accessor: Box = match (args.id, &args.path) { diff --git a/dev-tools/omdb/src/bin/omdb/nexus/quiesce.rs b/dev-tools/omdb/src/bin/omdb/nexus/quiesce.rs index 73720272521..7d2a16a5e64 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus/quiesce.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus/quiesce.rs @@ -11,10 +11,10 @@ use chrono::TimeDelta; use chrono::Utc; use clap::Args; use clap::Subcommand; -use nexus_client::types::PendingRecovery; -use nexus_client::types::QuiesceState; -use nexus_client::types::QuiesceStatus; -use nexus_client::types::SagaQuiesceStatus; +use nexus_lockstep_client::types::PendingRecovery; +use nexus_lockstep_client::types::QuiesceState; +use nexus_lockstep_client::types::QuiesceStatus; +use nexus_lockstep_client::types::SagaQuiesceStatus; use std::time::Duration; #[derive(Debug, Args)] @@ -41,7 +41,7 @@ pub struct QuiesceShowArgs { pub async fn cmd_nexus_quiesce( omdb: &Omdb, - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &QuiesceArgs, ) -> Result<(), anyhow::Error> { match &args.command { @@ -54,7 +54,7 @@ pub async fn cmd_nexus_quiesce( } async fn quiesce_show( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &QuiesceShowArgs, ) -> Result<(), anyhow::Error> { let now = Utc::now(); @@ -237,7 +237,7 @@ async fn quiesce_show( } async fn quiesce_start( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, _token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { client.quiesce_start().await.context("quiescing Nexus")?; diff --git a/dev-tools/omdb/src/bin/omdb/nexus/reconfigurator_config.rs b/dev-tools/omdb/src/bin/omdb/nexus/reconfigurator_config.rs index 2b734a81cca..c033e12bbdf 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus/reconfigurator_config.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus/reconfigurator_config.rs @@ -113,7 +113,7 @@ impl FromStr for ReconfiguratorConfigVersionOrCurrent { pub async fn cmd_nexus_reconfigurator_config( omdb: &Omdb, - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &ReconfiguratorConfigArgs, ) -> Result<(), anyhow::Error> { match &args.command { @@ -127,7 +127,7 @@ pub async fn cmd_nexus_reconfigurator_config( } } async fn reconfigurator_config_show( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &ReconfiguratorConfigShowArgs, ) -> Result<(), anyhow::Error> { let res = match args.version { @@ -161,7 +161,7 @@ async fn reconfigurator_config_show( } async fn reconfigurator_config_set( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, args: &ReconfiguratorConfigSetArgs, _destruction_token: DestructiveOperationToken, ) -> Result<(), anyhow::Error> { diff --git a/dev-tools/omdb/src/bin/omdb/nexus/update_status.rs b/dev-tools/omdb/src/bin/omdb/nexus/update_status.rs index 8cc6a8252a3..fbd524e48bb 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus/update_status.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus/update_status.rs @@ -16,7 +16,7 @@ use tabled::Tabled; /// Runs `omdb nexus update-status` pub async fn cmd_nexus_update_status( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> Result<(), anyhow::Error> { let status = client .update_status() diff --git a/dev-tools/omdb/src/bin/omdb/support_bundle.rs b/dev-tools/omdb/src/bin/omdb/support_bundle.rs index 05c0e467d62..2d939790455 100644 --- a/dev-tools/omdb/src/bin/omdb/support_bundle.rs +++ b/dev-tools/omdb/src/bin/omdb/support_bundle.rs @@ -14,8 +14,8 @@ use camino::Utf8PathBuf; use futures::Stream; use futures::StreamExt; use futures::TryStreamExt; -use nexus_client::types::SupportBundleInfo; -use nexus_client::types::SupportBundleState; +use nexus_lockstep_client::types::SupportBundleInfo; +use nexus_lockstep_client::types::SupportBundleState; use omicron_uuid_kinds::GenericUuid; use omicron_uuid_kinds::SupportBundleUuid; use std::io; @@ -31,7 +31,7 @@ use tokio::io::AsyncRead; use tokio::io::ReadBuf; pub struct StreamedFile<'a> { - client: &'a nexus_client::Client, + client: &'a nexus_lockstep_client::Client, id: SupportBundleUuid, path: Utf8PathBuf, stream: Option> + Send>>>, @@ -40,7 +40,7 @@ pub struct StreamedFile<'a> { impl<'a> StreamedFile<'a> { fn new( - client: &'a nexus_client::Client, + client: &'a nexus_lockstep_client::Client, id: SupportBundleUuid, path: Utf8PathBuf, ) -> Self { @@ -111,13 +111,13 @@ impl AsyncRead for StreamedFile<'_> { /// Access to a support bundle from the internal API pub struct InternalApiAccess<'a> { - client: &'a nexus_client::Client, + client: &'a nexus_lockstep_client::Client, id: SupportBundleUuid, } impl<'a> InternalApiAccess<'a> { pub fn new( - client: &'a nexus_client::Client, + client: &'a nexus_lockstep_client::Client, id: SupportBundleUuid, ) -> Self { Self { client, id } @@ -170,7 +170,7 @@ impl<'c> SupportBundleAccessor for InternalApiAccess<'c> { } async fn wait_for_bundle_to_be_collected( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, id: SupportBundleUuid, ) -> Result { let mut printed_wait_msg = false; @@ -207,7 +207,7 @@ async fn wait_for_bundle_to_be_collected( /// /// If a bundle is being collected, waits for it. pub async fn access_bundle_from_id( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, id: Option, ) -> Result, anyhow::Error> { let id = match id { diff --git a/dev-tools/omdb/tests/test_all_output.rs b/dev-tools/omdb/tests/test_all_output.rs index 99d46b935c9..3aaffbf98ec 100644 --- a/dev-tools/omdb/tests/test_all_output.rs +++ b/dev-tools/omdb/tests/test_all_output.rs @@ -136,8 +136,8 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { let cmd_path = path_to_executable(CMD_OMDB); let postgres_url = cptestctx.database.listen_url(); - let nexus_internal_url = - format!("http://{}/", cptestctx.internal_client.bind_address); + let nexus_lockstep_url = + format!("http://{}/", cptestctx.lockstep_client.bind_address); let mgs_url = cptestctx .gateway .get(&SwitchLocation::Switch0) @@ -156,7 +156,7 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { // Get the CockroachDB metadata from the blueprint so we can redact it let initial_blueprint: Blueprint = dropshot::test_util::read_json( &mut cptestctx - .internal_client + .lockstep_client .make_request_no_body( Method::GET, &format!("/deployment/blueprints/all/{initial_blueprint_id}"), @@ -306,7 +306,7 @@ async fn test_omdb_success_cases(cptestctx: &ControlPlaneTestContext) { for args in invocations { println!("running commands with args: {:?}", args); let p = postgres_url.to_string(); - let u = nexus_internal_url.clone(); + let u = nexus_lockstep_url.clone(); let g = mgs_url.clone(); let ox = ox_url.clone(); let ch = ch_url.clone(); @@ -396,8 +396,8 @@ async fn test_omdb_env_settings(cptestctx: &ControlPlaneTestContext) { let cmd_path = path_to_executable(CMD_OMDB); let postgres_url = cptestctx.database.listen_url().to_string(); - let nexus_internal_url = - format!("http://{}", cptestctx.internal_client.bind_address); + let nexus_lockstep_url = + format!("http://{}", cptestctx.lockstep_client.bind_address); let ox_url = format!("http://{}/", cptestctx.oximeter.server_address()); let ox_test_producer = cptestctx.producer.address().ip(); let ch_url = format!("http://{}/", cptestctx.clickhouse.http_address()); @@ -425,7 +425,7 @@ async fn test_omdb_env_settings(cptestctx: &ControlPlaneTestContext) { let args = &[ "nexus", "--nexus-internal-url", - &nexus_internal_url.clone(), + &nexus_lockstep_url.clone(), "background-tasks", "doc", ]; @@ -434,7 +434,7 @@ async fn test_omdb_env_settings(cptestctx: &ControlPlaneTestContext) { // Case 2: specified in multiple places (command-line argument wins) let args = &["nexus", "--nexus-internal-url", "junk", "background-tasks", "doc"]; - let n = nexus_internal_url.clone(); + let n = nexus_lockstep_url.clone(); do_run( &mut output, move |exec| exec.env("OMDB_NEXUS_URL", &n), diff --git a/dev-tools/omdb/tests/usage_errors.out b/dev-tools/omdb/tests/usage_errors.out index 869df0c426f..704e2cd2591 100644 --- a/dev-tools/omdb/tests/usage_errors.out +++ b/dev-tools/omdb/tests/usage_errors.out @@ -866,9 +866,10 @@ Options: -h, --help Print help Connection Options: - --nexus-internal-url URL of the Nexus internal API [env: - OMDB_NEXUS_URL=] - --dns-server [env: OMDB_DNS_SERVER=] + --nexus-internal-url + URL of the Nexus internal lockstep API [env: OMDB_NEXUS_URL=] + --dns-server + [env: OMDB_DNS_SERVER=] Safety Options: -w, --destructive Allow potentially-destructive subcommands @@ -897,9 +898,10 @@ Options: -h, --help Print help Connection Options: - --nexus-internal-url URL of the Nexus internal API [env: - OMDB_NEXUS_URL=] - --dns-server [env: OMDB_DNS_SERVER=] + --nexus-internal-url + URL of the Nexus internal lockstep API [env: OMDB_NEXUS_URL=] + --dns-server + [env: OMDB_DNS_SERVER=] Safety Options: -w, --destructive Allow potentially-destructive subcommands @@ -942,7 +944,7 @@ Options: Connection Options: --nexus-internal-url - URL of the Nexus internal API + URL of the Nexus internal lockstep API [env: OMDB_NEXUS_URL=] @@ -981,9 +983,10 @@ Options: -h, --help Print help Connection Options: - --nexus-internal-url URL of the Nexus internal API [env: - OMDB_NEXUS_URL=] - --dns-server [env: OMDB_DNS_SERVER=] + --nexus-internal-url + URL of the Nexus internal lockstep API [env: OMDB_NEXUS_URL=] + --dns-server + [env: OMDB_DNS_SERVER=] Safety Options: -w, --destructive Allow potentially-destructive subcommands @@ -1010,9 +1013,10 @@ Options: -h, --help Print help Connection Options: - --nexus-internal-url URL of the Nexus internal API [env: - OMDB_NEXUS_URL=] - --dns-server [env: OMDB_DNS_SERVER=] + --nexus-internal-url + URL of the Nexus internal lockstep API [env: OMDB_NEXUS_URL=] + --dns-server + [env: OMDB_DNS_SERVER=] Safety Options: -w, --destructive Allow potentially-destructive subcommands @@ -1049,9 +1053,10 @@ Options: -h, --help Print help Connection Options: - --nexus-internal-url URL of the Nexus internal API [env: - OMDB_NEXUS_URL=] - --dns-server [env: OMDB_DNS_SERVER=] + --nexus-internal-url + URL of the Nexus internal lockstep API [env: OMDB_NEXUS_URL=] + --dns-server + [env: OMDB_DNS_SERVER=] Safety Options: -w, --destructive Allow potentially-destructive subcommands diff --git a/end-to-end-tests/Cargo.toml b/end-to-end-tests/Cargo.toml index c3010bb5d21..1854484a857 100644 --- a/end-to-end-tests/Cargo.toml +++ b/end-to-end-tests/Cargo.toml @@ -8,19 +8,31 @@ license = "MPL-2.0" workspace = true [dependencies] +anstyle.workspace = true anyhow = { workspace = true, features = ["backtrace"] } async-trait.workspace = true base64.workspace = true bytes.workspace = true chrono.workspace = true -http.workspace = true +clap.workspace = true +colored.workspace = true +# On Git commit for trust-dns -> hickory switch. +# Switch back to released versions of dhcproto on next release. +dhcproto = { git = "https://github.com/bluecatengineering/dhcproto.git", rev = "120da6fcd8a7be84d417d372634ead84ce07e6da" } futures.workspace = true +hickory-resolver.workspace = true +http.workspace = true +humantime.workspace = true internal-dns-resolver.workspace = true internal-dns-types.workspace = true -nexus-client.workspace = true +internet-checksum.workspace = true +ispf.workspace = true +macaddr.workspace = true +nexus-lockstep-client.workspace = true omicron-sled-agent.workspace = true omicron-test-utils.workspace = true omicron-uuid-kinds.workspace = true +omicron-workspace-hack.workspace = true oxide-client.workspace = true oxide-tokio-rt.workspace = true rand.workspace = true @@ -32,21 +44,8 @@ serde_json.workspace = true sled-agent-types.workspace = true slog.workspace = true slog-error-chain.workspace = true +socket2.workspace = true thiserror.workspace = true tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } toml.workspace = true -hickory-resolver.workspace = true uuid.workspace = true -omicron-workspace-hack.workspace = true -ispf.workspace = true -internet-checksum.workspace = true -humantime.workspace = true -socket2.workspace = true -colored.workspace = true -anstyle.workspace = true -clap.workspace = true -macaddr.workspace = true - -# On Git commit for trust-dns -> hickory switch. -# Switch back to released versions of dhcproto on next release. -dhcproto = { git = "https://github.com/bluecatengineering/dhcproto.git", rev = "120da6fcd8a7be84d417d372634ead84ce07e6da" } diff --git a/end-to-end-tests/src/noop_blueprint.rs b/end-to-end-tests/src/noop_blueprint.rs index c64981fe54d..3f24d3ee287 100644 --- a/end-to-end-tests/src/noop_blueprint.rs +++ b/end-to-end-tests/src/noop_blueprint.rs @@ -5,7 +5,7 @@ use internal_dns_resolver::Resolver; use internal_dns_types::names::ServiceName; -use nexus_client::Client as NexusClient; +use nexus_lockstep_client::Client as NexusClient; use omicron_test_utils::dev::poll::{CondCheckError, wait_for_condition}; use omicron_test_utils::dev::test_setup_log; use omicron_uuid_kinds::GenericUuid; @@ -95,7 +95,10 @@ enum MakeNexusError { #[error("looking up Nexus IP in internal DNS")] Resolve(#[from] internal_dns_resolver::ResolveError), #[error("making request to Nexus")] - Request(#[from] nexus_client::Error), + Request( + #[from] + nexus_lockstep_client::Error, + ), } /// Make one attempt to look up the IP of Nexus in internal DNS and make an HTTP @@ -109,7 +112,8 @@ async fn make_nexus_client( log: &slog::Logger, ) -> Result { debug!(log, "doing DNS lookup for Nexus"); - let nexus_ip = resolver.lookup_socket_v6(ServiceName::Nexus).await?; + let nexus_ip = + resolver.lookup_socket_v6(ServiceName::NexusLockstep).await?; let url = format!("http://{}", nexus_ip); debug!(log, "found Nexus IP"; "nexus_ip" => %nexus_ip, "url" => &url); diff --git a/live-tests/Cargo.toml b/live-tests/Cargo.toml index 7961e3b87f2..41c896bd0a9 100644 --- a/live-tests/Cargo.toml +++ b/live-tests/Cargo.toml @@ -20,11 +20,11 @@ futures.workspace = true internal-dns-resolver.workspace = true internal-dns-types.workspace = true live-tests-macros.workspace = true -nexus-client.workspace = true nexus-config.workspace = true nexus-db-model.workspace = true nexus-db-queries.workspace = true nexus-inventory.workspace = true +nexus-lockstep-client.workspace = true nexus-reconfigurator-planning.workspace = true nexus-reconfigurator-preparation.workspace = true nexus-sled-agent-shared.workspace = true diff --git a/live-tests/tests/common/mod.rs b/live-tests/tests/common/mod.rs index 40ddbb14fe8..6bbbaf9b564 100644 --- a/live-tests/tests/common/mod.rs +++ b/live-tests/tests/common/mod.rs @@ -73,20 +73,20 @@ impl LiveTestContext { pub fn specific_internal_nexus_client( &self, sockaddr: SocketAddrV6, - ) -> nexus_client::Client { + ) -> nexus_lockstep_client::Client { let url = format!("http://{}", sockaddr); let log = self.logctx.log.new(o!("nexus_internal_url" => url.clone())); - nexus_client::Client::new(&url, log) + nexus_lockstep_client::Client::new(&url, log) } /// Returns a list of clients for the internal APIs for all Nexus instances /// found in DNS pub async fn all_internal_nexus_clients( &self, - ) -> Result, anyhow::Error> { + ) -> Result, anyhow::Error> { Ok(self .resolver - .lookup_all_socket_v6(ServiceName::Nexus) + .lookup_all_socket_v6(ServiceName::NexusLockstep) .await .context("looking up Nexus in internal DNS")? .into_iter() diff --git a/live-tests/tests/common/reconfigurator.rs b/live-tests/tests/common/reconfigurator.rs index 57247600053..1f1aa5e212d 100644 --- a/live-tests/tests/common/reconfigurator.rs +++ b/live-tests/tests/common/reconfigurator.rs @@ -5,9 +5,11 @@ //! Helpers common to Reconfigurator tests use anyhow::{Context, anyhow, bail, ensure}; -use nexus_client::types::{BackgroundTasksActivateRequest, BlueprintTargetSet}; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::DataStore; +use nexus_lockstep_client::types::{ + BackgroundTasksActivateRequest, BlueprintTargetSet, +}; use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; use nexus_reconfigurator_planning::planner::PlannerRng; use nexus_types::deployment::{Blueprint, BlueprintSource, PlanningInput}; @@ -25,7 +27,7 @@ use std::time::Duration; /// don't want to proceed with tests. pub async fn blueprint_load_target_enabled( log: &slog::Logger, - nexus: &nexus_client::Client, + nexus: &nexus_lockstep_client::Client, ) -> Result { // Fetch the current target configuration. info!(log, "editing current target blueprint"); @@ -80,7 +82,7 @@ pub async fn blueprint_edit_current_target( log: &slog::Logger, planning_input: &PlanningInput, collection: &Collection, - nexus: &nexus_client::Client, + nexus: &nexus_lockstep_client::Client, edit_fn: &dyn Fn(&mut BlueprintBuilder) -> Result<(), anyhow::Error>, ) -> Result<(Blueprint, Blueprint), anyhow::Error> { // Fetch the current target configuration. @@ -183,7 +185,7 @@ pub async fn blueprint_wait_sled_configs_propagated( opctx: &OpContext, datastore: &DataStore, blueprint: &Blueprint, - nexus: &nexus_client::Client, + nexus: &nexus_lockstep_client::Client, timeout: Duration, ) -> Result { wait_for_condition( diff --git a/live-tests/tests/test_nexus_add_remove.rs b/live-tests/tests/test_nexus_add_remove.rs index 2cf7acfb8d1..405c06ecc92 100644 --- a/live-tests/tests/test_nexus_add_remove.rs +++ b/live-tests/tests/test_nexus_add_remove.rs @@ -10,11 +10,11 @@ use common::LiveTestContext; use common::reconfigurator::blueprint_edit_current_target; use futures::TryStreamExt; use live_tests_macros::live_test; -use nexus_client::types::BlueprintTargetSet; -use nexus_client::types::QuiesceState; -use nexus_client::types::Saga; -use nexus_client::types::SagaState; use nexus_inventory::CollectionBuilder; +use nexus_lockstep_client::types::BlueprintTargetSet; +use nexus_lockstep_client::types::QuiesceState; +use nexus_lockstep_client::types::Saga; +use nexus_lockstep_client::types::SagaState; use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; use nexus_reconfigurator_planning::planner::Planner; use nexus_reconfigurator_planning::planner::PlannerRng; @@ -210,7 +210,7 @@ async fn test_nexus_add_remove(lc: &LiveTestContext) { wait_for_condition( || async { match new_zone_client.saga_list(None, None, None).await { - Err(nexus_client::Error::CommunicationError(error)) => { + Err(nexus_lockstep_client::Error::CommunicationError(error)) => { info!(log, "expunged Nexus no longer reachable"; "error" => slog_error_chain::InlineErrorChain::new(&error), ); @@ -374,7 +374,7 @@ async fn test_nexus_add_remove(lc: &LiveTestContext) { } async fn list_sagas( - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, ) -> Result, anyhow::Error> { client .saga_list_stream(None, None) diff --git a/live-tests/tests/test_nexus_handoff.rs b/live-tests/tests/test_nexus_handoff.rs index f27ec3f242e..4d87a6d4a28 100644 --- a/live-tests/tests/test_nexus_handoff.rs +++ b/live-tests/tests/test_nexus_handoff.rs @@ -10,8 +10,8 @@ use anyhow::Context; use common::LiveTestContext; use common::reconfigurator::blueprint_edit_current_target; use live_tests_macros::live_test; -use nexus_client::types::QuiesceState; use nexus_db_model::DbMetadataNexusState; +use nexus_lockstep_client::types::QuiesceState; use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; use nexus_reconfigurator_preparation::PlanningInputFromDb; use nexus_types::deployment::BlueprintZoneDisposition; diff --git a/nexus/Cargo.toml b/nexus/Cargo.toml index eb509439521..92846019f62 100644 --- a/nexus/Cargo.toml +++ b/nexus/Cargo.toml @@ -151,14 +151,17 @@ criterion.workspace = true diesel.workspace = true dns-server.workspace = true expectorate.workspace = true -hyper-rustls.workspace = true gateway-messages.workspace = true gateway-test-utils.workspace = true +hickory-resolver.workspace = true +httpmock.workspace = true +httptest.workspace = true hubtools.workspace = true +hyper-rustls.workspace = true nexus-db-queries = { workspace = true, features = ["testing"] } -nexus-client.workspace = true -nexus-test-utils-macros.workspace = true +nexus-lockstep-client.workspace = true nexus-test-utils.workspace = true +nexus-test-utils-macros.workspace = true omicron-sled-agent.workspace = true omicron-test-utils.workspace = true openapi-lint.workspace = true @@ -170,18 +173,15 @@ petgraph.workspace = true pretty_assertions.workspace = true rcgen.workspace = true regex.workspace = true +rustls.workspace = true similar-asserts.workspace = true sp-sim.workspace = true -rustls.workspace = true +strum.workspace = true subprocess.workspace = true term.workspace = true -hickory-resolver.workspace = true tufaceous.workspace = true -tufaceous-lib.workspace = true -httptest.workspace = true -httpmock.workspace = true -strum.workspace = true tufaceous-artifact.workspace = true +tufaceous-lib.workspace = true [[bench]] name = "setup_benchmark" diff --git a/nexus/internal-api/src/lib.rs b/nexus/internal-api/src/lib.rs index eeafb17dae9..6481db1c903 100644 --- a/nexus/internal-api/src/lib.rs +++ b/nexus/internal-api/src/lib.rs @@ -2,42 +2,28 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use dropshot::{ - Body, Header, HttpError, HttpResponseCreated, HttpResponseDeleted, - HttpResponseOk, HttpResponseUpdatedNoContent, Path, Query, RequestContext, - ResultsPage, TypedBody, + HttpError, HttpResponseCreated, HttpResponseOk, + HttpResponseUpdatedNoContent, Path, Query, RequestContext, ResultsPage, + TypedBody, }; -use http::Response; use nexus_types::{ - deployment::{ - Blueprint, BlueprintMetadata, BlueprintTarget, BlueprintTargetSet, - ClickhousePolicy, OximeterReadPolicy, ReconfiguratorConfigParam, - ReconfiguratorConfigView, - }, external_api::{ - headers::RangeRequest, - params::{self, PhysicalDiskPath, SledSelector, UninitializedSledId}, - shared::{self, ProbeInfo, UninitializedSled}, - views::{Ping, PingStatus, SledPolicy}, + shared::ProbeInfo, + views::{Ping, PingStatus}, }, internal_api::{ params::{ - InstanceMigrateRequest, OximeterInfo, RackInitializationRequest, - SledAgentInfo, SwitchPutRequest, SwitchPutResponse, - }, - views::{ - BackgroundTask, DemoSaga, MgsUpdateDriverStatus, NatEntryView, - QuiesceStatus, Saga, UpdateStatus, + OximeterInfo, RackInitializationRequest, SledAgentInfo, + SwitchPutRequest, SwitchPutResponse, }, + views::NatEntryView, }, }; use omicron_common::api::{ - external::{ - Instance, - http_pagination::{PaginatedById, PaginatedByTimeAndId}, - }, + external::http_pagination::PaginatedById, internal::nexus::{ DiskRuntimeState, DownstairsClientStopRequest, DownstairsClientStopped, ProducerEndpoint, ProducerRegistrationResponse, RepairFinishInfo, @@ -138,16 +124,6 @@ pub trait NexusInternalApi { new_runtime_state: TypedBody, ) -> Result; - #[endpoint { - method = POST, - path = "/instances/{instance_id}/migrate", - }] - async fn instance_migrate( - rqctx: RequestContext, - path_params: Path, - migrate_params: TypedBody, - ) -> Result, HttpError>; - /// Report updated state for a disk. #[endpoint { method = PUT, @@ -280,81 +256,11 @@ pub trait NexusInternalApi { downstairs_client_stopped: TypedBody, ) -> Result; - // Debug interfaces for sagas - - /// List sagas - #[endpoint { - method = GET, - path = "/sagas", - }] - async fn saga_list( - rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError>; - - /// Fetch a saga - #[endpoint { - method = GET, - path = "/sagas/{saga_id}", - }] - async fn saga_view( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; - - /// Kick off an instance of the "demo" saga + /// **Do not use in new code!** /// - /// This saga is used for demo and testing. The saga just waits until you - /// complete using the `saga_demo_complete` API. - #[endpoint { - method = POST, - path = "/demo-saga", - }] - async fn saga_demo_create( - rqctx: RequestContext, - ) -> Result, HttpError>; - - /// Complete a waiting demo saga - /// - /// Note that the id used here is not the same as the id of the saga. It's - /// the one returned by the `saga_demo_create` API. - #[endpoint { - method = POST, - path = "/demo-saga/{demo_saga_id}/complete", - }] - async fn saga_demo_complete( - rqctx: RequestContext, - path_params: Path, - ) -> Result; - - // Debug interfaces for background Tasks - - /// List background tasks - /// - /// This is a list of discrete background activities that Nexus carries out. - /// This is exposed for support and debugging. - #[endpoint { - method = GET, - path = "/bgtasks", - }] - async fn bgtask_list( - rqctx: RequestContext, - ) -> Result>, HttpError>; - - /// Fetch status of one background task - /// - /// This is exposed for support and debugging. - #[endpoint { - method = GET, - path = "/bgtasks/view/{bgtask_name}", - }] - async fn bgtask_view( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; - - /// Activates one or more background tasks, causing them to be run immediately - /// if idle, or scheduled to run again as soon as possible if already running. + /// Callers to this API should either be capable of using the nexus-lockstep + /// API or should be rewritten to use a doorbell API to activate a specific + /// task. Task names are internal to Nexus. #[endpoint { method = POST, path = "/bgtasks/activate", @@ -364,17 +270,6 @@ pub trait NexusInternalApi { body: TypedBody, ) -> Result; - // Debug interfaces for ongoing MGS updates - - /// Fetch information about ongoing MGS updates - #[endpoint { - method = GET, - path = "/mgs-updates", - }] - async fn mgs_updates( - rqctx: RequestContext, - ) -> Result, HttpError>; - // NAT RPW internal APIs /// Fetch NAT ChangeSet @@ -394,301 +289,6 @@ pub trait NexusInternalApi { query_params: Query, ) -> Result>, HttpError>; - // APIs for managing blueprints - // - // These are not (yet) intended for use by any other programs. Eventually, we - // will want this functionality part of the public API. But we don't want to - // commit to any of this yet. These properly belong in an RFD 399-style - // "Service and Support API". Absent that, we stick them here. - - /// Lists blueprints - #[endpoint { - method = GET, - path = "/deployment/blueprints/all", - }] - async fn blueprint_list( - rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError>; - - /// Fetches one blueprint - #[endpoint { - method = GET, - path = "/deployment/blueprints/all/{blueprint_id}", - }] - async fn blueprint_view( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; - - /// Deletes one blueprint - #[endpoint { - method = DELETE, - path = "/deployment/blueprints/all/{blueprint_id}", - }] - async fn blueprint_delete( - rqctx: RequestContext, - path_params: Path, - ) -> Result; - - // Managing the current target blueprint - - /// Fetches the current target blueprint, if any - #[endpoint { - method = GET, - path = "/deployment/blueprints/target", - }] - async fn blueprint_target_view( - rqctx: RequestContext, - ) -> Result, HttpError>; - - /// Make the specified blueprint the new target - #[endpoint { - method = POST, - path = "/deployment/blueprints/target", - }] - async fn blueprint_target_set( - rqctx: RequestContext, - target: TypedBody, - ) -> Result, HttpError>; - - /// Set the `enabled` field of the current target blueprint - #[endpoint { - method = PUT, - path = "/deployment/blueprints/target/enabled", - }] - async fn blueprint_target_set_enabled( - rqctx: RequestContext, - target: TypedBody, - ) -> Result, HttpError>; - - // Generating blueprints - - /// Generates a new blueprint for the current system, re-evaluating anything - /// that's changed since the last one was generated - #[endpoint { - method = POST, - path = "/deployment/blueprints/regenerate", - }] - async fn blueprint_regenerate( - rqctx: RequestContext, - ) -> Result, HttpError>; - - /// Imports a client-provided blueprint - /// - /// This is intended for development and support, not end users or operators. - #[endpoint { - method = POST, - path = "/deployment/blueprints/import", - }] - async fn blueprint_import( - rqctx: RequestContext, - blueprint: TypedBody, - ) -> Result; - - /// Get the current reconfigurator configuration - #[endpoint { - method = GET, - path = "/deployment/reconfigurator-config" - }] - async fn reconfigurator_config_show_current( - rqctx: RequestContext, - ) -> Result, HttpError>; - - /// Get the reconfigurator config at `version` if it exists - #[endpoint { - method = GET, - path = "/deployment/reconfigurator-config/{version}" - }] - async fn reconfigurator_config_show( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; - - /// Update the reconfigurator config at the latest versions - #[endpoint { - method = POST, - path = "/deployment/reconfigurator-config" - }] - async fn reconfigurator_config_set( - rqctx: RequestContext, - switches: TypedBody, - ) -> Result; - - /// Show deployed versions of artifacts - #[endpoint { - method = GET, - path = "/deployment/update-status" - }] - async fn update_status( - rqctx: RequestContext, - ) -> Result, HttpError>; - - /// List uninitialized sleds - #[endpoint { - method = GET, - path = "/sleds/uninitialized", - }] - async fn sled_list_uninitialized( - rqctx: RequestContext, - ) -> Result>, HttpError>; - - /// Add sled to initialized rack - // - // TODO: In the future this should really be a PUT request, once we resolve - // https://github.com/oxidecomputer/omicron/issues/4494. It should also - // explicitly be tied to a rack via a `rack_id` path param. For now we assume - // we are only operating on single rack systems. - #[endpoint { - method = POST, - path = "/sleds/add", - }] - async fn sled_add( - rqctx: RequestContext, - sled: TypedBody, - ) -> Result, HttpError>; - - /// Mark a sled as expunged - /// - /// This is an irreversible process! It should only be called after - /// sufficient warning to the operator. - /// - /// This is idempotent, and it returns the old policy of the sled. - #[endpoint { - method = POST, - path = "/sleds/expunge", - }] - async fn sled_expunge( - rqctx: RequestContext, - sled: TypedBody, - ) -> Result, HttpError>; - - /// Mark a physical disk as expunged - /// - /// This is an irreversible process! It should only be called after - /// sufficient warning to the operator. - /// - /// This is idempotent. - #[endpoint { - method = POST, - path = "/physical-disk/expunge", - }] - async fn physical_disk_expunge( - rqctx: RequestContext, - disk: TypedBody, - ) -> Result; - - // Support bundles (experimental) - - /// List all support bundles - #[endpoint { - method = GET, - path = "/experimental/v1/system/support-bundles", - }] - async fn support_bundle_list( - rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError>; - - /// View a support bundle - #[endpoint { - method = GET, - path = "/experimental/v1/system/support-bundles/{bundle_id}", - }] - async fn support_bundle_view( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; - - /// Download the index of a support bundle - #[endpoint { - method = GET, - path = "/experimental/v1/system/support-bundles/{bundle_id}/index", - }] - async fn support_bundle_index( - rqctx: RequestContext, - headers: Header, - path_params: Path, - ) -> Result, HttpError>; - - /// Download the contents of a support bundle - #[endpoint { - method = GET, - path = "/experimental/v1/system/support-bundles/{bundle_id}/download", - }] - async fn support_bundle_download( - rqctx: RequestContext, - headers: Header, - path_params: Path, - ) -> Result, HttpError>; - - /// Download a file within a support bundle - #[endpoint { - method = GET, - path = "/experimental/v1/system/support-bundles/{bundle_id}/download/{file}", - }] - async fn support_bundle_download_file( - rqctx: RequestContext, - headers: Header, - path_params: Path, - ) -> Result, HttpError>; - - /// Download the metadata of a support bundle - #[endpoint { - method = HEAD, - path = "/experimental/v1/system/support-bundles/{bundle_id}/download", - }] - async fn support_bundle_head( - rqctx: RequestContext, - headers: Header, - path_params: Path, - ) -> Result, HttpError>; - - /// Download the metadata of a file within the support bundle - #[endpoint { - method = HEAD, - path = "/experimental/v1/system/support-bundles/{bundle_id}/download/{file}", - }] - async fn support_bundle_head_file( - rqctx: RequestContext, - headers: Header, - path_params: Path, - ) -> Result, HttpError>; - - /// Create a new support bundle - #[endpoint { - method = POST, - path = "/experimental/v1/system/support-bundles", - }] - async fn support_bundle_create( - rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError>; - - /// Delete an existing support bundle - /// - /// May also be used to cancel a support bundle which is currently being - /// collected, or to remove metadata for a support bundle that has failed. - #[endpoint { - method = DELETE, - path = "/experimental/v1/system/support-bundles/{bundle_id}", - }] - async fn support_bundle_delete( - rqctx: RequestContext, - path_params: Path, - ) -> Result; - - /// Update a support bundle - #[endpoint { - method = PUT, - path = "/experimental/v1/system/support-bundles/{bundle_id}", - }] - async fn support_bundle_update( - rqctx: RequestContext, - path_params: Path, - body: TypedBody, - ) -> Result, HttpError>; - /// Get all the probes associated with a given sled. #[endpoint { method = GET, @@ -699,66 +299,6 @@ pub trait NexusInternalApi { path_params: Path, query_params: Query, ) -> Result>, HttpError>; - - /// Get the current clickhouse policy - #[endpoint { - method = GET, - path = "/clickhouse/policy" - }] - async fn clickhouse_policy_get( - rqctx: RequestContext, - ) -> Result, HttpError>; - - /// Set the new clickhouse policy - #[endpoint { - method = POST, - path = "/clickhouse/policy" - }] - async fn clickhouse_policy_set( - rqctx: RequestContext, - policy: TypedBody, - ) -> Result; - - /// Get the current oximeter read policy - #[endpoint { - method = GET, - path = "/oximeter/read-policy" - }] - async fn oximeter_read_policy_get( - rqctx: RequestContext, - ) -> Result, HttpError>; - - /// Set the new oximeter read policy - #[endpoint { - method = POST, - path = "/oximeter/read-policy" - }] - async fn oximeter_read_policy_set( - rqctx: RequestContext, - policy: TypedBody, - ) -> Result; - - /// Begin quiescing this Nexus instance - /// - /// This causes no new sagas to be started and eventually causes no database - /// connections to become available. This is a one-way trip. There's no - /// unquiescing Nexus. - #[endpoint { - method = POST, - path = "/quiesce" - }] - async fn quiesce_start( - rqctx: RequestContext, - ) -> Result; - - /// Check whether Nexus is running normally, quiescing, or fully quiesced. - #[endpoint { - method = GET, - path = "/quiesce" - }] - async fn quiesce_get( - rqctx: RequestContext, - ) -> Result, HttpError>; } /// Path parameters for Sled Agent requests (internal API) @@ -791,13 +331,6 @@ pub struct SwitchPathParam { pub switch_id: Uuid, } -/// Path parameters for Instance requests (internal API) -#[derive(Deserialize, JsonSchema)] -pub struct InstancePathParam { - #[schemars(with = "Uuid")] - pub instance_id: InstanceUuid, -} - /// Path parameters for VMM requests (internal API) #[derive(Deserialize, JsonSchema)] pub struct VmmPathParam { @@ -830,25 +363,6 @@ pub struct UpstairsDownstairsPathParam { pub downstairs_id: TypedUuid, } -/// Path parameters for Saga requests -#[derive(Deserialize, JsonSchema)] -pub struct SagaPathParam { - #[serde(rename = "saga_id")] - pub saga_id: Uuid, -} - -/// Path parameters for DemoSaga requests -#[derive(Deserialize, JsonSchema)] -pub struct DemoSagaPathParam { - pub demo_saga_id: DemoSagaUuid, -} - -/// Path parameters for Background Task requests -#[derive(Deserialize, JsonSchema)] -pub struct BackgroundTaskPathParam { - pub bgtask_name: String, -} - /// Query parameters for Background Task activation requests. #[derive(Deserialize, JsonSchema)] pub struct BackgroundTasksActivateRequest { @@ -880,8 +394,3 @@ pub struct ProbePathParam { #[schemars(with = "Uuid")] pub sled: SledUuid, } - -#[derive(Deserialize, JsonSchema)] -pub struct VersionPathParam { - pub version: u32, -} diff --git a/nexus/lockstep-api/Cargo.toml b/nexus/lockstep-api/Cargo.toml index 06ce4b373bf..1ec8dffb5b8 100644 --- a/nexus/lockstep-api/Cargo.toml +++ b/nexus/lockstep-api/Cargo.toml @@ -9,5 +9,11 @@ workspace = true [dependencies] dropshot.workspace = true +http.workspace = true nexus-types.workspace = true +omicron-common.workspace = true +omicron-uuid-kinds.workspace = true omicron-workspace-hack.workspace = true +schemars.workspace = true +serde.workspace = true +uuid.workspace = true diff --git a/nexus/lockstep-api/src/lib.rs b/nexus/lockstep-api/src/lib.rs index 5d41943a1c4..43b54ac9260 100644 --- a/nexus/lockstep-api/src/lib.rs +++ b/nexus/lockstep-api/src/lib.rs @@ -2,8 +2,55 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use dropshot::{HttpError, HttpResponseOk, RequestContext}; -use nexus_types::external_api::views::{Ping, PingStatus}; +use std::collections::BTreeMap; +use std::collections::BTreeSet; + +use dropshot::Body; +use dropshot::Header; +use dropshot::HttpError; +use dropshot::HttpResponseCreated; +use dropshot::HttpResponseDeleted; +use dropshot::HttpResponseOk; +use dropshot::HttpResponseUpdatedNoContent; +use dropshot::Path; +use dropshot::Query; +use dropshot::RequestContext; +use dropshot::ResultsPage; +use dropshot::TypedBody; +use http::Response; +use nexus_types::deployment::Blueprint; +use nexus_types::deployment::BlueprintMetadata; +use nexus_types::deployment::BlueprintTarget; +use nexus_types::deployment::BlueprintTargetSet; +use nexus_types::deployment::ClickhousePolicy; +use nexus_types::deployment::OximeterReadPolicy; +use nexus_types::deployment::ReconfiguratorConfigParam; +use nexus_types::deployment::ReconfiguratorConfigView; +use nexus_types::external_api::headers::RangeRequest; +use nexus_types::external_api::params; +use nexus_types::external_api::params::PhysicalDiskPath; +use nexus_types::external_api::params::SledSelector; +use nexus_types::external_api::params::UninitializedSledId; +use nexus_types::external_api::shared; +use nexus_types::external_api::shared::UninitializedSled; +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::views::BackgroundTask; +use nexus_types::internal_api::views::DemoSaga; +use nexus_types::internal_api::views::MgsUpdateDriverStatus; +use nexus_types::internal_api::views::QuiesceStatus; +use nexus_types::internal_api::views::Saga; +use nexus_types::internal_api::views::UpdateStatus; +use omicron_common::api::external::Instance; +use omicron_common::api::external::http_pagination::PaginatedById; +use omicron_common::api::external::http_pagination::PaginatedByTimeAndId; +use omicron_uuid_kinds::*; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use uuid::Uuid; #[dropshot::api_description] pub trait NexusLockstepApi { @@ -21,4 +68,505 @@ pub trait NexusLockstepApi { ) -> Result, HttpError> { Ok(HttpResponseOk(Ping { status: PingStatus::Ok })) } + + #[endpoint { + method = POST, + path = "/instances/{instance_id}/migrate", + }] + async fn instance_migrate( + rqctx: RequestContext, + path_params: Path, + migrate_params: TypedBody, + ) -> Result, HttpError>; + + // Debug interfaces for sagas + + /// List sagas + #[endpoint { + method = GET, + path = "/sagas", + }] + async fn saga_list( + rqctx: RequestContext, + query_params: Query, + ) -> Result>, HttpError>; + + /// Fetch a saga + #[endpoint { + method = GET, + path = "/sagas/{saga_id}", + }] + async fn saga_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError>; + + /// Kick off an instance of the "demo" saga + /// + /// This saga is used for demo and testing. The saga just waits until you + /// complete using the `saga_demo_complete` API. + #[endpoint { + method = POST, + path = "/demo-saga", + }] + async fn saga_demo_create( + rqctx: RequestContext, + ) -> Result, HttpError>; + + /// Complete a waiting demo saga + /// + /// Note that the id used here is not the same as the id of the saga. It's + /// the one returned by the `saga_demo_create` API. + #[endpoint { + method = POST, + path = "/demo-saga/{demo_saga_id}/complete", + }] + async fn saga_demo_complete( + rqctx: RequestContext, + path_params: Path, + ) -> Result; + + // Debug interfaces for background Tasks + + /// List background tasks + /// + /// This is a list of discrete background activities that Nexus carries out. + /// This is exposed for support and debugging. + #[endpoint { + method = GET, + path = "/bgtasks", + }] + async fn bgtask_list( + rqctx: RequestContext, + ) -> Result>, HttpError>; + + /// Fetch status of one background task + /// + /// This is exposed for support and debugging. + #[endpoint { + method = GET, + path = "/bgtasks/view/{bgtask_name}", + }] + async fn bgtask_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError>; + + /// Activates one or more background tasks, causing them to be run immediately + /// if idle, or scheduled to run again as soon as possible if already running. + #[endpoint { + method = POST, + path = "/bgtasks/activate", + }] + async fn bgtask_activate( + rqctx: RequestContext, + body: TypedBody, + ) -> Result; + + // Debug interfaces for ongoing MGS updates + + /// Fetch information about ongoing MGS updates + #[endpoint { + method = GET, + path = "/mgs-updates", + }] + async fn mgs_updates( + rqctx: RequestContext, + ) -> Result, HttpError>; + + // APIs for managing blueprints + // + // These are not (yet) intended for use by any other programs. Eventually, we + // will want this functionality part of the public API. But we don't want to + // commit to any of this yet. These properly belong in an RFD 399-style + // "Service and Support API". Absent that, we stick them here. + + /// Lists blueprints + #[endpoint { + method = GET, + path = "/deployment/blueprints/all", + }] + async fn blueprint_list( + rqctx: RequestContext, + query_params: Query, + ) -> Result>, HttpError>; + + /// Fetches one blueprint + #[endpoint { + method = GET, + path = "/deployment/blueprints/all/{blueprint_id}", + }] + async fn blueprint_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError>; + + /// Deletes one blueprint + #[endpoint { + method = DELETE, + path = "/deployment/blueprints/all/{blueprint_id}", + }] + async fn blueprint_delete( + rqctx: RequestContext, + path_params: Path, + ) -> Result; + + // Managing the current target blueprint + + /// Fetches the current target blueprint, if any + #[endpoint { + method = GET, + path = "/deployment/blueprints/target", + }] + async fn blueprint_target_view( + rqctx: RequestContext, + ) -> Result, HttpError>; + + /// Make the specified blueprint the new target + #[endpoint { + method = POST, + path = "/deployment/blueprints/target", + }] + async fn blueprint_target_set( + rqctx: RequestContext, + target: TypedBody, + ) -> Result, HttpError>; + + /// Set the `enabled` field of the current target blueprint + #[endpoint { + method = PUT, + path = "/deployment/blueprints/target/enabled", + }] + async fn blueprint_target_set_enabled( + rqctx: RequestContext, + target: TypedBody, + ) -> Result, HttpError>; + + // Generating blueprints + + /// Generates a new blueprint for the current system, re-evaluating anything + /// that's changed since the last one was generated + #[endpoint { + method = POST, + path = "/deployment/blueprints/regenerate", + }] + async fn blueprint_regenerate( + rqctx: RequestContext, + ) -> Result, HttpError>; + + /// Imports a client-provided blueprint + /// + /// This is intended for development and support, not end users or operators. + #[endpoint { + method = POST, + path = "/deployment/blueprints/import", + }] + async fn blueprint_import( + rqctx: RequestContext, + blueprint: TypedBody, + ) -> Result; + + /// Get the current reconfigurator configuration + #[endpoint { + method = GET, + path = "/deployment/reconfigurator-config" + }] + async fn reconfigurator_config_show_current( + rqctx: RequestContext, + ) -> Result, HttpError>; + + /// Get the reconfigurator config at `version` if it exists + #[endpoint { + method = GET, + path = "/deployment/reconfigurator-config/{version}" + }] + async fn reconfigurator_config_show( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError>; + + /// Update the reconfigurator config at the latest versions + #[endpoint { + method = POST, + path = "/deployment/reconfigurator-config" + }] + async fn reconfigurator_config_set( + rqctx: RequestContext, + switches: TypedBody, + ) -> Result; + + /// Show deployed versions of artifacts + #[endpoint { + method = GET, + path = "/deployment/update-status" + }] + async fn update_status( + rqctx: RequestContext, + ) -> Result, HttpError>; + + /// List uninitialized sleds + #[endpoint { + method = GET, + path = "/sleds/uninitialized", + }] + async fn sled_list_uninitialized( + rqctx: RequestContext, + ) -> Result>, HttpError>; + + /// Add sled to initialized rack + // + // TODO: In the future this should really be a PUT request, once we resolve + // https://github.com/oxidecomputer/omicron/issues/4494. It should also + // explicitly be tied to a rack via a `rack_id` path param. For now we assume + // we are only operating on single rack systems. + #[endpoint { + method = POST, + path = "/sleds/add", + }] + async fn sled_add( + rqctx: RequestContext, + sled: TypedBody, + ) -> Result, HttpError>; + + /// Mark a sled as expunged + /// + /// This is an irreversible process! It should only be called after + /// sufficient warning to the operator. + /// + /// This is idempotent, and it returns the old policy of the sled. + #[endpoint { + method = POST, + path = "/sleds/expunge", + }] + async fn sled_expunge( + rqctx: RequestContext, + sled: TypedBody, + ) -> Result, HttpError>; + + /// Mark a physical disk as expunged + /// + /// This is an irreversible process! It should only be called after + /// sufficient warning to the operator. + /// + /// This is idempotent. + #[endpoint { + method = POST, + path = "/physical-disk/expunge", + }] + async fn physical_disk_expunge( + rqctx: RequestContext, + disk: TypedBody, + ) -> Result; + + // Support bundles (experimental) + + /// List all support bundles + #[endpoint { + method = GET, + path = "/experimental/v1/system/support-bundles", + }] + async fn support_bundle_list( + rqctx: RequestContext, + query_params: Query, + ) -> Result>, HttpError>; + + /// View a support bundle + #[endpoint { + method = GET, + path = "/experimental/v1/system/support-bundles/{bundle_id}", + }] + async fn support_bundle_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError>; + + /// Download the index of a support bundle + #[endpoint { + method = GET, + path = "/experimental/v1/system/support-bundles/{bundle_id}/index", + }] + async fn support_bundle_index( + rqctx: RequestContext, + headers: Header, + path_params: Path, + ) -> Result, HttpError>; + + /// Download the contents of a support bundle + #[endpoint { + method = GET, + path = "/experimental/v1/system/support-bundles/{bundle_id}/download", + }] + async fn support_bundle_download( + rqctx: RequestContext, + headers: Header, + path_params: Path, + ) -> Result, HttpError>; + + /// Download a file within a support bundle + #[endpoint { + method = GET, + path = "/experimental/v1/system/support-bundles/{bundle_id}/download/{file}", + }] + async fn support_bundle_download_file( + rqctx: RequestContext, + headers: Header, + path_params: Path, + ) -> Result, HttpError>; + + /// Download the metadata of a support bundle + #[endpoint { + method = HEAD, + path = "/experimental/v1/system/support-bundles/{bundle_id}/download", + }] + async fn support_bundle_head( + rqctx: RequestContext, + headers: Header, + path_params: Path, + ) -> Result, HttpError>; + + /// Download the metadata of a file within the support bundle + #[endpoint { + method = HEAD, + path = "/experimental/v1/system/support-bundles/{bundle_id}/download/{file}", + }] + async fn support_bundle_head_file( + rqctx: RequestContext, + headers: Header, + path_params: Path, + ) -> Result, HttpError>; + + /// Create a new support bundle + #[endpoint { + method = POST, + path = "/experimental/v1/system/support-bundles", + }] + async fn support_bundle_create( + rqctx: RequestContext, + body: TypedBody, + ) -> Result, HttpError>; + + /// Delete an existing support bundle + /// + /// May also be used to cancel a support bundle which is currently being + /// collected, or to remove metadata for a support bundle that has failed. + #[endpoint { + method = DELETE, + path = "/experimental/v1/system/support-bundles/{bundle_id}", + }] + async fn support_bundle_delete( + rqctx: RequestContext, + path_params: Path, + ) -> Result; + + /// Update a support bundle + #[endpoint { + method = PUT, + path = "/experimental/v1/system/support-bundles/{bundle_id}", + }] + async fn support_bundle_update( + rqctx: RequestContext, + path_params: Path, + body: TypedBody, + ) -> Result, HttpError>; + + /// Get the current clickhouse policy + #[endpoint { + method = GET, + path = "/clickhouse/policy" + }] + async fn clickhouse_policy_get( + rqctx: RequestContext, + ) -> Result, HttpError>; + + /// Set the new clickhouse policy + #[endpoint { + method = POST, + path = "/clickhouse/policy" + }] + async fn clickhouse_policy_set( + rqctx: RequestContext, + policy: TypedBody, + ) -> Result; + + /// Get the current oximeter read policy + #[endpoint { + method = GET, + path = "/oximeter/read-policy" + }] + async fn oximeter_read_policy_get( + rqctx: RequestContext, + ) -> Result, HttpError>; + + /// Set the new oximeter read policy + #[endpoint { + method = POST, + path = "/oximeter/read-policy" + }] + async fn oximeter_read_policy_set( + rqctx: RequestContext, + policy: TypedBody, + ) -> Result; + + /// Begin quiescing this Nexus instance + /// + /// This causes no new sagas to be started and eventually causes no database + /// connections to become available. This is a one-way trip. There's no + /// unquiescing Nexus. + #[endpoint { + method = POST, + path = "/quiesce" + }] + async fn quiesce_start( + rqctx: RequestContext, + ) -> Result; + + /// Check whether Nexus is running normally, quiescing, or fully quiesced. + #[endpoint { + method = GET, + path = "/quiesce" + }] + async fn quiesce_get( + rqctx: RequestContext, + ) -> Result, HttpError>; +} + +/// Path parameters for Instance requests (internal API) +#[derive(Deserialize, JsonSchema)] +pub struct InstancePathParam { + pub instance_id: InstanceUuid, +} + +/// Path parameters for Saga requests +#[derive(Deserialize, JsonSchema)] +pub struct SagaPathParam { + #[serde(rename = "saga_id")] + pub saga_id: Uuid, +} + +/// Path parameters for DemoSaga requests +#[derive(Deserialize, JsonSchema)] +pub struct DemoSagaPathParam { + pub demo_saga_id: DemoSagaUuid, +} + +/// Path parameters for Background Task requests +#[derive(Deserialize, JsonSchema)] +pub struct BackgroundTaskPathParam { + pub bgtask_name: String, +} + +/// Query parameters for Background Task activation requests. +#[derive(Deserialize, JsonSchema)] +pub struct BackgroundTasksActivateRequest { + pub bgtask_names: BTreeSet, +} + +#[derive(Clone, Debug, Serialize, JsonSchema)] +pub struct SledId { + pub id: SledUuid, +} + +#[derive(Deserialize, JsonSchema)] +pub struct VersionPathParam { + pub version: u32, } diff --git a/nexus/reconfigurator/cli-integration-tests/Cargo.toml b/nexus/reconfigurator/cli-integration-tests/Cargo.toml index 3ee7bfba036..30c824a22f5 100644 --- a/nexus/reconfigurator/cli-integration-tests/Cargo.toml +++ b/nexus/reconfigurator/cli-integration-tests/Cargo.toml @@ -20,13 +20,13 @@ pq-sys = "*" reconfigurator-cli.workspace = true [dev-dependencies] -camino-tempfile.workspace = true camino.workspace = true -nexus-client.workspace = true +camino-tempfile.workspace = true nexus-db-queries.workspace = true +nexus-lockstep-client.workspace = true nexus-reconfigurator-preparation.workspace = true -nexus-test-utils-macros.workspace = true nexus-test-utils.workspace = true +nexus-test-utils-macros.workspace = true nexus-types.workspace = true omicron-common.workspace = true omicron-nexus.workspace = true diff --git a/nexus/reconfigurator/cli-integration-tests/tests/integration/blueprint_edit.rs b/nexus/reconfigurator/cli-integration-tests/tests/integration/blueprint_edit.rs index ae0b2c7e311..0ac5b596670 100644 --- a/nexus/reconfigurator/cli-integration-tests/tests/integration/blueprint_edit.rs +++ b/nexus/reconfigurator/cli-integration-tests/tests/integration/blueprint_edit.rs @@ -194,10 +194,10 @@ async fn test_blueprint_edit(cptestctx: &ControlPlaneTestContext) { assert_eq!(new_blueprint, new_blueprint2); // Import the new blueprint. - let nexus_internal_url = - format!("http://{}/", cptestctx.internal_client.bind_address); + let nexus_lockstep_url = + format!("http://{}/", cptestctx.lockstep_client.bind_address); let nexus_client = - nexus_client::Client::new(&nexus_internal_url, log.clone()); + nexus_lockstep_client::Client::new(&nexus_lockstep_url, log.clone()); nexus_client .blueprint_import(&new_blueprint) .await @@ -212,10 +212,12 @@ async fn test_blueprint_edit(cptestctx: &ControlPlaneTestContext) { // Set the blueprint as the (disabled) target. nexus_client - .blueprint_target_set(&nexus_client::types::BlueprintTargetSet { - target_id: new_blueprint.id, - enabled: false, - }) + .blueprint_target_set( + &nexus_lockstep_client::types::BlueprintTargetSet { + target_id: new_blueprint.id, + enabled: false, + }, + ) .await .context("setting target blueprint") .unwrap(); diff --git a/nexus/src/app/quiesce.rs b/nexus/src/app/quiesce.rs index b5c97354f1c..853425e3909 100644 --- a/nexus/src/app/quiesce.rs +++ b/nexus/src/app/quiesce.rs @@ -517,9 +517,9 @@ mod test { use diesel::ExpressionMethods; use diesel::QueryDsl; use http::StatusCode; - use nexus_client::types::QuiesceState; - use nexus_client::types::QuiesceStatus; use nexus_db_model::DbMetadataNexusState; + use nexus_lockstep_client::types::QuiesceState; + use nexus_lockstep_client::types::QuiesceStatus; use nexus_test_interface::NexusServer; use nexus_test_utils::db::TestDatabase; use nexus_test_utils_macros::nexus_test; @@ -540,11 +540,12 @@ mod test { type ControlPlaneTestContext = nexus_test_utils::ControlPlaneTestContext; - type NexusClientError = nexus_client::Error; + type NexusClientError = + nexus_lockstep_client::Error; async fn wait_quiesce( log: &Logger, - client: &nexus_client::Client, + client: &nexus_lockstep_client::Client, timeout: Duration, ) -> QuiesceStatus { wait_for_condition( @@ -558,9 +559,7 @@ mod test { if matches!(rv.state, QuiesceState::Quiesced { .. }) { Ok(rv) } else { - Err(CondCheckError::< - nexus_client::Error, - >::NotYet) + Err(CondCheckError::::NotYet) } }, &Duration::from_millis(50), @@ -628,12 +627,14 @@ mod test { #[nexus_test(server = crate::Server)] async fn test_quiesce_easy(cptestctx: &ControlPlaneTestContext) { let log = &cptestctx.logctx.log; - let nexus_internal_url = format!( + let nexus_lockstep_url = format!( "http://{}", - cptestctx.server.get_http_server_internal_address().await + cptestctx.server.get_http_server_lockstep_address().await + ); + let nexus_client = nexus_lockstep_client::Client::new( + &nexus_lockstep_url, + log.clone(), ); - let nexus_client = - nexus_client::Client::new(&nexus_internal_url, log.clone()); // We need to enable blueprint execution in order to complete a saga // assignment pass, which is required for quiescing to work. @@ -657,12 +658,14 @@ mod test { #[nexus_test(server = crate::Server)] async fn test_quiesce_full(cptestctx: &ControlPlaneTestContext) { let log = &cptestctx.logctx.log; - let nexus_internal_url = format!( + let nexus_lockstep_url = format!( "http://{}", - cptestctx.server.get_http_server_internal_address().await + cptestctx.server.get_http_server_lockstep_address().await + ); + let nexus_client = nexus_lockstep_client::Client::new( + &nexus_lockstep_url, + log.clone(), ); - let nexus_client = - nexus_client::Client::new(&nexus_internal_url, log.clone()); // We need to enable blueprint execution in order to complete a saga // assignment pass, which is required for quiescing to work. diff --git a/nexus/src/internal_api/http_entrypoints.rs b/nexus/src/internal_api/http_entrypoints.rs index f44c20e0f70..89eb026ffef 100644 --- a/nexus/src/internal_api/http_entrypoints.rs +++ b/nexus/src/internal_api/http_entrypoints.rs @@ -5,15 +5,10 @@ //! Handler functions (entrypoints) for HTTP APIs internal to the control plane use super::params::{OximeterInfo, RackInitializationRequest}; -use crate::app::support_bundles::SupportBundleQueryType; use crate::context::ApiContext; -use crate::external_api::shared; use dropshot::ApiDescription; -use dropshot::Body; -use dropshot::Header; use dropshot::HttpError; use dropshot::HttpResponseCreated; -use dropshot::HttpResponseDeleted; use dropshot::HttpResponseOk; use dropshot::HttpResponseUpdatedNoContent; use dropshot::Path; @@ -21,43 +16,14 @@ use dropshot::Query; use dropshot::RequestContext; use dropshot::ResultsPage; use dropshot::TypedBody; -use http::Response; use nexus_internal_api::*; -use nexus_types::deployment::Blueprint; -use nexus_types::deployment::BlueprintMetadata; -use nexus_types::deployment::BlueprintTarget; -use nexus_types::deployment::BlueprintTargetSet; -use nexus_types::deployment::ClickhousePolicy; -use nexus_types::deployment::OximeterReadPolicy; -use nexus_types::deployment::ReconfiguratorConfigParam; -use nexus_types::deployment::ReconfiguratorConfigView; -use nexus_types::external_api::headers::RangeRequest; -use nexus_types::external_api::params::PhysicalDiskPath; -use nexus_types::external_api::params::SledSelector; -use nexus_types::external_api::params::SupportBundleFilePath; -use nexus_types::external_api::params::SupportBundlePath; -use nexus_types::external_api::params::SupportBundleUpdate; -use nexus_types::external_api::params::UninitializedSledId; use nexus_types::external_api::shared::ProbeInfo; -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::SledAgentInfo; use nexus_types::internal_api::params::SwitchPutRequest; use nexus_types::internal_api::params::SwitchPutResponse; -use nexus_types::internal_api::views::BackgroundTask; -use nexus_types::internal_api::views::DemoSaga; -use nexus_types::internal_api::views::MgsUpdateDriverStatus; use nexus_types::internal_api::views::NatEntryView; -use nexus_types::internal_api::views::QuiesceStatus; -use nexus_types::internal_api::views::Saga; -use nexus_types::internal_api::views::UpdateStatus; -use nexus_types::internal_api::views::to_list; -use omicron_common::api::external::Instance; use omicron_common::api::external::http_pagination::PaginatedById; -use omicron_common::api::external::http_pagination::PaginatedByTimeAndId; use omicron_common::api::external::http_pagination::ScanById; -use omicron_common::api::external::http_pagination::ScanByTimeAndId; use omicron_common::api::external::http_pagination::ScanParams; use omicron_common::api::external::http_pagination::data_page_params_for; use omicron_common::api::internal::nexus::DiskRuntimeState; @@ -69,9 +35,6 @@ use omicron_common::api::internal::nexus::RepairFinishInfo; use omicron_common::api::internal::nexus::RepairProgress; use omicron_common::api::internal::nexus::RepairStartInfo; use omicron_common::api::internal::nexus::SledVmmState; -use omicron_uuid_kinds::*; -use range_requests::PotentialRange; -use std::collections::BTreeMap; type NexusApiDescription = ApiDescription; @@ -210,29 +173,6 @@ impl NexusInternalApi for NexusInternalApiImpl { .await } - async fn instance_migrate( - rqctx: RequestContext, - path_params: Path, - migrate_params: TypedBody, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let migrate = migrate_params.into_inner(); - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let instance = nexus - .instance_migrate(&opctx, path.instance_id, migrate) - .await?; - Ok(HttpResponseOk(instance.into())) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - async fn cpapi_disks_put( rqctx: RequestContext, path_params: Path, @@ -512,125 +452,6 @@ impl NexusInternalApi for NexusInternalApiImpl { .await } - // Debug interfaces for Sagas - - async fn saga_list( - rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let nexus = &apictx.nexus; - let query = query_params.into_inner(); - let pagparams = data_page_params_for(&rqctx, &query)?; - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let saga_stream = nexus.sagas_list(&opctx, &pagparams).await?; - let view_list = to_list(saga_stream).await; - Ok(HttpResponseOk(ScanById::results_page( - &query, - view_list, - &|_, saga: &Saga| saga.id, - )?)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn saga_view( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let saga = nexus.saga_get(&opctx, path.saga_id).await?; - Ok(HttpResponseOk(saga)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn saga_demo_create( - rqctx: RequestContext, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let nexus = &apictx.nexus; - let demo_saga = nexus.saga_demo_create().await?; - Ok(HttpResponseOk(demo_saga)) - }; - - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn saga_demo_complete( - rqctx: RequestContext, - path_params: Path, - ) -> Result { - let apictx = &rqctx.context().context; - let handler = async { - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - nexus.saga_demo_complete(path.demo_saga_id)?; - Ok(HttpResponseUpdatedNoContent()) - }; - - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - // Debug interfaces for Background Tasks - - async fn bgtask_list( - rqctx: RequestContext, - ) -> Result>, HttpError> - { - let apictx = &rqctx.context().context; - let handler = async { - let nexus = &apictx.nexus; - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let bgtask_list = nexus.bgtasks_list(&opctx).await?; - Ok(HttpResponseOk(bgtask_list)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn bgtask_view( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let bgtask = nexus.bgtask_status(&opctx, &path.bgtask_name).await?; - Ok(HttpResponseOk(bgtask)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - async fn bgtask_activate( rqctx: RequestContext, body: TypedBody, @@ -650,24 +471,6 @@ impl NexusInternalApi for NexusInternalApiImpl { .await } - // Debug interfaces for MGS updates - - async fn mgs_updates( - rqctx: RequestContext, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let nexus = &apictx.nexus; - Ok(HttpResponseOk(nexus.mgs_updates(&opctx).await?)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - // NAT RPW internal APIs async fn ipv4_nat_changeset( @@ -695,660 +498,11 @@ impl NexusInternalApi for NexusInternalApiImpl { .await } - // APIs for managing blueprints - async fn blueprint_list( + async fn probes_get( rqctx: RequestContext, + path_params: Path, query_params: Query, - ) -> Result>, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let nexus = &apictx.nexus; - let query = query_params.into_inner(); - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let pagparams = data_page_params_for(&rqctx, &query)?; - let blueprints = nexus.blueprint_list(&opctx, &pagparams).await?; - Ok(HttpResponseOk(ScanById::results_page( - &query, - blueprints, - &|_, blueprint: &BlueprintMetadata| { - blueprint.id.into_untyped_uuid() - }, - )?)) - }; - - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - /// Fetches one blueprint - async fn blueprint_view( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let blueprint = - nexus.blueprint_view(&opctx, path.blueprint_id).await?; - Ok(HttpResponseOk(blueprint)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - /// Deletes one blueprint - async fn blueprint_delete( - rqctx: RequestContext, - path_params: Path, - ) -> Result { - let apictx = &rqctx.context().context; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - nexus.blueprint_delete(&opctx, path.blueprint_id).await?; - Ok(HttpResponseDeleted()) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn blueprint_target_view( - rqctx: RequestContext, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let nexus = &apictx.nexus; - let target = nexus.blueprint_target_view(&opctx).await?; - Ok(HttpResponseOk(target)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn blueprint_target_set( - rqctx: RequestContext, - target: TypedBody, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let nexus = &apictx.nexus; - let target = target.into_inner(); - let target = nexus.blueprint_target_set(&opctx, target).await?; - Ok(HttpResponseOk(target)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn blueprint_target_set_enabled( - rqctx: RequestContext, - target: TypedBody, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let nexus = &apictx.nexus; - let target = target.into_inner(); - let target = - nexus.blueprint_target_set_enabled(&opctx, target).await?; - Ok(HttpResponseOk(target)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn blueprint_regenerate( - rqctx: RequestContext, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let nexus = &apictx.nexus; - let result = nexus.blueprint_create_regenerate(&opctx).await?; - Ok(HttpResponseOk(result)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn blueprint_import( - rqctx: RequestContext, - blueprint: TypedBody, - ) -> Result { - let apictx = &rqctx.context().context; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let nexus = &apictx.nexus; - let blueprint = blueprint.into_inner(); - nexus.blueprint_import(&opctx, blueprint).await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn reconfigurator_config_show_current( - rqctx: RequestContext, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let datastore = &apictx.nexus.datastore(); - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - match datastore.reconfigurator_config_get_latest(&opctx).await? { - Some(switches) => Ok(HttpResponseOk(switches)), - None => Err(HttpError::for_not_found( - None, - "No config in database".into(), - )), - } - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn reconfigurator_config_show( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let datastore = &apictx.nexus.datastore(); - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let version = path_params.into_inner().version; - match datastore.reconfigurator_config_get(&opctx, version).await? { - Some(switches) => Ok(HttpResponseOk(switches)), - None => Err(HttpError::for_not_found( - None, - format!("No config in database at version {version}"), - )), - } - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn reconfigurator_config_set( - rqctx: RequestContext, - switches: TypedBody, - ) -> Result { - let apictx = &rqctx.context().context; - let handler = async { - let datastore = &apictx.nexus.datastore(); - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - - datastore - .reconfigurator_config_insert_latest_version( - &opctx, - switches.into_inner(), - ) - .await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn update_status( - rqctx: RequestContext, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let nexus = &apictx.nexus; - let result = nexus.update_status(&opctx).await?; - Ok(HttpResponseOk(result)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn sled_list_uninitialized( - rqctx: RequestContext, - ) -> Result>, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let nexus = &apictx.nexus; - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let sleds = nexus.sled_list_uninitialized(&opctx).await?; - Ok(HttpResponseOk(ResultsPage { items: sleds, next_page: None })) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn sled_add( - rqctx: RequestContext, - sled: TypedBody, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let nexus = &apictx.nexus; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let id = nexus.sled_add(&opctx, sled.into_inner()).await?; - Ok(HttpResponseCreated(SledId { id })) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn sled_expunge( - rqctx: RequestContext, - sled: TypedBody, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let nexus = &apictx.nexus; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let previous_policy = - nexus.sled_expunge(&opctx, sled.into_inner().sled).await?; - Ok(HttpResponseOk(previous_policy)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn physical_disk_expunge( - rqctx: RequestContext, - disk: TypedBody, - ) -> Result { - let apictx = &rqctx.context().context; - let nexus = &apictx.nexus; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - nexus.physical_disk_expunge(&opctx, disk.into_inner()).await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn support_bundle_list( - rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError> - { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - - let query = query_params.into_inner(); - let pagparams = data_page_params_for(&rqctx, &query)?; - - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - - let bundles = nexus - .support_bundle_list(&opctx, &pagparams) - .await? - .into_iter() - .map(|p| p.into()) - .collect(); - - Ok(HttpResponseOk(ScanByTimeAndId::results_page( - &query, - bundles, - &|_, bundle: &shared::SupportBundleInfo| { - (bundle.time_created, bundle.id.into_untyped_uuid()) - }, - )?)) - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn support_bundle_view( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let path = path_params.into_inner(); - - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - - let bundle = nexus - .support_bundle_view( - &opctx, - SupportBundleUuid::from_untyped_uuid(path.bundle_id), - ) - .await?; - - Ok(HttpResponseOk(bundle.into())) - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn support_bundle_index( - rqctx: RequestContext, - headers: Header, - path_params: Path, - ) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let path = path_params.into_inner(); - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - - let head = false; - let range = headers - .into_inner() - .range - .map(|r| PotentialRange::new(r.as_bytes())); - - let body = nexus - .support_bundle_download( - &opctx, - SupportBundleUuid::from_untyped_uuid(path.bundle_id), - SupportBundleQueryType::Index, - head, - range, - ) - .await?; - Ok(body) - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn support_bundle_download( - rqctx: RequestContext, - headers: Header, - path_params: Path, - ) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let path = path_params.into_inner(); - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - - let head = false; - let range = headers - .into_inner() - .range - .map(|r| PotentialRange::new(r.as_bytes())); - - let body = nexus - .support_bundle_download( - &opctx, - SupportBundleUuid::from_untyped_uuid(path.bundle_id), - SupportBundleQueryType::Whole, - head, - range, - ) - .await?; - Ok(body) - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn support_bundle_download_file( - rqctx: RequestContext, - headers: Header, - path_params: Path, - ) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let path = path_params.into_inner(); - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let head = false; - let range = headers - .into_inner() - .range - .map(|r| PotentialRange::new(r.as_bytes())); - - let body = nexus - .support_bundle_download( - &opctx, - SupportBundleUuid::from_untyped_uuid(path.bundle.bundle_id), - SupportBundleQueryType::Path { file_path: path.file }, - head, - range, - ) - .await?; - Ok(body) - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn support_bundle_head( - rqctx: RequestContext, - headers: Header, - path_params: Path, - ) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let path = path_params.into_inner(); - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let head = true; - let range = headers - .into_inner() - .range - .map(|r| PotentialRange::new(r.as_bytes())); - - let body = nexus - .support_bundle_download( - &opctx, - SupportBundleUuid::from_untyped_uuid(path.bundle_id), - SupportBundleQueryType::Whole, - head, - range, - ) - .await?; - Ok(body) - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn support_bundle_head_file( - rqctx: RequestContext, - headers: Header, - path_params: Path, - ) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let path = path_params.into_inner(); - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let head = true; - let range = headers - .into_inner() - .range - .map(|r| PotentialRange::new(r.as_bytes())); - - let body = nexus - .support_bundle_download( - &opctx, - SupportBundleUuid::from_untyped_uuid(path.bundle.bundle_id), - SupportBundleQueryType::Path { file_path: path.file }, - head, - range, - ) - .await?; - Ok(body) - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn support_bundle_create( - rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let create_params = body.into_inner(); - - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - - let bundle = nexus - .support_bundle_create( - &opctx, - "Created by internal API", - create_params.user_comment, - ) - .await?; - Ok(HttpResponseCreated(bundle.into())) - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn support_bundle_delete( - rqctx: RequestContext, - path_params: Path, - ) -> Result { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let path = path_params.into_inner(); - - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - - nexus - .support_bundle_delete( - &opctx, - SupportBundleUuid::from_untyped_uuid(path.bundle_id), - ) - .await?; - - Ok(HttpResponseDeleted()) - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn support_bundle_update( - rqctx: RequestContext, - path_params: Path, - body: TypedBody, - ) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let path = path_params.into_inner(); - let update = body.into_inner(); - - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - - let bundle = nexus - .support_bundle_update_user_comment( - &opctx, - SupportBundleUuid::from_untyped_uuid(path.bundle_id), - update.user_comment, - ) - .await?; - - Ok(HttpResponseOk(bundle.into())) - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn probes_get( - rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> { let apictx = &rqctx.context().context; let handler = async { let query = query_params.into_inner(); @@ -1368,128 +522,4 @@ impl NexusInternalApi for NexusInternalApiImpl { .instrument_dropshot_handler(&rqctx, handler) .await } - - async fn clickhouse_policy_get( - rqctx: RequestContext, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let nexus = &apictx.nexus; - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - match nexus.datastore().clickhouse_policy_get_latest(&opctx).await? - { - Some(policy) => Ok(HttpResponseOk(policy)), - None => Err(HttpError::for_not_found( - None, - "No clickhouse policy in database".into(), - )), - } - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn clickhouse_policy_set( - rqctx: RequestContext, - policy: TypedBody, - ) -> Result { - let apictx = &rqctx.context().context; - let nexus = &apictx.nexus; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - nexus - .datastore() - .clickhouse_policy_insert_latest_version( - &opctx, - &policy.into_inner(), - ) - .await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn oximeter_read_policy_get( - rqctx: RequestContext, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let handler = async { - let nexus = &apictx.nexus; - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - let policy = nexus - .datastore() - .oximeter_read_policy_get_latest(&opctx) - .await?; - Ok(HttpResponseOk(policy)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn oximeter_read_policy_set( - rqctx: RequestContext, - policy: TypedBody, - ) -> Result { - let apictx = &rqctx.context().context; - let nexus = &apictx.nexus; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - nexus - .datastore() - .oximeter_read_policy_insert_latest_version( - &opctx, - &policy.into_inner(), - ) - .await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn quiesce_start( - rqctx: RequestContext, - ) -> Result { - let apictx = &rqctx.context().context; - let nexus = &apictx.nexus; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - nexus.quiesce_start(&opctx).await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - - async fn quiesce_get( - rqctx: RequestContext, - ) -> Result, HttpError> { - let apictx = &rqctx.context().context; - let nexus = &apictx.nexus; - let handler = async { - let opctx = - crate::context::op_context_for_internal_api(&rqctx).await; - Ok(HttpResponseOk(nexus.quiesce_state(&opctx).await?)) - }; - apictx - .internal_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } } diff --git a/nexus/src/lockstep_api/http_entrypoints.rs b/nexus/src/lockstep_api/http_entrypoints.rs index 5d771aa5912..463b0883deb 100644 --- a/nexus/src/lockstep_api/http_entrypoints.rs +++ b/nexus/src/lockstep_api/http_entrypoints.rs @@ -5,9 +5,61 @@ //! Handler functions (entrypoints) for HTTP APIs internal to the control plane //! whose callers are updated in lockstep with Nexus -use crate::context::ApiContext; +use std::collections::BTreeMap; + use dropshot::ApiDescription; +use dropshot::Body; +use dropshot::Header; +use dropshot::HttpError; +use dropshot::HttpResponseCreated; +use dropshot::HttpResponseDeleted; +use dropshot::HttpResponseOk; +use dropshot::HttpResponseUpdatedNoContent; +use dropshot::Path; +use dropshot::Query; +use dropshot::RequestContext; +use dropshot::ResultsPage; +use dropshot::TypedBody; +use http::Response; use nexus_lockstep_api::*; +use nexus_types::deployment::Blueprint; +use nexus_types::deployment::BlueprintMetadata; +use nexus_types::deployment::BlueprintTarget; +use nexus_types::deployment::BlueprintTargetSet; +use nexus_types::deployment::ClickhousePolicy; +use nexus_types::deployment::OximeterReadPolicy; +use nexus_types::deployment::ReconfiguratorConfigParam; +use nexus_types::deployment::ReconfiguratorConfigView; +use nexus_types::external_api::headers::RangeRequest; +use nexus_types::external_api::params::PhysicalDiskPath; +use nexus_types::external_api::params::SledSelector; +use nexus_types::external_api::params::SupportBundleFilePath; +use nexus_types::external_api::params::SupportBundlePath; +use nexus_types::external_api::params::SupportBundleUpdate; +use nexus_types::external_api::params::UninitializedSledId; +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::views::BackgroundTask; +use nexus_types::internal_api::views::DemoSaga; +use nexus_types::internal_api::views::MgsUpdateDriverStatus; +use nexus_types::internal_api::views::QuiesceStatus; +use nexus_types::internal_api::views::Saga; +use nexus_types::internal_api::views::UpdateStatus; +use nexus_types::internal_api::views::to_list; +use omicron_common::api::external::Instance; +use omicron_common::api::external::http_pagination::PaginatedById; +use omicron_common::api::external::http_pagination::PaginatedByTimeAndId; +use omicron_common::api::external::http_pagination::ScanById; +use omicron_common::api::external::http_pagination::ScanByTimeAndId; +use omicron_common::api::external::http_pagination::ScanParams; +use omicron_common::api::external::http_pagination::data_page_params_for; +use omicron_uuid_kinds::*; +use range_requests::PotentialRange; + +use crate::app::support_bundles::SupportBundleQueryType; +use crate::context::ApiContext; type NexusApiDescription = ApiDescription; @@ -21,4 +73,956 @@ enum NexusLockstepApiImpl {} impl NexusLockstepApi for NexusLockstepApiImpl { type Context = ApiContext; + + async fn instance_migrate( + rqctx: RequestContext, + path_params: Path, + migrate_params: TypedBody, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let migrate = migrate_params.into_inner(); + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let instance = nexus + .instance_migrate(&opctx, path.instance_id, migrate) + .await?; + Ok(HttpResponseOk(instance.into())) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + // Debug interfaces for Sagas + + async fn saga_list( + rqctx: RequestContext, + query_params: Query, + ) -> Result>, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let pagparams = data_page_params_for(&rqctx, &query)?; + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let saga_stream = nexus.sagas_list(&opctx, &pagparams).await?; + let view_list = to_list(saga_stream).await; + Ok(HttpResponseOk(ScanById::results_page( + &query, + view_list, + &|_, saga: &Saga| saga.id, + )?)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn saga_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let saga = nexus.saga_get(&opctx, path.saga_id).await?; + Ok(HttpResponseOk(saga)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn saga_demo_create( + rqctx: RequestContext, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let nexus = &apictx.nexus; + let demo_saga = nexus.saga_demo_create().await?; + Ok(HttpResponseOk(demo_saga)) + }; + + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn saga_demo_complete( + rqctx: RequestContext, + path_params: Path, + ) -> Result { + let apictx = &rqctx.context().context; + let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + nexus.saga_demo_complete(path.demo_saga_id)?; + Ok(HttpResponseUpdatedNoContent()) + }; + + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + // Debug interfaces for Background Tasks + + async fn bgtask_list( + rqctx: RequestContext, + ) -> Result>, HttpError> + { + let apictx = &rqctx.context().context; + let handler = async { + let nexus = &apictx.nexus; + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let bgtask_list = nexus.bgtasks_list(&opctx).await?; + Ok(HttpResponseOk(bgtask_list)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn bgtask_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let bgtask = nexus.bgtask_status(&opctx, &path.bgtask_name).await?; + Ok(HttpResponseOk(bgtask)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn bgtask_activate( + rqctx: RequestContext, + body: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + let body = body.into_inner(); + nexus.bgtask_activate(&opctx, body.bgtask_names).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + // Debug interfaces for MGS updates + + async fn mgs_updates( + rqctx: RequestContext, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + Ok(HttpResponseOk(nexus.mgs_updates(&opctx).await?)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + // APIs for managing blueprints + async fn blueprint_list( + rqctx: RequestContext, + query_params: Query, + ) -> Result>, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let pagparams = data_page_params_for(&rqctx, &query)?; + let blueprints = nexus.blueprint_list(&opctx, &pagparams).await?; + Ok(HttpResponseOk(ScanById::results_page( + &query, + blueprints, + &|_, blueprint: &BlueprintMetadata| { + blueprint.id.into_untyped_uuid() + }, + )?)) + }; + + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + /// Fetches one blueprint + async fn blueprint_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let blueprint = + nexus.blueprint_view(&opctx, path.blueprint_id).await?; + Ok(HttpResponseOk(blueprint)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + /// Deletes one blueprint + async fn blueprint_delete( + rqctx: RequestContext, + path_params: Path, + ) -> Result { + let apictx = &rqctx.context().context; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + nexus.blueprint_delete(&opctx, path.blueprint_id).await?; + Ok(HttpResponseDeleted()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn blueprint_target_view( + rqctx: RequestContext, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + let target = nexus.blueprint_target_view(&opctx).await?; + Ok(HttpResponseOk(target)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn blueprint_target_set( + rqctx: RequestContext, + target: TypedBody, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + let target = target.into_inner(); + let target = nexus.blueprint_target_set(&opctx, target).await?; + Ok(HttpResponseOk(target)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn blueprint_target_set_enabled( + rqctx: RequestContext, + target: TypedBody, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + let target = target.into_inner(); + let target = + nexus.blueprint_target_set_enabled(&opctx, target).await?; + Ok(HttpResponseOk(target)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn blueprint_regenerate( + rqctx: RequestContext, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + let result = nexus.blueprint_create_regenerate(&opctx).await?; + Ok(HttpResponseOk(result)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn blueprint_import( + rqctx: RequestContext, + blueprint: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + let blueprint = blueprint.into_inner(); + nexus.blueprint_import(&opctx, blueprint).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn reconfigurator_config_show_current( + rqctx: RequestContext, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let datastore = &apictx.nexus.datastore(); + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + match datastore.reconfigurator_config_get_latest(&opctx).await? { + Some(switches) => Ok(HttpResponseOk(switches)), + None => Err(HttpError::for_not_found( + None, + "No config in database".into(), + )), + } + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn reconfigurator_config_show( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let datastore = &apictx.nexus.datastore(); + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let version = path_params.into_inner().version; + match datastore.reconfigurator_config_get(&opctx, version).await? { + Some(switches) => Ok(HttpResponseOk(switches)), + None => Err(HttpError::for_not_found( + None, + format!("No config in database at version {version}"), + )), + } + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn reconfigurator_config_set( + rqctx: RequestContext, + switches: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let handler = async { + let datastore = &apictx.nexus.datastore(); + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + + datastore + .reconfigurator_config_insert_latest_version( + &opctx, + switches.into_inner(), + ) + .await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn update_status( + rqctx: RequestContext, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let nexus = &apictx.nexus; + let result = nexus.update_status(&opctx).await?; + Ok(HttpResponseOk(result)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn sled_list_uninitialized( + rqctx: RequestContext, + ) -> Result>, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let nexus = &apictx.nexus; + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let sleds = nexus.sled_list_uninitialized(&opctx).await?; + Ok(HttpResponseOk(ResultsPage { items: sleds, next_page: None })) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn sled_add( + rqctx: RequestContext, + sled: TypedBody, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let id = nexus.sled_add(&opctx, sled.into_inner()).await?; + Ok(HttpResponseCreated(SledId { id })) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn sled_expunge( + rqctx: RequestContext, + sled: TypedBody, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let previous_policy = + nexus.sled_expunge(&opctx, sled.into_inner().sled).await?; + Ok(HttpResponseOk(previous_policy)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn physical_disk_expunge( + rqctx: RequestContext, + disk: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus.physical_disk_expunge(&opctx, disk.into_inner()).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn support_bundle_list( + rqctx: RequestContext, + query_params: Query, + ) -> Result>, HttpError> + { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + + let query = query_params.into_inner(); + let pagparams = data_page_params_for(&rqctx, &query)?; + + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + + let bundles = nexus + .support_bundle_list(&opctx, &pagparams) + .await? + .into_iter() + .map(|p| p.into()) + .collect(); + + Ok(HttpResponseOk(ScanByTimeAndId::results_page( + &query, + bundles, + &|_, bundle: &shared::SupportBundleInfo| { + (bundle.time_created, bundle.id.into_untyped_uuid()) + }, + )?)) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn support_bundle_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let path = path_params.into_inner(); + + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + + let bundle = nexus + .support_bundle_view( + &opctx, + SupportBundleUuid::from_untyped_uuid(path.bundle_id), + ) + .await?; + + Ok(HttpResponseOk(bundle.into())) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn support_bundle_index( + rqctx: RequestContext, + headers: Header, + path_params: Path, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let path = path_params.into_inner(); + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + + let head = false; + let range = headers + .into_inner() + .range + .map(|r| PotentialRange::new(r.as_bytes())); + + let body = nexus + .support_bundle_download( + &opctx, + SupportBundleUuid::from_untyped_uuid(path.bundle_id), + SupportBundleQueryType::Index, + head, + range, + ) + .await?; + Ok(body) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn support_bundle_download( + rqctx: RequestContext, + headers: Header, + path_params: Path, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let path = path_params.into_inner(); + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + + let head = false; + let range = headers + .into_inner() + .range + .map(|r| PotentialRange::new(r.as_bytes())); + + let body = nexus + .support_bundle_download( + &opctx, + SupportBundleUuid::from_untyped_uuid(path.bundle_id), + SupportBundleQueryType::Whole, + head, + range, + ) + .await?; + Ok(body) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn support_bundle_download_file( + rqctx: RequestContext, + headers: Header, + path_params: Path, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let path = path_params.into_inner(); + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let head = false; + let range = headers + .into_inner() + .range + .map(|r| PotentialRange::new(r.as_bytes())); + + let body = nexus + .support_bundle_download( + &opctx, + SupportBundleUuid::from_untyped_uuid(path.bundle.bundle_id), + SupportBundleQueryType::Path { file_path: path.file }, + head, + range, + ) + .await?; + Ok(body) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn support_bundle_head( + rqctx: RequestContext, + headers: Header, + path_params: Path, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let path = path_params.into_inner(); + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let head = true; + let range = headers + .into_inner() + .range + .map(|r| PotentialRange::new(r.as_bytes())); + + let body = nexus + .support_bundle_download( + &opctx, + SupportBundleUuid::from_untyped_uuid(path.bundle_id), + SupportBundleQueryType::Whole, + head, + range, + ) + .await?; + Ok(body) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn support_bundle_head_file( + rqctx: RequestContext, + headers: Header, + path_params: Path, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let path = path_params.into_inner(); + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let head = true; + let range = headers + .into_inner() + .range + .map(|r| PotentialRange::new(r.as_bytes())); + + let body = nexus + .support_bundle_download( + &opctx, + SupportBundleUuid::from_untyped_uuid(path.bundle.bundle_id), + SupportBundleQueryType::Path { file_path: path.file }, + head, + range, + ) + .await?; + Ok(body) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn support_bundle_create( + rqctx: RequestContext, + body: TypedBody, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let create_params = body.into_inner(); + + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + + let bundle = nexus + .support_bundle_create( + &opctx, + "Created by internal API", + create_params.user_comment, + ) + .await?; + Ok(HttpResponseCreated(bundle.into())) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn support_bundle_delete( + rqctx: RequestContext, + path_params: Path, + ) -> Result { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let path = path_params.into_inner(); + + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + + nexus + .support_bundle_delete( + &opctx, + SupportBundleUuid::from_untyped_uuid(path.bundle_id), + ) + .await?; + + Ok(HttpResponseDeleted()) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn support_bundle_update( + rqctx: RequestContext, + path_params: Path, + body: TypedBody, + ) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.context.nexus; + let path = path_params.into_inner(); + let update = body.into_inner(); + + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + + let bundle = nexus + .support_bundle_update_user_comment( + &opctx, + SupportBundleUuid::from_untyped_uuid(path.bundle_id), + update.user_comment, + ) + .await?; + + Ok(HttpResponseOk(bundle.into())) + }; + apictx + .context + .external_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn clickhouse_policy_get( + rqctx: RequestContext, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let nexus = &apictx.nexus; + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + match nexus.datastore().clickhouse_policy_get_latest(&opctx).await? + { + Some(policy) => Ok(HttpResponseOk(policy)), + None => Err(HttpError::for_not_found( + None, + "No clickhouse policy in database".into(), + )), + } + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn clickhouse_policy_set( + rqctx: RequestContext, + policy: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus + .datastore() + .clickhouse_policy_insert_latest_version( + &opctx, + &policy.into_inner(), + ) + .await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn oximeter_read_policy_get( + rqctx: RequestContext, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let handler = async { + let nexus = &apictx.nexus; + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let policy = nexus + .datastore() + .oximeter_read_policy_get_latest(&opctx) + .await?; + Ok(HttpResponseOk(policy)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn oximeter_read_policy_set( + rqctx: RequestContext, + policy: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus + .datastore() + .oximeter_read_policy_insert_latest_version( + &opctx, + &policy.into_inner(), + ) + .await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn quiesce_start( + rqctx: RequestContext, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus.quiesce_start(&opctx).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn quiesce_get( + rqctx: RequestContext, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + Ok(HttpResponseOk(nexus.quiesce_state(&opctx).await?)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } } diff --git a/nexus/test-utils/Cargo.toml b/nexus/test-utils/Cargo.toml index 81ab9be3f9f..0423823fc6c 100644 --- a/nexus/test-utils/Cargo.toml +++ b/nexus/test-utils/Cargo.toml @@ -21,6 +21,7 @@ futures.workspace = true gateway-messages.workspace = true gateway-test-utils.workspace = true headers.workspace = true +hickory-resolver.workspace = true http.workspace = true http-body-util.workspace = true hyper.workspace = true @@ -28,9 +29,9 @@ id-map.workspace = true illumos-utils.workspace = true internal-dns-resolver.workspace = true internal-dns-types.workspace = true -nexus-client.workspace = true nexus-config.workspace = true nexus-db-queries = { workspace = true, features = [ "testing" ] } +nexus-lockstep-client.workspace = true nexus-sled-agent-shared.workspace = true nexus-test-interface.workspace = true nexus-types.workspace = true @@ -40,6 +41,7 @@ omicron-passwords.workspace = true omicron-sled-agent.workspace = true omicron-test-utils.workspace = true omicron-uuid-kinds.workspace = true +omicron-workspace-hack.workspace = true oximeter.workspace = true oximeter-collector.workspace = true oximeter-producer.workspace = true @@ -54,9 +56,7 @@ slog-error-chain.workspace = true tokio.workspace = true tokio-postgres = { workspace = true, features = ["with-serde_json-1"] } tokio-util.workspace = true -hickory-resolver.workspace = true uuid.workspace = true -omicron-workspace-hack.workspace = true [features] omicron-dev = ["omicron-test-utils/seed-gen"] diff --git a/nexus/test-utils/src/background.rs b/nexus/test-utils/src/background.rs index 725452c6e3c..7b39f69daa8 100644 --- a/nexus/test-utils/src/background.rs +++ b/nexus/test-utils/src/background.rs @@ -6,9 +6,9 @@ use crate::http_testing::NexusRequest; use dropshot::test_util::ClientTestContext; -use nexus_client::types::BackgroundTask; -use nexus_client::types::CurrentStatus; -use nexus_client::types::LastResult; +use nexus_lockstep_client::types::BackgroundTask; +use nexus_lockstep_client::types::CurrentStatus; +use nexus_lockstep_client::types::LastResult; use nexus_types::internal_api::background::*; use omicron_test_utils::dev::poll::{CondCheckError, wait_for_condition}; use slog::info; @@ -18,14 +18,14 @@ use std::time::Duration; /// running, then return the last polled `BackgroundTask` object. Panics if the /// task has never been activated. pub async fn wait_background_task( - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, task_name: &str, ) -> BackgroundTask { // Wait for the task to finish let last_task_poll = wait_for_condition( || async { let task = NexusRequest::object_get( - internal_client, + lockstep_client, &format!("/bgtasks/view/{task_name}"), ) .execute_and_parse_unwrap::() @@ -55,7 +55,7 @@ pub async fn wait_background_task( /// Given the name of a background task, activate it, then wait for it to /// complete. Return the `BackgroundTask` object from this invocation. pub async fn activate_background_task( - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, task_name: &str, ) -> BackgroundTask { // If it is running, wait for an existing task to complete - this function @@ -65,7 +65,7 @@ pub async fn activate_background_task( let previous_task = wait_for_condition( || async { let task = NexusRequest::object_get( - internal_client, + lockstep_client, &format!("/bgtasks/view/{task_name}"), ) .execute_and_parse_unwrap::() @@ -76,7 +76,7 @@ pub async fn activate_background_task( } info!( - internal_client.client_log, + lockstep_client.client_log, "waiting for {task_name} to go idle", ); @@ -88,7 +88,7 @@ pub async fn activate_background_task( .await .expect("task never went to idle"); - internal_client + lockstep_client .make_request( http::Method::POST, "/bgtasks/activate", @@ -110,7 +110,7 @@ pub async fn activate_background_task( let last_task_poll = wait_for_condition( || async { let task = NexusRequest::object_get( - internal_client, + lockstep_client, &format!("/bgtasks/view/{task_name}"), ) .execute_and_parse_unwrap::() @@ -174,10 +174,10 @@ pub async fn activate_background_task( /// Run the region_replacement background task, returning how many actions /// were taken pub async fn run_region_replacement( - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, ) -> usize { let last_background_task = - activate_background_task(&internal_client, "region_replacement").await; + activate_background_task(&lockstep_client, "region_replacement").await; let LastResult::Completed(last_result_completed) = last_background_task.last @@ -203,10 +203,10 @@ pub async fn run_region_replacement( /// Run the region_replacement_driver background task, returning how many actions /// were taken pub async fn run_region_replacement_driver( - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, ) -> usize { let last_background_task = - activate_background_task(&internal_client, "region_replacement_driver") + activate_background_task(&lockstep_client, "region_replacement_driver") .await; let LastResult::Completed(last_result_completed) = @@ -231,10 +231,10 @@ pub async fn run_region_replacement_driver( /// Run the region_snapshot_replacement_start background task, returning how many /// actions were taken pub async fn run_region_snapshot_replacement_start( - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, ) -> usize { let last_background_task = activate_background_task( - &internal_client, + &lockstep_client, "region_snapshot_replacement_start", ) .await; @@ -263,10 +263,10 @@ pub async fn run_region_snapshot_replacement_start( /// Run the region_snapshot_replacement_garbage_collection background task, /// returning how many actions were taken pub async fn run_region_snapshot_replacement_garbage_collection( - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, ) -> usize { let last_background_task = activate_background_task( - &internal_client, + &lockstep_client, "region_snapshot_replacement_garbage_collection", ) .await; @@ -294,10 +294,10 @@ pub async fn run_region_snapshot_replacement_garbage_collection( /// Run the region_snapshot_replacement_step background task, returning how many /// actions were taken pub async fn run_region_snapshot_replacement_step( - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, ) -> usize { let last_background_task = activate_background_task( - &internal_client, + &lockstep_client, "region_snapshot_replacement_step", ) .await; @@ -327,10 +327,10 @@ pub async fn run_region_snapshot_replacement_step( /// Run the region_snapshot_replacement_finish background task, returning how many /// actions were taken pub async fn run_region_snapshot_replacement_finish( - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, ) -> usize { let last_background_task = activate_background_task( - &internal_client, + &lockstep_client, "region_snapshot_replacement_finish", ) .await; @@ -359,10 +359,10 @@ pub async fn run_region_snapshot_replacement_finish( /// Run the read_only_region_replacement_start background task, returning how /// many actions were taken pub async fn run_read_only_region_replacement_start( - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, ) -> usize { let last_background_task = activate_background_task( - &internal_client, + &lockstep_client, "read_only_region_replacement_start", ) .await; @@ -391,24 +391,24 @@ pub async fn run_read_only_region_replacement_start( /// Run all replacement related background tasks and return how many actions /// were taken. pub async fn run_all_crucible_replacement_tasks( - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, ) -> usize { // region replacement related - run_region_replacement(internal_client).await + - run_region_replacement_driver(internal_client).await + + run_region_replacement(lockstep_client).await + + run_region_replacement_driver(lockstep_client).await + // region snapshot replacement related - run_region_snapshot_replacement_start(internal_client).await + - run_region_snapshot_replacement_garbage_collection(internal_client).await + - run_region_snapshot_replacement_step(internal_client).await + - run_region_snapshot_replacement_finish(internal_client).await + - run_read_only_region_replacement_start(internal_client).await + run_region_snapshot_replacement_start(lockstep_client).await + + run_region_snapshot_replacement_garbage_collection(lockstep_client).await + + run_region_snapshot_replacement_step(lockstep_client).await + + run_region_snapshot_replacement_finish(lockstep_client).await + + run_read_only_region_replacement_start(lockstep_client).await } pub async fn wait_tuf_artifact_replication_step( - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, ) -> TufArtifactReplicationStatus { let last_background_task = - wait_background_task(&internal_client, "tuf_artifact_replication") + wait_background_task(&lockstep_client, "tuf_artifact_replication") .await; let LastResult::Completed(last_result_completed) = @@ -429,10 +429,10 @@ pub async fn wait_tuf_artifact_replication_step( } pub async fn run_tuf_artifact_replication_step( - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, ) -> TufArtifactReplicationStatus { let last_background_task = - activate_background_task(&internal_client, "tuf_artifact_replication") + activate_background_task(&lockstep_client, "tuf_artifact_replication") .await; let LastResult::Completed(last_result_completed) = diff --git a/nexus/tests/integration_tests/crucible_replacements.rs b/nexus/tests/integration_tests/crucible_replacements.rs index efc87e503ec..fbca4c579c8 100644 --- a/nexus/tests/integration_tests/crucible_replacements.rs +++ b/nexus/tests/integration_tests/crucible_replacements.rs @@ -8,7 +8,6 @@ use async_bb8_diesel::AsyncRunQueryDsl; use diesel::ExpressionMethods; use diesel::QueryDsl; use dropshot::test_util::ClientTestContext; -use nexus_client::types::LastResult; use nexus_db_lookup::LookupPath; use nexus_db_model::PhysicalDiskPolicy; use nexus_db_model::ReadOnlyTargetReplacement; @@ -17,6 +16,7 @@ use nexus_db_model::RegionSnapshotReplacementState; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::DataStore; use nexus_db_queries::db::datastore::region_snapshot_replacement::*; +use nexus_lockstep_client::types::LastResult; use nexus_test_utils::background::*; use nexus_test_utils::http_testing::AuthnMode; use nexus_test_utils::http_testing::NexusRequest; @@ -95,13 +95,13 @@ where pub(crate) async fn wait_for_all_replacements( datastore: &Arc, - internal_client: &ClientTestContext, + lockstep_client: &ClientTestContext, ) { wait_for_condition( || { let datastore = datastore.clone(); let opctx = OpContext::for_tests( - internal_client.client_log.new(o!()), + lockstep_client.client_log.new(o!()), datastore.clone(), ); @@ -120,7 +120,7 @@ pub(crate) async fn wait_for_all_replacements( // can tell you that something is _currently_ moving but not // that all work is done. - run_all_crucible_replacement_tasks(internal_client).await; + run_all_crucible_replacement_tasks(lockstep_client).await; let ro_left_to_do = datastore .find_read_only_regions_on_expunged_physical_disks(&opctx) @@ -142,7 +142,7 @@ pub(crate) async fn wait_for_all_replacements( if ro_left_to_do + rw_left_to_do + rs_left_to_do > 0 { info!( - &internal_client.client_log, + &lockstep_client.client_log, "wait_for_all_replacements: ro {} rw {} rs {}", ro_left_to_do, rw_left_to_do, @@ -189,7 +189,7 @@ pub(crate) async fn wait_for_all_replacements( > 0 { info!( - &internal_client.client_log, + &lockstep_client.client_log, "wait_for_all_replacements: rr {} rsr {}", region_replacement_left, region_snapshot_replacement_left, @@ -271,10 +271,10 @@ async fn test_region_replacement_does_not_create_freed_region( // Now, run the first part of region replacement: this will move the deleted // region into a temporary volume. - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let _ = - activate_background_task(&internal_client, "region_replacement").await; + activate_background_task(&lockstep_client, "region_replacement").await; // Assert there are no freed crucible regions that result from that assert!(datastore.find_deleted_volume_regions().await.unwrap().is_empty()); @@ -297,7 +297,7 @@ mod region_replacement { datastore: Arc, disk_test: DiskTest<'a>, client: ClientTestContext, - internal_client: ClientTestContext, + lockstep_client: ClientTestContext, replacement_request_id: Uuid, } @@ -315,7 +315,7 @@ mod region_replacement { .await; let client = &cptestctx.external_client; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let datastore = nexus.datastore().clone(); let opctx = OpContext::for_tests( @@ -370,7 +370,7 @@ mod region_replacement { datastore, disk_test, client: client.clone(), - internal_client: internal_client.clone(), + lockstep_client: lockstep_client.clone(), replacement_request_id, } } @@ -397,7 +397,7 @@ mod region_replacement { pub async fn finish_test(&self) { // Make sure that all the background tasks can run to completion. - wait_for_all_replacements(&self.datastore, &self.internal_client) + wait_for_all_replacements(&self.datastore, &self.lockstep_client) .await; // Assert the request is in state Complete @@ -510,7 +510,7 @@ mod region_replacement { pub async fn transition_request_to_running(&self) { // Activate the "region replacement" background task - run_region_replacement(&self.internal_client).await; + run_region_replacement(&self.lockstep_client).await; // The activation above could only have started the associated saga, // so wait until the request is in state Running. @@ -529,7 +529,7 @@ mod region_replacement { // Run the "region replacement driver" task to attach the associated // volume to the simulated pantry. - run_region_replacement_driver(&self.internal_client).await; + run_region_replacement_driver(&self.lockstep_client).await; // The activation above could only have started the associated saga, // so wait until the request is in the expected end state. @@ -619,7 +619,7 @@ mod region_replacement { pub async fn transition_request_to_replacement_done(&self) { // Run the "region replacement driver" task - run_region_replacement_driver(&self.internal_client).await; + run_region_replacement_driver(&self.lockstep_client).await; // The activation above could only have started the associated saga, // so wait until the request is in the expected end state. @@ -865,10 +865,10 @@ async fn test_racing_replacements_for_soft_deleted_disk_volume( // 1) region replacement will allocate a new region and swap it into the // disk volume. - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let _ = - activate_background_task(&internal_client, "region_replacement").await; + activate_background_task(&lockstep_client, "region_replacement").await; // After that task invocation, there should be one running region // replacement for the disk's region. Filter out the replacement request for @@ -920,7 +920,7 @@ async fn test_racing_replacements_for_soft_deleted_disk_volume( // the snapshot volume let _ = activate_background_task( - &internal_client, + &lockstep_client, "region_snapshot_replacement_start", ) .await; @@ -1004,7 +1004,7 @@ async fn test_racing_replacements_for_soft_deleted_disk_volume( // reference count to zero. let _ = activate_background_task( - &internal_client, + &lockstep_client, "region_snapshot_replacement_garbage_collection", ) .await; @@ -1067,7 +1067,7 @@ async fn test_racing_replacements_for_soft_deleted_disk_volume( // ReplacementDone let last_background_task = - activate_background_task(&internal_client, "region_replacement_driver") + activate_background_task(&lockstep_client, "region_replacement_driver") .await; let res = match last_background_task.last { @@ -1157,7 +1157,7 @@ async fn test_racing_replacements_for_soft_deleted_disk_volume( let mut count = 0; loop { let actions_taken = - run_region_snapshot_replacement_step(&internal_client).await; + run_region_snapshot_replacement_step(&lockstep_client).await; if actions_taken == 0 { break; @@ -1171,7 +1171,7 @@ async fn test_racing_replacements_for_soft_deleted_disk_volume( } let _ = activate_background_task( - &internal_client, + &lockstep_client, "region_snapshot_replacement_finish", ) .await; @@ -1235,7 +1235,7 @@ async fn test_racing_replacements_for_soft_deleted_disk_volume( // Make sure that all the background tasks can run to completion. - wait_for_all_replacements(datastore, &internal_client).await; + wait_for_all_replacements(datastore, &lockstep_client).await; // The disk volume should be deleted by the snapshot delete: wait until this // happens @@ -1290,6 +1290,7 @@ mod region_snapshot_replacement { disk_test: DiskTest<'a>, client: ClientTestContext, internal_client: ClientTestContext, + lockstep_client: ClientTestContext, replacement_request_id: Uuid, snapshot_socket_addr: SocketAddr, } @@ -1309,6 +1310,7 @@ mod region_snapshot_replacement { let client = &cptestctx.external_client; let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let datastore = nexus.datastore().clone(); let opctx = OpContext::for_tests( @@ -1426,6 +1428,7 @@ mod region_snapshot_replacement { disk_test, client: client.clone(), internal_client: internal_client.clone(), + lockstep_client: lockstep_client.clone(), replacement_request_id, snapshot_socket_addr, } @@ -1473,7 +1476,7 @@ mod region_snapshot_replacement { pub async fn finish_test(&self) { // Make sure that all the background tasks can run to completion. - wait_for_all_replacements(&self.datastore, &self.internal_client) + wait_for_all_replacements(&self.datastore, &self.lockstep_client) .await; // Assert the request is in state Complete @@ -1594,7 +1597,7 @@ mod region_snapshot_replacement { pub async fn transition_request_to_replacement_done(&self) { // Activate the "region snapshot replacement start" background task - run_region_snapshot_replacement_start(&self.internal_client).await; + run_region_snapshot_replacement_start(&self.lockstep_client).await; // The activation above could only have started the associated saga, // so wait until the request is in state Running. @@ -1618,7 +1621,7 @@ mod region_snapshot_replacement { // background task run_region_snapshot_replacement_garbage_collection( - &self.internal_client, + &self.lockstep_client, ) .await; @@ -2095,8 +2098,8 @@ async fn test_replacement_sanity(cptestctx: &ControlPlaneTestContext) { .set_auto_activate_volumes(); // Now, run all replacement tasks to completion - let internal_client = &cptestctx.internal_client; - wait_for_all_replacements(&datastore, &internal_client).await; + let lockstep_client = &cptestctx.lockstep_client; + wait_for_all_replacements(&datastore, &lockstep_client).await; // Validate all regions are on non-expunged physical disks assert!( @@ -2172,7 +2175,7 @@ async fn test_region_replacement_triple_sanity( .await .unwrap(); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let disk_allocated_regions = datastore.get_allocated_regions(db_disk.volume_id()).await.unwrap(); @@ -2206,7 +2209,7 @@ async fn test_region_replacement_triple_sanity( .unwrap(); // Now, run all replacement tasks to completion - wait_for_all_replacements(&datastore, &internal_client).await; + wait_for_all_replacements(&datastore, &lockstep_client).await; } let disk_allocated_regions = @@ -2298,7 +2301,7 @@ async fn test_region_replacement_triple_sanity_2( .await .unwrap(); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let disk_allocated_regions = datastore.get_allocated_regions(db_disk.volume_id()).await.unwrap(); @@ -2338,7 +2341,7 @@ async fn test_region_replacement_triple_sanity_2( info!(log, "waiting for all replacements"); // Now, run all replacement tasks to completion - wait_for_all_replacements(&datastore, &internal_client).await; + wait_for_all_replacements(&datastore, &lockstep_client).await; // Expunge the last physical disk { @@ -2370,7 +2373,7 @@ async fn test_region_replacement_triple_sanity_2( info!(log, "waiting for all replacements"); // Now, run all replacement tasks to completion - wait_for_all_replacements(&datastore, &internal_client).await; + wait_for_all_replacements(&datastore, &lockstep_client).await; let disk_allocated_regions = datastore.get_allocated_regions(db_disk.volume_id()).await.unwrap(); @@ -2410,7 +2413,7 @@ async fn test_replacement_sanity_twice(cptestctx: &ControlPlaneTestContext) { let datastore = nexus.datastore(); let opctx = OpContext::for_tests(cptestctx.logctx.log.new(o!()), datastore.clone()); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; // Create one zpool per sled, each with one dataset. This is required for // region and region snapshot replacement to have somewhere to move the @@ -2478,7 +2481,7 @@ async fn test_replacement_sanity_twice(cptestctx: &ControlPlaneTestContext) { .await .unwrap(); - wait_for_all_replacements(&datastore, &internal_client).await; + wait_for_all_replacements(&datastore, &lockstep_client).await; } // Now, do it again, except this time specifying the read-only regions @@ -2503,7 +2506,7 @@ async fn test_replacement_sanity_twice(cptestctx: &ControlPlaneTestContext) { .await .unwrap(); - wait_for_all_replacements(&datastore, &internal_client).await; + wait_for_all_replacements(&datastore, &lockstep_client).await; } } @@ -2517,7 +2520,7 @@ async fn test_read_only_replacement_sanity( let datastore = nexus.datastore(); let opctx = OpContext::for_tests(cptestctx.logctx.log.new(o!()), datastore.clone()); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; // Create one zpool per sled, each with one dataset. This is required for // region and region snapshot replacement to have somewhere to move the @@ -2585,7 +2588,7 @@ async fn test_read_only_replacement_sanity( .await .unwrap(); - wait_for_all_replacements(&datastore, &internal_client).await; + wait_for_all_replacements(&datastore, &lockstep_client).await; } // Now expunge a sled with read-only regions on it. @@ -2619,7 +2622,7 @@ async fn test_read_only_replacement_sanity( .await .unwrap(); - wait_for_all_replacements(&datastore, &internal_client).await; + wait_for_all_replacements(&datastore, &lockstep_client).await; // Validate all regions are on non-expunged physical disks assert!( @@ -2648,7 +2651,7 @@ async fn test_replacement_sanity_twice_after_snapshot_delete( let datastore = nexus.datastore(); let opctx = OpContext::for_tests(cptestctx.logctx.log.new(o!()), datastore.clone()); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; // Create one zpool per sled, each with one dataset. This is required for // region and region snapshot replacement to have somewhere to move the @@ -2753,7 +2756,7 @@ async fn test_replacement_sanity_twice_after_snapshot_delete( .await .unwrap(); - wait_for_all_replacements(&datastore, &internal_client).await; + wait_for_all_replacements(&datastore, &lockstep_client).await; } // Now, do it again, except this time specifying the read-only regions @@ -2778,6 +2781,6 @@ async fn test_replacement_sanity_twice_after_snapshot_delete( .await .unwrap(); - wait_for_all_replacements(&datastore, &internal_client).await; + wait_for_all_replacements(&datastore, &lockstep_client).await; } } diff --git a/nexus/tests/integration_tests/demo_saga.rs b/nexus/tests/integration_tests/demo_saga.rs index b09a2917442..e5a973528ea 100644 --- a/nexus/tests/integration_tests/demo_saga.rs +++ b/nexus/tests/integration_tests/demo_saga.rs @@ -5,8 +5,8 @@ //! Smoke test for the demo saga use futures::TryStreamExt; -use nexus_client::types::Saga; -use nexus_client::types::SagaState; +use nexus_lockstep_client::types::Saga; +use nexus_lockstep_client::types::SagaState; use nexus_test_interface::NexusServer; use nexus_test_utils_macros::nexus_test; use omicron_test_utils::dev::poll::CondCheckError; @@ -21,12 +21,12 @@ type ControlPlaneTestContext = #[nexus_test] async fn test_demo_saga(cptestctx: &ControlPlaneTestContext) { let log = &cptestctx.logctx.log; - let nexus_internal_url = format!( + let nexus_lockstep_url = format!( "http://{}", - cptestctx.server.get_http_server_internal_address().await + cptestctx.server.get_http_server_lockstep_address().await ); let nexus_client = - nexus_client::Client::new(&nexus_internal_url, log.clone()); + nexus_lockstep_client::Client::new(&nexus_lockstep_url, log.clone()); let sagas_before = list_sagas(&nexus_client).await; eprintln!("found sagas (before): {:?}", sagas_before); @@ -69,6 +69,6 @@ async fn test_demo_saga(cptestctx: &ControlPlaneTestContext) { assert!(matches!(found.state, SagaState::Succeeded)); } -async fn list_sagas(client: &nexus_client::Client) -> Vec { +async fn list_sagas(client: &nexus_lockstep_client::Client) -> Vec { client.saga_list_stream(None, None).try_collect::>().await.unwrap() } diff --git a/nexus/tests/integration_tests/disks.rs b/nexus/tests/integration_tests/disks.rs index 483b000f957..ae12c3b37f4 100644 --- a/nexus/tests/integration_tests/disks.rs +++ b/nexus/tests/integration_tests/disks.rs @@ -2348,8 +2348,8 @@ async fn test_disk_expunge(cptestctx: &ControlPlaneTestContext) { assert_eq!(allocated_regions.len(), REGION_REDUNDANCY_THRESHOLD); // Expunge the sled - let int_client = &cptestctx.internal_client; - int_client + cptestctx + .lockstep_client .make_request( Method::POST, "/sleds/expunge", diff --git a/nexus/tests/integration_tests/instances.rs b/nexus/tests/integration_tests/instances.rs index 9b7a7ecc24e..65e5ede0a70 100644 --- a/nexus/tests/integration_tests/instances.rs +++ b/nexus/tests/integration_tests/instances.rs @@ -744,7 +744,7 @@ async fn test_instance_migrate(cptestctx: &ControlPlaneTestContext) { } let client = &cptestctx.external_client; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let apictx = &cptestctx.server.server_context(); let nexus = &apictx.nexus; let instance_name = "bird-ecology"; @@ -793,7 +793,7 @@ async fn test_instance_migrate(cptestctx: &ControlPlaneTestContext) { let migrate_url = format!("/instances/{}/migrate", &instance_id.to_string()); let instance = NexusRequest::new( - RequestBuilder::new(internal_client, Method::POST, &migrate_url) + RequestBuilder::new(lockstep_client, Method::POST, &migrate_url) .body(Some(&InstanceMigrateRequest { dst_sled_id })) .expect_status(Some(StatusCode::OK)), ) @@ -916,7 +916,7 @@ async fn test_instance_migrate_v2p_and_routes( cptestctx: &ControlPlaneTestContext, ) { let client = &cptestctx.external_client; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let apictx = &cptestctx.server.server_context(); let nexus = &apictx.nexus; let datastore = nexus.datastore(); @@ -995,7 +995,7 @@ async fn test_instance_migrate_v2p_and_routes( let migrate_url = format!("/instances/{}/migrate", &instance_id.to_string()); let _ = NexusRequest::new( - RequestBuilder::new(internal_client, Method::POST, &migrate_url) + RequestBuilder::new(lockstep_client, Method::POST, &migrate_url) .body(Some(&InstanceMigrateRequest { dst_sled_id })) .expect_status(Some(StatusCode::OK)), ) @@ -1117,7 +1117,7 @@ async fn test_instance_migration_compatible_cpu_platforms( } let client = &cptestctx.external_client; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let apictx = &cptestctx.server.server_context(); let nexus = &apictx.nexus; let instance_name = "bird-ecology"; @@ -1182,7 +1182,7 @@ async fn test_instance_migration_compatible_cpu_platforms( let migrate_url = format!("/instances/{}/migrate", &instance_id.to_string()); let instance = NexusRequest::new( - RequestBuilder::new(internal_client, Method::POST, &migrate_url) + RequestBuilder::new(lockstep_client, Method::POST, &migrate_url) .body(Some(&InstanceMigrateRequest { dst_sled_id })) .expect_status(Some(StatusCode::OK)), ) @@ -1307,7 +1307,7 @@ async fn test_instance_migration_incompatible_cpu_platforms( cptestctx: &ControlPlaneTestContext, ) { let client = &cptestctx.external_client; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let apictx = &cptestctx.server.server_context(); let nexus = &apictx.nexus; let instance_name = "bird-ecology"; @@ -1369,7 +1369,7 @@ async fn test_instance_migration_incompatible_cpu_platforms( let migrate_url = format!("/instances/{}/migrate", &instance_id.to_string()); NexusRequest::new( - RequestBuilder::new(internal_client, Method::POST, &migrate_url) + RequestBuilder::new(lockstep_client, Method::POST, &migrate_url) .body(Some(&InstanceMigrateRequest { dst_sled_id: milan_sled_id })) .expect_status(Some(http::StatusCode::INSUFFICIENT_STORAGE)), ) @@ -1384,7 +1384,7 @@ async fn test_instance_migration_unknown_sled_type( cptestctx: &ControlPlaneTestContext, ) { let client = &cptestctx.external_client; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let apictx = &cptestctx.server.server_context(); let nexus = &apictx.nexus; let instance_name = "bird-ecology"; @@ -1457,7 +1457,7 @@ async fn test_instance_migration_unknown_sled_type( let migrate_url = format!("/instances/{}/migrate", &instance_id.to_string()); NexusRequest::new( - RequestBuilder::new(internal_client, Method::POST, &migrate_url) + RequestBuilder::new(lockstep_client, Method::POST, &migrate_url) .body(Some(&InstanceMigrateRequest { dst_sled_id })) .expect_status(Some(expected_status)), ) @@ -1588,7 +1588,7 @@ async fn test_instance_failed_by_instance_watcher_can_be_deleted( .await; nexus_test_utils::background::activate_background_task( - &cptestctx.internal_client, + &cptestctx.lockstep_client, "instance_watcher", ) .await; @@ -1617,7 +1617,7 @@ async fn test_instance_failed_by_instance_watcher_can_be_restarted( .await; nexus_test_utils::background::activate_background_task( - &cptestctx.internal_client, + &cptestctx.lockstep_client, "instance_watcher", ) .await; @@ -1727,8 +1727,8 @@ async fn test_instance_failed_when_on_expunged_sled( "expunging sled"; "sled_id" => %default_sled_id, ); - let int_client = &cptestctx.internal_client; - int_client + cptestctx + .lockstep_client .make_request( Method::POST, "/sleds/expunge", @@ -1784,7 +1784,7 @@ async fn test_instance_failed_by_instance_watcher_automatically_reincarnates( dbg!( nexus_test_utils::background::activate_background_task( - &cptestctx.internal_client, + &cptestctx.lockstep_client, "instance_watcher", ) .await @@ -1858,7 +1858,7 @@ async fn test_instance_failed_by_stop_request_does_not_reincarnate( // Activate the reincarnation task. dbg!( nexus_test_utils::background::activate_background_task( - &cptestctx.internal_client, + &cptestctx.lockstep_client, "instance_reincarnation", ) .await @@ -1995,7 +1995,7 @@ async fn test_instances_are_not_marked_failed_on_other_sled_agent_errors_by_inst .await; nexus_test_utils::background::activate_background_task( - &cptestctx.internal_client, + &cptestctx.lockstep_client, "instance_watcher", ) .await; @@ -2228,7 +2228,7 @@ async fn test_instance_metrics_with_migration( cptestctx: &ControlPlaneTestContext, ) { let client = &cptestctx.external_client; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let apictx = &cptestctx.server.server_context(); let nexus = &apictx.nexus; let instance_name = "bird-ecology"; @@ -2312,7 +2312,7 @@ async fn test_instance_metrics_with_migration( let migrate_url = format!("/instances/{}/migrate", &instance_id.to_string()); let _ = NexusRequest::new( - RequestBuilder::new(internal_client, Method::POST, &migrate_url) + RequestBuilder::new(lockstep_client, Method::POST, &migrate_url) .body(Some(&InstanceMigrateRequest { dst_sled_id })) .expect_status(Some(StatusCode::OK)), ) diff --git a/nexus/tests/integration_tests/metrics.rs b/nexus/tests/integration_tests/metrics.rs index 0a9bf48475e..effb5a7bd45 100644 --- a/nexus/tests/integration_tests/metrics.rs +++ b/nexus/tests/integration_tests/metrics.rs @@ -258,14 +258,14 @@ async fn test_instance_watcher_metrics( filter timestamp > @2000-01-01"; let client = &cptestctx.external_client; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let nexus = &cptestctx.server.server_context().nexus; let oximeter = &cptestctx.oximeter; let activate_instance_watcher = || async { use nexus_test_utils::background::activate_background_task; - let _ = activate_background_task(&internal_client, "instance_watcher") + let _ = activate_background_task(&lockstep_client, "instance_watcher") .await; }; @@ -479,11 +479,11 @@ async fn test_project_timeseries_query( let i2p1 = create_instance(&client, "project1", "instance2").await; let _i3p2 = create_instance(&client, "project2", "instance3").await; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; // get the instance metrics to show up let _ = - activate_background_task(&internal_client, "instance_watcher").await; + activate_background_task(&lockstep_client, "instance_watcher").await; // Query with no project specified let q1 = "get virtual_machine:check"; diff --git a/nexus/tests/integration_tests/quiesce.rs b/nexus/tests/integration_tests/quiesce.rs index 3ab3452b62e..2d80bb1e16b 100644 --- a/nexus/tests/integration_tests/quiesce.rs +++ b/nexus/tests/integration_tests/quiesce.rs @@ -4,7 +4,7 @@ use anyhow::{Context, anyhow}; use nexus_auth::context::OpContext; -use nexus_client::types::QuiesceState; +use nexus_lockstep_client::types::QuiesceState; use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; use nexus_reconfigurator_planning::planner::PlannerRng; use nexus_reconfigurator_preparation::PlanningInputFromDb; @@ -29,12 +29,12 @@ async fn test_quiesce(cptestctx: &ControlPlaneTestContext) { let nexus = &cptestctx.server.server_context().nexus; let datastore = nexus.datastore(); let opctx = OpContext::for_tests(log.clone(), datastore.clone()); - let nexus_internal_url = format!( + let nexus_lockstep_url = format!( "http://{}", - cptestctx.server.get_http_server_internal_address().await + cptestctx.server.get_http_server_lockstep_address().await ); let nexus_client = - nexus_client::Client::new(&nexus_internal_url, log.clone()); + nexus_lockstep_client::Client::new(&nexus_lockstep_url, log.clone()); // Collect what we need to modify the blueprint. let collection = wait_for_condition( diff --git a/nexus/tests/integration_tests/rack.rs b/nexus/tests/integration_tests/rack.rs index 40d7b41fb48..f2f01b48a47 100644 --- a/nexus/tests/integration_tests/rack.rs +++ b/nexus/tests/integration_tests/rack.rs @@ -5,11 +5,11 @@ use dropshot::ResultsPage; use http::Method; use http::StatusCode; -use nexus_client::types::SledId; use nexus_db_model::SledBaseboard; use nexus_db_model::SledCpuFamily as DbSledCpuFamily; use nexus_db_model::SledSystemHardware; use nexus_db_model::SledUpdate; +use nexus_lockstep_client::types::SledId; use nexus_sled_agent_shared::inventory::SledCpuFamily; use nexus_sled_agent_shared::inventory::SledRole; use nexus_test_utils::TEST_SUITE_PASSWORD; diff --git a/nexus/tests/integration_tests/snapshots.rs b/nexus/tests/integration_tests/snapshots.rs index 83405813063..80807a78eb3 100644 --- a/nexus/tests/integration_tests/snapshots.rs +++ b/nexus/tests/integration_tests/snapshots.rs @@ -1680,8 +1680,8 @@ async fn test_snapshot_expunge(cptestctx: &ControlPlaneTestContext) { .await; // Expunge the sled - let int_client = &cptestctx.internal_client; - int_client + cptestctx + .lockstep_client .make_request( Method::POST, "/sleds/expunge", diff --git a/nexus/tests/integration_tests/support_bundles.rs b/nexus/tests/integration_tests/support_bundles.rs index 215936dbecb..64ecd43e171 100644 --- a/nexus/tests/integration_tests/support_bundles.rs +++ b/nexus/tests/integration_tests/support_bundles.rs @@ -10,7 +10,7 @@ use dropshot::HttpErrorResponseBody; use dropshot::test_util::ClientTestContext; use http::StatusCode; use http::method::Method; -use nexus_client::types::LastResult; +use nexus_lockstep_client::types::LastResult; use nexus_test_utils::http_testing::AuthnMode; use nexus_test_utils::http_testing::NexusRequest; use nexus_test_utils::http_testing::RequestBuilder; @@ -338,7 +338,7 @@ async fn activate_bundle_collection_background_task( use nexus_test_utils::background::activate_background_task; let task = activate_background_task( - &cptestctx.internal_client, + &cptestctx.lockstep_client, "support_bundle_collector", ) .await; diff --git a/nexus/tests/integration_tests/updates.rs b/nexus/tests/integration_tests/updates.rs index ef47345cfae..0f55a3e7f86 100644 --- a/nexus/tests/integration_tests/updates.rs +++ b/nexus/tests/integration_tests/updates.rs @@ -152,7 +152,7 @@ async fn test_repo_upload_unconfigured() -> Result<()> { // The artifact replication background task should have nothing to do. let status = - run_tuf_artifact_replication_step(&cptestctx.internal_client).await; + run_tuf_artifact_replication_step(&cptestctx.lockstep_client).await; assert_eq!( status.last_run_counters.put_ok + status.last_run_counters.copy_ok, 0 @@ -248,7 +248,7 @@ async fn test_repo_upload() -> Result<()> { // The artifact replication background task should have been activated, and // we should see a local repo and successful PUTs. let status = - wait_tuf_artifact_replication_step(&cptestctx.internal_client).await; + wait_tuf_artifact_replication_step(&cptestctx.lockstep_client).await; eprintln!("{status:?}"); assert_eq!(status.generation, 2u32.into()); assert_eq!(status.last_run_counters.put_config_ok, 4); @@ -267,7 +267,7 @@ async fn test_repo_upload() -> Result<()> { // Run the replication background task again; the local repos should be // dropped. let status = - run_tuf_artifact_replication_step(&cptestctx.internal_client).await; + run_tuf_artifact_replication_step(&cptestctx.lockstep_client).await; eprintln!("{status:?}"); assert_eq!(status.last_run_counters.put_config_ok, 4); assert_eq!(status.last_run_counters.list_ok, 4); @@ -516,7 +516,7 @@ async fn test_repo_upload() -> Result<()> { ); // ... and the task will have one artifact to replicate. let status = - wait_tuf_artifact_replication_step(&cptestctx.internal_client).await; + wait_tuf_artifact_replication_step(&cptestctx.lockstep_client).await; eprintln!("{status:?}"); assert_eq!(status.generation, 3u32.into()); assert_eq!(status.last_run_counters.list_ok, 4); diff --git a/nexus/tests/integration_tests/volume_management.rs b/nexus/tests/integration_tests/volume_management.rs index 6da752f7e71..5975a51d7f7 100644 --- a/nexus/tests/integration_tests/volume_management.rs +++ b/nexus/tests/integration_tests/volume_management.rs @@ -4062,7 +4062,7 @@ async fn test_read_only_region_reference_counting( cptestctx: &ControlPlaneTestContext, ) { let client = &cptestctx.external_client; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let apictx = &cptestctx.server.server_context(); let nexus = &apictx.nexus; let datastore = nexus.datastore(); @@ -4119,7 +4119,7 @@ async fn test_read_only_region_reference_counting( .await .unwrap(); - wait_for_all_replacements(datastore, &internal_client).await; + wait_for_all_replacements(datastore, &lockstep_client).await; // The snapshot's allocated regions should have the one read-only region @@ -4330,7 +4330,7 @@ async fn test_read_only_region_reference_counting_layers( cptestctx: &ControlPlaneTestContext, ) { let client = &cptestctx.external_client; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let apictx = &cptestctx.server.server_context(); let nexus = &apictx.nexus; let datastore = nexus.datastore(); @@ -4387,7 +4387,7 @@ async fn test_read_only_region_reference_counting_layers( .await .unwrap(); - wait_for_all_replacements(datastore, &internal_client).await; + wait_for_all_replacements(datastore, &lockstep_client).await; // Grab the read-only region in the snapshot volume @@ -5581,7 +5581,7 @@ async fn test_double_layer_with_read_only_region_delete( // 6) At the end, assert that all Crucible resources were cleaned up let client = &cptestctx.external_client; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let apictx = &cptestctx.server.server_context(); let nexus = &apictx.nexus; let datastore = nexus.datastore(); @@ -5646,7 +5646,7 @@ async fn test_double_layer_with_read_only_region_delete( .await .unwrap(); - wait_for_all_replacements(datastore, &internal_client).await; + wait_for_all_replacements(datastore, &lockstep_client).await; assert!(!disk_test.crucible_resources_deleted().await); @@ -5706,7 +5706,7 @@ async fn test_double_layer_snapshot_with_read_only_region_delete_2( // 6) At the end, assert that all Crucible resources were cleaned up let client = &cptestctx.external_client; - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let apictx = &cptestctx.server.server_context(); let nexus = &apictx.nexus; let datastore = nexus.datastore(); @@ -5757,7 +5757,7 @@ async fn test_double_layer_snapshot_with_read_only_region_delete_2( .await .unwrap(); - wait_for_all_replacements(datastore, &internal_client).await; + wait_for_all_replacements(datastore, &lockstep_client).await; wait_for_condition( || { @@ -5803,7 +5803,7 @@ async fn test_double_layer_snapshot_with_read_only_region_delete_2( .await .unwrap(); - wait_for_all_replacements(datastore, &internal_client).await; + wait_for_all_replacements(datastore, &lockstep_client).await; assert!(!disk_test.crucible_resources_deleted().await); @@ -5832,7 +5832,7 @@ async fn test_double_layer_snapshot_with_read_only_region_delete_2( .await .unwrap(); - wait_for_all_replacements(datastore, &internal_client).await; + wait_for_all_replacements(datastore, &lockstep_client).await; assert!(!disk_test.crucible_resources_deleted().await); diff --git a/nexus/tests/integration_tests/webhooks.rs b/nexus/tests/integration_tests/webhooks.rs index f7d6568be71..dab34a8d3e3 100644 --- a/nexus/tests/integration_tests/webhooks.rs +++ b/nexus/tests/integration_tests/webhooks.rs @@ -486,7 +486,7 @@ async fn test_cannot_subscribe_to_probes(cptestctx: &ControlPlaneTestContext) { #[nexus_test] async fn test_event_delivery(cptestctx: &ControlPlaneTestContext) { let nexus = cptestctx.server.server_context().nexus.clone(); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let datastore = nexus.datastore(); let opctx = @@ -539,9 +539,9 @@ async fn test_event_delivery(cptestctx: &ControlPlaneTestContext) { .expect("event should be published successfully"); dbg!(event); - dbg!(activate_background_task(internal_client, "alert_dispatcher").await); + dbg!(activate_background_task(lockstep_client, "alert_dispatcher").await); dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_async().await; @@ -550,7 +550,7 @@ async fn test_event_delivery(cptestctx: &ControlPlaneTestContext) { #[nexus_test] async fn test_multiple_secrets(cptestctx: &ControlPlaneTestContext) { let nexus = cptestctx.server.server_context().nexus.clone(); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let datastore = nexus.datastore(); let opctx = @@ -661,9 +661,9 @@ async fn test_multiple_secrets(cptestctx: &ControlPlaneTestContext) { .expect("event should be published successfully"); dbg!(event); - dbg!(activate_background_task(internal_client, "alert_dispatcher").await); + dbg!(activate_background_task(lockstep_client, "alert_dispatcher").await); dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_async().await; @@ -672,7 +672,7 @@ async fn test_multiple_secrets(cptestctx: &ControlPlaneTestContext) { #[nexus_test] async fn test_multiple_receivers(cptestctx: &ControlPlaneTestContext) { let nexus = cptestctx.server.server_context().nexus.clone(); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let client = &cptestctx.external_client; let datastore = nexus.datastore(); @@ -837,9 +837,9 @@ async fn test_multiple_receivers(cptestctx: &ControlPlaneTestContext) { .expect("event should be published successfully"); dbg!(event); - dbg!(activate_background_task(internal_client, "alert_dispatcher").await); + dbg!(activate_background_task(lockstep_client, "alert_dispatcher").await); dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); // The `test.foo.bar` receiver should have received 1 event. @@ -855,7 +855,7 @@ async fn test_multiple_receivers(cptestctx: &ControlPlaneTestContext) { #[nexus_test] async fn test_retry_backoff(cptestctx: &ControlPlaneTestContext) { let nexus = cptestctx.server.server_context().nexus.clone(); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let datastore = nexus.datastore(); let opctx = @@ -908,9 +908,9 @@ async fn test_retry_backoff(cptestctx: &ControlPlaneTestContext) { .expect("event should be published successfully"); dbg!(event); - dbg!(activate_background_task(internal_client, "alert_dispatcher").await); + dbg!(activate_background_task(lockstep_client, "alert_dispatcher").await); dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(1).await; @@ -941,7 +941,7 @@ async fn test_retry_backoff(cptestctx: &ControlPlaneTestContext) { // Okay, we are now in backoff. Activate the deliverator again --- no new // event should be delivered. dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); // Activating the deliverator whilst in backoff should not send another // request. @@ -978,13 +978,13 @@ async fn test_retry_backoff(cptestctx: &ControlPlaneTestContext) { // Wait out the backoff period for the first request. tokio::time::sleep(std::time::Duration::from_secs(15)).await; dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(1).await; // Again, we should be in backoff, so no request will be sent. dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(1).await; mock.delete_async().await; @@ -1048,13 +1048,13 @@ async fn test_retry_backoff(cptestctx: &ControlPlaneTestContext) { // tokio::time::sleep(std::time::Duration::from_secs(15)).await; dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(0).await; tokio::time::sleep(std::time::Duration::from_secs(5)).await; dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_async().await; @@ -1270,7 +1270,7 @@ async fn test_probe_resends_failed_deliveries( cptestctx: &ControlPlaneTestContext, ) { let nexus = cptestctx.server.server_context().nexus.clone(); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let server = httpmock::MockServer::start_async().await; let datastore = nexus.datastore(); @@ -1329,23 +1329,23 @@ async fn test_probe_resends_failed_deliveries( .expect("event2 should be published successfully") ); - dbg!(activate_background_task(internal_client, "alert_dispatcher").await); + dbg!(activate_background_task(lockstep_client, "alert_dispatcher").await); dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(2).await; // Backoff 1 tokio::time::sleep(std::time::Duration::from_secs(11)).await; dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(4).await; // Backoff 2 tokio::time::sleep(std::time::Duration::from_secs(22)).await; dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(6).await; @@ -1414,7 +1414,7 @@ async fn test_probe_resends_failed_deliveries( // Both events should be resent. dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(2).await; } @@ -1424,7 +1424,7 @@ async fn test_api_resends_failed_deliveries( cptestctx: &ControlPlaneTestContext, ) { let nexus = cptestctx.server.server_context().nexus.clone(); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let client = &cptestctx.external_client; let server = httpmock::MockServer::start_async().await; @@ -1490,18 +1490,18 @@ async fn test_api_resends_failed_deliveries( .expect("event should be published successfully"); dbg!(event2); - dbg!(activate_background_task(internal_client, "alert_dispatcher").await); + dbg!(activate_background_task(lockstep_client, "alert_dispatcher").await); dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); tokio::time::sleep(std::time::Duration::from_secs(11)).await; dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); tokio::time::sleep(std::time::Duration::from_secs(22)).await; dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(3).await; @@ -1543,7 +1543,7 @@ async fn test_api_resends_failed_deliveries( dbg!(error); dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(1).await; } @@ -1563,7 +1563,7 @@ async fn subscription_add_test( new_subscription: &str, ) { let nexus = cptestctx.server.server_context().nexus.clone(); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let datastore = nexus.datastore(); let opctx = @@ -1618,9 +1618,9 @@ async fn subscription_add_test( .expect("event should be published successfully"); dbg!(event); - dbg!(activate_background_task(internal_client, "alert_dispatcher").await); + dbg!(activate_background_task(lockstep_client, "alert_dispatcher").await); dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(0).await; @@ -1651,9 +1651,9 @@ async fn subscription_add_test( .expect("event should be published successfully"); dbg!(event); - dbg!(activate_background_task(internal_client, "alert_dispatcher").await); + dbg!(activate_background_task(lockstep_client, "alert_dispatcher").await); dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(1).await; @@ -1680,7 +1680,7 @@ async fn subscription_remove_test( deleted_subscription: &str, ) { let nexus = cptestctx.server.server_context().nexus.clone(); - let internal_client = &cptestctx.internal_client; + let lockstep_client = &cptestctx.lockstep_client; let datastore = nexus.datastore(); let opctx = @@ -1750,9 +1750,9 @@ async fn subscription_remove_test( .expect("event should be published successfully"); dbg!(event); - dbg!(activate_background_task(internal_client, "alert_dispatcher").await); + dbg!(activate_background_task(lockstep_client, "alert_dispatcher").await); dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(1).await; @@ -1782,9 +1782,9 @@ async fn subscription_remove_test( .expect("event should be published successfully"); dbg!(event); - dbg!(activate_background_task(internal_client, "alert_dispatcher").await); + dbg!(activate_background_task(lockstep_client, "alert_dispatcher").await); dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); // No new calls should be observed. @@ -1829,20 +1829,10 @@ async fn subscription_remove_test( .expect("event should be published successfully"); dbg!(event); - dbg!(activate_background_task(internal_client, "alert_dispatcher").await); + dbg!(activate_background_task(lockstep_client, "alert_dispatcher").await); dbg!( - activate_background_task(internal_client, "webhook_deliverator").await + activate_background_task(lockstep_client, "webhook_deliverator").await ); mock.assert_calls_async(1).await; - - // Deleting a subscription that doesn't exist should 404. - dbg!( - resource_helpers::object_delete_error( - &internal_client, - &subscription_remove_url(rx_id, &deleted_subscription), - http::StatusCode::NOT_FOUND - ) - .await - ); } diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index 2764e19fca4..7b1d7ea36a9 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -10,39 +10,10 @@ "version": "0.0.1" }, "paths": { - "/bgtasks": { - "get": { - "summary": "List background tasks", - "description": "This is a list of discrete background activities that Nexus carries out. This is exposed for support and debugging.", - "operationId": "bgtask_list", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "title": "Map_of_BackgroundTask", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/BackgroundTask" - } - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, "/bgtasks/activate": { "post": { - "summary": "Activates one or more background tasks, causing them to be run immediately", - "description": "if idle, or scheduled to run again as soon as possible if already running.", + "summary": "**Do not use in new code!**", + "description": "Callers to this API should either be capable of using the nexus-lockstep API or should be rewritten to use a doorbell API to activate a specific task. Task names are internal to Nexus.", "operationId": "bgtask_activate", "requestBody": { "content": { @@ -67,90 +38,6 @@ } } }, - "/bgtasks/view/{bgtask_name}": { - "get": { - "summary": "Fetch status of one background task", - "description": "This is exposed for support and debugging.", - "operationId": "bgtask_view", - "parameters": [ - { - "in": "path", - "name": "bgtask_name", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BackgroundTask" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/clickhouse/policy": { - "get": { - "summary": "Get the current clickhouse policy", - "operationId": "clickhouse_policy_get", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ClickhousePolicy" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - }, - "post": { - "summary": "Set the new clickhouse policy", - "operationId": "clickhouse_policy_set", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ClickhousePolicy" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, "/crucible/0/upstairs/{upstairs_id}/downstairs/{downstairs_id}/stop-request": { "post": { "summary": "An Upstairs will update this endpoint if a Downstairs client task is", @@ -362,21 +249,25 @@ } } }, - "/demo-saga": { + "/disk/{disk_id}/remove-read-only-parent": { "post": { - "summary": "Kick off an instance of the \"demo\" saga", - "description": "This saga is used for demo and testing. The saga just waits until you complete using the `saga_demo_complete` API.", - "operationId": "saga_demo_create", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DemoSaga" - } - } + "summary": "Request removal of a read_only_parent from a disk.", + "description": "This is a thin wrapper around the volume_remove_read_only_parent saga. All we are doing here is, given a disk UUID, figure out what the volume_id is for that disk, then use that to call the disk_remove_read_only_parent saga on it.", + "operationId": "cpapi_disk_remove_read_only_parent", + "parameters": [ + { + "in": "path", + "name": "disk_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" } + } + ], + "responses": { + "204": { + "description": "resource updated" }, "4XX": { "$ref": "#/components/responses/Error" @@ -387,21 +278,58 @@ } } }, - "/demo-saga/{demo_saga_id}/complete": { - "post": { - "summary": "Complete a waiting demo saga", - "description": "Note that the id used here is not the same as the id of the saga. It's the one returned by the `saga_demo_create` API.", - "operationId": "saga_demo_complete", + "/disks/{disk_id}": { + "put": { + "summary": "Report updated state for a disk.", + "operationId": "cpapi_disks_put", "parameters": [ { "in": "path", - "name": "demo_saga_id", + "name": "disk_id", "required": true, "schema": { - "$ref": "#/components/schemas/TypedUuidForDemoSagaKind" + "type": "string", + "format": "uuid" } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DiskRuntimeState" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/metrics/collectors": { + "post": { + "summary": "Accept a notification of a new oximeter collection server.", + "operationId": "cpapi_collectors_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OximeterInfo" + } + } + }, + "required": true + }, "responses": { "204": { "description": "resource updated" @@ -415,11 +343,21 @@ } } }, - "/deployment/blueprints/all": { + "/metrics/collectors/{collector_id}/producers": { "get": { - "summary": "Lists blueprints", - "operationId": "blueprint_list", + "summary": "List all metric producers assigned to an oximeter collector.", + "operationId": "cpapi_assigned_producers_list", "parameters": [ + { + "in": "path", + "name": "collector_id", + "description": "The ID of the oximeter collector.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, { "in": "query", "name": "limit", @@ -454,7 +392,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BlueprintMetadataResultsPage" + "$ref": "#/components/schemas/ProducerEndpointResultsPage" } } } @@ -471,29 +409,27 @@ } } }, - "/deployment/blueprints/all/{blueprint_id}": { - "get": { - "summary": "Fetches one blueprint", - "operationId": "blueprint_view", - "parameters": [ - { - "in": "path", - "name": "blueprint_id", - "description": "ID of the blueprint", - "required": true, - "schema": { - "type": "string", - "format": "uuid" + "/metrics/producers": { + "post": { + "summary": "Accept a registration from a new metric producer", + "operationId": "cpapi_producers_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProducerEndpoint" + } } - } - ], + }, + "required": true + }, "responses": { - "200": { - "description": "successful operation", + "201": { + "description": "successful creation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Blueprint" + "$ref": "#/components/schemas/ProducerRegistrationResponse" } } } @@ -505,25 +441,49 @@ "$ref": "#/components/responses/Error" } } - }, - "delete": { - "summary": "Deletes one blueprint", - "operationId": "blueprint_delete", + } + }, + "/nat/ipv4/changeset/{from_gen}": { + "get": { + "summary": "Fetch NAT ChangeSet", + "description": "Caller provides their generation as `from_gen`, along with a query parameter for the page size (`limit`). Endpoint will return changes that have occured since the caller's generation number up to the latest change or until the `limit` is reached. If there are no changes, an empty vec is returned.", + "operationId": "ipv4_nat_changeset", "parameters": [ { "in": "path", - "name": "blueprint_id", - "description": "ID of the blueprint", + "name": "from_gen", + "description": "which change number to start generating the change set from", "required": true, "schema": { - "type": "string", - "format": "uuid" + "type": "integer", + "format": "int64" + } + }, + { + "in": "query", + "name": "limit", + "required": true, + "schema": { + "type": "integer", + "format": "uint32", + "minimum": 0 } } ], "responses": { - "204": { - "description": "successful deletion" + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "title": "Array_of_NatEntryView", + "type": "array", + "items": { + "$ref": "#/components/schemas/NatEntryView" + } + } + } + } }, "4XX": { "$ref": "#/components/responses/Error" @@ -534,102 +494,59 @@ } } }, - "/deployment/blueprints/import": { - "post": { - "summary": "Imports a client-provided blueprint", - "description": "This is intended for development and support, not end users or operators.", - "operationId": "blueprint_import", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Blueprint" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/deployment/blueprints/regenerate": { - "post": { - "summary": "Generates a new blueprint for the current system, re-evaluating anything", - "description": "that's changed since the last one was generated", - "operationId": "blueprint_regenerate", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Blueprint" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/deployment/blueprints/target": { + "/probes/{sled}": { "get": { - "summary": "Fetches the current target blueprint, if any", - "operationId": "blueprint_target_view", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BlueprintTarget" - } - } + "summary": "Get all the probes associated with a given sled.", + "operationId": "probes_get", + "parameters": [ + { + "in": "path", + "name": "sled", + "required": true, + "schema": { + "type": "string", + "format": "uuid" } }, - "4XX": { - "$ref": "#/components/responses/Error" + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - }, - "post": { - "summary": "Make the specified blueprint the new target", - "operationId": "blueprint_target_set", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BlueprintTargetSet" - } + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" } }, - "required": true - }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/IdSortMode" + } + } + ], "responses": { "200": { "description": "successful operation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/BlueprintTarget" + "title": "Array_of_ProbeInfo", + "type": "array", + "items": { + "$ref": "#/components/schemas/ProbeInfo" + } } } } @@ -640,33 +557,41 @@ "5XX": { "$ref": "#/components/responses/Error" } + }, + "x-dropshot-pagination": { + "required": [] } } }, - "/deployment/blueprints/target/enabled": { + "/racks/{rack_id}/initialization-complete": { "put": { - "summary": "Set the `enabled` field of the current target blueprint", - "operationId": "blueprint_target_set_enabled", + "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/BlueprintTargetSet" + "$ref": "#/components/schemas/RackInitializationRequest" } } }, "required": true }, "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BlueprintTarget" - } - } - } + "204": { + "description": "resource updated" }, "4XX": { "$ref": "#/components/responses/Error" @@ -677,17 +602,27 @@ } } }, - "/deployment/reconfigurator-config": { + "/sled-agents/{sled_id}": { "get": { - "summary": "Get the current reconfigurator configuration", - "operationId": "reconfigurator_config_show_current", + "summary": "Return information about the given sled agent", + "operationId": "sled_agent_get", + "parameters": [ + { + "in": "path", + "name": "sled_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + } + } + ], "responses": { "200": { "description": "successful operation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReconfiguratorConfigView" + "$ref": "#/components/schemas/SledAgentInfo" } } } @@ -701,13 +636,23 @@ } }, "post": { - "summary": "Update the reconfigurator config at the latest versions", - "operationId": "reconfigurator_config_set", + "summary": "Report that the sled agent for the specified sled has come online.", + "operationId": "sled_agent_put", + "parameters": [ + { + "in": "path", + "name": "sled_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + } + } + ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReconfiguratorConfigParam" + "$ref": "#/components/schemas/SledAgentInfo" } } }, @@ -726,29 +671,65 @@ } } }, - "/deployment/reconfigurator-config/{version}": { - "get": { - "summary": "Get the reconfigurator config at `version` if it exists", - "operationId": "reconfigurator_config_show", + "/sled-agents/{sled_id}/firewall-rules-update": { + "post": { + "summary": "Request a new set of firewall rules for a sled.", + "description": "This causes Nexus to read the latest set of rules for the sled, and call a Sled endpoint which applies the rules to all OPTE ports that happen to exist.", + "operationId": "sled_firewall_rules_request", "parameters": [ { "in": "path", - "name": "version", + "name": "sled_id", "required": true, "schema": { - "type": "integer", - "format": "uint32", - "minimum": 0 + "$ref": "#/components/schemas/TypedUuidForSledKind" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/switch/{switch_id}": { + "put": { + "operationId": "switch_put", + "parameters": [ + { + "in": "path", + "name": "switch_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SwitchPutRequest" + } + } + }, + "required": true + }, "responses": { "200": { "description": "successful operation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReconfiguratorConfigView" + "$ref": "#/components/schemas/SwitchPutResponse" } } } @@ -762,17 +743,18 @@ } } }, - "/deployment/update-status": { + "/v1/ping": { "get": { - "summary": "Show deployed versions of artifacts", - "operationId": "update_status", + "summary": "Ping API", + "description": "Always responds with Ok if it responds at all.", + "operationId": "ping", "responses": { "200": { "description": "successful operation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateStatus" + "$ref": "#/components/schemas/Ping" } } } @@ -786,22 +768,30 @@ } } }, - "/disk/{disk_id}/remove-read-only-parent": { - "post": { - "summary": "Request removal of a read_only_parent from a disk.", - "description": "This is a thin wrapper around the volume_remove_read_only_parent saga. All we are doing here is, given a disk UUID, figure out what the volume_id is for that disk, then use that to call the disk_remove_read_only_parent saga on it.", - "operationId": "cpapi_disk_remove_read_only_parent", + "/vmms/{propolis_id}": { + "put": { + "summary": "Report updated state for a VMM.", + "operationId": "cpapi_instances_put", "parameters": [ { "in": "path", - "name": "disk_id", + "name": "propolis_id", "required": true, "schema": { - "type": "string", - "format": "uuid" + "$ref": "#/components/schemas/TypedUuidForPropolisKind" } } ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SledVmmState" + } + } + }, + "required": true + }, "responses": { "204": { "description": "resource updated" @@ -815,31 +805,21 @@ } } }, - "/disks/{disk_id}": { - "put": { - "summary": "Report updated state for a disk.", - "operationId": "cpapi_disks_put", + "/volume/{volume_id}/remove-read-only-parent": { + "post": { + "summary": "Request removal of a read_only_parent from a volume.", + "description": "A volume can be created with the source data for that volume being another volume that attached as a \"read_only_parent\". In the background there exists a scrubber that will copy the data from the read_only_parent into the volume. When that scrubber has completed copying the data, this endpoint can be called to update the database that the read_only_parent is no longer needed for a volume and future attachments of this volume should not include that read_only_parent.", + "operationId": "cpapi_volume_remove_read_only_parent", "parameters": [ { "in": "path", - "name": "disk_id", + "name": "volume_id", "required": true, "schema": { - "type": "string", - "format": "uuid" + "$ref": "#/components/schemas/TypedUuidForVolumeKind" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DiskRuntimeState" - } - } - }, - "required": true - }, "responses": { "204": { "description": "resource updated" @@ -852,6431 +832,2996 @@ } } } - }, - "/experimental/v1/system/support-bundles": { - "get": { - "summary": "List all support bundles", - "operationId": "support_bundle_list", - "parameters": [ - { - "in": "query", - "name": "limit", - "description": "Maximum number of items returned by a single call", - "schema": { - "nullable": true, - "type": "integer", - "format": "uint32", - "minimum": 1 - } - }, - { - "in": "query", - "name": "page_token", - "description": "Token returned by previous call to retrieve the subsequent page", - "schema": { - "nullable": true, - "type": "string" - } - }, - { - "in": "query", - "name": "sort_by", - "schema": { - "$ref": "#/components/schemas/TimeAndIdSortMode" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SupportBundleInfoResultsPage" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - }, - "x-dropshot-pagination": { - "required": [] - } - }, - "post": { - "summary": "Create a new support bundle", - "operationId": "support_bundle_create", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SupportBundleCreate" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "successful creation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SupportBundleInfo" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/experimental/v1/system/support-bundles/{bundle_id}": { - "get": { - "summary": "View a support bundle", - "operationId": "support_bundle_view", - "parameters": [ - { - "in": "path", - "name": "bundle_id", - "description": "ID of the support bundle", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SupportBundleInfo" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - }, - "put": { - "summary": "Update a support bundle", - "operationId": "support_bundle_update", - "parameters": [ - { - "in": "path", - "name": "bundle_id", - "description": "ID of the support bundle", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SupportBundleUpdate" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SupportBundleInfo" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - }, - "delete": { - "summary": "Delete an existing support bundle", - "description": "May also be used to cancel a support bundle which is currently being collected, or to remove metadata for a support bundle that has failed.", - "operationId": "support_bundle_delete", - "parameters": [ - { - "in": "path", - "name": "bundle_id", - "description": "ID of the support bundle", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "204": { - "description": "successful deletion" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/experimental/v1/system/support-bundles/{bundle_id}/download": { - "get": { - "summary": "Download the contents of a support bundle", - "operationId": "support_bundle_download", - "parameters": [ - { - "in": "header", - "name": "range", - "description": "A request to access a portion of the resource, such as `bytes=0-499`\n\nSee: ", - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "bundle_id", - "description": "ID of the support bundle", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "default": { - "description": "", - "content": { - "*/*": { - "schema": {} - } - } - } - } - }, - "head": { - "summary": "Download the metadata of a support bundle", - "operationId": "support_bundle_head", - "parameters": [ - { - "in": "header", - "name": "range", - "description": "A request to access a portion of the resource, such as `bytes=0-499`\n\nSee: ", - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "bundle_id", - "description": "ID of the support bundle", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "default": { - "description": "", - "content": { - "*/*": { - "schema": {} - } - } - } - } - } - }, - "/experimental/v1/system/support-bundles/{bundle_id}/download/{file}": { - "get": { - "summary": "Download a file within a support bundle", - "operationId": "support_bundle_download_file", - "parameters": [ - { - "in": "header", - "name": "range", - "description": "A request to access a portion of the resource, such as `bytes=0-499`\n\nSee: ", - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "bundle_id", - "description": "ID of the support bundle", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "in": "path", - "name": "file", - "description": "The file within the bundle to download", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "default": { - "description": "", - "content": { - "*/*": { - "schema": {} - } - } - } - } - }, - "head": { - "summary": "Download the metadata of a file within the support bundle", - "operationId": "support_bundle_head_file", - "parameters": [ - { - "in": "header", - "name": "range", - "description": "A request to access a portion of the resource, such as `bytes=0-499`\n\nSee: ", - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "bundle_id", - "description": "ID of the support bundle", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "in": "path", - "name": "file", - "description": "The file within the bundle to download", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "default": { - "description": "", - "content": { - "*/*": { - "schema": {} - } - } - } - } - } - }, - "/experimental/v1/system/support-bundles/{bundle_id}/index": { - "get": { - "summary": "Download the index of a support bundle", - "operationId": "support_bundle_index", - "parameters": [ - { - "in": "header", - "name": "range", - "description": "A request to access a portion of the resource, such as `bytes=0-499`\n\nSee: ", - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "bundle_id", - "description": "ID of the support bundle", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "default": { - "description": "", - "content": { - "*/*": { - "schema": {} - } - } - } - } - } - }, - "/instances/{instance_id}/migrate": { - "post": { - "operationId": "instance_migrate", - "parameters": [ - { - "in": "path", - "name": "instance_id", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InstanceMigrateRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Instance" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/metrics/collectors": { - "post": { - "summary": "Accept a notification of a new oximeter collection server.", - "operationId": "cpapi_collectors_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OximeterInfo" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/metrics/collectors/{collector_id}/producers": { - "get": { - "summary": "List all metric producers assigned to an oximeter collector.", - "operationId": "cpapi_assigned_producers_list", - "parameters": [ - { - "in": "path", - "name": "collector_id", - "description": "The ID of the oximeter collector.", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "in": "query", - "name": "limit", - "description": "Maximum number of items returned by a single call", - "schema": { - "nullable": true, - "type": "integer", - "format": "uint32", - "minimum": 1 - } - }, - { - "in": "query", - "name": "page_token", - "description": "Token returned by previous call to retrieve the subsequent page", - "schema": { - "nullable": true, - "type": "string" - } - }, - { - "in": "query", - "name": "sort_by", - "schema": { - "$ref": "#/components/schemas/IdSortMode" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProducerEndpointResultsPage" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - }, - "x-dropshot-pagination": { - "required": [] - } - } - }, - "/metrics/producers": { - "post": { - "summary": "Accept a registration from a new metric producer", - "operationId": "cpapi_producers_post", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProducerEndpoint" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "successful creation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProducerRegistrationResponse" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/mgs-updates": { - "get": { - "summary": "Fetch information about ongoing MGS updates", - "operationId": "mgs_updates", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MgsUpdateDriverStatus" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/nat/ipv4/changeset/{from_gen}": { - "get": { - "summary": "Fetch NAT ChangeSet", - "description": "Caller provides their generation as `from_gen`, along with a query parameter for the page size (`limit`). Endpoint will return changes that have occured since the caller's generation number up to the latest change or until the `limit` is reached. If there are no changes, an empty vec is returned.", - "operationId": "ipv4_nat_changeset", - "parameters": [ - { - "in": "path", - "name": "from_gen", - "description": "which change number to start generating the change set from", - "required": true, - "schema": { - "type": "integer", - "format": "int64" - } - }, - { - "in": "query", - "name": "limit", - "required": true, - "schema": { - "type": "integer", - "format": "uint32", - "minimum": 0 - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "title": "Array_of_NatEntryView", - "type": "array", - "items": { - "$ref": "#/components/schemas/NatEntryView" - } - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/oximeter/read-policy": { - "get": { - "summary": "Get the current oximeter read policy", - "operationId": "oximeter_read_policy_get", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OximeterReadPolicy" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - }, - "post": { - "summary": "Set the new oximeter read policy", - "operationId": "oximeter_read_policy_set", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OximeterReadPolicy" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/physical-disk/expunge": { - "post": { - "summary": "Mark a physical disk as expunged", - "description": "This is an irreversible process! It should only be called after sufficient warning to the operator.\n\nThis is idempotent.", - "operationId": "physical_disk_expunge", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PhysicalDiskPath" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/probes/{sled}": { - "get": { - "summary": "Get all the probes associated with a given sled.", - "operationId": "probes_get", - "parameters": [ - { - "in": "path", - "name": "sled", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "in": "query", - "name": "limit", - "description": "Maximum number of items returned by a single call", - "schema": { - "nullable": true, - "type": "integer", - "format": "uint32", - "minimum": 1 - } - }, - { - "in": "query", - "name": "page_token", - "description": "Token returned by previous call to retrieve the subsequent page", - "schema": { - "nullable": true, - "type": "string" - } - }, - { - "in": "query", - "name": "sort_by", - "schema": { - "$ref": "#/components/schemas/IdSortMode" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "title": "Array_of_ProbeInfo", - "type": "array", - "items": { - "$ref": "#/components/schemas/ProbeInfo" - } - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - }, - "x-dropshot-pagination": { - "required": [] - } - } - }, - "/quiesce": { - "get": { - "summary": "Check whether Nexus is running normally, quiescing, or fully quiesced.", - "operationId": "quiesce_get", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/QuiesceStatus" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - }, - "post": { - "summary": "Begin quiescing this Nexus instance", - "description": "This causes no new sagas to be started and eventually causes no database connections to become available. This is a one-way trip. There's no unquiescing Nexus.", - "operationId": "quiesce_start", - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/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", - "operationId": "saga_list", - "parameters": [ - { - "in": "query", - "name": "limit", - "description": "Maximum number of items returned by a single call", - "schema": { - "nullable": true, - "type": "integer", - "format": "uint32", - "minimum": 1 - } - }, - { - "in": "query", - "name": "page_token", - "description": "Token returned by previous call to retrieve the subsequent page", - "schema": { - "nullable": true, - "type": "string" - } - }, - { - "in": "query", - "name": "sort_by", - "schema": { - "$ref": "#/components/schemas/IdSortMode" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SagaResultsPage" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - }, - "x-dropshot-pagination": { - "required": [] - } - } - }, - "/sagas/{saga_id}": { - "get": { - "summary": "Fetch a saga", - "operationId": "saga_view", - "parameters": [ - { - "in": "path", - "name": "saga_id", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Saga" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/sled-agents/{sled_id}": { - "get": { - "summary": "Return information about the given sled agent", - "operationId": "sled_agent_get", - "parameters": [ - { - "in": "path", - "name": "sled_id", - "required": true, - "schema": { - "$ref": "#/components/schemas/TypedUuidForSledKind" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SledAgentInfo" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - }, - "post": { - "summary": "Report that the sled agent for the specified sled has come online.", - "operationId": "sled_agent_put", - "parameters": [ - { - "in": "path", - "name": "sled_id", - "required": true, - "schema": { - "$ref": "#/components/schemas/TypedUuidForSledKind" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SledAgentInfo" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/sled-agents/{sled_id}/firewall-rules-update": { - "post": { - "summary": "Request a new set of firewall rules for a sled.", - "description": "This causes Nexus to read the latest set of rules for the sled, and call a Sled endpoint which applies the rules to all OPTE ports that happen to exist.", - "operationId": "sled_firewall_rules_request", - "parameters": [ - { - "in": "path", - "name": "sled_id", - "required": true, - "schema": { - "$ref": "#/components/schemas/TypedUuidForSledKind" - } - } - ], - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/sleds/add": { - "post": { - "summary": "Add sled to initialized rack", - "operationId": "sled_add", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UninitializedSledId" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "successful creation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SledId" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/sleds/expunge": { - "post": { - "summary": "Mark a sled as expunged", - "description": "This is an irreversible process! It should only be called after sufficient warning to the operator.\n\nThis is idempotent, and it returns the old policy of the sled.", - "operationId": "sled_expunge", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SledSelector" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SledPolicy" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/sleds/uninitialized": { - "get": { - "summary": "List uninitialized sleds", - "operationId": "sled_list_uninitialized", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UninitializedSledResultsPage" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/switch/{switch_id}": { - "put": { - "operationId": "switch_put", - "parameters": [ - { - "in": "path", - "name": "switch_id", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SwitchPutRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SwitchPutResponse" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/v1/ping": { - "get": { - "summary": "Ping API", - "description": "Always responds with Ok if it responds at all.", - "operationId": "ping", - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Ping" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/vmms/{propolis_id}": { - "put": { - "summary": "Report updated state for a VMM.", - "operationId": "cpapi_instances_put", - "parameters": [ - { - "in": "path", - "name": "propolis_id", - "required": true, - "schema": { - "$ref": "#/components/schemas/TypedUuidForPropolisKind" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SledVmmState" - } - } - }, - "required": true - }, - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - }, - "/volume/{volume_id}/remove-read-only-parent": { - "post": { - "summary": "Request removal of a read_only_parent from a volume.", - "description": "A volume can be created with the source data for that volume being another volume that attached as a \"read_only_parent\". In the background there exists a scrubber that will copy the data from the read_only_parent into the volume. When that scrubber has completed copying the data, this endpoint can be called to update the database that the read_only_parent is no longer needed for a volume and future attachments of this volume should not include that read_only_parent.", - "operationId": "cpapi_volume_remove_read_only_parent", - "parameters": [ - { - "in": "path", - "name": "volume_id", - "required": true, - "schema": { - "$ref": "#/components/schemas/TypedUuidForVolumeKind" - } - } - ], - "responses": { - "204": { - "description": "resource updated" - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - } - } - } - }, - "components": { - "schemas": { - "ActivationReason": { - "description": "Describes why a background task was activated\n\nThis is only used for debugging. This is deliberately not made available to the background task itself. See \"Design notes\" in the module-level documentation for details.", - "type": "string", - "enum": [ - "signaled", - "timeout", - "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", - "pattern": "^[a-zA-Z0-9._+-]{1,63}$" - }, - "BackgroundTask": { - "description": "Background tasks\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", - "properties": { - "current": { - "description": "Describes the current task status", - "allOf": [ - { - "$ref": "#/components/schemas/CurrentStatus" - } - ] - }, - "description": { - "description": "brief summary (for developers) of what this task does", - "type": "string" - }, - "last": { - "description": "Describes the last completed activation", - "allOf": [ - { - "$ref": "#/components/schemas/LastResult" - } - ] - }, - "name": { - "description": "unique identifier for this background task", - "type": "string" - }, - "period": { - "description": "how long after an activation completes before another will be triggered automatically\n\n(activations can also be triggered for other reasons)", - "allOf": [ - { - "$ref": "#/components/schemas/Duration" - } - ] - } - }, - "required": [ - "current", - "description", - "last", - "name", - "period" - ] - }, - "BackgroundTasksActivateRequest": { - "description": "Query parameters for Background Task activation requests.", - "type": "object", - "properties": { - "bgtask_names": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - } - }, - "required": [ - "bgtask_names" - ] - }, - "Baseboard": { - "description": "Properties that uniquely identify an Oxide hardware component", - "type": "object", - "properties": { - "part": { - "type": "string" - }, - "revision": { - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "serial": { - "type": "string" - } - }, - "required": [ - "part", - "revision", - "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" - ] - }, - "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" - ] - }, - "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" - } - ] - }, - "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/TypedUuidForBlueprintKind" - } - ] - }, - "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/TypedUuidForBlueprintKind" - } - ] - }, - "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.", - "type": "object", - "properties": { - "artifact_version": { - "type": "string", - "enum": [ - "available" - ] - }, - "version": { - "$ref": "#/components/schemas/ArtifactVersion" - } - }, - "required": [ - "artifact_version", - "version" - ] - }, - { - "description": "The version could not be determined. This is non-fatal.", - "type": "object", - "properties": { - "artifact_version": { - "type": "string", - "enum": [ - "unknown" - ] - } - }, - "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/TypedUuidForDatasetKind" - }, - "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" - ] - }, - { - "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.", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "current_contents" - ] - } - }, - "required": [ - "type" - ] - }, - { - "description": "Set the phase 2 slot to the given artifact.\n\nThe artifact will come from an unpacked and distributed TUF repo.", - "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" - ] - } - ] - }, - "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" - ] - }, - "BlueprintMetadata": { - "description": "Describe high-level metadata about a blueprint", - "type": "object", - "properties": { - "cockroachdb_fingerprint": { - "description": "CockroachDB state fingerprint when this blueprint was created", - "type": "string" - }, - "cockroachdb_setting_preserve_downgrade": { - "nullable": true, - "description": "Whether to set `cluster.preserve_downgrade_option` and what to set it to (`None` if this value was retrieved from the database and was invalid)", - "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/TypedUuidForBlueprintKind" - } - ] - }, - "internal_dns_version": { - "description": "internal DNS version when this blueprint was created", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "nexus_generation": { - "description": "The Nexus generation number\n\nSee [`Blueprint::nexus_generation`].", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "parent_blueprint_id": { - "nullable": true, - "description": "which blueprint this blueprint is based on", - "allOf": [ - { - "$ref": "#/components/schemas/TypedUuidForBlueprintKind" - } - ] - }, - "source": { - "description": "source of the blueprint (for debugging)", - "allOf": [ - { - "$ref": "#/components/schemas/BlueprintSource" - } - ] - }, - "target_release_minimum_generation": { - "description": "The minimum generation for the target release.\n\nSee [`Blueprint::target_release_minimum_generation`].", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "time_created": { - "description": "when this blueprint was generated (for debugging)", - "type": "string", - "format": "date-time" - } - }, - "required": [ - "cockroachdb_fingerprint", - "comment", - "creator", - "external_dns_version", - "id", - "internal_dns_version", - "nexus_generation", - "source", - "target_release_minimum_generation", - "time_created" - ] - }, - "BlueprintMetadataResultsPage": { - "description": "A single page of results", - "type": "object", - "properties": { - "items": { - "description": "list of items on this page of results", - "type": "array", - "items": { - "$ref": "#/components/schemas/BlueprintMetadata" - } - }, - "next_page": { - "nullable": true, - "description": "token used to fetch the next page of results (if any)", - "type": "string" - } - }, - "required": [ - "items" - ] - }, - "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/TypedUuidForPhysicalDiskKind" - }, - "identity": { - "$ref": "#/components/schemas/DiskIdentity" - }, - "pool_id": { - "$ref": "#/components/schemas/TypedUuidForZpoolKind" - } - }, - "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.", - "type": "object", - "properties": { - "kind": { - "type": "string", - "enum": [ - "in_service" - ] - } - }, - "required": [ - "kind" - ] - }, - { - "description": "The physical disk is permanently gone.", - "type": "object", - "properties": { - "as_of_generation": { - "description": "Generation of the parent config in which this disk became expunged.", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] - }, - "kind": { - "type": "string", - "enum": [ - "expunged" - ] - }, - "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/TypedUuidForMupdateOverrideKind" - } - ] - }, - "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" - } - ] - }, - "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.", - "type": "object", - "properties": { - "source": { - "type": "string", - "enum": [ - "rss" - ] - } - }, - "required": [ - "source" - ] - }, - { - "description": "A blueprint created by the planner, and we still have the associated planning report.", - "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" - } - ] - }, - "source": { - "type": "string", - "enum": [ - "planner" - ] - }, - "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" - ] - }, - { - "description": "A blueprint created by the planner but loaded from the database, so we no longer have the associated planning report.", - "type": "object", - "properties": { - "source": { - "type": "string", - "enum": [ - "planner_loaded_from_database" - ] - } - }, - "required": [ - "source" - ] - }, - { - "description": "This blueprint was created by one of `reconfigurator-cli`'s blueprint editing subcommands.", - "type": "object", - "properties": { - "source": { - "type": "string", - "enum": [ - "reconfigurator_cli_edit" - ] - } - }, - "required": [ - "source" - ] - }, - { - "description": "This blueprint was constructed by hand by an automated test.", - "type": "object", - "properties": { - "source": { - "type": "string", - "enum": [ - "test" - ] - } - }, - "required": [ - "source" - ] - } - ] - }, - "BlueprintTarget": { - "description": "Describes what blueprint, if any, the system is currently working toward", - "type": "object", - "properties": { - "enabled": { - "description": "policy: should the system actively work towards this blueprint\n\nThis should generally be left enabled.", - "type": "boolean" - }, - "target_id": { - "description": "id of the blueprint that the system is trying to make real", - "allOf": [ - { - "$ref": "#/components/schemas/TypedUuidForBlueprintKind" - } - ] - }, - "time_made_target": { - "description": "when this blueprint was made the target", - "type": "string", - "format": "date-time" - } - }, - "required": [ - "enabled", - "target_id", - "time_made_target" - ] - }, - "BlueprintTargetSet": { - "description": "Specifies what blueprint, if any, the system should be working toward", - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "target_id": { - "$ref": "#/components/schemas/TypedUuidForBlueprintKind" - } - }, - "required": [ - "enabled", - "target_id" - ] - }, - "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/TypedUuidForOmicronZoneKind" - }, - "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" - ] - }, + } + }, + "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": { - "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": { + "allow": { "type": "string", "enum": [ - "external_dns" + "any" ] } }, "required": [ - "dataset", - "dns_address", - "http_address", - "nic", - "type" + "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": { - "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": { + "allow": { "type": "string", "enum": [ - "internal_dns" + "list" ] - } - }, - "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.", + "ips": { "type": "array", "items": { - "type": "string", - "format": "ip" + "$ref": "#/components/schemas/IpNet" } - }, - "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" + "allow", + "ips" ] - }, - { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "oximeter" - ] - } + } + ] + }, + "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", + "properties": { + "bgtask_names": { + "type": "array", + "items": { + "type": "string" }, - "required": [ - "address", - "type" - ] + "uniqueItems": true } + }, + "required": [ + "bgtask_names" ] }, - "ByteCount": { - "description": "Byte count to express memory or storage capacity.", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "Certificate": { + "Baseboard": { + "description": "Properties that uniquely identify an Oxide hardware component", "type": "object", "properties": { - "cert": { + "part": { "type": "string" }, - "key": { + "revision": { + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "serial": { "type": "string" } }, "required": [ - "cert", - "key" + "part", + "revision", + "serial" ] }, - "ClickhouseClusterConfig": { - "description": "Global configuration for all clickhouse servers (replicas) and keepers", + "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": { - "cluster_name": { - "description": "An arbitrary name for the Clickhouse cluster shared by all nodes", + "part_number": { + "description": "Oxide Part Number", "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.", + "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 }, - "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" - } - ] + "local": { + "nullable": true, + "type": "string", + "format": "ip" }, - "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.", + "mode": { + "$ref": "#/components/schemas/BfdMode" + }, + "remote": { + "type": "string", + "format": "ip" + }, + "required_rx": { "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" + "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" } }, - "max_used_keeper_id": { - "description": "Clickhouse Keeper IDs must be unique and are handed out monotonically. Keep track of the last used one.", + "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/KeeperId" + "$ref": "#/components/schemas/ImportExportPolicy" } ] }, - "max_used_server_id": { - "description": "Clickhouse Server IDs must be unique and are handed out monotonically. Keep track of the last used one.", + "allowed_import": { + "description": "Define import policy for a peer.", + "default": { + "type": "no_filtering" + }, "allOf": [ { - "$ref": "#/components/schemas/ServerId" + "$ref": "#/components/schemas/ImportExportPolicy" } ] }, - "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" + "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 } - } - }, - "required": [ - "cluster_name", - "cluster_secret", - "generation", - "highest_seen_keeper_leader_committed_log_index", - "keepers", - "max_used_keeper_id", - "max_used_server_id", - "servers" - ] - }, - "ClickhouseMode": { - "description": "How to deploy clickhouse nodes", - "oneOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "single_node_only" - ] - } - }, - "required": [ - "type" - ] }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "cluster_only" - ] - }, - "value": { - "type": "object", - "properties": { - "target_keepers": { - "type": "integer", - "format": "uint8", - "minimum": 0 - }, - "target_servers": { - "type": "integer", - "format": "uint8", - "minimum": 0 - } - }, - "required": [ - "target_keepers", - "target_servers" - ] - } - }, - "required": [ - "type", - "value" - ] + "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" }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "both" - ] - }, - "value": { - "type": "object", - "properties": { - "target_keepers": { - "type": "integer", - "format": "uint8", - "minimum": 0 - }, - "target_servers": { - "type": "integer", - "format": "uint8", - "minimum": 0 - } - }, - "required": [ - "target_keepers", - "target_servers" - ] - } - }, - "required": [ - "type", - "value" - ] - } - ] - }, - "ClickhousePolicy": { - "type": "object", - "properties": { - "mode": { - "$ref": "#/components/schemas/ClickhouseMode" + "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 }, - "time_created": { - "type": "string", - "format": "date-time" + "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 }, - "version": { + "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": [ - "mode", - "time_created", - "version" - ] - }, - "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" - ] - } + "addr", + "asn", + "port" ] }, - "CockroachdbUnsafeToShutdown": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "missing_live_nodes_stat" - ] + "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" } - }, - "required": [ - "type" ] }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "missing_underreplicated_stat" - ] - } - }, - "required": [ - "type" - ] + "cockroachdb_fingerprint": { + "description": "CockroachDB state fingerprint when this blueprint was created", + "type": "string" }, - { - "type": "object", - "properties": { - "live_nodes": { - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "type": { - "type": "string", - "enum": [ - "not_enough_live_nodes" - ] + "cockroachdb_setting_preserve_downgrade": { + "description": "Whether to set `cluster.preserve_downgrade_option` and what to set it to", + "allOf": [ + { + "$ref": "#/components/schemas/CockroachDbPreserveDowngrade" } - }, - "required": [ - "live_nodes", - "type" ] }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "not_enough_nodes" - ] - } - }, - "required": [ - "type" - ] + "comment": { + "description": "human-readable string describing why this blueprint was created (for debugging)", + "type": "string" }, - { - "type": "object", - "properties": { - "n": { - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "type": { - "type": "string", - "enum": [ - "underreplicated_ranges" - ] - } - }, - "required": [ - "n", - "type" - ] - } - ] - }, - "CompletedAttempt": { - "description": "externally-exposed status for a completed attempt", - "type": "object", - "properties": { - "elapsed": { - "$ref": "#/components/schemas/Duration" + "creator": { + "description": "identity of the component that generated the blueprint (for debugging) This would generally be the Uuid of a Nexus instance.", + "type": "string" }, - "nattempts_done": { - "type": "integer", - "format": "uint32", - "minimum": 0 + "external_dns_version": { + "description": "external DNS version when this blueprint was created", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] }, - "request": { - "$ref": "#/components/schemas/PendingMgsUpdate" + "id": { + "description": "unique identifier for this blueprint", + "allOf": [ + { + "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + } + ] }, - "result": { - "x-rust-type": { - "crate": "std", - "parameters": [ - { - "$ref": "#/components/schemas/UpdateCompletedHow" - }, - { - "type": "string" - } - ], - "path": "::std::result::Result", - "version": "*" - }, - "oneOf": [ + "internal_dns_version": { + "description": "internal DNS version when this blueprint was created", + "allOf": [ { - "type": "object", - "properties": { - "ok": { - "$ref": "#/components/schemas/UpdateCompletedHow" - } - }, - "required": [ - "ok" - ] - }, + "$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": [ { - "type": "object", - "properties": { - "err": { - "type": "string" - } - }, - "required": [ - "err" - ] + "$ref": "#/components/schemas/Generation" } ] }, - "time_done": { - "type": "string", - "format": "date-time" + "oximeter_read_mode": { + "description": "Whether oximeter should read from a single node or a cluster", + "allOf": [ + { + "$ref": "#/components/schemas/OximeterReadMode" + } + ] }, - "time_started": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "elapsed", - "nattempts_done", - "request", - "result", - "time_done", - "time_started" - ] - }, - "CompressionAlgorithm": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "on" - ] + "oximeter_read_version": { + "description": "Oximeter read policy version when this blueprint was created", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" } - }, - "required": [ - "type" ] }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "off" - ] + "parent_blueprint_id": { + "nullable": true, + "description": "which blueprint this blueprint is based on", + "allOf": [ + { + "$ref": "#/components/schemas/TypedUuidForBlueprintKind" } - }, - "required": [ - "type" ] }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "gzip" - ] + "pending_mgs_updates": { + "description": "List of pending MGS-mediated updates", + "allOf": [ + { + "$ref": "#/components/schemas/PendingMgsUpdates" } - }, - "required": [ - "type" ] }, - { + "sleds": { + "description": "A map of sled id -> desired configuration of the sled.", "type": "object", - "properties": { - "level": { - "$ref": "#/components/schemas/GzipLevel" - }, - "type": { - "type": "string", - "enum": [ - "gzip_n" - ] + "additionalProperties": { + "$ref": "#/components/schemas/BlueprintSledConfig" + } + }, + "source": { + "description": "Source of this blueprint (can include planning report)", + "allOf": [ + { + "$ref": "#/components/schemas/BlueprintSource" } - }, - "required": [ - "level", - "type" ] }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "lz4" - ] + "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" } - }, - "required": [ - "type" ] }, + "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.", "type": "object", "properties": { - "type": { + "artifact_version": { "type": "string", "enum": [ - "lzjb" + "available" ] + }, + "version": { + "$ref": "#/components/schemas/ArtifactVersion" } }, "required": [ - "type" + "artifact_version", + "version" ] }, { + "description": "The version could not be determined. This is non-fatal.", "type": "object", "properties": { - "type": { + "artifact_version": { "type": "string", "enum": [ - "zle" + "unknown" ] } }, "required": [ - "type" + "artifact_version" ] } ] }, - "CrucibleDatasetCreateRequest": { + "BlueprintDatasetConfig": { + "description": "Information about a dataset as recorded in a blueprint", "type": "object", "properties": { "address": { + "nullable": true, "type": "string" }, - "dataset_id": { + "compression": { + "$ref": "#/components/schemas/CompressionAlgorithm" + }, + "disposition": { + "$ref": "#/components/schemas/BlueprintDatasetDisposition" + }, + "id": { "$ref": "#/components/schemas/TypedUuidForDatasetKind" }, - "zpool_id": { - "$ref": "#/components/schemas/TypedUuidForZpoolKind" + "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": [ - "address", - "dataset_id", - "zpool_id" + "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" + ] + }, + { + "description": "The dataset is permanently gone.", + "type": "string", + "enum": [ + "expunged" + ] + } ] }, - "CurrentStatus": { - "description": "Describes the current status of a background task", + "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": "The background task is not running\n\nTypically, the task would be waiting for its next activation, which would happen after a timeout or some other event that triggers activation", + "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.", "type": "object", "properties": { - "current_status": { + "type": { "type": "string", "enum": [ - "idle" + "current_contents" ] } }, "required": [ - "current_status" + "type" ] }, { - "description": "The background task is currently running\n\nMore precisely, the task has been activated and has not yet finished this activation", + "description": "Set the phase 2 slot to the given artifact.\n\nThe artifact will come from an unpacked and distributed TUF repo.", "type": "object", "properties": { - "current_status": { + "hash": { + "type": "string", + "format": "hex string (32 bytes)" + }, + "type": { "type": "string", "enum": [ - "running" + "artifact" ] }, - "details": { - "$ref": "#/components/schemas/CurrentStatusRunning" + "version": { + "$ref": "#/components/schemas/BlueprintArtifactVersion" } }, "required": [ - "current_status", - "details" + "hash", + "type", + "version" ] } ] }, - "CurrentStatusRunning": { + "BlueprintHostPhase2DesiredSlots": { + "description": "Describes the desired contents for both host phase 2 slots.\n\nThis is the blueprint version of [`HostPhase2DesiredSlots`].", "type": "object", "properties": { - "iteration": { - "description": "which iteration this was (counter)", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "reason": { - "description": "what kind of event triggered this activation", - "allOf": [ - { - "$ref": "#/components/schemas/ActivationReason" - } - ] + "slot_a": { + "$ref": "#/components/schemas/BlueprintHostPhase2DesiredContents" }, - "start_time": { - "description": "wall-clock time when the current activation started", - "type": "string", - "format": "date-time" + "slot_b": { + "$ref": "#/components/schemas/BlueprintHostPhase2DesiredContents" } }, "required": [ - "iteration", - "reason", - "start_time" + "slot_a", + "slot_b" ] }, - "DatasetKind": { - "description": "The kind of dataset. See the `DatasetKind` enum in omicron-common for possible values.", - "type": "string" - }, - "DemoSaga": { - "description": "Identifies an instance of the demo saga", + "BlueprintPhysicalDiskConfig": { + "description": "Information about an Omicron physical disk as recorded in a bluerprint.", "type": "object", "properties": { - "demo_saga_id": { - "$ref": "#/components/schemas/TypedUuidForDemoSagaKind" + "disposition": { + "$ref": "#/components/schemas/BlueprintPhysicalDiskDisposition" }, - "saga_id": { - "type": "string", - "format": "uuid" + "id": { + "$ref": "#/components/schemas/TypedUuidForPhysicalDiskKind" + }, + "identity": { + "$ref": "#/components/schemas/DiskIdentity" + }, + "pool_id": { + "$ref": "#/components/schemas/TypedUuidForZpoolKind" } }, "required": [ - "demo_saga_id", - "saga_id" + "disposition", + "id", + "identity", + "pool_id" ] }, - "DiscretionaryZonePlacement": { - "type": "object", - "properties": { - "kind": { - "type": "string" + "BlueprintPhysicalDiskDisposition": { + "description": "The desired state of an Omicron-managed physical disk in a blueprint.", + "oneOf": [ + { + "description": "The physical disk is in-service.", + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "in_service" + ] + } + }, + "required": [ + "kind" + ] }, - "source": { - "type": "string" + { + "description": "The physical disk is permanently gone.", + "type": "object", + "properties": { + "as_of_generation": { + "description": "Generation of the parent config in which this disk became expunged.", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] + }, + "kind": { + "type": "string", + "enum": [ + "expunged" + ] + }, + "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" + ] } - }, - "required": [ - "kind", - "source" ] }, - "DiskIdentity": { - "description": "Uniquely identifies a disk.", + "BlueprintSledConfig": { + "description": "Information about the configuration of a sled as recorded in a blueprint.\n\nPart of [`Blueprint`].", "type": "object", "properties": { - "model": { - "type": "string" + "datasets": { + "$ref": "#/components/schemas/IdMapBlueprintDatasetConfig" }, - "serial": { - "type": "string" + "disks": { + "$ref": "#/components/schemas/IdMapBlueprintPhysicalDiskConfig" }, - "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", + "host_phase_2": { + "$ref": "#/components/schemas/BlueprintHostPhase2DesiredSlots" + }, + "remove_mupdate_override": { + "nullable": true, "allOf": [ { - "$ref": "#/components/schemas/DiskState" + "$ref": "#/components/schemas/TypedUuidForMupdateOverrideKind" } ] }, - "gen": { - "description": "generation number for this state", + "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" } ] }, - "time_updated": { - "description": "timestamp for this information", - "type": "string", - "format": "date-time" + "state": { + "$ref": "#/components/schemas/SledState" + }, + "zones": { + "$ref": "#/components/schemas/IdMapBlueprintZoneConfig" } }, "required": [ - "disk_state", - "gen", - "time_updated" + "datasets", + "disks", + "host_phase_2", + "sled_agent_generation", + "state", + "zones" ] }, - "DiskState": { - "description": "State of a Disk", + "BlueprintSource": { + "description": "Description of the source of a blueprint.", "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", + "description": "The initial blueprint created by the rack setup service.", "type": "object", "properties": { - "state": { + "source": { "type": "string", "enum": [ - "detached" + "rss" ] } }, "required": [ - "state" + "source" ] }, { - "description": "Disk is ready to receive blocks from an external source", + "description": "A blueprint created by the planner, and we still have the associated planning report.", "type": "object", "properties": { - "state": { - "type": "string", - "enum": [ - "import_ready" + "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" + } ] - } - }, - "required": [ - "state" - ] - }, - { - "description": "Disk is importing blocks from a URL", - "type": "object", - "properties": { - "state": { + }, + "source": { "type": "string", "enum": [ - "importing_from_url" + "planner" ] + }, + "zone_updates": { + "$ref": "#/components/schemas/PlanningZoneUpdatesStepReport" } }, "required": [ - "state" + "add", + "cockroachdb_settings", + "decommission", + "expunge", + "mgs_updates", + "nexus_generation_bump", + "noop_image_source", + "planner_config", + "source", + "zone_updates" ] }, { - "description": "Disk is importing blocks from bulk writes", + "description": "A blueprint created by the planner but loaded from the database, so we no longer have the associated planning report.", "type": "object", "properties": { - "state": { + "source": { "type": "string", "enum": [ - "importing_from_bulk_writes" + "planner_loaded_from_database" ] } }, "required": [ - "state" + "source" ] }, { - "description": "Disk is being finalized to state Detached", + "description": "This blueprint was created by one of `reconfigurator-cli`'s blueprint editing subcommands.", "type": "object", "properties": { - "state": { + "source": { "type": "string", "enum": [ - "finalizing" + "reconfigurator_cli_edit" ] } }, "required": [ - "state" + "source" ] }, { - "description": "Disk is undergoing maintenance", + "description": "This blueprint was constructed by hand by an automated test.", "type": "object", "properties": { - "state": { + "source": { "type": "string", "enum": [ - "maintenance" + "test" ] } }, "required": [ - "state" + "source" + ] + } + ] + }, + "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/TypedUuidForOmicronZoneKind" + }, + "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": "Disk is being attached to the given Instance", + "description": "The zone is in-service.", "type": "object", "properties": { - "instance": { - "type": "string", - "format": "uuid" - }, - "state": { + "kind": { "type": "string", "enum": [ - "attaching" + "in_service" ] } }, "required": [ - "instance", - "state" + "kind" ] }, { - "description": "Disk is attached to the given Instance", + "description": "The zone is permanently gone.", "type": "object", "properties": { - "instance": { - "type": "string", - "format": "uuid" + "as_of_generation": { + "description": "Generation of the parent config in which this zone became expunged.", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] }, - "state": { + "kind": { "type": "string", "enum": [ - "attached" + "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": [ - "instance", - "state" + "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": "Disk is being detached from the given Instance", + "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": { - "instance": { - "type": "string", - "format": "uuid" - }, - "state": { + "type": { "type": "string", "enum": [ - "detaching" + "install_dataset" ] } }, "required": [ - "instance", - "state" + "type" ] }, { - "description": "Disk has been destroyed", + "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": { - "state": { + "hash": { + "type": "string", + "format": "hex string (32 bytes)" + }, + "type": { "type": "string", "enum": [ - "destroyed" + "artifact" ] + }, + "version": { + "$ref": "#/components/schemas/BlueprintArtifactVersion" } }, "required": [ - "state" + "hash", + "type", + "version" ] - }, + } + ] + }, + "BlueprintZoneType": { + "oneOf": [ { - "description": "Disk is unavailable", "type": "object", "properties": { - "state": { + "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": [ - "faulted" + "boundary_ntp" ] } }, "required": [ - "state" + "address", + "dns_servers", + "external_ip", + "nic", + "ntp_servers", + "type" ] - } - ] - }, - "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": [ { + "description": "Used in single-node clickhouse setups", "type": "object", "properties": { - "data": { - "type": "string", - "format": "ipv4" + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" }, "type": { "type": "string", "enum": [ - "A" + "clickhouse" ] } }, "required": [ - "data", + "address", + "dataset", "type" ] }, { "type": "object", "properties": { - "data": { - "type": "string", - "format": "ipv6" + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" }, "type": { "type": "string", "enum": [ - "AAAA" + "clickhouse_keeper" ] } }, "required": [ - "data", + "address", + "dataset", "type" ] }, { + "description": "Used in replicated clickhouse setups", "type": "object", "properties": { - "data": { - "$ref": "#/components/schemas/Srv" + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" }, "type": { "type": "string", "enum": [ - "SRV" + "clickhouse_server" ] } }, "required": [ - "data", + "address", + "dataset", "type" ] }, { "type": "object", "properties": { - "data": { + "address": { "type": "string" }, + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" + }, "type": { "type": "string", "enum": [ - "NS" + "cockroach_db" ] } }, "required": [ - "data", + "address", + "dataset", "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" - ] - }, - "DownstairsUnderRepair": { - "type": "object", - "properties": { - "region_uuid": { - "$ref": "#/components/schemas/TypedUuidForDownstairsRegionKind" - }, - "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": { + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" + }, + "type": { "type": "string", "enum": [ - "no_valid_version" + "crucible" ] } }, "required": [ - "kind" + "address", + "dataset", + "type" ] }, { - "description": "We expect to find the specified version in this slot", "type": "object", "properties": { - "kind": { + "address": { + "type": "string" + }, + "type": { "type": "string", "enum": [ - "version" + "crucible_pantry" ] - }, - "version": { - "$ref": "#/components/schemas/ArtifactVersion" } }, "required": [ - "kind", - "version" + "address", + "type" ] - } - ] - }, - "ExternalPortDiscovery": { - "oneOf": [ + }, { "type": "object", "properties": { - "auto": { - "type": "object", - "additionalProperties": { - "type": "string", - "format": "ipv6" - } + "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": [ - "auto" - ], - "additionalProperties": false + "dataset", + "dns_address", + "http_address", + "nic", + "type" + ] }, { "type": "object", "properties": { - "static": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Name" - } - } + "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": [ - "static" - ], - "additionalProperties": false - } - ] - }, - "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 - }, - "HeldDbClaimInfo": { - "description": "Describes an outstanding database claim (for debugging why quiesce is stuck)", - "type": "object", - "properties": { - "debug": { - "type": "string" - }, - "held_since": { - "type": "string", - "format": "date-time" + "dataset", + "dns_address", + "gz_address", + "gz_address_index", + "http_address", + "type" + ] }, - "id": { - "type": "integer", - "format": "uint64", - "minimum": 0 - } - }, - "required": [ - "debug", - "held_since", - "id" - ] - }, - "HostPhase1Status": { - "oneOf": [ { - "description": "This device has no host phase 1 status because it is not a sled (e.g., it's a PSC or switch).", "type": "object", "properties": { - "kind": { + "address": { + "type": "string" + }, + "type": { "type": "string", "enum": [ - "not_a_sled" + "internal_ntp" ] } }, "required": [ - "kind" + "address", + "type" ] }, { "type": "object", "properties": { - "active_slot": { - "nullable": true, + "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/M2Slot" + "$ref": "#/components/schemas/OmicronZoneExternalFloatingIp" } ] }, - "kind": { - "type": "string", - "enum": [ - "sled" - ] + "external_tls": { + "description": "Whether Nexus's external endpoint should use TLS", + "type": "boolean" }, - "sled_id": { - "nullable": true, + "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/TypedUuidForSledKind" + "$ref": "#/components/schemas/Generation" } ] }, - "slot_a_version": { - "$ref": "#/components/schemas/TufRepoVersion" - }, - "slot_b_version": { - "$ref": "#/components/schemas/TufRepoVersion" - } - }, - "required": [ - "kind", - "slot_a_version", - "slot_b_version" - ] - } - ] - }, - "HostPhase2Status": { - "type": "object", - "properties": { - "boot_disk": { - "x-rust-type": { - "crate": "std", - "parameters": [ - { - "$ref": "#/components/schemas/M2Slot" - }, - { - "type": "string" - } - ], - "path": "::std::result::Result", - "version": "*" - }, - "oneOf": [ - { - "type": "object", - "properties": { - "ok": { - "$ref": "#/components/schemas/M2Slot" + "nic": { + "description": "The service vNIC providing external connectivity using OPTE.", + "allOf": [ + { + "$ref": "#/components/schemas/NetworkInterface" } - }, - "required": [ - "ok" ] }, - { - "type": "object", - "properties": { - "err": { - "type": "string" - } - }, - "required": [ - "err" - ] - } - ] - }, - "slot_a_version": { - "$ref": "#/components/schemas/TufRepoVersion" - }, - "slot_b_version": { - "$ref": "#/components/schemas/TufRepoVersion" - } - }, - "required": [ - "boot_disk", - "slot_a_version", - "slot_b_version" - ] - }, - "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" + "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": [ - "allow" + "oximeter" ] - }, - "value": { - "type": "array", - "items": { - "$ref": "#/components/schemas/IpNet" - } } }, "required": [ - "type", - "value" + "address", + "type" ] } ] }, - "InProgressUpdateStatus": { - "description": "externally-exposed status for each in-progress update", - "type": "object", - "properties": { - "baseboard_id": { - "$ref": "#/components/schemas/BaseboardId" - }, - "nattempts_done": { - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "status": { - "$ref": "#/components/schemas/UpdateAttemptStatus" - }, - "time_started": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "baseboard_id", - "nattempts_done", - "status", - "time_started" - ] + "ByteCount": { + "description": "Byte count to express memory or storage capacity.", + "type": "integer", + "format": "uint64", + "minimum": 0 }, - "Instance": { - "description": "View of an Instance", + "Certificate": { "type": "object", "properties": { - "auto_restart_cooldown_expiration": { - "nullable": true, - "description": "The time at which the auto-restart cooldown period for this instance completes, permitting it to be automatically restarted again. If the instance enters the `Failed` state, it will not be restarted until after this time.\n\nIf this is not present, then either the instance has never been automatically restarted, or the cooldown period has already expired, allowing the instance to be restarted immediately if it fails.", - "type": "string", - "format": "date-time" - }, - "auto_restart_enabled": { - "description": "`true` if this instance's auto-restart policy will permit the control plane to automatically restart it if it enters the `Failed` state.", - "type": "boolean" - }, - "auto_restart_policy": { - "nullable": true, - "description": "The auto-restart policy configured for this instance, or `null` if no explicit policy has been configured.\n\nThis policy determines whether the instance should be automatically restarted by the control plane on failure. If this is `null`, the control plane will use the default policy when determining whether or not to automatically restart this instance, which may or may not allow it to be restarted. The value of the `auto_restart_enabled` field indicates whether the instance will be auto-restarted, based on its current policy or the default if it has no configured policy.", - "allOf": [ - { - "$ref": "#/components/schemas/InstanceAutoRestartPolicy" - } - ] - }, - "boot_disk_id": { - "nullable": true, - "description": "the ID of the disk used to boot this Instance, if a specific one is assigned.", - "type": "string", - "format": "uuid" - }, - "cpu_platform": { - "nullable": true, - "description": "The CPU platform for this instance. If this is `null`, the instance requires no particular CPU platform.", - "allOf": [ - { - "$ref": "#/components/schemas/InstanceCpuPlatform" - } - ] - }, - "description": { - "description": "human-readable free-form text about a resource", + "cert": { "type": "string" }, - "hostname": { - "description": "RFC1035-compliant hostname for the Instance.", + "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" }, - "id": { - "description": "unique, immutable, system-controlled identifier for each resource", - "type": "string", - "format": "uuid" + "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" }, - "memory": { - "description": "memory allocated for this Instance", + "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/ByteCount" + "$ref": "#/components/schemas/Generation" } ] }, - "name": { - "description": "unique, mutable, user-controlled identifier for each resource", + "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/Name" + "$ref": "#/components/schemas/KeeperId" } ] }, - "ncpus": { - "description": "number of CPUs allocated for this Instance", + "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/InstanceCpuCount" + "$ref": "#/components/schemas/ServerId" } ] }, - "project_id": { - "description": "id for the project containing this Instance", - "type": "string", - "format": "uuid" - }, - "run_state": { - "$ref": "#/components/schemas/InstanceState" - }, - "time_created": { - "description": "timestamp when this resource was created", - "type": "string", - "format": "date-time" - }, - "time_last_auto_restarted": { - "nullable": true, - "description": "The timestamp of the most recent time this instance was automatically restarted by the control plane.\n\nIf this is not present, then this instance has not been automatically restarted.", - "type": "string", - "format": "date-time" - }, - "time_modified": { - "description": "timestamp when this resource was last modified", - "type": "string", - "format": "date-time" - }, - "time_run_state_updated": { - "type": "string", - "format": "date-time" + "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": [ - "auto_restart_enabled", - "description", - "hostname", - "id", - "memory", - "name", - "ncpus", - "project_id", - "run_state", - "time_created", - "time_modified", - "time_run_state_updated" + "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" ] }, - "InstanceAutoRestartPolicy": { - "description": "A policy determining when an instance should be automatically restarted by the control plane.", + "CockroachDbPreserveDowngrade": { + "description": "Whether to set `cluster.preserve_downgrade_option` and what to set it to.", "oneOf": [ { - "description": "The instance should not be automatically restarted by the control plane if it fails.", - "type": "string", - "enum": [ - "never" + "description": "Do not modify the setting.", + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "do_not_modify" + ] + } + }, + "required": [ + "action" ] }, { - "description": "If this instance is running and unexpectedly fails (e.g. due to a host software crash or unexpected host reboot), the control plane will make a best-effort attempt to restart it. The control plane may choose not to restart the instance to preserve the overall availability of the system.", - "type": "string", - "enum": [ - "best_effort" - ] - } - ] - }, - "InstanceCpuCount": { - "description": "The number of CPUs in an Instance", - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "InstanceCpuPlatform": { - "description": "A required CPU platform for an instance.\n\nWhen an instance specifies a required CPU platform:\n\n- The system may expose (to the VM) new CPU features that are only present on that platform (or on newer platforms of the same lineage that also support those features). - The instance must run on hosts that have CPUs that support all the features of the supplied platform.\n\nThat is, the instance is restricted to hosts that have the CPUs which support all features of the required platform, but in exchange the CPU features exposed by the platform are available for the guest to use. Note that this may prevent an instance from starting (if the hosts that could run it are full but there is capacity on other incompatible hosts).\n\nIf an instance does not specify a required CPU platform, then when it starts, the control plane selects a host for the instance and then supplies the guest with the \"minimum\" CPU platform supported by that host. This maximizes the number of hosts that can run the VM if it later needs to migrate to another host.\n\nIn all cases, the CPU features presented by a given CPU platform are a subset of what the corresponding hardware may actually support; features which cannot be used from a virtual environment or do not have full hypervisor support may be masked off. See RFD 314 for specific CPU features in a CPU platform.", - "oneOf": [ - { - "description": "An AMD Milan-like CPU platform.", - "type": "string", - "enum": [ - "amd_milan" + "description": "Ensure the setting is set to an empty string.", + "type": "object", + "properties": { + "action": { + "type": "string", + "enum": [ + "allow_upgrade" + ] + } + }, + "required": [ + "action" ] }, { - "description": "An AMD Turin-like CPU platform.", - "type": "string", - "enum": [ - "amd_turin" + "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" ] } ] }, - "InstanceMigrateRequest": { - "description": "Parameters used when migrating an instance.", - "type": "object", - "properties": { - "dst_sled_id": { - "description": "The ID of the sled to which to migrate the target instance.", - "type": "string", - "format": "uuid" - } - }, - "required": [ - "dst_sled_id" - ] - }, - "InstanceState": { - "description": "Running state of an Instance (primarily: booted or stopped)\n\nThis typically reflects whether it's starting, running, stopping, or stopped, but also includes states related to the Instance's lifecycle", + "CockroachdbUnsafeToShutdown": { "oneOf": [ { - "description": "The instance is being created.", - "type": "string", - "enum": [ - "creating" - ] - }, - { - "description": "The instance is currently starting up.", - "type": "string", - "enum": [ - "starting" - ] - }, - { - "description": "The instance is currently running.", - "type": "string", - "enum": [ - "running" - ] - }, - { - "description": "The instance has been requested to stop and a transition to \"Stopped\" is imminent.", - "type": "string", - "enum": [ - "stopping" - ] - }, - { - "description": "The instance is currently stopped.", - "type": "string", - "enum": [ - "stopped" - ] - }, - { - "description": "The instance is in the process of rebooting - it will remain in the \"rebooting\" state until the VM is starting once more.", - "type": "string", - "enum": [ - "rebooting" - ] - }, - { - "description": "The instance is in the process of migrating - it will remain in the \"migrating\" state until the migration process is complete and the destination propolis is ready to continue execution.", - "type": "string", - "enum": [ - "migrating" - ] - }, - { - "description": "The instance is attempting to recover from a failure.", - "type": "string", - "enum": [ - "repairing" + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "missing_live_nodes_stat" + ] + } + }, + "required": [ + "type" ] }, { - "description": "The instance has encountered a failure.", - "type": "string", - "enum": [ - "failed" + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "missing_underreplicated_stat" + ] + } + }, + "required": [ + "type" ] }, { - "description": "The instance has been deleted.", - "type": "string", - "enum": [ - "destroyed" - ] - } - ] - }, - "IpNet": { - "x-rust-type": { - "crate": "oxnet", - "path": "oxnet::IpNet", - "version": "0.1.0" - }, - "oneOf": [ + "type": "object", + "properties": { + "live_nodes": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "type": { + "type": "string", + "enum": [ + "not_enough_live_nodes" + ] + } + }, + "required": [ + "live_nodes", + "type" + ] + }, { - "title": "v4", - "allOf": [ - { - "$ref": "#/components/schemas/Ipv4Net" + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "not_enough_nodes" + ] } + }, + "required": [ + "type" ] }, { - "title": "v6", - "allOf": [ - { - "$ref": "#/components/schemas/Ipv6Net" + "type": "object", + "properties": { + "n": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "type": { + "type": "string", + "enum": [ + "underreplicated_ranges" + ] } + }, + "required": [ + "n", + "type" ] } ] }, - "IpRange": { + "CompressionAlgorithm": { "oneOf": [ { - "title": "v4", - "allOf": [ - { - "$ref": "#/components/schemas/Ipv4Range" + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "on" + ] } + }, + "required": [ + "type" ] }, { - "title": "v6", - "allOf": [ - { - "$ref": "#/components/schemas/Ipv6Range" + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "off" + ] } + }, + "required": [ + "type" ] - } - ] - }, - "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 - }, - "LastResult": { - "oneOf": [ { - "description": "The task has never completed an activation", "type": "object", "properties": { - "last_result": { + "type": { "type": "string", "enum": [ - "never_completed" + "gzip" ] } }, "required": [ - "last_result" + "type" ] }, { - "description": "The task has completed at least one activation", "type": "object", "properties": { - "details": { - "$ref": "#/components/schemas/LastResultCompleted" + "level": { + "$ref": "#/components/schemas/GzipLevel" }, - "last_result": { + "type": { "type": "string", "enum": [ - "completed" + "gzip_n" ] } }, "required": [ - "details", - "last_result" + "level", + "type" ] - } - ] - }, - "LastResultCompleted": { - "type": "object", - "properties": { - "details": { - "description": "arbitrary datum emitted by the background task" }, - "elapsed": { - "description": "total time elapsed during the activation", - "allOf": [ - { - "$ref": "#/components/schemas/Duration" + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "lz4" + ] } + }, + "required": [ + "type" ] }, - "iteration": { - "description": "which iteration this was (counter)", - "type": "integer", - "format": "uint64", - "minimum": 0 - }, - "reason": { - "description": "what kind of event triggered this activation", - "allOf": [ - { - "$ref": "#/components/schemas/ActivationReason" + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "lzjb" + ] } + }, + "required": [ + "type" ] }, - "start_time": { - "description": "wall-clock time when the activation started", - "type": "string", - "format": "date-time" - } - }, - "required": [ - "details", - "elapsed", - "iteration", - "reason", - "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" + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "zle" + ] } - ] - }, - "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": [ + "type" + ] } - }, - "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 - }, - "MgsDrivenUpdateStatus": { + "CrucibleDatasetCreateRequest": { "type": "object", "properties": { - "baseboard_description": { + "address": { "type": "string" }, - "host_os_phase_1": { - "$ref": "#/components/schemas/HostPhase1Status" - }, - "rot": { - "$ref": "#/components/schemas/RotStatus" - }, - "rot_bootloader": { - "$ref": "#/components/schemas/RotBootloaderStatus" + "dataset_id": { + "$ref": "#/components/schemas/TypedUuidForDatasetKind" }, - "sp": { - "$ref": "#/components/schemas/SpStatus" + "zpool_id": { + "$ref": "#/components/schemas/TypedUuidForZpoolKind" } }, "required": [ - "baseboard_description", - "host_os_phase_1", - "rot", - "rot_bootloader", - "sp" + "address", + "dataset_id", + "zpool_id" ] }, - "MgsUpdateDriverStatus": { - "description": "Status of ongoing update attempts, recently completed attempts, and update requests that are waiting for retry.", + "DatasetKind": { + "description": "The kind of dataset. See the `DatasetKind` enum in omicron-common for possible values.", + "type": "string" + }, + "DiscretionaryZonePlacement": { "type": "object", "properties": { - "in_progress": { - "title": "IdOrdMap", - "x-rust-type": { - "crate": "iddqd", - "parameters": [ - { - "$ref": "#/components/schemas/InProgressUpdateStatus" - } - ], - "path": "iddqd::IdOrdMap", - "version": "*" - }, - "type": "array", - "items": { - "$ref": "#/components/schemas/InProgressUpdateStatus" - }, - "uniqueItems": true - }, - "recent": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CompletedAttempt" - } + "kind": { + "type": "string" }, - "waiting": { - "title": "IdOrdMap", - "x-rust-type": { - "crate": "iddqd", - "parameters": [ - { - "$ref": "#/components/schemas/WaitingStatus" - } - ], - "path": "iddqd::IdOrdMap", - "version": "*" - }, - "type": "array", - "items": { - "$ref": "#/components/schemas/WaitingStatus" - }, - "uniqueItems": true + "source": { + "type": "string" } }, "required": [ - "in_progress", - "recent", - "waiting" + "kind", + "source" ] }, - "MigrationRuntimeState": { - "description": "An update from a sled regarding the state of a migration, indicating the role of the VMM whose migration state was updated.", + "DiskIdentity": { + "description": "Uniquely identifies a disk.", "type": "object", "properties": { - "gen": { - "$ref": "#/components/schemas/Generation" - }, - "migration_id": { - "type": "string", - "format": "uuid" + "model": { + "type": "string" }, - "state": { - "$ref": "#/components/schemas/MigrationState" + "serial": { + "type": "string" }, - "time_updated": { - "description": "Timestamp for the migration state update.", - "type": "string", - "format": "date-time" + "vendor": { + "type": "string" } }, "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" - ] - } + "model", + "serial", + "vendor" ] }, - "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.", + "DiskRuntimeState": { + "description": "Runtime state of the Disk, which includes its attach state and some minimal metadata", "type": "object", "properties": { - "deleted": { - "type": "boolean" - }, - "external_address": { - "type": "string", - "format": "ip" - }, - "first_port": { - "type": "integer", - "format": "uint16", - "minimum": 0 + "disk_state": { + "description": "runtime state of the Disk", + "allOf": [ + { + "$ref": "#/components/schemas/DiskState" + } + ] }, "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" + "description": "generation number for this state", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] }, - "ip": { + "time_updated": { + "description": "timestamp for this information", "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" + "format": "date-time" } - }, - "required": [ - "id", - "ip", - "kind", - "mac", - "name", - "primary", - "slot", - "subnet", - "vni" + }, + "required": [ + "disk_state", + "gen", + "time_updated" ] }, - "NetworkInterfaceKind": { - "description": "The type of network interface", + "DiskState": { + "description": "State of a Disk", "oneOf": [ { - "description": "A vNIC attached to a guest instance", + "description": "Disk is being initialized", "type": "object", "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { + "state": { "type": "string", "enum": [ - "instance" + "creating" ] } }, "required": [ - "id", - "type" + "state" ] }, { - "description": "A vNIC associated with an internal service", + "description": "Disk is ready but detached from any Instance", "type": "object", "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { + "state": { "type": "string", "enum": [ - "service" + "detached" ] } }, "required": [ - "id", - "type" + "state" ] }, { - "description": "A vNIC associated with a probe", + "description": "Disk is ready to receive blocks from an external source", "type": "object", "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "type": { + "state": { "type": "string", "enum": [ - "probe" + "import_ready" ] } }, "required": [ - "id", - "type" + "state" ] - } - ] - }, - "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", + "description": "Disk is importing blocks from a URL", "type": "object", "properties": { - "type": { + "state": { "type": "string", "enum": [ - "found_old_non_nexus_zones" + "importing_from_url" ] } }, "required": [ - "type" + "state" ] }, { - "description": "Waiting for the planner to deploy new-generation Nexus zones", + "description": "Disk is importing blocks from bulk writes", "type": "object", "properties": { - "type": { + "state": { "type": "string", "enum": [ - "missing_new_nexus_in_blueprint" + "importing_from_bulk_writes" ] } }, "required": [ - "type" + "state" ] }, { - "description": "Waiting for `db_metadata_nexus` records to be deployed for new-generation Nexus zones", + "description": "Disk is being finalized to state Detached", "type": "object", "properties": { - "type": { + "state": { "type": "string", "enum": [ - "missing_nexus_database_access_records" + "finalizing" ] } }, "required": [ - "type" + "state" ] }, { - "description": "Waiting for newly deployed Nexus zones to appear to inventory", + "description": "Disk is undergoing maintenance", "type": "object", "properties": { - "type": { + "state": { "type": "string", "enum": [ - "missing_new_nexus_in_inventory" + "maintenance" ] } }, "required": [ - "type" + "state" ] - } - ] - }, - "NodeName": { - "description": "Unique name for a saga [`Node`]\n\nEach node requires a string name that's unique within its DAG. The name is used to identify its output. Nodes that depend on a given node (either directly or indirectly) can access the node's output using its name.", - "type": "string" - }, - "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/TypedUuidForExternalIpKind" - } - }, - "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/TypedUuidForExternalIpKind" - }, - "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/TypedUuidForExternalIpKind" }, - "snat_cfg": { - "$ref": "#/components/schemas/SourceNatConfig" - } - }, - "required": [ - "id", - "snat_cfg" - ] - }, - "OmicronZoneType": { - "description": "Describes what kind of zone this is (i.e., what component is running in it) as well as any type-specific configuration", - "oneOf": [ { + "description": "Disk is being attached to the given Instance", "type": "object", "properties": { - "address": { - "type": "string" - }, - "dns_servers": { - "type": "array", - "items": { - "type": "string", - "format": "ip" - } - }, - "domain": { - "nullable": true, - "type": "string" - }, - "nic": { - "description": "The service vNIC providing outbound connectivity using OPTE.", - "allOf": [ - { - "$ref": "#/components/schemas/NetworkInterface" - } - ] - }, - "ntp_servers": { - "type": "array", - "items": { - "type": "string" - } - }, - "snat_cfg": { - "description": "The SNAT configuration for outbound connections.", - "allOf": [ - { - "$ref": "#/components/schemas/SourceNatConfig" - } - ] + "instance": { + "type": "string", + "format": "uuid" }, - "type": { + "state": { "type": "string", "enum": [ - "boundary_ntp" + "attaching" ] } }, "required": [ - "address", - "dns_servers", - "nic", - "ntp_servers", - "snat_cfg", - "type" + "instance", + "state" ] }, { - "description": "Type of clickhouse zone used for a single node clickhouse deployment", + "description": "Disk is attached to the given Instance", "type": "object", "properties": { - "address": { - "type": "string" - }, - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" + "instance": { + "type": "string", + "format": "uuid" }, - "type": { + "state": { "type": "string", "enum": [ - "clickhouse" + "attached" ] } }, "required": [ - "address", - "dataset", - "type" + "instance", + "state" ] }, { - "description": "A zone used to run a Clickhouse Keeper node\n\nKeepers are only used in replicated clickhouse setups", + "description": "Disk is being detached from the given Instance", "type": "object", "properties": { - "address": { - "type": "string" - }, - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" + "instance": { + "type": "string", + "format": "uuid" }, - "type": { + "state": { "type": "string", "enum": [ - "clickhouse_keeper" + "detaching" ] } }, "required": [ - "address", - "dataset", - "type" + "instance", + "state" ] }, { - "description": "A zone used to run a Clickhouse Server in a replicated deployment", + "description": "Disk has been destroyed", "type": "object", "properties": { - "address": { - "type": "string" - }, - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" - }, - "type": { + "state": { "type": "string", "enum": [ - "clickhouse_server" + "destroyed" ] } - }, - "required": [ - "address", - "dataset", - "type" + }, + "required": [ + "state" ] }, { + "description": "Disk is unavailable", "type": "object", "properties": { - "address": { - "type": "string" - }, - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" - }, - "type": { + "state": { "type": "string", "enum": [ - "cockroach_db" + "faulted" ] } }, "required": [ - "address", - "dataset", - "type" + "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": { - "address": { - "type": "string" - }, - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" + "data": { + "type": "string", + "format": "ipv4" }, "type": { "type": "string", "enum": [ - "crucible" + "A" ] } }, "required": [ - "address", - "dataset", + "data", "type" ] }, { "type": "object", "properties": { - "address": { - "type": "string" + "data": { + "type": "string", + "format": "ipv6" }, "type": { "type": "string", "enum": [ - "crucible_pantry" + "AAAA" ] } }, "required": [ - "address", + "data", "type" ] }, { "type": "object", "properties": { - "dataset": { - "$ref": "#/components/schemas/OmicronZoneDataset" - }, - "dns_address": { - "description": "The address at which the external DNS server is reachable.", - "type": "string" - }, - "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" - } - ] + "data": { + "$ref": "#/components/schemas/Srv" }, "type": { "type": "string", "enum": [ - "external_dns" + "SRV" ] } }, "required": [ - "dataset", - "dns_address", - "http_address", - "nic", + "data", "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": { + "data": { "type": "string" }, "type": { "type": "string", "enum": [ - "internal_dns" + "NS" ] } }, "required": [ - "dataset", - "dns_address", - "gz_address", - "gz_address_index", - "http_address", + "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" + ] + }, + "DownstairsUnderRepair": { + "type": "object", + "properties": { + "region_uuid": { + "$ref": "#/components/schemas/TypedUuidForDownstairsRegionKind" + }, + "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" }, - { - "type": "object", - "properties": { - "address": { - "type": "string" - }, - "type": { - "type": "string", - "enum": [ - "internal_ntp" - ] - } - }, - "required": [ - "address", - "type" - ] + "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": { - "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.", - "type": "string", - "format": "ip" - }, - "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 internal lockstep server is reachable. This shares the same IP address with `internal_address`.", - "default": 12232, - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "nic": { - "description": "The service vNIC providing external connectivity using OPTE.", - "allOf": [ - { - "$ref": "#/components/schemas/NetworkInterface" - } - ] - }, - "type": { + "kind": { "type": "string", "enum": [ - "nexus" + "no_valid_version" ] } }, "required": [ - "external_dns_servers", - "external_ip", - "external_tls", - "internal_address", - "nic", - "type" + "kind" ] }, { + "description": "We expect to find the specified version in this slot", "type": "object", "properties": { - "address": { - "type": "string" - }, - "type": { + "kind": { "type": "string", "enum": [ - "oximeter" + "version" ] + }, + "version": { + "$ref": "#/components/schemas/ArtifactVersion" } }, "required": [ - "address", - "type" + "kind", + "version" ] } ] }, - "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", + "ExternalPortDiscovery": { "oneOf": [ { "type": "object", "properties": { - "type": { - "type": "string", - "enum": [ - "single_node" - ] + "auto": { + "type": "object", + "additionalProperties": { + "type": "string", + "format": "ipv6" + } } }, "required": [ - "type" - ] + "auto" + ], + "additionalProperties": false }, { "type": "object", "properties": { - "type": { - "type": "string", - "enum": [ - "cluster" - ] + "static": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Name" + } + } } }, "required": [ - "type" - ] + "static" + ], + "additionalProperties": false } ] }, - "OximeterReadPolicy": { + "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", - "properties": { - "mode": { - "$ref": "#/components/schemas/OximeterReadMode" - }, - "time_created": { - "type": "string", - "format": "date-time" - }, - "version": { - "type": "integer", - "format": "uint32", - "minimum": 0 - } - }, - "required": [ - "mode", - "time_created", - "version" - ] + "additionalProperties": { + "$ref": "#/components/schemas/BlueprintDatasetConfig" + } }, - "PendingMgsUpdate": { + "IdMapBlueprintPhysicalDiskConfig": { "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" - ] + "additionalProperties": { + "$ref": "#/components/schemas/BlueprintPhysicalDiskConfig" + } }, - "PendingMgsUpdateDetails": { - "description": "Describes the component-specific details of a PendingMgsUpdate", + "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": "the SP itself is being updated", + "description": "Do not perform any filtering.", "type": "object", "properties": { - "component": { + "type": { "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" - } + "no_filtering" ] } }, "required": [ - "component", - "expected_active_version", - "expected_inactive_version" + "type" ] }, { - "description": "the RoT is being updated", "type": "object", "properties": { - "component": { + "type": { "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" - } + "allow" ] }, - "expected_transient_boot_preference": { - "nullable": true, - "description": "override persistent preference selection for a single boot", - "allOf": [ - { - "$ref": "#/components/schemas/RotSlot" - } - ] + "value": { + "type": "array", + "items": { + "$ref": "#/components/schemas/IpNet" + } } }, "required": [ - "component", - "expected_active_slot", - "expected_inactive_version", - "expected_persistent_boot_preference" + "type", + "value" ] - }, + } + ] + }, + "IpNet": { + "x-rust-type": { + "crate": "oxnet", + "path": "oxnet::IpNet", + "version": "0.1.0" + }, + "oneOf": [ { - "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" - } - ] + "title": "v4", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv4Net" } - }, - "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" + "title": "v6", + "allOf": [ + { + "$ref": "#/components/schemas/Ipv6Net" } - }, - "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" - ] - }, - "PendingRecovery": { - "description": "Snapshot of reassignment state when a recovery pass started", - "type": "object", - "properties": { - "blueprint_id": { - "nullable": true, - "description": "which blueprint id we'd be fully caught up to upon completion", + "IpRange": { + "oneOf": [ + { + "title": "v4", "allOf": [ { - "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + "$ref": "#/components/schemas/Ipv4Range" } ] }, - "generation": { - "description": "what `reassignment_generation` was when this recovery started", + { + "title": "v6", "allOf": [ { - "$ref": "#/components/schemas/Generation" + "$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": [ - "generation" + "first", + "last" ] }, - "PendingSagaInfo": { - "description": "Describes a pending saga (for debugging why quiesce is stuck)", + "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": { - "recovered": { - "description": "If true, we know the saga needs to be recovered. It may or may not be running already.\n\nIf false, this saga was created in this Nexus process's lifetime. It's still running.", - "type": "boolean" - }, - "saga_id": { + "first": { "type": "string", - "format": "uuid" - }, - "saga_name": { - "type": "string" + "format": "ipv6" }, - "time_pending": { + "last": { "type": "string", - "format": "date-time" + "format": "ipv6" } }, "required": [ - "recovered", - "saga_id", - "saga_name", - "time_pending" + "first", + "last" ] }, - "PhysicalDiskKind": { - "description": "Describes the form factor of physical disks.", + "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": [ - "m2", - "u2" - ] - }, - "PhysicalDiskPath": { - "type": "object", - "properties": { - "disk_id": { - "description": "ID of the physical disk", - "type": "string", - "format": "uuid" - } - }, - "required": [ - "disk_id" + "enabled", + "disabled", + "rx_only", + "tx_only" ] }, - "PhysicalDiskPutRequest": { + "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": { - "id": { - "$ref": "#/components/schemas/TypedUuidForPhysicalDiskKind" - }, - "model": { - "type": "string" - }, - "serial": { + "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" }, - "sled_id": { - "type": "string", - "format": "uuid" + "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" + } }, - "variant": { - "$ref": "#/components/schemas/PhysicalDiskKind" + "port_description": { + "nullable": true, + "description": "Port description to advertise. If this is not set, no description will be advertised.", + "type": "string" }, - "vendor": { + "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" - } - }, - "required": [ - "id", - "model", - "serial", - "sled_id", - "variant", - "vendor" - ] - }, - "Ping": { - "type": "object", - "properties": { + }, "status": { - "description": "Whether the external API is reachable. Will always be Ok if the endpoint returns anything at all.", + "description": "To what extent should this port participate in LLDP", "allOf": [ { - "$ref": "#/components/schemas/PingStatus" + "$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" ] }, - "PingStatus": { + "M2Slot": { + "description": "Describes an M.2 slot, often in the context of writing a system image to it.", "type": "string", "enum": [ - "ok" + "A", + "B" ] }, - "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" - ] + "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 }, - "PlanningAddOutOfEligibleSleds": { - "description": "How many discretionary zones we actually placed out of how many we wanted to place.", + "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": { - "placed": { - "type": "integer", - "format": "uint", - "minimum": 0 + "gen": { + "$ref": "#/components/schemas/Generation" }, - "wanted_to_place": { - "type": "integer", - "format": "uint", - "minimum": 0 + "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": [ - "placed", - "wanted_to_place" + "gen", + "migration_id", + "state", + "time_updated" ] }, - "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/TypedUuidForSledKind" - }, - "uniqueItems": true - }, - "sleds_missing_crucible_zone": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TypedUuidForZpoolKind" - } - } - }, - "sleds_missing_ntp_zone": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TypedUuidForSledKind" - }, - "uniqueItems": true - }, - "sleds_waiting_for_ntp_zone": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TypedUuidForSledKind" - }, - "uniqueItems": true - }, - "sleds_without_ntp_zones_in_inventory": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TypedUuidForSledKind" - }, - "uniqueItems": true + "MigrationState": { + "description": "The state of an instance's live migration.", + "oneOf": [ + { + "description": "The migration has not started for this VMM.", + "type": "string", + "enum": [ + "pending" + ] }, - "sleds_without_zpools_for_ntp_zones": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TypedUuidForSledKind" - }, - "uniqueItems": true + { + "description": "The migration is in progress.", + "type": "string", + "enum": [ + "in_progress" + ] }, - "sufficient_zones_exist": { - "description": "Discretionary zone kind → (wanted to place, num existing)", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/PlanningAddSufficientZonesExist" - } + { + "description": "The migration has failed.", + "type": "string", + "enum": [ + "failed" + ] }, - "waiting_on": { - "nullable": true, - "description": "What are we waiting on to start zone additions?", - "allOf": [ - { - "$ref": "#/components/schemas/ZoneAddWaitingOn" - } + { + "description": "The migration has completed.", + "type": "string", + "enum": [ + "completed" ] } - }, - "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" ] }, - "PlanningAddSufficientZonesExist": { - "description": "We have at least the minimum required number of zones of a given kind.", + "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": { - "num_existing": { + "deleted": { + "type": "boolean" + }, + "external_address": { + "type": "string", + "format": "ip" + }, + "first_port": { "type": "integer", - "format": "uint", + "format": "uint16", "minimum": 0 }, - "target_count": { + "gen": { "type": "integer", - "format": "uint", + "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": [ - "num_existing", - "target_count" - ] - }, - "PlanningCockroachdbSettingsStepReport": { - "type": "object", - "properties": { - "preserve_downgrade": { - "$ref": "#/components/schemas/CockroachDbPreserveDowngrade" - } - }, - "required": [ - "preserve_downgrade" + "deleted", + "external_address", + "first_port", + "gen", + "last_port", + "mac", + "sled_address", + "vni" ] }, - "PlanningDecommissionStepReport": { + "NetworkInterface": { + "description": "Information required to construct a virtual network interface", "type": "object", "properties": { - "zombie_sleds": { - "description": "Decommissioned sleds that unexpectedly appeared as commissioned.", + "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/TypedUuidForSledKind" + "$ref": "#/components/schemas/IpNet" } + }, + "vni": { + "$ref": "#/components/schemas/Vni" } }, "required": [ - "zombie_sleds" + "id", + "ip", + "kind", + "mac", + "name", + "primary", + "slot", + "subnet", + "vni" ] }, - "PlanningExpungeStepReport": { - "type": "object", - "properties": { - "orphan_disks": { - "description": "Expunged disks not present in the parent blueprint.", + "NetworkInterfaceKind": { + "description": "The type of network interface", + "oneOf": [ + { + "description": "A vNIC attached to a guest instance", "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/TypedUuidForPhysicalDiskKind" - } + "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" + ] } - }, - "required": [ - "orphan_disks" ] }, - "PlanningMgsUpdatesStepReport": { - "type": "object", - "properties": { - "pending_mgs_updates": { - "$ref": "#/components/schemas/PendingMgsUpdates" - } - }, - "required": [ - "pending_mgs_updates" - ] + "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" }, - "PlanningNexusGenerationBumpReport": { + "NexusGenerationBumpWaitingOn": { "oneOf": [ { - "description": "We have no reason to bump the Nexus generation number.", + "description": "Waiting for the planner to finish updating all non-Nexus zones", "type": "object", "properties": { - "component": { + "type": { "type": "string", "enum": [ - "nothing_to_report" + "found_old_non_nexus_zones" ] } }, "required": [ - "component" + "type" ] }, { - "description": "We are waiting on some condition before we can bump the Nexus generation.", + "description": "Waiting for the planner to deploy new-generation Nexus zones", "type": "object", "properties": { - "component": { + "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": [ - "waiting_on" + "missing_nexus_database_access_records" ] - }, - "value": { - "$ref": "#/components/schemas/NexusGenerationBumpWaitingOn" } }, "required": [ - "component", - "value" + "type" ] }, { - "description": "We are bumping the Nexus generation number to this value.", + "description": "Waiting for newly deployed Nexus zones to appear to inventory", "type": "object", "properties": { - "component": { + "type": { "type": "string", "enum": [ - "bumping_generation" + "missing_new_nexus_in_inventory" ] - }, - "value": { - "$ref": "#/components/schemas/Generation" } }, "required": [ - "component", - "value" + "type" ] } ] }, - "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.", + "OmicronZoneDataset": { + "description": "Describes a persistent ZFS dataset associated with an Omicron zone", "type": "object", "properties": { - "host_phase_2_slot_a_eligible": { - "type": "boolean" + "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" }, - "host_phase_2_slot_b_eligible": { - "type": "boolean" + "id": { + "$ref": "#/components/schemas/TypedUuidForExternalIpKind" + } + }, + "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/TypedUuidForExternalIpKind" }, - "num_dataset": { - "type": "integer", - "format": "uint", - "minimum": 0 + "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/TypedUuidForExternalIpKind" }, - "num_eligible": { - "type": "integer", - "format": "uint", - "minimum": 0 + "snat_cfg": { + "$ref": "#/components/schemas/SourceNatConfig" } }, "required": [ - "host_phase_2_slot_a_eligible", - "host_phase_2_slot_b_eligible", - "num_dataset", - "num_eligible" + "id", + "snat_cfg" ] }, - "PlanningNoopImageSourceSkipSledHostPhase2Reason": { + "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", @@ -7284,7 +3829,7 @@ "type": { "type": "string", "enum": [ - "both_slots_already_artifact" + "single_node" ] } }, @@ -7298,7 +3843,7 @@ "type": { "type": "string", "enum": [ - "sled_not_in_inventory" + "cluster" ] } }, @@ -7308,1545 +3853,1538 @@ } ] }, - "PlanningNoopImageSourceSkipSledZonesReason": { - "oneOf": [ - { - "type": "object", - "properties": { - "num_total": { - "type": "integer", - "format": "uint", - "minimum": 0 - }, - "type": { - "type": "string", - "enum": [ - "all_zones_already_artifact" - ] + "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" } - }, - "required": [ - "num_total", - "type" ] }, - { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "sled_not_in_inventory" - ] + "details": { + "description": "component-specific details of the pending update", + "allOf": [ + { + "$ref": "#/components/schemas/PendingMgsUpdateDetails" } - }, - "required": [ - "type" ] }, + "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": { - "error": { - "type": "string" - }, - "type": { + "component": { "type": "string", "enum": [ - "error_retrieving_zone_manifest" + "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": [ - "error", - "type" + "component", + "expected_active_version", + "expected_inactive_version" ] }, { + "description": "the RoT is being updated", "type": "object", "properties": { - "id": { - "$ref": "#/components/schemas/TypedUuidForMupdateOverrideKind" - }, - "type": { + "component": { "type": "string", "enum": [ - "remove_mupdate_override" + "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": [ - "id", - "type" + "component", + "expected_active_slot", + "expected_inactive_version", + "expected_persistent_boot_preference" ] - } - ] - }, - "PlanningNoopImageSourceSkipZoneReason": { - "oneOf": [ + }, { + "description": "the RoT bootloader is being updated", "type": "object", "properties": { - "file_name": { - "type": "string" - }, - "type": { + "component": { "type": "string", "enum": [ - "zone_not_in_manifest" + "rot_bootloader" ] }, - "zone_kind": { - "type": "string" + "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": [ - "file_name", - "type", - "zone_kind" + "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": { - "error": { - "type": "string" - }, - "file_name": { - "type": "string" - }, - "type": { + "component": { "type": "string", "enum": [ - "invalid_artifact" + "host_phase1" ] }, - "zone_kind": { - "type": "string" - } - }, - "required": [ - "error", - "file_name", - "type", - "zone_kind" - ] - }, - { - "type": "object", - "properties": { - "artifact_hash": { + "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)" }, - "file_name": { - "type": "string" + "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" + } + ] }, - "type": { + "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", - "enum": [ - "artifact_not_in_repo" + "format": "hex string (32 bytes)" + }, + "expected_boot_disk": { + "description": "Which slot the host OS most recently booted from.", + "allOf": [ + { + "$ref": "#/components/schemas/M2Slot" + } ] }, - "zone_kind": { + "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": [ - "artifact_hash", - "file_name", - "type", - "zone_kind" + "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" ] } ] }, - "PlanningNoopImageSourceStepReport": { + "PendingMgsUpdates": { "type": "object", "properties": { - "converted": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/PlanningNoopImageSourceConverted" - } + "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": { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/TypedUuidForPhysicalDiskKind" }, - "no_target_release": { - "type": "boolean" + "model": { + "type": "string" }, - "skipped_sled_host_phase_2": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/PlanningNoopImageSourceSkipSledHostPhase2Reason" - } + "serial": { + "type": "string" }, - "skipped_sled_zones": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/PlanningNoopImageSourceSkipSledZonesReason" - } + "sled_id": { + "type": "string", + "format": "uuid" }, - "skipped_zones": { - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/PlanningNoopImageSourceSkipZoneReason" - } + "variant": { + "$ref": "#/components/schemas/PhysicalDiskKind" + }, + "vendor": { + "type": "string" } }, "required": [ - "converted", - "no_target_release", - "skipped_sled_host_phase_2", - "skipped_sled_zones", - "skipped_zones" + "id", + "model", + "serial", + "sled_id", + "variant", + "vendor" ] }, - "PlanningOutOfDateZone": { - "description": "We have at least the minimum required number of zones of a given kind.", + "Ping": { "type": "object", "properties": { - "desired_image_source": { - "$ref": "#/components/schemas/BlueprintZoneImageSource" + "status": { + "description": "Whether the external API is reachable. Will always be Ok if the endpoint returns anything at all.", + "allOf": [ + { + "$ref": "#/components/schemas/PingStatus" + } + ] + } + }, + "required": [ + "status" + ] + }, + "PingStatus": { + "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" + ] + }, + "PlanningAddOutOfEligibleSleds": { + "description": "How many discretionary zones we actually placed out of how many we wanted to place.", + "type": "object", + "properties": { + "placed": { + "type": "integer", + "format": "uint", + "minimum": 0 }, - "zone_config": { - "$ref": "#/components/schemas/BlueprintZoneConfig" + "wanted_to_place": { + "type": "integer", + "format": "uint", + "minimum": 0 } }, "required": [ - "desired_image_source", - "zone_config" + "placed", + "wanted_to_place" ] }, - "PlanningZoneUpdatesStepReport": { + "PlanningAddStepReport": { "type": "object", "properties": { - "expunged_zones": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "$ref": "#/components/schemas/BlueprintZoneConfig" - } + "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" } }, - "out_of_date_zones": { + "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/PlanningOutOfDateZone" + "$ref": "#/components/schemas/DiscretionaryZonePlacement" } } }, - "unsafe_zones": { + "out_of_eligible_sleds": { + "description": "Discretionary zone kind → (placed, wanted to place)", "type": "object", "additionalProperties": { - "$ref": "#/components/schemas/ZoneUnsafeToShutdown" + "$ref": "#/components/schemas/PlanningAddOutOfEligibleSleds" } }, - "updated_zones": { + "sleds_getting_ntp_and_discretionary_zones": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true + }, + "sleds_missing_crucible_zone": { "type": "object", "additionalProperties": { "type": "array", "items": { - "$ref": "#/components/schemas/BlueprintZoneConfig" + "$ref": "#/components/schemas/TypedUuidForZpoolKind" } } }, - "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": { - "type": "object", - "properties": { - "addresses": { - "description": "This port's addresses and optional vlan IDs", + "sleds_missing_ntp_zone": { "type": "array", "items": { - "$ref": "#/components/schemas/UplinkAddressConfig" - } - }, - "autoneg": { - "description": "Whether or not to set autonegotiation", - "default": false, - "type": "boolean" + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true }, - "bgp_peers": { - "description": "BGP peers on this port", + "sleds_waiting_for_ntp_zone": { "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" + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true }, - "routes": { - "description": "The set of routes associated with this port.", + "sleds_without_ntp_zones_in_inventory": { "type": "array", "items": { - "$ref": "#/components/schemas/RouteConfig" - } + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true }, - "switch": { - "description": "Switch the port belongs to.", - "allOf": [ - { - "$ref": "#/components/schemas/SwitchLocation" - } - ] + "sleds_without_zpools_for_ntp_zones": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true }, - "tx_eq": { - "nullable": true, - "description": "TX-EQ configuration for this port", - "allOf": [ - { - "$ref": "#/components/schemas/TxEqConfig" - } - ] + "sufficient_zones_exist": { + "description": "Discretionary zone kind → (wanted to place, num existing)", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/PlanningAddSufficientZonesExist" + } }, - "uplink_port_fec": { + "waiting_on": { "nullable": true, - "description": "Port forward error correction type.", - "allOf": [ - { - "$ref": "#/components/schemas/PortFec" - } - ] - }, - "uplink_port_speed": { - "description": "Port speed.", + "description": "What are we waiting on to start zone additions?", "allOf": [ { - "$ref": "#/components/schemas/PortSpeed" + "$ref": "#/components/schemas/ZoneAddWaitingOn" } ] } }, "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" + "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" ] }, - "ProbeExternalIp": { + "PlanningAddSufficientZonesExist": { + "description": "We have at least the minimum required number of zones of a given kind.", "type": "object", "properties": { - "first_port": { + "num_existing": { "type": "integer", - "format": "uint16", + "format": "uint", "minimum": 0 }, - "ip": { - "type": "string", - "format": "ip" - }, - "kind": { - "$ref": "#/components/schemas/ProbeExternalIpKind" - }, - "last_port": { + "target_count": { "type": "integer", - "format": "uint16", + "format": "uint", "minimum": 0 } }, "required": [ - "first_port", - "ip", - "kind", - "last_port" - ] - }, - "ProbeExternalIpKind": { - "type": "string", - "enum": [ - "snat", - "floating", - "ephemeral" + "num_existing", + "target_count" ] }, - "ProbeInfo": { + "PlanningCockroachdbSettingsStepReport": { "type": "object", "properties": { - "external_ips": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ProbeExternalIp" - } - }, - "id": { - "type": "string", - "format": "uuid" - }, - "interface": { - "$ref": "#/components/schemas/NetworkInterface" - }, - "name": { - "$ref": "#/components/schemas/Name" - }, - "sled": { - "type": "string", - "format": "uuid" + "preserve_downgrade": { + "$ref": "#/components/schemas/CockroachDbPreserveDowngrade" + } + }, + "required": [ + "preserve_downgrade" + ] + }, + "PlanningDecommissionStepReport": { + "type": "object", + "properties": { + "zombie_sleds": { + "description": "Decommissioned sleds that unexpectedly appeared as commissioned.", + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + } } }, "required": [ - "external_ips", - "id", - "interface", - "name", - "sled" + "zombie_sleds" ] }, - "ProducerEndpoint": { - "description": "Information announced by a metric server, used so that clients can contact it and collect available metric data from it.", + "PlanningExpungeStepReport": { "type": "object", "properties": { - "address": { - "description": "The IP address and port at which `oximeter` can collect metrics from the producer.", - "type": "string" - }, - "id": { - "description": "A unique ID for this producer.", - "type": "string", - "format": "uuid" - }, - "interval": { - "description": "The interval on which `oximeter` should collect metrics.", - "allOf": [ - { - "$ref": "#/components/schemas/Duration" - } - ] - }, - "kind": { - "description": "The kind of producer.", - "allOf": [ - { - "$ref": "#/components/schemas/ProducerKind" - } - ] + "orphan_disks": { + "description": "Expunged disks not present in the parent blueprint.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/TypedUuidForPhysicalDiskKind" + } } }, "required": [ - "address", - "id", - "interval", - "kind" + "orphan_disks" ] }, - "ProducerEndpointResultsPage": { - "description": "A single page of results", + "PlanningMgsUpdatesStepReport": { "type": "object", "properties": { - "items": { - "description": "list of items on this page of results", - "type": "array", - "items": { - "$ref": "#/components/schemas/ProducerEndpoint" - } - }, - "next_page": { - "nullable": true, - "description": "token used to fetch the next page of results (if any)", - "type": "string" + "pending_mgs_updates": { + "$ref": "#/components/schemas/PendingMgsUpdates" } }, "required": [ - "items" + "pending_mgs_updates" ] }, - "ProducerKind": { - "description": "The kind of metric producer this is.", + "PlanningNexusGenerationBumpReport": { "oneOf": [ { - "description": "The producer is a sled-agent.", - "type": "string", - "enum": [ - "sled_agent" - ] - }, - { - "description": "The producer is an Omicron-managed service.", - "type": "string", - "enum": [ - "service" + "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 producer is a Propolis VMM managing a guest instance.", - "type": "string", - "enum": [ - "instance" + "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 producer is a management gateway service.", - "type": "string", - "enum": [ - "management_gateway" + "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" ] } ] }, - "ProducerRegistrationResponse": { - "description": "Response to a successful producer registration.", + "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.", "type": "object", "properties": { - "lease_duration": { - "description": "Period within which producers must renew their lease.\n\nProducers are required to periodically re-register with Nexus, to ensure that they are still collected from by `oximeter`.", - "allOf": [ - { - "$ref": "#/components/schemas/Duration" - } - ] + "host_phase_2_slot_a_eligible": { + "type": "boolean" + }, + "host_phase_2_slot_b_eligible": { + "type": "boolean" + }, + "num_dataset": { + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "num_eligible": { + "type": "integer", + "format": "uint", + "minimum": 0 } }, "required": [ - "lease_duration" + "host_phase_2_slot_a_eligible", + "host_phase_2_slot_b_eligible", + "num_dataset", + "num_eligible" ] }, - "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.", + "PlanningNoopImageSourceSkipSledHostPhase2Reason": { "oneOf": [ { - "description": "We have not yet determined based on persistent state if we're supposed to be quiesced or not", "type": "object", "properties": { - "state": { + "type": { "type": "string", "enum": [ - "undetermined" + "both_slots_already_artifact" ] } }, "required": [ - "state" + "type" ] }, { - "description": "Normal operation", "type": "object", "properties": { - "state": { + "type": { "type": "string", "enum": [ - "running" + "sled_not_in_inventory" ] } }, "required": [ - "state" + "type" + ] + } + ] + }, + "PlanningNoopImageSourceSkipSledZonesReason": { + "oneOf": [ + { + "type": "object", + "properties": { + "num_total": { + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "type": { + "type": "string", + "enum": [ + "all_zones_already_artifact" + ] + } + }, + "required": [ + "num_total", + "type" ] }, { - "description": "New sagas disallowed, but some are still running on some Nexus instances", "type": "object", "properties": { - "quiesce_details": { - "type": "object", - "properties": { - "time_requested": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "time_requested" + "type": { + "type": "string", + "enum": [ + "sled_not_in_inventory" ] + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "properties": { + "error": { + "type": "string" }, - "state": { + "type": { "type": "string", "enum": [ - "draining_sagas" + "error_retrieving_zone_manifest" ] } }, "required": [ - "quiesce_details", - "state" + "error", + "type" ] }, { - "description": "No sagas running on any Nexus instances\n\nNo new database connections may be claimed, but some database connections are still held.", "type": "object", "properties": { - "quiesce_details": { - "type": "object", - "properties": { - "duration_draining_sagas": { - "$ref": "#/components/schemas/Duration" - }, - "time_requested": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "duration_draining_sagas", - "time_requested" + "id": { + "$ref": "#/components/schemas/TypedUuidForMupdateOverrideKind" + }, + "type": { + "type": "string", + "enum": [ + "remove_mupdate_override" ] + } + }, + "required": [ + "id", + "type" + ] + } + ] + }, + "PlanningNoopImageSourceSkipZoneReason": { + "oneOf": [ + { + "type": "object", + "properties": { + "file_name": { + "type": "string" }, - "state": { + "type": { "type": "string", "enum": [ - "draining_db" + "zone_not_in_manifest" ] + }, + "zone_kind": { + "type": "string" } }, "required": [ - "quiesce_details", - "state" + "file_name", + "type", + "zone_kind" ] }, { - "description": "No database connections in use except to record the final \"quiesced\" state", "type": "object", "properties": { - "quiesce_details": { - "type": "object", - "properties": { - "duration_draining_db": { - "$ref": "#/components/schemas/Duration" - }, - "duration_draining_sagas": { - "$ref": "#/components/schemas/Duration" - }, - "time_requested": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "duration_draining_db", - "duration_draining_sagas", - "time_requested" - ] + "error": { + "type": "string" }, - "state": { + "file_name": { + "type": "string" + }, + "type": { "type": "string", "enum": [ - "recording_quiesce" + "invalid_artifact" ] + }, + "zone_kind": { + "type": "string" } }, "required": [ - "quiesce_details", - "state" + "error", + "file_name", + "type", + "zone_kind" ] }, { - "description": "Nexus has no sagas running and is not using the database", "type": "object", "properties": { - "quiesce_details": { - "type": "object", - "properties": { - "duration_draining_db": { - "$ref": "#/components/schemas/Duration" - }, - "duration_draining_sagas": { - "$ref": "#/components/schemas/Duration" - }, - "duration_recording_quiesce": { - "$ref": "#/components/schemas/Duration" - }, - "duration_total": { - "$ref": "#/components/schemas/Duration" - }, - "time_quiesced": { - "type": "string", - "format": "date-time" - }, - "time_requested": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "duration_draining_db", - "duration_draining_sagas", - "duration_recording_quiesce", - "duration_total", - "time_quiesced", - "time_requested" - ] + "artifact_hash": { + "type": "string", + "format": "hex string (32 bytes)" }, - "state": { + "file_name": { + "type": "string" + }, + "type": { "type": "string", "enum": [ - "quiesced" + "artifact_not_in_repo" ] + }, + "zone_kind": { + "type": "string" } }, "required": [ - "quiesce_details", - "state" - ] - } - ] - }, - "QuiesceStatus": { - "description": "Describes whether Nexus is quiescing or quiesced and what, if anything, is blocking the quiesce process\n\n**Quiescing** is the process of draining Nexus of running sagas and stopping all use of the database in preparation for upgrade. See [`QuiesceState`] for more on the stages involved.", - "type": "object", - "properties": { - "db_claims": { - "title": "IdOrdMap", - "description": "what database claims are currently held (by any part of Nexus)\n\nEntries here prevent transitioning from `WaitingForDb` to `Quiesced`.", - "x-rust-type": { - "crate": "iddqd", - "parameters": [ - { - "$ref": "#/components/schemas/HeldDbClaimInfo" - } - ], - "path": "iddqd::IdOrdMap", - "version": "*" - }, - "type": "array", - "items": { - "$ref": "#/components/schemas/HeldDbClaimInfo" - }, - "uniqueItems": true - }, - "sagas": { - "description": "information about saga quiescing", - "allOf": [ - { - "$ref": "#/components/schemas/SagaQuiesceStatus" - } - ] - }, - "state": { - "description": "what stage of quiescing is Nexus at", - "allOf": [ - { - "$ref": "#/components/schemas/QuiesceState" - } - ] - } - }, - "required": [ - "db_claims", - "sagas", - "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" - } + "artifact_hash", + "file_name", + "type", + "zone_kind" ] - }, - "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", + "PlanningNoopImageSourceStepReport": { "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" + "converted": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/PlanningNoopImageSourceConverted" } }, - "rack_subnet": { - "$ref": "#/components/schemas/Ipv6Net" - } - }, - "required": [ - "bgp", - "infra_ip_first", - "infra_ip_last", - "ports", - "rack_subnet" - ] - }, - "ReconfiguratorConfig": { - "type": "object", - "properties": { - "planner_config": { - "$ref": "#/components/schemas/PlannerConfig" - }, - "planner_enabled": { + "no_target_release": { "type": "boolean" - } - }, - "required": [ - "planner_config", - "planner_enabled" - ] - }, - "ReconfiguratorConfigParam": { - "type": "object", - "properties": { - "config": { - "$ref": "#/components/schemas/ReconfiguratorConfig" }, - "version": { - "type": "integer", - "format": "uint32", - "minimum": 0 - } - }, - "required": [ - "config", - "version" - ] - }, - "ReconfiguratorConfigView": { - "type": "object", - "properties": { - "config": { - "$ref": "#/components/schemas/ReconfiguratorConfig" + "skipped_sled_host_phase_2": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/PlanningNoopImageSourceSkipSledHostPhase2Reason" + } }, - "time_modified": { - "type": "string", - "format": "date-time" + "skipped_sled_zones": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/PlanningNoopImageSourceSkipSledZonesReason" + } }, - "version": { - "type": "integer", - "format": "uint32", - "minimum": 0 + "skipped_zones": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/PlanningNoopImageSourceSkipZoneReason" + } } }, "required": [ - "config", - "time_modified", - "version" + "converted", + "no_target_release", + "skipped_sled_host_phase_2", + "skipped_sled_zones", + "skipped_zones" ] }, - "RecoverySiloConfig": { + "PlanningOutOfDateZone": { + "description": "We have at least the minimum required number of zones of a given kind.", "type": "object", "properties": { - "silo_name": { - "$ref": "#/components/schemas/Name" - }, - "user_name": { - "$ref": "#/components/schemas/UserId" + "desired_image_source": { + "$ref": "#/components/schemas/BlueprintZoneImageSource" }, - "user_password_hash": { - "$ref": "#/components/schemas/NewPasswordHash" + "zone_config": { + "$ref": "#/components/schemas/BlueprintZoneConfig" } }, "required": [ - "silo_name", - "user_name", - "user_password_hash" + "desired_image_source", + "zone_config" ] }, - "RepairFinishInfo": { + "PlanningZoneUpdatesStepReport": { "type": "object", "properties": { - "aborted": { - "type": "boolean" + "expunged_zones": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlueprintZoneConfig" + } + } }, - "repair_id": { - "$ref": "#/components/schemas/TypedUuidForUpstairsRepairKind" + "out_of_date_zones": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlanningOutOfDateZone" + } + } }, - "repair_type": { - "$ref": "#/components/schemas/UpstairsRepairType" + "unsafe_zones": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/ZoneUnsafeToShutdown" + } }, - "repairs": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DownstairsUnderRepair" + "updated_zones": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlueprintZoneConfig" + } } }, - "session_id": { - "$ref": "#/components/schemas/TypedUuidForUpstairsSessionKind" + "waiting_on": { + "nullable": true, + "description": "What are we waiting on to start zone updates?", + "allOf": [ + { + "$ref": "#/components/schemas/ZoneUpdatesWaitingOn" + } + ] }, - "time": { - "type": "string", - "format": "date-time" + "waiting_zones": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/ZoneWaitingToExpunge" + } } }, "required": [ - "aborted", - "repair_id", - "repair_type", - "repairs", - "session_id", - "time" + "expunged_zones", + "out_of_date_zones", + "unsafe_zones", + "updated_zones", + "waiting_zones" ] }, - "RepairProgress": { + "PortConfigV2": { "type": "object", "properties": { - "current_item": { - "type": "integer", - "format": "int64" + "addresses": { + "description": "This port's addresses and optional vlan IDs", + "type": "array", + "items": { + "$ref": "#/components/schemas/UplinkAddressConfig" + } }, - "time": { - "type": "string", - "format": "date-time" + "autoneg": { + "description": "Whether or not to set autonegotiation", + "default": false, + "type": "boolean" }, - "total_items": { - "type": "integer", - "format": "int64" - } - }, - "required": [ - "current_item", - "time", - "total_items" - ] - }, - "RepairStartInfo": { - "type": "object", - "properties": { - "repair_id": { - "$ref": "#/components/schemas/TypedUuidForUpstairsRepairKind" + "bgp_peers": { + "description": "BGP peers on this port", + "type": "array", + "items": { + "$ref": "#/components/schemas/BgpPeerConfig" + } }, - "repair_type": { - "$ref": "#/components/schemas/UpstairsRepairType" + "lldp": { + "nullable": true, + "description": "LLDP configuration for this port", + "allOf": [ + { + "$ref": "#/components/schemas/LldpPortConfig" + } + ] }, - "repairs": { + "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/DownstairsUnderRepair" + "$ref": "#/components/schemas/RouteConfig" } }, - "session_id": { - "$ref": "#/components/schemas/TypedUuidForUpstairsSessionKind" + "switch": { + "description": "Switch the port belongs to.", + "allOf": [ + { + "$ref": "#/components/schemas/SwitchLocation" + } + ] }, - "time": { - "type": "string", - "format": "date-time" + "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": [ - "repair_id", - "repair_type", - "repairs", - "session_id", - "time" + "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" ] }, - "RotBootloaderStatus": { + "ProbeExternalIp": { "type": "object", "properties": { - "stage0_next_version": { - "$ref": "#/components/schemas/TufRepoVersion" + "first_port": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "ip": { + "type": "string", + "format": "ip" + }, + "kind": { + "$ref": "#/components/schemas/ProbeExternalIpKind" }, - "stage0_version": { - "$ref": "#/components/schemas/TufRepoVersion" + "last_port": { + "type": "integer", + "format": "uint16", + "minimum": 0 } }, "required": [ - "stage0_next_version", - "stage0_version" + "first_port", + "ip", + "kind", + "last_port" ] }, - "RotSlot": { - "oneOf": [ - { - "type": "object", - "properties": { - "slot": { - "type": "string", - "enum": [ - "a" - ] - } - }, - "required": [ - "slot" - ] - }, - { - "type": "object", - "properties": { - "slot": { - "type": "string", - "enum": [ - "b" - ] - } - }, - "required": [ - "slot" - ] - } + "ProbeExternalIpKind": { + "type": "string", + "enum": [ + "snat", + "floating", + "ephemeral" ] }, - "RotStatus": { + "ProbeInfo": { "type": "object", "properties": { - "active_slot": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/RotSlot" - } - ] + "external_ips": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProbeExternalIp" + } }, - "slot_a_version": { - "$ref": "#/components/schemas/TufRepoVersion" + "id": { + "type": "string", + "format": "uuid" + }, + "interface": { + "$ref": "#/components/schemas/NetworkInterface" + }, + "name": { + "$ref": "#/components/schemas/Name" }, - "slot_b_version": { - "$ref": "#/components/schemas/TufRepoVersion" + "sled": { + "type": "string", + "format": "uuid" } }, "required": [ - "slot_a_version", - "slot_b_version" + "external_ips", + "id", + "interface", + "name", + "sled" ] }, - "RouteConfig": { + "ProducerEndpoint": { + "description": "Information announced by a metric server, used so that clients can contact it and collect available metric data from it.", "type": "object", "properties": { - "destination": { - "description": "The destination of the route.", + "address": { + "description": "The IP address and port at which `oximeter` can collect metrics from the producer.", + "type": "string" + }, + "id": { + "description": "A unique ID for this producer.", + "type": "string", + "format": "uuid" + }, + "interval": { + "description": "The interval on which `oximeter` should collect metrics.", "allOf": [ { - "$ref": "#/components/schemas/IpNet" + "$ref": "#/components/schemas/Duration" } ] }, - "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 + "kind": { + "description": "The kind of producer.", + "allOf": [ + { + "$ref": "#/components/schemas/ProducerKind" + } + ] } }, "required": [ - "destination", - "nexthop" + "address", + "id", + "interval", + "kind" ] }, - "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.", + "ProducerEndpointResultsPage": { + "description": "A single page of results", "type": "object", "properties": { - "id": { - "type": "string", - "format": "uuid" + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/ProducerEndpoint" + } }, - "state": { - "$ref": "#/components/schemas/SagaState" + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" } }, "required": [ - "id", - "state" + "items" ] }, - "SagaErrorInfo": { + "ProducerKind": { + "description": "The kind of metric producer this is.", "oneOf": [ { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "action_failed" - ] - }, - "source_error": {} - }, - "required": [ - "error", - "source_error" + "description": "The producer is a sled-agent.", + "type": "string", + "enum": [ + "sled_agent" ] }, { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "deserialize_failed" - ] - }, - "message": { - "type": "string" - } - }, - "required": [ - "error", - "message" + "description": "The producer is an Omicron-managed service.", + "type": "string", + "enum": [ + "service" ] }, { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "injected_error" - ] - } - }, - "required": [ - "error" + "description": "The producer is a Propolis VMM managing a guest instance.", + "type": "string", + "enum": [ + "instance" ] }, { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "serialize_failed" - ] - }, - "message": { - "type": "string" - } - }, - "required": [ - "error", - "message" + "description": "The producer is a management gateway service.", + "type": "string", + "enum": [ + "management_gateway" ] - }, - { - "type": "object", - "properties": { - "error": { - "type": "string", - "enum": [ - "subsaga_create_failed" - ] - }, - "message": { - "type": "string" + } + ] + }, + "ProducerRegistrationResponse": { + "description": "Response to a successful producer registration.", + "type": "object", + "properties": { + "lease_duration": { + "description": "Period within which producers must renew their lease.\n\nProducers are required to periodically re-register with Nexus, to ensure that they are still collected from by `oximeter`.", + "allOf": [ + { + "$ref": "#/components/schemas/Duration" } - }, - "required": [ - "error", - "message" ] } + }, + "required": [ + "lease_duration" ] }, - "SagaQuiesceStatus": { + "RackInitializationRequest": { "type": "object", "properties": { - "drained_blueprint_id": { - "nullable": true, - "description": "blueprint id that we're \"fully drained up to\"\n\nIf this value is non-`None`, that means that:\n\n- saga creation is disallowed - no sagas are running - we have re-assigned sagas from other Nexus instances expunged in this blueprint or earlier - we have finished recovery for all those sagas (that had been assigned to us as of the re-assignment pass for this blueprint id)\n\nThis means that the only way we can wind up running another saga is if there's a new blueprint that expunges a different Nexus zone.", + "allowed_source_ips": { + "description": "IPs or subnets allowed to make requests to user-facing services", "allOf": [ { - "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + "$ref": "#/components/schemas/AllowedSourceIps" + } + ] + }, + "blueprint": { + "description": "Blueprint describing services initialized by RSS.", + "allOf": [ + { + "$ref": "#/components/schemas/Blueprint" } ] }, - "first_recovery_complete": { - "description": "whether at least one recovery pass has successfully completed\n\nWe have to track this because we can't quiesce until we know we've recovered all outstanding sagas.", - "type": "boolean" + "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" }, - "new_sagas_allowed": { - "description": "current policy: are we allowed to *create* new sagas?\n\nThis also affects re-assigning sagas from expunged Nexus instances to ourselves. It does **not** affect saga recovery.", + "external_port_count": { + "description": "The external qsfp ports per sidecar", "allOf": [ { - "$ref": "#/components/schemas/SagasAllowed" + "$ref": "#/components/schemas/ExternalPortDiscovery" } ] }, - "reassignment_blueprint_id": { - "nullable": true, - "description": "blueprint id associated with last successful saga reassignment\n\nSimilar to the generation number, this is used to track whether we've accounted for all sagas for all expungements up through this target blueprint.", + "internal_dns_zone_config": { + "description": "initial internal DNS config", "allOf": [ { - "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + "$ref": "#/components/schemas/DnsConfigParams" } ] }, - "reassignment_generation": { - "description": "generation number for the saga reassignment\n\nThis gets bumped whenever a saga reassignment operation completes that may have re-assigned us some sagas. It's used to keep track of when we've recovered all sagas that could be assigned to us.", - "allOf": [ - { - "$ref": "#/components/schemas/Generation" - } - ] + "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" + } }, - "reassignment_pending": { - "description": "whether there is a saga reassignment operation happening\n\nThese operatinos may assign new sagas to Nexus that must be recovered and completed before quiescing can finish.", - "type": "boolean" + "physical_disks": { + "description": "\"Managed\" physical disks owned by the control plane", + "type": "array", + "items": { + "$ref": "#/components/schemas/PhysicalDiskPutRequest" + } }, - "recovered_blueprint_id": { - "nullable": true, - "description": "blueprint id that saga recovery has \"caught up to\"\n\nThis means that we have finished recovering any sagas that were re-assigned to us due to expungements of other Nexus zones up through this blueprint. Put differently: we know that we will never be assigned more sagas due to expungement unless the target blueprint changes past this one.\n\nThis does not mean that we've fully drained all sagas up through this blueprint. There may still be sagas running.", + "rack_network_config": { + "description": "Initial rack network configuration", "allOf": [ { - "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + "$ref": "#/components/schemas/RackNetworkConfigV2" } ] }, - "recovered_reassignment_generation": { - "description": "\"saga reassignment generation number\" that was \"caught up to\" by the last recovery pass\n\nThis is used with `reassignment_generation` to help us know when we've recovered all the sagas that may have been assigned to us during a given reassignment pass. See `reassignment_done()` for details.", + "recovery_silo": { + "description": "configuration for the initial (recovery) Silo", "allOf": [ { - "$ref": "#/components/schemas/Generation" + "$ref": "#/components/schemas/RecoverySiloConfig" } ] }, - "recovery_pending": { - "nullable": true, - "description": "If a recovery pass is ongoing, a snapshot of reassignment state when it started (which reflects what we'll be caught up to when it finishes)", - "allOf": [ - { - "$ref": "#/components/schemas/PendingRecovery" - } - ] + "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" + } }, - "sagas_pending": { - "title": "IdOrdMap", - "description": "list of sagas we need to wait to complete before quiescing\n\nThese are basically running sagas. They may have been created in this Nexus process lifetime or created in another process and then recovered in this one.", - "x-rust-type": { - "crate": "iddqd", - "parameters": [ - { - "$ref": "#/components/schemas/PendingSagaInfo" - } - ], - "path": "iddqd::IdOrdMap", - "version": "*" - }, + "bgp": { + "description": "BGP configurations for connecting the rack to external networks", "type": "array", "items": { - "$ref": "#/components/schemas/PendingSagaInfo" - }, - "uniqueItems": true + "$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": [ - "first_recovery_complete", - "new_sagas_allowed", - "reassignment_generation", - "reassignment_pending", - "recovered_reassignment_generation", - "sagas_pending" + "bgp", + "infra_ip_first", + "infra_ip_last", + "ports", + "rack_subnet" ] }, - "SagaResultsPage": { - "description": "A single page of results", + "RecoverySiloConfig": { "type": "object", "properties": { - "items": { - "description": "list of items on this page of results", + "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": { + "aborted": { + "type": "boolean" + }, + "repair_id": { + "$ref": "#/components/schemas/TypedUuidForUpstairsRepairKind" + }, + "repair_type": { + "$ref": "#/components/schemas/UpstairsRepairType" + }, + "repairs": { "type": "array", "items": { - "$ref": "#/components/schemas/Saga" + "$ref": "#/components/schemas/DownstairsUnderRepair" } }, - "next_page": { - "nullable": true, - "description": "token used to fetch the next page of results (if any)", - "type": "string" + "session_id": { + "$ref": "#/components/schemas/TypedUuidForUpstairsSessionKind" + }, + "time": { + "type": "string", + "format": "date-time" } }, "required": [ - "items" + "aborted", + "repair_id", + "repair_type", + "repairs", + "session_id", + "time" ] }, - "SagaState": { - "oneOf": [ - { - "description": "Saga is currently executing", - "type": "object", - "properties": { - "state": { - "type": "string", - "enum": [ - "running" - ] - } - }, - "required": [ - "state" - ] + "RepairProgress": { + "type": "object", + "properties": { + "current_item": { + "type": "integer", + "format": "int64" }, - { - "description": "Saga completed successfully", - "type": "object", - "properties": { - "state": { - "type": "string", - "enum": [ - "succeeded" - ] - } - }, - "required": [ - "state" - ] + "time": { + "type": "string", + "format": "date-time" + }, + "total_items": { + "type": "integer", + "format": "int64" + } + }, + "required": [ + "current_item", + "time", + "total_items" + ] + }, + "RepairStartInfo": { + "type": "object", + "properties": { + "repair_id": { + "$ref": "#/components/schemas/TypedUuidForUpstairsRepairKind" }, + "repair_type": { + "$ref": "#/components/schemas/UpstairsRepairType" + }, + "repairs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DownstairsUnderRepair" + } + }, + "session_id": { + "$ref": "#/components/schemas/TypedUuidForUpstairsSessionKind" + }, + "time": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "repair_id", + "repair_type", + "repairs", + "session_id", + "time" + ] + }, + "RotSlot": { + "oneOf": [ { - "description": "One or more saga actions failed and the saga was successfully unwound (i.e., undo actions were executed for any actions that were completed). The saga is no longer running.", - "type": "object", - "properties": { - "error_info": { - "$ref": "#/components/schemas/SagaErrorInfo" - }, - "error_node_name": { - "$ref": "#/components/schemas/NodeName" - }, - "state": { + "type": "object", + "properties": { + "slot": { "type": "string", "enum": [ - "failed" + "a" ] } }, "required": [ - "error_info", - "error_node_name", - "state" + "slot" ] }, { - "description": "One or more saga actions failed, *and* one or more undo actions failed during unwinding. State managed by the saga may now be inconsistent. Support may be required to repair the state. The saga is no longer running.", "type": "object", "properties": { - "error_info": { - "$ref": "#/components/schemas/SagaErrorInfo" - }, - "error_node_name": { - "$ref": "#/components/schemas/NodeName" - }, - "state": { + "slot": { "type": "string", "enum": [ - "stuck" + "b" ] - }, - "undo_error_node_name": { - "$ref": "#/components/schemas/NodeName" - }, - "undo_source_error": {} + } }, "required": [ - "error_info", - "error_node_name", - "state", - "undo_error_node_name", - "undo_source_error" + "slot" ] } ] }, - "SagasAllowed": { - "description": "Policy determining whether new sagas are allowed to be started\n\nThis is used by Nexus quiesce to disallow creation of new sagas when we're trying to quiesce Nexus.", - "oneOf": [ - { - "description": "New sagas may be started (normal condition)", - "type": "string", - "enum": [ - "allowed" + "RouteConfig": { + "type": "object", + "properties": { + "destination": { + "description": "The destination of the route.", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } ] }, - { - "description": "New sagas may not be started because we're quiescing or quiesced", + "nexthop": { + "description": "The nexthop/gateway address.", "type": "string", - "enum": [ - "disallowed_quiesce" - ] + "format": "ip" }, - { - "description": "New sagas may not be started because we just started up and haven't determined if we're quiescing yet", - "type": "string", - "enum": [ - "disallowed_unknown" - ] + "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": { @@ -8941,40 +5479,6 @@ "usable_physical_ram" ] }, - "SledAgentUpdateStatus": { - "type": "object", - "properties": { - "host_phase_2": { - "$ref": "#/components/schemas/HostPhase2Status" - }, - "sled_id": { - "$ref": "#/components/schemas/TypedUuidForSledKind" - }, - "zones": { - "title": "IdOrdMap", - "x-rust-type": { - "crate": "iddqd", - "parameters": [ - { - "$ref": "#/components/schemas/ZoneStatus" - } - ], - "path": "iddqd::IdOrdMap", - "version": "*" - }, - "type": "array", - "items": { - "$ref": "#/components/schemas/ZoneStatus" - }, - "uniqueItems": true - } - }, - "required": [ - "host_phase_2", - "sled_id", - "zones" - ] - }, "SledCpuFamily": { "description": "Identifies the kind of CPU present on a sled, determined by reading CPUID.\n\nThis is intended to broadly support the control plane answering the question \"can I run this instance on that sled?\" given an instance with either no or some CPU platform requirement. It is not enough information for more precise placement questions - for example, is a CPU a high-frequency part or many-core part? We don't include Genoa here, but in that CPU family there are high frequency parts, many-core parts, and large-cache parts. To support those questions (or satisfactorily answer #8730) we would need to collect additional information and send it along.", "oneOf": [ @@ -9008,80 +5512,6 @@ } ] }, - "SledId": { - "type": "object", - "properties": { - "id": { - "$ref": "#/components/schemas/TypedUuidForSledKind" - } - }, - "required": [ - "id" - ] - }, - "SledPolicy": { - "description": "The operator-defined policy of a sled.", - "oneOf": [ - { - "description": "The operator has indicated that the sled is in-service.", - "type": "object", - "properties": { - "kind": { - "type": "string", - "enum": [ - "in_service" - ] - }, - "provision_policy": { - "description": "Determines whether new resources can be provisioned onto the sled.", - "allOf": [ - { - "$ref": "#/components/schemas/SledProvisionPolicy" - } - ] - } - }, - "required": [ - "kind", - "provision_policy" - ] - }, - { - "description": "The operator has indicated that the sled has been permanently removed from service.\n\nThis is a terminal state: once a particular sled ID is expunged, it will never return to service. (The actual hardware may be reused, but it will be treated as a brand-new sled.)\n\nAn expunged sled is always non-provisionable.", - "type": "object", - "properties": { - "kind": { - "type": "string", - "enum": [ - "expunged" - ] - } - }, - "required": [ - "kind" - ] - } - ] - }, - "SledProvisionPolicy": { - "description": "The operator-defined provision policy of a sled.\n\nThis controls whether new resources are going to be provisioned on this sled.", - "oneOf": [ - { - "description": "New resources will be provisioned on this sled.", - "type": "string", - "enum": [ - "provisionable" - ] - }, - { - "description": "New resources will not be provisioned on this sled. However, if the sled is currently in service, existing resources will continue to be on this sled unless manually migrated off.", - "type": "string", - "enum": [ - "non_provisionable" - ] - } - ] - }, "SledRole": { "description": "Describes the role of the sled within the rack.\n\nNote that this may change if the sled is physically moved within the rack.", "oneOf": [ @@ -9101,19 +5531,6 @@ } ] }, - "SledSelector": { - "type": "object", - "properties": { - "sled": { - "description": "ID of the sled", - "type": "string", - "format": "uuid" - } - }, - "required": [ - "sled" - ] - }, "SledState": { "description": "The current state of the sled.", "oneOf": [ @@ -9191,169 +5608,48 @@ } }, "required": [ - "first_port", - "ip", - "last_port" - ] - }, - "SpStatus": { - "type": "object", - "properties": { - "slot0_version": { - "$ref": "#/components/schemas/TufRepoVersion" - }, - "slot1_version": { - "$ref": "#/components/schemas/TufRepoVersion" - } - }, - "required": [ - "slot0_version", - "slot1_version" - ] - }, - "SpType": { - "description": "`SpType`\n\n
JSON schema\n\n```json { \"type\": \"string\", \"enum\": [ \"sled\", \"power\", \"switch\" ] } ```
", - "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" - ] - }, - "SupportBundleCreate": { - "type": "object", - "properties": { - "user_comment": { - "nullable": true, - "description": "User comment for the support bundle", - "type": "string" - } - } - }, - "SupportBundleInfo": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "reason_for_creation": { - "type": "string" - }, - "reason_for_failure": { - "nullable": true, - "type": "string" - }, - "state": { - "$ref": "#/components/schemas/SupportBundleState" - }, - "time_created": { - "type": "string", - "format": "date-time" - }, - "user_comment": { - "nullable": true, - "type": "string" - } - }, - "required": [ - "id", - "reason_for_creation", - "state", - "time_created" - ] - }, - "SupportBundleInfoResultsPage": { - "description": "A single page of results", - "type": "object", - "properties": { - "items": { - "description": "list of items on this page of results", - "type": "array", - "items": { - "$ref": "#/components/schemas/SupportBundleInfo" - } - }, - "next_page": { - "nullable": true, - "description": "token used to fetch the next page of results (if any)", - "type": "string" - } - }, - "required": [ - "items" + "first_port", + "ip", + "last_port" ] }, - "SupportBundleState": { - "oneOf": [ - { - "description": "Support Bundle still actively being collected.\n\nThis is the initial state for a Support Bundle, and it will automatically transition to either \"Failing\" or \"Active\".\n\nIf a user no longer wants to access a Support Bundle, they can request cancellation, which will transition to the \"Destroying\" state.", - "type": "string", - "enum": [ - "collecting" - ] - }, - { - "description": "Support Bundle is being destroyed.\n\nOnce backing storage has been freed, this bundle is destroyed.", - "type": "string", - "enum": [ - "destroying" - ] - }, - { - "description": "Support Bundle was not created successfully, or was created and has lost backing storage.\n\nThe record of the bundle still exists for readability, but the only valid operation on these bundles is to destroy them.", - "type": "string", - "enum": [ - "failed" - ] - }, - { - "description": "Support Bundle has been processed, and is ready for usage.", - "type": "string", - "enum": [ - "active" - ] - } + "SpType": { + "description": "`SpType`\n\n
JSON schema\n\n```json { \"type\": \"string\", \"enum\": [ \"sled\", \"power\", \"switch\" ] } ```
", + "type": "string", + "enum": [ + "sled", + "power", + "switch" ] }, - "SupportBundleUpdate": { + "Srv": { "type": "object", "properties": { - "user_comment": { - "nullable": true, - "description": "User comment for the support bundle", + "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", @@ -9393,75 +5689,6 @@ "SwitchPutResponse": { "type": "object" }, - "TufRepoVersion": { - "oneOf": [ - { - "type": "object", - "properties": { - "zone_status_version": { - "type": "string", - "enum": [ - "unknown" - ] - } - }, - "required": [ - "zone_status_version" - ] - }, - { - "type": "object", - "properties": { - "zone_status_version": { - "type": "string", - "enum": [ - "install_dataset" - ] - } - }, - "required": [ - "zone_status_version" - ] - }, - { - "type": "object", - "properties": { - "details": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" - }, - "zone_status_version": { - "type": "string", - "enum": [ - "version" - ] - } - }, - "required": [ - "details", - "zone_status_version" - ] - }, - { - "type": "object", - "properties": { - "details": { - "type": "string" - }, - "zone_status_version": { - "type": "string", - "enum": [ - "error" - ] - } - }, - "required": [ - "details", - "zone_status_version" - ] - } - ] - }, "TxEqConfig": { "description": "Per-port tx-eq overrides. This can be used to fine-tune the transceiver equalization settings to improve signal integrity.", "type": "object", @@ -9506,10 +5733,6 @@ "type": "string", "format": "uuid" }, - "TypedUuidForDemoSagaKind": { - "type": "string", - "format": "uuid" - }, "TypedUuidForDownstairsRegionKind": { "type": "string", "format": "uuid" @@ -9546,134 +5769,6 @@ "type": "string", "format": "uuid" }, - "UninitializedSled": { - "description": "A sled that has not been added to an initialized rack yet", - "type": "object", - "properties": { - "baseboard": { - "$ref": "#/components/schemas/Baseboard" - }, - "cubby": { - "type": "integer", - "format": "uint16", - "minimum": 0 - }, - "rack_id": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "baseboard", - "cubby", - "rack_id" - ] - }, - "UninitializedSledId": { - "description": "The unique hardware ID for a sled", - "type": "object", - "properties": { - "part": { - "type": "string" - }, - "serial": { - "type": "string" - } - }, - "required": [ - "part", - "serial" - ] - }, - "UninitializedSledResultsPage": { - "description": "A single page of results", - "type": "object", - "properties": { - "items": { - "description": "list of items on this page of results", - "type": "array", - "items": { - "$ref": "#/components/schemas/UninitializedSled" - } - }, - "next_page": { - "nullable": true, - "description": "token used to fetch the next page of results (if any)", - "type": "string" - } - }, - "required": [ - "items" - ] - }, - "UpdateAttemptStatus": { - "description": "status of a single update attempt", - "type": "string", - "enum": [ - "not_started", - "fetching_artifact", - "precheck", - "updating", - "update_waiting", - "post_update", - "post_update_wait", - "done" - ] - }, - "UpdateCompletedHow": { - "type": "string", - "enum": [ - "found_no_changes_needed", - "completed_update", - "waited_for_concurrent_update", - "took_over_concurrent_update" - ] - }, - "UpdateStatus": { - "type": "object", - "properties": { - "mgs_driven": { - "title": "IdOrdMap", - "x-rust-type": { - "crate": "iddqd", - "parameters": [ - { - "$ref": "#/components/schemas/MgsDrivenUpdateStatus" - } - ], - "path": "iddqd::IdOrdMap", - "version": "*" - }, - "type": "array", - "items": { - "$ref": "#/components/schemas/MgsDrivenUpdateStatus" - }, - "uniqueItems": true - }, - "sleds": { - "title": "IdOrdMap", - "x-rust-type": { - "crate": "iddqd", - "parameters": [ - { - "$ref": "#/components/schemas/SledAgentUpdateStatus" - } - ], - "path": "iddqd::IdOrdMap", - "version": "*" - }, - "type": "array", - "items": { - "$ref": "#/components/schemas/SledAgentUpdateStatus" - }, - "uniqueItems": true - } - }, - "required": [ - "mgs_driven", - "sleds" - ] - }, "UplinkAddressConfig": { "type": "object", "properties": { @@ -9807,29 +5902,6 @@ "format": "uint32", "minimum": 0 }, - "WaitingStatus": { - "description": "externally-exposed status for waiting updates", - "type": "object", - "properties": { - "baseboard_id": { - "$ref": "#/components/schemas/BaseboardId" - }, - "nattempts_done": { - "type": "integer", - "format": "uint32", - "minimum": 0 - }, - "next_attempt_time": { - "type": "string", - "format": "date-time" - } - }, - "required": [ - "baseboard_id", - "nattempts_done", - "next_attempt_time" - ] - }, "ZoneAddWaitingOn": { "oneOf": [ { @@ -9849,25 +5921,6 @@ } ] }, - "ZoneStatus": { - "type": "object", - "properties": { - "version": { - "$ref": "#/components/schemas/TufRepoVersion" - }, - "zone_id": { - "$ref": "#/components/schemas/TypedUuidForOmicronZoneKind" - }, - "zone_type": { - "$ref": "#/components/schemas/OmicronZoneType" - } - }, - "required": [ - "version", - "zone_id", - "zone_type" - ] - }, "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": [ @@ -10063,25 +6116,6 @@ } ] }, - "TimeAndIdSortMode": { - "description": "Supported set of sort modes for scanning by timestamp and ID", - "oneOf": [ - { - "description": "sort in increasing order of timestamp and ID, i.e., earliest first", - "type": "string", - "enum": [ - "time_and_id_ascending" - ] - }, - { - "description": "sort in increasing order of timestamp and ID, i.e., most recent first", - "type": "string", - "enum": [ - "time_and_id_descending" - ] - } - ] - }, "TypedUuidForPropolisKind": { "type": "string", "format": "uuid" diff --git a/openapi/nexus-lockstep.json b/openapi/nexus-lockstep.json index 5acf17a057f..8dbc8b3c484 100644 --- a/openapi/nexus-lockstep.json +++ b/openapi/nexus-lockstep.json @@ -10,74 +10,7178 @@ "version": "0.0.1" }, "paths": { - "/v1/ping": { + "/bgtasks": { "get": { - "summary": "Ping API", - "description": "Always responds with Ok if it responds at all.", - "operationId": "ping", + "summary": "List background tasks", + "description": "This is a list of discrete background activities that Nexus carries out. This is exposed for support and debugging.", + "operationId": "bgtask_list", "responses": { "200": { "description": "successful operation", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Ping" + "title": "Map_of_BackgroundTask", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/BackgroundTask" + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/bgtasks/activate": { + "post": { + "summary": "Activates one or more background tasks, causing them to be run immediately", + "description": "if idle, or scheduled to run again as soon as possible if already running.", + "operationId": "bgtask_activate", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BackgroundTasksActivateRequest" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/bgtasks/view/{bgtask_name}": { + "get": { + "summary": "Fetch status of one background task", + "description": "This is exposed for support and debugging.", + "operationId": "bgtask_view", + "parameters": [ + { + "in": "path", + "name": "bgtask_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BackgroundTask" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/clickhouse/policy": { + "get": { + "summary": "Get the current clickhouse policy", + "operationId": "clickhouse_policy_get", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClickhousePolicy" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Set the new clickhouse policy", + "operationId": "clickhouse_policy_set", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClickhousePolicy" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/demo-saga": { + "post": { + "summary": "Kick off an instance of the \"demo\" saga", + "description": "This saga is used for demo and testing. The saga just waits until you complete using the `saga_demo_complete` API.", + "operationId": "saga_demo_create", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DemoSaga" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/demo-saga/{demo_saga_id}/complete": { + "post": { + "summary": "Complete a waiting demo saga", + "description": "Note that the id used here is not the same as the id of the saga. It's the one returned by the `saga_demo_create` API.", + "operationId": "saga_demo_complete", + "parameters": [ + { + "in": "path", + "name": "demo_saga_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/TypedUuidForDemoSagaKind" + } + } + ], + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/deployment/blueprints/all": { + "get": { + "summary": "Lists blueprints", + "operationId": "blueprint_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/IdSortMode" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlueprintMetadataResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + } + }, + "/deployment/blueprints/all/{blueprint_id}": { + "get": { + "summary": "Fetches one blueprint", + "operationId": "blueprint_view", + "parameters": [ + { + "in": "path", + "name": "blueprint_id", + "description": "ID of the blueprint", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Blueprint" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Deletes one blueprint", + "operationId": "blueprint_delete", + "parameters": [ + { + "in": "path", + "name": "blueprint_id", + "description": "ID of the blueprint", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/deployment/blueprints/import": { + "post": { + "summary": "Imports a client-provided blueprint", + "description": "This is intended for development and support, not end users or operators.", + "operationId": "blueprint_import", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Blueprint" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/deployment/blueprints/regenerate": { + "post": { + "summary": "Generates a new blueprint for the current system, re-evaluating anything", + "description": "that's changed since the last one was generated", + "operationId": "blueprint_regenerate", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Blueprint" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/deployment/blueprints/target": { + "get": { + "summary": "Fetches the current target blueprint, if any", + "operationId": "blueprint_target_view", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlueprintTarget" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Make the specified blueprint the new target", + "operationId": "blueprint_target_set", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlueprintTargetSet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlueprintTarget" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/deployment/blueprints/target/enabled": { + "put": { + "summary": "Set the `enabled` field of the current target blueprint", + "operationId": "blueprint_target_set_enabled", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlueprintTargetSet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlueprintTarget" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/deployment/reconfigurator-config": { + "get": { + "summary": "Get the current reconfigurator configuration", + "operationId": "reconfigurator_config_show_current", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReconfiguratorConfigView" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Update the reconfigurator config at the latest versions", + "operationId": "reconfigurator_config_set", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReconfiguratorConfigParam" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/deployment/reconfigurator-config/{version}": { + "get": { + "summary": "Get the reconfigurator config at `version` if it exists", + "operationId": "reconfigurator_config_show", + "parameters": [ + { + "in": "path", + "name": "version", + "required": true, + "schema": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReconfiguratorConfigView" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/deployment/update-status": { + "get": { + "summary": "Show deployed versions of artifacts", + "operationId": "update_status", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateStatus" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/experimental/v1/system/support-bundles": { + "get": { + "summary": "List all support bundles", + "operationId": "support_bundle_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/TimeAndIdSortMode" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportBundleInfoResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + }, + "post": { + "summary": "Create a new support bundle", + "operationId": "support_bundle_create", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportBundleCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportBundleInfo" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/experimental/v1/system/support-bundles/{bundle_id}": { + "get": { + "summary": "View a support bundle", + "operationId": "support_bundle_view", + "parameters": [ + { + "in": "path", + "name": "bundle_id", + "description": "ID of the support bundle", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportBundleInfo" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "summary": "Update a support bundle", + "operationId": "support_bundle_update", + "parameters": [ + { + "in": "path", + "name": "bundle_id", + "description": "ID of the support bundle", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportBundleUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportBundleInfo" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "summary": "Delete an existing support bundle", + "description": "May also be used to cancel a support bundle which is currently being collected, or to remove metadata for a support bundle that has failed.", + "operationId": "support_bundle_delete", + "parameters": [ + { + "in": "path", + "name": "bundle_id", + "description": "ID of the support bundle", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/experimental/v1/system/support-bundles/{bundle_id}/download": { + "get": { + "summary": "Download the contents of a support bundle", + "operationId": "support_bundle_download", + "parameters": [ + { + "in": "header", + "name": "range", + "description": "A request to access a portion of the resource, such as `bytes=0-499`\n\nSee: ", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "bundle_id", + "description": "ID of the support bundle", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "default": { + "description": "", + "content": { + "*/*": { + "schema": {} + } + } + } + } + }, + "head": { + "summary": "Download the metadata of a support bundle", + "operationId": "support_bundle_head", + "parameters": [ + { + "in": "header", + "name": "range", + "description": "A request to access a portion of the resource, such as `bytes=0-499`\n\nSee: ", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "bundle_id", + "description": "ID of the support bundle", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "default": { + "description": "", + "content": { + "*/*": { + "schema": {} + } + } + } + } + } + }, + "/experimental/v1/system/support-bundles/{bundle_id}/download/{file}": { + "get": { + "summary": "Download a file within a support bundle", + "operationId": "support_bundle_download_file", + "parameters": [ + { + "in": "header", + "name": "range", + "description": "A request to access a portion of the resource, such as `bytes=0-499`\n\nSee: ", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "bundle_id", + "description": "ID of the support bundle", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "path", + "name": "file", + "description": "The file within the bundle to download", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "", + "content": { + "*/*": { + "schema": {} + } + } + } + } + }, + "head": { + "summary": "Download the metadata of a file within the support bundle", + "operationId": "support_bundle_head_file", + "parameters": [ + { + "in": "header", + "name": "range", + "description": "A request to access a portion of the resource, such as `bytes=0-499`\n\nSee: ", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "bundle_id", + "description": "ID of the support bundle", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "path", + "name": "file", + "description": "The file within the bundle to download", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "description": "", + "content": { + "*/*": { + "schema": {} + } + } + } + } + } + }, + "/experimental/v1/system/support-bundles/{bundle_id}/index": { + "get": { + "summary": "Download the index of a support bundle", + "operationId": "support_bundle_index", + "parameters": [ + { + "in": "header", + "name": "range", + "description": "A request to access a portion of the resource, such as `bytes=0-499`\n\nSee: ", + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "bundle_id", + "description": "ID of the support bundle", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "default": { + "description": "", + "content": { + "*/*": { + "schema": {} + } + } + } + } + } + }, + "/instances/{instance_id}/migrate": { + "post": { + "operationId": "instance_migrate", + "parameters": [ + { + "in": "path", + "name": "instance_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/TypedUuidForInstanceKind" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceMigrateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Instance" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/mgs-updates": { + "get": { + "summary": "Fetch information about ongoing MGS updates", + "operationId": "mgs_updates", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MgsUpdateDriverStatus" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/oximeter/read-policy": { + "get": { + "summary": "Get the current oximeter read policy", + "operationId": "oximeter_read_policy_get", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OximeterReadPolicy" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Set the new oximeter read policy", + "operationId": "oximeter_read_policy_set", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OximeterReadPolicy" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/physical-disk/expunge": { + "post": { + "summary": "Mark a physical disk as expunged", + "description": "This is an irreversible process! It should only be called after sufficient warning to the operator.\n\nThis is idempotent.", + "operationId": "physical_disk_expunge", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhysicalDiskPath" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/quiesce": { + "get": { + "summary": "Check whether Nexus is running normally, quiescing, or fully quiesced.", + "operationId": "quiesce_get", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuiesceStatus" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "post": { + "summary": "Begin quiescing this Nexus instance", + "description": "This causes no new sagas to be started and eventually causes no database connections to become available. This is a one-way trip. There's no unquiescing Nexus.", + "operationId": "quiesce_start", + "responses": { + "204": { + "description": "resource updated" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/sagas": { + "get": { + "summary": "List sagas", + "operationId": "saga_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/IdSortMode" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SagaResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + } + }, + "/sagas/{saga_id}": { + "get": { + "summary": "Fetch a saga", + "operationId": "saga_view", + "parameters": [ + { + "in": "path", + "name": "saga_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Saga" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/sleds/add": { + "post": { + "summary": "Add sled to initialized rack", + "operationId": "sled_add", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UninitializedSledId" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SledId" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/sleds/expunge": { + "post": { + "summary": "Mark a sled as expunged", + "description": "This is an irreversible process! It should only be called after sufficient warning to the operator.\n\nThis is idempotent, and it returns the old policy of the sled.", + "operationId": "sled_expunge", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SledSelector" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SledPolicy" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/sleds/uninitialized": { + "get": { + "summary": "List uninitialized sleds", + "operationId": "sled_list_uninitialized", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UninitializedSledResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/ping": { + "get": { + "summary": "Ping API", + "description": "Always responds with Ok if it responds at all.", + "operationId": "ping", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ping" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + } + }, + "components": { + "schemas": { + "ActivationReason": { + "description": "Describes why a background task was activated\n\nThis is only used for debugging. This is deliberately not made available to the background task itself. See \"Design notes\" in the module-level documentation for details.", + "type": "string", + "enum": [ + "signaled", + "timeout", + "dependency" + ] + }, + "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}$" + }, + "BackgroundTask": { + "description": "Background tasks\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", + "properties": { + "current": { + "description": "Describes the current task status", + "allOf": [ + { + "$ref": "#/components/schemas/CurrentStatus" + } + ] + }, + "description": { + "description": "brief summary (for developers) of what this task does", + "type": "string" + }, + "last": { + "description": "Describes the last completed activation", + "allOf": [ + { + "$ref": "#/components/schemas/LastResult" + } + ] + }, + "name": { + "description": "unique identifier for this background task", + "type": "string" + }, + "period": { + "description": "how long after an activation completes before another will be triggered automatically\n\n(activations can also be triggered for other reasons)", + "allOf": [ + { + "$ref": "#/components/schemas/Duration" + } + ] + } + }, + "required": [ + "current", + "description", + "last", + "name", + "period" + ] + }, + "BackgroundTasksActivateRequest": { + "description": "Query parameters for Background Task activation requests.", + "type": "object", + "properties": { + "bgtask_names": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + "required": [ + "bgtask_names" + ] + }, + "Baseboard": { + "description": "Properties that uniquely identify an Oxide hardware component", + "type": "object", + "properties": { + "part": { + "type": "string" + }, + "revision": { + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "serial": { + "type": "string" + } + }, + "required": [ + "part", + "revision", + "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" + ] + }, + "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" + } + ] + }, + "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/TypedUuidForBlueprintKind" + } + ] + }, + "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/TypedUuidForBlueprintKind" + } + ] + }, + "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.", + "type": "object", + "properties": { + "artifact_version": { + "type": "string", + "enum": [ + "available" + ] + }, + "version": { + "$ref": "#/components/schemas/ArtifactVersion" + } + }, + "required": [ + "artifact_version", + "version" + ] + }, + { + "description": "The version could not be determined. This is non-fatal.", + "type": "object", + "properties": { + "artifact_version": { + "type": "string", + "enum": [ + "unknown" + ] + } + }, + "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/TypedUuidForDatasetKind" + }, + "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" + ] + }, + { + "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.", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "current_contents" + ] + } + }, + "required": [ + "type" + ] + }, + { + "description": "Set the phase 2 slot to the given artifact.\n\nThe artifact will come from an unpacked and distributed TUF repo.", + "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" + ] + } + ] + }, + "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" + ] + }, + "BlueprintMetadata": { + "description": "Describe high-level metadata about a blueprint", + "type": "object", + "properties": { + "cockroachdb_fingerprint": { + "description": "CockroachDB state fingerprint when this blueprint was created", + "type": "string" + }, + "cockroachdb_setting_preserve_downgrade": { + "nullable": true, + "description": "Whether to set `cluster.preserve_downgrade_option` and what to set it to (`None` if this value was retrieved from the database and was invalid)", + "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/TypedUuidForBlueprintKind" + } + ] + }, + "internal_dns_version": { + "description": "internal DNS version when this blueprint was created", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] + }, + "nexus_generation": { + "description": "The Nexus generation number\n\nSee [`Blueprint::nexus_generation`].", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] + }, + "parent_blueprint_id": { + "nullable": true, + "description": "which blueprint this blueprint is based on", + "allOf": [ + { + "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + } + ] + }, + "source": { + "description": "source of the blueprint (for debugging)", + "allOf": [ + { + "$ref": "#/components/schemas/BlueprintSource" + } + ] + }, + "target_release_minimum_generation": { + "description": "The minimum generation for the target release.\n\nSee [`Blueprint::target_release_minimum_generation`].", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] + }, + "time_created": { + "description": "when this blueprint was generated (for debugging)", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "cockroachdb_fingerprint", + "comment", + "creator", + "external_dns_version", + "id", + "internal_dns_version", + "nexus_generation", + "source", + "target_release_minimum_generation", + "time_created" + ] + }, + "BlueprintMetadataResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/BlueprintMetadata" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "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/TypedUuidForPhysicalDiskKind" + }, + "identity": { + "$ref": "#/components/schemas/DiskIdentity" + }, + "pool_id": { + "$ref": "#/components/schemas/TypedUuidForZpoolKind" + } + }, + "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.", + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "in_service" + ] + } + }, + "required": [ + "kind" + ] + }, + { + "description": "The physical disk is permanently gone.", + "type": "object", + "properties": { + "as_of_generation": { + "description": "Generation of the parent config in which this disk became expunged.", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] + }, + "kind": { + "type": "string", + "enum": [ + "expunged" + ] + }, + "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/TypedUuidForMupdateOverrideKind" + } + ] + }, + "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" + } + ] + }, + "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.", + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "rss" + ] + } + }, + "required": [ + "source" + ] + }, + { + "description": "A blueprint created by the planner, and we still have the associated planning report.", + "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" + } + ] + }, + "source": { + "type": "string", + "enum": [ + "planner" + ] + }, + "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" + ] + }, + { + "description": "A blueprint created by the planner but loaded from the database, so we no longer have the associated planning report.", + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "planner_loaded_from_database" + ] + } + }, + "required": [ + "source" + ] + }, + { + "description": "This blueprint was created by one of `reconfigurator-cli`'s blueprint editing subcommands.", + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "reconfigurator_cli_edit" + ] + } + }, + "required": [ + "source" + ] + }, + { + "description": "This blueprint was constructed by hand by an automated test.", + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "test" + ] + } + }, + "required": [ + "source" + ] + } + ] + }, + "BlueprintTarget": { + "description": "Describes what blueprint, if any, the system is currently working toward", + "type": "object", + "properties": { + "enabled": { + "description": "policy: should the system actively work towards this blueprint\n\nThis should generally be left enabled.", + "type": "boolean" + }, + "target_id": { + "description": "id of the blueprint that the system is trying to make real", + "allOf": [ + { + "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + } + ] + }, + "time_made_target": { + "description": "when this blueprint was made the target", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "enabled", + "target_id", + "time_made_target" + ] + }, + "BlueprintTargetSet": { + "description": "Specifies what blueprint, if any, the system should be working toward", + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "target_id": { + "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + } + }, + "required": [ + "enabled", + "target_id" + ] + }, + "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/TypedUuidForOmicronZoneKind" + }, + "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 + }, + "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" + ] + }, + "ClickhouseMode": { + "description": "How to deploy clickhouse nodes", + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "single_node_only" + ] + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "cluster_only" + ] + }, + "value": { + "type": "object", + "properties": { + "target_keepers": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "target_servers": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "required": [ + "target_keepers", + "target_servers" + ] + } + }, + "required": [ + "type", + "value" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "both" + ] + }, + "value": { + "type": "object", + "properties": { + "target_keepers": { + "type": "integer", + "format": "uint8", + "minimum": 0 + }, + "target_servers": { + "type": "integer", + "format": "uint8", + "minimum": 0 + } + }, + "required": [ + "target_keepers", + "target_servers" + ] + } + }, + "required": [ + "type", + "value" + ] + } + ] + }, + "ClickhousePolicy": { + "type": "object", + "properties": { + "mode": { + "$ref": "#/components/schemas/ClickhouseMode" + }, + "time_created": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "mode", + "time_created", + "version" + ] + }, + "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" + ] + } + ] + }, + "CompletedAttempt": { + "description": "externally-exposed status for a completed attempt", + "type": "object", + "properties": { + "elapsed": { + "$ref": "#/components/schemas/Duration" + }, + "nattempts_done": { + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "request": { + "$ref": "#/components/schemas/PendingMgsUpdate" + }, + "result": { + "x-rust-type": { + "crate": "std", + "parameters": [ + { + "$ref": "#/components/schemas/UpdateCompletedHow" + }, + { + "type": "string" + } + ], + "path": "::std::result::Result", + "version": "*" + }, + "oneOf": [ + { + "type": "object", + "properties": { + "ok": { + "$ref": "#/components/schemas/UpdateCompletedHow" + } + }, + "required": [ + "ok" + ] + }, + { + "type": "object", + "properties": { + "err": { + "type": "string" + } + }, + "required": [ + "err" + ] + } + ] + }, + "time_done": { + "type": "string", + "format": "date-time" + }, + "time_started": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "elapsed", + "nattempts_done", + "request", + "result", + "time_done", + "time_started" + ] + }, + "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" + ] + } + ] + }, + "CurrentStatus": { + "description": "Describes the current status of a background task", + "oneOf": [ + { + "description": "The background task is not running\n\nTypically, the task would be waiting for its next activation, which would happen after a timeout or some other event that triggers activation", + "type": "object", + "properties": { + "current_status": { + "type": "string", + "enum": [ + "idle" + ] + } + }, + "required": [ + "current_status" + ] + }, + { + "description": "The background task is currently running\n\nMore precisely, the task has been activated and has not yet finished this activation", + "type": "object", + "properties": { + "current_status": { + "type": "string", + "enum": [ + "running" + ] + }, + "details": { + "$ref": "#/components/schemas/CurrentStatusRunning" + } + }, + "required": [ + "current_status", + "details" + ] + } + ] + }, + "CurrentStatusRunning": { + "type": "object", + "properties": { + "iteration": { + "description": "which iteration this was (counter)", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "reason": { + "description": "what kind of event triggered this activation", + "allOf": [ + { + "$ref": "#/components/schemas/ActivationReason" + } + ] + }, + "start_time": { + "description": "wall-clock time when the current activation started", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "iteration", + "reason", + "start_time" + ] + }, + "DatasetKind": { + "description": "The kind of dataset. See the `DatasetKind` enum in omicron-common for possible values.", + "type": "string" + }, + "DemoSaga": { + "description": "Identifies an instance of the demo saga", + "type": "object", + "properties": { + "demo_saga_id": { + "$ref": "#/components/schemas/TypedUuidForDemoSagaKind" + }, + "saga_id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "demo_saga_id", + "saga_id" + ] + }, + "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" + ] + }, + "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" + ] + } + ] + }, + "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 + }, + "HeldDbClaimInfo": { + "description": "Describes an outstanding database claim (for debugging why quiesce is stuck)", + "type": "object", + "properties": { + "debug": { + "type": "string" + }, + "held_since": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0 + } + }, + "required": [ + "debug", + "held_since", + "id" + ] + }, + "HostPhase1Status": { + "oneOf": [ + { + "description": "This device has no host phase 1 status because it is not a sled (e.g., it's a PSC or switch).", + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "not_a_sled" + ] + } + }, + "required": [ + "kind" + ] + }, + { + "type": "object", + "properties": { + "active_slot": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/M2Slot" + } + ] + }, + "kind": { + "type": "string", + "enum": [ + "sled" + ] + }, + "sled_id": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TypedUuidForSledKind" + } + ] + }, + "slot_a_version": { + "$ref": "#/components/schemas/TufRepoVersion" + }, + "slot_b_version": { + "$ref": "#/components/schemas/TufRepoVersion" + } + }, + "required": [ + "kind", + "slot_a_version", + "slot_b_version" + ] + } + ] + }, + "HostPhase2Status": { + "type": "object", + "properties": { + "boot_disk": { + "x-rust-type": { + "crate": "std", + "parameters": [ + { + "$ref": "#/components/schemas/M2Slot" + }, + { + "type": "string" + } + ], + "path": "::std::result::Result", + "version": "*" + }, + "oneOf": [ + { + "type": "object", + "properties": { + "ok": { + "$ref": "#/components/schemas/M2Slot" + } + }, + "required": [ + "ok" + ] + }, + { + "type": "object", + "properties": { + "err": { + "type": "string" + } + }, + "required": [ + "err" + ] + } + ] + }, + "slot_a_version": { + "$ref": "#/components/schemas/TufRepoVersion" + }, + "slot_b_version": { + "$ref": "#/components/schemas/TufRepoVersion" + } + }, + "required": [ + "boot_disk", + "slot_a_version", + "slot_b_version" + ] + }, + "IdMapBlueprintDatasetConfig": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/BlueprintDatasetConfig" + } + }, + "IdMapBlueprintPhysicalDiskConfig": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/BlueprintPhysicalDiskConfig" + } + }, + "IdMapBlueprintZoneConfig": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/BlueprintZoneConfig" + } + }, + "InProgressUpdateStatus": { + "description": "externally-exposed status for each in-progress update", + "type": "object", + "properties": { + "baseboard_id": { + "$ref": "#/components/schemas/BaseboardId" + }, + "nattempts_done": { + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "status": { + "$ref": "#/components/schemas/UpdateAttemptStatus" + }, + "time_started": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "baseboard_id", + "nattempts_done", + "status", + "time_started" + ] + }, + "Instance": { + "description": "View of an Instance", + "type": "object", + "properties": { + "auto_restart_cooldown_expiration": { + "nullable": true, + "description": "The time at which the auto-restart cooldown period for this instance completes, permitting it to be automatically restarted again. If the instance enters the `Failed` state, it will not be restarted until after this time.\n\nIf this is not present, then either the instance has never been automatically restarted, or the cooldown period has already expired, allowing the instance to be restarted immediately if it fails.", + "type": "string", + "format": "date-time" + }, + "auto_restart_enabled": { + "description": "`true` if this instance's auto-restart policy will permit the control plane to automatically restart it if it enters the `Failed` state.", + "type": "boolean" + }, + "auto_restart_policy": { + "nullable": true, + "description": "The auto-restart policy configured for this instance, or `null` if no explicit policy has been configured.\n\nThis policy determines whether the instance should be automatically restarted by the control plane on failure. If this is `null`, the control plane will use the default policy when determining whether or not to automatically restart this instance, which may or may not allow it to be restarted. The value of the `auto_restart_enabled` field indicates whether the instance will be auto-restarted, based on its current policy or the default if it has no configured policy.", + "allOf": [ + { + "$ref": "#/components/schemas/InstanceAutoRestartPolicy" + } + ] + }, + "boot_disk_id": { + "nullable": true, + "description": "the ID of the disk used to boot this Instance, if a specific one is assigned.", + "type": "string", + "format": "uuid" + }, + "cpu_platform": { + "nullable": true, + "description": "The CPU platform for this instance. If this is `null`, the instance requires no particular CPU platform.", + "allOf": [ + { + "$ref": "#/components/schemas/InstanceCpuPlatform" + } + ] + }, + "description": { + "description": "human-readable free-form text about a resource", + "type": "string" + }, + "hostname": { + "description": "RFC1035-compliant hostname for the Instance.", + "type": "string" + }, + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" + }, + "memory": { + "description": "memory allocated for this Instance", + "allOf": [ + { + "$ref": "#/components/schemas/ByteCount" + } + ] + }, + "name": { + "description": "unique, mutable, user-controlled identifier for each resource", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "ncpus": { + "description": "number of CPUs allocated for this Instance", + "allOf": [ + { + "$ref": "#/components/schemas/InstanceCpuCount" + } + ] + }, + "project_id": { + "description": "id for the project containing this Instance", + "type": "string", + "format": "uuid" + }, + "run_state": { + "$ref": "#/components/schemas/InstanceState" + }, + "time_created": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" + }, + "time_last_auto_restarted": { + "nullable": true, + "description": "The timestamp of the most recent time this instance was automatically restarted by the control plane.\n\nIf this is not present, then this instance has not been automatically restarted.", + "type": "string", + "format": "date-time" + }, + "time_modified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" + }, + "time_run_state_updated": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "auto_restart_enabled", + "description", + "hostname", + "id", + "memory", + "name", + "ncpus", + "project_id", + "run_state", + "time_created", + "time_modified", + "time_run_state_updated" + ] + }, + "InstanceAutoRestartPolicy": { + "description": "A policy determining when an instance should be automatically restarted by the control plane.", + "oneOf": [ + { + "description": "The instance should not be automatically restarted by the control plane if it fails.", + "type": "string", + "enum": [ + "never" + ] + }, + { + "description": "If this instance is running and unexpectedly fails (e.g. due to a host software crash or unexpected host reboot), the control plane will make a best-effort attempt to restart it. The control plane may choose not to restart the instance to preserve the overall availability of the system.", + "type": "string", + "enum": [ + "best_effort" + ] + } + ] + }, + "InstanceCpuCount": { + "description": "The number of CPUs in an Instance", + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "InstanceCpuPlatform": { + "description": "A required CPU platform for an instance.\n\nWhen an instance specifies a required CPU platform:\n\n- The system may expose (to the VM) new CPU features that are only present on that platform (or on newer platforms of the same lineage that also support those features). - The instance must run on hosts that have CPUs that support all the features of the supplied platform.\n\nThat is, the instance is restricted to hosts that have the CPUs which support all features of the required platform, but in exchange the CPU features exposed by the platform are available for the guest to use. Note that this may prevent an instance from starting (if the hosts that could run it are full but there is capacity on other incompatible hosts).\n\nIf an instance does not specify a required CPU platform, then when it starts, the control plane selects a host for the instance and then supplies the guest with the \"minimum\" CPU platform supported by that host. This maximizes the number of hosts that can run the VM if it later needs to migrate to another host.\n\nIn all cases, the CPU features presented by a given CPU platform are a subset of what the corresponding hardware may actually support; features which cannot be used from a virtual environment or do not have full hypervisor support may be masked off. See RFD 314 for specific CPU features in a CPU platform.", + "oneOf": [ + { + "description": "An AMD Milan-like CPU platform.", + "type": "string", + "enum": [ + "amd_milan" + ] + }, + { + "description": "An AMD Turin-like CPU platform.", + "type": "string", + "enum": [ + "amd_turin" + ] + } + ] + }, + "InstanceMigrateRequest": { + "description": "Parameters used when migrating an instance.", + "type": "object", + "properties": { + "dst_sled_id": { + "description": "The ID of the sled to which to migrate the target instance.", + "type": "string", + "format": "uuid" + } + }, + "required": [ + "dst_sled_id" + ] + }, + "InstanceState": { + "description": "Running state of an Instance (primarily: booted or stopped)\n\nThis typically reflects whether it's starting, running, stopping, or stopped, but also includes states related to the Instance's lifecycle", + "oneOf": [ + { + "description": "The instance is being created.", + "type": "string", + "enum": [ + "creating" + ] + }, + { + "description": "The instance is currently starting up.", + "type": "string", + "enum": [ + "starting" + ] + }, + { + "description": "The instance is currently running.", + "type": "string", + "enum": [ + "running" + ] + }, + { + "description": "The instance has been requested to stop and a transition to \"Stopped\" is imminent.", + "type": "string", + "enum": [ + "stopping" + ] + }, + { + "description": "The instance is currently stopped.", + "type": "string", + "enum": [ + "stopped" + ] + }, + { + "description": "The instance is in the process of rebooting - it will remain in the \"rebooting\" state until the VM is starting once more.", + "type": "string", + "enum": [ + "rebooting" + ] + }, + { + "description": "The instance is in the process of migrating - it will remain in the \"migrating\" state until the migration process is complete and the destination propolis is ready to continue execution.", + "type": "string", + "enum": [ + "migrating" + ] + }, + { + "description": "The instance is attempting to recover from a failure.", + "type": "string", + "enum": [ + "repairing" + ] + }, + { + "description": "The instance has encountered a failure.", + "type": "string", + "enum": [ + "failed" + ] + }, + { + "description": "The instance has been deleted.", + "type": "string", + "enum": [ + "destroyed" + ] + } + ] + }, + "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" + } + ] + } + ] + }, + "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])$" + }, + "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])$" + }, + "KeeperId": { + "description": "A unique ID for a ClickHouse Keeper", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "LastResult": { + "oneOf": [ + { + "description": "The task has never completed an activation", + "type": "object", + "properties": { + "last_result": { + "type": "string", + "enum": [ + "never_completed" + ] + } + }, + "required": [ + "last_result" + ] + }, + { + "description": "The task has completed at least one activation", + "type": "object", + "properties": { + "details": { + "$ref": "#/components/schemas/LastResultCompleted" + }, + "last_result": { + "type": "string", + "enum": [ + "completed" + ] + } + }, + "required": [ + "details", + "last_result" + ] + } + ] + }, + "LastResultCompleted": { + "type": "object", + "properties": { + "details": { + "description": "arbitrary datum emitted by the background task" + }, + "elapsed": { + "description": "total time elapsed during the activation", + "allOf": [ + { + "$ref": "#/components/schemas/Duration" + } + ] + }, + "iteration": { + "description": "which iteration this was (counter)", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "reason": { + "description": "what kind of event triggered this activation", + "allOf": [ + { + "$ref": "#/components/schemas/ActivationReason" + } + ] + }, + "start_time": { + "description": "wall-clock time when the activation started", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "details", + "elapsed", + "iteration", + "reason", + "start_time" + ] + }, + "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 + }, + "MgsDrivenUpdateStatus": { + "type": "object", + "properties": { + "baseboard_description": { + "type": "string" + }, + "host_os_phase_1": { + "$ref": "#/components/schemas/HostPhase1Status" + }, + "rot": { + "$ref": "#/components/schemas/RotStatus" + }, + "rot_bootloader": { + "$ref": "#/components/schemas/RotBootloaderStatus" + }, + "sp": { + "$ref": "#/components/schemas/SpStatus" + } + }, + "required": [ + "baseboard_description", + "host_os_phase_1", + "rot", + "rot_bootloader", + "sp" + ] + }, + "MgsUpdateDriverStatus": { + "description": "Status of ongoing update attempts, recently completed attempts, and update requests that are waiting for retry.", + "type": "object", + "properties": { + "in_progress": { + "title": "IdOrdMap", + "x-rust-type": { + "crate": "iddqd", + "parameters": [ + { + "$ref": "#/components/schemas/InProgressUpdateStatus" + } + ], + "path": "iddqd::IdOrdMap", + "version": "*" + }, + "type": "array", + "items": { + "$ref": "#/components/schemas/InProgressUpdateStatus" + }, + "uniqueItems": true + }, + "recent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CompletedAttempt" + } + }, + "waiting": { + "title": "IdOrdMap", + "x-rust-type": { + "crate": "iddqd", + "parameters": [ + { + "$ref": "#/components/schemas/WaitingStatus" + } + ], + "path": "iddqd::IdOrdMap", + "version": "*" + }, + "type": "array", + "items": { + "$ref": "#/components/schemas/WaitingStatus" + }, + "uniqueItems": true + } + }, + "required": [ + "in_progress", + "recent", + "waiting" + ] + }, + "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 + }, + "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" + ] + } + ] + }, + "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" + ] + } + ] + }, + "NodeName": { + "description": "Unique name for a saga [`Node`]\n\nEach node requires a string name that's unique within its DAG. The name is used to identify its output. Nodes that depend on a given node (either directly or indirectly) can access the node's output using its name.", + "type": "string" + }, + "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/TypedUuidForExternalIpKind" + } + }, + "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/TypedUuidForExternalIpKind" + }, + "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/TypedUuidForExternalIpKind" + }, + "snat_cfg": { + "$ref": "#/components/schemas/SourceNatConfig" + } + }, + "required": [ + "id", + "snat_cfg" + ] + }, + "OmicronZoneType": { + "description": "Describes what kind of zone this is (i.e., what component is running in it) as well as any type-specific configuration", + "oneOf": [ + { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "dns_servers": { + "type": "array", + "items": { + "type": "string", + "format": "ip" + } + }, + "domain": { + "nullable": true, + "type": "string" + }, + "nic": { + "description": "The service vNIC providing outbound connectivity using OPTE.", + "allOf": [ + { + "$ref": "#/components/schemas/NetworkInterface" + } + ] + }, + "ntp_servers": { + "type": "array", + "items": { + "type": "string" + } + }, + "snat_cfg": { + "description": "The SNAT configuration for outbound connections.", + "allOf": [ + { + "$ref": "#/components/schemas/SourceNatConfig" + } + ] + }, + "type": { + "type": "string", + "enum": [ + "boundary_ntp" + ] + } + }, + "required": [ + "address", + "dns_servers", + "nic", + "ntp_servers", + "snat_cfg", + "type" + ] + }, + { + "description": "Type of clickhouse zone used for a single node clickhouse deployment", + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" + }, + "type": { + "type": "string", + "enum": [ + "clickhouse" + ] + } + }, + "required": [ + "address", + "dataset", + "type" + ] + }, + { + "description": "A zone used to run a Clickhouse Keeper node\n\nKeepers are only used in replicated clickhouse setups", + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "dataset": { + "$ref": "#/components/schemas/OmicronZoneDataset" + }, + "type": { + "type": "string", + "enum": [ + "clickhouse_keeper" + ] + } + }, + "required": [ + "address", + "dataset", + "type" + ] + }, + { + "description": "A zone used to run a Clickhouse Server in a replicated deployment", + "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.", + "type": "string" + }, + "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.", + "type": "string", + "format": "ip" + }, + "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 internal lockstep server is reachable. This shares the same IP address with `internal_address`.", + "default": 12232, + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "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", + "nic", + "type" + ] + }, + { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "oximeter" + ] + } + }, + "required": [ + "address", + "type" + ] + } + ] + }, + "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" + ] + } + ] + }, + "OximeterReadPolicy": { + "type": "object", + "properties": { + "mode": { + "$ref": "#/components/schemas/OximeterReadMode" + }, + "time_created": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "mode", + "time_created", + "version" + ] + }, + "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" + ] + }, + "PendingRecovery": { + "description": "Snapshot of reassignment state when a recovery pass started", + "type": "object", + "properties": { + "blueprint_id": { + "nullable": true, + "description": "which blueprint id we'd be fully caught up to upon completion", + "allOf": [ + { + "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + } + ] + }, + "generation": { + "description": "what `reassignment_generation` was when this recovery started", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] + } + }, + "required": [ + "generation" + ] + }, + "PendingSagaInfo": { + "description": "Describes a pending saga (for debugging why quiesce is stuck)", + "type": "object", + "properties": { + "recovered": { + "description": "If true, we know the saga needs to be recovered. It may or may not be running already.\n\nIf false, this saga was created in this Nexus process's lifetime. It's still running.", + "type": "boolean" + }, + "saga_id": { + "type": "string", + "format": "uuid" + }, + "saga_name": { + "type": "string" + }, + "time_pending": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "recovered", + "saga_id", + "saga_name", + "time_pending" + ] + }, + "PhysicalDiskPath": { + "type": "object", + "properties": { + "disk_id": { + "description": "ID of the physical disk", + "type": "string", + "format": "uuid" + } + }, + "required": [ + "disk_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/PingStatus" + } + ] + } + }, + "required": [ + "status" + ] + }, + "PingStatus": { + "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" + ] + }, + "PlanningAddOutOfEligibleSleds": { + "description": "How many discretionary zones we actually placed out of how many we wanted to place.", + "type": "object", + "properties": { + "placed": { + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "wanted_to_place": { + "type": "integer", + "format": "uint", + "minimum": 0 + } + }, + "required": [ + "placed", + "wanted_to_place" + ] + }, + "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/TypedUuidForSledKind" + }, + "uniqueItems": true + }, + "sleds_missing_crucible_zone": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForZpoolKind" + } + } + }, + "sleds_missing_ntp_zone": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true + }, + "sleds_waiting_for_ntp_zone": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true + }, + "sleds_without_ntp_zones_in_inventory": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true + }, + "sleds_without_zpools_for_ntp_zones": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "uniqueItems": true + }, + "sufficient_zones_exist": { + "description": "Discretionary zone kind → (wanted to place, num existing)", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/PlanningAddSufficientZonesExist" + } + }, + "waiting_on": { + "nullable": true, + "description": "What are we waiting on to start zone additions?", + "allOf": [ + { + "$ref": "#/components/schemas/ZoneAddWaitingOn" + } + ] + } + }, + "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" + ] + }, + "PlanningAddSufficientZonesExist": { + "description": "We have at least the minimum required number of zones of a given kind.", + "type": "object", + "properties": { + "num_existing": { + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "target_count": { + "type": "integer", + "format": "uint", + "minimum": 0 + } + }, + "required": [ + "num_existing", + "target_count" + ] + }, + "PlanningCockroachdbSettingsStepReport": { + "type": "object", + "properties": { + "preserve_downgrade": { + "$ref": "#/components/schemas/CockroachDbPreserveDowngrade" + } + }, + "required": [ + "preserve_downgrade" + ] + }, + "PlanningDecommissionStepReport": { + "type": "object", + "properties": { + "zombie_sleds": { + "description": "Decommissioned sleds that unexpectedly appeared as commissioned.", + "type": "array", + "items": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + } + } + }, + "required": [ + "zombie_sleds" + ] + }, + "PlanningExpungeStepReport": { + "type": "object", + "properties": { + "orphan_disks": { + "description": "Expunged disks not present in the parent blueprint.", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/TypedUuidForPhysicalDiskKind" + } + } + }, + "required": [ + "orphan_disks" + ] + }, + "PlanningMgsUpdatesStepReport": { + "type": "object", + "properties": { + "pending_mgs_updates": { + "$ref": "#/components/schemas/PendingMgsUpdates" + } + }, + "required": [ + "pending_mgs_updates" + ] + }, + "PlanningNexusGenerationBumpReport": { + "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": "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": "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" + ] + } + ] + }, + "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.", + "type": "object", + "properties": { + "host_phase_2_slot_a_eligible": { + "type": "boolean" + }, + "host_phase_2_slot_b_eligible": { + "type": "boolean" + }, + "num_dataset": { + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "num_eligible": { + "type": "integer", + "format": "uint", + "minimum": 0 + } + }, + "required": [ + "host_phase_2_slot_a_eligible", + "host_phase_2_slot_b_eligible", + "num_dataset", + "num_eligible" + ] + }, + "PlanningNoopImageSourceSkipSledHostPhase2Reason": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "both_slots_already_artifact" + ] + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "sled_not_in_inventory" + ] + } + }, + "required": [ + "type" + ] + } + ] + }, + "PlanningNoopImageSourceSkipSledZonesReason": { + "oneOf": [ + { + "type": "object", + "properties": { + "num_total": { + "type": "integer", + "format": "uint", + "minimum": 0 + }, + "type": { + "type": "string", + "enum": [ + "all_zones_already_artifact" + ] + } + }, + "required": [ + "num_total", + "type" + ] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "sled_not_in_inventory" + ] + } + }, + "required": [ + "type" + ] + }, + { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "error_retrieving_zone_manifest" + ] + } + }, + "required": [ + "error", + "type" + ] + }, + { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/TypedUuidForMupdateOverrideKind" + }, + "type": { + "type": "string", + "enum": [ + "remove_mupdate_override" + ] + } + }, + "required": [ + "id", + "type" + ] + } + ] + }, + "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_config": { + "$ref": "#/components/schemas/BlueprintZoneConfig" + } + }, + "required": [ + "desired_image_source", + "zone_config" + ] + }, + "PlanningZoneUpdatesStepReport": { + "type": "object", + "properties": { + "expunged_zones": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlueprintZoneConfig" + } + } + }, + "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/BlueprintZoneConfig" + } + } + }, + "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" + ] + }, + "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": [ + { + "description": "We have not yet determined based on persistent state if we're supposed to be quiesced or not", + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "undetermined" + ] + } + }, + "required": [ + "state" + ] + }, + { + "description": "Normal operation", + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "running" + ] + } + }, + "required": [ + "state" + ] + }, + { + "description": "New sagas disallowed, but some are still running on some Nexus instances", + "type": "object", + "properties": { + "quiesce_details": { + "type": "object", + "properties": { + "time_requested": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "time_requested" + ] + }, + "state": { + "type": "string", + "enum": [ + "draining_sagas" + ] + } + }, + "required": [ + "quiesce_details", + "state" + ] + }, + { + "description": "No sagas running on any Nexus instances\n\nNo new database connections may be claimed, but some database connections are still held.", + "type": "object", + "properties": { + "quiesce_details": { + "type": "object", + "properties": { + "duration_draining_sagas": { + "$ref": "#/components/schemas/Duration" + }, + "time_requested": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "duration_draining_sagas", + "time_requested" + ] + }, + "state": { + "type": "string", + "enum": [ + "draining_db" + ] + } + }, + "required": [ + "quiesce_details", + "state" + ] + }, + { + "description": "No database connections in use except to record the final \"quiesced\" state", + "type": "object", + "properties": { + "quiesce_details": { + "type": "object", + "properties": { + "duration_draining_db": { + "$ref": "#/components/schemas/Duration" + }, + "duration_draining_sagas": { + "$ref": "#/components/schemas/Duration" + }, + "time_requested": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "duration_draining_db", + "duration_draining_sagas", + "time_requested" + ] + }, + "state": { + "type": "string", + "enum": [ + "recording_quiesce" + ] + } + }, + "required": [ + "quiesce_details", + "state" + ] + }, + { + "description": "Nexus has no sagas running and is not using the database", + "type": "object", + "properties": { + "quiesce_details": { + "type": "object", + "properties": { + "duration_draining_db": { + "$ref": "#/components/schemas/Duration" + }, + "duration_draining_sagas": { + "$ref": "#/components/schemas/Duration" + }, + "duration_recording_quiesce": { + "$ref": "#/components/schemas/Duration" + }, + "duration_total": { + "$ref": "#/components/schemas/Duration" + }, + "time_quiesced": { + "type": "string", + "format": "date-time" + }, + "time_requested": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "duration_draining_db", + "duration_draining_sagas", + "duration_recording_quiesce", + "duration_total", + "time_quiesced", + "time_requested" + ] + }, + "state": { + "type": "string", + "enum": [ + "quiesced" + ] + } + }, + "required": [ + "quiesce_details", + "state" + ] + } + ] + }, + "QuiesceStatus": { + "description": "Describes whether Nexus is quiescing or quiesced and what, if anything, is blocking the quiesce process\n\n**Quiescing** is the process of draining Nexus of running sagas and stopping all use of the database in preparation for upgrade. See [`QuiesceState`] for more on the stages involved.", + "type": "object", + "properties": { + "db_claims": { + "title": "IdOrdMap", + "description": "what database claims are currently held (by any part of Nexus)\n\nEntries here prevent transitioning from `WaitingForDb` to `Quiesced`.", + "x-rust-type": { + "crate": "iddqd", + "parameters": [ + { + "$ref": "#/components/schemas/HeldDbClaimInfo" + } + ], + "path": "iddqd::IdOrdMap", + "version": "*" + }, + "type": "array", + "items": { + "$ref": "#/components/schemas/HeldDbClaimInfo" + }, + "uniqueItems": true + }, + "sagas": { + "description": "information about saga quiescing", + "allOf": [ + { + "$ref": "#/components/schemas/SagaQuiesceStatus" + } + ] + }, + "state": { + "description": "what stage of quiescing is Nexus at", + "allOf": [ + { + "$ref": "#/components/schemas/QuiesceState" + } + ] + } + }, + "required": [ + "db_claims", + "sagas", + "state" + ] + }, + "ReconfiguratorConfig": { + "type": "object", + "properties": { + "planner_config": { + "$ref": "#/components/schemas/PlannerConfig" + }, + "planner_enabled": { + "type": "boolean" + } + }, + "required": [ + "planner_config", + "planner_enabled" + ] + }, + "ReconfiguratorConfigParam": { + "type": "object", + "properties": { + "config": { + "$ref": "#/components/schemas/ReconfiguratorConfig" + }, + "version": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "config", + "version" + ] + }, + "ReconfiguratorConfigView": { + "type": "object", + "properties": { + "config": { + "$ref": "#/components/schemas/ReconfiguratorConfig" + }, + "time_modified": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "config", + "time_modified", + "version" + ] + }, + "RotBootloaderStatus": { + "type": "object", + "properties": { + "stage0_next_version": { + "$ref": "#/components/schemas/TufRepoVersion" + }, + "stage0_version": { + "$ref": "#/components/schemas/TufRepoVersion" + } + }, + "required": [ + "stage0_next_version", + "stage0_version" + ] + }, + "RotSlot": { + "oneOf": [ + { + "type": "object", + "properties": { + "slot": { + "type": "string", + "enum": [ + "a" + ] + } + }, + "required": [ + "slot" + ] + }, + { + "type": "object", + "properties": { + "slot": { + "type": "string", + "enum": [ + "b" + ] + } + }, + "required": [ + "slot" + ] + } + ] + }, + "RotStatus": { + "type": "object", + "properties": { + "active_slot": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/RotSlot" + } + ] + }, + "slot_a_version": { + "$ref": "#/components/schemas/TufRepoVersion" + }, + "slot_b_version": { + "$ref": "#/components/schemas/TufRepoVersion" + } + }, + "required": [ + "slot_a_version", + "slot_b_version" + ] + }, + "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", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "state": { + "$ref": "#/components/schemas/SagaState" + } + }, + "required": [ + "id", + "state" + ] + }, + "SagaErrorInfo": { + "oneOf": [ + { + "type": "object", + "properties": { + "error": { + "type": "string", + "enum": [ + "action_failed" + ] + }, + "source_error": {} + }, + "required": [ + "error", + "source_error" + ] + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "enum": [ + "deserialize_failed" + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "error", + "message" + ] + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "enum": [ + "injected_error" + ] + } + }, + "required": [ + "error" + ] + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "enum": [ + "serialize_failed" + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "error", + "message" + ] + }, + { + "type": "object", + "properties": { + "error": { + "type": "string", + "enum": [ + "subsaga_create_failed" + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "error", + "message" + ] + } + ] + }, + "SagaQuiesceStatus": { + "type": "object", + "properties": { + "drained_blueprint_id": { + "nullable": true, + "description": "blueprint id that we're \"fully drained up to\"\n\nIf this value is non-`None`, that means that:\n\n- saga creation is disallowed - no sagas are running - we have re-assigned sagas from other Nexus instances expunged in this blueprint or earlier - we have finished recovery for all those sagas (that had been assigned to us as of the re-assignment pass for this blueprint id)\n\nThis means that the only way we can wind up running another saga is if there's a new blueprint that expunges a different Nexus zone.", + "allOf": [ + { + "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + } + ] + }, + "first_recovery_complete": { + "description": "whether at least one recovery pass has successfully completed\n\nWe have to track this because we can't quiesce until we know we've recovered all outstanding sagas.", + "type": "boolean" + }, + "new_sagas_allowed": { + "description": "current policy: are we allowed to *create* new sagas?\n\nThis also affects re-assigning sagas from expunged Nexus instances to ourselves. It does **not** affect saga recovery.", + "allOf": [ + { + "$ref": "#/components/schemas/SagasAllowed" + } + ] + }, + "reassignment_blueprint_id": { + "nullable": true, + "description": "blueprint id associated with last successful saga reassignment\n\nSimilar to the generation number, this is used to track whether we've accounted for all sagas for all expungements up through this target blueprint.", + "allOf": [ + { + "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + } + ] + }, + "reassignment_generation": { + "description": "generation number for the saga reassignment\n\nThis gets bumped whenever a saga reassignment operation completes that may have re-assigned us some sagas. It's used to keep track of when we've recovered all sagas that could be assigned to us.", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] + }, + "reassignment_pending": { + "description": "whether there is a saga reassignment operation happening\n\nThese operatinos may assign new sagas to Nexus that must be recovered and completed before quiescing can finish.", + "type": "boolean" + }, + "recovered_blueprint_id": { + "nullable": true, + "description": "blueprint id that saga recovery has \"caught up to\"\n\nThis means that we have finished recovering any sagas that were re-assigned to us due to expungements of other Nexus zones up through this blueprint. Put differently: we know that we will never be assigned more sagas due to expungement unless the target blueprint changes past this one.\n\nThis does not mean that we've fully drained all sagas up through this blueprint. There may still be sagas running.", + "allOf": [ + { + "$ref": "#/components/schemas/TypedUuidForBlueprintKind" + } + ] + }, + "recovered_reassignment_generation": { + "description": "\"saga reassignment generation number\" that was \"caught up to\" by the last recovery pass\n\nThis is used with `reassignment_generation` to help us know when we've recovered all the sagas that may have been assigned to us during a given reassignment pass. See `reassignment_done()` for details.", + "allOf": [ + { + "$ref": "#/components/schemas/Generation" + } + ] + }, + "recovery_pending": { + "nullable": true, + "description": "If a recovery pass is ongoing, a snapshot of reassignment state when it started (which reflects what we'll be caught up to when it finishes)", + "allOf": [ + { + "$ref": "#/components/schemas/PendingRecovery" + } + ] + }, + "sagas_pending": { + "title": "IdOrdMap", + "description": "list of sagas we need to wait to complete before quiescing\n\nThese are basically running sagas. They may have been created in this Nexus process lifetime or created in another process and then recovered in this one.", + "x-rust-type": { + "crate": "iddqd", + "parameters": [ + { + "$ref": "#/components/schemas/PendingSagaInfo" + } + ], + "path": "iddqd::IdOrdMap", + "version": "*" + }, + "type": "array", + "items": { + "$ref": "#/components/schemas/PendingSagaInfo" + }, + "uniqueItems": true + } + }, + "required": [ + "first_recovery_complete", + "new_sagas_allowed", + "reassignment_generation", + "reassignment_pending", + "recovered_reassignment_generation", + "sagas_pending" + ] + }, + "SagaResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Saga" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "SagaState": { + "oneOf": [ + { + "description": "Saga is currently executing", + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "running" + ] + } + }, + "required": [ + "state" + ] + }, + { + "description": "Saga completed successfully", + "type": "object", + "properties": { + "state": { + "type": "string", + "enum": [ + "succeeded" + ] + } + }, + "required": [ + "state" + ] + }, + { + "description": "One or more saga actions failed and the saga was successfully unwound (i.e., undo actions were executed for any actions that were completed). The saga is no longer running.", + "type": "object", + "properties": { + "error_info": { + "$ref": "#/components/schemas/SagaErrorInfo" + }, + "error_node_name": { + "$ref": "#/components/schemas/NodeName" + }, + "state": { + "type": "string", + "enum": [ + "failed" + ] + } + }, + "required": [ + "error_info", + "error_node_name", + "state" + ] + }, + { + "description": "One or more saga actions failed, *and* one or more undo actions failed during unwinding. State managed by the saga may now be inconsistent. Support may be required to repair the state. The saga is no longer running.", + "type": "object", + "properties": { + "error_info": { + "$ref": "#/components/schemas/SagaErrorInfo" + }, + "error_node_name": { + "$ref": "#/components/schemas/NodeName" + }, + "state": { + "type": "string", + "enum": [ + "stuck" + ] + }, + "undo_error_node_name": { + "$ref": "#/components/schemas/NodeName" + }, + "undo_source_error": {} + }, + "required": [ + "error_info", + "error_node_name", + "state", + "undo_error_node_name", + "undo_source_error" + ] + } + ] + }, + "SagasAllowed": { + "description": "Policy determining whether new sagas are allowed to be started\n\nThis is used by Nexus quiesce to disallow creation of new sagas when we're trying to quiesce Nexus.", + "oneOf": [ + { + "description": "New sagas may be started (normal condition)", + "type": "string", + "enum": [ + "allowed" + ] + }, + { + "description": "New sagas may not be started because we're quiescing or quiesced", + "type": "string", + "enum": [ + "disallowed_quiesce" + ] + }, + { + "description": "New sagas may not be started because we just started up and haven't determined if we're quiescing yet", + "type": "string", + "enum": [ + "disallowed_unknown" + ] + } + ] + }, + "ServerId": { + "description": "A unique ID for a Clickhouse Server", + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "SledAgentUpdateStatus": { + "type": "object", + "properties": { + "host_phase_2": { + "$ref": "#/components/schemas/HostPhase2Status" + }, + "sled_id": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + }, + "zones": { + "title": "IdOrdMap", + "x-rust-type": { + "crate": "iddqd", + "parameters": [ + { + "$ref": "#/components/schemas/ZoneStatus" } + ], + "path": "iddqd::IdOrdMap", + "version": "*" + }, + "type": "array", + "items": { + "$ref": "#/components/schemas/ZoneStatus" + }, + "uniqueItems": true + } + }, + "required": [ + "host_phase_2", + "sled_id", + "zones" + ] + }, + "SledId": { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/TypedUuidForSledKind" + } + }, + "required": [ + "id" + ] + }, + "SledPolicy": { + "description": "The operator-defined policy of a sled.", + "oneOf": [ + { + "description": "The operator has indicated that the sled is in-service.", + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "in_service" + ] + }, + "provision_policy": { + "description": "Determines whether new resources can be provisioned onto the sled.", + "allOf": [ + { + "$ref": "#/components/schemas/SledProvisionPolicy" + } + ] } - } + }, + "required": [ + "kind", + "provision_policy" + ] }, - "4XX": { - "$ref": "#/components/responses/Error" + { + "description": "The operator has indicated that the sled has been permanently removed from service.\n\nThis is a terminal state: once a particular sled ID is expunged, it will never return to service. (The actual hardware may be reused, but it will be treated as a brand-new sled.)\n\nAn expunged sled is always non-provisionable.", + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "expunged" + ] + } + }, + "required": [ + "kind" + ] + } + ] + }, + "SledProvisionPolicy": { + "description": "The operator-defined provision policy of a sled.\n\nThis controls whether new resources are going to be provisioned on this sled.", + "oneOf": [ + { + "description": "New resources will be provisioned on this sled.", + "type": "string", + "enum": [ + "provisionable" + ] }, - "5XX": { - "$ref": "#/components/responses/Error" + { + "description": "New resources will not be provisioned on this sled. However, if the sled is currently in service, existing resources will continue to be on this sled unless manually migrated off.", + "type": "string", + "enum": [ + "non_provisionable" + ] + } + ] + }, + "SledSelector": { + "type": "object", + "properties": { + "sled": { + "description": "ID of the sled", + "type": "string", + "format": "uuid" + } + }, + "required": [ + "sled" + ] + }, + "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" + ] + } + ] + }, + "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" + ] + }, + "SpStatus": { + "type": "object", + "properties": { + "slot0_version": { + "$ref": "#/components/schemas/TufRepoVersion" + }, + "slot1_version": { + "$ref": "#/components/schemas/TufRepoVersion" + } + }, + "required": [ + "slot0_version", + "slot1_version" + ] + }, + "SpType": { + "description": "`SpType`\n\n
JSON schema\n\n```json { \"type\": \"string\", \"enum\": [ \"sled\", \"power\", \"switch\" ] } ```
", + "type": "string", + "enum": [ + "sled", + "power", + "switch" + ] + }, + "SupportBundleCreate": { + "type": "object", + "properties": { + "user_comment": { + "nullable": true, + "description": "User comment for the support bundle", + "type": "string" } } - } - } - }, - "components": { - "schemas": { - "Error": { - "description": "Error information from a response.", + }, + "SupportBundleInfo": { "type": "object", "properties": { - "error_code": { + "id": { + "type": "string", + "format": "uuid" + }, + "reason_for_creation": { "type": "string" }, - "message": { + "reason_for_failure": { + "nullable": true, "type": "string" }, - "request_id": { + "state": { + "$ref": "#/components/schemas/SupportBundleState" + }, + "time_created": { + "type": "string", + "format": "date-time" + }, + "user_comment": { + "nullable": true, "type": "string" } }, "required": [ - "message", - "request_id" + "id", + "reason_for_creation", + "state", + "time_created" ] }, - "Ping": { + "SupportBundleInfoResultsPage": { + "description": "A single page of results", "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" + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/SupportBundleInfo" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "SupportBundleState": { + "oneOf": [ + { + "description": "Support Bundle still actively being collected.\n\nThis is the initial state for a Support Bundle, and it will automatically transition to either \"Failing\" or \"Active\".\n\nIf a user no longer wants to access a Support Bundle, they can request cancellation, which will transition to the \"Destroying\" state.", + "type": "string", + "enum": [ + "collecting" + ] + }, + { + "description": "Support Bundle is being destroyed.\n\nOnce backing storage has been freed, this bundle is destroyed.", + "type": "string", + "enum": [ + "destroying" + ] + }, + { + "description": "Support Bundle was not created successfully, or was created and has lost backing storage.\n\nThe record of the bundle still exists for readability, but the only valid operation on these bundles is to destroy them.", + "type": "string", + "enum": [ + "failed" + ] + }, + { + "description": "Support Bundle has been processed, and is ready for usage.", + "type": "string", + "enum": [ + "active" + ] + } + ] + }, + "SupportBundleUpdate": { + "type": "object", + "properties": { + "user_comment": { + "nullable": true, + "description": "User comment for the support bundle", + "type": "string" + } + } + }, + "TufRepoVersion": { + "oneOf": [ + { + "type": "object", + "properties": { + "zone_status_version": { + "type": "string", + "enum": [ + "unknown" + ] + } + }, + "required": [ + "zone_status_version" + ] + }, + { + "type": "object", + "properties": { + "zone_status_version": { + "type": "string", + "enum": [ + "install_dataset" + ] + } + }, + "required": [ + "zone_status_version" + ] + }, + { + "type": "object", + "properties": { + "details": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, + "zone_status_version": { + "type": "string", + "enum": [ + "version" + ] + } + }, + "required": [ + "details", + "zone_status_version" + ] + }, + { + "type": "object", + "properties": { + "details": { + "type": "string" + }, + "zone_status_version": { + "type": "string", + "enum": [ + "error" + ] } + }, + "required": [ + "details", + "zone_status_version" ] } + ] + }, + "TypedUuidForBlueprintKind": { + "type": "string", + "format": "uuid" + }, + "TypedUuidForDatasetKind": { + "type": "string", + "format": "uuid" + }, + "TypedUuidForDemoSagaKind": { + "type": "string", + "format": "uuid" + }, + "TypedUuidForExternalIpKind": { + "type": "string", + "format": "uuid" + }, + "TypedUuidForMupdateOverrideKind": { + "type": "string", + "format": "uuid" + }, + "TypedUuidForOmicronZoneKind": { + "type": "string", + "format": "uuid" + }, + "TypedUuidForPhysicalDiskKind": { + "type": "string", + "format": "uuid" + }, + "TypedUuidForSledKind": { + "type": "string", + "format": "uuid" + }, + "TypedUuidForZpoolKind": { + "type": "string", + "format": "uuid" + }, + "UninitializedSled": { + "description": "A sled that has not been added to an initialized rack yet", + "type": "object", + "properties": { + "baseboard": { + "$ref": "#/components/schemas/Baseboard" + }, + "cubby": { + "type": "integer", + "format": "uint16", + "minimum": 0 + }, + "rack_id": { + "type": "string", + "format": "uuid" + } }, "required": [ - "status" + "baseboard", + "cubby", + "rack_id" ] }, - "PingStatus": { + "UninitializedSledId": { + "description": "The unique hardware ID for a sled", + "type": "object", + "properties": { + "part": { + "type": "string" + }, + "serial": { + "type": "string" + } + }, + "required": [ + "part", + "serial" + ] + }, + "UninitializedSledResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/UninitializedSled" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "UpdateAttemptStatus": { + "description": "status of a single update attempt", "type": "string", "enum": [ - "ok" + "not_started", + "fetching_artifact", + "precheck", + "updating", + "update_waiting", + "post_update", + "post_update_wait", + "done" + ] + }, + "UpdateCompletedHow": { + "type": "string", + "enum": [ + "found_no_changes_needed", + "completed_update", + "waited_for_concurrent_update", + "took_over_concurrent_update" + ] + }, + "UpdateStatus": { + "type": "object", + "properties": { + "mgs_driven": { + "title": "IdOrdMap", + "x-rust-type": { + "crate": "iddqd", + "parameters": [ + { + "$ref": "#/components/schemas/MgsDrivenUpdateStatus" + } + ], + "path": "iddqd::IdOrdMap", + "version": "*" + }, + "type": "array", + "items": { + "$ref": "#/components/schemas/MgsDrivenUpdateStatus" + }, + "uniqueItems": true + }, + "sleds": { + "title": "IdOrdMap", + "x-rust-type": { + "crate": "iddqd", + "parameters": [ + { + "$ref": "#/components/schemas/SledAgentUpdateStatus" + } + ], + "path": "iddqd::IdOrdMap", + "version": "*" + }, + "type": "array", + "items": { + "$ref": "#/components/schemas/SledAgentUpdateStatus" + }, + "uniqueItems": true + } + }, + "required": [ + "mgs_driven", + "sleds" + ] + }, + "Vni": { + "description": "A Geneve Virtual Network Identifier", + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "WaitingStatus": { + "description": "externally-exposed status for waiting updates", + "type": "object", + "properties": { + "baseboard_id": { + "$ref": "#/components/schemas/BaseboardId" + }, + "nattempts_done": { + "type": "integer", + "format": "uint32", + "minimum": 0 + }, + "next_attempt_time": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "baseboard_id", + "nattempts_done", + "next_attempt_time" + ] + }, + "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" + ] + } + ] + }, + "ZoneStatus": { + "type": "object", + "properties": { + "version": { + "$ref": "#/components/schemas/TufRepoVersion" + }, + "zone_id": { + "$ref": "#/components/schemas/TypedUuidForOmicronZoneKind" + }, + "zone_type": { + "$ref": "#/components/schemas/OmicronZoneType" + } + }, + "required": [ + "version", + "zone_id", + "zone_type" + ] + }, + "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 discretionary zone placement.", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "discretionary_zones" + ] + } + }, + "required": [ + "type" + ] + }, + { + "description": "Waiting on updates to RoT / SP / Host OS / etc.", + "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}$" + }, + "IdSortMode": { + "description": "Supported set of sort modes for scanning by id only.\n\nCurrently, we only support scanning in ascending order.", + "oneOf": [ + { + "description": "sort in increasing order of \"id\"", + "type": "string", + "enum": [ + "id_ascending" + ] + } ] + }, + "TimeAndIdSortMode": { + "description": "Supported set of sort modes for scanning by timestamp and ID", + "oneOf": [ + { + "description": "sort in increasing order of timestamp and ID, i.e., earliest first", + "type": "string", + "enum": [ + "time_and_id_ascending" + ] + }, + { + "description": "sort in increasing order of timestamp and ID, i.e., most recent first", + "type": "string", + "enum": [ + "time_and_id_descending" + ] + } + ] + }, + "TypedUuidForInstanceKind": { + "type": "string", + "format": "uuid" } }, "responses": {