diff --git a/Cargo.lock b/Cargo.lock index 26b6de0e8c..536f81f402 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2043,12 +2043,12 @@ dependencies = [ [[package]] name = "dropshot" -version = "0.9.1-dev" -source = "git+https://github.com/oxidecomputer/dropshot?branch=main#29ae98d1f909c6832661408a4c03f929e8afa6e9" +version = "0.10.1-dev" +source = "git+https://github.com/oxidecomputer//dropshot?branch=sunshowers/spr/wip-prototype-for-trait-based-dropshot-servers#b5647d2986d68f3040ff6a959abaaf77c0deaf78" dependencies = [ "async-stream", "async-trait", - "base64 0.21.7", + "base64 0.22.0", "bytes", "camino", "chrono", @@ -2056,7 +2056,7 @@ dependencies = [ "dropshot_endpoint", "form_urlencoded", "futures", - "hostname", + "hostname 0.4.0", "http 0.2.12", "hyper 0.14.28", "indexmap 2.2.6", @@ -2064,7 +2064,6 @@ dependencies = [ "openapiv3", "paste", "percent-encoding", - "proc-macro2", "rustls 0.22.4", "rustls-pemfile 2.1.2", "schemars", @@ -2081,7 +2080,8 @@ dependencies = [ "tokio", "tokio-rustls 0.25.0", "toml 0.8.12", - "usdt 0.3.5", + "trait-variant", + "usdt 0.5.0", "uuid 1.8.0", "version_check", "waitgroup", @@ -2089,8 +2089,8 @@ dependencies = [ [[package]] name = "dropshot_endpoint" -version = "0.9.1-dev" -source = "git+https://github.com/oxidecomputer/dropshot?branch=main#29ae98d1f909c6832661408a4c03f929e8afa6e9" +version = "0.10.1-dev" +source = "git+https://github.com/oxidecomputer//dropshot?branch=sunshowers/spr/wip-prototype-for-trait-based-dropshot-servers#b5647d2986d68f3040ff6a959abaaf77c0deaf78" dependencies = [ "proc-macro2", "quote", @@ -3130,6 +3130,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "hostname" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +dependencies = [ + "cfg-if", + "libc", + "windows 0.52.0", +] + [[package]] name = "http" version = "0.2.12" @@ -3416,7 +3427,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows 0.48.0", ] [[package]] @@ -4646,6 +4657,24 @@ dependencies = [ "serde_json", ] +[[package]] +name = "nexus-internal-api" +version = "0.1.0" +dependencies = [ + "dropshot", + "expectorate", + "nexus-types", + "omicron-common", + "omicron-uuid-kinds", + "omicron-workspace-hack", + "openapi-lint", + "openapiv3", + "schemars", + "serde", + "serde_json", + "uuid 1.8.0", +] + [[package]] name = "nexus-inventory" version = "0.1.0" @@ -5393,6 +5422,7 @@ dependencies = [ "nexus-db-model", "nexus-db-queries", "nexus-defaults", + "nexus-internal-api", "nexus-inventory", "nexus-metrics-producer-gc", "nexus-networking", @@ -5737,7 +5767,6 @@ dependencies = [ "aho-corasick", "anyhow", "base16ct", - "base64 0.22.0", "bit-set", "bit-vec", "bitflags 1.3.2", @@ -5838,7 +5867,7 @@ dependencies = [ "trust-dns-proto", "unicode-bidi", "unicode-normalization", - "usdt 0.3.5", + "usdt 0.5.0", "usdt-impl 0.5.0", "uuid 1.8.0", "yasna", @@ -7629,7 +7658,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" dependencies = [ - "hostname", + "hostname 0.3.1", "quick-error", ] @@ -8761,7 +8790,7 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcaaf6e68789d3f0411f1e72bc443214ef252a1038b6e344836e50442541f190" dependencies = [ - "hostname", + "hostname 0.3.1", "slog", "slog-json", "time", @@ -9993,6 +10022,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "trust-dns-client" version = "0.22.0" @@ -11080,6 +11120,25 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 9e5691652a..e612d74389 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ members = [ "nexus/db-model", "nexus/db-queries", "nexus/defaults", + "nexus/internal-api", "nexus/inventory", "nexus/macros-common", "nexus/metrics-producer-gc", @@ -128,6 +129,7 @@ default-members = [ "nexus/db-model", "nexus/db-queries", "nexus/defaults", + "nexus/internal-api", "nexus/inventory", "nexus/reconfigurator/execution", "nexus/reconfigurator/planning", @@ -277,6 +279,7 @@ nexus-config = { path = "nexus-config" } nexus-db-model = { path = "nexus/db-model" } nexus-db-queries = { path = "nexus/db-queries" } nexus-defaults = { path = "nexus/defaults" } +nexus-internal-api = { path = "nexus/internal-api" } nexus-inventory = { path = "nexus/inventory" } nexus-macros-common = { path = "nexus/macros-common" } nexus-metrics-producer-gc = { path = "nexus/metrics-producer-gc" } @@ -619,8 +622,9 @@ opt-level = 3 # It's common during development to use a local copy of various complex # dependencies. If you want to use those, uncomment one of these blocks. # -#[patch."https://github.com/oxidecomputer/dropshot"] +[patch."https://github.com/oxidecomputer/dropshot"] #dropshot = { path = "../dropshot/dropshot" } +dropshot = { git = "https://github.com/oxidecomputer//dropshot", branch = "sunshowers/spr/wip-prototype-for-trait-based-dropshot-servers" } #[patch.crates-io] #steno = { path = "../steno" } #[patch."https://github.com/oxidecomputer/propolis"] diff --git a/nexus/Cargo.toml b/nexus/Cargo.toml index cdd5fc68a9..3d0e216a25 100644 --- a/nexus/Cargo.toml +++ b/nexus/Cargo.toml @@ -83,6 +83,7 @@ uuid.workspace = true nexus-defaults.workspace = true nexus-db-model.workspace = true nexus-db-queries.workspace = true +nexus-internal-api.workspace = true nexus-inventory.workspace = true nexus-metrics-producer-gc.workspace = true nexus-reconfigurator-execution.workspace = true diff --git a/nexus/db-model/src/external_ip.rs b/nexus/db-model/src/external_ip.rs index 1fdc4f8ce6..3d00d6542a 100644 --- a/nexus/db-model/src/external_ip.rs +++ b/nexus/db-model/src/external_ip.rs @@ -23,6 +23,8 @@ use nexus_types::deployment::OmicronZoneExternalSnatIp; use nexus_types::external_api::params; use nexus_types::external_api::shared; use nexus_types::external_api::views; +use nexus_types::external_api::views::ProbeExternalIp; +use nexus_types::external_api::views::ProbeIpKind; use nexus_types::inventory::SourceNatConfig; use omicron_common::api::external::Error; use omicron_common::api::external::IdentityMetadata; @@ -90,6 +92,16 @@ impl std::fmt::Display for IpKind { } } +impl From for ProbeIpKind { + fn from(kind: IpKind) -> ProbeIpKind { + match kind { + IpKind::Floating => ProbeIpKind::Floating, + IpKind::Ephemeral => ProbeIpKind::Ephemeral, + IpKind::SNat => ProbeIpKind::Snat, + } + } +} + /// The main model type for external IP addresses for instances /// and externally-facing services. /// @@ -140,6 +152,17 @@ pub struct ExternalIp { pub is_probe: bool, } +impl From for ProbeExternalIp { + fn from(ip: ExternalIp) -> Self { + Self { + ip: ip.ip.ip(), + first_port: ip.first_port.0, + last_port: ip.last_port.0, + kind: ip.kind.into(), + } + } +} + #[derive(Debug, thiserror::Error, SlogInlineError)] pub enum OmicronZoneExternalIpError { #[error("database IP is for an instance")] diff --git a/nexus/db-model/src/ipv4_nat_entry.rs b/nexus/db-model/src/ipv4_nat_entry.rs index c3763346c6..b56188dbb1 100644 --- a/nexus/db-model/src/ipv4_nat_entry.rs +++ b/nexus/db-model/src/ipv4_nat_entry.rs @@ -1,13 +1,10 @@ -use std::net::{Ipv4Addr, Ipv6Addr}; - use super::MacAddr; use crate::{ schema::ipv4_nat_changes, schema::ipv4_nat_entry, Ipv4Net, Ipv6Net, SqlU16, Vni, }; use chrono::{DateTime, Utc}; -use omicron_common::api::external; -use schemars::JsonSchema; +use nexus_types::internal_api::views::Ipv4NatEntryView; use serde::Deserialize; use serde::Serialize; use uuid::Uuid; @@ -65,19 +62,6 @@ pub struct Ipv4NatChange { pub deleted: bool, } -/// NAT Record -#[derive(Clone, Debug, Serialize, JsonSchema)] -pub struct Ipv4NatEntryView { - pub external_address: Ipv4Addr, - pub first_port: u16, - pub last_port: u16, - pub sled_address: Ipv6Addr, - pub vni: external::Vni, - pub mac: external::MacAddr, - pub gen: i64, - pub deleted: bool, -} - impl From for Ipv4NatEntryView { fn from(value: Ipv4NatChange) -> Self { Self { diff --git a/nexus/db-queries/src/db/datastore/ipv4_nat_entry.rs b/nexus/db-queries/src/db/datastore/ipv4_nat_entry.rs index fa3939b8ac..e8b8266526 100644 --- a/nexus/db-queries/src/db/datastore/ipv4_nat_entry.rs +++ b/nexus/db-queries/src/db/datastore/ipv4_nat_entry.rs @@ -10,7 +10,7 @@ use diesel::prelude::*; use diesel::sql_types::BigInt; use nexus_db_model::ExternalIp; use nexus_db_model::Ipv4NatChange; -use nexus_db_model::Ipv4NatEntryView; +use nexus_types::internal_api::views::Ipv4NatEntryView; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; diff --git a/nexus/db-queries/src/db/datastore/mod.rs b/nexus/db-queries/src/db/datastore/mod.rs index 4d6b16483d..0ab2f688e6 100644 --- a/nexus/db-queries/src/db/datastore/mod.rs +++ b/nexus/db-queries/src/db/datastore/mod.rs @@ -105,7 +105,6 @@ pub use dns::DnsVersionUpdateBuilder; pub use instance::InstanceAndActiveVmm; pub use inventory::DataStoreInventoryTest; use nexus_db_model::AllSchemaVersions; -pub use probe::ProbeInfo; pub use rack::RackInit; pub use silo::Discoverability; pub use switch_port::SwitchPortSettingsCombinedResult; diff --git a/nexus/db-queries/src/db/datastore/probe.rs b/nexus/db-queries/src/db/datastore/probe.rs index f1e737e353..bdb3e8e1af 100644 --- a/nexus/db-queries/src/db/datastore/probe.rs +++ b/nexus/db-queries/src/db/datastore/probe.rs @@ -1,5 +1,3 @@ -use std::net::IpAddr; - use crate::authz; use crate::context::OpContext; use crate::db; @@ -16,6 +14,7 @@ use nexus_db_model::IncompleteNetworkInterface; use nexus_db_model::Probe; use nexus_db_model::VpcSubnet; use nexus_types::external_api::params; +use nexus_types::external_api::views::ProbeInfo; use nexus_types::identity::Resource; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::CreateResult; @@ -29,56 +28,8 @@ use omicron_common::api::external::NameOrId; use omicron_common::api::external::ResourceType; use omicron_common::api::internal::shared::NetworkInterface; use ref_cast::RefCast; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use uuid::Uuid; -#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] -pub struct ProbeInfo { - pub id: Uuid, - pub name: Name, - sled: Uuid, - pub external_ips: Vec, - pub interface: NetworkInterface, -} - -#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] -pub struct ProbeExternalIp { - ip: IpAddr, - first_port: u16, - last_port: u16, - kind: IpKind, -} - -impl From for ProbeExternalIp { - fn from(value: nexus_db_model::ExternalIp) -> Self { - Self { - ip: value.ip.ip(), - first_port: value.first_port.0, - last_port: value.last_port.0, - kind: value.kind.into(), - } - } -} - -#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum IpKind { - Snat, - Floating, - Ephemeral, -} - -impl From for IpKind { - fn from(value: nexus_db_model::IpKind) -> Self { - match value { - nexus_db_model::IpKind::SNat => Self::Snat, - nexus_db_model::IpKind::Ephemeral => Self::Ephemeral, - nexus_db_model::IpKind::Floating => Self::Floating, - } - } -} - impl super::DataStore { /// List the probes for the given project. pub async fn probe_list( @@ -142,7 +93,7 @@ impl super::DataStore { result.push(ProbeInfo { id: probe.id(), - name: probe.name().clone().into(), + name: probe.name().clone(), sled: probe.sled, interface, external_ips, @@ -185,7 +136,7 @@ impl super::DataStore { Ok(ProbeInfo { id: probe.id(), - name: probe.name().clone().into(), + name: probe.name().clone(), sled: probe.sled, interface, external_ips, diff --git a/nexus/internal-api/Cargo.toml b/nexus/internal-api/Cargo.toml new file mode 100644 index 0000000000..70d5b95f98 --- /dev/null +++ b/nexus/internal-api/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "nexus-internal-api" +version = "0.1.0" +edition = "2021" + +[dependencies] +dropshot.workspace = true +nexus-types.workspace = true +omicron-common.workspace = true +omicron-uuid-kinds.workspace = true +omicron-workspace-hack.workspace = true +serde.workspace = true +schemars.workspace = true +uuid.workspace = true + +[dev-dependencies] +expectorate.workspace = true +openapiv3.workspace = true +openapi-lint.workspace = true +serde_json.workspace = true diff --git a/nexus/internal-api/src/lib.rs b/nexus/internal-api/src/lib.rs new file mode 100644 index 0000000000..0282e60db3 --- /dev/null +++ b/nexus/internal-api/src/lib.rs @@ -0,0 +1,587 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use std::collections::{BTreeMap, BTreeSet}; + +use dropshot::{ + dropshot_server, ApiDescription, FreeformBody, HttpError, + HttpResponseCreated, HttpResponseDeleted, HttpResponseOk, + HttpResponseUpdatedNoContent, OpenApiDefinition, Path, Query, + RequestContext, ResultsPage, TypedBody, +}; +use nexus_types::{ + deployment::{ + Blueprint, BlueprintMetadata, BlueprintTarget, BlueprintTargetSet, + }, + external_api::{ + params::{SledSelector, UninitializedSledId}, + shared::UninitializedSled, + views::{ProbeInfo, SledPolicy}, + }, + internal_api::{ + params::{ + OximeterInfo, RackInitializationRequest, SledAgentInfo, + SwitchPutRequest, SwitchPutResponse, + }, + views::{BackgroundTask, Ipv4NatEntryView, Saga}, + }, +}; +use omicron_common::{ + api::{ + external::http_pagination::PaginatedById, + internal::nexus::{ + DiskRuntimeState, DownstairsClientStopRequest, + DownstairsClientStopped, ProducerEndpoint, + ProducerRegistrationResponse, RepairFinishInfo, RepairProgress, + RepairStartInfo, SledInstanceState, + }, + }, + update::ArtifactId, +}; +use omicron_uuid_kinds::{DownstairsUuid, UpstairsRepairUuid, UpstairsUuid}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// Path parameters for Sled Agent requests (internal API) +#[derive(Deserialize, JsonSchema)] +pub struct SledAgentPathParam { + pub sled_id: Uuid, +} + +/// Path parameters for Rack requests. +#[derive(Deserialize, JsonSchema)] +pub struct RackPathParam { + pub rack_id: Uuid, +} + +/// Path parameters for Switch requests. +#[derive(Deserialize, JsonSchema)] +pub struct SwitchPathParam { + pub switch_id: Uuid, +} + +/// Path parameters for Instance requests (internal API) +#[derive(Deserialize, JsonSchema)] +pub struct InstancePathParam { + pub instance_id: Uuid, +} + +/// Path parameters for Disk requests (internal API) +#[derive(Deserialize, JsonSchema)] +pub struct DiskPathParam { + pub disk_id: Uuid, +} + +/// Path parameters for Volume requests (internal API) +#[derive(Deserialize, JsonSchema)] +pub struct VolumePathParam { + pub volume_id: Uuid, +} + +#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, Serialize)] +pub struct CollectorIdPathParams { + /// The ID of the oximeter collector. + pub collector_id: Uuid, +} + +/// Path parameters for Upstairs requests (internal API) +#[derive(Deserialize, JsonSchema)] +pub struct UpstairsPathParam { + pub upstairs_id: UpstairsUuid, +} + +/// Path parameters for Upstairs requests (internal API) +#[derive(Deserialize, JsonSchema)] +pub struct UpstairsRepairPathParam { + pub upstairs_id: UpstairsUuid, + pub repair_id: UpstairsRepairUuid, +} + +/// Path parameters for Downstairs requests (internal API) +#[derive(Deserialize, JsonSchema)] +pub struct UpstairsDownstairsPathParam { + pub upstairs_id: UpstairsUuid, + pub downstairs_id: DownstairsUuid, +} + +/// Path parameters for Saga requests +#[derive(Deserialize, JsonSchema)] +pub struct SagaPathParam { + pub saga_id: Uuid, +} + +/// 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, +} + +/// Path parameters for NAT ChangeSet +#[derive(Deserialize, JsonSchema)] +pub struct RpwNatPathParam { + /// which change number to start generating + /// the change set from + pub from_gen: i64, +} + +/// Query parameters for NAT ChangeSet +#[derive(Deserialize, JsonSchema)] +pub struct RpwNatQueryParam { + pub limit: u32, +} + +/// Path parameters for probes +#[derive(Deserialize, JsonSchema)] +pub struct ProbePathParam { + pub sled: Uuid, +} + +#[dropshot_server] +pub trait NexusInternalApi: Send + Sync + Sized + 'static { + /// Return information about the given sled agent + #[endpoint { + method = GET, + path = "/sled-agents/{sled_id}", + }] + async fn sled_agent_get( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError>; + + /// Report that the sled agent for the specified sled has come online. + #[endpoint { + method = POST, + path = "/sled-agents/{sled_id}", + }] + async fn sled_agent_put( + rqctx: RequestContext, + path_params: Path, + body: TypedBody, + ) -> Result; + + /// Request a new set of firewall rules for a sled. + /// + /// 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. + #[endpoint { + method = POST, + path = "/sled-agents/{sled_id}/firewall-rules-update", + }] + async fn sled_firewall_rules_request( + rqctx: RequestContext, + path_params: Path, + ) -> Result; + + /// Report that the Rack Setup Service initialization is complete + /// + /// See RFD 278 for more details. + #[endpoint { + method = PUT, + path = "/racks/{rack_id}/initialization-complete", + }] + async fn rack_initialization_complete( + rqctx: RequestContext, + path_params: Path, + info: TypedBody, + ) -> Result; + + #[endpoint { + method = PUT, + path = "/switch/{switch_id}", + }] + async fn switch_put( + rqctx: RequestContext, + path_params: Path, + body: TypedBody, + ) -> Result, HttpError>; + + /// Report updated state for an instance. + #[endpoint { + method = PUT, + path = "/instances/{instance_id}", + }] + async fn cpapi_instances_put( + rqctx: RequestContext, + path_params: Path, + new_runtime_state: TypedBody, + ) -> Result; + + /// Report updated state for a disk. + #[endpoint { + method = PUT, + path = "/disks/{disk_id}", + }] + async fn cpapi_disks_put( + rqctx: RequestContext, + path_params: Path, + new_runtime_state: TypedBody, + ) -> Result; + + /// Request removal of a read_only_parent from a volume + /// 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. + #[endpoint { + method = POST, + path = "/volume/{volume_id}/remove-read-only-parent", + }] + async fn cpapi_volume_remove_read_only_parent( + rqctx: RequestContext, + path_params: Path, + ) -> Result; + + /// Request removal of a read_only_parent from a disk + /// 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 + /// volume_remove_read_only_parent saga on it. + #[endpoint { + method = POST, + path = "/disk/{disk_id}/remove-read-only-parent", + }] + async fn cpapi_disk_remove_read_only_parent( + rqctx: RequestContext, + path_params: Path, + ) -> Result; + + /// Accept a registration from a new metric producer + #[endpoint { + method = POST, + path = "/metrics/producers", + }] + async fn cpapi_producers_post( + request_context: RequestContext, + producer_info: TypedBody, + ) -> Result, HttpError>; + + /// List all metric producers assigned to an oximeter collector. + #[endpoint { + method = GET, + path = "/metrics/collectors/{collector_id}/producers", + }] + async fn cpapi_assigned_producers_list( + request_context: RequestContext, + path_params: Path, + query_params: Query, + ) -> Result>, HttpError>; + + /// Accept a notification of a new oximeter collection server. + #[endpoint { + method = POST, + path = "/metrics/collectors", + }] + async fn cpapi_collectors_post( + request_context: RequestContext, + oximeter_info: TypedBody, + ) -> Result; + + /// Endpoint used by Sled Agents to download cached artifacts. + #[endpoint { + method = GET, + path = "/artifacts/{kind}/{name}/{version}", + }] + async fn cpapi_artifact_download( + request_context: RequestContext, + path_params: Path, + ) -> Result, HttpError>; + + /// An Upstairs will notify this endpoint when a repair starts + #[endpoint { + method = POST, + path = "/crucible/0/upstairs/{upstairs_id}/repair-start", + }] + async fn cpapi_upstairs_repair_start( + rqctx: RequestContext, + path_params: Path, + repair_start_info: TypedBody, + ) -> Result; + + /// An Upstairs will notify this endpoint when a repair finishes. + #[endpoint { + method = POST, + path = "/crucible/0/upstairs/{upstairs_id}/repair-finish", + }] + async fn cpapi_upstairs_repair_finish( + rqctx: RequestContext, + path_params: Path, + repair_finish_info: TypedBody, + ) -> Result; + + /// An Upstairs will update this endpoint with the progress of a repair + #[endpoint { + method = POST, + path = "/crucible/0/upstairs/{upstairs_id}/repair/{repair_id}/progress", + }] + async fn cpapi_upstairs_repair_progress( + rqctx: RequestContext, + path_params: Path, + repair_progress: TypedBody, + ) -> Result; + + /// An Upstairs will update this endpoint if a Downstairs client task is + /// requested to stop + #[endpoint { + method = POST, + path = "/crucible/0/upstairs/{upstairs_id}/downstairs/{downstairs_id}/stop-request", + }] + async fn cpapi_downstairs_client_stop_request( + rqctx: RequestContext, + path_params: Path, + downstairs_client_stop_request: TypedBody, + ) -> Result; + + /// An Upstairs will update this endpoint if a Downstairs client task stops for + /// any reason (not just after being requested to) + #[endpoint { + method = POST, + path = "/crucible/0/upstairs/{upstairs_id}/downstairs/{downstairs_id}/stopped", + }] + async fn cpapi_downstairs_client_stopped( + rqctx: RequestContext, + path_params: Path, + downstairs_client_stopped: TypedBody, + ) -> Result; + + // 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>; + + // 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; + + // NAT RPW internal APIs + + /// Fetch NAT ChangeSet + /// + /// 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. + #[endpoint { + method = GET, + path = "/nat/ipv4/changeset/{from_gen}" + }] + async fn ipv4_nat_changeset( + rqctx: RequestContext, + path_params: Path, + 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; + + /// 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; + + /// 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>; + + /// Get all the probes associated with a given sled. + #[endpoint { + method = GET, + path = "/probes/{sled}" + }] + async fn probes_get( + rqctx: RequestContext, + path_params: Path, + query_params: Query, + ) -> Result>, HttpError>; +} + +pub fn openapi_definition( + api: &ApiDescription<()>, +) -> OpenApiDefinition<'_, ()> { + let mut def = api.openapi("Nexus internal API", "0.0.1"); + def.description("Nexus internal API") + .contact_url("https://oxide.computer") + .contact_email("api@oxide.computer"); + def +} diff --git a/nexus/internal-api/tests/test_all/commands.rs b/nexus/internal-api/tests/test_all/commands.rs new file mode 100644 index 0000000000..b8d9f87be8 --- /dev/null +++ b/nexus/internal-api/tests/test_all/commands.rs @@ -0,0 +1,34 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use expectorate::assert_contents; +use nexus_internal_api::{ + openapi_definition, NexusInternalApi_to_stub_api_description, +}; +use openapiv3::OpenAPI; + +#[test] +fn test_nexus_openapi_internal() { + let api = NexusInternalApi_to_stub_api_description().unwrap(); + let def = openapi_definition(&api); + + // We can't use `def.json()` for the `assert_contents` string comparison + // below, because serde_json::Value may end up reordering items depending + // on which features are enabled. + let mut json_out = Vec::new(); + def.write(&mut json_out).unwrap(); + let json_str = String::from_utf8(json_out).unwrap(); + + let spec: OpenAPI = + serde_json::from_str(&json_str).expect("definition was valid OpenAPI"); + + // Check for lint errors. + let errors = openapi_lint::validate(&spec); + assert!(errors.is_empty(), "{}", errors.join("\n\n")); + + // Confirm that the output hasn't changed. It's expected that we'll change + // this file as the API evolves, but pay attention to the diffs to ensure + // that the changes match your expectations. + assert_contents("../../openapi/nexus-internal.json", &json_str); +} diff --git a/nexus/internal-api/tests/test_all/main.rs b/nexus/internal-api/tests/test_all/main.rs new file mode 100644 index 0000000000..1bf43dc00c --- /dev/null +++ b/nexus/internal-api/tests/test_all/main.rs @@ -0,0 +1,5 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +mod commands; diff --git a/nexus/src/app/mod.rs b/nexus/src/app/mod.rs index a2cbe2f7ae..36e4819de7 100644 --- a/nexus/src/app/mod.rs +++ b/nexus/src/app/mod.rs @@ -7,6 +7,7 @@ use self::external_endpoints::NexusCertResolver; use crate::app::oximeter::LazyTimeseriesClient; use crate::app::sagas::SagaRequest; +use crate::internal_api::http_entrypoints::NexusInternalApiImpl; use crate::populate::populate_start; use crate::populate::PopulateArgs; use crate::populate::PopulateStatus; @@ -143,7 +144,8 @@ pub struct Nexus { techport_external_server: std::sync::Mutex>, /// Internal dropshot server - internal_server: std::sync::Mutex>, + internal_server: + std::sync::Mutex>>, /// Status of background task to populate database populate_status: tokio::sync::watch::Receiver, @@ -586,7 +588,7 @@ impl Nexus { &self, external_server: DropshotServer, techport_external_server: DropshotServer, - internal_server: DropshotServer, + internal_server: dropshot::HttpServer, producer_server: ProducerServer, ) { // If any servers already exist, close them. diff --git a/nexus/src/app/probe.rs b/nexus/src/app/probe.rs index e85c040a28..9e9be8d87d 100644 --- a/nexus/src/app/probe.rs +++ b/nexus/src/app/probe.rs @@ -1,9 +1,9 @@ use nexus_db_model::Probe; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; -use nexus_db_queries::db::datastore::ProbeInfo; use nexus_db_queries::db::lookup; use nexus_types::external_api::params; +use nexus_types::external_api::views::ProbeInfo; use nexus_types::identity::Resource; use omicron_common::api::external::Error; use omicron_common::api::external::{ diff --git a/nexus/src/context.rs b/nexus/src/context.rs index cf2b9d6f17..bb1f2f484f 100644 --- a/nexus/src/context.rs +++ b/nexus/src/context.rs @@ -3,6 +3,7 @@ //! Shared state used by API request handlers use super::Nexus; +use crate::internal_api::http_entrypoints::NexusInternalApiImpl; use crate::saga_interface::SagaContext; use async_trait::async_trait; use authn::external::session_cookie::HttpAuthnSessionCookie; @@ -285,9 +286,9 @@ pub(crate) async fn op_context_for_external_api( } pub(crate) async fn op_context_for_internal_api( - rqctx: &dropshot::RequestContext>, + rqctx: &dropshot::RequestContext, ) -> OpContext { - let apictx = rqctx.context(); + let apictx = &rqctx.context().context; OpContext::new_async( &rqctx.log, async { diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 74244e112b..edfbfd5bc4 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -35,13 +35,13 @@ use dropshot::{ use dropshot::{ApiDescription, StreamingBody}; use dropshot::{ApiEndpoint, EmptyScanParams}; use ipnetwork::IpNetwork; +use nexus_db_queries::authz; use nexus_db_queries::db; use nexus_db_queries::db::identity::Resource; use nexus_db_queries::db::lookup::ImageLookup; use nexus_db_queries::db::lookup::ImageParentLookup; use nexus_db_queries::db::model::Name; -use nexus_db_queries::{authz, db::datastore::ProbeInfo}; -use nexus_types::external_api::shared::BfdStatus; +use nexus_types::external_api::{shared::BfdStatus, views::ProbeInfo}; use omicron_common::api::external::http_pagination::marker_for_name; use omicron_common::api::external::http_pagination::marker_for_name_or_id; use omicron_common::api::external::http_pagination::name_or_id_pagination; @@ -6198,7 +6198,7 @@ async fn probe_list( probes, &|_, p: &ProbeInfo| match paginated_by { PaginatedBy::Id(_) => NameOrId::Id(p.id), - PaginatedBy::Name(_) => NameOrId::Name(p.name.clone().into()), + PaginatedBy::Name(_) => NameOrId::Name(p.name.clone()), }, )?)) }; diff --git a/nexus/src/internal_api/http_entrypoints.rs b/nexus/src/internal_api/http_entrypoints.rs index 3770271ff5..c373f43fae 100644 --- a/nexus/src/internal_api/http_entrypoints.rs +++ b/nexus/src/internal_api/http_entrypoints.rs @@ -7,7 +7,6 @@ use crate::ServerContext; use super::params::{OximeterInfo, RackInitializationRequest}; -use dropshot::endpoint; use dropshot::ApiDescription; use dropshot::FreeformBody; use dropshot::HttpError; @@ -21,8 +20,24 @@ use dropshot::RequestContext; use dropshot::ResultsPage; use dropshot::TypedBody; use hyper::Body; -use nexus_db_model::Ipv4NatEntryView; -use nexus_db_queries::db::datastore::ProbeInfo; +use nexus_internal_api::BackgroundTaskPathParam; +use nexus_internal_api::BackgroundTasksActivateRequest; +use nexus_internal_api::CollectorIdPathParams; +use nexus_internal_api::DiskPathParam; +use nexus_internal_api::InstancePathParam; +use nexus_internal_api::NexusInternalApi; +use nexus_internal_api::NexusInternalApi_to_api_description; +use nexus_internal_api::ProbePathParam; +use nexus_internal_api::RackPathParam; +use nexus_internal_api::RpwNatPathParam; +use nexus_internal_api::RpwNatQueryParam; +use nexus_internal_api::SagaPathParam; +use nexus_internal_api::SledAgentPathParam; +use nexus_internal_api::SwitchPathParam; +use nexus_internal_api::UpstairsDownstairsPathParam; +use nexus_internal_api::UpstairsPathParam; +use nexus_internal_api::UpstairsRepairPathParam; +use nexus_internal_api::VolumePathParam; use nexus_types::deployment::Blueprint; use nexus_types::deployment::BlueprintMetadata; use nexus_types::deployment::BlueprintTarget; @@ -30,12 +45,14 @@ use nexus_types::deployment::BlueprintTargetSet; use nexus_types::external_api::params::SledSelector; use nexus_types::external_api::params::UninitializedSledId; use nexus_types::external_api::shared::UninitializedSled; +use nexus_types::external_api::views::ProbeInfo; use nexus_types::external_api::views::SledPolicy; 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::to_list; use nexus_types::internal_api::views::BackgroundTask; +use nexus_types::internal_api::views::Ipv4NatEntryView; use nexus_types::internal_api::views::Saga; use omicron_common::api::external::http_pagination::data_page_params_for; use omicron_common::api::external::http_pagination::PaginatedById; @@ -51,1060 +68,778 @@ use omicron_common::api::internal::nexus::RepairProgress; use omicron_common::api::internal::nexus::RepairStartInfo; use omicron_common::api::internal::nexus::SledInstanceState; use omicron_common::update::ArtifactId; -use omicron_uuid_kinds::DownstairsKind; -use omicron_uuid_kinds::TypedUuid; -use omicron_uuid_kinds::UpstairsKind; -use omicron_uuid_kinds::UpstairsRepairKind; -use schemars::JsonSchema; -use serde::Deserialize; use std::collections::BTreeMap; -use std::collections::BTreeSet; use std::sync::Arc; -use uuid::Uuid; -type NexusApiDescription = ApiDescription>; +type NexusApiDescription = ApiDescription; /// Returns a description of the internal nexus API pub(crate) fn internal_api() -> NexusApiDescription { - fn register_endpoints(api: &mut NexusApiDescription) -> Result<(), String> { - api.register(sled_agent_get)?; - api.register(sled_agent_put)?; - api.register(sled_firewall_rules_request)?; - api.register(switch_put)?; - api.register(rack_initialization_complete)?; - api.register(cpapi_instances_put)?; - api.register(cpapi_disks_put)?; - api.register(cpapi_volume_remove_read_only_parent)?; - api.register(cpapi_disk_remove_read_only_parent)?; - api.register(cpapi_producers_post)?; - api.register(cpapi_assigned_producers_list)?; - api.register(cpapi_collectors_post)?; - api.register(cpapi_artifact_download)?; - - api.register(cpapi_upstairs_repair_start)?; - api.register(cpapi_upstairs_repair_finish)?; - api.register(cpapi_upstairs_repair_progress)?; - api.register(cpapi_downstairs_client_stop_request)?; - api.register(cpapi_downstairs_client_stopped)?; - - api.register(saga_list)?; - api.register(saga_view)?; - - api.register(ipv4_nat_changeset)?; - - api.register(bgtask_list)?; - api.register(bgtask_view)?; - api.register(bgtask_activate)?; - - api.register(blueprint_list)?; - api.register(blueprint_view)?; - api.register(blueprint_delete)?; - api.register(blueprint_target_view)?; - api.register(blueprint_target_set)?; - api.register(blueprint_target_set_enabled)?; - api.register(blueprint_regenerate)?; - api.register(blueprint_import)?; - - api.register(sled_list_uninitialized)?; - api.register(sled_add)?; - api.register(sled_expunge)?; - - api.register(probes_get)?; - - Ok(()) - } - - let mut api = NexusApiDescription::new(); - if let Err(err) = register_endpoints(&mut api) { - panic!("failed to register entrypoints: {}", err); - } - api -} - -/// Path parameters for Sled Agent requests (internal API) -#[derive(Deserialize, JsonSchema)] -struct SledAgentPathParam { - sled_id: Uuid, -} - -/// Return information about the given sled agent -#[endpoint { - method = GET, - path = "/sled-agents/{sled_id}", - }] -async fn sled_agent_get( - rqctx: RequestContext>, - path_params: Path, -) -> Result, HttpError> { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - let path = path_params.into_inner(); - let sled_id = &path.sled_id; - let handler = async { - let (.., sled) = nexus.sled_lookup(&opctx, sled_id)?.fetch().await?; - Ok(HttpResponseOk(sled.into())) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await + NexusInternalApi_to_api_description().expect("registered entrypoints") } -/// Report that the sled agent for the specified sled has come online. -#[endpoint { - method = POST, - path = "/sled-agents/{sled_id}", - }] -async fn sled_agent_put( - rqctx: RequestContext>, - path_params: Path, - sled_info: TypedBody, -) -> Result { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - let path = path_params.into_inner(); - let info = sled_info.into_inner(); - let sled_id = &path.sled_id; - let handler = async { - nexus.upsert_sled(&opctx, *sled_id, info).await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} - -/// Request a new set of firewall rules for a sled. -/// -/// 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. -#[endpoint { - method = POST, - path = "/sled-agents/{sled_id}/firewall-rules-update", - }] -async fn sled_firewall_rules_request( - rqctx: RequestContext>, - path_params: Path, -) -> Result { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - let path = path_params.into_inner(); - let sled_id = &path.sled_id; - let handler = async { - nexus.sled_request_firewall_rules(&opctx, *sled_id).await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await +pub(crate) struct NexusInternalApiImpl { + pub(crate) context: Arc, } -/// Path parameters for Rack requests. -#[derive(Deserialize, JsonSchema)] -struct RackPathParam { - rack_id: Uuid, -} - -/// Report that the Rack Setup Service initialization is complete -/// -/// See RFD 278 for more details. -#[endpoint { - method = PUT, - path = "/racks/{rack_id}/initialization-complete", - }] -async fn rack_initialization_complete( - rqctx: RequestContext>, - path_params: Path, - info: TypedBody, -) -> Result { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let request = info.into_inner(); - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - - nexus.rack_initialize(&opctx, path.rack_id, request).await?; - - Ok(HttpResponseUpdatedNoContent()) -} - -/// Path parameters for Switch requests. -#[derive(Deserialize, JsonSchema)] -struct SwitchPathParam { - switch_id: Uuid, -} - -#[endpoint { - method = PUT, - path = "/switch/{switch_id}", -}] -async fn switch_put( - rqctx: RequestContext>, - path_params: Path, - body: TypedBody, -) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { +impl NexusInternalApi for NexusInternalApiImpl { + async fn sled_agent_get( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let switch = body.into_inner(); - nexus.switch_upsert(path.switch_id, switch).await?; - Ok(HttpResponseOk(SwitchPutResponse {})) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} - -/// Path parameters for Instance requests (internal API) -#[derive(Deserialize, JsonSchema)] -struct InstancePathParam { - instance_id: Uuid, -} - -/// Report updated state for an instance. -#[endpoint { - method = PUT, - path = "/instances/{instance_id}", - }] -async fn cpapi_instances_put( - rqctx: RequestContext>, - path_params: Path, - new_runtime_state: TypedBody, -) -> Result { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let new_state = new_runtime_state.into_inner(); - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - let handler = async { - nexus - .notify_instance_updated(&opctx, &path.instance_id, &new_state) - .await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} - -/// Path parameters for Disk requests (internal API) -#[derive(Deserialize, JsonSchema)] -struct DiskPathParam { - disk_id: Uuid, -} - -/// Report updated state for a disk. -#[endpoint { - method = PUT, - path = "/disks/{disk_id}", - }] -async fn cpapi_disks_put( - rqctx: RequestContext>, - path_params: Path, - new_runtime_state: TypedBody, -) -> Result { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let new_state = new_runtime_state.into_inner(); - let handler = async { let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - nexus.notify_disk_updated(&opctx, path.disk_id, &new_state).await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} - -/// Path parameters for Volume requests (internal API) -#[derive(Deserialize, JsonSchema)] -struct VolumePathParam { - volume_id: Uuid, -} - -/// Request removal of a read_only_parent from a volume -/// 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. -#[endpoint { - method = POST, - path = "/volume/{volume_id}/remove-read-only-parent", - }] -async fn cpapi_volume_remove_read_only_parent( - rqctx: RequestContext>, - path_params: Path, -) -> Result { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); + let path = path_params.into_inner(); + let sled_id = &path.sled_id; + let handler = async { + let (.., sled) = + nexus.sled_lookup(&opctx, sled_id)?.fetch().await?; + Ok(HttpResponseOk(sled.into())) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } - let handler = async { + async fn sled_agent_put( + rqctx: RequestContext, + path_params: Path, + sled_info: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - nexus.volume_remove_read_only_parent(&opctx, path.volume_id).await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} - -/// Request removal of a read_only_parent from a disk -/// 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 -/// volume_remove_read_only_parent saga on it. -#[endpoint { - method = POST, - path = "/disk/{disk_id}/remove-read-only-parent", - }] -async fn cpapi_disk_remove_read_only_parent( - rqctx: RequestContext>, - path_params: Path, -) -> Result { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); + let path = path_params.into_inner(); + let info = sled_info.into_inner(); + let sled_id = &path.sled_id; + let handler = async { + nexus.upsert_sled(&opctx, *sled_id, info).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } - let handler = async { + async fn sled_firewall_rules_request( + rqctx: RequestContext, + path_params: Path, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - nexus.disk_remove_read_only_parent(&opctx, path.disk_id).await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} - -/// Accept a registration from a new metric producer -#[endpoint { - method = POST, - path = "/metrics/producers", - }] -async fn cpapi_producers_post( - request_context: RequestContext>, - producer_info: TypedBody, -) -> Result, HttpError> { - let context = request_context.context(); - let handler = async { - let nexus = &context.nexus; - let producer_info = producer_info.into_inner(); - let opctx = - crate::context::op_context_for_internal_api(&request_context).await; - nexus - .assign_producer(&opctx, producer_info) + let path = path_params.into_inner(); + let sled_id = &path.sled_id; + let handler = async { + nexus.sled_request_firewall_rules(&opctx, *sled_id).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) .await - .map_err(HttpError::from) - .map(|_| { - HttpResponseCreated(ProducerRegistrationResponse { - lease_duration: - crate::app::oximeter::PRODUCER_LEASE_DURATION, - }) - }) - }; - context - .internal_latencies - .instrument_dropshot_handler(&request_context, handler) - .await -} - -#[derive( - Clone, - Copy, - Debug, - serde::Deserialize, - schemars::JsonSchema, - serde::Serialize, -)] -pub struct CollectorIdPathParams { - /// The ID of the oximeter collector. - pub collector_id: Uuid, -} + } -/// List all metric producers assigned to an oximeter collector. -#[endpoint { - method = GET, - path = "/metrics/collectors/{collector_id}/producers", - }] -async fn cpapi_assigned_producers_list( - request_context: RequestContext>, - path_params: Path, - query_params: Query, -) -> Result>, HttpError> { - let context = request_context.context(); - let handler = async { - let nexus = &context.nexus; - let collector_id = path_params.into_inner().collector_id; - let query = query_params.into_inner(); - let pagparams = data_page_params_for(&request_context, &query)?; - let opctx = - crate::context::op_context_for_internal_api(&request_context).await; - let producers = nexus - .list_assigned_producers(&opctx, collector_id, &pagparams) - .await?; - Ok(HttpResponseOk(ScanById::results_page( - &query, - producers, - &|_, producer: &ProducerEndpoint| producer.id, - )?)) - }; - context - .internal_latencies - .instrument_dropshot_handler(&request_context, handler) - .await -} + async fn rack_initialization_complete( + rqctx: RequestContext, + path_params: Path, + info: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let request = info.into_inner(); + let opctx = crate::context::op_context_for_internal_api(&rqctx).await; -/// Accept a notification of a new oximeter collection server. -#[endpoint { - method = POST, - path = "/metrics/collectors", - }] -async fn cpapi_collectors_post( - request_context: RequestContext>, - oximeter_info: TypedBody, -) -> Result { - let context = request_context.context(); - let handler = async { - let nexus = &context.nexus; - let oximeter_info = oximeter_info.into_inner(); - let opctx = - crate::context::op_context_for_internal_api(&request_context).await; - nexus.upsert_oximeter_collector(&opctx, &oximeter_info).await?; + nexus.rack_initialize(&opctx, path.rack_id, request).await?; Ok(HttpResponseUpdatedNoContent()) - }; - context - .internal_latencies - .instrument_dropshot_handler(&request_context, handler) - .await -} - -/// Endpoint used by Sled Agents to download cached artifacts. -#[endpoint { - method = GET, - path = "/artifacts/{kind}/{name}/{version}", -}] -async fn cpapi_artifact_download( - request_context: RequestContext>, - path_params: Path, -) -> Result, HttpError> { - let context = request_context.context(); - let nexus = &context.nexus; - let opctx = - crate::context::op_context_for_internal_api(&request_context).await; - // TODO: return 404 if the error we get here says that the record isn't found - let body = nexus - .updates_download_artifact(&opctx, path_params.into_inner()) - .await?; + } - Ok(HttpResponseOk(Body::from(body).into())) -} + async fn switch_put( + rqctx: RequestContext, + path_params: Path, + body: TypedBody, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; + + let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let switch = body.into_inner(); + nexus.switch_upsert(path.switch_id, switch).await?; + Ok(HttpResponseOk(SwitchPutResponse {})) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// Path parameters for Upstairs requests (internal API) -#[derive(Deserialize, JsonSchema)] -struct UpstairsPathParam { - upstairs_id: TypedUuid, -} + async fn cpapi_instances_put( + rqctx: RequestContext, + path_params: Path, + new_runtime_state: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let new_state = new_runtime_state.into_inner(); + let opctx = crate::context::op_context_for_internal_api(&rqctx).await; -/// An Upstairs will notify this endpoint when a repair starts -#[endpoint { - method = POST, - path = "/crucible/0/upstairs/{upstairs_id}/repair-start", - }] -async fn cpapi_upstairs_repair_start( - rqctx: RequestContext>, - path_params: Path, - repair_start_info: TypedBody, -) -> Result { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); + let handler = async { + nexus + .notify_instance_updated(&opctx, &path.instance_id, &new_state) + .await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - nexus - .upstairs_repair_start( - &opctx, - path.upstairs_id, - repair_start_info.into_inner(), - ) - .await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} + async fn cpapi_disks_put( + rqctx: RequestContext, + path_params: Path, + new_runtime_state: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let new_state = new_runtime_state.into_inner(); + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus.notify_disk_updated(&opctx, path.disk_id, &new_state).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// An Upstairs will notify this endpoint when a repair finishes. -#[endpoint { - method = POST, - path = "/crucible/0/upstairs/{upstairs_id}/repair-finish", - }] -async fn cpapi_upstairs_repair_finish( - rqctx: RequestContext>, - path_params: Path, - repair_finish_info: TypedBody, -) -> Result { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); + async fn cpapi_volume_remove_read_only_parent( + rqctx: RequestContext, + path_params: Path, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus + .volume_remove_read_only_parent(&opctx, path.volume_id) + .await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - nexus - .upstairs_repair_finish( - &opctx, - path.upstairs_id, - repair_finish_info.into_inner(), - ) - .await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} + async fn cpapi_disk_remove_read_only_parent( + rqctx: RequestContext, + path_params: Path, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus.disk_remove_read_only_parent(&opctx, path.disk_id).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// Path parameters for Upstairs requests (internal API) -#[derive(Deserialize, JsonSchema)] -struct UpstairsRepairPathParam { - upstairs_id: TypedUuid, - repair_id: TypedUuid, -} + async fn cpapi_producers_post( + rqctx: RequestContext, + producer_info: TypedBody, + ) -> Result, HttpError> + { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let handler = async { + let producer_info = producer_info.into_inner(); + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus + .assign_producer(&opctx, producer_info) + .await + .map_err(HttpError::from) + .map(|_| { + HttpResponseCreated(ProducerRegistrationResponse { + lease_duration: + crate::app::oximeter::PRODUCER_LEASE_DURATION, + }) + }) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// An Upstairs will update this endpoint with the progress of a repair -#[endpoint { - method = POST, - path = "/crucible/0/upstairs/{upstairs_id}/repair/{repair_id}/progress", - }] -async fn cpapi_upstairs_repair_progress( - rqctx: RequestContext>, - path_params: Path, - repair_progress: TypedBody, -) -> Result { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); + async fn cpapi_assigned_producers_list( + rqctx: RequestContext, + path_params: Path, + query_params: Query, + ) -> Result>, HttpError> { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let handler = async { + let collector_id = path_params.into_inner().collector_id; + 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 producers = nexus + .list_assigned_producers(&opctx, collector_id, &pagparams) + .await?; + Ok(HttpResponseOk(ScanById::results_page( + &query, + producers, + &|_, producer: &ProducerEndpoint| producer.id, + )?)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - nexus - .upstairs_repair_progress( - &opctx, - path.upstairs_id, - path.repair_id, - repair_progress.into_inner(), - ) - .await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} + async fn cpapi_collectors_post( + rqctx: RequestContext, + oximeter_info: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let handler = async { + let oximeter_info = oximeter_info.into_inner(); + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus.upsert_oximeter_collector(&opctx, &oximeter_info).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// Path parameters for Downstairs requests (internal API) -#[derive(Deserialize, JsonSchema)] -struct UpstairsDownstairsPathParam { - upstairs_id: TypedUuid, - downstairs_id: TypedUuid, -} + async fn cpapi_artifact_download( + rqctx: RequestContext, + path_params: Path, + ) -> 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 body = nexus + .updates_download_artifact(&opctx, path_params.into_inner()) + .await?; + Ok(HttpResponseOk(Body::from(body).into())) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// An Upstairs will update this endpoint if a Downstairs client task is -/// requested to stop -#[endpoint { - method = POST, - path = "/crucible/0/upstairs/{upstairs_id}/downstairs/{downstairs_id}/stop-request", - }] -async fn cpapi_downstairs_client_stop_request( - rqctx: RequestContext>, - path_params: Path, - downstairs_client_stop_request: TypedBody, -) -> Result { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); + async fn cpapi_upstairs_repair_start( + rqctx: RequestContext, + path_params: Path, + repair_start_info: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus + .upstairs_repair_start( + &opctx, + path.upstairs_id, + repair_start_info.into_inner(), + ) + .await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - nexus - .downstairs_client_stop_request_notification( - &opctx, - path.upstairs_id, - path.downstairs_id, - downstairs_client_stop_request.into_inner(), - ) - .await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} + async fn cpapi_upstairs_repair_finish( + rqctx: RequestContext, + path_params: Path, + repair_finish_info: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus + .upstairs_repair_finish( + &opctx, + path.upstairs_id, + repair_finish_info.into_inner(), + ) + .await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// An Upstairs will update this endpoint if a Downstairs client task stops for -/// any reason (not just after being requested to) -#[endpoint { - method = POST, - path = "/crucible/0/upstairs/{upstairs_id}/downstairs/{downstairs_id}/stopped", - }] -async fn cpapi_downstairs_client_stopped( - rqctx: RequestContext>, - path_params: Path, - downstairs_client_stopped: TypedBody, -) -> Result { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); + async fn cpapi_upstairs_repair_progress( + rqctx: RequestContext, + path_params: Path, + repair_progress: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus + .upstairs_repair_progress( + &opctx, + path.upstairs_id, + path.repair_id, + repair_progress.into_inner(), + ) + .await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - nexus - .downstairs_client_stopped_notification( - &opctx, - path.upstairs_id, - path.downstairs_id, - downstairs_client_stopped.into_inner(), - ) - .await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} + async fn cpapi_downstairs_client_stop_request( + rqctx: RequestContext, + path_params: Path, + downstairs_client_stop_request: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus + .downstairs_client_stop_request_notification( + &opctx, + path.upstairs_id, + path.downstairs_id, + downstairs_client_stop_request.into_inner(), + ) + .await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -// Sagas + async fn cpapi_downstairs_client_stopped( + rqctx: RequestContext, + path_params: Path, + downstairs_client_stopped: TypedBody, + ) -> Result { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let handler = async { + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + nexus + .downstairs_client_stopped_notification( + &opctx, + path.upstairs_id, + path.downstairs_id, + downstairs_client_stopped.into_inner(), + ) + .await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// List sagas -#[endpoint { - method = GET, - path = "/sagas", -}] -async fn saga_list( - rqctx: RequestContext>, - query_params: Query, -) -> Result>, HttpError> { - let apictx = rqctx.context(); - let handler = async { + async fn saga_list( + rqctx: RequestContext, + query_params: Query, + ) -> Result>, HttpError> { + let apictx = &rqctx.context().context; 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 -} -/// Path parameters for Saga requests -#[derive(Deserialize, JsonSchema)] -struct SagaPathParam { - saga_id: Uuid, -} + let handler = async { + 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 + } -/// Fetch a saga -#[endpoint { - method = GET, - path = "/sagas/{saga_id}", -}] -async fn saga_view( - rqctx: RequestContext>, - path_params: Path, -) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + async fn saga_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; 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 -} + let opctx = crate::context::op_context_for_internal_api(&rqctx).await; -// Background Tasks + let handler = async { + let saga = nexus.saga_get(&opctx, path.saga_id).await?; + Ok(HttpResponseOk(saga)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// 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> { - let apictx = rqctx.context(); - let handler = async { + async fn bgtask_list( + rqctx: RequestContext, + ) -> Result>, HttpError> + { + let apictx = &rqctx.context().context; 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 -} - -/// Path parameters for Background Task requests -#[derive(Deserialize, JsonSchema)] -struct BackgroundTaskPathParam { - bgtask_name: String, -} - -/// Query parameters for Background Task activation requests. -#[derive(Deserialize, JsonSchema)] -struct BackgroundTasksActivateRequest { - bgtask_names: BTreeSet, -} + let handler = async { + let bgtask_list = nexus.bgtasks_list(&opctx).await?; + Ok(HttpResponseOk(bgtask_list)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// 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> { - let apictx = rqctx.context(); - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + async fn bgtask_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; 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 -} - -/// 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 { - let apictx = rqctx.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 -} - -// NAT RPW internal APIs -/// Path parameters for NAT ChangeSet -#[derive(Deserialize, JsonSchema)] -struct RpwNatPathParam { - /// which change number to start generating - /// the change set from - from_gen: i64, -} + let handler = async { + let bgtask = nexus.bgtask_status(&opctx, &path.bgtask_name).await?; + Ok(HttpResponseOk(bgtask)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// Query parameters for NAT ChangeSet -#[derive(Deserialize, JsonSchema)] -struct RpwNatQueryParam { - limit: u32, -} + async fn bgtask_activate( + rqctx: RequestContext, + body: 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; + let body = body.into_inner(); + nexus.bgtask_activate(&opctx, body.bgtask_names).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// Fetch NAT ChangeSet -/// -/// 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. -#[endpoint { - method = GET, - path = "/nat/ipv4/changeset/{from_gen}" -}] -async fn ipv4_nat_changeset( - rqctx: RequestContext>, - path_params: Path, - query_params: Query, -) -> Result>, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + async fn ipv4_nat_changeset( + rqctx: RequestContext, + path_params: Path, + query_params: Query, + ) -> Result>, HttpError> { + let apictx = &rqctx.context().context; let nexus = &apictx.nexus; let path = path_params.into_inner(); let query = query_params.into_inner(); - let mut changeset = nexus - .datastore() - .ipv4_nat_changeset(&opctx, path.from_gen, query.limit) - .await?; - changeset.sort_by_key(|e| e.gen); - Ok(HttpResponseOk(changeset)) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} + let opctx = crate::context::op_context_for_internal_api(&rqctx).await; -// 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. + let handler = async { + let mut changeset = nexus + .datastore() + .ipv4_nat_changeset(&opctx, path.from_gen, query.limit) + .await?; + changeset.sort_by_key(|e| e.gen); + Ok(HttpResponseOk(changeset)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// Lists blueprints -#[endpoint { - method = GET, - path = "/deployment/blueprints/all", -}] -async fn blueprint_list( - rqctx: RequestContext>, - query_params: Query, -) -> Result>, HttpError> { - let apictx = rqctx.context(); - let handler = async { + async fn blueprint_list( + rqctx: RequestContext, + query_params: Query, + ) -> Result>, HttpError> { + let apictx = &rqctx.context().context; 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, - )?)) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} + let pagparams = data_page_params_for(&rqctx, &query)?; + let handler = async { + let blueprints = nexus.blueprint_list(&opctx, &pagparams).await?; + Ok(HttpResponseOk(ScanById::results_page( + &query, + blueprints, + &|_, blueprint: &BlueprintMetadata| blueprint.id, + )?)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// Fetches one blueprint -#[endpoint { - method = GET, - path = "/deployment/blueprints/all/{blueprint_id}", -}] -async fn blueprint_view( - rqctx: RequestContext>, - path_params: Path, -) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + async fn blueprint_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; 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 -#[endpoint { - method = DELETE, - path = "/deployment/blueprints/all/{blueprint_id}", -}] -async fn blueprint_delete( - rqctx: RequestContext>, - path_params: Path, -) -> Result { - let apictx = rqctx.context(); - let handler = async { let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + + let handler = async { + let blueprint = + nexus.blueprint_view(&opctx, path.blueprint_id).await?; + Ok(HttpResponseOk(blueprint)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } + + async fn blueprint_delete( + rqctx: RequestContext, + path_params: Path, + ) -> Result { + let apictx = &rqctx.context().context; 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 -} + let opctx = crate::context::op_context_for_internal_api(&rqctx).await; -// Managing the current target blueprint + let handler = async { + nexus.blueprint_delete(&opctx, path.blueprint_id).await?; + Ok(HttpResponseDeleted()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// Fetches the current target blueprint, if any -#[endpoint { - method = GET, - path = "/deployment/blueprints/target", -}] -async fn blueprint_target_view( - rqctx: RequestContext>, -) -> Result, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + async fn blueprint_target_view( + rqctx: RequestContext, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; let nexus = &apictx.nexus; - let target = nexus.blueprint_target_view(&opctx).await?; - Ok(HttpResponseOk(target)) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} - -/// 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> { - let apictx = rqctx.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 -} + let handler = async { + let target = nexus.blueprint_target_view(&opctx).await?; + Ok(HttpResponseOk(target)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// 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> { - let apictx = rqctx.context(); - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + async fn blueprint_target_set( + rqctx: RequestContext, + target: TypedBody, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; 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 -} - -// 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> { - let apictx = rqctx.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 -} + let handler = async { + 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 + } -/// 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 { - let apictx = rqctx.context(); - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + async fn blueprint_target_set_enabled( + rqctx: RequestContext, + target: TypedBody, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; 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 -} + let opctx = crate::context::op_context_for_internal_api(&rqctx).await; + let handler = async { + 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 + } -/// List uninitialized sleds -#[endpoint { - method = GET, - path = "/sleds/uninitialized", -}] -async fn sled_list_uninitialized( - rqctx: RequestContext>, -) -> Result>, HttpError> { - let apictx = rqctx.context(); - let handler = async { + async fn blueprint_regenerate( + rqctx: RequestContext, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; 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 -} + let handler = async { + let result = nexus.blueprint_create_regenerate(&opctx).await?; + Ok(HttpResponseOk(result)) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// 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 { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let handler = async { - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - nexus.sled_add(&opctx, sled.into_inner()).await?; - Ok(HttpResponseUpdatedNoContent()) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await -} + async fn blueprint_import( + rqctx: RequestContext, + blueprint: 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; + let blueprint = blueprint.into_inner(); + nexus.blueprint_import(&opctx, blueprint).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// 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> { - let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let handler = async { + async fn sled_list_uninitialized( + rqctx: RequestContext, + ) -> Result>, HttpError> { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; 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 -} + let handler = async { + 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 + } -/// Path parameters for probes -#[derive(Deserialize, JsonSchema)] -struct ProbePathParam { - sled: Uuid, -} + async fn sled_add( + rqctx: RequestContext, + sled: 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.sled_add(&opctx, sled.into_inner()).await?; + Ok(HttpResponseUpdatedNoContent()) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } -/// Get all the probes associated with a given sled. -#[endpoint { - method = GET, - path = "/probes/{sled}" -}] -async fn probes_get( - rqctx: RequestContext>, - path_params: Path, - query_params: Query, -) -> Result>, HttpError> { - let apictx = rqctx.context(); - let handler = async { - let query = query_params.into_inner(); - let path = path_params.into_inner(); + async fn sled_expunge( + rqctx: RequestContext, + sled: TypedBody, + ) -> Result, HttpError> { + let apictx = &rqctx.context().context; let nexus = &apictx.nexus; - let opctx = crate::context::op_context_for_internal_api(&rqctx).await; - let pagparams = data_page_params_for(&rqctx, &query)?; - Ok(HttpResponseOk( - nexus.probe_list_for_sled(&opctx, &pagparams, path.sled).await?, - )) - }; - apictx.internal_latencies.instrument_dropshot_handler(&rqctx, handler).await + 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 probes_get( + rqctx: RequestContext, + path_params: Path, + query_params: Query, + ) -> Result>, HttpError> { + let apictx = &rqctx.context().context; + let nexus = &apictx.nexus; + let handler = async { + let query = query_params.into_inner(); + let path = path_params.into_inner(); + let opctx = + crate::context::op_context_for_internal_api(&rqctx).await; + let pagparams = data_page_params_for(&rqctx, &query)?; + Ok(HttpResponseOk( + nexus + .probe_list_for_sled(&opctx, &pagparams, path.sled) + .await?, + )) + }; + apictx + .internal_latencies + .instrument_dropshot_handler(&rqctx, handler) + .await + } } diff --git a/nexus/src/lib.rs b/nexus/src/lib.rs index 81a4074392..1bf74050d6 100644 --- a/nexus/src/lib.rs +++ b/nexus/src/lib.rs @@ -27,6 +27,7 @@ use dropshot::ConfigDropshot; use external_api::http_entrypoints::external_api; use internal_api::http_entrypoints::internal_api; use nexus_config::NexusConfig; +use nexus_internal_api::NexusInternalApi_to_stub_api_description; use nexus_types::deployment::blueprint_zone_type; use nexus_types::deployment::Blueprint; use nexus_types::deployment::BlueprintZoneFilter; @@ -51,6 +52,8 @@ use std::net::{Ipv4Addr, SocketAddr, SocketAddrV6}; use std::sync::Arc; use uuid::Uuid; +use crate::internal_api::http_entrypoints::NexusInternalApiImpl; + #[macro_use] extern crate slog; @@ -67,11 +70,9 @@ pub fn run_openapi_external() -> Result<(), String> { } pub fn run_openapi_internal() -> Result<(), String> { - internal_api() - .openapi("Nexus internal API", "0.0.1") - .description("Nexus internal API") - .contact_url("https://oxide.computer") - .contact_email("api@oxide.computer") + let api = NexusInternalApi_to_stub_api_description() + .map_err(|e| e.to_string())?; + nexus_internal_api::openapi_definition(&api) .write(&mut std::io::stdout()) .map_err(|e| e.to_string()) } @@ -82,7 +83,7 @@ pub struct InternalServer { /// shared state used by API request handlers apictx: Arc, /// dropshot server for internal API - http_server_internal: dropshot::HttpServer>, + http_server_internal: dropshot::HttpServer, config: NexusConfig, log: Logger, @@ -107,7 +108,7 @@ impl InternalServer { let server_starter_internal = dropshot::HttpServerStarter::new( &config.deployment.dropshot_internal, internal_api(), - Arc::clone(&apictx), + NexusInternalApiImpl { context: Arc::clone(&apictx) }, &log.new(o!("component" => "dropshot_internal")), ) .map_err(|error| format!("initializing internal server: {}", error))?; diff --git a/nexus/tests/integration_tests/commands.rs b/nexus/tests/integration_tests/commands.rs index fd7a6c60c0..a77a0a8b56 100644 --- a/nexus/tests/integration_tests/commands.rs +++ b/nexus/tests/integration_tests/commands.rs @@ -180,19 +180,3 @@ fn test_nexus_openapi() { // renaming, or changing the tags are what you intend. assert_contents("tests/output/nexus_tags.txt", &tags); } - -#[test] -fn test_nexus_openapi_internal() { - let (stdout_text, _) = run_command_with_arg("--openapi-internal"); - let spec: OpenAPI = serde_json::from_str(&stdout_text) - .expect("stdout was not valid OpenAPI"); - - // Check for lint errors. - let errors = openapi_lint::validate(&spec); - assert!(errors.is_empty(), "{}", errors.join("\n\n")); - - // Confirm that the output hasn't changed. It's expected that we'll change - // this file as the API evolves, but pay attention to the diffs to ensure - // that the changes match your expectations. - assert_contents("../openapi/nexus-internal.json", &stdout_text); -} diff --git a/nexus/tests/integration_tests/probe.rs b/nexus/tests/integration_tests/probe.rs index 71a695bf8c..6b6053adc0 100644 --- a/nexus/tests/integration_tests/probe.rs +++ b/nexus/tests/integration_tests/probe.rs @@ -1,13 +1,12 @@ use dropshot::HttpErrorResponseBody; use http::{Method, StatusCode}; -use nexus_db_queries::db::datastore::ProbeInfo; use nexus_test_utils::{ http_testing::{AuthnMode, NexusRequest}, resource_helpers::{create_default_ip_pool, create_project}, SLED_AGENT_UUID, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::ProbeCreate; +use nexus_types::external_api::{params::ProbeCreate, views::ProbeInfo}; use omicron_common::api::external::{IdentityMetadataCreateParams, Probe}; type ControlPlaneTestContext = diff --git a/nexus/types/src/external_api/views.rs b/nexus/types/src/external_api/views.rs index 80ef24fcb2..8729794fa6 100644 --- a/nexus/types/src/external_api/views.rs +++ b/nexus/types/src/external_api/views.rs @@ -15,6 +15,7 @@ use omicron_common::api::external::{ ByteCount, Digest, Error, IdentityMetadata, InstanceState, Ipv4Net, Ipv6Net, Name, ObjectIdentity, RoleName, SimpleIdentity, }; +use omicron_common::api::internal::shared::NetworkInterface; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -948,3 +949,28 @@ pub struct Ping { /// returns anything at all. pub status: PingStatus, } + +#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] +pub struct ProbeInfo { + pub id: Uuid, + pub name: Name, + pub sled: Uuid, + pub external_ips: Vec, + pub interface: NetworkInterface, +} + +#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] +pub struct ProbeExternalIp { + pub ip: IpAddr, + pub first_port: u16, + pub last_port: u16, + pub kind: ProbeIpKind, +} + +#[derive(Debug, Copy, Clone, JsonSchema, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ProbeIpKind { + Snat, + Floating, + Ephemeral, +} diff --git a/nexus/types/src/internal_api/views.rs b/nexus/types/src/internal_api/views.rs index fde2d07072..b71fd04779 100644 --- a/nexus/types/src/internal_api/views.rs +++ b/nexus/types/src/internal_api/views.rs @@ -6,9 +6,13 @@ use chrono::DateTime; use chrono::Utc; use futures::future::ready; use futures::stream::StreamExt; +use omicron_common::api::external::MacAddr; use omicron_common::api::external::ObjectStream; +use omicron_common::api::external::Vni; use schemars::JsonSchema; use serde::Serialize; +use std::net::Ipv4Addr; +use std::net::Ipv6Addr; use std::time::Duration; use std::time::Instant; use steno::SagaResultErr; @@ -296,3 +300,16 @@ pub struct LastResultCompleted { /// arbitrary datum emitted by the background task pub details: serde_json::Value, } + +/// NAT Record +#[derive(Clone, Debug, Serialize, JsonSchema)] +pub struct Ipv4NatEntryView { + pub external_address: Ipv4Addr, + pub first_port: u16, + pub last_port: u16, + pub sled_address: Ipv6Addr, + pub vni: Vni, + pub mac: MacAddr, + pub gen: i64, + pub deleted: bool, +} diff --git a/openapi/nexus-internal.json b/openapi/nexus-internal.json index ac0426eb2b..502deb146e 100644 --- a/openapi/nexus-internal.json +++ b/openapi/nexus-internal.json @@ -2980,14 +2980,6 @@ } ] }, - "IpKind": { - "type": "string", - "enum": [ - "snat", - "floating", - "ephemeral" - ] - }, "IpNet": { "oneOf": [ { @@ -3643,7 +3635,7 @@ "format": "ip" }, "kind": { - "$ref": "#/components/schemas/IpKind" + "$ref": "#/components/schemas/ProbeIpKind" }, "last_port": { "type": "integer", @@ -3690,6 +3682,14 @@ "sled" ] }, + "ProbeIpKind": { + "type": "string", + "enum": [ + "snat", + "floating", + "ephemeral" + ] + }, "ProducerEndpoint": { "description": "Information announced by a metric server, used so that clients can contact it and collect available metric data from it.", "type": "object", diff --git a/openapi/nexus.json b/openapi/nexus.json index 76e75d1ada..402c0ada56 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -13974,14 +13974,6 @@ } ] }, - "IpKind": { - "type": "string", - "enum": [ - "snat", - "floating", - "ephemeral" - ] - }, "IpNet": { "oneOf": [ { @@ -15185,7 +15177,7 @@ "format": "ip" }, "kind": { - "$ref": "#/components/schemas/IpKind" + "$ref": "#/components/schemas/ProbeIpKind" }, "last_port": { "type": "integer", @@ -15253,6 +15245,14 @@ "items" ] }, + "ProbeIpKind": { + "type": "string", + "enum": [ + "snat", + "floating", + "ephemeral" + ] + }, "Project": { "description": "View of a Project", "type": "object", diff --git a/workspace-hack/Cargo.toml b/workspace-hack/Cargo.toml index 6eee92cf63..e2a78b0b19 100644 --- a/workspace-hack/Cargo.toml +++ b/workspace-hack/Cargo.toml @@ -18,7 +18,6 @@ ahash = { version = "0.8.8" } aho-corasick = { version = "1.1.2" } anyhow = { version = "1.0.82", features = ["backtrace"] } base16ct = { version = "0.2.0", default-features = false, features = ["alloc"] } -base64 = { version = "0.22.0" } bit-set = { version = "0.5.3" } bit-vec = { version = "0.6.3" } bitflags-dff4ba8e3ae991db = { package = "bitflags", version = "1.3.2" } @@ -112,7 +111,7 @@ tracing = { version = "0.1.40", features = ["log"] } trust-dns-proto = { version = "0.22.0" } unicode-bidi = { version = "0.3.15" } unicode-normalization = { version = "0.1.22" } -usdt = { version = "0.3.5" } +usdt = { version = "0.5.0" } usdt-impl = { version = "0.5.0", default-features = false, features = ["asm", "des"] } uuid = { version = "1.8.0", features = ["serde", "v4"] } yasna = { version = "0.5.2", features = ["bit-vec", "num-bigint", "std", "time"] } @@ -125,7 +124,6 @@ ahash = { version = "0.8.8" } aho-corasick = { version = "1.1.2" } anyhow = { version = "1.0.82", features = ["backtrace"] } base16ct = { version = "0.2.0", default-features = false, features = ["alloc"] } -base64 = { version = "0.22.0" } bit-set = { version = "0.5.3" } bit-vec = { version = "0.6.3" } bitflags-dff4ba8e3ae991db = { package = "bitflags", version = "1.3.2" } @@ -220,7 +218,7 @@ tracing = { version = "0.1.40", features = ["log"] } trust-dns-proto = { version = "0.22.0" } unicode-bidi = { version = "0.3.15" } unicode-normalization = { version = "0.1.22" } -usdt = { version = "0.3.5" } +usdt = { version = "0.5.0" } usdt-impl = { version = "0.5.0", default-features = false, features = ["asm", "des"] } uuid = { version = "1.8.0", features = ["serde", "v4"] } yasna = { version = "0.5.2", features = ["bit-vec", "num-bigint", "std", "time"] }