diff --git a/Cargo.lock b/Cargo.lock index f6ced2e6f42..703863a262c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7090,6 +7090,7 @@ dependencies = [ "ipnetwork", "itertools 0.14.0", "nexus-types", + "nexus-types-versions", "omicron-common", "omicron-uuid-kinds", "omicron-workspace-hack", @@ -7097,12 +7098,10 @@ dependencies = [ "oximeter-types 0.1.0", "oxnet", "oxql-types", - "proptest", "schemars 0.8.22", "scim2-rs", "serde", "serde_json", - "test-strategy", "trust-quorum-types", "tufaceous-artifact", "uuid", @@ -7181,6 +7180,7 @@ dependencies = [ "dropshot", "http", "nexus-types", + "nexus-types-versions", "omicron-common", "omicron-uuid-kinds", "omicron-workspace-hack", @@ -7705,6 +7705,7 @@ dependencies = [ "itertools 0.14.0", "newtype-uuid", "newtype_derive", + "nexus-types-versions", "omicron-common", "omicron-passwords", "omicron-test-utils", @@ -7745,6 +7746,39 @@ dependencies = [ "uuid", ] +[[package]] +name = "nexus-types-versions" +version = "0.1.0" +dependencies = [ + "anyhow", + "api_identity", + "base64 0.22.1", + "chrono", + "daft", + "dropshot", + "http", + "omicron-common", + "omicron-passwords", + "omicron-uuid-kinds", + "omicron-workspace-hack", + "openssl", + "oxnet", + "oxql-types", + "parse-display", + "regex", + "schemars 0.8.22", + "semver 1.0.27", + "serde", + "serde_json", + "sled-hardware-types", + "slog-error-chain", + "strum 0.27.2", + "tough", + "tufaceous-artifact", + "url", + "uuid", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -8566,6 +8600,7 @@ dependencies = [ "nexus-test-utils", "nexus-test-utils-macros", "nexus-types", + "nexus-types-versions", "ntp-admin-client", "num-integer", "omicron-cockroach-metrics", diff --git a/Cargo.toml b/Cargo.toml index c487cf051e3..0dbaeb5e95d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,6 +110,7 @@ members = [ "nexus/test-utils-macros", "nexus/test-utils", "nexus/types", + "nexus/types/versions", "ntp-admin", "ntp-admin/api", "ntp-admin/types", @@ -285,6 +286,7 @@ default-members = [ "nexus/test-utils-macros", "nexus/test-utils", "nexus/types", + "nexus/types/versions", "ntp-admin", "ntp-admin/api", "ntp-admin/types", @@ -610,6 +612,7 @@ nexus-test-interface = { path = "nexus/test-interface" } nexus-test-utils-macros = { path = "nexus/test-utils-macros" } nexus-test-utils = { path = "nexus/test-utils" } nexus-types = { path = "nexus/types" } +nexus-types-versions = { path = "nexus/types/versions" } nix = { version = "0.30", features = ["fs", "net"] } nom = "7.1.3" nonempty = "0.12.0" diff --git a/clients/nexus-client/src/lib.rs b/clients/nexus-client/src/lib.rs index cd8cb8a57b1..e58117d3af3 100644 --- a/clients/nexus-client/src/lib.rs +++ b/clients/nexus-client/src/lib.rs @@ -266,8 +266,8 @@ impl TryFrom } } -impl From for types::Baseboard { - fn from(value: nexus_types::external_api::shared::Baseboard) -> Self { +impl From for types::Baseboard { + fn from(value: nexus_types::external_api::hardware::Baseboard) -> Self { types::Baseboard { part: value.part, revision: value.revision, diff --git a/dev-tools/omdb/src/bin/omdb/db.rs b/dev-tools/omdb/src/bin/omdb/db.rs index 466bcd73d30..6686c29b675 100644 --- a/dev-tools/omdb/src/bin/omdb/db.rs +++ b/dev-tools/omdb/src/bin/omdb/db.rs @@ -137,11 +137,11 @@ use nexus_types::deployment::BlueprintZoneDisposition; use nexus_types::deployment::BlueprintZoneType; use nexus_types::deployment::DiskFilter; use nexus_types::deployment::SledFilter; -use nexus_types::external_api::params; -use nexus_types::external_api::views::PhysicalDiskPolicy; -use nexus_types::external_api::views::PhysicalDiskState; -use nexus_types::external_api::views::SledPolicy; -use nexus_types::external_api::views::SledState; +use nexus_types::external_api::disk::BlockSize; +use nexus_types::external_api::physical_disk::{ + PhysicalDiskPolicy, PhysicalDiskState, +}; +use nexus_types::external_api::sled::{SledPolicy, SledState}; use nexus_types::identity::Resource; use nexus_types::internal_api::params::DnsRecord; use nexus_types::internal_api::params::Srv; @@ -4086,7 +4086,7 @@ async fn cmd_db_dry_run_region_allocation( }; let size: external::ByteCount = args.size.try_into()?; - let block_size: params::BlockSize = args.block_size.try_into()?; + let block_size: BlockSize = args.block_size.try_into()?; let (blocks_per_extent, extent_count) = DataStore::get_crucible_allocation( &block_size.try_into().unwrap(), diff --git a/dev-tools/omdb/src/bin/omdb/nexus.rs b/dev-tools/omdb/src/bin/omdb/nexus.rs index 071ee0aebe8..7bf00ec8a68 100644 --- a/dev-tools/omdb/src/bin/omdb/nexus.rs +++ b/dev-tools/omdb/src/bin/omdb/nexus.rs @@ -3009,7 +3009,7 @@ fn print_task_alert_dispatcher(details: &serde_json::Value) { } } fn print_task_webhook_deliverator(details: &serde_json::Value) { - use nexus_types::external_api::views::WebhookDeliveryAttemptResult; + use nexus_types::external_api::alert::WebhookDeliveryAttemptResult; use nexus_types::internal_api::background::WebhookDeliveratorStatus; use nexus_types::internal_api::background::WebhookDeliveryFailure; use nexus_types::internal_api::background::WebhookRxDeliveryStatus; diff --git a/dev-tools/reconfigurator-cli/src/lib.rs b/dev-tools/reconfigurator-cli/src/lib.rs index a00c805bf5e..f0cabd50c32 100644 --- a/dev-tools/reconfigurator-cli/src/lib.rs +++ b/dev-tools/reconfigurator-cli/src/lib.rs @@ -49,8 +49,7 @@ use nexus_types::deployment::{ }; use nexus_types::deployment::{OmicronZoneNic, TargetReleaseDescription}; use nexus_types::deployment::{PendingMgsUpdateSpDetails, PlanningInput}; -use nexus_types::external_api::views::SledPolicy; -use nexus_types::external_api::views::SledProvisionPolicy; +use nexus_types::external_api::sled::{SledPolicy, SledProvisionPolicy}; use nexus_types::inventory::CollectionDisplayCliFilter; use omicron_common::address::REPO_DEPOT_PORT; use omicron_common::api::external::Generation; diff --git a/dev-tools/reconfigurator-cli/src/test_utils.rs b/dev-tools/reconfigurator-cli/src/test_utils.rs index 8d72d89c264..87ee6217ea5 100644 --- a/dev-tools/reconfigurator-cli/src/test_utils.rs +++ b/dev-tools/reconfigurator-cli/src/test_utils.rs @@ -24,7 +24,7 @@ use nexus_types::deployment::Blueprint; use nexus_types::deployment::BlueprintSource; use nexus_types::deployment::ClickhousePolicy; use nexus_types::deployment::PlanningInput; -use nexus_types::external_api::views::SledPolicy; +use nexus_types::external_api::sled::SledPolicy; use nexus_types::inventory::Collection; use omicron_uuid_kinds::SledUuid; use slog::Logger; diff --git a/live-tests/tests/common/reconfigurator.rs b/live-tests/tests/common/reconfigurator.rs index 7e97cb5a569..2e867b43f53 100644 --- a/live-tests/tests/common/reconfigurator.rs +++ b/live-tests/tests/common/reconfigurator.rs @@ -13,7 +13,7 @@ use nexus_lockstep_client::types::{ use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder; use nexus_reconfigurator_planning::planner::PlannerRng; use nexus_types::deployment::{Blueprint, BlueprintSource}; -use nexus_types::external_api::views::SledState; +use nexus_types::external_api::sled::SledState; use nexus_types::inventory::Collection; use omicron_test_utils::dev::poll::{CondCheckError, wait_for_condition}; use omicron_uuid_kinds::GenericUuid; diff --git a/nexus/Cargo.toml b/nexus/Cargo.toml index afa28295d69..13d97aba64f 100644 --- a/nexus/Cargo.toml +++ b/nexus/Cargo.toml @@ -141,6 +141,7 @@ nexus-reconfigurator-planning.workspace = true nexus-reconfigurator-preparation.workspace = true nexus-reconfigurator-rendezvous.workspace = true nexus-types.workspace = true +nexus-types-versions.workspace = true omicron-common.workspace = true omicron-passwords.workspace = true oxide-tokio-rt.workspace = true diff --git a/nexus/auth/src/authn/mod.rs b/nexus/auth/src/authn/mod.rs index 2d0f676365c..a133d571643 100644 --- a/nexus/auth/src/authn/mod.rs +++ b/nexus/auth/src/authn/mod.rs @@ -40,8 +40,8 @@ pub use nexus_db_fixed_data::user_builtin::USER_SERVICE_BALANCER; use crate::authz; use chrono::{DateTime, Utc}; use nexus_db_fixed_data::silo::DEFAULT_SILO; -use nexus_types::external_api::shared::FleetRole; -use nexus_types::external_api::shared::SiloRole; +use nexus_types::external_api::policy::FleetRole; +use nexus_types::external_api::policy::SiloRole; use nexus_types::identity::Asset; use omicron_common::api::external::LookupType; use omicron_uuid_kinds::BuiltInUserUuid; diff --git a/nexus/auth/src/authz/actor.rs b/nexus/auth/src/authz/actor.rs index c9a8bd1a113..9eaaacaff98 100644 --- a/nexus/auth/src/authz/actor.rs +++ b/nexus/auth/src/authz/actor.rs @@ -8,7 +8,7 @@ use super::roles::RoleSet; use crate::authn; use crate::authz::SiloUser; use nexus_db_model::DatabaseString; -use nexus_types::external_api::shared::FleetRole; +use nexus_types::external_api::policy::FleetRole; use omicron_common::api::external::LookupType; use omicron_common::api::external::ResourceType; use uuid::Uuid; diff --git a/nexus/auth/src/authz/api_resources.rs b/nexus/auth/src/authz/api_resources.rs index d453b7d8957..dccf93c8332 100644 --- a/nexus/auth/src/authz/api_resources.rs +++ b/nexus/auth/src/authz/api_resources.rs @@ -40,7 +40,7 @@ use authz_macros::authz_resource; use futures::FutureExt; use futures::future::BoxFuture; use nexus_db_fixed_data::FLEET_ID; -use nexus_types::external_api::shared::{FleetRole, ProjectRole, SiloRole}; +use nexus_types::external_api::policy::{FleetRole, ProjectRole, SiloRole}; use omicron_common::api::external::{Error, LookupType, ResourceType}; use oso::PolarClass; use serde::{Deserialize, Serialize}; diff --git a/nexus/db-fixed-data/src/project.rs b/nexus/db-fixed-data/src/project.rs index 50e0e43e86d..150711bf4e4 100644 --- a/nexus/db-fixed-data/src/project.rs +++ b/nexus/db-fixed-data/src/project.rs @@ -3,7 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use nexus_db_model as model; -use nexus_types::{external_api::params, silo::INTERNAL_SILO_ID}; +use nexus_types::{external_api::project, silo::INTERNAL_SILO_ID}; use omicron_common::api::external::IdentityMetadataCreateParams; use std::sync::LazyLock; @@ -22,7 +22,7 @@ pub static SERVICES_PROJECT: LazyLock = LazyLock::new(|| { model::Project::new_with_id( *SERVICES_PROJECT_ID, INTERNAL_SILO_ID, - params::ProjectCreate { + project::ProjectCreate { identity: IdentityMetadataCreateParams { name: SERVICES_DB_NAME.parse().unwrap(), description: "Built-in project for Oxide Services".to_string(), diff --git a/nexus/db-fixed-data/src/silo.rs b/nexus/db-fixed-data/src/silo.rs index b5dd6b41aad..9bf663ed05a 100644 --- a/nexus/db-fixed-data/src/silo.rs +++ b/nexus/db-fixed-data/src/silo.rs @@ -4,7 +4,7 @@ use nexus_db_model as model; use nexus_types::{ - external_api::{params, shared}, + external_api::silo::{SiloCreate, SiloIdentityMode, SiloQuotasCreate}, silo::{ DEFAULT_SILO_ID, INTERNAL_SILO_ID, default_silo_name, internal_silo_name, @@ -20,16 +20,16 @@ use std::sync::LazyLock; pub static DEFAULT_SILO: LazyLock = LazyLock::new(|| { model::Silo::new_with_id( DEFAULT_SILO_ID, - params::SiloCreate { + SiloCreate { identity: IdentityMetadataCreateParams { name: default_silo_name().clone(), description: "default silo".to_string(), }, // This quota is actually _unused_ because the default silo // isn't constructed in the same way a normal silo would be. - quotas: params::SiloQuotasCreate::empty(), + quotas: SiloQuotasCreate::empty(), discoverable: false, - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: SiloIdentityMode::LocalOnly, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: Default::default(), @@ -43,15 +43,15 @@ pub static DEFAULT_SILO: LazyLock = LazyLock::new(|| { pub static INTERNAL_SILO: LazyLock = LazyLock::new(|| { model::Silo::new_with_id( INTERNAL_SILO_ID, - params::SiloCreate { + SiloCreate { identity: IdentityMetadataCreateParams { name: internal_silo_name().clone(), description: "Built-in internal Silo.".to_string(), }, // The internal silo contains no virtual resources, so it has no allotted capacity. - quotas: params::SiloQuotasCreate::empty(), + quotas: SiloQuotasCreate::empty(), discoverable: false, - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: SiloIdentityMode::LocalOnly, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: Default::default(), diff --git a/nexus/db-fixed-data/src/vpc.rs b/nexus/db-fixed-data/src/vpc.rs index fc263a02da1..6647eb06849 100644 --- a/nexus/db-fixed-data/src/vpc.rs +++ b/nexus/db-fixed-data/src/vpc.rs @@ -4,7 +4,7 @@ use crate::project::SERVICES_DB_NAME; use nexus_db_model as model; -use nexus_types::external_api::params; +use nexus_types::external_api::vpc; use omicron_common::address::SERVICE_VPC_IPV6_PREFIX; use omicron_common::api::external::IdentityMetadataCreateParams; use std::sync::LazyLock; @@ -53,7 +53,7 @@ pub static SERVICES_VPC: LazyLock = LazyLock::new(|| { *SERVICES_VPC_ID, *super::project::SERVICES_PROJECT_ID, *SERVICES_VPC_ROUTER_ID, - params::VpcCreate { + vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: SERVICES_DB_NAME.parse().unwrap(), description: "Built-in VPC for Oxide Services".to_string(), diff --git a/nexus/db-model/src/affinity.rs b/nexus/db-model/src/affinity.rs index 06c223e5941..e86cb79d52b 100644 --- a/nexus/db-model/src/affinity.rs +++ b/nexus/db-model/src/affinity.rs @@ -15,8 +15,7 @@ use nexus_db_schema::schema::affinity_group; use nexus_db_schema::schema::affinity_group_instance_membership; use nexus_db_schema::schema::anti_affinity_group; use nexus_db_schema::schema::anti_affinity_group_instance_membership; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::affinity; use omicron_common::api::external; use omicron_common::api::external::IdentityMetadata; use omicron_uuid_kinds::AffinityGroupKind; @@ -95,7 +94,10 @@ pub struct AffinityGroup { } impl AffinityGroup { - pub fn new(project_id: Uuid, params: params::AffinityGroupCreate) -> Self { + pub fn new( + project_id: Uuid, + params: affinity::AffinityGroupCreate, + ) -> Self { Self { identity: AffinityGroupIdentity::new( Uuid::new_v4(), @@ -108,7 +110,7 @@ impl AffinityGroup { } } -impl From for views::AffinityGroup { +impl From for affinity::AffinityGroup { fn from(group: AffinityGroup) -> Self { let identity = IdentityMetadata { id: group.identity.id, @@ -135,8 +137,8 @@ pub struct AffinityGroupUpdate { pub time_modified: DateTime, } -impl From for AffinityGroupUpdate { - fn from(params: params::AffinityGroupUpdate) -> Self { +impl From for AffinityGroupUpdate { + fn from(params: affinity::AffinityGroupUpdate) -> Self { Self { name: params.identity.name.map(Name), description: params.identity.description, @@ -160,7 +162,7 @@ pub struct AntiAffinityGroup { impl AntiAffinityGroup { pub fn new( project_id: Uuid, - params: params::AntiAffinityGroupCreate, + params: affinity::AntiAffinityGroupCreate, ) -> Self { Self { identity: AntiAffinityGroupIdentity::new( @@ -174,7 +176,7 @@ impl AntiAffinityGroup { } } -impl From for views::AntiAffinityGroup { +impl From for affinity::AntiAffinityGroup { fn from(group: AntiAffinityGroup) -> Self { let identity = IdentityMetadata { id: group.identity.id, @@ -201,8 +203,8 @@ pub struct AntiAffinityGroupUpdate { pub time_modified: DateTime, } -impl From for AntiAffinityGroupUpdate { - fn from(params: params::AntiAffinityGroupUpdate) -> Self { +impl From for AntiAffinityGroupUpdate { + fn from(params: affinity::AntiAffinityGroupUpdate) -> Self { Self { name: params.identity.name.map(Name), description: params.identity.description, diff --git a/nexus/db-model/src/alert_class.rs b/nexus/db-model/src/alert_class.rs index 5f0b2129707..6c85e4593aa 100644 --- a/nexus/db-model/src/alert_class.rs +++ b/nexus/db-model/src/alert_class.rs @@ -3,7 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use super::impl_enum_type; -use nexus_types::external_api::views; +use nexus_types::external_api::alert; use serde::de::{self, Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; use std::fmt; @@ -123,7 +123,7 @@ impl std::str::FromStr for AlertClass { } } -impl From for views::AlertClass { +impl From for alert::AlertClass { fn from(class: AlertClass) -> Self { Self { name: class.to_string(), diff --git a/nexus/db-model/src/alert_delivery_state.rs b/nexus/db-model/src/alert_delivery_state.rs index 5c5553820bf..64aeadfc7a2 100644 --- a/nexus/db-model/src/alert_delivery_state.rs +++ b/nexus/db-model/src/alert_delivery_state.rs @@ -3,7 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use super::impl_enum_type; -use nexus_types::external_api::views; +use nexus_types::external_api::alert; use serde::Deserialize; use serde::Serialize; use std::fmt; @@ -35,11 +35,11 @@ impl_enum_type!( impl fmt::Display for AlertDeliveryState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Forward to the canonical implementation in nexus-types. - views::AlertDeliveryState::from(*self).fmt(f) + alert::AlertDeliveryState::from(*self).fmt(f) } } -impl From for views::AlertDeliveryState { +impl From for alert::AlertDeliveryState { fn from(trigger: AlertDeliveryState) -> Self { match trigger { AlertDeliveryState::Pending => Self::Pending, @@ -49,12 +49,12 @@ impl From for views::AlertDeliveryState { } } -impl From for AlertDeliveryState { - fn from(trigger: views::AlertDeliveryState) -> Self { +impl From for AlertDeliveryState { + fn from(trigger: alert::AlertDeliveryState) -> Self { match trigger { - views::AlertDeliveryState::Pending => Self::Pending, - views::AlertDeliveryState::Failed => Self::Failed, - views::AlertDeliveryState::Delivered => Self::Delivered, + alert::AlertDeliveryState::Pending => Self::Pending, + alert::AlertDeliveryState::Failed => Self::Failed, + alert::AlertDeliveryState::Delivered => Self::Delivered, } } } @@ -62,6 +62,6 @@ impl From for AlertDeliveryState { impl FromStr for AlertDeliveryState { type Err = omicron_common::api::external::Error; fn from_str(s: &str) -> Result { - views::AlertDeliveryState::from_str(s).map(Into::into) + alert::AlertDeliveryState::from_str(s).map(Into::into) } } diff --git a/nexus/db-model/src/alert_delivery_trigger.rs b/nexus/db-model/src/alert_delivery_trigger.rs index 6fd84d2d8d5..a5f2f9bf143 100644 --- a/nexus/db-model/src/alert_delivery_trigger.rs +++ b/nexus/db-model/src/alert_delivery_trigger.rs @@ -3,7 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use super::impl_enum_type; -use nexus_types::external_api::views; +use nexus_types::external_api::alert; use serde::Deserialize; use serde::Serialize; use std::fmt; @@ -39,11 +39,11 @@ impl AlertDeliveryTrigger { impl fmt::Display for AlertDeliveryTrigger { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Forward to the canonical implementation in nexus-types. - views::AlertDeliveryTrigger::from(*self).fmt(f) + alert::AlertDeliveryTrigger::from(*self).fmt(f) } } -impl From for views::AlertDeliveryTrigger { +impl From for alert::AlertDeliveryTrigger { fn from(trigger: AlertDeliveryTrigger) -> Self { match trigger { AlertDeliveryTrigger::Alert => Self::Alert, @@ -53,12 +53,12 @@ impl From for views::AlertDeliveryTrigger { } } -impl From for AlertDeliveryTrigger { - fn from(trigger: views::AlertDeliveryTrigger) -> Self { +impl From for AlertDeliveryTrigger { + fn from(trigger: alert::AlertDeliveryTrigger) -> Self { match trigger { - views::AlertDeliveryTrigger::Alert => Self::Alert, - views::AlertDeliveryTrigger::Resend => Self::Resend, - views::AlertDeliveryTrigger::Probe => Self::Probe, + alert::AlertDeliveryTrigger::Alert => Self::Alert, + alert::AlertDeliveryTrigger::Resend => Self::Resend, + alert::AlertDeliveryTrigger::Probe => Self::Probe, } } } @@ -66,6 +66,6 @@ impl From for AlertDeliveryTrigger { impl FromStr for AlertDeliveryTrigger { type Err = omicron_common::api::external::Error; fn from_str(s: &str) -> Result { - views::AlertDeliveryTrigger::from_str(s).map(Into::into) + alert::AlertDeliveryTrigger::from_str(s).map(Into::into) } } diff --git a/nexus/db-model/src/alert_subscription.rs b/nexus/db-model/src/alert_subscription.rs index 1f1c559d0e4..a29f0ed4e79 100644 --- a/nexus/db-model/src/alert_subscription.rs +++ b/nexus/db-model/src/alert_subscription.rs @@ -8,7 +8,7 @@ use crate::SemverVersion; use crate::typed_uuid::DbTypedUuid; use chrono::{DateTime, Utc}; use nexus_db_schema::schema::{alert_glob, alert_subscription}; -use nexus_types::external_api::shared; +use nexus_types::external_api::alert; use omicron_common::api::external::Error; use omicron_uuid_kinds::{AlertReceiverKind, AlertReceiverUuid}; use serde::{Deserialize, Serialize}; @@ -96,7 +96,7 @@ impl AlertSubscriptionKind { } } -impl TryFrom for shared::AlertSubscription { +impl TryFrom for alert::AlertSubscription { type Error = Error; fn try_from(kind: AlertSubscriptionKind) -> Result { match kind { @@ -113,10 +113,10 @@ impl TryFrom for shared::AlertSubscription { } } -impl TryFrom for AlertSubscriptionKind { +impl TryFrom for AlertSubscriptionKind { type Error = Error; fn try_from( - subscription: shared::AlertSubscription, + subscription: alert::AlertSubscription, ) -> Result { Self::new(String::from(subscription)) } diff --git a/nexus/db-model/src/allow_list.rs b/nexus/db-model/src/allow_list.rs index 7472bd367ca..67b0f66ba1d 100644 --- a/nexus/db-model/src/allow_list.rs +++ b/nexus/db-model/src/allow_list.rs @@ -11,8 +11,7 @@ use chrono::DateTime; use chrono::Utc; use ipnetwork::IpNetwork; use nexus_db_schema::schema::allow_list; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::system; use omicron_common::api::external; use omicron_common::api::external::Error; use serde::Deserialize; @@ -68,8 +67,8 @@ pub struct AllowListUpdate { pub allowed_ips: Option>, } -impl From for AllowListUpdate { - fn from(params: params::AllowListUpdate) -> Self { +impl From for AllowListUpdate { + fn from(params: system::AllowListUpdate) -> Self { let allowed_ips = match params.allowed_ips { external::AllowedSourceIps::Any => None, external::AllowedSourceIps::List(list) => { @@ -80,7 +79,7 @@ impl From for AllowListUpdate { } } -impl TryFrom for views::AllowList { +impl TryFrom for system::AllowList { type Error = Error; fn try_from(db: AllowList) -> Result { diff --git a/nexus/db-model/src/audit_log.rs b/nexus/db-model/src/audit_log.rs index 17f18ad0066..f8a794ba12d 100644 --- a/nexus/db-model/src/audit_log.rs +++ b/nexus/db-model/src/audit_log.rs @@ -11,7 +11,7 @@ use chrono::{DateTime, Utc}; use diesel::prelude::*; use ipnetwork::IpNetwork; use nexus_db_schema::schema::{audit_log, audit_log_complete}; -use nexus_types::external_api::views; +use nexus_types::external_api::audit; use omicron_common::api::external::Error; use omicron_uuid_kinds::BuiltInUserUuid; use omicron_uuid_kinds::GenericUuid; @@ -112,15 +112,15 @@ impl_enum_type!( Spoof => b"spoof" ); -impl From for views::AuthMethod { +impl From for audit::AuthMethod { fn from(m: AuditLogAuthMethod) -> Self { match m { AuditLogAuthMethod::SessionCookie => { - views::AuthMethod::SessionCookie + audit::AuthMethod::SessionCookie } - AuditLogAuthMethod::AccessToken => views::AuthMethod::AccessToken, - AuditLogAuthMethod::ScimToken => views::AuthMethod::ScimToken, - AuditLogAuthMethod::Spoof => views::AuthMethod::Spoof, + AuditLogAuthMethod::AccessToken => audit::AuthMethod::AccessToken, + AuditLogAuthMethod::ScimToken => audit::AuthMethod::ScimToken, + AuditLogAuthMethod::Spoof => audit::AuthMethod::Spoof, } } } @@ -328,7 +328,7 @@ impl From for AuditLogCompletionUpdate { /// None of the error cases here should be possible given the DB constraints and /// the way we construct these rows when writing them to the database. -impl TryFrom for views::AuditLogEntry { +impl TryFrom for audit::AuditLogEntry { type Error = Error; fn try_from(entry: AuditLogEntry) -> Result { @@ -347,7 +347,7 @@ impl TryFrom for views::AuditLogEntry { "UserBuiltin actor missing actor_id", ) })?; - views::AuditLogEntryActor::UserBuiltin { + audit::AuditLogEntryActor::UserBuiltin { user_builtin_id: BuiltInUserUuid::from_untyped_uuid( user_builtin_id, ), @@ -362,7 +362,7 @@ impl TryFrom for views::AuditLogEntry { "SiloUser actor missing actor_silo_id", ) })?; - views::AuditLogEntryActor::SiloUser { + audit::AuditLogEntryActor::SiloUser { silo_user_id: SiloUserUuid::from_untyped_uuid( silo_user_id, ), @@ -375,10 +375,10 @@ impl TryFrom for views::AuditLogEntry { "Scim actor missing actor_silo_id", ) })?; - views::AuditLogEntryActor::Scim { silo_id } + audit::AuditLogEntryActor::Scim { silo_id } } AuditLogActorKind::Unauthenticated => { - views::AuditLogEntryActor::Unauthenticated + audit::AuditLogEntryActor::Unauthenticated } }, auth_method: entry.auth_method.map(Into::into), @@ -389,7 +389,7 @@ impl TryFrom for views::AuditLogEntry { .ok_or_else(|| Error::internal_error( "Audit log success result without http_status_code", ))?; - views::AuditLogEntryResult::Success { + audit::AuditLogEntryResult::Success { http_status_code: http_status_code.0, } } @@ -404,14 +404,14 @@ impl TryFrom for views::AuditLogEntry { .ok_or_else(|| Error::internal_error( "Audit log error result without http_status_code", ))?; - views::AuditLogEntryResult::Error { + audit::AuditLogEntryResult::Error { http_status_code: http_status_code.0, error_code: entry.error_code, error_message, } } AuditLogResultKind::Timeout => { - views::AuditLogEntryResult::Unknown + audit::AuditLogEntryResult::Unknown } }, credential_id: entry.credential_id, diff --git a/nexus/db-model/src/bgp.rs b/nexus/db-model/src/bgp.rs index e2a02308741..8f25d2f7045 100644 --- a/nexus/db-model/src/bgp.rs +++ b/nexus/db-model/src/bgp.rs @@ -8,7 +8,7 @@ use ipnetwork::IpNetwork; use nexus_db_schema::schema::{ bgp_announce_set, bgp_announcement, bgp_config, bgp_peer_view, }; -use nexus_types::external_api::params; +use nexus_types::external_api::networking; use nexus_types::identity::Resource; use omicron_common::api::external::Error; use omicron_common::api::{ @@ -64,7 +64,7 @@ impl TryFrom for external::BgpConfig { impl BgpConfig { pub fn from_config_create( - c: ¶ms::BgpConfigCreate, + c: &networking::BgpConfigCreate, bgp_announce_set_id: Uuid, ) -> BgpConfig { BgpConfig { @@ -101,8 +101,8 @@ pub struct BgpAnnounceSet { pub identity: BgpAnnounceSetIdentity, } -impl From for BgpAnnounceSet { - fn from(x: params::BgpAnnounceSetCreate) -> BgpAnnounceSet { +impl From for BgpAnnounceSet { + fn from(x: networking::BgpAnnounceSetCreate) -> BgpAnnounceSet { BgpAnnounceSet { identity: BgpAnnounceSetIdentity::new( Uuid::new_v4(), diff --git a/nexus/db-model/src/block_size.rs b/nexus/db-model/src/block_size.rs index 2bdbb456eb4..68286f1c525 100644 --- a/nexus/db-model/src/block_size.rs +++ b/nexus/db-model/src/block_size.rs @@ -3,7 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use super::impl_enum_type; -use nexus_types::external_api::params; +use nexus_types::external_api::disk; use omicron_common::api::external; use serde::{Deserialize, Serialize}; @@ -35,9 +35,9 @@ impl Into for BlockSize { } } -impl TryFrom for BlockSize { +impl TryFrom for BlockSize { type Error = anyhow::Error; - fn try_from(block_size: params::BlockSize) -> Result { + fn try_from(block_size: disk::BlockSize) -> Result { match block_size.0 { 512 => Ok(BlockSize::Traditional), 2048 => Ok(BlockSize::Iso), diff --git a/nexus/db-model/src/certificate.rs b/nexus/db-model/src/certificate.rs index 9870f014032..d0b3c3211c0 100644 --- a/nexus/db-model/src/certificate.rs +++ b/nexus/db-model/src/certificate.rs @@ -5,8 +5,7 @@ use super::ServiceKind; use db_macros::Resource; use nexus_db_schema::schema::certificate; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::certificate as certificate_types; use nexus_types::identity::Resource; use omicron_certificates::CertificateValidator; use omicron_common::api::external::Error; @@ -44,7 +43,7 @@ impl Certificate { silo_id: Uuid, id: Uuid, service: ServiceKind, - params: params::CertificateCreate, + params: certificate_types::CertificateCreate, possible_dns_names: &[String], ) -> Result { let validator = CertificateValidator::default(); @@ -64,7 +63,7 @@ impl Certificate { silo_id: Uuid, id: Uuid, service: ServiceKind, - params: params::CertificateCreate, + params: certificate_types::CertificateCreate, ) -> Self { Self { identity: CertificateIdentity::new(id, params.identity), @@ -76,7 +75,7 @@ impl Certificate { } } -impl TryFrom for views::Certificate { +impl TryFrom for certificate_types::Certificate { type Error = Error; fn try_from(cert: Certificate) -> Result { Ok(Self { diff --git a/nexus/db-model/src/console_session.rs b/nexus/db-model/src/console_session.rs index db0ef14d7de..8dd187f0caa 100644 --- a/nexus/db-model/src/console_session.rs +++ b/nexus/db-model/src/console_session.rs @@ -4,7 +4,7 @@ use chrono::{DateTime, Utc}; use nexus_db_schema::schema::console_session; -use nexus_types::external_api::views; +use nexus_types::external_api::device; use omicron_uuid_kinds::SiloUserKind; use omicron_uuid_kinds::SiloUserUuid; use omicron_uuid_kinds::{ConsoleSessionKind, ConsoleSessionUuid, GenericUuid}; @@ -54,7 +54,7 @@ impl ConsoleSession { } } -impl From for views::ConsoleSession { +impl From for device::ConsoleSession { fn from(session: ConsoleSession) -> Self { Self { id: session.id.into_untyped_uuid(), diff --git a/nexus/db-model/src/device_auth.rs b/nexus/db-model/src/device_auth.rs index 7cca0adb0ae..29ed2c04632 100644 --- a/nexus/db-model/src/device_auth.rs +++ b/nexus/db-model/src/device_auth.rs @@ -10,7 +10,7 @@ use nexus_db_schema::schema::{device_access_token, device_auth_request}; use chrono::{DateTime, Duration, Utc}; -use nexus_types::external_api::views; +use nexus_types::external_api::device; use omicron_uuid_kinds::{ AccessTokenKind, GenericUuid, SiloUserKind, SiloUserUuid, TypedUuid, }; @@ -47,9 +47,9 @@ impl DeviceAuthRequest { self, tls: bool, host: &str, - ) -> views::DeviceAuthResponse { + ) -> device::DeviceAuthResponse { let scheme = if tls { "https" } else { "http" }; - views::DeviceAuthResponse { + device::DeviceAuthResponse { verification_uri: format!("{scheme}://{host}/device/verify"), user_code: self.user_code, device_code: self.device_code, @@ -181,18 +181,18 @@ impl DeviceAccessToken { } } -impl From for views::DeviceAccessTokenGrant { +impl From for device::DeviceAccessTokenGrant { fn from(access_token: DeviceAccessToken) -> Self { Self { access_token: format!("oxide-token-{}", access_token.token), - token_type: views::DeviceAccessTokenType::Bearer, + token_type: device::DeviceAccessTokenType::Bearer, token_id: access_token.id.into_untyped_uuid(), time_expires: access_token.time_expires, } } } -impl From for views::DeviceAccessToken { +impl From for device::DeviceAccessToken { fn from(access_token: DeviceAccessToken) -> Self { Self { id: access_token.id.into_untyped_uuid(), diff --git a/nexus/db-model/src/disk.rs b/nexus/db-model/src/disk.rs index 4271e007b03..7cea6d4a504 100644 --- a/nexus/db-model/src/disk.rs +++ b/nexus/db-model/src/disk.rs @@ -11,7 +11,7 @@ use crate::unsigned::SqlU8; use chrono::{DateTime, Utc}; use db_macros::Resource; use nexus_db_schema::schema::disk; -use nexus_types::external_api::params; +use nexus_types::external_api::disk as disk_types; use omicron_common::api::external; use omicron_common::api::internal; use serde::{Deserialize, Serialize}; @@ -85,7 +85,7 @@ impl Disk { pub fn new( disk_id: Uuid, project_id: Uuid, - params: ¶ms::DiskCreate, + params: &disk_types::DiskCreate, block_size: BlockSize, runtime_initial: DiskRuntimeState, disk_type: DiskType, diff --git a/nexus/db-model/src/disk_type_crucible.rs b/nexus/db-model/src/disk_type_crucible.rs index 1fe3fbf62f5..f2dfddcde44 100644 --- a/nexus/db-model/src/disk_type_crucible.rs +++ b/nexus/db-model/src/disk_type_crucible.rs @@ -4,7 +4,7 @@ use crate::typed_uuid::DbTypedUuid; use nexus_db_schema::schema::disk_type_crucible; -use nexus_types::external_api::params; +use nexus_types::external_api::disk; use omicron_uuid_kinds::VolumeKind; use omicron_uuid_kinds::VolumeUuid; use serde::{Deserialize, Serialize}; @@ -50,16 +50,16 @@ impl DiskTypeCrucible { pub fn new( disk_id: Uuid, volume_id: VolumeUuid, - disk_source: ¶ms::DiskSource, + disk_source: &disk::DiskSource, ) -> Self { let mut create_snapshot_id = None; let mut create_image_id = None; let read_only = match disk_source { - ¶ms::DiskSource::Snapshot { snapshot_id, read_only } => { + &disk::DiskSource::Snapshot { snapshot_id, read_only } => { create_snapshot_id = Some(snapshot_id); read_only } - ¶ms::DiskSource::Image { image_id, read_only } => { + &disk::DiskSource::Image { image_id, read_only } => { // XXX further enum here for different image types? create_image_id = Some(image_id); read_only diff --git a/nexus/db-model/src/external_ip.rs b/nexus/db-model/src/external_ip.rs index 00a91029e69..7f86a221009 100644 --- a/nexus/db-model/src/external_ip.rs +++ b/nexus/db-model/src/external_ip.rs @@ -20,11 +20,9 @@ use nexus_db_schema::schema::floating_ip; use nexus_types::deployment::OmicronZoneExternalFloatingIp; use nexus_types::deployment::OmicronZoneExternalIp; use nexus_types::deployment::OmicronZoneExternalSnatIp; -use nexus_types::external_api::params; -use nexus_types::external_api::shared; -use nexus_types::external_api::shared::ProbeExternalIp; -use nexus_types::external_api::shared::ProbeExternalIpKind; -use nexus_types::external_api::views; +use nexus_types::external_api::external_ip as external_ip_types; +use nexus_types::external_api::floating_ip as floating_ip_types; +use nexus_types::external_api::probe::{ProbeExternalIp, ProbeExternalIpKind}; use nexus_types::inventory::SourceNatConfigGeneric; use omicron_common::api::external::Error; use omicron_common::api::external::IdentityMetadata; @@ -504,13 +502,13 @@ impl IpKind { } } -impl TryFrom for shared::IpKind { +impl TryFrom for external_ip_types::IpKind { type Error = Error; fn try_from(kind: IpKind) -> Result { match kind { - IpKind::Ephemeral => Ok(shared::IpKind::Ephemeral), - IpKind::Floating => Ok(shared::IpKind::Floating), + IpKind::Ephemeral => Ok(external_ip_types::IpKind::Ephemeral), + IpKind::Floating => Ok(external_ip_types::IpKind::Floating), _ => Err(Error::internal_error( "SNAT IP addresses should not be exposed in the API", )), @@ -518,7 +516,7 @@ impl TryFrom for shared::IpKind { } } -impl TryFrom for views::ExternalIp { +impl TryFrom for external_ip_types::ExternalIp { type Error = Error; fn try_from(ip: ExternalIp) -> Result { @@ -528,17 +526,21 @@ impl TryFrom for views::ExternalIp { )); } match ip.kind { - IpKind::Floating => Ok(views::ExternalIp::Floating(ip.try_into()?)), - IpKind::Ephemeral => Ok(views::ExternalIp::Ephemeral { + IpKind::Floating => { + Ok(external_ip_types::ExternalIp::Floating(ip.try_into()?)) + } + IpKind::Ephemeral => Ok(external_ip_types::ExternalIp::Ephemeral { ip: ip.ip.ip(), ip_pool_id: ip.ip_pool_id, }), - IpKind::SNat => Ok(views::ExternalIp::SNat(views::SNatIp { - ip: ip.ip.ip(), - first_port: u16::from(ip.first_port), - last_port: u16::from(ip.last_port), - ip_pool_id: ip.ip_pool_id, - })), + IpKind::SNat => Ok(external_ip_types::ExternalIp::SNat( + external_ip_types::SNatIp { + ip: ip.ip.ip(), + first_port: u16::from(ip.first_port), + last_port: u16::from(ip.last_port), + ip_pool_id: ip.ip_pool_id, + }, + )), } } } @@ -591,7 +593,7 @@ impl TryFrom for FloatingIp { } } -impl TryFrom for views::FloatingIp { +impl TryFrom for floating_ip_types::FloatingIp { type Error = Error; fn try_from(ip: ExternalIp) -> Result { @@ -599,7 +601,7 @@ impl TryFrom for views::FloatingIp { } } -impl From for views::FloatingIp { +impl From for floating_ip_types::FloatingIp { fn from(ip: FloatingIp) -> Self { let identity = IdentityMetadata { id: ip.identity.id, @@ -609,7 +611,7 @@ impl From for views::FloatingIp { time_modified: ip.identity.time_modified, }; - views::FloatingIp { + floating_ip_types::FloatingIp { ip: ip.ip.ip(), ip_pool_id: ip.ip_pool_id, identity, @@ -627,8 +629,8 @@ pub struct FloatingIpUpdate { pub time_modified: DateTime, } -impl From for FloatingIpUpdate { - fn from(params: params::FloatingIpUpdate) -> Self { +impl From for FloatingIpUpdate { + fn from(params: floating_ip_types::FloatingIpUpdate) -> Self { Self { name: params.identity.name.map(Name), description: params.identity.description, diff --git a/nexus/db-model/src/external_subnet.rs b/nexus/db-model/src/external_subnet.rs index 074d509fa36..37156ef50bd 100644 --- a/nexus/db-model/src/external_subnet.rs +++ b/nexus/db-model/src/external_subnet.rs @@ -22,8 +22,8 @@ use nexus_db_schema::schema::external_subnet; use nexus_db_schema::schema::subnet_pool; use nexus_db_schema::schema::subnet_pool_member; use nexus_db_schema::schema::subnet_pool_silo_link; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::external_subnet as external_subnet_types; +use nexus_types::external_api::subnet_pool as subnet_pool_types; use nexus_types::identity::Resource as _; use omicron_common::api::external; use omicron_common::api::external::Error; @@ -75,7 +75,7 @@ impl SubnetPool { } } -impl From for views::SubnetPool { +impl From for subnet_pool_types::SubnetPool { fn from(value: SubnetPool) -> Self { Self { identity: value.identity(), ip_version: value.ip_version.into() } } @@ -89,8 +89,8 @@ pub struct SubnetPoolUpdate { pub time_modified: DateTime, } -impl From for SubnetPoolUpdate { - fn from(value: params::SubnetPoolUpdate) -> Self { +impl From for SubnetPoolUpdate { + fn from(value: subnet_pool_types::SubnetPoolUpdate) -> Self { Self { name: value.identity.name.map(Into::into), description: value.identity.description, @@ -116,7 +116,7 @@ pub struct SubnetPoolMember { rcgen: Generation, } -impl From for views::SubnetPoolMember { +impl From for subnet_pool_types::SubnetPoolMember { fn from(value: SubnetPoolMember) -> Self { Self { id: value.id.into_untyped_uuid(), @@ -131,7 +131,7 @@ impl From for views::SubnetPoolMember { impl SubnetPoolMember { pub fn new( - params: ¶ms::SubnetPoolMemberAdd, + params: &subnet_pool_types::SubnetPoolMemberAdd, pool_id: SubnetPoolUuid, ) -> Result { // Require that the subnet is actually a network, i.e., @@ -236,7 +236,7 @@ pub struct ExternalSubnet { pub instance_id: Option>, } -impl From for views::ExternalSubnet { +impl From for external_subnet_types::ExternalSubnet { fn from(value: ExternalSubnet) -> Self { Self { identity: value.identity(), @@ -259,8 +259,10 @@ pub struct ExternalSubnetUpdate { pub time_modified: DateTime, } -impl From for ExternalSubnetUpdate { - fn from(value: params::ExternalSubnetUpdate) -> Self { +impl From + for ExternalSubnetUpdate +{ + fn from(value: external_subnet_types::ExternalSubnetUpdate) -> Self { Self { name: value.identity.name.map(Into::into), description: value.identity.description, @@ -280,7 +282,7 @@ pub struct SubnetPoolSiloLink { pub is_default: bool, } -impl From for views::SubnetPoolSiloLink { +impl From for subnet_pool_types::SubnetPoolSiloLink { fn from(value: SubnetPoolSiloLink) -> Self { Self { subnet_pool_id: value.subnet_pool_id.into_untyped_uuid(), diff --git a/nexus/db-model/src/identity_provider.rs b/nexus/db-model/src/identity_provider.rs index 080ba503981..c2c5f93b8cd 100644 --- a/nexus/db-model/src/identity_provider.rs +++ b/nexus/db-model/src/identity_provider.rs @@ -7,7 +7,7 @@ use db_macros::Resource; use nexus_db_schema::schema::{identity_provider, saml_identity_provider}; use nexus_types::identity::Resource; -use nexus_types::external_api::views; +use nexus_types::external_api::identity_provider as idp_types; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -21,10 +21,10 @@ impl_enum_type!( Saml => b"saml" ); -impl From for views::IdentityProviderType { +impl From for idp_types::IdentityProviderType { fn from(idp_type: IdentityProviderType) -> Self { match idp_type { - IdentityProviderType::Saml => views::IdentityProviderType::Saml, + IdentityProviderType::Saml => idp_types::IdentityProviderType::Saml, } } } @@ -40,7 +40,7 @@ pub struct IdentityProvider { pub provider_type: IdentityProviderType, } -impl From for views::IdentityProvider { +impl From for idp_types::IdentityProvider { fn from(idp: IdentityProvider) -> Self { Self { identity: idp.identity(), @@ -84,7 +84,7 @@ pub struct SamlIdentityProvider { pub group_attribute_name: Option, } -impl From for views::SamlIdentityProvider { +impl From for idp_types::SamlIdentityProvider { fn from(saml_idp: SamlIdentityProvider) -> Self { Self { identity: saml_idp.identity(), diff --git a/nexus/db-model/src/image.rs b/nexus/db-model/src/image.rs index f62ec89eb3a..0c02d2f5238 100644 --- a/nexus/db-model/src/image.rs +++ b/nexus/db-model/src/image.rs @@ -12,7 +12,7 @@ use super::{BlockSize, ByteCount, Digest}; use crate::typed_uuid::DbTypedUuid; use db_macros::Resource; use nexus_db_schema::schema::{image, project_image, silo_image}; -use nexus_types::external_api::views; +use nexus_types::external_api::image as image_types; use nexus_types::identity::Resource; use omicron_common::api::external::Error; use omicron_uuid_kinds::VolumeKind; @@ -218,7 +218,7 @@ impl From for Image { } } -impl From for views::Image { +impl From for image_types::Image { fn from(image: Image) -> Self { Self { identity: image.identity(), diff --git a/nexus/db-model/src/instance.rs b/nexus/db-model/src/instance.rs index b56cb41fa1d..4c59da0951a 100644 --- a/nexus/db-model/src/instance.rs +++ b/nexus/db-model/src/instance.rs @@ -17,7 +17,7 @@ use diesel::pg; use diesel::prelude::*; use diesel::sql_types::{Bool, Nullable}; use nexus_db_schema::schema::{disk, external_ip, external_subnet, instance}; -use nexus_types::external_api::params; +use nexus_types::external_api::instance as instance_types; use omicron_uuid_kinds::{GenericUuid, InstanceUuid}; use serde::Deserialize; use serde::Serialize; @@ -112,7 +112,7 @@ impl Instance { pub fn new( instance_id: InstanceUuid, project_id: Uuid, - params: ¶ms::InstanceCreate, + params: &instance_types::InstanceCreate, ) -> Self { let identity = InstanceIdentity::new( instance_id.into_untyped_uuid(), diff --git a/nexus/db-model/src/internet_gateway.rs b/nexus/db-model/src/internet_gateway.rs index 59b77f6768a..b4eae2bd00f 100644 --- a/nexus/db-model/src/internet_gateway.rs +++ b/nexus/db-model/src/internet_gateway.rs @@ -5,7 +5,7 @@ use ipnetwork::IpNetwork; use nexus_db_schema::schema::{ internet_gateway, internet_gateway_ip_address, internet_gateway_ip_pool, }; -use nexus_types::external_api::{params, views}; +use nexus_types::external_api::internet_gateway as api; use nexus_types::identity::Resource; use omicron_common::api::external::IdentityMetadataCreateParams; use uuid::Uuid; @@ -25,7 +25,7 @@ impl InternetGateway { pub fn new( gateway_id: Uuid, vpc_id: Uuid, - params: params::InternetGatewayCreate, + params: api::InternetGatewayCreate, ) -> Self { let identity = InternetGatewayIdentity::new(gateway_id, params.identity); @@ -33,7 +33,7 @@ impl InternetGateway { } } -impl From for views::InternetGateway { +impl From for api::InternetGateway { fn from(value: InternetGateway) -> Self { Self { identity: value.identity(), vpc_id: value.vpc_id } } @@ -61,7 +61,7 @@ impl InternetGatewayIpPool { } } -impl From for views::InternetGatewayIpPool { +impl From for api::InternetGatewayIpPool { fn from(value: InternetGatewayIpPool) -> Self { Self { identity: value.identity(), @@ -85,7 +85,7 @@ impl InternetGatewayIpAddress { pub fn new( pool_id: Uuid, internet_gateway_id: Uuid, - params: params::InternetGatewayIpAddressCreate, + params: api::InternetGatewayIpAddressCreate, ) -> Self { let identity = InternetGatewayIpAddressIdentity::new(pool_id, params.identity); @@ -97,7 +97,7 @@ impl InternetGatewayIpAddress { } } -impl From for views::InternetGatewayIpAddress { +impl From for api::InternetGatewayIpAddress { fn from(value: InternetGatewayIpAddress) -> Self { Self { identity: value.identity(), diff --git a/nexus/db-model/src/ip_pool.rs b/nexus/db-model/src/ip_pool.rs index 233edf802ed..70c4b6a852a 100644 --- a/nexus/db-model/src/ip_pool.rs +++ b/nexus/db-model/src/ip_pool.rs @@ -15,9 +15,7 @@ use ipnetwork::IpNetwork; use nexus_db_schema::schema::ip_pool; use nexus_db_schema::schema::ip_pool_range; use nexus_db_schema::schema::ip_pool_resource; -use nexus_types::external_api::params; -use nexus_types::external_api::shared; -use nexus_types::external_api::views; +use nexus_types::external_api::ip_pool as ip_pool_types; use nexus_types::identity::Resource; use omicron_common::api::external; use std::net::IpAddr; @@ -111,16 +109,16 @@ impl ::std::fmt::Display for IpVersion { } } -impl From for IpVersion { - fn from(value: shared::IpVersion) -> Self { +impl From for IpVersion { + fn from(value: ip_pool_types::IpVersion) -> Self { match value { - shared::IpVersion::V4 => Self::V4, - shared::IpVersion::V6 => Self::V6, + ip_pool_types::IpVersion::V4 => Self::V4, + ip_pool_types::IpVersion::V6 => Self::V6, } } } -impl From for shared::IpVersion { +impl From for ip_pool_types::IpVersion { fn from(value: IpVersion) -> Self { match value { IpVersion::V4 => Self::V4, @@ -129,16 +127,16 @@ impl From for shared::IpVersion { } } -impl From for IpPoolType { - fn from(value: shared::IpPoolType) -> Self { +impl From for IpPoolType { + fn from(value: ip_pool_types::IpPoolType) -> Self { match value { - shared::IpPoolType::Unicast => Self::Unicast, - shared::IpPoolType::Multicast => Self::Multicast, + ip_pool_types::IpPoolType::Unicast => Self::Unicast, + ip_pool_types::IpPoolType::Multicast => Self::Multicast, } } } -impl From for shared::IpPoolType { +impl From for ip_pool_types::IpPoolType { fn from(value: IpPoolType) -> Self { match value { IpPoolType::Unicast => Self::Unicast, @@ -227,7 +225,7 @@ impl IpPool { } } -impl From for views::IpPool { +impl From for ip_pool_types::IpPool { fn from(pool: IpPool) -> Self { let identity = pool.identity(); let pool_type = pool.pool_type; @@ -252,8 +250,8 @@ pub struct IpPoolUpdate { pub time_modified: DateTime, } -impl From for IpPoolUpdate { - fn from(params: params::IpPoolUpdate) -> Self { +impl From for IpPoolUpdate { + fn from(params: ip_pool_types::IpPoolUpdate) -> Self { Self { name: params.identity.name.map(|n| n.into()), description: params.identity.description, @@ -318,7 +316,7 @@ pub struct IncompleteIpPoolResource { pub is_default: bool, } -impl From for views::IpPoolSiloLink { +impl From for ip_pool_types::IpPoolSiloLink { fn from(assoc: IpPoolResource) -> Self { Self { ip_pool_id: assoc.ip_pool_id, @@ -348,7 +346,7 @@ pub struct IpPoolRange { } impl IpPoolRange { - pub fn new(range: &shared::IpRange, ip_pool_id: Uuid) -> Self { + pub fn new(range: &ip_pool_types::IpRange, ip_pool_id: Uuid) -> Self { let now = Utc::now(); let first_address = range.first_address(); let last_address = range.last_address(); @@ -371,11 +369,11 @@ impl IpPoolRange { } } -impl TryFrom for views::IpPoolRange { +impl TryFrom for ip_pool_types::IpPoolRange { type Error = external::Error; fn try_from(range: IpPoolRange) -> Result { - let ip_range = shared::IpRange::try_from(&range).map_err(|e| { + let ip_range = ip_pool_types::IpRange::try_from(&range).map_err(|e| { external::Error::internal_error(&format!( "Invalid IP range in database (id={}, pool={}, first={}, last={}): {e:#}", range.id, range.ip_pool_id, @@ -392,20 +390,20 @@ impl TryFrom for views::IpPoolRange { } } -impl TryFrom<&IpPoolRange> for shared::IpRange { +impl TryFrom<&IpPoolRange> for ip_pool_types::IpRange { type Error = IpRangeConversionError; fn try_from(range: &IpPoolRange) -> Result { match (range.first_address.ip(), range.last_address.ip()) { (IpAddr::V4(first), IpAddr::V4(last)) => { - shared::IpRange::try_from((first, last)).map_err(|e| { + ip_pool_types::IpRange::try_from((first, last)).map_err(|e| { IpRangeConversionError::InvalidRange { msg: format!("Invalid IPv4 range: {e:#}",), } }) } (IpAddr::V6(first), IpAddr::V6(last)) => { - shared::IpRange::try_from((first, last)).map_err(|e| { + ip_pool_types::IpRange::try_from((first, last)).map_err(|e| { IpRangeConversionError::InvalidRange { msg: format!("Invalid IPv6 range: {e:#}"), } diff --git a/nexus/db-model/src/lib.rs b/nexus/db-model/src/lib.rs index b4d5e54c7dd..49c45acfa01 100644 --- a/nexus/db-model/src/lib.rs +++ b/nexus/db-model/src/lib.rs @@ -465,9 +465,7 @@ pub trait DatabaseString: Sized { } use anyhow::anyhow; -use nexus_types::external_api::shared::FleetRole; -use nexus_types::external_api::shared::ProjectRole; -use nexus_types::external_api::shared::SiloRole; +use nexus_types::external_api::policy::{FleetRole, ProjectRole, SiloRole}; use std::borrow::Cow; impl DatabaseString for FleetRole { diff --git a/nexus/db-model/src/multicast_group.rs b/nexus/db-model/src/multicast_group.rs index 8af9c7adc2f..a6bd415138a 100644 --- a/nexus/db-model/src/multicast_group.rs +++ b/nexus/db-model/src/multicast_group.rs @@ -91,7 +91,7 @@ use db_macros::Resource; use nexus_db_schema::schema::{ multicast_group, multicast_group_member, underlay_multicast_group, }; -use nexus_types::external_api::views; +use nexus_types::external_api::multicast as multicast_types; use omicron_common::api::external::{self, IdentityMetadata}; use omicron_uuid_kinds::SledKind; @@ -298,11 +298,11 @@ pub struct MulticastGroupMember { // Conversions to external API views -impl TryFrom for views::MulticastGroupMember { +impl TryFrom for multicast_types::MulticastGroupMember { type Error = external::Error; fn try_from(member: MulticastGroupMember) -> Result { - Ok(views::MulticastGroupMember { + Ok(multicast_types::MulticastGroupMember { identity: IdentityMetadata { id: member.id, name: format!("member-{}", member.id).parse().map_err(|e| { diff --git a/nexus/db-model/src/network_interface.rs b/nexus/db-model/src/network_interface.rs index d1f0c35c31d..64caafeafda 100644 --- a/nexus/db-model/src/network_interface.rs +++ b/nexus/db-model/src/network_interface.rs @@ -17,8 +17,8 @@ use itertools::Itertools; use nexus_db_schema::schema::instance_network_interface; use nexus_db_schema::schema::network_interface; use nexus_db_schema::schema::service_network_interface; -use nexus_types::external_api::params; -use nexus_types::external_api::params::PrivateIpStackCreate; +use nexus_types::external_api::instance::InstanceNetworkInterfaceUpdate; +use nexus_types::external_api::instance::PrivateIpStackCreate; use nexus_types::identity::Resource; use omicron_common::api::external::Error; use omicron_common::api::external::PrivateIpStack; @@ -628,8 +628,8 @@ impl TryFrom for external::InstanceNetworkInterface { } } -impl From for NetworkInterfaceUpdate { - fn from(params: params::InstanceNetworkInterfaceUpdate) -> Self { +impl From for NetworkInterfaceUpdate { + fn from(params: InstanceNetworkInterfaceUpdate) -> Self { let primary = if params.primary { Some(true) } else { None }; let (transit_ips_v4, transit_ips_v6): (Vec<_>, Vec<_>) = params.transit_ips.into_iter().partition_map(|net| match net { diff --git a/nexus/db-model/src/physical_disk.rs b/nexus/db-model/src/physical_disk.rs index 36369591ceb..5f52f151a99 100644 --- a/nexus/db-model/src/physical_disk.rs +++ b/nexus/db-model/src/physical_disk.rs @@ -10,7 +10,8 @@ use crate::collection::DatastoreCollectionConfig; use chrono::{DateTime, Utc}; use db_macros::Asset; use nexus_db_schema::schema::{physical_disk, zpool}; -use nexus_types::{external_api::views, identity::Asset}; +use nexus_types::external_api::physical_disk as physical_disk_types; +use nexus_types::identity::Asset; use omicron_uuid_kinds::PhysicalDiskKind as PhysicalDiskUuidKind; use omicron_uuid_kinds::PhysicalDiskUuid; use omicron_uuid_kinds::SledKind; @@ -73,7 +74,7 @@ impl PhysicalDisk { } } -impl From for views::PhysicalDisk { +impl From for physical_disk_types::PhysicalDisk { fn from(disk: PhysicalDisk) -> Self { Self { identity: disk.identity(), @@ -101,10 +102,7 @@ mod diesel_util { prelude::*, query_dsl::methods::FilterDsl, }; - use nexus_types::{ - deployment::DiskFilter, - external_api::views::{PhysicalDiskPolicy, PhysicalDiskState}, - }; + use nexus_types::deployment::DiskFilter; /// An extension trait to apply a [`DiskFilter`] to a Diesel expression. /// @@ -132,13 +130,9 @@ mod diesel_util { // These are only boxed for ease of reference above. let all_matching_policies: BoxedIterator< crate::PhysicalDiskPolicy, - > = Box::new( - PhysicalDiskPolicy::all_matching(filter).map(Into::into), - ); + > = Box::new(filter.all_matching_policies().map(Into::into)); let all_matching_states: BoxedIterator = - Box::new( - PhysicalDiskState::all_matching(filter).map(Into::into), - ); + Box::new(filter.all_matching_states().map(Into::into)); FilterDsl::filter( self, diff --git a/nexus/db-model/src/physical_disk_kind.rs b/nexus/db-model/src/physical_disk_kind.rs index 8b2317a163c..7cf0c487478 100644 --- a/nexus/db-model/src/physical_disk_kind.rs +++ b/nexus/db-model/src/physical_disk_kind.rs @@ -3,7 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use super::impl_enum_type; -use nexus_types::external_api; +use nexus_types::external_api::physical_disk; use serde::{Deserialize, Serialize}; impl_enum_type!( @@ -17,20 +17,20 @@ impl_enum_type!( U2 => b"u2" ); -impl From for PhysicalDiskKind { - fn from(k: external_api::params::PhysicalDiskKind) -> Self { +impl From for PhysicalDiskKind { + fn from(k: physical_disk::PhysicalDiskKind) -> Self { match k { - external_api::params::PhysicalDiskKind::M2 => PhysicalDiskKind::M2, - external_api::params::PhysicalDiskKind::U2 => PhysicalDiskKind::U2, + physical_disk::PhysicalDiskKind::M2 => PhysicalDiskKind::M2, + physical_disk::PhysicalDiskKind::U2 => PhysicalDiskKind::U2, } } } -impl From for external_api::params::PhysicalDiskKind { +impl From for physical_disk::PhysicalDiskKind { fn from(value: PhysicalDiskKind) -> Self { match value { - PhysicalDiskKind::M2 => external_api::params::PhysicalDiskKind::M2, - PhysicalDiskKind::U2 => external_api::params::PhysicalDiskKind::U2, + PhysicalDiskKind::M2 => physical_disk::PhysicalDiskKind::M2, + PhysicalDiskKind::U2 => physical_disk::PhysicalDiskKind::U2, } } } diff --git a/nexus/db-model/src/physical_disk_policy.rs b/nexus/db-model/src/physical_disk_policy.rs index 9d58dcb116d..4d0773f8978 100644 --- a/nexus/db-model/src/physical_disk_policy.rs +++ b/nexus/db-model/src/physical_disk_policy.rs @@ -14,7 +14,7 @@ //! as gone. use super::impl_enum_type; -use nexus_types::external_api::views; +use nexus_types::external_api::physical_disk; use serde::{Deserialize, Serialize}; impl_enum_type!( @@ -28,24 +28,28 @@ impl_enum_type!( Expunged => b"expunged" ); -impl From for views::PhysicalDiskPolicy { +impl From for physical_disk::PhysicalDiskPolicy { fn from(policy: PhysicalDiskPolicy) -> Self { match policy { PhysicalDiskPolicy::InService => { - views::PhysicalDiskPolicy::InService + physical_disk::PhysicalDiskPolicy::InService + } + PhysicalDiskPolicy::Expunged => { + physical_disk::PhysicalDiskPolicy::Expunged } - PhysicalDiskPolicy::Expunged => views::PhysicalDiskPolicy::Expunged, } } } -impl From for PhysicalDiskPolicy { - fn from(policy: views::PhysicalDiskPolicy) -> Self { +impl From for PhysicalDiskPolicy { + fn from(policy: physical_disk::PhysicalDiskPolicy) -> Self { match policy { - views::PhysicalDiskPolicy::InService => { + physical_disk::PhysicalDiskPolicy::InService => { PhysicalDiskPolicy::InService } - views::PhysicalDiskPolicy::Expunged => PhysicalDiskPolicy::Expunged, + physical_disk::PhysicalDiskPolicy::Expunged => { + PhysicalDiskPolicy::Expunged + } } } } diff --git a/nexus/db-model/src/physical_disk_state.rs b/nexus/db-model/src/physical_disk_state.rs index 5a3d9593cd2..18be1ac2177 100644 --- a/nexus/db-model/src/physical_disk_state.rs +++ b/nexus/db-model/src/physical_disk_state.rs @@ -5,7 +5,7 @@ //! Database representation of a physical disk's state as understood by Nexus. use super::impl_enum_type; -use nexus_types::external_api::views; +use nexus_types::external_api::physical_disk; use serde::{Deserialize, Serialize}; use std::fmt; use strum::EnumIter; @@ -24,26 +24,30 @@ impl_enum_type!( impl fmt::Display for PhysicalDiskState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Forward to the canonical implementation in nexus-types. - views::PhysicalDiskState::from(*self).fmt(f) + physical_disk::PhysicalDiskState::from(*self).fmt(f) } } -impl From for views::PhysicalDiskState { +impl From for physical_disk::PhysicalDiskState { fn from(state: PhysicalDiskState) -> Self { match state { - PhysicalDiskState::Active => views::PhysicalDiskState::Active, + PhysicalDiskState::Active => { + physical_disk::PhysicalDiskState::Active + } PhysicalDiskState::Decommissioned => { - views::PhysicalDiskState::Decommissioned + physical_disk::PhysicalDiskState::Decommissioned } } } } -impl From for PhysicalDiskState { - fn from(state: views::PhysicalDiskState) -> Self { +impl From for PhysicalDiskState { + fn from(state: physical_disk::PhysicalDiskState) -> Self { match state { - views::PhysicalDiskState::Active => PhysicalDiskState::Active, - views::PhysicalDiskState::Decommissioned => { + physical_disk::PhysicalDiskState::Active => { + PhysicalDiskState::Active + } + physical_disk::PhysicalDiskState::Decommissioned => { PhysicalDiskState::Decommissioned } } diff --git a/nexus/db-model/src/probe.rs b/nexus/db-model/src/probe.rs index d041da3cce5..ce3081538fe 100644 --- a/nexus/db-model/src/probe.rs +++ b/nexus/db-model/src/probe.rs @@ -5,7 +5,7 @@ use super::DbTypedUuid; use db_macros::Resource; use nexus_db_schema::schema::probe; -use nexus_types::external_api::params; +use nexus_types::external_api::probe as probe_types; use nexus_types::identity::Resource; use omicron_common::api::external; use omicron_common::api::external::IdentityMetadataCreateParams; @@ -35,7 +35,7 @@ pub struct Probe { } impl Probe { - pub fn from_create(p: ¶ms::ProbeCreate, project_id: Uuid) -> Self { + pub fn from_create(p: &probe_types::ProbeCreate, project_id: Uuid) -> Self { Self { identity: ProbeIdentity::new( Uuid::new_v4(), diff --git a/nexus/db-model/src/project.rs b/nexus/db-model/src/project.rs index 4194de051c0..725b7cba5b6 100644 --- a/nexus/db-model/src/project.rs +++ b/nexus/db-model/src/project.rs @@ -14,8 +14,7 @@ use nexus_db_schema::schema::{ affinity_group, anti_affinity_group, disk, image, instance, project, snapshot, vpc, }; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::project as project_types; use nexus_types::identity::Resource; use serde::Deserialize; use serde::Serialize; @@ -44,7 +43,7 @@ pub struct Project { impl Project { /// Creates a new database Project object. - pub fn new(silo_id: Uuid, params: params::ProjectCreate) -> Self { + pub fn new(silo_id: Uuid, params: project_types::ProjectCreate) -> Self { Self::new_with_id(Uuid::new_v4(), silo_id, params) } @@ -52,7 +51,7 @@ impl Project { pub fn new_with_id( id: Uuid, silo_id: Uuid, - params: params::ProjectCreate, + params: project_types::ProjectCreate, ) -> Self { Self { identity: ProjectIdentity::new(id, params.identity), @@ -62,7 +61,7 @@ impl Project { } } -impl From for views::Project { +impl From for project_types::Project { fn from(project: Project) -> Self { Self { identity: project.identity() } } @@ -126,8 +125,8 @@ pub struct ProjectUpdate { pub time_modified: DateTime, } -impl From for ProjectUpdate { - fn from(params: params::ProjectUpdate) -> Self { +impl From for ProjectUpdate { + fn from(params: project_types::ProjectUpdate) -> Self { Self { name: params.identity.name.map(Name), description: params.identity.description, diff --git a/nexus/db-model/src/quota.rs b/nexus/db-model/src/quota.rs index e6b31e62ae0..2e3f8b39864 100644 --- a/nexus/db-model/src/quota.rs +++ b/nexus/db-model/src/quota.rs @@ -1,7 +1,7 @@ use super::ByteCount; use chrono::{DateTime, Utc}; use nexus_db_schema::schema::silo_quotas; -use nexus_types::external_api::{params, views}; +use nexus_types::external_api::silo; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -51,7 +51,7 @@ impl SiloQuotas { } pub fn arbitrarily_high_default(silo_id: Uuid) -> Self { - let count = params::SiloQuotasCreate::arbitrarily_high_default(); + let count = silo::SiloQuotasCreate::arbitrarily_high_default(); Self::new( silo_id, count.cpus, @@ -61,11 +61,11 @@ impl SiloQuotas { } } -impl From for views::SiloQuotas { +impl From for silo::SiloQuotas { fn from(silo_quotas: SiloQuotas) -> Self { Self { silo_id: silo_quotas.silo_id, - limits: views::VirtualResourceCounts { + limits: silo::VirtualResourceCounts { cpus: silo_quotas.cpus, memory: silo_quotas.memory.into(), storage: silo_quotas.storage.into(), @@ -86,8 +86,8 @@ pub struct SiloQuotasUpdate { pub time_modified: DateTime, } -impl From for SiloQuotasUpdate { - fn from(params: params::SiloQuotasUpdate) -> Self { +impl From for SiloQuotasUpdate { + fn from(params: silo::SiloQuotasUpdate) -> Self { Self { cpus: params.cpus, memory: params.memory.map(|f| f.into()), diff --git a/nexus/db-model/src/rack.rs b/nexus/db-model/src/rack.rs index ac7062b3c41..2d51e5d4692 100644 --- a/nexus/db-model/src/rack.rs +++ b/nexus/db-model/src/rack.rs @@ -5,7 +5,8 @@ use db_macros::Asset; use ipnetwork::IpNetwork; use nexus_db_schema::schema::rack; -use nexus_types::{external_api::views, identity::Asset}; +use nexus_types::external_api::rack as rack_types; +use nexus_types::identity::Asset; use uuid::Uuid; /// Information about a local rack. @@ -30,7 +31,7 @@ impl Rack { } } -impl From for views::Rack { +impl From for rack_types::Rack { fn from(rack: Rack) -> Self { Self { identity: rack.identity() } } diff --git a/nexus/db-model/src/role_assignment.rs b/nexus/db-model/src/role_assignment.rs index 8407e3769a7..60c1e48c71e 100644 --- a/nexus/db-model/src/role_assignment.rs +++ b/nexus/db-model/src/role_assignment.rs @@ -5,7 +5,7 @@ use super::{DatabaseString, impl_enum_type}; use anyhow::anyhow; use nexus_db_schema::schema::role_assignment; -use nexus_types::external_api::shared; +use nexus_types::external_api::policy; use omicron_common::api::external::Error; use omicron_uuid_kinds::BuiltInUserUuid; use omicron_uuid_kinds::GenericUuid; @@ -33,16 +33,16 @@ impl_enum_type!( SiloGroup => b"silo_group" ); -impl From for IdentityType { - fn from(other: shared::IdentityType) -> Self { +impl From for IdentityType { + fn from(other: policy::IdentityType) -> Self { match other { - shared::IdentityType::SiloUser => IdentityType::SiloUser, - shared::IdentityType::SiloGroup => IdentityType::SiloGroup, + policy::IdentityType::SiloUser => IdentityType::SiloUser, + policy::IdentityType::SiloGroup => IdentityType::SiloGroup, } } } -impl TryFrom for shared::IdentityType { +impl TryFrom for policy::IdentityType { type Error = anyhow::Error; fn try_from(other: IdentityType) -> Result { @@ -50,8 +50,8 @@ impl TryFrom for shared::IdentityType { IdentityType::UserBuiltin => { Err(anyhow!("unsupported db identity type: {:?}", other)) } - IdentityType::SiloUser => Ok(shared::IdentityType::SiloUser), - IdentityType::SiloGroup => Ok(shared::IdentityType::SiloGroup), + IdentityType::SiloUser => Ok(policy::IdentityType::SiloUser), + IdentityType::SiloGroup => Ok(policy::IdentityType::SiloGroup), } } } @@ -119,7 +119,7 @@ impl RoleAssignment { } impl TryFrom - for shared::RoleAssignment + for policy::RoleAssignment where AllowedRoles: DatabaseString, { @@ -127,7 +127,7 @@ where fn try_from(role_asgn: RoleAssignment) -> Result { Ok(Self { - identity_type: shared::IdentityType::try_from( + identity_type: policy::IdentityType::try_from( role_asgn.identity_type, ) .map_err(|error| { @@ -203,7 +203,7 @@ mod tests { }; let error = - >::try_from(bad_input_role) + >::try_from(bad_input_role) .expect_err("unexpectedly succeeding parsing database role"); println!("error: {:#}", error); if let Error::InternalError { internal_message } = error { @@ -221,7 +221,7 @@ mod tests { } let error = - >::try_from(bad_input_idtype) + >::try_from(bad_input_idtype) .expect_err("unexpectedly succeeding parsing database role"); println!("error: {:#}", error); if let Error::InternalError { internal_message } = error { @@ -238,10 +238,10 @@ mod tests { ); } - let success = >::try_from(ok_input) + let success = >::try_from(ok_input) .expect("parsing valid role assignment from database"); println!("success: {:?}", success); - assert_eq!(success.identity_type, shared::IdentityType::SiloUser); + assert_eq!(success.identity_type, policy::IdentityType::SiloUser); assert_eq!(success.identity_id, identity_id); assert_eq!(success.role_name, DummyRoles::Bogus); } diff --git a/nexus/db-model/src/scim_client_bearer_token.rs b/nexus/db-model/src/scim_client_bearer_token.rs index 09c3c4c27b8..5299ad96f19 100644 --- a/nexus/db-model/src/scim_client_bearer_token.rs +++ b/nexus/db-model/src/scim_client_bearer_token.rs @@ -5,7 +5,7 @@ use chrono::DateTime; use chrono::Utc; use nexus_db_schema::schema::scim_client_bearer_token; -use nexus_types::external_api::views; +use nexus_types::external_api::scim; use uuid::Uuid; /// A SCIM client sends requests to a SCIM provider (in this case, Nexus) using @@ -31,9 +31,9 @@ impl ScimClientBearerToken { } } -impl From for views::ScimClientBearerToken { - fn from(t: ScimClientBearerToken) -> views::ScimClientBearerToken { - views::ScimClientBearerToken { +impl From for scim::ScimClientBearerToken { + fn from(t: ScimClientBearerToken) -> scim::ScimClientBearerToken { + scim::ScimClientBearerToken { id: t.id, time_created: t.time_created, time_expires: t.time_expires, @@ -41,9 +41,9 @@ impl From for views::ScimClientBearerToken { } } -impl From for views::ScimClientBearerTokenValue { - fn from(t: ScimClientBearerToken) -> views::ScimClientBearerTokenValue { - views::ScimClientBearerTokenValue { +impl From for scim::ScimClientBearerTokenValue { + fn from(t: ScimClientBearerToken) -> scim::ScimClientBearerTokenValue { + scim::ScimClientBearerTokenValue { id: t.id, time_created: t.time_created, time_expires: t.time_expires, diff --git a/nexus/db-model/src/service_kind.rs b/nexus/db-model/src/service_kind.rs index 610497e4798..310c1ee8d9a 100644 --- a/nexus/db-model/src/service_kind.rs +++ b/nexus/db-model/src/service_kind.rs @@ -3,8 +3,8 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use super::impl_enum_type; -use external_api::shared::ServiceUsingCertificate; -use nexus_types::{external_api, internal_api}; +use nexus_types::external_api::certificate::ServiceUsingCertificate; +use nexus_types::internal_api; use serde::{Deserialize, Serialize}; use strum::EnumIter; diff --git a/nexus/db-model/src/silo.rs b/nexus/db-model/src/silo.rs index e285c8f01e6..98d0b4c08d9 100644 --- a/nexus/db-model/src/silo.rs +++ b/nexus/db-model/src/silo.rs @@ -7,11 +7,11 @@ use crate::collection::DatastoreCollectionConfig; use crate::{DatabaseString, Image, impl_enum_type}; use db_macros::Resource; use nexus_db_schema::schema::{image, project, silo}; -use nexus_types::external_api::shared::{ - FleetRole, SiloIdentityMode, SiloRole, +use nexus_types::external_api::policy::{FleetRole, SiloRole}; +use nexus_types::external_api::silo::{ + self as silo_types, AuthenticationMode as SharedAuthenticationMode, + SiloIdentityMode, UserProvisionType as SharedUserProvisionType, }; -use nexus_types::external_api::views; -use nexus_types::external_api::{params, shared}; use nexus_types::identity::Resource; use omicron_common::api::external::Error; use std::collections::BTreeMap; @@ -29,16 +29,16 @@ impl_enum_type!( Saml => b"saml" ); -impl From for AuthenticationMode { - fn from(params: shared::AuthenticationMode) -> Self { +impl From for AuthenticationMode { + fn from(params: SharedAuthenticationMode) -> Self { match params { - shared::AuthenticationMode::Local => AuthenticationMode::Local, - shared::AuthenticationMode::Saml => AuthenticationMode::Saml, + SharedAuthenticationMode::Local => AuthenticationMode::Local, + SharedAuthenticationMode::Saml => AuthenticationMode::Saml, } } } -impl From for shared::AuthenticationMode { +impl From for SharedAuthenticationMode { fn from(model: AuthenticationMode) -> Self { match model { AuthenticationMode::Local => Self::Local, @@ -59,17 +59,17 @@ impl_enum_type!( Scim => b"scim" ); -impl From for UserProvisionType { - fn from(params: shared::UserProvisionType) -> Self { +impl From for UserProvisionType { + fn from(params: SharedUserProvisionType) -> Self { match params { - shared::UserProvisionType::ApiOnly => UserProvisionType::ApiOnly, - shared::UserProvisionType::Jit => UserProvisionType::Jit, - shared::UserProvisionType::Scim => UserProvisionType::Scim, + SharedUserProvisionType::ApiOnly => UserProvisionType::ApiOnly, + SharedUserProvisionType::Jit => UserProvisionType::Jit, + SharedUserProvisionType::Scim => UserProvisionType::Scim, } } } -impl From for shared::UserProvisionType { +impl From for SharedUserProvisionType { fn from(model: UserProvisionType) -> Self { match model { UserProvisionType::ApiOnly => Self::ApiOnly, @@ -169,13 +169,13 @@ impl<'a> From<&'a BTreeMap>> impl Silo { /// Creates a new database Silo object. - pub fn new(params: params::SiloCreate) -> Result { + pub fn new(params: silo_types::SiloCreate) -> Result { Self::new_with_id(Uuid::new_v4(), params) } pub fn new_with_id( id: Uuid, - params: params::SiloCreate, + params: silo_types::SiloCreate, ) -> Result { let mapped_fleet_roles = serde_json::to_value( &SerializedMappedFleetRoles::from(¶ms.mapped_fleet_roles).0, @@ -215,7 +215,7 @@ impl Silo { } } -impl TryFrom for views::Silo { +impl TryFrom for silo_types::Silo { type Error = Error; fn try_from(silo: Silo) -> Result { let authn_mode = &silo.authentication_mode; diff --git a/nexus/db-model/src/silo_auth_settings.rs b/nexus/db-model/src/silo_auth_settings.rs index 7fce1be0646..9a9d9032ddf 100644 --- a/nexus/db-model/src/silo_auth_settings.rs +++ b/nexus/db-model/src/silo_auth_settings.rs @@ -1,7 +1,7 @@ use crate::SqlU32; use chrono::{DateTime, Utc}; use nexus_db_schema::schema::silo_auth_settings; -use nexus_types::external_api::{params, views}; +use nexus_types::external_api::silo; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -37,7 +37,7 @@ impl SiloAuthSettings { } } -impl From for views::SiloAuthSettings { +impl From for silo::SiloAuthSettings { fn from(silo_auth_settings: SiloAuthSettings) -> Self { Self { silo_id: silo_auth_settings.silo_id, @@ -58,8 +58,8 @@ pub struct SiloAuthSettingsUpdate { pub time_modified: DateTime, } -impl From for SiloAuthSettingsUpdate { - fn from(params: params::SiloAuthSettingsUpdate) -> Self { +impl From for SiloAuthSettingsUpdate { + fn from(params: silo::SiloAuthSettingsUpdate) -> Self { Self { device_token_max_ttl_seconds: Some( params.device_token_max_ttl_seconds.map(|ttl| ttl.get().into()), diff --git a/nexus/db-model/src/sled.rs b/nexus/db-model/src/sled.rs index 690a2bd3950..47631f3ac09 100644 --- a/nexus/db-model/src/sled.rs +++ b/nexus/db-model/src/sled.rs @@ -6,7 +6,6 @@ use super::{ByteCount, Generation, SledState, SqlU16, SqlU32}; use crate::DbTypedUuid; use crate::collection::DatastoreCollectionConfig; use crate::ipv6; -use crate::sled::shared::Baseboard; use crate::sled_cpu_family::SledCpuFamily; use crate::sled_policy::DbSledPolicy; use chrono::{DateTime, Utc}; @@ -14,7 +13,7 @@ use db_macros::Asset; use nexus_db_schema::schema::{physical_disk, sled, zpool}; use nexus_types::deployment::execution; use nexus_types::{ - external_api::{shared, views}, + external_api::{hardware, sled as sled_types}, identity::Asset, internal_api::params, }; @@ -126,9 +125,9 @@ impl Sled { &self.part_number } - /// The policy here is the `views::SledPolicy` because we expect external + /// The policy here is the `sled_types::SledPolicy` because we expect external /// users to always use that. - pub fn policy(&self) -> views::SledPolicy { + pub fn policy(&self) -> sled_types::SledPolicy { self.policy.into() } @@ -142,12 +141,12 @@ impl Sled { } } -impl From for views::Sled { +impl From for sled_types::Sled { fn from(sled: Sled) -> Self { Self { identity: sled.identity(), rack_id: sled.rack_id, - baseboard: shared::Baseboard { + baseboard: hardware::Baseboard { serial: sled.serial_number, part: sled.part_number, revision: *sled.revision, @@ -191,7 +190,7 @@ impl From for params::SledAgentInfo { sa_address: sled.address(), repo_depot_port: sled.repo_depot_port.into(), role, - baseboard: Baseboard { + baseboard: hardware::Baseboard { serial: sled.serial_number.clone(), part: sled.part_number.clone(), revision: *sled.revision, @@ -422,10 +421,7 @@ mod diesel_util { query_dsl::methods::FilterDsl, }; use nexus_db_schema::schema::sled::{sled_policy, sled_state}; - use nexus_types::{ - deployment::SledFilter, - external_api::views::{SledPolicy, SledState}, - }; + use nexus_types::deployment::SledFilter; /// An extension trait to apply a [`SledFilter`] to a Diesel expression. /// @@ -451,11 +447,10 @@ mod diesel_util { use nexus_db_schema::schema::sled::dsl as sled_dsl; // These are only boxed for ease of reference above. - let all_matching_policies: BoxedIterator = Box::new( - SledPolicy::all_matching(filter).map(to_db_sled_policy), - ); + let all_matching_policies: BoxedIterator = + Box::new(filter.all_matching_policies().map(to_db_sled_policy)); let all_matching_states: BoxedIterator = - Box::new(SledState::all_matching(filter).map(Into::into)); + Box::new(filter.all_matching_states().map(Into::into)); FilterDsl::filter( self, diff --git a/nexus/db-model/src/sled_instance.rs b/nexus/db-model/src/sled_instance.rs index 28fdad234e3..a184eee391a 100644 --- a/nexus/db-model/src/sled_instance.rs +++ b/nexus/db-model/src/sled_instance.rs @@ -2,7 +2,7 @@ use crate::Name; use crate::VmmState; use db_macros::Asset; use nexus_db_schema::schema::sled_instance; -use nexus_types::external_api::views; +use nexus_types::external_api::sled; use nexus_types::identity::Asset; use serde::Deserialize; use serde::Serialize; @@ -26,7 +26,7 @@ pub struct SledInstance { pub memory: i64, } -impl From for views::SledInstance { +impl From for sled::SledInstance { fn from(sled_instance: SledInstance) -> Self { Self { identity: sled_instance.identity(), diff --git a/nexus/db-model/src/sled_policy.rs b/nexus/db-model/src/sled_policy.rs index 8fe649e88f9..d38f256d450 100644 --- a/nexus/db-model/src/sled_policy.rs +++ b/nexus/db-model/src/sled_policy.rs @@ -14,7 +14,7 @@ //! as gone. use super::impl_enum_type; -use nexus_types::external_api::views::{SledPolicy, SledProvisionPolicy}; +use nexus_types::external_api::sled::{SledPolicy, SledProvisionPolicy}; use serde::{Deserialize, Serialize}; impl_enum_type!( diff --git a/nexus/db-model/src/sled_state.rs b/nexus/db-model/src/sled_state.rs index 448f161f9eb..8b7e14447eb 100644 --- a/nexus/db-model/src/sled_state.rs +++ b/nexus/db-model/src/sled_state.rs @@ -14,7 +14,7 @@ //! as gone. use super::impl_enum_type; -use nexus_types::external_api::views; +use nexus_types::external_api::sled; use serde::{Deserialize, Serialize}; use std::fmt; use strum::EnumIter; @@ -33,24 +33,24 @@ impl_enum_type!( impl fmt::Display for SledState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Forward to the canonical implementation in nexus-types. - views::SledState::from(*self).fmt(f) + sled::SledState::from(*self).fmt(f) } } -impl From for views::SledState { +impl From for sled::SledState { fn from(state: SledState) -> Self { match state { - SledState::Active => views::SledState::Active, - SledState::Decommissioned => views::SledState::Decommissioned, + SledState::Active => sled::SledState::Active, + SledState::Decommissioned => sled::SledState::Decommissioned, } } } -impl From for SledState { - fn from(state: views::SledState) -> Self { +impl From for SledState { + fn from(state: sled::SledState) -> Self { match state { - views::SledState::Active => SledState::Active, - views::SledState::Decommissioned => SledState::Decommissioned, + sled::SledState::Active => SledState::Active, + sled::SledState::Decommissioned => SledState::Decommissioned, } } } diff --git a/nexus/db-model/src/snapshot.rs b/nexus/db-model/src/snapshot.rs index ec73aef2be6..f2902a74343 100644 --- a/nexus/db-model/src/snapshot.rs +++ b/nexus/db-model/src/snapshot.rs @@ -8,7 +8,7 @@ use crate::Generation; use crate::typed_uuid::DbTypedUuid; use db_macros::Resource; use nexus_db_schema::schema::snapshot; -use nexus_types::external_api::views; +use nexus_types::external_api::snapshot as snapshot_types; use nexus_types::identity::Resource; use omicron_uuid_kinds::VolumeKind; use omicron_uuid_kinds::VolumeUuid; @@ -60,7 +60,7 @@ pub struct Snapshot { pub size: ByteCount, } -impl From for views::Snapshot { +impl From for snapshot_types::Snapshot { fn from(snapshot: Snapshot) -> Self { Self { identity: snapshot.identity(), @@ -72,7 +72,7 @@ impl From for views::Snapshot { } } -impl From for views::SnapshotState { +impl From for snapshot_types::SnapshotState { fn from(state: SnapshotState) -> Self { match state { SnapshotState::Creating => Self::Creating, diff --git a/nexus/db-model/src/ssh_key.rs b/nexus/db-model/src/ssh_key.rs index 869d21fb8ec..af0a1c6db0f 100644 --- a/nexus/db-model/src/ssh_key.rs +++ b/nexus/db-model/src/ssh_key.rs @@ -5,8 +5,7 @@ use db_macros::Resource; use nexus_db_schema::schema::instance_ssh_key; use nexus_db_schema::schema::ssh_key; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::ssh_key as ssh_key_types; use nexus_types::identity::Resource; use omicron_uuid_kinds::SiloUserKind; use omicron_uuid_kinds::SiloUserUuid; @@ -29,7 +28,7 @@ pub struct SshKey { impl SshKey { pub fn new( silo_user_id: SiloUserUuid, - params: params::SshKeyCreate, + params: ssh_key_types::SshKeyCreate, ) -> Self { Self::new_with_id(Uuid::new_v4(), silo_user_id, params) } @@ -37,7 +36,7 @@ impl SshKey { pub fn new_with_id( id: Uuid, silo_user_id: SiloUserUuid, - params: params::SshKeyCreate, + params: ssh_key_types::SshKeyCreate, ) -> Self { Self { identity: SshKeyIdentity::new(id, params.identity), @@ -51,7 +50,7 @@ impl SshKey { } } -impl From for views::SshKey { +impl From for ssh_key_types::SshKey { fn from(ssh_key: SshKey) -> Self { Self { identity: ssh_key.identity(), diff --git a/nexus/db-model/src/support_bundle.rs b/nexus/db-model/src/support_bundle.rs index b9dc9f55b17..81e0d7e2fc7 100644 --- a/nexus/db-model/src/support_bundle.rs +++ b/nexus/db-model/src/support_bundle.rs @@ -7,8 +7,7 @@ use crate::typed_uuid::DbTypedUuid; use nexus_db_schema::schema::support_bundle; use chrono::{DateTime, Utc}; -use nexus_types::external_api::shared::SupportBundleInfo as SupportBundleView; -use nexus_types::external_api::shared::SupportBundleState as SupportBundleStateView; +use nexus_types::external_api::support_bundle as support_bundle_types; use omicron_uuid_kinds::DatasetKind; use omicron_uuid_kinds::DatasetUuid; use omicron_uuid_kinds::OmicronZoneKind; @@ -53,14 +52,14 @@ impl SupportBundleState { } } -impl From for SupportBundleStateView { +impl From for support_bundle_types::SupportBundleState { fn from(state: SupportBundleState) -> Self { use SupportBundleState::*; match state { - Collecting => SupportBundleStateView::Collecting, - Active => SupportBundleStateView::Active, - Destroying => SupportBundleStateView::Destroying, + Collecting => support_bundle_types::SupportBundleState::Collecting, + Active => support_bundle_types::SupportBundleState::Active, + Destroying => support_bundle_types::SupportBundleState::Destroying, // The distinction between "failing" and "failed" should not be // visible to end-users. This is internal book-keeping to decide // whether or not the bundle record can be safely deleted. @@ -69,8 +68,8 @@ impl From for SupportBundleStateView { // If a user requests that we delete a bundle in these states: // - "Failing" bundles will become "Destroying" // - "Failed" bundles can be deleted immediately - Failing => SupportBundleStateView::Failed, - Failed => SupportBundleStateView::Failed, + Failing => support_bundle_types::SupportBundleState::Failed, + Failed => support_bundle_types::SupportBundleState::Failed, } } } @@ -124,7 +123,7 @@ impl SupportBundle { } } -impl From for SupportBundleView { +impl From for support_bundle_types::SupportBundleInfo { fn from(bundle: SupportBundle) -> Self { Self { id: bundle.id.into(), diff --git a/nexus/db-model/src/switch.rs b/nexus/db-model/src/switch.rs index f64de4422ab..d0b1753444a 100644 --- a/nexus/db-model/src/switch.rs +++ b/nexus/db-model/src/switch.rs @@ -7,7 +7,9 @@ use crate::SqlU32; use chrono::{DateTime, Utc}; use db_macros::Asset; use nexus_db_schema::schema::switch; -use nexus_types::{external_api::shared, external_api::views, identity::Asset}; +use nexus_types::external_api::hardware as hardware_types; +use nexus_types::external_api::switch as switch_types; +use nexus_types::identity::Asset; use uuid::Uuid; /// Baseboard information about a switch. @@ -57,12 +59,12 @@ impl Switch { } } -impl From for views::Switch { +impl From for switch_types::Switch { fn from(switch: Switch) -> Self { Self { identity: switch.identity(), rack_id: switch.rack_id, - baseboard: shared::Baseboard { + baseboard: hardware_types::Baseboard { serial: switch.serial_number, part: switch.part_number, revision: *switch.revision, diff --git a/nexus/db-model/src/switch_interface.rs b/nexus/db-model/src/switch_interface.rs index ee6e40912ce..14424978879 100644 --- a/nexus/db-model/src/switch_interface.rs +++ b/nexus/db-model/src/switch_interface.rs @@ -6,7 +6,7 @@ use crate::impl_enum_type; use db_macros::Asset; use ipnetwork::IpNetwork; use nexus_db_schema::schema::{loopback_address, switch_vlan_interface_config}; -use nexus_types::external_api::params; +use nexus_types::external_api::networking as networking_types; use nexus_types::identity::Asset; use omicron_common::api::external; use omicron_uuid_kinds::LoopbackAddressKind; @@ -34,14 +34,16 @@ impl_enum_type!( Loopback => b"loopback" ); -impl From for DbSwitchInterfaceKind { - fn from(k: params::SwitchInterfaceKind) -> Self { +impl From for DbSwitchInterfaceKind { + fn from(k: networking_types::SwitchInterfaceKind) -> Self { match k { - params::SwitchInterfaceKind::Primary => { + networking_types::SwitchInterfaceKind::Primary => { DbSwitchInterfaceKind::Primary } - params::SwitchInterfaceKind::Vlan(_) => DbSwitchInterfaceKind::Vlan, - params::SwitchInterfaceKind::Loopback => { + networking_types::SwitchInterfaceKind::Vlan(_) => { + DbSwitchInterfaceKind::Vlan + } + networking_types::SwitchInterfaceKind::Loopback => { DbSwitchInterfaceKind::Loopback } } diff --git a/nexus/db-model/src/switch_port.rs b/nexus/db-model/src/switch_port.rs index c9e7852ce93..0c98b14304f 100644 --- a/nexus/db-model/src/switch_port.rs +++ b/nexus/db-model/src/switch_port.rs @@ -19,7 +19,7 @@ use nexus_db_schema::schema::{ switch_port_settings_port_config, switch_port_settings_route_config, tx_eq_config, }; -use nexus_types::external_api::params; +use nexus_types::external_api::networking as networking_types; use nexus_types::identity::Resource; use omicron_common::api::external; use omicron_common::api::external::{BgpPeer, ImportExportPolicy}; @@ -47,23 +47,23 @@ impl_enum_type!( Sfp28x4 => b"Sfp28x4" ); -impl PartialEq for SwitchPortGeometry { - fn eq(&self, other: ¶ms::SwitchPortGeometry) -> bool { +impl PartialEq for SwitchPortGeometry { + fn eq(&self, other: &networking_types::SwitchPortGeometry) -> bool { match self { Self::Qsfp28x1 => { - return matches!(other, params::SwitchPortGeometry::Qsfp28x1); + matches!(other, networking_types::SwitchPortGeometry::Qsfp28x1) } Self::Qsfp28x2 => { - return matches!(other, params::SwitchPortGeometry::Qsfp28x2); + matches!(other, networking_types::SwitchPortGeometry::Qsfp28x2) } Self::Sfp28x4 => { - return matches!(other, params::SwitchPortGeometry::Sfp28x4); + matches!(other, networking_types::SwitchPortGeometry::Sfp28x4) } } } } -impl PartialEq for params::SwitchPortGeometry { +impl PartialEq for networking_types::SwitchPortGeometry { fn eq(&self, other: &SwitchPortGeometry) -> bool { other.eq(self) } @@ -193,16 +193,18 @@ impl From for external::LinkSpeed { } } -impl From for SwitchPortGeometry { - fn from(g: params::SwitchPortGeometry) -> Self { +impl From for SwitchPortGeometry { + fn from(g: networking_types::SwitchPortGeometry) -> Self { match g { - params::SwitchPortGeometry::Qsfp28x1 => { + networking_types::SwitchPortGeometry::Qsfp28x1 => { SwitchPortGeometry::Qsfp28x1 } - params::SwitchPortGeometry::Qsfp28x2 => { + networking_types::SwitchPortGeometry::Qsfp28x2 => { SwitchPortGeometry::Qsfp28x2 } - params::SwitchPortGeometry::Sfp28x4 => SwitchPortGeometry::Sfp28x4, + networking_types::SwitchPortGeometry::Sfp28x4 => { + SwitchPortGeometry::Sfp28x4 + } } } } diff --git a/nexus/db-model/src/tuf_repo.rs b/nexus/db-model/src/tuf_repo.rs index d898e4ca006..6bfe74e9a30 100644 --- a/nexus/db-model/src/tuf_repo.rs +++ b/nexus/db-model/src/tuf_repo.rs @@ -11,8 +11,8 @@ use diesel::{deserialize::FromSql, serialize::ToSql}; use nexus_db_schema::schema::{ tuf_artifact, tuf_repo, tuf_repo_artifact, tuf_trust_root, }; -use nexus_types::external_api::shared::TufSignedRootRole; -use nexus_types::external_api::views::{self, TufRepoUploadStatus}; +use nexus_types::external_api::update as update_types; +use nexus_types::external_api::update::TufSignedRootRole; use omicron_common::{api::external, update::ArtifactId}; use omicron_uuid_kinds::GenericUuid; use omicron_uuid_kinds::TufArtifactKind; @@ -150,9 +150,9 @@ impl TufRepo { } } -impl From for views::TufRepo { - fn from(repo: TufRepo) -> views::TufRepo { - views::TufRepo { +impl From for update_types::TufRepo { + fn from(repo: TufRepo) -> update_types::TufRepo { + update_types::TufRepo { hash: repo.sha256.into(), system_version: repo.system_version.into(), file_name: repo.file_name, @@ -379,9 +379,9 @@ impl TufTrustRoot { } } -impl From for views::UpdatesTrustRoot { - fn from(trust_root: TufTrustRoot) -> views::UpdatesTrustRoot { - views::UpdatesTrustRoot { +impl From for update_types::UpdatesTrustRoot { + fn from(trust_root: TufTrustRoot) -> update_types::UpdatesTrustRoot { + update_types::UpdatesTrustRoot { id: trust_root.id.into_untyped_uuid(), time_created: trust_root.time_created, root_role: trust_root.root_role.0, @@ -428,12 +428,12 @@ impl FromSql for DbTufSignedRootRole { /// The return value of the tuf repo insert function pub struct TufRepoUpload { pub recorded: TufRepoDescription, - pub status: TufRepoUploadStatus, + pub status: update_types::TufRepoUploadStatus, } -impl From for views::TufRepoUpload { +impl From for update_types::TufRepoUpload { fn from(upload: TufRepoUpload) -> Self { - views::TufRepoUpload { + update_types::TufRepoUpload { repo: upload.recorded.repo.into(), status: upload.status, } diff --git a/nexus/db-model/src/user_builtin.rs b/nexus/db-model/src/user_builtin.rs index fc1e266719b..4f1a53994f2 100644 --- a/nexus/db-model/src/user_builtin.rs +++ b/nexus/db-model/src/user_builtin.rs @@ -4,8 +4,7 @@ use db_macros::Resource; use nexus_db_schema::schema::user_builtin; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::user; use nexus_types::identity::Resource; use omicron_uuid_kinds::BuiltInUserUuid; @@ -20,12 +19,12 @@ pub struct UserBuiltin { impl UserBuiltin { /// Creates a new database UserBuiltin object. - pub fn new(id: BuiltInUserUuid, params: params::UserBuiltinCreate) -> Self { + pub fn new(id: BuiltInUserUuid, params: user::UserBuiltinCreate) -> Self { Self { identity: UserBuiltinIdentity::new(id, params.identity) } } } -impl From for views::UserBuiltin { +impl From for user::UserBuiltin { fn from(user: UserBuiltin) -> Self { Self { identity: user.identity() } } diff --git a/nexus/db-model/src/utilization.rs b/nexus/db-model/src/utilization.rs index 3db4bee80ff..9eafa55359d 100644 --- a/nexus/db-model/src/utilization.rs +++ b/nexus/db-model/src/utilization.rs @@ -1,7 +1,8 @@ use crate::ByteCount; use crate::Name; use nexus_db_schema::schema::silo_utilization; -use nexus_types::external_api::views; +use nexus_types::external_api::ip_pool; +use nexus_types::external_api::silo; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -21,17 +22,17 @@ pub struct SiloUtilization { pub storage_provisioned: ByteCount, } -impl From for views::SiloUtilization { +impl From for silo::SiloUtilization { fn from(silo_utilization: SiloUtilization) -> Self { Self { silo_id: silo_utilization.silo_id, silo_name: silo_utilization.silo_name.into(), - provisioned: views::VirtualResourceCounts { + provisioned: silo::VirtualResourceCounts { cpus: silo_utilization.cpus_provisioned, memory: silo_utilization.memory_provisioned.into(), storage: silo_utilization.storage_provisioned.into(), }, - allocated: views::VirtualResourceCounts { + allocated: silo::VirtualResourceCounts { cpus: silo_utilization.cpus_allocated, memory: silo_utilization.memory_allocated.into(), storage: silo_utilization.storage_allocated.into(), @@ -40,15 +41,15 @@ impl From for views::SiloUtilization { } } -impl From for views::Utilization { +impl From for silo::Utilization { fn from(silo_utilization: SiloUtilization) -> Self { Self { - provisioned: views::VirtualResourceCounts { + provisioned: silo::VirtualResourceCounts { cpus: silo_utilization.cpus_provisioned, memory: silo_utilization.memory_provisioned.into(), storage: silo_utilization.storage_provisioned.into(), }, - capacity: views::VirtualResourceCounts { + capacity: silo::VirtualResourceCounts { cpus: silo_utilization.cpus_allocated, memory: silo_utilization.memory_allocated.into(), storage: silo_utilization.storage_allocated.into(), @@ -64,7 +65,7 @@ pub struct IpPoolUtilization { pub capacity: f64, } -impl From for views::IpPoolUtilization { +impl From for ip_pool::IpPoolUtilization { fn from(util: IpPoolUtilization) -> Self { Self { remaining: util.remaining, capacity: util.capacity } } diff --git a/nexus/db-model/src/vpc.rs b/nexus/db-model/src/vpc.rs index 3c534a43cb1..0c5ef1eb927 100644 --- a/nexus/db-model/src/vpc.rs +++ b/nexus/db-model/src/vpc.rs @@ -10,8 +10,7 @@ use db_macros::Resource; use ipnetwork::IpNetwork; use nexus_db_schema::schema::{vpc, vpc_firewall_rule, vpc_subnet}; use nexus_defaults as defaults; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::vpc as vpc_types; use nexus_types::identity::Resource; use omicron_common::api::external; use omicron_common::api::external::Ipv6NetExt; @@ -48,7 +47,7 @@ pub struct Vpc { pub subnet_gen: Generation, } -impl From for views::Vpc { +impl From for vpc_types::Vpc { fn from(vpc: Vpc) -> Self { Self { identity: vpc.identity(), @@ -81,7 +80,7 @@ impl IncompleteVpc { vpc_id: Uuid, project_id: Uuid, system_router_id: Uuid, - params: params::VpcCreate, + params: vpc_types::VpcCreate, ) -> Result { let identity = VpcIdentity::new(vpc_id, params.identity); let ipv6_prefix = oxnet::IpNet::from(match params.ipv6_prefix { @@ -134,8 +133,8 @@ pub struct VpcUpdate { pub dns_name: Option, } -impl From for VpcUpdate { - fn from(params: params::VpcUpdate) -> Self { +impl From for VpcUpdate { + fn from(params: vpc_types::VpcUpdate) -> Self { Self { name: params.identity.name.map(Name), description: params.identity.description, diff --git a/nexus/db-model/src/vpc_route.rs b/nexus/db-model/src/vpc_route.rs index 6e1727b4502..675a502fd19 100644 --- a/nexus/db-model/src/vpc_route.rs +++ b/nexus/db-model/src/vpc_route.rs @@ -7,7 +7,7 @@ use chrono::{DateTime, Utc}; use db_macros::Resource; use diesel::sql_types; use nexus_db_schema::schema::router_route; -use nexus_types::external_api::params; +use nexus_types::external_api::vpc; use nexus_types::identity::Resource; use omicron_common::api::external; use std::borrow::Cow; @@ -92,7 +92,7 @@ impl RouterRoute { route_id: Uuid, vpc_router_id: Uuid, kind: external::RouterRouteKind, - params: params::RouterRouteCreate, + params: vpc::RouterRouteCreate, ) -> Self { let identity = RouterRouteIdentity::new(route_id, params.identity); Self { @@ -217,8 +217,8 @@ impl RouterRouteUpdate { } } -impl From for RouterRouteUpdate { - fn from(params: params::RouterRouteUpdate) -> Self { +impl From for RouterRouteUpdate { + fn from(params: vpc::RouterRouteUpdate) -> Self { Self { name: params.identity.name.map(Name), description: params.identity.description, diff --git a/nexus/db-model/src/vpc_router.rs b/nexus/db-model/src/vpc_router.rs index 24558b430a8..9bd8362662f 100644 --- a/nexus/db-model/src/vpc_router.rs +++ b/nexus/db-model/src/vpc_router.rs @@ -8,8 +8,7 @@ use crate::{DatastoreAttachTargetConfig, VpcSubnet}; use chrono::{DateTime, Utc}; use db_macros::Resource; use nexus_db_schema::schema::{router_route, vpc_router, vpc_subnet}; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::vpc; use nexus_types::identity::Resource; use uuid::Uuid; @@ -24,7 +23,7 @@ impl_enum_type!( Custom => b"custom" ); -impl From for views::VpcRouterKind { +impl From for vpc::VpcRouterKind { fn from(kind: VpcRouterKind) -> Self { match kind { VpcRouterKind::Custom => Self::Custom, @@ -50,7 +49,7 @@ impl VpcRouter { router_id: Uuid, vpc_id: Uuid, kind: VpcRouterKind, - params: params::VpcRouterCreate, + params: vpc::VpcRouterCreate, ) -> Self { let identity = VpcRouterIdentity::new(router_id, params.identity); Self { @@ -63,7 +62,7 @@ impl VpcRouter { } } -impl From for views::VpcRouter { +impl From for vpc::VpcRouter { fn from(router: VpcRouter) -> Self { Self { identity: router.identity(), @@ -88,8 +87,8 @@ pub struct VpcRouterUpdate { pub time_modified: DateTime, } -impl From for VpcRouterUpdate { - fn from(params: params::VpcRouterUpdate) -> Self { +impl From for VpcRouterUpdate { + fn from(params: vpc::VpcRouterUpdate) -> Self { Self { name: params.identity.name.map(Name), description: params.identity.description, diff --git a/nexus/db-model/src/vpc_subnet.rs b/nexus/db-model/src/vpc_subnet.rs index 0b63f158f9d..1e9c8b2c621 100644 --- a/nexus/db-model/src/vpc_subnet.rs +++ b/nexus/db-model/src/vpc_subnet.rs @@ -11,8 +11,7 @@ use db_macros::Resource; use nexus_config::NUM_INITIAL_RESERVED_IP_ADDRESSES; use nexus_db_schema::schema::network_interface; use nexus_db_schema::schema::vpc_subnet; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::vpc; use nexus_types::identity::Resource; use omicron_common::api::external; use serde::Deserialize; @@ -97,7 +96,7 @@ pub enum RequestAddressError { Broadcast, } -impl From for views::VpcSubnet { +impl From for vpc::VpcSubnet { fn from(subnet: VpcSubnet) -> Self { Self { identity: subnet.identity(), @@ -118,8 +117,8 @@ pub struct VpcSubnetUpdate { pub custom_router_id: Option>, } -impl From for VpcSubnetUpdate { - fn from(params: params::VpcSubnetUpdate) -> Self { +impl From for VpcSubnetUpdate { + fn from(params: vpc::VpcSubnetUpdate) -> Self { Self { name: params.identity.name.map(Name), description: params.identity.description, diff --git a/nexus/db-model/src/webhook_delivery.rs b/nexus/db-model/src/webhook_delivery.rs index 923eb86405d..66f567c3170 100644 --- a/nexus/db-model/src/webhook_delivery.rs +++ b/nexus/db-model/src/webhook_delivery.rs @@ -13,7 +13,7 @@ use crate::serde_time_delta::optional_time_delta; use crate::typed_uuid::DbTypedUuid; use chrono::{DateTime, TimeDelta, Utc}; use nexus_db_schema::schema::{webhook_delivery, webhook_delivery_attempt}; -use nexus_types::external_api::views; +use nexus_types::external_api::alert; use omicron_uuid_kinds::GenericUuid; use omicron_uuid_kinds::{ AlertKind, AlertReceiverKind, AlertReceiverUuid, AlertUuid, @@ -115,22 +115,22 @@ impl WebhookDelivery { &self, alert_class: AlertClass, attempts: &[WebhookDeliveryAttempt], - ) -> views::AlertDelivery { + ) -> alert::AlertDelivery { let mut attempts: Vec<_> = - attempts.iter().map(views::WebhookDeliveryAttempt::from).collect(); + attempts.iter().map(alert::WebhookDeliveryAttempt::from).collect(); // Make sure attempts are in order; each attempt entry also includes an // attempt number, which should be used authoritatively to determine the // ordering of attempts, but it seems nice to also sort the list, // because we can... attempts.sort_by_key(|a| a.attempt); - views::AlertDelivery { + alert::AlertDelivery { id: self.id.into_untyped_uuid(), receiver_id: self.rx_id.into(), alert_class: alert_class.as_str().to_owned(), alert_id: self.alert_id.into(), state: self.state.into(), trigger: self.triggered_by.into(), - attempts: views::AlertDeliveryAttempts::Webhook(attempts), + attempts: alert::AlertDeliveryAttempts::Webhook(attempts), time_started: self.time_created, } } @@ -174,15 +174,15 @@ pub struct WebhookDeliveryAttempt { } impl WebhookDeliveryAttempt { - fn response_view(&self) -> Option { - Some(views::WebhookDeliveryResponse { + fn response_view(&self) -> Option { + Some(alert::WebhookDeliveryResponse { status: self.response_status?.into(), duration_ms: self.response_duration?.num_milliseconds() as usize, }) } } -impl From<&'_ WebhookDeliveryAttempt> for views::WebhookDeliveryAttempt { +impl From<&'_ WebhookDeliveryAttempt> for alert::WebhookDeliveryAttempt { fn from(attempt: &WebhookDeliveryAttempt) -> Self { let response = attempt.response_view(); Self { diff --git a/nexus/db-model/src/webhook_delivery_attempt_result.rs b/nexus/db-model/src/webhook_delivery_attempt_result.rs index fd5c2221a42..b222616748d 100644 --- a/nexus/db-model/src/webhook_delivery_attempt_result.rs +++ b/nexus/db-model/src/webhook_delivery_attempt_result.rs @@ -3,7 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use super::impl_enum_type; -use nexus_types::external_api::views; +use nexus_types::external_api::alert; use serde::Deserialize; use serde::Serialize; use std::fmt; @@ -33,19 +33,19 @@ impl_enum_type!( impl WebhookDeliveryAttemptResult { pub fn is_failed(&self) -> bool { // Use canonical implementation from the API type. - views::WebhookDeliveryAttemptResult::from(*self).is_failed() + alert::WebhookDeliveryAttemptResult::from(*self).is_failed() } } impl fmt::Display for WebhookDeliveryAttemptResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Use canonical format from the API type. - views::WebhookDeliveryAttemptResult::from(*self).fmt(f) + alert::WebhookDeliveryAttemptResult::from(*self).fmt(f) } } impl From - for views::WebhookDeliveryAttemptResult + for alert::WebhookDeliveryAttemptResult { fn from(result: WebhookDeliveryAttemptResult) -> Self { match result { diff --git a/nexus/db-model/src/webhook_rx.rs b/nexus/db-model/src/webhook_rx.rs index 508431595fb..555829ecdb6 100644 --- a/nexus/db-model/src/webhook_rx.rs +++ b/nexus/db-model/src/webhook_rx.rs @@ -14,8 +14,7 @@ use db_macros::{Asset, Resource}; use nexus_db_schema::schema::{ alert_glob, alert_receiver, alert_subscription, webhook_secret, }; -use nexus_types::external_api::shared; -use nexus_types::external_api::views; +use nexus_types::external_api::alert; use nexus_types::identity::Resource; use omicron_common::api::external::Error; use omicron_uuid_kinds::{ @@ -33,15 +32,15 @@ pub struct WebhookReceiverConfig { pub subscriptions: Vec, } -impl TryFrom for views::WebhookReceiver { +impl TryFrom for alert::WebhookReceiver { type Error = Error; fn try_from( WebhookReceiverConfig { rx, secrets, subscriptions }: WebhookReceiverConfig, - ) -> Result { - let secrets = secrets.iter().map(views::WebhookSecret::from).collect(); + ) -> Result { + let secrets = secrets.iter().map(alert::WebhookSecret::from).collect(); let subscriptions = subscriptions .into_iter() - .map(shared::AlertSubscription::try_from) + .map(alert::AlertSubscription::try_from) .collect::, _>>()?; let endpoint = rx.endpoint.parse().map_err(|e| Error::InternalError { @@ -52,10 +51,10 @@ impl TryFrom for views::WebhookReceiver { rx.endpoint, ), })?; - Ok(views::WebhookReceiver { + Ok(alert::WebhookReceiver { identity: rx.identity(), subscriptions, - config: views::WebhookReceiverConfig { secrets, endpoint }, + config: alert::WebhookReceiverConfig { secrets, endpoint }, }) } } @@ -153,7 +152,7 @@ impl WebhookSecret { } } -impl From<&'_ WebhookSecret> for views::WebhookSecret { +impl From<&'_ WebhookSecret> for alert::WebhookSecret { fn from(secret: &WebhookSecret) -> Self { Self { id: secret.identity.id.into_untyped_uuid(), @@ -162,7 +161,7 @@ impl From<&'_ WebhookSecret> for views::WebhookSecret { } } -impl From for views::WebhookSecret { +impl From for alert::WebhookSecret { fn from(secret: WebhookSecret) -> Self { Self::from(&secret) } diff --git a/nexus/db-queries/src/db/datastore/address_lot.rs b/nexus/db-queries/src/db/datastore/address_lot.rs index 80c4032b030..03b322be662 100644 --- a/nexus/db-queries/src/db/datastore/address_lot.rs +++ b/nexus/db-queries/src/db/datastore/address_lot.rs @@ -20,7 +20,7 @@ use nexus_db_errors::ErrorHandler; use nexus_db_errors::OptionalError; use nexus_db_errors::TransactionError; use nexus_db_errors::public_error_from_diesel; -use nexus_types::external_api::params; +use nexus_types::external_api::networking; use nexus_types::identity::Resource; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ @@ -41,7 +41,7 @@ impl DataStore { pub async fn address_lot_create( &self, opctx: &OpContext, - params: ¶ms::AddressLotCreate, + params: &networking::AddressLotCreate, ) -> CreateResult { use nexus_db_schema::schema::address_lot::dsl as lot_dsl; use nexus_db_schema::schema::address_lot_block::dsl as block_dsl; diff --git a/nexus/db-queries/src/db/datastore/affinity.rs b/nexus/db-queries/src/db/datastore/affinity.rs index d9a8486e8c8..7689ec06e39 100644 --- a/nexus/db-queries/src/db/datastore/affinity.rs +++ b/nexus/db-queries/src/db/datastore/affinity.rs @@ -1234,7 +1234,7 @@ mod tests { use nexus_db_lookup::LookupPath; use nexus_db_model::Resources; use nexus_db_model::SledResourceVmm; - use nexus_types::external_api::params; + use nexus_types::external_api::affinity; use omicron_common::api::external::{ self, ByteCount, DataPageParams, IdentityMetadataCreateParams, SimpleIdentityOrName, @@ -1256,7 +1256,7 @@ mod tests { ) -> CreateResult { let group = AffinityGroup::new( authz_project.id(), - params::AffinityGroupCreate { + affinity::AffinityGroupCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: "".to_string(), @@ -1278,7 +1278,7 @@ mod tests { ) -> CreateResult { let group = AntiAffinityGroup::new( authz_project.id(), - params::AntiAffinityGroupCreate { + affinity::AntiAffinityGroupCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: "".to_string(), diff --git a/nexus/db-queries/src/db/datastore/alert_rx.rs b/nexus/db-queries/src/db/datastore/alert_rx.rs index 65e0207213e..38d14c5160d 100644 --- a/nexus/db-queries/src/db/datastore/alert_rx.rs +++ b/nexus/db-queries/src/db/datastore/alert_rx.rs @@ -44,7 +44,7 @@ use nexus_db_schema::schema::alert_subscription::dsl as subscription_dsl; use nexus_db_schema::schema::webhook_delivery::dsl as delivery_dsl; use nexus_db_schema::schema::webhook_delivery_attempt::dsl as delivery_attempt_dsl; use nexus_db_schema::schema::webhook_secret::dsl as secret_dsl; -use nexus_types::external_api::params; +use nexus_types::external_api::alert; use nexus_types::identity::Resource; use nexus_types::internal_api::background::AlertGlobStatus; use omicron_common::api::external::CreateResult; @@ -64,19 +64,15 @@ impl DataStore { pub async fn webhook_rx_create( &self, opctx: &OpContext, - params: params::WebhookCreate, + params: alert::WebhookCreate, ) -> CreateResult { // TODO(eliza): someday we gotta allow creating webhooks with more // restrictive permissions... opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; let conn = self.pool_connection_authorized(opctx).await?; - let params::WebhookCreate { - identity, - endpoint, - secrets, - subscriptions, - } = params; + let alert::WebhookCreate { identity, endpoint, secrets, subscriptions } = + params; let subscriptions = subscriptions .into_iter() @@ -342,7 +338,7 @@ impl DataStore { &self, opctx: &OpContext, authz_rx: &authz::AlertReceiver, - params: params::WebhookReceiverUpdate, + params: alert::WebhookReceiverUpdate, ) -> UpdateResult { opctx.authorize(authz::Action::Modify, authz_rx).await?; let conn = self.pool_connection_authorized(opctx).await?; @@ -1186,7 +1182,7 @@ mod test { datastore .webhook_rx_create( opctx, - params::WebhookCreate { + alert::WebhookCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: "it'sa webhook".to_string(), diff --git a/nexus/db-queries/src/db/datastore/bfd.rs b/nexus/db-queries/src/db/datastore/bfd.rs index ae24f3ce80b..a053371bb62 100644 --- a/nexus/db-queries/src/db/datastore/bfd.rs +++ b/nexus/db-queries/src/db/datastore/bfd.rs @@ -12,7 +12,7 @@ use nexus_db_errors::ErrorHandler; use nexus_db_errors::public_error_from_diesel; use nexus_db_model::BfdSession; use nexus_db_model::SqlU32; -use nexus_types::external_api::params; +use nexus_types::external_api::networking; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::{ CreateResult, DeleteResult, ListResultVec, @@ -38,7 +38,7 @@ impl DataStore { pub async fn bfd_session_create( &self, opctx: &OpContext, - config: ¶ms::BfdSessionEnable, + config: &networking::BfdSessionEnable, ) -> CreateResult { use nexus_db_schema::schema::bfd_session::dsl; let conn = self.pool_connection_authorized(opctx).await?; @@ -69,7 +69,7 @@ impl DataStore { pub async fn bfd_session_delete( &self, opctx: &OpContext, - config: ¶ms::BfdSessionDisable, + config: &networking::BfdSessionDisable, ) -> DeleteResult { use nexus_db_schema::schema::bfd_session::dsl; let conn = self.pool_connection_authorized(opctx).await?; diff --git a/nexus/db-queries/src/db/datastore/bgp.rs b/nexus/db-queries/src/db/datastore/bgp.rs index c2413b9981a..4a070986366 100644 --- a/nexus/db-queries/src/db/datastore/bgp.rs +++ b/nexus/db-queries/src/db/datastore/bgp.rs @@ -16,7 +16,7 @@ use nexus_db_model::{ BgpPeerView, SwitchPortBgpPeerConfigAllowExport, SwitchPortBgpPeerConfigAllowImport, SwitchPortBgpPeerConfigCommunity, }; -use nexus_types::external_api::params; +use nexus_types::external_api::networking; use nexus_types::identity::Resource; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ @@ -30,7 +30,7 @@ impl DataStore { pub async fn bgp_config_create( &self, opctx: &OpContext, - config: ¶ms::BgpConfigCreate, + config: &networking::BgpConfigCreate, ) -> CreateResult { use nexus_db_schema::schema::bgp_config::dsl; use nexus_db_schema::schema::{ @@ -222,7 +222,7 @@ impl DataStore { pub async fn bgp_config_delete( &self, opctx: &OpContext, - sel: ¶ms::BgpConfigSelector, + sel: &networking::BgpConfigSelector, ) -> DeleteResult { use nexus_db_schema::schema::bgp_config; use nexus_db_schema::schema::bgp_config::dsl as bgp_config_dsl; @@ -435,7 +435,7 @@ impl DataStore { pub async fn bgp_announcement_list( &self, opctx: &OpContext, - sel: ¶ms::BgpAnnounceSetSelector, + sel: &networking::BgpAnnounceSetSelector, ) -> ListResultVec { use nexus_db_schema::schema::{ bgp_announce_set, bgp_announce_set::dsl as announce_set_dsl, @@ -528,7 +528,7 @@ impl DataStore { pub async fn bgp_update_announce_set( &self, opctx: &OpContext, - announce: ¶ms::BgpAnnounceSetCreate, + announce: &networking::BgpAnnounceSetCreate, ) -> CreateResult<(BgpAnnounceSet, Vec)> { use nexus_db_schema::schema::bgp_announce_set::dsl as announce_set_dsl; use nexus_db_schema::schema::bgp_announcement::dsl as bgp_announcement_dsl; @@ -607,7 +607,7 @@ impl DataStore { pub async fn bgp_create_announce_set( &self, opctx: &OpContext, - announce: ¶ms::BgpAnnounceSetCreate, + announce: &networking::BgpAnnounceSetCreate, ) -> CreateResult<(BgpAnnounceSet, Vec)> { use nexus_db_schema::schema::bgp_announce_set::dsl as announce_set_dsl; use nexus_db_schema::schema::bgp_announcement::dsl as bgp_announcement_dsl; @@ -694,7 +694,7 @@ impl DataStore { pub async fn bgp_delete_announce_set( &self, opctx: &OpContext, - sel: ¶ms::BgpAnnounceSetSelector, + sel: &networking::BgpAnnounceSetSelector, ) -> DeleteResult { use nexus_db_schema::schema::bgp_announce_set; use nexus_db_schema::schema::bgp_announce_set::dsl as announce_set_dsl; @@ -1073,7 +1073,7 @@ mod tests { datastore .bgp_create_announce_set( &opctx, - ¶ms::BgpAnnounceSetCreate { + &networking::BgpAnnounceSetCreate { identity: IdentityMetadataCreateParams { name: announce_name.clone(), description: String::from("a test announce set"), @@ -1087,7 +1087,7 @@ mod tests { datastore .bgp_config_create( &opctx, - ¶ms::BgpConfigCreate { + &networking::BgpConfigCreate { identity: IdentityMetadataCreateParams { name: config_name.clone(), description: String::from("a test config"), @@ -1106,7 +1106,7 @@ mod tests { datastore .bgp_config_delete( &opctx, - ¶ms::BgpConfigSelector { + &networking::BgpConfigSelector { name_or_id: NameOrId::Name(config_name), }, ) @@ -1116,7 +1116,7 @@ mod tests { datastore .bgp_delete_announce_set( &opctx, - ¶ms::BgpAnnounceSetSelector { + &networking::BgpAnnounceSetSelector { announce_set: NameOrId::Name(announce_name), }, ) diff --git a/nexus/db-queries/src/db/datastore/deployment.rs b/nexus/db-queries/src/db/datastore/deployment.rs index a21d0b94b9c..a1a85a5e098 100644 --- a/nexus/db-queries/src/db/datastore/deployment.rs +++ b/nexus/db-queries/src/db/datastore/deployment.rs @@ -3130,10 +3130,10 @@ mod tests { use nexus_types::deployment::SledDisk; use nexus_types::deployment::SledFilter; use nexus_types::deployment::SledResources; - use nexus_types::external_api::views::PhysicalDiskPolicy; - use nexus_types::external_api::views::PhysicalDiskState; - use nexus_types::external_api::views::SledPolicy; - use nexus_types::external_api::views::SledState; + use nexus_types::external_api::physical_disk::{ + PhysicalDiskPolicy, PhysicalDiskState, + }; + use nexus_types::external_api::sled::{SledPolicy, SledState}; use nexus_types::inventory::Collection; use omicron_common::address::IpRange; use omicron_common::address::Ipv6Subnet; @@ -3152,7 +3152,6 @@ mod tests { use omicron_uuid_kinds::ZpoolUuid; use pretty_assertions::assert_eq; use rand::Rng; - use sled_hardware_types::BaseboardId; use std::collections::BTreeSet; use std::mem; use std::net::Ipv6Addr; diff --git a/nexus/db-queries/src/db/datastore/deployment/external_networking.rs b/nexus/db-queries/src/db/datastore/deployment/external_networking.rs index c71a1e886f7..24784251529 100644 --- a/nexus/db-queries/src/db/datastore/deployment/external_networking.rs +++ b/nexus/db-queries/src/db/datastore/deployment/external_networking.rs @@ -16,7 +16,7 @@ use nexus_db_model::IncompleteNetworkInterface; use nexus_db_model::IpPool; use nexus_types::deployment::BlueprintZoneConfig; use nexus_types::deployment::OmicronZoneExternalIp; -use nexus_types::external_api::params::PrivateIpStackCreate; +use nexus_types::external_api::instance::PrivateIpStackCreate; use omicron_common::api::external::Error; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IpVersion; diff --git a/nexus/db-queries/src/db/datastore/disk.rs b/nexus/db-queries/src/db/datastore/disk.rs index 8ef72b3a8c8..ba4b2deddcf 100644 --- a/nexus/db-queries/src/db/datastore/disk.rs +++ b/nexus/db-queries/src/db/datastore/disk.rs @@ -46,7 +46,7 @@ use nexus_db_errors::ErrorHandler; use nexus_db_errors::OptionalError; use nexus_db_errors::public_error_from_diesel; use nexus_db_lookup::LookupPath; -use nexus_types::external_api::params; +use nexus_types::external_api::disk as disk_types; use nexus_types::identity::Asset; use omicron_common::api; use omicron_common::api::external; @@ -1798,11 +1798,11 @@ impl DataStore { opctx: &OpContext, authz_project: &authz::Project, disk_id: &Uuid, - params: ¶ms::DiskCreate, + params: &disk_types::DiskCreate, ) -> CreateResult { opctx.authorize(authz::Action::CreateChild, authz_project).await?; - let params::DiskBackend::Distributed { ref disk_source } = + let disk_types::DiskBackend::Distributed { ref disk_source } = params.disk_backend else { // This is an internal error rather than an invalid argument or @@ -1818,14 +1818,17 @@ impl DataStore { // authz resource into the transaction so that it can refresh the lookup // to insure the transaction still exists once the transaction executes. let src = match disk_source { - ¶ms::DiskSource::Image { image_id, read_only: true } => { + &disk_types::DiskSource::Image { image_id, read_only: true } => { let (_, authz_image, _) = LookupPath::new(opctx, self) .image_id(image_id) .fetch() .await?; ReadOnlyDiskSource::Image(authz_image) } - ¶ms::DiskSource::Snapshot { snapshot_id, read_only: true } => { + &disk_types::DiskSource::Snapshot { + snapshot_id, + read_only: true, + } => { let (_, _, authz_snapshot, _) = LookupPath::new(opctx, self) .snapshot_id(snapshot_id) .fetch() @@ -1883,7 +1886,7 @@ impl DataStore { err: OptionalError, authz_project: &authz::Project, disk_id: &Uuid, - params: ¶ms::DiskCreate, + params: &disk_types::DiskCreate, src: &ReadOnlyDiskSource, ) -> Result { use crate::db::datastore::CrucibleTargets; @@ -2036,7 +2039,7 @@ impl DataStore { runtime_initial, db::model::DiskType::Crucible, ); - let params::DiskBackend::Distributed { ref disk_source } = + let disk_types::DiskBackend::Distributed { ref disk_source } = params.disk_backend else { return Err(err.bail(Error::internal_error( @@ -2070,7 +2073,8 @@ mod tests { use super::*; use crate::db::pub_test_utils::TestDatabase; - use nexus_types::external_api::params; + use nexus_types::external_api::disk as disk_types; + use nexus_types::external_api::project; use omicron_test_utils::dev; #[tokio::test] @@ -2088,7 +2092,7 @@ mod tests { &opctx, Project::new( silo_id, - params::ProjectCreate { + project::ProjectCreate { identity: external::IdentityMetadataCreateParams { name: "testpost".parse().unwrap(), description: "please ignore".to_string(), @@ -2101,16 +2105,16 @@ mod tests { let disk_id = Uuid::new_v4(); - let disk_source = params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + let disk_source = disk_types::DiskSource::Blank { + block_size: disk_types::BlockSize::try_from(512).unwrap(), }; - let create_params = params::DiskCreate { + let create_params = disk_types::DiskCreate { identity: external::IdentityMetadataCreateParams { name: "first-post".parse().unwrap(), description: "just trying things out".to_string(), }, - disk_backend: params::DiskBackend::Distributed { + disk_backend: disk_types::DiskBackend::Distributed { disk_source: disk_source.clone(), }, size: external::ByteCount::from(2147483648), diff --git a/nexus/db-queries/src/db/datastore/external_ip.rs b/nexus/db-queries/src/db/datastore/external_ip.rs index d471ac878a0..1e8dd67953d 100644 --- a/nexus/db-queries/src/db/datastore/external_ip.rs +++ b/nexus/db-queries/src/db/datastore/external_ip.rs @@ -1334,11 +1334,12 @@ mod tests { use nexus_db_model::{IpPool, VpcSubnetIdentity}; use nexus_types::deployment::OmicronZoneExternalFloatingIp; use nexus_types::deployment::OmicronZoneExternalSnatIp; - use nexus_types::external_api::params::{self, PrivateIpStackCreate}; - use nexus_types::external_api::shared::IpRange; - use nexus_types::external_api::shared::Ipv4Range; + use nexus_types::external_api::instance::PrivateIpStackCreate; + use nexus_types::external_api::vpc; use nexus_types::identity::Resource; use nexus_types::inventory::SourceNatConfigGeneric; + use omicron_common::address::IpRange; + use omicron_common::address::Ipv4Range; use omicron_common::address::NUM_SOURCE_NAT_PORTS; use omicron_common::api::external::{ self, IdentityMetadataCreateParams, LookupType, @@ -1627,7 +1628,7 @@ mod tests { Uuid::new_v4(), project.id(), Uuid::new_v4(), - params::VpcCreate { + vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: "default".parse().unwrap(), description: String::new(), diff --git a/nexus/db-queries/src/db/datastore/external_subnet.rs b/nexus/db-queries/src/db/datastore/external_subnet.rs index d3062417fb5..104c54e5d85 100644 --- a/nexus/db-queries/src/db/datastore/external_subnet.rs +++ b/nexus/db-queries/src/db/datastore/external_subnet.rs @@ -59,8 +59,8 @@ use nexus_db_model::SubnetPoolSiloLink; use nexus_db_model::SubnetPoolUpdate; use nexus_db_model::Vni; use nexus_db_model::to_db_typed_uuid; -use nexus_types::external_api::params; -use nexus_types::external_api::params::ExternalSubnetCreate; +use nexus_types::external_api::external_subnet::ExternalSubnetCreate; +use nexus_types::external_api::subnet_pool as subnet_pool_types; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::DeleteResult; @@ -172,7 +172,7 @@ impl DataStore { pub async fn create_subnet_pool( &self, opctx: &OpContext, - params: params::SubnetPoolCreate, + params: subnet_pool_types::SubnetPoolCreate, ) -> CreateResult { opctx .authorize(authz::Action::CreateChild, &authz::SUBNET_POOL_LIST) @@ -520,7 +520,7 @@ impl DataStore { opctx: &OpContext, authz_pool: &authz::SubnetPool, db_pool: &SubnetPool, - params: ¶ms::SubnetPoolMemberAdd, + params: &subnet_pool_types::SubnetPoolMemberAdd, ) -> CreateResult { opctx.authorize(authz::Action::CreateChild, authz_pool).await?; @@ -644,7 +644,7 @@ impl DataStore { opctx: &OpContext, silo_id: &Uuid, authz_project: &authz::Project, - params: params::ExternalSubnetCreate, + params: ExternalSubnetCreate, ) -> CreateResult { opctx.authorize(authz::Action::CreateChild, authz_project).await?; let ExternalSubnetCreate { identity, allocator } = params; @@ -1404,12 +1404,12 @@ mod tests { use nexus_db_model::SubnetPoolMember; use nexus_db_model::SubnetPoolUpdate; use nexus_db_model::to_db_typed_uuid; - use nexus_types::external_api::params::ExternalSubnetAllocator; - use nexus_types::external_api::params::ExternalSubnetCreate; - use nexus_types::external_api::params::PoolSelector; - use nexus_types::external_api::params::ProjectCreate; - use nexus_types::external_api::params::SubnetPoolCreate; - use nexus_types::external_api::params::SubnetPoolMemberAdd; + use nexus_types::external_api::external_subnet::ExternalSubnetAllocator; + use nexus_types::external_api::external_subnet::ExternalSubnetCreate; + use nexus_types::external_api::ip_pool::PoolSelector; + use nexus_types::external_api::project::ProjectCreate; + use nexus_types::external_api::subnet_pool::SubnetPoolCreate; + use nexus_types::external_api::subnet_pool::SubnetPoolMemberAdd; use nexus_types::identity::Resource; use nexus_types::silo::DEFAULT_SILO_ID; use omicron_common::address::IpRange; diff --git a/nexus/db-queries/src/db/datastore/instance.rs b/nexus/db-queries/src/db/datastore/instance.rs index 0a96c00b2c2..3f7673f954d 100644 --- a/nexus/db-queries/src/db/datastore/instance.rs +++ b/nexus/db-queries/src/db/datastore/instance.rs @@ -2312,7 +2312,8 @@ mod tests { use nexus_db_model::VmmCpuPlatform; use nexus_db_model::VmmRuntimeState; use nexus_db_model::VmmState; - use nexus_types::external_api::params; + use nexus_types::external_api::instance as instance_types; + use nexus_types::external_api::project; use nexus_types::identity::Asset; use nexus_types::silo::DEFAULT_SILO_ID; use omicron_common::api::external; @@ -2332,7 +2333,7 @@ mod tests { Project::new_with_id( project_id, silo_id, - params::ProjectCreate { + project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "stuff".parse().unwrap(), description: "Where I keep my stuff".into(), @@ -2359,7 +2360,7 @@ mod tests { Instance::new( instance_id, authz_project.id(), - ¶ms::InstanceCreate { + &instance_types::InstanceCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: "It's an instance".into(), @@ -2369,7 +2370,7 @@ mod tests { hostname: "myhostname".try_into().unwrap(), user_data: Vec::new(), network_interfaces: - params::InstanceNetworkInterfaceAttachment::None, + instance_types::InstanceNetworkInterfaceAttachment::None, external_ips: Vec::new(), disks: Vec::new(), boot_disk: None, diff --git a/nexus/db-queries/src/db/datastore/ip_pool.rs b/nexus/db-queries/src/db/datastore/ip_pool.rs index 0ddacd8c6c8..35b5190ae0f 100644 --- a/nexus/db-queries/src/db/datastore/ip_pool.rs +++ b/nexus/db-queries/src/db/datastore/ip_pool.rs @@ -51,8 +51,8 @@ use nexus_db_model::Project; use nexus_db_model::Vpc; use nexus_db_schema::enums::IpKindEnum; use nexus_db_schema::enums::IpPoolReservationTypeEnum; -use nexus_types::external_api::shared::IpRange; use nexus_types::silo::INTERNAL_SILO_ID; +use omicron_common::address::IpRange; use omicron_common::address::{IPV4_SSM_SUBNET, IPV6_SSM_SUBNET}; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DataPageParams; @@ -2514,8 +2514,9 @@ mod test { use nexus_types::deployment::{ OmicronZoneExternalFloatingIp, OmicronZoneExternalIp, }; - use nexus_types::external_api::params::{ - self, SubnetPoolCreate, SubnetPoolMemberAdd, + use nexus_types::external_api::project; + use nexus_types::external_api::subnet_pool::{ + SubnetPoolCreate, SubnetPoolMemberAdd, }; use nexus_types::identity::Resource; use nexus_types::silo::INTERNAL_SILO_ID; @@ -2824,7 +2825,7 @@ mod test { let authz_silo = opctx.authn.silo_required().unwrap(); let project = Project::new( authz_silo.id(), - params::ProjectCreate { + project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "my-project".parse().unwrap(), description: "".to_string(), @@ -2941,7 +2942,7 @@ mod test { let authz_silo = opctx.authn.silo_required().unwrap(); let project = Project::new( authz_silo.id(), - params::ProjectCreate { + project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "my-project".parse().unwrap(), description: "".to_string(), diff --git a/nexus/db-queries/src/db/datastore/migration.rs b/nexus/db-queries/src/db/datastore/migration.rs index 230340e2696..bd62a585e3b 100644 --- a/nexus/db-queries/src/db/datastore/migration.rs +++ b/nexus/db-queries/src/db/datastore/migration.rs @@ -182,7 +182,8 @@ mod tests { use crate::db::pub_test_utils::TestDatabase; use nexus_db_lookup::LookupPath; use nexus_db_model::Project; - use nexus_types::external_api::params; + use nexus_types::external_api::instance as instance_types; + use nexus_types::external_api::project; use nexus_types::silo::DEFAULT_SILO_ID; use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; @@ -204,7 +205,7 @@ mod tests { Project::new_with_id( project_id, silo_id, - params::ProjectCreate { + project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "stuff".parse().unwrap(), description: "Where I keep my stuff".into(), @@ -221,7 +222,7 @@ mod tests { Instance::new( instance_id, project_id, - ¶ms::InstanceCreate { + &instance_types::InstanceCreate { identity: IdentityMetadataCreateParams { name: "myinstance".parse().unwrap(), description: "It's an instance".into(), @@ -231,7 +232,7 @@ mod tests { hostname: "myhostname".try_into().unwrap(), user_data: Vec::new(), network_interfaces: - params::InstanceNetworkInterfaceAttachment::None, + instance_types::InstanceNetworkInterfaceAttachment::None, external_ips: Vec::new(), disks: Vec::new(), boot_disk: None, diff --git a/nexus/db-queries/src/db/datastore/mod.rs b/nexus/db-queries/src/db/datastore/mod.rs index 739d8f20a65..6865aab3f92 100644 --- a/nexus/db-queries/src/db/datastore/mod.rs +++ b/nexus/db-queries/src/db/datastore/mod.rs @@ -629,7 +629,9 @@ mod test { use nexus_db_model::to_db_typed_uuid; use nexus_types::deployment::Blueprint; use nexus_types::deployment::BlueprintTarget; - use nexus_types::external_api::params; + use nexus_types::external_api::disk as disk_types; + use nexus_types::external_api::project; + use nexus_types::external_api::ssh_key; use nexus_types::silo::DEFAULT_SILO_ID; use omicron_common::address::REPO_DEPOT_PORT; use omicron_common::api::external::{ @@ -694,7 +696,7 @@ mod test { let project = Project::new( authz_silo.id(), - params::ProjectCreate { + project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "project".parse().unwrap(), description: "desc".to_string(), @@ -1166,8 +1168,8 @@ mod test { // Allocate regions from the datasets for this disk. Do it a few times // for good measure. for alloc_seed in 0..10 { - let disk_source = params::DiskSource::Blank { - block_size: params::BlockSize::try_from(4096).unwrap(), + let disk_source = disk_types::DiskSource::Blank { + block_size: disk_types::BlockSize::try_from(4096).unwrap(), }; let size = ByteCount::from_mebibytes_u32(1); let volume_id = VolumeUuid::new_v4(); @@ -1260,8 +1262,8 @@ mod test { // Allocate regions from the datasets for this disk. Do it a few times // for good measure. for alloc_seed in 0..10 { - let disk_source = params::DiskSource::Blank { - block_size: params::BlockSize::try_from(4096).unwrap(), + let disk_source = disk_types::DiskSource::Blank { + block_size: disk_types::BlockSize::try_from(4096).unwrap(), }; let size = ByteCount::from_mebibytes_u32(1); let volume_id = VolumeUuid::new_v4(); @@ -1348,8 +1350,8 @@ mod test { // Allocate regions from the datasets for this disk. Do it a few times // for good measure. for alloc_seed in 0..10 { - let disk_source = params::DiskSource::Blank { - block_size: params::BlockSize::try_from(4096).unwrap(), + let disk_source = disk_types::DiskSource::Blank { + block_size: disk_types::BlockSize::try_from(4096).unwrap(), }; let size = ByteCount::from_mebibytes_u32(1); let volume_id = VolumeUuid::new_v4(); @@ -1394,8 +1396,8 @@ mod test { .await; // Allocate regions from the datasets for this volume. - let disk_source = params::DiskSource::Blank { - block_size: params::BlockSize::try_from(4096).unwrap(), + let disk_source = disk_types::DiskSource::Blank { + block_size: disk_types::BlockSize::try_from(4096).unwrap(), }; let size = ByteCount::from_mebibytes_u32(500); let volume_id = VolumeUuid::new_v4(); @@ -1498,8 +1500,8 @@ mod test { .await; // Allocate regions from the datasets for this volume. - let disk_source = params::DiskSource::Blank { - block_size: params::BlockSize::try_from(4096).unwrap(), + let disk_source = disk_types::DiskSource::Blank { + block_size: disk_types::BlockSize::try_from(4096).unwrap(), }; let size = ByteCount::from_mebibytes_u32(500); let volume1_id = VolumeUuid::new_v4(); @@ -1592,8 +1594,8 @@ mod test { .await; // Allocate regions from the datasets for this volume. - let disk_source = params::DiskSource::Blank { - block_size: params::BlockSize::try_from(4096).unwrap(), + let disk_source = disk_types::DiskSource::Blank { + block_size: disk_types::BlockSize::try_from(4096).unwrap(), }; let size = ByteCount::from_mebibytes_u32(500); let volume1_id = VolumeUuid::new_v4(); @@ -1684,8 +1686,8 @@ mod test { ]; let volume_id = VolumeUuid::new_v4(); - let disk_source = params::DiskSource::Blank { - block_size: params::BlockSize::try_from(4096).unwrap(), + let disk_source = disk_types::DiskSource::Blank { + block_size: disk_types::BlockSize::try_from(4096).unwrap(), }; let size = ByteCount::from_mebibytes_u32(500); @@ -1751,8 +1753,8 @@ mod test { .await; let disk_size = test_zpool_size(); - let disk_source = params::DiskSource::Blank { - block_size: params::BlockSize::try_from(4096).unwrap(), + let disk_source = disk_types::DiskSource::Blank { + block_size: disk_types::BlockSize::try_from(4096).unwrap(), }; let alloc_size = ByteCount::try_from(disk_size.to_bytes() * 2).unwrap(); let volume1_id = VolumeUuid::new_v4(); @@ -1902,7 +1904,7 @@ mod test { let public_key = "ssh-test AAAAAAAAKEY".to_string(); let ssh_key = SshKey::new( silo_user_id, - params::SshKeyCreate { + ssh_key::SshKeyCreate { identity: IdentityMetadataCreateParams { name: key_name.clone(), description: "my SSH public key".to_string(), diff --git a/nexus/db-queries/src/db/datastore/multicast/groups.rs b/nexus/db-queries/src/db/datastore/multicast/groups.rs index dd171e6ee6b..b2ea63aadcd 100644 --- a/nexus/db-queries/src/db/datastore/multicast/groups.rs +++ b/nexus/db-queries/src/db/datastore/multicast/groups.rs @@ -29,7 +29,7 @@ use uuid::Uuid; use nexus_db_errors::{ErrorHandler, public_error_from_diesel}; use nexus_db_lookup::DbConnection; -use nexus_types::external_api::views; +use nexus_types::external_api::multicast::{self as views}; use nexus_types::identity::Resource; use nexus_types::multicast::MulticastGroupCreate; use omicron_common::api::external::http_pagination::PaginatedBy; @@ -938,6 +938,7 @@ mod tests { use std::net::{IpAddr, Ipv4Addr}; use nexus_types::identity::Resource; + use nexus_types::multicast::MulticastGroupCreate; use omicron_common::address::{IpRange, Ipv4Range}; use omicron_test_utils::dev; use omicron_uuid_kinds::{GenericUuid, InstanceUuid}; diff --git a/nexus/db-queries/src/db/datastore/network_interface.rs b/nexus/db-queries/src/db/datastore/network_interface.rs index 2727d798d21..a9f1286f5a7 100644 --- a/nexus/db-queries/src/db/datastore/network_interface.rs +++ b/nexus/db-queries/src/db/datastore/network_interface.rs @@ -1068,7 +1068,7 @@ mod tests { use crate::db::pub_test_utils::TestDatabase; use nexus_config::NUM_INITIAL_RESERVED_IP_ADDRESSES; use nexus_db_fixed_data::vpc_subnet::NEXUS_VPC_SUBNET; - use nexus_types::external_api::params::PrivateIpStackCreate; + use nexus_types::external_api::instance::PrivateIpStackCreate; use omicron_common::address::NEXUS_OPTE_IPV4_SUBNET; use omicron_test_utils::dev; use std::collections::BTreeSet; diff --git a/nexus/db-queries/src/db/datastore/probe.rs b/nexus/db-queries/src/db/datastore/probe.rs index d62f804203f..017347ed2e5 100644 --- a/nexus/db-queries/src/db/datastore/probe.rs +++ b/nexus/db-queries/src/db/datastore/probe.rs @@ -22,9 +22,10 @@ use nexus_db_model::IncompleteNetworkInterface; use nexus_db_model::IpVersion; use nexus_db_model::Probe; use nexus_db_model::VpcSubnet; -use nexus_types::external_api::params::PrivateIpStackCreate; -use nexus_types::external_api::shared::ProbeInfo; +use nexus_types::external_api::instance::PrivateIpStackCreate; +use nexus_types::external_api::probe::ProbeInfo; use nexus_types::identity::Resource; +use nexus_types::inventory::NetworkInterface; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::DeleteResult; @@ -35,7 +36,6 @@ use omicron_common::api::external::LookupType; use omicron_common::api::external::NameOrId; use omicron_common::api::external::ResourceType; use omicron_common::api::external::http_pagination::PaginatedBy; -use omicron_common::api::internal::shared::NetworkInterface; use omicron_uuid_kinds::GenericUuid as _; use omicron_uuid_kinds::ProbeUuid; use omicron_uuid_kinds::SledUuid; diff --git a/nexus/db-queries/src/db/datastore/rack.rs b/nexus/db-queries/src/db/datastore/rack.rs index c90d82cefd0..432ee8ab89b 100644 --- a/nexus/db-queries/src/db/datastore/rack.rs +++ b/nexus/db-queries/src/db/datastore/rack.rs @@ -50,14 +50,13 @@ use nexus_types::deployment::BlueprintZoneConfig; use nexus_types::deployment::BlueprintZoneType; use nexus_types::deployment::OmicronZoneExternalIp; use nexus_types::deployment::blueprint_zone_type; -use nexus_types::external_api::params as external_params; -use nexus_types::external_api::params::PrivateIpStackCreate; -use nexus_types::external_api::shared; -use nexus_types::external_api::shared::IpRange; -use nexus_types::external_api::shared::SiloRole; +use nexus_types::external_api::instance::PrivateIpStackCreate; +use nexus_types::external_api::policy::{RoleAssignment, SiloRole}; +use nexus_types::external_api::silo as silo_types; use nexus_types::identity::Resource; use nexus_types::internal_api::params::InitialTrustQuorumConfig; use nexus_types::inventory::NetworkInterface; +use omicron_common::address::IpRange; use omicron_common::api::external::AllowedSourceIps; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::Error; @@ -91,7 +90,7 @@ pub struct RackInit { pub service_ip_pool_ranges: Vec, pub internal_dns: InitialDnsGroup, pub external_dns: InitialDnsGroup, - pub recovery_silo: external_params::SiloCreate, + pub recovery_silo: silo_types::SiloCreate, pub recovery_silo_fq_dns_name: String, pub recovery_user_id: UserId, pub recovery_user_password_hash: omicron_passwords::PasswordHashString, @@ -434,7 +433,7 @@ impl DataStore { opctx: &OpContext, conn: &async_bb8_diesel::Connection, log: &slog::Logger, - recovery_silo: external_params::SiloCreate, + recovery_silo: silo_types::SiloCreate, recovery_silo_fq_dns_name: String, recovery_user_id: UserId, recovery_user_password_hash: omicron_passwords::PasswordHashString, @@ -442,7 +441,7 @@ impl DataStore { ) -> Result<(), RackInitError> { if !matches!( &recovery_silo.identity_mode, - shared::SiloIdentityMode::LocalOnly + silo_types::SiloIdentityMode::LocalOnly ) { return Err(RackInitError::Silo(Error::invalid_request( "recovery silo should only use identity mode LocalOnly", @@ -516,10 +515,7 @@ impl DataStore { let (q1, q2) = Self::role_assignment_replace_visible_queries( opctx, &authz_silo, - &[shared::RoleAssignment::for_silo_user( - silo_user_id, - SiloRole::Admin, - )], + &[RoleAssignment::for_silo_user(silo_user_id, SiloRole::Admin)], ) .await .map_err(RackInitError::RoleAssignment)?; @@ -1089,7 +1085,7 @@ mod test { use nexus_types::deployment::PendingMgsUpdates; use nexus_types::deployment::SledFilter; use nexus_types::deployment::{BlueprintZoneImageSource, OximeterReadMode}; - use nexus_types::external_api::shared::SiloIdentityMode; + use nexus_types::external_api::silo::SiloIdentityMode; use nexus_types::identity::Asset; use nexus_types::internal_api::params::DnsRecord; use omicron_common::address::NEXUS_OPTE_IPV4_SUBNET; @@ -1155,13 +1151,14 @@ mod test { "test suite", HashMap::new(), ), - recovery_silo: external_params::SiloCreate { + recovery_silo: silo_types::SiloCreate { identity: IdentityMetadataCreateParams { name: "test-silo".parse().unwrap(), description: String::new(), }, // Set a default quota of a half rack's worth of resources - quotas: external_params::SiloQuotasCreate::arbitrarily_high_default(), + quotas: + silo_types::SiloQuotasCreate::arbitrarily_high_default(), discoverable: false, identity_mode: SiloIdentityMode::LocalOnly, admin_group_name: None, @@ -1185,7 +1182,7 @@ mod test { "test suite".to_string(), ), allowed_source_ips: AllowedSourceIps::Any, - initial_trust_quorum_configuration: None + initial_trust_quorum_configuration: None, } } } diff --git a/nexus/db-queries/src/db/datastore/region.rs b/nexus/db-queries/src/db/datastore/region.rs index d803c31d5f2..c1377909360 100644 --- a/nexus/db-queries/src/db/datastore/region.rs +++ b/nexus/db-queries/src/db/datastore/region.rs @@ -27,7 +27,7 @@ use nexus_config::RegionAllocationStrategy; use nexus_db_errors::ErrorHandler; use nexus_db_errors::public_error_from_diesel; use nexus_db_lookup::LookupPath; -use nexus_types::external_api::params; +use nexus_types::external_api::disk as disk_types; use omicron_common::api::external; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; @@ -52,7 +52,7 @@ pub enum RegionAllocationFor { /// Describe the region(s) to be allocated pub enum RegionAllocationParameters<'a> { FromDiskSource { - disk_source: &'a params::DiskSource, + disk_source: &'a disk_types::DiskSource, size: external::ByteCount, }, @@ -126,14 +126,14 @@ impl DataStore { async fn get_block_size_from_disk_source( &self, opctx: &OpContext, - disk_source: ¶ms::DiskSource, + disk_source: &disk_types::DiskSource, ) -> Result { match &disk_source { - params::DiskSource::Blank { block_size } => { + disk_types::DiskSource::Blank { block_size } => { Ok(db::model::BlockSize::try_from(*block_size) .map_err(|e| Error::invalid_request(&e.to_string()))?) } - params::DiskSource::Snapshot { snapshot_id, read_only: _ } => { + disk_types::DiskSource::Snapshot { snapshot_id, read_only: _ } => { let (.., db_snapshot) = LookupPath::new(opctx, self) .snapshot_id(*snapshot_id) .fetch() @@ -141,7 +141,7 @@ impl DataStore { Ok(db_snapshot.block_size) } - params::DiskSource::Image { image_id, read_only: _ } => { + disk_types::DiskSource::Image { image_id, read_only: _ } => { let (.., db_image) = LookupPath::new(opctx, self) .image_id(*image_id) .fetch() @@ -149,7 +149,7 @@ impl DataStore { Ok(db_image.block_size) } - params::DiskSource::ImportingBlocks { block_size } => { + disk_types::DiskSource::ImportingBlocks { block_size } => { Ok(db::model::BlockSize::try_from(*block_size) .map_err(|e| Error::invalid_request(&e.to_string()))?) } @@ -184,7 +184,7 @@ impl DataStore { &self, opctx: &OpContext, volume_id: VolumeUuid, - disk_source: ¶ms::DiskSource, + disk_source: &disk_types::DiskSource, size: external::ByteCount, allocation_strategy: &RegionAllocationStrategy, ) -> Result, Error> { diff --git a/nexus/db-queries/src/db/datastore/role.rs b/nexus/db-queries/src/db/datastore/role.rs index a058938758a..18e9f5910dc 100644 --- a/nexus/db-queries/src/db/datastore/role.rs +++ b/nexus/db-queries/src/db/datastore/role.rs @@ -21,7 +21,7 @@ use nexus_db_errors::TransactionError; use nexus_db_errors::public_error_from_diesel; use nexus_db_fixed_data::role_assignment::BUILTIN_ROLE_ASSIGNMENTS; use nexus_db_lookup::DbConnection; -use nexus_types::external_api::shared; +use nexus_types::external_api::policy; use omicron_common::api::external::Error; use omicron_common::api::external::ListResultVec; use omicron_common::bail_unless; @@ -124,7 +124,7 @@ impl DataStore { &self, opctx: &OpContext, authz_resource: &T, - new_assignments: &[shared::RoleAssignment], + new_assignments: &[policy::RoleAssignment], ) -> ListResultVec where T: authz::ApiResourceWithRolesType + AuthorizedResource + Clone, @@ -172,7 +172,7 @@ impl DataStore { pub async fn role_assignment_replace_visible_queries( opctx: &OpContext, authz_resource: &T, - new_assignments: &[shared::RoleAssignment], + new_assignments: &[policy::RoleAssignment], ) -> Result< ( impl RunnableQueryNoReturn + use, @@ -186,7 +186,7 @@ impl DataStore { opctx.authorize(authz::Action::ModifyPolicy, authz_resource).await?; bail_unless!( - new_assignments.len() <= shared::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE + new_assignments.len() <= policy::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE ); let resource_type = authz_resource.resource_type(); diff --git a/nexus/db-queries/src/db/datastore/scim_provider_store.rs b/nexus/db-queries/src/db/datastore/scim_provider_store.rs index b9414c4fbfb..f0cc8f0f92f 100644 --- a/nexus/db-queries/src/db/datastore/scim_provider_store.rs +++ b/nexus/db-queries/src/db/datastore/scim_provider_store.rs @@ -28,7 +28,7 @@ use nexus_db_errors::OptionalError; use nexus_db_lookup::DbConnection; use nexus_db_model::DatabaseString; use nexus_db_model::IdentityType; -use nexus_types::external_api::shared::SiloRole; +use nexus_types::external_api::policy::SiloRole; use omicron_common::api::external::LookupType; use omicron_uuid_kinds::GenericUuid; use omicron_uuid_kinds::SiloGroupUuid; diff --git a/nexus/db-queries/src/db/datastore/silo.rs b/nexus/db-queries/src/db/datastore/silo.rs index 72a5530301a..64b3f007e92 100644 --- a/nexus/db-queries/src/db/datastore/silo.rs +++ b/nexus/db-queries/src/db/datastore/silo.rs @@ -32,9 +32,9 @@ use nexus_db_model::Certificate; use nexus_db_model::ServiceKind; use nexus_db_model::SiloAuthSettings; use nexus_db_model::SiloQuotas; -use nexus_types::external_api::params; -use nexus_types::external_api::shared; -use nexus_types::external_api::shared::SiloRole; +use nexus_types::external_api::policy; +use nexus_types::external_api::policy::SiloRole; +use nexus_types::external_api::silo as silo_types; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::DeleteResult; @@ -150,7 +150,7 @@ impl DataStore { &self, opctx: &OpContext, nexus_opctx: &OpContext, - new_silo_params: params::SiloCreate, + new_silo_params: silo_types::SiloCreate, new_silo_dns_names: &[String], dns_update: DnsVersionUpdateBuilder, ) -> CreateResult { @@ -174,7 +174,7 @@ impl DataStore { conn: &async_bb8_diesel::Connection, opctx: &OpContext, nexus_opctx: &OpContext, - new_silo_params: params::SiloCreate, + new_silo_params: silo_types::SiloCreate, new_silo_dns_names: &[String], dns_update: DnsVersionUpdateBuilder, ) -> Result> { @@ -195,7 +195,7 @@ impl DataStore { { let maybe_silo_admin_group = match new_silo_params.identity_mode.user_provision_type() { - shared::UserProvisionType::ApiOnly => { + silo_types::UserProvisionType::ApiOnly => { Some(db::model::SiloGroup::new_api_only( silo_group_id, silo_id, @@ -203,7 +203,7 @@ impl DataStore { )) } - shared::UserProvisionType::Jit => { + silo_types::UserProvisionType::Jit => { Some(db::model::SiloGroup::new_jit( silo_group_id, silo_id, @@ -211,7 +211,7 @@ impl DataStore { )) } - shared::UserProvisionType::Scim => { + silo_types::UserProvisionType::Scim => { // Do not create any group automatically, the SCIM // provisioning client is responsible for all user and // group CRUD. @@ -239,9 +239,9 @@ impl DataStore { let silo_admin_group_role_assignment_queries = if silo_admin_group_ensure_query.is_some() { // Grant silo admin role for members of the admin group. - let policy = shared::Policy { + let policy = policy::Policy { role_assignments: vec![ - shared::RoleAssignment::for_silo_group( + policy::RoleAssignment::for_silo_group( silo_group_id, SiloRole::Admin, ), diff --git a/nexus/db-queries/src/db/datastore/silo_group.rs b/nexus/db-queries/src/db/datastore/silo_group.rs index 8026b163dc8..bfae7a83127 100644 --- a/nexus/db-queries/src/db/datastore/silo_group.rs +++ b/nexus/db-queries/src/db/datastore/silo_group.rs @@ -22,7 +22,7 @@ use diesel::prelude::*; use nexus_db_errors::ErrorHandler; use nexus_db_errors::TransactionError; use nexus_db_errors::public_error_from_diesel; -use nexus_types::external_api::views; +use nexus_types::external_api::user; use nexus_types::identity::Asset; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DataPageParams; @@ -128,8 +128,8 @@ impl From for model::SiloGroup { } } -impl From for views::Group { - fn from(u: SiloGroup) -> views::Group { +impl From for user::Group { + fn from(u: SiloGroup) -> user::Group { match u { SiloGroup::ApiOnly(u) => u.into(), SiloGroup::Jit(u) => u.into(), @@ -186,9 +186,9 @@ impl From for SiloGroup { } } -impl From for views::Group { - fn from(u: SiloGroupApiOnly) -> views::Group { - views::Group { +impl From for user::Group { + fn from(u: SiloGroupApiOnly) -> user::Group { + user::Group { id: u.id, // TODO the use of external_id as display_name is temporary display_name: u.external_id, @@ -245,9 +245,9 @@ impl From for SiloGroup { } } -impl From for views::Group { - fn from(u: SiloGroupJit) -> views::Group { - views::Group { +impl From for user::Group { + fn from(u: SiloGroupJit) -> user::Group { + user::Group { id: u.id, // TODO the use of external_id as display_name is temporary display_name: u.external_id, @@ -312,9 +312,9 @@ impl From for SiloGroup { } } -impl From for views::Group { - fn from(u: SiloGroupScim) -> views::Group { - views::Group { +impl From for user::Group { + fn from(u: SiloGroupScim) -> user::Group { + user::Group { id: u.id, // TODO the use of display name as display_name is temporary display_name: u.display_name, diff --git a/nexus/db-queries/src/db/datastore/silo_user.rs b/nexus/db-queries/src/db/datastore/silo_user.rs index f33ccfe6b97..c43f34cde0c 100644 --- a/nexus/db-queries/src/db/datastore/silo_user.rs +++ b/nexus/db-queries/src/db/datastore/silo_user.rs @@ -25,8 +25,7 @@ use chrono::Utc; use diesel::prelude::*; use nexus_db_errors::ErrorHandler; use nexus_db_errors::public_error_from_diesel; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::user; use nexus_types::identity::Asset; use nexus_types::identity::Resource; use omicron_common::api::external::CreateResult; @@ -168,8 +167,8 @@ impl From for model::SiloUser { } } -impl From for views::User { - fn from(u: SiloUser) -> views::User { +impl From for user::User { + fn from(u: SiloUser) -> user::User { match u { SiloUser::ApiOnly(u) => u.into(), SiloUser::Jit(u) => u.into(), @@ -227,9 +226,9 @@ impl From for SiloUser { } } -impl From for views::User { - fn from(u: SiloUserApiOnly) -> views::User { - views::User { +impl From for user::User { + fn from(u: SiloUserApiOnly) -> user::User { + user::User { id: u.id, // TODO the use of external_id as display_name is temporary display_name: u.external_id, @@ -287,9 +286,9 @@ impl From for SiloUser { } } -impl From for views::User { - fn from(u: SiloUserJit) -> views::User { - views::User { +impl From for user::User { + fn from(u: SiloUserJit) -> user::User { + user::User { id: u.id, // TODO the use of external_id as display_name is temporary display_name: u.external_id, @@ -358,9 +357,9 @@ impl From for SiloUser { } } -impl From for views::User { - fn from(u: SiloUserScim) -> views::User { - views::User { +impl From for user::User { + fn from(u: SiloUserScim) -> user::User { + user::User { id: u.id, // TODO the use of user_name as display_name is temporary display_name: u.user_name, @@ -884,7 +883,7 @@ impl DataStore { .map(|u| { UserBuiltin::new( u.id, - params::UserBuiltinCreate { + user::UserBuiltinCreate { identity: IdentityMetadataCreateParams { name: u.name.clone(), description: String::from(u.description), diff --git a/nexus/db-queries/src/db/datastore/sled.rs b/nexus/db-queries/src/db/datastore/sled.rs index 53f29a31cf1..c1e107beff9 100644 --- a/nexus/db-queries/src/db/datastore/sled.rs +++ b/nexus/db-queries/src/db/datastore/sled.rs @@ -39,8 +39,7 @@ use nexus_db_lookup::DbConnection; use nexus_db_model::ApplySledFilterExt; use nexus_db_model::DbTypedUuid; use nexus_types::deployment::SledFilter; -use nexus_types::external_api::views::SledPolicy; -use nexus_types::external_api::views::SledProvisionPolicy; +use nexus_types::external_api::sled::{SledPolicy, SledProvisionPolicy}; use nexus_types::identity::Asset; use nonempty::NonEmpty; use omicron_common::api::external; @@ -1796,7 +1795,7 @@ pub(in crate::db::datastore) mod test { use nexus_db_model::PhysicalDiskState; use nexus_db_model::{Generation, SledCpuFamily}; use nexus_db_model::{InstanceCpuPlatform, PhysicalDisk}; - use nexus_types::external_api::params; + use nexus_types::external_api::{disk, instance}; use nexus_types::identity::Asset; use nexus_types::identity::Resource; use omicron_common::api::external; @@ -3547,9 +3546,9 @@ pub(in crate::db::datastore) mod test { ( // In-service and active sleds can be marked as expunged. Before::new( - predicate::in_iter(SledPolicy::all_matching( - SledFilter::InService, - )), + predicate::in_iter( + SledFilter::InService.all_matching_policies(), + ), predicate::eq(SledState::Active), ), SledTransition::Policy(SledPolicy::Expunged), @@ -3558,9 +3557,9 @@ pub(in crate::db::datastore) mod test { // The provision policy of in-service sleds can be changed, or // kept the same (1 of 2). Before::new( - predicate::in_iter(SledPolicy::all_matching( - SledFilter::InService, - )), + predicate::in_iter( + SledFilter::InService.all_matching_policies(), + ), predicate::eq(SledState::Active), ), SledTransition::Policy(SledPolicy::InService { @@ -3570,9 +3569,9 @@ pub(in crate::db::datastore) mod test { ( // (2 of 2) Before::new( - predicate::in_iter(SledPolicy::all_matching( - SledFilter::InService, - )), + predicate::in_iter( + SledFilter::InService.all_matching_policies(), + ), predicate::eq(SledState::Active), ), SledTransition::Policy(SledPolicy::InService { @@ -4024,16 +4023,16 @@ pub(in crate::db::datastore) mod test { } } - for disk in &instance.disks { - let params = params::DiskCreate { + for d in &instance.disks { + let params = disk::DiskCreate { identity: external::IdentityMetadataCreateParams { - name: disk.name.clone(), + name: d.name.clone(), description: String::from("desc"), }, - disk_backend: params::DiskBackend::Local {}, + disk_backend: disk::DiskBackend::Local {}, - size: disk.size, + size: d.size, }; datastore @@ -4043,7 +4042,7 @@ pub(in crate::db::datastore) mod test { db::datastore::Disk::LocalStorage( db::datastore::LocalStorageDisk::new( db::model::Disk::new( - disk.id, + d.id, authz_project.id(), ¶ms, db::model::BlockSize::AdvancedFormat, @@ -4051,7 +4050,7 @@ pub(in crate::db::datastore) mod test { db::model::DiskType::LocalStorage, ), db::model::DiskTypeLocalStorage::new( - disk.id, disk.size, + d.id, d.size, ) .unwrap(), ), @@ -4061,7 +4060,7 @@ pub(in crate::db::datastore) mod test { .unwrap(); let (.., authz_disk) = LookupPath::new(&opctx, datastore) - .disk_id(disk.id) + .disk_id(d.id) .lookup_for(authz::Action::Read) .await .unwrap(); @@ -4260,7 +4259,7 @@ pub(in crate::db::datastore) mod test { db::model::Instance::new( instance_id, authz_project.id(), - ¶ms::InstanceCreate { + &instance::InstanceCreate { identity: external::IdentityMetadataCreateParams { name: name.parse().unwrap(), description: "It's an instance".into(), @@ -4270,7 +4269,7 @@ pub(in crate::db::datastore) mod test { hostname: "myhostname".try_into().unwrap(), user_data: Vec::new(), network_interfaces: - params::InstanceNetworkInterfaceAttachment::None, + instance::InstanceNetworkInterfaceAttachment::None, external_ips: Vec::new(), disks: Vec::new(), boot_disk: None, diff --git a/nexus/db-queries/src/db/datastore/switch_interface.rs b/nexus/db-queries/src/db/datastore/switch_interface.rs index 7254dcf6fa5..c841a763b15 100644 --- a/nexus/db-queries/src/db/datastore/switch_interface.rs +++ b/nexus/db-queries/src/db/datastore/switch_interface.rs @@ -17,7 +17,7 @@ use nexus_db_errors::ErrorHandler; use nexus_db_errors::OptionalError; use nexus_db_errors::public_error_from_diesel; use nexus_db_model::to_db_typed_uuid; -use nexus_types::external_api::params::LoopbackAddressCreate; +use nexus_types::external_api::networking::LoopbackAddressCreate; use omicron_common::api::external::{ CreateResult, DataPageParams, DeleteResult, Error, ListResultVec, LookupResult, ResourceType, diff --git a/nexus/db-queries/src/db/datastore/switch_port.rs b/nexus/db-queries/src/db/datastore/switch_port.rs index 8a44e2e600d..2f032b11edf 100644 --- a/nexus/db-queries/src/db/datastore/switch_port.rs +++ b/nexus/db-queries/src/db/datastore/switch_port.rs @@ -34,7 +34,7 @@ use nexus_db_model::{ SwitchPortBgpPeerConfigAllowExport, SwitchPortBgpPeerConfigAllowImport, SwitchPortBgpPeerConfigCommunity, }; -use nexus_types::external_api::params; +use nexus_types::external_api::networking; use nexus_types::identity::Resource; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ @@ -220,7 +220,7 @@ impl DataStore { pub async fn switch_port_settings_create( &self, opctx: &OpContext, - params: ¶ms::SwitchPortSettingsCreate, + params: &networking::SwitchPortSettingsCreate, id: Option, ) -> CreateResult { let err = OptionalError::new(); @@ -267,7 +267,7 @@ impl DataStore { pub async fn switch_port_settings_delete( &self, opctx: &OpContext, - params: ¶ms::SwitchPortSettingsSelector, + params: &networking::SwitchPortSettingsSelector, ) -> DeleteResult { let conn = self.pool_connection_authorized(opctx).await?; @@ -314,7 +314,7 @@ impl DataStore { pub async fn switch_port_settings_update( &self, opctx: &OpContext, - params: ¶ms::SwitchPortSettingsCreate, + params: &networking::SwitchPortSettingsCreate, id: Uuid, ) -> UpdateResult { let delete_err = OptionalError::new(); @@ -808,7 +808,7 @@ impl DataStore { &self, opctx: &OpContext, portname: &external::Name, - params: ¶ms::SwitchPortSelector, + params: &networking::SwitchPortSelector, ) -> DeleteResult { #[derive(Debug)] enum SwitchPortDeleteError { @@ -1195,7 +1195,7 @@ type SpsCreateError = SwitchPortSettingsCreateError; async fn do_switch_port_settings_create( conn: &Connection>, id: Option, - params: ¶ms::SwitchPortSettingsCreate, + params: &networking::SwitchPortSettingsCreate, err: OptionalError, ) -> Result { use nexus_db_schema::schema::{ @@ -1355,7 +1355,7 @@ async fn do_switch_port_settings_create( i.kind.into(), ); interface_config.push(ifx_config.clone()); - if let params::SwitchInterfaceKind::Vlan(vlan_if) = i.kind { + if let networking::SwitchInterfaceKind::Vlan(vlan_if) = i.kind { vlan_interface_config.push(SwitchVlanInterfaceConfig::new( ifx_config.id, vlan_if.vid, @@ -1868,7 +1868,7 @@ async fn do_switch_port_settings_delete( mod test { use crate::db::datastore::UpdatePrecondition; use crate::db::pub_test_utils::TestDatabase; - use nexus_types::external_api::params::{ + use nexus_types::external_api::networking::{ BgpAnnounceSetCreate, BgpConfigCreate, BgpPeerConfig, SwitchPortConfigCreate, SwitchPortGeometry, SwitchPortSettingsCreate, }; diff --git a/nexus/db-queries/src/db/datastore/test_utils.rs b/nexus/db-queries/src/db/datastore/test_utils.rs index 310607f99aa..31f6722d19a 100644 --- a/nexus/db-queries/src/db/datastore/test_utils.rs +++ b/nexus/db-queries/src/db/datastore/test_utils.rs @@ -15,8 +15,7 @@ use anyhow::ensure; use futures::future::try_join_all; use nexus_db_lookup::LookupPath; use nexus_db_model::SledState; -use nexus_types::external_api::views::SledPolicy; -use nexus_types::external_api::views::SledProvisionPolicy; +use nexus_types::external_api::sled::{SledPolicy, SledProvisionPolicy}; use omicron_uuid_kinds::SledUuid; use strum::EnumCount; diff --git a/nexus/db-queries/src/db/datastore/update.rs b/nexus/db-queries/src/db/datastore/update.rs index 64e956e42a1..c48d656a7d3 100644 --- a/nexus/db-queries/src/db/datastore/update.rs +++ b/nexus/db-queries/src/db/datastore/update.rs @@ -24,7 +24,7 @@ use nexus_db_model::{ ArtifactHash, TargetRelease, TufArtifact, TufRepo, TufRepoDescription, TufRepoUpload, TufTrustRoot, to_db_typed_uuid, }; -use nexus_types::external_api::views::TufRepoUploadStatus; +use nexus_types::external_api::update::TufRepoUploadStatus; use omicron_common::api::external::{ self, CreateResult, DataPageParams, DeleteResult, Generation, ListResultVec, LookupResult, LookupType, ResourceType, UpdateResult, diff --git a/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs b/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs index 5cf7f9a9f73..1cf604d102b 100644 --- a/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs +++ b/nexus/db-queries/src/db/datastore/virtual_provisioning_collection.rs @@ -331,7 +331,8 @@ mod test { use nexus_db_model::Instance; use nexus_db_model::Project; use nexus_db_model::SiloQuotasUpdate; - use nexus_types::external_api::params; + use nexus_types::external_api::instance as instance_types; + use nexus_types::external_api::project; use nexus_types::silo::DEFAULT_SILO_ID; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_test_utils::dev; @@ -389,7 +390,7 @@ mod test { Project::new_with_id( project_id, silo_id, - params::ProjectCreate { + project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "myproject".parse().unwrap(), description: "It's a project".into(), @@ -441,7 +442,7 @@ mod test { Instance::new( instance_id, project_id, - ¶ms::InstanceCreate { + &instance_types::InstanceCreate { identity: IdentityMetadataCreateParams { name: "myinstance".parse().unwrap(), description: "It's an instance".into(), @@ -451,7 +452,7 @@ mod test { hostname: "myhostname".try_into().unwrap(), user_data: Vec::new(), network_interfaces: - params::InstanceNetworkInterfaceAttachment::None, + instance_types::InstanceNetworkInterfaceAttachment::None, external_ips: Vec::new(), disks: Vec::new(), boot_disk: None, diff --git a/nexus/db-queries/src/db/datastore/volume.rs b/nexus/db-queries/src/db/datastore/volume.rs index e671c79ecae..a3192f69f31 100644 --- a/nexus/db-queries/src/db/datastore/volume.rs +++ b/nexus/db-queries/src/db/datastore/volume.rs @@ -4574,7 +4574,7 @@ mod tests { use crate::db::pub_test_utils::TestDatabase; use nexus_config::RegionAllocationStrategy; use nexus_db_model::SqlU16; - use nexus_types::external_api::params::DiskSource; + use nexus_types::external_api::disk::DiskSource; use omicron_common::api::external::ByteCount; use omicron_test_utils::dev; use omicron_uuid_kinds::VolumeUuid; diff --git a/nexus/db-queries/src/db/datastore/vpc.rs b/nexus/db-queries/src/db/datastore/vpc.rs index 40e570c9dde..9be837b5458 100644 --- a/nexus/db-queries/src/db/datastore/vpc.rs +++ b/nexus/db-queries/src/db/datastore/vpc.rs @@ -141,7 +141,7 @@ impl DataStore { let igw = db::model::InternetGateway::new( *SERVICES_INTERNET_GATEWAY_ID, authz_vpc.id(), - nexus_types::external_api::params::InternetGatewayCreate { + nexus_types::external_api::internet_gateway::InternetGatewayCreate { identity: IdentityMetadataCreateParams { name: "default".parse().unwrap(), description: String::from("Default VPC gateway"), @@ -170,7 +170,7 @@ impl DataStore { SERVICES_VPC.system_router_id, *SERVICES_VPC_ID, VpcRouterKind::System, - nexus_types::external_api::params::VpcRouterCreate { + nexus_types::external_api::vpc::VpcRouterCreate { identity: IdentityMetadataCreateParams { name: "system".parse().unwrap(), description: "Built-in VPC Router for Oxide Services" @@ -187,7 +187,7 @@ impl DataStore { *SERVICES_INTERNET_GATEWAY_DEFAULT_ROUTE_V4, SERVICES_VPC.system_router_id, ExternalRouteKind::Default, - nexus_types::external_api::params::RouterRouteCreate { + nexus_types::external_api::vpc::RouterRouteCreate { identity: IdentityMetadataCreateParams { name: "default-v4".parse().unwrap(), description: String::from("Default IPv4 route"), @@ -205,7 +205,7 @@ impl DataStore { *SERVICES_INTERNET_GATEWAY_DEFAULT_ROUTE_V6, SERVICES_VPC.system_router_id, ExternalRouteKind::Default, - nexus_types::external_api::params::RouterRouteCreate { + nexus_types::external_api::vpc::RouterRouteCreate { identity: IdentityMetadataCreateParams { name: "default-v6".parse().unwrap(), description: String::from("Default IPv6 route"), @@ -2989,8 +2989,10 @@ mod tests { use nexus_types::deployment::BlueprintZoneConfig; use nexus_types::deployment::BlueprintZoneDisposition; use nexus_types::deployment::BlueprintZoneImageSource; - use nexus_types::external_api::params; - use nexus_types::external_api::params::PrivateIpStackCreate; + use nexus_types::external_api::instance as instance_types; + use nexus_types::external_api::instance::PrivateIpStackCreate; + use nexus_types::external_api::project; + use nexus_types::external_api::vpc; use nexus_types::identity::Asset; use omicron_common::api::external; use omicron_common::api::external::Generation; @@ -3021,7 +3023,7 @@ mod tests { let (opctx, datastore) = (db.opctx(), db.datastore()); // Create a project. - let project_params = params::ProjectCreate { + let project_params = project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "project".parse().unwrap(), description: String::from("test project"), @@ -3043,7 +3045,7 @@ mod tests { Uuid::new_v4(), authz_project.id(), Uuid::new_v4(), - params::VpcCreate { + vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: name.clone(), description: description.clone(), @@ -3084,7 +3086,7 @@ mod tests { Uuid::new_v4(), authz_project.id(), Uuid::new_v4(), - params::VpcCreate { + vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: name.clone(), description: description.clone(), @@ -3126,7 +3128,7 @@ mod tests { let (opctx, datastore) = (db.opctx(), db.datastore()); // Create a project. - let project_params = params::ProjectCreate { + let project_params = project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "project".parse().unwrap(), description: String::from("test project"), @@ -3148,7 +3150,7 @@ mod tests { Uuid::new_v4(), authz_project.id(), Uuid::new_v4(), - params::VpcCreate { + vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: name.clone(), description: description.clone(), @@ -3190,7 +3192,7 @@ mod tests { Uuid::new_v4(), authz_project.id(), Uuid::new_v4(), - params::VpcCreate { + vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: name.clone(), description: description.clone(), @@ -3541,7 +3543,7 @@ mod tests { datastore: &DataStore, ) -> (authz::Project, authz::Vpc, Vpc, authz::VpcRouter, VpcRouter) { // Create a project and VPC. - let project_params = params::ProjectCreate { + let project_params = project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "project".parse().unwrap(), description: String::from("test project"), @@ -3559,7 +3561,7 @@ mod tests { Uuid::new_v4(), authz_project.id(), Uuid::new_v4(), - params::VpcCreate { + vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: vpc_name.clone(), description: description.clone(), @@ -3594,7 +3596,7 @@ mod tests { db_vpc.system_router_id, db_vpc.id(), VpcRouterKind::System, - nexus_types::external_api::params::VpcRouterCreate { + nexus_types::external_api::vpc::VpcRouterCreate { identity: IdentityMetadataCreateParams { name: "system".parse().unwrap(), description: description.clone(), @@ -3969,7 +3971,7 @@ mod tests { Uuid::new_v4(), authz_router.id(), external::RouterRouteKind::Custom, - params::RouterRouteCreate { + vpc::RouterRouteCreate { identity: IdentityMetadataCreateParams { name: "to-vpn".parse().unwrap(), description: "A rule...".into(), @@ -4005,7 +4007,7 @@ mod tests { db::model::Instance::new( InstanceUuid::new_v4(), authz_project.id(), - ¶ms::InstanceCreate { + &instance_types::InstanceCreate { identity: IdentityMetadataCreateParams { name: inst_name.clone(), description: "An instance...".into(), @@ -4015,7 +4017,7 @@ mod tests { hostname: "insty".parse().unwrap(), user_data: vec![], network_interfaces: - params::InstanceNetworkInterfaceAttachment::None, + instance_types::InstanceNetworkInterfaceAttachment::None, external_ips: vec![], disks: vec![], boot_disk: None, diff --git a/nexus/db-queries/src/db/datastore/webhook_delivery.rs b/nexus/db-queries/src/db/datastore/webhook_delivery.rs index 7fec81761d4..f12781e9ed9 100644 --- a/nexus/db-queries/src/db/datastore/webhook_delivery.rs +++ b/nexus/db-queries/src/db/datastore/webhook_delivery.rs @@ -451,7 +451,7 @@ mod test { use crate::db::pagination::Paginator; use crate::db::pub_test_utils::TestDatabase; use crate::db::raw_query_builder::expectorate_query_contents; - use nexus_types::external_api::params; + use nexus_types::external_api::alert; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_test_utils::dev; use omicron_uuid_kinds::AlertUuid; @@ -469,7 +469,7 @@ mod test { let rx = datastore .webhook_rx_create( opctx, - params::WebhookCreate { + alert::WebhookCreate { identity: IdentityMetadataCreateParams { name: "test-webhook".parse().unwrap(), description: String::new(), diff --git a/nexus/db-queries/src/db/pub_test_utils/helpers.rs b/nexus/db-queries/src/db/pub_test_utils/helpers.rs index c30c3306083..72b6a05cf47 100644 --- a/nexus/db-queries/src/db/pub_test_utils/helpers.rs +++ b/nexus/db-queries/src/db/pub_test_utils/helpers.rs @@ -32,7 +32,9 @@ use nexus_db_model::Snapshot; use nexus_db_model::SnapshotIdentity; use nexus_db_model::SnapshotState; use nexus_db_model::Vmm; -use nexus_types::external_api::params; +use nexus_types::external_api::affinity; +use nexus_types::external_api::instance as instance_types; +use nexus_types::external_api::project; use nexus_types::identity::Resource; use omicron_common::api::external; use omicron_common::api::external::{ @@ -62,7 +64,7 @@ pub async fn create_project( let project = Project::new( authz_silo.id(), - params::ProjectCreate { + project::ProjectCreate { identity: external::IdentityMetadataCreateParams { name: name.parse().unwrap(), description: "desc".to_string(), @@ -231,7 +233,7 @@ pub async fn create_stopped_instance_record( let instance = Instance::new( InstanceUuid::new_v4(), authz_project.id(), - ¶ms::InstanceCreate { + &instance_types::InstanceCreate { identity: external::IdentityMetadataCreateParams { name: name.parse().unwrap(), description: "".to_string(), @@ -241,7 +243,7 @@ pub async fn create_stopped_instance_record( hostname: "myhostname".try_into().unwrap(), user_data: Vec::new(), network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultDualStack, + instance_types::InstanceNetworkInterfaceAttachment::DefaultDualStack, external_ips: Vec::new(), disks: Vec::new(), boot_disk: None, @@ -304,7 +306,7 @@ pub async fn create_affinity_group( &authz_project, nexus_db_model::AffinityGroup::new( authz_project.id(), - params::AffinityGroupCreate { + affinity::AffinityGroupCreate { identity: external::IdentityMetadataCreateParams { name: group_name.parse().unwrap(), description: "desc".to_string(), @@ -348,7 +350,7 @@ pub async fn create_anti_affinity_group( &authz_project, nexus_db_model::AntiAffinityGroup::new( authz_project.id(), - params::AntiAffinityGroupCreate { + affinity::AntiAffinityGroupCreate { identity: external::IdentityMetadataCreateParams { name: group_name.parse().unwrap(), description: "desc".to_string(), diff --git a/nexus/db-queries/src/db/pub_test_utils/multicast.rs b/nexus/db-queries/src/db/pub_test_utils/multicast.rs index 65fa0455158..968a4daeb0a 100644 --- a/nexus/db-queries/src/db/pub_test_utils/multicast.rs +++ b/nexus/db-queries/src/db/pub_test_utils/multicast.rs @@ -18,10 +18,10 @@ use nexus_db_model::{ IncompleteIpPoolResource, IncompleteVpc, IpPool, IpPoolReservationType, IpPoolResourceType, IpVersion, }; -use nexus_types::external_api::params; -use nexus_types::external_api::shared::{IpRange, Ipv4Range}; +use nexus_types::external_api::vpc; use nexus_types::identity::Resource; use nexus_types::multicast::MulticastGroupCreate; +use omicron_common::address::{IpRange, Ipv4Range}; use omicron_common::api::external::{IdentityMetadataCreateParams, LookupType}; use omicron_uuid_kinds::{GenericUuid, MulticastGroupUuid, SledUuid}; @@ -73,7 +73,7 @@ pub async fn create_test_setup_with_range( let project_id = project.id(); // Create VPC for multicast groups - let vpc_params = params::VpcCreate { + let vpc_params = vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: format!("{}-vpc", project_name).parse().unwrap(), description: format!("Test VPC for project {}", project_name), diff --git a/nexus/db-queries/src/db/queries/external_ip.rs b/nexus/db-queries/src/db/queries/external_ip.rs index ebbe69d1ea1..cddcc2da2f9 100644 --- a/nexus/db-queries/src/db/queries/external_ip.rs +++ b/nexus/db-queries/src/db/queries/external_ip.rs @@ -907,10 +907,10 @@ mod tests { use nexus_types::deployment::OmicronZoneExternalFloatingIp; use nexus_types::deployment::OmicronZoneExternalIp; use nexus_types::deployment::OmicronZoneExternalSnatIp; - use nexus_types::external_api::params::InstanceCreate; - use nexus_types::external_api::params::InstanceNetworkInterfaceAttachment; - use nexus_types::external_api::shared::IpRange; + use nexus_types::external_api::instance::InstanceCreate; + use nexus_types::external_api::instance::InstanceNetworkInterfaceAttachment; use nexus_types::inventory::SourceNatConfigGeneric; + use omicron_common::address::IpRange; use omicron_common::address::NUM_SOURCE_NAT_PORTS; use omicron_common::api::external::Error; use omicron_common::api::external::IdentityMetadataCreateParams; diff --git a/nexus/db-queries/src/db/queries/external_subnet.rs b/nexus/db-queries/src/db/queries/external_subnet.rs index cdbd22e0100..0c22872a790 100644 --- a/nexus/db-queries/src/db/queries/external_subnet.rs +++ b/nexus/db-queries/src/db/queries/external_subnet.rs @@ -22,8 +22,8 @@ use nexus_db_model::SubnetPoolMember; use nexus_db_model::SubnetPoolSiloLink; use nexus_db_model::to_db_typed_uuid; use nexus_db_schema::enums::IpVersionEnum; -use nexus_types::external_api::params::ExternalSubnetAllocator; -use nexus_types::external_api::params::PoolSelector; +use nexus_types::external_api::external_subnet::ExternalSubnetAllocator; +use nexus_types::external_api::ip_pool::PoolSelector; use omicron_common::api::external::Error; use omicron_common::api::external::LookupType; use omicron_common::api::external::NameOrId; @@ -1309,9 +1309,9 @@ mod tests { use chrono::Utc; use nexus_db_model::ExternalSubnetIdentity; use nexus_db_model::SubnetPoolMember; - use nexus_types::external_api::params::ExternalSubnetAllocator; - use nexus_types::external_api::params::PoolSelector; - use nexus_types::external_api::params::SubnetPoolMemberAdd; + use nexus_types::external_api::external_subnet::ExternalSubnetAllocator; + use nexus_types::external_api::ip_pool::PoolSelector; + use nexus_types::external_api::subnet_pool::SubnetPoolMemberAdd; use omicron_common::address::IpVersion; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::NameOrId; diff --git a/nexus/db-queries/src/db/queries/network_interface.rs b/nexus/db-queries/src/db/queries/network_interface.rs index 229eccaec6b..063e8315e6f 100644 --- a/nexus/db-queries/src/db/queries/network_interface.rs +++ b/nexus/db-queries/src/db/queries/network_interface.rs @@ -31,7 +31,7 @@ use nexus_db_model::SqlU8; use nexus_db_model::{MAX_NICS_PER_INSTANCE, NetworkInterfaceKind}; use nexus_db_schema::enums::NetworkInterfaceKindEnum; use nexus_db_schema::schema::network_interface::dsl; -use nexus_types::external_api::params::IpAssignment; +use nexus_types::external_api::instance::IpAssignment; use omicron_common::address::ConcreteIp; use omicron_common::api::external::MacAddr; use omicron_common::api::external::{self, Error}; @@ -1964,12 +1964,12 @@ mod tests { use model::NetworkInterfaceKind; use nexus_db_lookup::LookupPath; use nexus_db_model::IpVersion; - use nexus_types::external_api::params; - use nexus_types::external_api::params::InstanceCreate; - use nexus_types::external_api::params::InstanceNetworkInterfaceAttachment; - use nexus_types::external_api::params::Ipv4Assignment; - use nexus_types::external_api::params::PrivateIpStackCreate; - use nexus_types::external_api::params::PrivateIpv4StackCreate; + use nexus_types::external_api::instance::InstanceCreate; + use nexus_types::external_api::instance::InstanceNetworkInterfaceAttachment; + use nexus_types::external_api::instance::Ipv4Assignment; + use nexus_types::external_api::instance::PrivateIpStackCreate; + use nexus_types::external_api::instance::PrivateIpv4StackCreate; + use nexus_types::external_api::project; use omicron_common::api::external; use omicron_common::api::external::ByteCount; use omicron_common::api::external::Error; @@ -2138,7 +2138,7 @@ mod tests { // Create a project let project = Project::new( authz_silo.id(), - params::ProjectCreate { + project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "project".parse().unwrap(), description: "desc".to_string(), diff --git a/nexus/db-queries/src/policy_test/mod.rs b/nexus/db-queries/src/policy_test/mod.rs index 85cf2b75a8c..d841739cc94 100644 --- a/nexus/db-queries/src/policy_test/mod.rs +++ b/nexus/db-queries/src/policy_test/mod.rs @@ -26,10 +26,10 @@ use nexus_auth::authn::SiloAuthnPolicy; use nexus_auth::authn::USER_TEST_PRIVILEGED; use nexus_auth::authz; use nexus_auth::context::OpContext; -use nexus_types::external_api::params; -use nexus_types::external_api::shared; -use nexus_types::external_api::shared::FleetRole; -use nexus_types::external_api::shared::SiloRole; +use nexus_types::external_api::policy; +use nexus_types::external_api::policy::FleetRole; +use nexus_types::external_api::policy::SiloRole; +use nexus_types::external_api::silo as silo_types; use nexus_types::identity::Asset; use nexus_types::identity::Resource; use omicron_common::api::external::Error; @@ -81,14 +81,14 @@ async fn test_iam_prep( .silo_create( &opctx, &opctx, - params::SiloCreate { + silo_types::SiloCreate { identity: IdentityMetadataCreateParams { name: "main-silo".parse().unwrap(), description: "".into(), }, - quotas: params::SiloQuotasCreate::empty(), + quotas: silo_types::SiloQuotasCreate::empty(), discoverable: true, - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: silo_types::SiloIdentityMode::LocalOnly, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: Default::default(), @@ -115,7 +115,7 @@ async fn test_iam_prep( .role_assignment_replace_visible( &opctx, &main_silo, - &[shared::RoleAssignment::for_silo_user( + &[policy::RoleAssignment::for_silo_user( USER_TEST_PRIVILEGED.id(), SiloRole::Admin, )], diff --git a/nexus/db-queries/src/policy_test/resource_builder.rs b/nexus/db-queries/src/policy_test/resource_builder.rs index f41f6dcc672..7cccfbeff1e 100644 --- a/nexus/db-queries/src/policy_test/resource_builder.rs +++ b/nexus/db-queries/src/policy_test/resource_builder.rs @@ -16,7 +16,7 @@ use nexus_auth::authz::ApiResourceWithRolesType; use nexus_auth::authz::AuthorizedResource; use nexus_auth::context::OpContext; use nexus_db_model::DatabaseString; -use nexus_types::external_api::shared; +use nexus_types::external_api::policy; use omicron_common::api::external::Error; use omicron_common::api::external::LookupType; use omicron_uuid_kinds::SiloUserUuid; @@ -126,7 +126,7 @@ impl<'a> ResourceBuilder<'a> { let new_role_assignments = old_role_assignments .into_iter() .map(|r| r.try_into().unwrap()) - .chain(std::iter::once(shared::RoleAssignment::for_silo_user( + .chain(std::iter::once(policy::RoleAssignment::for_silo_user( user_id, role, ))) .collect::>(); diff --git a/nexus/external-api/Cargo.toml b/nexus/external-api/Cargo.toml index 509112cfc63..5d9a6cbc757 100644 --- a/nexus/external-api/Cargo.toml +++ b/nexus/external-api/Cargo.toml @@ -19,6 +19,7 @@ hyper.workspace = true ipnetwork.workspace = true itertools.workspace = true nexus-types.workspace = true +nexus-types-versions.workspace = true omicron-common.workspace = true omicron-uuid-kinds.workspace = true omicron-workspace-hack.workspace = true @@ -35,6 +36,4 @@ tufaceous-artifact.workspace = true uuid.workspace = true [dev-dependencies] -proptest.workspace = true serde_json.workspace = true -test-strategy.workspace = true diff --git a/nexus/external-api/src/lib.rs b/nexus/external-api/src/lib.rs index cae2791de7a..4127c69c3d2 100644 --- a/nexus/external-api/src/lib.rs +++ b/nexus/external-api/src/lib.rs @@ -17,13 +17,22 @@ use dropshot::{ use dropshot_api_manager_types::{ValidationContext, api_versions}; use http::Response; use ipnetwork::IpNetwork; -use nexus_types::{ - authn::cookies::Cookies, - external_api::{ - headers, params, shared, - views::{self, MulticastGroupMember, RackMembershipStatus}, - }, -}; +use nexus_types::authn::cookies::Cookies; +use nexus_types_versions::latest; +use nexus_types_versions::latest::headers; +use nexus_types_versions::v2025_11_20_00; +use nexus_types_versions::v2025_12_03_00; +use nexus_types_versions::v2025_12_12_00; +use nexus_types_versions::v2025_12_23_00; +use nexus_types_versions::v2026_01_01_00; +use nexus_types_versions::v2026_01_03_00; +use nexus_types_versions::v2026_01_05_00; +use nexus_types_versions::v2026_01_08_00; +use nexus_types_versions::v2026_01_16_00; +use nexus_types_versions::v2026_01_16_01; +use nexus_types_versions::v2026_01_22_00; +use nexus_types_versions::v2026_01_30_01; +use omicron_common::address::IpRange; use omicron_common::api::external::{ http_pagination::{ PaginatedById, PaginatedByName, PaginatedByNameOrId, @@ -33,37 +42,26 @@ use omicron_common::api::external::{ }; use openapiv3::OpenAPI; -/// Copies of data types that changed between versions -mod v2025112000; -mod v2025120300; -pub mod v2025121200; -mod v2025122300; -mod v2026010100; -mod v2026010300; -mod v2026010500; -mod v2026011501; -mod v2026011600; -mod v2026012200; -mod v2026012300; -mod v2026013000; -mod v2026013001; -pub mod v2026020600; - -#[cfg(test)] -mod test_utils; +/// Types that convert to/from `omicron-common` types and thus cannot live in +/// `nexus-types-versions`. These will go away once `omicron-common-versions` +/// exists. +mod v2025_11_20_00_local; +mod v2026_01_01_00_local; +mod v2026_01_30_00_local; api_versions!([ - // API versions are in the format YYYYMMDDNN.0.0, defined below as - // YYYYMMDDNN. Here, NN is a two-digit number starting at 00 for a + // API versions are in the format YYYY_MM_DD_NN.0.0, defined below as + // YYYY_MM_DD_NN. Here, NN is a two-digit number starting at 00 for a // particular date. // // WHEN CHANGING THE API (part 1 of 2): // // +- First, determine the next API version number to use. // | - // | * On the main branch: Take today's date in YYYYMMDD format, e.g. 20251112. - // | Find the smallest NN that isn't already defined in the list below. In - // | most cases, that is 00, but if 00 is already taken, use 01, 02, etc. + // | * On the main branch: Take today's date in YYYY_MM_DD format, e.g. + // | 2025_11_12. Find the smallest NN that isn't already defined in the + // | list below. In most cases, that is 00, but if 00 is already taken, + // | use 01, 02, etc. // | // | * On a release branch, don't alter the date. Instead, always bump the NN. // | @@ -79,35 +77,35 @@ api_versions!([ // | will panic at runtime if they're not in descending order.) The newest // | date-based version should be at the top of the list. // v - // (next_yyyymmddnn, IDENT), - (2026021301, BGP_UNNUMBERED_PEERS), - (2026021300, STALE_DOCS_AND_PUNCTUATION), - (2026020901, UPDATE_EXTERNAL_SUBNET_DOCS), - (2026020900, RENAME_POOL_ENDPOINTS), - (2026020600, ADD_SILO_SUBNET_POOLS), - (2026020200, TRUST_QUORUM_ABORT_CONFIG), - (2026013100, READ_ONLY_DISKS_NULLABLE), - (2026013001, READ_ONLY_DISKS), - (2026013000, INSTANCES_EXTERNAL_SUBNETS), - (2026012800, REMOVE_SUBNET_POOL_POOL_TYPE), - (2026012300, DUAL_STACK_EPHEMERAL_IP), - (2026012201, EXTERNAL_SUBNET_ALLOCATOR_UPDATE), - (2026012200, FLOATING_IP_ALLOCATOR_UPDATE), - (2026012100, TRUST_QUORUM_ADD_SLEDS_AND_GET_LATEST_CONFIG), - (2026011601, EXTERNAL_SUBNET_ATTACHMENT), - (2026011600, RENAME_ADDRESS_SELECTOR_TO_ADDRESS_ALLOCATOR), - (2026011501, AUDIT_LOG_CREDENTIAL_ID), - (2026011500, AUDIT_LOG_AUTH_METHOD_ENUM), - (2026011300, DOC_LINT_SUMMARY_TRAILING_PERIOD), - (2026011100, MULTICAST_JOIN_LEAVE_DOCS), - (2026010800, MULTICAST_IMPLICIT_LIFECYCLE_UPDATES), - (2026010500, POOL_SELECTION_ENUMS), - (2026010300, DUAL_STACK_NICS), - (2026010100, SILO_PROJECT_IP_VERSION_AND_POOL_TYPE), - (2025122300, IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS), - (2025121200, BGP_PEER_COLLISION_STATE), - (2025120300, LOCAL_STORAGE), - (2025112000, INITIAL), + // (next_yyyy_mm_dd_nn, IDENT), + (2026_02_13_01, BGP_UNNUMBERED_PEERS), + (2026_02_13_00, STALE_DOCS_AND_PUNCTUATION), + (2026_02_09_01, UPDATE_EXTERNAL_SUBNET_DOCS), + (2026_02_09_00, RENAME_POOL_ENDPOINTS), + (2026_02_06_00, ADD_SILO_SUBNET_POOLS), + (2026_02_02_00, TRUST_QUORUM_ABORT_CONFIG), + (2026_01_31_00, READ_ONLY_DISKS_NULLABLE), + (2026_01_30_01, READ_ONLY_DISKS), + (2026_01_30_00, INSTANCES_EXTERNAL_SUBNETS), + (2026_01_28_00, REMOVE_SUBNET_POOL_POOL_TYPE), + (2026_01_23_00, DUAL_STACK_EPHEMERAL_IP), + (2026_01_22_01, EXTERNAL_SUBNET_ALLOCATOR_UPDATE), + (2026_01_22_00, FLOATING_IP_ALLOCATOR_UPDATE), + (2026_01_21_00, TRUST_QUORUM_ADD_SLEDS_AND_GET_LATEST_CONFIG), + (2026_01_16_01, EXTERNAL_SUBNET_ATTACHMENT), + (2026_01_16_00, RENAME_ADDRESS_SELECTOR_TO_ADDRESS_ALLOCATOR), + (2026_01_15_01, AUDIT_LOG_CREDENTIAL_ID), + (2026_01_15_00, AUDIT_LOG_AUTH_METHOD_ENUM), + (2026_01_13_00, DOC_LINT_SUMMARY_TRAILING_PERIOD), + (2026_01_11_00, MULTICAST_JOIN_LEAVE_DOCS), + (2026_01_08_00, MULTICAST_IMPLICIT_LIFECYCLE_UPDATES), + (2026_01_05_00, POOL_SELECTION_ENUMS), + (2026_01_03_00, DUAL_STACK_NICS), + (2026_01_01_00, SILO_PROJECT_IP_VERSION_AND_POOL_TYPE), + (2025_12_23_00, IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS), + (2025_12_12_00, BGP_PEER_COLLISION_STATE), + (2025_12_03_00, LOCAL_STORAGE), + (2025_11_20_00, INITIAL), ]); // WHEN CHANGING THE API (part 2 of 2): @@ -117,7 +115,7 @@ api_versions!([ // the version when a particular endpoint was added or removed. For example, if // you used: // -// (2025120100, ADD_FOOBAR) +// (2025_12_01_00, ADD_FOOBAR) // // Then you could use `VERSION_ADD_FOOBAR` as the version in which endpoints // were added or removed. @@ -382,8 +380,10 @@ pub trait NexusExternalApi { }] async fn ping( _rqctx: RequestContext, - ) -> Result, HttpError> { - Ok(HttpResponseOk(views::Ping { status: views::PingStatus::Ok })) + ) -> Result, HttpError> { + Ok(HttpResponseOk(latest::system::Ping { + status: latest::system::PingStatus::Ok, + })) } /// Fetch top-level IAM policy @@ -394,7 +394,10 @@ pub trait NexusExternalApi { }] async fn system_policy_view( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Update top-level IAM policy #[endpoint { @@ -404,8 +407,13 @@ pub trait NexusExternalApi { }] async fn system_policy_update( rqctx: RequestContext, - new_policy: TypedBody>, - ) -> Result>, HttpError>; + new_policy: TypedBody< + latest::policy::Policy, + >, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Fetch current silo's IAM policy #[endpoint { @@ -415,7 +423,10 @@ pub trait NexusExternalApi { }] async fn policy_view( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Update current silo's IAM policy #[endpoint { @@ -425,8 +436,11 @@ pub trait NexusExternalApi { }] async fn policy_update( rqctx: RequestContext, - new_policy: TypedBody>, - ) -> Result>, HttpError>; + new_policy: TypedBody>, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Fetch current silo's auth settings #[endpoint { @@ -436,7 +450,7 @@ pub trait NexusExternalApi { }] async fn auth_settings_view( rqctx: RequestContext, - ) -> Result, HttpError>; + ) -> Result, HttpError>; /// Update current silo's auth settings #[endpoint { @@ -446,8 +460,8 @@ pub trait NexusExternalApi { }] async fn auth_settings_update( rqctx: RequestContext, - new_settings: TypedBody, - ) -> Result, HttpError>; + new_settings: TypedBody, + ) -> Result, HttpError>; /// Fetch resource utilization for user's current silo #[endpoint { @@ -457,7 +471,7 @@ pub trait NexusExternalApi { }] async fn utilization_view( rqctx: RequestContext, - ) -> Result, HttpError>; + ) -> Result, HttpError>; /// Fetch current utilization for given silo #[endpoint { @@ -467,8 +481,8 @@ pub trait NexusExternalApi { }] async fn silo_utilization_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// List current utilization state for all silos #[endpoint { @@ -479,7 +493,10 @@ pub trait NexusExternalApi { async fn silo_utilization_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Lists resource quotas for all silos #[endpoint { @@ -490,7 +507,7 @@ pub trait NexusExternalApi { async fn system_quotas_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Fetch resource quotas for silo #[endpoint { @@ -500,8 +517,8 @@ pub trait NexusExternalApi { }] async fn silo_quotas_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// Update resource quotas for silo /// @@ -513,9 +530,9 @@ pub trait NexusExternalApi { }] async fn silo_quotas_update( rqctx: RequestContext, - path_params: Path, - new_quota: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + new_quota: TypedBody, + ) -> Result, HttpError>; /// List silos /// @@ -528,7 +545,7 @@ pub trait NexusExternalApi { async fn silo_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Create silo #[endpoint { @@ -538,8 +555,8 @@ pub trait NexusExternalApi { }] async fn silo_create( rqctx: RequestContext, - new_silo_params: TypedBody, - ) -> Result, HttpError>; + new_silo_params: TypedBody, + ) -> Result, HttpError>; /// Fetch silo /// @@ -551,8 +568,8 @@ pub trait NexusExternalApi { }] async fn silo_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// List IP pools linked to silo /// @@ -560,25 +577,19 @@ pub trait NexusExternalApi { /// can have at most one default pool. IPs are allocated from the default /// pool when users ask for one without specifying a pool. #[endpoint { - operation_id = "silo_ip_pool_list", method = GET, path = "/v1/system/silos/{silo}/ip-pools", tags = ["system/silos"], - versions = ..VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE, + versions = VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE.., }] - async fn v2025122300_silo_ip_pool_list( + async fn silo_ip_pool_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError> - { - let page = - Self::silo_ip_pool_list(rqctx, path_params, query_params).await?.0; - Ok(HttpResponseOk(ResultsPage { - items: page.items.into_iter().map(Into::into).collect(), - next_page: page.next_page, - })) - } + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// List IP pools linked to silo /// @@ -586,16 +597,27 @@ pub trait NexusExternalApi { /// can have at most one default pool. IPs are allocated from the default /// pool when users ask for one without specifying a pool. #[endpoint { + operation_id = "silo_ip_pool_list", method = GET, path = "/v1/system/silos/{silo}/ip-pools", tags = ["system/silos"], - versions = VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE.., + versions = ..VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE, }] - async fn silo_ip_pool_list( + async fn silo_ip_pool_list_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + > { + let page = + Self::silo_ip_pool_list(rqctx, path_params, query_params).await?.0; + Ok(HttpResponseOk(ResultsPage { + items: page.items.into_iter().map(Into::into).collect(), + next_page: page.next_page, + })) + } /// Delete silo /// @@ -607,7 +629,7 @@ pub trait NexusExternalApi { }] async fn silo_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; /// Fetch silo IAM policy @@ -618,8 +640,11 @@ pub trait NexusExternalApi { }] async fn silo_policy_view( rqctx: RequestContext, - path_params: Path, - ) -> Result>, HttpError>; + path_params: Path, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Update silo IAM policy #[endpoint { @@ -629,9 +654,12 @@ pub trait NexusExternalApi { }] async fn silo_policy_update( rqctx: RequestContext, - path_params: Path, - new_policy: TypedBody>, - ) -> Result>, HttpError>; + path_params: Path, + new_policy: TypedBody>, + ) -> Result< + HttpResponseOk>, + HttpError, + >; // Silo-specific user endpoints @@ -643,8 +671,8 @@ pub trait NexusExternalApi { }] async fn silo_user_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query>, + ) -> Result>, HttpError>; /// Fetch built-in (system) user #[endpoint { @@ -654,9 +682,9 @@ pub trait NexusExternalApi { }] async fn silo_user_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; // Silo identity providers @@ -670,8 +698,13 @@ pub trait NexusExternalApi { }] async fn silo_identity_provider_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query>, + ) -> Result< + HttpResponseOk< + ResultsPage, + >, + HttpError, + >; // Silo SAML identity providers @@ -683,9 +716,14 @@ pub trait NexusExternalApi { }] async fn saml_identity_provider_create( rqctx: RequestContext, - query_params: Query, - new_provider: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + new_provider: TypedBody< + latest::identity_provider::SamlIdentityProviderCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + >; /// Fetch SAML identity provider #[endpoint { @@ -695,9 +733,12 @@ pub trait NexusExternalApi { }] async fn saml_identity_provider_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result< + HttpResponseOk, + HttpError, + >; // TODO: no DELETE for identity providers? @@ -715,9 +756,9 @@ pub trait NexusExternalApi { }] async fn local_idp_user_create( rqctx: RequestContext, - query_params: Query, - new_user_params: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + new_user_params: TypedBody, + ) -> Result, HttpError>; /// Delete user #[endpoint { @@ -727,8 +768,8 @@ pub trait NexusExternalApi { }] async fn local_idp_user_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// Set or invalidate user's password @@ -742,9 +783,9 @@ pub trait NexusExternalApi { }] async fn local_idp_user_set_password( rqctx: RequestContext, - path_params: Path, - query_params: Query, - update: TypedBody, + path_params: Path, + query_params: Query, + update: TypedBody, ) -> Result; // SAML+SCIM Identity Provider @@ -759,8 +800,11 @@ pub trait NexusExternalApi { }] async fn scim_token_list( rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError>; + query_params: Query, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Create SCIM token /// @@ -774,8 +818,11 @@ pub trait NexusExternalApi { }] async fn scim_token_create( rqctx: RequestContext, - query_params: Query, - ) -> Result, HttpError>; + query_params: Query, + ) -> Result< + HttpResponseCreated, + HttpError, + >; /// Fetch SCIM token /// @@ -787,9 +834,9 @@ pub trait NexusExternalApi { }] async fn scim_token_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; /// Delete SCIM token /// @@ -801,8 +848,8 @@ pub trait NexusExternalApi { }] async fn scim_token_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; // SCIM user endpoints @@ -827,7 +874,7 @@ pub trait NexusExternalApi { }] async fn scim_v2_get_user( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, ) -> Result, HttpError>; @@ -850,7 +897,7 @@ pub trait NexusExternalApi { }] async fn scim_v2_put_user( rqctx: RequestContext, - path_params: Path, + path_params: Path, body: TypedBody, ) -> Result, HttpError>; @@ -862,7 +909,7 @@ pub trait NexusExternalApi { }] async fn scim_v2_patch_user( rqctx: RequestContext, - path_params: Path, + path_params: Path, body: TypedBody, ) -> Result, HttpError>; @@ -874,7 +921,7 @@ pub trait NexusExternalApi { }] async fn scim_v2_delete_user( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; // SCIM group endpoints @@ -898,7 +945,7 @@ pub trait NexusExternalApi { }] async fn scim_v2_get_group( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, ) -> Result, HttpError>; @@ -921,7 +968,7 @@ pub trait NexusExternalApi { }] async fn scim_v2_put_group( rqctx: RequestContext, - path_params: Path, + path_params: Path, body: TypedBody, ) -> Result, HttpError>; @@ -933,7 +980,7 @@ pub trait NexusExternalApi { }] async fn scim_v2_patch_group( rqctx: RequestContext, - path_params: Path, + path_params: Path, body: TypedBody, ) -> Result, HttpError>; @@ -945,7 +992,7 @@ pub trait NexusExternalApi { }] async fn scim_v2_delete_group( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; // Projects @@ -959,7 +1006,7 @@ pub trait NexusExternalApi { async fn project_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Create project #[endpoint { @@ -969,8 +1016,8 @@ pub trait NexusExternalApi { }] async fn project_create( rqctx: RequestContext, - new_project: TypedBody, - ) -> Result, HttpError>; + new_project: TypedBody, + ) -> Result, HttpError>; /// Fetch project #[endpoint { @@ -980,8 +1027,8 @@ pub trait NexusExternalApi { }] async fn project_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// Delete project #[endpoint { @@ -991,7 +1038,7 @@ pub trait NexusExternalApi { }] async fn project_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; // TODO-correctness: Is it valid for PUT to accept application/json that's @@ -1007,9 +1054,9 @@ pub trait NexusExternalApi { }] async fn project_update( rqctx: RequestContext, - path_params: Path, - updated_project: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + updated_project: TypedBody, + ) -> Result, HttpError>; /// Fetch project's IAM policy #[endpoint { @@ -1019,8 +1066,11 @@ pub trait NexusExternalApi { }] async fn project_policy_view( rqctx: RequestContext, - path_params: Path, - ) -> Result>, HttpError>; + path_params: Path, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Update project's IAM policy #[endpoint { @@ -1030,31 +1080,31 @@ pub trait NexusExternalApi { }] async fn project_policy_update( rqctx: RequestContext, - path_params: Path, - new_policy: TypedBody>, - ) -> Result>, HttpError>; + path_params: Path, + new_policy: TypedBody< + latest::policy::Policy, + >, + ) -> Result< + HttpResponseOk>, + HttpError, + >; // IP Pools /// List IP pools #[endpoint { - operation_id = "project_ip_pool_list", method = GET, path = "/v1/ip-pools", - tags = ["projects"], - versions = ..VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE, + tags = ["ip-pools"], + versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn v2025122300_project_ip_pool_list( + async fn ip_pool_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> - { - let page = Self::ip_pool_list(rqctx, query_params).await?.0; - Ok(HttpResponseOk(ResultsPage { - items: page.items.into_iter().map(Into::into).collect(), - next_page: page.next_page, - })) - } + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// List IP pools #[endpoint { @@ -1064,41 +1114,49 @@ pub trait NexusExternalApi { tags = ["projects"], versions = VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_project_ip_pool_list( + async fn project_ip_pool_list_v2026_01_01_00( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> { + ) -> Result< + HttpResponseOk>, + HttpError, + > { Self::ip_pool_list(rqctx, query_params).await } /// List IP pools #[endpoint { + operation_id = "project_ip_pool_list", method = GET, path = "/v1/ip-pools", - tags = ["ip-pools"], - versions = VERSION_RENAME_POOL_ENDPOINTS.., + tags = ["projects"], + versions = ..VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE, }] - async fn ip_pool_list( + async fn project_ip_pool_list_v2025_11_20_00( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + > { + let page = Self::ip_pool_list(rqctx, query_params).await?.0; + Ok(HttpResponseOk(ResultsPage { + items: page.items.into_iter().map(Into::into).collect(), + next_page: page.next_page, + })) + } /// Fetch IP pool #[endpoint { - operation_id = "project_ip_pool_view", method = GET, path = "/v1/ip-pools/{pool}", - tags = ["projects"], - versions = ..VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE, + tags = ["ip-pools"], + versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn v2025122300_project_ip_pool_view( + async fn ip_pool_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { - Self::ip_pool_view(rqctx, path_params) - .await - .map(|resp| resp.map(Into::into)) - } + path_params: Path, + ) -> Result, HttpError>; /// Fetch IP pool #[endpoint { @@ -1108,38 +1166,30 @@ pub trait NexusExternalApi { tags = ["projects"], versions = VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_project_ip_pool_view( + async fn project_ip_pool_view_v2026_01_01_00( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> + { Self::ip_pool_view(rqctx, path_params).await } /// Fetch IP pool #[endpoint { + operation_id = "project_ip_pool_view", method = GET, path = "/v1/ip-pools/{pool}", - tags = ["ip-pools"], - versions = VERSION_RENAME_POOL_ENDPOINTS.., - }] - async fn ip_pool_view( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; - - /// List IP pools - #[endpoint { - operation_id = "ip_pool_list", - method = GET, - path = "/v1/system/ip-pools", - tags = ["system/ip-pools"], - versions = ..VERSION_RENAME_POOL_ENDPOINTS, + tags = ["projects"], + versions = ..VERSION_SILO_PROJECT_IP_VERSION_AND_POOL_TYPE, }] - async fn v2026020900_ip_pool_list( + async fn project_ip_pool_view_v2025_11_20_00( rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError> { - Self::system_ip_pool_list(rqctx, query_params).await + path_params: Path, + ) -> Result, HttpError> + { + Self::ip_pool_view(rqctx, path_params) + .await + .map(|resp| resp.map(Into::into)) } /// List IP pools @@ -1152,21 +1202,24 @@ pub trait NexusExternalApi { async fn system_ip_pool_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; - /// Create IP pool + /// List IP pools #[endpoint { - operation_id = "ip_pool_create", - method = POST, + operation_id = "ip_pool_list", + method = GET, path = "/v1/system/ip-pools", tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_create( + async fn ip_pool_list_v2025_11_20_00( rqctx: RequestContext, - pool_params: TypedBody, - ) -> Result, HttpError> { - Self::system_ip_pool_create(rqctx, pool_params).await + query_params: Query, + ) -> Result< + HttpResponseOk>, + HttpError, + > { + Self::system_ip_pool_list(rqctx, query_params).await } /// Create IP pool @@ -1178,22 +1231,23 @@ pub trait NexusExternalApi { }] async fn system_ip_pool_create( rqctx: RequestContext, - pool_params: TypedBody, - ) -> Result, HttpError>; + pool_params: TypedBody, + ) -> Result, HttpError>; - /// Fetch IP pool + /// Create IP pool #[endpoint { - operation_id = "ip_pool_view", - method = GET, - path = "/v1/system/ip-pools/{pool}", + operation_id = "ip_pool_create", + method = POST, + path = "/v1/system/ip-pools", tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_view( + async fn ip_pool_create_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { - Self::system_ip_pool_view(rqctx, path_params).await + pool_params: TypedBody, + ) -> Result, HttpError> + { + Self::system_ip_pool_create(rqctx, pool_params).await } /// Fetch IP pool @@ -1205,22 +1259,23 @@ pub trait NexusExternalApi { }] async fn system_ip_pool_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; - /// Delete IP pool + /// Fetch IP pool #[endpoint { - operation_id = "ip_pool_delete", - method = DELETE, + operation_id = "ip_pool_view", + method = GET, path = "/v1/system/ip-pools/{pool}", tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_delete( + async fn ip_pool_view_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - ) -> Result { - Self::system_ip_pool_delete(rqctx, path_params).await + path_params: Path, + ) -> Result, HttpError> + { + Self::system_ip_pool_view(rqctx, path_params).await } /// Delete IP pool @@ -1232,23 +1287,22 @@ pub trait NexusExternalApi { }] async fn system_ip_pool_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; - /// Update IP pool + /// Delete IP pool #[endpoint { - operation_id = "ip_pool_update", - method = PUT, + operation_id = "ip_pool_delete", + method = DELETE, path = "/v1/system/ip-pools/{pool}", tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_update( + async fn ip_pool_delete_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - updates: TypedBody, - ) -> Result, HttpError> { - Self::system_ip_pool_update(rqctx, path_params, updates).await + path_params: Path, + ) -> Result { + Self::system_ip_pool_delete(rqctx, path_params).await } /// Update IP pool @@ -1260,23 +1314,25 @@ pub trait NexusExternalApi { }] async fn system_ip_pool_update( rqctx: RequestContext, - path_params: Path, - updates: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + updates: TypedBody, + ) -> Result, HttpError>; - /// Fetch IP pool utilization + /// Update IP pool #[endpoint { - operation_id = "ip_pool_utilization_view", - method = GET, - path = "/v1/system/ip-pools/{pool}/utilization", + operation_id = "ip_pool_update", + method = PUT, + path = "/v1/system/ip-pools/{pool}", tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_utilization_view( + async fn ip_pool_update_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { - Self::system_ip_pool_utilization_view(rqctx, path_params).await + path_params: Path, + updates: TypedBody, + ) -> Result, HttpError> + { + Self::system_ip_pool_update(rqctx, path_params, updates).await } /// Fetch IP pool utilization @@ -1288,24 +1344,25 @@ pub trait NexusExternalApi { }] async fn system_ip_pool_utilization_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; - /// List IP pool's linked silos + /// Fetch IP pool utilization #[endpoint { - operation_id = "ip_pool_silo_list", + operation_id = "ip_pool_utilization_view", method = GET, - path = "/v1/system/ip-pools/{pool}/silos", + path = "/v1/system/ip-pools/{pool}/utilization", tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_silo_list( + async fn ip_pool_utilization_view_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result>, HttpError> - { - Self::system_ip_pool_silo_list(rqctx, path_params, query_params).await + path_params: Path, + ) -> Result< + HttpResponseOk, + HttpError, + > { + Self::system_ip_pool_utilization_view(rqctx, path_params).await } /// List IP pool's linked silos @@ -1317,38 +1374,40 @@ pub trait NexusExternalApi { }] async fn system_ip_pool_silo_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, // paginating by resource_id because they're unique per pool. most robust // option would be to paginate by a composite key representing the (pool, // resource_type, resource) query_params: Query, - // TODO: this could just list views::Silo -- it's not like knowing silo_id + // TODO: this could just list latest::silo::Silo -- it's not like knowing silo_id // and nothing else is particularly useful -- except we also want to say // whether the pool is marked default on each silo. So one option would // be to do the same as we did with SiloIpPool -- include is_default on // whatever the thing is. Still... all we'd have to do to make this usable // in both places would be to make it { ...IpPool, silo_id, silo_name, // is_default } - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; - /// Link IP pool to silo - /// - /// Users in linked silos can allocate external IPs from this pool for their - /// instances. A silo can have at most one default pool. IPs are allocated - /// from the default pool when users ask for one without specifying a pool. + /// List IP pool's linked silos #[endpoint { - operation_id = "ip_pool_silo_link", - method = POST, + operation_id = "ip_pool_silo_list", + method = GET, path = "/v1/system/ip-pools/{pool}/silos", tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_silo_link( + async fn ip_pool_silo_list_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - resource_assoc: TypedBody, - ) -> Result, HttpError> { - Self::system_ip_pool_silo_link(rqctx, path_params, resource_assoc).await + path_params: Path, + query_params: Query, + ) -> Result< + HttpResponseOk>, + HttpError, + > { + Self::system_ip_pool_silo_list(rqctx, path_params, query_params).await } /// Link IP pool to silo @@ -1364,25 +1423,36 @@ pub trait NexusExternalApi { }] async fn system_ip_pool_silo_link( rqctx: RequestContext, - path_params: Path, - resource_assoc: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + resource_assoc: TypedBody, + ) -> Result, HttpError>; - /// Unlink IP pool from silo + /// Link IP pool to silo /// - /// Will fail if there are any outstanding IPs allocated in the silo. + /// Users in linked silos can allocate external IPs from this pool for their + /// instances. A silo can have at most one default pool. IPs are allocated + /// from the default pool when users ask for one without specifying a pool. #[endpoint { - operation_id = "ip_pool_silo_unlink", - method = DELETE, - path = "/v1/system/ip-pools/{pool}/silos/{silo}", + operation_id = "ip_pool_silo_link", + method = POST, + path = "/v1/system/ip-pools/{pool}/silos", tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_silo_unlink( + async fn ip_pool_silo_link_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - ) -> Result { - Self::system_ip_pool_silo_unlink(rqctx, path_params).await + path_params: Path, + resource_assoc: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { + Self::system_ip_pool_silo_link( + rqctx, + path_params, + resource_assoc.map(Into::into), + ) + .await } /// Unlink IP pool from silo @@ -1396,29 +1466,24 @@ pub trait NexusExternalApi { }] async fn system_ip_pool_silo_unlink( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; - /// Make IP pool default for silo + /// Unlink IP pool from silo /// - /// When a user asks for an IP (e.g., at instance create time) without - /// specifying a pool, the IP comes from the default pool if a default is - /// configured. When a pool is made the default for a silo, any existing - /// default will remain linked to the silo, but will no longer be the - /// default. + /// Will fail if there are any outstanding IPs allocated in the silo. #[endpoint { - operation_id = "ip_pool_silo_update", - method = PUT, + operation_id = "ip_pool_silo_unlink", + method = DELETE, path = "/v1/system/ip-pools/{pool}/silos/{silo}", tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_silo_update( + async fn ip_pool_silo_unlink_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - update: TypedBody, - ) -> Result, HttpError> { - Self::system_ip_pool_silo_update(rqctx, path_params, update).await + path_params: Path, + ) -> Result { + Self::system_ip_pool_silo_unlink(rqctx, path_params).await } /// Make IP pool default for silo @@ -1436,22 +1501,38 @@ pub trait NexusExternalApi { }] async fn system_ip_pool_silo_update( rqctx: RequestContext, - path_params: Path, - update: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + update: TypedBody, + ) -> Result, HttpError>; - /// Fetch Oxide service IP pool + /// Make IP pool default for silo + /// + /// When a user asks for an IP (e.g., at instance create time) without + /// specifying a pool, the IP comes from the default pool if a default is + /// configured. When a pool is made the default for a silo, any existing + /// default will remain linked to the silo, but will no longer be the + /// default. #[endpoint { - operation_id = "ip_pool_service_view", - method = GET, - path = "/v1/system/ip-pools-service", + operation_id = "ip_pool_silo_update", + method = PUT, + path = "/v1/system/ip-pools/{pool}/silos/{silo}", tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_service_view( + async fn ip_pool_silo_update_v2025_11_20_00( rqctx: RequestContext, - ) -> Result, HttpError> { - Self::system_ip_pool_service_view(rqctx).await + path_params: Path, + update: TypedBody, + ) -> Result< + HttpResponseOk, + HttpError, + > { + Self::system_ip_pool_silo_update( + rqctx, + path_params, + update.map(Into::into), + ) + .await } /// Fetch Oxide service IP pool @@ -1463,25 +1544,21 @@ pub trait NexusExternalApi { }] async fn system_ip_pool_service_view( rqctx: RequestContext, - ) -> Result, HttpError>; + ) -> Result, HttpError>; - /// List ranges for IP pool - /// - /// Ranges are ordered by their first address. + /// Fetch Oxide service IP pool #[endpoint { - operation_id = "ip_pool_range_list", + operation_id = "ip_pool_service_view", method = GET, - path = "/v1/system/ip-pools/{pool}/ranges", + path = "/v1/system/ip-pools-service", tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_range_list( + async fn ip_pool_service_view_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result>, HttpError> + ) -> Result, HttpError> { - Self::system_ip_pool_range_list(rqctx, path_params, query_params).await + Self::system_ip_pool_service_view(rqctx).await } /// List ranges for IP pool @@ -1495,9 +1572,33 @@ pub trait NexusExternalApi { }] async fn system_ip_pool_range_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, + query_params: Query, + ) -> Result< + HttpResponseOk>, + HttpError, + >; + + /// List ranges for IP pool + /// + /// Ranges are ordered by their first address. + #[endpoint { + operation_id = "ip_pool_range_list", + method = GET, + path = "/v1/system/ip-pools/{pool}/ranges", + tags = ["system/ip-pools"], + versions = ..VERSION_RENAME_POOL_ENDPOINTS, + }] + async fn ip_pool_range_list_v2025_11_20_00( + rqctx: RequestContext, + path_params: Path, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + > { + Self::system_ip_pool_range_list(rqctx, path_params, query_params).await + } /// Add range to IP pool /// @@ -1508,19 +1609,16 @@ pub trait NexusExternalApi { /// ASM: IPv4 addresses outside 232.0.0.0/8, IPv6 addresses with flag field != 3 /// SSM: IPv4 addresses in 232.0.0.0/8, IPv6 addresses with flag field = 3 #[endpoint { - operation_id = "ip_pool_range_add", method = POST, path = "/v1/system/ip-pools/{pool}/ranges/add", tags = ["system/ip-pools"], - versions = ..VERSION_RENAME_POOL_ENDPOINTS, + versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn v2026020900_ip_pool_range_add( + async fn system_ip_pool_range_add( rqctx: RequestContext, - path_params: Path, - range_params: TypedBody, - ) -> Result, HttpError> { - Self::system_ip_pool_range_add(rqctx, path_params, range_params).await - } + path_params: Path, + range_params: TypedBody, + ) -> Result, HttpError>; /// Add range to IP pool /// @@ -1531,16 +1629,35 @@ pub trait NexusExternalApi { /// ASM: IPv4 addresses outside 232.0.0.0/8, IPv6 addresses with flag field != 3 /// SSM: IPv4 addresses in 232.0.0.0/8, IPv6 addresses with flag field = 3 #[endpoint { + operation_id = "ip_pool_range_add", method = POST, path = "/v1/system/ip-pools/{pool}/ranges/add", tags = ["system/ip-pools"], + versions = ..VERSION_RENAME_POOL_ENDPOINTS, + }] + async fn ip_pool_range_add_v2025_11_20_00( + rqctx: RequestContext, + path_params: Path, + range_params: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { + Self::system_ip_pool_range_add(rqctx, path_params, range_params).await + } + + /// Remove range from IP pool + #[endpoint { + method = POST, + path = "/v1/system/ip-pools/{pool}/ranges/remove", + tags = ["system/ip-pools"], versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn system_ip_pool_range_add( + async fn system_ip_pool_range_remove( rqctx: RequestContext, - path_params: Path, - range_params: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + range_params: TypedBody, + ) -> Result; /// Remove range from IP pool #[endpoint { @@ -1550,27 +1667,31 @@ pub trait NexusExternalApi { tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_range_remove( + async fn ip_pool_range_remove_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - range_params: TypedBody, + path_params: Path, + range_params: TypedBody, ) -> Result { Self::system_ip_pool_range_remove(rqctx, path_params, range_params) .await } - /// Remove range from IP pool + /// List IP ranges for the Oxide service pool + /// + /// Ranges are ordered by their first address. #[endpoint { - method = POST, - path = "/v1/system/ip-pools/{pool}/ranges/remove", + method = GET, + path = "/v1/system/ip-pools-service/ranges", tags = ["system/ip-pools"], versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn system_ip_pool_range_remove( + async fn system_ip_pool_service_range_list( rqctx: RequestContext, - path_params: Path, - range_params: TypedBody, - ) -> Result; + query_params: Query, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// List IP ranges for the Oxide service pool /// @@ -1582,27 +1703,29 @@ pub trait NexusExternalApi { tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_service_range_list( + async fn ip_pool_service_range_list_v2025_11_20_00( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk>, + HttpError, + > { Self::system_ip_pool_service_range_list(rqctx, query_params).await } - /// List IP ranges for the Oxide service pool + /// Add IP range to Oxide service pool /// - /// Ranges are ordered by their first address. + /// IPv6 ranges are not allowed yet. #[endpoint { - method = GET, - path = "/v1/system/ip-pools-service/ranges", + method = POST, + path = "/v1/system/ip-pools-service/ranges/add", tags = ["system/ip-pools"], versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn system_ip_pool_service_range_list( + async fn system_ip_pool_service_range_add( rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError>; + range_params: TypedBody, + ) -> Result, HttpError>; /// Add IP range to Oxide service pool /// @@ -1614,26 +1737,27 @@ pub trait NexusExternalApi { tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_service_range_add( + async fn ip_pool_service_range_add_v2025_11_20_00( rqctx: RequestContext, - range_params: TypedBody, - ) -> Result, HttpError> { + range_params: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { Self::system_ip_pool_service_range_add(rqctx, range_params).await } - /// Add IP range to Oxide service pool - /// - /// IPv6 ranges are not allowed yet. + /// Remove IP range from Oxide service pool #[endpoint { method = POST, - path = "/v1/system/ip-pools-service/ranges/add", + path = "/v1/system/ip-pools-service/ranges/remove", tags = ["system/ip-pools"], versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn system_ip_pool_service_range_add( + async fn system_ip_pool_service_range_remove( rqctx: RequestContext, - range_params: TypedBody, - ) -> Result, HttpError>; + range_params: TypedBody, + ) -> Result; /// Remove IP range from Oxide service pool #[endpoint { @@ -1643,25 +1767,13 @@ pub trait NexusExternalApi { tags = ["system/ip-pools"], versions = ..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_ip_pool_service_range_remove( + async fn ip_pool_service_range_remove_v2025_11_20_00( rqctx: RequestContext, - range_params: TypedBody, + range_params: TypedBody, ) -> Result { Self::system_ip_pool_service_range_remove(rqctx, range_params).await } - /// Remove IP range from Oxide service pool - #[endpoint { - method = POST, - path = "/v1/system/ip-pools-service/ranges/remove", - tags = ["system/ip-pools"], - versions = VERSION_RENAME_POOL_ENDPOINTS.., - }] - async fn system_ip_pool_service_range_remove( - rqctx: RequestContext, - range_params: TypedBody, - ) -> Result; - // Subnet Pools /// List subnet pools @@ -1674,7 +1786,10 @@ pub trait NexusExternalApi { async fn system_subnet_pool_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// List subnet pools #[endpoint { @@ -1684,10 +1799,13 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_REMOVE_SUBNET_POOL_POOL_TYPE..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_subnet_pool_list( + async fn subnet_pool_list_v2026_01_28_00( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> { + ) -> Result< + HttpResponseOk>, + HttpError, + > { Self::system_subnet_pool_list(rqctx, query_params).await } @@ -1699,11 +1817,13 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_REMOVE_SUBNET_POOL_POOL_TYPE, }] - async fn v2026012300_subnet_pool_list( + async fn subnet_pool_list_v2026_01_16_01( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk>, + HttpError, + > { let HttpResponseOk(ResultsPage { items, next_page }) = Self::system_subnet_pool_list(rqctx, query_params).await?; let items = items.into_iter().map(Into::into).collect(); @@ -1719,8 +1839,8 @@ pub trait NexusExternalApi { }] async fn system_subnet_pool_create( rqctx: RequestContext, - pool_params: TypedBody, - ) -> Result, HttpError>; + pool_params: TypedBody, + ) -> Result, HttpError>; /// Create subnet pool #[endpoint { @@ -1730,10 +1850,13 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_REMOVE_SUBNET_POOL_POOL_TYPE..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_subnet_pool_create( + async fn subnet_pool_create_v2026_01_28_00( rqctx: RequestContext, - pool_params: TypedBody, - ) -> Result, HttpError> { + pool_params: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { Self::system_subnet_pool_create(rqctx, pool_params).await } @@ -1746,10 +1869,13 @@ pub trait NexusExternalApi { versions = VERSION_EXTERNAL_SUBNET_ALLOCATOR_UPDATE..VERSION_REMOVE_SUBNET_POOL_POOL_TYPE, }] - async fn v2026012300_subnet_pool_create( + async fn subnet_pool_create_v2026_01_22_01( rqctx: RequestContext, - pool_params: TypedBody, - ) -> Result, HttpError> { + pool_params: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { let HttpResponseCreated(pool) = Self::system_subnet_pool_create(rqctx, pool_params).await?; Ok(HttpResponseCreated(pool.into())) @@ -1764,13 +1890,16 @@ pub trait NexusExternalApi { versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_EXTERNAL_SUBNET_ALLOCATOR_UPDATE, }] - async fn v2026012200_subnet_pool_create( + async fn subnet_pool_create_v2026_01_16_01( rqctx: RequestContext, - pool_params: TypedBody, - ) -> Result, HttpError> { + pool_params: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { let pool_params = pool_params.try_map(TryInto::try_into)?; let HttpResponseCreated(pool) = - Self::v2026012300_subnet_pool_create(rqctx, pool_params).await?; + Self::subnet_pool_create_v2026_01_22_01(rqctx, pool_params).await?; Ok(HttpResponseCreated(pool)) } @@ -1783,8 +1912,8 @@ pub trait NexusExternalApi { }] async fn system_subnet_pool_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// Fetch subnet pool #[endpoint { @@ -1794,10 +1923,13 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_REMOVE_SUBNET_POOL_POOL_TYPE..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_subnet_pool_view( + async fn subnet_pool_view_v2026_01_28_00( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result< + HttpResponseOk, + HttpError, + > { Self::system_subnet_pool_view(rqctx, path_params).await } @@ -1810,10 +1942,13 @@ pub trait NexusExternalApi { versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_REMOVE_SUBNET_POOL_POOL_TYPE, }] - async fn v2026012300_subnet_pool_view( + async fn subnet_pool_view_v2026_01_16_01( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result< + HttpResponseOk, + HttpError, + > { let HttpResponseOk(pool) = Self::system_subnet_pool_view(rqctx, path_params).await?; Ok(HttpResponseOk(pool.into())) @@ -1828,9 +1963,9 @@ pub trait NexusExternalApi { }] async fn system_subnet_pool_update( rqctx: RequestContext, - path_params: Path, - updates: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + updates: TypedBody, + ) -> Result, HttpError>; /// Update subnet pool #[endpoint { @@ -1840,11 +1975,14 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_REMOVE_SUBNET_POOL_POOL_TYPE..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_subnet_pool_update( + async fn subnet_pool_update_v2026_01_28_00( rqctx: RequestContext, - path_params: Path, - updates: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + updates: TypedBody, + ) -> Result< + HttpResponseOk, + HttpError, + > { Self::system_subnet_pool_update(rqctx, path_params, updates).await } @@ -1857,11 +1995,14 @@ pub trait NexusExternalApi { versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_REMOVE_SUBNET_POOL_POOL_TYPE, }] - async fn v2026012300_subnet_pool_update( + async fn subnet_pool_update_v2026_01_16_01( rqctx: RequestContext, - path_params: Path, - updates: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + updates: TypedBody, + ) -> Result< + HttpResponseOk, + HttpError, + > { let HttpResponseOk(pool) = Self::system_subnet_pool_update(rqctx, path_params, updates) .await?; @@ -1870,30 +2011,30 @@ pub trait NexusExternalApi { /// Delete subnet pool #[endpoint { - operation_id = "subnet_pool_delete", method = DELETE, path = "/v1/system/subnet-pools/{pool}", tags = ["system/subnet-pools"], - versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_RENAME_POOL_ENDPOINTS, + versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn v2026020900_subnet_pool_delete( + async fn system_subnet_pool_delete( rqctx: RequestContext, - path_params: Path, - ) -> Result { - Self::system_subnet_pool_delete(rqctx, path_params).await - } + path_params: Path, + ) -> Result; /// Delete subnet pool #[endpoint { + operation_id = "subnet_pool_delete", method = DELETE, path = "/v1/system/subnet-pools/{pool}", tags = ["system/subnet-pools"], - versions = VERSION_RENAME_POOL_ENDPOINTS.., + versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn system_subnet_pool_delete( + async fn subnet_pool_delete_v2026_01_16_01( rqctx: RequestContext, - path_params: Path, - ) -> Result; + path_params: Path, + ) -> Result { + Self::system_subnet_pool_delete(rqctx, path_params).await + } /// List members in subnet pool #[endpoint { @@ -1904,9 +2045,12 @@ pub trait NexusExternalApi { }] async fn system_subnet_pool_member_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// List members in subnet pool #[endpoint { @@ -1916,12 +2060,16 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_REMOVE_SUBNET_POOL_POOL_TYPE..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_subnet_pool_member_list( + async fn subnet_pool_member_list_v2026_01_28_00( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk< + ResultsPage, + >, + HttpError, + > { Self::system_subnet_pool_member_list(rqctx, path_params, query_params) .await } @@ -1944,12 +2092,14 @@ pub trait NexusExternalApi { versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_REMOVE_SUBNET_POOL_POOL_TYPE, }] - async fn v2026012300_subnet_pool_member_list( + async fn subnet_pool_member_list_v2026_01_16_01( _rqctx: RequestContext, - _path_params: Path, + _path_params: Path, _query_params: Query, ) -> Result< - HttpResponseOk>, + HttpResponseOk< + ResultsPage, + >, HttpError, > { Err(HttpError::for_internal_error( @@ -1966,9 +2116,12 @@ pub trait NexusExternalApi { }] async fn system_subnet_pool_member_add( rqctx: RequestContext, - path_params: Path, - subnet_params: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + subnet_params: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + >; /// Add member to subnet pool #[endpoint { @@ -1978,11 +2131,16 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_REMOVE_SUBNET_POOL_POOL_TYPE..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_subnet_pool_member_add( + async fn subnet_pool_member_add_v2026_01_28_00( rqctx: RequestContext, - path_params: Path, - subnet_params: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + subnet_params: TypedBody< + v2026_01_22_00::subnet_pool::SubnetPoolMemberAdd, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + > { Self::system_subnet_pool_member_add(rqctx, path_params, subnet_params) .await } @@ -1996,12 +2154,16 @@ pub trait NexusExternalApi { versions = VERSION_EXTERNAL_SUBNET_ALLOCATOR_UPDATE..VERSION_REMOVE_SUBNET_POOL_POOL_TYPE, }] - async fn v2026012300_subnet_pool_member_add( + async fn subnet_pool_member_add_v2026_01_22_01( rqctx: RequestContext, - path_params: Path, - subnet_params: TypedBody, - ) -> Result, HttpError> - { + path_params: Path, + subnet_params: TypedBody< + v2026_01_22_00::subnet_pool::SubnetPoolMemberAdd, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + > { let HttpResponseCreated(pool) = Self::system_subnet_pool_member_add( rqctx, path_params, @@ -2020,12 +2182,16 @@ pub trait NexusExternalApi { versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_EXTERNAL_SUBNET_ALLOCATOR_UPDATE, }] - async fn v2026012200_subnet_pool_member_add( + async fn subnet_pool_member_add_v2026_01_16_01( rqctx: RequestContext, - path_params: Path, - subnet_params: TypedBody, - ) -> Result, HttpError> - { + path_params: Path, + subnet_params: TypedBody< + v2026_01_16_01::subnet_pool::SubnetPoolMemberAdd, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + > { let HttpResponseCreated(pool) = Self::system_subnet_pool_member_add( rqctx, path_params, @@ -2035,6 +2201,19 @@ pub trait NexusExternalApi { Ok(HttpResponseCreated(pool.into())) } + /// Remove member from subnet pool + #[endpoint { + method = POST, + path = "/v1/system/subnet-pools/{pool}/members/remove", + tags = ["system/subnet-pools"], + versions = VERSION_RENAME_POOL_ENDPOINTS.., + }] + async fn system_subnet_pool_member_remove( + rqctx: RequestContext, + path_params: Path, + subnet_params: TypedBody, + ) -> Result; + /// Remove member from subnet pool #[endpoint { operation_id = "subnet_pool_member_remove", @@ -2043,10 +2222,12 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_subnet_pool_member_remove( + async fn subnet_pool_member_remove_v2026_01_16_01( rqctx: RequestContext, - path_params: Path, - subnet_params: TypedBody, + path_params: Path, + subnet_params: TypedBody< + v2026_01_16_01::subnet_pool::SubnetPoolMemberRemove, + >, ) -> Result { Self::system_subnet_pool_member_remove( rqctx, @@ -2056,18 +2237,21 @@ pub trait NexusExternalApi { .await } - /// Remove member from subnet pool + /// List silos linked to subnet pool #[endpoint { - method = POST, - path = "/v1/system/subnet-pools/{pool}/members/remove", + method = GET, + path = "/v1/system/subnet-pools/{pool}/silos", tags = ["system/subnet-pools"], versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn system_subnet_pool_member_remove( + async fn system_subnet_pool_silo_list( rqctx: RequestContext, - path_params: Path, - subnet_params: TypedBody, - ) -> Result; + path_params: Path, + query_params: Query, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// List silos linked to subnet pool #[endpoint { @@ -2077,29 +2261,20 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_subnet_pool_silo_list( + async fn subnet_pool_silo_list_v2026_01_16_01( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk< + ResultsPage, + >, + HttpError, + > { Self::system_subnet_pool_silo_list(rqctx, path_params, query_params) .await } - /// List silos linked to subnet pool - #[endpoint { - method = GET, - path = "/v1/system/subnet-pools/{pool}/silos", - tags = ["system/subnet-pools"], - versions = VERSION_RENAME_POOL_ENDPOINTS.., - }] - async fn system_subnet_pool_silo_list( - rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result>, HttpError>; - /// List subnet pools linked to a silo #[endpoint { method = GET, @@ -2109,37 +2284,47 @@ pub trait NexusExternalApi { }] async fn silo_subnet_pool_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// List subnet pools #[endpoint { - operation_id = "current_silo_subnet_pool_list", method = GET, path = "/v1/subnet-pools", - tags = ["projects"], - versions = VERSION_ADD_SILO_SUBNET_POOLS..VERSION_RENAME_POOL_ENDPOINTS, + tags = ["subnet-pools"], + versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn v2026020900_current_silo_subnet_pool_list( + async fn subnet_pool_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> - { - Self::subnet_pool_list(rqctx, query_params).await - } + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// List subnet pools #[endpoint { + operation_id = "current_silo_subnet_pool_list", method = GET, path = "/v1/subnet-pools", - tags = ["subnet-pools"], - versions = VERSION_RENAME_POOL_ENDPOINTS.., + tags = ["projects"], + versions = VERSION_ADD_SILO_SUBNET_POOLS..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn subnet_pool_list( + async fn current_silo_subnet_pool_list_v2026_02_06_00( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk< + ResultsPage, + >, + HttpError, + > { + Self::subnet_pool_list(rqctx, query_params).await + } /// Fetch subnet pool #[endpoint { @@ -2150,8 +2335,24 @@ pub trait NexusExternalApi { }] async fn subnet_pool_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; + + /// Link subnet pool to silo + #[endpoint { + method = POST, + path = "/v1/system/subnet-pools/{pool}/silos", + tags = ["system/subnet-pools"], + versions = VERSION_RENAME_POOL_ENDPOINTS.., + }] + async fn system_subnet_pool_silo_link( + rqctx: RequestContext, + path_params: Path, + silo_link: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + >; /// Link subnet pool to silo #[endpoint { @@ -2161,26 +2362,32 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_subnet_pool_silo_link( + async fn subnet_pool_silo_link_v2026_01_16_01( rqctx: RequestContext, - path_params: Path, - silo_link: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + silo_link: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { Self::system_subnet_pool_silo_link(rqctx, path_params, silo_link).await } - /// Link subnet pool to silo + /// Update subnet pool's link to silo #[endpoint { - method = POST, - path = "/v1/system/subnet-pools/{pool}/silos", + method = PUT, + path = "/v1/system/subnet-pools/{pool}/silos/{silo}", tags = ["system/subnet-pools"], versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn system_subnet_pool_silo_link( + async fn system_subnet_pool_silo_update( rqctx: RequestContext, - path_params: Path, - silo_link: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + update: TypedBody, + ) -> Result< + HttpResponseOk, + HttpError, + >; /// Update subnet pool's link to silo #[endpoint { @@ -2190,26 +2397,28 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_subnet_pool_silo_update( + async fn subnet_pool_silo_update_v2026_01_16_01( rqctx: RequestContext, - path_params: Path, - update: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + update: TypedBody, + ) -> Result< + HttpResponseOk, + HttpError, + > { Self::system_subnet_pool_silo_update(rqctx, path_params, update).await } - /// Update subnet pool's link to silo + /// Unlink subnet pool from silo #[endpoint { - method = PUT, + method = DELETE, path = "/v1/system/subnet-pools/{pool}/silos/{silo}", tags = ["system/subnet-pools"], versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn system_subnet_pool_silo_update( + async fn system_subnet_pool_silo_unlink( rqctx: RequestContext, - path_params: Path, - update: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result; /// Unlink subnet pool from silo #[endpoint { @@ -2219,24 +2428,27 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_subnet_pool_silo_unlink( + async fn subnet_pool_silo_unlink_v2026_01_16_01( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { Self::system_subnet_pool_silo_unlink(rqctx, path_params).await } - /// Unlink subnet pool from silo + /// Fetch subnet pool utilization #[endpoint { - method = DELETE, - path = "/v1/system/subnet-pools/{pool}/silos/{silo}", + method = GET, + path = "/v1/system/subnet-pools/{pool}/utilization", tags = ["system/subnet-pools"], versions = VERSION_RENAME_POOL_ENDPOINTS.., }] - async fn system_subnet_pool_silo_unlink( + async fn system_subnet_pool_utilization_view( rqctx: RequestContext, - path_params: Path, - ) -> Result; + path_params: Path, + ) -> Result< + HttpResponseOk, + HttpError, + >; /// Fetch subnet pool utilization #[endpoint { @@ -2246,25 +2458,16 @@ pub trait NexusExternalApi { tags = ["system/subnet-pools"], versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_RENAME_POOL_ENDPOINTS, }] - async fn v2026020900_subnet_pool_utilization_view( + async fn subnet_pool_utilization_view_v2026_01_16_01( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result< + HttpResponseOk, + HttpError, + > { Self::system_subnet_pool_utilization_view(rqctx, path_params).await } - /// Fetch subnet pool utilization - #[endpoint { - method = GET, - path = "/v1/system/subnet-pools/{pool}/utilization", - tags = ["system/subnet-pools"], - versions = VERSION_RENAME_POOL_ENDPOINTS.., - }] - async fn system_subnet_pool_utilization_view( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; - // External Subnets /// List external subnets in a project @@ -2276,8 +2479,13 @@ pub trait NexusExternalApi { }] async fn external_subnet_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Create an external subnet #[endpoint { @@ -2288,9 +2496,12 @@ pub trait NexusExternalApi { }] async fn external_subnet_create( rqctx: RequestContext, - query_params: Query, - subnet_params: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + subnet_params: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + >; /// Create an external subnet #[endpoint { @@ -2301,11 +2512,16 @@ pub trait NexusExternalApi { versions = VERSION_EXTERNAL_SUBNET_ATTACHMENT..VERSION_EXTERNAL_SUBNET_ALLOCATOR_UPDATE, }] - async fn v2026012200_external_subnet_create( + async fn external_subnet_create_v2026_01_16_01( rqctx: RequestContext, - query_params: Query, - subnet_params: TypedBody, - ) -> Result, HttpError> { + query_params: Query, + subnet_params: TypedBody< + v2026_01_16_01::external_subnet::ExternalSubnetCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + > { let subnet_params = subnet_params.try_map(TryInto::try_into)?; Self::external_subnet_create(rqctx, query_params, subnet_params).await } @@ -2319,9 +2535,12 @@ pub trait NexusExternalApi { }] async fn external_subnet_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result< + HttpResponseOk, + HttpError, + >; /// Update an external subnet #[endpoint { @@ -2332,10 +2551,13 @@ pub trait NexusExternalApi { }] async fn external_subnet_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - subnet_params: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + subnet_params: TypedBody, + ) -> Result< + HttpResponseOk, + HttpError, + >; /// Delete an external subnet #[endpoint { @@ -2346,8 +2568,8 @@ pub trait NexusExternalApi { }] async fn external_subnet_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// Attach an external subnet to an instance @@ -2359,10 +2581,13 @@ pub trait NexusExternalApi { }] async fn external_subnet_attach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - attach_params: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + attach_params: TypedBody, + ) -> Result< + HttpResponseAccepted, + HttpError, + >; /// Detach an external subnet from an instance #[endpoint { @@ -2373,9 +2598,12 @@ pub trait NexusExternalApi { }] async fn external_subnet_detach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result< + HttpResponseAccepted, + HttpError, + >; // Floating IP Addresses @@ -2387,8 +2615,13 @@ pub trait NexusExternalApi { }] async fn floating_ip_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Create a floating IP /// @@ -2402,9 +2635,9 @@ pub trait NexusExternalApi { }] async fn floating_ip_create( rqctx: RequestContext, - query_params: Query, - floating_params: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + floating_params: TypedBody, + ) -> Result, HttpError>; /// Create floating IP #[endpoint { @@ -2414,11 +2647,16 @@ pub trait NexusExternalApi { tags = ["floating-ips"], versions = VERSION_RENAME_ADDRESS_SELECTOR_TO_ADDRESS_ALLOCATOR..VERSION_FLOATING_IP_ALLOCATOR_UPDATE, }] - async fn v2026011600_floating_ip_create( + async fn floating_ip_create_v2026_01_16_00( rqctx: RequestContext, - query_params: Query, - floating_params: TypedBody, - ) -> Result, HttpError> { + query_params: Query, + floating_params: TypedBody< + v2026_01_16_00::floating_ip::FloatingIpCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + > { Self::floating_ip_create( rqctx, query_params, @@ -2435,12 +2673,17 @@ pub trait NexusExternalApi { tags = ["floating-ips"], versions = VERSION_POOL_SELECTION_ENUMS..VERSION_RENAME_ADDRESS_SELECTOR_TO_ADDRESS_ALLOCATOR, }] - async fn v2026011501_floating_ip_create( + async fn floating_ip_create_v2026_01_05_00( rqctx: RequestContext, - query_params: Query, - floating_params: TypedBody, - ) -> Result, HttpError> { - Self::v2026011600_floating_ip_create( + query_params: Query, + floating_params: TypedBody< + v2026_01_05_00::floating_ip::FloatingIpCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + > { + Self::floating_ip_create_v2026_01_16_00( rqctx, query_params, floating_params.map(Into::into), @@ -2457,14 +2700,18 @@ pub trait NexusExternalApi { versions = VERSION_IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS ..VERSION_POOL_SELECTION_ENUMS, }] - async fn v2026010300_floating_ip_create( + async fn floating_ip_create_v2025_12_23_00( rqctx: RequestContext, - query_params: Query, - floating_params: TypedBody, - ) -> Result, HttpError> { - let floating_params = - floating_params.try_map(v2026011501::FloatingIpCreate::try_from)?; - Self::v2026011501_floating_ip_create( + query_params: Query, + floating_params: TypedBody< + v2025_12_23_00::floating_ip::FloatingIpCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + > { + let floating_params = floating_params.try_map(TryInto::try_into)?; + Self::floating_ip_create_v2026_01_05_00( rqctx, query_params, floating_params, @@ -2480,17 +2727,20 @@ pub trait NexusExternalApi { tags = ["floating-ips"], versions = ..VERSION_IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS, }] - async fn v2025121200_floating_ip_create( + async fn floating_ip_create_v2025_11_20_00( rqctx: RequestContext, - query_params: Query, - floating_params: TypedBody, - ) -> Result, HttpError> { - let floating_params = - floating_params.map(v2026010300::FloatingIpCreate::from); - Self::v2026010300_floating_ip_create( + query_params: Query, + floating_params: TypedBody< + v2025_11_20_00::floating_ip::FloatingIpCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + > { + Self::floating_ip_create_v2025_12_23_00( rqctx, query_params, - floating_params, + floating_params.map(Into::into), ) .await } @@ -2503,10 +2753,10 @@ pub trait NexusExternalApi { }] async fn floating_ip_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - updated_floating_ip: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + updated_floating_ip: TypedBody, + ) -> Result, HttpError>; /// Delete floating IP #[endpoint { @@ -2516,8 +2766,8 @@ pub trait NexusExternalApi { }] async fn floating_ip_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// Fetch floating IP @@ -2528,9 +2778,9 @@ pub trait NexusExternalApi { }] async fn floating_ip_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; /// Attach floating IP /// @@ -2542,10 +2792,10 @@ pub trait NexusExternalApi { }] async fn floating_ip_attach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - target: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + target: TypedBody, + ) -> Result, HttpError>; /// Detach floating IP /// @@ -2557,15 +2807,30 @@ pub trait NexusExternalApi { }] async fn floating_ip_detach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; // Multicast Groups // // TODO: Consider adding `.map()` to dropshot's `Path` (like `TypedBody`) // to enable inline delegation when path types differ between API versions. + /// List multicast groups + #[endpoint { + method = GET, + path = "/v1/multicast-groups", + tags = ["experimental"], + versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., + }] + async fn multicast_group_list( + rqctx: RequestContext, + query_params: Query, + ) -> Result< + HttpResponseOk>, + HttpError, + >; + /// List multicast groups #[endpoint { method = GET, @@ -2574,11 +2839,11 @@ pub trait NexusExternalApi { operation_id = "multicast_group_list", versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025121200_multicast_group_list( + async fn multicast_group_list_v2025_11_20_00( rqctx: RequestContext, query_params: Query, ) -> Result< - HttpResponseOk>, + HttpResponseOk>, HttpError, > { Self::multicast_group_list(rqctx, query_params).await.map( @@ -2591,18 +2856,6 @@ pub trait NexusExternalApi { ) } - /// List multicast groups - #[endpoint { - method = GET, - path = "/v1/multicast-groups", - tags = ["experimental"], - versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., - }] - async fn multicast_group_list( - rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError>; - /// Create a multicast group /// /// Deprecated: Groups are created implicitly when adding members in newer @@ -2614,11 +2867,13 @@ pub trait NexusExternalApi { tags = ["experimental"], versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025121200_multicast_group_create( + async fn multicast_group_create_v2025_11_20_00( _rqctx: RequestContext, - _new_group: TypedBody, - ) -> Result, HttpError> - { + _new_group: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { Err(HttpError::for_client_error( None, dropshot::ClientErrorStatusCode::GONE, @@ -2629,6 +2884,21 @@ pub trait NexusExternalApi { )) } + /// Fetch multicast group + /// + /// The group can be specified by name, UUID, or multicast IP address. + /// (e.g., "224.1.2.3" or "ff38::1"). + #[endpoint { + method = GET, + path = "/v1/multicast-groups/{multicast_group}", + tags = ["experimental"], + versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., + }] + async fn multicast_group_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError>; + /// Fetch a multicast group /// /// The group can be specified by name or UUID. @@ -2639,31 +2909,19 @@ pub trait NexusExternalApi { operation_id = "multicast_group_view", versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025121200_multicast_group_view( + async fn multicast_group_view_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result< + HttpResponseOk, + HttpError, + > { let path = path_params.map(Into::into); Self::multicast_group_view(rqctx, path) .await .map(|resp| resp.map(Into::into)) } - /// Fetch multicast group - /// - /// The group can be specified by name, UUID, or multicast IP address. - /// (e.g., "224.1.2.3" or "ff38::1"). - #[endpoint { - method = GET, - path = "/v1/multicast-groups/{multicast_group}", - tags = ["experimental"], - versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., - }] - async fn multicast_group_view( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; - /// Update a multicast group /// /// Deprecated: groups are managed implicitly through member operations. @@ -2674,11 +2932,16 @@ pub trait NexusExternalApi { tags = ["experimental"], versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025121200_multicast_group_update( + async fn multicast_group_update_v2025_11_20_00( _rqctx: RequestContext, - _path_params: Path, - _update_params: TypedBody, - ) -> Result, HttpError> { + _path_params: Path, + _update_params: TypedBody< + v2025_11_20_00::multicast::MulticastGroupUpdate, + >, + ) -> Result< + HttpResponseOk, + HttpError, + > { Err(HttpError::for_client_error( None, dropshot::ClientErrorStatusCode::GONE, @@ -2698,9 +2961,9 @@ pub trait NexusExternalApi { tags = ["experimental"], versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025121200_multicast_group_delete( + async fn multicast_group_delete_v2025_11_20_00( _rqctx: RequestContext, - _path_params: Path, + _path_params: Path, ) -> Result { Err(HttpError::for_client_error( None, @@ -2711,6 +2974,24 @@ pub trait NexusExternalApi { )) } + /// List members of multicast group + /// + /// The group can be specified by name, UUID, or multicast IP address. + #[endpoint { + method = GET, + path = "/v1/multicast-groups/{multicast_group}/members", + tags = ["experimental"], + versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., + }] + async fn multicast_group_member_list( + rqctx: RequestContext, + path_params: Path, + query_params: Query, + ) -> Result< + HttpResponseOk>, + HttpError, + >; + /// List members of multicast group /// /// The group can be specified by name or UUID. @@ -2721,12 +3002,14 @@ pub trait NexusExternalApi { operation_id = "multicast_group_member_list", versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025121200_multicast_group_member_list( + async fn multicast_group_member_list_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, ) -> Result< - HttpResponseOk>, + HttpResponseOk< + ResultsPage, + >, HttpError, > { let path = path_params.map(Into::into); @@ -2740,21 +3023,6 @@ pub trait NexusExternalApi { ) } - /// List members of multicast group - /// - /// The group can be specified by name, UUID, or multicast IP address. - #[endpoint { - method = GET, - path = "/v1/multicast-groups/{multicast_group}/members", - tags = ["experimental"], - versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., - }] - async fn multicast_group_member_list( - rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result>, HttpError>; - /// Add instance to multicast group /// /// Deprecated: use the instance join endpoint which supports implicit group @@ -2766,13 +3034,17 @@ pub trait NexusExternalApi { operation_id = "multicast_group_member_add", versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025121200_multicast_group_member_add( + async fn multicast_group_member_add_v2025_11_20_00( _rqctx: RequestContext, - _path_params: Path, - _query_params: Query, - _member_params: TypedBody, - ) -> Result, HttpError> - { + _path_params: Path, + _query_params: Query, + _member_params: TypedBody< + v2025_11_20_00::multicast::MulticastGroupMemberAdd, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + > { Err(HttpError::for_client_error( None, dropshot::ClientErrorStatusCode::GONE, @@ -2793,10 +3065,10 @@ pub trait NexusExternalApi { operation_id = "multicast_group_member_remove", versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025121200_multicast_group_member_remove( + async fn multicast_group_member_remove_v2025_11_20_00( _rqctx: RequestContext, - _path_params: Path, - _query_params: Query, + _path_params: Path, + _query_params: Query, ) -> Result { Err(HttpError::for_client_error( None, @@ -2817,10 +3089,15 @@ pub trait NexusExternalApi { tags = ["experimental"], versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025121200_lookup_multicast_group_by_ip( + async fn lookup_multicast_group_by_ip_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path< + v2025_11_20_00::multicast::MulticastGroupIpLookupPath, + >, + ) -> Result< + HttpResponseOk, + HttpError, + > { let path = path_params.map(Into::into); Self::multicast_group_view(rqctx, path) .await @@ -2831,29 +3108,17 @@ pub trait NexusExternalApi { /// List disks #[endpoint { - operation_id = "disk_list", method = GET, path = "/v1/disks", tags = ["disks"], - versions = ..VERSION_LOCAL_STORAGE, + versions = VERSION_READ_ONLY_DISKS.., }] - async fn v2025112000_disk_list( + async fn disk_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError> { - Self::v2026013000_disk_list(rqctx, query_params).await.and_then( - |HttpResponseOk(page)| { - let items: Result, _> = - page.items.into_iter().map(TryInto::try_into).collect(); - items.map(|items| { - HttpResponseOk(ResultsPage { - next_page: page.next_page, - items, - }) - }) - }, - ) - } + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result>, HttpError>; /// List disks #[endpoint { @@ -2863,10 +3128,15 @@ pub trait NexusExternalApi { tags = ["disks"], versions = VERSION_LOCAL_STORAGE..VERSION_READ_ONLY_DISKS, }] - async fn v2026013000_disk_list( + async fn disk_list_v2025_12_03_00( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError> { + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result< + HttpResponseOk>, + HttpError, + > { Self::disk_list(rqctx, query_params).await.map( |HttpResponseOk(page)| { let items: Vec<_> = @@ -2878,34 +3148,48 @@ pub trait NexusExternalApi { /// List disks #[endpoint { + operation_id = "disk_list", method = GET, path = "/v1/disks", tags = ["disks"], - versions = VERSION_READ_ONLY_DISKS.., + versions = ..VERSION_LOCAL_STORAGE, }] - async fn disk_list( + async fn disk_list_v2025_11_20_00( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result< + HttpResponseOk>, + HttpError, + > { + Self::disk_list_v2025_12_03_00(rqctx, query_params).await.and_then( + |HttpResponseOk(page)| { + let items: Result, _> = + page.items.into_iter().map(TryInto::try_into).collect(); + items.map(|items| { + HttpResponseOk(ResultsPage { + next_page: page.next_page, + items, + }) + }) + }, + ) + } // TODO-correctness See note about instance create. This should be async. - /// Create a disk + /// Create disk #[endpoint { - operation_id = "disk_create", method = POST, path = "/v1/disks", tags = ["disks"], - versions = ..VERSION_LOCAL_STORAGE, + versions = VERSION_READ_ONLY_DISKS_NULLABLE.., }] - async fn v2025112000_disk_create( + async fn disk_create( rqctx: RequestContext, - query_params: Query, - new_disk: TypedBody, - ) -> Result, HttpError> { - Self::disk_create(rqctx, query_params, new_disk.map(Into::into)) - .await - .and_then(|resp| resp.try_map(TryInto::try_into)) - } + query_params: Query, + new_disk: TypedBody, + ) -> Result, HttpError>; // TODO-correctness See note about instance create. This should be async. /// Create disk @@ -2914,16 +3198,14 @@ pub trait NexusExternalApi { method = POST, path = "/v1/disks", tags = ["disks"], - versions = VERSION_LOCAL_STORAGE..VERSION_READ_ONLY_DISKS, + versions = VERSION_READ_ONLY_DISKS..VERSION_READ_ONLY_DISKS_NULLABLE, }] - async fn v2026013000_disk_create( + async fn disk_create_v2026_01_30_01( rqctx: RequestContext, - query_params: Query, - new_disk: TypedBody, - ) -> Result, HttpError> { - Self::disk_create(rqctx, query_params, new_disk.map(Into::into)) - .await - .map(|resp| resp.map(Into::into)) + query_params: Query, + new_disk: TypedBody, + ) -> Result, HttpError> { + Self::disk_create(rqctx, query_params, new_disk.map(Into::into)).await } // TODO-correctness See note about instance create. This should be async. @@ -2933,47 +3215,56 @@ pub trait NexusExternalApi { method = POST, path = "/v1/disks", tags = ["disks"], - versions = VERSION_READ_ONLY_DISKS..VERSION_READ_ONLY_DISKS_NULLABLE, + versions = VERSION_LOCAL_STORAGE..VERSION_READ_ONLY_DISKS, }] - async fn v2026013001_disk_create( + async fn disk_create_v2025_12_03_00( rqctx: RequestContext, - query_params: Query, - new_disk: TypedBody, - ) -> Result, HttpError> { - Self::disk_create(rqctx, query_params, new_disk.map(Into::into)).await + query_params: Query, + new_disk: TypedBody, + ) -> Result, HttpError> + { + Self::disk_create_v2026_01_30_01( + rqctx, + query_params, + new_disk.map(Into::into), + ) + .await + .map(|resp| resp.map(Into::into)) } // TODO-correctness See note about instance create. This should be async. - /// Create disk + /// Create a disk #[endpoint { + operation_id = "disk_create", method = POST, path = "/v1/disks", tags = ["disks"], - versions = VERSION_READ_ONLY_DISKS_NULLABLE.., + versions = ..VERSION_LOCAL_STORAGE, }] - async fn disk_create( + async fn disk_create_v2025_11_20_00( rqctx: RequestContext, - query_params: Query, - new_disk: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + new_disk: TypedBody, + ) -> Result, HttpError> + { + let body = new_disk.map(Into::into); + Self::disk_create_v2025_12_03_00(rqctx, query_params, body) + .await + .and_then(|resp| resp.try_map(TryInto::try_into)) + } /// Fetch disk #[endpoint { - operation_id = "disk_view", method = GET, path = "/v1/disks/{disk}", tags = ["disks"], - versions = ..VERSION_LOCAL_STORAGE, + versions = VERSION_READ_ONLY_DISKS.., }] - async fn v2025112000_disk_view( - rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> { - Self::disk_view(rqctx, path_params, query_params) - .await - .and_then(|resp| resp.try_map(TryInto::try_into)) - } + async fn disk_view( + rqctx: RequestContext, + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; /// Fetch disk #[endpoint { @@ -2983,11 +3274,11 @@ pub trait NexusExternalApi { tags = ["disks"], versions = VERSION_LOCAL_STORAGE..VERSION_READ_ONLY_DISKS, }] - async fn v2026013000_disk_view( + async fn disk_view_v2025_12_03_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ) -> Result, HttpError> { Self::disk_view(rqctx, path_params, query_params) .await .map(|resp| resp.map(Into::into)) @@ -2995,16 +3286,21 @@ pub trait NexusExternalApi { /// Fetch disk #[endpoint { + operation_id = "disk_view", method = GET, path = "/v1/disks/{disk}", tags = ["disks"], - versions = VERSION_READ_ONLY_DISKS.., + versions = ..VERSION_LOCAL_STORAGE, }] - async fn disk_view( + async fn disk_view_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError> { + Self::disk_view(rqctx, path_params, query_params) + .await + .and_then(|resp| resp.try_map(TryInto::try_into)) + } /// Delete disk #[endpoint { @@ -3014,8 +3310,8 @@ pub trait NexusExternalApi { }] async fn disk_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// Start importing blocks into disk @@ -3028,8 +3324,8 @@ pub trait NexusExternalApi { }] async fn disk_bulk_write_import_start( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// Import blocks into disk @@ -3041,9 +3337,9 @@ pub trait NexusExternalApi { }] async fn disk_bulk_write_import( rqctx: RequestContext, - path_params: Path, - query_params: Query, - import_params: TypedBody, + path_params: Path, + query_params: Query, + import_params: TypedBody, ) -> Result; /// Stop importing blocks into disk @@ -3056,8 +3352,8 @@ pub trait NexusExternalApi { }] async fn disk_bulk_write_import_stop( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// Confirm disk block import completion @@ -3068,9 +3364,9 @@ pub trait NexusExternalApi { }] async fn disk_finalize_import( rqctx: RequestContext, - path_params: Path, - query_params: Query, - finalize_params: TypedBody, + path_params: Path, + query_params: Query, + finalize_params: TypedBody, ) -> Result; // Instances @@ -3083,28 +3379,38 @@ pub trait NexusExternalApi { }] async fn instance_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query< + PaginatedByNameOrId, + >, ) -> Result>, HttpError>; /// Create instance + #[endpoint { + method = POST, + path = "/v1/instances", + tags = ["instances"], + versions = VERSION_READ_ONLY_DISKS_NULLABLE.., + }] + async fn instance_create( + rqctx: RequestContext, + query_params: Query, + new_instance: TypedBody, + ) -> Result, HttpError>; + #[endpoint { operation_id = "instance_create", method = POST, path = "/v1/instances", tags = ["instances"], - versions = ..VERSION_LOCAL_STORAGE, + versions = VERSION_READ_ONLY_DISKS..VERSION_READ_ONLY_DISKS_NULLABLE, }] - async fn v2025112000_instance_create( + async fn instance_create_v2026_01_30_01( rqctx: RequestContext, - query_params: Query, - new_instance: TypedBody, + query_params: Query, + new_instance: TypedBody, ) -> Result, HttpError> { - Self::v2025121200_instance_create( - rqctx, - query_params, - new_instance.map(Into::into), - ) - .await + Self::instance_create(rqctx, query_params, new_instance.map(Into::into)) + .await } /// Create instance @@ -3113,14 +3419,14 @@ pub trait NexusExternalApi { method = POST, path = "/v1/instances", tags = ["instances"], - versions = VERSION_LOCAL_STORAGE..VERSION_IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS, + versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES..VERSION_READ_ONLY_DISKS, }] - async fn v2025121200_instance_create( + async fn instance_create_v2026_01_08_00( rqctx: RequestContext, - query_params: Query, - new_instance: TypedBody, + query_params: Query, + new_instance: TypedBody, ) -> Result, HttpError> { - Self::v2026010100_instance_create( + Self::instance_create_v2026_01_30_01( rqctx, query_params, new_instance.map(Into::into), @@ -3134,16 +3440,19 @@ pub trait NexusExternalApi { method = POST, path = "/v1/instances", tags = ["instances"], - versions = - VERSION_IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS..VERSION_DUAL_STACK_NICS, + versions = VERSION_POOL_SELECTION_ENUMS..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2026010100_instance_create( + async fn instance_create_v2026_01_05_00( rqctx: RequestContext, - query_params: Query, - new_instance: TypedBody, + query_params: Query, + new_instance: TypedBody, ) -> Result, HttpError> { - let new_instance = new_instance.try_map(TryInto::try_into)?; - Self::instance_create(rqctx, query_params, new_instance).await + Self::instance_create_v2026_01_08_00( + rqctx, + query_params, + new_instance.map(Into::into), + ) + .await } /// Create instance @@ -3154,13 +3463,14 @@ pub trait NexusExternalApi { tags = ["instances"], versions = VERSION_DUAL_STACK_NICS..VERSION_POOL_SELECTION_ENUMS, }] - async fn v2026010300_instance_create( + async fn instance_create_v2026_01_03_00( rqctx: RequestContext, - query_params: Query, - new_instance: TypedBody, + query_params: Query, + new_instance: TypedBody, ) -> Result, HttpError> { let new_instance = new_instance.try_map(TryInto::try_into)?; - Self::instance_create(rqctx, query_params, new_instance).await + Self::instance_create_v2026_01_05_00(rqctx, query_params, new_instance) + .await } /// Create instance @@ -3169,14 +3479,16 @@ pub trait NexusExternalApi { method = POST, path = "/v1/instances", tags = ["instances"], - versions = VERSION_POOL_SELECTION_ENUMS..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, + versions = + VERSION_IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS..VERSION_DUAL_STACK_NICS, }] - async fn v2026010500_instance_create( + async fn instance_create_v2025_12_23_00( rqctx: RequestContext, - query_params: Query, - new_instance: TypedBody, + query_params: Query, + new_instance: TypedBody, ) -> Result, HttpError> { - Self::instance_create(rqctx, query_params, new_instance.map(Into::into)) + let new_instance = new_instance.try_map(TryInto::try_into)?; + Self::instance_create_v2026_01_03_00(rqctx, query_params, new_instance) .await } @@ -3186,46 +3498,42 @@ pub trait NexusExternalApi { method = POST, path = "/v1/instances", tags = ["instances"], - versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES..VERSION_READ_ONLY_DISKS, + versions = VERSION_LOCAL_STORAGE..VERSION_IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS, }] - async fn v2026013000_instance_create( + async fn instance_create_v2025_12_03_00( rqctx: RequestContext, - query_params: Query, - new_instance: TypedBody, + query_params: Query, + new_instance: TypedBody, ) -> Result, HttpError> { - Self::instance_create(rqctx, query_params, new_instance.map(Into::into)) - .await + Self::instance_create_v2025_12_23_00( + rqctx, + query_params, + new_instance.map(Into::into), + ) + .await } + /// Create instance #[endpoint { operation_id = "instance_create", method = POST, path = "/v1/instances", tags = ["instances"], - versions = VERSION_READ_ONLY_DISKS..VERSION_READ_ONLY_DISKS_NULLABLE, + versions = ..VERSION_LOCAL_STORAGE, }] - async fn v2026013001_instance_create( + async fn instance_create_v2025_11_20_00( rqctx: RequestContext, - query_params: Query, - new_instance: TypedBody, + query_params: Query, + new_instance: TypedBody, ) -> Result, HttpError> { - Self::instance_create(rqctx, query_params, new_instance.map(Into::into)) - .await + Self::instance_create_v2025_12_03_00( + rqctx, + query_params, + new_instance.map(Into::into), + ) + .await } - /// Create instance - #[endpoint { - method = POST, - path = "/v1/instances", - tags = ["instances"], - versions = VERSION_READ_ONLY_DISKS_NULLABLE.., - }] - async fn instance_create( - rqctx: RequestContext, - query_params: Query, - new_instance: TypedBody, - ) -> Result, HttpError>; - /// Fetch instance #[endpoint { method = GET, @@ -3234,8 +3542,8 @@ pub trait NexusExternalApi { }] async fn instance_view( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError>; /// Delete instance @@ -3246,10 +3554,24 @@ pub trait NexusExternalApi { }] async fn instance_delete( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result; + /// Update instance + #[endpoint { + method = PUT, + path = "/v1/instances/{instance}", + tags = ["instances"], + versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., + }] + async fn instance_update( + rqctx: RequestContext, + query_params: Query, + path_params: Path, + instance_config: TypedBody, + ) -> Result, HttpError>; + /// Update instance #[endpoint { operation_id = "instance_update", @@ -3258,11 +3580,11 @@ pub trait NexusExternalApi { tags = ["instances"], versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025120300_instance_update( + async fn instance_update_v2025_11_20_00( rqctx: RequestContext, - query_params: Query, - path_params: Path, - instance_config: TypedBody, + query_params: Query, + path_params: Path, + instance_config: TypedBody, ) -> Result, HttpError> { Self::instance_update( rqctx, @@ -3273,20 +3595,6 @@ pub trait NexusExternalApi { .await } - /// Update instance - #[endpoint { - method = PUT, - path = "/v1/instances/{instance}", - tags = ["instances"], - versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., - }] - async fn instance_update( - rqctx: RequestContext, - query_params: Query, - path_params: Path, - instance_config: TypedBody, - ) -> Result, HttpError>; - /// Reboot instance #[endpoint { method = POST, @@ -3295,8 +3603,8 @@ pub trait NexusExternalApi { }] async fn instance_reboot( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError>; /// Boot instance @@ -3307,8 +3615,8 @@ pub trait NexusExternalApi { }] async fn instance_start( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError>; /// Stop instance @@ -3319,8 +3627,8 @@ pub trait NexusExternalApi { }] async fn instance_stop( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError>; /// Fetch instance serial console @@ -3331,9 +3639,12 @@ pub trait NexusExternalApi { }] async fn instance_serial_console( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result< + HttpResponseOk, + HttpError, + >; /// Stream instance serial console #[channel { @@ -3343,8 +3654,10 @@ pub trait NexusExternalApi { }] async fn instance_serial_console_stream( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query< + latest::instance::InstanceSerialConsoleStreamRequest, + >, conn: WebsocketConnection, ) -> WebsocketChannelResult; @@ -3360,40 +3673,26 @@ pub trait NexusExternalApi { }] async fn instance_ssh_public_key_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// List disks for instance #[endpoint { - operation_id = "instance_disk_list", method = GET, path = "/v1/instances/{instance}/disks", tags = ["instances"], - versions = ..VERSION_LOCAL_STORAGE, + versions = VERSION_READ_ONLY_DISKS.., }] - async fn v2025112000_instance_disk_list( + async fn instance_disk_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, - path_params: Path, - ) -> Result>, HttpError> { - Self::instance_disk_list(rqctx, query_params, path_params) - .await - .and_then(|HttpResponseOk(page)| { - let items: Result, _> = - page.items.into_iter().map(TryInto::try_into).collect(); - items.map(|items| { - HttpResponseOk(ResultsPage { - next_page: page.next_page, - items, - }) - }) - }) - } + path_params: Path, + ) -> Result>, HttpError>; /// List disks for instance #[endpoint { @@ -3403,13 +3702,18 @@ pub trait NexusExternalApi { tags = ["instances"], versions = VERSION_LOCAL_STORAGE..VERSION_READ_ONLY_DISKS, }] - async fn v2026013000_instance_disk_list( + async fn instance_disk_list_v2025_12_03_00( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId< + v2025_11_20_00::project::OptionalProjectSelector, + >, >, - path_params: Path, - ) -> Result>, HttpError> { + path_params: Path, + ) -> Result< + HttpResponseOk>, + HttpError, + > { Self::instance_disk_list(rqctx, query_params, path_params).await.map( |HttpResponseOk(page)| { let items: Vec<_> = @@ -3421,18 +3725,51 @@ pub trait NexusExternalApi { /// List disks for instance #[endpoint { + operation_id = "instance_disk_list", method = GET, path = "/v1/instances/{instance}/disks", tags = ["instances"], - versions = VERSION_READ_ONLY_DISKS.., + versions = ..VERSION_LOCAL_STORAGE, }] - async fn instance_disk_list( + async fn instance_disk_list_v2025_11_20_00( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId< + v2025_11_20_00::project::OptionalProjectSelector, + >, >, - path_params: Path, - ) -> Result>, HttpError>; + path_params: Path, + ) -> Result< + HttpResponseOk>, + HttpError, + > { + Self::instance_disk_list(rqctx, query_params, path_params) + .await + .and_then(|HttpResponseOk(page)| { + let items: Result, _> = + page.items.into_iter().map(TryInto::try_into).collect(); + items.map(|items| { + HttpResponseOk(ResultsPage { + next_page: page.next_page, + items, + }) + }) + }) + } + + /// Attach disk to instance + #[endpoint { + method = POST, + path = "/v1/instances/{instance}/disks/attach", + tags = ["instances"], + versions = VERSION_READ_ONLY_DISKS.., + }] + async fn instance_disk_attach( + rqctx: RequestContext, + path_params: Path, + query_params: Query, + disk_to_attach: TypedBody, + ) -> Result, HttpError>; /// Attach disk to instance #[endpoint { @@ -3440,14 +3777,15 @@ pub trait NexusExternalApi { method = POST, path = "/v1/instances/{instance}/disks/attach", tags = ["instances"], - versions = ..VERSION_LOCAL_STORAGE, + versions = VERSION_LOCAL_STORAGE..VERSION_READ_ONLY_DISKS, }] - async fn v2025112000_instance_disk_attach( + async fn instance_disk_attach_v2025_12_03_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - disk_to_attach: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + disk_to_attach: TypedBody, + ) -> Result, HttpError> + { Self::instance_disk_attach( rqctx, path_params, @@ -3455,7 +3793,7 @@ pub trait NexusExternalApi { disk_to_attach, ) .await - .and_then(|resp| resp.try_map(TryInto::try_into)) + .map(|resp| resp.map(Into::into)) } /// Attach disk to instance @@ -3464,14 +3802,15 @@ pub trait NexusExternalApi { method = POST, path = "/v1/instances/{instance}/disks/attach", tags = ["instances"], - versions = VERSION_LOCAL_STORAGE..VERSION_READ_ONLY_DISKS, + versions = ..VERSION_LOCAL_STORAGE, }] - async fn v2026013000_instance_disk_attach( + async fn instance_disk_attach_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - disk_to_attach: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + disk_to_attach: TypedBody, + ) -> Result, HttpError> + { Self::instance_disk_attach( rqctx, path_params, @@ -3479,21 +3818,21 @@ pub trait NexusExternalApi { disk_to_attach, ) .await - .map(|resp| resp.map(Into::into)) + .and_then(|resp| resp.try_map(TryInto::try_into)) } - /// Attach disk to instance + /// Detach disk from instance #[endpoint { method = POST, - path = "/v1/instances/{instance}/disks/attach", + path = "/v1/instances/{instance}/disks/detach", tags = ["instances"], versions = VERSION_READ_ONLY_DISKS.., }] - async fn instance_disk_attach( + async fn instance_disk_detach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - disk_to_attach: TypedBody, + path_params: Path, + query_params: Query, + disk_to_detach: TypedBody, ) -> Result, HttpError>; /// Detach disk from instance @@ -3502,14 +3841,15 @@ pub trait NexusExternalApi { method = POST, path = "/v1/instances/{instance}/disks/detach", tags = ["instances"], - versions = ..VERSION_LOCAL_STORAGE, + versions = VERSION_LOCAL_STORAGE..VERSION_READ_ONLY_DISKS, }] - async fn v2025112000_instance_disk_detach( + async fn instance_disk_detach_v2025_12_03_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - disk_to_detach: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + disk_to_detach: TypedBody, + ) -> Result, HttpError> + { Self::instance_disk_detach( rqctx, path_params, @@ -3517,7 +3857,7 @@ pub trait NexusExternalApi { disk_to_detach, ) .await - .and_then(|resp| resp.try_map(TryInto::try_into)) + .map(|resp| resp.map(Into::into)) } /// Detach disk from instance @@ -3526,14 +3866,15 @@ pub trait NexusExternalApi { method = POST, path = "/v1/instances/{instance}/disks/detach", tags = ["instances"], - versions = VERSION_LOCAL_STORAGE..VERSION_READ_ONLY_DISKS, + versions = ..VERSION_LOCAL_STORAGE, }] - async fn v2026013000_instance_disk_detach( + async fn instance_disk_detach_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - disk_to_detach: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + disk_to_detach: TypedBody, + ) -> Result, HttpError> + { Self::instance_disk_detach( rqctx, path_params, @@ -3541,23 +3882,9 @@ pub trait NexusExternalApi { disk_to_detach, ) .await - .map(|resp| resp.map(Into::into)) + .and_then(|resp| resp.try_map(TryInto::try_into)) } - /// Detach disk from instance - #[endpoint { - method = POST, - path = "/v1/instances/{instance}/disks/detach", - tags = ["instances"], - versions = VERSION_READ_ONLY_DISKS.., - }] - async fn instance_disk_detach( - rqctx: RequestContext, - path_params: Path, - query_params: Query, - disk_to_detach: TypedBody, - ) -> Result, HttpError>; - /// List affinity groups containing instance #[endpoint { method = GET, @@ -3567,10 +3894,13 @@ pub trait NexusExternalApi { async fn instance_affinity_group_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, - path_params: Path, - ) -> Result>, HttpError>; + path_params: Path, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// List anti-affinity groups containing instance #[endpoint { @@ -3581,10 +3911,13 @@ pub trait NexusExternalApi { async fn instance_anti_affinity_group_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, - path_params: Path, - ) -> Result>, HttpError>; + path_params: Path, + ) -> Result< + HttpResponseOk>, + HttpError, + >; // Affinity Groups @@ -3596,8 +3929,13 @@ pub trait NexusExternalApi { }] async fn affinity_group_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Fetch affinity group #[endpoint { @@ -3607,9 +3945,9 @@ pub trait NexusExternalApi { }] async fn affinity_group_view( rqctx: RequestContext, - query_params: Query, - path_params: Path, - ) -> Result, HttpError>; + query_params: Query, + path_params: Path, + ) -> Result, HttpError>; /// List affinity group members #[endpoint { @@ -3620,9 +3958,9 @@ pub trait NexusExternalApi { async fn affinity_group_member_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, - path_params: Path, + path_params: Path, ) -> Result>, HttpError>; /// Fetch affinity group member @@ -3633,8 +3971,8 @@ pub trait NexusExternalApi { }] async fn affinity_group_member_instance_view( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError>; /// Add member to affinity group @@ -3645,8 +3983,8 @@ pub trait NexusExternalApi { }] async fn affinity_group_member_instance_add( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError>; /// Remove member from affinity group @@ -3657,8 +3995,8 @@ pub trait NexusExternalApi { }] async fn affinity_group_member_instance_delete( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result; /// Create affinity group @@ -3669,9 +4007,11 @@ pub trait NexusExternalApi { }] async fn affinity_group_create( rqctx: RequestContext, - query_params: Query, - new_affinity_group_params: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + new_affinity_group_params: TypedBody< + latest::affinity::AffinityGroupCreate, + >, + ) -> Result, HttpError>; /// Update affinity group #[endpoint { @@ -3681,10 +4021,10 @@ pub trait NexusExternalApi { }] async fn affinity_group_update( rqctx: RequestContext, - query_params: Query, - path_params: Path, - updated_group: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + path_params: Path, + updated_group: TypedBody, + ) -> Result, HttpError>; /// Delete affinity group #[endpoint { @@ -3694,8 +4034,8 @@ pub trait NexusExternalApi { }] async fn affinity_group_delete( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result; /// List anti-affinity groups @@ -3706,8 +4046,13 @@ pub trait NexusExternalApi { }] async fn anti_affinity_group_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Fetch anti-affinity group #[endpoint { @@ -3717,9 +4062,9 @@ pub trait NexusExternalApi { }] async fn anti_affinity_group_view( rqctx: RequestContext, - query_params: Query, - path_params: Path, - ) -> Result, HttpError>; + query_params: Query, + path_params: Path, + ) -> Result, HttpError>; /// List anti-affinity group members #[endpoint { @@ -3730,9 +4075,9 @@ pub trait NexusExternalApi { async fn anti_affinity_group_member_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, - path_params: Path, + path_params: Path, ) -> Result>, HttpError>; /// Fetch anti-affinity group member @@ -3743,8 +4088,10 @@ pub trait NexusExternalApi { }] async fn anti_affinity_group_member_instance_view( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path< + latest::affinity::AntiAffinityInstanceGroupMemberPath, + >, ) -> Result, HttpError>; /// Add member to anti-affinity group @@ -3755,8 +4102,10 @@ pub trait NexusExternalApi { }] async fn anti_affinity_group_member_instance_add( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path< + latest::affinity::AntiAffinityInstanceGroupMemberPath, + >, ) -> Result, HttpError>; /// Remove member from anti-affinity group @@ -3767,8 +4116,10 @@ pub trait NexusExternalApi { }] async fn anti_affinity_group_member_instance_delete( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path< + latest::affinity::AntiAffinityInstanceGroupMemberPath, + >, ) -> Result; /// Create anti-affinity group @@ -3779,9 +4130,14 @@ pub trait NexusExternalApi { }] async fn anti_affinity_group_create( rqctx: RequestContext, - query_params: Query, - new_affinity_group_params: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + new_affinity_group_params: TypedBody< + latest::affinity::AntiAffinityGroupCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + >; /// Update anti-affinity group #[endpoint { @@ -3791,10 +4147,10 @@ pub trait NexusExternalApi { }] async fn anti_affinity_group_update( rqctx: RequestContext, - query_params: Query, - path_params: Path, - updated_group: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + path_params: Path, + updated_group: TypedBody, + ) -> Result, HttpError>; /// Delete anti-affinity group #[endpoint { @@ -3804,8 +4160,8 @@ pub trait NexusExternalApi { }] async fn anti_affinity_group_delete( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result; // Certificates @@ -3823,7 +4179,10 @@ pub trait NexusExternalApi { async fn certificate_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Create new system-wide x.509 certificate /// @@ -3836,8 +4195,8 @@ pub trait NexusExternalApi { }] async fn certificate_create( rqctx: RequestContext, - new_cert: TypedBody, - ) -> Result, HttpError>; + new_cert: TypedBody, + ) -> Result, HttpError>; /// Fetch certificate /// @@ -3849,8 +4208,8 @@ pub trait NexusExternalApi { }] async fn certificate_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// Delete certificate /// @@ -3862,7 +4221,7 @@ pub trait NexusExternalApi { }] async fn certificate_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; /// Create address lot @@ -3873,7 +4232,7 @@ pub trait NexusExternalApi { }] async fn networking_address_lot_create( rqctx: RequestContext, - new_address_lot: TypedBody, + new_address_lot: TypedBody, ) -> Result, HttpError>; /// Delete address lot @@ -3884,7 +4243,7 @@ pub trait NexusExternalApi { }] async fn networking_address_lot_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; /// List address lots @@ -3906,7 +4265,7 @@ pub trait NexusExternalApi { }] async fn networking_address_lot_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// List blocks in address lot @@ -3917,7 +4276,7 @@ pub trait NexusExternalApi { }] async fn networking_address_lot_block_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, ) -> Result>, HttpError>; @@ -3929,7 +4288,9 @@ pub trait NexusExternalApi { }] async fn networking_loopback_address_create( rqctx: RequestContext, - new_loopback_address: TypedBody, + new_loopback_address: TypedBody< + latest::networking::LoopbackAddressCreate, + >, ) -> Result, HttpError>; /// Delete loopback address @@ -3940,7 +4301,7 @@ pub trait NexusExternalApi { }] async fn networking_loopback_address_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result; /// List loopback addresses @@ -3954,18 +4315,35 @@ pub trait NexusExternalApi { query_params: Query, ) -> Result>, HttpError>; + /// Create switch port settings + #[endpoint { + method = POST, + path = "/v1/system/networking/switch-port-settings", + tags = ["system/networking"], + versions = VERSION_BGP_UNNUMBERED_PEERS.., + }] + async fn networking_switch_port_settings_create( + rqctx: RequestContext, + new_settings: TypedBody, + ) -> Result, HttpError>; + /// Create switch port settings (old version with required BgpPeer.addr) #[endpoint { + operation_id = "networking_switch_port_settings_create", method = POST, path = "/v1/system/networking/switch-port-settings", tags = ["system/networking"], versions = ..VERSION_BGP_UNNUMBERED_PEERS, }] - async fn v2026010300_networking_switch_port_settings_create( + async fn networking_switch_port_settings_create_v2025_11_20_00( rqctx: RequestContext, - new_settings: TypedBody, - ) -> Result, HttpError> - { + new_settings: TypedBody< + v2025_11_20_00::networking::SwitchPortSettingsCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + > { match Self::networking_switch_port_settings_create( rqctx, new_settings.map(Into::into), @@ -3974,22 +4352,10 @@ pub trait NexusExternalApi { { Ok(HttpResponseCreated(result)) => { Ok(HttpResponseCreated(result.try_into()?)) - } - Err(e) => Err(e), - } - } - - /// Create switch port settings - #[endpoint { - method = POST, - path = "/v1/system/networking/switch-port-settings", - tags = ["system/networking"], - versions = VERSION_BGP_UNNUMBERED_PEERS.., - }] - async fn networking_switch_port_settings_create( - rqctx: RequestContext, - new_settings: TypedBody, - ) -> Result, HttpError>; + } + Err(e) => Err(e), + } + } /// Delete switch port settings #[endpoint { @@ -3999,7 +4365,7 @@ pub trait NexusExternalApi { }] async fn networking_switch_port_settings_delete( rqctx: RequestContext, - query_params: Query, + query_params: Query, ) -> Result; /// List switch port settings @@ -4011,25 +4377,40 @@ pub trait NexusExternalApi { async fn networking_switch_port_settings_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, ) -> Result< HttpResponseOk>, HttpError, >; + /// Get information about switch port + #[endpoint { + method = GET, + path = "/v1/system/networking/switch-port-settings/{port}", + tags = ["system/networking"], + versions = VERSION_BGP_UNNUMBERED_PEERS.., + }] + async fn networking_switch_port_settings_view( + rqctx: RequestContext, + path_params: Path, + ) -> Result, HttpError>; + /// Get information about switch port (old version with required BgpPeer.addr) #[endpoint { + operation_id = "networking_switch_port_settings_view", method = GET, path = "/v1/system/networking/switch-port-settings/{port}", tags = ["system/networking"], versions = ..VERSION_BGP_UNNUMBERED_PEERS, }] - async fn v2026010300_networking_switch_port_settings_view( + async fn networking_switch_port_settings_view_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> - { + path_params: Path, + ) -> Result< + HttpResponseOk, + HttpError, + > { match Self::networking_switch_port_settings_view(rqctx, path_params) .await { @@ -4040,18 +4421,6 @@ pub trait NexusExternalApi { } } - /// Get information about switch port - #[endpoint { - method = GET, - path = "/v1/system/networking/switch-port-settings/{port}", - tags = ["system/networking"], - versions = VERSION_BGP_UNNUMBERED_PEERS.., - }] - async fn networking_switch_port_settings_view( - rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; - /// List switch ports #[endpoint { method = GET, @@ -4060,7 +4429,9 @@ pub trait NexusExternalApi { }] async fn networking_switch_port_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query< + PaginatedById, + >, ) -> Result>, HttpError>; /// Get switch port status @@ -4071,9 +4442,9 @@ pub trait NexusExternalApi { }] async fn networking_switch_port_status( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; /// Apply switch port settings #[endpoint { @@ -4083,9 +4454,9 @@ pub trait NexusExternalApi { }] async fn networking_switch_port_apply_settings( rqctx: RequestContext, - path_params: Path, - query_params: Query, - settings_body: TypedBody, + path_params: Path, + query_params: Query, + settings_body: TypedBody, ) -> Result; /// Clear switch port settings @@ -4096,8 +4467,8 @@ pub trait NexusExternalApi { }] async fn networking_switch_port_clear_settings( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// Fetch the LLDP configuration for a switch port @@ -4108,8 +4479,8 @@ pub trait NexusExternalApi { }] async fn networking_switch_port_lldp_config_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError>; /// Update the LLDP configuration for a switch port @@ -4120,8 +4491,8 @@ pub trait NexusExternalApi { }] async fn networking_switch_port_lldp_config_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, config: TypedBody, ) -> Result; @@ -4133,7 +4504,7 @@ pub trait NexusExternalApi { }] async fn networking_switch_port_lldp_neighbors( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, ) -> Result>, HttpError>; @@ -4142,49 +4513,110 @@ pub trait NexusExternalApi { method = POST, path = "/v1/system/networking/bgp", tags = ["system/networking"], - versions = ..VERSION_BGP_UNNUMBERED_PEERS, + versions = VERSION_BGP_UNNUMBERED_PEERS.., }] - async fn v2026010300_networking_bgp_config_create( + async fn networking_bgp_config_create( rqctx: RequestContext, - config: TypedBody, - ) -> Result, HttpError>; + config: TypedBody, + ) -> Result, HttpError>; /// Create new BGP configuration #[endpoint { + operation_id = "networking_bgp_config_create", method = POST, path = "/v1/system/networking/bgp", tags = ["system/networking"], - versions = VERSION_BGP_UNNUMBERED_PEERS.., + versions = ..VERSION_BGP_UNNUMBERED_PEERS, }] - async fn networking_bgp_config_create( + async fn networking_bgp_config_create_v2025_11_20_00( rqctx: RequestContext, - config: TypedBody, - ) -> Result, HttpError>; + config: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { + match Self::networking_bgp_config_create(rqctx, config.map(Into::into)) + .await + { + Ok(HttpResponseCreated(result)) => { + Ok(HttpResponseCreated(result.into())) + } + Err(e) => Err(e), + } + } /// List BGP configurations #[endpoint { method = GET, path = "/v1/system/networking/bgp", tags = ["system/networking"], - versions = ..VERSION_BGP_UNNUMBERED_PEERS, + versions = VERSION_BGP_UNNUMBERED_PEERS.., }] - async fn v2026010300_networking_bgp_config_list( + async fn networking_bgp_config_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// List BGP configurations #[endpoint { + operation_id = "networking_bgp_config_list", method = GET, path = "/v1/system/networking/bgp", tags = ["system/networking"], - versions = VERSION_BGP_UNNUMBERED_PEERS.., + versions = ..VERSION_BGP_UNNUMBERED_PEERS, }] - async fn networking_bgp_config_list( + async fn networking_bgp_config_list_v2025_11_20_00( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + > { + let page = + Self::networking_bgp_config_list(rqctx, query_params).await?.0; + Ok(HttpResponseOk(ResultsPage { + items: page.items.into_iter().map(Into::into).collect(), + next_page: page.next_page, + })) + } + + /// Get BGP peer status + #[endpoint { + method = GET, + path = "/v1/system/networking/bgp-status", + tags = ["system/networking"], + versions = VERSION_BGP_UNNUMBERED_PEERS.., + }] + async fn networking_bgp_status( + rqctx: RequestContext, + ) -> Result>, HttpError>; + + //TODO pagination? the normal by-name/by-id stuff does not work here + /// Get BGP peer status + #[endpoint { + operation_id = "networking_bgp_status", + method = GET, + path = "/v1/system/networking/bgp-status", + tags = ["system/networking"], + versions = VERSION_BGP_PEER_COLLISION_STATE..VERSION_BGP_UNNUMBERED_PEERS, + }] + async fn networking_bgp_status_v2025_12_12_00( + rqctx: RequestContext, + ) -> Result< + HttpResponseOk>, + HttpError, + > { + Ok(HttpResponseOk( + Self::networking_bgp_status(rqctx) + .await? + .0 + .into_iter() + .map(v2025_12_12_00::networking::BgpPeerStatus::from) + .collect(), + )) + } + //TODO pagination? the normal by-name/by-id stuff does not work here #[endpoint { operation_id = "networking_bgp_status", method = GET, @@ -4192,38 +4624,42 @@ pub trait NexusExternalApi { tags = ["system/networking"], versions = ..VERSION_BGP_PEER_COLLISION_STATE, }] - async fn v2025120300_networking_bgp_status( + async fn networking_bgp_status_v2025_11_20_00( rqctx: RequestContext, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk>, + HttpError, + > { let result = Self::networking_bgp_status(rqctx).await?.0; Ok(HttpResponseOk( result .into_iter() - .map(|x| v2025120300::BgpPeerStatus { + .map(|x| v2025_11_20_00::networking::BgpPeerStatus { addr: x.addr, local_asn: x.local_asn, remote_asn: x.remote_asn, state: match x.state { - BgpPeerState::Idle => v2025120300::BgpPeerState::Idle, + BgpPeerState::Idle => { + v2025_11_20_00::networking::BgpPeerState::Idle + } BgpPeerState::Connect => { - v2025120300::BgpPeerState::Connect + v2025_11_20_00::networking::BgpPeerState::Connect } BgpPeerState::Active => { - v2025120300::BgpPeerState::Active + v2025_11_20_00::networking::BgpPeerState::Active } BgpPeerState::OpenSent => { - v2025120300::BgpPeerState::OpenSent + v2025_11_20_00::networking::BgpPeerState::OpenSent } BgpPeerState::OpenConfirm => { - v2025120300::BgpPeerState::OpenConfirm + v2025_11_20_00::networking::BgpPeerState::OpenConfirm } BgpPeerState::ConnectionCollision | BgpPeerState::SessionSetup => { - v2025120300::BgpPeerState::SessionSetup + v2025_11_20_00::networking::BgpPeerState::SessionSetup } BgpPeerState::Established => { - v2025120300::BgpPeerState::Established + v2025_11_20_00::networking::BgpPeerState::Established } }, state_duration_millis: x.state_duration_millis, @@ -4233,61 +4669,35 @@ pub trait NexusExternalApi { )) } - //TODO pagination? the normal by-name/by-id stuff does not work here - /// Get BGP peer status - #[endpoint { - method = GET, - path = "/v1/system/networking/bgp-status", - tags = ["system/networking"], - versions = VERSION_BGP_PEER_COLLISION_STATE..VERSION_BGP_UNNUMBERED_PEERS, - }] - async fn networking_bgp_status_v2026020600( - rqctx: RequestContext, - ) -> Result>, HttpError> - { - Ok(HttpResponseOk( - Self::networking_bgp_status(rqctx) - .await? - .0 - .into_iter() - .map(v2026020600::BgpPeerStatus::from) - .collect(), - )) - } - - /// Get BGP peer status + /// List BGP exported routes #[endpoint { method = GET, - path = "/v1/system/networking/bgp-status", + path = "/v1/system/networking/bgp-exported", tags = ["system/networking"], versions = VERSION_BGP_UNNUMBERED_PEERS.., }] - async fn networking_bgp_status( + async fn networking_bgp_exported( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; //TODO pagination? the normal by-name/by-id stuff does not work here /// Get BGP exported routes #[endpoint { + operation_id = "networking_bgp_exported", method = GET, path = "/v1/system/networking/bgp-exported", tags = ["system/networking"], versions = ..VERSION_BGP_UNNUMBERED_PEERS, }] - async fn v2026020600_networking_bgp_exported( - rqctx: RequestContext, - ) -> Result, HttpError>; - - /// List BGP exported routes - #[endpoint { - method = GET, - path = "/v1/system/networking/bgp-exported", - tags = ["system/networking"], - versions = VERSION_BGP_UNNUMBERED_PEERS.., - }] - async fn networking_bgp_exported( + async fn networking_bgp_exported_v2025_11_20_00( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk, + HttpError, + > { + let result = Self::networking_bgp_exported(rqctx).await?.0; + Ok(HttpResponseOk(result.into())) + } /// Get BGP router message history #[endpoint { @@ -4297,7 +4707,7 @@ pub trait NexusExternalApi { }] async fn networking_bgp_message_history( rqctx: RequestContext, - query_params: Query, + query_params: Query, ) -> Result, HttpError>; //TODO pagination? the normal by-name/by-id stuff does not work here @@ -4310,8 +4720,11 @@ pub trait NexusExternalApi { }] async fn networking_bgp_imported_routes_ipv4( rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError>; + query_params: Query, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Get imported IPv4 BGP routes #[endpoint { @@ -4322,7 +4735,7 @@ pub trait NexusExternalApi { }] async fn networking_bgp_imported( rqctx: RequestContext, - query_params: Query, + query_params: Query, ) -> Result>, HttpError>; /// Delete BGP configuration @@ -4333,7 +4746,7 @@ pub trait NexusExternalApi { }] async fn networking_bgp_config_delete( rqctx: RequestContext, - sel: Query, + sel: Query, ) -> Result; /// Update BGP announce set @@ -4347,7 +4760,7 @@ pub trait NexusExternalApi { }] async fn networking_bgp_announce_set_update( rqctx: RequestContext, - config: TypedBody, + config: TypedBody, ) -> Result, HttpError>; /// List BGP announce sets @@ -4369,7 +4782,7 @@ pub trait NexusExternalApi { }] async fn networking_bgp_announce_set_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; // TODO: is pagination necessary here? How large do we expect the list of @@ -4382,7 +4795,7 @@ pub trait NexusExternalApi { }] async fn networking_bgp_announcement_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result>, HttpError>; /// Enable BFD session @@ -4393,7 +4806,7 @@ pub trait NexusExternalApi { }] async fn networking_bfd_enable( rqctx: RequestContext, - session: TypedBody, + session: TypedBody, ) -> Result; /// Disable BFD session @@ -4404,7 +4817,7 @@ pub trait NexusExternalApi { }] async fn networking_bfd_disable( rqctx: RequestContext, - session: TypedBody, + session: TypedBody, ) -> Result; /// Get BFD status @@ -4415,7 +4828,7 @@ pub trait NexusExternalApi { }] async fn networking_bfd_status( rqctx: RequestContext, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Get user-facing services IP allowlist #[endpoint { @@ -4425,7 +4838,7 @@ pub trait NexusExternalApi { }] async fn networking_allow_list_view( rqctx: RequestContext, - ) -> Result, HttpError>; + ) -> Result, HttpError>; /// Update user-facing services IP allowlist #[endpoint { @@ -4435,8 +4848,8 @@ pub trait NexusExternalApi { }] async fn networking_allow_list_update( rqctx: RequestContext, - params: TypedBody, - ) -> Result, HttpError>; + params: TypedBody, + ) -> Result, HttpError>; /// Return whether API services can receive limited ICMP traffic #[endpoint { @@ -4473,9 +4886,9 @@ pub trait NexusExternalApi { async fn image_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Create image /// @@ -4487,9 +4900,9 @@ pub trait NexusExternalApi { }] async fn image_create( rqctx: RequestContext, - query_params: Query, - new_image: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + new_image: TypedBody, + ) -> Result, HttpError>; /// Fetch image /// @@ -4501,9 +4914,9 @@ pub trait NexusExternalApi { }] async fn image_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; /// Delete image /// @@ -4517,8 +4930,8 @@ pub trait NexusExternalApi { }] async fn image_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// Promote project image @@ -4531,9 +4944,9 @@ pub trait NexusExternalApi { }] async fn image_promote( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; /// Demote silo image /// @@ -4545,9 +4958,23 @@ pub trait NexusExternalApi { }] async fn image_demote( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; + + /// List network interfaces + #[endpoint { + method = GET, + path = "/v1/network-interfaces", + tags = ["instances"], + versions = VERSION_DUAL_STACK_NICS.., + }] + async fn instance_network_interface_list( + rqctx: RequestContext, + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result>, HttpError>; /// List network interfaces #[endpoint { @@ -4557,11 +4984,15 @@ pub trait NexusExternalApi { tags = ["instances"], versions = ..VERSION_DUAL_STACK_NICS, }] - async fn v2026010100_instance_network_interface_list( + async fn instance_network_interface_list_v2025_11_20_00( rqctx: RequestContext, - query_params: Query>, + query_params: Query< + PaginatedByNameOrId, + >, ) -> Result< - HttpResponseOk>, + HttpResponseOk< + ResultsPage, + >, HttpError, > { let HttpResponseOk(ResultsPage { next_page, items }) = @@ -4574,17 +5005,20 @@ pub trait NexusExternalApi { .map_err(HttpError::from) } - /// List network interfaces + /// Create network interface #[endpoint { - method = GET, + method = POST, path = "/v1/network-interfaces", tags = ["instances"], versions = VERSION_DUAL_STACK_NICS.., }] - async fn instance_network_interface_list( + async fn instance_network_interface_create( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query, + interface_params: TypedBody< + latest::instance::InstanceNetworkInterfaceCreate, + >, + ) -> Result, HttpError>; /// Create network interface #[endpoint { @@ -4594,14 +5028,14 @@ pub trait NexusExternalApi { tags = ["instances"], versions = ..VERSION_DUAL_STACK_NICS, }] - async fn v2026010100_instance_network_interface_create( + async fn instance_network_interface_create_v2025_11_20_00( rqctx: RequestContext, - query_params: Query, + query_params: Query, interface_params: TypedBody< - v2026010100::InstanceNetworkInterfaceCreate, + v2026_01_01_00_local::InstanceNetworkInterfaceCreate, >, ) -> Result< - HttpResponseCreated, + HttpResponseCreated, HttpError, > { let interface_params = interface_params.try_map(TryInto::try_into)?; @@ -4614,19 +5048,6 @@ pub trait NexusExternalApi { nic.try_into().map(HttpResponseCreated).map_err(HttpError::from) } - /// Create network interface - #[endpoint { - method = POST, - path = "/v1/network-interfaces", - tags = ["instances"], - versions = VERSION_DUAL_STACK_NICS.., - }] - async fn instance_network_interface_create( - rqctx: RequestContext, - query_params: Query, - interface_params: TypedBody, - ) -> Result, HttpError>; - /// Delete network interface /// /// Note that the primary interface for an instance cannot be deleted if there @@ -4640,10 +5061,23 @@ pub trait NexusExternalApi { }] async fn instance_network_interface_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; + /// Fetch network interface + #[endpoint { + method = GET, + path = "/v1/network-interfaces/{interface}", + tags = ["instances"], + versions = VERSION_DUAL_STACK_NICS.., + }] + async fn instance_network_interface_view( + rqctx: RequestContext, + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; + /// Fetch network interface #[endpoint { operation_id = "instance_network_interface_view", @@ -4652,12 +5086,14 @@ pub trait NexusExternalApi { tags = ["instances"], versions = ..VERSION_DUAL_STACK_NICS, }] - async fn v2026010100_instance_network_interface_view( + async fn instance_network_interface_view_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> - { + path_params: Path, + query_params: Query, + ) -> Result< + HttpResponseOk, + HttpError, + > { let HttpResponseOk(nic) = Self::instance_network_interface_view( rqctx, path_params, @@ -4667,17 +5103,20 @@ pub trait NexusExternalApi { nic.try_into().map(HttpResponseOk).map_err(HttpError::from) } - /// Fetch network interface + /// Update network interface #[endpoint { - method = GET, + method = PUT, path = "/v1/network-interfaces/{interface}", tags = ["instances"], versions = VERSION_DUAL_STACK_NICS.., }] - async fn instance_network_interface_view( + async fn instance_network_interface_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, + updated_iface: TypedBody< + latest::instance::InstanceNetworkInterfaceUpdate, + >, ) -> Result, HttpError>; /// Update network interface @@ -4688,13 +5127,17 @@ pub trait NexusExternalApi { tags = ["instances"], versions = ..VERSION_DUAL_STACK_NICS, }] - async fn v2026010100_instance_network_interface_update( + async fn instance_network_interface_update_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - updated_iface: TypedBody, - ) -> Result, HttpError> - { + path_params: Path, + query_params: Query, + updated_iface: TypedBody< + v2025_11_20_00::instance::InstanceNetworkInterfaceUpdate, + >, + ) -> Result< + HttpResponseOk, + HttpError, + > { let HttpResponseOk(nic) = Self::instance_network_interface_update( rqctx, path_params, @@ -4705,20 +5148,6 @@ pub trait NexusExternalApi { nic.try_into().map(HttpResponseOk).map_err(HttpError::from) } - /// Update network interface - #[endpoint { - method = PUT, - path = "/v1/network-interfaces/{interface}", - tags = ["instances"], - versions = VERSION_DUAL_STACK_NICS.., - }] - async fn instance_network_interface_update( - rqctx: RequestContext, - path_params: Path, - query_params: Query, - updated_iface: TypedBody, - ) -> Result, HttpError>; - // External IP addresses for instances /// List external IP addresses @@ -4729,9 +5158,26 @@ pub trait NexusExternalApi { }] async fn instance_external_ip_list( rqctx: RequestContext, - query_params: Query, - path_params: Path, - ) -> Result>, HttpError>; + query_params: Query, + path_params: Path, + ) -> Result< + HttpResponseOk>, + HttpError, + >; + + /// Allocate and attach ephemeral IP to instance + #[endpoint { + method = POST, + path = "/v1/instances/{instance}/external-ips/ephemeral", + tags = ["instances"], + versions = VERSION_POOL_SELECTION_ENUMS.., + }] + async fn instance_ephemeral_ip_attach( + rqctx: RequestContext, + path_params: Path, + query_params: Query, + ip_to_create: TypedBody, + ) -> Result, HttpError>; /// Allocate and attach ephemeral IP to instance #[endpoint { @@ -4739,19 +5185,24 @@ pub trait NexusExternalApi { method = POST, path = "/v1/instances/{instance}/external-ips/ephemeral", tags = ["instances"], - versions = ..VERSION_IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS, + versions = VERSION_IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS + ..VERSION_POOL_SELECTION_ENUMS, }] - async fn v2025121200_instance_ephemeral_ip_attach( + async fn instance_ephemeral_ip_attach_v2025_12_23_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ip_to_create: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ip_to_create: TypedBody, + ) -> Result< + HttpResponseAccepted, + HttpError, + > { + let ip_to_create = ip_to_create.try_map(TryInto::try_into)?; Self::instance_ephemeral_ip_attach( rqctx, path_params, query_params, - ip_to_create.map(Into::into), + ip_to_create, ) .await } @@ -4762,38 +5213,41 @@ pub trait NexusExternalApi { method = POST, path = "/v1/instances/{instance}/external-ips/ephemeral", tags = ["instances"], - versions = VERSION_IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS - ..VERSION_POOL_SELECTION_ENUMS, + versions = ..VERSION_IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS, }] - async fn v2026010300_instance_ephemeral_ip_attach( + async fn instance_ephemeral_ip_attach_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ip_to_create: TypedBody, - ) -> Result, HttpError> { - let ip_to_create = ip_to_create.try_map(TryInto::try_into)?; - Self::instance_ephemeral_ip_attach( + path_params: Path, + query_params: Query, + ip_to_create: TypedBody, + ) -> Result< + HttpResponseAccepted, + HttpError, + > { + Self::instance_ephemeral_ip_attach_v2025_12_23_00( rqctx, path_params, query_params, - ip_to_create, + ip_to_create.map(Into::into), ) .await } - /// Allocate and attach ephemeral IP to instance + /// Detach and deallocate ephemeral IP from instance + /// + /// When an instance has both IPv4 and IPv6 ephemeral IPs, the `ip_version` + /// query parameter must be specified to identify which IP to detach. #[endpoint { - method = POST, + method = DELETE, path = "/v1/instances/{instance}/external-ips/ephemeral", tags = ["instances"], - versions = VERSION_POOL_SELECTION_ENUMS.., + versions = VERSION_DUAL_STACK_EPHEMERAL_IP.., }] - async fn instance_ephemeral_ip_attach( + async fn instance_ephemeral_ip_detach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ip_to_create: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result; /// Detach and deallocate ephemeral IP from instance #[endpoint { @@ -4803,13 +5257,13 @@ pub trait NexusExternalApi { tags = ["instances"], versions = ..VERSION_DUAL_STACK_EPHEMERAL_IP, }] - async fn v2026012200_instance_ephemeral_ip_detach( + async fn instance_ephemeral_ip_detach_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { let query_params = - query_params.map(|q| params::EphemeralIpDetachSelector { + query_params.map(|q| latest::instance::EphemeralIpDetachSelector { project: q.project, ip_version: None, }); @@ -4817,22 +5271,6 @@ pub trait NexusExternalApi { .await } - /// Detach and deallocate ephemeral IP from instance - /// - /// When an instance has both IPv4 and IPv6 ephemeral IPs, the `ip_version` - /// query parameter must be specified to identify which IP to detach. - #[endpoint { - method = DELETE, - path = "/v1/instances/{instance}/external-ips/ephemeral", - tags = ["instances"], - versions = VERSION_DUAL_STACK_EPHEMERAL_IP.., - }] - async fn instance_ephemeral_ip_detach( - rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result; - // Instance External Subnets /// List external subnets attached to instance @@ -4844,12 +5282,33 @@ pub trait NexusExternalApi { }] async fn instance_external_subnet_list( rqctx: RequestContext, - query_params: Query, - path_params: Path, - ) -> Result>, HttpError>; + query_params: Query, + path_params: Path, + ) -> Result< + HttpResponseOk>, + HttpError, + >; // Instance Multicast Groups + /// List multicast groups for an instance + #[endpoint { + method = GET, + path = "/v1/instances/{instance}/multicast-groups", + tags = ["experimental"], + versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., + }] + async fn instance_multicast_group_list( + rqctx: RequestContext, + query_params: Query< + PaginatedById, + >, + path_params: Path, + ) -> Result< + HttpResponseOk>, + HttpError, + >; + /// List multicast groups for an instance #[endpoint { method = GET, @@ -4858,12 +5317,14 @@ pub trait NexusExternalApi { operation_id = "instance_multicast_group_list", versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025121200_instance_multicast_group_list( + async fn instance_multicast_group_list_v2025_11_20_00( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result< - HttpResponseOk>, + HttpResponseOk< + ResultsPage, + >, HttpError, > { let query_params = @@ -4886,19 +5347,29 @@ pub trait NexusExternalApi { }) } - /// List multicast groups for an instance + /// Join multicast group by name, IP address, or UUID + /// + /// Groups can be referenced by name, IP address, or UUID. If the group + /// doesn't exist, it's implicitly created with an auto-allocated IP from a + /// multicast pool linked to the caller's silo. When referencing by UUID, + /// the group must already exist. + /// + /// Source IPs are optional for ASM addresses but required for SSM addresses + /// (232.0.0.0/8 for IPv4, ff3x::/32 for IPv6). Duplicate IPs in the request + /// are automatically deduplicated, with a maximum of 64 source IPs allowed. #[endpoint { - method = GET, - path = "/v1/instances/{instance}/multicast-groups", + method = PUT, + path = "/v1/instances/{instance}/multicast-groups/{multicast_group}", tags = ["experimental"], versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., }] - async fn instance_multicast_group_list( + async fn instance_multicast_group_join( rqctx: RequestContext, - query_params: Query>, - path_params: Path, + path_params: Path, + query_params: Query, + body_params: TypedBody, ) -> Result< - HttpResponseOk>, + HttpResponseCreated, HttpError, >; @@ -4914,34 +5385,29 @@ pub trait NexusExternalApi { operation_id = "instance_multicast_group_join", versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025121200_instance_multicast_group_join( + async fn instance_multicast_group_join_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path< + v2025_11_20_00::multicast::InstanceMulticastGroupPath, + >, + query_params: Query, + ) -> Result< + HttpResponseCreated, + HttpError, + >; - /// Join multicast group by name, IP address, or UUID - /// - /// Groups can be referenced by name, IP address, or UUID. If the group - /// doesn't exist, it's implicitly created with an auto-allocated IP from a - /// multicast pool linked to the caller's silo. When referencing by UUID, - /// the group must already exist. - /// - /// Source IPs are optional for ASM addresses but required for SSM addresses - /// (232.0.0.0/8 for IPv4, ff3x::/32 for IPv6). Duplicate IPs in the request - /// are automatically deduplicated, with a maximum of 64 source IPs allowed. + /// Leave multicast group by name, IP address, or UUID #[endpoint { - method = PUT, + method = DELETE, path = "/v1/instances/{instance}/multicast-groups/{multicast_group}", tags = ["experimental"], versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., }] - async fn instance_multicast_group_join( + async fn instance_multicast_group_leave( rqctx: RequestContext, - path_params: Path, - query_params: Query, - body_params: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result; /// Leave multicast group /// @@ -4953,28 +5419,17 @@ pub trait NexusExternalApi { operation_id = "instance_multicast_group_leave", versions = ..VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES, }] - async fn v2025121200_instance_multicast_group_leave( + async fn instance_multicast_group_leave_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path< + v2025_11_20_00::multicast::InstanceMulticastGroupPath, + >, + query_params: Query, ) -> Result { let path = path_params.map(Into::into); Self::instance_multicast_group_leave(rqctx, path, query_params).await } - /// Leave multicast group by name, IP address, or UUID - #[endpoint { - method = DELETE, - path = "/v1/instances/{instance}/multicast-groups/{multicast_group}", - tags = ["experimental"], - versions = VERSION_MULTICAST_IMPLICIT_LIFECYCLE_UPDATES.., - }] - async fn instance_multicast_group_leave( - rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result; - // Snapshots /// List snapshots @@ -4985,8 +5440,13 @@ pub trait NexusExternalApi { }] async fn snapshot_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Create snapshot /// @@ -4998,9 +5458,9 @@ pub trait NexusExternalApi { }] async fn snapshot_create( rqctx: RequestContext, - query_params: Query, - new_snapshot: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + new_snapshot: TypedBody, + ) -> Result, HttpError>; /// Fetch snapshot #[endpoint { @@ -5010,9 +5470,9 @@ pub trait NexusExternalApi { }] async fn snapshot_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; /// Delete snapshot #[endpoint { @@ -5022,8 +5482,8 @@ pub trait NexusExternalApi { }] async fn snapshot_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; // VPCs @@ -5036,8 +5496,10 @@ pub trait NexusExternalApi { }] async fn vpc_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result>, HttpError>; /// Create VPC #[endpoint { @@ -5047,9 +5509,9 @@ pub trait NexusExternalApi { }] async fn vpc_create( rqctx: RequestContext, - query_params: Query, - body: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + body: TypedBody, + ) -> Result, HttpError>; /// Fetch VPC #[endpoint { @@ -5059,9 +5521,9 @@ pub trait NexusExternalApi { }] async fn vpc_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; /// Update VPC #[endpoint { @@ -5071,10 +5533,10 @@ pub trait NexusExternalApi { }] async fn vpc_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - updated_vpc: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + updated_vpc: TypedBody, + ) -> Result, HttpError>; /// Delete VPC #[endpoint { @@ -5084,8 +5546,8 @@ pub trait NexusExternalApi { }] async fn vpc_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// List subnets @@ -5096,8 +5558,8 @@ pub trait NexusExternalApi { }] async fn vpc_subnet_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query>, + ) -> Result>, HttpError>; /// Create subnet #[endpoint { @@ -5107,9 +5569,9 @@ pub trait NexusExternalApi { }] async fn vpc_subnet_create( rqctx: RequestContext, - query_params: Query, - create_params: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + create_params: TypedBody, + ) -> Result, HttpError>; /// Fetch subnet #[endpoint { @@ -5119,9 +5581,9 @@ pub trait NexusExternalApi { }] async fn vpc_subnet_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; /// Delete subnet #[endpoint { @@ -5131,8 +5593,8 @@ pub trait NexusExternalApi { }] async fn vpc_subnet_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// Update subnet @@ -5143,15 +5605,30 @@ pub trait NexusExternalApi { }] async fn vpc_subnet_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - subnet_params: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + subnet_params: TypedBody, + ) -> Result, HttpError>; // This endpoint is likely temporary. We would rather list all IPs allocated in // a subnet whether they come from NICs or something else. See // https://github.com/oxidecomputer/omicron/issues/2476 + /// List network interfaces + #[endpoint { + method = GET, + path = "/v1/vpc-subnets/{subnet}/network-interfaces", + tags = ["vpcs"], + versions = VERSION_DUAL_STACK_NICS.., + }] + async fn vpc_subnet_list_network_interfaces( + rqctx: RequestContext, + path_params: Path, + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result>, HttpError>; + /// List network interfaces #[endpoint { operation_id = "vpc_subnet_list_network_interfaces", @@ -5160,12 +5637,16 @@ pub trait NexusExternalApi { tags = ["vpcs"], versions = ..VERSION_DUAL_STACK_NICS, }] - async fn v2026010100_vpc_subnet_list_network_interfaces( + async fn vpc_subnet_list_network_interfaces_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query>, + path_params: Path, + query_params: Query< + PaginatedByNameOrId, + >, ) -> Result< - HttpResponseOk>, + HttpResponseOk< + ResultsPage, + >, HttpError, > { let HttpResponseOk(ResultsPage { items, next_page }) = @@ -5183,19 +5664,6 @@ pub trait NexusExternalApi { .map_err(HttpError::from) } - /// List network interfaces - #[endpoint { - method = GET, - path = "/v1/vpc-subnets/{subnet}/network-interfaces", - tags = ["vpcs"], - versions = VERSION_DUAL_STACK_NICS.., - }] - async fn vpc_subnet_list_network_interfaces( - rqctx: RequestContext, - path_params: Path, - query_params: Query>, - ) -> Result>, HttpError>; - // VPC Firewalls /// List firewall rules @@ -5206,7 +5674,7 @@ pub trait NexusExternalApi { }] async fn vpc_firewall_rules_view( rqctx: RequestContext, - query_params: Query, + query_params: Query, ) -> Result, HttpError>; // Note: the limits in the below comment come from the firewall rules model @@ -5233,7 +5701,7 @@ pub trait NexusExternalApi { }] async fn vpc_firewall_rules_update( rqctx: RequestContext, - query_params: Query, + query_params: Query, router_params: TypedBody, ) -> Result, HttpError>; @@ -5247,8 +5715,8 @@ pub trait NexusExternalApi { }] async fn vpc_router_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query>, + ) -> Result>, HttpError>; /// Fetch router #[endpoint { @@ -5258,9 +5726,9 @@ pub trait NexusExternalApi { }] async fn vpc_router_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; /// Create VPC router #[endpoint { @@ -5270,9 +5738,9 @@ pub trait NexusExternalApi { }] async fn vpc_router_create( rqctx: RequestContext, - query_params: Query, - create_params: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + create_params: TypedBody, + ) -> Result, HttpError>; /// Delete router #[endpoint { @@ -5282,8 +5750,8 @@ pub trait NexusExternalApi { }] async fn vpc_router_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// Update router @@ -5294,10 +5762,10 @@ pub trait NexusExternalApi { }] async fn vpc_router_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - router_params: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + router_params: TypedBody, + ) -> Result, HttpError>; /// List routes /// @@ -5309,7 +5777,7 @@ pub trait NexusExternalApi { }] async fn vpc_router_route_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError>; // Vpc Router Routes @@ -5322,8 +5790,8 @@ pub trait NexusExternalApi { }] async fn vpc_router_route_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError>; /// Create route @@ -5334,8 +5802,8 @@ pub trait NexusExternalApi { }] async fn vpc_router_route_create( rqctx: RequestContext, - query_params: Query, - create_params: TypedBody, + query_params: Query, + create_params: TypedBody, ) -> Result, HttpError>; /// Delete route @@ -5346,8 +5814,8 @@ pub trait NexusExternalApi { }] async fn vpc_router_route_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// Update route @@ -5358,9 +5826,9 @@ pub trait NexusExternalApi { }] async fn vpc_router_route_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - router_params: TypedBody, + path_params: Path, + query_params: Query, + router_params: TypedBody, ) -> Result, HttpError>; // Internet gateways @@ -5373,8 +5841,11 @@ pub trait NexusExternalApi { }] async fn internet_gateway_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query>, + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Fetch internet gateway #[endpoint { @@ -5384,9 +5855,12 @@ pub trait NexusExternalApi { }] async fn internet_gateway_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result< + HttpResponseOk, + HttpError, + >; /// Create VPC internet gateway #[endpoint { @@ -5396,9 +5870,14 @@ pub trait NexusExternalApi { }] async fn internet_gateway_create( rqctx: RequestContext, - query_params: Query, - create_params: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + create_params: TypedBody< + latest::internet_gateway::InternetGatewayCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + >; /// Delete internet gateway #[endpoint { @@ -5408,8 +5887,10 @@ pub trait NexusExternalApi { }] async fn internet_gateway_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query< + latest::internet_gateway::InternetGatewayDeleteSelector, + >, ) -> Result; /// List IP pools attached to internet gateway @@ -5421,10 +5902,14 @@ pub trait NexusExternalApi { async fn internet_gateway_ip_pool_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId< + latest::internet_gateway::InternetGatewaySelector, + >, >, ) -> Result< - HttpResponseOk>, + HttpResponseOk< + ResultsPage, + >, HttpError, >; @@ -5436,9 +5921,14 @@ pub trait NexusExternalApi { }] async fn internet_gateway_ip_pool_create( rqctx: RequestContext, - query_params: Query, - create_params: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + create_params: TypedBody< + latest::internet_gateway::InternetGatewayIpPoolCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + >; /// Detach IP pool from internet gateway #[endpoint { @@ -5448,8 +5938,10 @@ pub trait NexusExternalApi { }] async fn internet_gateway_ip_pool_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query< + latest::internet_gateway::DeleteInternetGatewayElementSelector, + >, ) -> Result; /// List IP addresses attached to internet gateway @@ -5461,10 +5953,14 @@ pub trait NexusExternalApi { async fn internet_gateway_ip_address_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId< + latest::internet_gateway::InternetGatewaySelector, + >, >, ) -> Result< - HttpResponseOk>, + HttpResponseOk< + ResultsPage, + >, HttpError, >; @@ -5476,9 +5972,14 @@ pub trait NexusExternalApi { }] async fn internet_gateway_ip_address_create( rqctx: RequestContext, - query_params: Query, - create_params: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + create_params: TypedBody< + latest::internet_gateway::InternetGatewayIpAddressCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + >; /// Detach IP address from internet gateway #[endpoint { @@ -5488,8 +5989,10 @@ pub trait NexusExternalApi { }] async fn internet_gateway_ip_address_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query< + latest::internet_gateway::DeleteInternetGatewayElementSelector, + >, ) -> Result; // Racks @@ -5503,7 +6006,7 @@ pub trait NexusExternalApi { async fn rack_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Fetch rack #[endpoint { @@ -5513,8 +6016,8 @@ pub trait NexusExternalApi { }] async fn rack_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// List uninitialized sleds #[endpoint { @@ -5525,7 +6028,10 @@ pub trait NexusExternalApi { async fn sled_list_uninitialized( rqctx: RequestContext, query: Query>, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Add sled to initialized rack // @@ -5540,8 +6046,8 @@ pub trait NexusExternalApi { }] async fn sled_add( rqctx: RequestContext, - sled: TypedBody, - ) -> Result, HttpError>; + sled: TypedBody, + ) -> Result, HttpError>; /// Add new sleds to rack membership #[endpoint { @@ -5552,9 +6058,9 @@ pub trait NexusExternalApi { }] async fn rack_membership_add_sleds( rqctx: RequestContext, - path_params: Path, - req: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + req: TypedBody, + ) -> Result, HttpError>; /// Abort the latest rack membership change /// @@ -5571,8 +6077,8 @@ pub trait NexusExternalApi { }] async fn rack_membership_abort( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// Retrieve the rack cluster membership status /// @@ -5586,9 +6092,9 @@ pub trait NexusExternalApi { }] async fn rack_membership_status( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; // Sleds @@ -5601,7 +6107,7 @@ pub trait NexusExternalApi { async fn sled_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Fetch sled #[endpoint { @@ -5611,8 +6117,8 @@ pub trait NexusExternalApi { }] async fn sled_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// Set sled provision policy #[endpoint { @@ -5622,9 +6128,12 @@ pub trait NexusExternalApi { }] async fn sled_set_provision_policy( rqctx: RequestContext, - path_params: Path, - new_provision_state: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + new_provision_state: TypedBody, + ) -> Result< + HttpResponseOk, + HttpError, + >; /// List instances running on given sled #[endpoint { @@ -5634,9 +6143,12 @@ pub trait NexusExternalApi { }] async fn sled_instance_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; // Physical disks @@ -5649,7 +6161,10 @@ pub trait NexusExternalApi { async fn physical_disk_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Get physical disk #[endpoint { @@ -5659,8 +6174,8 @@ pub trait NexusExternalApi { }] async fn physical_disk_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; // Switches @@ -5673,7 +6188,7 @@ pub trait NexusExternalApi { async fn switch_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Fetch switch #[endpoint { @@ -5683,8 +6198,8 @@ pub trait NexusExternalApi { }] async fn switch_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// List physical disks attached to sleds #[endpoint { @@ -5694,9 +6209,12 @@ pub trait NexusExternalApi { }] async fn sled_physical_disk_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; // Metrics @@ -5710,11 +6228,14 @@ pub trait NexusExternalApi { }] async fn system_metric( rqctx: RequestContext, - path_params: Path, + path_params: Path, pag_params: Query< - PaginationParams, + PaginationParams< + latest::metrics::ResourceMetrics, + latest::metrics::ResourceMetrics, + >, >, - other_params: Query, + other_params: Query, ) -> Result< HttpResponseOk>, HttpError, @@ -5730,11 +6251,14 @@ pub trait NexusExternalApi { }] async fn silo_metric( rqctx: RequestContext, - path_params: Path, + path_params: Path, pag_params: Query< - PaginationParams, + PaginationParams< + latest::metrics::ResourceMetrics, + latest::metrics::ResourceMetrics, + >, >, - other_params: Query, + other_params: Query, ) -> Result< HttpResponseOk>, HttpError, @@ -5766,8 +6290,8 @@ pub trait NexusExternalApi { }] async fn system_timeseries_query( rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError>; + body: TypedBody, + ) -> Result, HttpError>; // TODO: list endpoint for project-scoped schemas is blocked on // https://github.com/oxidecomputer/omicron/issues/5942: the authz scope for @@ -5785,9 +6309,9 @@ pub trait NexusExternalApi { }] async fn timeseries_query( rqctx: RequestContext, - query_params: Query, - body: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + body: TypedBody, + ) -> Result, HttpError>; // Updates @@ -5802,9 +6326,9 @@ pub trait NexusExternalApi { }] async fn system_update_repository_upload( rqctx: RequestContext, - query: Query, + query: Query, body: StreamingBody, - ) -> Result, HttpError>; + ) -> Result, HttpError>; /// Fetch system release repository by version #[endpoint { @@ -5814,8 +6338,8 @@ pub trait NexusExternalApi { }] async fn system_update_repository_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// List all TUF repositories /// @@ -5829,7 +6353,7 @@ pub trait NexusExternalApi { async fn system_update_repository_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// List root roles in the updates trust store /// @@ -5845,7 +6369,10 @@ pub trait NexusExternalApi { async fn system_update_trust_root_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Add trusted root role to updates trust store #[endpoint { @@ -5855,8 +6382,8 @@ pub trait NexusExternalApi { }] async fn system_update_trust_root_create( rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError>; + body: TypedBody, + ) -> Result, HttpError>; /// Fetch trusted root role #[endpoint { @@ -5866,8 +6393,8 @@ pub trait NexusExternalApi { }] async fn system_update_trust_root_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// Delete trusted root role /// @@ -5881,7 +6408,7 @@ pub trait NexusExternalApi { }] async fn system_update_trust_root_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; /// Set target release @@ -5898,7 +6425,7 @@ pub trait NexusExternalApi { }] async fn target_release_update( rqctx: RequestContext, - params: TypedBody, + params: TypedBody, ) -> Result; /// Fetch system update status @@ -5912,7 +6439,7 @@ pub trait NexusExternalApi { }] async fn system_update_status( rqctx: RequestContext, - ) -> Result, HttpError>; + ) -> Result, HttpError>; // Silo users @@ -5924,8 +6451,8 @@ pub trait NexusExternalApi { }] async fn user_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query>, + ) -> Result>, HttpError>; /// Fetch user #[endpoint { @@ -5935,8 +6462,8 @@ pub trait NexusExternalApi { }] async fn user_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// List user's access tokens #[endpoint { @@ -5946,9 +6473,12 @@ pub trait NexusExternalApi { }] async fn user_token_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// List user's console sessions #[endpoint { @@ -5958,9 +6488,12 @@ pub trait NexusExternalApi { }] async fn user_session_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Log user out /// @@ -5973,7 +6506,7 @@ pub trait NexusExternalApi { }] async fn user_logout( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; // Silo groups @@ -5987,7 +6520,7 @@ pub trait NexusExternalApi { async fn group_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Fetch group #[endpoint { @@ -5997,8 +6530,8 @@ pub trait NexusExternalApi { }] async fn group_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; // Built-in (system) users @@ -6011,7 +6544,7 @@ pub trait NexusExternalApi { async fn user_builtin_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Fetch built-in user #[endpoint { @@ -6021,8 +6554,8 @@ pub trait NexusExternalApi { }] async fn user_builtin_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; // Current user @@ -6034,7 +6567,7 @@ pub trait NexusExternalApi { }] async fn current_user_view( rqctx: RequestContext, - ) -> Result, HttpError>; + ) -> Result, HttpError>; /// Fetch current user's groups #[endpoint { @@ -6045,7 +6578,7 @@ pub trait NexusExternalApi { async fn current_user_groups( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; // Per-user SSH public keys @@ -6060,7 +6593,7 @@ pub trait NexusExternalApi { async fn current_user_ssh_key_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result>, HttpError>; /// Create SSH public key /// @@ -6072,8 +6605,8 @@ pub trait NexusExternalApi { }] async fn current_user_ssh_key_create( rqctx: RequestContext, - new_key: TypedBody, - ) -> Result, HttpError>; + new_key: TypedBody, + ) -> Result, HttpError>; /// Fetch SSH public key /// @@ -6085,8 +6618,8 @@ pub trait NexusExternalApi { }] async fn current_user_ssh_key_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// Delete SSH public key /// @@ -6098,7 +6631,7 @@ pub trait NexusExternalApi { }] async fn current_user_ssh_key_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; /// List access tokens @@ -6112,7 +6645,10 @@ pub trait NexusExternalApi { async fn current_user_access_token_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Delete access token /// @@ -6124,7 +6660,7 @@ pub trait NexusExternalApi { }] async fn current_user_access_token_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; // Support bundles (experimental) @@ -6138,7 +6674,10 @@ pub trait NexusExternalApi { async fn support_bundle_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// View support bundle #[endpoint { @@ -6148,8 +6687,11 @@ pub trait NexusExternalApi { }] async fn support_bundle_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result< + HttpResponseOk, + HttpError, + >; /// Download the index of a support bundle #[endpoint { @@ -6160,7 +6702,7 @@ pub trait NexusExternalApi { async fn support_bundle_index( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// Download the contents of a support bundle @@ -6172,7 +6714,7 @@ pub trait NexusExternalApi { async fn support_bundle_download( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// Download a file within a support bundle @@ -6184,7 +6726,7 @@ pub trait NexusExternalApi { async fn support_bundle_download_file( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// Download the metadata of a support bundle @@ -6196,7 +6738,7 @@ pub trait NexusExternalApi { async fn support_bundle_head( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// Download the metadata of a file within the support bundle @@ -6208,7 +6750,7 @@ pub trait NexusExternalApi { async fn support_bundle_head_file( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// Create a new support bundle @@ -6219,8 +6761,11 @@ pub trait NexusExternalApi { }] async fn support_bundle_create( rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError>; + body: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + >; /// Delete an existing support bundle /// @@ -6233,7 +6778,7 @@ pub trait NexusExternalApi { }] async fn support_bundle_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; /// Update a support bundle @@ -6244,12 +6789,29 @@ pub trait NexusExternalApi { }] async fn support_bundle_update( rqctx: RequestContext, - path_params: Path, - body: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + body: TypedBody, + ) -> Result< + HttpResponseOk, + HttpError, + >; // Probes (experimental) + /// List instrumentation probes + #[endpoint { + method = GET, + path = "/experimental/v1/probes", + tags = ["experimental"], // system/probes: only one tag is allowed + versions = VERSION_DUAL_STACK_NICS.., + }] + async fn probe_list( + rqctx: RequestContext, + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result>, HttpError>; + /// List instrumentation probes #[endpoint { operation_id = "probe_list", @@ -6258,11 +6820,15 @@ pub trait NexusExternalApi { tags = ["experimental"], // system/probes: only one tag is allowed versions = ..VERSION_DUAL_STACK_NICS, }] - async fn v2026010100_probe_list( + async fn probe_list_v2025_11_20_00( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError> - { + query_params: Query< + PaginatedByNameOrId, + >, + ) -> Result< + HttpResponseOk>, + HttpError, + > { let HttpResponseOk(ResultsPage { items, next_page }) = Self::probe_list(rqctx, query_params).await?; items @@ -6273,17 +6839,18 @@ pub trait NexusExternalApi { .map_err(HttpError::from) } - /// List instrumentation probes + /// View instrumentation probe #[endpoint { method = GET, - path = "/experimental/v1/probes", + path = "/experimental/v1/probes/{probe}", tags = ["experimental"], // system/probes: only one tag is allowed versions = VERSION_DUAL_STACK_NICS.., }] - async fn probe_list( + async fn probe_view( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; /// View instrumentation probe #[endpoint { @@ -6293,28 +6860,29 @@ pub trait NexusExternalApi { tags = ["experimental"], // system/probes: only one tag is allowed versions = ..VERSION_DUAL_STACK_NICS, }] - async fn v2026010100_probe_view( + async fn probe_view_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ) -> Result, HttpError> + { let HttpResponseOk(info) = Self::probe_view(rqctx, path_params, query_params).await?; info.try_into().map(HttpResponseOk).map_err(HttpError::from) } - /// View instrumentation probe + /// Create instrumentation probe #[endpoint { - method = GET, - path = "/experimental/v1/probes/{probe}", + method = POST, + path = "/experimental/v1/probes", tags = ["experimental"], // system/probes: only one tag is allowed - versions = VERSION_DUAL_STACK_NICS.., + versions = VERSION_POOL_SELECTION_ENUMS.., }] - async fn probe_view( + async fn probe_create( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + query_params: Query, + new_probe: TypedBody, + ) -> Result, HttpError>; /// Create instrumentation probe #[endpoint { @@ -6324,27 +6892,14 @@ pub trait NexusExternalApi { tags = ["experimental"], // system/probes: only one tag is allowed versions = ..VERSION_POOL_SELECTION_ENUMS, }] - async fn v2026010300_probe_create( + async fn probe_create_v2025_11_20_00( rqctx: RequestContext, - query_params: Query, - new_probe: TypedBody, + query_params: Query, + new_probe: TypedBody, ) -> Result, HttpError> { Self::probe_create(rqctx, query_params, new_probe.map(Into::into)).await } - /// Create instrumentation probe - #[endpoint { - method = POST, - path = "/experimental/v1/probes", - tags = ["experimental"], // system/probes: only one tag is allowed - versions = VERSION_POOL_SELECTION_ENUMS.., - }] - async fn probe_create( - rqctx: RequestContext, - query_params: Query, - new_probe: TypedBody, - ) -> Result, HttpError>; - /// Delete instrumentation probe #[endpoint { method = DELETE, @@ -6353,8 +6908,8 @@ pub trait NexusExternalApi { }] async fn probe_delete( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result; // Audit logging @@ -6385,8 +6940,13 @@ pub trait NexusExternalApi { }] async fn audit_log_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError>; + query_params: Query< + PaginatedByTimeAndId, + >, + ) -> Result< + HttpResponseOk>, + HttpError, + >; // Console API: logins @@ -6399,8 +6959,8 @@ pub trait NexusExternalApi { }] async fn login_saml_begin( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError>; /// Get a redirect straight to the IdP @@ -6416,8 +6976,8 @@ pub trait NexusExternalApi { }] async fn login_saml_redirect( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result; /// Authenticate a user via SAML @@ -6428,7 +6988,7 @@ pub trait NexusExternalApi { }] async fn login_saml( rqctx: RequestContext, - path_params: Path, + path_params: Path, body_bytes: dropshot::UntypedBody, ) -> Result; @@ -6440,8 +7000,8 @@ pub trait NexusExternalApi { }] async fn login_local_begin( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError>; /// Authenticate a user via username and password @@ -6452,8 +7012,8 @@ pub trait NexusExternalApi { }] async fn login_local( rqctx: RequestContext, - path_params: Path, - credentials: TypedBody, + path_params: Path, + credentials: TypedBody, ) -> Result, HttpError>; /// Log user out of web console by deleting session on client and server @@ -6476,7 +7036,7 @@ pub trait NexusExternalApi { }] async fn login_begin( rqctx: RequestContext, - query_params: Query, + query_params: Query, ) -> Result; // Console API: Pages @@ -6492,7 +7052,7 @@ pub trait NexusExternalApi { }] async fn console_projects( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; #[endpoint { @@ -6502,7 +7062,7 @@ pub trait NexusExternalApi { }] async fn console_settings_page( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; #[endpoint { @@ -6512,7 +7072,7 @@ pub trait NexusExternalApi { }] async fn console_system_page( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; #[endpoint { @@ -6522,7 +7082,7 @@ pub trait NexusExternalApi { }] async fn console_lookup( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; #[endpoint { @@ -6578,7 +7138,7 @@ pub trait NexusExternalApi { }] async fn asset( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// Start an OAuth 2.0 Device Authorization Grant @@ -6594,7 +7154,7 @@ pub trait NexusExternalApi { }] async fn device_auth_request( rqctx: RequestContext, - params: TypedBody, + params: TypedBody, ) -> Result, HttpError>; /// Verify an OAuth 2.0 Device Authorization Grant @@ -6642,7 +7202,7 @@ pub trait NexusExternalApi { }] async fn device_auth_confirm( rqctx: RequestContext, - params: TypedBody, + params: TypedBody, ) -> Result; /// Request a device access token @@ -6657,7 +7217,7 @@ pub trait NexusExternalApi { }] async fn device_access_token( rqctx: RequestContext, - params: TypedBody, + params: TypedBody, ) -> Result, HttpError>; // Alerts @@ -6671,10 +7231,10 @@ pub trait NexusExternalApi { async fn alert_class_list( rqctx: RequestContext, pag_params: Query< - PaginationParams, + PaginationParams, >, - filter: Query, - ) -> Result>, HttpError>; + filter: Query, + ) -> Result>, HttpError>; /// List alert receivers #[endpoint { @@ -6685,7 +7245,10 @@ pub trait NexusExternalApi { async fn alert_receiver_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Fetch alert receiver #[endpoint { @@ -6695,8 +7258,8 @@ pub trait NexusExternalApi { }] async fn alert_receiver_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// Delete alert receiver #[endpoint { @@ -6706,7 +7269,7 @@ pub trait NexusExternalApi { }] async fn alert_receiver_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; /// Add alert receiver subscription @@ -6717,9 +7280,12 @@ pub trait NexusExternalApi { }] async fn alert_receiver_subscription_add( rqctx: RequestContext, - path_params: Path, - params: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + params: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + >; /// Remove alert receiver subscription #[endpoint { @@ -6729,7 +7295,7 @@ pub trait NexusExternalApi { }] async fn alert_receiver_subscription_remove( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; /// List delivery attempts to alert receiver @@ -6746,10 +7312,13 @@ pub trait NexusExternalApi { }] async fn alert_delivery_list( rqctx: RequestContext, - path_params: Path, - state_filter: Query, + path_params: Path, + state_filter: Query, pagination: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// Send liveness probe to alert receiver /// @@ -6781,9 +7350,9 @@ pub trait NexusExternalApi { }] async fn alert_receiver_probe( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError>; + path_params: Path, + query_params: Query, + ) -> Result, HttpError>; /// Request re-delivery of alert #[endpoint { @@ -6793,9 +7362,9 @@ pub trait NexusExternalApi { }] async fn alert_delivery_resend( rqctx: RequestContext, - path_params: Path, - receiver: Query, - ) -> Result, HttpError>; + path_params: Path, + receiver: Query, + ) -> Result, HttpError>; // ALERTS: WEBHOOKS @@ -6807,8 +7376,8 @@ pub trait NexusExternalApi { }] async fn webhook_receiver_create( rqctx: RequestContext, - params: TypedBody, - ) -> Result, HttpError>; + params: TypedBody, + ) -> Result, HttpError>; /// Update webhook receiver /// @@ -6822,8 +7391,8 @@ pub trait NexusExternalApi { }] async fn webhook_receiver_update( rqctx: RequestContext, - path_params: Path, - params: TypedBody, + path_params: Path, + params: TypedBody, ) -> Result; /// List webhook receiver secret IDs @@ -6834,8 +7403,8 @@ pub trait NexusExternalApi { }] async fn webhook_secrets_list( rqctx: RequestContext, - query_params: Query, - ) -> Result, HttpError>; + query_params: Query, + ) -> Result, HttpError>; /// Add secret to webhook receiver #[endpoint { @@ -6845,9 +7414,9 @@ pub trait NexusExternalApi { }] async fn webhook_secrets_add( rqctx: RequestContext, - query_params: Query, - params: TypedBody, - ) -> Result, HttpError>; + query_params: Query, + params: TypedBody, + ) -> Result, HttpError>; /// Remove secret from webhook receiver #[endpoint { @@ -6857,7 +7426,7 @@ pub trait NexusExternalApi { }] async fn webhook_secrets_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; } diff --git a/nexus/external-api/src/test_utils.rs b/nexus/external-api/src/test_utils.rs deleted file mode 100644 index fc04d8dae6f..00000000000 --- a/nexus/external-api/src/test_utils.rs +++ /dev/null @@ -1,75 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Common proptest strategies for versioned API type tests. -//! -//! This module provides reusable strategies for generating test data -//! used in property-based tests across version modules. - -use nexus_types::external_api::params; -use omicron_common::api::external::{ - IdentityMetadataCreateParams, IpVersion, Name, NameOrId, -}; -use proptest::prelude::*; -use std::net::IpAddr; -use uuid::Uuid; - -/// Strategy for generating valid `Name` values. -/// -/// Per RFD 4, names must be 1-63 characters and match the regex -/// `[a-z]([-a-z0-9]*[a-z0-9])?`. We use `{0,61}` instead of `*` to -/// bound generation length and avoid filtering out long strings. -pub fn name_strategy() -> impl Strategy { - "[a-z]([-a-z0-9]{0,61}[a-z0-9])?" - .prop_filter_map("valid name", |s| s.parse::().ok()) -} - -/// Strategy for generating `NameOrId` values. -pub fn name_or_id_strategy() -> impl Strategy { - prop_oneof![ - name_strategy().prop_map(NameOrId::Name), - any::().prop_map(|n| NameOrId::Id(Uuid::from_u128(n))), - ] -} - -/// Strategy for generating `IpVersion` values. -pub fn ip_version_strategy() -> impl Strategy { - prop_oneof![Just(IpVersion::V4), Just(IpVersion::V6)] -} - -/// Strategy for generating `PoolSelector` values. -/// -/// Generates one of: -/// - `Explicit { pool }`: Use a specific pool identified by name or ID -/// - `Auto { ip_version }`: Use the silo's default pool, optionally filtered -/// by IP version (IPv4/IPv6) -pub fn pool_selector_strategy() -> impl Strategy { - prop_oneof![ - name_or_id_strategy() - .prop_map(|pool| params::PoolSelector::Explicit { pool }), - proptest::option::of(ip_version_strategy()) - .prop_map(|ip_version| params::PoolSelector::Auto { ip_version }), - ] -} - -/// Strategy for generating `IdentityMetadataCreateParams` values. -/// -/// Description is limited to 512 characters in the database. -pub fn identity_strategy() -> impl Strategy -{ - (name_strategy(), ".{0,512}").prop_map(|(name, description)| { - IdentityMetadataCreateParams { name, description } - }) -} - -/// Strategy for generating optional IP addresses. -pub fn optional_ip_strategy() -> impl Strategy> { - proptest::option::of(any::()) -} - -/// Strategy for generating optional `NameOrId` values. -pub fn optional_name_or_id_strategy() -> impl Strategy> -{ - proptest::option::of(name_or_id_strategy()) -} diff --git a/nexus/external-api/src/v2025112000.rs b/nexus/external-api/src/v2025112000.rs deleted file mode 100644 index 3434bbae9bd..00000000000 --- a/nexus/external-api/src/v2025112000.rs +++ /dev/null @@ -1,412 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Nexus external types that changed from 2025112000 to 2025120300 - -use crate::v2025121200; -use crate::v2026010100; -use crate::v2026013000; -use nexus_types::external_api::params; -use omicron_common::api::external; -use schemars::JsonSchema; -use serde::Deserialize; -use serde::Serialize; -use uuid::Uuid; - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum DiskType { - Crucible, -} - -impl From for external::DiskType { - fn from(old: DiskType) -> external::DiskType { - match old { - DiskType::Crucible => external::DiskType::Distributed, - } - } -} - -/// View of a Disk -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Disk { - #[serde(flatten)] - pub identity: external::IdentityMetadata, - pub project_id: Uuid, - /// ID of snapshot from which disk was created, if any - pub snapshot_id: Option, - /// ID of image from which disk was created, if any - pub image_id: Option, - pub size: external::ByteCount, - pub block_size: external::ByteCount, - pub state: external::DiskState, - pub device_path: String, - pub disk_type: DiskType, -} - -impl From for external::Disk { - fn from(old: Disk) -> external::Disk { - external::Disk { - identity: old.identity, - project_id: old.project_id, - snapshot_id: old.snapshot_id, - image_id: old.image_id, - size: old.size, - block_size: old.block_size, - state: old.state, - device_path: old.device_path, - disk_type: old.disk_type.into(), - read_only: false, // read_only defaults to false - } - } -} - -impl TryFrom for Disk { - type Error = dropshot::HttpError; - - fn try_from(new: external::Disk) -> Result { - Ok(Disk { - identity: new.identity, - project_id: new.project_id, - snapshot_id: new.snapshot_id, - image_id: new.image_id, - size: new.size, - block_size: new.block_size, - state: new.state, - device_path: new.device_path, - disk_type: match new.disk_type { - external::DiskType::Distributed => DiskType::Crucible, - - _ => { - // Cannot display any other variant for this old client - return Err(dropshot::HttpError::for_client_error( - Some(String::from("Not Acceptable")), - dropshot::ClientErrorStatusCode::NOT_ACCEPTABLE, - String::from( - "disk type variant not supported for client version", - ), - )); - } - }, - }) - } -} - -impl From for v2026013000::Disk { - fn from(old: Disk) -> v2026013000::Disk { - v2026013000::Disk { - identity: old.identity, - project_id: old.project_id, - snapshot_id: old.snapshot_id, - image_id: old.image_id, - size: old.size, - block_size: old.block_size, - state: old.state, - device_path: old.device_path, - disk_type: old.disk_type.into(), - } - } -} - -impl TryFrom for Disk { - type Error = dropshot::HttpError; - - fn try_from(new: v2026013000::Disk) -> Result { - Ok(Disk { - identity: new.identity, - project_id: new.project_id, - snapshot_id: new.snapshot_id, - image_id: new.image_id, - size: new.size, - block_size: new.block_size, - state: new.state, - device_path: new.device_path, - disk_type: match new.disk_type { - external::DiskType::Distributed => DiskType::Crucible, - - _ => { - // Cannot display any other variant for this old client - return Err(dropshot::HttpError::for_client_error( - Some(String::from("Not Acceptable")), - dropshot::ClientErrorStatusCode::NOT_ACCEPTABLE, - String::from( - "disk type variant not supported for client version", - ), - )); - } - }, - }) - } -} - -/// Different sources for a disk -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum DiskSource { - /// Create a blank disk - Blank { - /// size of blocks for this Disk. valid values are: 512, 2048, or 4096 - block_size: params::BlockSize, - }, - - /// Create a disk from a disk snapshot - Snapshot { snapshot_id: Uuid }, - - /// Create a disk from an image - Image { image_id: Uuid }, - - /// Create a blank disk that will accept bulk writes or pull blocks from an - /// external source. - ImportingBlocks { block_size: params::BlockSize }, -} - -impl From for params::DiskSource { - fn from(old: DiskSource) -> params::DiskSource { - match old { - DiskSource::Blank { block_size } => { - params::DiskSource::Blank { block_size } - } - - DiskSource::Snapshot { snapshot_id } => { - params::DiskSource::Snapshot { - snapshot_id, - // read only defaults to false if the client doesn't know - // about it - read_only: false, - } - } - - DiskSource::Image { image_id } => { - params::DiskSource::Image { - image_id, - // read only defaults to false if the client doesn't know - // about it - read_only: false, - } - } - - DiskSource::ImportingBlocks { block_size } => { - params::DiskSource::ImportingBlocks { block_size } - } - } - } -} - -/// Create-time parameters for a `Disk` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct DiskCreate { - /// The common identifying metadata for the disk - #[serde(flatten)] - pub identity: external::IdentityMetadataCreateParams, - - /// The initial source for this disk - pub disk_source: DiskSource, - - /// The total size of the Disk (in bytes) - pub size: external::ByteCount, -} - -impl From for params::DiskCreate { - fn from(old: DiskCreate) -> params::DiskCreate { - params::DiskCreate { - identity: old.identity, - disk_backend: params::DiskBackend::Distributed { - disk_source: old.disk_source.into(), - }, - size: old.size, - } - } -} - -impl From for v2026013000::DiskCreate { - fn from(old: DiskCreate) -> v2026013000::DiskCreate { - v2026013000::DiskCreate { - identity: old.identity, - disk_backend: v2026013000::DiskBackend::Distributed { - disk_source: old.disk_source, - }, - size: old.size, - } - } -} - -/// Describe the instance's disks at creation time -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum InstanceDiskAttachment { - /// During instance creation, create and attach disks - Create(DiskCreate), - - /// During instance creation, attach this disk - Attach(params::InstanceDiskAttach), -} - -impl From for params::InstanceDiskAttachment { - fn from(old: InstanceDiskAttachment) -> params::InstanceDiskAttachment { - match old { - InstanceDiskAttachment::Create(create) => { - params::InstanceDiskAttachment::Create(create.into()) - } - - InstanceDiskAttachment::Attach(attach) => { - params::InstanceDiskAttachment::Attach(attach) - } - } - } -} - -impl From for v2026013000::InstanceDiskAttachment { - fn from( - old: InstanceDiskAttachment, - ) -> v2026013000::InstanceDiskAttachment { - match old { - InstanceDiskAttachment::Create(create) => { - v2026013000::InstanceDiskAttachment::Create(create.into()) - } - - InstanceDiskAttachment::Attach(attach) => { - v2026013000::InstanceDiskAttachment::Attach(attach) - } - } - } -} - -/// Create-time parameters for an `Instance` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceCreate { - #[serde(flatten)] - pub identity: external::IdentityMetadataCreateParams, - /// The number of vCPUs to be allocated to the instance - pub ncpus: external::InstanceCpuCount, - /// The amount of RAM (in bytes) to be allocated to the instance - pub memory: external::ByteCount, - /// The hostname to be assigned to the instance - pub hostname: external::Hostname, - - /// User data for instance initialization systems (such as cloud-init). - /// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / - /// characters with padding). Maximum 32 KiB unencoded data. - // While serde happily accepts #[serde(with = "")] as a shorthand for - // specifying `serialize_with` and `deserialize_with`, schemars requires the - // argument to `with` to be a type rather than merely a path prefix (i.e. a - // mod or type). It's admittedly a bit tricky for schemars to address; - // unlike `serialize` or `deserialize`, `JsonSchema` requires several - // functions working together. It's unfortunate that schemars has this - // built-in incompatibility, exacerbated by its glacial rate of progress - // and immunity to offers of help. - #[serde(default, with = "params::UserData")] - pub user_data: Vec, - - /// The network interfaces to be created for this instance. - #[serde(default)] - pub network_interfaces: v2026010100::InstanceNetworkInterfaceAttachment, - - /// The external IP addresses provided to this instance. - /// - /// By default, all instances have outbound connectivity, but no inbound - /// connectivity. These external addresses can be used to provide a fixed, - /// known IP address for making inbound connections to the instance. - // Delegates through v2025121200 → params::ExternalIpCreate - #[serde(default)] - pub external_ips: Vec, - - /// The multicast groups this instance should join. - /// - /// The instance will be automatically added as a member of the specified - /// multicast groups during creation, enabling it to send and receive - /// multicast traffic for those groups. - #[serde(default)] - pub multicast_groups: Vec, - - /// A list of disks to be attached to the instance. - /// - /// Disk attachments of type "create" will be created, while those of type - /// "attach" must already exist. - /// - /// The order of this list does not guarantee a boot order for the instance. - /// Use the boot_disk attribute to specify a boot disk. When boot_disk is - /// specified it will count against the disk attachment limit. - #[serde(default)] - pub disks: Vec, - - /// The disk the instance is configured to boot from. - /// - /// This disk can either be attached if it already exists or created along - /// with the instance. - /// - /// Specifying a boot disk is optional but recommended to ensure predictable - /// boot behavior. The boot disk can be set during instance creation or - /// later if the instance is stopped. The boot disk counts against the disk - /// attachment limit. - /// - /// An instance that does not have a boot disk set will use the boot - /// options specified in its UEFI settings, which are controlled by both the - /// instance's UEFI firmware and the guest operating system. Boot options - /// can change as disks are attached and detached, which may result in an - /// instance that only boots to the EFI shell until a boot disk is set. - #[serde(default)] - pub boot_disk: Option, - - /// An allowlist of SSH public keys to be transferred to the instance via - /// cloud-init during instance creation. - /// - /// If not provided, all SSH public keys from the user's profile will be sent. - /// If an empty list is provided, no public keys will be transmitted to the - /// instance. - pub ssh_public_keys: Option>, - - /// Should this instance be started upon creation; true by default. - #[serde(default = "params::bool_true")] - pub start: bool, - - /// The auto-restart policy for this instance. - /// - /// This policy determines whether the instance should be automatically - /// restarted by the control plane on failure. If this is `null`, no - /// auto-restart policy will be explicitly configured for this instance, and - /// the control plane will select the default policy when determining - /// whether the instance can be automatically restarted. - /// - /// Currently, the global default auto-restart policy is "best-effort", so - /// instances with `null` auto-restart policies will be automatically - /// restarted. However, in the future, the default policy may be - /// configurable through other mechanisms, such as on a per-project basis. - /// In that case, any configured default policy will be used if this is - /// `null`. - #[serde(default)] - pub auto_restart_policy: Option, - - /// Anti-Affinity groups which this instance should be added. - #[serde(default)] - pub anti_affinity_groups: Vec, - - /// The CPU platform to be used for this instance. If this is `null`, the - /// instance requires no particular CPU platform; when it is started the - /// instance will have the most general CPU platform supported by the sled - /// it is initially placed on. - #[serde(default)] - pub cpu_platform: Option, -} - -impl From for v2025121200::InstanceCreate { - fn from(old: InstanceCreate) -> v2025121200::InstanceCreate { - v2025121200::InstanceCreate { - identity: old.identity, - ncpus: old.ncpus, - memory: old.memory, - hostname: old.hostname, - user_data: old.user_data, - network_interfaces: old.network_interfaces, - external_ips: old.external_ips, - multicast_groups: old.multicast_groups, - disks: old.disks.into_iter().map(Into::into).collect(), - boot_disk: old.boot_disk.map(Into::into), - ssh_public_keys: old.ssh_public_keys, - start: old.start, - auto_restart_policy: old.auto_restart_policy, - anti_affinity_groups: old.anti_affinity_groups, - cpu_platform: old.cpu_platform, - } - } -} diff --git a/nexus/external-api/src/v2025120300.rs b/nexus/external-api/src/v2025120300.rs deleted file mode 100644 index b67ad4c984a..00000000000 --- a/nexus/external-api/src/v2025120300.rs +++ /dev/null @@ -1,61 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Nexus external types that changed from 2025120300 to 2025121200 - -use std::net::IpAddr; - -use omicron_common::api::external; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// The current status of a BGP peer. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct BgpPeerStatus { - /// IP address of the peer. - pub addr: IpAddr, - - /// Local autonomous system number. - pub local_asn: u32, - - /// Remote autonomous system number. - pub remote_asn: u32, - - /// State of the peer. - pub state: BgpPeerState, - - /// Time of last state change. - pub state_duration_millis: u64, - - /// Switch with the peer session. - pub switch: external::SwitchLocation, -} - -/// The current state of a BGP peer. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum BgpPeerState { - /// Initial state. Refuse all incoming BGP connections. No resources - /// allocated to peer. - Idle, - - /// Waiting for the TCP connection to be completed. - Connect, - - /// Trying to acquire peer by listening for and accepting a TCP connection. - Active, - - /// Waiting for open message from peer. - OpenSent, - - /// Waiting for keepaliave or notification from peer. - OpenConfirm, - - /// Synchronizing with peer. - SessionSetup, - - /// Session established. Able to exchange update, notification and keepalive - /// messages with peers. - Established, -} diff --git a/nexus/external-api/src/v2025121200.rs b/nexus/external-api/src/v2025121200.rs deleted file mode 100644 index 5e2145e2666..00000000000 --- a/nexus/external-api/src/v2025121200.rs +++ /dev/null @@ -1,247 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Nexus external types that changed from 2025121200 to 2026010100. -//! -//! Version 2025121200 types (before `ip_version` preference was added for -//! default IP pool selection). -//! -//! ## IP Pool Selection Changes -//! -//! Key differences from newer API versions: -//! - [`FloatingIpCreate`], [`EphemeralIpCreate`], and [`ExternalIpCreate`] -//! don't have the `ip_version` field. Newer versions allow specifying -//! IPv4/IPv6 preference when allocating from default pools. -//! - When multiple default pools of different IP versions exist for a silo, -//! older clients cannot resolve the conflict. Newer API versions -//! require the `ip_version` field in this scenario. -//! -//! Affected endpoints: -//! - `POST /v1/floating-ips` (floating_ip_create) -//! - `POST /v1/instances/{instance}/external-ips/ephemeral` (instance_ephemeral_ip_attach) -//! - `POST /v1/instances` (instance_create) -//! -//! ## Multicast Types -//! -//! Multicast types are re-exported from `v2025122300`. -//! Both versions use `NameOrId` for group references and have the same -//! explicit create/update semantics. -//! -//! [`FloatingIpCreate`]: self::FloatingIpCreate -//! [`EphemeralIpCreate`]: self::EphemeralIpCreate -//! [`ExternalIpCreate`]: self::ExternalIpCreate - -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use nexus_types::external_api::params; -use omicron_common::api::external; - -use crate::{v2026010100, v2026010300, v2026013000}; - -// Re-export multicast types from v2025122300. -// They're identical for both versions (both use NameOrId, explicit -// create/update, no source_ips per member) -pub use super::v2025122300::{ - InstanceMulticastGroupPath, InstanceUpdate, MulticastGroup, - MulticastGroupByIpPath, MulticastGroupCreate, MulticastGroupMember, - MulticastGroupMemberAdd, MulticastGroupMemberPath, MulticastGroupPath, - MulticastGroupUpdate, -}; - -/// Parameters for creating an ephemeral IP address for an instance. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct EphemeralIpCreate { - /// Name or ID of the IP pool used to allocate an address. - /// If unspecified, the default IP pool will be used. - pub pool: Option, -} - -// Converts directly to params::EphemeralIpCreate using PoolSelector -impl From for params::EphemeralIpCreate { - fn from(old: EphemeralIpCreate) -> params::EphemeralIpCreate { - let pool_selector = match old.pool { - Some(pool) => params::PoolSelector::Explicit { pool }, - None => params::PoolSelector::Auto { ip_version: None }, - }; - params::EphemeralIpCreate { pool_selector } - } -} - -/// The type of IP address to attach to an instance during creation. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum ExternalIpCreate { - /// An IP address providing both inbound and outbound access. - /// The address is automatically assigned from the provided IP pool - /// or the default IP pool if not specified. - Ephemeral { - /// Name or ID of the IP pool to use. If unspecified, the - /// default IP pool will be used. - pool: Option, - }, - /// A floating IP address. - Floating { - /// The name or ID of the floating IP address to attach. - floating_ip: external::NameOrId, - }, -} - -// Converts to v2026010300::ExternalIpCreate (adds ip_version: None) -impl From for v2026010300::ExternalIpCreate { - fn from(old: ExternalIpCreate) -> v2026010300::ExternalIpCreate { - match old { - ExternalIpCreate::Ephemeral { pool } => { - v2026010300::ExternalIpCreate::Ephemeral { - pool, - ip_version: None, - } - } - ExternalIpCreate::Floating { floating_ip } => { - v2026010300::ExternalIpCreate::Floating { floating_ip } - } - } - } -} - -/// Parameters for creating a new floating IP address for instances. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct FloatingIpCreate { - /// common identifying metadata - #[serde(flatten)] - pub identity: external::IdentityMetadataCreateParams, - /// An IP address to reserve for use as a floating IP. This field is - /// optional: when not set, an address will be automatically chosen from - /// `pool`. If set, then the IP must be available in the resolved `pool`. - pub ip: Option, - /// The parent IP pool that a floating IP is pulled from. If unset, the - /// default pool is selected. - pub pool: Option, -} - -// Converts to v2026010300::FloatingIpCreate (adds ip_version: None) -impl From for v2026010300::FloatingIpCreate { - fn from(old: FloatingIpCreate) -> v2026010300::FloatingIpCreate { - v2026010300::FloatingIpCreate { - identity: old.identity, - ip: old.ip, - pool: old.pool, - ip_version: None, - } - } -} - -/// Create-time parameters for an `Instance` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceCreate { - #[serde(flatten)] - pub identity: external::IdentityMetadataCreateParams, - /// The number of vCPUs to be allocated to the instance - pub ncpus: external::InstanceCpuCount, - /// The amount of RAM (in bytes) to be allocated to the instance - pub memory: external::ByteCount, - /// The hostname to be assigned to the instance - pub hostname: external::Hostname, - /// User data for instance initialization systems (such as cloud-init). - #[serde(default, with = "params::UserData")] - pub user_data: Vec, - /// The network interfaces to be created for this instance. - #[serde(default)] - pub network_interfaces: v2026010100::InstanceNetworkInterfaceAttachment, - /// The external IP addresses provided to this instance. - #[serde(default)] - pub external_ips: Vec, - /// The multicast groups this instance should join. - #[serde(default)] - pub multicast_groups: Vec, - /// A list of disks to be attached to the instance. - #[serde(default)] - pub disks: Vec, - /// The disk the instance is configured to boot from. - #[serde(default)] - pub boot_disk: Option, - /// An allowlist of SSH public keys to be transferred to the instance. - pub ssh_public_keys: Option>, - /// Should this instance be started upon creation; true by default. - #[serde(default = "params::bool_true")] - pub start: bool, - /// The auto-restart policy for this instance. - #[serde(default)] - pub auto_restart_policy: Option, - /// Anti-Affinity groups which this instance should be added. - #[serde(default)] - pub anti_affinity_groups: Vec, - /// The CPU platform to be used for this instance. - #[serde(default)] - pub cpu_platform: Option, -} - -impl From for v2026010100::InstanceCreate { - fn from(old: InstanceCreate) -> v2026010100::InstanceCreate { - v2026010100::InstanceCreate { - identity: old.identity, - ncpus: old.ncpus, - memory: old.memory, - hostname: old.hostname, - user_data: old.user_data, - network_interfaces: old.network_interfaces, - external_ips: old - .external_ips - .into_iter() - .map(Into::into) - .collect(), - multicast_groups: old.multicast_groups, - disks: old.disks, - boot_disk: old.boot_disk, - ssh_public_keys: old.ssh_public_keys, - start: old.start, - auto_restart_policy: old.auto_restart_policy, - anti_affinity_groups: old.anti_affinity_groups, - cpu_platform: old.cpu_platform, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::{ - identity_strategy, optional_ip_strategy, optional_name_or_id_strategy, - }; - use proptest::prelude::*; - use test_strategy::proptest; - - fn floating_ip_create_strategy() -> impl Strategy - { - ( - identity_strategy(), - optional_ip_strategy(), - optional_name_or_id_strategy(), - ) - .prop_map(|(identity, ip, pool)| FloatingIpCreate { - identity, - ip, - pool, - }) - } - - /// Verifies that the conversion from v2025121200::FloatingIpCreate to - /// v2026010300::FloatingIpCreate preserves all existing fields, and that - /// the ip_version field is set to None. - #[proptest] - fn floating_ip_create_converts_correctly( - #[strategy(floating_ip_create_strategy())] input: FloatingIpCreate, - ) { - let output: v2026010300::FloatingIpCreate = input.clone().into(); - - prop_assert_eq!(input.identity.name, output.identity.name); - prop_assert_eq!( - input.identity.description, - output.identity.description - ); - prop_assert_eq!(input.ip, output.ip); - prop_assert_eq!(input.pool, output.pool); - prop_assert_eq!(output.ip_version, None); - } -} diff --git a/nexus/external-api/src/v2025122300.rs b/nexus/external-api/src/v2025122300.rs deleted file mode 100644 index 01b63662a1c..00000000000 --- a/nexus/external-api/src/v2025122300.rs +++ /dev/null @@ -1,360 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Nexus external types that changed from 2025122300 to 2026010100. -//! -//! This module contains both **views** (response bodies) and **params** -//! (request bodies) that differ from newer API versions. -//! -//! ## SiloIpPool Changes -//! -//! [`SiloIpPool`] doesn't have `ip_version` or `pool_type` fields. -//! Newer versions include these fields to indicate the IP version -//! and pool type (unicast or multicast) of the pool. -//! -//! Affected endpoints: -//! - `GET /v1/ip-pools` (project_ip_pool_list) -//! - `GET /v1/ip-pools/{pool}` (project_ip_pool_view) -//! - `GET /v1/system/silos/{silo}/ip-pools` (silo_ip_pool_list) -//! -//! ## Multicast Changes -//! -//! Version 2025122300 types (before [`MulticastGroupIdentifier`] was introduced -//! and before implicit group lifecycle). -//! -//! Key differences: -//! - Uses [`NameOrId`] for multicast group references (not [`MulticastGroupIdentifier`]). -//! Newer versions accept name, UUID, or multicast IP address, while this version -//! only accepts name or UUID. -//! - Had explicit create/update endpoints for multicast groups (removed in newer -//! versions which create/delete groups implicitly via member operations). -//! - [`MulticastGroupMemberAdd`] doesn't have `source_ips` field. -//! -//! Affected endpoints: -//! - `GET /v1/multicast-groups` (multicast_group_list) -//! - `GET /v1/multicast-groups/{multicast_group}` (multicast_group_view) -//! - `POST /v1/multicast-groups/{multicast_group}/members` (multicast_group_member_add) -//! - `POST /v1/instances` (instance_create) -//! - `PUT /v1/instances/{instance}` (instance_update) -//! -//! [`SiloIpPool`]: self::SiloIpPool -//! [`MulticastGroupIdentifier`]: nexus_types::external_api::params::MulticastGroupIdentifier -//! [`NameOrId`]: omicron_common::api::external::NameOrId -//! [`MulticastGroupMemberAdd`]: self::MulticastGroupMemberAdd - -use std::net::IpAddr; - -use chrono::{DateTime, Utc}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use nexus_types::external_api::{params, views}; -use nexus_types::multicast::MulticastGroupCreate as InternalMulticastGroupCreate; -use omicron_common::api::external::{ - ByteCount, IdentityMetadata, IdentityMetadataCreateParams, - InstanceAutoRestartPolicy, InstanceCpuCount, InstanceCpuPlatform, Name, - NameOrId, Nullable, -}; -use omicron_common::vlan::VlanID; - -/// Path parameter for multicast group operations. -/// -/// Uses `NameOrId` instead of `MulticastGroupIdentifier`. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupPath { - /// Name or ID of the multicast group - pub multicast_group: NameOrId, -} - -impl From for params::MulticastGroupPath { - fn from(old: MulticastGroupPath) -> Self { - Self { multicast_group: old.multicast_group.into() } - } -} - -/// Path parameters for multicast group member operations. -/// -/// Uses `NameOrId` instead of `MulticastGroupIdentifier` for the group. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupMemberPath { - /// Name or ID of the multicast group - pub multicast_group: NameOrId, - /// Name or ID of the instance - pub instance: NameOrId, -} - -impl From for params::MulticastGroupMemberPath { - fn from(old: MulticastGroupMemberPath) -> Self { - Self { - multicast_group: old.multicast_group.into(), - instance: old.instance, - } - } -} - -/// Path parameters for instance multicast group operations. -/// -/// Uses `NameOrId` instead of `MulticastGroupIdentifier` for the group. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceMulticastGroupPath { - /// Name or ID of the instance - pub instance: NameOrId, - /// Name or ID of the multicast group - pub multicast_group: NameOrId, -} - -impl From for params::InstanceMulticastGroupPath { - fn from(old: InstanceMulticastGroupPath) -> Self { - Self { - instance: old.instance, - multicast_group: old.multicast_group.into(), - } - } -} - -/// Create-time parameters for a multicast group. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupCreate { - pub name: Name, - pub description: String, - /// The multicast IP address to allocate. If None, one will be allocated - /// from the default pool. - #[serde(default)] - pub multicast_ip: Option, - /// Source IP addresses for source-filtered multicast. - /// - /// - **ASM**: Sources are optional. None or empty list allows any source. - /// A non-empty list enables source filtering via IGMPv3/MLDv2. - /// - **SSM**: Sources are required for SSM addresses (232/8, ff3x::/32). - #[serde(default)] - pub source_ips: Option>, - /// Name or ID of the IP pool to allocate from. If None, uses the default - /// multicast pool. - #[serde(default)] - pub pool: Option, - /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. - /// Tags packets leaving the rack to traverse VLAN-segmented upstream networks. - /// - /// Valid range: 2-4094 (VLAN IDs 0-1 are reserved by IEEE 802.1Q standard). - #[serde(default)] - pub mvlan: Option, -} - -/// Update-time parameters for a multicast group. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupUpdate { - /// New name for the multicast group - #[serde(default)] - pub name: Option, - /// New description for the multicast group - #[serde(default)] - pub description: Option, - /// Update source IPs for source filtering (ASM can have sources, but - /// SSM requires them) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub source_ips: Option>, - /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. - /// Set to null to clear the MVLAN. Valid range: 2-4094 when provided. - /// Omit the field to leave mvlan unchanged. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub mvlan: Option>, -} - -/// Parameters for adding an instance to a multicast group. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupMemberAdd { - /// Name or ID of the instance to add to the multicast group - pub instance: NameOrId, -} - -impl From for params::MulticastGroupMemberAdd { - fn from(old: MulticastGroupMemberAdd) -> Self { - Self { instance: old.instance, source_ips: None } - } -} - -/// Path parameter for looking up a multicast group by IP address. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupByIpPath { - /// IP address of the multicast group - pub address: IpAddr, -} - -impl From for params::MulticastGroupPath { - fn from(old: MulticastGroupByIpPath) -> Self { - Self { multicast_group: old.address.into() } - } -} - -impl From for InternalMulticastGroupCreate { - fn from(old: MulticastGroupCreate) -> Self { - // Note: `source_ips` is ignored because it's per-member in new version, - // not per-group. - // - // The old API field is kept for backward compatibility but ignored. - // We still use `has_sources` for pool selection preference. - let has_sources = - old.source_ips.as_ref().is_some_and(|s| !s.is_empty()); - Self { - identity: IdentityMetadataCreateParams { - name: old.name, - description: old.description, - }, - multicast_ip: old.multicast_ip, - mvlan: old.mvlan, - has_sources, - // Old API version doesn't have ip_version preference - ip_version: None, - } - } -} - -/// View of a Multicast Group. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroup { - #[serde(flatten)] - pub identity: IdentityMetadata, - /// The multicast IP address held by this resource. - pub multicast_ip: IpAddr, - /// Source IP addresses for multicast source filtering (SSM requires these; - /// ASM can optionally use them via IGMPv3/MLDv2). Empty array means any source. - pub source_ips: Vec, - /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. - /// None means no VLAN tagging on egress. - pub mvlan: Option, - /// The ID of the IP pool this resource belongs to. - pub ip_pool_id: Uuid, - /// Current state of the multicast group. - pub state: String, -} - -impl From for MulticastGroup { - fn from(v: views::MulticastGroup) -> Self { - Self { - identity: v.identity, - multicast_ip: v.multicast_ip, - source_ips: v.source_ips, - mvlan: v.mvlan, - ip_pool_id: v.ip_pool_id, - state: v.state, - } - } -} - -/// View of a Multicast Group Member. -/// -/// This version omits `multicast_ip` and `source_ips` fields added in later versions. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupMember { - /// unique, immutable, system-controlled identifier for each resource - pub id: Uuid, - /// unique, mutable, user-controlled identifier for each resource - pub name: Name, - /// human-readable free-form text about a resource - pub description: String, - /// timestamp when this resource was created - pub time_created: DateTime, - /// timestamp when this resource was last modified - pub time_modified: DateTime, - /// The ID of the multicast group this member belongs to. - pub multicast_group_id: Uuid, - /// The ID of the instance that is a member of this group. - pub instance_id: Uuid, - /// Current state of the multicast group membership. - pub state: String, -} - -impl From for MulticastGroupMember { - fn from(v: views::MulticastGroupMember) -> Self { - Self { - id: v.identity.id, - name: v.identity.name, - description: v.identity.description, - time_created: v.identity.time_created, - time_modified: v.identity.time_modified, - multicast_group_id: v.multicast_group_id, - instance_id: v.instance_id, - state: v.state, - } - } -} - -/// Parameters of an `Instance` that can be reconfigured after creation. -/// -/// This version uses `Vec` for multicast_groups instead of -/// `Vec` in newer versions. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceUpdate { - /// The number of vCPUs to be allocated to the instance - pub ncpus: InstanceCpuCount, - - /// The amount of RAM (in bytes) to be allocated to the instance - pub memory: ByteCount, - - /// The disk the instance is configured to boot from. - pub boot_disk: Nullable, - - /// The auto-restart policy for this instance. - pub auto_restart_policy: Nullable, - - /// The CPU platform to be used for this instance. - pub cpu_platform: Nullable, - - /// Multicast groups this instance should join. - /// - /// When specified, this replaces the instance's current multicast group - /// membership with the new set of groups. The instance will leave any - /// groups not listed here and join any new groups that are specified. - /// - /// If not provided (None), the instance's multicast group membership - /// will not be changed. - /// - /// Accepts group names or UUIDs. Newer API versions also accept multicast - /// IP addresses. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub multicast_groups: Option>, -} - -impl From for params::InstanceUpdate { - fn from(old: InstanceUpdate) -> Self { - Self { - ncpus: old.ncpus, - memory: old.memory, - boot_disk: old.boot_disk, - auto_restart_policy: old.auto_restart_policy, - cpu_platform: old.cpu_platform, - multicast_groups: old.multicast_groups.map(|groups| { - groups - .into_iter() - .map(|g| params::MulticastGroupJoinSpec { - group: g.into(), - source_ips: None, - ip_version: None, - }) - .collect() - }), - } - } -} - -/// An IP pool in the context of a silo (pre-2026010100 API version). -/// -/// This version does not include `ip_version` or `pool_type` fields. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SiloIpPool { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// When a pool is the default for a silo, floating IPs and instance - /// ephemeral IPs will come from that pool when no other pool is specified. - /// There can be at most one default for a given silo. - pub is_default: bool, -} - -impl From for SiloIpPool { - fn from(new: views::SiloIpPool) -> SiloIpPool { - SiloIpPool { identity: new.identity, is_default: new.is_default } - } -} diff --git a/nexus/external-api/src/v2025_11_20_00_local.rs b/nexus/external-api/src/v2025_11_20_00_local.rs new file mode 100644 index 00000000000..871e99e2fa4 --- /dev/null +++ b/nexus/external-api/src/v2025_11_20_00_local.rs @@ -0,0 +1,139 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Types from API version 2025_11_20_00 that cannot live in `nexus-types-versions` +//! because they convert to/from `omicron-common` types (orphan rule). + +use crate::v2026_01_30_00_local; +use omicron_common::api::external; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use uuid::Uuid; + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum DiskType { + Crucible, +} + +impl From for external::DiskType { + fn from(old: DiskType) -> external::DiskType { + match old { + DiskType::Crucible => external::DiskType::Distributed, + } + } +} + +/// View of a Disk +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Disk { + #[serde(flatten)] + pub identity: external::IdentityMetadata, + pub project_id: Uuid, + /// ID of snapshot from which disk was created, if any + pub snapshot_id: Option, + /// ID of image from which disk was created, if any + pub image_id: Option, + pub size: external::ByteCount, + pub block_size: external::ByteCount, + pub state: external::DiskState, + pub device_path: String, + pub disk_type: DiskType, +} + +impl From for external::Disk { + fn from(old: Disk) -> external::Disk { + external::Disk { + identity: old.identity, + project_id: old.project_id, + snapshot_id: old.snapshot_id, + image_id: old.image_id, + size: old.size, + block_size: old.block_size, + state: old.state, + device_path: old.device_path, + disk_type: old.disk_type.into(), + read_only: false, // read_only defaults to false + } + } +} + +impl TryFrom for Disk { + type Error = dropshot::HttpError; + + fn try_from(new: external::Disk) -> Result { + Ok(Disk { + identity: new.identity, + project_id: new.project_id, + snapshot_id: new.snapshot_id, + image_id: new.image_id, + size: new.size, + block_size: new.block_size, + state: new.state, + device_path: new.device_path, + disk_type: match new.disk_type { + external::DiskType::Distributed => DiskType::Crucible, + + _ => { + // Cannot display any other variant for this old client. + return Err(dropshot::HttpError::for_client_error( + Some(String::from("Not Acceptable")), + dropshot::ClientErrorStatusCode::NOT_ACCEPTABLE, + String::from( + "disk type variant not supported for client version", + ), + )); + } + }, + }) + } +} + +impl From for v2026_01_30_00_local::Disk { + fn from(old: Disk) -> v2026_01_30_00_local::Disk { + v2026_01_30_00_local::Disk { + identity: old.identity, + project_id: old.project_id, + snapshot_id: old.snapshot_id, + image_id: old.image_id, + size: old.size, + block_size: old.block_size, + state: old.state, + device_path: old.device_path, + disk_type: old.disk_type.into(), + } + } +} + +impl TryFrom for Disk { + type Error = dropshot::HttpError; + + fn try_from(new: v2026_01_30_00_local::Disk) -> Result { + Ok(Disk { + identity: new.identity, + project_id: new.project_id, + snapshot_id: new.snapshot_id, + image_id: new.image_id, + size: new.size, + block_size: new.block_size, + state: new.state, + device_path: new.device_path, + disk_type: match new.disk_type { + external::DiskType::Distributed => DiskType::Crucible, + + _ => { + // Cannot display any other variant for this old client. + return Err(dropshot::HttpError::for_client_error( + Some(String::from("Not Acceptable")), + dropshot::ClientErrorStatusCode::NOT_ACCEPTABLE, + String::from( + "disk type variant not supported for client version", + ), + )); + } + }, + }) + } +} diff --git a/nexus/external-api/src/v2026010300.rs b/nexus/external-api/src/v2026010300.rs deleted file mode 100644 index 756010e98a4..00000000000 --- a/nexus/external-api/src/v2026010300.rs +++ /dev/null @@ -1,457 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Nexus external types that changed from 2026010300 to 2026010500. -//! -//! ## Pool Selection Changes -//! -//! [`FloatingIpCreate`], [`EphemeralIpCreate`], and [`ExternalIpCreate`] -//! use flat structures with `pool` and `ip_version` fields. Newer versions -//! use tagged enums ([`AddressSelector`] and [`PoolSelector`]) that -//! make invalid states unrepresentable. -//! -//! Affected endpoints: -//! - `POST /v1/floating-ips` (floating_ip_create) -//! - `POST /v1/instances/{instance}/external-ips/ephemeral` (instance_ephemeral_ip_attach) -//! - `POST /v1/instances` (instance_create) -//! - `POST /experimental/v1/probes` (probe_create) -//! -//! ## Multicast Changes -//! -//! [`InstanceCreate`] uses `Vec` for `multicast_groups`. Newer -//! versions use `Vec` for implicit lifecycle support. -//! -//! [`FloatingIpCreate`]: self::FloatingIpCreate -//! [`EphemeralIpCreate`]: self::EphemeralIpCreate -//! [`ExternalIpCreate`]: self::ExternalIpCreate -//! [`InstanceCreate`]: self::InstanceCreate -//! [`AddressSelector`]: crate::v2026011501::AddressSelector -//! [`PoolSelector`]: nexus_types::external_api::params::PoolSelector -//! [`MulticastGroupJoinSpec`]: nexus_types::external_api::params::MulticastGroupJoinSpec - -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use nexus_types::external_api::params; -use omicron_common::api::external; - -use crate::v2026011501; -use crate::v2026013000; -use omicron_common::api::external::{ - ByteCount, Hostname, IdentityMetadataCreateParams, - InstanceAutoRestartPolicy, InstanceCpuCount, InstanceCpuPlatform, - IpVersion, NameOrId, -}; - -/// Parameters for creating an ephemeral IP address for an instance. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct EphemeralIpCreate { - /// Name or ID of the IP pool used to allocate an address. - /// If unspecified, the default IP pool will be used. - pub pool: Option, - /// The IP version preference for address allocation. Only used when - /// allocating from the default pool (i.e., when `pool` is not specified). - /// If a silo has multiple default pools of different IP versions, this - /// field is required to disambiguate. - pub ip_version: Option, -} - -impl TryFrom for params::EphemeralIpCreate { - type Error = external::Error; - - fn try_from( - old: EphemeralIpCreate, - ) -> Result { - let pool_selector = match (old.pool, old.ip_version) { - // Named pool specified -> ip_version must not be set - (Some(pool), None) => params::PoolSelector::Explicit { pool }, - // Named pool & ip_version is an invalid combination - (Some(_), Some(_)) => { - return Err(external::Error::invalid_request( - "cannot specify both `pool` and `ip_version`; \ - `ip_version` is only used when allocating from the default pool", - )); - } - // Default pool with optional ip_version preference - (None, ip_version) => params::PoolSelector::Auto { ip_version }, - }; - Ok(params::EphemeralIpCreate { pool_selector }) - } -} - -/// The type of IP address to attach to an instance during creation. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum ExternalIpCreate { - /// An IP address providing both inbound and outbound access. - /// The address is automatically assigned from the provided IP pool - /// or the default IP pool if not specified. - Ephemeral { - /// Name or ID of the IP pool to use. If unspecified, the - /// default IP pool will be used. - pool: Option, - /// The IP version preference for address allocation. Only used when - /// allocating from the default pool (i.e., when `pool` is not - /// specified). If a silo has multiple default pools of different IP - /// versions, this field is required to disambiguate. - ip_version: Option, - }, - /// A floating IP address. - Floating { - /// The name or ID of the floating IP address to attach. - floating_ip: NameOrId, - }, -} - -impl TryFrom for params::ExternalIpCreate { - type Error = external::Error; - - fn try_from( - old: ExternalIpCreate, - ) -> Result { - match old { - ExternalIpCreate::Ephemeral { pool, ip_version } => { - let pool_selector = match (pool, ip_version) { - // Named pool specified -> ip_version must not be set - (Some(pool), None) => { - params::PoolSelector::Explicit { pool } - } - // Named pool & ip_version is an invalid combination - (Some(_), Some(_)) => { - return Err(external::Error::invalid_request( - "cannot specify both `pool` and `ip_version`; \ - `ip_version` is only used when allocating from the default pool", - )); - } - // Default pool with optional ip_version preference - (None, ip_version) => { - params::PoolSelector::Auto { ip_version } - } - }; - Ok(params::ExternalIpCreate::Ephemeral { pool_selector }) - } - ExternalIpCreate::Floating { floating_ip } => { - Ok(params::ExternalIpCreate::Floating { floating_ip }) - } - } - } -} - -/// Parameters for creating a new floating IP address for instances. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct FloatingIpCreate { - /// common identifying metadata - #[serde(flatten)] - pub identity: omicron_common::api::external::IdentityMetadataCreateParams, - /// An IP address to reserve for use as a floating IP. This field is - /// optional: when not set, an address will be automatically chosen from - /// `pool`. If set, then the IP must be available in the resolved `pool`. - pub ip: Option, - /// The parent IP pool that a floating IP is pulled from. If unset, the - /// default pool is selected. - pub pool: Option, - /// The IP version preference for address allocation. Only used when - /// allocating from the default pool (i.e., when `pool` and `ip` are not - /// specified). If a silo has multiple default pools of different IP - /// versions, this field is required to disambiguate. - pub ip_version: Option, -} - -impl TryFrom for v2026011501::FloatingIpCreate { - type Error = external::Error; - - fn try_from( - old: FloatingIpCreate, - ) -> Result { - let address_selector = match (old.ip, old.pool, old.ip_version) { - // Explicit IP address provided -> ip_version must not be set - (Some(ip), pool, None) => { - v2026011501::AddressSelector::Explicit { ip, pool } - } - // Explicit IP and ip_version is an invalid combination - (Some(_), _, Some(_)) => { - return Err(external::Error::invalid_request( - "cannot specify both `ip` and `ip_version`; \ - the IP version is determined by the explicit IP address", - )); - } - // No explicit IP, but named pool specified -> ip_version must not be set - (None, Some(pool), None) => v2026011501::AddressSelector::Auto { - pool_selector: params::PoolSelector::Explicit { pool }, - }, - // Named pool and ip_version is an invalid combination - (None, Some(_), Some(_)) => { - return Err(external::Error::invalid_request( - "cannot specify both `pool` and `ip_version`; \ - `ip_version` is only used when allocating from the default pool", - )); - } - // Allocate from default pool with optional IP version preference - (None, None, ip_version) => v2026011501::AddressSelector::Auto { - pool_selector: params::PoolSelector::Auto { ip_version }, - }, - }; - Ok(v2026011501::FloatingIpCreate { - identity: old.identity, - address_selector, - }) - } -} - -// v2026010300 (DUAL_STACK_NICS) uses the current `InstanceNetworkInterfaceAttachment` -// with `DefaultIpv4`, `DefaultIpv6`, `DefaultDualStack` variants. -pub use params::InstanceNetworkInterfaceAttachment; - -/// Create-time parameters for an `Instance` -/// -/// This version uses `Vec` for `multicast_groups` instead of -/// `Vec` in newer versions. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// The number of vCPUs to be allocated to the instance - pub ncpus: InstanceCpuCount, - /// The amount of RAM (in bytes) to be allocated to the instance - pub memory: ByteCount, - /// The hostname to be assigned to the instance - pub hostname: Hostname, - /// User data for instance initialization systems (such as cloud-init). - #[serde(default, with = "params::UserData")] - pub user_data: Vec, - /// The network interfaces to be created for this instance. - #[serde(default)] - pub network_interfaces: InstanceNetworkInterfaceAttachment, - /// The external IP addresses provided to this instance. - // Uses local ExternalIpCreate (has ip_version field) → params::ExternalIpCreate - #[serde(default)] - pub external_ips: Vec, - /// The multicast groups this instance should join. - /// - /// The instance will be automatically added as a member of the specified - /// multicast groups during creation, enabling it to send and receive - /// multicast traffic for those groups. - #[serde(default)] - pub multicast_groups: Vec, - /// A list of disks to be attached to the instance. - #[serde(default)] - pub disks: Vec, - /// The disk the instance is configured to boot from. - #[serde(default)] - pub boot_disk: Option, - /// An allowlist of SSH public keys to be transferred to the instance. - pub ssh_public_keys: Option>, - /// Should this instance be started upon creation; true by default. - #[serde(default = "params::bool_true")] - pub start: bool, - /// The auto-restart policy for this instance. - #[serde(default)] - pub auto_restart_policy: Option, - /// Anti-Affinity groups which this instance should be added. - #[serde(default)] - pub anti_affinity_groups: Vec, - /// The CPU platform to be used for this instance. - #[serde(default)] - pub cpu_platform: Option, -} - -impl TryFrom for params::InstanceCreate { - type Error = external::Error; - - fn try_from( - old: InstanceCreate, - ) -> Result { - let external_ips: Vec = old - .external_ips - .into_iter() - .map(TryInto::try_into) - .collect::, _>>()?; - - Ok(params::InstanceCreate { - identity: old.identity, - ncpus: old.ncpus, - memory: old.memory, - hostname: old.hostname, - user_data: old.user_data, - network_interfaces: old.network_interfaces, - external_ips, - multicast_groups: old - .multicast_groups - .into_iter() - .map(|g| params::MulticastGroupJoinSpec { - group: g.into(), - source_ips: None, - ip_version: None, - }) - .collect(), - disks: old.disks.into_iter().map(Into::into).collect(), - boot_disk: old.boot_disk.map(Into::into), - ssh_public_keys: old.ssh_public_keys, - start: old.start, - auto_restart_policy: old.auto_restart_policy, - anti_affinity_groups: old.anti_affinity_groups, - cpu_platform: old.cpu_platform, - }) - } -} - -/// Create time parameters for probes. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct ProbeCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - #[schemars(with = "uuid::Uuid")] - pub sled: omicron_uuid_kinds::SledUuid, - pub ip_pool: Option, -} - -impl From for params::ProbeCreate { - fn from(old: ProbeCreate) -> params::ProbeCreate { - let pool_selector = match old.ip_pool { - Some(pool) => params::PoolSelector::Explicit { pool }, - None => params::PoolSelector::Auto { ip_version: None }, - }; - params::ProbeCreate { - identity: old.identity, - sled: old.sled, - pool_selector, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::{ - identity_strategy, ip_version_strategy, name_or_id_strategy, - optional_ip_strategy, optional_name_or_id_strategy, - }; - use proptest::prelude::*; - use std::net::IpAddr; - use test_strategy::proptest; - - /// Strategy for valid FloatingIpCreate inputs (ones that won't error). - /// - /// Valid when `ip_version` is not specified or when both `ip` and `pool` - /// are `None`. - fn valid_floating_ip_create_strategy() - -> impl Strategy { - let ip_or_pool = - (optional_ip_strategy(), optional_name_or_id_strategy()) - .prop_map(|(ip, pool)| (ip, pool, None)); - - let ip_version_only = - ip_version_strategy().prop_map(|v| (None, None, Some(v))); - - (identity_strategy(), prop_oneof![ip_or_pool, ip_version_only]) - .prop_map(|(identity, (ip, pool, ip_version))| FloatingIpCreate { - identity, - ip, - pool, - ip_version, - }) - } - - /// Strategy for invalid FloatingIpCreate inputs (ones that will error). - /// - /// Invalid when `ip_version` is specified along with `ip` and/or `pool`. - fn invalid_floating_ip_create_strategy() - -> impl Strategy { - let at_least_one_of_ip_or_pool = prop_oneof![ - // only ip - any::().prop_map(|ip| (Some(ip), None)), - // only pool - name_or_id_strategy().prop_map(|pool| (None, Some(pool))), - // both - (any::(), name_or_id_strategy()) - .prop_map(|(ip, pool)| (Some(ip), Some(pool))), - ]; - - (identity_strategy(), at_least_one_of_ip_or_pool, ip_version_strategy()) - .prop_map(|(identity, (ip, pool), ip_version)| FloatingIpCreate { - identity, - ip, - pool, - ip_version: Some(ip_version), - }) - } - - /// Verifies that valid inputs convert to v2026011501::FloatingIpCreate - /// with the correct AddressSelector variant based on ip/pool/ip_version. - #[proptest] - fn floating_ip_create_valid_converts_correctly( - #[strategy(valid_floating_ip_create_strategy())] - input: FloatingIpCreate, - ) { - use proptest::test_runner::TestCaseError; - - let output: v2026011501::FloatingIpCreate = - input.clone().try_into().map_err(|e| { - TestCaseError::fail(format!("unexpected error: {e}")) - })?; - - prop_assert_eq!(input.identity.name, output.identity.name); - prop_assert_eq!( - input.identity.description, - output.identity.description - ); - - match (input.ip, input.pool, input.ip_version) { - // Explicit IP address provided -> AddressSelector::Explicit. - (Some(ip), pool, None) => { - let v2026011501::AddressSelector::Explicit { - ip: out_ip, - pool: out_pool, - } = output.address_selector - else { - return Err(TestCaseError::fail( - "expected Explicit variant", - )); - }; - prop_assert_eq!(ip, out_ip); - prop_assert_eq!(pool, out_pool); - } - // Pool specified without IP -> AddressSelector::Auto with - // PoolSelector::Explicit. - (None, Some(pool), None) => { - let v2026011501::AddressSelector::Auto { - pool_selector: - params::PoolSelector::Explicit { pool: out_pool }, - } = output.address_selector - else { - return Err(TestCaseError::fail( - "expected Auto with Explicit pool_selector", - )); - }; - prop_assert_eq!(pool, out_pool); - } - // Neither IP nor pool -> AddressSelector::Auto with - // PoolSelector::Auto. - (None, None, ip_version) => { - let v2026011501::AddressSelector::Auto { - pool_selector: - params::PoolSelector::Auto { ip_version: out_ip_version }, - } = output.address_selector - else { - return Err(TestCaseError::fail( - "expected Auto with Auto pool_selector", - )); - }; - prop_assert_eq!(ip_version, out_ip_version); - } - // We shouldn't get here with valid_floating_ip_create_strategy. - _ => unreachable!(), - } - } - - /// Verifies that invalid inputs (ip_version with ip or pool) return errors. - #[proptest] - fn floating_ip_create_invalid_returns_error( - #[strategy(invalid_floating_ip_create_strategy())] - input: FloatingIpCreate, - ) { - let result: Result = input.try_into(); - prop_assert!(result.is_err()); - } -} diff --git a/nexus/external-api/src/v2026010500.rs b/nexus/external-api/src/v2026010500.rs deleted file mode 100644 index 8538b97d915..00000000000 --- a/nexus/external-api/src/v2026010500.rs +++ /dev/null @@ -1,128 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Nexus external types that changed from 2026010500 to 2026010800. -//! -//! ## Multicast Changes -//! -//! [`InstanceCreate`] uses `Vec` for `multicast_groups`. Newer -//! versions use `Vec` which supports implicit group -//! lifecycle (groups are created automatically when referenced). -//! -//! Affected endpoints: -//! - `POST /v1/instances` (instance_create) -//! -//! [`InstanceCreate`]: self::InstanceCreate -//! [`MulticastGroupJoinSpec`]: nexus_types::external_api::params::MulticastGroupJoinSpec - -use crate::v2026013000; -use nexus_types::external_api::params; -use omicron_common::api::external::{ - ByteCount, Hostname, IdentityMetadataCreateParams, - InstanceAutoRestartPolicy, InstanceCpuCount, InstanceCpuPlatform, NameOrId, -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -// v2026010500 (POOL_SELECTION_ENUMS) uses the current `InstanceNetworkInterfaceAttachment` -// with `DefaultIpv4`, `DefaultIpv6`, `DefaultDualStack` variants. -// -// Only the multicast_groups field differs (Vec vs Vec). -pub use params::InstanceNetworkInterfaceAttachment; - -/// Create-time parameters for an `Instance` -/// -/// This version uses `Vec` for `multicast_groups` instead of -/// `Vec` in newer versions. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// The number of vCPUs to be allocated to the instance - pub ncpus: InstanceCpuCount, - /// The amount of RAM (in bytes) to be allocated to the instance - pub memory: ByteCount, - /// The hostname to be assigned to the instance - pub hostname: Hostname, - - /// User data for instance initialization systems (such as cloud-init). - /// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / - /// characters with padding). Maximum 32 KiB unencoded data. - #[serde(default, with = "params::UserData")] - pub user_data: Vec, - - /// The network interfaces to be created for this instance. - #[serde(default)] - pub network_interfaces: InstanceNetworkInterfaceAttachment, - - /// The external IP addresses provided to this instance. - #[serde(default)] - pub external_ips: Vec, - - /// The multicast groups this instance should join. - /// - /// The instance will be automatically added as a member of the specified - /// multicast groups during creation, enabling it to send and receive - /// multicast traffic for those groups. - #[serde(default)] - pub multicast_groups: Vec, - - /// A list of disks to be attached to the instance. - #[serde(default)] - pub disks: Vec, - - /// The disk the instance is configured to boot from. - #[serde(default)] - pub boot_disk: Option, - - /// An allowlist of SSH public keys to be transferred to the instance via - /// cloud-init during instance creation. - pub ssh_public_keys: Option>, - - /// Should this instance be started upon creation; true by default. - #[serde(default = "params::bool_true")] - pub start: bool, - - /// The auto-restart policy for this instance. - #[serde(default)] - pub auto_restart_policy: Option, - - /// Anti-Affinity groups which this instance should be added. - #[serde(default)] - pub anti_affinity_groups: Vec, - - /// The CPU platform to be used for this instance. - #[serde(default)] - pub cpu_platform: Option, -} - -impl From for params::InstanceCreate { - fn from(value: InstanceCreate) -> Self { - Self { - identity: value.identity, - ncpus: value.ncpus, - memory: value.memory, - hostname: value.hostname, - user_data: value.user_data, - network_interfaces: value.network_interfaces, - external_ips: value.external_ips, - multicast_groups: value - .multicast_groups - .into_iter() - .map(|g| params::MulticastGroupJoinSpec { - group: g.into(), - source_ips: None, - ip_version: None, - }) - .collect(), - disks: value.disks.into_iter().map(Into::into).collect(), - boot_disk: value.boot_disk.map(Into::into), - ssh_public_keys: value.ssh_public_keys, - start: value.start, - auto_restart_policy: value.auto_restart_policy, - anti_affinity_groups: value.anti_affinity_groups, - cpu_platform: value.cpu_platform, - } - } -} diff --git a/nexus/external-api/src/v2026011501.rs b/nexus/external-api/src/v2026011501.rs deleted file mode 100644 index d9c08417eaf..00000000000 --- a/nexus/external-api/src/v2026011501.rs +++ /dev/null @@ -1,257 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Types from API version 2026010500 (`POOL_SELECTION_ENUMS`) that changed in -//! version 2026011600 (`RENAME_ADDRESS_SELECTOR_TO_ADDRESS_ALLOCATOR`). -//! -//! ## AddressAllocator Rename -//! -//! [`FloatingIpCreate`] has an `address_selector` field with type -//! [`AddressSelector`]. The "selector" naming is a misnomer in our current -//! scheme, where "selector" implies filtering/fetching from existing resources. -//! `RENAME_ADDRESS_SELECTOR_TO_ADDRESS_ALLOCATOR` renames these to -//! `address_allocator` and [`AddressAllocator`], which better describes the -//! action of reserving/assigning a floating IP from a pool. -//! -//! Affected endpoints: -//! - `POST /v1/floating-ips` (floating_ip_create) -//! -//! [`FloatingIpCreate`]: self::FloatingIpCreate -//! [`AddressSelector`]: self::AddressSelector -//! [`AddressAllocator`]: crate::v2026011600::AddressAllocator - -use std::net::IpAddr; - -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::v2026011600; -use nexus_types::external_api::params; -use omicron_common::api::external::{IdentityMetadataCreateParams, NameOrId}; - -/// Specify how to allocate a floating IP address. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum AddressSelector { - /// Reserve a specific IP address. - Explicit { - /// The IP address to reserve. Must be available in the pool. - ip: IpAddr, - /// The pool containing this address. If not specified, the default - /// pool for the address's IP version is used. - pool: Option, - }, - /// Automatically allocate an IP address from a specified pool. - Auto { - /// Pool selection. - /// - /// If omitted, this field uses the silo's default pool. If the - /// silo has default pools for both IPv4 and IPv6, the request will - /// fail unless `ip_version` is specified in the pool selector. - #[serde(default)] - pool_selector: params::PoolSelector, - }, -} - -impl Default for AddressSelector { - fn default() -> Self { - AddressSelector::Auto { pool_selector: params::PoolSelector::default() } - } -} - -impl From for v2026011600::AddressAllocator { - fn from(value: AddressSelector) -> Self { - match value { - AddressSelector::Explicit { ip, pool } => { - v2026011600::AddressAllocator::Explicit { ip, pool } - } - AddressSelector::Auto { pool_selector } => { - v2026011600::AddressAllocator::Auto { pool_selector } - } - } - } -} - -impl From for params::AddressAllocator { - fn from(value: AddressSelector) -> Self { - v2026011600::AddressAllocator::from(value).into() - } -} - -/// Parameters for creating a new floating IP address for instances. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct FloatingIpCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// IP address allocation method. - #[serde(default)] - pub address_selector: AddressSelector, -} - -impl From for v2026011600::FloatingIpCreate { - fn from(value: FloatingIpCreate) -> Self { - Self { - identity: value.identity, - address_allocator: value.address_selector.into(), - } - } -} - -impl From for params::FloatingIpCreate { - fn from(value: FloatingIpCreate) -> Self { - v2026011600::FloatingIpCreate::from(value).into() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::{ - identity_strategy, optional_name_or_id_strategy, pool_selector_strategy, - }; - use omicron_common::api::external::IpVersion; - use proptest::prelude::*; - use std::net::IpAddr; - use test_strategy::proptest; - - fn address_selector_strategy() -> impl Strategy { - prop_oneof![ - (any::(), optional_name_or_id_strategy()) - .prop_map(|(ip, pool)| AddressSelector::Explicit { ip, pool }), - pool_selector_strategy().prop_map(|pool_selector| { - AddressSelector::Auto { pool_selector } - }), - ] - } - - fn floating_ip_create_strategy() -> impl Strategy - { - (identity_strategy(), address_selector_strategy()).prop_map( - |(identity, address_selector)| FloatingIpCreate { - identity, - address_selector, - }, - ) - } - - /// Verifies that conversion to params::FloatingIpCreate preserves identity - /// and correctly maps AddressSelector to AddressAllocator. - #[proptest] - fn floating_ip_create_converts_correctly( - #[strategy(floating_ip_create_strategy())] expected: FloatingIpCreate, - ) { - use proptest::test_runner::TestCaseError; - let actual: params::FloatingIpCreate = expected.clone().into(); - - prop_assert_eq!(expected.identity.name, actual.identity.name); - prop_assert_eq!( - expected.identity.description, - actual.identity.description - ); - - match expected.address_selector { - AddressSelector::Explicit { ip: expected_ip, .. } => { - match actual.address_allocator { - params::AddressAllocator::Explicit { ip } => { - prop_assert_eq!(expected_ip, ip); - } - _ => { - return Err(TestCaseError::fail( - "expected Explicit variant", - )); - } - } - } - AddressSelector::Auto { pool_selector: expected_pool_selector } => { - match actual.address_allocator { - params::AddressAllocator::Auto { - pool_selector: actual_pool_selector, - } => { - prop_assert_eq!( - expected_pool_selector, - actual_pool_selector - ); - } - _ => { - return Err(TestCaseError::fail( - "expected Auto variant", - )); - } - } - } - } - } - - /// Verifies explicit JSON wire format parses into versioned types. - /// Conversion to latest params is tested by floating_ip_create_converts_correctly. - #[test] - fn explicit_json_wire_format() { - let json = - r#"{"type": "explicit", "ip": "10.0.0.1", "pool": "my-pool"}"#; - - // Must parse into this version (AddressSelector) - let v2026011501: AddressSelector = serde_json::from_str(json).unwrap(); - assert!(matches!(v2026011501, AddressSelector::Explicit { .. })); - - // Must also parse into v2026011600 (AddressAllocator) - let v2026011600: v2026011600::AddressAllocator = - serde_json::from_str(json).unwrap(); - assert!(matches!( - v2026011600, - v2026011600::AddressAllocator::Explicit { .. } - )); - } - - /// Verifies auto JSON wire format with explicit pool selector. - #[test] - fn auto_explicit_pool_json_wire_format() { - let json = r#"{"type": "auto", "pool_selector": {"type": "explicit", "pool": "my-pool"}}"#; - - let parsed: AddressSelector = serde_json::from_str(json).unwrap(); - match parsed { - AddressSelector::Auto { pool_selector } => { - assert!(matches!( - pool_selector, - params::PoolSelector::Explicit { .. } - )); - } - _ => panic!("Expected Auto variant"), - } - } - - /// Verifies auto JSON wire format with auto pool selector. - #[test] - fn auto_auto_pool_json_wire_format() { - let json = r#"{"type": "auto", "pool_selector": {"type": "auto", "ip_version": "v4"}}"#; - - let parsed: AddressSelector = serde_json::from_str(json).unwrap(); - match parsed { - AddressSelector::Auto { pool_selector } => match pool_selector { - params::PoolSelector::Auto { ip_version } => { - assert_eq!(ip_version, Some(IpVersion::V4)); - } - _ => panic!("Expected Auto pool selector"), - }, - _ => panic!("Expected Auto variant"), - } - } - - /// Verifies auto JSON wire format with default pool selector. - #[test] - fn auto_default_json_wire_format() { - let json = r#"{"type": "auto"}"#; - - let parsed: AddressSelector = serde_json::from_str(json).unwrap(); - match parsed { - AddressSelector::Auto { pool_selector } => match pool_selector { - params::PoolSelector::Auto { ip_version } => { - assert_eq!(ip_version, None); - } - _ => panic!("Expected Auto pool selector"), - }, - _ => panic!("Expected Auto variant"), - } - } -} diff --git a/nexus/external-api/src/v2026011600.rs b/nexus/external-api/src/v2026011600.rs deleted file mode 100644 index 8291db9afa1..00000000000 --- a/nexus/external-api/src/v2026011600.rs +++ /dev/null @@ -1,239 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Types from API version 2026011600 (`RENAME_ADDRESS_SELECTOR_TO_ADDRESS_ALLOCATOR`) -//! that changed in version 2026012200 (`FLOATING_IP_ALLOCATOR_UPDATE`). -//! -//! These types are also valid through version 2026011601 (`EXTERNAL_SUBNET_ATTACHMENT`), -//! which did not modify them. -//! -//! ## AddressAllocator Changes -//! -//! This version's [`AddressAllocator::Explicit`] has both `ip` and optional `pool` fields. -//! `FLOATING_IP_ALLOCATOR_UPDATE` simplifies `Explicit` to only require `ip` (pool is -//! inferred from the address), with pool-based allocation moving to -//! `Auto(PoolSelector::Explicit { pool })`. -//! -//! Affected endpoints: -//! - `POST /v1/floating-ips` (floating_ip_create) -//! -//! [`AddressAllocator::Explicit`]: self::AddressAllocator::Explicit - -use std::net::IpAddr; - -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use nexus_types::external_api::params; -use omicron_common::api::external::{IdentityMetadataCreateParams, NameOrId}; - -/// Specify how to allocate a floating IP address. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum AddressAllocator { - /// Reserve a specific IP address. - Explicit { - /// The IP address to reserve. Must be available in the pool. - ip: IpAddr, - /// The pool containing this address. If not specified, the default - /// pool for the address's IP version is used. - pool: Option, - }, - /// Automatically allocate an IP address from a specified pool. - Auto { - /// Pool selection. - /// - /// If omitted, this field uses the silo's default pool. If the - /// silo has default pools for both IPv4 and IPv6, the request will - /// fail unless `ip_version` is specified in the pool selector. - #[serde(default)] - pool_selector: params::PoolSelector, - }, -} - -impl Default for AddressAllocator { - fn default() -> Self { - AddressAllocator::Auto { - pool_selector: params::PoolSelector::default(), - } - } -} - -impl From for params::AddressAllocator { - fn from(value: AddressAllocator) -> Self { - match value { - // Pool field is ignored since the IP uniquely identifies the pool - AddressAllocator::Explicit { ip, pool: _ } => { - params::AddressAllocator::Explicit { ip } - } - AddressAllocator::Auto { pool_selector } => { - params::AddressAllocator::Auto { pool_selector } - } - } - } -} - -/// Parameters for creating a new floating IP address for instances. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct FloatingIpCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// IP address allocation method. - #[serde(default)] - pub address_allocator: AddressAllocator, -} - -impl From for params::FloatingIpCreate { - fn from(value: FloatingIpCreate) -> Self { - Self { - identity: value.identity, - address_allocator: value.address_allocator.into(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::{ - identity_strategy, optional_name_or_id_strategy, pool_selector_strategy, - }; - use omicron_common::api::external::IpVersion; - use proptest::prelude::*; - use std::net::IpAddr; - use test_strategy::proptest; - - fn address_allocator_strategy() -> impl Strategy { - prop_oneof![ - (any::(), optional_name_or_id_strategy()) - .prop_map(|(ip, pool)| AddressAllocator::Explicit { ip, pool }), - pool_selector_strategy().prop_map(|pool_selector| { - AddressAllocator::Auto { pool_selector } - }), - ] - } - - fn floating_ip_create_strategy() -> impl Strategy - { - (identity_strategy(), address_allocator_strategy()).prop_map( - |(identity, address_allocator)| FloatingIpCreate { - identity, - address_allocator, - }, - ) - } - - /// Verifies that conversion to params::FloatingIpCreate preserves identity - /// and correctly maps AddressAllocator. - #[proptest] - fn floating_ip_create_converts_correctly( - #[strategy(floating_ip_create_strategy())] expected: FloatingIpCreate, - ) { - use proptest::test_runner::TestCaseError; - let actual: params::FloatingIpCreate = expected.clone().into(); - - prop_assert_eq!(expected.identity.name, actual.identity.name); - prop_assert_eq!( - expected.identity.description, - actual.identity.description - ); - - match expected.address_allocator { - AddressAllocator::Explicit { ip: expected_ip, .. } => { - match actual.address_allocator { - params::AddressAllocator::Explicit { ip } => { - prop_assert_eq!(expected_ip, ip); - } - _ => { - return Err(TestCaseError::fail( - "expected Explicit variant", - )); - } - } - } - AddressAllocator::Auto { - pool_selector: expected_pool_selector, - } => match actual.address_allocator { - params::AddressAllocator::Auto { - pool_selector: actual_pool_selector, - } => { - prop_assert_eq!( - expected_pool_selector, - actual_pool_selector - ); - } - _ => { - return Err(TestCaseError::fail("expected Auto variant")); - } - }, - } - } - - /// Verifies explicit JSON wire format. - #[test] - fn explicit_json_wire_format() { - let json = r#"{"type": "explicit", "ip": "10.0.0.1"}"#; - - let parsed: AddressAllocator = serde_json::from_str(json).unwrap(); - match parsed { - AddressAllocator::Explicit { ip, .. } => { - let expected: IpAddr = "10.0.0.1".parse().unwrap(); - assert_eq!(ip, expected); - } - _ => panic!("Expected Explicit variant"), - } - } - - /// Verifies auto JSON wire format with explicit pool selector. - #[test] - fn auto_explicit_pool_json_wire_format() { - let json = r#"{"type": "auto", "pool_selector": {"type": "explicit", "pool": "my-pool"}}"#; - - let parsed: AddressAllocator = serde_json::from_str(json).unwrap(); - match parsed { - AddressAllocator::Auto { pool_selector } => { - assert!(matches!( - pool_selector, - params::PoolSelector::Explicit { .. } - )); - } - _ => panic!("Expected Auto variant"), - } - } - - /// Verifies auto JSON wire format with auto pool selector. - #[test] - fn auto_auto_pool_json_wire_format() { - let json = r#"{"type": "auto", "pool_selector": {"type": "auto", "ip_version": "v4"}}"#; - - let parsed: AddressAllocator = serde_json::from_str(json).unwrap(); - match parsed { - AddressAllocator::Auto { pool_selector } => match pool_selector { - params::PoolSelector::Auto { ip_version } => { - assert_eq!(ip_version, Some(IpVersion::V4)); - } - _ => panic!("Expected Auto pool selector"), - }, - _ => panic!("Expected Auto variant"), - } - } - - /// Verifies auto JSON wire format with default pool selector. - #[test] - fn auto_default_json_wire_format() { - let json = r#"{"type": "auto"}"#; - - let parsed: AddressAllocator = serde_json::from_str(json).unwrap(); - match parsed { - AddressAllocator::Auto { pool_selector } => match pool_selector { - params::PoolSelector::Auto { ip_version } => { - assert_eq!(ip_version, None); - } - _ => panic!("Expected Auto pool selector"), - }, - _ => panic!("Expected Auto variant"), - } - } -} diff --git a/nexus/external-api/src/v2026012200.rs b/nexus/external-api/src/v2026012200.rs deleted file mode 100644 index a4f9896df71..00000000000 --- a/nexus/external-api/src/v2026012200.rs +++ /dev/null @@ -1,143 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Types that changed from v2026012200 to v2026012201. - -use nexus_types::external_api::params; -use nexus_types::external_api::params::PoolSelector; -use nexus_types::external_api::shared; -use omicron_common::address::IpVersion; -use omicron_common::api::external::Error; -use omicron_common::api::external::IdentityMetadataCreateParams; -use omicron_common::api::external::NameOrId; -use oxnet::IpNet; -use schemars::JsonSchema; -use serde::Deserialize; -use serde::Serialize; - -/// Create a subnet pool -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SubnetPoolCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// The IP version for this pool (IPv4 or IPv6). All subnets in the pool - /// must match this version. - pub ip_version: IpVersion, - /// Type of subnet pool (defaults to Unicast) - #[serde(default)] - pub pool_type: shared::IpPoolType, -} - -impl TryFrom for params::SubnetPoolCreate { - type Error = Error; - - fn try_from(value: SubnetPoolCreate) -> Result { - let SubnetPoolCreate { identity, ip_version, pool_type } = value; - if pool_type != shared::IpPoolType::Unicast { - return Err(Error::invalid_request( - "Subnet Pools must have pool type unicast", - )); - } - Ok(Self { identity, ip_version }) - } -} -/// Add a member (subnet) to a subnet pool -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SubnetPoolMemberAdd { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// The subnet to add to the pool - pub subnet: IpNet, - /// Minimum prefix length for allocations from this subnet; a smaller prefix - /// means larger allocations are allowed (e.g. a /16 prefix yields larger - /// subnet allocations than a /24 prefix). - /// - /// Valid values: 0-32 for IPv4, 0-128 for IPv6. - /// Default if not specified is equal to the subnet's prefix length. - pub min_prefix_length: Option, - /// Maximum prefix length for allocations from this subnet; a larger prefix - /// means smaller allocations are allowed (e.g. a /24 prefix yields smaller - /// subnet allocations than a /16 prefix). - /// - /// Valid values: 0-32 for IPv4, 0-128 for IPv6. - /// Default if not specified is 32 for IPv4 and 128 for IPv6. - pub max_prefix_length: Option, -} - -impl From for params::SubnetPoolMemberAdd { - fn from(value: SubnetPoolMemberAdd) -> Self { - let SubnetPoolMemberAdd { - subnet, - min_prefix_length, - max_prefix_length, - .. - } = value; - Self { subnet, min_prefix_length, max_prefix_length } - } -} - -/// Specify how to allocate an external subnet. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum ExternalSubnetAllocator { - /// Reserve a specific subnet. - Explicit { - /// The subnet CIDR to reserve. Must be available in the pool. - subnet: IpNet, - /// The pool containing this subnet. If not specified, the default - /// subnet pool for the subnet's IP version is used. - pool: Option, - }, - /// Automatically allocate a subnet with the specified prefix length. - Auto { - /// The prefix length for the allocated subnet (e.g., 24 for a /24). - prefix_len: u8, - /// Pool selection. - /// - /// If omitted, this field uses the silo's default pool. If the - /// silo has default pools for both IPv4 and IPv6, the request will - /// fail unless `ip_version` is specified in the pool selector. - #[serde(default)] - pool_selector: PoolSelector, - }, -} - -impl TryFrom for params::ExternalSubnetAllocator { - type Error = Error; - - fn try_from(value: ExternalSubnetAllocator) -> Result { - match value { - ExternalSubnetAllocator::Explicit { subnet, pool } => { - if pool.is_some() { - return Err(Error::invalid_request( - "May not specify both an IP subnet and a Subnet Pool", - )); - } - Ok(Self::Explicit { subnet }) - } - ExternalSubnetAllocator::Auto { prefix_len, pool_selector } => { - Ok(Self::Auto { prefix_len, pool_selector }) - } - } - } -} - -/// Create an external subnet -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct ExternalSubnetCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// Subnet allocation method. - pub allocator: ExternalSubnetAllocator, -} - -impl TryFrom for params::ExternalSubnetCreate { - type Error = Error; - - fn try_from(value: ExternalSubnetCreate) -> Result { - let ExternalSubnetCreate { identity, allocator } = value; - allocator.try_into().map(|allocator| Self { identity, allocator }) - } -} diff --git a/nexus/external-api/src/v2026012300.rs b/nexus/external-api/src/v2026012300.rs deleted file mode 100644 index 2f1acff25f8..00000000000 --- a/nexus/external-api/src/v2026012300.rs +++ /dev/null @@ -1,90 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Types that changed from v2026012300 to v2026013000. -//! -//! # Summary of changes -//! -//! - Remove the `pool_type` from subnet pools. It's not clear what that really -//! means, and no customers need it at this point. -//! - Removed the identity metadata from subnet pool members. The name isn't -//! user-controllable, and the IP subnet itself is the identity. This is -//! similar to IP Pool Ranges. -//! - Changed subnet pool member pagination to be by-IP-subnet. - -use api_identity::ObjectIdentity; -use nexus_types::external_api::shared; -use nexus_types::external_api::views; -use omicron_common::address::IpVersion; -use omicron_common::api::external::IdentityMetadata; -use omicron_common::api::external::ObjectIdentity; -use oxnet::IpNet; -use schemars::JsonSchema; -use serde::Deserialize; -use serde::Serialize; -use uuid::Uuid; - -/// A pool of subnets for external subnet allocation -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SubnetPool { - #[serde(flatten)] - pub identity: IdentityMetadata, - /// The IP version for this pool - pub ip_version: IpVersion, - /// Type of subnet pool (unicast or multicast) - pub pool_type: shared::IpPoolType, -} - -impl From for SubnetPool { - fn from(value: views::SubnetPool) -> Self { - // Assume unicast pool type, this was the only thing it could be from - // this version of the API. - Self { - identity: value.identity, - ip_version: value.ip_version, - pool_type: shared::IpPoolType::Unicast, - } - } -} - -/// A member (subnet) within a subnet pool -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SubnetPoolMember { - #[serde(flatten)] - pub identity: IdentityMetadata, - /// ID of the parent subnet pool - pub subnet_pool_id: Uuid, - /// The subnet CIDR - pub subnet: IpNet, - /// Minimum prefix length for allocations from this subnet; a smaller prefix - /// means larger allocations are allowed (e.g. a /16 prefix yields larger - /// subnet allocations than a /24 prefix). - pub min_prefix_length: u8, - /// Maximum prefix length for allocations from this subnet; a larger prefix - /// means smaller allocations are allowed (e.g. a /24 prefix yields smaller - /// subnet allocations than a /16 prefix). - pub max_prefix_length: u8, -} - -impl From for SubnetPoolMember { - fn from(value: views::SubnetPoolMember) -> Self { - // The identity metadata has gone away in the newer version. The name - // wasn't user controllable, so we fake up a dummy name. Since the - // members can't be updated, the modification time is always the same as - // the creation time too. - Self { - identity: IdentityMetadata { - id: value.id, - name: "unused".parse().unwrap(), - description: String::new(), - time_created: value.time_created, - time_modified: value.time_created, - }, - subnet_pool_id: value.subnet_pool_id, - subnet: value.subnet, - min_prefix_length: value.min_prefix_length, - max_prefix_length: value.max_prefix_length, - } - } -} diff --git a/nexus/external-api/src/v2026013001.rs b/nexus/external-api/src/v2026013001.rs deleted file mode 100644 index 3e6b90eb848..00000000000 --- a/nexus/external-api/src/v2026013001.rs +++ /dev/null @@ -1,279 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Types from API version 2026013001 (`READ_ONLY_DISKS`) that changed -//! in version 2026013100 (`READ_ONLY_DISKS_NULLABLE`). -//! -//! This is one of the silliest API versions so far (if not *the* silliest), -//! since all the Rust structs are completely identical. However, making the API -//! accept [`DiskSource::Snapshot`] and [`DiskSource::Image`] messages _without_ -//! a `read_only` field requires adding a `#[serde(default)]` attribute, which -//! changes the generated OpenAPI document, and the easiest way to generate a -//! new OpenAPI document is just to...copy and paste all the types again. Yay. - -use nexus_types::external_api::params; -use omicron_common::api::external; -use omicron_common::api::external::ByteCount; -use omicron_common::api::external::IdentityMetadataCreateParams; -use schemars::JsonSchema; -use serde::Deserialize; -use serde::Serialize; -use uuid::Uuid; - -/// Create-time parameters for a `Disk` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct DiskCreate { - /// The common identifying metadata for the disk - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// The source for this `Disk`'s blocks - pub disk_backend: DiskBackend, - - /// The total size of the Disk (in bytes) - pub size: ByteCount, -} - -impl From for params::DiskCreate { - fn from(old: DiskCreate) -> Self { - let DiskCreate { identity, disk_backend, size } = old; - params::DiskCreate { identity, disk_backend: disk_backend.into(), size } - } -} - -/// The source of a `Disk`'s blocks -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum DiskBackend { - Local {}, - - Distributed { - /// The initial source for this disk - disk_source: DiskSource, - }, -} - -impl From for params::DiskBackend { - fn from(old: DiskBackend) -> Self { - match old { - DiskBackend::Local {} => params::DiskBackend::Local {}, - DiskBackend::Distributed { disk_source } => { - params::DiskBackend::Distributed { - disk_source: disk_source.into(), - } - } - } - } -} - -/// Different sources for a Distributed Disk -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum DiskSource { - /// Create a blank disk - Blank { - /// size of blocks for this Disk. valid values are: 512, 2048, or 4096 - block_size: params::BlockSize, - }, - - /// Create a disk from a disk snapshot - Snapshot { - snapshot_id: Uuid, - /// If `true`, the disk created from this snapshot will be read-only. - read_only: bool, - }, - - /// Create a disk from an image - Image { - image_id: Uuid, - /// If `true`, the disk created from this image will be read-only. - read_only: bool, - }, - - /// Create a blank disk that will accept bulk writes or pull blocks from an - /// external source. - ImportingBlocks { block_size: params::BlockSize }, -} - -impl From for params::DiskSource { - fn from(old: DiskSource) -> Self { - // This is the funny part: you'll note that all these types are - // ~*EXACTLY THE SAME*~. I love API versioning! - match old { - DiskSource::Blank { block_size } => Self::Blank { block_size }, - DiskSource::Snapshot { snapshot_id, read_only } => { - Self::Snapshot { snapshot_id, read_only } - } - DiskSource::Image { image_id, read_only } => { - Self::Image { image_id, read_only } - } - DiskSource::ImportingBlocks { block_size } => { - Self::ImportingBlocks { block_size } - } - } - } -} - -/// Describe the instance's disks at creation time -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum InstanceDiskAttachment { - /// During instance creation, create and attach disks - Create(DiskCreate), - - /// During instance creation, attach this disk - Attach(params::InstanceDiskAttach), -} - -impl From for params::InstanceDiskAttachment { - fn from(old: InstanceDiskAttachment) -> params::InstanceDiskAttachment { - match old { - InstanceDiskAttachment::Create(create) => { - params::InstanceDiskAttachment::Create(create.into()) - } - - InstanceDiskAttachment::Attach(attach) => { - params::InstanceDiskAttachment::Attach(attach) - } - } - } -} - -/// Create-time parameters for an `Instance` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceCreate { - #[serde(flatten)] - pub identity: external::IdentityMetadataCreateParams, - /// The number of vCPUs to be allocated to the instance - pub ncpus: external::InstanceCpuCount, - /// The amount of RAM (in bytes) to be allocated to the instance - pub memory: external::ByteCount, - /// The hostname to be assigned to the instance - pub hostname: external::Hostname, - - /// User data for instance initialization systems (such as cloud-init). - /// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / - /// characters with padding). Maximum 32 KiB unencoded data. - // While serde happily accepts #[serde(with = "")] as a shorthand for - // specifying `serialize_with` and `deserialize_with`, schemars requires the - // argument to `with` to be a type rather than merely a path prefix (i.e. a - // mod or type). It's admittedly a bit tricky for schemars to address; - // unlike `serialize` or `deserialize`, `JsonSchema` requires several - // functions working together. It's unfortunate that schemars has this - // built-in incompatibility, exacerbated by its glacial rate of progress - // and immunity to offers of help. - #[serde(default, with = "params::UserData")] - pub user_data: Vec, - - /// The network interfaces to be created for this instance. - #[serde(default)] - pub network_interfaces: params::InstanceNetworkInterfaceAttachment, - - /// The external IP addresses provided to this instance. - /// - /// By default, all instances have outbound connectivity, but no inbound - /// connectivity. These external addresses can be used to provide a fixed, - /// known IP address for making inbound connections to the instance. - // Delegates through v2025121200 → params::ExternalIpCreate - #[serde(default)] - pub external_ips: Vec, - - /// Multicast groups this instance should join at creation. - /// - /// Groups can be specified by name, UUID, or IP address. Non-existent - /// groups are created automatically. - #[serde(default)] - pub multicast_groups: Vec, - - /// A list of disks to be attached to the instance. - /// - /// Disk attachments of type "create" will be created, while those of type - /// "attach" must already exist. - /// - /// The order of this list does not guarantee a boot order for the instance. - /// Use the boot_disk attribute to specify a boot disk. When boot_disk is - /// specified it will count against the disk attachment limit. - #[serde(default)] - pub disks: Vec, - - /// The disk the instance is configured to boot from. - /// - /// This disk can either be attached if it already exists or created along - /// with the instance. - /// - /// Specifying a boot disk is optional but recommended to ensure predictable - /// boot behavior. The boot disk can be set during instance creation or - /// later if the instance is stopped. The boot disk counts against the disk - /// attachment limit. - /// - /// An instance that does not have a boot disk set will use the boot - /// options specified in its UEFI settings, which are controlled by both the - /// instance's UEFI firmware and the guest operating system. Boot options - /// can change as disks are attached and detached, which may result in an - /// instance that only boots to the EFI shell until a boot disk is set. - #[serde(default)] - pub boot_disk: Option, - - /// An allowlist of SSH public keys to be transferred to the instance via - /// cloud-init during instance creation. - /// - /// If not provided, all SSH public keys from the user's profile will be sent. - /// If an empty list is provided, no public keys will be transmitted to the - /// instance. - pub ssh_public_keys: Option>, - - /// Should this instance be started upon creation; true by default. - #[serde(default = "params::bool_true")] - pub start: bool, - - /// The auto-restart policy for this instance. - /// - /// This policy determines whether the instance should be automatically - /// restarted by the control plane on failure. If this is `null`, no - /// auto-restart policy will be explicitly configured for this instance, and - /// the control plane will select the default policy when determining - /// whether the instance can be automatically restarted. - /// - /// Currently, the global default auto-restart policy is "best-effort", so - /// instances with `null` auto-restart policies will be automatically - /// restarted. However, in the future, the default policy may be - /// configurable through other mechanisms, such as on a per-project basis. - /// In that case, any configured default policy will be used if this is - /// `null`. - #[serde(default)] - pub auto_restart_policy: Option, - - /// Anti-Affinity groups which this instance should be added. - #[serde(default)] - pub anti_affinity_groups: Vec, - - /// The CPU platform to be used for this instance. If this is `null`, the - /// instance requires no particular CPU platform; when it is started the - /// instance will have the most general CPU platform supported by the sled - /// it is initially placed on. - #[serde(default)] - pub cpu_platform: Option, -} - -impl From for params::InstanceCreate { - fn from(old: InstanceCreate) -> params::InstanceCreate { - params::InstanceCreate { - identity: old.identity, - ncpus: old.ncpus, - memory: old.memory, - hostname: old.hostname, - user_data: old.user_data, - network_interfaces: old.network_interfaces, - external_ips: old.external_ips, - multicast_groups: old.multicast_groups, - disks: old.disks.into_iter().map(Into::into).collect(), - boot_disk: old.boot_disk.map(Into::into), - ssh_public_keys: old.ssh_public_keys, - start: old.start, - auto_restart_policy: old.auto_restart_policy, - anti_affinity_groups: old.anti_affinity_groups, - cpu_platform: old.cpu_platform, - } - } -} diff --git a/nexus/external-api/src/v2026020600.rs b/nexus/external-api/src/v2026020600.rs deleted file mode 100644 index 181598b4e3c..00000000000 --- a/nexus/external-api/src/v2026020600.rs +++ /dev/null @@ -1,506 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Nexus external types that changed from 2026020600 to 2026020700. -//! -//! Version 2026020600 types (before BGP unnumbered peers support was added). -//! -//! Key differences from newer API versions: -//! - [`BgpPeer`] has a required `addr` field. Newer versions make `addr` -//! optional to support BGP unnumbered sessions where the peer address -//! is discovered dynamically via the interface rather than being specified. -//! - [`BgpPeer`] lacks the `router_lifetime` field. Newer versions include -//! `router_lifetime` to configure IPv6 router advertisement lifetime. -//! - [`BgpPeerConfig`] contains the old [`BgpPeer`] type with required `addr`. -//! - [`SwitchPortSettingsCreate`] uses the old [`BgpPeerConfig`] type. -//! - [`SwitchPortSettings`] contains the old [`BgpPeer`] type with required `addr`. -//! - [`BgpConfigCreate`] lacks the `max_paths` field. Newer versions include -//! `max_paths` to configure BGP multipath support. -//! - [`BgpConfig`] lacks the `max_paths` field. Newer versions include -//! `max_paths` to configure BGP multipath support. -//! - [`BgpPeerStatus`] lacks the `peer_id` field. Newer versions include -//! `peer_id` to identify the peer. -//! - [`BgpImportedRouteIpv4`] is IPv4-only. Newer versions use a generic -//! `BgpImported` type that supports both IPv4 and IPv6. -//! - [`BgpExported`] is IPv4-only with peer addresses as strings. Newer -//! versions use a type that supports both IPv4 and IPv6. -//! -//! [`BgpPeer`]: self::BgpPeer -//! [`BgpPeerConfig`]: self::BgpPeerConfig -//! [`SwitchPortSettingsCreate`]: self::SwitchPortSettingsCreate -//! [`SwitchPortSettings`]: self::SwitchPortSettings -//! [`BgpConfigCreate`]: self::BgpConfigCreate -//! [`BgpConfig`]: self::BgpConfig -//! [`BgpPeerStatus`]: self::BgpPeerStatus -//! [`BgpImportedRouteIpv4`]: self::BgpImportedRouteIpv4 -//! [`BgpExported`]: self::BgpExported - -use api_identity::ObjectIdentity; -use omicron_common::api::external::{ - self, IdentityMetadata, IdentityMetadataCreateParams, ImportExportPolicy, - Name, NameOrId, -}; -use omicron_common::api::external::{ - BgpImported, ObjectIdentity, SwitchLocation, -}; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::collections::hash_map::Entry; -use std::net::{IpAddr, Ipv4Addr}; - -/// A BGP peer configuration for an interface. Includes the set of announcements -/// that will be advertised to the peer identified by `addr`. The `bgp_config` -/// parameter is a reference to global BGP parameters. The `interface_name` -/// indicates what interface the peer should be contacted on. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct BgpPeer { - /// The global BGP configuration used for establishing a session with this - /// peer. - pub bgp_config: NameOrId, - - /// The name of interface to peer on. This is relative to the port - /// configuration this BGP peer configuration is a part of. For example this - /// value could be phy0 to refer to a primary physical interface. Or it - /// could be vlan47 to refer to a VLAN interface. - pub interface_name: Name, - - /// The address of the host to peer with. - pub addr: IpAddr, - - /// How long to hold peer connections between keepalives (seconds). - pub hold_time: u32, - - /// How long to hold a peer in idle before attempting a new session - /// (seconds). - pub idle_hold_time: u32, - - /// How long to delay sending an open request after establishing a TCP - /// session (seconds). - pub delay_open: u32, - - /// How long to to wait between TCP connection retries (seconds). - pub connect_retry: u32, - - /// How often to send keepalive requests (seconds). - pub keepalive: u32, - - /// Require that a peer has a specified ASN. - pub remote_asn: Option, - - /// Require messages from a peer have a minimum IP time to live field. - pub min_ttl: Option, - - /// Use the given key for TCP-MD5 authentication with the peer. - pub md5_auth_key: Option, - - /// Apply the provided multi-exit discriminator (MED) updates sent to the peer. - pub multi_exit_discriminator: Option, - - /// Include the provided communities in updates sent to the peer. - pub communities: Vec, - - /// Apply a local preference to routes received from this peer. - pub local_pref: Option, - - /// Enforce that the first AS in paths received from this peer is the peer's AS. - pub enforce_first_as: bool, - - /// Define import policy for a peer. - pub allowed_import: ImportExportPolicy, - - /// Define export policy for a peer. - pub allowed_export: ImportExportPolicy, - - /// Associate a VLAN ID with a peer. - pub vlan_id: Option, -} - -impl From for external::BgpPeer { - fn from(old: BgpPeer) -> external::BgpPeer { - external::BgpPeer { - bgp_config: old.bgp_config, - interface_name: old.interface_name, - addr: Some(old.addr), - hold_time: old.hold_time, - idle_hold_time: old.idle_hold_time, - delay_open: old.delay_open, - connect_retry: old.connect_retry, - keepalive: old.keepalive, - remote_asn: old.remote_asn, - min_ttl: old.min_ttl, - md5_auth_key: old.md5_auth_key, - multi_exit_discriminator: old.multi_exit_discriminator, - communities: old.communities, - local_pref: old.local_pref, - enforce_first_as: old.enforce_first_as, - allowed_import: old.allowed_import, - allowed_export: old.allowed_export, - vlan_id: old.vlan_id, - router_lifetime: 0, - } - } -} - -impl TryFrom for BgpPeer { - type Error = external::Error; - - fn try_from(new: external::BgpPeer) -> Result { - let addr = new.addr.ok_or_else(|| { - external::Error::invalid_request( - "BGP peer has no address configured, but the API version \ - in use requires an address. Update your client to use \ - BGP unnumbered peers.", - ) - })?; - Ok(BgpPeer { - bgp_config: new.bgp_config, - interface_name: new.interface_name, - addr, - hold_time: new.hold_time, - idle_hold_time: new.idle_hold_time, - delay_open: new.delay_open, - connect_retry: new.connect_retry, - keepalive: new.keepalive, - remote_asn: new.remote_asn, - min_ttl: new.min_ttl, - md5_auth_key: new.md5_auth_key, - multi_exit_discriminator: new.multi_exit_discriminator, - communities: new.communities, - local_pref: new.local_pref, - enforce_first_as: new.enforce_first_as, - allowed_import: new.allowed_import, - allowed_export: new.allowed_export, - vlan_id: new.vlan_id, - }) - } -} - -// --- Params types that contain BgpPeer --- - -use nexus_types::external_api::params; - -/// BGP peer configuration for a link (old version with required addr). -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct BgpPeerConfig { - /// Link that the peer is reachable on. - /// On ports that are not broken out, this is always phy0. - /// On a 2x breakout the options are phy0 and phy1, on 4x - /// phy0-phy3, etc. - pub link_name: Name, - - pub peers: Vec, -} - -impl From for params::BgpPeerConfig { - fn from(old: BgpPeerConfig) -> params::BgpPeerConfig { - params::BgpPeerConfig { - link_name: old.link_name, - peers: old.peers.into_iter().map(Into::into).collect(), - } - } -} - -impl TryFrom for BgpPeerConfig { - type Error = external::Error; - - fn try_from(new: params::BgpPeerConfig) -> Result { - Ok(BgpPeerConfig { - link_name: new.link_name, - peers: new - .peers - .into_iter() - .map(BgpPeer::try_from) - .collect::, _>>()?, - }) - } -} - -/// Parameters for creating switch port settings (old version with required BgpPeer.addr). -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SwitchPortSettingsCreate { - #[serde(flatten)] - pub identity: external::IdentityMetadataCreateParams, - - pub port_config: params::SwitchPortConfigCreate, - - #[serde(default)] - pub groups: Vec, - - /// Link configurations. - pub links: Vec, - - /// Interface configurations. - #[serde(default)] - pub interfaces: Vec, - - /// Route configurations. - #[serde(default)] - pub routes: Vec, - - /// BGP peer configurations. - #[serde(default)] - pub bgp_peers: Vec, - - /// Address configurations. - pub addresses: Vec, -} - -impl From for params::SwitchPortSettingsCreate { - fn from(old: SwitchPortSettingsCreate) -> params::SwitchPortSettingsCreate { - params::SwitchPortSettingsCreate { - identity: old.identity, - port_config: old.port_config, - groups: old.groups, - links: old.links, - interfaces: old.interfaces, - routes: old.routes, - bgp_peers: old.bgp_peers.into_iter().map(Into::into).collect(), - addresses: old.addresses, - } - } -} - -// --- Response types that contain BgpPeer --- - -/// Switch port settings (old version with required BgpPeer.addr). -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SwitchPortSettings { - #[serde(flatten)] - pub identity: external::IdentityMetadata, - - /// Switch port settings included from other switch port settings groups. - pub groups: Vec, - - /// Layer 1 physical port settings. - pub port: external::SwitchPortConfig, - - /// Layer 2 link settings. - pub links: Vec, - - /// Layer 3 interface settings. - pub interfaces: Vec, - - /// Vlan interface settings. - pub vlan_interfaces: Vec, - - /// IP route settings. - pub routes: Vec, - - /// BGP peer settings. - pub bgp_peers: Vec, - - /// Layer 3 IP address settings. - pub addresses: Vec, -} - -impl TryFrom for SwitchPortSettings { - type Error = external::Error; - - fn try_from( - new: external::SwitchPortSettings, - ) -> Result { - Ok(SwitchPortSettings { - identity: new.identity, - groups: new.groups, - port: new.port, - links: new.links, - interfaces: new.interfaces, - vlan_interfaces: new.vlan_interfaces, - routes: new.routes, - bgp_peers: new - .bgp_peers - .into_iter() - .map(BgpPeer::try_from) - .collect::, _>>()?, - addresses: new.addresses, - }) - } -} - -/// Parameters for creating a BGP configuration. This includes and autonomous -/// system number (ASN) and a virtual routing and forwarding (VRF) identifier. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct BgpConfigCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// The autonomous system number of this BGP configuration. - pub asn: u32, - - pub bgp_announce_set_id: NameOrId, - - /// Optional virtual routing and forwarding identifier for this BGP - /// configuration. - pub vrf: Option, - - // Dynamic BGP policy is not yet available so we skip adding it to the API - /// A shaper program to apply to outgoing open and update messages. - #[serde(skip)] - pub shaper: Option, - /// A checker program to apply to incoming open and update messages. - #[serde(skip)] - pub checker: Option, -} - -impl From for params::BgpConfigCreate { - fn from(old: BgpConfigCreate) -> params::BgpConfigCreate { - params::BgpConfigCreate { - identity: old.identity, - asn: old.asn, - bgp_announce_set_id: old.bgp_announce_set_id, - vrf: old.vrf, - shaper: old.shaper, - checker: old.checker, - max_paths: Default::default(), - } - } -} - -/// A base BGP configuration. -#[derive( - ObjectIdentity, Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq, -)] -pub struct BgpConfig { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// The autonomous system number of this BGP configuration. - pub asn: u32, - - /// Optional virtual routing and forwarding identifier for this BGP - /// configuration. - pub vrf: Option, -} - -impl From for external::BgpConfig { - fn from(old: BgpConfig) -> external::BgpConfig { - external::BgpConfig { - identity: old.identity, - asn: old.asn, - vrf: old.vrf, - max_paths: Default::default(), - } - } -} - -/// The current status of a BGP peer. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct BgpPeerStatus { - /// IP address of the peer. - pub addr: IpAddr, - - /// Local autonomous system number. - pub local_asn: u32, - - /// Remote autonomous system number. - pub remote_asn: u32, - - /// State of the peer. - pub state: external::BgpPeerState, - - /// Time of last state change. - pub state_duration_millis: u64, - - /// Switch with the peer session. - pub switch: external::SwitchLocation, -} - -impl From for external::BgpPeerStatus { - fn from(value: BgpPeerStatus) -> Self { - Self { - addr: value.addr, - peer_id: String::default(), - local_asn: value.local_asn, - remote_asn: value.remote_asn, - state: value.state.clone(), - state_duration_millis: value.state_duration_millis, - switch: value.switch, - } - } -} - -impl From for BgpPeerStatus { - fn from(value: external::BgpPeerStatus) -> Self { - Self { - addr: value.addr, - local_asn: value.local_asn, - remote_asn: value.remote_asn, - state: value.state.clone(), - state_duration_millis: value.state_duration_millis, - switch: value.switch, - } - } -} - -/// A route imported from a BGP peer. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct BgpImportedRouteIpv4 { - /// The destination network prefix. - pub prefix: oxnet::Ipv4Net, - - /// The nexthop the prefix is reachable through. - pub nexthop: Ipv4Addr, - - /// BGP identifier of the originating router. - pub id: u32, - - /// Switch the route is imported into. - pub switch: SwitchLocation, -} - -impl TryFrom for BgpImportedRouteIpv4 { - type Error = String; - - fn try_from(value: BgpImported) -> Result { - let BgpImported { prefix, nexthop, id, switch } = value; - - let prefix = match prefix { - oxnet::IpNet::V4(ipv4_net) => Ok(ipv4_net), - oxnet::IpNet::V6(ipv6_net) => { - Err(format!("prefix must be Ipv4Net but it is {ipv6_net}")) - } - }?; - - let nexthop = match nexthop { - IpAddr::V4(ipv4_addr) => Ok(ipv4_addr), - IpAddr::V6(ipv6_addr) => { - Err(format!("nexthop must be Ipv4Addr but it is {ipv6_addr}")) - } - }?; - - Ok(Self { prefix, nexthop, id, switch }) - } -} - -/// The current status of a BGP peer. -#[derive( - Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq, Default, -)] -pub struct BgpExported { - /// Exported routes indexed by peer address. - pub exports: HashMap>, -} - -impl From> for BgpExported { - fn from(values: Vec) -> Self { - let mut out = Self::default(); - - for export in values { - let oxnet::IpNet::V4(net) = export.prefix else { - continue; - }; - match out.exports.entry(export.peer_id) { - Entry::Occupied(mut occupied_entry) => { - occupied_entry.get_mut().push(net); - } - Entry::Vacant(vacant_entry) => { - vacant_entry.insert(vec![net]); - } - } - } - - out - } -} diff --git a/nexus/external-api/src/v2026010100.rs b/nexus/external-api/src/v2026_01_01_00_local.rs similarity index 53% rename from nexus/external-api/src/v2026010100.rs rename to nexus/external-api/src/v2026_01_01_00_local.rs index 6b95a37f812..c5131eb2699 100644 --- a/nexus/external-api/src/v2026010100.rs +++ b/nexus/external-api/src/v2026_01_01_00_local.rs @@ -2,46 +2,25 @@ // 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/. -//! Nexus external types that changed from 2026010100 to 2026010300. -//! -//! ## Network Interface Changes -//! -//! This version adds dual-stack NIC support with [`InstanceNetworkInterfaceAttachment`] -//! and [`InstanceNetworkInterfaceCreate`] changes. -//! -//! ## Multicast Changes -//! -//! `InstanceCreate.multicast_groups` uses `Vec` instead of -//! `Vec`. The conversion adds default values for -//! `source_ips` and `ip_version` fields. -//! -//! Affected endpoints: -//! - `POST /v1/instances` (instance_create) -//! -//! [`InstanceNetworkInterfaceAttachment`]: self::InstanceNetworkInterfaceAttachment -//! [`InstanceNetworkInterfaceCreate`]: self::InstanceNetworkInterfaceCreate +//! Types from API version 2026_01_01_00 that cannot live in `nexus-types-versions` +//! because they convert to/from `omicron-common` types (orphan rule). -use api_identity::ObjectIdentity; use itertools::Either; use itertools::Itertools as _; -use nexus_types::external_api::params; -use nexus_types::external_api::params::IpAssignment; -use nexus_types::external_api::params::PrivateIpStackCreate; -use nexus_types::external_api::params::PrivateIpv4StackCreate; -use nexus_types::external_api::params::PrivateIpv6StackCreate; -use nexus_types::external_api::shared::ProbeExternalIp; +use nexus_types::external_api::instance; +use nexus_types::external_api::instance::IpAssignment; +use nexus_types::external_api::instance::PrivateIpStackCreate; +use nexus_types::external_api::instance::PrivateIpv4StackCreate; +use nexus_types::external_api::instance::PrivateIpv6StackCreate; use omicron_common::api::external; use omicron_common::api::external::IdentityMetadata; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::MacAddr; use omicron_common::api::external::Name; -use omicron_common::api::external::ObjectIdentity; use omicron_common::api::external::PrivateIpStack; use omicron_common::api::external::PrivateIpv4Stack; use omicron_common::api::external::PrivateIpv6Stack; -use omicron_common::api::internal::shared::v1::NetworkInterface as NetworkInterfaceV1; -use omicron_uuid_kinds::SledUuid; use oxnet::IpNet; use schemars::JsonSchema; use serde::Deserialize; @@ -49,9 +28,6 @@ use serde::Serialize; use std::net::IpAddr; use uuid::Uuid; -use crate::v2026010300; -use crate::v2026013000; - /// Describes an attachment of an `InstanceNetworkInterface` to an `Instance`, /// at the time the instance is created. // NOTE: VPC's are an organizing concept for networking resources, not for @@ -88,7 +64,7 @@ pub enum InstanceNetworkInterfaceAttachment { } impl TryFrom - for params::InstanceNetworkInterfaceAttachment + for instance::InstanceNetworkInterfaceAttachment { type Error = external::Error; @@ -111,7 +87,7 @@ impl TryFrom /// An `InstanceNetworkInterface` represents a virtual network interface device /// attached to an instance. -#[derive(ObjectIdentity, Clone, Debug, Deserialize, JsonSchema, Serialize)] +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] pub struct InstanceNetworkInterface { /// common identifying metadata #[serde(flatten)] @@ -246,7 +222,7 @@ pub struct InstanceNetworkInterfaceCreate { } impl TryFrom - for params::InstanceNetworkInterfaceCreate + for instance::InstanceNetworkInterfaceCreate { type Error = external::Error; @@ -310,181 +286,3 @@ impl TryFrom }) } } - -/// Create-time parameters for an `Instance` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceCreate { - #[serde(flatten)] - pub identity: external::IdentityMetadataCreateParams, - /// The number of vCPUs to be allocated to the instance - pub ncpus: external::InstanceCpuCount, - /// The amount of RAM (in bytes) to be allocated to the instance - pub memory: external::ByteCount, - /// The hostname to be assigned to the instance - pub hostname: external::Hostname, - - /// User data for instance initialization systems (such as cloud-init). - /// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / - /// characters with padding). Maximum 32 KiB unencoded data. - // While serde happily accepts #[serde(with = "")] as a shorthand for - // specifying `serialize_with` and `deserialize_with`, schemars requires the - // argument to `with` to be a type rather than merely a path prefix (i.e. a - // mod or type). It's admittedly a bit tricky for schemars to address; - // unlike `serialize` or `deserialize`, `JsonSchema` requires several - // functions working together. It's unfortunate that schemars has this - // built-in incompatibility, exacerbated by its glacial rate of progress - // and immunity to offers of help. - #[serde(default, with = "params::UserData")] - pub user_data: Vec, - - /// The network interfaces to be created for this instance. - #[serde(default)] - pub network_interfaces: InstanceNetworkInterfaceAttachment, - - /// The external IP addresses provided to this instance. - /// - /// By default, all instances have outbound connectivity, but no inbound - /// connectivity. These external addresses can be used to provide a fixed, - /// known IP address for making inbound connections to the instance. - #[serde(default)] - pub external_ips: Vec, - - /// The multicast groups this instance should join. - /// - /// The instance will be automatically added as a member of the specified - /// multicast groups during creation, enabling it to send and receive - /// multicast traffic for those groups. - #[serde(default)] - pub multicast_groups: Vec, - - /// A list of disks to be attached to the instance. - /// - /// Disk attachments of type "create" will be created, while those of type - /// "attach" must already exist. - /// - /// The order of this list does not guarantee a boot order for the instance. - /// Use the boot_disk attribute to specify a boot disk. When boot_disk is - /// specified it will count against the disk attachment limit. - #[serde(default)] - pub disks: Vec, - - /// The disk the instance is configured to boot from. - /// - /// This disk can either be attached if it already exists or created along - /// with the instance. - /// - /// Specifying a boot disk is optional but recommended to ensure predictable - /// boot behavior. The boot disk can be set during instance creation or - /// later if the instance is stopped. The boot disk counts against the disk - /// attachment limit. - /// - /// An instance that does not have a boot disk set will use the boot - /// options specified in its UEFI settings, which are controlled by both the - /// instance's UEFI firmware and the guest operating system. Boot options - /// can change as disks are attached and detached, which may result in an - /// instance that only boots to the EFI shell until a boot disk is set. - #[serde(default)] - pub boot_disk: Option, - - /// An allowlist of SSH public keys to be transferred to the instance via - /// cloud-init during instance creation. - /// - /// If not provided, all SSH public keys from the user's profile will be sent. - /// If an empty list is provided, no public keys will be transmitted to the - /// instance. - pub ssh_public_keys: Option>, - - /// Should this instance be started upon creation; true by default. - #[serde(default = "params::bool_true")] - pub start: bool, - - /// The auto-restart policy for this instance. - /// - /// This policy determines whether the instance should be automatically - /// restarted by the control plane on failure. If this is `null`, no - /// auto-restart policy will be explicitly configured for this instance, and - /// the control plane will select the default policy when determining - /// whether the instance can be automatically restarted. - /// - /// Currently, the global default auto-restart policy is "best-effort", so - /// instances with `null` auto-restart policies will be automatically - /// restarted. However, in the future, the default policy may be - /// configurable through other mechanisms, such as on a per-project basis. - /// In that case, any configured default policy will be used if this is - /// `null`. - #[serde(default)] - pub auto_restart_policy: Option, - - /// Anti-Affinity groups which this instance should be added. - #[serde(default)] - pub anti_affinity_groups: Vec, - - /// The CPU platform to be used for this instance. If this is `null`, the - /// instance requires no particular CPU platform; when it is started the - /// instance will have the most general CPU platform supported by the sled - /// it is initially placed on. - #[serde(default)] - pub cpu_platform: Option, -} - -impl TryFrom for params::InstanceCreate { - type Error = external::Error; - - fn try_from(value: InstanceCreate) -> Result { - let network_interfaces = value.network_interfaces.try_into()?; - Ok(Self { - identity: value.identity, - ncpus: value.ncpus, - memory: value.memory, - hostname: value.hostname, - user_data: value.user_data, - network_interfaces, - external_ips: value - .external_ips - .into_iter() - .map(TryInto::try_into) - .collect::, _>>()?, - multicast_groups: value - .multicast_groups - .into_iter() - .map(|g| params::MulticastGroupJoinSpec { - group: g.into(), - source_ips: None, - ip_version: None, - }) - .collect(), - disks: value.disks.into_iter().map(Into::into).collect(), - boot_disk: value.boot_disk.map(Into::into), - ssh_public_keys: value.ssh_public_keys, - start: value.start, - auto_restart_policy: value.auto_restart_policy, - anti_affinity_groups: value.anti_affinity_groups, - cpu_platform: value.cpu_platform, - }) - } -} - -#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] -pub struct ProbeInfo { - pub id: Uuid, - pub name: Name, - #[schemars(with = "Uuid")] - pub sled: SledUuid, - pub external_ips: Vec, - pub interface: NetworkInterfaceV1, -} - -impl TryFrom for ProbeInfo { - type Error = omicron_common::api::external::Error; - fn try_from( - value: nexus_types::external_api::shared::ProbeInfo, - ) -> Result { - Ok(Self { - id: value.id, - name: value.name, - sled: value.sled, - external_ips: value.external_ips, - interface: value.interface.try_into()?, - }) - } -} diff --git a/nexus/external-api/src/v2026_01_30_00_local.rs b/nexus/external-api/src/v2026_01_30_00_local.rs new file mode 100644 index 00000000000..2cc21371593 --- /dev/null +++ b/nexus/external-api/src/v2026_01_30_00_local.rs @@ -0,0 +1,63 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Types from API version 2026_01_30_00 that cannot live in `nexus-types-versions` +//! because they convert to/from `omicron-common` types (orphan rule). + +use api_identity::ObjectIdentity; +use omicron_common::api::external; +use omicron_common::api::external::ByteCount; +use omicron_common::api::external::DiskState; +use omicron_common::api::external::DiskType; +use omicron_common::api::external::IdentityMetadata; +use omicron_common::api::external::ObjectIdentity; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use uuid::Uuid; + +/// View of a Disk +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Disk { + #[serde(flatten)] + pub identity: IdentityMetadata, + pub project_id: Uuid, + /// ID of snapshot from which disk was created, if any + pub snapshot_id: Option, + /// ID of image from which disk was created, if any + pub image_id: Option, + pub size: ByteCount, + pub block_size: ByteCount, + pub state: DiskState, + pub device_path: String, + pub disk_type: DiskType, +} + +impl From for Disk { + fn from(new: external::Disk) -> Self { + let external::Disk { + identity, + project_id, + snapshot_id, + image_id, + size, + block_size, + state, + device_path, + disk_type, + read_only: _, // read_only doth not exist in v2026_01_30_00 + } = new; + Self { + identity, + project_id, + snapshot_id, + image_id, + size, + block_size, + state, + device_path, + disk_type, + } + } +} diff --git a/nexus/internal-api/src/lib.rs b/nexus/internal-api/src/lib.rs index a211aa868d6..9cd5d38fd7f 100644 --- a/nexus/internal-api/src/lib.rs +++ b/nexus/internal-api/src/lib.rs @@ -12,8 +12,8 @@ use dropshot::{ use dropshot_api_manager_types::api_versions; use nexus_types::{ external_api::{ - shared::ProbeExternalIp, - views::{Ping, PingStatus}, + probe::ProbeExternalIp, + system::{Ping, PingStatus}, }, internal_api::{ params::{ diff --git a/nexus/lockstep-api/Cargo.toml b/nexus/lockstep-api/Cargo.toml index 4a194d19154..e10f6c7ece8 100644 --- a/nexus/lockstep-api/Cargo.toml +++ b/nexus/lockstep-api/Cargo.toml @@ -11,6 +11,7 @@ workspace = true dropshot.workspace = true http.workspace = true nexus-types.workspace = true +nexus-types-versions.workspace = true omicron-common.workspace = true omicron-uuid-kinds.workspace = true omicron-workspace-hack.workspace = true diff --git a/nexus/lockstep-api/src/lib.rs b/nexus/lockstep-api/src/lib.rs index 8e6a7d8f460..3011e21fc63 100644 --- a/nexus/lockstep-api/src/lib.rs +++ b/nexus/lockstep-api/src/lib.rs @@ -26,16 +26,13 @@ use nexus_types::deployment::ClickhousePolicy; use nexus_types::deployment::OximeterReadPolicy; use nexus_types::deployment::ReconfiguratorConfigParam; use nexus_types::deployment::ReconfiguratorConfigView; -use nexus_types::external_api::headers::RangeRequest; -use nexus_types::external_api::params; -use nexus_types::external_api::params::PhysicalDiskPath; -use nexus_types::external_api::params::SledSelector; -use nexus_types::external_api::params::UninitializedSledId; -use nexus_types::external_api::shared; -use nexus_types::external_api::shared::UninitializedSled; -use nexus_types::external_api::views::Ping; -use nexus_types::external_api::views::PingStatus; -use nexus_types::external_api::views::SledPolicy; +use nexus_types::external_api::hardware::{ + UninitializedSled, UninitializedSledId, +}; +use nexus_types::external_api::path_params::{BlueprintPath, PhysicalDiskPath}; +use nexus_types::external_api::sled::{SledPolicy, SledSelector}; +use nexus_types::external_api::support_bundle; +use nexus_types::external_api::system::{Ping, PingStatus}; use nexus_types::internal_api::params::InstanceMigrateRequest; use nexus_types::internal_api::params::RackInitializationRequest; use nexus_types::internal_api::views::BackgroundTask; @@ -45,6 +42,7 @@ use nexus_types::internal_api::views::QuiesceStatus; use nexus_types::internal_api::views::Saga; use nexus_types::internal_api::views::UpdateStatus; use nexus_types::trust_quorum::TrustQuorumConfig; +use nexus_types_versions::latest::headers::RangeRequest; use omicron_common::api::external::Instance; use omicron_common::api::external::http_pagination::PaginatedById; use omicron_common::api::external::http_pagination::PaginatedByTimeAndId; @@ -234,7 +232,7 @@ pub trait NexusLockstepApi { }] async fn blueprint_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// Deletes one blueprint @@ -244,7 +242,7 @@ pub trait NexusLockstepApi { }] async fn blueprint_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; // Managing the current target blueprint @@ -404,7 +402,10 @@ pub trait NexusLockstepApi { async fn support_bundle_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError>; + ) -> Result< + HttpResponseOk>, + HttpError, + >; /// View a support bundle #[endpoint { @@ -413,8 +414,8 @@ pub trait NexusLockstepApi { }] async fn support_bundle_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError>; + path_params: Path, + ) -> Result, HttpError>; /// Download the index of a support bundle #[endpoint { @@ -424,7 +425,7 @@ pub trait NexusLockstepApi { async fn support_bundle_index( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// Download the contents of a support bundle @@ -435,7 +436,7 @@ pub trait NexusLockstepApi { async fn support_bundle_download( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// Download a file within a support bundle @@ -446,7 +447,7 @@ pub trait NexusLockstepApi { async fn support_bundle_download_file( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// Download the metadata of a support bundle @@ -457,7 +458,7 @@ pub trait NexusLockstepApi { async fn support_bundle_head( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// Download the metadata of a file within the support bundle @@ -468,7 +469,7 @@ pub trait NexusLockstepApi { async fn support_bundle_head_file( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError>; /// Create a new support bundle @@ -478,8 +479,8 @@ pub trait NexusLockstepApi { }] async fn support_bundle_create( rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError>; + body: TypedBody, + ) -> Result, HttpError>; /// Delete an existing support bundle /// @@ -491,7 +492,7 @@ pub trait NexusLockstepApi { }] async fn support_bundle_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result; /// Update a support bundle @@ -501,9 +502,9 @@ pub trait NexusLockstepApi { }] async fn support_bundle_update( rqctx: RequestContext, - path_params: Path, - body: TypedBody, - ) -> Result, HttpError>; + path_params: Path, + body: TypedBody, + ) -> Result, HttpError>; /// Get the current clickhouse policy #[endpoint { @@ -573,7 +574,9 @@ pub trait NexusLockstepApi { }] async fn trust_quorum_get_config( rqctx: RequestContext, - path_params: Path, + path_params: Path< + nexus_types::external_api::rack::RackMembershipConfigPathParams, + >, query_params: Query, ) -> Result, HttpError>; diff --git a/nexus/reconfigurator/blippy/src/checks.rs b/nexus/reconfigurator/blippy/src/checks.rs index 7e81f7a2ace..aad1f9b3c49 100644 --- a/nexus/reconfigurator/blippy/src/checks.rs +++ b/nexus/reconfigurator/blippy/src/checks.rs @@ -611,7 +611,7 @@ fn check_mupdate_override(blippy: &mut Blippy<'_>) { // Perform checks for invariants that should be upheld if // remove_mupdate_override is set for a sled. for (&sled_id, sled) in &blippy.blueprint().sleds { - if !sled.state.matches(SledFilter::InService) { + if !SledFilter::InService.matches_state(sled.state) { continue; } diff --git a/nexus/reconfigurator/execution/src/database.rs b/nexus/reconfigurator/execution/src/database.rs index 7d641f929bd..9d42013a631 100644 --- a/nexus/reconfigurator/execution/src/database.rs +++ b/nexus/reconfigurator/execution/src/database.rs @@ -83,7 +83,7 @@ mod test { use nexus_types::deployment::OximeterReadMode; use nexus_types::deployment::PendingMgsUpdates; use nexus_types::deployment::blueprint_zone_type; - use nexus_types::external_api::views::SledState; + use nexus_types::external_api::sled::SledState; use nexus_types::inventory::NetworkInterface; use nexus_types::inventory::NetworkInterfaceKind; use omicron_common::address::Ipv6Subnet; diff --git a/nexus/reconfigurator/execution/src/dns.rs b/nexus/reconfigurator/execution/src/dns.rs index 4e8b2a15f64..b4f30e71712 100644 --- a/nexus/reconfigurator/execution/src/dns.rs +++ b/nexus/reconfigurator/execution/src/dns.rs @@ -354,11 +354,10 @@ mod test { use nexus_types::deployment::OximeterReadMode; use nexus_types::deployment::PendingMgsUpdates; use nexus_types::deployment::blueprint_zone_type; - use nexus_types::external_api::params; - use nexus_types::external_api::shared; - use nexus_types::external_api::views::SledPolicy; - use nexus_types::external_api::views::SledProvisionPolicy; - use nexus_types::external_api::views::SledState; + use nexus_types::external_api::silo; + use nexus_types::external_api::sled::SledPolicy; + use nexus_types::external_api::sled::SledProvisionPolicy; + use nexus_types::external_api::sled::SledState; use nexus_types::identity::Resource; use nexus_types::internal_api::params::DnsConfigParams; use nexus_types::internal_api::params::DnsConfigZone; @@ -1066,14 +1065,14 @@ mod test { blueprint.internal_dns_version = Generation::new(); blueprint.external_dns_version = Generation::new(); - let my_silo = Silo::new(params::SiloCreate { + let my_silo = Silo::new(silo::SiloCreate { identity: IdentityMetadataCreateParams { name: "my-silo".parse().unwrap(), description: String::new(), }, - quotas: params::SiloQuotasCreate::empty(), + quotas: silo::SiloQuotasCreate::empty(), discoverable: false, - identity_mode: shared::SiloIdentityMode::SamlJit, + identity_mode: silo::SiloIdentityMode::SamlJit, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: Default::default(), @@ -1758,7 +1757,7 @@ mod test { &cptestctx.external_client, silo_name, false, - shared::SiloIdentityMode::SamlJit, + silo::SiloIdentityMode::SamlJit, ) .await; diff --git a/nexus/reconfigurator/execution/src/omicron_sled_config.rs b/nexus/reconfigurator/execution/src/omicron_sled_config.rs index 9f08a815c82..c69517166dd 100644 --- a/nexus/reconfigurator/execution/src/omicron_sled_config.rs +++ b/nexus/reconfigurator/execution/src/omicron_sled_config.rs @@ -93,9 +93,9 @@ mod tests { use nexus_types::deployment::BlueprintZoneType; use nexus_types::deployment::LastAllocatedSubnetIpOffset; use nexus_types::deployment::blueprint_zone_type; - use nexus_types::external_api::views::SledPolicy; - use nexus_types::external_api::views::SledProvisionPolicy; - use nexus_types::external_api::views::SledState; + use nexus_types::external_api::sled::SledPolicy; + use nexus_types::external_api::sled::SledProvisionPolicy; + use nexus_types::external_api::sled::SledState; use omicron_common::address::Ipv6Subnet; use omicron_common::address::REPO_DEPOT_PORT; use omicron_common::api::external::Generation; diff --git a/nexus/reconfigurator/execution/src/sled_state.rs b/nexus/reconfigurator/execution/src/sled_state.rs index dedc4c0cd2b..f9cf5500d67 100644 --- a/nexus/reconfigurator/execution/src/sled_state.rs +++ b/nexus/reconfigurator/execution/src/sled_state.rs @@ -12,7 +12,7 @@ use nexus_db_queries::context::OpContext; use nexus_db_queries::db::DataStore; use nexus_db_queries::db::datastore::TransitionError; use nexus_types::deployment::Blueprint; -use nexus_types::external_api::views::SledState; +use nexus_types::external_api::sled::SledState; use omicron_uuid_kinds::SledUuid; pub(crate) async fn decommission_sleds( diff --git a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs index 6d6a3ba424d..b32f8f3ecac 100644 --- a/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs +++ b/nexus/reconfigurator/planning/src/blueprint_builder/builder.rs @@ -51,7 +51,7 @@ use nexus_types::deployment::TufRepoContentsError; use nexus_types::deployment::UpstreamNtpConfig; use nexus_types::deployment::ZpoolName; use nexus_types::deployment::blueprint_zone_type; -use nexus_types::external_api::views::SledState; +use nexus_types::external_api::sled::SledState; use omicron_common::address::CLICKHOUSE_HTTP_PORT; use omicron_common::address::DNS_HTTP_PORT; use omicron_common::address::DNS_PORT; @@ -2625,7 +2625,7 @@ pub mod test { use nexus_types::deployment::OmicronZoneNetworkResources; use nexus_types::deployment::PlanningInput; use nexus_types::deployment::SledFilter; - use nexus_types::external_api::views::SledPolicy; + use nexus_types::external_api::sled::SledPolicy; use omicron_common::address::IpRange; use omicron_test_utils::dev::test_setup_log; use std::collections::BTreeSet; diff --git a/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs b/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs index 3e5fda5cee3..686a9963f1c 100644 --- a/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs +++ b/nexus/reconfigurator/planning/src/blueprint_editor/sled_editor.rs @@ -33,7 +33,7 @@ use nexus_types::deployment::BlueprintZoneType; use nexus_types::deployment::LastAllocatedSubnetIpOffset; use nexus_types::deployment::PendingMgsUpdate; use nexus_types::deployment::blueprint_zone_type; -use nexus_types::external_api::views::SledState; +use nexus_types::external_api::sled::SledState; use omicron_common::address::Ipv6Subnet; use omicron_common::address::SLED_PREFIX; use omicron_common::api::external::Generation; diff --git a/nexus/reconfigurator/planning/src/example.rs b/nexus/reconfigurator/planning/src/example.rs index b0018cfffd5..789acbe17ee 100644 --- a/nexus/reconfigurator/planning/src/example.rs +++ b/nexus/reconfigurator/planning/src/example.rs @@ -38,7 +38,7 @@ use nexus_types::deployment::OmicronZoneNic; use nexus_types::deployment::PlanningInput; use nexus_types::deployment::SledFilter; use nexus_types::deployment::TargetReleaseDescription; -use nexus_types::external_api::views::SledPolicy; +use nexus_types::external_api::sled::SledPolicy; use nexus_types::inventory::Collection; use omicron_common::address::Ipv4Range; use omicron_common::api::external::TufRepoDescription; @@ -684,8 +684,8 @@ impl ExampleSystemBuilder { if self.create_zones { // Add NTP zones. On the first N discretionary sleds, we'll add // BoundaryNtp zones. On all other sleds, add InternalNtp zones. - let is_discretionary = - sled_details.policy.matches(SledFilter::Discretionary); + let is_discretionary = SledFilter::Discretionary + .matches_policy(sled_details.policy); let will_get_boundary_ntp = is_discretionary && discretionary_ix < self.boundary_ntp_count.0; diff --git a/nexus/reconfigurator/planning/src/planner.rs b/nexus/reconfigurator/planning/src/planner.rs index 1970ccb7f96..6ee31df83ba 100644 --- a/nexus/reconfigurator/planning/src/planner.rs +++ b/nexus/reconfigurator/planning/src/planner.rs @@ -53,9 +53,9 @@ use nexus_types::deployment::{ PlanningReport, PlanningZoneUpdatesStepReport, ZoneAddWaitingOn, ZoneUpdatesWaitingOn, ZoneWaitingToExpunge, }; -use nexus_types::external_api::views::PhysicalDiskPolicy; -use nexus_types::external_api::views::SledPolicy; -use nexus_types::external_api::views::SledState; +use nexus_types::external_api::physical_disk::PhysicalDiskPolicy; +use nexus_types::external_api::sled::SledPolicy; +use nexus_types::external_api::sled::SledState; use nexus_types::inventory::Collection; use nexus_types::inventory::SpType; use omicron_common::api::external::Generation; diff --git a/nexus/reconfigurator/planning/src/system.rs b/nexus/reconfigurator/planning/src/system.rs index 6e2fc1d027f..79b8369defa 100644 --- a/nexus/reconfigurator/planning/src/system.rs +++ b/nexus/reconfigurator/planning/src/system.rs @@ -31,11 +31,11 @@ use nexus_types::deployment::SledDisk; use nexus_types::deployment::SledResources; use nexus_types::deployment::TargetReleaseDescription; use nexus_types::deployment::TufRepoPolicy; -use nexus_types::external_api::views::PhysicalDiskPolicy; -use nexus_types::external_api::views::PhysicalDiskState; -use nexus_types::external_api::views::SledPolicy; -use nexus_types::external_api::views::SledProvisionPolicy; -use nexus_types::external_api::views::SledState; +use nexus_types::external_api::physical_disk::PhysicalDiskPolicy; +use nexus_types::external_api::physical_disk::PhysicalDiskState; +use nexus_types::external_api::sled::SledPolicy; +use nexus_types::external_api::sled::SledProvisionPolicy; +use nexus_types::external_api::sled::SledState; use nexus_types::inventory::Caboose; use nexus_types::inventory::CabooseWhich; use nexus_types::inventory::PowerState; diff --git a/nexus/reconfigurator/planning/tests/integration_tests/planner.rs b/nexus/reconfigurator/planning/tests/integration_tests/planner.rs index 366e220c04f..fcd5ca3369e 100644 --- a/nexus/reconfigurator/planning/tests/integration_tests/planner.rs +++ b/nexus/reconfigurator/planning/tests/integration_tests/planner.rs @@ -34,11 +34,11 @@ use nexus_types::deployment::TargetReleaseDescription; use nexus_types::deployment::ZoneRunningStatus; use nexus_types::deployment::blueprint_zone_type; use nexus_types::deployment::blueprint_zone_type::InternalDns; -use nexus_types::external_api::views::PhysicalDiskPolicy; -use nexus_types::external_api::views::PhysicalDiskState; -use nexus_types::external_api::views::SledPolicy; -use nexus_types::external_api::views::SledProvisionPolicy; -use nexus_types::external_api::views::SledState; +use nexus_types::external_api::physical_disk::PhysicalDiskPolicy; +use nexus_types::external_api::physical_disk::PhysicalDiskState; +use nexus_types::external_api::sled::SledPolicy; +use nexus_types::external_api::sled::SledProvisionPolicy; +use nexus_types::external_api::sled::SledState; use nexus_types::inventory::CockroachStatus; use nexus_types::inventory::Collection; use nexus_types::inventory::InternalDnsGenerationStatus; diff --git a/nexus/src/app/address_lot.rs b/nexus/src/app/address_lot.rs index 3f30e7a3f49..96b886d24dd 100644 --- a/nexus/src/app/address_lot.rs +++ b/nexus/src/app/address_lot.rs @@ -2,7 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use crate::external_api::params; use db::model::{AddressLot, AddressLotBlock}; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; @@ -10,6 +9,7 @@ use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; use nexus_db_queries::db::datastore::AddressLotCreateResult; +use nexus_types::external_api::networking; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::LookupResult; use omicron_common::api::external::NameOrId; @@ -44,7 +44,7 @@ impl super::Nexus { pub(crate) async fn address_lot_create( self: &Arc, opctx: &OpContext, - params: params::AddressLotCreate, + params: networking::AddressLotCreate, ) -> CreateResult { opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; validate_blocks(¶ms)?; @@ -84,7 +84,7 @@ impl super::Nexus { } } -fn validate_blocks(lot: ¶ms::AddressLotCreate) -> Result<(), Error> { +fn validate_blocks(lot: &networking::AddressLotCreate) -> Result<(), Error> { for b in &lot.blocks { match (&b.first_address, &b.last_address) { (IpAddr::V4(first), IpAddr::V4(last)) => { diff --git a/nexus/src/app/affinity.rs b/nexus/src/app/affinity.rs index be2edd4ff73..db6cb78ad12 100644 --- a/nexus/src/app/affinity.rs +++ b/nexus/src/app/affinity.rs @@ -10,8 +10,8 @@ use nexus_db_model::AffinityGroup; use nexus_db_model::AntiAffinityGroup; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::affinity; +use nexus_types::external_api::project; use nexus_types::identity::Resource; use omicron_common::api::external; use omicron_common::api::external::CreateResult; @@ -29,10 +29,10 @@ impl super::Nexus { pub fn affinity_group_lookup<'a>( &'a self, opctx: &'a OpContext, - affinity_group_selector: params::AffinityGroupSelector, + affinity_group_selector: affinity::AffinityGroupSelector, ) -> LookupResult> { match affinity_group_selector { - params::AffinityGroupSelector { + affinity::AffinityGroupSelector { affinity_group: NameOrId::Id(id), project: None, } => { @@ -40,16 +40,19 @@ impl super::Nexus { .affinity_group_id(id); Ok(affinity_group) } - params::AffinityGroupSelector { + affinity::AffinityGroupSelector { affinity_group: NameOrId::Name(name), project: Some(project), } => { let affinity_group = self - .project_lookup(opctx, params::ProjectSelector { project })? + .project_lookup( + opctx, + project::ProjectSelector { project }, + )? .affinity_group_name_owned(name.into()); Ok(affinity_group) } - params::AffinityGroupSelector { + affinity::AffinityGroupSelector { affinity_group: NameOrId::Id(_), .. } => Err(Error::invalid_request( @@ -64,10 +67,10 @@ impl super::Nexus { pub fn anti_affinity_group_lookup<'a>( &'a self, opctx: &'a OpContext, - anti_affinity_group_selector: params::AntiAffinityGroupSelector, + anti_affinity_group_selector: affinity::AntiAffinityGroupSelector, ) -> LookupResult> { match anti_affinity_group_selector { - params::AntiAffinityGroupSelector { + affinity::AntiAffinityGroupSelector { anti_affinity_group: NameOrId::Id(id), project: None, } => { @@ -76,16 +79,19 @@ impl super::Nexus { .anti_affinity_group_id(id); Ok(anti_affinity_group) } - params::AntiAffinityGroupSelector { + affinity::AntiAffinityGroupSelector { anti_affinity_group: NameOrId::Name(name), project: Some(project), } => { let anti_affinity_group = self - .project_lookup(opctx, params::ProjectSelector { project })? + .project_lookup( + opctx, + project::ProjectSelector { project }, + )? .anti_affinity_group_name_owned(name.into()); Ok(anti_affinity_group) } - params::AntiAffinityGroupSelector { + affinity::AntiAffinityGroupSelector { anti_affinity_group: NameOrId::Id(_), .. } => Err(Error::invalid_request( @@ -102,7 +108,7 @@ impl super::Nexus { opctx: &OpContext, project_lookup: &lookup::Project<'_>, pagparams: &PaginatedBy<'_>, - ) -> ListResultVec { + ) -> ListResultVec { let (.., authz_project) = project_lookup.lookup_for(authz::Action::ListChildren).await?; @@ -120,7 +126,7 @@ impl super::Nexus { opctx: &OpContext, project_lookup: &lookup::Project<'_>, pagparams: &PaginatedBy<'_>, - ) -> ListResultVec { + ) -> ListResultVec { let (.., authz_project) = project_lookup.lookup_for(authz::Action::ListChildren).await?; @@ -137,8 +143,8 @@ impl super::Nexus { &self, opctx: &OpContext, project_lookup: &lookup::Project<'_>, - affinity_group_params: params::AffinityGroupCreate, - ) -> CreateResult { + affinity_group_params: affinity::AffinityGroupCreate, + ) -> CreateResult { let (.., authz_project) = project_lookup.lookup_for(authz::Action::CreateChild).await?; @@ -154,8 +160,8 @@ impl super::Nexus { &self, opctx: &OpContext, project_lookup: &lookup::Project<'_>, - anti_affinity_group_params: params::AntiAffinityGroupCreate, - ) -> CreateResult { + anti_affinity_group_params: affinity::AntiAffinityGroupCreate, + ) -> CreateResult { let (.., authz_project) = project_lookup.lookup_for(authz::Action::CreateChild).await?; @@ -177,8 +183,8 @@ impl super::Nexus { &self, opctx: &OpContext, group_lookup: &lookup::AffinityGroup<'_>, - updates: ¶ms::AffinityGroupUpdate, - ) -> UpdateResult { + updates: &affinity::AffinityGroupUpdate, + ) -> UpdateResult { let (.., authz_group) = group_lookup.lookup_for(authz::Action::Modify).await?; self.db_datastore @@ -191,8 +197,8 @@ impl super::Nexus { &self, opctx: &OpContext, group_lookup: &lookup::AntiAffinityGroup<'_>, - updates: ¶ms::AntiAffinityGroupUpdate, - ) -> UpdateResult { + updates: &affinity::AntiAffinityGroupUpdate, + ) -> UpdateResult { let (.., authz_group) = group_lookup.lookup_for(authz::Action::Modify).await?; self.db_datastore diff --git a/nexus/src/app/alert.rs b/nexus/src/app/alert.rs index 91f21571723..ed8354076ae 100644 --- a/nexus/src/app/alert.rs +++ b/nexus/src/app/alert.rs @@ -156,9 +156,7 @@ use nexus_db_queries::db::model::AlertDeliveryState; use nexus_db_queries::db::model::AlertDeliveryTrigger; use nexus_db_queries::db::model::WebhookDelivery; use nexus_db_queries::db::model::WebhookReceiverConfig; -use nexus_types::external_api::params; -use nexus_types::external_api::shared; -use nexus_types::external_api::views; +use nexus_types::external_api::alert; use nexus_types::identity::Asset; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DataPageParams; @@ -215,7 +213,7 @@ impl Nexus { pub fn alert_receiver_lookup<'a>( &'a self, opctx: &'a OpContext, - rx_selector: params::AlertReceiverSelector, + rx_selector: alert::AlertReceiverSelector, ) -> LookupResult> { match rx_selector.receiver { NameOrId::Id(id) => { @@ -236,7 +234,7 @@ impl Nexus { pub fn alert_lookup<'a>( &'a self, opctx: &'a OpContext, - params::AlertSelector { alert_id }: params::AlertSelector, + alert::AlertSelector { alert_id }: alert::AlertSelector, ) -> LookupResult> { let event = LookupPath::new(opctx, &self.db_datastore) .alert_id(AlertUuid::from_untyped_uuid(alert_id)); @@ -249,9 +247,9 @@ impl Nexus { pub async fn alert_class_list( &self, opctx: &OpContext, - filter: params::AlertClassFilter, - pagparams: DataPageParams<'_, params::AlertClassPage>, - ) -> ListResultVec { + filter: alert::AlertClassFilter, + pagparams: DataPageParams<'_, alert::AlertClassPage>, + ) -> ListResultVec { opctx .authorize(authz::Action::ListChildren, &authz::ALERT_CLASS_LIST) .await?; @@ -260,9 +258,9 @@ impl Nexus { // This is factored out to avoid having to make a whole Nexus to test it. fn actually_list_alert_classes( - params::AlertClassFilter { filter }: params::AlertClassFilter, - pagparams: DataPageParams<'_, params::AlertClassPage>, - ) -> ListResultVec { + alert::AlertClassFilter { filter }: alert::AlertClassFilter, + pagparams: DataPageParams<'_, alert::AlertClassPage>, + ) -> ListResultVec { use nexus_db_model::AlertSubscriptionKind; let regex = if let Some(filter) = filter { @@ -287,7 +285,7 @@ impl Nexus { }; // If we're resuming a previous scan, figure out where to start. - let start = if let Some(params::AlertClassPage { last_seen }) = + let start = if let Some(alert::AlertClassPage { last_seen }) = pagparams.marker { let start = AlertClass::ALL_CLASSES.iter().enumerate().find_map( @@ -357,9 +355,9 @@ impl Nexus { &self, opctx: &OpContext, rx: lookup::AlertReceiver<'_>, - filter: params::AlertDeliveryStateFilter, + filter: alert::AlertDeliveryStateFilter, pagparams: &DataPageParams<'_, (DateTime, Uuid)>, - ) -> ListResultVec { + ) -> ListResultVec { let (authz_rx,) = rx.lookup_for(authz::Action::ListChildren).await?; let only_states = if filter.include_all() { Vec::new() @@ -403,8 +401,8 @@ impl Nexus { &self, opctx: &OpContext, rx: lookup::AlertReceiver<'_>, - params::AlertSubscriptionCreate { subscription}: params::AlertSubscriptionCreate, - ) -> CreateResult { + alert::AlertSubscriptionCreate { subscription}: alert::AlertSubscriptionCreate, + ) -> CreateResult { let (authz_rx,) = rx.lookup_for(authz::Action::Modify).await?; let db_subscription = nexus_db_model::AlertSubscriptionKind::try_from( subscription.clone(), @@ -413,14 +411,14 @@ impl Nexus { .datastore() .alert_subscription_add(opctx, &authz_rx, db_subscription) .await?; - Ok(views::AlertSubscriptionCreated { subscription }) + Ok(alert::AlertSubscriptionCreated { subscription }) } pub async fn alert_receiver_subscription_remove( &self, opctx: &OpContext, rx: lookup::AlertReceiver<'_>, - subscription: shared::AlertSubscription, + subscription: alert::AlertSubscription, ) -> DeleteResult { let (authz_rx,) = rx.lookup_for(authz::Action::Modify).await?; let db_subscription = @@ -503,11 +501,11 @@ mod tests { last_seen: Option<&str>, limit: u32, ) -> Vec { - let filter = params::AlertClassFilter { + let filter = alert::AlertClassFilter { filter: dbg!(filter).map(|f| f.parse().unwrap()), }; let marker = dbg!(last_seen).map(|last_seen| { - params::AlertClassPage { last_seen: last_seen.to_string() } + alert::AlertClassPage { last_seen: last_seen.to_string() } }); let result = Nexus::actually_list_alert_classes( filter, diff --git a/nexus/src/app/allow_list.rs b/nexus/src/app/allow_list.rs index d25400a5122..9755c018f4b 100644 --- a/nexus/src/app/allow_list.rs +++ b/nexus/src/app/allow_list.rs @@ -7,8 +7,7 @@ //! Nexus methods for operating on source IP allowlists. use nexus_db_queries::context::OpContext; -use nexus_types::external_api::params; -use nexus_types::external_api::views::AllowList; +use nexus_types::external_api::system; use omicron_common::api::external; use omicron_common::api::external::Error; use std::net::IpAddr; @@ -20,11 +19,11 @@ impl super::Nexus { pub async fn allow_list_view( &self, opctx: &OpContext, - ) -> Result { + ) -> Result { self.db_datastore .allow_list_view(opctx) .await - .and_then(AllowList::try_from) + .and_then(system::AllowList::try_from) } /// Upsert the allowlist of source IPs that can reach user-facing services. @@ -33,8 +32,8 @@ impl super::Nexus { opctx: &OpContext, remote_addr: IpAddr, server_kind: ServerKind, - params: params::AllowListUpdate, - ) -> Result { + params: system::AllowListUpdate, + ) -> Result { if let external::AllowedSourceIps::List(list) = ¶ms.allowed_ips { // Size limits on the allowlist. const MAX_ALLOWLIST_LENGTH: usize = 1000; @@ -93,7 +92,7 @@ impl super::Nexus { .db_datastore .allow_list_upsert(opctx, params.allowed_ips.clone()) .await - .and_then(AllowList::try_from)?; + .and_then(system::AllowList::try_from)?; // Notify the sled-agents of the updated firewall rules. // diff --git a/nexus/src/app/background/tasks/blueprint_execution.rs b/nexus/src/app/background/tasks/blueprint_execution.rs index 761e30c05e4..6715cca5eaf 100644 --- a/nexus/src/app/background/tasks/blueprint_execution.rs +++ b/nexus/src/app/background/tasks/blueprint_execution.rs @@ -247,7 +247,7 @@ mod test { CockroachDbPreserveDowngrade, OximeterReadMode, PendingMgsUpdates, blueprint_zone_type, }; - use nexus_types::external_api::views::SledState; + use nexus_types::external_api::sled::SledState; use omicron_common::address::Ipv6Subnet; use omicron_common::api::external; use omicron_common::api::external::Generation; diff --git a/nexus/src/app/background/tasks/external_endpoints.rs b/nexus/src/app/background/tasks/external_endpoints.rs index 8295b5804f9..eda20b2c95d 100644 --- a/nexus/src/app/background/tasks/external_endpoints.rs +++ b/nexus/src/app/background/tasks/external_endpoints.rs @@ -116,7 +116,7 @@ mod test { use nexus_db_queries::db::fixed_data::silo::DEFAULT_SILO; use nexus_test_utils::resource_helpers::create_silo; use nexus_test_utils_macros::nexus_test; - use nexus_types::external_api::shared::SiloIdentityMode; + use nexus_types::external_api::silo::SiloIdentityMode; use nexus_types::identity::Resource; use tokio::sync::watch; diff --git a/nexus/src/app/background/tasks/instance_reincarnation.rs b/nexus/src/app/background/tasks/instance_reincarnation.rs index 72523d8e220..b6a9ce77a32 100644 --- a/nexus/src/app/background/tasks/instance_reincarnation.rs +++ b/nexus/src/app/background/tasks/instance_reincarnation.rs @@ -322,7 +322,6 @@ impl InstanceReincarnation { mod test { use super::*; use crate::app::sagas::test_helpers; - use crate::external_api::params; use chrono::Utc; use nexus_db_lookup::LookupPath; use nexus_db_model::Generation; @@ -338,6 +337,7 @@ mod test { create_default_ip_pools, create_project, object_create, }; use nexus_test_utils_macros::nexus_test; + use nexus_types::external_api::instance; use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::InstanceAutoRestartPolicy; @@ -385,7 +385,7 @@ mod test { object_create::<_, omicron_common::api::external::Instance>( &cptestctx.external_client, &instances_url, - ¶ms::InstanceCreate { + &instance::InstanceCreate { identity: IdentityMetadataCreateParams { name, description: "It's an instance".into(), @@ -401,7 +401,7 @@ mod test { hostname: "myhostname".try_into().unwrap(), user_data: Vec::new(), network_interfaces: - params::InstanceNetworkInterfaceAttachment::None, + instance::InstanceNetworkInterfaceAttachment::None, external_ips: Vec::new(), disks: Vec::new(), boot_disk: None, diff --git a/nexus/src/app/background/tasks/instance_watcher.rs b/nexus/src/app/background/tasks/instance_watcher.rs index bab4451d28f..0249b106fe3 100644 --- a/nexus/src/app/background/tasks/instance_watcher.rs +++ b/nexus/src/app/background/tasks/instance_watcher.rs @@ -15,7 +15,7 @@ use nexus_db_model::Vmm; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::DataStore; use nexus_db_queries::db::pagination::Paginator; -use nexus_types::external_api::views::SledPolicy; +use nexus_types::external_api::sled::SledPolicy; use nexus_types::identity::Asset; use nexus_types::identity::Resource; use omicron_common::api::external::Error; diff --git a/nexus/src/app/background/tasks/read_only_region_replacement_start.rs b/nexus/src/app/background/tasks/read_only_region_replacement_start.rs index 9f3b769dc60..f4d6219e549 100644 --- a/nexus/src/app/background/tasks/read_only_region_replacement_start.rs +++ b/nexus/src/app/background/tasks/read_only_region_replacement_start.rs @@ -163,7 +163,6 @@ mod test { use super::*; use crate::app::MIN_DISK_SIZE_BYTES; use crate::app::RegionAllocationStrategy; - use crate::external_api::params; use chrono::Utc; use nexus_db_lookup::LookupPath; use nexus_db_model::BlockSize; @@ -178,6 +177,7 @@ mod test { use nexus_db_queries::db::datastore::RegionAllocationParameters; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils_macros::nexus_test; + use nexus_types::external_api::disk; use omicron_common::api::external; use omicron_uuid_kinds::DatasetUuid; @@ -237,8 +237,8 @@ mod test { &opctx, RegionAllocationFor::SnapshotVolume { volume_id, snapshot_id }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, size: external::ByteCount::from_gibibytes_u32(1), }, diff --git a/nexus/src/app/background/tasks/sync_switch_configuration.rs b/nexus/src/app/background/tasks/sync_switch_configuration.rs index 3599e01b902..11b2d201a9a 100644 --- a/nexus/src/app/background/tasks/sync_switch_configuration.rs +++ b/nexus/src/app/background/tasks/sync_switch_configuration.rs @@ -40,8 +40,8 @@ use nexus_db_queries::{ context::OpContext, db::{DataStore, datastore::SwitchPortSettingsCombinedResult}, }; -use nexus_types::identity::Asset; -use nexus_types::{external_api::params, identity::Resource}; +use nexus_types::external_api::networking; +use nexus_types::identity::{Asset, Resource}; use omicron_common::OMICRON_DPD_TAG; use omicron_common::{ address::{Ipv6Subnet, get_sled_address}, @@ -621,7 +621,7 @@ impl BackgroundTask for SwitchPortSettingsManager { .datastore .bgp_announcement_list( opctx, - ¶ms::BgpAnnounceSetSelector { + &networking::BgpAnnounceSetSelector { announce_set: bgp_config .bgp_announce_set_id .into(), diff --git a/nexus/src/app/background/tasks/vpc_routes.rs b/nexus/src/app/background/tasks/vpc_routes.rs index dc44ef285ec..6d173152b48 100644 --- a/nexus/src/app/background/tasks/vpc_routes.rs +++ b/nexus/src/app/background/tasks/vpc_routes.rs @@ -11,7 +11,7 @@ use nexus_db_model::{Sled, SledState, Vni}; use nexus_db_queries::{context::OpContext, db::DataStore}; use nexus_networking::sled_client_from_address; use nexus_types::{ - deployment::SledFilter, external_api::views::SledPolicy, identity::Asset, + deployment::SledFilter, external_api::sled::SledPolicy, identity::Asset, identity::Resource, }; use omicron_common::api::internal::shared::{ diff --git a/nexus/src/app/bfd.rs b/nexus/src/app/bfd.rs index 1ae958c20d4..1194568d844 100644 --- a/nexus/src/app/bfd.rs +++ b/nexus/src/app/bfd.rs @@ -2,10 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use crate::external_api::params; use mg_admin_client::types::BfdPeerState; use nexus_db_queries::context::OpContext; -use nexus_types::external_api::shared::{BfdState, BfdStatus}; +use nexus_types::external_api::bfd; +use nexus_types::external_api::networking; use omicron_common::api::{external::Error, internal::shared::SwitchLocation}; impl super::Nexus { @@ -34,7 +34,7 @@ impl super::Nexus { pub async fn bfd_enable( &self, opctx: &OpContext, - session: params::BfdSessionEnable, + session: networking::BfdSessionEnable, ) -> Result<(), Error> { // add the bfd session to the db and trigger the bfd manager to handle // the reset @@ -49,7 +49,7 @@ impl super::Nexus { pub async fn bfd_disable( &self, opctx: &OpContext, - session: params::BfdSessionDisable, + session: networking::BfdSessionDisable, ) -> Result<(), Error> { // remove the bfd session from the db and trigger the bfd manager to // handle the reset @@ -64,7 +64,7 @@ impl super::Nexus { pub async fn bfd_status( &self, _opctx: &OpContext, - ) -> Result, Error> { + ) -> Result, Error> { // ask each rack switch about all its BFD sessions. This will need to // be updated for multirack. let mut result = Vec::new(); @@ -81,13 +81,13 @@ impl super::Nexus { .into_inner(); for info in status.iter() { - result.push(BfdStatus { + result.push(bfd::BfdStatus { peer: info.config.peer, state: match info.state { - BfdPeerState::Up => BfdState::Up, - BfdPeerState::Down => BfdState::Down, - BfdPeerState::Init => BfdState::Init, - BfdPeerState::AdminDown => BfdState::AdminDown, + BfdPeerState::Up => bfd::BfdState::Up, + BfdPeerState::Down => bfd::BfdState::Down, + BfdPeerState::Init => bfd::BfdState::Init, + BfdPeerState::AdminDown => bfd::BfdState::AdminDown, }, switch: s.to_string().parse().unwrap(), local: Some(info.config.listen), diff --git a/nexus/src/app/bgp.rs b/nexus/src/app/bgp.rs index cf6eb5691b6..430c7e58ca0 100644 --- a/nexus/src/app/bgp.rs +++ b/nexus/src/app/bgp.rs @@ -3,10 +3,10 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use crate::app::authz; -use crate::external_api::params; use mg_admin_client::types::MessageHistoryRequest; use nexus_db_model::{BgpAnnounceSet, BgpAnnouncement, BgpConfig}; use nexus_db_queries::context::OpContext; +use nexus_types::external_api::networking; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ self, BgpExported, BgpImported, BgpMessageHistory, BgpPeerStatus, @@ -18,7 +18,7 @@ impl super::Nexus { pub async fn bgp_config_create( &self, opctx: &OpContext, - config: ¶ms::BgpConfigCreate, + config: &networking::BgpConfigCreate, ) -> CreateResult { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; let result = self.db_datastore.bgp_config_create(opctx, config).await?; @@ -46,7 +46,7 @@ impl super::Nexus { pub async fn bgp_config_delete( &self, opctx: &OpContext, - sel: ¶ms::BgpConfigSelector, + sel: &networking::BgpConfigSelector, ) -> DeleteResult { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; let result = self.db_datastore.bgp_config_delete(opctx, sel).await?; @@ -56,7 +56,7 @@ impl super::Nexus { pub async fn bgp_update_announce_set( &self, opctx: &OpContext, - announce: ¶ms::BgpAnnounceSetCreate, + announce: &networking::BgpAnnounceSetCreate, ) -> CreateResult<(BgpAnnounceSet, Vec)> { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; let result = @@ -80,7 +80,7 @@ impl super::Nexus { pub async fn bgp_delete_announce_set( &self, opctx: &OpContext, - sel: ¶ms::BgpAnnounceSetSelector, + sel: &networking::BgpAnnounceSetSelector, ) -> DeleteResult { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; let result = @@ -91,7 +91,7 @@ impl super::Nexus { pub async fn bgp_announcement_list( &self, opctx: &OpContext, - sel: ¶ms::BgpAnnounceSetSelector, + sel: &networking::BgpAnnounceSetSelector, ) -> ListResultVec { opctx.authorize(authz::Action::Read, &authz::FLEET).await?; self.db_datastore.bgp_announcement_list(opctx, sel).await @@ -221,7 +221,7 @@ impl super::Nexus { pub async fn bgp_message_history( &self, opctx: &OpContext, - sel: ¶ms::BgpRouteSelector, + sel: &networking::BgpRouteSelector, ) -> ListResultVec { opctx.authorize(authz::Action::Read, &authz::FLEET).await?; @@ -264,7 +264,7 @@ impl super::Nexus { pub async fn bgp_imported_routes( &self, opctx: &OpContext, - _sel: ¶ms::BgpRouteSelector, + _sel: &networking::BgpRouteSelector, ) -> ListResultVec { opctx.authorize(authz::Action::Read, &authz::FLEET).await?; let mut result = Vec::new(); diff --git a/nexus/src/app/certificate.rs b/nexus/src/app/certificate.rs index f4fabf241d8..d31edaf57a1 100644 --- a/nexus/src/app/certificate.rs +++ b/nexus/src/app/certificate.rs @@ -4,8 +4,6 @@ //! x.509 Certificates -use crate::external_api::params; -use crate::external_api::shared; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; use nexus_db_queries::authz; @@ -13,6 +11,7 @@ use nexus_db_queries::context::OpContext; use nexus_db_queries::db; use nexus_db_queries::db::model::Name; use nexus_db_queries::db::model::ServiceKind; +use nexus_types::external_api::certificate; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::InternalContext; @@ -40,7 +39,7 @@ impl super::Nexus { pub(crate) async fn certificate_create( &self, opctx: &OpContext, - params: params::CertificateCreate, + params: certificate::CertificateCreate, ) -> CreateResult { let authz_silo = opctx .authn @@ -78,7 +77,7 @@ impl super::Nexus { .await?; match kind { - shared::ServiceUsingCertificate::ExternalApi => { + certificate::ServiceUsingCertificate::ExternalApi => { // TODO We could improve the latency of other Nexus instances // noticing this certificate change with an explicit request to // them. Today, Nexus instances generally don't talk to each diff --git a/nexus/src/app/deployment.rs b/nexus/src/app/deployment.rs index b4ea1d6f4cf..bbc24cbc291 100644 --- a/nexus/src/app/deployment.rs +++ b/nexus/src/app/deployment.rs @@ -416,7 +416,7 @@ mod tests { use nexus_reconfigurator_planning::example::example; use nexus_types::deployment::BlueprintHostPhase2DesiredSlots; use nexus_types::deployment::BlueprintZoneDisposition; - use nexus_types::external_api::views::SledState; + use nexus_types::external_api::sled::SledState; use omicron_common::api::external::Generation; use omicron_test_utils::dev::test_setup_log; use omicron_uuid_kinds::MupdateOverrideUuid; diff --git a/nexus/src/app/device_auth.rs b/nexus/src/app/device_auth.rs index 3a9893fdbf9..3252f6cf5fc 100644 --- a/nexus/src/app/device_auth.rs +++ b/nexus/src/app/device_auth.rs @@ -54,8 +54,7 @@ use nexus_db_queries::db::model::{DeviceAccessToken, DeviceAuthRequest}; use omicron_uuid_kinds::{GenericUuid, SiloUserUuid}; use anyhow::anyhow; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::device; use omicron_common::api::external::{ CreateResult, DataPageParams, Error, ListResultVec, }; @@ -78,7 +77,7 @@ impl super::Nexus { pub(crate) async fn device_auth_request_create( &self, opctx: &OpContext, - params: params::DeviceAuthRequest, + params: device::DeviceAuthRequest, ) -> CreateResult { // TODO-correctness: the `user_code` generated for a new request // is used as a primary key, but may potentially collide with an @@ -293,7 +292,7 @@ impl super::Nexus { pub(crate) async fn device_access_token( &self, opctx: &OpContext, - params: params::DeviceAccessTokenRequest, + params: device::DeviceAccessTokenRequest, ) -> Result, HttpError> { // RFC 8628 §3.4 if params.grant_type != "urn:ietf:params:oauth:grant-type:device_code" { @@ -318,7 +317,7 @@ impl super::Nexus { Ok(response) => match response { Granted(token) => self.build_oauth_response( StatusCode::OK, - &views::DeviceAccessTokenGrant::from(token), + &device::DeviceAccessTokenGrant::from(token), ), Pending => self.build_oauth_response( StatusCode::BAD_REQUEST, diff --git a/nexus/src/app/disk.rs b/nexus/src/app/disk.rs index 82eb9534495..acf094c8fb9 100644 --- a/nexus/src/app/disk.rs +++ b/nexus/src/app/disk.rs @@ -5,7 +5,6 @@ //! Disks use crate::app::sagas; -use crate::external_api::params; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; use nexus_db_model::DiskTypeLocalStorage; @@ -14,6 +13,8 @@ use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; use nexus_db_queries::db::datastore; +use nexus_types::external_api::disk; +use nexus_types::external_api::project; use omicron_common::api::external; use omicron_common::api::external::ByteCount; use omicron_common::api::external::CreateResult; @@ -37,24 +38,27 @@ impl super::Nexus { pub fn disk_lookup<'a>( &'a self, opctx: &'a OpContext, - disk_selector: params::DiskSelector, + disk_selector: disk::DiskSelector, ) -> LookupResult> { match disk_selector { - params::DiskSelector { disk: NameOrId::Id(id), project: None } => { + disk::DiskSelector { disk: NameOrId::Id(id), project: None } => { let disk = LookupPath::new(opctx, &self.db_datastore).disk_id(id); Ok(disk) } - params::DiskSelector { + disk::DiskSelector { disk: NameOrId::Name(name), project: Some(project), } => { let disk = self - .project_lookup(opctx, params::ProjectSelector { project })? + .project_lookup( + opctx, + project::ProjectSelector { project }, + )? .disk_name_owned(name.into()); Ok(disk) } - params::DiskSelector { disk: NameOrId::Id(_), .. } => { + disk::DiskSelector { disk: NameOrId::Id(_), .. } => { Err(Error::invalid_request( "when providing disk as an ID project should not be specified", )) @@ -68,7 +72,7 @@ impl super::Nexus { pub async fn disk_get( &self, opctx: &OpContext, - disk_selector: params::DiskSelector, + disk_selector: disk::DiskSelector, ) -> LookupResult { let disk_lookup = self.disk_lookup(opctx, disk_selector)?; @@ -82,15 +86,15 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, authz_project: &authz::Project, - disk_source: ¶ms::DiskSource, + disk_source: &disk::DiskSource, size: ByteCount, ) -> Result { let block_size: u64 = match disk_source { - params::DiskSource::Blank { block_size } - | params::DiskSource::ImportingBlocks { block_size } => { + disk::DiskSource::Blank { block_size } + | disk::DiskSource::ImportingBlocks { block_size } => { (*block_size).into() } - ¶ms::DiskSource::Snapshot { snapshot_id, read_only } => { + &disk::DiskSource::Snapshot { snapshot_id, read_only } => { let (.., db_snapshot) = LookupPath::new(opctx, &self.db_datastore) .snapshot_id(snapshot_id) @@ -131,7 +135,7 @@ impl super::Nexus { db_snapshot.block_size.to_bytes().into() } - ¶ms::DiskSource::Image { image_id, read_only } => { + &disk::DiskSource::Image { image_id, read_only } => { let (.., db_image) = LookupPath::new(opctx, &self.db_datastore) .image_id(image_id) .fetch() @@ -181,10 +185,10 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, authz_project: &authz::Project, - params: ¶ms::DiskCreate, + params: &disk::DiskCreate, ) -> Result<(), Error> { let block_size: u64 = match ¶ms.disk_backend { - params::DiskBackend::Distributed { disk_source } => { + disk::DiskBackend::Distributed { disk_source } => { self.validate_crucible_disk_create_params( opctx, &authz_project, @@ -194,7 +198,7 @@ impl super::Nexus { .await? } - params::DiskBackend::Local { .. } => { + disk::DiskBackend::Local { .. } => { // All LocalStorage disks have a 4k block size 4096 } @@ -242,7 +246,7 @@ impl super::Nexus { // Check for disk type specific restrictions match ¶ms.disk_backend { - params::DiskBackend::Distributed { .. } => { + disk::DiskBackend::Distributed { .. } => { // Reject disks where the size is greated than // MAX_DISK_SIZE_BYTES. This restriction will be changed or // removed when multi-subvolume Volumes can be created by Nexus, @@ -258,7 +262,7 @@ impl super::Nexus { } } - params::DiskBackend::Local {} => { + disk::DiskBackend::Local {} => { // If a user requests some outlandish number of TB for local // storage, and there isn't a sled allocation that can fulfill // this, instance create will work but instance start (which @@ -312,7 +316,7 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, project_lookup: &lookup::Project<'_>, - params: ¶ms::DiskCreate, + params: &disk::DiskCreate, ) -> CreateResult { let (.., authz_project) = project_lookup.lookup_for(authz::Action::CreateChild).await?; @@ -528,7 +532,7 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, disk_lookup: &lookup::Disk<'_>, - param: params::ImportBlocksBulkWrite, + param: disk::ImportBlocksBulkWrite, ) -> UpdateResult<()> { let (.., authz_disk) = disk_lookup.lookup_for(authz::Action::Modify).await?; @@ -719,7 +723,7 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, disk_lookup: &lookup::Disk<'_>, - finalize_params: ¶ms::FinalizeDisk, + finalize_params: &disk::FinalizeDisk, ) -> UpdateResult<()> { let (authz_silo, authz_proj, authz_disk, _db_disk) = disk_lookup.fetch_for(authz::Action::Modify).await?; diff --git a/nexus/src/app/external_endpoints.rs b/nexus/src/app/external_endpoints.rs index 891a43ac6df..d8a490e97a7 100644 --- a/nexus/src/app/external_endpoints.rs +++ b/nexus/src/app/external_endpoints.rs @@ -805,8 +805,8 @@ mod test { use nexus_db_model::DnsZone; use nexus_db_model::ServiceKind; use nexus_db_model::Silo; - use nexus_types::external_api::params; - use nexus_types::external_api::shared; + use nexus_types::external_api::certificate; + use nexus_types::external_api::silo; use nexus_types::identity::Resource; use omicron_common::api::external::Error; use omicron_common::api::external::IdentityMetadataCreateParams; @@ -818,16 +818,16 @@ mod test { fn create_silo(silo_id: Option, name: &str, saml: bool) -> Silo { let identity_mode = if saml { - shared::SiloIdentityMode::SamlJit + silo::SiloIdentityMode::SamlJit } else { - shared::SiloIdentityMode::LocalOnly + silo::SiloIdentityMode::LocalOnly }; - let params = params::SiloCreate { + let params = silo::SiloCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: String::new(), }, - quotas: params::SiloQuotasCreate::empty(), + quotas: silo::SiloQuotasCreate::empty(), discoverable: false, identity_mode, admin_group_name: None, @@ -846,7 +846,7 @@ mod test { fn create_certificate( domain: &str, expired: bool, - ) -> params::CertificateCreate { + ) -> certificate::CertificateCreate { let mut cert_params = rcgen::CertificateParams::new(vec![domain.to_string()]); if expired { @@ -857,14 +857,14 @@ mod test { cert.serialize_pem().expect("serializing certificate as PEM"); let key_pem = cert.serialize_private_key_pem(); let namestr = format!("cert-for-{}", domain.replace('.', "-")); - params::CertificateCreate { + certificate::CertificateCreate { identity: IdentityMetadataCreateParams { name: namestr.parse().unwrap(), description: String::new(), }, cert: cert_pem, key: key_pem, - service: shared::ServiceUsingCertificate::ExternalApi, + service: certificate::ServiceUsingCertificate::ExternalApi, } } diff --git a/nexus/src/app/external_ip.rs b/nexus/src/app/external_ip.rs index 1fcbfc8c889..4b0dc48c6cf 100644 --- a/nexus/src/app/external_ip.rs +++ b/nexus/src/app/external_ip.rs @@ -6,8 +6,6 @@ use std::sync::Arc; -use crate::external_api::views::ExternalIp; -use crate::external_api::views::FloatingIp; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; use nexus_db_model::IpAttachState; @@ -15,8 +13,11 @@ use nexus_db_model::IpVersion; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::datastore::FloatingIpAllocation; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::external_ip; +use nexus_types::external_api::floating_ip; +use nexus_types::external_api::instance; +use nexus_types::external_api::ip_pool; +use nexus_types::external_api::project; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; @@ -33,7 +34,7 @@ impl super::Nexus { &self, opctx: &OpContext, instance_lookup: &lookup::Instance<'_>, - ) -> ListResultVec { + ) -> ListResultVec { let (.., authz_instance) = instance_lookup.lookup_for(authz::Action::Read).await?; Ok(self @@ -57,10 +58,10 @@ impl super::Nexus { pub(crate) fn floating_ip_lookup<'a>( &'a self, opctx: &'a OpContext, - fip_selector: params::FloatingIpSelector, + fip_selector: floating_ip::FloatingIpSelector, ) -> LookupResult> { match fip_selector { - params::FloatingIpSelector { + floating_ip::FloatingIpSelector { floating_ip: NameOrId::Id(id), project: None, } => { @@ -68,17 +69,21 @@ impl super::Nexus { .floating_ip_id(id); Ok(floating_ip) } - params::FloatingIpSelector { + floating_ip::FloatingIpSelector { floating_ip: NameOrId::Name(name), project: Some(project), } => { let floating_ip = self - .project_lookup(opctx, params::ProjectSelector { project })? + .project_lookup( + opctx, + project::ProjectSelector { project }, + )? .floating_ip_name_owned(name.into()); Ok(floating_ip) } - params::FloatingIpSelector { - floating_ip: NameOrId::Id(_), .. + floating_ip::FloatingIpSelector { + floating_ip: NameOrId::Id(_), + .. } => Err(Error::invalid_request( "when providing Floating IP as an ID project should not be specified", )), @@ -93,7 +98,7 @@ impl super::Nexus { opctx: &OpContext, project_lookup: &lookup::Project<'_>, pagparams: &PaginatedBy<'_>, - ) -> ListResultVec { + ) -> ListResultVec { let (.., authz_project) = project_lookup.lookup_for(authz::Action::ListChildren).await?; @@ -110,20 +115,21 @@ impl super::Nexus { &self, opctx: &OpContext, project_lookup: &lookup::Project<'_>, - params: params::FloatingIpCreate, - ) -> CreateResult { + params: floating_ip::FloatingIpCreate, + ) -> CreateResult { let (.., authz_project) = project_lookup.lookup_for(authz::Action::CreateChild).await?; - let params::FloatingIpCreate { identity, address_allocator } = params; + let floating_ip::FloatingIpCreate { identity, address_allocator } = + params; let allocation = match address_allocator { - params::AddressAllocator::Explicit { ip } => { + floating_ip::AddressAllocator::Explicit { ip } => { FloatingIpAllocation::Explicit { ip } } - params::AddressAllocator::Auto { pool_selector } => { + floating_ip::AddressAllocator::Auto { pool_selector } => { match pool_selector { - params::PoolSelector::Explicit { pool } => { + ip_pool::PoolSelector::Explicit { pool } => { let authz_pool = self .ip_pool_lookup(opctx, &pool)? .lookup_for(authz::Action::CreateChild) @@ -134,7 +140,7 @@ impl super::Nexus { ip_version: None, } } - params::PoolSelector::Auto { ip_version } => { + ip_pool::PoolSelector::Auto { ip_version } => { FloatingIpAllocation::Auto { pool: None, ip_version: ip_version.map(Into::into), @@ -161,8 +167,8 @@ impl super::Nexus { &self, opctx: &OpContext, ip_lookup: lookup::FloatingIp<'_>, - params: params::FloatingIpUpdate, - ) -> UpdateResult { + params: floating_ip::FloatingIpUpdate, + ) -> UpdateResult { let (.., authz_fip) = ip_lookup.lookup_for(authz::Action::Modify).await?; Ok(self @@ -187,20 +193,20 @@ impl super::Nexus { pub(crate) async fn floating_ip_attach( self: &Arc, opctx: &OpContext, - fip_selector: params::FloatingIpSelector, - target: params::FloatingIpAttach, - ) -> UpdateResult { + fip_selector: floating_ip::FloatingIpSelector, + target: floating_ip::FloatingIpAttach, + ) -> UpdateResult { let fip_lookup = self.floating_ip_lookup(opctx, fip_selector)?; let (.., authz_project, authz_fip, db_fip) = fip_lookup.fetch_for(authz::Action::Modify).await?; match target.kind { - params::FloatingIpParentKind::Instance => { + floating_ip::FloatingIpParentKind::Instance => { // Handle the cases where the FIP and instance are specified by // name and ID (or ID and name) respectively. We remove the project // from the instance lookup if using the instance's ID, and insert // the floating IP's project ID otherwise. - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: match &target.parent { NameOrId::Id(_) => None, NameOrId::Name(_) => Some(authz_project.id().into()), @@ -223,7 +229,7 @@ impl super::Nexus { authz_project, ) .await - .and_then(FloatingIp::try_from) + .and_then(floating_ip::FloatingIp::try_from) } } } @@ -232,7 +238,7 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, ip_lookup: lookup::FloatingIp<'_>, - ) -> UpdateResult { + ) -> UpdateResult { // XXX: Today, this only happens for instances. // In future, we will need to separate out by the *type* of // parent attached to a floating IP. We don't yet store this @@ -245,17 +251,17 @@ impl super::Nexus { return Ok(db_fip.into()); }; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: None, instance: parent_id.into(), }; let instance = self.instance_lookup(opctx, instance_selector)?; - let attach_params = ¶ms::ExternalIpDetach::Floating { + let attach_params = &instance::ExternalIpDetach::Floating { floating_ip: authz_fip.id().into(), }; self.instance_detach_external_ip(opctx, &instance, attach_params) .await - .and_then(FloatingIp::try_from) + .and_then(floating_ip::FloatingIp::try_from) } } diff --git a/nexus/src/app/external_subnet.rs b/nexus/src/app/external_subnet.rs index 5f50484f2ad..695fe177b23 100644 --- a/nexus/src/app/external_subnet.rs +++ b/nexus/src/app/external_subnet.rs @@ -15,7 +15,9 @@ use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; -use nexus_types::external_api::{params, views}; +use nexus_types::external_api::external_subnet as external_subnet_types; +use nexus_types::external_api::instance as instance_types; +use nexus_types::external_api::project as project_types; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; use omicron_common::api::external::InternalContext as _; @@ -32,21 +34,24 @@ impl super::Nexus { pub(crate) fn external_subnet_lookup<'a>( &'a self, opctx: &'a OpContext, - selector: params::ExternalSubnetSelector, + selector: external_subnet_types::ExternalSubnetSelector, ) -> LookupResult> { match selector { - params::ExternalSubnetSelector { + external_subnet_types::ExternalSubnetSelector { external_subnet: NameOrId::Id(id), project: None, } => Ok(LookupPath::new(opctx, self.datastore()) .external_subnet_id(ExternalSubnetUuid::from_untyped_uuid(id))), - params::ExternalSubnetSelector { + external_subnet_types::ExternalSubnetSelector { external_subnet: NameOrId::Name(name), project: Some(project), } => self - .project_lookup(opctx, params::ProjectSelector { project }) + .project_lookup( + opctx, + project_types::ProjectSelector { project }, + ) .map(|p| p.external_subnet_name_owned(name.into())), - params::ExternalSubnetSelector { + external_subnet_types::ExternalSubnetSelector { external_subnet: NameOrId::Id(_), project: Some(_), } => Err(Error::invalid_request( @@ -65,7 +70,7 @@ impl super::Nexus { opctx: &OpContext, project_lookup: &lookup::Project<'_>, pagparams: &PaginatedBy<'_>, - ) -> ListResultVec { + ) -> ListResultVec { let (.., authz_project, _db_project) = project_lookup.fetch_for(authz::Action::ListChildren).await?; self.datastore() @@ -78,8 +83,8 @@ impl super::Nexus { &self, opctx: &OpContext, project_lookup: &lookup::Project<'_>, - params: params::ExternalSubnetCreate, - ) -> Result { + params: external_subnet_types::ExternalSubnetCreate, + ) -> Result { let (authz_silo, authz_project, _db_project) = project_lookup.fetch_for(authz::Action::CreateChild).await?; self.datastore() @@ -96,8 +101,8 @@ impl super::Nexus { pub(crate) async fn external_subnet_view( &self, opctx: &OpContext, - selector: params::ExternalSubnetSelector, - ) -> LookupResult { + selector: external_subnet_types::ExternalSubnetSelector, + ) -> LookupResult { let (.., db_subnet) = self .external_subnet_lookup(opctx, selector)? .fetch_for(authz::Action::Read) @@ -108,9 +113,9 @@ impl super::Nexus { pub(crate) async fn external_subnet_update( &self, opctx: &OpContext, - selector: params::ExternalSubnetSelector, - params: params::ExternalSubnetUpdate, - ) -> UpdateResult { + selector: external_subnet_types::ExternalSubnetSelector, + params: external_subnet_types::ExternalSubnetUpdate, + ) -> UpdateResult { let (.., authz_subnet, _db_subnet) = self .external_subnet_lookup(opctx, selector)? .fetch_for(authz::Action::Modify) @@ -124,7 +129,7 @@ impl super::Nexus { pub(crate) async fn external_subnet_delete( &self, opctx: &OpContext, - selector: params::ExternalSubnetSelector, + selector: external_subnet_types::ExternalSubnetSelector, ) -> DeleteResult { let (.., authz_subnet, _db_subnet) = self .external_subnet_lookup(opctx, selector)? @@ -136,19 +141,19 @@ impl super::Nexus { pub(crate) async fn external_subnet_attach( &self, opctx: &OpContext, - selector: params::ExternalSubnetSelector, - attach: params::ExternalSubnetAttach, - ) -> UpdateResult { + selector: external_subnet_types::ExternalSubnetSelector, + attach: external_subnet_types::ExternalSubnetAttach, + ) -> UpdateResult { let (.., authz_project, authz_subnet, db_subnet) = self .external_subnet_lookup(opctx, selector)? .fetch_for(authz::Action::Modify) .await?; let instance_selector = match &attach.instance { - NameOrId::Id(id) => params::InstanceSelector { + NameOrId::Id(id) => instance_types::InstanceSelector { project: None, instance: NameOrId::Id(*id), }, - NameOrId::Name(name) => params::InstanceSelector { + NameOrId::Name(name) => instance_types::InstanceSelector { project: Some(NameOrId::Id(authz_project.id())), instance: NameOrId::Name(name.clone()), }, @@ -173,7 +178,9 @@ impl super::Nexus { .saga_execute::(params) .await?; output - .lookup_node_output::("output") + .lookup_node_output::( + "output", + ) .map_err(|e| Error::internal_error(&format!("{e:#}"))) .internal_context("looking up output from subnet attach saga") } @@ -181,8 +188,8 @@ impl super::Nexus { pub(crate) async fn external_subnet_detach( &self, opctx: &OpContext, - selector: params::ExternalSubnetSelector, - ) -> UpdateResult { + selector: external_subnet_types::ExternalSubnetSelector, + ) -> UpdateResult { let (.., authz_subnet, db_subnet) = self .external_subnet_lookup(opctx, selector)? .fetch_for(authz::Action::Modify) @@ -192,7 +199,7 @@ impl super::Nexus { "External subnet is not attached to an instance", )); }; - let instance_selector = params::InstanceSelector { + let instance_selector = instance_types::InstanceSelector { project: None, instance: NameOrId::Id(instance_id.into_untyped_uuid()), }; @@ -210,7 +217,9 @@ impl super::Nexus { .saga_execute::(params) .await?; output - .lookup_node_output::("output") + .lookup_node_output::( + "output", + ) .map_err(|e| Error::internal_error(&format!("{e:#}"))) .internal_context("looking up output from subnet detach saga") } @@ -219,7 +228,7 @@ impl super::Nexus { &self, opctx: &OpContext, instance_lookup: &lookup::Instance<'_>, - ) -> ListResultVec { + ) -> ListResultVec { let (.., authz_project, authz_instance) = instance_lookup.lookup_for(authz::Action::Read).await?; diff --git a/nexus/src/app/iam.rs b/nexus/src/app/iam.rs index aa1b7e99364..8e37414e483 100644 --- a/nexus/src/app/iam.rs +++ b/nexus/src/app/iam.rs @@ -4,7 +4,6 @@ //! Built-ins and roles -use crate::external_api::shared; use anyhow::Context; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; @@ -14,7 +13,8 @@ use nexus_db_queries::db; use nexus_db_queries::db::datastore::SiloGroup; use nexus_db_queries::db::datastore::SiloUser; use nexus_db_queries::db::model::Name; -use nexus_types::external_api::params; +use nexus_types::external_api::policy; +use nexus_types::external_api::user; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::Error; use omicron_common::api::external::InternalContext; @@ -34,7 +34,7 @@ impl super::Nexus { pub(crate) async fn fleet_fetch_policy( &self, opctx: &OpContext, - ) -> LookupResult> { + ) -> LookupResult> { let role_assignments = self .db_datastore .role_assignment_fetch_visible(opctx, &authz::FLEET) @@ -43,14 +43,14 @@ impl super::Nexus { .map(|r| r.try_into().context("parsing database role assignment")) .collect::, _>>() .map_err(|error| Error::internal_error(&format!("{:#}", error)))?; - Ok(shared::Policy { role_assignments }) + Ok(policy::Policy { role_assignments }) } pub(crate) async fn fleet_update_policy( &self, opctx: &OpContext, - policy: &shared::Policy, - ) -> UpdateResult> { + policy: &policy::Policy, + ) -> UpdateResult> { let role_assignments = self .db_datastore .role_assignment_replace_visible( @@ -62,7 +62,7 @@ impl super::Nexus { .into_iter() .map(|r| r.try_into()) .collect::, _>>()?; - Ok(shared::Policy { role_assignments }) + Ok(policy::Policy { role_assignments }) } // Silo users @@ -166,15 +166,13 @@ impl super::Nexus { pub fn user_builtin_lookup<'a>( &'a self, opctx: &'a OpContext, - user_selector: &'a params::UserBuiltinSelector, + user_selector: &'a user::UserBuiltinSelector, ) -> LookupResult> { let lookup_path = LookupPath::new(opctx, &self.db_datastore); let user = match user_selector { - params::UserBuiltinSelector { user: NameOrId::Id(id) } => { - lookup_path - .user_builtin_id(BuiltInUserUuid::from_untyped_uuid(*id)) - } - params::UserBuiltinSelector { user: NameOrId::Name(name) } => { + user::UserBuiltinSelector { user: NameOrId::Id(id) } => lookup_path + .user_builtin_id(BuiltInUserUuid::from_untyped_uuid(*id)), + user::UserBuiltinSelector { user: NameOrId::Name(name) } => { lookup_path.user_builtin_name(Name::ref_cast(name)) } }; diff --git a/nexus/src/app/image.rs b/nexus/src/app/image.rs index 9f54d436dbb..74f6b2a0bdd 100644 --- a/nexus/src/app/image.rs +++ b/nexus/src/app/image.rs @@ -4,7 +4,6 @@ //! Images (both project and silo scoped) -use crate::external_api::params; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; use nexus_db_lookup::lookup::ImageLookup; @@ -13,6 +12,8 @@ use nexus_db_queries::authn; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; +use nexus_types::external_api::image; +use nexus_types::external_api::project; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; @@ -30,13 +31,10 @@ impl super::Nexus { pub(crate) async fn image_lookup<'a>( &'a self, opctx: &'a OpContext, - image_selector: params::ImageSelector, + image_selector: image::ImageSelector, ) -> LookupResult> { match image_selector { - params::ImageSelector { - image: NameOrId::Id(id), - project: None, - } => { + image::ImageSelector { image: NameOrId::Id(id), project: None } => { let (.., db_image) = LookupPath::new(opctx, &self.db_datastore) .image_id(id) .fetch() @@ -53,16 +51,19 @@ impl super::Nexus { }; Ok(lookup) } - params::ImageSelector { + image::ImageSelector { image: NameOrId::Name(name), project: Some(project), } => { let image = self - .project_lookup(opctx, params::ProjectSelector { project })? + .project_lookup( + opctx, + project::ProjectSelector { project }, + )? .project_image_name_owned(name.into()); Ok(ImageLookup::ProjectImage(image)) } - params::ImageSelector { + image::ImageSelector { image: NameOrId::Name(name), project: None, } => { @@ -71,7 +72,7 @@ impl super::Nexus { .silo_image_name_owned(name.into()); Ok(ImageLookup::SiloImage(image)) } - params::ImageSelector { image: NameOrId::Id(_), .. } => { + image::ImageSelector { image: NameOrId::Id(_), .. } => { Err(Error::invalid_request( "when providing image as an ID, project should not be specified", )) @@ -84,7 +85,7 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, lookup_parent: &ImageParentLookup<'_>, - params: ¶ms::ImageCreate, + params: &image::ImageCreate, ) -> CreateResult { let image_type = match lookup_parent { ImageParentLookup::Project(project) => { diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index ac66836e731..659e863d795 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -17,7 +17,6 @@ use crate::app::sagas; use crate::app::sagas::NexusSaga; use crate::db::datastore::Disk; use crate::db::datastore::LocalStorageAllocation; -use crate::external_api::params; use cancel_safe_futures::prelude::*; use futures::future::Fuse; use futures::{FutureExt, SinkExt, StreamExt}; @@ -41,7 +40,12 @@ use nexus_db_queries::db::DataStore; use nexus_db_queries::db::datastore::InstanceAndActiveVmm; use nexus_db_queries::db::datastore::InstanceStateComputer; use nexus_db_queries::db::identity::Resource; -use nexus_types::external_api::views; +use nexus_types::external_api::disk; +use nexus_types::external_api::external_ip; +use nexus_types::external_api::instance; +use nexus_types::external_api::ip_pool; +use nexus_types::external_api::multicast; +use nexus_types::external_api::project; use omicron_common::address::ConcreteIp; use omicron_common::api::external::ByteCount; use omicron_common::api::external::CreateResult; @@ -336,10 +340,10 @@ impl super::Nexus { pub fn instance_lookup<'a>( &'a self, opctx: &'a OpContext, - instance_selector: params::InstanceSelector, + instance_selector: instance::InstanceSelector, ) -> LookupResult> { match instance_selector { - params::InstanceSelector { + instance::InstanceSelector { instance: NameOrId::Id(id), project: None, } => { @@ -347,20 +351,23 @@ impl super::Nexus { LookupPath::new(opctx, &self.db_datastore).instance_id(id); Ok(instance) } - params::InstanceSelector { + instance::InstanceSelector { instance: NameOrId::Name(name), project: Some(project), } => { let instance = self - .project_lookup(opctx, params::ProjectSelector { project })? + .project_lookup( + opctx, + project::ProjectSelector { project }, + )? .instance_name_owned(name.into()); Ok(instance) } - params::InstanceSelector { instance: NameOrId::Id(_), .. } => { - Err(Error::invalid_request( - "when providing instance as an ID project should not be specified", - )) - } + instance::InstanceSelector { + instance: NameOrId::Id(_), .. + } => Err(Error::invalid_request( + "when providing instance as an ID project should not be specified", + )), _ => Err(Error::invalid_request( "instance should either be UUID or project should be specified", )), @@ -376,7 +383,7 @@ impl super::Nexus { &self, opctx: &OpContext, authz_instance: &authz::Instance, - multicast_groups: &[params::MulticastGroupJoinSpec], + multicast_groups: &[multicast::MulticastGroupJoinSpec], ) -> Result<(), Error> { let instance_id = authz_instance.id(); @@ -537,12 +544,12 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, instance_lookup: &lookup::Instance<'_>, - params: ¶ms::InstanceUpdate, + params: &instance::InstanceUpdate, ) -> UpdateResult { let (.., authz_project, authz_instance) = instance_lookup.lookup_for(authz::Action::Modify).await?; - let params::InstanceUpdate { + let instance::InstanceUpdate { ncpus, memory, auto_restart_policy, @@ -555,7 +562,7 @@ impl super::Nexus { let boot_disk_id = match boot_disk.as_ref() { Some(disk) => { - let selector = params::DiskSelector { + let selector = disk::DiskSelector { project: match &disk { NameOrId::Name(_) => Some(authz_project.id().into()), NameOrId::Id(_) => None, @@ -616,14 +623,14 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, project_lookup: &lookup::Project<'_>, - params: ¶ms::InstanceCreate, + params: &instance::InstanceCreate, ) -> CreateResult { let (.., authz_project) = project_lookup.lookup_for(authz::Action::CreateChild).await?; check_instance_cpu_memory_sizes(params.ncpus, params.memory)?; - let all_disks: Vec<¶ms::InstanceDiskAttachment> = + let all_disks: Vec<&instance::InstanceDiskAttachment> = params.boot_disk.iter().chain(params.disks.iter()).collect(); // Validate parameters @@ -635,7 +642,7 @@ impl super::Nexus { } for disk in all_disks.iter() { - if let params::InstanceDiskAttachment::Create(create) = disk { + if let instance::InstanceDiskAttachment::Create(create) = disk { self.validate_disk_create_params(opctx, &authz_project, create) .await?; } @@ -660,7 +667,7 @@ impl super::Nexus { .external_ips .iter() .filter_map(|v| match v { - params::ExternalIpCreate::Ephemeral { pool_selector } => { + instance::ExternalIpCreate::Ephemeral { pool_selector } => { Some(pool_selector) } _ => None, @@ -684,8 +691,9 @@ impl super::Nexus { .await?; } - if let params::InstanceNetworkInterfaceAttachment::Create(ref ifaces) = - params.network_interfaces + if let instance::InstanceNetworkInterfaceAttachment::Create( + ref ifaces, + ) = params.network_interfaces { if ifaces.len() > MAX_NICS_PER_INSTANCE { return Err(Error::invalid_request(&format!( @@ -746,7 +754,7 @@ impl super::Nexus { let saga_params = sagas::instance_create::Params { serialized_authn: authn::saga::Serialized::for_opctx(opctx), project_id: authz_project.id(), - create_params: params::InstanceCreate { + create_params: instance::InstanceCreate { ssh_public_keys: ssh_keys, anti_affinity_groups, ..params.clone() @@ -1803,7 +1811,7 @@ impl super::Nexus { let (.., authz_project_disk, authz_disk) = self .disk_lookup( opctx, - params::DiskSelector { + disk::DiskSelector { project: match disk { NameOrId::Name(_) => Some(authz_project.id().into()), NameOrId::Id(_) => None, @@ -1864,7 +1872,7 @@ impl super::Nexus { let (.., authz_disk) = self .disk_lookup( opctx, - params::DiskSelector { + disk::DiskSelector { project: match disk { NameOrId::Name(_) => Some(authz_project.id().into()), NameOrId::Id(_) => None, @@ -1968,8 +1976,8 @@ impl super::Nexus { &self, opctx: &OpContext, instance_lookup: &lookup::Instance<'_>, - params: ¶ms::InstanceSerialConsoleRequest, - ) -> Result { + params: &instance::InstanceSerialConsoleRequest, + ) -> Result { let (_, client) = self .propolis_client_for_instance( opctx, @@ -1998,7 +2006,7 @@ impl super::Nexus { )) })? .into_inner(); - Ok(params::InstanceSerialConsoleData { + Ok(instance::InstanceSerialConsoleData { data: data.data, last_byte_offset: data.last_byte_offset, }) @@ -2009,7 +2017,7 @@ impl super::Nexus { opctx: &OpContext, mut client_stream: WebSocketStream, instance_lookup: &lookup::Instance<'_>, - params: ¶ms::InstanceSerialConsoleStreamRequest, + params: &instance::InstanceSerialConsoleStreamRequest, ) -> Result<(), Error> { let (_, client_addr) = match self .propolis_addr_for_instance( @@ -2295,7 +2303,7 @@ impl super::Nexus { instance_lookup: &lookup::Instance<'_>, pool: Option, ip_version: Option, - ) -> UpdateResult { + ) -> UpdateResult { // Validate pool/ip_version compatibility upfront for clear error // communication. // @@ -2336,10 +2344,10 @@ impl super::Nexus { async fn validate_ephemeral_ip_pair( &self, opctx: &OpContext, - first: ¶ms::PoolSelector, - second: ¶ms::PoolSelector, + first: &ip_pool::PoolSelector, + second: &ip_pool::PoolSelector, ) -> Result<(), Error> { - use params::PoolSelector; + use ip_pool::PoolSelector; match (first, second) { // Reject any case where ip_version is not specified. This keeps @@ -2438,7 +2446,7 @@ impl super::Nexus { authz_fip: authz::FloatingIp, ip_version: IpVersion, authz_fip_project: authz::Project, - ) -> UpdateResult { + ) -> UpdateResult { let (.., authz_project, authz_instance) = instance_lookup.lookup_for(authz::Action::Modify).await?; @@ -2464,7 +2472,7 @@ impl super::Nexus { authz_instance: authz::Instance, project_id: Uuid, ext_ip: ExternalIpAttach, - ) -> UpdateResult { + ) -> UpdateResult { let saga_params = sagas::instance_ip_attach::Params { create_params: ext_ip.clone(), authz_instance, @@ -2480,7 +2488,7 @@ impl super::Nexus { .await?; let out = saga_outputs - .lookup_node_output::("output") + .lookup_node_output::("output") .map_err(|e| Error::internal_error(&format!("{:#}", &e))) .internal_context("looking up output from ip attach saga"); @@ -2498,8 +2506,8 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, instance_lookup: &lookup::Instance<'_>, - ext_ip: ¶ms::ExternalIpDetach, - ) -> UpdateResult { + ext_ip: &instance::ExternalIpDetach, + ) -> UpdateResult { let (.., authz_project, authz_instance) = instance_lookup.lookup_for(authz::Action::Modify).await?; @@ -2518,7 +2526,7 @@ impl super::Nexus { .await?; saga_outputs - .lookup_node_output::>("output") + .lookup_node_output::>("output") .map_err(|e| Error::internal_error(&format!("{:#}", &e))) .internal_context("looking up output from ip detach saga") .and_then(|eip| { @@ -2939,6 +2947,7 @@ mod tests { use super::*; use core::time::Duration; use futures::{SinkExt, StreamExt}; + use instance::InstanceNetworkInterfaceAttachment; use nexus_db_model::{ Instance as DbInstance, InstanceState as DbInstanceState, VmmCpuPlatform, VmmState as DbVmmState, @@ -2947,7 +2956,6 @@ mod tests { Hostname, IdentityMetadataCreateParams, InstanceCpuCount, Name, }; use omicron_test_utils::dev::test_setup_log; - use params::InstanceNetworkInterfaceAttachment; use propolis_client::support::tungstenite::protocol::Role; use propolis_client::support::{ InstanceSerialConsoleHelper, WSClientOffset, @@ -3050,7 +3058,7 @@ mod tests { /// that the VMM is *not* installed in the instance's `active_propolis_id` /// field. fn make_instance_and_vmm() -> (DbInstance, DbVmm) { - let params = params::InstanceCreate { + let params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from("elysium".to_owned()).unwrap(), description: "this instance is disco".to_owned(), diff --git a/nexus/src/app/internet_gateway.rs b/nexus/src/app/internet_gateway.rs index 46da3bcd7e0..fe19c7c48f6 100644 --- a/nexus/src/app/internet_gateway.rs +++ b/nexus/src/app/internet_gateway.rs @@ -4,12 +4,13 @@ //! Internet gateways -use crate::external_api::params; use nexus_auth::authz; use nexus_auth::context::OpContext; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; use nexus_db_queries::db; +use nexus_types::external_api::internet_gateway; +use nexus_types::external_api::vpc; use nexus_types::identity::Resource; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; @@ -27,10 +28,10 @@ impl super::Nexus { pub fn internet_gateway_lookup<'a>( &'a self, opctx: &'a OpContext, - igw_selector: params::InternetGatewaySelector, + igw_selector: internet_gateway::InternetGatewaySelector, ) -> LookupResult> { match igw_selector { - params::InternetGatewaySelector { + internet_gateway::InternetGatewaySelector { gateway: NameOrId::Id(id), vpc: None, project: None, @@ -39,17 +40,20 @@ impl super::Nexus { .internet_gateway_id(id); Ok(gw) } - params::InternetGatewaySelector { + internet_gateway::InternetGatewaySelector { gateway: NameOrId::Name(name), - vpc: Some(vpc), + vpc: Some(igw_vpc), project, } => { let gw = self - .vpc_lookup(opctx, params::VpcSelector { project, vpc })? + .vpc_lookup( + opctx, + vpc::VpcSelector { project, vpc: igw_vpc }, + )? .internet_gateway_name_owned(name.into()); Ok(gw) } - params::InternetGatewaySelector { + internet_gateway::InternetGatewaySelector { gateway: NameOrId::Id(_), .. } => Err(Error::invalid_request( @@ -66,7 +70,7 @@ impl super::Nexus { &self, opctx: &OpContext, vpc_lookup: &lookup::Vpc<'_>, - params: ¶ms::InternetGatewayCreate, + params: &internet_gateway::InternetGatewayCreate, ) -> CreateResult { let (.., authz_vpc) = vpc_lookup.lookup_for(authz::Action::CreateChild).await?; @@ -148,10 +152,10 @@ impl super::Nexus { pub fn internet_gateway_ip_pool_lookup<'a>( &'a self, opctx: &'a OpContext, - pool_selector: params::InternetGatewayIpPoolSelector, + pool_selector: internet_gateway::InternetGatewayIpPoolSelector, ) -> LookupResult> { match pool_selector { - params::InternetGatewayIpPoolSelector { + internet_gateway::InternetGatewayIpPoolSelector { pool: NameOrId::Id(id), gateway: None, vpc: None, @@ -161,25 +165,25 @@ impl super::Nexus { .internet_gateway_ip_pool_id(id); Ok(route) } - params::InternetGatewayIpPoolSelector { + internet_gateway::InternetGatewayIpPoolSelector { pool: NameOrId::Name(name), gateway: Some(gateway), - vpc, + vpc: igw_vpc, project, } => { let route = self .internet_gateway_lookup( opctx, - params::InternetGatewaySelector { + internet_gateway::InternetGatewaySelector { project, - vpc, + vpc: igw_vpc, gateway, }, )? .internet_gateway_ip_pool_name_owned(name.into()); Ok(route) } - params::InternetGatewayIpPoolSelector { + internet_gateway::InternetGatewayIpPoolSelector { pool: NameOrId::Id(_), .. } => Err(Error::invalid_request( @@ -196,7 +200,7 @@ impl super::Nexus { &self, opctx: &OpContext, lookup: &lookup::InternetGateway<'_>, - params: ¶ms::InternetGatewayIpPoolCreate, + params: &internet_gateway::InternetGatewayIpPoolCreate, ) -> CreateResult { let (.., authz_igw, _) = lookup.fetch_for(authz::Action::CreateChild).await?; @@ -278,10 +282,10 @@ impl super::Nexus { pub fn internet_gateway_ip_address_lookup<'a>( &'a self, opctx: &'a OpContext, - address_allocator: params::InternetGatewayIpAddressSelector, + address_allocator: internet_gateway::InternetGatewayIpAddressSelector, ) -> LookupResult> { match address_allocator { - params::InternetGatewayIpAddressSelector { + internet_gateway::InternetGatewayIpAddressSelector { address: NameOrId::Id(id), gateway: None, vpc: None, @@ -291,25 +295,25 @@ impl super::Nexus { .internet_gateway_ip_address_id(id); Ok(route) } - params::InternetGatewayIpAddressSelector { + internet_gateway::InternetGatewayIpAddressSelector { address: NameOrId::Name(name), gateway: Some(gateway), - vpc, + vpc: igw_vpc, project, } => { let route = self .internet_gateway_lookup( opctx, - params::InternetGatewaySelector { + internet_gateway::InternetGatewaySelector { project, - vpc, + vpc: igw_vpc, gateway, }, )? .internet_gateway_ip_address_name_owned(name.into()); Ok(route) } - params::InternetGatewayIpAddressSelector { + internet_gateway::InternetGatewayIpAddressSelector { address: NameOrId::Id(_), .. } => Err(Error::invalid_request( @@ -326,7 +330,7 @@ impl super::Nexus { &self, opctx: &OpContext, lookup: &lookup::InternetGateway<'_>, - params: ¶ms::InternetGatewayIpAddressCreate, + params: &internet_gateway::InternetGatewayIpAddressCreate, ) -> CreateResult { let (.., authz_igw, _) = lookup.fetch_for(authz::Action::CreateChild).await?; diff --git a/nexus/src/app/ip_pool.rs b/nexus/src/app/ip_pool.rs index 210b05f77a1..75a4c52259b 100644 --- a/nexus/src/app/ip_pool.rs +++ b/nexus/src/app/ip_pool.rs @@ -4,8 +4,6 @@ //! IP Pools, collections of external IP addresses for guest instances -use crate::external_api::params; -use crate::external_api::shared; use ipnetwork::IpNetwork; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; @@ -19,6 +17,7 @@ use nexus_db_queries::authz::ApiResource; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; use nexus_db_queries::db::model::Name; +use nexus_types::external_api::ip_pool; use nexus_types::identity::Resource; use omicron_common::address::{ IPV4_LINK_LOCAL_MULTICAST_SUBNET, IPV4_SSM_SUBNET, @@ -74,11 +73,11 @@ fn not_found_from_lookup(pool_lookup: &lookup::IpPool<'_>) -> Error { /// /// Validates early so operators get immediate feedback rather than errors /// when allocating addresses later. -fn validate_multicast_range(range: &shared::IpRange) -> Result<(), Error> { +fn validate_multicast_range(range: &ip_pool::IpRange) -> Result<(), Error> { // These restrictions match the validation performed by Dendrite DPD // management (see dendrite/dpd/src/mcast/validate.rs). match range { - shared::IpRange::V4(v4_range) => { + ip_pool::IpRange::V4(v4_range) => { let first = v4_range.first_address(); let last = v4_range.last_address(); @@ -109,7 +108,7 @@ fn validate_multicast_range(range: &shared::IpRange) -> Result<(), Error> { )); } } - shared::IpRange::V6(v6_range) => { + ip_pool::IpRange::V6(v6_range) => { let first = v6_range.first_address(); let last = v6_range.last_address(); @@ -202,17 +201,17 @@ impl super::Nexus { pub(crate) async fn ip_pool_create( &self, opctx: &OpContext, - pool_params: ¶ms::IpPoolCreate, + pool_params: &ip_pool::IpPoolCreate, ) -> CreateResult { let ip_version = pool_params.ip_version.into(); - let pool = match pool_params.pool_type.clone() { - shared::IpPoolType::Unicast => IpPool::new( + let pool = match pool_params.pool_type { + ip_pool::IpPoolType::Unicast => IpPool::new( &pool_params.identity, ip_version, IpPoolReservationType::ExternalSilos, ), - shared::IpPoolType::Multicast => IpPool::new_multicast( + ip_pool::IpPoolType::Multicast => IpPool::new_multicast( &pool_params.identity, ip_version, IpPoolReservationType::ExternalSilos, @@ -310,7 +309,7 @@ impl super::Nexus { &self, opctx: &OpContext, pool_lookup: &lookup::IpPool<'_>, - silo_link: ¶ms::IpPoolLinkSilo, + silo_link: &ip_pool::IpPoolLinkSilo, ) -> CreateResult { let (authz_pool,) = pool_lookup.lookup_for(authz::Action::Modify).await?; @@ -362,7 +361,7 @@ impl super::Nexus { opctx: &OpContext, pool_lookup: &lookup::IpPool<'_>, silo_lookup: &lookup::Silo<'_>, - update: ¶ms::IpPoolSiloUpdate, + update: &ip_pool::IpPoolSiloUpdate, ) -> CreateResult { let (.., authz_pool) = pool_lookup.lookup_for(authz::Action::Modify).await?; @@ -411,7 +410,7 @@ impl super::Nexus { &self, opctx: &OpContext, pool_lookup: &lookup::IpPool<'_>, - updates: ¶ms::IpPoolUpdate, + updates: &ip_pool::IpPoolUpdate, ) -> UpdateResult { let (.., authz_pool) = pool_lookup.lookup_for(authz::Action::Modify).await?; @@ -447,7 +446,7 @@ impl super::Nexus { &self, opctx: &OpContext, pool_lookup: &lookup::IpPool<'_>, - range: &shared::IpRange, + range: &ip_pool::IpRange, ) -> UpdateResult { let (.., authz_pool, db_pool) = pool_lookup.fetch_for(authz::Action::Modify).await?; @@ -459,7 +458,7 @@ impl super::Nexus { // Validate uniformity and pool type constraints. // Extract first/last addresses once and reuse for all validation checks. match range { - shared::IpRange::V4(v4_range) => { + ip_pool::IpRange::V4(v4_range) => { let first = v4_range.first_address(); let last = v4_range.last_address(); let first_is_multicast = first.is_multicast(); @@ -491,7 +490,7 @@ impl super::Nexus { } } } - shared::IpRange::V6(v6_range) => { + ip_pool::IpRange::V6(v6_range) => { let first = v6_range.first_address(); let last = v6_range.last_address(); let first_is_multicast = first.is_multicast(); @@ -534,7 +533,7 @@ impl super::Nexus { &self, opctx: &OpContext, pool_lookup: &lookup::IpPool<'_>, - range: &shared::IpRange, + range: &ip_pool::IpRange, ) -> DeleteResult { let (.., authz_pool, _db_pool) = pool_lookup.fetch_for(authz::Action::Modify).await?; @@ -584,7 +583,7 @@ impl super::Nexus { pub(crate) async fn ip_pool_service_add_range( &self, opctx: &OpContext, - range: &shared::IpRange, + range: &ip_pool::IpRange, ) -> UpdateResult { let (authz_pool, db_pool) = self .db_datastore @@ -601,7 +600,7 @@ impl super::Nexus { // pool utilization. // // See https://github.com/oxidecomputer/omicron/issues/8761. - if matches!(range, shared::IpRange::V6(_)) { + if matches!(range, ip_pool::IpRange::V6(_)) { return Err(Error::invalid_request( "IPv6 ranges are not allowed yet", )); @@ -610,7 +609,7 @@ impl super::Nexus { // Validate uniformity and pool type constraints. // Extract first/last addresses once and reuse for all validation checks. match range { - shared::IpRange::V4(v4_range) => { + ip_pool::IpRange::V4(v4_range) => { let first = v4_range.first_address(); let last = v4_range.last_address(); let first_is_multicast = first.is_multicast(); @@ -642,7 +641,7 @@ impl super::Nexus { } } } - shared::IpRange::V6(v6_range) => { + ip_pool::IpRange::V6(v6_range) => { let first = v6_range.first_address(); let last = v6_range.last_address(); let first_is_multicast = first.is_multicast(); @@ -684,7 +683,7 @@ impl super::Nexus { pub(crate) async fn ip_pool_service_delete_range( &self, opctx: &OpContext, - range: &shared::IpRange, + range: &ip_pool::IpRange, ) -> DeleteResult { let (authz_pool, ..) = self .db_datastore @@ -698,7 +697,7 @@ impl super::Nexus { #[cfg(test)] mod tests { use super::*; - use crate::external_api::shared::IpRange; + use omicron_common::address::IpRange; use std::net::{Ipv4Addr, Ipv6Addr}; // IPv6 underlay validation tests diff --git a/nexus/src/app/login.rs b/nexus/src/app/login.rs index 712267cbfee..9415c8d4041 100644 --- a/nexus/src/app/login.rs +++ b/nexus/src/app/login.rs @@ -6,7 +6,7 @@ use dropshot::{HttpError, HttpResponseFound, http_response_found}; use nexus_auth::context::OpContext; use nexus_db_model::{ConsoleSession, Name}; use nexus_db_queries::authn::silos::IdentityProviderType; -use nexus_types::external_api::{params::RelativeUri, shared::RelayState}; +use nexus_types::external_api::saml::{RelativeUri, RelayState}; impl super::Nexus { pub(crate) async fn login_saml_redirect( diff --git a/nexus/src/app/metrics.rs b/nexus/src/app/metrics.rs index facffca3fb7..a137838083e 100644 --- a/nexus/src/app/metrics.rs +++ b/nexus/src/app/metrics.rs @@ -4,13 +4,12 @@ //! Metrics -use crate::external_api::params::ResourceMetrics; use dropshot::PaginationParams; use nexus_db_lookup::lookup; use nexus_db_queries::authz; use nexus_db_queries::{context::OpContext, db::fixed_data::FLEET_ID}; use nexus_external_api::TimeseriesSchemaPaginationParams; -use nexus_types::external_api::params::SystemMetricName; +use nexus_types::external_api::metrics::{ResourceMetrics, SystemMetricName}; use omicron_common::api::external::{Error, InternalContext}; use oximeter_db::{ Measurement, OxqlResult, TimeseriesSchema, oxql::query::QueryAuthzScope, diff --git a/nexus/src/app/multicast/mod.rs b/nexus/src/app/multicast/mod.rs index 61f806b5cfb..c5f6a066839 100644 --- a/nexus/src/app/multicast/mod.rs +++ b/nexus/src/app/multicast/mod.rs @@ -59,7 +59,7 @@ use nexus_db_model::Name; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::datastore::multicast::ExternalMulticastGroupWithSources; use nexus_db_queries::{authz, db}; -use nexus_types::external_api::{params, views}; +use nexus_types::external_api::multicast; use nexus_types::multicast::MulticastGroupCreate; use omicron_common::address::is_ssm_address; use omicron_common::api::external::{ @@ -119,23 +119,23 @@ impl super::Nexus { pub(crate) async fn multicast_group_lookup<'a>( &'a self, opctx: &'a OpContext, - multicast_group_selector: &'a params::MulticastGroupSelector, + multicast_group_selector: &'a multicast::MulticastGroupSelector, ) -> LookupResult> { // Multicast groups are fleet-scoped (like IP pools) match &multicast_group_selector.multicast_group { - params::MulticastGroupIdentifier::Id(id) => { + multicast::MulticastGroupIdentifier::Id(id) => { let multicast_group = LookupPath::new(opctx, &self.db_datastore) .multicast_group_id(*id); Ok(multicast_group) } - params::MulticastGroupIdentifier::Name(name) => { + multicast::MulticastGroupIdentifier::Name(name) => { let multicast_group = LookupPath::new(opctx, &self.db_datastore) .multicast_group_name(Name::ref_cast(name)); Ok(multicast_group) } - params::MulticastGroupIdentifier::Ip(ip) => { + multicast::MulticastGroupIdentifier::Ip(ip) => { // IP lookup requires fetching first to resolve the ID let group = self .db_datastore @@ -184,10 +184,10 @@ impl super::Nexus { pub(crate) async fn multicast_group_view( &self, opctx: &OpContext, - selector: ¶ms::MulticastGroupSelector, - ) -> Result { + selector: &multicast::MulticastGroupSelector, + ) -> Result { let group = match &selector.multicast_group { - params::MulticastGroupIdentifier::Ip(ip) => { + multicast::MulticastGroupIdentifier::Ip(ip) => { // IP lookup -> fetch once and authorize let group = self .db_datastore @@ -225,7 +225,7 @@ impl super::Nexus { &self, opctx: &OpContext, group: db::model::ExternalMulticastGroup, - ) -> Result { + ) -> Result { let group_id = MulticastGroupUuid::from_untyped_uuid(group.identity.id); let filter_state_map = self @@ -245,7 +245,7 @@ impl super::Nexus { &self, opctx: &OpContext, pagparams: &PaginatedBy<'_>, - ) -> Result, external::Error> { + ) -> Result, external::Error> { opctx .authorize( authz::Action::ListChildren, @@ -297,7 +297,7 @@ impl super::Nexus { pub(crate) async fn instance_join_multicast_group( self: &Arc, opctx: &OpContext, - group_identifier: ¶ms::MulticastGroupIdentifier, + group_identifier: &multicast::MulticastGroupIdentifier, instance_lookup: &lookup::Instance<'_>, source_ips: Option<&[IpAddr]>, ip_version: Option, @@ -337,11 +337,11 @@ impl super::Nexus { // Find or create the group based on identifier type. // SSM validation happens inside resolve functions. let group_id = match group_identifier { - params::MulticastGroupIdentifier::Ip(ip) => { + multicast::MulticastGroupIdentifier::Ip(ip) => { self.resolve_or_create_group_by_ip(opctx, *ip, source_ips) .await? } - params::MulticastGroupIdentifier::Name(name) => { + multicast::MulticastGroupIdentifier::Name(name) => { self.resolve_or_create_group_by_name( opctx, name.clone().into(), @@ -350,7 +350,7 @@ impl super::Nexus { ) .await? } - params::MulticastGroupIdentifier::Id(id) => { + multicast::MulticastGroupIdentifier::Id(id) => { self.resolve_group_by_id(opctx, *id, source_ips).await? } }; @@ -468,8 +468,8 @@ impl super::Nexus { source_ips: Option<&[IpAddr]>, ip_version: Option, ) -> Result { - let selector = params::MulticastGroupSelector { - multicast_group: params::MulticastGroupIdentifier::Name( + let selector = multicast::MulticastGroupSelector { + multicast_group: multicast::MulticastGroupIdentifier::Name( name.clone().into(), ), }; @@ -562,8 +562,8 @@ impl super::Nexus { id: uuid::Uuid, source_ips: Option<&[IpAddr]>, ) -> Result { - let selector = params::MulticastGroupSelector { - multicast_group: params::MulticastGroupIdentifier::Id(id), + let selector = multicast::MulticastGroupSelector { + multicast_group: multicast::MulticastGroupIdentifier::Id(id), }; let group_lookup = self.multicast_group_lookup(opctx, &selector).await?; @@ -591,9 +591,9 @@ impl super::Nexus { pub(crate) async fn resolve_multicast_group_identifier( &self, opctx: &OpContext, - identifier: ¶ms::MulticastGroupIdentifier, + identifier: &multicast::MulticastGroupIdentifier, ) -> Result { - let selector = params::MulticastGroupSelector { + let selector = multicast::MulticastGroupSelector { multicast_group: identifier.clone(), }; let group_lookup = @@ -635,15 +635,15 @@ impl super::Nexus { pub(crate) async fn resolve_multicast_group_identifier_with_sources( &self, opctx: &OpContext, - identifier: ¶ms::MulticastGroupIdentifier, + identifier: &multicast::MulticastGroupIdentifier, source_ips: Option<&[IpAddr]>, ip_version: Option, ) -> Result { match identifier { - params::MulticastGroupIdentifier::Ip(ip) => { + multicast::MulticastGroupIdentifier::Ip(ip) => { self.resolve_or_create_group_by_ip(opctx, *ip, source_ips).await } - params::MulticastGroupIdentifier::Name(name) => { + multicast::MulticastGroupIdentifier::Name(name) => { // Name-based: implicit auto-create if default pool exists. self.resolve_or_create_group_by_name( opctx, @@ -653,7 +653,7 @@ impl super::Nexus { ) .await } - params::MulticastGroupIdentifier::Id(id) => { + multicast::MulticastGroupIdentifier::Id(id) => { // ID-based: lookup only (UUID implies existing resource). self.resolve_group_by_id(opctx, *id, source_ips).await } diff --git a/nexus/src/app/network_interface.rs b/nexus/src/app/network_interface.rs index 4be22aa46be..95dded351b6 100644 --- a/nexus/src/app/network_interface.rs +++ b/nexus/src/app/network_interface.rs @@ -8,7 +8,7 @@ use nexus_db_lookup::lookup; use nexus_db_queries::authz::ApiResource; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::queries::network_interface; -use nexus_types::external_api::params; +use nexus_types::external_api::instance; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; @@ -30,10 +30,10 @@ impl super::Nexus { pub fn instance_network_interface_lookup<'a>( &'a self, opctx: &'a OpContext, - network_interface_selector: params::InstanceNetworkInterfaceSelector, + network_interface_selector: instance::InstanceNetworkInterfaceSelector, ) -> LookupResult> { match network_interface_selector { - params::InstanceNetworkInterfaceSelector { + instance::InstanceNetworkInterfaceSelector { network_interface: NameOrId::Id(id), instance: None, project: None, @@ -43,20 +43,20 @@ impl super::Nexus { .instance_network_interface_id(id); Ok(network_interface) } - params::InstanceNetworkInterfaceSelector { + instance::InstanceNetworkInterfaceSelector { network_interface: NameOrId::Name(name), - instance: Some(instance), + instance: Some(inst), project, } => { let network_interface = self .instance_lookup( opctx, - params::InstanceSelector { project, instance }, + instance::InstanceSelector { project, instance: inst }, )? .instance_network_interface_name_owned(name.into()); Ok(network_interface) } - params::InstanceNetworkInterfaceSelector { + instance::InstanceNetworkInterfaceSelector { network_interface: NameOrId::Id(_), .. } => Err(Error::invalid_request( @@ -76,7 +76,7 @@ impl super::Nexus { &self, opctx: &OpContext, instance_lookup: &lookup::Instance<'_>, - params: ¶ms::InstanceNetworkInterfaceCreate, + params: &instance::InstanceNetworkInterfaceCreate, ) -> CreateResult { let (.., authz_project, authz_instance) = instance_lookup.lookup_for(authz::Action::Modify).await?; @@ -150,7 +150,7 @@ impl super::Nexus { &self, opctx: &OpContext, network_interface_lookup: &lookup::InstanceNetworkInterface<'_>, - updates: params::InstanceNetworkInterfaceUpdate, + updates: instance::InstanceNetworkInterfaceUpdate, ) -> UpdateResult { let (.., authz_instance, authz_interface) = network_interface_lookup.lookup_for(authz::Action::Modify).await?; diff --git a/nexus/src/app/oximeter.rs b/nexus/src/app/oximeter.rs index 139a828fba7..2834fb0e5e3 100644 --- a/nexus/src/app/oximeter.rs +++ b/nexus/src/app/oximeter.rs @@ -4,12 +4,12 @@ //! Oximeter-related functionality -use crate::external_api::params::ResourceMetrics; use crate::internal_api::params::OximeterInfo; use dropshot::PaginationParams; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; use nexus_db_queries::db::DataStore; +use nexus_types::external_api::metrics::ResourceMetrics; use omicron_common::api::external::{DataPageParams, Error, ListResultVec}; use omicron_common::api::internal::nexus::{self, ProducerEndpoint}; use oximeter_client::Client as OximeterClient; diff --git a/nexus/src/app/probe.rs b/nexus/src/app/probe.rs index fa97dde7eb4..ab6247bacb7 100644 --- a/nexus/src/app/probe.rs +++ b/nexus/src/app/probe.rs @@ -6,8 +6,8 @@ use nexus_db_lookup::lookup; use nexus_db_model::Probe; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; -use nexus_types::external_api::params; -use nexus_types::external_api::shared::ProbeInfo; +use nexus_types::external_api::ip_pool; +use nexus_types::external_api::probe; use nexus_types::identity::Resource; use omicron_common::api::external::Error; use omicron_common::api::external::{ @@ -22,7 +22,7 @@ impl super::Nexus { opctx: &OpContext, project_lookup: &lookup::Project<'_>, pagparams: &PaginatedBy<'_>, - ) -> ListResultVec { + ) -> ListResultVec { let (.., authz_project) = project_lookup.lookup_for(authz::Action::ListChildren).await?; self.db_datastore.probe_list(opctx, &authz_project, pagparams).await @@ -34,7 +34,7 @@ impl super::Nexus { opctx: &OpContext, project_lookup: &lookup::Project<'_>, name_or_id: &NameOrId, - ) -> LookupResult { + ) -> LookupResult { let (.., authz_project) = project_lookup.lookup_for(authz::Action::CreateChild).await?; self.db_datastore.probe_get(opctx, &authz_project, &name_or_id).await @@ -48,17 +48,17 @@ impl super::Nexus { &self, opctx: &OpContext, project_lookup: &lookup::Project<'_>, - new_probe_params: ¶ms::ProbeCreate, + new_probe_params: &probe::ProbeCreate, ) -> CreateResult { let (.., authz_project) = project_lookup.lookup_for(authz::Action::CreateChild).await?; // Destructure pool_selector to get pool and ip_version let (pool, ip_version) = match &new_probe_params.pool_selector { - params::PoolSelector::Explicit { pool } => { + ip_pool::PoolSelector::Explicit { pool } => { (Some(pool.clone()), None) } - params::PoolSelector::Auto { ip_version } => (None, *ip_version), + ip_pool::PoolSelector::Auto { ip_version } => (None, *ip_version), }; // resolve NameOrId into authz::IpPool diff --git a/nexus/src/app/project.rs b/nexus/src/app/project.rs index 0f994a8c58e..a43928aa1dd 100644 --- a/nexus/src/app/project.rs +++ b/nexus/src/app/project.rs @@ -5,8 +5,6 @@ //! Project APIs use crate::app::sagas; -use crate::external_api::params; -use crate::external_api::shared; use anyhow::Context; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; @@ -14,6 +12,8 @@ use nexus_db_queries::authn; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; +use nexus_types::external_api::policy; +use nexus_types::external_api::project; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; @@ -29,14 +29,14 @@ impl super::Nexus { pub fn project_lookup<'a>( &'a self, opctx: &'a OpContext, - project_selector: params::ProjectSelector, + project_selector: project::ProjectSelector, ) -> LookupResult> { let lookup_path = LookupPath::new(opctx, &self.db_datastore); Ok(match project_selector { - params::ProjectSelector { project: NameOrId::Id(id) } => { + project::ProjectSelector { project: NameOrId::Id(id) } => { lookup_path.project_id(id) } - params::ProjectSelector { project: NameOrId::Name(name) } => { + project::ProjectSelector { project: NameOrId::Name(name) } => { lookup_path.project_name_owned(name.into()) } }) @@ -45,7 +45,7 @@ impl super::Nexus { pub(crate) async fn project_create( self: &Arc, opctx: &OpContext, - new_project: ¶ms::ProjectCreate, + new_project: &project::ProjectCreate, ) -> CreateResult { let authz_silo = opctx .authn @@ -85,7 +85,7 @@ impl super::Nexus { &self, opctx: &OpContext, project_lookup: &lookup::Project<'_>, - new_params: ¶ms::ProjectUpdate, + new_params: &project::ProjectUpdate, ) -> UpdateResult { let (.., authz_project) = project_lookup.lookup_for(authz::Action::Modify).await?; @@ -112,7 +112,7 @@ impl super::Nexus { &self, opctx: &OpContext, project_lookup: &lookup::Project<'_>, - ) -> LookupResult> { + ) -> LookupResult> { let (.., authz_project) = project_lookup.lookup_for(authz::Action::ReadPolicy).await?; let role_assignments = self @@ -123,15 +123,15 @@ impl super::Nexus { .map(|r| r.try_into().context("parsing database role assignment")) .collect::, _>>() .map_err(|error| Error::internal_error(&format!("{:#}", error)))?; - Ok(shared::Policy { role_assignments }) + Ok(policy::Policy { role_assignments }) } pub(crate) async fn project_update_policy( &self, opctx: &OpContext, project_lookup: &lookup::Project<'_>, - policy: &shared::Policy, - ) -> UpdateResult> { + new_policy: &policy::Policy, + ) -> UpdateResult> { let (.., authz_project) = project_lookup.lookup_for(authz::Action::ModifyPolicy).await?; @@ -140,12 +140,12 @@ impl super::Nexus { .role_assignment_replace_visible( opctx, &authz_project, - &policy.role_assignments, + &new_policy.role_assignments, ) .await? .into_iter() .map(|r| r.try_into()) .collect::, _>>()?; - Ok(shared::Policy { role_assignments }) + Ok(policy::Policy { role_assignments }) } } diff --git a/nexus/src/app/quota.rs b/nexus/src/app/quota.rs index ee131171ce8..b96f48a714c 100644 --- a/nexus/src/app/quota.rs +++ b/nexus/src/app/quota.rs @@ -8,7 +8,7 @@ use nexus_db_lookup::lookup; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; -use nexus_types::external_api::params; +use nexus_types::external_api::silo; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::Error; use omicron_common::api::external::ListResultVec; @@ -38,7 +38,7 @@ impl super::Nexus { &self, opctx: &OpContext, silo_lookup: &lookup::Silo<'_>, - updates: ¶ms::SiloQuotasUpdate, + updates: &silo::SiloQuotasUpdate, ) -> UpdateResult { let (.., authz_silo) = silo_lookup.lookup_for(authz::Action::Modify).await?; diff --git a/nexus/src/app/rack.rs b/nexus/src/app/rack.rs index 00f754e1fc8..379f94a2502 100644 --- a/nexus/src/app/rack.rs +++ b/nexus/src/app/rack.rs @@ -5,9 +5,6 @@ //! Rack management use crate::app::CONTROL_PLANE_STORAGE_BUFFER; -use crate::external_api::params; -use crate::external_api::params::CertificateCreate; -use crate::external_api::shared::ServiceUsingCertificate; use crate::internal_api::params::RackInitializationRequest; use internal_dns_types::names::DNS_ZONE; use ipnetwork::{IpNetwork, Ipv6Network}; @@ -23,27 +20,12 @@ use nexus_db_queries::db::datastore::RackInit; use nexus_db_queries::db::datastore::SledUnderlayAllocationResult; use nexus_types::deployment::CockroachDbClusterVersion; use nexus_types::deployment::SledFilter; -use nexus_types::external_api::params::Address; -use nexus_types::external_api::params::AddressConfig; -use nexus_types::external_api::params::AddressLotBlockCreate; -use nexus_types::external_api::params::BgpAnnounceSetCreate; -use nexus_types::external_api::params::BgpAnnouncementCreate; -use nexus_types::external_api::params::BgpConfigCreate; -use nexus_types::external_api::params::LinkConfigCreate; -use nexus_types::external_api::params::LldpLinkConfigCreate; -use nexus_types::external_api::params::RouteConfig; -use nexus_types::external_api::params::SwitchPortConfigCreate; -use nexus_types::external_api::params::UninitializedSledId; -use nexus_types::external_api::params::{ - AddressLotCreate, BgpPeerConfig, Route, SiloCreate, - SwitchPortSettingsCreate, -}; -use nexus_types::external_api::shared::Baseboard; -use nexus_types::external_api::shared::FleetRole; -use nexus_types::external_api::shared::SiloIdentityMode; -use nexus_types::external_api::shared::SiloRole; -use nexus_types::external_api::shared::UninitializedSled; -use nexus_types::external_api::views; +use nexus_types::external_api::certificate; +use nexus_types::external_api::hardware; +use nexus_types::external_api::networking; +use nexus_types::external_api::policy; +use nexus_types::external_api::silo; +use nexus_types::external_api::sled as sled_types; use nexus_types::inventory::SpType; use nexus_types::silo::silo_dns_name; use omicron_common::address::{Ipv6Subnet, RACK_PREFIX, get_64_subnet}; @@ -161,7 +143,7 @@ impl super::Nexus { // certificates start from one (e.g., certificate names // "default-1", "default-2", etc). let i = i + 1; - CertificateCreate { + certificate::CertificateCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(format!("default-{i}")).unwrap(), description: format!( @@ -170,7 +152,7 @@ impl super::Nexus { }, cert: c.cert, key: c.key, - service: ServiceUsingCertificate::ExternalApi, + service: certificate::ServiceUsingCertificate::ExternalApi, } }) .collect(); @@ -288,11 +270,11 @@ impl super::Nexus { // Administrators of the Recovery Silo are automatically made // administrators of the Fleet. let mapped_fleet_roles = BTreeMap::from([( - SiloRole::Admin, - BTreeSet::from([FleetRole::Admin]), + policy::SiloRole::Admin, + BTreeSet::from([policy::FleetRole::Admin]), )]); - let recovery_silo = SiloCreate { + let recovery_silo = silo::SiloCreate { identity: IdentityMetadataCreateParams { name: request.recovery_silo.silo_name, description: "built-in recovery Silo".to_string(), @@ -301,9 +283,9 @@ impl super::Nexus { // it's not intended to be used to deploy workloads. Operators can // add capacity after the fact if they want to use it for that // purpose. - quotas: params::SiloQuotasCreate::empty(), + quotas: silo::SiloQuotasCreate::empty(), discoverable: false, - identity_mode: SiloIdentityMode::LocalOnly, + identity_mode: silo::SiloIdentityMode::LocalOnly, admin_group_name: None, tls_certificates, mapped_fleet_roles, @@ -405,11 +387,13 @@ impl super::Nexus { let first_address = rack_network_config.infra_ip_first; let last_address = rack_network_config.infra_ip_last; - let ipv4_block = AddressLotBlockCreate { first_address, last_address }; + let ipv4_block = + networking::AddressLotBlockCreate { first_address, last_address }; let blocks = vec![ipv4_block]; - let address_lot_params = AddressLotCreate { identity, kind, blocks }; + let address_lot_params = + networking::AddressLotCreate { identity, kind, blocks }; match self .db_datastore @@ -443,7 +427,7 @@ impl super::Nexus { .db_datastore .address_lot_create( &opctx, - &AddressLotCreate { + &networking::AddressLotCreate { identity: IdentityMetadataCreateParams { name: address_lot_name, description: format!( @@ -475,7 +459,7 @@ impl super::Nexus { .db_datastore .bgp_create_announce_set( &opctx, - &BgpAnnounceSetCreate { + &networking::BgpAnnounceSetCreate { identity: IdentityMetadataCreateParams { name: announce_set_name.clone(), description: format!( @@ -486,7 +470,7 @@ impl super::Nexus { announcement: bgp_config .originate .iter() - .map(|ipnet| BgpAnnouncementCreate { + .map(|ipnet| networking::BgpAnnouncementCreate { address_lot_block: NameOrId::Name( format!("as{}", bgp_config.asn) .parse() @@ -513,7 +497,7 @@ impl super::Nexus { .db_datastore .bgp_config_create( &opctx, - &BgpConfigCreate { + &networking::BgpConfigCreate { identity: IdentityMetadataCreateParams { name: bgp_config_name, description: format!( @@ -559,25 +543,26 @@ impl super::Nexus { description: "initial uplink configuration".to_string(), }; - let port_config = SwitchPortConfigCreate { - geometry: nexus_types::external_api::params::SwitchPortGeometry::Qsfp28x1, - }; - - let mut port_settings_params = SwitchPortSettingsCreate { - identity, - port_config, - groups: vec![], - links: vec![], - interfaces: vec![], - routes: vec![], - bgp_peers: vec![], - addresses: vec![], + let port_config = networking::SwitchPortConfigCreate { + geometry: networking::SwitchPortGeometry::Qsfp28x1, }; - let addresses: Vec
= uplink_config + let mut port_settings_params = + networking::SwitchPortSettingsCreate { + identity, + port_config, + groups: vec![], + links: vec![], + interfaces: vec![], + routes: vec![], + bgp_peers: vec![], + addresses: vec![], + }; + + let addresses: Vec = uplink_config .addresses .iter() - .map(|a| Address { + .map(|a| networking::Address { address_lot: NameOrId::Name(address_lot_name.clone()), address: a.address.unwrap_or_else(|| { IpNet::V6(Ipv6Net::host_net(Ipv6Addr::UNSPECIFIED)) @@ -589,15 +574,15 @@ impl super::Nexus { let link_name = Name::from_str("phy0").expect("interface name should be valid"); - port_settings_params.addresses.push(AddressConfig { + port_settings_params.addresses.push(networking::AddressConfig { link_name: link_name.clone(), addresses, }); - let routes: Vec = uplink_config + let routes: Vec = uplink_config .routes .iter() - .map(|r| Route { + .map(|r| networking::Route { dst: r.destination, gw: r.nexthop, vid: r.vlan_id, @@ -605,9 +590,10 @@ impl super::Nexus { }) .collect(); - port_settings_params - .routes - .push(RouteConfig { link_name: link_name.clone(), routes }); + port_settings_params.routes.push(networking::RouteConfig { + link_name: link_name.clone(), + routes, + }); let peers: Vec = uplink_config .bgp_peers @@ -641,16 +627,17 @@ impl super::Nexus { }) .collect(); - port_settings_params - .bgp_peers - .push(BgpPeerConfig { link_name: link_name.clone(), peers }); + port_settings_params.bgp_peers.push(networking::BgpPeerConfig { + link_name: link_name.clone(), + peers, + }); let lldp = match &uplink_config.lldp { - None => LldpLinkConfigCreate { + None => networking::LldpLinkConfigCreate { enabled: false, ..Default::default() }, - Some(l) => LldpLinkConfigCreate { + Some(l) => networking::LldpLinkConfigCreate { enabled: l.status == LldpAdminStatus::Enabled, link_name: l.port_id.clone(), link_description: l.port_description.clone(), @@ -664,7 +651,7 @@ impl super::Nexus { }, }; - let link = LinkConfigCreate { + let link = networking::LinkConfigCreate { link_name: link_name.clone(), //TODO https://github.com/oxidecomputer/omicron/issues/2274 mtu: 1500, @@ -808,7 +795,7 @@ impl super::Nexus { pub(crate) async fn sled_list_uninitialized( &self, opctx: &OpContext, - ) -> ListResultVec { + ) -> ListResultVec { debug!(self.log, "Getting latest collection"); // Grab the SPs from the last collection let collection = @@ -833,29 +820,32 @@ impl super::Nexus { .sled_list(opctx, &pagparams, SledFilter::InService) .await?; - let mut uninitialized_sleds: Vec = collection - .sps + let mut uninitialized_sleds: Vec = + collection + .sps + .into_iter() + .filter_map(|(k, v)| { + if v.sp_type == SpType::Sled { + Some(hardware::UninitializedSled { + baseboard: hardware::Baseboard { + serial: k.serial_number.clone(), + part: k.part_number.clone(), + revision: v.baseboard_revision, + }, + rack_id: self.rack_id, + cubby: v.sp_slot, + }) + } else { + None + } + }) + .collect(); + + let sled_baseboards: BTreeSet = sleds .into_iter() - .filter_map(|(k, v)| { - if v.sp_type == SpType::Sled { - Some(UninitializedSled { - baseboard: Baseboard { - serial: k.serial_number.clone(), - part: k.part_number.clone(), - revision: v.baseboard_revision, - }, - rack_id: self.rack_id, - cubby: v.sp_slot, - }) - } else { - None - } - }) + .map(|s| sled_types::Sled::from(s).baseboard) .collect(); - let sled_baseboards: BTreeSet = - sleds.into_iter().map(|s| views::Sled::from(s).baseboard).collect(); - // Retain all sleds that exist but are not in the sled table uninitialized_sleds.retain(|s| !sled_baseboards.contains(&s.baseboard)); Ok(uninitialized_sleds) @@ -865,7 +855,7 @@ impl super::Nexus { pub(crate) async fn sled_add( &self, opctx: &OpContext, - sled: UninitializedSledId, + sled: hardware::UninitializedSledId, ) -> Result { let baseboard_id = sled.clone().into(); let hw_baseboard_id = self diff --git a/nexus/src/app/sagas/disk_create.rs b/nexus/src/app/sagas/disk_create.rs index ced3f475f18..b06641010d8 100644 --- a/nexus/src/app/sagas/disk_create.rs +++ b/nexus/src/app/sagas/disk_create.rs @@ -11,9 +11,9 @@ use super::{ }; use crate::app::sagas::declare_saga_actions; use crate::app::{authn, authz, db}; -use crate::external_api::params; use nexus_db_lookup::LookupPath; use nexus_db_queries::db::identity::{Asset, Resource}; +use nexus_types::external_api::disk; use omicron_common::api::external::DiskState; use omicron_common::api::external::Error; use omicron_uuid_kinds::VolumeUuid; @@ -34,7 +34,7 @@ use uuid::Uuid; pub(crate) struct Params { pub serialized_authn: authn::saga::Serialized, pub project_id: Uuid, - pub create_params: params::DiskCreate, + pub create_params: disk::DiskCreate, } // disk create saga: actions @@ -114,10 +114,10 @@ impl NexusSaga for SagaDiskCreate { builder.append(space_account_action()); match ¶ms.create_params.disk_backend { - params::DiskBackend::Distributed { + disk::DiskBackend::Distributed { disk_source: - params::DiskSource::Snapshot { read_only: true, .. } - | params::DiskSource::Image { read_only: true, .. }, + disk::DiskSource::Snapshot { read_only: true, .. } + | disk::DiskSource::Image { read_only: true, .. }, } => { builder.append(create_readonly_disk_records_action()); @@ -126,7 +126,7 @@ impl NexusSaga for SagaDiskCreate { return Ok(builder.build()?); } - params::DiskBackend::Distributed { .. } => { + disk::DiskBackend::Distributed { .. } => { builder.append(create_crucible_disk_record_action()); builder.append(regions_alloc_action()); builder.append(regions_ensure_undo_action()); @@ -134,7 +134,7 @@ impl NexusSaga for SagaDiskCreate { builder.append(create_volume_record_action()); } - params::DiskBackend::Local {} => { + disk::DiskBackend::Local {} => { builder.append(create_local_storage_disk_record_action()); } } @@ -142,26 +142,25 @@ impl NexusSaga for SagaDiskCreate { builder.append(finalize_disk_record_action()); match ¶ms.create_params.disk_backend { - params::DiskBackend::Distributed { disk_source } => { - match disk_source { - params::DiskSource::ImportingBlocks { .. } => { - builder.append(get_pantry_address_action()); - builder.append(call_pantry_attach_for_disk_action()); - } - - params::DiskSource::Snapshot { - read_only: true, .. - } => { - unreachable!( - "the previous match should have returned early if \ - the disk is readonly and created from a snapshot", - ) - } - _ => {} + disk::DiskBackend::Distributed { disk_source } => match disk_source + { + disk::DiskSource::ImportingBlocks { .. } => { + builder.append(get_pantry_address_action()); + builder.append(call_pantry_attach_for_disk_action()); } - } - params::DiskBackend::Local {} => { + disk::DiskSource::Snapshot { read_only: true, .. } + | disk::DiskSource::Image { read_only: true, .. } => { + unreachable!( + "the previous match should have returned early if \ + the disk is readonly and created from a snapshot or image", + ) + } + + _ => {} + }, + + disk::DiskBackend::Local {} => { // nothing to do! } } @@ -189,9 +188,9 @@ async fn sdc_create_crucible_disk_record( ); let disk_source = match ¶ms.create_params.disk_backend { - params::DiskBackend::Distributed { disk_source } => disk_source, + disk::DiskBackend::Distributed { disk_source } => disk_source, - params::DiskBackend::Local {} => { + disk::DiskBackend::Local {} => { // This should be unreachable given the match performed in // `make_saga_dag`! return Err(ActionError::action_failed(Error::internal_error( @@ -201,14 +200,14 @@ async fn sdc_create_crucible_disk_record( }; let block_size: db::model::BlockSize = match &disk_source { - params::DiskSource::Blank { block_size } => { + disk::DiskSource::Blank { block_size } => { db::model::BlockSize::try_from(*block_size).map_err(|e| { ActionError::action_failed(Error::internal_error( &e.to_string(), )) })? } - params::DiskSource::Snapshot { snapshot_id, read_only: false } => { + disk::DiskSource::Snapshot { snapshot_id, read_only: false } => { let (.., db_snapshot) = LookupPath::new(&opctx, osagactx.datastore()) .snapshot_id(*snapshot_id) @@ -222,7 +221,7 @@ async fn sdc_create_crucible_disk_record( db_snapshot.block_size } - params::DiskSource::Image { image_id, read_only: false } => { + disk::DiskSource::Image { image_id, read_only: false } => { let (.., image) = LookupPath::new(&opctx, osagactx.datastore()) .image_id(*image_id) .fetch() @@ -235,13 +234,13 @@ async fn sdc_create_crucible_disk_record( image.block_size } - params::DiskSource::Image { read_only: true, .. } - | params::DiskSource::Snapshot { read_only: true, .. } => { + disk::DiskSource::Image { read_only: true, .. } + | disk::DiskSource::Snapshot { read_only: true, .. } => { return Err(ActionError::action_failed(Error::internal_error( NOT_NEEDED_FOR_READONLY, ))); } - params::DiskSource::ImportingBlocks { block_size } => { + disk::DiskSource::ImportingBlocks { block_size } => { db::model::BlockSize::try_from(*block_size).map_err(|e| { ActionError::action_failed(Error::internal_error( &e.to_string(), @@ -322,7 +321,7 @@ async fn sdc_create_local_storage_disk_record( ); let block_size = match ¶ms.create_params.disk_backend { - params::DiskBackend::Distributed { .. } => { + disk::DiskBackend::Distributed { .. } => { // This should be unreachable given the match performed in // `make_saga_dag`! return Err(ActionError::action_failed(Error::internal_error( @@ -330,7 +329,7 @@ async fn sdc_create_local_storage_disk_record( ))); } - params::DiskBackend::Local {} => { + disk::DiskBackend::Local {} => { // All LocalStorage disks have a block size of 4k db::model::BlockSize::AdvancedFormat } @@ -399,9 +398,9 @@ async fn sdc_alloc_regions( let strategy = &osagactx.nexus().default_region_allocation_strategy; let disk_source = match ¶ms.create_params.disk_backend { - params::DiskBackend::Distributed { disk_source } => disk_source, + disk::DiskBackend::Distributed { disk_source } => disk_source, - params::DiskBackend::Local {} => { + disk::DiskBackend::Local {} => { // This should be unreachable given the match performed in // `make_saga_dag`! return Err(ActionError::action_failed(Error::internal_error( @@ -527,9 +526,9 @@ async fn sdc_regions_ensure( ); let disk_source = match ¶ms.create_params.disk_backend { - params::DiskBackend::Distributed { disk_source } => disk_source, + disk::DiskBackend::Distributed { disk_source } => disk_source, - params::DiskBackend::Local {} => { + disk::DiskBackend::Local {} => { // This should be unreachable given the match performed in // `make_saga_dag`! return Err(ActionError::action_failed(Error::internal_error( @@ -540,15 +539,15 @@ async fn sdc_regions_ensure( let mut read_only_parent: Option> = match disk_source { - params::DiskSource::Blank { block_size: _ } => None, - params::DiskSource::Snapshot { read_only: true, .. } - | params::DiskSource::Image { read_only: true, .. } => { + disk::DiskSource::Blank { block_size: _ } => None, + disk::DiskSource::Snapshot { read_only: true, .. } + | disk::DiskSource::Image { read_only: true, .. } => { return Err(ActionError::action_failed(Error::internal_error( NOT_NEEDED_FOR_READONLY, ))); } - params::DiskSource::Snapshot { snapshot_id, read_only: false } => { + disk::DiskSource::Snapshot { snapshot_id, read_only: false } => { debug!(log, "grabbing snapshot {}", snapshot_id); let (.., db_snapshot) = @@ -592,7 +591,7 @@ async fn sdc_regions_ensure( }, )?)) } - params::DiskSource::Image { image_id, read_only: false } => { + disk::DiskSource::Image { image_id, read_only: false } => { debug!(log, "grabbing image {}", image_id); let (.., image) = LookupPath::new(&opctx, osagactx.datastore()) @@ -637,7 +636,7 @@ async fn sdc_regions_ensure( }, )?)) } - params::DiskSource::ImportingBlocks { block_size: _ } => None, + disk::DiskSource::ImportingBlocks { block_size: _ } => None, }; // Each ID should be unique to this disk @@ -837,14 +836,14 @@ async fn sdc_finalize_disk_record( // It would be better if this were better guaranteed. match params.create_params.disk_backend { - params::DiskBackend::Distributed { disk_source } => { + disk::DiskBackend::Distributed { disk_source } => { let disk_created = db::datastore::Disk::Crucible( sagactx .lookup::("crucible_disk")?, ); match disk_source { - params::DiskSource::ImportingBlocks { .. } => { + disk::DiskSource::ImportingBlocks { .. } => { datastore .disk_update_runtime( &opctx, @@ -870,7 +869,7 @@ async fn sdc_finalize_disk_record( Ok(disk_created) } - params::DiskBackend::Local {} => { + disk::DiskBackend::Local {} => { let disk_created = db::datastore::Disk::LocalStorage( sagactx.lookup::( "local_storage_disk", @@ -982,7 +981,7 @@ async fn sdc_create_readonly_disk_records( let params = sagactx.saga_params::()?; let create_params = ¶ms.create_params; - let params::DiskBackend::Distributed { ref disk_source } = + let disk::DiskBackend::Distributed { ref disk_source } = create_params.disk_backend else { return Err(ActionError::action_failed(Error::internal_error( @@ -1073,8 +1072,7 @@ fn randomize_volume_construction_request_ids( pub(crate) mod test { use crate::{ app::saga::create_saga_dag, app::sagas::disk_create::Params, - app::sagas::disk_create::SagaDiskCreate, external_api::params, - external_api::views, + app::sagas::disk_create::SagaDiskCreate, }; use async_bb8_diesel::{AsyncRunQueryDsl, AsyncSimpleConnection}; use diesel::{ @@ -1087,6 +1085,8 @@ pub(crate) mod test { use nexus_test_utils::resource_helpers; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils_macros::nexus_test; + use nexus_types::external_api::disk; + use nexus_types::external_api::snapshot; use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::Name; @@ -1101,15 +1101,15 @@ pub(crate) mod test { const DISK_NAME: &str = "my-disk"; const PROJECT_NAME: &str = "springfield-squidport"; - pub fn new_disk_create_params() -> params::DiskCreate { - params::DiskCreate { + pub fn new_disk_create_params() -> disk::DiskCreate { + disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().expect("Invalid disk name"), description: "My disk".to_string(), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize(512), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize(512), }, }, size: ByteCount::from_gibibytes_u32(1), @@ -1119,15 +1119,15 @@ pub(crate) mod test { fn new_readonly_disk_create_params( opctx: &OpContext, project_id: Uuid, - snapshot: &views::Snapshot, + snapshot: &snapshot::Snapshot, ) -> Params { - let create_params = params::DiskCreate { + let create_params = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().expect("Invalid disk name"), description: "My disk".to_string(), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Snapshot { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Snapshot { read_only: true, snapshot_id: snapshot.identity.id, }, @@ -1581,7 +1581,7 @@ pub(crate) mod test { async fn destroy_disk(cptestctx: &ControlPlaneTestContext) { let nexus = &cptestctx.server.server_context().nexus; let opctx = test_opctx(&cptestctx); - let disk_selector = params::DiskSelector { + let disk_selector = disk::DiskSelector { project: Some( Name::try_from(PROJECT_NAME.to_string()).unwrap().into(), ), diff --git a/nexus/src/app/sagas/disk_delete.rs b/nexus/src/app/sagas/disk_delete.rs index 06c7fb2b098..39a7dd190f9 100644 --- a/nexus/src/app/sagas/disk_delete.rs +++ b/nexus/src/app/sagas/disk_delete.rs @@ -304,7 +304,7 @@ pub(crate) mod test { use nexus_test_utils::resource_helpers::DiskTest; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils_macros::nexus_test; - use nexus_types::external_api::params; + use nexus_types::external_api::project; use omicron_common::api::external::Name; type ControlPlaneTestContext = @@ -323,7 +323,7 @@ pub(crate) mod test { let nexus = &cptestctx.server.server_context().nexus; let opctx = test_opctx(&cptestctx); - let project_selector = params::ProjectSelector { + let project_selector = project::ProjectSelector { project: Name::try_from(PROJECT_NAME.to_string()).unwrap().into(), }; diff --git a/nexus/src/app/sagas/finalize_disk.rs b/nexus/src/app/sagas/finalize_disk.rs index 0a43a201669..6741445cafa 100644 --- a/nexus/src/app/sagas/finalize_disk.rs +++ b/nexus/src/app/sagas/finalize_disk.rs @@ -12,10 +12,10 @@ use super::SagaInitError; use super::declare_saga_actions; use crate::app::sagas::common_storage::call_pantry_detach; use crate::app::sagas::snapshot_create; -use crate::external_api::params; use nexus_db_lookup::LookupPath; use nexus_db_model::Generation; use nexus_db_queries::{authn, authz, db::datastore}; +use nexus_types::external_api::snapshot; use omicron_common::api::external; use omicron_common::api::external::Error; use omicron_common::api::external::Name; @@ -83,7 +83,7 @@ impl NexusSaga for SagaFinalizeDisk { disk: params.disk.clone(), attach_instance_id: None, use_the_pantry: true, - create_params: params::SnapshotCreate { + create_params: snapshot::SnapshotCreate { identity: external::IdentityMetadataCreateParams { name: snapshot_name.clone(), description: format!( diff --git a/nexus/src/app/sagas/image_create.rs b/nexus/src/app/sagas/image_create.rs index 60e09b3381c..592d98788d0 100644 --- a/nexus/src/app/sagas/image_create.rs +++ b/nexus/src/app/sagas/image_create.rs @@ -8,8 +8,8 @@ use super::{ }; use crate::app::sagas::declare_saga_actions; use crate::app::{authn, authz, db}; -use crate::external_api::params; use nexus_db_lookup::LookupPath; +use nexus_types::external_api::image; use omicron_common::api::external; use omicron_common::api::external::Error; use omicron_uuid_kinds::GenericUuid; @@ -52,7 +52,7 @@ impl ImageType { pub(crate) struct Params { pub serialized_authn: authn::saga::Serialized, pub image_type: ImageType, - pub create_params: params::ImageCreate, + pub create_params: image::ImageCreate, } // image create saga: actions @@ -98,9 +98,9 @@ impl NexusSaga for SagaImageCreate { )); match ¶ms.create_params.source { - params::ImageSource::Snapshot { .. } => {} + image::ImageSource::Snapshot { .. } => {} - params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine => { + image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine => { builder.append(Node::action( "alpine_volume_id", "GenerateAlpineVolumeId", @@ -137,7 +137,7 @@ async fn simc_get_source_volume( ); match ¶ms.create_params.source { - params::ImageSource::Snapshot { id } => { + image::ImageSource::Snapshot { id } => { let (.., db_snapshot) = LookupPath::new(&opctx, osagactx.datastore()) .snapshot_id(*id) @@ -181,7 +181,7 @@ async fn simc_get_source_volume( }) } - params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine => { + image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine => { let alpine_volume_id = sagactx.lookup::("alpine_volume_id")?; @@ -248,7 +248,7 @@ async fn simc_create_image_record( let source_volume = sagactx.lookup::("source_volume")?; let record = match ¶ms.create_params.source { - params::ImageSource::Snapshot { .. } => { + image::ImageSource::Snapshot { .. } => { db::model::Image { identity: db::model::ImageIdentity::new( image_id, @@ -266,7 +266,7 @@ async fn simc_create_image_record( } } - params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine => { + image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine => { db::model::Image { identity: db::model::ImageIdentity::new( image_id, @@ -355,7 +355,7 @@ async fn simc_create_image_record_undo( #[cfg(test)] pub(crate) mod test { use super::*; - use crate::{app::saga::create_saga_dag, external_api::params}; + use crate::app::saga::create_saga_dag; use async_bb8_diesel::AsyncRunQueryDsl; use diesel::{ ExpressionMethods, OptionalExtension, QueryDsl, SelectableHelper, @@ -395,13 +395,12 @@ pub(crate) mod test { Params { serialized_authn: Serialized::for_opctx(&opctx), image_type: ImageType::Silo { authz_silo }, - create_params: params::ImageCreate { + create_params: image::ImageCreate { identity: IdentityMetadataCreateParams { name: "image".parse().unwrap(), description: String::from("description"), }, - source: - params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + source: image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, os: "debian".to_string(), version: "12".to_string(), }, @@ -446,12 +445,12 @@ pub(crate) mod test { let params = Params { serialized_authn: Serialized::for_opctx(&opctx), image_type: ImageType::Project { authz_silo, authz_project }, - create_params: params::ImageCreate { + create_params: image::ImageCreate { identity: IdentityMetadataCreateParams { name: "image".parse().unwrap(), description: String::from("description"), }, - source: params::ImageSource::Snapshot { + source: image::ImageSource::Snapshot { id: snapshot.identity.id, }, os: "debian".to_string(), @@ -603,7 +602,7 @@ pub(crate) mod test { // Delete the (silo) image, and verify clean slate - let image_selector = params::ImageSelector { + let image_selector = image::ImageSelector { project: None, image: Name::try_from("image".to_string()).unwrap().into(), }; diff --git a/nexus/src/app/sagas/instance_create.rs b/nexus/src/app/sagas/instance_create.rs index 07d671c034f..38f3d18ac58 100644 --- a/nexus/src/app/sagas/instance_create.rs +++ b/nexus/src/app/sagas/instance_create.rs @@ -10,16 +10,17 @@ use super::subsaga_append; use crate::app::MAX_DISKS_PER_INSTANCE; use crate::app::sagas::declare_saga_actions; use crate::app::sagas::disk_create::{self, SagaDiskCreate}; -use crate::external_api::params; use nexus_db_lookup::LookupPath; use nexus_db_model::NetworkInterfaceKind; use nexus_db_model::{ExternalIp, IpVersion}; use nexus_db_queries::db::queries::network_interface::InsertError as InsertNicError; use nexus_db_queries::{authn, authz, db}; use nexus_defaults::DEFAULT_PRIMARY_NIC_NAME; -use nexus_types::external_api::params::{ - InstanceDiskAttachment, PrivateIpStackCreate, +use nexus_types::external_api::instance::{ + InstanceDiskAttachment, InstanceNetworkInterfaceAttachment, + PrivateIpStackCreate, }; +use nexus_types::external_api::{instance, ip_pool, multicast}; use nexus_types::identity::Resource; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::Name; @@ -47,7 +48,7 @@ use uuid::Uuid; pub(crate) struct Params { pub serialized_authn: authn::saga::Serialized, pub project_id: Uuid, - pub create_params: params::InstanceCreate, + pub create_params: instance::InstanceCreate, pub boundary_switches: HashSet, } @@ -76,7 +77,7 @@ enum DefaultNicKind { #[derive(Clone, Debug, Deserialize, Serialize)] enum InstanceNicSpec { Default(DefaultNicKind), - Custom(params::InstanceNetworkInterfaceCreate), + Custom(instance::InstanceNetworkInterfaceCreate), } #[derive(Debug, Deserialize, Serialize)] @@ -95,7 +96,7 @@ struct ExternalIpParams { instance_id: InstanceUuid, project_id: Uuid, new_eip_id: Uuid, - eip_spec: params::ExternalIpCreate, + eip_spec: instance::ExternalIpCreate, ip_index: usize, } @@ -103,7 +104,7 @@ struct ExternalIpParams { struct MulticastParams { serialized_authn: authn::saga::Serialized, instance_id: InstanceUuid, - join_spec: params::MulticastGroupJoinSpec, + join_spec: multicast::MulticastGroupJoinSpec, } // instance create saga: actions @@ -239,22 +240,20 @@ impl NexusSaga for SagaInstanceCreate { // above) handles much of the details. let mut nic_specs = vec![]; match ¶ms.create_params.network_interfaces { - params::InstanceNetworkInterfaceAttachment::DefaultIpv4 => { + InstanceNetworkInterfaceAttachment::DefaultIpv4 => { nic_specs.push(InstanceNicSpec::Default(DefaultNicKind::Ipv4)) } - params::InstanceNetworkInterfaceAttachment::DefaultIpv6 => { + InstanceNetworkInterfaceAttachment::DefaultIpv6 => { nic_specs.push(InstanceNicSpec::Default(DefaultNicKind::Ipv6)) } - params::InstanceNetworkInterfaceAttachment::DefaultDualStack => { - nic_specs - .push(InstanceNicSpec::Default(DefaultNicKind::DualStack)) - } - params::InstanceNetworkInterfaceAttachment::Create(creates) => { + InstanceNetworkInterfaceAttachment::DefaultDualStack => nic_specs + .push(InstanceNicSpec::Default(DefaultNicKind::DualStack)), + InstanceNetworkInterfaceAttachment::Create(creates) => { nic_specs.extend( creates.into_iter().cloned().map(InstanceNicSpec::Custom), ); } - params::InstanceNetworkInterfaceAttachment::None => {} + InstanceNetworkInterfaceAttachment::None => {} } for (i, nic_spec) in nic_specs.into_iter().enumerate() { @@ -302,7 +301,7 @@ impl NexusSaga for SagaInstanceCreate { // https://github.com/oxidecomputer/omicron/issues/9683, but as noted // above, fixing #4317 is the right long-term solution. match ¶ms.create_params.network_interfaces { - params::InstanceNetworkInterfaceAttachment::Create(nics) => { + InstanceNetworkInterfaceAttachment::Create(nics) => { if let Some(primary) = nics.first() { if primary.ip_config.has_ipv4_stack() { builder.append(Node::action( @@ -314,7 +313,7 @@ impl NexusSaga for SagaInstanceCreate { } } } - params::InstanceNetworkInterfaceAttachment::DefaultIpv4 => { + InstanceNetworkInterfaceAttachment::DefaultIpv4 => { builder.append(Node::action( "snat_ipv4_id", "CreateSnatIpv4Id", @@ -322,8 +321,8 @@ impl NexusSaga for SagaInstanceCreate { )); builder.append(create_snat_ipv4_action()); } - params::InstanceNetworkInterfaceAttachment::DefaultIpv6 => {} - params::InstanceNetworkInterfaceAttachment::DefaultDualStack => { + InstanceNetworkInterfaceAttachment::DefaultIpv6 => {} + InstanceNetworkInterfaceAttachment::DefaultDualStack => { builder.append(Node::action( "snat_ipv4_id", "CreateSnatIpv4Id", @@ -331,7 +330,7 @@ impl NexusSaga for SagaInstanceCreate { )); builder.append(create_snat_ipv4_action()); } - params::InstanceNetworkInterfaceAttachment::None => {} + InstanceNetworkInterfaceAttachment::None => {} } // See the comment above where we add nodes for creating NICs. We use @@ -637,7 +636,7 @@ async fn sic_create_network_interface_undo( async fn create_custom_network_interface( sagactx: &NexusActionContext, saga_params: &NicParams, - interface_params: ¶ms::InstanceNetworkInterfaceCreate, + interface_params: &instance::InstanceNetworkInterfaceCreate, ) -> Result<(), ActionError> { let NicParams { serialized_authn, instance_id, interface_id, .. } = saga_params; @@ -747,7 +746,7 @@ async fn create_default_primary_network_interface( let iface_name = Name::try_from(DEFAULT_PRIMARY_NIC_NAME.to_string()).unwrap(); - let interface_params = params::InstanceNetworkInterfaceCreate { + let interface_params = instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: iface_name.clone(), description: format!( @@ -905,12 +904,14 @@ async fn sic_allocate_instance_external_ip( // Runtime state should never be able to make 'complete_op' fallible. let ip = match eip_spec { // Allocate a new IP address from the target, possibly default, pool - params::ExternalIpCreate::Ephemeral { pool_selector } => { + instance::ExternalIpCreate::Ephemeral { pool_selector } => { let (pool, ip_version) = match pool_selector { - params::PoolSelector::Explicit { pool } => { + ip_pool::PoolSelector::Explicit { pool } => { (Some(pool.clone()), None) } - params::PoolSelector::Auto { ip_version } => (None, ip_version), + ip_pool::PoolSelector::Auto { ip_version } => { + (None, ip_version) + } }; let pool = if let Some(name_or_id) = pool { Some( @@ -941,7 +942,7 @@ async fn sic_allocate_instance_external_ip( .0 } // Set the parent of an existing floating IP to the new instance's ID. - params::ExternalIpCreate::Floating { floating_ip } => { + instance::ExternalIpCreate::Floating { floating_ip } => { let (.., authz_project, authz_fip, db_fip) = match &floating_ip { NameOrId::Name(name) => LookupPath::new(&opctx, datastore) .project_id(project_id) @@ -1027,10 +1028,10 @@ async fn sic_allocate_instance_external_ip_undo( }; match eip_spec { - params::ExternalIpCreate::Ephemeral { .. } => { + instance::ExternalIpCreate::Ephemeral { .. } => { datastore.deallocate_external_ip(&opctx, ip.id).await?; } - params::ExternalIpCreate::Floating { .. } => { + instance::ExternalIpCreate::Floating { .. } => { let (.., authz_fip) = LookupPath::new(&opctx, datastore) .floating_ip_id(ip.id) .lookup_for(authz::Action::Modify) @@ -1165,7 +1166,7 @@ async fn sic_join_instance_multicast_group_undo( let instance_id = sagactx.lookup::("instance_id")?; // Look up the multicast group by identifier using the existing nexus method - let multicast_group_selector = params::MulticastGroupSelector { + let multicast_group_selector = multicast::MulticastGroupSelector { multicast_group: join_spec.group.clone(), }; let multicast_group_lookup = osagactx @@ -1473,7 +1474,7 @@ pub mod test { use crate::{ app::saga::create_saga_dag, app::sagas::instance_create::Params, app::sagas::instance_create::SagaInstanceCreate, - app::sagas::test_helpers, external_api::params, + app::sagas::test_helpers, }; use async_bb8_diesel::AsyncRunQueryDsl; use diesel::{ @@ -1488,6 +1489,8 @@ pub mod test { use nexus_test_utils::resource_helpers::create_disk; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils_macros::nexus_test; + use nexus_types::external_api::instance as instance_types; + use nexus_types::external_api::ip_pool::PoolSelector; use omicron_common::address::IpVersion; use omicron_common::api::external::{ ByteCount, IdentityMetadataCreateParams, InstanceCpuCount, @@ -1516,7 +1519,7 @@ pub mod test { Params { serialized_authn: Serialized::for_opctx(opctx), project_id, - create_params: params::InstanceCreate { + create_params: instance_types::InstanceCreate { identity: IdentityMetadataCreateParams { name: INSTANCE_NAME.parse().unwrap(), description: "My instance".to_string(), @@ -1527,17 +1530,19 @@ pub mod test { user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultDualStack, - external_ips: vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Auto { + instance_types::InstanceNetworkInterfaceAttachment::DefaultDualStack, + external_ips: vec![instance_types::ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Auto { ip_version: Some(IpVersion::V4), }, }], - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { - name: DISK_NAME.parse().unwrap(), - }, - )), + boot_disk: Some( + instance_types::InstanceDiskAttachment::Attach( + instance_types::InstanceDiskAttach { + name: DISK_NAME.parse().unwrap(), + }, + ), + ), cpu_platform: None, disks: Vec::new(), start: false, diff --git a/nexus/src/app/sagas/instance_delete.rs b/nexus/src/app/sagas/instance_delete.rs index f9ad9f61b27..700cccd0353 100644 --- a/nexus/src/app/sagas/instance_delete.rs +++ b/nexus/src/app/sagas/instance_delete.rs @@ -229,7 +229,7 @@ mod test { app::saga::create_saga_dag, app::sagas::instance_create::test::verify_clean_slate, app::sagas::instance_delete::Params, - app::sagas::instance_delete::SagaInstanceDelete, external_api::params, + app::sagas::instance_delete::SagaInstanceDelete, }; use dropshot::test_util::ClientTestContext; use nexus_db_lookup::LookupPath; @@ -239,6 +239,8 @@ mod test { use nexus_test_utils::resource_helpers::create_disk; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils_macros::nexus_test; + use nexus_types::external_api::ip_pool::PoolSelector; + use nexus_types::external_api::{instance as instance_types, project}; use nexus_types::identity::Resource; use omicron_common::address::IpVersion; use omicron_common::api::external::{ @@ -283,8 +285,8 @@ mod test { } // Helper for creating instance create parameters - fn new_instance_create_params() -> params::InstanceCreate { - params::InstanceCreate { + fn new_instance_create_params() -> instance_types::InstanceCreate { + instance_types::InstanceCreate { identity: IdentityMetadataCreateParams { name: INSTANCE_NAME.parse().unwrap(), description: "My instance".to_string(), @@ -295,14 +297,16 @@ mod test { user_data: vec![], ssh_public_keys: Some(Vec::new()), network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultDualStack, - external_ips: vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Auto { + instance_types::InstanceNetworkInterfaceAttachment::DefaultDualStack, + external_ips: vec![instance_types::ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Auto { ip_version: Some(IpVersion::V4), }, }], - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { name: DISK_NAME.parse().unwrap() }, + boot_disk: Some(instance_types::InstanceDiskAttachment::Attach( + instance_types::InstanceDiskAttach { + name: DISK_NAME.parse().unwrap(), + }, )), cpu_platform: None, disks: Vec::new(), @@ -346,12 +350,12 @@ mod test { async fn create_instance( cptestctx: &ControlPlaneTestContext, - params: params::InstanceCreate, + params: instance_types::InstanceCreate, ) -> db::model::Instance { let nexus = &cptestctx.server.server_context().nexus; let opctx = test_opctx(&cptestctx); - let project_selector = params::ProjectSelector { + let project_selector = project::ProjectSelector { project: PROJECT_NAME.to_string().try_into().unwrap(), }; let project_lookup = diff --git a/nexus/src/app/sagas/instance_ip_attach.rs b/nexus/src/app/sagas/instance_ip_attach.rs index bd6b0c75070..dab399ca6c8 100644 --- a/nexus/src/app/sagas/instance_ip_attach.rs +++ b/nexus/src/app/sagas/instance_ip_attach.rs @@ -11,7 +11,7 @@ use super::{ActionRegistry, NexusActionContext, NexusSaga}; use crate::app::sagas::declare_saga_actions; use crate::app::{authn, authz}; use nexus_db_model::{IpAttachState, NatEntry}; -use nexus_types::external_api::views; +use nexus_types::external_api::external_ip; use omicron_common::api::external::Error; use omicron_uuid_kinds::{GenericUuid, InstanceUuid}; use serde::Deserialize; @@ -273,7 +273,7 @@ async fn siia_update_opte_undo( async fn siia_complete_attach( sagactx: NexusActionContext, -) -> Result { +) -> Result { let log = sagactx.user_data().log(); let params = sagactx.saga_params::()?; let target_ip = sagactx.lookup::("target_ip")?; @@ -344,7 +344,8 @@ pub(crate) mod test { create_project, }; use nexus_test_utils_macros::nexus_test; - use nexus_types::external_api::params; + use nexus_types::external_api::floating_ip; + use nexus_types::external_api::ip_pool; use omicron_common::api::external::SimpleIdentityOrName; use sled_agent_types::instance::InstanceExternalIpBody; @@ -362,8 +363,8 @@ pub(crate) mod test { client, FIP_NAME, &project.identity.id.to_string(), - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v4_pool.identity.name.clone().into(), }, }, diff --git a/nexus/src/app/sagas/instance_ip_detach.rs b/nexus/src/app/sagas/instance_ip_detach.rs index 95d30681c22..cef4af8e490 100644 --- a/nexus/src/app/sagas/instance_ip_detach.rs +++ b/nexus/src/app/sagas/instance_ip_detach.rs @@ -10,10 +10,10 @@ use super::instance_common::{ use super::{ActionRegistry, NexusActionContext, NexusSaga}; use crate::app::sagas::declare_saga_actions; use crate::app::{authn, authz, db}; -use crate::external_api::params; use nexus_db_lookup::LookupPath; use nexus_db_model::IpAttachState; -use nexus_types::external_api::views; +use nexus_types::external_api::external_ip; +use nexus_types::external_api::instance; use omicron_common::api::external::{Error, NameOrId}; use omicron_uuid_kinds::{GenericUuid, InstanceUuid}; use ref_cast::RefCast; @@ -53,7 +53,7 @@ declare_saga_actions! { #[derive(Debug, Deserialize, Serialize)] pub struct Params { - pub delete_params: params::ExternalIpDetach, + pub delete_params: instance::ExternalIpDetach, pub authz_instance: authz::Instance, pub project_id: Uuid, /// Authentication context to use to fetch the instance's current state from @@ -75,7 +75,7 @@ async fn siid_begin_detach_ip( let instance_id = InstanceUuid::from_untyped_uuid(params.authz_instance.id()); match ¶ms.delete_params { - params::ExternalIpDetach::Ephemeral { ip_version } => { + instance::ExternalIpDetach::Ephemeral { ip_version } => { let eph_ips = datastore .instance_lookup_ephemeral_ips(&opctx, instance_id) .await @@ -118,7 +118,7 @@ async fn siid_begin_detach_ip( }) } } - params::ExternalIpDetach::Floating { floating_ip } => { + instance::ExternalIpDetach::Floating { floating_ip } => { let (.., authz_fip) = match floating_ip { NameOrId::Name(name) => LookupPath::new(&opctx, datastore) .project_id(params.project_id) @@ -244,7 +244,7 @@ async fn siid_update_opte_undo( async fn siid_complete_detach( sagactx: NexusActionContext, -) -> Result, ActionError> { +) -> Result, ActionError> { let log = sagactx.user_data().log(); let params = sagactx.saga_params::()?; let target_ip = sagactx.lookup::("target_ip")?; @@ -331,11 +331,11 @@ pub(crate) mod test { use_floating: bool, ) -> Params { let delete_params = if use_floating { - params::ExternalIpDetach::Floating { + instance::ExternalIpDetach::Floating { floating_ip: FIP_NAME.parse::().unwrap().into(), } } else { - params::ExternalIpDetach::Ephemeral { ip_version: None } + instance::ExternalIpDetach::Ephemeral { ip_version: None } }; let (.., authz_project, authz_instance) = diff --git a/nexus/src/app/sagas/instance_migrate.rs b/nexus/src/app/sagas/instance_migrate.rs index 81d4af9a441..f7cdc8af319 100644 --- a/nexus/src/app/sagas/instance_migrate.rs +++ b/nexus/src/app/sagas/instance_migrate.rs @@ -619,12 +619,12 @@ async fn sim_instance_migrate( mod tests { use super::*; use crate::app::sagas::test_helpers; - use crate::external_api::params; use dropshot::test_util::ClientTestContext; use nexus_test_utils::resource_helpers::{ create_default_ip_pools, create_project, object_create, }; use nexus_test_utils_macros::nexus_test; + use nexus_types::external_api::instance as instance_types; use omicron_common::api::external::{ ByteCount, IdentityMetadataCreateParams, InstanceCpuCount, }; @@ -648,7 +648,7 @@ mod tests { object_create( client, &instances_url, - ¶ms::InstanceCreate { + &instance_types::InstanceCreate { identity: IdentityMetadataCreateParams { name: INSTANCE_NAME.parse().unwrap(), description: format!("instance {:?}", INSTANCE_NAME), @@ -659,7 +659,7 @@ mod tests { user_data: b"#cloud-config".to_vec(), ssh_public_keys: Some(Vec::new()), network_interfaces: - params::InstanceNetworkInterfaceAttachment::None, + instance_types::InstanceNetworkInterfaceAttachment::None, external_ips: vec![], disks: vec![], boot_disk: None, diff --git a/nexus/src/app/sagas/instance_start.rs b/nexus/src/app/sagas/instance_start.rs index b186ff52fe3..a13c511d655 100644 --- a/nexus/src/app/sagas/instance_start.rs +++ b/nexus/src/app/sagas/instance_start.rs @@ -1120,13 +1120,13 @@ mod test { use std::net::SocketAddrV6; use crate::app::{saga::create_saga_dag, sagas::test_helpers}; - use crate::external_api::params; use dropshot::test_util::ClientTestContext; use nexus_db_queries::authn; use nexus_test_utils::resource_helpers::{ create_default_ip_pools, create_project, object_create, }; use nexus_test_utils_macros::nexus_test; + use nexus_types::external_api::{instance as instance_types, networking}; use nexus_types::identity::Resource; use omicron_common::api::external::{ ByteCount, IdentityMetadataCreateParams, InstanceCpuCount, Name, @@ -1156,7 +1156,7 @@ mod test { object_create( client, &instances_url, - ¶ms::InstanceCreate { + &instance_types::InstanceCreate { identity: IdentityMetadataCreateParams { name: INSTANCE_NAME.parse().unwrap(), description: format!("instance {:?}", INSTANCE_NAME), @@ -1167,7 +1167,7 @@ mod test { user_data: b"#cloud-config".to_vec(), ssh_public_keys: Some(Vec::new()), network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance_types::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -1247,16 +1247,16 @@ mod test { // We only eagerly populate NAT entries on switches that have // uplinks configured, so we need to do some switch configuration // before starting the instance in order to test that section of logic - let mut uplink0_params = params::SwitchPortSettingsCreate::new( + let mut uplink0_params = networking::SwitchPortSettingsCreate::new( IdentityMetadataCreateParams { name: "test-uplink0".parse().unwrap(), description: "test uplink".into(), }, ); - uplink0_params.routes = vec![params::RouteConfig { + uplink0_params.routes = vec![networking::RouteConfig { link_name: "phy0".parse().unwrap(), - routes: vec![params::Route { + routes: vec![networking::Route { dst: "0.0.0.0/0".parse().unwrap(), gw: "1.1.1.1".parse().unwrap(), vid: None, @@ -1264,16 +1264,16 @@ mod test { }], }]; - let mut uplink1_params = params::SwitchPortSettingsCreate::new( + let mut uplink1_params = networking::SwitchPortSettingsCreate::new( IdentityMetadataCreateParams { name: "test-uplink1".parse().unwrap(), description: "test uplink".into(), }, ); - uplink1_params.routes = vec![params::RouteConfig { + uplink1_params.routes = vec![networking::RouteConfig { link_name: "phy0".parse().unwrap(), - routes: vec![params::Route { + routes: vec![networking::Route { dst: "0.0.0.0/0".parse().unwrap(), gw: "2.2.2.2".parse().unwrap(), vid: None, diff --git a/nexus/src/app/sagas/instance_update/mod.rs b/nexus/src/app/sagas/instance_update/mod.rs index ece906a09b8..152184c7ea8 100644 --- a/nexus/src/app/sagas/instance_update/mod.rs +++ b/nexus/src/app/sagas/instance_update/mod.rs @@ -1540,7 +1540,6 @@ mod test { use crate::app::db::model::VmmRuntimeState; use crate::app::saga::create_saga_dag; use crate::app::sagas::test_helpers; - use crate::external_api::params; use chrono::Utc; use dropshot::test_util::ClientTestContext; use nexus_db_lookup::LookupPath; @@ -1549,6 +1548,7 @@ mod test { create_default_ip_pools, create_project, object_create, }; use nexus_test_utils_macros::nexus_test; + use nexus_types::external_api::instance as instance_types; use nexus_types::internal_api::params::InstanceMigrateRequest; use omicron_common::api::internal::nexus::{ MigrationRuntimeState, MigrationState, Migrations, @@ -1601,7 +1601,7 @@ mod test { object_create( client, &instances_url, - ¶ms::InstanceCreate { + &instance_types::InstanceCreate { identity: IdentityMetadataCreateParams { name: INSTANCE_NAME.parse().unwrap(), description: format!("instance {:?}", INSTANCE_NAME), @@ -1612,7 +1612,7 @@ mod test { user_data: b"#cloud-config".to_vec(), ssh_public_keys: Some(Vec::new()), network_interfaces: - params::InstanceNetworkInterfaceAttachment::None, + instance_types::InstanceNetworkInterfaceAttachment::None, external_ips: vec![], disks: vec![], boot_disk: None, diff --git a/nexus/src/app/sagas/multicast_group_dpd_ensure.rs b/nexus/src/app/sagas/multicast_group_dpd_ensure.rs index 53cbee4deaf..98fb3c020ba 100644 --- a/nexus/src/app/sagas/multicast_group_dpd_ensure.rs +++ b/nexus/src/app/sagas/multicast_group_dpd_ensure.rs @@ -347,10 +347,11 @@ mod test { create_default_ip_pools, link_ip_pool, object_create, }; use nexus_test_utils_macros::nexus_test; - use nexus_types::external_api::params::IpPoolCreate; - use nexus_types::external_api::shared::{IpRange, Ipv4Range}; - use nexus_types::external_api::views::{IpPool, IpPoolRange, IpVersion}; + use nexus_types::external_api::ip_pool::{ + IpPool, IpPoolCreate, IpPoolRange, + }; use nexus_types::multicast::MulticastGroupCreate; + use omicron_common::address::{IpRange, IpVersion, Ipv4Range}; use omicron_common::api::external::IdentityMetadataCreateParams; use crate::app::saga::create_saga_dag; diff --git a/nexus/src/app/sagas/project_create.rs b/nexus/src/app/sagas/project_create.rs index 37434275c02..312c76910b3 100644 --- a/nexus/src/app/sagas/project_create.rs +++ b/nexus/src/app/sagas/project_create.rs @@ -7,9 +7,9 @@ use super::NexusActionContext; use super::NexusSaga; use crate::app::sagas; use crate::app::sagas::declare_saga_actions; -use crate::external_api::params; use nexus_db_queries::{authn, authz, db}; use nexus_defaults as defaults; +use nexus_types::external_api::{project, vpc}; use nexus_types::identity::Resource; use omicron_common::api::external::IdentityMetadataCreateParams; use serde::Deserialize; @@ -21,7 +21,7 @@ use steno::ActionError; #[derive(Debug, Deserialize, Serialize)] pub(crate) struct Params { pub serialized_authn: authn::saga::Serialized, - pub project_create: params::ProjectCreate, + pub project_create: project::ProjectCreate, pub authz_silo: authz::Silo, } @@ -132,7 +132,7 @@ async fn spc_create_vpc_params( .map_err(ActionError::action_failed)?, ); - let vpc_create = params::VpcCreate { + let vpc_create = vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: "default".parse().unwrap(), description: "Default VPC".to_string(), @@ -155,7 +155,7 @@ async fn spc_create_vpc_params( mod test { use crate::{ app::sagas::project_create::Params, - app::sagas::project_create::SagaProjectCreate, external_api::params, + app::sagas::project_create::SagaProjectCreate, }; use async_bb8_diesel::{AsyncRunQueryDsl, AsyncSimpleConnection}; use diesel::{ @@ -166,6 +166,7 @@ mod test { db::datastore::DataStore, }; use nexus_test_utils_macros::nexus_test; + use nexus_types::external_api::project; use omicron_common::api::external::IdentityMetadataCreateParams; type ControlPlaneTestContext = @@ -175,7 +176,7 @@ mod test { fn new_test_params(opctx: &OpContext, authz_silo: authz::Silo) -> Params { Params { serialized_authn: Serialized::for_opctx(opctx), - project_create: params::ProjectCreate { + project_create: project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "my-project".parse().unwrap(), description: "My Project".to_string(), diff --git a/nexus/src/app/sagas/region_snapshot_replacement_start.rs b/nexus/src/app/sagas/region_snapshot_replacement_start.rs index a834e37d4a2..5a45272ce39 100644 --- a/nexus/src/app/sagas/region_snapshot_replacement_start.rs +++ b/nexus/src/app/sagas/region_snapshot_replacement_start.rs @@ -1236,7 +1236,7 @@ pub(crate) mod test { use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils::resource_helpers::create_snapshot; use nexus_test_utils_macros::nexus_test; - use nexus_types::external_api::views; + use nexus_types::external_api::snapshot; use nexus_types::identity::Asset; use sled_agent_client::VolumeConstructionRequest; @@ -1303,7 +1303,7 @@ pub(crate) mod test { struct PrepareResult<'a> { db_disk: db::datastore::CrucibleDisk, - snapshot: views::Snapshot, + snapshot: snapshot::Snapshot, db_snapshot: nexus_db_model::Snapshot, disk_test: DiskTest<'a, crate::Server>, } diff --git a/nexus/src/app/sagas/snapshot_create.rs b/nexus/src/app/sagas/snapshot_create.rs index e779ccd2f25..089230d215f 100644 --- a/nexus/src/app/sagas/snapshot_create.rs +++ b/nexus/src/app/sagas/snapshot_create.rs @@ -98,11 +98,11 @@ use super::{ }; use crate::app::sagas::declare_saga_actions; use crate::app::{authn, authz, db}; -use crate::external_api::params; use anyhow::anyhow; use nexus_db_lookup::LookupPath; use nexus_db_model::Generation; use nexus_db_queries::db::identity::{Asset, Resource}; +use nexus_types::external_api::{disk, snapshot}; use omicron_common::api::external::Error; use omicron_common::progenitor_operation_retry::ProgenitorOperationRetryError; use omicron_common::{ @@ -137,7 +137,7 @@ pub(crate) struct Params { pub disk: db::datastore::CrucibleDisk, pub attach_instance_id: Option, pub use_the_pantry: bool, - pub create_params: params::SnapshotCreate, + pub create_params: snapshot::SnapshotCreate, } // snapshot create saga: actions @@ -457,8 +457,8 @@ async fn ssc_alloc_regions( .disk_region_allocate( &opctx, destination_volume_id, - ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from( + &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from( disk.block_size.to_bytes(), ) .map_err(|e| ActionError::action_failed(e.to_string()))?, @@ -1744,7 +1744,7 @@ mod test { use nexus_test_utils::resource_helpers::delete_disk; use nexus_test_utils::resource_helpers::object_create; use nexus_test_utils_macros::nexus_test; - use nexus_types::external_api::params::InstanceDiskAttachment; + use nexus_types::external_api::instance as instance_types; use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::Instance; @@ -1985,7 +1985,7 @@ mod test { disk: db_disk, attach_instance_id, use_the_pantry, - create_params: params::SnapshotCreate { + create_params: snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "my-snapshot".parse().expect("Invalid disk name"), description: "My snapshot".to_string(), @@ -2115,18 +2115,19 @@ mod test { async fn setup_test_instance( cptestctx: &ControlPlaneTestContext, client: &ClientTestContext, - disks_to_attach: Vec, + disks_to_attach: Vec, ) -> InstanceAndActiveVmm { let instances_url = format!("/v1/instances?project={}", PROJECT_NAME,); let mut disks_iter = disks_to_attach.into_iter(); let boot_disk = disks_iter.next(); - let data_disks: Vec = disks_iter.collect(); + let data_disks: Vec = + disks_iter.collect(); let instance: Instance = object_create( client, &instances_url, - ¶ms::InstanceCreate { + &instance_types::InstanceCreate { identity: IdentityMetadataCreateParams { name: INSTANCE_NAME.parse().unwrap(), description: format!("instance {:?}", INSTANCE_NAME), @@ -2139,7 +2140,7 @@ mod test { .to_vec(), ssh_public_keys: Some(Vec::new()), network_interfaces: - params::InstanceNetworkInterfaceAttachment::None, + instance_types::InstanceNetworkInterfaceAttachment::None, boot_disk, cpu_platform: None, disks: data_disks, @@ -2271,8 +2272,8 @@ mod test { let state = setup_test_instance( cptestctx, client, - vec![params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + vec![instance_types::InstanceDiskAttachment::Attach( + instance_types::InstanceDiskAttach { name: Name::from_str(DISK_NAME) .unwrap(), }, @@ -2422,8 +2423,8 @@ mod test { let _instance_and_vmm = setup_test_instance( &cptestctx, &client, - vec![params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + vec![instance_types::InstanceDiskAttachment::Attach( + instance_types::InstanceDiskAttach { name: Name::from_str(DISK_NAME).unwrap(), }, )], @@ -2573,8 +2574,8 @@ mod test { let instance_state = setup_test_instance( cptestctx, client, - vec![params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + vec![instance_types::InstanceDiskAttachment::Attach( + instance_types::InstanceDiskAttach { name: Name::from_str(DISK_NAME).unwrap(), }, )], diff --git a/nexus/src/app/sagas/subnet_attach.rs b/nexus/src/app/sagas/subnet_attach.rs index eeb2f01f5b1..0e0095700bc 100644 --- a/nexus/src/app/sagas/subnet_attach.rs +++ b/nexus/src/app/sagas/subnet_attach.rs @@ -22,7 +22,7 @@ use nexus_db_model::IpNet; use nexus_db_model::IpVersion; use nexus_db_queries::db::datastore::ExternalSubnetBeginOpResult; use nexus_db_queries::db::datastore::ExternalSubnetCompleteOpResult; -use nexus_types::external_api::views; +use nexus_types::external_api::external_subnet; use nexus_types::identity::Resource; use serde::Deserialize; use serde::Serialize; @@ -195,7 +195,7 @@ async fn ssa_update_opte_undo( async fn ssa_complete_attach( sagactx: NexusActionContext, -) -> Result { +) -> Result { let osagactx = sagactx.user_data(); let log = osagactx.log(); let datastore = osagactx.datastore(); @@ -269,11 +269,11 @@ pub(crate) mod test { use nexus_test_utils::resource_helpers::create_subnet_pool; use nexus_test_utils::resource_helpers::create_subnet_pool_member; use nexus_test_utils_macros::nexus_test; - use nexus_types::external_api::params; - use nexus_types::external_api::views::ExternalSubnet; - use nexus_types::external_api::views::Project; - use nexus_types::external_api::views::SubnetPool; - use nexus_types::external_api::views::SubnetPoolMember; + use nexus_types::external_api::external_subnet; + use nexus_types::external_api::external_subnet::ExternalSubnet; + use nexus_types::external_api::project::Project; + use nexus_types::external_api::subnet_pool::SubnetPool; + use nexus_types::external_api::subnet_pool::SubnetPoolMember; use omicron_common::address::IpVersion; use omicron_common::api::external::NameOrId; use omicron_common::api::external::SimpleIdentityOrName; @@ -590,7 +590,7 @@ pub(crate) mod test { let (.., db_subnet) = nexus .external_subnet_lookup( &opctx, - params::ExternalSubnetSelector { + external_subnet::ExternalSubnetSelector { project: None, external_subnet: NameOrId::Id(subnet_id), }, diff --git a/nexus/src/app/sagas/subnet_detach.rs b/nexus/src/app/sagas/subnet_detach.rs index e20b696a219..97df1906ebc 100644 --- a/nexus/src/app/sagas/subnet_detach.rs +++ b/nexus/src/app/sagas/subnet_detach.rs @@ -18,7 +18,7 @@ use anyhow::Context as _; use nexus_db_model::IpAttachState; use nexus_db_queries::db::datastore::ExternalSubnetBeginOpResult; use nexus_db_queries::db::datastore::ExternalSubnetCompleteOpResult; -use nexus_types::external_api::views; +use nexus_types::external_api::external_subnet; use serde::Deserialize; use serde::Serialize; use steno::ActionError; @@ -208,7 +208,7 @@ async fn ssd_notify_opte_undo( async fn ssd_complete_detach( sagactx: NexusActionContext, -) -> Result { +) -> Result { let log = sagactx.user_data().log(); let datastore = sagactx.user_data().datastore(); let params = sagactx.saga_params::()?; @@ -278,10 +278,10 @@ pub(crate) mod test { use nexus_test_utils::resource_helpers::create_subnet_pool; use nexus_test_utils::resource_helpers::create_subnet_pool_member; use nexus_test_utils_macros::nexus_test; - use nexus_types::external_api::views::ExternalSubnet; - use nexus_types::external_api::views::Project; - use nexus_types::external_api::views::SubnetPool; - use nexus_types::external_api::views::SubnetPoolMember; + use nexus_types::external_api::external_subnet::ExternalSubnet; + use nexus_types::external_api::project::Project; + use nexus_types::external_api::subnet_pool::SubnetPool; + use nexus_types::external_api::subnet_pool::SubnetPoolMember; use omicron_common::address::IpVersion; use omicron_common::api::external::LookupType; use omicron_common::api::external::SimpleIdentityOrName; diff --git a/nexus/src/app/sagas/test_helpers.rs b/nexus/src/app/sagas/test_helpers.rs index 89d350bba3f..29bcb40642d 100644 --- a/nexus/src/app/sagas/test_helpers.rs +++ b/nexus/src/app/sagas/test_helpers.rs @@ -49,7 +49,7 @@ pub(crate) async fn instance_start( let nexus = &cptestctx.server.server_context().nexus; let opctx = test_opctx(&cptestctx); let instance_selector = - nexus_types::external_api::params::InstanceSelector { + nexus_types::external_api::instance::InstanceSelector { project: None, instance: NameOrId::from(id.into_untyped_uuid()), }; @@ -69,7 +69,7 @@ pub(crate) async fn instance_stop( let nexus = &cptestctx.server.server_context().nexus; let opctx = test_opctx(&cptestctx); let instance_selector = - nexus_types::external_api::params::InstanceSelector { + nexus_types::external_api::instance::InstanceSelector { project: None, instance: NameOrId::from(id.into_untyped_uuid()), }; @@ -90,7 +90,7 @@ pub(crate) async fn instance_stop_by_name( let nexus = &cptestctx.server.server_context().nexus; let opctx = test_opctx(&cptestctx); let instance_selector = - nexus_types::external_api::params::InstanceSelector { + nexus_types::external_api::instance::InstanceSelector { project: Some(project_name.to_string().try_into().unwrap()), instance: name.to_string().try_into().unwrap(), }; @@ -111,7 +111,7 @@ pub(crate) async fn instance_delete_by_name( let nexus = &cptestctx.server.server_context().nexus; let opctx = test_opctx(&cptestctx); let instance_selector = - nexus_types::external_api::params::InstanceSelector { + nexus_types::external_api::instance::InstanceSelector { project: Some(project_name.to_string().try_into().unwrap()), instance: name.to_string().try_into().unwrap(), }; @@ -175,7 +175,7 @@ pub(crate) async fn instance_simulate_by_name( let nexus = &cptestctx.server.server_context().nexus; let opctx = test_opctx(&cptestctx); let instance_selector = - nexus_types::external_api::params::InstanceSelector { + nexus_types::external_api::instance::InstanceSelector { project: Some(project_name.to_string().try_into().unwrap()), instance: name.to_string().try_into().unwrap(), }; @@ -268,7 +268,7 @@ pub async fn instance_fetch_by_name( let datastore = nexus.datastore(); let opctx = test_opctx(&cptestctx); let instance_selector = - nexus_types::external_api::params::InstanceSelector { + nexus_types::external_api::instance::InstanceSelector { project: Some(project_name.to_string().try_into().unwrap()), instance: name.to_string().try_into().unwrap(), }; @@ -316,7 +316,7 @@ pub async fn instance_wait_for_state_by_name( let nexus = &cptestctx.server.server_context().nexus; let opctx = test_opctx(&cptestctx); let instance_selector = - nexus_types::external_api::params::InstanceSelector { + nexus_types::external_api::instance::InstanceSelector { project: Some(project_name.to_string().try_into().unwrap()), instance: name.to_string().try_into().unwrap(), }; diff --git a/nexus/src/app/sagas/vpc_create.rs b/nexus/src/app/sagas/vpc_create.rs index dacb81a90e4..3049a9313e6 100644 --- a/nexus/src/app/sagas/vpc_create.rs +++ b/nexus/src/app/sagas/vpc_create.rs @@ -7,11 +7,11 @@ use super::ActionRegistry; use super::NexusActionContext; use super::NexusSaga; use crate::app::sagas::declare_saga_actions; -use crate::external_api::params; use nexus_db_model::InternetGatewayIpPool; use nexus_db_queries::db::queries::vpc_subnet::InsertVpcSubnetError; use nexus_db_queries::{authn, authz, db}; use nexus_defaults as defaults; +use nexus_types::external_api::{internet_gateway, vpc}; use nexus_types::identity::Resource; use omicron_common::api::external; use omicron_common::api::external::IdentityMetadataCreateParams; @@ -28,7 +28,7 @@ use uuid::Uuid; #[derive(Debug, Deserialize, Serialize)] pub(crate) struct Params { pub serialized_authn: authn::saga::Serialized, - pub vpc_create: params::VpcCreate, + pub vpc_create: vpc::VpcCreate, pub authz_project: authz::Project, } @@ -215,7 +215,7 @@ async fn svc_create_router( system_router_id, vpc_id, db::model::VpcRouterKind::System, - params::VpcRouterCreate { + vpc::VpcRouterCreate { identity: IdentityMetadataCreateParams { name: "system".parse().unwrap(), description: "Routes are automatically added to this \ @@ -300,7 +300,7 @@ async fn svc_create_route( route_id, system_router_id, external::RouterRouteKind::Default, - params::RouterRouteCreate { + vpc::RouterRouteCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: "The default route of a vpc".to_string(), @@ -548,7 +548,7 @@ async fn svc_create_gateway( let igw = db::model::InternetGateway::new( default_igw_id, vpc_id, - params::InternetGatewayCreate { + internet_gateway::InternetGatewayCreate { identity: IdentityMetadataCreateParams { name: "default".parse().unwrap(), description: "Automatically created default VPC gateway".into(), @@ -649,7 +649,6 @@ async fn svc_notify_sleds( pub(crate) mod test { use crate::{ app::sagas::vpc_create::Params, app::sagas::vpc_create::SagaVpcCreate, - external_api::params, }; use async_bb8_diesel::AsyncRunQueryDsl; use diesel::{ @@ -665,6 +664,7 @@ pub(crate) mod test { use nexus_test_utils::resource_helpers::create_default_ip_pools; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils_macros::nexus_test; + use nexus_types::external_api::{project, vpc as vpc_types}; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::Name; use omicron_common::api::external::NameOrId; @@ -688,7 +688,7 @@ pub(crate) mod test { ) -> Params { Params { serialized_authn: Serialized::for_opctx(opctx), - vpc_create: params::VpcCreate { + vpc_create: vpc_types::VpcCreate { identity: IdentityMetadataCreateParams { name: "my-vpc".parse().unwrap(), description: "My VPC".to_string(), @@ -714,7 +714,7 @@ pub(crate) mod test { ) -> authz::Project { let nexus = &cptestctx.server.server_context().nexus; let project_selector = - params::ProjectSelector { project: NameOrId::Id(project_id) }; + project::ProjectSelector { project: NameOrId::Id(project_id) }; let opctx = test_opctx(&cptestctx); let (.., authz_project) = nexus .project_lookup(&opctx, project_selector) diff --git a/nexus/src/app/sagas/vpc_subnet_create.rs b/nexus/src/app/sagas/vpc_subnet_create.rs index 03723cec928..84c42de685e 100644 --- a/nexus/src/app/sagas/vpc_subnet_create.rs +++ b/nexus/src/app/sagas/vpc_subnet_create.rs @@ -7,10 +7,10 @@ use super::ActionRegistry; use super::NexusActionContext; use super::NexusSaga; use crate::app::sagas::declare_saga_actions; -use crate::external_api::params; use nexus_db_lookup::LookupPath; use nexus_db_queries::db::queries::vpc_subnet::InsertVpcSubnetError; use nexus_db_queries::{authn, authz, db}; +use nexus_types::external_api::vpc; use omicron_common::api::external; use oxnet::IpNet; use oxnet::Ipv6Net; @@ -25,7 +25,7 @@ use uuid::Uuid; #[derive(Debug, Deserialize, Serialize)] pub(crate) struct Params { pub serialized_authn: authn::saga::Serialized, - pub subnet_create: params::VpcSubnetCreate, + pub subnet_create: vpc::VpcSubnetCreate, /// We create at most one IPv6 block in the subnet, but have a retry loop /// in case of collisions when randomly generating a block. Our random /// choices are fixed ahead of saga start for idempotency. @@ -345,7 +345,6 @@ pub(crate) mod test { use crate::{ app::sagas::vpc_subnet_create::Params, app::sagas::vpc_subnet_create::SagaVpcSubnetCreate, - external_api::params, }; use async_bb8_diesel::AsyncRunQueryDsl; use diesel::{ExpressionMethods, QueryDsl, SelectableHelper}; @@ -360,7 +359,8 @@ pub(crate) mod test { use nexus_test_utils::resource_helpers::create_default_ip_pools; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils_macros::nexus_test; - use nexus_types::external_api::params::VpcSelector; + use nexus_types::external_api::vpc as vpc_types; + use nexus_types::external_api::vpc::VpcSelector; use omicron_common::api::external::NameOrId; use omicron_common::api::external::{ self, IdentityMetadataCreateParams, Ipv6NetExt, @@ -393,7 +393,7 @@ pub(crate) mod test { Params { serialized_authn: Serialized::for_opctx(opctx), - subnet_create: params::VpcSubnetCreate { + subnet_create: vpc_types::VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: "my-subnet".parse().unwrap(), description: "My New Subnet.".to_string(), diff --git a/nexus/src/app/sagas/vpc_subnet_delete.rs b/nexus/src/app/sagas/vpc_subnet_delete.rs index 3645a5139a8..4c6d34c50b0 100644 --- a/nexus/src/app/sagas/vpc_subnet_delete.rs +++ b/nexus/src/app/sagas/vpc_subnet_delete.rs @@ -125,13 +125,13 @@ pub(crate) mod test { use crate::{ app::sagas::vpc_subnet_delete::Params, app::sagas::vpc_subnet_delete::SagaVpcSubnetDelete, - external_api::params, }; use dropshot::test_util::ClientTestContext; use nexus_db_queries::{authn::saga::Serialized, context::OpContext}; use nexus_test_utils::resource_helpers::create_default_ip_pools; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils_macros::nexus_test; + use nexus_types::external_api::vpc; use omicron_common::api::external::NameOrId; use uuid::Uuid; @@ -162,7 +162,7 @@ pub(crate) mod test { let (.., authz_vpc, authz_subnet, db_subnet) = nexus .vpc_subnet_lookup( &opctx, - params::SubnetSelector { + vpc::SubnetSelector { project: Some(project_id.into()), vpc: Some(NameOrId::Name("default".parse().unwrap())), subnet: NameOrId::Name("default".parse().unwrap()), diff --git a/nexus/src/app/sagas/vpc_subnet_update.rs b/nexus/src/app/sagas/vpc_subnet_update.rs index d87a2b325c4..8436c9f1123 100644 --- a/nexus/src/app/sagas/vpc_subnet_update.rs +++ b/nexus/src/app/sagas/vpc_subnet_update.rs @@ -106,7 +106,6 @@ pub(crate) mod test { use crate::{ app::sagas::vpc_subnet_update::Params, app::sagas::vpc_subnet_update::SagaVpcSubnetUpdate, - external_api::params, }; use chrono::Utc; use dropshot::test_util::ClientTestContext; @@ -116,6 +115,7 @@ pub(crate) mod test { use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils::resource_helpers::create_router; use nexus_test_utils_macros::nexus_test; + use nexus_types::external_api::vpc; use omicron_common::api::external::NameOrId; use uuid::Uuid; @@ -152,7 +152,7 @@ pub(crate) mod test { let (.., authz_vpc, authz_subnet, _) = nexus .vpc_subnet_lookup( &opctx, - params::SubnetSelector { + vpc::SubnetSelector { project: Some(project_id.into()), vpc: Some(NameOrId::Name("default".parse().unwrap())), subnet: NameOrId::Name("default".parse().unwrap()), @@ -166,7 +166,7 @@ pub(crate) mod test { let (.., custom_router, _) = nexus .vpc_router_lookup( &opctx, - params::RouterSelector { + vpc::RouterSelector { project: None, vpc: None, router: NameOrId::Id(router_id), diff --git a/nexus/src/app/scim.rs b/nexus/src/app/scim.rs index 92c2aafa947..58dd1fce453 100644 --- a/nexus/src/app/scim.rs +++ b/nexus/src/app/scim.rs @@ -17,7 +17,7 @@ use nexus_db_lookup::lookup; use nexus_db_queries::authn::{Actor, Reason}; use nexus_db_queries::context::OpContext; use nexus_db_queries::db::datastore::CrdbScimProviderStore; -use nexus_types::external_api::views; +use nexus_types::external_api::scim; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; @@ -33,7 +33,7 @@ impl super::Nexus { &self, opctx: &OpContext, silo_lookup: &lookup::Silo<'_>, - ) -> ListResultVec { + ) -> ListResultVec { let (.., authz_silo, _) = silo_lookup.fetch().await?; let tokens = @@ -46,7 +46,7 @@ impl super::Nexus { &self, opctx: &OpContext, silo_lookup: &lookup::Silo<'_>, - ) -> CreateResult { + ) -> CreateResult { let (.., authz_silo, _) = silo_lookup.fetch().await?; let token = @@ -60,7 +60,7 @@ impl super::Nexus { opctx: &OpContext, silo_lookup: &lookup::Silo<'_>, token_id: Uuid, - ) -> LookupResult { + ) -> LookupResult { let (.., authz_silo, _) = silo_lookup.fetch().await?; let token = self diff --git a/nexus/src/app/silo.rs b/nexus/src/app/silo.rs index 1a9dbccdfde..5c5d5f1a6ca 100644 --- a/nexus/src/app/silo.rs +++ b/nexus/src/app/silo.rs @@ -4,8 +4,6 @@ //! Silos, Users, and SSH Keys. -use crate::external_api::params; -use crate::external_api::shared; use anyhow::Context; use chrono::TimeDelta; use nexus_db_lookup::LookupPath; @@ -27,6 +25,10 @@ use nexus_db_queries::db::datastore::SiloUserLookup; use nexus_db_queries::db::identity::Resource; use nexus_db_queries::{authn, authz}; use nexus_types::deployment::execution::blueprint_nexus_external_ips; +use nexus_types::external_api::identity_provider; +use nexus_types::external_api::policy; +use nexus_types::external_api::silo as silo_types; +use nexus_types::external_api::user; use nexus_types::internal_api::params::DnsRecord; use nexus_types::silo::silo_dns_name; use omicron_common::api::external::ListResultVec; @@ -98,7 +100,7 @@ impl super::Nexus { pub(crate) async fn silo_create( &self, opctx: &OpContext, - new_silo_params: params::SiloCreate, + new_silo_params: silo_types::SiloCreate, ) -> CreateResult { // Silo creation involves several operations that ordinary users cannot // generally do, like reading and modifying the fleet-wide external DNS @@ -203,7 +205,7 @@ impl super::Nexus { &self, opctx: &OpContext, silo_lookup: &lookup::Silo<'_>, - ) -> LookupResult> { + ) -> LookupResult> { let (.., authz_silo) = silo_lookup.lookup_for(authz::Action::ReadPolicy).await?; let role_assignments = self @@ -214,15 +216,15 @@ impl super::Nexus { .map(|r| r.try_into().context("parsing database role assignment")) .collect::, _>>() .map_err(|error| Error::internal_error(&format!("{:#}", error)))?; - Ok(shared::Policy { role_assignments }) + Ok(policy::Policy { role_assignments }) } pub(crate) async fn silo_update_policy( &self, opctx: &OpContext, silo_lookup: &lookup::Silo<'_>, - policy: &shared::Policy, - ) -> UpdateResult> { + new_policy: &policy::Policy, + ) -> UpdateResult> { let (.., authz_silo) = silo_lookup.lookup_for(authz::Action::ModifyPolicy).await?; @@ -231,14 +233,14 @@ impl super::Nexus { .role_assignment_replace_visible( opctx, &authz_silo, - &policy.role_assignments, + &new_policy.role_assignments, ) .await? .into_iter() .map(|r| r.try_into()) .collect::, _>>()?; - Ok(shared::Policy { role_assignments }) + Ok(policy::Policy { role_assignments }) } pub(crate) async fn silo_fetch_auth_settings( @@ -255,7 +257,7 @@ impl super::Nexus { &self, opctx: &OpContext, silo_lookup: &lookup::Silo<'_>, - settings: ¶ms::SiloAuthSettingsUpdate, + settings: &silo_types::SiloAuthSettingsUpdate, ) -> UpdateResult { // TODO: modify seems fine, but look into why policy has its own // separate permission @@ -456,7 +458,7 @@ impl super::Nexus { &self, opctx: &OpContext, silo_lookup: &lookup::Silo<'_>, - new_user_params: params::UserCreate, + new_user_params: user::UserCreate, ) -> CreateResult { let (authz_silo, db_silo) = self.local_idp_fetch_silo(silo_lookup).await?; @@ -699,7 +701,7 @@ impl super::Nexus { opctx: &OpContext, silo_lookup: &lookup::Silo<'_>, silo_user_id: SiloUserUuid, - password_value: params::UserPassword, + password_value: user::UserPassword, ) -> UpdateResult<()> { let (authz_silo, db_silo) = self.local_idp_fetch_silo(silo_lookup).await?; @@ -742,11 +744,11 @@ impl super::Nexus { db_silo: &db::model::Silo, authz_silo_user: &authz::SiloUser, silo_user: &SiloUserApiOnly, - password_value: params::UserPassword, + password_value: user::UserPassword, ) -> UpdateResult<()> { let password_hash = match password_value { - params::UserPassword::LoginDisallowed => None, - params::UserPassword::Password(password) => { + user::UserPassword::LoginDisallowed => None, + user::UserPassword::Password(password) => { let mut hasher = omicron_passwords::Hasher::default(); let password_hash = hasher .create_password(password.as_ref()) @@ -821,7 +823,7 @@ impl super::Nexus { &self, opctx: &OpContext, silo_lookup: &lookup::Silo<'_>, - credentials: params::UsernamePasswordCredentials, + credentials: user::UsernamePasswordCredentials, ) -> Result { let (authz_silo, _) = self.local_idp_fetch_silo(silo_lookup).await?; @@ -922,10 +924,10 @@ impl super::Nexus { pub fn saml_identity_provider_lookup<'a>( &'a self, opctx: &'a OpContext, - saml_identity_provider_selector: params::SamlIdentityProviderSelector, + saml_identity_provider_selector: identity_provider::SamlIdentityProviderSelector, ) -> LookupResult> { match saml_identity_provider_selector { - params::SamlIdentityProviderSelector { + identity_provider::SamlIdentityProviderSelector { saml_identity_provider: NameOrId::Id(id), silo: None, } => { @@ -933,7 +935,7 @@ impl super::Nexus { .saml_identity_provider_id(id); Ok(saml_provider) } - params::SamlIdentityProviderSelector { + identity_provider::SamlIdentityProviderSelector { saml_identity_provider: NameOrId::Name(name), silo: Some(silo), } => { @@ -942,7 +944,7 @@ impl super::Nexus { .saml_identity_provider_name_owned(name.into()); Ok(saml_provider) } - params::SamlIdentityProviderSelector { + identity_provider::SamlIdentityProviderSelector { saml_identity_provider: NameOrId::Id(_), silo: _, } => Err(Error::invalid_request( @@ -974,7 +976,7 @@ impl super::Nexus { &self, opctx: &OpContext, silo_lookup: &lookup::Silo<'_>, - params: params::SamlIdentityProviderCreate, + idp_params: identity_provider::SamlIdentityProviderCreate, ) -> CreateResult { // TODO-security: This should likely be fetch_for CreateChild on the silo let (authz_silo, db_silo) = silo_lookup.fetch().await?; @@ -1011,8 +1013,9 @@ impl super::Nexus { ))); } - let idp_metadata_document_string = match ¶ms.idp_metadata_source { - params::IdpMetadataSource::Url { url } => { + let idp_metadata_document_string = match &idp_params.idp_metadata_source + { + identity_provider::IdpMetadataSource::Url { url } => { // Download the SAML IdP descriptor, and write it into the DB. // This is so that it can be deserialized later. // @@ -1054,7 +1057,7 @@ impl super::Nexus { })? } - params::IdpMetadataSource::Base64EncodedXml { data } => { + identity_provider::IdpMetadataSource::Base64EncodedXml { data } => { let bytes = base64::Engine::decode( &base64::engine::general_purpose::STANDARD, data, @@ -1125,27 +1128,27 @@ impl super::Nexus { let provider = db::model::SamlIdentityProvider { identity: db::model::SamlIdentityProviderIdentity::new( Uuid::new_v4(), - params.identity, + idp_params.identity, ), silo_id: db_silo.id(), idp_metadata_document_string, - idp_entity_id: params.idp_entity_id, - sp_client_id: params.sp_client_id, - acs_url: params.acs_url, - slo_url: params.slo_url, - technical_contact_email: params.technical_contact_email, - public_cert: params + idp_entity_id: idp_params.idp_entity_id, + sp_client_id: idp_params.sp_client_id, + acs_url: idp_params.acs_url, + slo_url: idp_params.slo_url, + technical_contact_email: idp_params.technical_contact_email, + public_cert: idp_params .signing_keypair .as_ref() .map(|x| x.public_cert.clone()), - private_key: params + private_key: idp_params .signing_keypair .as_ref() .map(|x| x.private_key.clone()), - group_attribute_name: params.group_attribute_name.clone(), + group_attribute_name: idp_params.group_attribute_name.clone(), }; let _authn_provider: authn::silos::SamlIdentityProvider = diff --git a/nexus/src/app/sled.rs b/nexus/src/app/sled.rs index 28c1a5dafb9..883162ee92b 100644 --- a/nexus/src/app/sled.rs +++ b/nexus/src/app/sled.rs @@ -4,7 +4,6 @@ //! Sleds, and the hardware and services within them. -use crate::external_api::params; use crate::internal_api::params::{ PhysicalDiskPutRequest, SledAgentInfo, ZpoolPutRequest, }; @@ -15,9 +14,9 @@ use nexus_db_queries::context::OpContext; use nexus_db_queries::db; use nexus_types::deployment::DiskFilter; use nexus_types::deployment::SledFilter; -use nexus_types::external_api::views::PhysicalDiskPolicy; -use nexus_types::external_api::views::SledPolicy; -use nexus_types::external_api::views::SledProvisionPolicy; +use nexus_types::external_api::path_params; +use nexus_types::external_api::physical_disk::PhysicalDiskPolicy; +use nexus_types::external_api::sled::{SledPolicy, SledProvisionPolicy}; use omicron_common::api::external::ByteCount; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::Error; @@ -214,7 +213,7 @@ impl super::Nexus { pub fn physical_disk_lookup<'a>( &'a self, opctx: &'a OpContext, - disk_selector: ¶ms::PhysicalDiskPath, + disk_selector: &path_params::PhysicalDiskPath, ) -> Result, Error> { // XXX how to do typed UUID as part of dropshot path? Ok(LookupPath::new(&opctx, &self.db_datastore).physical_disk( @@ -302,7 +301,7 @@ impl super::Nexus { pub(crate) async fn physical_disk_expunge( &self, opctx: &OpContext, - disk: params::PhysicalDiskPath, + disk: path_params::PhysicalDiskPath, ) -> Result<(), Error> { let physical_disk_lookup = self.physical_disk_lookup(opctx, &disk)?; let (authz_disk,) = diff --git a/nexus/src/app/snapshot.rs b/nexus/src/app/snapshot.rs index da7cb8d8496..126e676cb3f 100644 --- a/nexus/src/app/snapshot.rs +++ b/nexus/src/app/snapshot.rs @@ -13,8 +13,9 @@ use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; use nexus_db_queries::db::datastore; -use nexus_types::external_api::params; -use nexus_types::external_api::params::DiskSelector; +use nexus_types::external_api::disk::DiskSelector; +use nexus_types::external_api::project; +use nexus_types::external_api::snapshot; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; @@ -31,10 +32,10 @@ impl super::Nexus { pub fn snapshot_lookup<'a>( &'a self, opctx: &'a OpContext, - snapshot_selector: params::SnapshotSelector, + snapshot_selector: snapshot::SnapshotSelector, ) -> LookupResult> { match snapshot_selector { - params::SnapshotSelector { + snapshot::SnapshotSelector { snapshot: NameOrId::Id(id), project: None, } => { @@ -42,21 +43,24 @@ impl super::Nexus { LookupPath::new(opctx, &self.db_datastore).snapshot_id(id); Ok(snapshot) } - params::SnapshotSelector { + snapshot::SnapshotSelector { snapshot: NameOrId::Name(name), - project: Some(project), + project: Some(proj), } => { let snapshot = self - .project_lookup(opctx, params::ProjectSelector { project })? + .project_lookup( + opctx, + project::ProjectSelector { project: proj }, + )? .snapshot_name_owned(name.into()); Ok(snapshot) } - params::SnapshotSelector { snapshot: NameOrId::Id(_), .. } => { - Err(Error::invalid_request( - "when providing snapshot as an ID, project should not \ + snapshot::SnapshotSelector { + snapshot: NameOrId::Id(_), .. + } => Err(Error::invalid_request( + "when providing snapshot as an ID, project should not \ be specified", - )) - } + )), _ => Err(Error::invalid_request( "snapshot should either be an ID or project should be specified", )), @@ -68,7 +72,7 @@ impl super::Nexus { opctx: &OpContext, // Is passed by value due to `disk_name` taking ownership of `self` below project_lookup: lookup::Project<'_>, - params: ¶ms::SnapshotCreate, + params: &snapshot::SnapshotCreate, ) -> CreateResult { let authz_silo: authz::Silo; let authz_disk_project: authz::Project; diff --git a/nexus/src/app/ssh_key.rs b/nexus/src/app/ssh_key.rs index c4e6fb06136..7135233b011 100644 --- a/nexus/src/app/ssh_key.rs +++ b/nexus/src/app/ssh_key.rs @@ -1,4 +1,3 @@ -use crate::external_api::params; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; use nexus_db_queries::authz; @@ -6,6 +5,7 @@ use nexus_db_queries::context::OpContext; use nexus_db_queries::db; use nexus_db_queries::db::model::Name; use nexus_db_queries::db::model::SshKey; +use nexus_types::external_api::ssh_key; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::ListResultVec; @@ -20,25 +20,25 @@ impl super::Nexus { pub fn ssh_key_lookup<'a>( &'a self, opctx: &'a OpContext, - ssh_key_selector: &'a params::SshKeySelector, + ssh_key_selector: &'a ssh_key::SshKeySelector, ) -> LookupResult> { match ssh_key_selector { - params::SshKeySelector { + ssh_key::SshKeySelector { silo_user_id: _, ssh_key: NameOrId::Id(id), } => { - let ssh_key = + let key = LookupPath::new(opctx, &self.db_datastore).ssh_key_id(*id); - Ok(ssh_key) + Ok(key) } - params::SshKeySelector { + ssh_key::SshKeySelector { silo_user_id, ssh_key: NameOrId::Name(name), } => { - let ssh_key = LookupPath::new(opctx, &self.db_datastore) + let key = LookupPath::new(opctx, &self.db_datastore) .silo_user_id(*silo_user_id) .ssh_key_name(Name::ref_cast(name)); - Ok(ssh_key) + Ok(key) } } } @@ -47,7 +47,7 @@ impl super::Nexus { &self, opctx: &OpContext, silo_user_id: SiloUserUuid, - params: params::SshKeyCreate, + params: ssh_key::SshKeyCreate, ) -> CreateResult { let ssh_key = db::model::SshKey::new(silo_user_id, params); let (.., authz_user) = LookupPath::new(opctx, self.datastore()) diff --git a/nexus/src/app/subnet_pool.rs b/nexus/src/app/subnet_pool.rs index 73c3cdd8b99..73ed18c2566 100644 --- a/nexus/src/app/subnet_pool.rs +++ b/nexus/src/app/subnet_pool.rs @@ -17,7 +17,7 @@ use nexus_db_lookup::lookup; use nexus_db_model::SubnetPool; use nexus_db_model::SubnetPoolSiloLink; use nexus_db_queries::context::OpContext; -use nexus_types::external_api::{params, views}; +use nexus_types::external_api::subnet_pool as subnet_pool_types; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; @@ -48,7 +48,7 @@ impl super::Nexus { &self, opctx: &OpContext, pagparams: &PaginatedBy<'_>, - ) -> ListResultVec { + ) -> ListResultVec { self.datastore() .list_subnet_pools(opctx, pagparams) .await @@ -58,8 +58,8 @@ impl super::Nexus { pub(crate) async fn subnet_pool_create( &self, opctx: &OpContext, - pool_params: params::SubnetPoolCreate, - ) -> Result { + pool_params: subnet_pool_types::SubnetPoolCreate, + ) -> Result { self.datastore() .create_subnet_pool(opctx, pool_params) .await @@ -70,7 +70,7 @@ impl super::Nexus { &self, opctx: &OpContext, pool: &NameOrId, - ) -> LookupResult { + ) -> LookupResult { self.datastore() .lookup_subnet_pool(opctx, pool) .fetch() @@ -82,8 +82,8 @@ impl super::Nexus { &self, opctx: &OpContext, pool: &NameOrId, - params: params::SubnetPoolUpdate, - ) -> UpdateResult { + params: subnet_pool_types::SubnetPoolUpdate, + ) -> UpdateResult { let (authz_pool, _db_pool) = self .datastore() .lookup_subnet_pool(opctx, pool) @@ -115,7 +115,7 @@ impl super::Nexus { opctx: &OpContext, pool: &NameOrId, pagparams: &DataPageParams<'_, IpNet>, - ) -> ListResultVec { + ) -> ListResultVec { let (authz_pool, _db_pool) = self .datastore() .lookup_subnet_pool(opctx, pool) @@ -144,8 +144,8 @@ impl super::Nexus { &self, opctx: &OpContext, pool: &NameOrId, - params: ¶ms::SubnetPoolMemberAdd, - ) -> Result { + params: &subnet_pool_types::SubnetPoolMemberAdd, + ) -> Result { let (authz_pool, db_pool) = self .datastore() .lookup_subnet_pool(opctx, pool) @@ -161,7 +161,7 @@ impl super::Nexus { &self, opctx: &OpContext, pool: &NameOrId, - params: ¶ms::SubnetPoolMemberRemove, + params: &subnet_pool_types::SubnetPoolMemberRemove, ) -> DeleteResult { let (authz_pool, _db_pool) = self .datastore() @@ -180,7 +180,7 @@ impl super::Nexus { opctx: &OpContext, pool: &NameOrId, pagparams: &DataPageParams<'_, Uuid>, - ) -> ListResultVec { + ) -> ListResultVec { let (authz_pool, _db_pool) = self .datastore() .lookup_subnet_pool(opctx, pool) @@ -256,9 +256,9 @@ impl super::Nexus { &self, opctx: &OpContext, pool: &NameOrId, - params: params::SubnetPoolLinkSilo, - ) -> Result { - let params::SubnetPoolLinkSilo { silo, is_default } = params; + params: subnet_pool_types::SubnetPoolLinkSilo, + ) -> Result { + let subnet_pool_types::SubnetPoolLinkSilo { silo, is_default } = params; let (authz_pool, _db_pool) = self .datastore() .lookup_subnet_pool(opctx, pool) @@ -284,9 +284,9 @@ impl super::Nexus { opctx: &OpContext, pool: NameOrId, silo: NameOrId, - params: params::SubnetPoolSiloUpdate, - ) -> UpdateResult { - let params::SubnetPoolSiloUpdate { is_default } = params; + params: subnet_pool_types::SubnetPoolSiloUpdate, + ) -> UpdateResult { + let subnet_pool_types::SubnetPoolSiloUpdate { is_default } = params; let (authz_pool, _db_pool) = self .datastore() .lookup_subnet_pool(opctx, &pool) @@ -339,7 +339,7 @@ impl super::Nexus { &self, opctx: &OpContext, pool: &NameOrId, - ) -> LookupResult { + ) -> LookupResult { let not_found = not_found_error(pool, ResourceType::SubnetPool); Err(self .unimplemented_todo(opctx, Unimpl::ProtectedLookup(not_found)) diff --git a/nexus/src/app/switch.rs b/nexus/src/app/switch.rs index ec1c5e8a470..148857ce718 100644 --- a/nexus/src/app/switch.rs +++ b/nexus/src/app/switch.rs @@ -4,7 +4,7 @@ use nexus_db_model::Switch; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; -use nexus_types::external_api::params; +use nexus_types::external_api::sled; use nexus_types::internal_api::params::SwitchPutRequest; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::Error; @@ -17,7 +17,7 @@ impl super::Nexus { pub fn switch_lookup<'a>( &'a self, opctx: &'a OpContext, - switch_selector: params::SwitchSelector, + switch_selector: sled::SwitchSelector, ) -> LookupResult> { Ok(LookupPath::new(opctx, &self.db_datastore) .switch_id(switch_selector.switch)) diff --git a/nexus/src/app/switch_interface.rs b/nexus/src/app/switch_interface.rs index 5c4ebd6b117..d9f53c1c293 100644 --- a/nexus/src/app/switch_interface.rs +++ b/nexus/src/app/switch_interface.rs @@ -2,13 +2,13 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use crate::external_api::params; use db::model::{LoopbackAddress, Name}; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; +use nexus_types::external_api::networking; use omicron_common::api::external::LookupResult; use omicron_common::api::external::{ CreateResult, DataPageParams, DeleteResult, Error, ListResultVec, @@ -35,7 +35,7 @@ impl super::Nexus { pub(crate) async fn loopback_address_create( self: &Arc, opctx: &OpContext, - params: params::LoopbackAddressCreate, + params: networking::LoopbackAddressCreate, ) -> CreateResult { opctx.authorize(authz::Action::CreateChild, &authz::FLEET).await?; diff --git a/nexus/src/app/switch_port.rs b/nexus/src/app/switch_port.rs index 331af66019d..3d505045d1f 100644 --- a/nexus/src/app/switch_port.rs +++ b/nexus/src/app/switch_port.rs @@ -2,8 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use crate::external_api::params; -use crate::external_api::shared::SwitchLinkState; use db::datastore::SwitchPortSettingsCombinedResult; use dpd_client::types::LinkId; use dpd_client::types::PortId; @@ -14,6 +12,8 @@ use nexus_db_queries::db; use nexus_db_queries::db::DataStore; use nexus_db_queries::db::datastore::UpdatePrecondition; use nexus_db_queries::db::model::{SwitchPort, SwitchPortSettings}; +use nexus_types::external_api::networking; +use nexus_types::external_api::switch::SwitchLinkState; use omicron_common::api::external::SwitchLocation; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{ @@ -27,7 +27,7 @@ impl super::Nexus { pub(crate) async fn switch_port_settings_post( self: &Arc, opctx: &OpContext, - params: params::SwitchPortSettingsCreate, + params: networking::SwitchPortSettingsCreate, ) -> CreateResult { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; Self::switch_port_settings_validate(¶ms)?; @@ -57,7 +57,7 @@ impl super::Nexus { // TODO: more validation wanted fn switch_port_settings_validate( - params: ¶ms::SwitchPortSettingsCreate, + params: &networking::SwitchPortSettingsCreate, ) -> CreateResult<()> { for x in ¶ms.bgp_peers { for p in x.peers.iter() { @@ -97,7 +97,7 @@ impl super::Nexus { pub async fn switch_port_settings_create( self: &Arc, opctx: &OpContext, - params: params::SwitchPortSettingsCreate, + params: networking::SwitchPortSettingsCreate, id: Option, ) -> CreateResult { let result = self @@ -116,7 +116,7 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, switch_port_settings_id: Uuid, - new_settings: params::SwitchPortSettingsCreate, + new_settings: networking::SwitchPortSettingsCreate, ) -> CreateResult { let result = self .db_datastore @@ -152,7 +152,7 @@ impl super::Nexus { pub(crate) async fn switch_port_settings_delete( &self, opctx: &OpContext, - params: ¶ms::SwitchPortSettingsSelector, + params: &networking::SwitchPortSettingsSelector, ) -> DeleteResult { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; self.db_datastore.switch_port_settings_delete(opctx, params).await @@ -224,8 +224,8 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, port: &Name, - selector: ¶ms::SwitchPortSelector, - settings: ¶ms::SwitchPortApplySettings, + selector: &networking::SwitchPortSelector, + settings: &networking::SwitchPortApplySettings, ) -> UpdateResult<()> { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; let switch_port_id = self @@ -266,7 +266,7 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, port: &Name, - params: ¶ms::SwitchPortSelector, + params: &networking::SwitchPortSelector, ) -> UpdateResult<()> { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; let switch_port_id = self diff --git a/nexus/src/app/update.rs b/nexus/src/app/update.rs index ef4de61e79f..5f56d87025b 100644 --- a/nexus/src/app/update.rs +++ b/nexus/src/app/update.rs @@ -17,8 +17,8 @@ use nexus_db_queries::context::OpContext; use nexus_db_queries::db::{datastore::SQL_BATCH_SIZE, pagination::Paginator}; use nexus_types::deployment::SledFilter; use nexus_types::deployment::TargetReleaseDescription; -use nexus_types::external_api::shared::TufSignedRootRole; -use nexus_types::external_api::views; +use nexus_types::external_api::update; +use nexus_types::external_api::update::TufSignedRootRole; use nexus_types::identity::Asset; use nexus_types::internal_api::views as internal_views; use omicron_common::api::external::InternalContext; @@ -188,7 +188,7 @@ impl super::Nexus { pub async fn update_status_external( &self, opctx: &OpContext, - ) -> Result { + ) -> Result { let db_target_release = self.datastore().target_release_get_current(opctx).await?; @@ -202,7 +202,7 @@ impl super::Nexus { }; let target_release = - current_tuf_repo.as_ref().map(|repo| views::TargetRelease { + current_tuf_repo.as_ref().map(|repo| update::TargetRelease { time_requested: db_target_release.time_requested, version: repo.repo.system_version.0.clone(), }); @@ -234,7 +234,7 @@ impl super::Nexus { let suspended = *db_target_release.generation < blueprint_target.blueprint.target_release_minimum_generation; - Ok(views::UpdateStatus { + Ok(update::UpdateStatus { target_release: Nullable(target_release), components_by_release_version, time_last_step_planned, diff --git a/nexus/src/app/vpc.rs b/nexus/src/app/vpc.rs index 110ba337a37..60a044fad16 100644 --- a/nexus/src/app/vpc.rs +++ b/nexus/src/app/vpc.rs @@ -5,7 +5,6 @@ //! VPCs and firewall rules use crate::app::sagas; -use crate::external_api::params; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; use nexus_db_queries::authn; @@ -14,6 +13,8 @@ use nexus_db_queries::context::OpContext; use nexus_db_queries::db; use nexus_db_queries::db::model::Name; use nexus_defaults as defaults; +use nexus_types::external_api::project; +use nexus_types::external_api::vpc; use omicron_common::api::external; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; @@ -38,23 +39,26 @@ impl super::Nexus { pub fn vpc_lookup<'a>( &'a self, opctx: &'a OpContext, - vpc_selector: params::VpcSelector, + vpc_selector: vpc::VpcSelector, ) -> LookupResult> { match vpc_selector { - params::VpcSelector { vpc: NameOrId::Id(id), project: None } => { - let vpc = LookupPath::new(opctx, &self.db_datastore).vpc_id(id); - Ok(vpc) + vpc::VpcSelector { vpc: NameOrId::Id(id), project: None } => { + let v = LookupPath::new(opctx, &self.db_datastore).vpc_id(id); + Ok(v) } - params::VpcSelector { + vpc::VpcSelector { vpc: NameOrId::Name(name), - project: Some(project), + project: Some(proj), } => { - let vpc = self - .project_lookup(opctx, params::ProjectSelector { project })? + let v = self + .project_lookup( + opctx, + project::ProjectSelector { project: proj }, + )? .vpc_name_owned(name.into()); - Ok(vpc) + Ok(v) } - params::VpcSelector { vpc: NameOrId::Id(_), project: Some(_) } => { + vpc::VpcSelector { vpc: NameOrId::Id(_), project: Some(_) } => { Err(Error::invalid_request( "when providing vpc as an ID, project should not be specified", )) @@ -69,7 +73,7 @@ impl super::Nexus { self: &Arc, opctx: &OpContext, project_lookup: &lookup::Project<'_>, - params: ¶ms::VpcCreate, + params: &vpc::VpcCreate, ) -> CreateResult { let (.., authz_project) = project_lookup.lookup_for(authz::Action::CreateChild).await?; @@ -111,7 +115,7 @@ impl super::Nexus { &self, opctx: &OpContext, vpc_lookup: &lookup::Vpc<'_>, - params: ¶ms::VpcUpdate, + params: &vpc::VpcUpdate, ) -> UpdateResult { let (.., authz_vpc) = vpc_lookup.lookup_for(authz::Action::Modify).await?; diff --git a/nexus/src/app/vpc_router.rs b/nexus/src/app/vpc_router.rs index b08de606b71..86c49dd0dc4 100644 --- a/nexus/src/app/vpc_router.rs +++ b/nexus/src/app/vpc_router.rs @@ -4,7 +4,6 @@ //! VPC routers and routes -use crate::external_api::params; use nexus_db_lookup::LookupPath; use nexus_db_lookup::lookup; use nexus_db_queries::authz; @@ -13,6 +12,7 @@ use nexus_db_queries::db; use nexus_db_queries::db::model::RouterRoute; use nexus_db_queries::db::model::VpcRouter; use nexus_db_queries::db::model::VpcRouterKind; +use nexus_types::external_api::vpc; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; @@ -33,10 +33,10 @@ impl super::Nexus { pub fn vpc_router_lookup<'a>( &'a self, opctx: &'a OpContext, - router_selector: params::RouterSelector, + router_selector: vpc::RouterSelector, ) -> LookupResult> { match router_selector { - params::RouterSelector { + vpc::RouterSelector { router: NameOrId::Id(id), vpc: None, project: None, @@ -45,17 +45,17 @@ impl super::Nexus { .vpc_router_id(id); Ok(router) } - params::RouterSelector { + vpc::RouterSelector { router: NameOrId::Name(name), vpc: Some(vpc), project, } => { let router = self - .vpc_lookup(opctx, params::VpcSelector { project, vpc })? + .vpc_lookup(opctx, vpc::VpcSelector { project, vpc })? .vpc_router_name_owned(name.into()); Ok(router) } - params::RouterSelector { router: NameOrId::Id(_), .. } => { + vpc::RouterSelector { router: NameOrId::Id(_), .. } => { Err(Error::invalid_request( "when providing router as an ID vpc and project should not be specified", )) @@ -77,7 +77,7 @@ impl super::Nexus { let (.., vpc, rtr) = (match router { key @ NameOrId::Name(_) => self.vpc_router_lookup( opctx, - params::RouterSelector { + vpc::RouterSelector { project: None, vpc: Some(NameOrId::Id(authz_vpc.id())), router: key.clone(), @@ -85,7 +85,7 @@ impl super::Nexus { )?, key @ NameOrId::Id(_) => self.vpc_router_lookup( opctx, - params::RouterSelector { + vpc::RouterSelector { project: None, vpc: None, router: key.clone(), @@ -110,7 +110,7 @@ impl super::Nexus { opctx: &OpContext, vpc_lookup: &lookup::Vpc<'_>, kind: &VpcRouterKind, - params: ¶ms::VpcRouterCreate, + params: &vpc::VpcRouterCreate, ) -> CreateResult { let (.., authz_vpc) = vpc_lookup.lookup_for(authz::Action::CreateChild).await?; @@ -151,7 +151,7 @@ impl super::Nexus { &self, opctx: &OpContext, vpc_router_lookup: &lookup::VpcRouter<'_>, - params: ¶ms::VpcRouterUpdate, + params: &vpc::VpcRouterUpdate, ) -> UpdateResult { let (.., authz_router) = vpc_router_lookup.lookup_for(authz::Action::Modify).await?; @@ -187,10 +187,10 @@ impl super::Nexus { pub fn vpc_router_route_lookup<'a>( &'a self, opctx: &'a OpContext, - route_selector: params::RouteSelector, + route_selector: vpc::RouteSelector, ) -> LookupResult> { match route_selector { - params::RouteSelector { + vpc::RouteSelector { route: NameOrId::Id(id), router: None, vpc: None, @@ -200,7 +200,7 @@ impl super::Nexus { .router_route_id(id); Ok(route) } - params::RouteSelector { + vpc::RouteSelector { route: NameOrId::Name(name), router: Some(router), vpc, @@ -209,12 +209,12 @@ impl super::Nexus { let route = self .vpc_router_lookup( opctx, - params::RouterSelector { project, vpc, router }, + vpc::RouterSelector { project, vpc, router }, )? .router_route_name_owned(name.into()); Ok(route) } - params::RouteSelector { route: NameOrId::Id(_), .. } => { + vpc::RouteSelector { route: NameOrId::Id(_), .. } => { Err(Error::invalid_request( "when providing route as an ID router, subnet, vpc, and project should not be specified", )) @@ -230,7 +230,7 @@ impl super::Nexus { opctx: &OpContext, router_lookup: &lookup::VpcRouter<'_>, kind: &RouterRouteKind, - params: ¶ms::RouterRouteCreate, + params: &vpc::RouterRouteCreate, ) -> CreateResult { let (.., authz_router, db_router) = router_lookup.fetch_for(authz::Action::CreateChild).await?; @@ -281,7 +281,7 @@ impl super::Nexus { &self, opctx: &OpContext, route_lookup: &lookup::RouterRoute<'_>, - params: ¶ms::RouterRouteUpdate, + params: &vpc::RouterRouteUpdate, ) -> UpdateResult { let (.., authz_router, authz_route, db_route) = route_lookup.fetch_for(authz::Action::Modify).await?; diff --git a/nexus/src/app/vpc_subnet.rs b/nexus/src/app/vpc_subnet.rs index f1c40c86ed7..93427998a91 100644 --- a/nexus/src/app/vpc_subnet.rs +++ b/nexus/src/app/vpc_subnet.rs @@ -5,7 +5,6 @@ //! VPC Subnets and their network interfaces use super::sagas; -use crate::external_api::params; use nexus_auth::authn; use nexus_config::MIN_VPC_IPV4_SUBNET_PREFIX; use nexus_db_lookup::LookupPath; @@ -14,6 +13,7 @@ use nexus_db_queries::authz; use nexus_db_queries::context::OpContext; use nexus_db_queries::db; use nexus_db_queries::db::model::VpcSubnet; +use nexus_types::external_api::vpc; use omicron_common::api::external; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DeleteResult; @@ -30,10 +30,10 @@ impl super::Nexus { pub fn vpc_subnet_lookup<'a>( &'a self, opctx: &'a OpContext, - subnet_selector: params::SubnetSelector, + subnet_selector: vpc::SubnetSelector, ) -> LookupResult> { match subnet_selector { - params::SubnetSelector { + vpc::SubnetSelector { subnet: NameOrId::Id(id), vpc: None, project: None, @@ -42,17 +42,17 @@ impl super::Nexus { .vpc_subnet_id(id); Ok(subnet) } - params::SubnetSelector { + vpc::SubnetSelector { subnet: NameOrId::Name(name), vpc: Some(vpc), project, } => { let subnet = self - .vpc_lookup(opctx, params::VpcSelector { project, vpc })? + .vpc_lookup(opctx, vpc::VpcSelector { project, vpc })? .vpc_subnet_name_owned(name.into()); Ok(subnet) } - params::SubnetSelector { + vpc::SubnetSelector { subnet: NameOrId::Id(_), vpc: _, project: _, @@ -69,7 +69,7 @@ impl super::Nexus { &self, opctx: &OpContext, vpc_lookup: &lookup::Vpc<'_>, - params: ¶ms::VpcSubnetCreate, + params: &vpc::VpcSubnetCreate, ) -> CreateResult { let (.., authz_vpc, db_vpc) = vpc_lookup.fetch().await?; let (.., authz_system_router) = @@ -184,7 +184,7 @@ impl super::Nexus { &self, opctx: &OpContext, vpc_subnet_lookup: &lookup::VpcSubnet<'_>, - params: ¶ms::VpcSubnetUpdate, + params: &vpc::VpcSubnetUpdate, ) -> UpdateResult { let (.., authz_vpc, authz_subnet) = vpc_subnet_lookup.lookup_for(authz::Action::Modify).await?; diff --git a/nexus/src/app/webhook.rs b/nexus/src/app/webhook.rs index 33aa5aac486..d11efabbebf 100644 --- a/nexus/src/app/webhook.rs +++ b/nexus/src/app/webhook.rs @@ -48,8 +48,7 @@ use nexus_db_queries::db::model::WebhookDeliveryAttempt; use nexus_db_queries::db::model::WebhookDeliveryAttemptResult; use nexus_db_queries::db::model::WebhookReceiverConfig; use nexus_db_queries::db::model::WebhookSecret; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::alert; use nexus_types::identity::Asset; use nexus_types::identity::Resource; use omicron_common::api::external::CreateResult; @@ -74,7 +73,7 @@ impl Nexus { pub fn webhook_secret_lookup<'a>( &'a self, opctx: &'a OpContext, - secret_selector: params::WebhookSecretSelector, + secret_selector: alert::WebhookSecretSelector, ) -> LookupResult> { let lookup = LookupPath::new(&opctx, self.datastore()) .webhook_secret_id(WebhookSecretUuid::from_untyped_uuid( @@ -86,7 +85,7 @@ impl Nexus { pub async fn webhook_receiver_create( &self, opctx: &OpContext, - params: params::WebhookCreate, + params: alert::WebhookCreate, ) -> CreateResult { self.datastore().webhook_rx_create(&opctx, params).await } @@ -95,7 +94,7 @@ impl Nexus { &self, opctx: &OpContext, rx: lookup::AlertReceiver<'_>, - params: params::WebhookReceiverUpdate, + params: alert::WebhookReceiverUpdate, ) -> UpdateResult<()> { let (authz_rx,) = rx.lookup_for(authz::Action::Modify).await?; let _ = self @@ -132,7 +131,7 @@ impl Nexus { opctx: &OpContext, rx: lookup::AlertReceiver<'_>, secret: String, - ) -> Result { + ) -> Result { let (authz_rx,) = rx.lookup_for(authz::Action::CreateChild).await?; let secret = WebhookSecret::new(authz_rx.id(), secret); let secret = self @@ -176,8 +175,8 @@ impl Nexus { &self, opctx: &OpContext, rx: lookup::AlertReceiver<'_>, - params: params::AlertReceiverProbe, - ) -> Result { + params: alert::AlertReceiverProbe, + ) -> Result { let (authz_rx, rx) = rx.fetch_for(authz::Action::ListChildren).await?; let rx_id = authz_rx.id(); let datastore = self.datastore(); @@ -302,7 +301,7 @@ impl Nexus { None }; - Ok(views::AlertProbeResult { + Ok(alert::AlertProbeResult { probe: delivery.to_api_delivery(CLASS, &[attempt]), resends_started, }) @@ -404,7 +403,7 @@ impl<'a> ReceiverClient<'a> { id: WebhookDeliveryUuid, receiver_id: AlertReceiverUuid, sent_at: &'a str, - trigger: views::AlertDeliveryTrigger, + trigger: alert::AlertDeliveryTrigger, } // okay, actually do the thing... diff --git a/nexus/src/cidata.rs b/nexus/src/cidata.rs index b7329226f66..7499691852d 100644 --- a/nexus/src/cidata.rs +++ b/nexus/src/cidata.rs @@ -91,7 +91,7 @@ fn build_vfat(meta_data: &[u8], user_data: &[u8]) -> io::Result> { #[cfg(test)] mod tests { - use nexus_types::external_api::params::MAX_USER_DATA_BYTES; + use nexus_types::external_api::instance::MAX_USER_DATA_BYTES; /// the fatfs crate has some unfortunate panics if you ask it to do /// incredibly stupid things, like format an empty disk or create a diff --git a/nexus/src/external_api/console_api.rs b/nexus/src/external_api/console_api.rs index 06b312dfbb6..58faed1c9b6 100644 --- a/nexus/src/external_api/console_api.rs +++ b/nexus/src/external_api/console_api.rs @@ -15,7 +15,8 @@ use dropshot::{HttpError, Path, RequestContext}; use futures::TryStreamExt; use http::{HeaderName, HeaderValue, Response, StatusCode}; use nexus_db_model::AuthenticationMode; -use nexus_types::external_api::params::{self, RelativeUri}; +use nexus_types::external_api::console; +use nexus_types::external_api::saml::RelativeUri; use nexus_types::identity::Resource; use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::{DataPageParams, Error, NameOrId}; @@ -219,7 +220,7 @@ pub(crate) async fn get_login_url( // Stick redirect_url into the state param and URL encode it so it can be // used as a query string. We assume it's not already encoded. - let query_data = params::LoginUrlQuery { redirect_uri }; + let query_data = console::LoginUrlQuery { redirect_uri }; Ok(match serde_urlencoded::to_string(query_data) { // only put the ? in front if there's something there @@ -404,7 +405,7 @@ async fn serve_static( /// integration tests pub(crate) async fn asset( rqctx: &RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { // asset URLs contain hashes, so cache for 1 year const CACHE_CONTROL: HeaderValue = diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index 274d0e36745..7a59723f14f 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -4,19 +4,10 @@ //! Handler functions (entrypoints) for external HTTP APIs -use super::{ - console_api, params, - views::{ - self, Certificate, FloatingIp, Group, IdentityProvider, Image, IpPool, - IpPoolRange, PhysicalDisk, Project, Rack, Silo, SiloQuotas, - SiloUtilization, Sled, Snapshot, SshKey, User, UserBuiltin, - Utilization, Vpc, VpcRouter, VpcSubnet, - }, -}; +use super::console_api; use crate::app::external_endpoints::authority_for_request; use crate::app::support_bundles::SupportBundleQueryType; use crate::context::{ApiContext, audit_and_time}; -use crate::external_api::shared; use dropshot::Body; use dropshot::EmptyScanParams; use dropshot::Header; @@ -48,15 +39,38 @@ use nexus_db_queries::db; use nexus_db_queries::db::identity::Resource; use nexus_db_queries::db::model::Name; use nexus_external_api::*; -use nexus_types::{ - authn::cookies::Cookies, - external_api::{ - headers::RangeRequest, - params::SystemMetricsPathParam, - shared::{BfdStatus, ProbeInfo}, - views::RackMembershipStatus, - }, +use nexus_types::authn::cookies::Cookies; +use nexus_types::external_api::{ + affinity, alert, audit, certificate, console, device, disk, external_ip, + external_subnet, floating_ip, hardware, identity_provider, image, instance, + internet_gateway, ip_pool, metrics, multicast, networking, oxql, + path_params, policy, probe, project, rack, scim, silo, sled, snapshot, + ssh_key, subnet_pool, support_bundle, switch, system, timeseries, update, + user, vpc, +}; +// Type imports for API implementations (per RFD 619) +use nexus_types::external_api::bfd::BfdStatus; +use nexus_types::external_api::certificate::Certificate; +use nexus_types::external_api::floating_ip::FloatingIp; +use nexus_types::external_api::identity_provider::IdentityProvider; +use nexus_types::external_api::image::Image; +use nexus_types::external_api::ip_pool::{IpPool, IpPoolRange}; +use nexus_types::external_api::metrics::SystemMetricsPathParam; +use nexus_types::external_api::physical_disk::PhysicalDisk; +use nexus_types::external_api::probe::ProbeInfo; +use nexus_types::external_api::project::Project; +use nexus_types::external_api::rack::{Rack, RackMembershipStatus}; +use nexus_types::external_api::silo::{ + Silo, SiloQuotas, SiloUtilization, Utilization, }; +use nexus_types::external_api::sled::Sled; +use nexus_types::external_api::snapshot::Snapshot; +use nexus_types::external_api::ssh_key::SshKey; +use nexus_types::external_api::user::{Group, User, UserBuiltin}; +use nexus_types::external_api::vpc::{Vpc, VpcRouter, VpcSubnet}; +use nexus_types_versions::latest::headers::RangeRequest; +use nexus_types_versions::v2025_11_20_00; +use omicron_common::address::IpRange; use omicron_common::api::external::AddressLot; use omicron_common::api::external::AddressLotBlock; use omicron_common::api::external::AddressLotCreateResponse; @@ -132,7 +146,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_policy_view( rqctx: RequestContext, - ) -> Result>, HttpError> + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -151,14 +165,14 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_policy_update( rqctx: RequestContext, - new_policy: TypedBody>, - ) -> Result>, HttpError> + new_policy: TypedBody>, + ) -> Result>, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let new_policy = new_policy.into_inner(); let nasgns = new_policy.role_assignments.len(); // This should have been validated during parsing. - bail_unless!(nasgns <= shared::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE); + bail_unless!(nasgns <= policy::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE); let policy = nexus.fleet_update_policy(&opctx, &new_policy).await?; Ok(HttpResponseOk(policy)) }) @@ -167,7 +181,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn policy_view( rqctx: RequestContext, - ) -> Result>, HttpError> + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -194,14 +208,14 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn policy_update( rqctx: RequestContext, - new_policy: TypedBody>, - ) -> Result>, HttpError> + new_policy: TypedBody>, + ) -> Result>, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let new_policy = new_policy.into_inner(); let nasgns = new_policy.role_assignments.len(); // This should have been validated during parsing. - bail_unless!(nasgns <= shared::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE); + bail_unless!(nasgns <= policy::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE); let silo: NameOrId = opctx .authn .silo_required() @@ -219,7 +233,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn auth_settings_view( rqctx: RequestContext, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -246,8 +260,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn auth_settings_update( rqctx: RequestContext, - new_settings: TypedBody, - ) -> Result, HttpError> { + new_settings: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let new_settings = new_settings.into_inner(); let silo: NameOrId = opctx @@ -288,7 +302,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_utilization_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -379,7 +393,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_quotas_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -401,8 +415,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_quotas_update( rqctx: RequestContext, - path_params: Path, - new_quota: TypedBody, + path_params: Path, + new_quota: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -450,7 +464,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_create( rqctx: RequestContext, - new_silo_params: TypedBody, + new_silo_params: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let new_silo_params = new_silo_params.into_inner(); @@ -462,7 +476,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -483,9 +497,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_ip_pool_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> + { let apictx = rqctx.context(); let handler = async { let opctx = @@ -503,7 +518,7 @@ impl NexusExternalApi for NexusExternalApiImpl { .silo_ip_pool_list(&opctx, &silo_lookup, &paginated_by) .await? .iter() - .map(|(pool, silo_link)| views::SiloIpPool { + .map(|(pool, silo_link)| ip_pool::SiloIpPool { identity: pool.identity(), is_default: silo_link.is_default, ip_version: pool.ip_version.into(), @@ -526,7 +541,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let params = path_params.into_inner(); @@ -539,8 +554,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_policy_view( rqctx: RequestContext, - path_params: Path, - ) -> Result>, HttpError> + path_params: Path, + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -561,16 +576,16 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_policy_update( rqctx: RequestContext, - path_params: Path, - new_policy: TypedBody>, - ) -> Result>, HttpError> + path_params: Path, + new_policy: TypedBody>, + ) -> Result>, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let new_policy = new_policy.into_inner(); let nasgns = new_policy.role_assignments.len(); // This should have been validated during parsing. - bail_unless!(nasgns <= shared::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE); + bail_unless!(nasgns <= policy::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE); let silo_lookup = nexus.silo_lookup(&opctx, path.silo)?; let policy = nexus .silo_update_policy(&opctx, &silo_lookup, &new_policy) @@ -584,7 +599,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_user_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -617,8 +632,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_user_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -644,7 +659,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_identity_provider_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -680,10 +695,12 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn saml_identity_provider_create( rqctx: RequestContext, - query_params: Query, - new_provider: TypedBody, - ) -> Result, HttpError> - { + query_params: Query, + new_provider: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); let new_provider = new_provider.into_inner(); @@ -702,9 +719,12 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn saml_identity_provider_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ) -> Result< + HttpResponseOk, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let opctx = @@ -713,7 +733,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let path = path_params.into_inner(); let query = query_params.into_inner(); let saml_identity_provider_selector = - params::SamlIdentityProviderSelector { + identity_provider::SamlIdentityProviderSelector { silo: query.silo, saml_identity_provider: path.provider, }; @@ -739,8 +759,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn local_idp_user_create( rqctx: RequestContext, - query_params: Query, - new_user_params: TypedBody, + query_params: Query, + new_user_params: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); @@ -756,8 +776,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn local_idp_user_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -773,9 +793,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn local_idp_user_set_password( rqctx: RequestContext, - path_params: Path, - query_params: Query, - update: TypedBody, + path_params: Path, + query_params: Query, + update: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -797,8 +817,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn scim_token_list( rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError> + query_params: Query, + ) -> Result>, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; @@ -820,8 +840,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn scim_token_create( rqctx: RequestContext, - query_params: Query, - ) -> Result, HttpError> + query_params: Query, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); @@ -835,9 +855,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn scim_token_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; let query = query_params.into_inner(); @@ -860,8 +880,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn scim_token_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); @@ -900,7 +920,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn scim_v2_get_user( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); @@ -932,7 +952,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn scim_v2_put_user( rqctx: RequestContext, - path_params: Path, + path_params: Path, body: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { @@ -945,7 +965,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn scim_v2_patch_user( rqctx: RequestContext, - path_params: Path, + path_params: Path, body: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { @@ -958,7 +978,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn scim_v2_delete_user( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -988,7 +1008,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn scim_v2_get_group( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); @@ -1020,7 +1040,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn scim_v2_put_group( rqctx: RequestContext, - path_params: Path, + path_params: Path, body: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { @@ -1033,7 +1053,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn scim_v2_patch_group( rqctx: RequestContext, - path_params: Path, + path_params: Path, body: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { @@ -1046,7 +1066,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn scim_v2_delete_group( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -1089,7 +1109,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn project_create( rqctx: RequestContext, - new_project: TypedBody, + new_project: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let new_project = new_project.into_inner(); @@ -1101,7 +1121,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn project_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; @@ -1110,7 +1130,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let project_selector = - params::ProjectSelector { project: path.project }; + project::ProjectSelector { project: path.project }; let (.., project) = nexus.project_lookup(&opctx, project_selector)?.fetch().await?; Ok(HttpResponseOk(project.into())) @@ -1124,12 +1144,12 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn project_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let project_selector = - params::ProjectSelector { project: path.project }; + project::ProjectSelector { project: path.project }; let project_lookup = nexus.project_lookup(&opctx, project_selector)?; nexus.project_delete(&opctx, &project_lookup).await?; @@ -1145,14 +1165,14 @@ impl NexusExternalApi for NexusExternalApiImpl { // "application/json-patch")? We should see what other APIs do. async fn project_update( rqctx: RequestContext, - path_params: Path, - updated_project: TypedBody, + path_params: Path, + updated_project: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let updated_project = updated_project.into_inner(); let project_selector = - params::ProjectSelector { project: path.project }; + project::ProjectSelector { project: path.project }; let project_lookup = nexus.project_lookup(&opctx, project_selector)?; let project = nexus @@ -1165,8 +1185,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn project_policy_view( rqctx: RequestContext, - path_params: Path, - ) -> Result>, HttpError> + path_params: Path, + ) -> Result>, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; @@ -1175,7 +1195,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let project_selector = - params::ProjectSelector { project: path.project }; + project::ProjectSelector { project: path.project }; let project_lookup = nexus.project_lookup(&opctx, project_selector)?; let policy = @@ -1191,15 +1211,15 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn project_policy_update( rqctx: RequestContext, - path_params: Path, - new_policy: TypedBody>, - ) -> Result>, HttpError> + path_params: Path, + new_policy: TypedBody>, + ) -> Result>, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let new_policy = new_policy.into_inner(); let project_selector = - params::ProjectSelector { project: path.project }; + project::ProjectSelector { project: path.project }; let project_lookup = nexus.project_lookup(&opctx, project_selector)?; nexus @@ -1215,7 +1235,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn ip_pool_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> + { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -1229,7 +1250,7 @@ impl NexusExternalApi for NexusExternalApiImpl { .current_silo_ip_pool_list(&opctx, &paginated_by) .await? .into_iter() - .map(|(pool, silo_link)| views::SiloIpPool { + .map(|(pool, silo_link)| ip_pool::SiloIpPool { identity: pool.identity(), is_default: silo_link.is_default, ip_version: pool.ip_version.into(), @@ -1251,8 +1272,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn ip_pool_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let opctx = @@ -1261,7 +1282,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let pool_selector = path_params.into_inner().pool; let (.., pool, silo_link) = nexus.silo_ip_pool_fetch(&opctx, &pool_selector).await?; - Ok(HttpResponseOk(views::SiloIpPool { + Ok(HttpResponseOk(ip_pool::SiloIpPool { identity: pool.identity(), is_default: silo_link.is_default, ip_version: pool.ip_version.into(), @@ -1309,8 +1330,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_create( rqctx: RequestContext, - pool_params: TypedBody, - ) -> Result, HttpError> { + pool_params: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let pool_params = pool_params.into_inner(); let pool = nexus.ip_pool_create(&opctx, &pool_params).await?; @@ -1321,8 +1342,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let opctx = @@ -1344,7 +1365,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -1357,9 +1378,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_update( rqctx: RequestContext, - path_params: Path, - updates: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + updates: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let updates = updates.into_inner(); @@ -1373,8 +1394,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_utilization_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let opctx = @@ -1397,19 +1418,19 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_silo_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, // paginating by resource_id because they're unique per pool. most robust // option would be to paginate by a composite key representing the (pool, // resource_type, resource) query_params: Query, - // TODO: this could just list views::Silo -- it's not like knowing silo_id + // TODO: this could just list silo::Silo -- it's not like knowing silo_id // and nothing else is particularly useful -- except we also want to say // whether the pool is marked default on each silo. So one option would // be to do the same as we did with SiloIpPool -- include is_default on // whatever the thing is. Still... all we'd have to do to make this usable // in both places would be to make it { ...IpPool, silo_id, silo_name, // is_default } - ) -> Result>, HttpError> + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -1433,7 +1454,7 @@ impl NexusExternalApi for NexusExternalApiImpl { Ok(HttpResponseOk(ScanById::results_page( &query, assocs, - &|_, x: &views::IpPoolSiloLink| x.silo_id, + &|_, x: &ip_pool::IpPoolSiloLink| x.silo_id, )?)) }; apictx @@ -1445,9 +1466,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_silo_link( rqctx: RequestContext, - path_params: Path, - resource_assoc: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + resource_assoc: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let resource_assoc = resource_assoc.into_inner(); @@ -1462,7 +1483,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_silo_unlink( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -1478,9 +1499,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_silo_update( rqctx: RequestContext, - path_params: Path, - update: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + update: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let update = update.into_inner(); @@ -1501,7 +1522,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_service_view( rqctx: RequestContext, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; let handler = async { @@ -1519,7 +1540,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_range_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -1562,8 +1583,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_range_add( rqctx: RequestContext, - path_params: Path, - range_params: TypedBody, + path_params: Path, + range_params: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -1578,8 +1599,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_range_remove( rqctx: RequestContext, - path_params: Path, - range_params: TypedBody, + path_params: Path, + range_params: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -1633,7 +1654,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_service_range_add( rqctx: RequestContext, - range_params: TypedBody, + range_params: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let range = range_params.into_inner(); @@ -1645,7 +1666,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_ip_pool_service_range_remove( rqctx: RequestContext, - range_params: TypedBody, + range_params: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let range = range_params.into_inner(); @@ -1660,7 +1681,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> + { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -1686,8 +1708,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_create( rqctx: RequestContext, - pool_params: TypedBody, - ) -> Result, HttpError> { + pool_params: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let pool_params = pool_params.into_inner(); let pool = nexus.subnet_pool_create(&opctx, pool_params).await?; @@ -1698,8 +1720,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let opctx = @@ -1718,9 +1740,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_update( rqctx: RequestContext, - path_params: Path, - updates: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + updates: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let updates = updates.into_inner(); @@ -1733,7 +1755,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -1745,10 +1767,12 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_member_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let opctx = @@ -1773,7 +1797,9 @@ impl NexusExternalApi for NexusExternalApiImpl { ResultsPage::new( items, &EmptyScanParams {}, - |member: &views::SubnetPoolMember, _| member.subnet, + |member: &subnet_pool::SubnetPoolMember, _| { + member.subnet + }, ) }) .map(HttpResponseOk) @@ -1787,9 +1813,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_member_add( rqctx: RequestContext, - path_params: Path, - subnet_params: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + subnet_params: TypedBody, + ) -> Result, HttpError> + { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let subnet_params = subnet_params.into_inner(); @@ -1803,8 +1830,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_member_remove( rqctx: RequestContext, - path_params: Path, - subnet_params: TypedBody, + path_params: Path, + subnet_params: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -1819,10 +1846,12 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_silo_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let opctx = @@ -1837,7 +1866,7 @@ impl NexusExternalApi for NexusExternalApiImpl { Ok(HttpResponseOk(ScanById::results_page( &query, links, - &|_, x: &views::SubnetPoolSiloLink| x.silo_id, + &|_, x: &subnet_pool::SubnetPoolSiloLink| x.silo_id, )?)) }; apictx @@ -1849,10 +1878,12 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn silo_subnet_pool_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let opctx = @@ -1869,7 +1900,7 @@ impl NexusExternalApi for NexusExternalApiImpl { .silo_subnet_pool_list(&opctx, &silo_lookup, &paginated_by) .await? .into_iter() - .map(|(pool, silo_link)| views::SiloSubnetPool { + .map(|(pool, silo_link)| subnet_pool::SiloSubnetPool { identity: pool.identity(), is_default: silo_link.is_default, ip_version: pool.ip_version.into(), @@ -1892,8 +1923,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn subnet_pool_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let opctx = @@ -1907,7 +1940,7 @@ impl NexusExternalApi for NexusExternalApiImpl { .current_silo_subnet_pool_list(&opctx, &paginated_by) .await? .into_iter() - .map(|(pool, silo_link)| views::SiloSubnetPool { + .map(|(pool, silo_link)| subnet_pool::SiloSubnetPool { identity: pool.identity(), is_default: silo_link.is_default, ip_version: pool.ip_version.into(), @@ -1928,8 +1961,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn subnet_pool_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let opctx = @@ -1938,7 +1971,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let pool_selector = path_params.into_inner().pool; let (pool, silo_link) = nexus.silo_subnet_pool_fetch(&opctx, &pool_selector).await?; - Ok(HttpResponseOk(views::SiloSubnetPool { + Ok(HttpResponseOk(subnet_pool::SiloSubnetPool { identity: pool.identity(), is_default: silo_link.is_default, ip_version: pool.ip_version.into(), @@ -1953,9 +1986,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_silo_link( rqctx: RequestContext, - path_params: Path, - silo_link: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + silo_link: TypedBody, + ) -> Result, HttpError> + { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let silo_link = silo_link.into_inner(); @@ -1969,9 +2003,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_silo_update( rqctx: RequestContext, - path_params: Path, - update: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + update: TypedBody, + ) -> Result, HttpError> + { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let update = update.into_inner(); @@ -1985,7 +2020,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_silo_unlink( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -1997,8 +2032,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_subnet_pool_utilization_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> + { let apictx = rqctx.context(); let handler = async { let opctx = @@ -2020,9 +2056,11 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn external_subnet_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError> - { + query_params: Query>, + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -2052,9 +2090,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn external_subnet_create( rqctx: RequestContext, - query_params: Query, - subnet_params: TypedBody, - ) -> Result, HttpError> { + query_params: Query, + subnet_params: TypedBody, + ) -> Result, HttpError> + { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); let params = subnet_params.into_inner(); @@ -2069,9 +2108,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn external_subnet_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ) -> Result, HttpError> + { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -2079,7 +2119,7 @@ impl NexusExternalApi for NexusExternalApiImpl { crate::context::op_context_for_external_api(&rqctx).await?; let path = path_params.into_inner(); let query = query_params.into_inner(); - let selector = params::ExternalSubnetSelector { + let selector = external_subnet::ExternalSubnetSelector { external_subnet: path.external_subnet, project: query.project, }; @@ -2095,15 +2135,16 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn external_subnet_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - subnet_params: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + subnet_params: TypedBody, + ) -> Result, HttpError> + { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let params = subnet_params.into_inner(); - let selector = params::ExternalSubnetSelector { + let selector = external_subnet::ExternalSubnetSelector { external_subnet: path.external_subnet, project: query.project, }; @@ -2116,13 +2157,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn external_subnet_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let selector = params::ExternalSubnetSelector { + let selector = external_subnet::ExternalSubnetSelector { external_subnet: path.external_subnet, project: query.project, }; @@ -2134,15 +2175,16 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn external_subnet_attach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - attach_params: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + attach_params: TypedBody, + ) -> Result, HttpError> + { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let attach = attach_params.into_inner(); - let selector = params::ExternalSubnetSelector { + let selector = external_subnet::ExternalSubnetSelector { external_subnet: path.external_subnet, project: query.project, }; @@ -2155,13 +2197,14 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn external_subnet_detach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ) -> Result, HttpError> + { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let selector = params::ExternalSubnetSelector { + let selector = external_subnet::ExternalSubnetSelector { external_subnet: path.external_subnet, project: query.project, }; @@ -2175,8 +2218,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn floating_ip_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError> { + query_params: Query>, + ) -> Result>, HttpError> + { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -2206,9 +2250,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn floating_ip_create( rqctx: RequestContext, - query_params: Query, - floating_params: TypedBody, - ) -> Result, HttpError> { + query_params: Query, + floating_params: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let project_selector = query_params.into_inner(); let floating_params = floating_params.into_inner(); @@ -2224,15 +2268,15 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn floating_ip_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - updated_floating_ip: TypedBody, + path_params: Path, + query_params: Query, + updated_floating_ip: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let updated_floating_ip_params = updated_floating_ip.into_inner(); - let floating_ip_selector = params::FloatingIpSelector { + let floating_ip_selector = floating_ip::FloatingIpSelector { project: query.project, floating_ip: path.floating_ip, }; @@ -2252,13 +2296,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn floating_ip_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let floating_ip_selector = params::FloatingIpSelector { + let floating_ip_selector = floating_ip::FloatingIpSelector { floating_ip: path.floating_ip, project: query.project, }; @@ -2272,9 +2316,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn floating_ip_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let opctx = @@ -2282,7 +2326,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let nexus = &apictx.context.nexus; let path = path_params.into_inner(); let query = query_params.into_inner(); - let floating_ip_selector = params::FloatingIpSelector { + let floating_ip_selector = floating_ip::FloatingIpSelector { floating_ip: path.floating_ip, project: query.project, }; @@ -2301,15 +2345,15 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn floating_ip_attach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - target: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + target: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let target = target.into_inner(); - let floating_ip_selector = params::FloatingIpSelector { + let floating_ip_selector = floating_ip::FloatingIpSelector { floating_ip: path.floating_ip, project: query.project, }; @@ -2323,13 +2367,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn floating_ip_detach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let floating_ip_selector = params::FloatingIpSelector { + let floating_ip_selector = floating_ip::FloatingIpSelector { floating_ip: path.floating_ip, project: query.project, }; @@ -2346,7 +2390,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn multicast_group_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -2375,14 +2419,14 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn multicast_group_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let path = path_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let group_selector = params::MulticastGroupSelector { + let group_selector = multicast::MulticastGroupSelector { multicast_group: path.multicast_group, }; let group = apictx @@ -2403,10 +2447,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn multicast_group_member_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, ) -> Result< - HttpResponseOk>, + HttpResponseOk>, HttpError, > { let apictx = rqctx.context(); @@ -2416,7 +2460,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let pag_params = data_page_params_for(&rqctx, &query)?; - let group_selector = params::MulticastGroupSelector { + let group_selector = multicast::MulticastGroupSelector { multicast_group: path.multicast_group, }; let group_lookup = apictx @@ -2435,12 +2479,14 @@ impl NexusExternalApi for NexusExternalApiImpl { .await?; let results = members .into_iter() - .map(views::MulticastGroupMember::try_from) + .map(multicast::MulticastGroupMember::try_from) .collect::, _>>()?; Ok(HttpResponseOk(ScanById::results_page( &query, results, - &|_, member: &views::MulticastGroupMember| member.identity.id, + &|_, member: &multicast::MulticastGroupMember| { + member.identity.id + }, )?)) }; apictx @@ -2454,7 +2500,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn disk_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -2488,8 +2534,8 @@ impl NexusExternalApi for NexusExternalApiImpl { // TODO-correctness See note about instance create. This should be async. async fn disk_create( rqctx: RequestContext, - query_params: Query, - new_disk: TypedBody, + query_params: Query, + new_disk: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); @@ -2505,8 +2551,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn disk_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -2515,10 +2561,8 @@ impl NexusExternalApi for NexusExternalApiImpl { let nexus = &apictx.context.nexus; let path = path_params.into_inner(); let query = query_params.into_inner(); - let disk_selector = params::DiskSelector { - disk: path.disk, - project: query.project, - }; + let disk_selector = + disk::DiskSelector { disk: path.disk, project: query.project }; let disk = nexus.disk_get(&opctx, disk_selector).await?; Ok(HttpResponseOk(disk.into())) }; @@ -2531,16 +2575,14 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn disk_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let disk_selector = params::DiskSelector { - disk: path.disk, - project: query.project, - }; + let disk_selector = + disk::DiskSelector { disk: path.disk, project: query.project }; let disk_lookup = nexus.disk_lookup(&opctx, disk_selector)?; nexus.project_delete_disk(&opctx, &disk_lookup).await?; Ok(HttpResponseDeleted()) @@ -2550,16 +2592,14 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn disk_bulk_write_import_start( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let disk_selector = params::DiskSelector { - disk: path.disk, - project: query.project, - }; + let disk_selector = + disk::DiskSelector { disk: path.disk, project: query.project }; let disk_lookup = nexus.disk_lookup(&opctx, disk_selector)?; nexus.disk_manual_import_start(&opctx, &disk_lookup).await?; Ok(HttpResponseUpdatedNoContent()) @@ -2569,18 +2609,16 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn disk_bulk_write_import( rqctx: RequestContext, - path_params: Path, - query_params: Query, - import_params: TypedBody, + path_params: Path, + query_params: Query, + import_params: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let import_params = import_params.into_inner(); - let disk_selector = params::DiskSelector { - disk: path.disk, - project: query.project, - }; + let disk_selector = + disk::DiskSelector { disk: path.disk, project: query.project }; let disk_lookup = nexus.disk_lookup(&opctx, disk_selector)?; nexus .disk_manual_import(&opctx, &disk_lookup, import_params) @@ -2592,16 +2630,14 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn disk_bulk_write_import_stop( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let disk_selector = params::DiskSelector { - disk: path.disk, - project: query.project, - }; + let disk_selector = + disk::DiskSelector { disk: path.disk, project: query.project }; let disk_lookup = nexus.disk_lookup(&opctx, disk_selector)?; nexus.disk_manual_import_stop(&opctx, &disk_lookup).await?; Ok(HttpResponseUpdatedNoContent()) @@ -2611,18 +2647,16 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn disk_finalize_import( rqctx: RequestContext, - path_params: Path, - query_params: Query, - finalize_params: TypedBody, + path_params: Path, + query_params: Query, + finalize_params: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let finalize_params = finalize_params.into_inner(); - let disk_selector = params::DiskSelector { - disk: path.disk, - project: query.project, - }; + let disk_selector = + disk::DiskSelector { disk: path.disk, project: query.project }; let disk_lookup = nexus.disk_lookup(&opctx, disk_selector)?; nexus .disk_finalize_import(&opctx, &disk_lookup, &finalize_params) @@ -2636,7 +2670,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -2670,8 +2704,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_create( rqctx: RequestContext, - query_params: Query, - new_instance: TypedBody, + query_params: Query, + new_instance: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let project_selector = query_params.into_inner(); @@ -2692,8 +2726,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_view( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; @@ -2702,7 +2736,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let handler = async { let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -2725,13 +2759,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_delete( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -2745,14 +2779,14 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_update( rqctx: RequestContext, - query_params: Query, - path_params: Path, - instance_config: TypedBody, + query_params: Query, + path_params: Path, + instance_config: TypedBody, ) -> Result, HttpError> { let query = query_params.into_inner(); let path = path_params.into_inner(); let instance_config = instance_config.into_inner(); - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -2773,12 +2807,12 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_reboot( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError> { let path = path_params.into_inner(); let query = query_params.into_inner(); - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -2794,12 +2828,12 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_start( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError> { let path = path_params.into_inner(); let query = query_params.into_inner(); - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -2820,12 +2854,12 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_stop( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError> { let path = path_params.into_inner(); let query = query_params.into_inner(); - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -2841,9 +2875,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_serial_console( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> + path_params: Path, + query_params: Query, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -2852,7 +2886,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let nexus = &apictx.context.nexus; let path = path_params.into_inner(); let query = query_params.into_inner(); - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project.clone(), instance: path.instance, }; @@ -2872,8 +2906,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_serial_console_stream( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, conn: WebsocketConnection, ) -> WebsocketChannelResult { let apictx = rqctx.context(); @@ -2881,7 +2915,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let path = path_params.into_inner(); let query = query_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project.clone(), instance: path.instance, }; @@ -2918,9 +2952,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_ssh_public_key_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -2933,7 +2967,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let paginated_by = name_or_id_pagination(&pag_params, scan_params)?; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: scan_params.selector.project.clone(), instance: path.instance, }; @@ -2961,9 +2995,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_disk_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, - path_params: Path, + path_params: Path, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -2975,7 +3009,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let paginated_by = name_or_id_pagination(&pag_params, scan_params)?; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: scan_params.selector.project.clone(), instance: path.instance, }; @@ -3002,15 +3036,15 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_disk_attach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - disk_to_attach: TypedBody, + path_params: Path, + query_params: Query, + disk_to_attach: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let disk = disk_to_attach.into_inner().disk; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -3026,15 +3060,15 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_disk_detach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - disk_to_detach: TypedBody, + path_params: Path, + query_params: Query, + disk_to_detach: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let disk = disk_to_detach.into_inner().disk; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -3051,10 +3085,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_affinity_group_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, - path_params: Path, - ) -> Result>, HttpError> + path_params: Path, + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -3066,7 +3100,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let paginated_by = name_or_id_pagination(&pag_params, scan_params)?; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: scan_params.selector.project.clone(), instance: path.instance, }; @@ -3098,11 +3132,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_anti_affinity_group_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, - path_params: Path, - ) -> Result>, HttpError> - { + path_params: Path, + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -3113,7 +3149,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let paginated_by = name_or_id_pagination(&pag_params, scan_params)?; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: scan_params.selector.project.clone(), instance: path.instance, }; @@ -3146,8 +3182,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn affinity_group_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError> + query_params: Query>, + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -3178,9 +3214,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn affinity_group_view( rqctx: RequestContext, - query_params: Query, - path_params: Path, - ) -> Result, HttpError> { + query_params: Query, + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -3189,7 +3225,7 @@ impl NexusExternalApi for NexusExternalApiImpl { crate::context::op_context_for_external_api(&rqctx).await?; let query = query_params.into_inner(); - let group_selector = params::AffinityGroupSelector { + let group_selector = affinity::AffinityGroupSelector { affinity_group: path.affinity_group, project: query.project.clone(), }; @@ -3211,9 +3247,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn affinity_group_member_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, - path_params: Path, + path_params: Path, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -3227,7 +3263,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let scan_params = ScanByNameOrId::from_query(&query)?; let paginated_by = name_or_id_pagination(&pag_params, scan_params)?; - let group_selector = params::AffinityGroupSelector { + let group_selector = affinity::AffinityGroupSelector { project: scan_params.selector.project.clone(), affinity_group: path.affinity_group, }; @@ -3255,8 +3291,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn affinity_group_member_instance_view( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -3267,7 +3303,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let query = query_params.into_inner(); // Select group - let group_selector = params::AffinityGroupSelector { + let group_selector = affinity::AffinityGroupSelector { affinity_group: path.affinity_group, project: query.project.clone(), }; @@ -3275,7 +3311,7 @@ impl NexusExternalApi for NexusExternalApiImpl { nexus.affinity_group_lookup(&opctx, group_selector)?; // Select instance - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -3301,20 +3337,20 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn affinity_group_member_instance_add( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let group_selector = params::AffinityGroupSelector { + let group_selector = affinity::AffinityGroupSelector { affinity_group: path.affinity_group, project: query.project.clone(), }; let group_lookup = nexus.affinity_group_lookup(&opctx, group_selector)?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -3335,20 +3371,20 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn affinity_group_member_instance_delete( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let group_selector = params::AffinityGroupSelector { + let group_selector = affinity::AffinityGroupSelector { affinity_group: path.affinity_group, project: query.project.clone(), }; let group_lookup = nexus.affinity_group_lookup(&opctx, group_selector)?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -3368,9 +3404,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn affinity_group_create( rqctx: RequestContext, - query_params: Query, - new_affinity_group_params: TypedBody, - ) -> Result, HttpError> { + query_params: Query, + new_affinity_group_params: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); let new_affinity_group = new_affinity_group_params.into_inner(); @@ -3389,15 +3425,15 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn affinity_group_update( rqctx: RequestContext, - query_params: Query, - path_params: Path, - updated_group: TypedBody, - ) -> Result, HttpError> { + query_params: Query, + path_params: Path, + updated_group: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let updates = updated_group.into_inner(); - let group_selector = params::AffinityGroupSelector { + let group_selector = affinity::AffinityGroupSelector { project: query.project, affinity_group: path.affinity_group, }; @@ -3413,13 +3449,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn affinity_group_delete( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let group_selector = params::AffinityGroupSelector { + let group_selector = affinity::AffinityGroupSelector { project: query.project, affinity_group: path.affinity_group, }; @@ -3433,9 +3469,11 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn anti_affinity_group_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError> - { + query_params: Query>, + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -3469,9 +3507,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn anti_affinity_group_view( rqctx: RequestContext, - query_params: Query, - path_params: Path, - ) -> Result, HttpError> { + query_params: Query, + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -3480,7 +3518,7 @@ impl NexusExternalApi for NexusExternalApiImpl { crate::context::op_context_for_external_api(&rqctx).await?; let query = query_params.into_inner(); - let group_selector = params::AntiAffinityGroupSelector { + let group_selector = affinity::AntiAffinityGroupSelector { anti_affinity_group: path.anti_affinity_group, project: query.project.clone(), }; @@ -3502,9 +3540,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn anti_affinity_group_member_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, - path_params: Path, + path_params: Path, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -3518,7 +3556,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let scan_params = ScanByNameOrId::from_query(&query)?; let paginated_by = name_or_id_pagination(&pag_params, scan_params)?; - let group_selector = params::AntiAffinityGroupSelector { + let group_selector = affinity::AntiAffinityGroupSelector { project: scan_params.selector.project.clone(), anti_affinity_group: path.anti_affinity_group, }; @@ -3546,8 +3584,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn anti_affinity_group_member_instance_view( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -3558,7 +3596,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let query = query_params.into_inner(); // Select group - let group_selector = params::AntiAffinityGroupSelector { + let group_selector = affinity::AntiAffinityGroupSelector { anti_affinity_group: path.anti_affinity_group, project: query.project.clone(), }; @@ -3566,7 +3604,7 @@ impl NexusExternalApi for NexusExternalApiImpl { nexus.anti_affinity_group_lookup(&opctx, group_selector)?; // Select instance - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -3592,20 +3630,20 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn anti_affinity_group_member_instance_add( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let group_selector = params::AntiAffinityGroupSelector { + let group_selector = affinity::AntiAffinityGroupSelector { anti_affinity_group: path.anti_affinity_group, project: query.project.clone(), }; let group_lookup = nexus.anti_affinity_group_lookup(&opctx, group_selector)?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -3626,20 +3664,20 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn anti_affinity_group_member_instance_delete( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let group_selector = params::AntiAffinityGroupSelector { + let group_selector = affinity::AntiAffinityGroupSelector { anti_affinity_group: path.anti_affinity_group, project: query.project.clone(), }; let group_lookup = nexus.anti_affinity_group_lookup(&opctx, group_selector)?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -3660,11 +3698,12 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn anti_affinity_group_create( rqctx: RequestContext, - query_params: Query, + query_params: Query, new_anti_affinity_group_params: TypedBody< - params::AntiAffinityGroupCreate, + affinity::AntiAffinityGroupCreate, >, - ) -> Result, HttpError> { + ) -> Result, HttpError> + { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); let new_anti_affinity_group = @@ -3684,15 +3723,15 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn anti_affinity_group_update( rqctx: RequestContext, - query_params: Query, - path_params: Path, - updated_group: TypedBody, - ) -> Result, HttpError> { + query_params: Query, + path_params: Path, + updated_group: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let updates = updated_group.into_inner(); - let group_selector = params::AntiAffinityGroupSelector { + let group_selector = affinity::AntiAffinityGroupSelector { project: query.project, anti_affinity_group: path.anti_affinity_group, }; @@ -3708,13 +3747,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn anti_affinity_group_delete( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let group_selector = params::AntiAffinityGroupSelector { + let group_selector = affinity::AntiAffinityGroupSelector { project: query.project, anti_affinity_group: path.anti_affinity_group, }; @@ -3762,7 +3801,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn certificate_create( rqctx: RequestContext, - new_cert: TypedBody, + new_cert: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let new_cert_params = new_cert.into_inner(); @@ -3775,7 +3814,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn certificate_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -3798,7 +3837,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn certificate_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -3815,7 +3854,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_address_lot_create( rqctx: RequestContext, - new_address_lot: TypedBody, + new_address_lot: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let params = new_address_lot.into_inner(); @@ -3830,7 +3869,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_address_lot_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -3860,7 +3899,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_address_lot_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -3907,7 +3946,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_address_lot_block_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -3946,7 +3985,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_loopback_address_create( rqctx: RequestContext, - new_loopback_address: TypedBody, + new_loopback_address: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let params = new_loopback_address.into_inner(); @@ -3959,7 +3998,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_loopback_address_delete( rqctx: RequestContext, - path: Path, + path: Path, ) -> Result { let path = path.into_inner(); let addr = match IpNetwork::new(path.address, path.subnet_mask) { @@ -4016,7 +4055,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_settings_create( rqctx: RequestContext, - new_settings: TypedBody, + new_settings: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let params = new_settings.into_inner(); @@ -4030,7 +4069,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_settings_delete( rqctx: RequestContext, - query_params: Query, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let selector = query_params.into_inner(); @@ -4043,7 +4082,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_settings_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, ) -> Result< HttpResponseOk>, @@ -4080,7 +4119,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_settings_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -4101,7 +4140,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -4132,9 +4171,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_status( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -4161,9 +4200,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_apply_settings( rqctx: RequestContext, - path_params: Path, - query_params: Query, - settings_body: TypedBody, + path_params: Path, + query_params: Query, + settings_body: TypedBody, ) -> Result { let port = path_params.into_inner().port; audit_and_time(&rqctx, |opctx, nexus| async move { @@ -4179,8 +4218,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_clear_settings( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { let port = path_params.into_inner().port; audit_and_time(&rqctx, |opctx, nexus| async move { @@ -4193,8 +4232,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_lldp_config_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -4222,8 +4261,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_lldp_config_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, config: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { @@ -4246,7 +4285,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_switch_port_lldp_neighbors( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -4284,36 +4323,9 @@ impl NexusExternalApi for NexusExternalApiImpl { .await } - async fn v2026010300_networking_bgp_config_create( - rqctx: RequestContext, - config: TypedBody, - ) -> Result, HttpError> { - audit_and_time(&rqctx, |opctx, nexus| async move { - let old = config.into_inner(); - let config = params::BgpConfigCreate { - identity: old.identity, - asn: old.asn, - vrf: old.vrf, - bgp_announce_set_id: old.bgp_announce_set_id, - shaper: old.shaper, - checker: old.checker, - max_paths: Default::default(), - }; - let new: BgpConfig = - nexus.bgp_config_create(&opctx, &config).await?.try_into()?; - let result = v2026020600::BgpConfig { - identity: new.identity, - asn: new.asn, - vrf: new.vrf, - }; - Ok(HttpResponseCreated(result)) - }) - .await - } - async fn networking_bgp_config_create( rqctx: RequestContext, - config: TypedBody, + config: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let config = config.into_inner(); @@ -4323,44 +4335,6 @@ impl NexusExternalApi for NexusExternalApiImpl { .await } - async fn v2026010300_networking_bgp_config_list( - rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError> - { - let apictx = rqctx.context(); - let handler = async { - let nexus = &apictx.context.nexus; - let query = query_params.into_inner(); - let pag_params = data_page_params_for(&rqctx, &query)?; - let scan_params = ScanByNameOrId::from_query(&query)?; - let paginated_by = name_or_id_pagination(&pag_params, scan_params)?; - let opctx = - crate::context::op_context_for_external_api(&rqctx).await?; - let configs = nexus - .bgp_config_list(&opctx, &paginated_by) - .await? - .into_iter() - .map(|p| v2026020600::BgpConfig { - identity: p.identity(), - asn: *p.asn, - vrf: p.vrf, - }) - .collect(); - - Ok(HttpResponseOk(ScanByNameOrId::results_page( - &query, - configs, - &marker_for_name_or_id, - )?)) - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - async fn networking_bgp_config_list( rqctx: RequestContext, query_params: Query, @@ -4412,24 +4386,6 @@ impl NexusExternalApi for NexusExternalApiImpl { .await } - //TODO pagination? the normal by-name/by-id stuff does not work here - async fn v2026020600_networking_bgp_exported( - rqctx: RequestContext, - ) -> Result, HttpError> { - let apictx = rqctx.context(); - let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let handler = async { - let nexus = &apictx.context.nexus; - let result = nexus.bgp_exported(&opctx).await?.into(); - Ok(HttpResponseOk(result)) - }; - apictx - .context - .external_latencies - .instrument_dropshot_handler(&rqctx, handler) - .await - } - async fn networking_bgp_exported( rqctx: RequestContext, ) -> Result>, HttpError> { @@ -4449,7 +4405,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_bgp_message_history( rqctx: RequestContext, - query_params: Query, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; @@ -4469,19 +4425,19 @@ impl NexusExternalApi for NexusExternalApiImpl { //TODO pagination? the normal by-name/by-id stuff does not work here async fn networking_bgp_imported_routes_ipv4( rqctx: RequestContext, - query_params: Query, - ) -> Result>, HttpError> - { + query_params: Query, + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let handler = async { let nexus = &apictx.context.nexus; let sel = query_params.into_inner(); let all_routes = nexus.bgp_imported_routes(&opctx, &sel).await?; - let result: Vec = all_routes - .into_iter() - .flat_map(|r| r.try_into().ok()) - .collect(); + let result: Vec = + all_routes.into_iter().flat_map(|r| r.try_into().ok()).collect(); Ok(HttpResponseOk(result)) }; apictx @@ -4493,7 +4449,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_bgp_imported( rqctx: RequestContext, - query_params: Query, + query_params: Query, ) -> Result>, HttpError> { let apictx = rqctx.context(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; @@ -4512,7 +4468,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_bgp_config_delete( rqctx: RequestContext, - sel: Query, + sel: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let sel = sel.into_inner(); @@ -4524,7 +4480,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_bgp_announce_set_update( rqctx: RequestContext, - config: TypedBody, + config: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let config = config.into_inner(); @@ -4564,7 +4520,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_bgp_announce_set_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let sel = path_params.into_inner(); @@ -4576,7 +4532,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_bgp_announcement_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -4603,7 +4559,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_bfd_enable( rqctx: RequestContext, - session: TypedBody, + session: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let session = session.into_inner(); @@ -4616,7 +4572,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_bfd_disable( rqctx: RequestContext, - session: TypedBody, + session: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let session = session.into_inner(); @@ -4648,7 +4604,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_allow_list_view( rqctx: RequestContext, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -4669,8 +4625,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn networking_allow_list_update( rqctx: RequestContext, - params: TypedBody, - ) -> Result, HttpError> { + params: TypedBody, + ) -> Result, HttpError> { let server_kind = rqctx.context().kind; let remote_addr = rqctx.request.remote_addr().ip(); audit_and_time(&rqctx, |opctx, nexus| async move { @@ -4725,7 +4681,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn image_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -4741,7 +4697,7 @@ impl NexusExternalApi for NexusExternalApiImpl { Some(project) => { let project_lookup = nexus.project_lookup( &opctx, - params::ProjectSelector { project }, + project::ProjectSelector { project }, )?; ImageParentLookup::Project(project_lookup) } @@ -4771,8 +4727,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn image_create( rqctx: RequestContext, - query_params: Query, - new_image: TypedBody, + query_params: Query, + new_image: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); @@ -4781,7 +4737,7 @@ impl NexusExternalApi for NexusExternalApiImpl { Some(project) => { let project_lookup = nexus.project_lookup( &opctx, - params::ProjectSelector { project }, + project::ProjectSelector { project }, )?; ImageParentLookup::Project(project_lookup) } @@ -4799,8 +4755,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn image_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -4812,7 +4768,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let image: nexus_db_model::Image = match nexus .image_lookup( &opctx, - params::ImageSelector { + image::ImageSelector { image: path.image, project: query.project, }, @@ -4839,8 +4795,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn image_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -4848,7 +4804,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let image_lookup = nexus .image_lookup( &opctx, - params::ImageSelector { + image::ImageSelector { image: path.image, project: query.project, }, @@ -4862,8 +4818,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn image_promote( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -4871,7 +4827,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let image_lookup = nexus .image_lookup( &opctx, - params::ImageSelector { + image::ImageSelector { image: path.image, project: query.project, }, @@ -4885,8 +4841,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn image_demote( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -4894,7 +4850,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let image_lookup = nexus .image_lookup( &opctx, - params::ImageSelector { image: path.image, project: None }, + image::ImageSelector { image: path.image, project: None }, ) .await?; let project_lookup = nexus.project_lookup(&opctx, query)?; @@ -4908,7 +4864,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_network_interface_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -4947,8 +4903,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_network_interface_create( rqctx: RequestContext, - query_params: Query, - interface_params: TypedBody, + query_params: Query, + interface_params: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); @@ -4968,17 +4924,18 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_network_interface_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let interface_selector = params::InstanceNetworkInterfaceSelector { - project: query.project, - instance: query.instance, - network_interface: path.interface, - }; + let interface_selector = + instance::InstanceNetworkInterfaceSelector { + project: query.project, + instance: query.instance, + network_interface: path.interface, + }; let interface_lookup = nexus.instance_network_interface_lookup( &opctx, interface_selector, @@ -4993,8 +4950,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_network_interface_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -5003,11 +4960,12 @@ impl NexusExternalApi for NexusExternalApiImpl { let nexus = &apictx.context.nexus; let path = path_params.into_inner(); let query = query_params.into_inner(); - let interface_selector = params::InstanceNetworkInterfaceSelector { - project: query.project, - instance: query.instance, - network_interface: path.interface, - }; + let interface_selector = + instance::InstanceNetworkInterfaceSelector { + project: query.project, + instance: query.instance, + network_interface: path.interface, + }; let (.., interface) = nexus .instance_network_interface_lookup(&opctx, interface_selector)? .fetch() @@ -5023,16 +4981,16 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_network_interface_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - updated_iface: TypedBody, + path_params: Path, + query_params: Query, + updated_iface: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let updated_iface = updated_iface.into_inner(); let network_interface_selector = - params::InstanceNetworkInterfaceSelector { + instance::InstanceNetworkInterfaceSelector { project: query.project, instance: query.instance, network_interface: path.interface, @@ -5058,9 +5016,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_external_ip_list( rqctx: RequestContext, - query_params: Query, - path_params: Path, - ) -> Result>, HttpError> { + query_params: Query, + path_params: Path, + ) -> Result>, HttpError> + { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -5068,7 +5027,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let query = query_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -5088,24 +5047,26 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_ephemeral_ip_attach( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ip_to_create: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ip_to_create: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let params::EphemeralIpCreate { pool_selector } = + let instance::EphemeralIpCreate { pool_selector } = ip_to_create.into_inner(); - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; let instance_lookup = nexus.instance_lookup(&opctx, instance_selector)?; let (pool, ip_version) = match pool_selector { - params::PoolSelector::Explicit { pool } => (Some(pool), None), - params::PoolSelector::Auto { ip_version } => (None, ip_version), + ip_pool::PoolSelector::Explicit { pool } => (Some(pool), None), + ip_pool::PoolSelector::Auto { ip_version } => { + (None, ip_version) + } }; let ip = nexus .instance_attach_ephemeral_ip( @@ -5122,13 +5083,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_ephemeral_ip_detach( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -5138,7 +5099,7 @@ impl NexusExternalApi for NexusExternalApiImpl { .instance_detach_external_ip( &opctx, &instance_lookup, - ¶ms::ExternalIpDetach::Ephemeral { + &instance::ExternalIpDetach::Ephemeral { ip_version: query.ip_version, }, ) @@ -5152,10 +5113,12 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_external_subnet_list( rqctx: RequestContext, - query_params: Query, - path_params: Path, - ) -> Result>, HttpError> - { + query_params: Query, + path_params: Path, + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -5163,7 +5126,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let query = query_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: query.project, instance: path.instance, }; @@ -5188,10 +5151,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_multicast_group_list( rqctx: RequestContext, - query_params: Query>, - path_params: Path, + query_params: Query>, + path_params: Path, ) -> Result< - HttpResponseOk>, + HttpResponseOk>, HttpError, > { let apictx = rqctx.context(); @@ -5206,7 +5169,7 @@ impl NexusExternalApi for NexusExternalApiImpl { // Note: When instance is specified by UUID, project should be `None` // (UUIDs are globally unique). Project is only needed for name-based lookup. - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: match &path.instance { NameOrId::Name(_) => scan_params.selector.project.clone(), NameOrId::Id(_) => None, @@ -5224,12 +5187,14 @@ impl NexusExternalApi for NexusExternalApiImpl { .await?; let results = members .into_iter() - .map(views::MulticastGroupMember::try_from) + .map(multicast::MulticastGroupMember::try_from) .collect::, _>>()?; Ok(HttpResponseOk(ScanById::results_page( &query, results, - &|_, member: &views::MulticastGroupMember| member.identity.id, + &|_, member: &multicast::MulticastGroupMember| { + member.identity.id + }, )?)) }; apictx @@ -5241,16 +5206,16 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_multicast_group_join( rqctx: RequestContext, - path_params: Path, - query_params: Query, - body_params: TypedBody, - ) -> Result, HttpError> + path_params: Path, + query_params: Query, + body_params: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let body = body_params.into_inner(); - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: match &path.instance { NameOrId::Name(_) => query.project.clone(), NameOrId::Id(_) => None, @@ -5268,7 +5233,7 @@ impl NexusExternalApi for NexusExternalApiImpl { body.ip_version, ) .await?; - Ok(HttpResponseCreated(views::MulticastGroupMember::try_from( + Ok(HttpResponseCreated(multicast::MulticastGroupMember::try_from( result, )?)) }) @@ -5277,13 +5242,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn instance_multicast_group_leave( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: match &path.instance { NameOrId::Name(_) => query.project.clone(), NameOrId::Id(_) => None, @@ -5292,7 +5257,7 @@ impl NexusExternalApi for NexusExternalApiImpl { }; let instance_lookup = nexus.instance_lookup(&opctx, instance_selector)?; - let group_selector = params::MulticastGroupSelector { + let group_selector = multicast::MulticastGroupSelector { multicast_group: path.multicast_group, }; let group_lookup = @@ -5312,20 +5277,24 @@ impl NexusExternalApi for NexusExternalApiImpl { // Cannot delegate to lib.rs: old API version has no body parameter, but the // new `instance_multicast_group_join` requires `TypedBody`. // TypedBody has no public constructor, so we can't create a default body for delegation. - async fn v2025121200_instance_multicast_group_join( + async fn instance_multicast_group_join_v2025_11_20_00( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> - { + path_params: Path< + v2025_11_20_00::multicast::InstanceMulticastGroupPath, + >, + query_params: Query, + ) -> Result< + HttpResponseCreated, + HttpError, + > { let apictx = rqctx.context(); let handler = async { - let path: params::InstanceMulticastGroupPath = + let path: multicast::InstanceMulticastGroupPath = path_params.into_inner().into(); let query = query_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let instance_selector = params::InstanceSelector { + let instance_selector = instance::InstanceSelector { project: match &path.instance { NameOrId::Name(_) => query.project.clone(), NameOrId::Id(_) => None, @@ -5347,7 +5316,7 @@ impl NexusExternalApi for NexusExternalApiImpl { None, // Old API version doesn't support ip_version ) .await?; - let member = views::MulticastGroupMember::try_from(result)?; + let member = multicast::MulticastGroupMember::try_from(result)?; Ok(HttpResponseCreated(member.into())) }; apictx @@ -5361,7 +5330,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn snapshot_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -5395,8 +5364,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn snapshot_create( rqctx: RequestContext, - query_params: Query, - new_snapshot: TypedBody, + query_params: Query, + new_snapshot: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); @@ -5412,8 +5381,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn snapshot_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -5422,7 +5391,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let nexus = &apictx.context.nexus; let path = path_params.into_inner(); let query = query_params.into_inner(); - let snapshot_selector = params::SnapshotSelector { + let snapshot_selector = snapshot::SnapshotSelector { project: query.project, snapshot: path.snapshot, }; @@ -5441,13 +5410,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn snapshot_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let snapshot_selector = params::SnapshotSelector { + let snapshot_selector = snapshot::SnapshotSelector { project: query.project, snapshot: path.snapshot, }; @@ -5463,7 +5432,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -5498,8 +5467,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_create( rqctx: RequestContext, - query_params: Query, - body: TypedBody, + query_params: Query, + body: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); @@ -5515,8 +5484,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -5526,7 +5495,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let opctx = crate::context::op_context_for_external_api(&rqctx).await?; let vpc_selector = - params::VpcSelector { project: query.project, vpc: path.vpc }; + vpc::VpcSelector { project: query.project, vpc: path.vpc }; let (.., vpc) = nexus.vpc_lookup(&opctx, vpc_selector)?.fetch().await?; Ok(HttpResponseOk(vpc.into())) @@ -5540,16 +5509,16 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - updated_vpc: TypedBody, + path_params: Path, + query_params: Query, + updated_vpc: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let updated_vpc_params = updated_vpc.into_inner(); let vpc_selector = - params::VpcSelector { project: query.project, vpc: path.vpc }; + vpc::VpcSelector { project: query.project, vpc: path.vpc }; let vpc_lookup = nexus.vpc_lookup(&opctx, vpc_selector)?; let vpc = nexus .project_update_vpc(&opctx, &vpc_lookup, &updated_vpc_params) @@ -5561,14 +5530,14 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let vpc_selector = - params::VpcSelector { project: query.project, vpc: path.vpc }; + vpc::VpcSelector { project: query.project, vpc: path.vpc }; let vpc_lookup = nexus.vpc_lookup(&opctx, vpc_selector)?; nexus.project_delete_vpc(&opctx, &vpc_lookup).await?; Ok(HttpResponseDeleted()) @@ -5578,7 +5547,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_subnet_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -5612,8 +5581,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_subnet_create( rqctx: RequestContext, - query_params: Query, - create_params: TypedBody, + query_params: Query, + create_params: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); @@ -5628,8 +5597,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_subnet_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -5638,7 +5607,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let query = query_params.into_inner(); let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let subnet_selector = params::SubnetSelector { + let subnet_selector = vpc::SubnetSelector { project: query.project, vpc: query.vpc, subnet: path.subnet, @@ -5658,13 +5627,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_subnet_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let subnet_selector = params::SubnetSelector { + let subnet_selector = vpc::SubnetSelector { project: query.project, vpc: query.vpc, subnet: path.subnet, @@ -5679,15 +5648,15 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_subnet_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - subnet_params: TypedBody, + path_params: Path, + query_params: Query, + subnet_params: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let subnet_params = subnet_params.into_inner(); - let subnet_selector = params::SubnetSelector { + let subnet_selector = vpc::SubnetSelector { project: query.project, vpc: query.vpc, subnet: path.subnet, @@ -5708,8 +5677,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_subnet_list_network_interfaces( rqctx: RequestContext, - path_params: Path, - query_params: Query>, + path_params: Path, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -5722,7 +5691,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let paginated_by = name_or_id_pagination(&pag_params, scan_params)?; let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let subnet_selector = params::SubnetSelector { + let subnet_selector = vpc::SubnetSelector { project: scan_params.selector.project.clone(), vpc: scan_params.selector.vpc.clone(), subnet: path.subnet, @@ -5756,7 +5725,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_firewall_rules_view( rqctx: RequestContext, - query_params: Query, + query_params: Query, ) -> Result, HttpError> { // TODO: Check If-Match and fail if the ETag doesn't match anymore. // Without this check, if firewall rules change while someone is listing @@ -5786,7 +5755,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_firewall_rules_update( rqctx: RequestContext, - query_params: Query, + query_params: Query, router_params: TypedBody, ) -> Result, HttpError> { // TODO: Check If-Match and fail if the ETag doesn't match anymore. @@ -5809,7 +5778,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_router_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -5843,8 +5812,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_router_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; @@ -5853,7 +5822,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let handler = async { let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let router_selector = params::RouterSelector { + let router_selector = vpc::RouterSelector { project: query.project, vpc: query.vpc, router: path.router, @@ -5873,8 +5842,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_router_create( rqctx: RequestContext, - query_params: Query, - create_params: TypedBody, + query_params: Query, + create_params: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); @@ -5895,13 +5864,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_router_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let router_selector = params::RouterSelector { + let router_selector = vpc::RouterSelector { project: query.project, vpc: query.vpc, router: path.router, @@ -5916,15 +5885,15 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_router_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - router_params: TypedBody, + path_params: Path, + query_params: Query, + router_params: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let router_params = router_params.into_inner(); - let router_selector = params::RouterSelector { + let router_selector = vpc::RouterSelector { project: query.project, vpc: query.vpc, router: path.router, @@ -5941,7 +5910,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_router_route_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -5977,8 +5946,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_router_route_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; @@ -5987,7 +5956,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let handler = async { let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let route_selector = params::RouteSelector { + let route_selector = vpc::RouteSelector { project: query.project, vpc: query.vpc, router: query.router, @@ -6008,8 +5977,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_router_route_create( rqctx: RequestContext, - query_params: Query, - create_params: TypedBody, + query_params: Query, + create_params: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); @@ -6030,13 +5999,13 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_router_route_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let route_selector = params::RouteSelector { + let route_selector = vpc::RouteSelector { project: query.project, vpc: query.vpc, router: query.router, @@ -6052,15 +6021,15 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn vpc_router_route_update( rqctx: RequestContext, - path_params: Path, - query_params: Query, - router_params: TypedBody, + path_params: Path, + query_params: Query, + router_params: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); let router_params = router_params.into_inner(); - let route_selector = params::RouteSelector { + let route_selector = vpc::RouteSelector { project: query.project, vpc: query.vpc, router: query.router, @@ -6081,9 +6050,11 @@ impl NexusExternalApi for NexusExternalApiImpl { /// List internet gateways async fn internet_gateway_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError> - { + query_params: Query>, + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let opctx = @@ -6117,9 +6088,10 @@ impl NexusExternalApi for NexusExternalApiImpl { /// Fetch internet gateway async fn internet_gateway_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ) -> Result, HttpError> + { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; let path = path_params.into_inner(); @@ -6127,7 +6099,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let handler = async { let opctx = crate::context::op_context_for_external_api(&rqctx).await?; - let selector = params::InternetGatewaySelector { + let selector = internet_gateway::InternetGatewaySelector { project: query.project, vpc: query.vpc, gateway: path.gateway, @@ -6148,9 +6120,10 @@ impl NexusExternalApi for NexusExternalApiImpl { /// Create VPC internet gateway async fn internet_gateway_create( rqctx: RequestContext, - query_params: Query, - create_params: TypedBody, - ) -> Result, HttpError> { + query_params: Query, + create_params: TypedBody, + ) -> Result, HttpError> + { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); let create = create_params.into_inner(); @@ -6166,13 +6139,13 @@ impl NexusExternalApi for NexusExternalApiImpl { /// Delete internet gateway async fn internet_gateway_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let selector = params::InternetGatewaySelector { + let selector = internet_gateway::InternetGatewaySelector { project: query.project, vpc: query.vpc, gateway: path.gateway, @@ -6190,10 +6163,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn internet_gateway_ip_pool_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, ) -> Result< - HttpResponseOk>, + HttpResponseOk>, HttpError, > { let apictx = rqctx.context(); @@ -6231,10 +6204,12 @@ impl NexusExternalApi for NexusExternalApiImpl { /// Attach an IP pool to an internet gateway async fn internet_gateway_ip_pool_create( rqctx: RequestContext, - query_params: Query, - create_params: TypedBody, - ) -> Result, HttpError> - { + query_params: Query, + create_params: TypedBody, + ) -> Result< + HttpResponseCreated, + HttpError, + > { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); let create = create_params.into_inner(); @@ -6250,13 +6225,15 @@ impl NexusExternalApi for NexusExternalApiImpl { /// Detach an IP pool from an internet gateway async fn internet_gateway_ip_pool_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query< + internet_gateway::DeleteInternetGatewayElementSelector, + >, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let selector = params::InternetGatewayIpPoolSelector { + let selector = internet_gateway::InternetGatewayIpPoolSelector { project: query.project, vpc: query.vpc, gateway: query.gateway, @@ -6276,10 +6253,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn internet_gateway_ip_address_list( rqctx: RequestContext, query_params: Query< - PaginatedByNameOrId, + PaginatedByNameOrId, >, ) -> Result< - HttpResponseOk>, + HttpResponseOk>, HttpError, > { let apictx = rqctx.context(); @@ -6321,10 +6298,14 @@ impl NexusExternalApi for NexusExternalApiImpl { /// Attach an IP address to an internet gateway async fn internet_gateway_ip_address_create( rqctx: RequestContext, - query_params: Query, - create_params: TypedBody, - ) -> Result, HttpError> - { + query_params: Query, + create_params: TypedBody< + internet_gateway::InternetGatewayIpAddressCreate, + >, + ) -> Result< + HttpResponseCreated, + HttpError, + > { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query_params.into_inner(); let create = create_params.into_inner(); @@ -6340,13 +6321,15 @@ impl NexusExternalApi for NexusExternalApiImpl { /// Detach an IP address from an internet gateway async fn internet_gateway_ip_address_delete( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query< + internet_gateway::DeleteInternetGatewayElementSelector, + >, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let query = query_params.into_inner(); - let selector = params::InternetGatewayIpAddressSelector { + let selector = internet_gateway::InternetGatewayIpAddressSelector { project: query.project, vpc: query.vpc, gateway: query.gateway, @@ -6372,8 +6355,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn rack_membership_add_sleds( rqctx: RequestContext, - path_params: Path, - req: TypedBody, + path_params: Path, + req: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let req = req.into_inner(); @@ -6388,7 +6371,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn rack_membership_abort( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let rack_id = @@ -6401,8 +6384,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn rack_membership_status( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; @@ -6466,7 +6449,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn rack_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; @@ -6487,8 +6470,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn sled_list_uninitialized( rqctx: RequestContext, query: Query>, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); // We don't actually support real pagination let pag_params = query.into_inner(); @@ -6515,12 +6500,12 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn sled_add( rqctx: RequestContext, - sled: TypedBody, - ) -> Result, HttpError> { + sled: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let sled = sled.into_inner(); let id = nexus.sled_add(&opctx, sled).await?; - Ok(HttpResponseCreated(views::SledId { id })) + Ok(HttpResponseCreated(sled::SledId { id })) }) .await } @@ -6558,7 +6543,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn sled_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; @@ -6579,9 +6564,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn sled_set_provision_policy( rqctx: RequestContext, - path_params: Path, - new_provision_state: TypedBody, - ) -> Result, HttpError> + path_params: Path, + new_provision_state: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -6591,7 +6576,7 @@ impl NexusExternalApi for NexusExternalApiImpl { .sled_set_provision_policy(&opctx, &sled_lookup, new_state) .await?; let response = - params::SledProvisionPolicyResponse { old_state, new_state }; + sled::SledProvisionPolicyResponse { old_state, new_state }; Ok(HttpResponseOk(response)) }) .await @@ -6599,9 +6584,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn sled_instance_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError> + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -6624,7 +6609,7 @@ impl NexusExternalApi for NexusExternalApiImpl { Ok(HttpResponseOk(ScanById::results_page( &query, sled_instances, - &|_, sled_instance: &views::SledInstance| { + &|_, sled_instance: &sled::SledInstance| { sled_instance.identity.id }, )?)) @@ -6672,7 +6657,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn physical_disk_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -6697,7 +6682,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn switch_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -6713,7 +6698,7 @@ impl NexusExternalApi for NexusExternalApiImpl { Ok(HttpResponseOk(ScanById::results_page( &query, switches, - &|_, switch: &views::Switch| switch.identity.id, + &|_, switch: &switch::Switch| switch.identity.id, )?)) }; apictx @@ -6725,8 +6710,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn switch_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; let path = path_params.into_inner(); @@ -6736,7 +6721,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let (.., switch) = nexus .switch_lookup( &opctx, - params::SwitchSelector { switch: path.switch_id }, + sled::SwitchSelector { switch: path.switch_id }, )? .fetch() .await?; @@ -6751,7 +6736,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn sled_physical_disk_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -6790,9 +6775,12 @@ impl NexusExternalApi for NexusExternalApiImpl { rqctx: RequestContext, path_params: Path, pag_params: Query< - PaginationParams, + PaginationParams< + metrics::ResourceMetrics, + metrics::ResourceMetrics, + >, >, - other_params: Query, + other_params: Query, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -6832,9 +6820,12 @@ impl NexusExternalApi for NexusExternalApiImpl { rqctx: RequestContext, path_params: Path, pag_params: Query< - PaginationParams, + PaginationParams< + metrics::ResourceMetrics, + metrics::ResourceMetrics, + >, >, - other_params: Query, + other_params: Query, ) -> Result>, HttpError> { let apictx = rqctx.context(); @@ -6848,7 +6839,7 @@ impl NexusExternalApi for NexusExternalApiImpl { crate::context::op_context_for_external_api(&rqctx).await?; let project_lookup = match project_selector { Some(project) => { - let project_selector = params::ProjectSelector { project }; + let project_selector = project::ProjectSelector { project }; Some(nexus.project_lookup(&opctx, project_selector)?) } _ => None, @@ -6902,8 +6893,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_timeseries_query( rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError> { + body: TypedBody, + ) -> Result, HttpError> { // Not audited: this is a read-only query that uses POST only because // the query is too large to fit in a URL. let apictx = rqctx.context(); @@ -6918,7 +6909,7 @@ impl NexusExternalApi for NexusExternalApiImpl { .timeseries_query(&opctx, &query) .await .map(|result| { - HttpResponseOk(views::OxqlQueryResult { + HttpResponseOk(oxql::OxqlQueryResult { tables: result .tables .into_iter() @@ -6944,9 +6935,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn timeseries_query( rqctx: RequestContext, - query_params: Query, - body: TypedBody, - ) -> Result, HttpError> { + query_params: Query, + body: TypedBody, + ) -> Result, HttpError> { // Not audited: this is a read-only query that uses POST only because // the query is too large to fit in a URL. let apictx = rqctx.context(); @@ -6964,7 +6955,7 @@ impl NexusExternalApi for NexusExternalApiImpl { .timeseries_query_project(&opctx, &project_lookup, &query) .await .map(|result| { - HttpResponseOk(views::OxqlQueryResult { + HttpResponseOk(oxql::OxqlQueryResult { tables: result .tables .into_iter() @@ -6992,9 +6983,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_update_repository_upload( rqctx: RequestContext, - query: Query, + query: Query, body: StreamingBody, - ) -> Result, HttpError> { + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let query = query.into_inner(); let body = body.into_stream(); @@ -7008,8 +6999,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_update_repository_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; let params = path_params.into_inner(); @@ -7031,7 +7022,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_update_repository_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; let handler = async { @@ -7042,13 +7033,13 @@ impl NexusExternalApi for NexusExternalApiImpl { let repos = nexus.updates_list_repositories(&opctx, &pagparams).await?; - let responses: Vec = + let responses: Vec = repos.into_iter().map(Into::into).collect(); Ok(HttpResponseOk(ScanByVersion::results_page( &query, responses, - &|_scan_params, item: &views::TufRepo| { + &|_scan_params, item: &update::TufRepo| { item.system_version.clone() }, )?)) @@ -7063,7 +7054,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_update_trust_root_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -7084,7 +7075,7 @@ impl NexusExternalApi for NexusExternalApiImpl { Ok(HttpResponseOk(ScanById::results_page( &query, trust_roots, - &|_, trust_root: &views::UpdatesTrustRoot| { + &|_, trust_root: &update::UpdatesTrustRoot| { trust_root.id.into_untyped_uuid() }, )?)) @@ -7098,8 +7089,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_update_trust_root_create( rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError> { + body: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let body = body.into_inner(); Ok(HttpResponseCreated( @@ -7111,8 +7102,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_update_trust_root_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let opctx = @@ -7136,7 +7127,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_update_trust_root_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { let id = TufTrustRootUuid::from_untyped_uuid( path_params.into_inner().trust_root_id, @@ -7150,7 +7141,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn target_release_update( rqctx: RequestContext, - body: TypedBody, + body: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let params = body.into_inner(); @@ -7225,7 +7216,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn system_update_status( rqctx: RequestContext, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -7245,7 +7236,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn user_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -7285,8 +7276,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn user_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -7306,9 +7297,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn user_token_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError> + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -7322,7 +7313,7 @@ impl NexusExternalApi for NexusExternalApiImpl { .silo_user_token_list(&opctx, path.user_id, &pag_params) .await? .into_iter() - .map(views::DeviceAccessToken::from) + .map(device::DeviceAccessToken::from) .collect(); Ok(HttpResponseOk(ScanById::results_page( &query, @@ -7339,9 +7330,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn user_session_list( rqctx: RequestContext, - path_params: Path, + path_params: Path, query_params: Query, - ) -> Result>, HttpError> + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -7363,7 +7354,7 @@ impl NexusExternalApi for NexusExternalApiImpl { ) .await? .into_iter() - .map(views::ConsoleSession::from) + .map(device::ConsoleSession::from) .collect(); Ok(HttpResponseOk(ScanById::results_page( &query, @@ -7380,7 +7371,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn user_logout( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -7424,7 +7415,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn group_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -7477,7 +7468,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn user_builtin_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -7502,7 +7493,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn current_user_view( rqctx: RequestContext, - ) -> Result, HttpError> { + ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; let handler = async { @@ -7529,7 +7520,7 @@ impl NexusExternalApi for NexusExternalApiImpl { Err(e) => return Err(e.into()), }; - Ok(HttpResponseOk(views::CurrentUser { + Ok(HttpResponseOk(user::CurrentUser { user: user.into(), silo_name: silo.name().clone(), fleet_viewer, @@ -7546,7 +7537,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn current_user_groups( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> { + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let opctx = @@ -7565,7 +7556,7 @@ impl NexusExternalApi for NexusExternalApiImpl { Ok(HttpResponseOk(ScanById::results_page( &query, groups, - &|_, group: &views::Group| group.id.into_untyped_uuid(), + &|_, group: &user::Group| group.id.into_untyped_uuid(), )?)) }; apictx @@ -7624,7 +7615,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn current_user_ssh_key_create( rqctx: RequestContext, - new_key: TypedBody, + new_key: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let new_key = new_key.into_inner(); @@ -7651,7 +7642,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn current_user_ssh_key_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -7674,7 +7665,7 @@ impl NexusExternalApi for NexusExternalApiImpl { }; let ssh_key_selector = - params::SshKeySelector { silo_user_id, ssh_key: path.ssh_key }; + ssh_key::SshKeySelector { silo_user_id, ssh_key: path.ssh_key }; let ssh_key_lookup = nexus.ssh_key_lookup(&opctx, &ssh_key_selector)?; @@ -7692,7 +7683,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn current_user_ssh_key_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -7711,7 +7702,7 @@ impl NexusExternalApi for NexusExternalApiImpl { }; let ssh_key_selector = - params::SshKeySelector { silo_user_id, ssh_key: path.ssh_key }; + ssh_key::SshKeySelector { silo_user_id, ssh_key: path.ssh_key }; let ssh_key_lookup = nexus.ssh_key_lookup(&opctx, &ssh_key_selector)?; nexus.ssh_key_delete(&opctx, silo_user_id, &ssh_key_lookup).await?; @@ -7723,7 +7714,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn current_user_access_token_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -7736,7 +7727,7 @@ impl NexusExternalApi for NexusExternalApiImpl { .current_user_token_list(&opctx, &pag_params) .await? .into_iter() - .map(views::DeviceAccessToken::from) + .map(device::DeviceAccessToken::from) .collect(); Ok(HttpResponseOk(ScanById::results_page( &query, @@ -7753,7 +7744,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn current_user_access_token_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -7766,8 +7757,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn support_bundle_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -7788,7 +7781,7 @@ impl NexusExternalApi for NexusExternalApiImpl { Ok(HttpResponseOk(ScanByTimeAndId::results_page( &query, bundles, - &|_, bundle: &shared::SupportBundleInfo| { + &|_, bundle: &support_bundle::SupportBundleInfo| { (bundle.time_created, bundle.id.into_untyped_uuid()) }, )?)) @@ -7802,8 +7795,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn support_bundle_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> + { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -7831,7 +7825,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn support_bundle_index( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -7867,7 +7861,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn support_bundle_download( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -7903,7 +7897,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn support_bundle_download_file( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -7938,7 +7932,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn support_bundle_head( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -7973,7 +7967,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn support_bundle_head_file( rqctx: RequestContext, headers: Header, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -8007,8 +8001,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn support_bundle_create( rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError> { + body: TypedBody, + ) -> Result, HttpError> + { audit_and_time(&rqctx, |opctx, nexus| async move { let create_params = body.into_inner(); let bundle = nexus @@ -8025,7 +8020,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn support_bundle_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); @@ -8042,9 +8037,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn support_bundle_update( rqctx: RequestContext, - path_params: Path, - body: TypedBody, - ) -> Result, HttpError> { + path_params: Path, + body: TypedBody, + ) -> Result, HttpError> + { audit_and_time(&rqctx, |opctx, nexus| async move { let path = path_params.into_inner(); let update = body.into_inner(); @@ -8062,7 +8058,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn probe_list( rqctx: RequestContext, - query_params: Query>, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -8100,8 +8096,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn probe_view( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -8127,8 +8123,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn probe_create( rqctx: RequestContext, - query_params: Query, - new_probe: TypedBody, + query_params: Query, + new_probe: TypedBody, ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; @@ -8146,8 +8142,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn probe_delete( rqctx: RequestContext, - query_params: Query, - path_params: Path, + query_params: Query, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { opctx.authorize(authz::Action::Modify, &authz::FLEET).await?; @@ -8163,8 +8159,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn audit_log_list( rqctx: RequestContext, - query_params: Query>, - ) -> Result>, HttpError> + query_params: Query>, + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -8190,7 +8186,7 @@ impl NexusExternalApi for NexusExternalApiImpl { .into_iter() .map(TryInto::try_into) .collect::, _>>()?, - &|_, entry: &views::AuditLogEntry| { + &|_, entry: &audit::AuditLogEntry| { (entry.time_completed, entry.id) }, )?)) @@ -8204,8 +8200,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn login_saml_begin( rqctx: RequestContext, - _path_params: Path, - _query_params: Query, + _path_params: Path, + _query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { console_api::serve_console_index(&rqctx).await }; @@ -8218,8 +8214,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn login_saml_redirect( rqctx: RequestContext, - path_params: Path, - query_params: Query, + path_params: Path, + query_params: Query, ) -> Result { let apictx = rqctx.context(); let handler = async { @@ -8249,7 +8245,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn login_saml( rqctx: RequestContext, - path_params: Path, + path_params: Path, body_bytes: dropshot::UntypedBody, ) -> Result { let apictx = rqctx.context(); @@ -8305,8 +8301,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn login_local_begin( rqctx: RequestContext, - _path_params: Path, - _query_params: Query, + _path_params: Path, + _query_params: Query, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { console_api::serve_console_index(&rqctx).await }; @@ -8319,8 +8315,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn login_local( rqctx: RequestContext, - path_params: Path, - credentials: TypedBody, + path_params: Path, + credentials: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); @@ -8433,7 +8429,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn login_begin( rqctx: RequestContext, - query_params: Query, + query_params: Query, ) -> Result { let apictx = rqctx.context(); let handler = async { @@ -8451,28 +8447,28 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn console_projects( rqctx: RequestContext, - _path_params: Path, + _path_params: Path, ) -> Result, HttpError> { console_api::console_index_or_login_redirect(rqctx).await } async fn console_settings_page( rqctx: RequestContext, - _path_params: Path, + _path_params: Path, ) -> Result, HttpError> { console_api::console_index_or_login_redirect(rqctx).await } async fn console_system_page( rqctx: RequestContext, - _path_params: Path, + _path_params: Path, ) -> Result, HttpError> { console_api::console_index_or_login_redirect(rqctx).await } async fn console_lookup( rqctx: RequestContext, - _path_params: Path, + _path_params: Path, ) -> Result, HttpError> { console_api::console_index_or_login_redirect(rqctx).await } @@ -8509,7 +8505,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn asset( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { console_api::asset(&rqctx, path_params).await }; @@ -8529,7 +8525,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn device_auth_request( rqctx: RequestContext, - params: TypedBody, + params: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); let nexus = &apictx.context.nexus; @@ -8599,7 +8595,7 @@ impl NexusExternalApi for NexusExternalApiImpl { // the true login endpoints `login_local` and `login_saml`. async fn device_auth_confirm( rqctx: RequestContext, - params: TypedBody, + params: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let params = params.into_inner(); @@ -8631,7 +8627,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn device_access_token( rqctx: RequestContext, - params: TypedBody, + params: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -8650,10 +8646,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn alert_class_list( rqctx: RequestContext, pag_params: Query< - PaginationParams, + PaginationParams, >, - filter: Query, - ) -> Result>, HttpError> { + filter: Query, + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -8676,7 +8672,7 @@ impl NexusExternalApi for NexusExternalApiImpl { Ok(HttpResponseOk(ResultsPage::new( alert_classes, &EmptyScanParams {}, - |class: &views::AlertClass, _| params::AlertClassPage { + |class: &alert::AlertClass, _| alert::AlertClassPage { last_seen: class.name.clone(), }, )?)) @@ -8691,7 +8687,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn alert_receiver_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -8710,8 +8706,8 @@ impl NexusExternalApi for NexusExternalApiImpl { .await? .into_iter() .map(|webhook| { - views::WebhookReceiver::try_from(webhook) - .map(views::AlertReceiver::from) + alert::WebhookReceiver::try_from(webhook) + .map(alert::AlertReceiver::from) }) .collect::, _>>()?; @@ -8731,8 +8727,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn alert_receiver_view( rqctx: RequestContext, - path_params: Path, - ) -> Result, HttpError> { + path_params: Path, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -8743,7 +8739,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let rx = nexus.alert_receiver_lookup(&opctx, webhook_selector)?; let webhook = nexus.alert_receiver_config_fetch(&opctx, rx).await?; Ok(HttpResponseOk( - views::WebhookReceiver::try_from(webhook)?.into(), + alert::WebhookReceiver::try_from(webhook)?.into(), )) }; apictx @@ -8755,21 +8751,21 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn webhook_receiver_create( rqctx: RequestContext, - params: TypedBody, - ) -> Result, HttpError> { + params: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let params = params.into_inner(); let receiver = nexus.webhook_receiver_create(&opctx, params).await?; - Ok(HttpResponseCreated(views::WebhookReceiver::try_from(receiver)?)) + Ok(HttpResponseCreated(alert::WebhookReceiver::try_from(receiver)?)) }) .await } async fn webhook_receiver_update( rqctx: RequestContext, - path_params: Path, - params: TypedBody, + path_params: Path, + params: TypedBody, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let webhook_selector = path_params.into_inner(); @@ -8783,7 +8779,7 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn alert_receiver_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let webhook_selector = path_params.into_inner(); @@ -8796,9 +8792,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn alert_receiver_subscription_add( rqctx: RequestContext, - path_params: Path, - params: TypedBody, - ) -> Result, HttpError> + path_params: Path, + params: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let webhook_selector = path_params.into_inner(); @@ -8814,10 +8810,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn alert_receiver_subscription_remove( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { - let params::AlertSubscriptionSelector { receiver, subscription } = + let alert::AlertSubscriptionSelector { receiver, subscription } = path_params.into_inner(); let rx = nexus.alert_receiver_lookup(&opctx, receiver)?; nexus @@ -8830,9 +8826,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn alert_receiver_probe( rqctx: RequestContext, - path_params: Path, - query_params: Query, - ) -> Result, HttpError> { + path_params: Path, + query_params: Query, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let webhook_selector = path_params.into_inner(); let probe_params = query_params.into_inner(); @@ -8846,8 +8842,8 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn webhook_secrets_list( rqctx: RequestContext, - query_params: Query, - ) -> Result, HttpError> { + query_params: Query, + ) -> Result, HttpError> { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -8864,7 +8860,7 @@ impl NexusExternalApi for NexusExternalApiImpl { .map(Into::into) .collect(); - Ok(HttpResponseOk(views::WebhookSecrets { secrets })) + Ok(HttpResponseOk(alert::WebhookSecrets { secrets })) }; apictx .context @@ -8876,11 +8872,11 @@ impl NexusExternalApi for NexusExternalApiImpl { /// Add a secret to a webhook. async fn webhook_secrets_add( rqctx: RequestContext, - query_params: Query, - params: TypedBody, - ) -> Result, HttpError> { + query_params: Query, + params: TypedBody, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { - let params::WebhookSecretCreate { secret } = params.into_inner(); + let alert::WebhookSecretCreate { secret } = params.into_inner(); let webhook_selector = query_params.into_inner(); let rx = nexus.alert_receiver_lookup(&opctx, webhook_selector)?; let secret = @@ -8893,7 +8889,7 @@ impl NexusExternalApi for NexusExternalApiImpl { /// Delete a secret from a webhook receiver. async fn webhook_secrets_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { audit_and_time(&rqctx, |opctx, nexus| async move { let secret_selector = path_params.into_inner(); @@ -8907,10 +8903,10 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn alert_delivery_list( rqctx: RequestContext, - receiver: Path, - filter: Query, + receiver: Path, + filter: Query, query: Query, - ) -> Result>, HttpError> + ) -> Result>, HttpError> { let apictx = rqctx.context(); let handler = async { @@ -8943,9 +8939,9 @@ impl NexusExternalApi for NexusExternalApiImpl { async fn alert_delivery_resend( rqctx: RequestContext, - path_params: Path, - receiver: Query, - ) -> Result, HttpError> { + path_params: Path, + receiver: Query, + ) -> Result, HttpError> { audit_and_time(&rqctx, |opctx, nexus| async move { let event_selector = path_params.into_inner(); let webhook_selector = receiver.into_inner(); @@ -8953,7 +8949,7 @@ impl NexusExternalApi for NexusExternalApiImpl { let rx = nexus.alert_receiver_lookup(&opctx, webhook_selector)?; let delivery_id = nexus.alert_receiver_resend(&opctx, rx, event).await?; - Ok(HttpResponseCreated(views::AlertDeliveryId { + Ok(HttpResponseCreated(alert::AlertDeliveryId { delivery_id: delivery_id.into_untyped_uuid(), })) }) diff --git a/nexus/src/external_api/mod.rs b/nexus/src/external_api/mod.rs index 0a6d18aa8b8..1ba205831b9 100644 --- a/nexus/src/external_api/mod.rs +++ b/nexus/src/external_api/mod.rs @@ -4,7 +4,3 @@ pub mod console_api; pub(crate) mod http_entrypoints; - -pub(crate) use nexus_types::external_api::params; -pub(crate) use nexus_types::external_api::shared; -pub(crate) use nexus_types::external_api::views; diff --git a/nexus/src/lockstep_api/http_entrypoints.rs b/nexus/src/lockstep_api/http_entrypoints.rs index 13f02da4d58..2c378a3fe30 100644 --- a/nexus/src/lockstep_api/http_entrypoints.rs +++ b/nexus/src/lockstep_api/http_entrypoints.rs @@ -32,17 +32,15 @@ use nexus_types::deployment::ClickhousePolicy; use nexus_types::deployment::OximeterReadPolicy; use nexus_types::deployment::ReconfiguratorConfigParam; use nexus_types::deployment::ReconfiguratorConfigView; -use nexus_types::external_api::headers::RangeRequest; -use nexus_types::external_api::params::PhysicalDiskPath; -use nexus_types::external_api::params::RackMembershipConfigPathParams; -use nexus_types::external_api::params::SledSelector; -use nexus_types::external_api::params::SupportBundleFilePath; -use nexus_types::external_api::params::SupportBundlePath; -use nexus_types::external_api::params::SupportBundleUpdate; -use nexus_types::external_api::params::UninitializedSledId; -use nexus_types::external_api::shared; -use nexus_types::external_api::shared::UninitializedSled; -use nexus_types::external_api::views::SledPolicy; +use nexus_types::external_api::hardware::{ + UninitializedSled, UninitializedSledId, +}; +use nexus_types::external_api::path_params::{BlueprintPath, PhysicalDiskPath}; +use nexus_types::external_api::rack::RackMembershipConfigPathParams; +use nexus_types::external_api::sled::{SledPolicy, SledSelector}; +use nexus_types::external_api::support_bundle::{ + self, SupportBundleFilePath, SupportBundlePath, SupportBundleUpdate, +}; use nexus_types::internal_api::params::InstanceMigrateRequest; use nexus_types::internal_api::params::RackInitializationRequest; use nexus_types::internal_api::views::BackgroundTask; @@ -53,6 +51,7 @@ use nexus_types::internal_api::views::Saga; use nexus_types::internal_api::views::UpdateStatus; use nexus_types::internal_api::views::to_list; use nexus_types::trust_quorum::TrustQuorumConfig; +use nexus_types_versions::latest::headers::RangeRequest; use omicron_common::api::external::Error; use omicron_common::api::external::Instance; use omicron_common::api::external::http_pagination::PaginatedById; @@ -346,7 +345,7 @@ impl NexusLockstepApi for NexusLockstepApiImpl { /// Fetches one blueprint async fn blueprint_view( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result, HttpError> { let apictx = &rqctx.context().context; let handler = async { @@ -367,7 +366,7 @@ impl NexusLockstepApi for NexusLockstepApiImpl { /// Deletes one blueprint async fn blueprint_delete( rqctx: RequestContext, - path_params: Path, + path_params: Path, ) -> Result { let apictx = &rqctx.context().context; let handler = async { @@ -638,8 +637,10 @@ impl NexusLockstepApi for NexusLockstepApiImpl { async fn support_bundle_list( rqctx: RequestContext, query_params: Query, - ) -> Result>, HttpError> - { + ) -> Result< + HttpResponseOk>, + HttpError, + > { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -660,7 +661,7 @@ impl NexusLockstepApi for NexusLockstepApiImpl { Ok(HttpResponseOk(ScanByTimeAndId::results_page( &query, bundles, - &|_, bundle: &shared::SupportBundleInfo| { + &|_, bundle: &support_bundle::SupportBundleInfo| { (bundle.time_created, bundle.id.into_untyped_uuid()) }, )?)) @@ -675,7 +676,8 @@ impl NexusLockstepApi for NexusLockstepApiImpl { async fn support_bundle_view( rqctx: RequestContext, path_params: Path, - ) -> Result, HttpError> { + ) -> Result, HttpError> + { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -879,8 +881,9 @@ impl NexusLockstepApi for NexusLockstepApiImpl { async fn support_bundle_create( rqctx: RequestContext, - body: TypedBody, - ) -> Result, HttpError> { + body: TypedBody, + ) -> Result, HttpError> + { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; @@ -937,7 +940,8 @@ impl NexusLockstepApi for NexusLockstepApiImpl { rqctx: RequestContext, path_params: Path, body: TypedBody, - ) -> Result, HttpError> { + ) -> Result, HttpError> + { let apictx = rqctx.context(); let handler = async { let nexus = &apictx.context.nexus; diff --git a/nexus/test-utils/src/resource_helpers.rs b/nexus/test-utils/src/resource_helpers.rs index e9fcaf6a939..69f328a6a00 100644 --- a/nexus/test-utils/src/resource_helpers.rs +++ b/nexus/test-utils/src/resource_helpers.rs @@ -17,35 +17,43 @@ use http::header; use nexus_db_queries::db::fixed_data::silo::DEFAULT_SILO; use nexus_test_interface::NexusServer; use nexus_types::deployment::Blueprint; -use nexus_types::external_api::params; -use nexus_types::external_api::params::ExternalSubnetAllocator; -use nexus_types::external_api::params::PoolSelector; -use nexus_types::external_api::params::{ - DeviceAccessTokenRequest, DeviceAuthRequest, DeviceAuthVerify, +use nexus_types::external_api::affinity; +use nexus_types::external_api::affinity::{AffinityGroup, AntiAffinityGroup}; +use nexus_types::external_api::certificate; +use nexus_types::external_api::certificate::Certificate; +use nexus_types::external_api::device::{ + DeviceAccessTokenGrant, DeviceAccessTokenRequest, DeviceAuthRequest, + DeviceAuthResponse, DeviceAuthVerify, }; -use nexus_types::external_api::shared; -use nexus_types::external_api::shared::Baseboard; -use nexus_types::external_api::shared::IpRange; -use nexus_types::external_api::views; -use nexus_types::external_api::views::AffinityGroup; -use nexus_types::external_api::views::AntiAffinityGroup; -use nexus_types::external_api::views::Certificate; -use nexus_types::external_api::views::ExternalSubnet; -use nexus_types::external_api::views::FloatingIp; -use nexus_types::external_api::views::InternetGateway; -use nexus_types::external_api::views::InternetGatewayIpAddress; -use nexus_types::external_api::views::InternetGatewayIpPool; -use nexus_types::external_api::views::IpPool; -use nexus_types::external_api::views::IpPoolRange; -use nexus_types::external_api::views::IpVersion; -use nexus_types::external_api::views::SubnetPool; -use nexus_types::external_api::views::SubnetPoolMember; -use nexus_types::external_api::views::User; -use nexus_types::external_api::views::VpcSubnet; -use nexus_types::external_api::views::{ - DeviceAccessTokenGrant, DeviceAuthResponse, +use nexus_types::external_api::disk; +use nexus_types::external_api::external_subnet; +use nexus_types::external_api::external_subnet::ExternalSubnet; +use nexus_types::external_api::floating_ip; +use nexus_types::external_api::floating_ip::FloatingIp; +use nexus_types::external_api::hardware::Baseboard; +use nexus_types::external_api::image; +use nexus_types::external_api::instance; +use nexus_types::external_api::internet_gateway; +use nexus_types::external_api::internet_gateway::{ + InternetGateway, InternetGatewayIpAddress, InternetGatewayIpPool, }; -use nexus_types::external_api::views::{Project, Silo, Vpc, VpcRouter}; +use nexus_types::external_api::ip_pool; +use nexus_types::external_api::ip_pool::{ + IpPool, IpPoolRange, IpRange, IpVersion, +}; +use nexus_types::external_api::multicast; +use nexus_types::external_api::policy; +use nexus_types::external_api::project; +use nexus_types::external_api::project::Project; +use nexus_types::external_api::silo; +use nexus_types::external_api::silo::Silo; +use nexus_types::external_api::snapshot; +use nexus_types::external_api::subnet_pool; +use nexus_types::external_api::subnet_pool::{SubnetPool, SubnetPoolMember}; +use nexus_types::external_api::switch; +use nexus_types::external_api::user::User; +use nexus_types::external_api::vpc; +use nexus_types::external_api::vpc::{Vpc, VpcRouter, VpcSubnet}; use nexus_types::identity::Resource; use nexus_types::internal_api::params as internal_params; use omicron_common::api::external::AffinityPolicy; @@ -282,7 +290,7 @@ pub async fn create_ip_pool( pool_name: &str, ip_range: Option, ) -> (IpPool, IpPoolRange) { - let pool_params = params::IpPoolCreate::new( + let pool_params = ip_pool::IpPoolCreate::new( IdentityMetadataCreateParams { name: pool_name.parse().unwrap(), description: String::from("an ip pool"), @@ -290,7 +298,7 @@ pub async fn create_ip_pool( ip_range .as_ref() .map(|r| r.version()) - .unwrap_or_else(views::IpVersion::v4), + .unwrap_or_else(ip_pool::IpVersion::v4), ); let pool = object_create(client, "/v1/system/ip-pools", &pool_params).await; @@ -320,14 +328,14 @@ pub async fn create_multicast_ip_pool( let pool = object_create( client, "/v1/system/ip-pools", - ¶ms::IpPoolCreate::new_multicast( + &ip_pool::IpPoolCreate::new_multicast( IdentityMetadataCreateParams { name: pool_name.parse().unwrap(), description: String::from("a multicast ip pool"), }, ip_range .map(|r| r.version()) - .unwrap_or_else(|| views::IpVersion::V4), + .unwrap_or_else(|| ip_pool::IpVersion::V4), ), ) .await; @@ -352,9 +360,9 @@ pub async fn link_ip_pool( is_default: bool, ) { let link = - params::IpPoolLinkSilo { silo: NameOrId::Id(*silo_id), is_default }; + ip_pool::IpPoolLinkSilo { silo: NameOrId::Id(*silo_id), is_default }; let url = format!("/v1/system/ip-pools/{pool_name}/silos"); - object_create::( + object_create::( client, &url, &link, ) .await; @@ -365,7 +373,7 @@ pub async fn link_ip_pool( /// What you want for any test that is not testing IP logic specifically pub async fn create_default_ip_pools( client: &ClientTestContext, -) -> (views::IpPool, views::IpPool) { +) -> (IpPool, IpPool) { let ranges = [ IpRange::try_from(( std::net::Ipv4Addr::new(10, 0, 0, 0), @@ -391,12 +399,12 @@ pub async fn create_floating_ip( client: &ClientTestContext, fip_name: &str, project: &str, - address_allocator: params::AddressAllocator, + address_allocator: floating_ip::AddressAllocator, ) -> FloatingIp { object_create( client, &format!("/v1/floating-ips?project={project}"), - ¶ms::FloatingIpCreate { + &floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: fip_name.parse().unwrap(), description: String::from("a floating ip"), @@ -415,7 +423,7 @@ pub async fn create_subnet_pool( object_create( client, "/v1/system/subnet-pools/", - ¶ms::SubnetPoolCreate { + &subnet_pool::SubnetPoolCreate { identity: IdentityMetadataCreateParams { name: pool_name.parse().unwrap(), description: String::from("a subnet pool"), @@ -436,7 +444,7 @@ pub async fn create_subnet_pool_member( object_create( client, &format!("/v1/system/subnet-pools/{pool_name}/members/add"), - ¶ms::SubnetPoolMemberAdd { + &subnet_pool::SubnetPoolMemberAdd { subnet, min_prefix_length: None, max_prefix_length: None, @@ -455,7 +463,7 @@ pub async fn create_subnet_pool_member_with_prefix_lengths( object_create( client, &format!("/v1/system/subnet-pools/{pool_name}/members/add"), - ¶ms::SubnetPoolMemberAdd { + &subnet_pool::SubnetPoolMemberAdd { subnet, min_prefix_length: Some(min_prefix_length), max_prefix_length: Some(max_prefix_length), @@ -470,12 +478,15 @@ pub async fn link_subnet_pool( silo_id: &Uuid, is_default: bool, ) { - let link = - params::SubnetPoolLinkSilo { silo: NameOrId::Id(*silo_id), is_default }; + let link = subnet_pool::SubnetPoolLinkSilo { + silo: NameOrId::Id(*silo_id), + is_default, + }; let url = format!("/v1/system/subnet-pools/{pool_name}/silos"); - object_create::( - client, &url, &link, - ) + object_create::< + subnet_pool::SubnetPoolLinkSilo, + subnet_pool::SubnetPoolSiloLink, + >(client, &url, &link) .await; } @@ -486,14 +497,14 @@ pub async fn create_external_subnet_in_pool( subnet_name: &str, prefix_len: u8, ) -> ExternalSubnet { - let params = params::ExternalSubnetCreate { + let params = external_subnet::ExternalSubnetCreate { identity: IdentityMetadataCreateParams { name: subnet_name.parse().unwrap(), description: format!("external subnet {subnet_name}"), }, - allocator: ExternalSubnetAllocator::Auto { + allocator: external_subnet::ExternalSubnetAllocator::Auto { prefix_len, - pool_selector: PoolSelector::Explicit { + pool_selector: ip_pool::PoolSelector::Explicit { pool: pool_name.parse::().unwrap().into(), }, }, @@ -516,14 +527,14 @@ pub async fn create_certificate( object_create( client, &url, - ¶ms::CertificateCreate { + &certificate::CertificateCreate { identity: IdentityMetadataCreateParams { name: cert_name.parse().unwrap(), description: String::from("sells rainsticks"), }, cert, key, - service: shared::ServiceUsingCertificate::ExternalApi, + service: certificate::ServiceUsingCertificate::ExternalApi, }, ) .await @@ -540,7 +551,7 @@ pub async fn create_switch( part: &str, revision: u32, rack_id: Uuid, -) -> views::Switch { +) -> switch::Switch { object_put( client, "/switches", @@ -560,7 +571,7 @@ pub async fn create_silo( client: &ClientTestContext, silo_name: &str, discoverable: bool, - identity_mode: shared::SiloIdentityMode, + identity_mode: silo::SiloIdentityMode, ) -> Silo { create_silo_with_admin_group_name( client, @@ -576,18 +587,18 @@ pub async fn create_silo_with_admin_group_name( client: &ClientTestContext, silo_name: &str, discoverable: bool, - identity_mode: shared::SiloIdentityMode, + identity_mode: silo::SiloIdentityMode, admin_group_name: Option, ) -> Silo { object_create( client, "/v1/system/silos", - ¶ms::SiloCreate { + &silo::SiloCreate { identity: IdentityMetadataCreateParams { name: silo_name.parse().unwrap(), description: "a silo".to_string(), }, - quotas: params::SiloQuotasCreate::arbitrarily_high_default(), + quotas: silo::SiloQuotasCreate::arbitrarily_high_default(), discoverable, identity_mode, admin_group_name, @@ -634,7 +645,7 @@ pub mod test_params { pub async fn create_local_user( client: &ClientTestContext, - silo: &views::Silo, + silo: &Silo, username: &UserId, password: test_params::UserPassword, ) -> User { @@ -656,7 +667,7 @@ pub async fn create_project( object_create( client, "/v1/projects", - ¶ms::ProjectCreate { + &project::ProjectCreate { identity: IdentityMetadataCreateParams { name: project_name.parse().unwrap(), description: "a pier".to_string(), @@ -676,14 +687,14 @@ pub async fn create_disk( object_create( client, &url, - ¶ms::DiskCreate { + &disk::DiskCreate { identity: IdentityMetadataCreateParams { name: disk_name.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: ByteCount::from_gibibytes_u32(1), @@ -696,20 +707,20 @@ pub async fn create_disk_from_snapshot( client: &ClientTestContext, project_name: &str, disk_name: &str, - snapshot: &views::Snapshot, + snapshot: &snapshot::Snapshot, read_only: bool, ) -> Disk { let url = format!("/v1/disks?project={}", project_name); object_create( client, &url, - ¶ms::DiskCreate { + &disk::DiskCreate { identity: IdentityMetadataCreateParams { name: disk_name.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Snapshot { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Snapshot { snapshot_id: snapshot.identity.id, read_only, }, @@ -724,20 +735,20 @@ pub async fn create_disk_from_image( client: &ClientTestContext, project_name: &str, disk_name: &str, - image: &views::Image, + image: &image::Image, read_only: bool, ) -> Disk { let url = format!("/v1/disks?project={}", project_name); object_create( client, &url, - ¶ms::DiskCreate { + &disk::DiskCreate { identity: IdentityMetadataCreateParams { name: disk_name.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: image.identity.id, read_only, }, @@ -753,13 +764,13 @@ pub async fn create_snapshot( project_name: &str, disk_name: &str, snapshot_name: &str, -) -> views::Snapshot { +) -> snapshot::Snapshot { let snapshots_url = format!("/v1/snapshots?project={}", project_name); object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: snapshot_name.parse().unwrap(), description: format!("snapshot {:?}", snapshot_name), @@ -793,19 +804,19 @@ pub async fn create_alpine_project_image( client: &ClientTestContext, project_name: &str, image_name: &str, -) -> views::Image { +) -> image::Image { let images_url = format!("/v1/images?project={}", project_name); object_create( client, &images_url, - ¶ms::ImageCreate { + &image::ImageCreate { identity: IdentityMetadataCreateParams { name: image_name.parse().unwrap(), description: String::from( "you can boot any image, as long as it's alpine", ), }, - source: params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + source: image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, os: "alpine".to_string(), version: "edge".to_string(), }, @@ -818,17 +829,17 @@ pub async fn create_project_image_from_snapshot( project_name: &str, image_name: &str, snapshot_id: Uuid, -) -> views::Image { +) -> image::Image { let images_url = format!("/v1/images?project={}", project_name); object_create( client, &images_url, - ¶ms::ImageCreate { + &image::ImageCreate { identity: IdentityMetadataCreateParams { name: image_name.parse().unwrap(), description: String::from("it's an image alright"), }, - source: params::ImageSource::Snapshot { id: snapshot_id }, + source: image::ImageSource::Snapshot { id: snapshot_id }, os: "os".to_string(), version: "version".to_string(), }, @@ -857,16 +868,16 @@ pub async fn create_instance( client, project_name, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, + &instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, // Disks= - Vec::::new(), + Vec::::new(), // External IPs= - Vec::::new(), + Vec::::new(), true, Default::default(), None, // Multicast groups= - Vec::::new(), + Vec::::new(), ) .await } @@ -878,20 +889,20 @@ pub async fn create_instance_with( client: &ClientTestContext, project_name: &str, instance_name: &str, - nics: ¶ms::InstanceNetworkInterfaceAttachment, - disks: Vec, - external_ips: Vec, + nics: &instance::InstanceNetworkInterfaceAttachment, + disks: Vec, + external_ips: Vec, start: bool, auto_restart_policy: Option, cpu_platform: Option, - multicast_groups: Vec, + multicast_groups: Vec, ) -> Instance { let url = format!("/v1/instances?project={}", project_name); object_create( client, &url, - ¶ms::InstanceCreate { + &instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: format!("instance {:?}", instance_name), @@ -942,7 +953,7 @@ pub async fn create_affinity_group( object_create( &client, format!("/v1/affinity-groups?project={}", &project_name).as_str(), - ¶ms::AffinityGroupCreate { + &affinity::AffinityGroupCreate { identity: IdentityMetadataCreateParams { name: group_name.parse().unwrap(), description: String::from("affinity group description"), @@ -962,7 +973,7 @@ pub async fn create_anti_affinity_group( object_create( &client, format!("/v1/anti-affinity-groups?project={}", &project_name).as_str(), - ¶ms::AntiAffinityGroupCreate { + &affinity::AntiAffinityGroupCreate { identity: IdentityMetadataCreateParams { name: group_name.parse().unwrap(), description: String::from("anti-affinity group description"), @@ -982,7 +993,7 @@ pub async fn create_vpc( object_create( &client, format!("/v1/vpcs?project={}", &project_name).as_str(), - ¶ms::VpcCreate { + &vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: vpc_name.parse().unwrap(), description: "vpc description".to_string(), @@ -1008,7 +1019,7 @@ pub async fn create_vpc_with_error( Method::POST, format!("/v1/vpcs?project={}", &project_name).as_str(), ) - .body(Some(¶ms::VpcCreate { + .body(Some(&vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: vpc_name.parse().unwrap(), description: String::from("vpc description"), @@ -1038,7 +1049,7 @@ pub async fn create_vpc_subnet( object_create( &client, &format!("/v1/vpc-subnets?project={project_name}&vpc={vpc_name}"), - ¶ms::VpcSubnetCreate { + &vpc::VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: subnet_name.parse().unwrap(), description: "vpc description".to_string(), @@ -1062,7 +1073,7 @@ pub async fn create_router( &client, format!("/v1/vpc-routers?project={}&vpc={}", &project_name, &vpc_name) .as_str(), - ¶ms::VpcRouterCreate { + &vpc::VpcRouterCreate { identity: IdentityMetadataCreateParams { name: router_name.parse().unwrap(), description: String::from("router description"), @@ -1093,7 +1104,7 @@ pub async fn create_route( &project_name, &vpc_name, &router_name ) .as_str(), - ¶ms::RouterRouteCreate { + &vpc::RouterRouteCreate { identity: IdentityMetadataCreateParams { name: route_name.parse().unwrap(), description: String::from("route description"), @@ -1131,7 +1142,7 @@ pub async fn create_route_with_error( ) .as_str(), ) - .body(Some(¶ms::RouterRouteCreate { + .body(Some(&vpc::RouterRouteCreate { identity: IdentityMetadataCreateParams { name: route_name.parse().unwrap(), description: String::from("route description"), @@ -1162,7 +1173,7 @@ pub async fn create_internet_gateway( &project_name, &vpc_name ) .as_str(), - ¶ms::VpcRouterCreate { + &vpc::VpcRouterCreate { identity: IdentityMetadataCreateParams { name: internet_gateway_name.parse().unwrap(), description: String::from("internet gateway description"), @@ -1215,7 +1226,7 @@ pub async fn attach_ip_pool_to_igw( NexusRequest::objects_post( &client, url.as_str(), - ¶ms::InternetGatewayIpPoolCreate { + &internet_gateway::InternetGatewayIpPoolCreate { identity: IdentityMetadataCreateParams { name: attachment_name.parse().unwrap(), description: String::from("attached pool descriptoion"), @@ -1267,7 +1278,7 @@ pub async fn attach_ip_address_to_igw( NexusRequest::objects_post( &client, url.as_str(), - ¶ms::InternetGatewayIpAddressCreate { + &internet_gateway::InternetGatewayIpAddressCreate { identity: IdentityMetadataCreateParams { name: attachment_name.parse().unwrap(), description: String::from("attached pool descriptoion"), @@ -1315,7 +1326,8 @@ pub async fn assert_ip_pool_utilization( capacity: f64, ) { let url = format!("/v1/system/ip-pools/{}/utilization", pool_name); - let utilization: views::IpPoolUtilization = object_get(client, &url).await; + let utilization: ip_pool::IpPoolUtilization = + object_get(client, &url).await; let remaining = capacity - f64::from(allocated); assert_eq!( remaining, utilization.remaining, @@ -1345,7 +1357,7 @@ pub async fn grant_iam( T: serde::Serialize + serde::de::DeserializeOwned, { let policy_url = format!("{}/policy", grant_resource_url); - let existing_policy: shared::Policy = + let existing_policy: policy::Policy = NexusRequest::object_get(client, &policy_url) .authn_as(run_as.clone()) .execute() @@ -1354,14 +1366,14 @@ pub async fn grant_iam( .parsed_body() .expect("failed to parse policy"); let new_role_assignment = - shared::RoleAssignment::for_silo_user(grant_user, grant_role); + policy::RoleAssignment::for_silo_user(grant_user, grant_role); let new_role_assignments = existing_policy .role_assignments .into_iter() .chain(std::iter::once(new_role_assignment)) .collect(); - let new_policy = shared::Policy { role_assignments: new_role_assignments }; + let new_policy = policy::Policy { role_assignments: new_role_assignments }; // TODO-correctness use etag when we have it NexusRequest::object_put(client, &policy_url, Some(&new_policy)) @@ -1387,7 +1399,7 @@ pub async fn grant_iam_for_group( T: serde::Serialize + serde::de::DeserializeOwned, { let policy_url = format!("{}/policy", grant_resource_url); - let existing_policy: shared::Policy = + let existing_policy: policy::Policy = NexusRequest::object_get(client, &policy_url) .authn_as(run_as.clone()) .execute() @@ -1396,14 +1408,14 @@ pub async fn grant_iam_for_group( .parsed_body() .expect("failed to parse policy"); let new_role_assignment = - shared::RoleAssignment::for_silo_group(grant_group, grant_role); + policy::RoleAssignment::for_silo_group(grant_group, grant_role); let new_role_assignments = existing_policy .role_assignments .into_iter() .chain(std::iter::once(new_role_assignment)) .collect(); - let new_policy = shared::Policy { role_assignments: new_role_assignments }; + let new_policy = policy::Policy { role_assignments: new_role_assignments }; // TODO-correctness use etag when we have it NexusRequest::object_put(client, &policy_url, Some(&new_policy)) @@ -1934,7 +1946,7 @@ impl<'a, N: NexusServer> DiskTest<'a, N> { serial: disk_identity.serial.clone(), model: disk_identity.model.clone(), variant: - nexus_types::external_api::params::PhysicalDiskKind::U2, + nexus_types::external_api::physical_disk::PhysicalDiskKind::U2, sled_id, }; diff --git a/nexus/test-utils/src/starter.rs b/nexus/test-utils/src/starter.rs index 9b2d6feda58..cfcf700058c 100644 --- a/nexus/test-utils/src/starter.rs +++ b/nexus/test-utils/src/starter.rs @@ -55,7 +55,7 @@ use nexus_types::deployment::PendingMgsUpdates; use nexus_types::deployment::PlannerConfig; use nexus_types::deployment::ReconfiguratorConfig; use nexus_types::deployment::blueprint_zone_type; -use nexus_types::external_api::views::SledState; +use nexus_types::external_api::sled::SledState; use nexus_types::internal_api::params::DnsConfigParams; use omicron_common::address::DNS_OPTE_IPV4_SUBNET; use omicron_common::address::DNS_OPTE_IPV6_SUBNET; diff --git a/nexus/tests/integration_tests/address_lots.rs b/nexus/tests/integration_tests/address_lots.rs index 3a52825f3b4..a8f874a5f46 100644 --- a/nexus/tests/integration_tests/address_lots.rs +++ b/nexus/tests/integration_tests/address_lots.rs @@ -10,7 +10,7 @@ use nexus_test_utils::http_testing::AuthnMode; use nexus_test_utils::http_testing::NexusRequest; use nexus_test_utils::http_testing::RequestBuilder; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::{ +use nexus_types::external_api::networking::{ AddressLotBlockCreate, AddressLotCreate, }; use omicron_common::api::external::{ diff --git a/nexus/tests/integration_tests/affinity.rs b/nexus/tests/integration_tests/affinity.rs index d1b1c60c7e2..0f959ecfc1f 100644 --- a/nexus/tests/integration_tests/affinity.rs +++ b/nexus/tests/integration_tests/affinity.rs @@ -23,11 +23,12 @@ use nexus_test_utils::resource_helpers::object_put; use nexus_test_utils::resource_helpers::object_put_error; use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::views::AffinityGroup; -use nexus_types::external_api::views::AntiAffinityGroup; -use nexus_types::external_api::views::Sled; -use nexus_types::external_api::views::SledInstance; +use nexus_types::external_api::affinity; +use nexus_types::external_api::affinity::AffinityGroup; +use nexus_types::external_api::affinity::AntiAffinityGroup; +use nexus_types::external_api::instance; +use nexus_types::external_api::sled::Sled; +use nexus_types::external_api::sled::SledInstance; use omicron_common::api::external; use omicron_common::api::external::AffinityGroupMember; use omicron_common::api::external::AntiAffinityGroupMember; @@ -62,11 +63,11 @@ impl ProjectScopedApiHelper<'_, T> { &self.client, &self.project.as_ref().expect("Need to specify project name"), instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::None, + &instance::InstanceNetworkInterfaceAttachment::None, // Disks= - Vec::::new(), + Vec::::new(), // External IPs= - Vec::::new(), + Vec::::new(), // Start= false, // Auto-restart policy= @@ -351,14 +352,14 @@ struct AffinityType; impl AffinityGroupish for AffinityType { type Group = AffinityGroup; type Member = AffinityGroupMember; - type CreateParams = params::AffinityGroupCreate; - type UpdateParams = params::AffinityGroupUpdate; + type CreateParams = affinity::AffinityGroupCreate; + type UpdateParams = affinity::AffinityGroupUpdate; const URL_COMPONENT: &'static str = "affinity-groups"; const RESOURCE_NAME: &'static str = "affinity-group"; fn make_create_params(group_name: &str) -> Self::CreateParams { - params::AffinityGroupCreate { + affinity::AffinityGroupCreate { identity: external::IdentityMetadataCreateParams { name: group_name.parse().unwrap(), description: String::from("This is a description"), @@ -369,7 +370,7 @@ impl AffinityGroupish for AffinityType { } fn make_update_params() -> Self::UpdateParams { - params::AffinityGroupUpdate { + affinity::AffinityGroupUpdate { identity: external::IdentityMetadataUpdateParams { name: None, description: Some(NEW_DESCRIPTION.to_string()), @@ -383,14 +384,14 @@ struct AntiAffinityType; impl AffinityGroupish for AntiAffinityType { type Group = AntiAffinityGroup; type Member = AntiAffinityGroupMember; - type CreateParams = params::AntiAffinityGroupCreate; - type UpdateParams = params::AntiAffinityGroupUpdate; + type CreateParams = affinity::AntiAffinityGroupCreate; + type UpdateParams = affinity::AntiAffinityGroupUpdate; const URL_COMPONENT: &'static str = "anti-affinity-groups"; const RESOURCE_NAME: &'static str = "anti-affinity-group"; fn make_create_params(group_name: &str) -> Self::CreateParams { - params::AntiAffinityGroupCreate { + affinity::AntiAffinityGroupCreate { identity: external::IdentityMetadataCreateParams { name: group_name.parse().unwrap(), description: String::from("This is a description"), @@ -401,7 +402,7 @@ impl AffinityGroupish for AntiAffinityType { } fn make_update_params() -> Self::UpdateParams { - params::AntiAffinityGroupUpdate { + affinity::AntiAffinityGroupUpdate { identity: external::IdentityMetadataUpdateParams { name: None, description: Some(NEW_DESCRIPTION.to_string()), diff --git a/nexus/tests/integration_tests/allow_list.rs b/nexus/tests/integration_tests/allow_list.rs index fb59ecb81fe..677eff5fff9 100644 --- a/nexus/tests/integration_tests/allow_list.rs +++ b/nexus/tests/integration_tests/allow_list.rs @@ -7,7 +7,7 @@ use dropshot::test_util::ClientTestContext; use nexus_test_utils::http_testing::{AuthnMode, NexusRequest}; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::{params, views}; +use nexus_types::external_api::system::{AllowList, AllowListUpdate}; use omicron_common::api::external::AllowedSourceIps; use oxnet::IpNet; use std::net::IpAddr; @@ -22,7 +22,7 @@ const URL: &str = "/v1/system/networking/allow-list"; async fn test_allow_list(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; // We should start with the default of any. - let list: views::AllowList = NexusRequest::object_get(client, URL) + let list: AllowList = NexusRequest::object_get(client, URL) .authn_as(AuthnMode::PrivilegedUser) .execute() .await @@ -43,11 +43,10 @@ async fn test_allow_list(cptestctx: &ControlPlaneTestContext) { client: &ClientTestContext, allowed_ips: AllowedSourceIps, ) { - let new_list = - params::AllowListUpdate { allowed_ips: allowed_ips.clone() }; + let new_list = AllowListUpdate { allowed_ips: allowed_ips.clone() }; // PUT the list, which returns it, and ensure we get back what we set. - let list: views::AllowList = + let list: AllowList = NexusRequest::object_put(client, URL, Some(&new_list)) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -61,7 +60,7 @@ async fn test_allow_list(cptestctx: &ControlPlaneTestContext) { ); // GET it as well. - let get_list: views::AllowList = NexusRequest::object_get(client, URL) + let get_list: AllowList = NexusRequest::object_get(client, URL) .authn_as(AuthnMode::PrivilegedUser) .execute() .await @@ -105,7 +104,7 @@ async fn test_allow_list(cptestctx: &ControlPlaneTestContext) { let addrs = vec![IpNet::host_net(IpAddr::from(Ipv4Addr::new(1, 1, 1, 1)))]; let allowed_ips = AllowedSourceIps::try_from(addrs.clone()) .expect("Expected a valid IP list"); - let new_list = params::AllowListUpdate { allowed_ips: allowed_ips.clone() }; + let new_list = AllowListUpdate { allowed_ips: allowed_ips.clone() }; let err: dropshot::HttpErrorResponseBody = NexusRequest::expect_failure_with_body( client, diff --git a/nexus/tests/integration_tests/audit_log.rs b/nexus/tests/integration_tests/audit_log.rs index 3bfc1c5a97a..6b91a6ea908 100644 --- a/nexus/tests/integration_tests/audit_log.rs +++ b/nexus/tests/integration_tests/audit_log.rs @@ -14,7 +14,19 @@ use nexus_test_utils::resource_helpers::{ object_delete, objects_list_page_authz, test_params, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::{params, shared, views}; +use nexus_types::external_api::audit::{ + self, AuditLogEntry, AuditLogEntryActor, AuditLogEntryResult, +}; +use nexus_types::external_api::device; +use nexus_types::external_api::instance::{ + ExternalIpCreate, InstanceDiskAttachment, + InstanceNetworkInterfaceAttachment, +}; +use nexus_types::external_api::policy; +use nexus_types::external_api::project::ProjectCreate; +use nexus_types::external_api::scim; +use nexus_types::external_api::silo::{self, SiloIdentityMode}; +use nexus_types::external_api::user::CurrentUser; use nexus_types::{identity::Asset, silo::DEFAULT_SILO_ID}; use omicron_common::api::external::{ IdentityMetadataCreateParams, InstanceAutoRestartPolicy, @@ -33,7 +45,7 @@ async fn fetch_log( client: &ClientTestContext, start: DateTime, end: Option>, -) -> ResultsPage { +) -> ResultsPage { // Use a large limit to avoid pagination hiding results let mut qs = vec![format!("start_time={}", to_q(start)), "limit=1000".to_string()]; @@ -41,7 +53,7 @@ async fn fetch_log( qs.push(format!("end_time={}", to_q(end))); } let url = format!("/v1/system/audit-log?{}", qs.join("&")); - objects_list_page_authz::(client, &url).await + objects_list_page_authz::(client, &url).await } #[nexus_test] @@ -72,7 +84,7 @@ async fn test_audit_log_list(ctx: &ControlPlaneTestContext) { // we have to do this rigmarole instead of using create_project in order to // get the user agent header in there and to use a session cookie to test // the auth_method field - let body = ¶ms::ProjectCreate { + let body = &ProjectCreate { identity: IdentityMetadataCreateParams { name: "test-proj2".parse().unwrap(), description: "a pier".to_string(), @@ -104,13 +116,13 @@ async fn test_audit_log_list(ctx: &ControlPlaneTestContext) { assert_eq!(e1.operation_id, "project_create"); assert_eq!(e1.source_ip.to_string(), "127.0.0.1"); assert_eq!(e1.user_agent, None); // no user agent passed by default - assert_eq!(e1.auth_method, Some(views::AuthMethod::Spoof)); + assert_eq!(e1.auth_method, Some(audit::AuthMethod::Spoof)); assert_eq!(e1.credential_id, None); // spoof auth has no credential assert!(e1.time_started >= t1 && e1.time_started <= t2); assert!(e1.time_completed > e1.time_started); assert_eq!( e1.actor, - views::AuditLogEntryActor::SiloUser { + AuditLogEntryActor::SiloUser { silo_user_id: USER_TEST_PRIVILEGED.id(), silo_id: DEFAULT_SILO_ID, } @@ -127,20 +139,20 @@ async fn test_audit_log_list(ctx: &ControlPlaneTestContext) { assert!(e2.time_completed > e2.time_started); // login attempts are unauthenticated (until the user is authenticated) - assert_eq!(e2.actor, views::AuditLogEntryActor::Unauthenticated); + assert_eq!(e2.actor, AuditLogEntryActor::Unauthenticated); // session create was the test suite user in the test suite silo, which // is different from the privileged user, so we need to fetch the user // and silo ID using the session to check them against the audit log let session_authn = AuthnMode::Session(session_token); - let me: views::CurrentUser = NexusRequest::object_get(client, "/v1/me") + let me: CurrentUser = NexusRequest::object_get(client, "/v1/me") .authn_as(session_authn.clone()) .execute_and_parse_unwrap() .await; // get the session ID to verify credential_id let sessions_url = format!("/v1/users/{}/sessions", me.user.id); - let sessions: ResultsPage = + let sessions: ResultsPage = NexusRequest::object_get(client, &sessions_url) .authn_as(session_authn) .execute_and_parse_unwrap() @@ -156,13 +168,13 @@ async fn test_audit_log_list(ctx: &ControlPlaneTestContext) { assert_eq!(e3.operation_id, "project_create"); assert_eq!(e3.source_ip.to_string(), "127.0.0.1"); assert_eq!(e3.user_agent.clone().unwrap(), "A".repeat(256)); - assert_eq!(e3.auth_method, Some(views::AuthMethod::SessionCookie)); + assert_eq!(e3.auth_method, Some(audit::AuthMethod::SessionCookie)); assert_eq!(e3.credential_id, Some(session_id)); assert!(e3.time_started >= t3 && e3.time_started <= t4); assert!(e3.time_completed > e3.time_started); assert_eq!( e3.actor, - views::AuditLogEntryActor::SiloUser { + AuditLogEntryActor::SiloUser { silo_user_id: me.user.id, silo_id: me.user.silo_id, } @@ -184,7 +196,7 @@ async fn test_audit_log_list(ctx: &ControlPlaneTestContext) { to_q(t1) ); let reverse_log = - objects_list_page_authz::(client, &url).await; + objects_list_page_authz::(client, &url).await; assert_eq!(reverse_log.items.len(), 3); assert_eq!(e1.id, reverse_log.items[2].id); assert_eq!(e2.id, reverse_log.items[1].id); @@ -192,8 +204,7 @@ async fn test_audit_log_list(ctx: &ControlPlaneTestContext) { // test pagination cursor. with limit 1, we only get one item, and it's e1 let url = format!("/v1/system/audit-log?start_time={}&limit=1", to_q(t1)); - let log = - objects_list_page_authz::(client, &url).await; + let log = objects_list_page_authz::(client, &url).await; assert_eq!(log.items.len(), 1); assert_eq!(e1.id, log.items[0].id); @@ -202,8 +213,7 @@ async fn test_audit_log_list(ctx: &ControlPlaneTestContext) { "/v1/system/audit-log?page_token={}&limit=1", log.next_page.clone().unwrap() ); - let log = - objects_list_page_authz::(client, &url).await; + let log = objects_list_page_authz::(client, &url).await; assert_eq!(log.items.len(), 1); assert_eq!(e2.id, log.items[0].id); } @@ -218,7 +228,7 @@ async fn test_audit_log_login_local(ctx: &ControlPlaneTestContext) { // Create test silo and user first let silo_name = Name::from_str("test-silo").unwrap(); - let local = shared::SiloIdentityMode::LocalOnly; + let local = SiloIdentityMode::LocalOnly; let silo = create_silo(client, silo_name.as_str(), true, local).await; let test_user = UserId::from_str("test-user").unwrap(); @@ -252,7 +262,7 @@ async fn test_audit_log_login_local(ctx: &ControlPlaneTestContext) { assert_eq!(e1.source_ip.to_string(), "127.0.0.1"); assert_eq!( e1.result, - views::AuditLogEntryResult::Error { + AuditLogEntryResult::Error { http_status_code: 401, error_code: Some("Unauthorized".to_string()), error_message: "credentials missing or invalid".to_string(), @@ -267,7 +277,7 @@ async fn test_audit_log_login_local(ctx: &ControlPlaneTestContext) { assert_eq!(e2.source_ip.to_string(), "127.0.0.1"); assert_eq!( e2.result, - views::AuditLogEntryResult::Success { http_status_code: 204 } + AuditLogEntryResult::Success { http_status_code: 204 } ); assert!(e2.time_started >= t2 && e2.time_started <= t3); assert!(e2.time_completed > e2.time_started); @@ -336,9 +346,9 @@ async fn test_audit_log_create_delete_ops(ctx: &ControlPlaneTestContext) { client, "test-project", "test-instance", - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, - Vec::::new(), - Vec::::new(), + &InstanceNetworkInterfaceAttachment::DefaultIpv4, + Vec::::new(), + Vec::::new(), false, // start=false, so instance is created in stopped state None::, None::, @@ -637,7 +647,7 @@ async fn test_audit_log_coverage(ctx: &ControlPlaneTestContext) { } fn verify_entry( - entry: &views::AuditLogEntry, + entry: &AuditLogEntry, operation_id: &str, request_uri: &str, http_status_code: u16, @@ -647,22 +657,19 @@ fn verify_entry( // Verify operation-specific fields assert_eq!(entry.operation_id, operation_id); assert_eq!(entry.request_uri, request_uri); - assert_eq!( - entry.result, - views::AuditLogEntryResult::Success { http_status_code } - ); + assert_eq!(entry.result, AuditLogEntryResult::Success { http_status_code }); assert!(entry.time_started >= start_time && entry.time_started <= end_time); // Verify fields common to all test-generated entries assert_eq!( entry.actor, - views::AuditLogEntryActor::SiloUser { + AuditLogEntryActor::SiloUser { silo_user_id: USER_TEST_PRIVILEGED.id(), silo_id: DEFAULT_SILO_ID, } ); assert_eq!(entry.source_ip.to_string(), "127.0.0.1"); - assert_eq!(entry.auth_method, Some(views::AuthMethod::Spoof)); + assert_eq!(entry.auth_method, Some(audit::AuthMethod::Spoof)); assert!(entry.time_completed > entry.time_started); } @@ -676,7 +683,7 @@ async fn test_audit_log_access_token_auth(ctx: &ControlPlaneTestContext) { let t1 = Utc::now(); // Make an audited request using the access token - let body = ¶ms::ProjectCreate { + let body = &ProjectCreate { identity: IdentityMetadataCreateParams { name: "token-project".parse().unwrap(), description: "created with access token".to_string(), @@ -702,18 +709,18 @@ async fn test_audit_log_access_token_auth(ctx: &ControlPlaneTestContext) { let entry = &audit_log.items[0]; assert_eq!(entry.operation_id, "project_create"); assert_eq!(entry.request_uri, "/v1/projects"); - assert_eq!(entry.auth_method, Some(views::AuthMethod::AccessToken)); + assert_eq!(entry.auth_method, Some(audit::AuthMethod::AccessToken)); assert_eq!(entry.credential_id, Some(token_grant.token_id)); assert_eq!( entry.actor, - views::AuditLogEntryActor::SiloUser { + AuditLogEntryActor::SiloUser { silo_user_id: USER_TEST_PRIVILEGED.id(), silo_id: DEFAULT_SILO_ID, } ); assert_eq!( entry.result, - views::AuditLogEntryResult::Success { http_status_code: 201 } + AuditLogEntryResult::Success { http_status_code: 201 } ); } @@ -724,19 +731,15 @@ async fn test_audit_log_scim_token_auth(ctx: &ControlPlaneTestContext) { // Create a SAML+SCIM silo (required for SCIM tokens) const SILO_NAME: &str = "scim-audit-test-silo"; - let silo = create_silo( - client, - SILO_NAME, - true, - shared::SiloIdentityMode::SamlScim, - ) - .await; + let silo = + create_silo(client, SILO_NAME, true, silo::SiloIdentityMode::SamlScim) + .await; // Grant the privileged user admin role on this silo so they can create tokens grant_iam( client, &format!("/v1/system/silos/{SILO_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, USER_TEST_PRIVILEGED.id(), AuthnMode::PrivilegedUser, ) @@ -744,7 +747,7 @@ async fn test_audit_log_scim_token_auth(ctx: &ControlPlaneTestContext) { // Create a SCIM token let url = format!("/v1/system/scim/tokens?silo={SILO_NAME}"); - let created_token: views::ScimClientBearerTokenValue = + let created_token: scim::ScimClientBearerTokenValue = object_create_no_body(client, &url).await; let t1 = Utc::now(); @@ -773,14 +776,14 @@ async fn test_audit_log_scim_token_auth(ctx: &ControlPlaneTestContext) { let entry = &audit_log.items[0]; assert_eq!(entry.operation_id, "scim_v2_create_user"); assert_eq!(entry.request_uri, "/scim/v2/Users"); - assert_eq!(entry.auth_method, Some(views::AuthMethod::ScimToken)); + assert_eq!(entry.auth_method, Some(audit::AuthMethod::ScimToken)); assert_eq!(entry.credential_id, Some(created_token.id)); assert_eq!( entry.actor, - views::AuditLogEntryActor::Scim { silo_id: silo.identity.id } + AuditLogEntryActor::Scim { silo_id: silo.identity.id } ); assert_eq!( entry.result, - views::AuditLogEntryResult::Success { http_status_code: 201 } + AuditLogEntryResult::Success { http_status_code: 201 } ); } diff --git a/nexus/tests/integration_tests/authz.rs b/nexus/tests/integration_tests/authz.rs index fda3f5de0e6..0dba542e510 100644 --- a/nexus/tests/integration_tests/authz.rs +++ b/nexus/tests/integration_tests/authz.rs @@ -8,9 +8,10 @@ use nexus_test_utils::http_testing::{AuthnMode, NexusRequest, RequestBuilder}; use nexus_test_utils::resource_helpers::test_params; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::shared; -use nexus_types::external_api::views; +use nexus_types::external_api::identity_provider; +use nexus_types::external_api::silo; +use nexus_types::external_api::ssh_key; +use nexus_types::external_api::user; use omicron_common::api::external::IdentityMetadataCreateParams; use dropshot::ResultsPage; @@ -27,13 +28,9 @@ async fn test_cannot_read_others_ssh_keys(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; // Create a silo with a two unprivileged users - let silo = create_silo( - &client, - "authz", - true, - shared::SiloIdentityMode::LocalOnly, - ) - .await; + let silo = + create_silo(&client, "authz", true, silo::SiloIdentityMode::LocalOnly) + .await; let user1 = create_local_user( client, @@ -59,10 +56,10 @@ async fn test_cannot_read_others_ssh_keys(cptestctx: &ControlPlaneTestContext) { let public_key = "AAAAAAAAAAAAAAA"; // Create a key - let _new_key: views::SshKey = NexusRequest::objects_post( + let _new_key: ssh_key::SshKey = NexusRequest::objects_post( client, "/v1/me/ssh-keys", - ¶ms::SshKeyCreate { + &ssh_key::SshKeyCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: description.to_string(), @@ -78,7 +75,7 @@ async fn test_cannot_read_others_ssh_keys(cptestctx: &ControlPlaneTestContext) { .unwrap(); // user1 can read that key - let _fetched_key: views::SshKey = + let _fetched_key: ssh_key::SshKey = NexusRequest::object_get(client, &format!("/v1/me/ssh-keys/{}", name)) .authn_as(AuthnMode::SiloUser(user1)) .execute() @@ -115,7 +112,7 @@ async fn test_cannot_read_others_ssh_keys(cptestctx: &ControlPlaneTestContext) { .expect("GET request should have failed"); // it also shouldn't show up in their list - let user2_keys: ResultsPage = + let user2_keys: ResultsPage = NexusRequest::object_get(client, &"/v1/me/ssh-keys") .authn_as(AuthnMode::SiloUser(user2)) .execute() @@ -133,13 +130,9 @@ async fn test_list_silo_users_for_unpriv(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; // Create a silo with an unprivileged user - let silo = create_silo( - &client, - "authz", - true, - shared::SiloIdentityMode::LocalOnly, - ) - .await; + let silo = + create_silo(&client, "authz", true, silo::SiloIdentityMode::LocalOnly) + .await; let new_silo_user_id = create_local_user( client, @@ -151,13 +144,9 @@ async fn test_list_silo_users_for_unpriv(cptestctx: &ControlPlaneTestContext) { .id; // Create another silo with another unprivileged user - let silo = create_silo( - &client, - "other", - true, - shared::SiloIdentityMode::LocalOnly, - ) - .await; + let silo = + create_silo(&client, "other", true, silo::SiloIdentityMode::LocalOnly) + .await; create_local_user( client, @@ -168,7 +157,7 @@ async fn test_list_silo_users_for_unpriv(cptestctx: &ControlPlaneTestContext) { .await; // Listing users should work - let users: ResultsPage = + let users: ResultsPage = NexusRequest::object_get(client, &"/v1/users") .authn_as(AuthnMode::SiloUser(new_silo_user_id)) .execute() @@ -190,13 +179,9 @@ async fn test_list_silo_idps_for_unpriv(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; // Create a silo with an unprivileged user - let silo = create_silo( - &client, - "authz", - true, - shared::SiloIdentityMode::LocalOnly, - ) - .await; + let silo = + create_silo(&client, "authz", true, silo::SiloIdentityMode::LocalOnly) + .await; let new_silo_user_id = create_local_user( client, @@ -207,7 +192,7 @@ async fn test_list_silo_idps_for_unpriv(cptestctx: &ControlPlaneTestContext) { .await .id; - let _users: ResultsPage = + let _users: ResultsPage = NexusRequest::object_get( client, &"/v1/system/identity-providers?silo=authz", @@ -226,13 +211,9 @@ async fn test_session_me_for_unpriv(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; // Create a silo with an unprivileged user - let silo = create_silo( - &client, - "authz", - true, - shared::SiloIdentityMode::LocalOnly, - ) - .await; + let silo = + create_silo(&client, "authz", true, silo::SiloIdentityMode::LocalOnly) + .await; let new_silo_user_id = create_local_user( client, @@ -256,13 +237,9 @@ async fn test_silo_read_for_unpriv(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; // Create a silo with an unprivileged user - let silo = create_silo( - &client, - "authz", - true, - shared::SiloIdentityMode::LocalOnly, - ) - .await; + let silo = + create_silo(&client, "authz", true, silo::SiloIdentityMode::LocalOnly) + .await; let new_silo_user_id = create_local_user( client, @@ -274,16 +251,12 @@ async fn test_silo_read_for_unpriv(cptestctx: &ControlPlaneTestContext) { .id; // Create another silo - let _silo = create_silo( - &client, - "other", - true, - shared::SiloIdentityMode::LocalOnly, - ) - .await; + let _silo = + create_silo(&client, "other", true, silo::SiloIdentityMode::LocalOnly) + .await; // That user can access their own silo - let _silo: views::Silo = + let _silo: silo::Silo = NexusRequest::object_get(client, &"/v1/system/silos/authz") .authn_as(AuthnMode::SiloUser(new_silo_user_id)) .execute() diff --git a/nexus/tests/integration_tests/basic.rs b/nexus/tests/integration_tests/basic.rs index 1345bbe07ee..87c65816060 100644 --- a/nexus/tests/integration_tests/basic.rs +++ b/nexus/tests/integration_tests/basic.rs @@ -10,8 +10,9 @@ use dropshot::HttpErrorResponseBody; use http::StatusCode; use http::method::Method; -use nexus_types::external_api::params; -use nexus_types::external_api::views::{self, Project}; +use nexus_types::external_api::project; +use nexus_types::external_api::project::Project; +use nexus_types::external_api::system; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; use omicron_common::api::external::Name; @@ -159,7 +160,7 @@ async fn test_projects_basic(cptestctx: &ControlPlaneTestContext) { let new_project: Project = NexusRequest::objects_post( client, projects_url, - ¶ms::ProjectCreate { + &project::ProjectCreate { identity: IdentityMetadataCreateParams { name: project_name.parse().unwrap(), description: String::from( @@ -258,7 +259,7 @@ async fn test_projects_basic(cptestctx: &ControlPlaneTestContext) { } NexusRequest::new( RequestBuilder::new(client, Method::PUT, "/v1/projects/simproject2") - .body(Some(¶ms::ProjectUpdate { + .body(Some(&project::ProjectUpdate { identity: IdentityMetadataUpdateParams { name: None, description: None, @@ -299,7 +300,7 @@ async fn test_projects_basic(cptestctx: &ControlPlaneTestContext) { // Update "simproject3". We'll make sure that's reflected in the other // requests. - let project_update = params::ProjectUpdate { + let project_update = project::ProjectUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some("Li'l lightnin'".to_string()), @@ -329,7 +330,7 @@ async fn test_projects_basic(cptestctx: &ControlPlaneTestContext) { // Update "simproject3" in a way that changes its name. This is a deeper // operation under the hood. This case also exercises changes to multiple // fields in one request. - let project_update = params::ProjectUpdate { + let project_update = project::ProjectUpdate { identity: IdentityMetadataUpdateParams { name: Some("lil-lightnin".parse().unwrap()), description: Some("little lightning".to_string()), @@ -362,7 +363,7 @@ async fn test_projects_basic(cptestctx: &ControlPlaneTestContext) { .expect("expected success"); // Try to create a project with a name that conflicts with an existing one. - let project_create = params::ProjectCreate { + let project_create = project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "simproject1".parse().unwrap(), description: "a duplicate of simproject1".to_string(), @@ -410,7 +411,7 @@ async fn test_projects_basic(cptestctx: &ControlPlaneTestContext) { )); // Now, really do create another project. - let project_create = params::ProjectCreate { + let project_create = project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "honor-roller".parse().unwrap(), description: "a soapbox racer".to_string(), @@ -552,7 +553,7 @@ async fn test_ping(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; let health = NexusRequest::object_get(client, "/v1/ping") - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; - assert_eq!(health.status, views::PingStatus::Ok); + assert_eq!(health.status, system::PingStatus::Ok); } diff --git a/nexus/tests/integration_tests/certificates.rs b/nexus/tests/integration_tests/certificates.rs index 531f9bf7b5d..cf9ff0da11c 100644 --- a/nexus/tests/integration_tests/certificates.rs +++ b/nexus/tests/integration_tests/certificates.rs @@ -16,9 +16,9 @@ use nexus_test_utils::http_testing::NexusRequest; use nexus_test_utils::resource_helpers::create_certificate; use nexus_test_utils::resource_helpers::delete_certificate; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::shared; -use nexus_types::external_api::views::Certificate; +use nexus_types::external_api::certificate::{ + Certificate, CertificateCreate, ServiceUsingCertificate, +}; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::internal::nexus::Certificate as InternalCertificate; use omicron_test_utils::certificates::CertificateChain; @@ -85,14 +85,14 @@ async fn cert_create_expect_error( key: String, ) -> String { let url = CERTS_URL.to_string(); - let params = params::CertificateCreate { + let params = CertificateCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: String::from("sells rainsticks"), }, cert, key, - service: shared::ServiceUsingCertificate::ExternalApi, + service: ServiceUsingCertificate::ExternalApi, }; NexusRequest::expect_failure_with_body( diff --git a/nexus/tests/integration_tests/console_api.rs b/nexus/tests/integration_tests/console_api.rs index d67c5b27cc3..833ada6f73f 100644 --- a/nexus/tests/integration_tests/console_api.rs +++ b/nexus/tests/integration_tests/console_api.rs @@ -22,11 +22,12 @@ use nexus_test_utils::resource_helpers::{ create_silo, grant_iam, object_create, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::{self, ProjectCreate}; -use nexus_types::external_api::shared::{ - FleetRole, SiloIdentityMode, SiloRole, -}; -use nexus_types::external_api::{shared, views}; +use nexus_types::external_api::identity_provider; +use nexus_types::external_api::policy; +use nexus_types::external_api::policy::{FleetRole, SiloRole}; +use nexus_types::external_api::project::ProjectCreate; +use nexus_types::external_api::silo::SiloIdentityMode; +use nexus_types::external_api::user; use omicron_common::api::external::{Error, IdentityMetadataCreateParams}; use omicron_test_utils::dev::poll::{CondCheckError, wait_for_condition}; @@ -80,7 +81,7 @@ async fn test_sessions(cptestctx: &ControlPlaneTestContext) { // We'll remove that privilege afterwards. let silo_url = format!("/v1/system/silos/{}", DEFAULT_SILO.identity().name); let policy_url = format!("{}/policy", silo_url); - let initial_policy: shared::Policy = + let initial_policy: policy::Policy = NexusRequest::object_get(testctx, &policy_url) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -425,13 +426,13 @@ async fn test_session_me(cptestctx: &ControlPlaneTestContext) { .execute() .await .expect("failed to get current user") - .parsed_body::() + .parsed_body::() .unwrap(); assert_eq!( priv_user, - views::CurrentUser { - user: views::User { + user::CurrentUser { + user: user::User { id: USER_TEST_PRIVILEGED.id(), display_name: USER_TEST_PRIVILEGED.external_id.clone().unwrap(), silo_id: DEFAULT_SILO.id(), @@ -444,13 +445,13 @@ async fn test_session_me(cptestctx: &ControlPlaneTestContext) { let unpriv_user = NexusRequest::object_get(testctx, "/v1/me") .authn_as(AuthnMode::UnprivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; assert_eq!( unpriv_user, - views::CurrentUser { - user: views::User { + user::CurrentUser { + user: user::User { id: USER_TEST_UNPRIVILEGED.id(), display_name: USER_TEST_UNPRIVILEGED .external_id @@ -476,7 +477,7 @@ async fn test_session_me(cptestctx: &ControlPlaneTestContext) { let unpriv_user = NexusRequest::object_get(testctx, "/v1/me") .authn_as(AuthnMode::UnprivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; assert!(!unpriv_user.fleet_viewer); assert!(unpriv_user.silo_admin); @@ -493,7 +494,7 @@ async fn test_session_me(cptestctx: &ControlPlaneTestContext) { let unpriv_user = NexusRequest::object_get(testctx, "/v1/me") .authn_as(AuthnMode::UnprivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; assert!(unpriv_user.fleet_viewer); assert!(unpriv_user.silo_admin); @@ -530,7 +531,7 @@ async fn test_session_me_groups(cptestctx: &ControlPlaneTestContext) { .execute() .await .expect("failed to get current user") - .parsed_body::>() + .parsed_body::>() .unwrap(); assert_eq!(priv_user_groups.items, vec![]); @@ -540,7 +541,7 @@ async fn test_session_me_groups(cptestctx: &ControlPlaneTestContext) { .execute() .await .expect("failed to get current user") - .parsed_body::>() + .parsed_body::>() .unwrap(); assert_eq!(unpriv_user_groups.items, vec![]); @@ -629,7 +630,7 @@ async fn test_login_redirect_multiple_silos( let nidps = i + 1; for j in 0..nidps { let idp_name = format!("idp{}", j); - let idp_params = params::SamlIdentityProviderCreate { + let idp_params = identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: idp_name.parse().unwrap(), description: format!( @@ -639,7 +640,7 @@ async fn test_login_redirect_multiple_silos( }, idp_metadata_source: - params::IdpMetadataSource::Base64EncodedXml { + identity_provider::IdpMetadataSource::Base64EncodedXml { data: base64::engine::general_purpose::STANDARD .encode(SAML_RESPONSE_IDP_DESCRIPTOR), }, @@ -658,7 +659,7 @@ async fn test_login_redirect_multiple_silos( "/v1/system/identity-providers/saml?silo={}", &silo.identity.name ); - let _: views::SamlIdentityProvider = + let _: identity_provider::SamlIdentityProvider = object_create(client, &idp_create_url, &idp_params).await; } } diff --git a/nexus/tests/integration_tests/crucible_replacements.rs b/nexus/tests/integration_tests/crucible_replacements.rs index 2474b3becef..b093a2fbf9f 100644 --- a/nexus/tests/integration_tests/crucible_replacements.rs +++ b/nexus/tests/integration_tests/crucible_replacements.rs @@ -30,8 +30,7 @@ use nexus_test_utils::resource_helpers::create_snapshot; use nexus_test_utils::resource_helpers::delete_snapshot; use nexus_test_utils::resource_helpers::object_create; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::snapshot; use nexus_types::identity::Asset; use nexus_types::identity::Resource; use nexus_types::internal_api::background::*; @@ -778,10 +777,10 @@ async fn test_racing_replacements_for_soft_deleted_disk_volume( let snapshots_url = format!("/v1/snapshots?project={}", PROJECT_NAME); - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "snapshot".parse().unwrap(), description: String::from("a snapshot"), @@ -1255,7 +1254,9 @@ async fn test_racing_replacements_for_soft_deleted_disk_volume( let snapshots_url = get_snapshots_url(); assert_eq!( - collection_list::(&client, &snapshots_url).await.len(), + collection_list::(&client, &snapshots_url) + .await + .len(), 0 ); diff --git a/nexus/tests/integration_tests/device_auth.rs b/nexus/tests/integration_tests/device_auth.rs index 63a045c2316..b28d91adcab 100644 --- a/nexus/tests/integration_tests/device_auth.rs +++ b/nexus/tests/integration_tests/device_auth.rs @@ -21,12 +21,14 @@ use nexus_test_utils::{ resource_helpers::grant_iam, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::{params, views}; use nexus_types::external_api::{ - params::{DeviceAccessTokenRequest, DeviceAuthRequest, DeviceAuthVerify}, - views::{ - DeviceAccessTokenGrant, DeviceAccessTokenType, DeviceAuthResponse, + device::{ + ConsoleSession, DeviceAccessToken, DeviceAccessTokenGrant, + DeviceAccessTokenRequest, DeviceAccessTokenType, DeviceAuthRequest, + DeviceAuthResponse, DeviceAuthVerify, }, + silo::{Silo, SiloAuthSettings, SiloAuthSettingsUpdate}, + user::CurrentUser, }; use omicron_uuid_kinds::SiloUserUuid; @@ -248,7 +250,7 @@ async fn test_device_auth_flow(cptestctx: &ControlPlaneTestContext) { async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) { let testctx = &cptestctx.external_client; - let settings: views::SiloAuthSettings = + let settings: SiloAuthSettings = object_get(testctx, "/v1/auth-settings").await; assert_eq!(settings.device_token_max_ttl_seconds, None); @@ -311,10 +313,10 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) { assert!(error.message.starts_with(&msg)); // set token expiration on silo to 3 seconds - let settings: views::SiloAuthSettings = object_put( + let settings: SiloAuthSettings = object_put( testctx, "/v1/auth-settings", - ¶ms::SiloAuthSettingsUpdate { + &SiloAuthSettingsUpdate { device_token_max_ttl_seconds: NonZeroU32::new(3).into(), }, ) @@ -323,7 +325,7 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) { assert_eq!(settings.device_token_max_ttl_seconds, Some(3)); // might as well test the get endpoint as well - let settings: views::SiloAuthSettings = + let settings: SiloAuthSettings = object_get(testctx, "/v1/auth-settings").await; assert_eq!(settings.device_token_max_ttl_seconds, Some(3)); @@ -379,17 +381,15 @@ async fn test_device_token_expiration(cptestctx: &ControlPlaneTestContext) { assert_eq!(tokens[0].time_expires, None); // now test setting the silo max TTL back to null - let settings: views::SiloAuthSettings = object_put( + let settings: SiloAuthSettings = object_put( testctx, "/v1/auth-settings", - ¶ms::SiloAuthSettingsUpdate { - device_token_max_ttl_seconds: None.into(), - }, + &SiloAuthSettingsUpdate { device_token_max_ttl_seconds: None.into() }, ) .await; assert_eq!(settings.device_token_max_ttl_seconds, None); - let settings: views::SiloAuthSettings = + let settings: SiloAuthSettings = object_get(testctx, "/v1/auth-settings").await; assert_eq!(settings.device_token_max_ttl_seconds, None); } @@ -458,10 +458,10 @@ async fn test_device_token_request_ttl(cptestctx: &ControlPlaneTestContext) { let testctx = &cptestctx.external_client; // Set silo max TTL to 10 seconds - let settings = params::SiloAuthSettingsUpdate { + let settings = SiloAuthSettingsUpdate { device_token_max_ttl_seconds: NonZeroU32::new(10).into(), }; - let _: views::SiloAuthSettings = + let _: SiloAuthSettings = object_put(testctx, "/v1/auth-settings", &settings).await; // Request TTL above the max should fail at verification time @@ -581,10 +581,10 @@ async fn test_device_token_clamps_to_auth_token_when_no_ttl_specified( let testctx = &cptestctx.external_client; // Set silo max TTL to 15 seconds - let _: views::SiloAuthSettings = object_put( + let _: SiloAuthSettings = object_put( testctx, "/v1/auth-settings", - ¶ms::SiloAuthSettingsUpdate { + &SiloAuthSettingsUpdate { device_token_max_ttl_seconds: NonZeroU32::new(15).into(), }, ) @@ -646,10 +646,10 @@ async fn test_device_token_cannot_exceed_auth_token_expiration( let testctx = &cptestctx.external_client; // Set silo max TTL to 15 seconds - let _: views::SiloAuthSettings = object_put( + let _: SiloAuthSettings = object_put( testctx, "/v1/auth-settings", - ¶ms::SiloAuthSettingsUpdate { + &SiloAuthSettingsUpdate { device_token_max_ttl_seconds: NonZeroU32::new(15).into(), }, ) @@ -716,14 +716,14 @@ async fn test_session_auth_does_not_clamp_device_token_ttl( let testctx = &cptestctx.external_client; // Get the silo for the privileged user - let me = object_get::(testctx, "/v1/me").await; + let me = object_get::(testctx, "/v1/me").await; let silo_name = me.silo_name.as_str(); // Set silo max TTL to 15 seconds - let _: views::SiloAuthSettings = object_put( + let _: SiloAuthSettings = object_put( testctx, "/v1/auth-settings", - ¶ms::SiloAuthSettingsUpdate { + &SiloAuthSettingsUpdate { device_token_max_ttl_seconds: NonZeroU32::new(15).into(), }, ) @@ -731,7 +731,7 @@ async fn test_session_auth_does_not_clamp_device_token_ttl( // Create a local user and get a session token let silo_url = format!("/v1/system/silos/{silo_name}"); - let test_silo: views::Silo = object_get(testctx, &silo_url).await; + let test_silo: Silo = object_get(testctx, &silo_url).await; let _test_user = create_local_user( testctx, &test_silo, @@ -796,7 +796,7 @@ async fn test_admin_logout_deletes_tokens_and_sessions( let silo_name = cptestctx.silo_name.as_str(); // create users so we can have user IDs to pass to authn_as let silo_url = format!("/v1/system/silos/{}", silo_name); - let test_suite_silo: views::Silo = object_get(testctx, &silo_url).await; + let test_suite_silo: Silo = object_get(testctx, &silo_url).await; let user1 = create_local_user( testctx, &test_suite_silo, @@ -970,7 +970,7 @@ async fn test_session_list_with_config( let silo_name = cptestctx.silo_name.as_str(); let silo_url = format!("/v1/system/silos/{}", silo_name); - let test_suite_silo: views::Silo = object_get(testctx, &silo_url).await; + let test_suite_silo: Silo = object_get(testctx, &silo_url).await; let user1 = create_local_user( testctx, &test_suite_silo, @@ -997,10 +997,10 @@ async fn test_session_list_with_config( async fn get_tokens_priv( testctx: &ClientTestContext, -) -> Vec { +) -> Vec { NexusRequest::object_get(testctx, "/v1/me/access-tokens") .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items } @@ -1008,10 +1008,10 @@ async fn get_tokens_priv( async fn list_user_tokens( testctx: &ClientTestContext, user_id: SiloUserUuid, -) -> Vec { +) -> Vec { NexusRequest::object_get(testctx, "/v1/me/access-tokens") .authn_as(AuthnMode::SiloUser(user_id)) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items } @@ -1019,11 +1019,11 @@ async fn list_user_tokens( async fn list_user_sessions( testctx: &ClientTestContext, user_id: SiloUserUuid, -) -> Vec { +) -> Vec { let url = format!("/v1/users/{}/sessions", user_id); NexusRequest::object_get(testctx, &url) .authn_as(AuthnMode::SiloUser(user_id)) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items } @@ -1089,10 +1089,10 @@ async fn fetch_device_token( async fn get_tokens_unpriv( testctx: &ClientTestContext, -) -> Vec { +) -> Vec { NexusRequest::object_get(testctx, "/v1/me/access-tokens") .authn_as(AuthnMode::UnprivilegedUser) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items } diff --git a/nexus/tests/integration_tests/disks.rs b/nexus/tests/integration_tests/disks.rs index 1b2353dff85..8f7581cc9b5 100644 --- a/nexus/tests/integration_tests/disks.rs +++ b/nexus/tests/integration_tests/disks.rs @@ -30,8 +30,11 @@ use nexus_test_utils::resource_helpers::create_instance; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils::resource_helpers::object_create_error; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::disk; +use nexus_types::external_api::image; +use nexus_types::external_api::path_params; +use nexus_types::external_api::sled; +use nexus_types::external_api::snapshot; use nexus_types::identity::Asset; use nexus_types::silo::DEFAULT_SILO_ID; use omicron_common::api::external::ByteCount; @@ -359,14 +362,14 @@ async fn test_disk_create_disk_that_already_exists_fails( let disks_url = get_disks_url(); // Create a disk. - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: ByteCount::from_gibibytes_u32(1), @@ -578,7 +581,7 @@ async fn test_disk_move_between_instances(cptestctx: &ControlPlaneTestContext) { let error: HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url_instance2_attach_disk) - .body(Some(¶ms::DiskPath { + .body(Some(&path_params::DiskPath { disk: disk.identity.name.clone().into(), })) .expect_status(Some(StatusCode::BAD_REQUEST)), @@ -639,7 +642,7 @@ async fn test_disk_move_between_instances(cptestctx: &ControlPlaneTestContext) { // instance (the first one). let error: HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url_instance_attach_disk) - .body(Some(¶ms::DiskPath { + .body(Some(&path_params::DiskPath { disk: disk.identity.name.clone().into(), })) .expect_status(Some(StatusCode::BAD_REQUEST)), @@ -765,14 +768,14 @@ async fn test_disk_region_creation_failure( ); let disks_url = get_disks_url(); - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -818,14 +821,14 @@ async fn test_disk_invalid_block_size_rejected( let disks_url = get_disks_url(); - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize(1024), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize(1024), }, }, size: disk_size, @@ -863,14 +866,14 @@ async fn test_disk_reject_total_size_not_divisible_by_block_size( ); let disks_url = get_disks_url(); - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -899,14 +902,14 @@ async fn test_disk_reject_total_size_less_than_min_disk_size_bytes( // Attempt to allocate the disk, observe a server error. let disks_url = get_disks_url(); - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -944,14 +947,14 @@ async fn test_disk_reject_total_size_greater_than_max_disk_size_bytes( // Atempt to allocate the disk, observe a server error. let disks_url = get_disks_url(); - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -990,14 +993,14 @@ async fn test_disk_reject_total_size_not_divisible_by_min_disk_size( // Attempt to allocate the disk, observe a server error. let disks_url = get_disks_url(); - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1042,14 +1045,14 @@ async fn test_disk_backed_by_multiple_region_sets( // Ask for a 20 gibibyte disk. let disk_size = ByteCount::from_gibibytes_u32(20); let disks_url = get_disks_url(); - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1080,14 +1083,14 @@ async fn test_disk_too_big(cptestctx: &ControlPlaneTestContext) { // Ask for a 300 gibibyte disk (but only 16 is available) let disk_size = ByteCount::from_gibibytes_u32(300); let disks_url = get_disks_url(); - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1169,14 +1172,14 @@ async fn test_disk_virtual_provisioning_collection( // in which it was allocated let disk_size = ByteCount::from_gibibytes_u32(1); let disks_url = get_disks_url(); - let disk_one = params::DiskCreate { + let disk_one = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk-one".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1230,14 +1233,14 @@ async fn test_disk_virtual_provisioning_collection( // Each project should be using "one disk" of real storage, but the org // should be using both. let disks_url = format!("/v1/disks?project={}", PROJECT_NAME_2); - let disk_one = params::DiskCreate { + let disk_one = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk-two".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1334,14 +1337,14 @@ async fn test_disk_virtual_provisioning_collection_failed_delete( // Create a 1 GB disk let disk_size = ByteCount::from_gibibytes_u32(1); let disks_url = get_disks_url(); - let disk_one = params::DiskCreate { + let disk_one = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk-one".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1470,14 +1473,14 @@ async fn test_phantom_disk_rename(cptestctx: &ControlPlaneTestContext) { // Create a 1 GB disk let disk_size = ByteCount::from_gibibytes_u32(1); let disks_url = get_disks_url(); - let disk_one = params::DiskCreate { + let disk_one = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk-one".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1612,14 +1615,14 @@ async fn test_disk_size_accounting(cptestctx: &ControlPlaneTestContext) { let disk_size = ByteCount::from_gibibytes_u32(7); let disks_url = get_disks_url(); - let disk_one = params::DiskCreate { + let disk_one = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk-one".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1649,14 +1652,14 @@ async fn test_disk_size_accounting(cptestctx: &ControlPlaneTestContext) { // Ask for a 6 gibibyte disk, this should fail because there isn't space // available. let disk_size = ByteCount::from_gibibytes_u32(6); - let disk_two = params::DiskCreate { + let disk_two = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk-two".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1703,14 +1706,14 @@ async fn test_disk_size_accounting(cptestctx: &ControlPlaneTestContext) { // Ask for a 10 gibibyte disk. let disk_size = ByteCount::from_gibibytes_u32(10); - let disk_three = params::DiskCreate { + let disk_three = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk-three".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1756,14 +1759,14 @@ async fn test_multiple_disks_multiple_zpools( let disk_size = ByteCount::from_gibibytes_u32(10); let disks_url = get_disks_url(); - let disk_one = params::DiskCreate { + let disk_one = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk-one".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1781,14 +1784,14 @@ async fn test_multiple_disks_multiple_zpools( // Ask for another 10 gibibyte disk let disk_size = ByteCount::from_gibibytes_u32(10); - let disk_two = params::DiskCreate { + let disk_two = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk-two".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1812,14 +1815,14 @@ async fn test_disk_create_for_importing(cptestctx: &ControlPlaneTestContext) { create_project_and_pool(client).await; let disks_url = get_disks_url(); - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::ImportingBlocks { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::ImportingBlocks { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: ByteCount::from_gibibytes_u32(1), @@ -1859,14 +1862,14 @@ async fn test_project_delete_disk_no_auth_idempotent( // Create a disk let disks_url = get_disks_url(); - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: ByteCount::from_gibibytes_u32(1), @@ -1947,8 +1950,8 @@ async fn test_single_region_allocate(cptestctx: &ControlPlaneTestContext) { &opctx, RegionAllocationFor::DiskVolume { volume_id }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, size: ByteCount::from_gibibytes_u32(1), }, @@ -2042,8 +2045,8 @@ async fn test_region_allocation_strategy_random_is_idempotent( .disk_region_allocate( &opctx, db_disk.volume_id(), - ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from( + &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from( region.block_size().to_bytes() as u32, ) .unwrap(), @@ -2083,8 +2086,8 @@ async fn test_region_allocation_strategy_random_is_idempotent_arbitrary( &opctx, RegionAllocationFor::DiskVolume { volume_id }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, size: ByteCount::from_gibibytes_u32(1), }, @@ -2103,8 +2106,8 @@ async fn test_region_allocation_strategy_random_is_idempotent_arbitrary( &opctx, RegionAllocationFor::DiskVolume { volume_id }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, size: ByteCount::from_gibibytes_u32(1), }, @@ -2173,8 +2176,8 @@ async fn test_single_region_allocate_for_replace( &opctx, RegionAllocationFor::DiskVolume { volume_id: db_disk.volume_id() }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from( + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from( region_to_replace.block_size().to_bytes() as u32, ) .unwrap(), @@ -2254,8 +2257,8 @@ async fn test_single_region_allocate_for_replace_not_enough_zpools( &opctx, RegionAllocationFor::DiskVolume { volume_id: db_disk.volume_id() }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from( + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from( region_to_replace.block_size().to_bytes() as u32, ) .unwrap(), @@ -2275,8 +2278,8 @@ async fn test_single_region_allocate_for_replace_not_enough_zpools( &opctx, RegionAllocationFor::DiskVolume { volume_id: db_disk.volume_id() }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from( + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from( region_to_replace.block_size().to_bytes() as u32, ) .unwrap(), @@ -2400,9 +2403,7 @@ async fn test_disk_expunge(cptestctx: &ControlPlaneTestContext) { .make_request( Method::POST, "/sleds/expunge", - Some(params::SledSelector { - sled: SLED_AGENT_UUID.parse().unwrap(), - }), + Some(sled::SledSelector { sled: SLED_AGENT_UUID.parse().unwrap() }), StatusCode::OK, ) .await @@ -2489,14 +2490,14 @@ async fn test_do_not_provision_on_dataset_not_enough( let disks_url = get_disks_url(); - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: ByteCount::from_gibibytes_u32(1), @@ -2556,14 +2557,14 @@ async fn test_zpool_control_plane_storage_buffer( let disks_url = get_disks_url(); // Creating a 8G disk will work (10G size used due to reservation overhead) - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk1".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: ByteCount::from_gibibytes_u32(8), @@ -2581,14 +2582,14 @@ async fn test_zpool_control_plane_storage_buffer( // Creating a 4G disk will also work (5G size used due to reservation // overhead plus the previous 10G size used is less than 16G) - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk2".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: ByteCount::from_gibibytes_u32(4), @@ -2680,14 +2681,14 @@ async fn test_list_all_types_of_disk(cptestctx: &ControlPlaneTestContext) { let disks_url = get_disks_url(); // Distributed disk - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk1".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: ByteCount::from_gibibytes_u32(1), @@ -2705,12 +2706,12 @@ async fn test_list_all_types_of_disk(cptestctx: &ControlPlaneTestContext) { // Local disk - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk2".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Local {}, + disk_backend: disk::DiskBackend::Local {}, size: ByteCount::from_gibibytes_u32(1), }; @@ -2747,14 +2748,14 @@ async fn test_create_read_only_disk_from_snapshot( let disks_url = get_disks_url(); // Define a global image - let image_create_params = params::ImageCreate { + let image_create_params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "alpine".parse().unwrap(), description: String::from( "you can boot any image, as long as it's alpine", ), }, - source: params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + source: image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, os: "alpine".to_string(), version: "edge".to_string(), }; @@ -2763,20 +2764,20 @@ async fn test_create_read_only_disk_from_snapshot( let image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; // Create a base disk from this image, which we will then create a snapshot // from in order to create our read-only disk from that snapshot. let disk_size = ByteCount::from_gibibytes_u32(2); let base_disk_name = "base-disk"; - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: image.identity.id, read_only: false, }, @@ -2945,14 +2946,14 @@ async fn test_cannot_snapshot_read_only_disk( let disks_url = get_disks_url(); // Define a global image - let image_create_params = params::ImageCreate { + let image_create_params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "alpine".parse().unwrap(), description: String::from( "you can boot any image, as long as it's alpine", ), }, - source: params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + source: image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, os: "alpine".to_string(), version: "edge".to_string(), }; @@ -2961,20 +2962,20 @@ async fn test_cannot_snapshot_read_only_disk( let image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; // Create a base disk from this image, which we will then create a snapshot // from in order to create our read-only disk from that snapshot. let disk_size = ByteCount::from_gibibytes_u32(2); let base_disk_name = "base-disk"; - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: image.identity.id, read_only: false, }, @@ -3023,7 +3024,7 @@ async fn test_cannot_snapshot_read_only_disk( object_create_error( client, &format!("/v1/snapshots?project={}", PROJECT_NAME), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "will-not-work".parse().unwrap(), description: String::from("no thanks"), @@ -3059,7 +3060,7 @@ async fn disk_post( ) -> Disk { NexusRequest::new( RequestBuilder::new(client, Method::POST, url) - .body(Some(¶ms::DiskPath { disk: disk_name.into() })) + .body(Some(&path_params::DiskPath { disk: disk_name.into() })) .expect_status(Some(StatusCode::ACCEPTED)), ) .authn_as(AuthnMode::PrivilegedUser) diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index 2fae4d7825a..190c3091634 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -19,13 +19,36 @@ use nexus_test_utils::RACK_UUID; use nexus_test_utils::SLED_AGENT_UUID; use nexus_test_utils::SWITCH_UUID; use nexus_test_utils::resource_helpers::test_params; -use nexus_types::external_api::params; -use nexus_types::external_api::params::PrivateIpStackCreate; -use nexus_types::external_api::shared; -use nexus_types::external_api::shared::IpRange; -use nexus_types::external_api::shared::IpVersion; -use nexus_types::external_api::shared::Ipv4Range; -use nexus_types::external_api::views::SledProvisionPolicy; +use nexus_types::external_api::affinity; +use nexus_types::external_api::alert; +use nexus_types::external_api::certificate; +use nexus_types::external_api::disk; +use nexus_types::external_api::external_subnet; +use nexus_types::external_api::floating_ip; +use nexus_types::external_api::hardware; +use nexus_types::external_api::identity_provider; +use nexus_types::external_api::image; +use nexus_types::external_api::instance; +use nexus_types::external_api::instance::PrivateIpStackCreate; +use nexus_types::external_api::internet_gateway; +use nexus_types::external_api::ip_pool; +use nexus_types::external_api::multicast; +use nexus_types::external_api::networking; +use nexus_types::external_api::path_params; +use nexus_types::external_api::policy; +use nexus_types::external_api::project; +use nexus_types::external_api::rack; +use nexus_types::external_api::silo; +use nexus_types::external_api::sled; +use nexus_types::external_api::snapshot; +use nexus_types::external_api::ssh_key; +use nexus_types::external_api::subnet_pool; +use nexus_types::external_api::support_bundle; +use nexus_types::external_api::system; +use nexus_types::external_api::timeseries; +use nexus_types::external_api::update; +use nexus_types::external_api::vpc; +use omicron_common::address::{IpRange, IpVersion, Ipv4Range}; use omicron_common::api::external::AddressLotKind; use omicron_common::api::external::AffinityPolicy; use omicron_common::api::external::AllowedSourceIps; @@ -80,8 +103,8 @@ pub static HARDWARE_RACK_MEMBERSHIP_ABORT_URL: LazyLock = format!("/v1/system/hardware/racks/{}/membership/abort", RACK_UUID) }); pub static DEMO_RACK_ADD_SLEDS_REQUEST: LazyLock< - params::RackMembershipAddSledsRequest, -> = LazyLock::new(|| params::RackMembershipAddSledsRequest { + rack::RackMembershipAddSledsRequest, +> = LazyLock::new(|| rack::RackMembershipAddSledsRequest { sled_ids: BTreeSet::from([sled_hardware_types::BaseboardId { serial_number: "demo-serial".to_string(), part_number: "demo-part".to_string(), @@ -89,9 +112,9 @@ pub static DEMO_RACK_ADD_SLEDS_REQUEST: LazyLock< }); pub static DEMO_SLED_PROVISION_POLICY: LazyLock< - params::SledProvisionPolicyParams, -> = LazyLock::new(|| params::SledProvisionPolicyParams { - state: SledProvisionPolicy::NonProvisionable, + sled::SledProvisionPolicyParams, +> = LazyLock::new(|| sled::SledProvisionPolicyParams { + state: sled::SledProvisionPolicy::NonProvisionable, }); pub static HARDWARE_SWITCH_URL: LazyLock = @@ -107,8 +130,8 @@ pub static HARDWARE_SLED_DISK_URL: LazyLock = LazyLock::new(|| { pub static SLED_INSTANCES_URL: LazyLock = LazyLock::new(|| { format!("/v1/system/hardware/sleds/{}/instances", SLED_AGENT_UUID) }); -pub static DEMO_UNINITIALIZED_SLED: LazyLock = - LazyLock::new(|| params::UninitializedSledId { +pub static DEMO_UNINITIALIZED_SLED: LazyLock = + LazyLock::new(|| hardware::UninitializedSledId { serial: "demo-serial".to_string(), part: "demo-part".to_string(), }); @@ -184,15 +207,15 @@ pub static DEMO_SILO_POLICY_URL: LazyLock = LazyLock::new(|| format!("/v1/system/silos/{}/policy", *DEMO_SILO_NAME)); pub static DEMO_SILO_QUOTAS_URL: LazyLock = LazyLock::new(|| format!("/v1/system/silos/{}/quotas", *DEMO_SILO_NAME)); -pub static DEMO_SILO_CREATE: LazyLock = - LazyLock::new(|| params::SiloCreate { +pub static DEMO_SILO_CREATE: LazyLock = + LazyLock::new(|| silo::SiloCreate { identity: IdentityMetadataCreateParams { name: DEMO_SILO_NAME.clone(), description: String::from(""), }, - quotas: params::SiloQuotasCreate::arbitrarily_high_default(), + quotas: silo::SiloQuotasCreate::arbitrarily_high_default(), discoverable: true, - identity_mode: shared::SiloIdentityMode::SamlJit, + identity_mode: silo::SiloIdentityMode::SamlJit, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: Default::default(), @@ -268,8 +291,8 @@ pub static DEMO_PROJECT_URL_VPCS: LazyLock = pub static DEMO_PROJECT_URL_FIPS: LazyLock = LazyLock::new(|| { format!("/v1/floating-ips?project={}", *DEMO_PROJECT_NAME) }); -pub static DEMO_PROJECT_CREATE: LazyLock = - LazyLock::new(|| params::ProjectCreate { +pub static DEMO_PROJECT_CREATE: LazyLock = + LazyLock::new(|| project::ProjectCreate { identity: IdentityMetadataCreateParams { name: DEMO_PROJECT_NAME.clone(), description: String::from(""), @@ -291,8 +314,8 @@ pub static DEMO_VPC_URL_ROUTERS: LazyLock = LazyLock::new(|| format!("/v1/vpc-routers?{}", *DEMO_VPC_SELECTOR)); pub static DEMO_VPC_URL_SUBNETS: LazyLock = LazyLock::new(|| format!("/v1/vpc-subnets?{}", *DEMO_VPC_SELECTOR)); -pub static DEMO_VPC_CREATE: LazyLock = - LazyLock::new(|| params::VpcCreate { +pub static DEMO_VPC_CREATE: LazyLock = + LazyLock::new(|| vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: DEMO_VPC_NAME.clone(), description: String::from(""), @@ -314,8 +337,8 @@ pub static DEMO_VPC_SUBNET_INTERFACES_URL: LazyLock = *DEMO_VPC_SUBNET_NAME, *DEMO_VPC_SELECTOR ) }); -pub static DEMO_VPC_SUBNET_CREATE: LazyLock = - LazyLock::new(|| params::VpcSubnetCreate { +pub static DEMO_VPC_SUBNET_CREATE: LazyLock = + LazyLock::new(|| vpc::VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: DEMO_VPC_SUBNET_NAME.clone(), description: String::from(""), @@ -340,8 +363,8 @@ pub static DEMO_VPC_ROUTER_URL_ROUTES: LazyLock = LazyLock::new(|| { *DEMO_PROJECT_NAME, *DEMO_VPC_NAME, *DEMO_VPC_ROUTER_NAME ) }); -pub static DEMO_VPC_ROUTER_CREATE: LazyLock = - LazyLock::new(|| params::VpcRouterCreate { +pub static DEMO_VPC_ROUTER_CREATE: LazyLock = + LazyLock::new(|| vpc::VpcRouterCreate { identity: IdentityMetadataCreateParams { name: DEMO_VPC_ROUTER_NAME.clone(), description: String::from(""), @@ -360,8 +383,8 @@ pub static DEMO_ROUTER_ROUTE_URL: LazyLock = LazyLock::new(|| { *DEMO_VPC_ROUTER_NAME ) }); -pub static DEMO_ROUTER_ROUTE_CREATE: LazyLock = - LazyLock::new(|| params::RouterRouteCreate { +pub static DEMO_ROUTER_ROUTE_CREATE: LazyLock = + LazyLock::new(|| vpc::RouterRouteCreate { identity: IdentityMetadataCreateParams { name: DEMO_ROUTER_ROUTE_NAME.clone(), description: String::from(""), @@ -386,16 +409,16 @@ pub static DEMO_INTERNET_GATEWAY_URL: LazyLock = LazyLock::new(|| { ) }); pub static DEMO_INTERNET_GATEWAY_CREATE: LazyLock< - params::InternetGatewayCreate, -> = LazyLock::new(|| params::InternetGatewayCreate { + internet_gateway::InternetGatewayCreate, +> = LazyLock::new(|| internet_gateway::InternetGatewayCreate { identity: IdentityMetadataCreateParams { name: DEMO_INTERNET_GATEWAY_NAME.clone(), description: String::from(""), }, }); pub static DEMO_INTERNET_GATEWAY_IP_POOL_CREATE: LazyLock< - params::InternetGatewayIpPoolCreate, -> = LazyLock::new(|| params::InternetGatewayIpPoolCreate { + internet_gateway::InternetGatewayIpPoolCreate, +> = LazyLock::new(|| internet_gateway::InternetGatewayIpPoolCreate { identity: IdentityMetadataCreateParams { name: DEMO_INTERNET_GATEWAY_NAME.clone(), description: String::from(""), @@ -403,8 +426,8 @@ pub static DEMO_INTERNET_GATEWAY_IP_POOL_CREATE: LazyLock< ip_pool: NameOrId::Id(uuid::Uuid::new_v4()), }); pub static DEMO_INTERNET_GATEWAY_IP_ADDRESS_CREATE: LazyLock< - params::InternetGatewayIpAddressCreate, -> = LazyLock::new(|| params::InternetGatewayIpAddressCreate { + internet_gateway::InternetGatewayIpAddressCreate, +> = LazyLock::new(|| internet_gateway::InternetGatewayIpAddressCreate { identity: IdentityMetadataCreateParams { name: DEMO_INTERNET_GATEWAY_NAME.clone(), description: String::from(""), @@ -461,38 +484,37 @@ pub static DEMO_DISKS_URL: LazyLock = pub static DEMO_DISK_URL: LazyLock = LazyLock::new(|| { format!("/v1/disks/{}?{}", *DEMO_DISK_NAME, *DEMO_PROJECT_SELECTOR) }); -pub static DEMO_DISK_CREATE: LazyLock = - LazyLock::new(|| { - params::DiskCreate { - identity: IdentityMetadataCreateParams { - name: DEMO_DISK_NAME.clone(), - description: "".parse().unwrap(), - }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(4096).unwrap(), - }, +pub static DEMO_DISK_CREATE: LazyLock = LazyLock::new(|| { + disk::DiskCreate { + identity: IdentityMetadataCreateParams { + name: DEMO_DISK_NAME.clone(), + description: "".parse().unwrap(), + }, + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(4096).unwrap(), }, - size: ByteCount::from_gibibytes_u32( - // divide by at least two to leave space for snapshot blocks - DiskTest::DEFAULT_ZPOOL_SIZE_GIB / 5, - ), - } - }); + }, + size: ByteCount::from_gibibytes_u32( + // divide by at least two to leave space for snapshot blocks + DiskTest::DEFAULT_ZPOOL_SIZE_GIB / 5, + ), + } +}); // Related to importing blocks from an external source pub static DEMO_IMPORT_DISK_NAME: LazyLock = LazyLock::new(|| "demo-import-disk".parse().unwrap()); -pub static DEMO_IMPORT_DISK_CREATE: LazyLock = +pub static DEMO_IMPORT_DISK_CREATE: LazyLock = LazyLock::new(|| { - params::DiskCreate { + disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DEMO_IMPORT_DISK_NAME.clone(), description: "".parse().unwrap(), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::ImportingBlocks { - block_size: params::BlockSize::try_from(4096).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::ImportingBlocks { + block_size: disk::BlockSize::try_from(4096).unwrap(), }, }, size: ByteCount::from_gibibytes_u32( @@ -556,8 +578,8 @@ pub static DEMO_AFFINITY_GROUP_INSTANCE_MEMBER_URL: LazyLock = *DEMO_PROJECT_SELECTOR ) }); -pub static DEMO_AFFINITY_GROUP_CREATE: LazyLock = - LazyLock::new(|| params::AffinityGroupCreate { +pub static DEMO_AFFINITY_GROUP_CREATE: LazyLock = + LazyLock::new(|| affinity::AffinityGroupCreate { identity: IdentityMetadataCreateParams { name: DEMO_AFFINITY_GROUP_NAME.clone(), description: String::from(""), @@ -565,8 +587,8 @@ pub static DEMO_AFFINITY_GROUP_CREATE: LazyLock = policy: AffinityPolicy::Allow, failure_domain: FailureDomain::Sled, }); -pub static DEMO_AFFINITY_GROUP_UPDATE: LazyLock = - LazyLock::new(|| params::AffinityGroupUpdate { +pub static DEMO_AFFINITY_GROUP_UPDATE: LazyLock = + LazyLock::new(|| affinity::AffinityGroupUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some(String::from("an updated description")), @@ -603,8 +625,8 @@ pub static DEMO_ANTI_AFFINITY_GROUP_INSTANCE_MEMBER_URL: LazyLock = ) }); pub static DEMO_ANTI_AFFINITY_GROUP_CREATE: LazyLock< - params::AntiAffinityGroupCreate, -> = LazyLock::new(|| params::AntiAffinityGroupCreate { + affinity::AntiAffinityGroupCreate, +> = LazyLock::new(|| affinity::AntiAffinityGroupCreate { identity: IdentityMetadataCreateParams { name: DEMO_ANTI_AFFINITY_GROUP_NAME.clone(), description: String::from(""), @@ -613,8 +635,8 @@ pub static DEMO_ANTI_AFFINITY_GROUP_CREATE: LazyLock< failure_domain: FailureDomain::Sled, }); pub static DEMO_ANTI_AFFINITY_GROUP_UPDATE: LazyLock< - params::AntiAffinityGroupUpdate, -> = LazyLock::new(|| params::AntiAffinityGroupUpdate { + affinity::AntiAffinityGroupUpdate, +> = LazyLock::new(|| affinity::AntiAffinityGroupUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some(String::from("an updated description")), @@ -727,8 +749,8 @@ pub static DEMO_INSTANCE_EXTERNAL_SUBNETS_URL: LazyLock = *DEMO_INSTANCE_NAME, *DEMO_PROJECT_SELECTOR ) }); -pub static DEMO_INSTANCE_CREATE: LazyLock = - LazyLock::new(|| params::InstanceCreate { +pub static DEMO_INSTANCE_CREATE: LazyLock = + LazyLock::new(|| instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: DEMO_INSTANCE_NAME.clone(), description: String::from(""), @@ -739,9 +761,9 @@ pub static DEMO_INSTANCE_CREATE: LazyLock = user_data: vec![], ssh_public_keys: Some(Vec::new()), network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, - external_ips: vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + external_ips: vec![instance::ExternalIpCreate::Ephemeral { + pool_selector: ip_pool::PoolSelector::Explicit { pool: DEMO_IP_POOL_NAME.clone().into(), }, }], @@ -753,8 +775,8 @@ pub static DEMO_INSTANCE_CREATE: LazyLock = anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), }); -pub static DEMO_STOPPED_INSTANCE_CREATE: LazyLock = - LazyLock::new(|| params::InstanceCreate { +pub static DEMO_STOPPED_INSTANCE_CREATE: LazyLock = + LazyLock::new(|| instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: DEMO_STOPPED_INSTANCE_NAME.clone(), description: String::from(""), @@ -765,9 +787,9 @@ pub static DEMO_STOPPED_INSTANCE_CREATE: LazyLock = user_data: vec![], ssh_public_keys: Some(Vec::new()), network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, - external_ips: vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + external_ips: vec![instance::ExternalIpCreate::Ephemeral { + pool_selector: ip_pool::PoolSelector::Explicit { pool: DEMO_IP_POOL_NAME.clone().into(), }, }], @@ -779,8 +801,8 @@ pub static DEMO_STOPPED_INSTANCE_CREATE: LazyLock = anti_affinity_groups: Vec::new(), multicast_groups: Vec::new(), }); -pub static DEMO_INSTANCE_UPDATE: LazyLock = - LazyLock::new(|| params::InstanceUpdate { +pub static DEMO_INSTANCE_UPDATE: LazyLock = + LazyLock::new(|| instance::InstanceUpdate { boot_disk: Nullable(None), cpu_platform: Nullable(None), auto_restart_policy: Nullable(None), @@ -799,8 +821,8 @@ pub static DEMO_INSTANCE_NIC_URL: LazyLock = LazyLock::new(|| { ) }); pub static DEMO_INSTANCE_NIC_CREATE: LazyLock< - params::InstanceNetworkInterfaceCreate, -> = LazyLock::new(|| params::InstanceNetworkInterfaceCreate { + instance::InstanceNetworkInterfaceCreate, +> = LazyLock::new(|| instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: DEMO_INSTANCE_NIC_NAME.clone(), description: String::from(""), @@ -810,8 +832,8 @@ pub static DEMO_INSTANCE_NIC_CREATE: LazyLock< ip_config: PrivateIpStackCreate::auto_ipv4(), }); pub static DEMO_INSTANCE_NIC_PUT: LazyLock< - params::InstanceNetworkInterfaceUpdate, -> = LazyLock::new(|| params::InstanceNetworkInterfaceUpdate { + instance::InstanceNetworkInterfaceUpdate, +> = LazyLock::new(|| instance::InstanceNetworkInterfaceUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some(String::from("an updated description")), @@ -828,15 +850,15 @@ pub const DEMO_CERTIFICATE_URL: &'static str = pub static DEMO_CERTIFICATE: LazyLock = LazyLock::new(|| { CertificateChain::new(format!("*.sys.{DNS_ZONE_EXTERNAL_TESTING}")) }); -pub static DEMO_CERTIFICATE_CREATE: LazyLock = - LazyLock::new(|| params::CertificateCreate { +pub static DEMO_CERTIFICATE_CREATE: LazyLock = + LazyLock::new(|| certificate::CertificateCreate { identity: IdentityMetadataCreateParams { name: DEMO_CERTIFICATE_NAME.clone(), description: String::from(""), }, cert: DEMO_CERTIFICATE.cert_chain_as_pem(), key: DEMO_CERTIFICATE.end_cert_private_key_as_pem(), - service: shared::ServiceUsingCertificate::ExternalApi, + service: certificate::ServiceUsingCertificate::ExternalApi, }); // Multicast groups and members @@ -867,8 +889,8 @@ pub static DEMO_INSTANCE_MULTICAST_GROUP_JOIN_URL: LazyLock = ) }); pub static DEMO_INSTANCE_MULTICAST_GROUP_JOIN: LazyLock< - params::InstanceMulticastGroupJoin, -> = LazyLock::new(|| params::InstanceMulticastGroupJoin { + multicast::InstanceMulticastGroupJoin, +> = LazyLock::new(|| multicast::InstanceMulticastGroupJoin { source_ips: None, ip_version: None, }); @@ -885,8 +907,8 @@ pub static DEMO_SWITCH_PORT_SETTINGS_APPLY_URL: LazyLock = ) }); pub static DEMO_SWITCH_PORT_SETTINGS: LazyLock< - params::SwitchPortApplySettings, -> = LazyLock::new(|| params::SwitchPortApplySettings { + networking::SwitchPortApplySettings, +> = LazyLock::new(|| networking::SwitchPortApplySettings { port_settings: NameOrId::Name("portofino".parse().unwrap()), }); /* TODO requires dpd access @@ -909,8 +931,8 @@ pub static DEMO_LOOPBACK_URL: LazyLock = LazyLock::new(|| { "203.0.113.99/24", ) }); -pub static DEMO_LOOPBACK_CREATE: LazyLock = - LazyLock::new(|| params::LoopbackAddressCreate { +pub static DEMO_LOOPBACK_CREATE: LazyLock = + LazyLock::new(|| networking::LoopbackAddressCreate { address_lot: NameOrId::Name("parkinglot".parse().unwrap()), rack_id: uuid::Uuid::new_v4(), switch_location: "switch0".parse().unwrap(), @@ -924,9 +946,9 @@ pub const DEMO_SWITCH_PORT_SETTINGS_URL: &'static str = pub const DEMO_SWITCH_PORT_SETTINGS_INFO_URL: &'static str = "/v1/system/networking/switch-port-settings/protofino"; pub static DEMO_SWITCH_PORT_SETTINGS_CREATE: LazyLock< - params::SwitchPortSettingsCreate, + networking::SwitchPortSettingsCreate, > = LazyLock::new(|| { - params::SwitchPortSettingsCreate::new(IdentityMetadataCreateParams { + networking::SwitchPortSettingsCreate::new(IdentityMetadataCreateParams { name: "portofino".parse().unwrap(), description: "just a port".into(), }) @@ -938,14 +960,14 @@ pub const DEMO_ADDRESS_LOT_URL: &'static str = "/v1/system/networking/address-lot/parkinglot"; pub const DEMO_ADDRESS_LOT_BLOCKS_URL: &'static str = "/v1/system/networking/address-lot/parkinglot/blocks"; -pub static DEMO_ADDRESS_LOT_CREATE: LazyLock = - LazyLock::new(|| params::AddressLotCreate { +pub static DEMO_ADDRESS_LOT_CREATE: LazyLock = + LazyLock::new(|| networking::AddressLotCreate { identity: IdentityMetadataCreateParams { name: "parkinglot".parse().unwrap(), description: "an address parking lot".into(), }, kind: AddressLotKind::Infra, - blocks: vec![params::AddressLotBlockCreate { + blocks: vec![networking::AddressLotBlockCreate { first_address: "203.0.113.10".parse().unwrap(), last_address: "203.0.113.20".parse().unwrap(), }], @@ -953,8 +975,8 @@ pub static DEMO_ADDRESS_LOT_CREATE: LazyLock = pub const DEMO_BGP_CONFIG_CREATE_URL: &'static str = "/v1/system/networking/bgp?name_or_id=as47"; -pub static DEMO_BGP_CONFIG: LazyLock = - LazyLock::new(|| params::BgpConfigCreate { +pub static DEMO_BGP_CONFIG: LazyLock = + LazyLock::new(|| networking::BgpConfigCreate { identity: IdentityMetadataCreateParams { name: "as47".parse().unwrap(), description: "BGP config for AS47".into(), @@ -968,13 +990,13 @@ pub static DEMO_BGP_CONFIG: LazyLock = }); pub const DEMO_BGP_ANNOUNCE_SET_URL: &'static str = "/v1/system/networking/bgp-announce-set"; -pub static DEMO_BGP_ANNOUNCE: LazyLock = - LazyLock::new(|| params::BgpAnnounceSetCreate { +pub static DEMO_BGP_ANNOUNCE: LazyLock = + LazyLock::new(|| networking::BgpAnnounceSetCreate { identity: IdentityMetadataCreateParams { name: "a-bag-of-addrs".parse().unwrap(), description: "a bag of addrs".into(), }, - announcement: vec![params::BgpAnnouncementCreate { + announcement: vec![networking::BgpAnnouncementCreate { address_lot_block: NameOrId::Name("some-block".parse().unwrap()), network: "10.0.0.0/16".parse().unwrap(), }], @@ -1001,8 +1023,8 @@ pub const DEMO_BFD_ENABLE_URL: &'static str = pub const DEMO_BFD_DISABLE_URL: &'static str = "/v1/system/networking/bfd-disable"; -pub static DEMO_BFD_ENABLE: LazyLock = - LazyLock::new(|| params::BfdSessionEnable { +pub static DEMO_BFD_ENABLE: LazyLock = + LazyLock::new(|| networking::BfdSessionEnable { local: None, remote: "10.0.0.1".parse().unwrap(), detection_threshold: 3, @@ -1011,8 +1033,8 @@ pub static DEMO_BFD_ENABLE: LazyLock = mode: omicron_common::api::external::BfdMode::MultiHop, }); -pub static DEMO_BFD_DISABLE: LazyLock = - LazyLock::new(|| params::BfdSessionDisable { +pub static DEMO_BFD_DISABLE: LazyLock = + LazyLock::new(|| networking::BfdSessionDisable { remote: "10.0.0.1".parse().unwrap(), switch: "switch0".parse().unwrap(), }); @@ -1040,13 +1062,13 @@ pub static DEMO_SILO_DEMOTE_IMAGE_URL: LazyLock = LazyLock::new(|| { ) }); -pub static DEMO_IMAGE_CREATE: LazyLock = - LazyLock::new(|| params::ImageCreate { +pub static DEMO_IMAGE_CREATE: LazyLock = + LazyLock::new(|| image::ImageCreate { identity: IdentityMetadataCreateParams { name: DEMO_IMAGE_NAME.clone(), description: String::from(""), }, - source: params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + source: image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, os: "fake-os".to_string(), version: "1.0".to_string(), }); @@ -1057,9 +1079,9 @@ pub static DEMO_IP_POOLS_PROJ_URL: LazyLock = pub const DEMO_IP_POOLS_URL: &'static str = "/v1/system/ip-pools"; pub static DEMO_IP_POOL_NAME: LazyLock = LazyLock::new(|| "default".parse().unwrap()); -pub static DEMO_IP_POOL_CREATE: LazyLock = +pub static DEMO_IP_POOL_CREATE: LazyLock = LazyLock::new(|| { - params::IpPoolCreate::new( + ip_pool::IpPoolCreate::new( IdentityMetadataCreateParams { name: DEMO_IP_POOL_NAME.clone(), description: String::from("an IP pool"), @@ -1077,8 +1099,8 @@ pub static DEMO_IP_POOL_URL: LazyLock = LazyLock::new(|| format!("/v1/system/ip-pools/{}", *DEMO_IP_POOL_NAME)); pub static DEMO_IP_POOL_UTILIZATION_URL: LazyLock = LazyLock::new(|| format!("{}/utilization", *DEMO_IP_POOL_URL)); -pub static DEMO_IP_POOL_UPDATE: LazyLock = - LazyLock::new(|| params::IpPoolUpdate { +pub static DEMO_IP_POOL_UPDATE: LazyLock = + LazyLock::new(|| ip_pool::IpPoolUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some(String::from("a new IP pool")), @@ -1088,9 +1110,9 @@ pub static DEMO_IP_POOL_UPDATE: LazyLock = // Multicast IP Pool pub static DEMO_MULTICAST_IP_POOL_NAME: LazyLock = LazyLock::new(|| "default-multicast".parse().unwrap()); -pub static DEMO_MULTICAST_IP_POOL_CREATE: LazyLock = +pub static DEMO_MULTICAST_IP_POOL_CREATE: LazyLock = LazyLock::new(|| { - params::IpPoolCreate::new_multicast( + ip_pool::IpPoolCreate::new_multicast( IdentityMetadataCreateParams { name: DEMO_MULTICAST_IP_POOL_NAME.clone(), description: String::from("a multicast IP pool"), @@ -1116,16 +1138,17 @@ pub static DEMO_MULTICAST_IP_POOL_RANGE: LazyLock = }); pub static DEMO_MULTICAST_IP_POOL_RANGES_ADD_URL: LazyLock = LazyLock::new(|| format!("{}/ranges/add", *DEMO_MULTICAST_IP_POOL_URL)); -pub static DEMO_MULTICAST_IP_POOL_SILOS_BODY: LazyLock = - LazyLock::new(|| params::IpPoolLinkSilo { - silo: NameOrId::Id(DEFAULT_SILO.identity().id), - is_default: false, // multicast pool is not the default - }); +pub static DEMO_MULTICAST_IP_POOL_SILOS_BODY: LazyLock< + ip_pool::IpPoolLinkSilo, +> = LazyLock::new(|| ip_pool::IpPoolLinkSilo { + silo: NameOrId::Id(DEFAULT_SILO.identity().id), + is_default: false, // multicast pool is not the default +}); pub static DEMO_IP_POOL_SILOS_URL: LazyLock = LazyLock::new(|| format!("{}/silos", *DEMO_IP_POOL_URL)); -pub static DEMO_IP_POOL_SILOS_BODY: LazyLock = - LazyLock::new(|| params::IpPoolLinkSilo { +pub static DEMO_IP_POOL_SILOS_BODY: LazyLock = + LazyLock::new(|| ip_pool::IpPoolLinkSilo { silo: NameOrId::Id(DEFAULT_SILO.identity().id), is_default: true, // necessary for demo instance create to go through }); @@ -1133,8 +1156,8 @@ pub static DEMO_IP_POOL_SILOS_BODY: LazyLock = pub static DEMO_IP_POOL_SILO_URL: LazyLock = LazyLock::new(|| { format!("{}/silos/{}", *DEMO_IP_POOL_URL, *DEMO_SILO_NAME) }); -pub static DEMO_IP_POOL_SILO_UPDATE_BODY: LazyLock = - LazyLock::new(|| params::IpPoolSiloUpdate { is_default: false }); +pub static DEMO_IP_POOL_SILO_UPDATE_BODY: LazyLock = + LazyLock::new(|| ip_pool::IpPoolSiloUpdate { is_default: false }); pub static DEMO_IP_POOL_RANGE: LazyLock = LazyLock::new(|| { IpRange::V4( @@ -1166,8 +1189,8 @@ pub static DEMO_IP_POOL_SERVICE_RANGES_DEL_URL: LazyLock = pub const DEMO_SUBNET_POOLS_URL: &'static str = "/v1/system/subnet-pools"; pub static DEMO_SUBNET_POOL_NAME: LazyLock = LazyLock::new(|| "demo-subnet-pool".parse().unwrap()); -pub static DEMO_SUBNET_POOL_CREATE: LazyLock = - LazyLock::new(|| params::SubnetPoolCreate { +pub static DEMO_SUBNET_POOL_CREATE: LazyLock = + LazyLock::new(|| subnet_pool::SubnetPoolCreate { identity: IdentityMetadataCreateParams { name: DEMO_SUBNET_POOL_NAME.clone(), description: String::from("a subnet pool"), @@ -1177,8 +1200,8 @@ pub static DEMO_SUBNET_POOL_CREATE: LazyLock = pub static DEMO_SUBNET_POOL_URL: LazyLock = LazyLock::new(|| { format!("/v1/system/subnet-pools/{}", *DEMO_SUBNET_POOL_NAME) }); -pub static DEMO_SUBNET_POOL_UPDATE: LazyLock = - LazyLock::new(|| params::SubnetPoolUpdate { +pub static DEMO_SUBNET_POOL_UPDATE: LazyLock = + LazyLock::new(|| subnet_pool::SubnetPoolUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some(String::from("an updated subnet pool")), @@ -1190,32 +1213,34 @@ pub static DEMO_SUBNET_POOL_MEMBERS_ADD_URL: LazyLock = LazyLock::new(|| format!("{}/add", *DEMO_SUBNET_POOL_MEMBERS_URL)); pub static DEMO_SUBNET_POOL_MEMBERS_REMOVE_URL: LazyLock = LazyLock::new(|| format!("{}/remove", *DEMO_SUBNET_POOL_MEMBERS_URL)); -pub static DEMO_SUBNET_POOL_MEMBER_ADD: LazyLock = - LazyLock::new(|| params::SubnetPoolMemberAdd { - subnet: "10.1.0.0/16".parse().unwrap(), - min_prefix_length: None, - max_prefix_length: None, - }); +pub static DEMO_SUBNET_POOL_MEMBER_ADD: LazyLock< + subnet_pool::SubnetPoolMemberAdd, +> = LazyLock::new(|| subnet_pool::SubnetPoolMemberAdd { + subnet: "10.1.0.0/16".parse().unwrap(), + min_prefix_length: None, + max_prefix_length: None, +}); pub static DEMO_SUBNET_POOL_MEMBER_REMOVE: LazyLock< - params::SubnetPoolMemberRemove, -> = LazyLock::new(|| params::SubnetPoolMemberRemove { + subnet_pool::SubnetPoolMemberRemove, +> = LazyLock::new(|| subnet_pool::SubnetPoolMemberRemove { subnet: "10.0.0.0/16".parse().unwrap(), }); pub static DEMO_SUBNET_POOL_SILOS_URL: LazyLock = LazyLock::new(|| format!("{}/silos", *DEMO_SUBNET_POOL_URL)); pub static DEMO_SILO_SUBNET_POOLS_URL: LazyLock = LazyLock::new(|| format!("{}/subnet-pools", *DEMO_SILO_URL)); -pub static DEMO_SUBNET_POOL_LINK_SILO: LazyLock = - LazyLock::new(|| params::SubnetPoolLinkSilo { - silo: NameOrId::Id(DEFAULT_SILO.identity().id), - is_default: true, - }); +pub static DEMO_SUBNET_POOL_LINK_SILO: LazyLock< + subnet_pool::SubnetPoolLinkSilo, +> = LazyLock::new(|| subnet_pool::SubnetPoolLinkSilo { + silo: NameOrId::Id(DEFAULT_SILO.identity().id), + is_default: true, +}); pub static DEMO_SUBNET_POOL_SILO_URL: LazyLock = LazyLock::new(|| { format!("{}/silos/{}", *DEMO_SUBNET_POOL_URL, *DEMO_SILO_NAME) }); pub static DEMO_SUBNET_POOL_SILO_UPDATE: LazyLock< - params::SubnetPoolSiloUpdate, -> = LazyLock::new(|| params::SubnetPoolSiloUpdate { is_default: true }); + subnet_pool::SubnetPoolSiloUpdate, +> = LazyLock::new(|| subnet_pool::SubnetPoolSiloUpdate { is_default: true }); pub static DEMO_SUBNET_POOL_UTILIZATION_URL: LazyLock = LazyLock::new(|| format!("{}/utilization", *DEMO_SUBNET_POOL_URL)); pub static DEMO_CURRENT_SILO_SUBNET_POOLS_URL: &str = "/v1/subnet-pools"; @@ -1228,30 +1253,32 @@ pub static DEMO_EXTERNAL_SUBNETS_URL: LazyLock = LazyLock::new(|| { }); pub static DEMO_EXTERNAL_SUBNET_NAME: LazyLock = LazyLock::new(|| "demo-external-subnet".parse().unwrap()); -pub static DEMO_EXTERNAL_SUBNET_CREATE: LazyLock = - LazyLock::new(|| params::ExternalSubnetCreate { - identity: IdentityMetadataCreateParams { - name: DEMO_EXTERNAL_SUBNET_NAME.clone(), - description: String::from("an external subnet"), - }, - allocator: params::ExternalSubnetAllocator::Auto { - prefix_len: 24, - pool_selector: params::PoolSelector::default(), - }, - }); +pub static DEMO_EXTERNAL_SUBNET_CREATE: LazyLock< + external_subnet::ExternalSubnetCreate, +> = LazyLock::new(|| external_subnet::ExternalSubnetCreate { + identity: IdentityMetadataCreateParams { + name: DEMO_EXTERNAL_SUBNET_NAME.clone(), + description: String::from("an external subnet"), + }, + allocator: external_subnet::ExternalSubnetAllocator::Auto { + prefix_len: 24, + pool_selector: ip_pool::PoolSelector::default(), + }, +}); pub static DEMO_EXTERNAL_SUBNET_URL: LazyLock = LazyLock::new(|| { format!( "/v1/external-subnets/{}?project={}", *DEMO_EXTERNAL_SUBNET_NAME, *DEMO_PROJECT_NAME ) }); -pub static DEMO_EXTERNAL_SUBNET_UPDATE: LazyLock = - LazyLock::new(|| params::ExternalSubnetUpdate { - identity: IdentityMetadataUpdateParams { - name: None, - description: Some(String::from("an updated external subnet")), - }, - }); +pub static DEMO_EXTERNAL_SUBNET_UPDATE: LazyLock< + external_subnet::ExternalSubnetUpdate, +> = LazyLock::new(|| external_subnet::ExternalSubnetUpdate { + identity: IdentityMetadataUpdateParams { + name: None, + description: Some(String::from("an updated external subnet")), + }, +}); pub static DEMO_EXTERNAL_SUBNET_ATTACH_URL: LazyLock = LazyLock::new(|| { format!( @@ -1259,10 +1286,11 @@ pub static DEMO_EXTERNAL_SUBNET_ATTACH_URL: LazyLock = *DEMO_EXTERNAL_SUBNET_NAME, *DEMO_PROJECT_NAME ) }); -pub static DEMO_EXTERNAL_SUBNET_ATTACH: LazyLock = - LazyLock::new(|| params::ExternalSubnetAttach { - instance: DEMO_INSTANCE_NAME.clone().into(), - }); +pub static DEMO_EXTERNAL_SUBNET_ATTACH: LazyLock< + external_subnet::ExternalSubnetAttach, +> = LazyLock::new(|| external_subnet::ExternalSubnetAttach { + instance: DEMO_INSTANCE_NAME.clone().into(), +}); pub static DEMO_EXTERNAL_SUBNET_DETACH_URL: LazyLock = LazyLock::new(|| { format!( @@ -1280,8 +1308,8 @@ pub static DEMO_SNAPSHOT_URL: LazyLock = LazyLock::new(|| { *DEMO_SNAPSHOT_NAME, *DEMO_PROJECT_NAME ) }); -pub static DEMO_SNAPSHOT_CREATE: LazyLock = - LazyLock::new(|| params::SnapshotCreate { +pub static DEMO_SNAPSHOT_CREATE: LazyLock = + LazyLock::new(|| snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: DEMO_SNAPSHOT_NAME.clone(), description: String::from(""), @@ -1294,8 +1322,8 @@ pub const DEMO_SSHKEYS_URL: &'static str = "/v1/me/ssh-keys"; pub static DEMO_SSHKEY_NAME: LazyLock = LazyLock::new(|| "aaaaa-ssh-key".parse().unwrap()); -pub static DEMO_SSHKEY_CREATE: LazyLock = - LazyLock::new(|| params::SshKeyCreate { +pub static DEMO_SSHKEY_CREATE: LazyLock = + LazyLock::new(|| ssh_key::SshKeyCreate { identity: IdentityMetadataCreateParams { name: DEMO_SSHKEY_NAME.clone(), description: "a demo key".to_string(), @@ -1333,33 +1361,33 @@ pub static DEMO_FLOATING_IP_DETACH_URL: LazyLock = ) }); -pub static DEMO_FLOAT_IP_CREATE: LazyLock = - LazyLock::new(|| params::FloatingIpCreate { +pub static DEMO_FLOAT_IP_CREATE: LazyLock = + LazyLock::new(|| floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: DEMO_FLOAT_IP_NAME.clone(), description: String::from("a new IP pool"), }, - address_allocator: params::AddressAllocator::Explicit { + address_allocator: floating_ip::AddressAllocator::Explicit { ip: Ipv4Addr::new(10, 0, 0, 141).into(), }, }); -pub static DEMO_FLOAT_IP_UPDATE: LazyLock = - LazyLock::new(|| params::FloatingIpUpdate { +pub static DEMO_FLOAT_IP_UPDATE: LazyLock = + LazyLock::new(|| floating_ip::FloatingIpUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some(String::from("an updated Floating IP")), }, }); -pub static DEMO_FLOAT_IP_ATTACH: LazyLock = - LazyLock::new(|| params::FloatingIpAttach { - kind: params::FloatingIpParentKind::Instance, +pub static DEMO_FLOAT_IP_ATTACH: LazyLock = + LazyLock::new(|| floating_ip::FloatingIpAttach { + kind: floating_ip::FloatingIpParentKind::Instance, parent: DEMO_FLOAT_IP_NAME.clone().into(), }); -pub static DEMO_EPHEMERAL_IP_ATTACH: LazyLock = - LazyLock::new(|| params::EphemeralIpCreate { - pool_selector: params::PoolSelector::Auto { ip_version: None }, +pub static DEMO_EPHEMERAL_IP_ATTACH: LazyLock = + LazyLock::new(|| instance::EphemeralIpCreate { + pool_selector: ip_pool::PoolSelector::Auto { ip_version: None }, }); // Identity providers pub const IDENTITY_PROVIDERS_URL: &'static str = @@ -1378,14 +1406,14 @@ pub static SPECIFIC_SAML_IDENTITY_PROVIDER_URL: LazyLock = }); pub static SAML_IDENTITY_PROVIDER: LazyLock< - params::SamlIdentityProviderCreate, -> = LazyLock::new(|| params::SamlIdentityProviderCreate { + identity_provider::SamlIdentityProviderCreate, +> = LazyLock::new(|| identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: DEMO_SAML_IDENTITY_PROVIDER_NAME.clone(), description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Url { + idp_metadata_source: identity_provider::IdpMetadataSource::Url { url: HTTP_SERVER.url("/descriptor").to_string(), }, @@ -1426,8 +1454,8 @@ pub static SYSTEM_TIMESERIES_LIST_URL: LazyLock = pub static SYSTEM_TIMESERIES_QUERY_URL: LazyLock = LazyLock::new(|| String::from("/v1/system/timeseries/query")); -pub static DEMO_TIMESERIES_QUERY: LazyLock = - LazyLock::new(|| params::TimeseriesQuery { +pub static DEMO_TIMESERIES_QUERY: LazyLock = + LazyLock::new(|| timeseries::TimeseriesQuery { query: String::from("get http_service:request_latency_histogram"), include_summaries: false, }); @@ -1442,14 +1470,14 @@ pub static DEMO_USER_CREATE: LazyLock = // Allowlist for user-facing services. pub static ALLOW_LIST_URL: LazyLock = LazyLock::new(|| String::from("/v1/system/networking/allow-list")); -pub static ALLOW_LIST_UPDATE: LazyLock = - LazyLock::new(|| params::AllowListUpdate { +pub static ALLOW_LIST_UPDATE: LazyLock = + LazyLock::new(|| system::AllowListUpdate { allowed_ips: AllowedSourceIps::Any, }); // Updates -pub static DEMO_TARGET_RELEASE: LazyLock = - LazyLock::new(|| params::SetTargetReleaseParams { +pub static DEMO_TARGET_RELEASE: LazyLock = + LazyLock::new(|| update::SetTargetReleaseParams { system_version: Version::new(0, 0, 0), }); @@ -1460,8 +1488,8 @@ pub static WEBHOOK_RECEIVERS_URL: &'static str = "/v1/webhook-receivers"; pub static DEMO_WEBHOOK_RECEIVER_NAME: LazyLock = LazyLock::new(|| "my-great-webhook".parse().unwrap()); -pub static DEMO_WEBHOOK_RECEIVER_CREATE: LazyLock = - LazyLock::new(|| params::WebhookCreate { +pub static DEMO_WEBHOOK_RECEIVER_CREATE: LazyLock = + LazyLock::new(|| alert::WebhookCreate { identity: IdentityMetadataCreateParams { name: DEMO_WEBHOOK_RECEIVER_NAME.clone(), description: "webhook, line, and sinker".to_string(), @@ -1475,8 +1503,8 @@ pub static DEMO_WEBHOOK_RECEIVER_CREATE: LazyLock = }); pub static DEMO_WEBHOOK_RECEIVER_UPDATE: LazyLock< - params::WebhookReceiverUpdate, -> = LazyLock::new(|| params::WebhookReceiverUpdate { + alert::WebhookReceiverUpdate, +> = LazyLock::new(|| alert::WebhookReceiverUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some("webhooked on phonics".to_string()), @@ -1500,12 +1528,12 @@ pub static DEMO_ALERT_DELIVERIES_URL: LazyLock = pub static DEMO_ALERT_SUBSCRIPTIONS_URL: LazyLock = LazyLock::new(|| format!("{}/subscriptions", *DEMO_ALERT_RECEIVER_URL)); -pub static DEMO_ALERT_SUBSCRIPTION: LazyLock = +pub static DEMO_ALERT_SUBSCRIPTION: LazyLock = LazyLock::new(|| "test.foo.**".parse().unwrap()); pub static DEMO_ALERT_SUBSCRIPTION_CREATE: LazyLock< - params::AlertSubscriptionCreate, -> = LazyLock::new(|| params::AlertSubscriptionCreate { + alert::AlertSubscriptionCreate, +> = LazyLock::new(|| alert::AlertSubscriptionCreate { subscription: DEMO_ALERT_SUBSCRIPTION.clone(), }); @@ -1529,8 +1557,8 @@ pub static DEMO_WEBHOOK_SECRET_DELETE_URL: LazyLock = ) }); -pub static DEMO_WEBHOOK_SECRET_CREATE: LazyLock = - LazyLock::new(|| params::WebhookSecretCreate { +pub static DEMO_WEBHOOK_SECRET_CREATE: LazyLock = + LazyLock::new(|| alert::WebhookSecretCreate { secret: "TRUSTNO1".to_string(), }); @@ -1753,8 +1781,8 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( allowed_methods: vec![ AllowedMethod::Get, AllowedMethod::Put( - serde_json::to_value(&shared::Policy::< - shared::FleetRole, + serde_json::to_value(&policy::Policy::< + policy::FleetRole, > { role_assignments: vec![], }) @@ -1957,9 +1985,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( url: &DEMO_SILO_SUBNET_POOLS_URL, visibility: Visibility::Protected, unprivileged_access: UnprivilegedAccess::None, - allowed_methods: vec![ - AllowedMethod::Get, - ], + allowed_methods: vec![AllowedMethod::Get], }, VerifyEndpoint { url: &DEMO_SUBNET_POOL_SILO_URL, @@ -1983,9 +2009,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( url: &DEMO_CURRENT_SILO_SUBNET_POOLS_URL, visibility: Visibility::Protected, unprivileged_access: UnprivilegedAccess::ReadOnly, - allowed_methods: vec![ - AllowedMethod::Get, - ], + allowed_methods: vec![AllowedMethod::Get], }, VerifyEndpoint { url: &DEMO_CURRENT_SILO_SUBNET_POOL_URL, @@ -2024,7 +2048,8 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( visibility: Visibility::Protected, unprivileged_access: UnprivilegedAccess::None, allowed_methods: vec![AllowedMethod::Post( - serde_json::to_value(&*DEMO_EXTERNAL_SUBNET_ATTACH).unwrap(), + serde_json::to_value(&*DEMO_EXTERNAL_SUBNET_ATTACH) + .unwrap(), )], }, VerifyEndpoint { @@ -2069,8 +2094,8 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( allowed_methods: vec![ AllowedMethod::Get, AllowedMethod::Put( - serde_json::to_value(&shared::Policy::< - shared::SiloRole, + serde_json::to_value(&policy::Policy::< + policy::SiloRole, > { role_assignments: vec![], }) @@ -2085,7 +2110,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( allowed_methods: vec![ AllowedMethod::Get, AllowedMethod::Put( - serde_json::to_value(params::SiloQuotasCreate::empty()) + serde_json::to_value(silo::SiloQuotasCreate::empty()) .unwrap(), ), ], @@ -2121,8 +2146,8 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( allowed_methods: vec![ AllowedMethod::Get, AllowedMethod::Put( - serde_json::to_value(&shared::Policy::< - shared::SiloRole, + serde_json::to_value(&policy::Policy::< + policy::SiloRole, > { role_assignments: vec![], }) @@ -2137,7 +2162,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( allowed_methods: vec![ AllowedMethod::Get, AllowedMethod::Put( - serde_json::to_value(¶ms::SiloAuthSettingsUpdate { + serde_json::to_value(&silo::SiloAuthSettingsUpdate { device_token_max_ttl_seconds: Nullable( NonZeroU32::new(3), ), @@ -2257,7 +2282,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( AllowedMethod::Get, AllowedMethod::Delete, AllowedMethod::Put( - serde_json::to_value(params::ProjectUpdate { + serde_json::to_value(project::ProjectUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some("different".to_string()), @@ -2274,8 +2299,8 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( allowed_methods: vec![ AllowedMethod::Get, AllowedMethod::Put( - serde_json::to_value(&shared::Policy::< - shared::ProjectRole, + serde_json::to_value(&policy::Policy::< + policy::ProjectRole, > { role_assignments: vec![], }) @@ -2302,7 +2327,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( allowed_methods: vec![ AllowedMethod::Get, AllowedMethod::Put( - serde_json::to_value(¶ms::VpcUpdate { + serde_json::to_value(&vpc::VpcUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some("different".to_string()), @@ -2348,7 +2373,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( allowed_methods: vec![ AllowedMethod::Get, AllowedMethod::Put( - serde_json::to_value(¶ms::VpcSubnetUpdate { + serde_json::to_value(&vpc::VpcSubnetUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some("different".to_string()), @@ -2385,7 +2410,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( allowed_methods: vec![ AllowedMethod::Get, AllowedMethod::Put( - serde_json::to_value(¶ms::VpcRouterUpdate { + serde_json::to_value(&vpc::VpcRouterUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some("different".to_string()), @@ -2416,7 +2441,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( allowed_methods: vec![ AllowedMethod::Get, AllowedMethod::Put( - serde_json::to_value(¶ms::RouterRouteUpdate { + serde_json::to_value(&vpc::RouterRouteUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some("different".to_string()), @@ -2527,7 +2552,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( visibility: Visibility::Protected, unprivileged_access: UnprivilegedAccess::None, allowed_methods: vec![AllowedMethod::Post( - serde_json::to_value(params::DiskPath { + serde_json::to_value(path_params::DiskPath { disk: DEMO_DISK_NAME.clone().into(), }) .unwrap(), @@ -2538,7 +2563,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( visibility: Visibility::Protected, unprivileged_access: UnprivilegedAccess::None, allowed_methods: vec![AllowedMethod::Post( - serde_json::to_value(params::DiskPath { + serde_json::to_value(path_params::DiskPath { disk: DEMO_DISK_NAME.clone().into(), }) .unwrap(), @@ -2663,7 +2688,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( allowed_methods: { use base64::prelude::*; vec![AllowedMethod::Post( - serde_json::to_value(params::ImportBlocksBulkWrite { + serde_json::to_value(disk::ImportBlocksBulkWrite { offset: 0, base64_encoded_data: BASE64_STANDARD .encode([0; 4096]), @@ -2904,14 +2929,16 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( unprivileged_access: UnprivilegedAccess::None, allowed_methods: vec![AllowedMethod::Post( serde_json::to_value(&*DEMO_RACK_ADD_SLEDS_REQUEST) - .unwrap() + .unwrap(), )], }, VerifyEndpoint { url: &HARDWARE_RACK_MEMBERSHIP_ABORT_URL, visibility: Visibility::Protected, unprivileged_access: UnprivilegedAccess::None, - allowed_methods: vec![AllowedMethod::Post(serde_json::Value::Null)], + allowed_methods: vec![AllowedMethod::Post( + serde_json::Value::Null, + )], }, VerifyEndpoint { url: &HARDWARE_UNINITIALIZED_SLEDS, @@ -2989,9 +3016,14 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( unprivileged_access: UnprivilegedAccess::None, allowed_methods: vec![ AllowedMethod::Get, - AllowedMethod::Post(serde_json::to_value(&nexus_types::external_api::params::SupportBundleCreate { - user_comment: None, - }).unwrap()), + AllowedMethod::Post( + serde_json::to_value( + &support_bundle::SupportBundleCreate { + user_comment: None, + }, + ) + .unwrap(), + ), ], }, VerifyEndpoint { @@ -3002,9 +3034,11 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( AllowedMethod::Get, AllowedMethod::Delete, AllowedMethod::Put( - serde_json::to_value(¶ms::SupportBundleUpdate { - user_comment: None, - }) + serde_json::to_value( + &support_bundle::SupportBundleUpdate { + user_comment: None, + }, + ) .unwrap(), ), ], @@ -3067,7 +3101,7 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( serde_json::Value::Null, ), // get doesn't use the query param but it doesn't break if it's there - AllowedMethod::Get + AllowedMethod::Get, ], }, VerifyEndpoint { @@ -3080,19 +3114,15 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( url: "/v1/system/update/target-release", visibility: Visibility::Public, unprivileged_access: UnprivilegedAccess::None, - allowed_methods: vec![ - AllowedMethod::Put( - serde_json::to_value(&*DEMO_TARGET_RELEASE).unwrap(), - ), - ], + allowed_methods: vec![AllowedMethod::Put( + serde_json::to_value(&*DEMO_TARGET_RELEASE).unwrap(), + )], }, VerifyEndpoint { url: "/v1/system/update/status", visibility: Visibility::Public, unprivileged_access: UnprivilegedAccess::None, - allowed_methods: vec![ - AllowedMethod::Get, - ], + allowed_methods: vec![AllowedMethod::Get], }, /* Metrics */ VerifyEndpoint { @@ -3279,7 +3309,10 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( url: &DEMO_ADDRESS_LOT_URL, visibility: Visibility::Protected, unprivileged_access: UnprivilegedAccess::None, - allowed_methods: vec![AllowedMethod::GetNonexistent, AllowedMethod::Delete], + allowed_methods: vec![ + AllowedMethod::GetNonexistent, + AllowedMethod::Delete, + ], }, VerifyEndpoint { url: &DEMO_ADDRESS_LOT_BLOCKS_URL, @@ -3558,7 +3591,6 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( unprivileged_access: UnprivilegedAccess::None, allowed_methods: vec![AllowedMethod::Get], }, - // Multicast groups // Multicast groups are fleet-scoped. Any authenticated user in @@ -3594,7 +3626,12 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( visibility: Visibility::Protected, unprivileged_access: UnprivilegedAccess::None, allowed_methods: vec![ - AllowedMethod::Put(serde_json::to_value(&*DEMO_INSTANCE_MULTICAST_GROUP_JOIN).unwrap()), + AllowedMethod::Put( + serde_json::to_value( + &*DEMO_INSTANCE_MULTICAST_GROUP_JOIN, + ) + .unwrap(), + ), AllowedMethod::Delete, ], }, @@ -3740,7 +3777,9 @@ pub static VERIFY_ENDPOINTS: LazyLock> = LazyLock::new( url: &DEMO_ALERT_RESEND_URL, visibility: Visibility::Protected, unprivileged_access: UnprivilegedAccess::None, - allowed_methods: vec![AllowedMethod::Post(serde_json::json!({}))], + allowed_methods: vec![AllowedMethod::Post(serde_json::json!( + {} + ))], }, // Access Token Delete VerifyEndpoint { diff --git a/nexus/tests/integration_tests/external_ips.rs b/nexus/tests/integration_tests/external_ips.rs index 1e45eb2f95a..8d4a37198f9 100644 --- a/nexus/tests/integration_tests/external_ips.rs +++ b/nexus/tests/integration_tests/external_ips.rs @@ -43,12 +43,15 @@ use nexus_test_utils::resource_helpers::object_get; use nexus_test_utils::resource_helpers::object_put; use nexus_test_utils::resource_helpers::test_params; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::params::InstanceNetworkInterfaceAttachment; -use nexus_types::external_api::shared; -use nexus_types::external_api::shared::SiloRole; -use nexus_types::external_api::views; -use nexus_types::external_api::views::FloatingIp; +use nexus_types::external_api::external_ip; +use nexus_types::external_api::floating_ip; +use nexus_types::external_api::floating_ip::FloatingIp; +use nexus_types::external_api::instance; +use nexus_types::external_api::instance::InstanceNetworkInterfaceAttachment; +use nexus_types::external_api::ip_pool; +use nexus_types::external_api::policy::SiloRole; +use nexus_types::external_api::project; +use nexus_types::external_api::silo; use nexus_types::identity::Resource; use omicron_common::address::IpRange; use omicron_common::address::IpVersion; @@ -135,8 +138,8 @@ async fn test_floating_ip_access(cptestctx: &ControlPlaneTestContext) { client, fip_name, &project.identity.id.to_string(), - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v6_pool.identity.name.clone().into(), }, }, @@ -203,8 +206,8 @@ async fn test_floating_ip_create(cptestctx: &ControlPlaneTestContext) { client, fip_name, project.identity.name.as_str(), - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: default_pool.identity.name.clone().into(), }, }, @@ -225,7 +228,7 @@ async fn test_floating_ip_create(cptestctx: &ControlPlaneTestContext) { client, fip_name, project.identity.name.as_str(), - params::AddressAllocator::Explicit { ip: ip_addr }, + floating_ip::AddressAllocator::Explicit { ip: ip_addr }, ) .await; assert_eq!(fip.identity.name.as_str(), fip_name); @@ -238,13 +241,13 @@ async fn test_floating_ip_create(cptestctx: &ControlPlaneTestContext) { // Creating with other-pool fails with 404 until it is linked to the current silo let fip_name = FIP_NAMES[2]; - let params = params::FloatingIpCreate { + let params = floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: fip_name.parse().unwrap(), description: String::from("a floating ip"), }, - address_allocator: params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + address_allocator: floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name("other-pool".parse().unwrap()), }, }, @@ -277,7 +280,7 @@ async fn test_floating_ip_create(cptestctx: &ControlPlaneTestContext) { client, fip_name, project.identity.name.as_str(), - params::AddressAllocator::Explicit { ip: ip_addr }, + floating_ip::AddressAllocator::Explicit { ip: ip_addr }, ) .await; assert_eq!(fip.identity.name.as_str(), fip_name); @@ -296,7 +299,7 @@ async fn test_floating_ip_create_non_admin( let client = &cptestctx.external_client; let silo_url = format!("/v1/system/silos/{}", cptestctx.silo_name); - let silo: views::Silo = object_get(client, &silo_url).await; + let silo: silo::Silo = object_get(client, &silo_url).await; // manually create default pool and link to test silo, as opposed to default // silo, which is what the helper would do @@ -341,7 +344,7 @@ async fn test_floating_ip_create_non_admin( NexusRequest::objects_post( client, "/v1/projects", - ¶ms::ProjectCreate { + &project::ProjectCreate { identity: IdentityMetadataCreateParams { name: PROJECT_NAME.parse().unwrap(), description: "floating ip project".to_string(), @@ -356,16 +359,16 @@ async fn test_floating_ip_create_non_admin( let create_url = get_floating_ips_url(PROJECT_NAME); // create a floating IP as this user, first with default pool - let body = params::FloatingIpCreate { + let body = floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: "root-beer".parse().unwrap(), description: String::from("a floating ip"), }, - address_allocator: params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Auto { ip_version: None }, + address_allocator: floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Auto { ip_version: None }, }, }; - let fip: views::FloatingIp = + let fip: floating_ip::FloatingIp = NexusRequest::objects_post(client, &create_url, &body) .authn_as(AuthnMode::SiloUser(user.id)) .execute_and_parse_unwrap() @@ -373,18 +376,18 @@ async fn test_floating_ip_create_non_admin( assert_eq!(fip.identity.name.to_string(), "root-beer"); // now with other pool linked to my silo - let body = params::FloatingIpCreate { + let body = floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: "another-soda".parse().unwrap(), description: String::from("a floating ip"), }, - address_allocator: params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + address_allocator: floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name("other-pool".parse().unwrap()), }, }, }; - let fip: views::FloatingIp = + let fip: floating_ip::FloatingIp = NexusRequest::objects_post(client, &create_url, &body) .authn_as(AuthnMode::SiloUser(user.id)) .execute_and_parse_unwrap() @@ -392,13 +395,13 @@ async fn test_floating_ip_create_non_admin( assert_eq!(fip.identity.name.to_string(), "another-soda"); // now with pool not linked to my silo (fails with 404) - let body = params::FloatingIpCreate { + let body = floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: "secret-third-soda".parse().unwrap(), description: String::from("a floating ip"), }, - address_allocator: params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + address_allocator: floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name("unlinked-pool".parse().unwrap()), }, }, @@ -431,7 +434,7 @@ async fn test_floating_ip_create_fails_in_other_silo_pool( &client, "not-my-silo", true, - shared::SiloIdentityMode::SamlJit, + silo::SiloIdentityMode::SamlJit, ) .await; let other_pool_range = IpRange::V4( @@ -447,13 +450,13 @@ async fn test_floating_ip_create_fails_in_other_silo_pool( // does not exist let url = format!("/v1/floating-ips?project={}", project.identity.name.as_str()); - let body = params::FloatingIpCreate { + let body = floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: fip_name.parse().unwrap(), description: String::from("a floating ip"), }, - address_allocator: params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + address_allocator: floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name("external-silo-pool".parse().unwrap()), }, }, @@ -493,7 +496,7 @@ async fn test_floating_ip_create_ip_in_use( client, FIP_NAMES[0], project.identity.name.as_str(), - params::AddressAllocator::Explicit { ip: contested_ip }, + floating_ip::AddressAllocator::Explicit { ip: contested_ip }, ) .await; @@ -505,13 +508,13 @@ async fn test_floating_ip_create_ip_in_use( Method::POST, &get_floating_ips_url(PROJECT_NAME), ) - .body(Some(¶ms::FloatingIpCreate { + .body(Some(&floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: FIP_NAMES[1].parse().unwrap(), description: "another fip".into(), }, // Explicit IP - address_allocator: params::AddressAllocator::Explicit { + address_allocator: floating_ip::AddressAllocator::Explicit { ip: contested_ip, }, })) @@ -544,12 +547,12 @@ async fn test_floating_ip_create_ip_not_in_pool( Method::POST, &get_floating_ips_url(PROJECT_NAME), ) - .body(Some(¶ms::FloatingIpCreate { + .body(Some(&floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: FIP_NAMES[0].parse().unwrap(), description: "fip with IP not in pool".into(), }, - address_allocator: params::AddressAllocator::Explicit { + address_allocator: floating_ip::AddressAllocator::Explicit { ip: ip_not_in_pool, }, })) @@ -586,8 +589,8 @@ async fn test_floating_ip_create_name_in_use( client, contested_name, project.identity.name.as_str(), - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v4_pool.identity.name.clone().into(), }, }, @@ -602,13 +605,13 @@ async fn test_floating_ip_create_name_in_use( Method::POST, &get_floating_ips_url(PROJECT_NAME), ) - .body(Some(¶ms::FloatingIpCreate { + .body(Some(&floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: contested_name.parse().unwrap(), description: "another fip".into(), }, - address_allocator: params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + address_allocator: floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v4_pool.identity.name.clone().into(), }, }, @@ -639,8 +642,8 @@ async fn test_floating_ip_update(cptestctx: &ControlPlaneTestContext) { client, FIP_NAMES[0], project.identity.name.as_str(), - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v6_pool.identity.name.clone().into(), }, }, @@ -658,12 +661,13 @@ async fn test_floating_ip_update(cptestctx: &ControlPlaneTestContext) { // Set up the updated values let new_fip_name: &str = "updated"; let new_fip_desc: &str = "updated description"; - let updates: params::FloatingIpUpdate = params::FloatingIpUpdate { - identity: IdentityMetadataUpdateParams { - name: Some(String::from(new_fip_name).parse().unwrap()), - description: Some(String::from(new_fip_desc).parse().unwrap()), - }, - }; + let updates: floating_ip::FloatingIpUpdate = + floating_ip::FloatingIpUpdate { + identity: IdentityMetadataUpdateParams { + name: Some(String::from(new_fip_name).parse().unwrap()), + description: Some(String::from(new_fip_desc).parse().unwrap()), + }, + }; // Update the Floating IP let new_fip: FloatingIp = @@ -693,8 +697,8 @@ async fn test_floating_ip_delete(cptestctx: &ControlPlaneTestContext) { client, FIP_NAMES[0], project.identity.name.as_str(), - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v4_pool.identity.name.clone().into(), }, }, @@ -738,8 +742,8 @@ async fn test_floating_ip_create_attachment( client, FIP_NAMES[0], project.identity.name.as_str(), - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v6_pool.identity.name.clone().into(), }, }, @@ -752,7 +756,7 @@ async fn test_floating_ip_create_attachment( client, instance_name, true, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultDualStack, + &InstanceNetworkInterfaceAttachment::DefaultDualStack, None, &FIP_NAMES[..1], ) @@ -850,8 +854,8 @@ async fn test_external_ip_live_attach_detach( client, FIP_NAMES[i], project.identity.name.as_str(), - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v4_pool.identity.name.clone().into(), }, }, @@ -869,7 +873,7 @@ async fn test_external_ip_live_attach_detach( client, INSTANCE_NAMES[i], *start, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, + &InstanceNetworkInterfaceAttachment::DefaultIpv4, None, &[], ) @@ -891,7 +895,7 @@ async fn test_external_ip_live_attach_detach( assert_eq!(eips.len(), 1, "Expected exactly 1 SNAT external IP"); assert_eq!( eips[0].kind(), - shared::IpKind::SNat, + external_ip::IpKind::SNat, "Expected exactly 1 SNAT external IP" ); instances.push(instance); @@ -931,7 +935,7 @@ async fn test_external_ip_live_attach_detach( assert!( eip_list .iter() - .any(|v| matches!(v, views::ExternalIp::Floating(..)) + .any(|v| matches!(v, external_ip::ExternalIp::Floating(..)) && v.ip() == fip_resp.ip) ); assert_eq!(fip.ip, fip_resp.ip); @@ -1003,11 +1007,11 @@ async fn test_external_ip_live_attach_detach( let floating_ip_name = fips[0].identity.name.as_str(); let instance_id = instances[0].identity.id; let url = attach_floating_ip_url(floating_ip_name, PROJECT_NAME); - let body = params::FloatingIpAttach { - kind: params::FloatingIpParentKind::Instance, + let body = floating_ip::FloatingIpAttach { + kind: floating_ip::FloatingIpParentKind::Instance, parent: instance_id.into(), }; - let attached: views::FloatingIp = NexusRequest::new( + let attached: floating_ip::FloatingIp = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) .body(Some(&body)) .expect_status(Some(StatusCode::ACCEPTED)), @@ -1029,7 +1033,7 @@ async fn test_external_ip_live_attach_detach( eip_list .iter() .find_map(|eip| { - if eip.kind() == shared::IpKind::Floating { + if eip.kind() == external_ip::IpKind::Floating { Some(eip.ip()) } else { None @@ -1044,11 +1048,11 @@ async fn test_external_ip_live_attach_detach( let floating_ip_id = fips[1].identity.id; let instance_name = instances[1].identity.name.as_str(); let url = format!("/v1/floating-ips/{floating_ip_id}/attach"); - let body = params::FloatingIpAttach { - kind: params::FloatingIpParentKind::Instance, + let body = floating_ip::FloatingIpAttach { + kind: floating_ip::FloatingIpParentKind::Instance, parent: instance_name.parse::().unwrap().into(), }; - let attached: views::FloatingIp = NexusRequest::new( + let attached: floating_ip::FloatingIp = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) .body(Some(&body)) .expect_status(Some(StatusCode::ACCEPTED)), @@ -1069,7 +1073,7 @@ async fn test_external_ip_live_attach_detach( eip_list .iter() .find_map(|eip| { - if eip.kind() == shared::IpKind::Floating { + if eip.kind() == external_ip::IpKind::Floating { Some(eip.ip()) } else { None @@ -1101,8 +1105,8 @@ async fn test_floating_ip_attach_fail_between_projects( client, FIP_NAMES[0], "proj2", - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v4_pool.identity.name.clone().into(), }, }, @@ -1114,7 +1118,7 @@ async fn test_floating_ip_attach_fail_between_projects( client, INSTANCE_NAMES[0], true, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultDualStack, + &InstanceNetworkInterfaceAttachment::DefaultDualStack, None, &[], ) @@ -1123,8 +1127,8 @@ async fn test_floating_ip_attach_fail_between_projects( let url = attach_floating_ip_uuid(&fip.identity.id); let error: HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::FloatingIpAttach { - kind: params::FloatingIpParentKind::Instance, + .body(Some(&floating_ip::FloatingIpAttach { + kind: floating_ip::FloatingIpParentKind::Instance, parent: instance.identity.id.into(), })) .expect_status(Some(StatusCode::BAD_REQUEST)), @@ -1145,7 +1149,7 @@ async fn test_floating_ip_attach_fail_between_projects( let error = object_create_error( client, &url, - ¶ms::InstanceCreate { + &instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: INSTANCE_NAMES[1].parse().unwrap(), description: "".into(), @@ -1158,8 +1162,8 @@ async fn test_floating_ip_attach_fail_between_projects( .to_vec(), ssh_public_keys: Some(Vec::new()), network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, - external_ips: vec![params::ExternalIpCreate::Floating { + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + external_ips: vec![instance::ExternalIpCreate::Floating { floating_ip: fip.identity.id.into(), }], disks: vec![], @@ -1198,8 +1202,8 @@ async fn test_external_ip_attach_fail_if_in_use_by_other( client, FIP_NAMES[i], project.identity.name.as_str(), - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v6_pool.identity.name.clone().into(), }, }, @@ -1209,7 +1213,7 @@ async fn test_external_ip_attach_fail_if_in_use_by_other( client, INSTANCE_NAMES[i], true, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultDualStack, + &InstanceNetworkInterfaceAttachment::DefaultDualStack, None, &[FIP_NAMES[i]], ) @@ -1228,8 +1232,8 @@ async fn test_external_ip_attach_fail_if_in_use_by_other( attach_floating_ip_url(fips[1].identity.name.as_str(), PROJECT_NAME); let error: HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::FloatingIpAttach { - kind: params::FloatingIpParentKind::Instance, + .body(Some(&floating_ip::FloatingIpAttach { + kind: floating_ip::FloatingIpParentKind::Instance, parent: INSTANCE_NAMES[0].parse::().unwrap().into(), })) .expect_status(Some(StatusCode::BAD_REQUEST)), @@ -1263,8 +1267,8 @@ async fn test_external_ip_attach_fails_after_maximum( client, &fip_name, project.identity.name.as_str(), - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v4_pool.identity.name.clone().into(), }, }, @@ -1289,7 +1293,7 @@ async fn test_external_ip_attach_fails_after_maximum( client, instance_name, true, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, + &InstanceNetworkInterfaceAttachment::DefaultIpv4, None, &fip_name_slice[..32], ) @@ -1299,8 +1303,8 @@ async fn test_external_ip_attach_fails_after_maximum( let url = attach_floating_ip_url(fip_name_slice[32], PROJECT_NAME); let error: HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::FloatingIpAttach { - kind: params::FloatingIpParentKind::Instance, + .body(Some(&floating_ip::FloatingIpAttach { + kind: floating_ip::FloatingIpParentKind::Instance, parent: instance_name.parse::().unwrap().into(), })) .expect_status(Some(StatusCode::BAD_REQUEST)), @@ -1321,8 +1325,8 @@ async fn test_external_ip_attach_fails_after_maximum( let url = instance_ephemeral_ip_url(instance_name, PROJECT_NAME); let error: HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::EphemeralIpCreate { - pool_selector: params::PoolSelector::Explicit { + .body(Some(&instance::EphemeralIpCreate { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v6_pool .identity .name @@ -1370,7 +1374,7 @@ async fn test_external_ip_attach_ephemeral_at_pool_exhaustion( client, name, false, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultDualStack, + &InstanceNetworkInterfaceAttachment::DefaultDualStack, None, &[], ) @@ -1395,8 +1399,8 @@ async fn test_external_ip_attach_ephemeral_at_pool_exhaustion( let url = instance_ephemeral_ip_url(INSTANCE_NAMES[1], PROJECT_NAME); let error: HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { + .body(Some(&instance::ExternalIpCreate::Ephemeral { + pool_selector: ip_pool::PoolSelector::Explicit { pool: pool_name.clone().into(), }, })) @@ -1500,13 +1504,13 @@ async fn test_floating_ip_ip_version_conflict( let url = get_floating_ips_url(PROJECT_NAME); // Without `ip_version`, should fail with conflict error - let fip_params = params::FloatingIpCreate { + let fip_params = floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: "should-fail".parse().unwrap(), description: "this should fail".to_string(), }, - address_allocator: params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Auto { ip_version: None }, + address_allocator: floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Auto { ip_version: None }, }, }; let error = @@ -1520,14 +1524,14 @@ async fn test_floating_ip_ip_version_conflict( ); // With explicit `ip_version` (V4), this should succeed - let fip_v4_params = params::FloatingIpCreate { + let fip_v4_params = floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: "fip-v4".parse().unwrap(), description: "IPv4 floating IP".to_string(), }, - address_allocator: params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Auto { - ip_version: Some(views::IpVersion::V4), + address_allocator: floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Auto { + ip_version: Some(IpVersion::V4), }, }, }; @@ -1535,14 +1539,14 @@ async fn test_floating_ip_ip_version_conflict( assert!(fip_v4.ip.is_ipv4(), "Expected IPv4 address"); // With explicit `ip_version` (V6), this should succeed - let fip_v6_params = params::FloatingIpCreate { + let fip_v6_params = floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: "fip-v6".parse().unwrap(), description: "IPv6 floating IP".to_string(), }, - address_allocator: params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Auto { - ip_version: Some(views::IpVersion::V6), + address_allocator: floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Auto { + ip_version: Some(IpVersion::V6), }, }, }; @@ -1565,7 +1569,7 @@ async fn test_ephemeral_ip_ip_version_conflict( client, INSTANCE_NAMES[0], false, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultDualStack, + &InstanceNetworkInterfaceAttachment::DefaultDualStack, None, &[], ) @@ -1573,8 +1577,8 @@ async fn test_ephemeral_ip_ip_version_conflict( let url = instance_ephemeral_ip_url(INSTANCE_NAMES[0], PROJECT_NAME); let error: HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::EphemeralIpCreate { - pool_selector: params::PoolSelector::Auto { ip_version: None }, + .body(Some(&instance::EphemeralIpCreate { + pool_selector: ip_pool::PoolSelector::Auto { ip_version: None }, })) .expect_status(Some(StatusCode::BAD_REQUEST)), ) @@ -1591,11 +1595,11 @@ async fn test_ephemeral_ip_ip_version_conflict( ); // With explicit IP version: V4, should succeed - let eph_v4: views::ExternalIp = NexusRequest::new( + let eph_v4: external_ip::ExternalIp = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::EphemeralIpCreate { - pool_selector: params::PoolSelector::Auto { - ip_version: Some(views::IpVersion::V4), + .body(Some(&instance::EphemeralIpCreate { + pool_selector: ip_pool::PoolSelector::Auto { + ip_version: Some(IpVersion::V4), }, })) .expect_status(Some(StatusCode::ACCEPTED)), @@ -1607,7 +1611,7 @@ async fn test_ephemeral_ip_ip_version_conflict( .parsed_body() .unwrap(); assert!( - matches!(&eph_v4, views::ExternalIp::Ephemeral { ip, .. } if ip.is_ipv4()), + matches!(&eph_v4, external_ip::ExternalIp::Ephemeral { ip, .. } if ip.is_ipv4()), "Expected IPv4 ephemeral IP" ); @@ -1622,11 +1626,11 @@ async fn test_ephemeral_ip_ip_version_conflict( .unwrap(); // With explicit IP version: V6, should succeed - let eph_v6: views::ExternalIp = NexusRequest::new( + let eph_v6: external_ip::ExternalIp = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::EphemeralIpCreate { - pool_selector: params::PoolSelector::Auto { - ip_version: Some(views::IpVersion::V6), + .body(Some(&instance::EphemeralIpCreate { + pool_selector: ip_pool::PoolSelector::Auto { + ip_version: Some(IpVersion::V6), }, })) .expect_status(Some(StatusCode::ACCEPTED)), @@ -1638,7 +1642,7 @@ async fn test_ephemeral_ip_ip_version_conflict( .parsed_body() .unwrap(); assert!( - matches!(&eph_v6, views::ExternalIp::Ephemeral { ip, .. } if ip.is_ipv6()), + matches!(&eph_v6, external_ip::ExternalIp::Ephemeral { ip, .. } if ip.is_ipv6()), "Expected IPv6 ephemeral IP" ); } @@ -1656,7 +1660,7 @@ async fn test_ephemeral_ip_detach_requires_version_with_dual_stack( client, instance_name, false, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultDualStack, + &instance::InstanceNetworkInterfaceAttachment::DefaultDualStack, None, &[], ) @@ -1680,10 +1684,8 @@ async fn test_ephemeral_ip_detach_requires_version_with_dual_stack( "instance has two ephemeral IPs; specify ip_version to select which to detach" ); - ephemeral_ip_detach(client, instance_name, Some(views::IpVersion::V4)) - .await; - ephemeral_ip_detach(client, instance_name, Some(views::IpVersion::V6)) - .await; + ephemeral_ip_detach(client, instance_name, Some(IpVersion::V4)).await; + ephemeral_ip_detach(client, instance_name, Some(IpVersion::V6)).await; } #[nexus_test] @@ -1717,8 +1719,8 @@ async fn cannot_attach_floating_ipv4_to_instance_missing_ipv4_stack( client, fip_name, PROJECT_NAME, - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v4_pool.identity.name.clone().into(), }, }, @@ -1727,8 +1729,8 @@ async fn cannot_attach_floating_ipv4_to_instance_missing_ipv4_stack( let url = attach_floating_ip_url(fip_name, PROJECT_NAME); let result = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::FloatingIpAttach { - kind: params::FloatingIpParentKind::Instance, + .body(Some(&floating_ip::FloatingIpAttach { + kind: floating_ip::FloatingIpParentKind::Instance, parent: instance_name.parse::().unwrap().into(), })) .expect_status(Some(StatusCode::BAD_REQUEST)), @@ -1785,8 +1787,8 @@ async fn cannot_attach_floating_ipv6_to_instance_missing_ipv6_stack( client, fip_name, PROJECT_NAME, - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v6_pool.identity.name.clone().into(), }, }, @@ -1795,8 +1797,8 @@ async fn cannot_attach_floating_ipv6_to_instance_missing_ipv6_stack( let url = attach_floating_ip_url(fip_name, PROJECT_NAME); let result = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::FloatingIpAttach { - kind: params::FloatingIpParentKind::Instance, + .body(Some(&floating_ip::FloatingIpAttach { + kind: floating_ip::FloatingIpParentKind::Instance, parent: instance_name.parse::().unwrap().into(), })) .expect_status(Some(StatusCode::BAD_REQUEST)), @@ -1849,8 +1851,8 @@ async fn cannot_attach_ephemeral_ipv4_to_instance_missing_ipv4_stack( let url = instance_ephemeral_ip_url(instance_name, PROJECT_NAME); let result = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::EphemeralIpCreate { - pool_selector: params::PoolSelector::Explicit { + .body(Some(&instance::EphemeralIpCreate { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v4_pool.identity.name.clone().into(), }, })) @@ -1904,8 +1906,8 @@ async fn cannot_attach_ephemeral_ipv6_to_instance_missing_ipv6_stack( let url = instance_ephemeral_ip_url(instance_name, PROJECT_NAME); let result = NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::EphemeralIpCreate { - pool_selector: params::PoolSelector::Explicit { + .body(Some(&instance::EphemeralIpCreate { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v6_pool.identity.name.clone().into(), }, })) @@ -1990,7 +1992,7 @@ async fn can_list_instance_snat_ip(cptestctx: &ControlPlaneTestContext) { client, instance_name, true, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultDualStack, + &InstanceNetworkInterfaceAttachment::DefaultDualStack, None, &[], ) @@ -2080,10 +2082,10 @@ async fn can_create_instance_with_ephemeral_ipv6_address( &client, PROJECT_NAME, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv6, + &InstanceNetworkInterfaceAttachment::DefaultIpv6, /* disks = */ vec![], - vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { + vec![instance::ExternalIpCreate::Ephemeral { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v6_pool.identity.id.into(), }, }], @@ -2171,8 +2173,8 @@ async fn can_create_instance_with_floating_ipv6_address( client, fip_name, &project.identity.id.to_string(), - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v6_pool.identity.name.clone().into(), }, }, @@ -2185,9 +2187,9 @@ async fn can_create_instance_with_floating_ipv6_address( &client, PROJECT_NAME, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv6, + &InstanceNetworkInterfaceAttachment::DefaultIpv6, /* disks = */ vec![], - vec![params::ExternalIpCreate::Floating { + vec![instance::ExternalIpCreate::Floating { floating_ip: NameOrId::Id(fip.identity.id), }], /* start = */ false, @@ -2272,19 +2274,19 @@ async fn instance_for_external_ips( client: &ClientTestContext, instance_name: &str, start: bool, - nic: ¶ms::InstanceNetworkInterfaceAttachment, + nic: &InstanceNetworkInterfaceAttachment, ephemeral_ip_version: Option, floating_ip_names: &[&str], ) -> Instance { let mut eips: Vec<_> = floating_ip_names .iter() - .map(|s| params::ExternalIpCreate::Floating { + .map(|s| instance::ExternalIpCreate::Floating { floating_ip: s.parse::().unwrap().into(), }) .collect(); if let Some(ip_version) = ephemeral_ip_version { - eips.push(params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Auto { + eips.push(instance::ExternalIpCreate::Ephemeral { + pool_selector: ip_pool::PoolSelector::Auto { ip_version: Some(ip_version), }, }) @@ -2309,17 +2311,17 @@ async fn ephemeral_ip_attach( instance_name: &str, pool_name: Option<&str>, ip_version: Option, -) -> views::ExternalIp { +) -> external_ip::ExternalIp { let url = instance_ephemeral_ip_url(instance_name, PROJECT_NAME); let pool_selector = match pool_name { - Some(name) => params::PoolSelector::Explicit { + Some(name) => ip_pool::PoolSelector::Explicit { pool: name.parse::().unwrap().into(), }, - None => params::PoolSelector::Auto { ip_version }, + None => ip_pool::PoolSelector::Auto { ip_version }, }; NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::EphemeralIpCreate { pool_selector })) + .body(Some(&instance::EphemeralIpCreate { pool_selector })) .expect_status(Some(StatusCode::ACCEPTED)), ) .authn_as(AuthnMode::PrivilegedUser) @@ -2333,7 +2335,7 @@ async fn ephemeral_ip_attach( async fn ephemeral_ip_detach( client: &ClientTestContext, instance_name: &str, - ip_version: Option, + ip_version: Option, ) { let mut url = instance_ephemeral_ip_url(instance_name, PROJECT_NAME); if let Some(version) = ip_version { @@ -2346,12 +2348,12 @@ async fn floating_ip_attach( client: &ClientTestContext, instance_name: &str, floating_ip_name: &str, -) -> views::FloatingIp { +) -> floating_ip::FloatingIp { let url = attach_floating_ip_url(floating_ip_name, PROJECT_NAME); NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::FloatingIpAttach { - kind: params::FloatingIpParentKind::Instance, + .body(Some(&floating_ip::FloatingIpAttach { + kind: floating_ip::FloatingIpParentKind::Instance, parent: instance_name.parse::().unwrap().into(), })) .expect_status(Some(StatusCode::ACCEPTED)), @@ -2367,7 +2369,7 @@ async fn floating_ip_attach( async fn floating_ip_detach( client: &ClientTestContext, floating_ip_name: &str, -) -> views::FloatingIp { +) -> floating_ip::FloatingIp { let url = detach_floating_ip_url(floating_ip_name, PROJECT_NAME); NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) @@ -2424,7 +2426,7 @@ async fn test_ephemeral_ip_idempotent_attach_with_exhausted_explicit_pool( client, instance_name, false, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultDualStack, + &instance::InstanceNetworkInterfaceAttachment::DefaultDualStack, None, // No ephemeral IP at creation &[], ) @@ -2469,7 +2471,7 @@ async fn no_automatic_snat_for_ipv6(cptestctx: &ControlPlaneTestContext) { client, project_name, "niccy", - ¶ms::InstanceNetworkInterfaceAttachment::DefaultDualStack, + &instance::InstanceNetworkInterfaceAttachment::DefaultDualStack, vec![], vec![], false, @@ -2491,7 +2493,7 @@ async fn no_automatic_snat_for_ipv6(cptestctx: &ControlPlaneTestContext) { assert_eq!(eips.len(), 1, "Expected exactly 1 SNAT external IP"); assert_eq!( eips[0].kind(), - shared::IpKind::SNat, + external_ip::IpKind::SNat, "Expected exactly 1 SNAT external IP" ); assert!(matches!(eips[0].ip(), IpAddr::V4(_))); diff --git a/nexus/tests/integration_tests/external_subnets.rs b/nexus/tests/integration_tests/external_subnets.rs index 9f57077d97e..1f68540a1a2 100644 --- a/nexus/tests/integration_tests/external_subnets.rs +++ b/nexus/tests/integration_tests/external_subnets.rs @@ -22,8 +22,9 @@ use nexus_test_utils::resource_helpers::create_subnet_pool; use nexus_test_utils::resource_helpers::create_subnet_pool_member; use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::views::ExternalSubnet; +use nexus_types::external_api::external_subnet as external_subnet_types; +use nexus_types::external_api::external_subnet::ExternalSubnet; +use nexus_types::external_api::ip_pool; use omicron_common::address::IpVersion; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; @@ -84,14 +85,14 @@ async fn external_subnet_basic_crud(cptestctx: &ControlPlaneTestContext) { let _project = create_project(client, PROJECT_NAME).await; // Sanity check, can we CRUD a single subnet. - let create_params = params::ExternalSubnetCreate { + let create_params = external_subnet_types::ExternalSubnetCreate { identity: IdentityMetadataCreateParams { name: EXTERNAL_SUBNET_NAME.parse().unwrap(), description: String::from("A test external subnet"), }, - allocator: params::ExternalSubnetAllocator::Auto { + allocator: external_subnet_types::ExternalSubnetAllocator::Auto { prefix_len: 28, - pool_selector: params::PoolSelector::Explicit { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name(SUBNET_POOL_NAME.parse().unwrap()), }, }, @@ -131,7 +132,7 @@ async fn external_subnet_basic_crud(cptestctx: &ControlPlaneTestContext) { // Update the metadata let new_name = "quartzite".parse::().unwrap(); - let updates = params::ExternalSubnetUpdate { + let updates = external_subnet_types::ExternalSubnetUpdate { identity: IdentityMetadataUpdateParams { name: Some(new_name.clone()), description: None, @@ -191,14 +192,14 @@ async fn external_subnet_pagination(cptestctx: &ControlPlaneTestContext) { let n_subnets = 10; let mut subnets = Vec::with_capacity(n_subnets); for i in 0..n_subnets { - let create_params = params::ExternalSubnetCreate { + let create_params = external_subnet_types::ExternalSubnetCreate { identity: IdentityMetadataCreateParams { name: format!("{EXTERNAL_SUBNET_NAME}-{i}").parse().unwrap(), description: String::from("A test external subnet"), }, - allocator: params::ExternalSubnetAllocator::Auto { + allocator: external_subnet_types::ExternalSubnetAllocator::Auto { prefix_len: 28, - pool_selector: params::PoolSelector::Explicit { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name(SUBNET_POOL_NAME.parse().unwrap()), }, }, @@ -290,14 +291,14 @@ async fn attach_test_impl( let _ = create_project(client, PROJECT_NAME).await; // Then create a subnet in the pool. - let create_params = params::ExternalSubnetCreate { + let create_params = external_subnet_types::ExternalSubnetCreate { identity: IdentityMetadataCreateParams { name: EXTERNAL_SUBNET_NAME.parse().unwrap(), description: String::from("A test external subnet"), }, - allocator: params::ExternalSubnetAllocator::Auto { + allocator: external_subnet_types::ExternalSubnetAllocator::Auto { prefix_len: 28, - pool_selector: params::PoolSelector::Explicit { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name(SUBNET_POOL_NAME.parse().unwrap()), }, }, @@ -408,14 +409,14 @@ async fn cannot_attach_subnet_in_another_project( let _ = create_project(client, PROJECT_NAME).await; // Then create a subnet in the pool. - let create_params = params::ExternalSubnetCreate { + let create_params = external_subnet_types::ExternalSubnetCreate { identity: IdentityMetadataCreateParams { name: EXTERNAL_SUBNET_NAME.parse().unwrap(), description: String::from("A test external subnet"), }, - allocator: params::ExternalSubnetAllocator::Auto { + allocator: external_subnet_types::ExternalSubnetAllocator::Auto { prefix_len: 28, - pool_selector: params::PoolSelector::Explicit { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name(SUBNET_POOL_NAME.parse().unwrap()), }, }, @@ -442,7 +443,7 @@ async fn cannot_attach_subnet_in_another_project( instance_wait_for_state(client, instance_id, InstanceState::Running).await; // We should not be able to attach the subnet to it. - let params = params::ExternalSubnetAttach { + let params = external_subnet_types::ExternalSubnetAttach { instance: NameOrId::Id(instance.identity.id), }; NexusRequest::expect_failure_with_body( @@ -475,14 +476,14 @@ async fn cannot_attach_subnet_attached_to_another_instance( let _ = create_project(client, PROJECT_NAME).await; // Then create a subnet in the pool. - let create_params = params::ExternalSubnetCreate { + let create_params = external_subnet_types::ExternalSubnetCreate { identity: IdentityMetadataCreateParams { name: EXTERNAL_SUBNET_NAME.parse().unwrap(), description: String::from("A test external subnet"), }, - allocator: params::ExternalSubnetAllocator::Auto { + allocator: external_subnet_types::ExternalSubnetAllocator::Auto { prefix_len: 28, - pool_selector: params::PoolSelector::Explicit { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name(SUBNET_POOL_NAME.parse().unwrap()), }, }, @@ -526,7 +527,7 @@ async fn cannot_attach_subnet_attached_to_another_instance( instance_wait_for_state(client, instance_id, InstanceState::Running).await; // We should not be able to attach the subnet to it. - let params = params::ExternalSubnetAttach { + let params = external_subnet_types::ExternalSubnetAttach { instance: NameOrId::Id(instance2.identity.id), }; NexusRequest::expect_failure_with_body( @@ -559,14 +560,14 @@ async fn cannot_detach_subnet_that_is_not_attached( let _ = create_project(client, PROJECT_NAME).await; // Then create a subnet in the pool. - let create_params = params::ExternalSubnetCreate { + let create_params = external_subnet_types::ExternalSubnetCreate { identity: IdentityMetadataCreateParams { name: EXTERNAL_SUBNET_NAME.parse().unwrap(), description: String::from("A test external subnet"), }, - allocator: params::ExternalSubnetAllocator::Auto { + allocator: external_subnet_types::ExternalSubnetAllocator::Auto { prefix_len: 28, - pool_selector: params::PoolSelector::Explicit { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name(SUBNET_POOL_NAME.parse().unwrap()), }, }, @@ -629,14 +630,14 @@ async fn cannot_attach_too_many_subnets(cptestctx: &ControlPlaneTestContext) { ); for i in 0..MAX_ATTACHED_SUBNETS_PER_INSTANCE { let name = format!("{EXTERNAL_SUBNET_NAME}-{i}"); - let create_params = params::ExternalSubnetCreate { + let create_params = external_subnet_types::ExternalSubnetCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: String::from("A test external subnet"), }, - allocator: params::ExternalSubnetAllocator::Auto { + allocator: external_subnet_types::ExternalSubnetAllocator::Auto { prefix_len: 30, - pool_selector: params::PoolSelector::Explicit { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name(SUBNET_POOL_NAME.parse().unwrap()), }, }, @@ -655,14 +656,14 @@ async fn cannot_attach_too_many_subnets(cptestctx: &ControlPlaneTestContext) { } // Create one more external subnet. - let create_params = params::ExternalSubnetCreate { + let create_params = external_subnet_types::ExternalSubnetCreate { identity: IdentityMetadataCreateParams { name: SECOND_EXTERNAL_SUBNET_NAME.parse().unwrap(), description: String::from("A test external subnet"), }, - allocator: params::ExternalSubnetAllocator::Auto { + allocator: external_subnet_types::ExternalSubnetAllocator::Auto { prefix_len: 30, - pool_selector: params::PoolSelector::Explicit { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name(SUBNET_POOL_NAME.parse().unwrap()), }, }, @@ -683,7 +684,7 @@ async fn cannot_attach_too_many_subnets(cptestctx: &ControlPlaneTestContext) { StatusCode::BAD_REQUEST, Method::POST, &external_subnet_attach_url(SECOND_EXTERNAL_SUBNET_NAME, PROJECT_NAME), - ¶ms::ExternalSubnetAttach { + &external_subnet_types::ExternalSubnetAttach { instance: INSTANCE_NAME.parse::().unwrap().into(), }, ) @@ -710,14 +711,14 @@ async fn cannot_delete_attached_external_subnet( let _ = create_project(client, PROJECT_NAME).await; // Then create a subnet in the pool. - let create_params = params::ExternalSubnetCreate { + let create_params = external_subnet_types::ExternalSubnetCreate { identity: IdentityMetadataCreateParams { name: EXTERNAL_SUBNET_NAME.parse().unwrap(), description: String::from("A test external subnet"), }, - allocator: params::ExternalSubnetAllocator::Auto { + allocator: external_subnet_types::ExternalSubnetAllocator::Auto { prefix_len: 28, - pool_selector: params::PoolSelector::Explicit { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name(SUBNET_POOL_NAME.parse().unwrap()), }, }, @@ -772,7 +773,7 @@ async fn attach_external_subnet( let url = external_subnet_attach_url(subnet_name, PROJECT_NAME); NexusRequest::new( RequestBuilder::new(client, Method::POST, &url) - .body(Some(¶ms::ExternalSubnetAttach { + .body(Some(&external_subnet_types::ExternalSubnetAttach { instance: instance_name.parse::().unwrap().into(), })) .expect_status(Some(StatusCode::ACCEPTED)), diff --git a/nexus/tests/integration_tests/images.rs b/nexus/tests/integration_tests/images.rs index f9894b1a8d9..b7e81d45c86 100644 --- a/nexus/tests/integration_tests/images.rs +++ b/nexus/tests/integration_tests/images.rs @@ -16,9 +16,10 @@ use nexus_test_utils::resource_helpers::DiskTest; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils::resource_helpers::grant_iam; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::shared::ProjectRole; -use nexus_types::external_api::shared::SiloRole; -use nexus_types::external_api::{params, views}; +use nexus_types::external_api::disk; +use nexus_types::external_api::image; +use nexus_types::external_api::policy::{ProjectRole, SiloRole}; +use nexus_types::external_api::snapshot; use nexus_types::identity::Asset; use nexus_types::identity::Resource; use omicron_common::api::external::Disk; @@ -33,8 +34,8 @@ fn get_project_images_url(project_name: &str) -> String { format!("/v1/images?project={}", project_name) } -fn get_image_create(source: params::ImageSource) -> params::ImageCreate { - params::ImageCreate { +fn get_image_create(source: image::ImageSource) -> image::ImageCreate { + image::ImageCreate { identity: IdentityMetadataCreateParams { name: "alpine-edge".parse().unwrap(), description: String::from( @@ -71,7 +72,7 @@ async fn test_image_create(cptestctx: &ControlPlaneTestContext) { let images = NexusRequest::object_get(client, &images_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items; @@ -79,18 +80,18 @@ async fn test_image_create(cptestctx: &ControlPlaneTestContext) { // Create an image in the project let image_create_params = get_image_create( - params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, ); NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; // one image in the project let images = NexusRequest::object_get(client, &images_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items; @@ -108,7 +109,7 @@ async fn test_silo_image_create(cptestctx: &ControlPlaneTestContext) { // Expect no images in the silo let images = NexusRequest::object_get(client, &silo_images_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items; @@ -116,18 +117,18 @@ async fn test_silo_image_create(cptestctx: &ControlPlaneTestContext) { // Create an image in the project let image_create_params = get_image_create( - params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, ); // Create image NexusRequest::objects_post(client, &silo_images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; let images = NexusRequest::object_get(client, &silo_images_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items; @@ -145,7 +146,7 @@ async fn test_make_disk_from_image(cptestctx: &ControlPlaneTestContext) { // Create an image in the project let image_create_params = get_image_create( - params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, ); let images_url = get_project_images_url(PROJECT_NAME); @@ -153,16 +154,16 @@ async fn test_make_disk_from_image(cptestctx: &ControlPlaneTestContext) { let alpine_image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: alpine_image.identity.id, read_only: false, }, @@ -190,21 +191,21 @@ async fn test_make_disk_from_other_project_image_fails( let images_url = get_project_images_url(PROJECT_NAME); let image_create_params = get_image_create( - params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, ); let image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "stolen-disk".parse().unwrap(), description: String::from("yoink"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: image.identity.id, read_only: false, }, @@ -241,7 +242,7 @@ async fn test_make_disk_from_image_too_small( // Create an image in the project let image_create_params = get_image_create( - params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, ); let images_url = get_project_images_url(PROJECT_NAME); @@ -249,16 +250,16 @@ async fn test_make_disk_from_image_too_small( let alpine_image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk".parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: alpine_image.identity.id, read_only: false, }, @@ -300,24 +301,24 @@ async fn test_image_promotion(cptestctx: &ControlPlaneTestContext) { let images = NexusRequest::object_get(client, &images_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items; assert_eq!(images.len(), 0); let image_create_params = get_image_create( - params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, ); NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; let project_images = NexusRequest::object_get(client, &images_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items; @@ -329,7 +330,7 @@ async fn test_image_promotion(cptestctx: &ControlPlaneTestContext) { let project_image = NexusRequest::object_get(client, &project_image_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; assert_eq!(project_image.identity.id, image_id); @@ -341,12 +342,12 @@ async fn test_image_promotion(cptestctx: &ControlPlaneTestContext) { .expect_status(Some(http::StatusCode::ACCEPTED)), ) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; let silo_images = NexusRequest::object_get(client, &silo_images_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items; @@ -356,7 +357,7 @@ async fn test_image_promotion(cptestctx: &ControlPlaneTestContext) { // Ensure there are no more project images let project_images = NexusRequest::object_get(client, &images_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items; @@ -365,7 +366,7 @@ async fn test_image_promotion(cptestctx: &ControlPlaneTestContext) { let silo_image_url = format!("/v1/images/{}", image_id); let silo_image = NexusRequest::object_get(client, &silo_image_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; assert_eq!(silo_image.identity.id, image_id); @@ -373,13 +374,13 @@ async fn test_image_promotion(cptestctx: &ControlPlaneTestContext) { // Create another project image with the same name NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; // Ensure project image was created let project_images = NexusRequest::object_get(client, &images_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items; @@ -415,21 +416,21 @@ async fn test_image_from_other_project_snapshot_fails( // Create an image let image_create_params = get_image_create( - params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, ); - let image: views::Image = + let image: image::Image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) .execute_and_parse_unwrap() .await; // Create a disk from this image - let disk_create_params = params::DiskCreate { + let disk_create_params = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk".parse().unwrap(), description: "meow".into(), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: image.identity.id, read_only: false, }, @@ -445,14 +446,14 @@ async fn test_image_from_other_project_snapshot_fails( .parsed_body() .unwrap(); // Create a snapshot from this disk - let snapshot_create_params = params::SnapshotCreate { + let snapshot_create_params = snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "snapshot".parse().unwrap(), description: "meow".into(), }, disk: disk.identity.id.into(), }; - let snapshot: views::Snapshot = NexusRequest::objects_post( + let snapshot: snapshot::Snapshot = NexusRequest::objects_post( client, &snapshots_url, &snapshot_create_params, @@ -468,7 +469,7 @@ async fn test_image_from_other_project_snapshot_fails( let another_project = create_project(client, "another-proj").await; let images_url = get_project_images_url(another_project.identity.name.as_str()); - let image_create_params = get_image_create(params::ImageSource::Snapshot { + let image_create_params = get_image_create(image::ImageSource::Snapshot { id: snapshot.identity.id, }); let error = NexusRequest::new( @@ -522,13 +523,13 @@ async fn test_image_deletion_permissions(cptestctx: &ControlPlaneTestContext) { let images_url = get_project_images_url(PROJECT_NAME); let image_create_params = get_image_create( - params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, ); let image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; let image_id = image.identity.id; @@ -541,12 +542,12 @@ async fn test_image_deletion_permissions(cptestctx: &ControlPlaneTestContext) { .expect_status(Some(http::StatusCode::ACCEPTED)), ) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; let silo_images = NexusRequest::object_get(client, &silo_images_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await .items; @@ -574,7 +575,7 @@ async fn test_image_deletion_permissions(cptestctx: &ControlPlaneTestContext) { .expect_status(Some(http::StatusCode::ACCEPTED)), ) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; // now the unpriviledged user should be able to delete that image diff --git a/nexus/tests/integration_tests/instances.rs b/nexus/tests/integration_tests/instances.rs index f7890476f9b..a539926199a 100644 --- a/nexus/tests/integration_tests/instances.rs +++ b/nexus/tests/integration_tests/instances.rs @@ -43,19 +43,27 @@ use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils::resource_helpers::test_params; use nexus_test_utils::start_sled_agent_with_config; use nexus_test_utils::wait_for_producer; -use nexus_types::external_api::params::IpAssignment; -use nexus_types::external_api::params::PrivateIpStackCreate; -use nexus_types::external_api::params::PrivateIpv4StackCreate; -use nexus_types::external_api::params::PrivateIpv6StackCreate; -use nexus_types::external_api::params::SshKeyCreate; -use nexus_types::external_api::shared::IpKind; -use nexus_types::external_api::shared::IpRange; -use nexus_types::external_api::shared::Ipv4Range; -use nexus_types::external_api::shared::SiloIdentityMode; -use nexus_types::external_api::views::Sled; -use nexus_types::external_api::views::SshKey; -use nexus_types::external_api::views::VpcSubnet; -use nexus_types::external_api::{params, views}; +use nexus_types::external_api::affinity; +use nexus_types::external_api::disk; +use nexus_types::external_api::external_ip::{ExternalIp, IpKind}; +use nexus_types::external_api::floating_ip; +use nexus_types::external_api::image; +use nexus_types::external_api::instance; +use nexus_types::external_api::instance::ExternalIpCreate; +use nexus_types::external_api::instance::IpAssignment; +use nexus_types::external_api::instance::PrivateIpStackCreate; +use nexus_types::external_api::instance::PrivateIpv4StackCreate; +use nexus_types::external_api::instance::PrivateIpv6StackCreate; +use nexus_types::external_api::ip_pool::{ + self, IpRange, Ipv4Range, PoolSelector, +}; +use nexus_types::external_api::path_params; +use nexus_types::external_api::project; +use nexus_types::external_api::silo::{self, SiloIdentityMode}; +use nexus_types::external_api::sled::{self, Sled, SledProvisionPolicy}; +use nexus_types::external_api::ssh_key::{SshKey, SshKeyCreate}; +use nexus_types::external_api::vpc; +use nexus_types::external_api::vpc::VpcSubnet; use nexus_types::identity::Resource; use nexus_types::internal_api::params::InstanceMigrateRequest; use nexus_types::silo::DEFAULT_SILO_ID; @@ -110,7 +118,7 @@ use nexus_test_utils::resource_helpers::{ create_project, create_vpc, create_vpc_subnet, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::shared::{ProjectRole, SiloRole}; +use nexus_types::external_api::policy::{ProjectRole, SiloRole}; use omicron_test_utils::dev::poll; use omicron_test_utils::dev::poll::CondCheckError; @@ -167,7 +175,7 @@ const SLEDS_URL: &'static str = "/v1/system/hardware/sleds"; pub async fn create_project_and_pool( client: &ClientTestContext, -) -> views::Project { +) -> project::Project { create_default_ip_pools(client).await; create_project(client, PROJECT_NAME).await } @@ -251,7 +259,7 @@ async fn test_create_instance_with_bad_hostname_impl( // We'll do this by creating a _valid_ set of parameters, convert it to // JSON, and then muck with the hostname. let instance_name = "happy-accident"; - let params = params::InstanceCreate { + let params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: format!("instance {:?}", instance_name), @@ -358,7 +366,7 @@ async fn test_instances_create_reboot_halt( // Attempt to create a second instance with a conflicting name. let error: HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(client, Method::POST, &get_instances_url()) - .body(Some(¶ms::InstanceCreate { + .body(Some(&instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance.identity.name.clone(), description: format!( @@ -372,7 +380,7 @@ async fn test_instances_create_reboot_halt( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -787,9 +795,9 @@ async fn test_instance_migrate(cptestctx: &ControlPlaneTestContext) { client, PROJECT_NAME, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, - Vec::::new(), - Vec::::new(), + &instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + Vec::::new(), + Vec::::new(), true, Default::default(), None, @@ -960,11 +968,11 @@ async fn test_instance_migrate_v2p_and_routes( client, PROJECT_NAME, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, + &instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, // Omit disks: simulated sled agent assumes that disks are always co- // located with their instances. - Vec::::new(), - Vec::::new(), + Vec::::new(), + Vec::::new(), true, Default::default(), None, @@ -1177,9 +1185,9 @@ async fn test_instance_migration_compatible_cpu_platforms( client, PROJECT_NAME, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, - Vec::::new(), - Vec::::new(), + &instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + Vec::::new(), + Vec::::new(), true, Default::default(), Some(InstanceCpuPlatform::AmdMilan), @@ -1366,9 +1374,9 @@ async fn test_instance_migration_incompatible_cpu_platforms( client, PROJECT_NAME, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, - Vec::::new(), - Vec::::new(), + &instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + Vec::::new(), + Vec::::new(), true, Default::default(), Some(InstanceCpuPlatform::AmdTurin), @@ -1443,9 +1451,9 @@ async fn test_instance_migration_unknown_sled_type( client, PROJECT_NAME, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, - Vec::::new(), - Vec::::new(), + &instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + Vec::::new(), + Vec::::new(), true, Default::default(), None, @@ -1682,7 +1690,7 @@ async fn test_instance_failed_when_on_expunged_sled( .sled_set_provision_policy( &opctx, &authz_sled, - views::SledProvisionPolicy::NonProvisionable, + SledProvisionPolicy::NonProvisionable, ) .await .expect("set sled provision policy"); @@ -1699,11 +1707,11 @@ async fn test_instance_failed_when_on_expunged_sled( client, PROJECT_NAME, name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, + &instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, // Disks= - Vec::::new(), + Vec::::new(), // External IPs= - Vec::::new(), + Vec::::new(), true, Some(auto_restart), None, @@ -1743,7 +1751,7 @@ async fn test_instance_failed_when_on_expunged_sled( .sled_set_provision_policy( &opctx, &authz_sled, - views::SledProvisionPolicy::Provisionable, + SledProvisionPolicy::Provisionable, ) .await .expect("set sled provision policy"); @@ -1760,7 +1768,7 @@ async fn test_instance_failed_when_on_expunged_sled( .make_request( Method::POST, "/sleds/expunge", - Some(params::SledSelector { sled: default_sled_id }), + Some(sled::SledSelector { sled: default_sled_id }), StatusCode::OK, ) .await @@ -2050,11 +2058,11 @@ async fn make_forgotten_instance( client, PROJECT_NAME, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, + &instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, // Disks= - Vec::::new(), + Vec::::new(), // External IPs= - Vec::::new(), + Vec::::new(), true, Some(auto_restart), None, @@ -2283,9 +2291,9 @@ async fn test_instance_metrics_with_migration( client, PROJECT_NAME, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, - Vec::::new(), - Vec::::new(), + &instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + Vec::::new(), + Vec::::new(), true, Default::default(), None, @@ -2441,7 +2449,7 @@ async fn test_instances_create_stopped_start( let instance: Instance = object_create( client, &get_instances_url(), - ¶ms::InstanceCreate { + &instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: format!("instance {}", instance_name), @@ -2452,7 +2460,7 @@ async fn test_instances_create_stopped_start( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -2593,7 +2601,7 @@ async fn test_instance_using_image_from_other_project_fails( // Create an image in springfield-squidport. let images_url = format!("/v1/images?project={}", PROJECT_NAME); - let image_create_params = params::ImageCreate { + let image_create_params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "alpine-edge".parse().unwrap(), description: String::from( @@ -2602,12 +2610,12 @@ async fn test_instance_using_image_from_other_project_fails( }, os: "alpine".to_string(), version: "edge".to_string(), - source: params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + source: image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, }; let image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; // Try and fail to create an instance in another project. @@ -2616,7 +2624,7 @@ async fn test_instance_using_image_from_other_project_fails( format!("/v1/instances?project={}", project.identity.name); let error: HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(client, Method::POST, &instances_url) - .body(Some(¶ms::InstanceCreate { + .body(Some(&instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "stolen".parse().unwrap(), description: "i stole an image".into(), @@ -2627,16 +2635,16 @@ async fn test_instance_using_image_from_other_project_fails( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], - disks: vec![params::InstanceDiskAttachment::Create( - params::DiskCreate { + disks: vec![instance::InstanceDiskAttachment::Create( + disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "stolen".parse().unwrap(), description: "i stole an image".into(), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: image.identity.id, read_only: false, }, @@ -2685,7 +2693,7 @@ async fn test_instance_create_saga_removes_instance_database_record( let default_name = "default".parse::().unwrap(); let requested_address = "172.30.0.10".parse::().unwrap(); - let if0_params = params::InstanceNetworkInterfaceCreate { + let if0_params = instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("if0")).unwrap(), description: String::from("first custom interface"), @@ -2695,12 +2703,12 @@ async fn test_instance_create_saga_removes_instance_database_record( ip_config: PrivateIpStackCreate::from_ipv4(requested_address), }; let interface_params = - params::InstanceNetworkInterfaceAttachment::Create(vec![ + instance::InstanceNetworkInterfaceAttachment::Create(vec![ if0_params.clone(), ]); // Create the parameters for the instance itself, and create it. - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("unwind-test-inst")).unwrap(), description: String::from("instance to test saga unwind"), @@ -2733,7 +2741,7 @@ async fn test_instance_create_saga_removes_instance_database_record( // Try to create a _new_ instance, with the same IP address. Note that the // other data does not conflict yet. - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("unwind-test-inst2")).unwrap(), description: String::from("instance to test saga unwind 2"), @@ -2771,7 +2779,7 @@ async fn test_instance_create_saga_removes_instance_database_record( // fully unwind the saga and delete the instance database record. let requested_address = "172.30.0.11".parse::().unwrap(); - let if0_params = params::InstanceNetworkInterfaceCreate { + let if0_params = instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("if0")).unwrap(), description: String::from("first custom interface"), @@ -2781,10 +2789,10 @@ async fn test_instance_create_saga_removes_instance_database_record( ip_config: PrivateIpStackCreate::from_ipv4(requested_address), }; let interface_params = - params::InstanceNetworkInterfaceAttachment::Create(vec![ + instance::InstanceNetworkInterfaceAttachment::Create(vec![ if0_params.clone(), ]); - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { network_interfaces: interface_params, ..instance_params.clone() }; @@ -2873,7 +2881,7 @@ async fn test_instance_with_single_explicit_ip_address_impl( ) { // Create the parameters for the interface. let default_name = "default".parse::().unwrap(); - let if0_params = params::InstanceNetworkInterfaceCreate { + let if0_params = instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("if0")).unwrap(), description: String::from("first custom interface"), @@ -2883,12 +2891,12 @@ async fn test_instance_with_single_explicit_ip_address_impl( ip_config: ip_config.clone(), }; let interface_params = - params::InstanceNetworkInterfaceAttachment::Create(vec![ + instance::InstanceNetworkInterfaceAttachment::Create(vec![ if0_params.clone(), ]); // Create the parameters for the instance itself, and create it. - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("nic-test-inst")).unwrap(), description: String::from("instance to test multiple nics"), @@ -3021,7 +3029,7 @@ async fn test_instance_with_new_custom_network_interfaces( let default_name = Name::try_from(String::from("default")).unwrap(); let non_default_subnet_name = Name::try_from(String::from("non-default-subnet")).unwrap(); - let vpc_subnet_params = params::VpcSubnetCreate { + let vpc_subnet_params = vpc::VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: non_default_subnet_name.clone(), description: String::from("A non-default subnet"), @@ -3039,12 +3047,12 @@ async fn test_instance_with_new_custom_network_interfaces( .execute() .await .expect("Failed to create custom VPC Subnet") - .parsed_body::() + .parsed_body::() .unwrap(); // Create the parameters for the interfaces. These will be created during // the saga for instance creation. - let if0_params = params::InstanceNetworkInterfaceCreate { + let if0_params = instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("if0")).unwrap(), description: String::from("first custom interface"), @@ -3053,7 +3061,7 @@ async fn test_instance_with_new_custom_network_interfaces( subnet_name: default_name.clone(), ip_config: PrivateIpStackCreate::auto_ipv4(), }; - let if1_params = params::InstanceNetworkInterfaceCreate { + let if1_params = instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("if1")).unwrap(), description: String::from("second custom interface"), @@ -3063,13 +3071,13 @@ async fn test_instance_with_new_custom_network_interfaces( ip_config: PrivateIpStackCreate::auto_dual_stack(), }; let interface_params = - params::InstanceNetworkInterfaceAttachment::Create(vec![ + instance::InstanceNetworkInterfaceAttachment::Create(vec![ if0_params.clone(), if1_params.clone(), ]); // Create the parameters for the instance itself, and create it. - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("nic-test-inst")).unwrap(), description: String::from("instance to test multiple nics"), @@ -3181,7 +3189,7 @@ async fn test_instance_create_delete_network_interface( create_project_and_pool(&client).await; // Create the VPC Subnet for the secondary interface - let secondary_subnet = params::VpcSubnetCreate { + let secondary_subnet = vpc::VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("secondary")).unwrap(), description: String::from("A secondary VPC subnet"), @@ -3201,7 +3209,7 @@ async fn test_instance_create_delete_network_interface( .expect("Failed to create secondary VPC Subnet"); // Create an instance with no network interfaces - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("instance to test attaching new nic"), @@ -3211,7 +3219,7 @@ async fn test_instance_create_delete_network_interface( hostname: "nic-test".parse().unwrap(), user_data: vec![], ssh_public_keys: None, - network_interfaces: params::InstanceNetworkInterfaceAttachment::None, + network_interfaces: instance::InstanceNetworkInterfaceAttachment::None, external_ips: vec![], disks: vec![], boot_disk: None, @@ -3249,7 +3257,7 @@ async fn test_instance_create_delete_network_interface( // Parameters for the interfaces to create/attach let if_params = [ - params::InstanceNetworkInterfaceCreate { + instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: "if0".parse().unwrap(), description: String::from("a new nic"), @@ -3264,7 +3272,7 @@ async fn test_instance_create_delete_network_interface( ], }), }, - params::InstanceNetworkInterfaceCreate { + instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: "if1".parse().unwrap(), description: String::from("a new nic"), @@ -3459,7 +3467,7 @@ async fn test_instance_update_network_interfaces( create_project_and_pool(&client).await; // Create the VPC Subnet for the secondary interface - let secondary_subnet = params::VpcSubnetCreate { + let secondary_subnet = vpc::VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("secondary")).unwrap(), description: String::from("A secondary VPC subnet"), @@ -3479,7 +3487,7 @@ async fn test_instance_update_network_interfaces( .expect("Failed to create secondary VPC Subnet"); // Create an instance with no network interfaces - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("instance to test updatin nics"), @@ -3489,7 +3497,7 @@ async fn test_instance_update_network_interfaces( hostname: "nic-test".parse().unwrap(), user_data: vec![], ssh_public_keys: None, - network_interfaces: params::InstanceNetworkInterfaceAttachment::None, + network_interfaces: instance::InstanceNetworkInterfaceAttachment::None, external_ips: vec![], disks: vec![], boot_disk: None, @@ -3516,7 +3524,7 @@ async fn test_instance_update_network_interfaces( // Parameters for each interface to try to modify. let if_params = [ - params::InstanceNetworkInterfaceCreate { + instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: "if0".parse().unwrap(), description: String::from("a new nic"), @@ -3527,7 +3535,7 @@ async fn test_instance_update_network_interfaces( "172.30.0.10".parse().unwrap(), ), }, - params::InstanceNetworkInterfaceCreate { + instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: "if1".parse().unwrap(), description: String::from("a new nic"), @@ -3579,7 +3587,7 @@ async fn test_instance_update_network_interfaces( // We'll change the interface's name and description let new_name = Name::try_from(String::from("new-if0")).unwrap(); let new_description = String::from("new description"); - let updates = params::InstanceNetworkInterfaceUpdate { + let updates = instance::InstanceNetworkInterfaceUpdate { identity: IdentityMetadataUpdateParams { name: Some(new_name.clone()), description: Some(new_description.clone()), @@ -3666,7 +3674,7 @@ async fn test_instance_update_network_interfaces( // Try with the same request again, but this time only changing // `primary`. This should have no effect. - let updates = params::InstanceNetworkInterfaceUpdate { + let updates = instance::InstanceNetworkInterfaceUpdate { identity: IdentityMetadataUpdateParams { name: None, description: None, @@ -3764,7 +3772,7 @@ async fn test_instance_update_network_interfaces( // Verify that we can set the secondary as the new primary, and that nothing // else changes about the NICs. - let updates = params::InstanceNetworkInterfaceUpdate { + let updates = instance::InstanceNetworkInterfaceUpdate { identity: IdentityMetadataUpdateParams { name: None, description: None, @@ -3877,7 +3885,7 @@ async fn cannot_make_new_primary_nic_lacking_ip_stack_for_external_addresses( let _ = create_project(client, PROJECT_NAME).await; // Create the VPC Subnet for the secondary interface. - let secondary_subnet = params::VpcSubnetCreate { + let secondary_subnet = vpc::VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("secondary")).unwrap(), description: String::from("A secondary VPC subnet"), @@ -3898,7 +3906,7 @@ async fn cannot_make_new_primary_nic_lacking_ip_stack_for_external_addresses( // Create an instance with one single-stack IPv4 NIC and one external IPv4 // address. - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("instance to test updatin nics"), @@ -3909,9 +3917,9 @@ async fn cannot_make_new_primary_nic_lacking_ip_stack_for_external_addresses( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, - external_ips: vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + external_ips: vec![ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Explicit { pool: v4_pool.identity.id.into(), }, }], @@ -3939,7 +3947,7 @@ async fn cannot_make_new_primary_nic_lacking_ip_stack_for_external_addresses( ); // Create a second NIC, with an auto-assigned IPv6 address only. - let if_params = params::InstanceNetworkInterfaceCreate { + let if_params = instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: "if0".parse().unwrap(), description: String::from("a new nic"), @@ -3976,7 +3984,7 @@ async fn cannot_make_new_primary_nic_lacking_ip_stack_for_external_addresses( // // This should fail, because the instance has an external IPv4 address, but // this NIC has only an IPv6 address. - let updates = params::InstanceNetworkInterfaceUpdate { + let updates = instance::InstanceNetworkInterfaceUpdate { identity: IdentityMetadataUpdateParams { name: None, description: None, @@ -4014,7 +4022,7 @@ async fn test_instance_update_network_interface_transit_ips( &client, PROJECT_NAME, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, + &instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, vec![], vec![], false, @@ -4029,7 +4037,7 @@ async fn test_instance_update_network_interface_transit_ips( instance.identity.id ); - let base_update = params::InstanceNetworkInterfaceUpdate { + let base_update = instance::InstanceNetworkInterfaceUpdate { identity: IdentityMetadataUpdateParams { name: None, description: None, @@ -4054,7 +4062,7 @@ async fn test_instance_update_network_interface_transit_ips( // Non-canonical form (e.g., host identifier is nonzero) subnets should // be rejected. - let with_extra_bits = params::InstanceNetworkInterfaceUpdate { + let with_extra_bits = instance::InstanceNetworkInterfaceUpdate { transit_ips: vec![ "10.0.0.0/9".parse().unwrap(), "10.128.0.0/9".parse().unwrap(), @@ -4076,7 +4084,7 @@ async fn test_instance_update_network_interface_transit_ips( ); // Multicast IP blocks should be rejected. - let with_mc1 = params::InstanceNetworkInterfaceUpdate { + let with_mc1 = instance::InstanceNetworkInterfaceUpdate { transit_ips: vec![ "10.0.0.0/9".parse().unwrap(), "10.128.0.0/9".parse().unwrap(), @@ -4096,7 +4104,7 @@ async fn test_instance_update_network_interface_transit_ips( "transit IP block 224.0.0.0/4 is a multicast network", ); - let with_mc2 = params::InstanceNetworkInterfaceUpdate { + let with_mc2 = instance::InstanceNetworkInterfaceUpdate { transit_ips: vec![ "10.0.0.0/9".parse().unwrap(), "10.128.0.0/9".parse().unwrap(), @@ -4117,7 +4125,7 @@ async fn test_instance_update_network_interface_transit_ips( ); // Loopback ranges. - let with_lo1 = params::InstanceNetworkInterfaceUpdate { + let with_lo1 = instance::InstanceNetworkInterfaceUpdate { transit_ips: vec![ "10.0.0.0/9".parse().unwrap(), "10.128.0.0/9".parse().unwrap(), @@ -4137,7 +4145,7 @@ async fn test_instance_update_network_interface_transit_ips( "transit IP block 127.42.77.0/24 is a loopback network", ); - let with_lo2 = params::InstanceNetworkInterfaceUpdate { + let with_lo2 = instance::InstanceNetworkInterfaceUpdate { transit_ips: vec![ "10.0.0.0/9".parse().unwrap(), "10.128.0.0/9".parse().unwrap(), @@ -4155,7 +4163,7 @@ async fn test_instance_update_network_interface_transit_ips( assert_eq!(err.message, "transit IP 127.0.0.1/32 is a loopback address"); // Overlapping IP ranges should be rejected, as should identical ranges. - let with_dup1 = params::InstanceNetworkInterfaceUpdate { + let with_dup1 = instance::InstanceNetworkInterfaceUpdate { transit_ips: vec![ "10.0.0.0/9".parse().unwrap(), "10.128.0.0/9".parse().unwrap(), @@ -4175,7 +4183,7 @@ async fn test_instance_update_network_interface_transit_ips( "transit IP block 10.0.0.0/9 overlaps with 10.0.0.0/9", ); - let with_dup2 = params::InstanceNetworkInterfaceUpdate { + let with_dup2 = instance::InstanceNetworkInterfaceUpdate { transit_ips: vec![ "10.0.0.0/9".parse().unwrap(), "10.128.0.0/9".parse().unwrap(), @@ -4196,7 +4204,7 @@ async fn test_instance_update_network_interface_transit_ips( ); // Verify that we also catch more specific CIDRs appearing sooner in the list. - let with_dup3 = params::InstanceNetworkInterfaceUpdate { + let with_dup3 = instance::InstanceNetworkInterfaceUpdate { transit_ips: vec![ "10.20.20.0/30".parse().unwrap(), "10.0.0.0/8".parse().unwrap(), @@ -4225,7 +4233,7 @@ async fn test_instance_update_network_interface_transit_ips( // As a final sanity test, we can still effectively remove spoof checking // using the unspecified network address. - let allow_all = params::InstanceNetworkInterfaceUpdate { + let allow_all = instance::InstanceNetworkInterfaceUpdate { transit_ips: vec!["0.0.0.0/0".parse().unwrap()], ..base_update.clone() }; @@ -4255,7 +4263,7 @@ async fn test_instance_with_multiple_nics_unwinds_completely( // error on creation of the second NIC, and we'll make sure that both are // deleted. let default_name = "default".parse::().unwrap(); - let if0_params = params::InstanceNetworkInterfaceCreate { + let if0_params = instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("if0")).unwrap(), description: String::from("first custom interface"), @@ -4266,7 +4274,7 @@ async fn test_instance_with_multiple_nics_unwinds_completely( "172.30.0.6".parse().unwrap(), ), }; - let if1_params = params::InstanceNetworkInterfaceCreate { + let if1_params = instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("if1")).unwrap(), description: String::from("second custom interface"), @@ -4278,13 +4286,13 @@ async fn test_instance_with_multiple_nics_unwinds_completely( ), }; let interface_params = - params::InstanceNetworkInterfaceAttachment::Create(vec![ + instance::InstanceNetworkInterfaceAttachment::Create(vec![ if0_params.clone(), if1_params.clone(), ]); // Create the parameters for the instance itself, and create it. - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("nic-fail-test-inst")).unwrap(), description: String::from("instance to test multiple bad nics"), @@ -4357,7 +4365,7 @@ async fn test_attach_one_disk_to_instance(cptestctx: &ControlPlaneTestContext) { let disk_name = Name::try_from(String::from("probablydata")).unwrap(); // Create the instance - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("probably serving data"), @@ -4368,10 +4376,10 @@ async fn test_attach_one_disk_to_instance(cptestctx: &ControlPlaneTestContext) { user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { name: disk_name.clone() }, + boot_disk: Some(instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: disk_name.clone() }, )), cpu_platform: None, disks: Vec::new(), @@ -4421,7 +4429,7 @@ async fn test_instance_create_attach_disks( let attachable_disk = create_disk(&client, PROJECT_NAME, "attachable-disk").await; - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("nfs")).unwrap(), description: String::from("probably serving data"), @@ -4432,10 +4440,10 @@ async fn test_instance_create_attach_disks( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], - boot_disk: Some(params::InstanceDiskAttachment::Create( - params::DiskCreate { + boot_disk: Some(instance::InstanceDiskAttachment::Create( + disk::DiskCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("created-disk")).unwrap(), description: String::from( @@ -4443,15 +4451,15 @@ async fn test_instance_create_attach_disks( ), }, size: ByteCount::from_gibibytes_u32(4), - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, }, )), disks: vec![ - params::InstanceDiskAttachment::Create(params::DiskCreate { + instance::InstanceDiskAttachment::Create(disk::DiskCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("created-disk2")) .unwrap(), @@ -4460,14 +4468,14 @@ async fn test_instance_create_attach_disks( ), }, size: ByteCount::from_gibibytes_u32(4), - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, }), - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: attachable_disk.identity.name.clone(), }, ), @@ -4541,7 +4549,7 @@ async fn test_instance_create_attach_disks_undo( assert_eq!(disks[1].identity.id, faulted_disk.identity.id); assert_eq!(disks[1].state, DiskState::Faulted); - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("nfs")).unwrap(), description: String::from("probably serving data"), @@ -4552,26 +4560,30 @@ async fn test_instance_create_attach_disks_undo( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![ - params::InstanceDiskAttachment::Create(params::DiskCreate { + instance::InstanceDiskAttachment::Create(disk::DiskCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("probablydata")).unwrap(), description: String::from("probably data"), }, size: ByteCount::from_gibibytes_u32(4), - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, }), - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { name: regular_disk.identity.name }, + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { + name: regular_disk.identity.name, + }, ), - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { name: faulted_disk.identity.name }, + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { + name: faulted_disk.identity.name, + }, ), ], boot_disk: None, @@ -4634,7 +4646,7 @@ async fn test_attach_eight_disks_to_instance( assert_eq!(disks.len(), 8); // Try to boot an instance that has 8 disks attached - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("nfs")).unwrap(), description: String::from("probably serving data"), @@ -4645,17 +4657,17 @@ async fn test_attach_eight_disks_to_instance( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + boot_disk: Some(instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from("probablydata0".to_string()).unwrap(), }, )), disks: (1..8) .map(|i| { - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from(format!("probablydata{}", i)) .unwrap(), }, @@ -4731,8 +4743,7 @@ async fn test_disk_attach_limit(cptestctx: &ControlPlaneTestContext) { .all_items; assert_eq!(disks.len(), 13); - // Try to boot an instance that has 13 disks attached - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("nfs")).unwrap(), description: String::from("probably serving data"), @@ -4743,17 +4754,17 @@ async fn test_disk_attach_limit(cptestctx: &ControlPlaneTestContext) { user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + boot_disk: Some(instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from("probablydata0".to_string()).unwrap(), }, )), disks: (0..13) .map(|i| { - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from(format!("probablydata{}", i)) .unwrap(), }, @@ -4837,7 +4848,7 @@ async fn test_cannot_attach_faulted_disks(cptestctx: &ControlPlaneTestContext) { } // Try to boot the instance - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("nfs")).unwrap(), description: String::from("probably serving data"), @@ -4848,17 +4859,17 @@ async fn test_cannot_attach_faulted_disks(cptestctx: &ControlPlaneTestContext) { user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + boot_disk: Some(instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from("probablydata0".to_string()).unwrap(), }, )), disks: (1..8) .map(|i| { - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from(format!("probablydata{}", i)) .unwrap(), }, @@ -4931,7 +4942,7 @@ async fn test_disks_detached_when_instance_destroyed( } // Boot the instance - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("probably serving data"), @@ -4942,17 +4953,17 @@ async fn test_disks_detached_when_instance_destroyed( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + boot_disk: Some(instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from("probablydata0".to_string()).unwrap(), }, )), disks: (1..8) .map(|i| { - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from(format!("probablydata{}", i)) .unwrap(), }, @@ -5032,7 +5043,7 @@ async fn test_disks_detached_when_instance_destroyed( } // Ensure that the disks can be attached to another instance - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "nfsv2".parse().unwrap(), description: String::from("probably serving data too!"), @@ -5043,17 +5054,17 @@ async fn test_disks_detached_when_instance_destroyed( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + boot_disk: Some(instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from("probablydata0".to_string()).unwrap(), }, )), disks: (1..8) .map(|i| { - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from(format!("probablydata{}", i)) .unwrap(), }, @@ -5121,7 +5132,7 @@ async fn test_duplicate_disk_attach_requests_ok( assert_eq!(disks[1].state, DiskState::Detached); // Create the instance with a duplicate disks entry - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "nfs".parse().unwrap(), description: String::from("probably serving data"), @@ -5132,16 +5143,16 @@ async fn test_duplicate_disk_attach_requests_ok( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![ - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from(String::from("probablydata")).unwrap(), }, ), - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from(String::from("probablydata")).unwrap(), }, ), @@ -5171,7 +5182,7 @@ async fn test_duplicate_disk_attach_requests_ok( // Create the instance with a disk mentioned both as a data disk and a boot // disk - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "nfs2".parse().unwrap(), description: String::from("probably serving data"), @@ -5182,15 +5193,15 @@ async fn test_duplicate_disk_attach_requests_ok( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + boot_disk: Some(instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from(String::from("alsodata")).unwrap(), }, )), - disks: vec![params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + disks: vec![instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from(String::from("alsodata")).unwrap(), }, )], @@ -5235,7 +5246,7 @@ async fn test_cannot_detach_boot_disk(cptestctx: &ControlPlaneTestContext) { create_disk(&client, PROJECT_NAME, "probablydata0").await; // Create the instance - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("probably serving data"), @@ -5246,10 +5257,10 @@ async fn test_cannot_detach_boot_disk(cptestctx: &ControlPlaneTestContext) { user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + boot_disk: Some(instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from(String::from("probablydata0")).unwrap(), }, )), @@ -5298,7 +5309,7 @@ async fn test_cannot_detach_boot_disk(cptestctx: &ControlPlaneTestContext) { http::Method::POST, &url_instance_detach_disk, ) - .body(Some(¶ms::DiskPath { disk: disks[0].identity.id.into() })) + .body(Some(&path_params::DiskPath { disk: disks[0].identity.id.into() })) .expect_status(Some(http::StatusCode::CONFLICT)); let response = NexusRequest::new(builder) .authn_as(AuthnMode::PrivilegedUser) @@ -5315,7 +5326,7 @@ async fn test_cannot_detach_boot_disk(cptestctx: &ControlPlaneTestContext) { let instance = expect_instance_reconfigure_ok( &client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { boot_disk: Nullable(None), auto_restart_policy: Nullable(None), cpu_platform: Nullable(None), @@ -5333,7 +5344,7 @@ async fn test_cannot_detach_boot_disk(cptestctx: &ControlPlaneTestContext) { http::Method::POST, &url_instance_detach_disk, ) - .body(Some(¶ms::DiskPath { disk: disks[0].identity.id.into() })) + .body(Some(&path_params::DiskPath { disk: disks[0].identity.id.into() })) .expect_status(Some(http::StatusCode::ACCEPTED)); NexusRequest::new(builder) .authn_as(AuthnMode::PrivilegedUser) @@ -5369,7 +5380,7 @@ async fn test_updating_running_instance_boot_disk_is_conflict( let probablydata = Name::try_from(String::from("probablydata")).unwrap(); let alsodata = Name::try_from(String::from("alsodata")).unwrap(); - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from(instance_name)).unwrap(), description: String::from("instance to run and fail to update"), @@ -5380,18 +5391,18 @@ async fn test_updating_running_instance_boot_disk_is_conflict( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![ - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { name: probablydata.clone() }, + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: probablydata.clone() }, ), - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { name: alsodata.clone() }, + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: alsodata.clone() }, ), ], - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { name: probablydata.clone() }, + boot_disk: Some(instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: probablydata.clone() }, )), cpu_platform: None, start: true, @@ -5423,7 +5434,7 @@ async fn test_updating_running_instance_boot_disk_is_conflict( let error = expect_instance_reconfigure_err( &client, &instance_id.into_untyped_uuid(), - params::InstanceUpdate { + instance::InstanceUpdate { boot_disk: Nullable(Some(alsodata.clone().into())), auto_restart_policy: Nullable(None), cpu_platform: Nullable(None), @@ -5441,7 +5452,7 @@ async fn test_updating_running_instance_boot_disk_is_conflict( expect_instance_reconfigure_ok( &client, &instance_id.into_untyped_uuid(), - params::InstanceUpdate { + instance::InstanceUpdate { // Leave the boot disk the same as the one with which the instance // was created. boot_disk: Nullable(Some(probablydata.clone().into())), @@ -5469,7 +5480,7 @@ async fn test_updating_missing_instance_is_not_found( let error = expect_instance_reconfigure_err( &client, &UUID_THAT_DOESNT_EXIST, - params::InstanceUpdate { + instance::InstanceUpdate { boot_disk: Nullable(None), auto_restart_policy: Nullable(None), cpu_platform: Nullable(None), @@ -5489,7 +5500,7 @@ async fn test_updating_missing_instance_is_not_found( async fn expect_instance_reconfigure_ok( external_client: &ClientTestContext, instance_id: &Uuid, - update: params::InstanceUpdate, + update: instance::InstanceUpdate, ) -> Instance { let url_instance_update = format!("/v1/instances/{instance_id}"); @@ -5515,7 +5526,7 @@ async fn expect_instance_reconfigure_ok( async fn expect_instance_reconfigure_err( external_client: &ClientTestContext, instance_id: &Uuid, - update: params::InstanceUpdate, + update: instance::InstanceUpdate, status: http::StatusCode, ) -> HttpErrorResponseBody { let url_instance_update = format!("/v1/instances/{instance_id}"); @@ -5550,7 +5561,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { let initial_ncpus = InstanceCpuCount::try_from(2).unwrap(); let initial_memory = ByteCount::from_gibibytes_u32(4); - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("stuff"), @@ -5561,7 +5572,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], boot_disk: None, cpu_platform: None, @@ -5591,7 +5602,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { let new_ncpus = InstanceCpuCount::try_from(4).unwrap(); let new_memory = ByteCount::from_gibibytes_u32(8); - let base_update = params::InstanceUpdate { + let base_update = instance::InstanceUpdate { auto_restart_policy: Nullable(auto_restart_policy), boot_disk: Nullable(boot_disk_nameorid.clone()), cpu_platform: Nullable(None), @@ -5604,7 +5615,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { let err = expect_instance_reconfigure_err( client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { ncpus: new_ncpus, memory: new_memory, multicast_groups: None, @@ -5626,7 +5637,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { let instance = expect_instance_reconfigure_ok( client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { ncpus: new_ncpus, memory: new_memory, multicast_groups: None, @@ -5641,7 +5652,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { let instance = expect_instance_reconfigure_ok( client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { ncpus: initial_ncpus, memory: new_memory, multicast_groups: None, @@ -5655,7 +5666,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { let instance = expect_instance_reconfigure_ok( client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { ncpus: initial_ncpus, memory: initial_memory, multicast_groups: None, @@ -5673,7 +5684,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { let err = expect_instance_reconfigure_err( client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { ncpus: InstanceCpuCount(MAX_VCPU_PER_INSTANCE + 1), memory: instance.memory, multicast_groups: None, @@ -5694,7 +5705,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { let err = expect_instance_reconfigure_err( client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { ncpus: instance.ncpus, memory: ByteCount::from_mebibytes_u32(0), multicast_groups: None, @@ -5709,7 +5720,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { let err = expect_instance_reconfigure_err( client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { ncpus: instance.ncpus, memory: ByteCount::try_from(MAX_MEMORY_BYTES_PER_INSTANCE - 1) .unwrap(), @@ -5726,7 +5737,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { let err = expect_instance_reconfigure_err( client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { ncpus: instance.ncpus, memory: ByteCount::from_mebibytes_u32( (max_mib + 1024).try_into().unwrap(), @@ -5748,7 +5759,7 @@ async fn test_size_can_be_changed(cptestctx: &ControlPlaneTestContext) { expect_instance_reconfigure_err( client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { ncpus: new_ncpus, memory: new_memory, multicast_groups: None, @@ -5769,7 +5780,7 @@ async fn test_auto_restart_policy_can_be_changed( create_project_and_pool(&client).await; - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("stuff"), @@ -5780,7 +5791,7 @@ async fn test_auto_restart_policy_can_be_changed( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], boot_disk: None, cpu_platform: None, @@ -5811,7 +5822,7 @@ async fn test_auto_restart_policy_can_be_changed( let instance = expect_instance_reconfigure_ok( client, &instance.identity.id, - dbg!(params::InstanceUpdate { + dbg!(instance::InstanceUpdate { auto_restart_policy: Nullable(auto_restart_policy), boot_disk: Nullable(None), cpu_platform: Nullable(None), @@ -5845,7 +5856,7 @@ async fn test_cpu_platform_can_be_changed(cptestctx: &ControlPlaneTestContext) { create_project_and_pool(&client).await; - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("stuff"), @@ -5856,7 +5867,7 @@ async fn test_cpu_platform_can_be_changed(cptestctx: &ControlPlaneTestContext) { user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], boot_disk: None, // Start out with None @@ -5887,7 +5898,7 @@ async fn test_cpu_platform_can_be_changed(cptestctx: &ControlPlaneTestContext) { let instance = expect_instance_reconfigure_ok( client, &instance.identity.id, - dbg!(params::InstanceUpdate { + dbg!(instance::InstanceUpdate { auto_restart_policy: Nullable(None), boot_disk: Nullable(None), cpu_platform: Nullable(cpu_platform), @@ -5936,7 +5947,7 @@ async fn test_boot_disk_can_be_changed(cptestctx: &ControlPlaneTestContext) { assert_eq!(disks[1].state, DiskState::Detached); // Create the instance - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("probably serving data"), @@ -5947,15 +5958,15 @@ async fn test_boot_disk_can_be_changed(cptestctx: &ControlPlaneTestContext) { user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + boot_disk: Some(instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from(String::from("probablydata0")).unwrap(), }, )), - disks: vec![params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + disks: vec![instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: Name::try_from(String::from("probablydata1")).unwrap(), }, )], @@ -5985,7 +5996,7 @@ async fn test_boot_disk_can_be_changed(cptestctx: &ControlPlaneTestContext) { let instance = expect_instance_reconfigure_ok( &client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { boot_disk: Nullable(Some(disks[1].identity.id.into())), auto_restart_policy: Nullable(None), cpu_platform: Nullable(None), @@ -6019,7 +6030,7 @@ async fn test_boot_disk_must_be_attached(cptestctx: &ControlPlaneTestContext) { .all_items; // Create the instance - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("probably serving data"), @@ -6030,7 +6041,7 @@ async fn test_boot_disk_must_be_attached(cptestctx: &ControlPlaneTestContext) { user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -6057,7 +6068,7 @@ async fn test_boot_disk_must_be_attached(cptestctx: &ControlPlaneTestContext) { let error = expect_instance_reconfigure_err( &client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { boot_disk: Nullable(Some(disks[0].identity.id.into())), auto_restart_policy: Nullable(None), cpu_platform: Nullable(None), @@ -6080,7 +6091,7 @@ async fn test_boot_disk_must_be_attached(cptestctx: &ControlPlaneTestContext) { http::Method::POST, &url_instance_detach_disk, ) - .body(Some(¶ms::DiskPath { disk: disks[0].identity.id.into() })) + .body(Some(&path_params::DiskPath { disk: disks[0].identity.id.into() })) .expect_status(Some(http::StatusCode::ACCEPTED)); NexusRequest::new(builder) .authn_as(AuthnMode::PrivilegedUser) @@ -6092,7 +6103,7 @@ async fn test_boot_disk_must_be_attached(cptestctx: &ControlPlaneTestContext) { let instance = expect_instance_reconfigure_ok( &client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { boot_disk: Nullable(Some(disks[0].identity.id.into())), auto_restart_policy: Nullable(None), cpu_platform: Nullable(None), @@ -6116,7 +6127,7 @@ async fn test_instances_memory_rejected_less_than_min_memory_size( // Attempt to create the instance, observe a server error. let instance_name = "just-rainsticks"; - let instance = params::InstanceCreate { + let instance = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: format!("instance {:?}", &instance_name), @@ -6129,7 +6140,7 @@ async fn test_instances_memory_rejected_less_than_min_memory_size( .to_vec(), ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -6172,7 +6183,7 @@ async fn test_instances_memory_not_divisible_by_min_memory_size( // Attempt to create the instance, observe a server error. let instance_name = "just-rainsticks"; - let instance = params::InstanceCreate { + let instance = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: format!("instance {:?}", &instance_name), @@ -6185,7 +6196,7 @@ async fn test_instances_memory_not_divisible_by_min_memory_size( .to_vec(), ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -6227,7 +6238,7 @@ async fn test_instances_memory_greater_than_max_size( // Attempt to create the instance, observe a server error. let instance_name = "just-rainsticks"; - let instance = params::InstanceCreate { + let instance = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: format!("instance {:?}", &instance_name), @@ -6241,7 +6252,7 @@ async fn test_instances_memory_greater_than_max_size( .to_vec(), ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -6272,10 +6283,10 @@ async fn create_anti_affinity_groups( groups: &[&str], ) { for name in groups { - let _: views::AntiAffinityGroup = object_create( + let _: affinity::AntiAffinityGroup = object_create( client, &anti_affinity_groups_url(), - ¶ms::AntiAffinityGroupCreate { + &affinity::AntiAffinityGroupCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: String::from("This is a description"), @@ -6296,7 +6307,7 @@ async fn ensure_anti_affinity_groups_match( let mut expected_groups = expected_groups.to_vec(); expected_groups.sort(); - let groups = objects_list_page_authz::( + let groups = objects_list_page_authz::( client, &format!( "/v1/instances/{instance_name}/anti-affinity-groups?{}&sort_by=name_ascending", @@ -6339,7 +6350,7 @@ async fn test_instance_create_with_anti_affinity_groups( .collect(); // Create an instance belonging to all the groups - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("probably serving data"), @@ -6352,7 +6363,7 @@ async fn test_instance_create_with_anti_affinity_groups( hostname: instance_name.parse().unwrap(), user_data: vec![], network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -6411,7 +6422,7 @@ async fn test_instance_create_with_duplicate_anti_affinity_groups( anti_affinity_groups_param.append(&mut anti_affinity_groups_param.clone()); // Create an instance belonging to all the groups - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("probably serving data"), @@ -6424,7 +6435,7 @@ async fn test_instance_create_with_duplicate_anti_affinity_groups( hostname: instance_name.parse().unwrap(), user_data: vec![], network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -6484,7 +6495,7 @@ async fn test_instance_create_with_anti_affinity_groups_that_do_not_exist( .collect(); // Create an instance belonging to all the groups - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("probably serving data"), @@ -6497,7 +6508,7 @@ async fn test_instance_create_with_anti_affinity_groups_that_do_not_exist( hostname: instance_name.parse().unwrap(), user_data: vec![], network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -6569,7 +6580,7 @@ async fn test_instance_create_with_ssh_keys( } // Create an instance - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("probably serving data"), @@ -6583,7 +6594,7 @@ async fn test_instance_create_with_ssh_keys( hostname: instance_name.parse().unwrap(), user_data: vec![], network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -6621,7 +6632,7 @@ async fn test_instance_create_with_ssh_keys( let instance_name = "ssh-keys-2"; // Create an instance - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("probably serving data"), @@ -6635,7 +6646,7 @@ async fn test_instance_create_with_ssh_keys( hostname: instance_name.parse().unwrap(), user_data: vec![], network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -6672,7 +6683,7 @@ async fn test_instance_create_with_ssh_keys( let instance_name = "ssh-keys-3"; // Create an instance - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: String::from("probably serving data"), @@ -6686,7 +6697,7 @@ async fn test_instance_create_with_ssh_keys( hostname: instance_name.parse().unwrap(), user_data: vec![], network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -6778,7 +6789,7 @@ async fn expect_instance_stop_ok( async fn expect_instance_creation_ok( client: &ClientTestContext, url_instances: &str, - instance_params: ¶ms::InstanceCreate, + instance_params: &instance::InstanceCreate, ) { let builder = RequestBuilder::new(client, http::Method::POST, &url_instances) @@ -6817,7 +6828,7 @@ async fn test_cannot_provision_instance_beyond_cpu_capacity( for config in &configs { let name = Name::try_from(config.0.to_string()).unwrap(); let ncpus = InstanceCpuCount::try_from(i64::from(config.1)).unwrap(); - let params = params::InstanceCreate { + let params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name, description: String::from("probably serving data"), @@ -6828,7 +6839,7 @@ async fn test_cannot_provision_instance_beyond_cpu_capacity( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -6879,7 +6890,7 @@ async fn test_cannot_provision_instance_beyond_cpu_limit( // Try to boot an instance that uses more CPUs than the limit let name1 = Name::try_from(String::from("test")).unwrap(); - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: name1.clone(), description: String::from("probably serving data"), @@ -6890,7 +6901,7 @@ async fn test_cannot_provision_instance_beyond_cpu_limit( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -6937,7 +6948,7 @@ async fn test_cannot_provision_instance_beyond_ram_capacity( let mut instances = Vec::new(); for config in &configs { let name = Name::try_from(config.0.to_string()).unwrap(); - let params = params::InstanceCreate { + let params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name, description: String::from("probably serving data"), @@ -6948,7 +6959,7 @@ async fn test_cannot_provision_instance_beyond_ram_capacity( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -7041,7 +7052,7 @@ async fn test_can_start_instance_with_cpu_platform( create_project_and_pool(client).await; let name1 = Name::try_from(String::from("test")).unwrap(); - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: name1.clone(), description: String::from("probably serving data"), @@ -7052,7 +7063,7 @@ async fn test_can_start_instance_with_cpu_platform( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -7096,7 +7107,7 @@ async fn test_can_start_instance_with_cpu_platform( let instance = expect_instance_reconfigure_ok( &client, &instance.identity.id, - params::InstanceUpdate { + instance::InstanceUpdate { boot_disk: Nullable(None), auto_restart_policy: Nullable(None), cpu_platform: Nullable(Some(InstanceCpuPlatform::AmdTurin)), @@ -7154,7 +7165,7 @@ async fn test_cannot_start_instance_with_unsatisfiable_cpu_platform( create_project_and_pool(client).await; let name1 = Name::try_from(String::from("test")).unwrap(); - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: name1.clone(), description: String::from("probably serving data"), @@ -7165,7 +7176,7 @@ async fn test_cannot_start_instance_with_unsatisfiable_cpu_platform( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, @@ -7292,7 +7303,7 @@ async fn test_instance_serial(cptestctx: &ControlPlaneTestContext) { let expected = "This is simulated serial console output for ".as_bytes(); let mut actual = Vec::new(); while actual.len() < expected.len() { - let serial_data: params::InstanceSerialConsoleData = + let serial_data: instance::InstanceSerialConsoleData = NexusRequest::object_get( client, &format!("{}&from_start={}", instance_serial_url, actual.len()), @@ -7405,10 +7416,10 @@ async fn test_instance_ephemeral_ip_from_correct_pool( assert_ip_pool_utilization(client, "pool2", 1, capacity2).await; // make pool2 default and create instance with default pool. check that it now it comes from pool2 - let _: views::IpPoolSiloLink = object_put( + let _: ip_pool::IpPoolSiloLink = object_put( client, &format!("/v1/system/ip-pools/pool2/silos/{}", DEFAULT_SILO.id()), - ¶ms::IpPoolSiloUpdate { is_default: true }, + &ip_pool::IpPoolSiloUpdate { is_default: true }, ) .await; @@ -7454,7 +7465,7 @@ async fn test_instance_ephemeral_ip_from_correct_pool( // create instance with pool1, expecting allocation to fail let instance_name = "pool1-inst-fail"; let url = format!("/v1/instances?project={}", PROJECT_NAME); - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: format!("instance {:?}", instance_name), @@ -7464,9 +7475,9 @@ async fn test_instance_ephemeral_ip_from_correct_pool( hostname: "the-host".parse().unwrap(), user_data: vec![], network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, - external_ips: vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + external_ips: vec![ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Explicit { pool: "pool1".parse::().unwrap().into(), }, }], @@ -7529,7 +7540,7 @@ async fn test_instance_ephemeral_ip_from_orphan_pool( create_ip_pool(&client, "orphan-pool", Some(orphan_pool_range)).await; let instance_name = "orphan-pool-inst"; - let body = params::InstanceCreate { + let body = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: format!("instance {:?}", instance_name), @@ -7539,9 +7550,9 @@ async fn test_instance_ephemeral_ip_from_orphan_pool( hostname: "the-host".parse().unwrap(), user_data: vec![], network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, - external_ips: vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + external_ips: vec![ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Explicit { pool: "orphan-pool".parse::().unwrap().into(), }, }], @@ -7568,11 +7579,11 @@ async fn test_instance_ephemeral_ip_from_orphan_pool( // associate the pool with a different silo and we should get the same // error on instance create - let params = params::IpPoolLinkSilo { + let params = ip_pool::IpPoolLinkSilo { silo: NameOrId::Name(cptestctx.silo_name.clone()), is_default: false, }; - let _: views::IpPoolSiloLink = + let _: ip_pool::IpPoolSiloLink = object_create(client, "/v1/system/ip-pools/orphan-pool/silos", ¶ms) .await; @@ -7598,7 +7609,7 @@ async fn test_instance_ephemeral_ip_no_default_pool_error( // important: no pool create, so there is no pool - let body = params::InstanceCreate { + let body = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "no-default-pool".parse().unwrap(), description: "".to_string(), @@ -7608,9 +7619,9 @@ async fn test_instance_ephemeral_ip_no_default_pool_error( hostname: "the-host".parse().unwrap(), user_data: vec![], network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, - external_ips: vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Auto { ip_version: None }, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + external_ips: vec![ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Auto { ip_version: None }, }], ssh_public_keys: None, disks: vec![], @@ -7632,7 +7643,7 @@ async fn test_instance_ephemeral_ip_no_default_pool_error( // Specifying a nonexistent pool also fails with 404, but we need to // avoid SNAT allocation (which uses the default pool) to actually // exercise the explicit pool lookup. Use network_interfaces: None. - let body = params::InstanceCreate { + let body = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "nonexistent-pool-inst".parse().unwrap(), description: "".to_string(), @@ -7641,9 +7652,9 @@ async fn test_instance_ephemeral_ip_no_default_pool_error( memory: ByteCount::from_gibibytes_u32(1), hostname: "the-host".parse().unwrap(), user_data: vec![], - network_interfaces: params::InstanceNetworkInterfaceAttachment::None, - external_ips: vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { + network_interfaces: instance::InstanceNetworkInterfaceAttachment::None, + external_ips: vec![ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Explicit { pool: "nonexistent-pool".parse::().unwrap().into(), }, }], @@ -7687,10 +7698,10 @@ async fn test_instance_attach_several_external_ips( assert_ip_pool_utilization(client, "default", 0, capacity).await; // Create several floating IPs for the instance, totalling 8 IPs. - let mut external_ip_create = vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Auto { ip_version: None }, - }]; let mut fips = vec![]; + let mut external_ip_create = vec![ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Auto { ip_version: None }, + }]; for i in 1..8 { let name = format!("fip-{i}"); fips.push( @@ -7698,11 +7709,11 @@ async fn test_instance_attach_several_external_ips( &client, &name, PROJECT_NAME, - params::AddressAllocator::default(), + floating_ip::AddressAllocator::default(), ) .await, ); - external_ip_create.push(params::ExternalIpCreate::Floating { + external_ip_create.push(ExternalIpCreate::Floating { floating_ip: name.parse::().unwrap().into(), }); } @@ -7713,7 +7724,7 @@ async fn test_instance_attach_several_external_ips( &client, PROJECT_NAME, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, + &instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, vec![], external_ip_create, true, @@ -7771,13 +7782,10 @@ async fn test_instance_rejects_three_ephemeral_ips( let _ = create_project(&client, PROJECT_NAME).await; // don't need any IP pools because request fails at parse time - - let ephemeral_create = params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { - pool: "default".parse::().unwrap().into(), - }, + let ephemeral_create = ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Auto { ip_version: None }, }; - let create_params = params::InstanceCreate { + let create_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "default-pool-inst".parse().unwrap(), description: "instance default-pool-inst".into(), @@ -7790,7 +7798,7 @@ async fn test_instance_rejects_three_ephemeral_ips( .to_vec(), ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![ ephemeral_create.clone(), ephemeral_create.clone(), @@ -7827,14 +7835,14 @@ async fn test_instance_rejects_two_ephemeral_auto_without_version( let _ = create_project(&client, PROJECT_NAME).await; let external_ips = vec![ - params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Auto { ip_version: None }, + ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Auto { ip_version: None }, }, - params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Auto { ip_version: None }, + ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Auto { ip_version: None }, }, ]; - let create_params = params::InstanceCreate { + let create_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "two-auto-ephemeral".parse().unwrap(), description: "instance two-auto-ephemeral".into(), @@ -7845,7 +7853,7 @@ async fn test_instance_rejects_two_ephemeral_auto_without_version( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips, disks: vec![], boot_disk: None, @@ -7882,16 +7890,16 @@ async fn test_instance_rejects_two_ephemeral_auto_none_with_explicit( // One explicit pool, one auto without version: rejected because the auto // selector doesn't specify ip_version. let external_ips = vec![ - params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Auto { ip_version: None }, + ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Auto { ip_version: None }, }, - params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { + ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Explicit { pool: "default-v4".parse::().unwrap().into(), }, }, ]; - let create_params = params::InstanceCreate { + let create_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "explicit-plus-auto-ephemeral".parse().unwrap(), description: "instance explicit-plus-auto-ephemeral".into(), @@ -7902,7 +7910,7 @@ async fn test_instance_rejects_two_ephemeral_auto_none_with_explicit( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultDualStack, + instance::InstanceNetworkInterfaceAttachment::DefaultDualStack, external_ips, disks: vec![], boot_disk: None, @@ -7937,18 +7945,18 @@ async fn test_instance_rejects_two_ephemeral_same_pool( create_default_ip_pools(client).await; let external_ips = vec![ - params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { + ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Explicit { pool: "default-v4".parse::().unwrap().into(), }, }, - params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { + ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Explicit { pool: "default-v4".parse::().unwrap().into(), }, }, ]; - let create_params = params::InstanceCreate { + let create_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "same-pool-ephemeral".parse().unwrap(), description: "instance same-pool-ephemeral".into(), @@ -7959,7 +7967,7 @@ async fn test_instance_rejects_two_ephemeral_same_pool( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips, disks: vec![], boot_disk: None, @@ -7992,14 +8000,14 @@ async fn create_instance_with_pool( client, PROJECT_NAME, instance_name, - ¶ms::InstanceNetworkInterfaceAttachment::DefaultIpv4, + &instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, vec![], - vec![params::ExternalIpCreate::Ephemeral { + vec![ExternalIpCreate::Ephemeral { pool_selector: match pool_name { - Some(name) => params::PoolSelector::Explicit { + Some(name) => PoolSelector::Explicit { pool: name.parse::().unwrap().into(), }, - None => params::PoolSelector::Auto { ip_version: None }, + None => PoolSelector::Auto { ip_version: None }, }, }], true, @@ -8032,7 +8040,7 @@ pub async fn fetch_instance_external_ips( client: &ClientTestContext, instance_name: &str, project_name: &str, -) -> Vec { +) -> Vec { let ips_url = format!( "/v1/instances/{instance_name}/external-ips?project={project_name}", ); @@ -8041,7 +8049,7 @@ pub async fn fetch_instance_external_ips( .execute() .await .expect("Failed to fetch external IPs") - .parsed_body::>() + .parsed_body::>() .expect("Failed to parse external IPs"); ips.items } @@ -8049,7 +8057,7 @@ pub async fn fetch_instance_external_ips( async fn fetch_instance_ephemeral_ip( client: &ClientTestContext, instance_name: &str, -) -> views::ExternalIp { +) -> ExternalIp { fetch_instance_external_ips(client, instance_name, PROJECT_NAME) .await .into_iter() @@ -8092,7 +8100,7 @@ async fn test_instance_create_in_silo(cptestctx: &ControlPlaneTestContext) { NexusRequest::objects_post( client, "/v1/projects", - ¶ms::ProjectCreate { + &project::ProjectCreate { identity: IdentityMetadataCreateParams { name: PROJECT_NAME.parse().unwrap(), description: String::new(), @@ -8103,13 +8111,13 @@ async fn test_instance_create_in_silo(cptestctx: &ControlPlaneTestContext) { .execute() .await .expect("failed to create Project") - .parsed_body::() + .parsed_body::() .expect("failed to parse new Project"); // Create an instance using the authorization granted to the collaborator // Silo User. let instance_name = "collaborate-with-me"; - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from(instance_name)).unwrap(), description: String::from("instance to test creation in a silo"), @@ -8120,12 +8128,8 @@ async fn test_instance_create_in_silo(cptestctx: &ControlPlaneTestContext) { user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, - external_ips: vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Explicit { - pool: "default".parse::().unwrap().into(), - }, - }], + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + external_ips: vec![], disks: vec![], boot_disk: None, cpu_platform: None, @@ -8251,7 +8255,7 @@ async fn test_instance_create_with_cross_project_subnet( .await; // Get the default silo - let silo: views::Silo = NexusRequest::object_get( + let silo: silo::Silo = NexusRequest::object_get( client, &format!("/v1/system/silos/{}", DEFAULT_SILO.name()), ) @@ -8281,7 +8285,7 @@ async fn test_instance_create_with_cross_project_subnet( // Test: Limited collaborator CANNOT create an instance in project A // with a NIC that references a subnet from project B (where they have no access) - let if0_params = params::InstanceNetworkInterfaceCreate { + let if0_params = instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("cross-project-nic")).unwrap(), description: String::from( @@ -8293,7 +8297,7 @@ async fn test_instance_create_with_cross_project_subnet( ip_config: PrivateIpStackCreate::auto_ipv4(), }; - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("cross-project-instance")) .unwrap(), @@ -8306,9 +8310,10 @@ async fn test_instance_create_with_cross_project_subnet( hostname: "inst-cross".parse().unwrap(), user_data: vec![], ssh_public_keys: None, - network_interfaces: params::InstanceNetworkInterfaceAttachment::Create( - vec![if0_params], - ), + network_interfaces: + instance::InstanceNetworkInterfaceAttachment::Create(vec![ + if0_params, + ]), external_ips: vec![], multicast_groups: vec![], disks: vec![], @@ -8383,7 +8388,7 @@ async fn test_silo_limited_collaborator_cross_project_subnet( .await; // Get the default silo - let silo: views::Silo = NexusRequest::object_get( + let silo: silo::Silo = NexusRequest::object_get( client, &format!("/v1/system/silos/{}", DEFAULT_SILO.name()), ) @@ -8413,7 +8418,7 @@ async fn test_silo_limited_collaborator_cross_project_subnet( // Test 1: Silo limited collaborator CAN create an instance in project A // with a NIC using project A's own subnet (success case) - let if_same_project = params::InstanceNetworkInterfaceCreate { + let if_same_project = instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("nic-a")).unwrap(), description: String::from("NIC using same project's subnet"), @@ -8423,7 +8428,7 @@ async fn test_silo_limited_collaborator_cross_project_subnet( ip_config: PrivateIpStackCreate::auto_ipv4(), }; - let instance_same_project = params::InstanceCreate { + let instance_same_project = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("instance-same-project")) .unwrap(), @@ -8434,9 +8439,10 @@ async fn test_silo_limited_collaborator_cross_project_subnet( hostname: "inst-same".parse().unwrap(), user_data: vec![], ssh_public_keys: None, - network_interfaces: params::InstanceNetworkInterfaceAttachment::Create( - vec![if_same_project], - ), + network_interfaces: + instance::InstanceNetworkInterfaceAttachment::Create(vec![ + if_same_project, + ]), external_ips: vec![], multicast_groups: vec![], disks: vec![], @@ -8475,7 +8481,7 @@ async fn test_silo_limited_collaborator_cross_project_subnet( // Test 2: Silo limited collaborator CANNOT create an instance in project A // with a NIC that references a subnet from project B (failure case) - let if_cross_project = params::InstanceNetworkInterfaceCreate { + let if_cross_project = instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("cross-project-nic")).unwrap(), description: String::from( @@ -8487,7 +8493,7 @@ async fn test_silo_limited_collaborator_cross_project_subnet( ip_config: PrivateIpStackCreate::auto_ipv4(), }; - let instance_cross_project = params::InstanceCreate { + let instance_cross_project = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: Name::try_from(String::from("instance-cross-project")) .unwrap(), @@ -8500,9 +8506,10 @@ async fn test_silo_limited_collaborator_cross_project_subnet( hostname: "inst-cross".parse().unwrap(), user_data: vec![], ssh_public_keys: None, - network_interfaces: params::InstanceNetworkInterfaceAttachment::Create( - vec![if_cross_project], - ), + network_interfaces: + instance::InstanceNetworkInterfaceAttachment::Create(vec![ + if_cross_project, + ]), external_ips: vec![], multicast_groups: vec![], disks: vec![], @@ -9209,14 +9216,14 @@ async fn test_instance_with_max_disks(cptestctx: &ControlPlaneTestContext) { let disks_url = get_disks_url(); for n in 0..MAX_DISKS_PER_INSTANCE { - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: format!("disk{n:02}").parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: ByteCount::from_gibibytes_u32(1), @@ -9233,7 +9240,7 @@ async fn test_instance_with_max_disks(cptestctx: &ControlPlaneTestContext) { .unwrap(); } - let create_params = params::InstanceCreate { + let create_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "lots-of-disks".parse().unwrap(), description: "alan hates descriptions".into(), @@ -9245,12 +9252,12 @@ async fn test_instance_with_max_disks(cptestctx: &ControlPlaneTestContext) { b"#cloud-config\nsystem_info:\n default_user:\n name: oxide" .to_vec(), ssh_public_keys: None, - network_interfaces: params::InstanceNetworkInterfaceAttachment::None, + network_interfaces: instance::InstanceNetworkInterfaceAttachment::None, external_ips: vec![], disks: (0..MAX_DISKS_PER_INSTANCE) .map(|n| { - params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { + instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: format!("disk{n:02}").parse().unwrap(), }, ) @@ -9304,48 +9311,46 @@ async fn can_create_instance_with_multiple_nics_and_ephemeral_ip( None, ) .await; - let nics = params::InstanceNetworkInterfaceAttachment::Create(vec![ - params::InstanceNetworkInterfaceCreate { + let nics = instance::InstanceNetworkInterfaceAttachment::Create(vec![ + instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: "net0".parse().unwrap(), description: String::new(), }, vpc_name: "default".parse().unwrap(), subnet_name: "default".parse().unwrap(), - ip_config: params::PrivateIpStackCreate::DualStack { - v4: params::PrivateIpv4StackCreate { - ip: params::Ipv4Assignment::Auto, + ip_config: PrivateIpStackCreate::DualStack { + v4: PrivateIpv4StackCreate { + ip: instance::Ipv4Assignment::Auto, transit_ips: vec![], }, - v6: params::PrivateIpv6StackCreate { - ip: params::Ipv6Assignment::Auto, + v6: PrivateIpv6StackCreate { + ip: instance::Ipv6Assignment::Auto, transit_ips: vec![], }, }, }, - params::InstanceNetworkInterfaceCreate { + instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: "net1".parse().unwrap(), description: String::new(), }, vpc_name: "default".parse().unwrap(), subnet_name: second_vpc_subnet_name.parse().unwrap(), - ip_config: params::PrivateIpStackCreate::DualStack { - v4: params::PrivateIpv4StackCreate { - ip: params::Ipv4Assignment::Auto, + ip_config: PrivateIpStackCreate::DualStack { + v4: PrivateIpv4StackCreate { + ip: instance::Ipv4Assignment::Auto, transit_ips: vec![], }, - v6: params::PrivateIpv6StackCreate { - ip: params::Ipv6Assignment::Auto, + v6: PrivateIpv6StackCreate { + ip: instance::Ipv6Assignment::Auto, transit_ips: vec![], }, }, }, ]); - let external_ips = vec![params::ExternalIpCreate::Ephemeral { - pool_selector: params::PoolSelector::Auto { - ip_version: Some(IpVersion::V4), - }, + let external_ips = vec![ExternalIpCreate::Ephemeral { + pool_selector: PoolSelector::Auto { ip_version: Some(IpVersion::V4) }, }]; let _ = create_instance_with( client, diff --git a/nexus/tests/integration_tests/internet_gateway.rs b/nexus/tests/integration_tests/internet_gateway.rs index 6e72098cad4..23819ea888f 100644 --- a/nexus/tests/integration_tests/internet_gateway.rs +++ b/nexus/tests/integration_tests/internet_gateway.rs @@ -17,14 +17,18 @@ use nexus_test_utils::{ }, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::{ - params::{ - self, ExternalIpCreate, InstanceNetworkInterfaceAttachment, - InstanceNetworkInterfaceCreate, PrivateIpStackCreate, - }, - shared::SiloRole, - views::{InternetGateway, InternetGatewayIpAddress, InternetGatewayIpPool}, +use nexus_types::external_api::floating_ip; +use nexus_types::external_api::instance::{ + ExternalIpCreate, InstanceNetworkInterfaceAttachment, + InstanceNetworkInterfaceCreate, PrivateIpStackCreate, +}; +use nexus_types::external_api::internet_gateway::{ + InternetGateway, InternetGatewayIpAddress, InternetGatewayIpPool, + InternetGatewayIpPoolCreate, }; +use nexus_types::external_api::ip_pool; +use nexus_types::external_api::policy::SiloRole; +use nexus_types::external_api::silo::Silo; use nexus_types::identity::Resource; use omicron_common::{ address::{IpRange, Ipv4Range}, @@ -265,7 +269,7 @@ async fn test_igw_ip_pool_attach_silo_user(ctx: &ControlPlaneTestContext) { // Create a non-admin user let silo_url = format!("/v1/system/silos/{}", DEFAULT_SILO.name()); - let silo: nexus_types::external_api::views::Silo = + let silo: Silo = nexus_test_utils::resource_helpers::object_get(c, &silo_url).await; let user = create_local_user( @@ -300,7 +304,7 @@ async fn test_igw_ip_pool_attach_silo_user(ctx: &ControlPlaneTestContext) { "/v1/internet-gateway-ip-pools?project={}&vpc={}&gateway={}", PROJECT_NAME, VPC_NAME, IGW_NAME ); - let params = params::InternetGatewayIpPoolCreate { + let params = InternetGatewayIpPoolCreate { identity: IdentityMetadataCreateParams { name: IP_POOL_ATTACHMENT_NAME.parse().unwrap(), description: "Test attachment".to_string(), @@ -360,8 +364,8 @@ async fn test_setup(c: &ClientTestContext) { c, FLOATING_IP_NAME, PROJECT_NAME, - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name(IP_POOL_NAME.parse().unwrap()), }, }, diff --git a/nexus/tests/integration_tests/ip_pools.rs b/nexus/tests/integration_tests/ip_pools.rs index 66e13d992e6..b781ab07c22 100644 --- a/nexus/tests/integration_tests/ip_pools.rs +++ b/nexus/tests/integration_tests/ip_pools.rs @@ -38,22 +38,23 @@ use nexus_test_utils::resource_helpers::object_put_error; use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils::resource_helpers::test_params; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::params::IpPoolCreate; -use nexus_types::external_api::params::IpPoolLinkSilo; -use nexus_types::external_api::params::IpPoolSiloUpdate; -use nexus_types::external_api::params::IpPoolUpdate; -use nexus_types::external_api::shared::IpPoolType; -use nexus_types::external_api::shared::IpRange; -use nexus_types::external_api::shared::Ipv4Range; -use nexus_types::external_api::shared::SiloIdentityMode; -use nexus_types::external_api::shared::SiloRole; -use nexus_types::external_api::views::IpPool; -use nexus_types::external_api::views::IpPoolRange; -use nexus_types::external_api::views::IpPoolSiloLink; -use nexus_types::external_api::views::IpVersion; -use nexus_types::external_api::views::Silo; -use nexus_types::external_api::views::SiloIpPool; +use nexus_types::external_api::floating_ip; +use nexus_types::external_api::ip_pool; +use nexus_types::external_api::ip_pool::IpPool; +use nexus_types::external_api::ip_pool::IpPoolCreate; +use nexus_types::external_api::ip_pool::IpPoolLinkSilo; +use nexus_types::external_api::ip_pool::IpPoolRange; +use nexus_types::external_api::ip_pool::IpPoolSiloLink; +use nexus_types::external_api::ip_pool::IpPoolSiloUpdate; +use nexus_types::external_api::ip_pool::IpPoolType; +use nexus_types::external_api::ip_pool::IpPoolUpdate; +use nexus_types::external_api::ip_pool::IpRange; +use nexus_types::external_api::ip_pool::IpVersion; +use nexus_types::external_api::ip_pool::Ipv4Range; +use nexus_types::external_api::ip_pool::SiloIpPool; +use nexus_types::external_api::policy::SiloRole; +use nexus_types::external_api::silo::Silo; +use nexus_types::external_api::silo::SiloIdentityMode; use nexus_types::identity::Resource; use nexus_types::silo::INTERNAL_SILO_ID; use omicron_common::address::Ipv6Range; @@ -126,7 +127,7 @@ async fn test_ip_pool_basic_crud(cptestctx: &ControlPlaneTestContext) { let error = object_create_error( client, ip_pools_url, - ¶ms::IpPoolCreate::new( + &ip_pool::IpPoolCreate::new( IdentityMetadataCreateParams { name: pool_name.parse().unwrap(), description: String::new(), @@ -378,7 +379,7 @@ async fn test_ip_pool_service_no_cud(cptestctx: &ControlPlaneTestContext) { assert_eq!(error.message, not_found_id); // Update not allowed - let put_body = params::IpPoolUpdate { + let put_body = ip_pool::IpPoolUpdate { identity: IdentityMetadataUpdateParams { name: Some("test".parse().unwrap()), description: Some("test".to_string()), @@ -432,7 +433,7 @@ async fn test_ip_pool_service_no_cud(cptestctx: &ControlPlaneTestContext) { assert_eq!(error.message, not_found_name); // linking not allowed by name or ID - let body = params::IpPoolLinkSilo { + let body = ip_pool::IpPoolLinkSilo { silo: NameOrId::Name(cptestctx.silo_name.clone()), is_default: false, }; @@ -473,7 +474,7 @@ async fn test_ip_pool_silo_link(cptestctx: &ControlPlaneTestContext) { // expect 404 on association if the specified silo doesn't exist let nonexistent_silo_id = Uuid::new_v4(); - let params = params::IpPoolLinkSilo { + let params = ip_pool::IpPoolLinkSilo { silo: NameOrId::Id(nonexistent_silo_id), is_default: false, }; @@ -495,7 +496,7 @@ async fn test_ip_pool_silo_link(cptestctx: &ControlPlaneTestContext) { assert_eq!(error.message, not_found); // associate by name with silo that exists - let params = params::IpPoolLinkSilo { + let params = ip_pool::IpPoolLinkSilo { silo: NameOrId::Name(silo.name().clone()), is_default: false, }; @@ -531,7 +532,7 @@ async fn test_ip_pool_silo_link(cptestctx: &ControlPlaneTestContext) { assert_eq!(silo_pools[0].is_default, false); // associate same silo to other pool by ID instead of name - let link_params = params::IpPoolLinkSilo { + let link_params = ip_pool::IpPoolLinkSilo { silo: NameOrId::Id(silo_id), is_default: true, }; @@ -658,8 +659,8 @@ async fn cannot_unlink_ip_pool_with_outstanding_floating_ips( client, "fip", proj.name().as_str(), - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: NameOrId::Name(POOL_NAME.parse().unwrap()), }, }, @@ -743,7 +744,7 @@ async fn test_ip_pool_update_default(cptestctx: &ControlPlaneTestContext) { ); // associate both pools with the test silo - let params = params::IpPoolLinkSilo { + let params = ip_pool::IpPoolLinkSilo { silo: NameOrId::Name(silo.name().clone()), is_default: false, }; diff --git a/nexus/tests/integration_tests/local_storage.rs b/nexus/tests/integration_tests/local_storage.rs index 35fcbc30329..1b9cadb54e0 100644 --- a/nexus/tests/integration_tests/local_storage.rs +++ b/nexus/tests/integration_tests/local_storage.rs @@ -13,7 +13,10 @@ use nexus_test_utils::http_testing::RequestBuilder; use nexus_test_utils::resource_helpers::create_default_ip_pools; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::{params, views}; +use nexus_types::external_api::{ + disk::{DiskBackend, DiskCreate}, + project::Project, +}; use omicron_common::api::external; use omicron_nexus::app::MAX_DISK_SIZE_BYTES; use omicron_nexus::app::MIN_DISK_SIZE_BYTES; @@ -35,9 +38,7 @@ fn get_disks_url() -> String { format!("/v1/disks?{}", get_project_selector()) } -pub async fn create_project_and_pool( - client: &ClientTestContext, -) -> views::Project { +pub async fn create_project_and_pool(client: &ClientTestContext) -> Project { create_default_ip_pools(client).await; create_project(client, PROJECT_NAME).await } @@ -60,7 +61,7 @@ async fn test_reject_creating_local_storage_disk( // local storage disks have a block size of 4096) let error = NexusRequest::new( RequestBuilder::new(client, Method::POST, &disks_url) - .body(Some(¶ms::DiskCreate { + .body(Some(&DiskCreate { identity: external::IdentityMetadataCreateParams { name: "bad-disk".parse().unwrap(), description: String::from("bad disk"), @@ -71,7 +72,7 @@ async fn test_reject_creating_local_storage_disk( ) .unwrap(), - disk_backend: params::DiskBackend::Local {}, + disk_backend: DiskBackend::Local {}, })) .expect_status(Some(StatusCode::BAD_REQUEST)), ) @@ -91,7 +92,7 @@ async fn test_reject_creating_local_storage_disk( // the size let error = NexusRequest::new( RequestBuilder::new(client, Method::POST, &disks_url) - .body(Some(¶ms::DiskCreate { + .body(Some(&DiskCreate { identity: external::IdentityMetadataCreateParams { name: "bad-disk".parse().unwrap(), description: String::from("bad disk"), @@ -102,7 +103,7 @@ async fn test_reject_creating_local_storage_disk( ) .unwrap(), - disk_backend: params::DiskBackend::Local {}, + disk_backend: DiskBackend::Local {}, })) .expect_status(Some(StatusCode::BAD_REQUEST)), ) @@ -152,7 +153,7 @@ async fn test_create_large_local_storage_disk( NexusRequest::new( RequestBuilder::new(client, Method::POST, &disks_url) - .body(Some(¶ms::DiskCreate { + .body(Some(&DiskCreate { identity: external::IdentityMetadataCreateParams { name: "chonk-disk".parse().unwrap(), description: String::from("chonk"), @@ -160,7 +161,7 @@ async fn test_create_large_local_storage_disk( size: large_disk_size, - disk_backend: params::DiskBackend::Local {}, + disk_backend: DiskBackend::Local {}, })) .expect_status(Some(StatusCode::CREATED)), ) diff --git a/nexus/tests/integration_tests/metrics.rs b/nexus/tests/integration_tests/metrics.rs index 639afc39b33..bee654097d3 100644 --- a/nexus/tests/integration_tests/metrics.rs +++ b/nexus/tests/integration_tests/metrics.rs @@ -22,8 +22,9 @@ use nexus_test_utils::resource_helpers::{ }; use nexus_test_utils::wait_for_producer; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::shared::ProjectRole; -use nexus_types::external_api::views; +use nexus_types::external_api::oxql; +use nexus_types::external_api::policy::ProjectRole; +use nexus_types::external_api::timeseries; use nexus_types::silo::DEFAULT_SILO_ID; use omicron_uuid_kinds::{GenericUuid, InstanceUuid}; use oximeter::TimeseriesSchema; @@ -271,7 +272,7 @@ async fn test_instance_watcher_metrics( #[track_caller] fn count_state( - table: &views::OxqlTable, + table: &oxql::OxqlTable, instance_id: InstanceUuid, state: &'static str, ) -> Result { @@ -580,7 +581,7 @@ async fn test_project_timeseries_query( // expect error when querying a metric that has no project_id on it let q6 = "get integration_target:integration_metric"; let url = "/v1/timeseries/query?project=project1"; - let body = nexus_types::external_api::params::TimeseriesQuery { + let body = timeseries::TimeseriesQuery { query: q6.to_string(), include_summaries: false, }; @@ -601,7 +602,7 @@ async fn test_project_timeseries_query( // nonexistent project let url = "/v1/timeseries/query?project=nonexistent"; - let body = nexus_types::external_api::params::TimeseriesQuery { + let body = timeseries::TimeseriesQuery { query: q6.to_string(), include_summaries: false, }; @@ -611,7 +612,7 @@ async fn test_project_timeseries_query( // unprivileged user gets 404 on project that exists, but which they can't read let url = "/v1/timeseries/query?project=project1"; - let body = nexus_types::external_api::params::TimeseriesQuery { + let body = timeseries::TimeseriesQuery { query: q1.to_string(), include_summaries: false, }; @@ -644,14 +645,14 @@ async fn test_project_timeseries_query( .expect_status(Some(StatusCode::OK)); let result = NexusRequest::new(request) .authn_as(AuthnMode::UnprivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; assert_eq!(result.tables.len(), 1); assert_eq!(result.tables[0].timeseries.len(), 2); // two instances assert!(result.query_summaries.is_none()); // Request metrics again, this time with query summaries. - let body = nexus_types::external_api::params::TimeseriesQuery { + let body = timeseries::TimeseriesQuery { query: q1.to_string(), include_summaries: true, }; @@ -660,7 +661,7 @@ async fn test_project_timeseries_query( .expect_status(Some(StatusCode::OK)); let result = NexusRequest::new(request) .authn_as(AuthnMode::UnprivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; assert_eq!(result.tables.len(), 1); assert_eq!(result.tables[0].timeseries.len(), 2); // two instances diff --git a/nexus/tests/integration_tests/metrics_querier.rs b/nexus/tests/integration_tests/metrics_querier.rs index 704673eab32..20bfc78e86d 100644 --- a/nexus/tests/integration_tests/metrics_querier.rs +++ b/nexus/tests/integration_tests/metrics_querier.rs @@ -12,8 +12,8 @@ use nexus_test_utils::http_testing::AuthnMode; use nexus_test_utils::http_testing::NexusRequest; use nexus_test_utils::http_testing::RequestBuilder; use nexus_test_utils::resource_helpers::objects_list_page_authz; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::oxql; +use nexus_types::external_api::timeseries; use omicron_test_utils::dev::poll; use omicron_test_utils::dev::poll::CondCheckError; use omicron_test_utils::dev::poll::wait_for_condition; @@ -93,7 +93,7 @@ impl<'a, N> MetricsQuerier<'a, N> { cond: F, ) -> T where - F: Fn(Vec) -> Result, + F: Fn(Vec) -> Result, { self.timeseries_query_until("/v1/system/timeseries/query", query, cond) .await @@ -108,7 +108,7 @@ impl<'a, N> MetricsQuerier<'a, N> { cond: F, ) -> T where - F: Fn(Vec) -> Result, + F: Fn(Vec) -> Result, { self.timeseries_query_until( &format!("/v1/timeseries/query?project={project}"), @@ -128,7 +128,7 @@ impl<'a, N> MetricsQuerier<'a, N> { &self, project: &str, query: &str, - ) -> Vec { + ) -> Vec { self.project_timeseries_query_until(project, query, |tables| Ok(tables)) .await } @@ -270,7 +270,7 @@ impl<'a, N> MetricsQuerier<'a, N> { cond: F, ) -> T where - F: Fn(Vec) -> Result, + F: Fn(Vec) -> Result, { let result = wait_for_condition( || async { @@ -339,7 +339,8 @@ impl<'a, N> MetricsQuerier<'a, N> { query: String, ) -> TimeseriesQueryResult { // Issue the query. - let body = params::TimeseriesQuery { query, include_summaries: false }; + let body = + timeseries::TimeseriesQuery { query, include_summaries: false }; let query = &body.query; let rsp = NexusRequest::new( RequestBuilder::new( @@ -375,7 +376,7 @@ impl<'a, N> MetricsQuerier<'a, N> { // Try to parse the query as usual, which will fail on other kinds of // errors. TimeseriesQueryResult::Ok( - rsp.parsed_body::() + rsp.parsed_body::() .unwrap_or_else(|e| { panic!( "could not parse timeseries query response: {e:?}\n\ @@ -389,5 +390,5 @@ impl<'a, N> MetricsQuerier<'a, N> { enum TimeseriesQueryResult { TimeseriesNotFound, - Ok(Vec), + Ok(Vec), } diff --git a/nexus/tests/integration_tests/multicast/api.rs b/nexus/tests/integration_tests/multicast/api.rs index 3de42223bf6..1e96b7bbf18 100644 --- a/nexus/tests/integration_tests/multicast/api.rs +++ b/nexus/tests/integration_tests/multicast/api.rs @@ -33,11 +33,13 @@ use nexus_test_utils::resource_helpers::{ object_create_error, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::{ - InstanceCreate, InstanceMulticastGroupJoin, - InstanceNetworkInterfaceAttachment, +use nexus_types::external_api::instance::{ + InstanceCreate, InstanceNetworkInterfaceAttachment, +}; +use nexus_types::external_api::multicast::{ + InstanceMulticastGroupJoin, MulticastGroup, MulticastGroupIdentifier, + MulticastGroupJoinSpec, MulticastGroupMember, }; -use nexus_types::external_api::views::MulticastGroupMember; use omicron_common::address::is_ssm_address; use omicron_common::api::external::{ ByteCount, IdentityMetadataCreateParams, Instance, InstanceCpuCount, diff --git a/nexus/tests/integration_tests/multicast/authorization.rs b/nexus/tests/integration_tests/multicast/authorization.rs index b2e6c0a25aa..88550abab40 100644 --- a/nexus/tests/integration_tests/multicast/authorization.rs +++ b/nexus/tests/integration_tests/multicast/authorization.rs @@ -20,15 +20,17 @@ use nexus_test_utils::resource_helpers::{ create_project, grant_iam, link_ip_pool, object_get, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::{ - InstanceCreate, InstanceMulticastGroupJoin, - InstanceNetworkInterfaceAttachment, ProjectCreate, SiloCreate, - SiloQuotasCreate, +use nexus_types::external_api::instance::{ + InstanceCreate, InstanceNetworkInterfaceAttachment, }; -use nexus_types::external_api::shared::{ - ProjectRole, SiloIdentityMode, SiloRole, +use nexus_types::external_api::multicast::{ + InstanceMulticastGroupJoin, MulticastGroup, MulticastGroupMember, +}; +use nexus_types::external_api::policy::{ProjectRole, SiloRole}; +use nexus_types::external_api::project::ProjectCreate; +use nexus_types::external_api::silo::{ + Silo, SiloCreate, SiloIdentityMode, SiloQuotasCreate, }; -use nexus_types::external_api::views::{MulticastGroup, Silo}; use omicron_common::api::external::{ ByteCount, Hostname, IdentityMetadataCreateParams, Instance, InstanceCpuCount, diff --git a/nexus/tests/integration_tests/multicast/cache_invalidation.rs b/nexus/tests/integration_tests/multicast/cache_invalidation.rs index a38f0fe2a36..de744c19d9d 100644 --- a/nexus/tests/integration_tests/multicast/cache_invalidation.rs +++ b/nexus/tests/integration_tests/multicast/cache_invalidation.rs @@ -21,7 +21,7 @@ use nexus_test_utils::resource_helpers::{ }; use nexus_test_utils_macros::nexus_test; use nexus_types::deployment::SledFilter; -use nexus_types::external_api::params; +use nexus_types::external_api::sled; use nexus_types::inventory::SpType; use omicron_nexus::Server; use omicron_nexus::TestInterfaces; @@ -553,7 +553,7 @@ async fn test_sled_expunge_removes_from_multicast_cache( .sled_set_provision_policy( &opctx, &authz_sled, - nexus_types::external_api::views::SledProvisionPolicy::NonProvisionable, + nexus_types::external_api::sled::SledProvisionPolicy::NonProvisionable, ) .await .expect("set sled provision policy"); @@ -598,7 +598,7 @@ async fn test_sled_expunge_removes_from_multicast_cache( .make_request( Method::POST, "/sleds/expunge", - Some(params::SledSelector { sled: first_sled_id }), + Some(sled::SledSelector { sled: first_sled_id }), StatusCode::OK, ) .await diff --git a/nexus/tests/integration_tests/multicast/failures.rs b/nexus/tests/integration_tests/multicast/failures.rs index fa9972af930..96735ce9684 100644 --- a/nexus/tests/integration_tests/multicast/failures.rs +++ b/nexus/tests/integration_tests/multicast/failures.rs @@ -37,12 +37,17 @@ use nexus_test_utils::resource_helpers::{ create_project, object_get, objects_list_page_authz, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::{ - ExternalIpCreate, InstanceDiskAttachment, InstanceMulticastGroupJoin, - InstanceNetworkInterfaceAttachment, MulticastGroupJoinSpec, +use nexus_types::external_api::instance::{ + ExternalIpCreate, InstanceDiskAttachment, + InstanceNetworkInterfaceAttachment, +}; +use nexus_types::external_api::multicast::{ + InstanceMulticastGroupJoin, MulticastGroup, MulticastGroupJoinSpec, + MulticastGroupMember, +}; +use omicron_common::api::external::{ + IdentityMetadataCreateParams, InstanceState, NameOrId, SwitchLocation, }; -use nexus_types::external_api::views::{MulticastGroup, MulticastGroupMember}; -use omicron_common::api::external::{InstanceState, SwitchLocation}; use omicron_uuid_kinds::{InstanceUuid, MulticastGroupUuid}; use super::*; @@ -629,7 +634,7 @@ async fn test_implicit_deletion_race_with_instance_join( { Some( response - .parsed_body::() + .parsed_body::() .unwrap(), ) } diff --git a/nexus/tests/integration_tests/multicast/groups.rs b/nexus/tests/integration_tests/multicast/groups.rs index 738aee6d5b4..fffe0a3f19f 100644 --- a/nexus/tests/integration_tests/multicast/groups.rs +++ b/nexus/tests/integration_tests/multicast/groups.rs @@ -40,12 +40,11 @@ use nexus_test_utils::resource_helpers::{ object_get, object_get_error, object_put_error, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::{ - InstanceMulticastGroupJoin, IpPoolCreate, +use nexus_types::external_api::ip_pool::{ + IpPool, IpPoolCreate, IpPoolRange, IpRange, IpVersion, Ipv4Range, Ipv6Range, }; -use nexus_types::external_api::shared::{IpRange, Ipv4Range, Ipv6Range}; -use nexus_types::external_api::views::{ - IpPool, IpPoolRange, IpVersion, MulticastGroup, MulticastGroupMember, +use nexus_types::external_api::multicast::{ + InstanceMulticastGroupJoin, MulticastGroup, MulticastGroupMember, }; use omicron_common::api::external::{ IdentityMetadataCreateParams, InstanceState, diff --git a/nexus/tests/integration_tests/multicast/instances.rs b/nexus/tests/integration_tests/multicast/instances.rs index 75be25d09b8..245e284248e 100644 --- a/nexus/tests/integration_tests/multicast/instances.rs +++ b/nexus/tests/integration_tests/multicast/instances.rs @@ -32,12 +32,13 @@ use nexus_test_utils::resource_helpers::{ object_delete, object_get, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::{ - InstanceCreate, InstanceMulticastGroupJoin, - InstanceNetworkInterfaceAttachment, InstanceUpdate, MulticastGroupJoinSpec, +use nexus_types::external_api::instance::{ + InstanceCreate, InstanceNetworkInterfaceAttachment, InstanceUpdate, +}; +use nexus_types::external_api::multicast::{ + InstanceMulticastGroupJoin, MulticastGroup, MulticastGroupJoinSpec, + MulticastGroupMember, }; - -use nexus_types::external_api::views::{MulticastGroup, MulticastGroupMember}; use nexus_types::internal_api::params::InstanceMigrateRequest; use omicron_common::api::external::{ @@ -1385,7 +1386,7 @@ async fn test_source_ips_preserved_on_instance_reconfigure( async fn test_instance_create_with_ssm_multicast_groups( cptestctx: &ControlPlaneTestContext, ) { - use nexus_types::external_api::params::MulticastGroupJoinSpec; + use nexus_types::external_api::multicast::MulticastGroupJoinSpec; let client = &cptestctx.external_client; let project_name = "ssm-create-project"; @@ -1739,7 +1740,7 @@ async fn test_multicast_ipv6_lifecycle(cptestctx: &ControlPlaneTestContext) { let member: MulticastGroupMember = put_upsert( client, &join_url, - &nexus_types::external_api::params::InstanceMulticastGroupJoin { + &nexus_types::external_api::multicast::InstanceMulticastGroupJoin { source_ips: None, ip_version: None, // Only one pool, no ambiguity }, diff --git a/nexus/tests/integration_tests/multicast/mod.rs b/nexus/tests/integration_tests/multicast/mod.rs index a47e117983b..42830c60da9 100644 --- a/nexus/tests/integration_tests/multicast/mod.rs +++ b/nexus/tests/integration_tests/multicast/mod.rs @@ -31,14 +31,15 @@ use nexus_test_utils::resource_helpers::{ link_ip_pool, object_create, object_delete, object_get, }; use nexus_types::deployment::SledFilter; -use nexus_types::external_api::params::{ - InstanceCreate, InstanceMulticastGroupJoin, - InstanceNetworkInterfaceAttachment, IpPoolCreate, MulticastGroupIdentifier, - MulticastGroupJoinSpec, +use nexus_types::external_api::instance::{ + InstanceCreate, InstanceNetworkInterfaceAttachment, }; -use nexus_types::external_api::shared::{IpRange, Ipv4Range}; -use nexus_types::external_api::views::{ - IpPool, IpPoolRange, IpVersion, MulticastGroup, MulticastGroupMember, +use nexus_types::external_api::ip_pool::{ + IpPool, IpPoolCreate, IpPoolRange, IpRange, IpVersion, Ipv4Range, +}; +use nexus_types::external_api::multicast::{ + InstanceMulticastGroupJoin, MulticastGroup, MulticastGroupIdentifier, + MulticastGroupJoinSpec, MulticastGroupMember, }; use nexus_types::identity::{Asset, Resource}; use omicron_common::api::external::{ @@ -178,7 +179,7 @@ pub(crate) async fn create_multicast_ip_pool_v6( client: &ClientTestContext, pool_name: &str, ) -> IpPool { - use nexus_types::external_api::shared::Ipv6Range; + use nexus_types::external_api::ip_pool::Ipv6Range; use std::net::Ipv6Addr; let pool_params = IpPoolCreate::new_multicast( diff --git a/nexus/tests/integration_tests/multicast/networking_integration.rs b/nexus/tests/integration_tests/multicast/networking_integration.rs index 3a6f56d6aef..a68b2badf44 100644 --- a/nexus/tests/integration_tests/multicast/networking_integration.rs +++ b/nexus/tests/integration_tests/multicast/networking_integration.rs @@ -18,12 +18,17 @@ use nexus_test_utils::resource_helpers::{ create_default_ip_pools, create_project, object_create, object_delete, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::{ - self, EphemeralIpCreate, ExternalIpCreate, FloatingIpAttach, - InstanceCreate, InstanceNetworkInterfaceAttachment, PoolSelector, +use nexus_types::external_api::floating_ip::{ + AddressAllocator, FloatingIp, FloatingIpAttach, +}; +use nexus_types::external_api::instance::{ + EphemeralIpCreate, ExternalIpCreate, InstanceCreate, + InstanceNetworkInterfaceAttachment, +}; +use nexus_types::external_api::ip_pool::{IpVersion, PoolSelector}; +use nexus_types::external_api::multicast::{ + MulticastGroup, MulticastGroupMember, }; -use nexus_types::external_api::views::FloatingIp; -use omicron_common::api::external::IpVersion; use omicron_common::api::external::{ ByteCount, IdentityMetadataCreateParams, Instance, InstanceCpuCount, NameOrId, @@ -499,8 +504,8 @@ async fn test_multicast_with_floating_ip_basic( client, floating_ip_name, project_name, - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + AddressAllocator::Auto { + pool_selector: PoolSelector::Explicit { pool: v4_pool.identity.name.clone().into(), }, }, @@ -586,7 +591,7 @@ async fn test_multicast_with_floating_ip_basic( "/v1/floating-ips/{floating_ip_name}/attach?project={project_name}" ); let attach_params = FloatingIpAttach { - kind: nexus_types::external_api::params::FloatingIpParentKind::Instance, + kind: nexus_types::external_api::floating_ip::FloatingIpParentKind::Instance, parent: NameOrId::Name(instance_name.parse().unwrap()), }; diff --git a/nexus/tests/integration_tests/multicast/pool_selection.rs b/nexus/tests/integration_tests/multicast/pool_selection.rs index 22831173b42..e9ebde8b924 100644 --- a/nexus/tests/integration_tests/multicast/pool_selection.rs +++ b/nexus/tests/integration_tests/multicast/pool_selection.rs @@ -11,8 +11,10 @@ use http::StatusCode; use nexus_test_utils::http_testing::{AuthnMode, NexusRequest, RequestBuilder}; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::InstanceMulticastGroupJoin; -use nexus_types::external_api::views::{IpVersion, MulticastGroupMember}; +use nexus_types::external_api::ip_pool::IpVersion; +use nexus_types::external_api::multicast::{ + InstanceMulticastGroupJoin, MulticastGroupMember, +}; use std::net::IpAddr; use nexus_test_utils::resource_helpers::{ diff --git a/nexus/tests/integration_tests/pantry.rs b/nexus/tests/integration_tests/pantry.rs index 451103857cf..a987acf58de 100644 --- a/nexus/tests/integration_tests/pantry.rs +++ b/nexus/tests/integration_tests/pantry.rs @@ -19,8 +19,9 @@ use nexus_test_utils::resource_helpers::create_instance; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils::resource_helpers::object_create; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::views::Snapshot; +use nexus_types::external_api::disk; +use nexus_types::external_api::path_params; +use nexus_types::external_api::snapshot::Snapshot; use omicron_common::api::external::ByteCount; use omicron_common::api::external::Disk; use omicron_common::api::external::DiskState; @@ -120,14 +121,14 @@ async fn create_disk_with_state_importing_blocks(client: &ClientTestContext) { let _disk: Disk = object_create( client, &url, - ¶ms::DiskCreate { + &disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::ImportingBlocks { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::ImportingBlocks { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: ByteCount::from_gibibytes_u32(1), @@ -158,7 +159,7 @@ async fn create_instance_and_attach_disk( NexusRequest::new( RequestBuilder::new(client, Method::POST, &url_instance_attach_disk) - .body(Some(¶ms::DiskPath { + .body(Some(&path_params::DiskPath { disk: DISK_NAME.to_string().try_into().unwrap(), })) .expect_status(Some(expected_status)), @@ -174,7 +175,7 @@ async fn attach_disk_to_instance(client: &ClientTestContext) { NexusRequest::new( RequestBuilder::new(client, Method::POST, &url_instance_attach_disk) - .body(Some(¶ms::DiskPath { + .body(Some(&path_params::DiskPath { disk: DISK_NAME.to_string().try_into().unwrap(), })) .expect_status(Some(StatusCode::ACCEPTED)), @@ -214,7 +215,7 @@ async fn bulk_write_bytes(client: &ClientTestContext) { for block in 0..8 { NexusRequest::new( RequestBuilder::new(client, Method::POST, &bulk_write_url) - .body(Some(¶ms::ImportBlocksBulkWrite { + .body(Some(&disk::ImportBlocksBulkWrite { offset: block * CHUNK_SIZE, base64_encoded_data: base64::Engine::encode( &base64::engine::general_purpose::STANDARD, @@ -238,7 +239,7 @@ async fn bulk_write_bytes_expect_failure(client: &ClientTestContext) { NexusRequest::new( RequestBuilder::new(client, Method::POST, &bulk_write_url) - .body(Some(¶ms::ImportBlocksBulkWrite { + .body(Some(&disk::ImportBlocksBulkWrite { offset: CHUNK_SIZE, base64_encoded_data: base64::Engine::encode( &base64::engine::general_purpose::STANDARD, @@ -264,7 +265,7 @@ async fn bulk_write_bytes_manual( NexusRequest::new( RequestBuilder::new(client, Method::POST, &bulk_write_url) - .body(Some(¶ms::ImportBlocksBulkWrite { + .body(Some(&disk::ImportBlocksBulkWrite { offset, base64_encoded_data: base64::Engine::encode( &base64::engine::general_purpose::STANDARD, @@ -349,14 +350,14 @@ async fn test_disk_create_for_importing(cptestctx: &ControlPlaneTestContext) { create_project_and_pool(client).await; let disks_url = get_disks_url(); - let new_disk = params::DiskCreate { + let new_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: DISK_NAME.parse().unwrap(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::ImportingBlocks { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::ImportingBlocks { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: ByteCount::from_gibibytes_u32(1), @@ -685,7 +686,7 @@ async fn test_cannot_bulk_write_data_non_base64( NexusRequest::new( RequestBuilder::new(client, Method::POST, &bulk_write_url) - .body(Some(¶ms::ImportBlocksBulkWrite { + .body(Some(&disk::ImportBlocksBulkWrite { offset: 0, base64_encoded_data: "this is not base64!".to_string(), })) diff --git a/nexus/tests/integration_tests/password_login.rs b/nexus/tests/integration_tests/password_login.rs index 6d9099fa91e..27c82c8aec5 100644 --- a/nexus/tests/integration_tests/password_login.rs +++ b/nexus/tests/integration_tests/password_login.rs @@ -9,8 +9,10 @@ use nexus_test_utils::resource_helpers::grant_iam; use nexus_test_utils::resource_helpers::test_params; use nexus_test_utils::resource_helpers::{create_local_user, create_silo}; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::shared::{self, SiloRole}; -use nexus_types::external_api::views; +use nexus_types::external_api::policy::SiloRole; +use nexus_types::external_api::silo; +use nexus_types::external_api::silo::SiloIdentityMode; +use nexus_types::external_api::user; use omicron_common::api::external::{Name, UserId}; use omicron_passwords::MIN_EXPECTED_PASSWORD_VERIFY_TIME; use std::str::FromStr; @@ -37,7 +39,7 @@ async fn test_local_users(cptestctx: &ControlPlaneTestContext) { client, silo_name.as_str(), true, - shared::SiloIdentityMode::LocalOnly, + SiloIdentityMode::LocalOnly, ) .await; test_local_user_basic(client, &silo).await; @@ -52,7 +54,7 @@ async fn test_local_users(cptestctx: &ControlPlaneTestContext) { .unwrap(); } -async fn test_local_user_basic(client: &ClientTestContext, silo: &views::Silo) { +async fn test_local_user_basic(client: &ClientTestContext, silo: &silo::Silo) { let silo_name = &silo.identity.name; // First, try logging in with a non-existent user. This naturally should @@ -298,7 +300,7 @@ async fn test_local_user_basic(client: &ClientTestContext, silo: &views::Silo) { async fn test_local_user_with_no_initial_password( client: &ClientTestContext, - silo: &views::Silo, + silo: &silo::Silo, ) { let silo_name = &silo.identity.name; @@ -349,10 +351,10 @@ async fn test_local_user_with_no_initial_password( async fn expect_session_valid( client: &ClientTestContext, session_token: &str, -) -> views::CurrentUser { +) -> user::CurrentUser { NexusRequest::object_get(client, "/v1/me") .authn_as(AuthnMode::Session(session_token.to_string())) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await } diff --git a/nexus/tests/integration_tests/probe.rs b/nexus/tests/integration_tests/probe.rs index dfcca36bd5d..2d4f4fbf09c 100644 --- a/nexus/tests/integration_tests/probe.rs +++ b/nexus/tests/integration_tests/probe.rs @@ -6,9 +6,8 @@ use nexus_test_utils::{ resource_helpers::{create_default_ip_pools, create_project}, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::{ - params, params::ProbeCreate, shared::ProbeInfo, -}; +use nexus_types::external_api::ip_pool::PoolSelector; +use nexus_types::external_api::probe::{ProbeCreate, ProbeInfo}; use omicron_common::api::external::{ IdentityMetadataCreateParams, IpVersion, Probe, }; @@ -40,7 +39,7 @@ async fn test_probe_basic_crud(ctx: &ControlPlaneTestContext) { name: "class1".parse().unwrap(), description: "subspace relay probe".to_owned(), }, - pool_selector: params::PoolSelector::Explicit { + pool_selector: PoolSelector::Explicit { pool: v6_pool.identity.name.clone().into(), }, sled: SLED_AGENT_UUID.parse().unwrap(), @@ -147,9 +146,7 @@ async fn test_probe_pool_selector_ip_version(ctx: &ControlPlaneTestContext) { name: "probe-v6".parse().unwrap(), description: "IPv6 probe".to_owned(), }, - pool_selector: params::PoolSelector::Auto { - ip_version: Some(IpVersion::V6), - }, + pool_selector: PoolSelector::Auto { ip_version: Some(IpVersion::V6) }, sled: SLED_AGENT_UUID.parse().unwrap(), }; @@ -195,9 +192,7 @@ async fn test_probe_pool_selector_ip_version(ctx: &ControlPlaneTestContext) { name: "probe-v4".parse().unwrap(), description: "IPv4 probe".to_owned(), }, - pool_selector: params::PoolSelector::Auto { - ip_version: Some(IpVersion::V4), - }, + pool_selector: PoolSelector::Auto { ip_version: Some(IpVersion::V4) }, sled: SLED_AGENT_UUID.parse().unwrap(), }; diff --git a/nexus/tests/integration_tests/projects.rs b/nexus/tests/integration_tests/projects.rs index 85c459e467b..79047a52970 100644 --- a/nexus/tests/integration_tests/projects.rs +++ b/nexus/tests/integration_tests/projects.rs @@ -24,11 +24,15 @@ use nexus_test_utils::resource_helpers::project_get; use nexus_test_utils::resource_helpers::projects_list; use nexus_test_utils::resource_helpers::test_params; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::shared::SiloRole; -use nexus_types::external_api::views; -use nexus_types::external_api::views::Project; -use nexus_types::external_api::views::Silo; +use nexus_types::external_api::floating_ip; +use nexus_types::external_api::image; +use nexus_types::external_api::instance; +use nexus_types::external_api::ip_pool; +use nexus_types::external_api::policy::SiloRole; +use nexus_types::external_api::project; +use nexus_types::external_api::project::Project; +use nexus_types::external_api::silo::Silo; +use nexus_types::external_api::snapshot; use nexus_types::identity::Resource; use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; @@ -160,7 +164,7 @@ async fn test_project_deletion_with_instance( let _: Instance = object_create( client, &format!("/v1/instances?project={}", name), - ¶ms::InstanceCreate { + &instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "my-instance".parse().unwrap(), description: "description".to_string(), @@ -171,7 +175,7 @@ async fn test_project_deletion_with_instance( user_data: b"none".to_vec(), ssh_public_keys: Some(Vec::new()), network_interfaces: - params::InstanceNetworkInterfaceAttachment::None, + instance::InstanceNetworkInterfaceAttachment::None, external_ips: vec![], disks: vec![], boot_disk: None, @@ -249,8 +253,8 @@ async fn test_project_deletion_with_floating_ip( &client, "my-fip", &name, - params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v6_pool.identity.name.clone().into(), }, }, @@ -283,7 +287,7 @@ async fn test_project_deletion_with_image(cptestctx: &ControlPlaneTestContext) { delete_project_default_subnet(&name, &client).await; delete_project_default_vpc(&name, &client).await; - let image_create_params = params::ImageCreate { + let image_create_params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "alpine-edge".parse().unwrap(), description: String::from( @@ -292,14 +296,14 @@ async fn test_project_deletion_with_image(cptestctx: &ControlPlaneTestContext) { }, os: "alpine".to_string(), version: "edge".to_string(), - source: params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + source: image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, }; let images_url = format!("/v1/images?project={}", name); let image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; assert_eq!( @@ -344,10 +348,10 @@ async fn test_project_deletion_with_snapshot( delete_project_default_vpc(&name, &client).await; create_disk(&client, &name, "my-disk").await; - let _: views::Snapshot = object_create( + let _: snapshot::Snapshot = object_create( client, &format!("/v1/snapshots?project={}", name), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "my-snapshot".parse().unwrap(), description: "not attached to instance".into(), @@ -519,7 +523,7 @@ async fn test_limited_collaborator_cannot_create_project( // Attempt to create a project - should fail with 403 Forbidden let error: HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(client, Method::POST, "/v1/projects") - .body(Some(¶ms::ProjectCreate { + .body(Some(&project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "forbidden-project".parse().unwrap(), description: "should not be created".to_string(), diff --git a/nexus/tests/integration_tests/quotas.rs b/nexus/tests/integration_tests/quotas.rs index f05adce854c..4de4955af88 100644 --- a/nexus/tests/integration_tests/quotas.rs +++ b/nexus/tests/integration_tests/quotas.rs @@ -15,10 +15,14 @@ use nexus_test_utils::resource_helpers::object_create; use nexus_test_utils::resource_helpers::object_create_error; use nexus_test_utils::resource_helpers::test_params; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::shared; -use nexus_types::external_api::shared::SiloRole; -use nexus_types::external_api::views::{Silo, SiloQuotas}; +use nexus_types::external_api::disk; +use nexus_types::external_api::instance; +use nexus_types::external_api::policy::SiloRole; +use nexus_types::external_api::project; +use nexus_types::external_api::silo::{ + Silo, SiloCreate, SiloIdentityMode, SiloQuotas, SiloQuotasCreate, + SiloQuotasUpdate, +}; use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::InstanceCpuCount; @@ -39,7 +43,7 @@ impl ResourceAllocator { async fn set_quotas( &self, client: &ClientTestContext, - quotas: params::SiloQuotasUpdate, + quotas: SiloQuotasUpdate, ) -> Result { NexusRequest::object_put( client, @@ -54,7 +58,7 @@ impl ResourceAllocator { async fn set_quotas_expect_error( &self, client: &ClientTestContext, - quotas: params::SiloQuotasUpdate, + quotas: SiloQuotasUpdate, code: http::StatusCode, ) -> HttpErrorResponseBody { NexusRequest::expect_failure_with_body( @@ -95,7 +99,7 @@ impl ResourceAllocator { NexusRequest::objects_post( client, "/v1/instances?project=project", - ¶ms::InstanceCreate { + &instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: "".into(), @@ -106,9 +110,9 @@ impl ResourceAllocator { user_data: b"#cloud-config\nsystem_info:\n default_user:\n name: oxide" .to_vec(), ssh_public_keys: Some(Vec::new()), - network_interfaces: params::InstanceNetworkInterfaceAttachment::DefaultIpv4, - external_ips: Vec::::new(), - disks: Vec::::new(), + network_interfaces: instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, + external_ips: Vec::::new(), + disks: Vec::::new(), boot_disk: None, cpu_platform: None, start: false, @@ -177,15 +181,15 @@ impl ResourceAllocator { Method::POST, "/v1/disks?project=project", ) - .body(Some(¶ms::DiskCreate { + .body(Some(&disk::DiskCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: "".into(), }, size: ByteCount::from_gibibytes_u32(size), - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, })), @@ -199,19 +203,19 @@ impl ResourceAllocator { async fn setup_silo_with_quota( client: &ClientTestContext, silo_name: &str, - quotas: params::SiloQuotasCreate, + quotas: SiloQuotasCreate, ) -> ResourceAllocator { let silo: Silo = object_create( client, "/v1/system/silos", - ¶ms::SiloCreate { + &SiloCreate { identity: IdentityMetadataCreateParams { name: silo_name.parse().unwrap(), description: "".into(), }, quotas, discoverable: true, - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: SiloIdentityMode::LocalOnly, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: Default::default(), @@ -248,7 +252,7 @@ async fn setup_silo_with_quota( NexusRequest::objects_post( client, "/v1/projects", - ¶ms::ProjectCreate { + &project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "project".parse().unwrap(), description: "".into(), @@ -273,7 +277,7 @@ async fn test_quotas(cptestctx: &ControlPlaneTestContext) { let system = setup_silo_with_quota( &client, "quota-test-silo", - params::SiloQuotasCreate::empty(), + SiloQuotasCreate::empty(), ) .await; @@ -295,7 +299,7 @@ async fn test_quotas(cptestctx: &ControlPlaneTestContext) { system .set_quotas( client, - params::SiloQuotasUpdate { + SiloQuotasUpdate { cpus: Some(4), memory: Some(ByteCount::from_gibibytes_u32(15)), storage: Some(ByteCount::from_gibibytes_u32(2)), @@ -354,12 +358,12 @@ async fn test_quota_limits(cptestctx: &ControlPlaneTestContext) { let system = setup_silo_with_quota( &client, "quota-test-silo", - params::SiloQuotasCreate::empty(), + SiloQuotasCreate::empty(), ) .await; // Maximal legal limits should be allowed. - let quota_limit = params::SiloQuotasUpdate { + let quota_limit = SiloQuotasUpdate { cpus: Some(i64::MAX), memory: Some(i64::MAX.try_into().unwrap()), storage: Some(i64::MAX.try_into().unwrap()), @@ -418,19 +422,19 @@ async fn test_negative_quota(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; // Can't make a silo with a negative quota - let mut quotas = params::SiloQuotasCreate::empty(); + let mut quotas = SiloQuotasCreate::empty(); quotas.cpus = -1; let response = object_create_error( client, "/v1/system/silos", - ¶ms::SiloCreate { + &SiloCreate { identity: IdentityMetadataCreateParams { name: "negative-cpus-not-allowed".parse().unwrap(), description: "".into(), }, quotas, discoverable: true, - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: SiloIdentityMode::LocalOnly, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: Default::default(), @@ -451,12 +455,12 @@ async fn test_negative_quota(cptestctx: &ControlPlaneTestContext) { let system = setup_silo_with_quota( &client, "quota-test-silo", - params::SiloQuotasCreate::empty(), + SiloQuotasCreate::empty(), ) .await; // Can't update a silo with a negative quota - let quota_limit = params::SiloQuotasUpdate { + let quota_limit = SiloQuotasUpdate { cpus: Some(-1), memory: Some(0_u64.try_into().unwrap()), storage: Some(0_u64.try_into().unwrap()), diff --git a/nexus/tests/integration_tests/rack.rs b/nexus/tests/integration_tests/rack.rs index 3c29591500e..68f5cb15644 100644 --- a/nexus/tests/integration_tests/rack.rs +++ b/nexus/tests/integration_tests/rack.rs @@ -16,9 +16,9 @@ use nexus_test_utils::http_testing::NexusRequest; use nexus_test_utils::http_testing::RequestBuilder; use nexus_test_utils::resource_helpers::test_params; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::shared::UninitializedSled; -use nexus_types::external_api::views::Rack; +use nexus_types::external_api::hardware; +use nexus_types::external_api::hardware::UninitializedSled; +use nexus_types::external_api::rack::Rack; use omicron_common::api::external::ByteCount; use omicron_common::api::external::Generation; use omicron_uuid_kinds::SledUuid; @@ -186,7 +186,7 @@ async fn test_sled_add(cptestctx: &ControlPlaneTestContext) { let sled_id = NexusRequest::objects_post( external_client, add_url, - ¶ms::UninitializedSledId { + &hardware::UninitializedSledId { serial: baseboard.serial.clone(), part: baseboard.part.clone(), }, @@ -202,7 +202,7 @@ async fn test_sled_add(cptestctx: &ControlPlaneTestContext) { let repeat_sled_id = NexusRequest::objects_post( external_client, add_url, - ¶ms::UninitializedSledId { + &hardware::UninitializedSledId { serial: baseboard.serial.clone(), part: baseboard.part.clone(), }, @@ -247,7 +247,7 @@ async fn test_sled_add(cptestctx: &ControlPlaneTestContext) { http::StatusCode::BAD_REQUEST, http::Method::POST, add_url, - ¶ms::UninitializedSledId { + &hardware::UninitializedSledId { serial: baseboard.serial.clone(), part: baseboard.part.clone(), }, diff --git a/nexus/tests/integration_tests/role_assignments.rs b/nexus/tests/integration_tests/role_assignments.rs index 40cefc8e662..4424b77a975 100644 --- a/nexus/tests/integration_tests/role_assignments.rs +++ b/nexus/tests/integration_tests/role_assignments.rs @@ -19,8 +19,9 @@ use nexus_test_utils::http_testing::AuthnMode; use nexus_test_utils::http_testing::NexusRequest; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::shared; -use nexus_types::external_api::views; +use nexus_types::external_api::policy; +use nexus_types::external_api::project; +use nexus_types::external_api::sled; use omicron_common::api::external::ObjectIdentity; type ControlPlaneTestContext = @@ -74,7 +75,7 @@ trait RoleAssignmentTest { fn verify_initial<'a, 'b, 'c, 'd>( &'a self, client: &'b ClientTestContext, - current_policy: &'c shared::Policy, + current_policy: &'c policy::Policy, ) -> BoxFuture<'d, ()> where 'a: 'd, @@ -104,8 +105,8 @@ async fn test_role_assignments_fleet(cptestctx: &ControlPlaneTestContext) { struct FleetRoleAssignmentTest; impl RoleAssignmentTest for FleetRoleAssignmentTest { - type RoleType = shared::FleetRole; - const ROLE: Self::RoleType = shared::FleetRole::Admin; + type RoleType = policy::FleetRole; + const ROLE: Self::RoleType = policy::FleetRole::Admin; const VISIBLE_TO_UNPRIVILEGED: bool = true; fn policy_url(&self) -> String { String::from("/v1/system/policy") @@ -114,7 +115,7 @@ async fn test_role_assignments_fleet(cptestctx: &ControlPlaneTestContext) { fn verify_initial<'a, 'b, 'c, 'd>( &'a self, client: &'b ClientTestContext, - _current_policy: &'c shared::Policy, + _current_policy: &'c policy::Policy, ) -> BoxFuture<'d, ()> where 'a: 'd, @@ -148,7 +149,7 @@ async fn test_role_assignments_fleet(cptestctx: &ControlPlaneTestContext) { 'b: 'c, { async { - let _: dropshot::ResultsPage = + let _: dropshot::ResultsPage = NexusRequest::object_get(client, RESOURCE_URL) .authn_as(AuthnMode::UnprivilegedUser) .execute() @@ -169,8 +170,8 @@ async fn test_role_assignments_fleet(cptestctx: &ControlPlaneTestContext) { async fn test_role_assignments_silo(cptestctx: &ControlPlaneTestContext) { struct SiloRoleAssignmentTest; impl RoleAssignmentTest for SiloRoleAssignmentTest { - type RoleType = shared::SiloRole; - const ROLE: Self::RoleType = shared::SiloRole::Admin; + type RoleType = policy::SiloRole; + const ROLE: Self::RoleType = policy::SiloRole::Admin; const VISIBLE_TO_UNPRIVILEGED: bool = true; fn policy_url(&self) -> String { format!( @@ -182,7 +183,7 @@ async fn test_role_assignments_silo(cptestctx: &ControlPlaneTestContext) { fn verify_initial<'a, 'b, 'c, 'd>( &'a self, _: &'b ClientTestContext, - _current_policy: &'c shared::Policy, + _current_policy: &'c policy::Policy, ) -> BoxFuture<'d, ()> where 'a: 'd, @@ -225,8 +226,8 @@ async fn test_role_assignments_silo_implicit( ) { struct SiloRoleAssignmentTest; impl RoleAssignmentTest for SiloRoleAssignmentTest { - type RoleType = shared::SiloRole; - const ROLE: Self::RoleType = shared::SiloRole::Admin; + type RoleType = policy::SiloRole; + const ROLE: Self::RoleType = policy::SiloRole::Admin; const VISIBLE_TO_UNPRIVILEGED: bool = true; fn policy_url(&self) -> String { "/v1/policy".to_string() @@ -235,7 +236,7 @@ async fn test_role_assignments_silo_implicit( fn verify_initial<'a, 'b, 'c, 'd>( &'a self, _: &'b ClientTestContext, - _current_policy: &'c shared::Policy, + _current_policy: &'c policy::Policy, ) -> BoxFuture<'d, ()> where 'a: 'd, @@ -289,8 +290,8 @@ async fn test_role_assignments_project(cptestctx: &ControlPlaneTestContext) { policy_url: format!("/v1/projects/{}/policy", project_name), }; impl RoleAssignmentTest for ProjectRoleAssignmentTest { - type RoleType = shared::ProjectRole; - const ROLE: Self::RoleType = shared::ProjectRole::Admin; + type RoleType = policy::ProjectRole; + const ROLE: Self::RoleType = policy::ProjectRole::Admin; const VISIBLE_TO_UNPRIVILEGED: bool = false; fn policy_url(&self) -> String { self.policy_url.clone() @@ -299,7 +300,7 @@ async fn test_role_assignments_project(cptestctx: &ControlPlaneTestContext) { fn verify_initial<'a, 'b, 'c, 'd>( &'a self, client: &'b ClientTestContext, - current_policy: &'c shared::Policy, + current_policy: &'c policy::Policy, ) -> BoxFuture<'d, ()> where 'a: 'd, @@ -322,7 +323,7 @@ async fn test_role_assignments_project(cptestctx: &ControlPlaneTestContext) { 'a: 'c, 'b: 'c, { - resource_privileged_conditions::( + resource_privileged_conditions::( client, &self.project_url, &self.project_name, @@ -343,7 +344,7 @@ async fn test_role_assignments_project(cptestctx: &ControlPlaneTestContext) { fn resource_initial_conditions<'a, 'b, 'c, 'd, T>( client: &'a ClientTestContext, resource_url: &'b str, - current_policy: &'c shared::Policy, + current_policy: &'c policy::Policy, ) -> impl Future + 'd where 'a: 'd, @@ -424,7 +425,7 @@ async fn run_test( // resource. This is a little ugly, but we don't have a way of creating // silo users yet and it's worth testing this. let mut new_policy = initial_policy.clone(); - let role_assignment = shared::RoleAssignment::for_silo_user( + let role_assignment = policy::RoleAssignment::for_silo_user( USER_TEST_UNPRIVILEGED.id(), T::ROLE, ); @@ -457,7 +458,7 @@ async fn run_test( test_case.verify_initial(client, ¤t_policy).await; // Okay, really grant them access. - let mut updated_policy: shared::Policy = + let mut updated_policy: policy::Policy = NexusRequest::object_put(client, &policy_url, Some(&new_policy)) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -493,7 +494,7 @@ async fn run_test( // The way we've defined things, the unprivileged user ought to be able to // revoke their own access. - let updated_policy: shared::Policy = + let updated_policy: policy::Policy = NexusRequest::object_put(client, &policy_url, Some(&initial_policy)) .authn_as(AuthnMode::UnprivilegedUser) .execute() @@ -514,7 +515,7 @@ async fn run_test( async fn policy_fetch( client: &ClientTestContext, policy_url: &str, -) -> shared::Policy { +) -> policy::Policy { NexusRequest::object_get(client, policy_url) .authn_as(AuthnMode::PrivilegedUser) .execute() diff --git a/nexus/tests/integration_tests/router_routes.rs b/nexus/tests/integration_tests/router_routes.rs index 6da0ba39cda..7f4683c007d 100644 --- a/nexus/tests/integration_tests/router_routes.rs +++ b/nexus/tests/integration_tests/router_routes.rs @@ -17,8 +17,7 @@ use nexus_test_utils::resource_helpers::object_put; use nexus_test_utils::resource_helpers::object_put_error; use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::params::RouterRouteUpdate; +use nexus_types::external_api::vpc::{RouterRouteCreate, RouterRouteUpdate}; use omicron_common::api::external::SimpleIdentityOrName; use omicron_common::api::external::{ IdentityMetadataCreateParams, IdentityMetadataUpdateParams, @@ -174,7 +173,7 @@ async fn test_router_routes_crud_operations( let route_created: RouterRoute = NexusRequest::objects_post( client, get_routes_url(vpc_name, router_name).as_str(), - ¶ms::RouterRouteCreate { + &RouterRouteCreate { identity: IdentityMetadataCreateParams { name: route_name.parse().unwrap(), description: "It's a route, what else can I say?".to_string(), @@ -215,7 +214,7 @@ async fn test_router_routes_crud_operations( NexusRequest::object_put( client, route_url.as_str(), - Some(¶ms::RouterRouteUpdate { + Some(&RouterRouteUpdate { identity: IdentityMetadataUpdateParams { name: Some(route_name.parse().unwrap()), description: None, @@ -574,7 +573,7 @@ pub async fn update_route_with_error( ) .as_str(), ) - .body(Some(¶ms::RouterRouteUpdate { + .body(Some(&RouterRouteUpdate { identity: IdentityMetadataUpdateParams { name: None, description: None, diff --git a/nexus/tests/integration_tests/saml.rs b/nexus/tests/integration_tests/saml.rs index 0995a193009..7d96ff4ae46 100644 --- a/nexus/tests/integration_tests/saml.rs +++ b/nexus/tests/integration_tests/saml.rs @@ -9,9 +9,11 @@ use nexus_test_utils::assert_same_items; use nexus_test_utils::http_testing::{AuthnMode, NexusRequest, RequestBuilder}; use nexus_test_utils::resource_helpers::{create_silo, object_create}; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::shared::RelayState; -use nexus_types::external_api::views::{self, Silo}; -use nexus_types::external_api::{params, shared}; +use nexus_types::external_api::identity_provider; +use nexus_types::external_api::saml::RelayState; +use nexus_types::external_api::silo; +use nexus_types::external_api::silo::Silo; +use nexus_types::external_api::user; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_nexus::TestInterfaces; use omicron_uuid_kinds::SiloGroupUuid; @@ -40,7 +42,7 @@ async fn test_create_a_saml_idp(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; const SILO_NAME: &str = "saml-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; let silo: Silo = NexusRequest::object_get( &client, @@ -61,10 +63,10 @@ async fn test_create_a_saml_idp(cptestctx: &ControlPlaneTestContext) { .respond_with(status_code(200).body(saml_idp_descriptor)), ); - let silo_saml_idp: views::SamlIdentityProvider = object_create( + let silo_saml_idp: identity_provider::SamlIdentityProvider = object_create( client, &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), - ¶ms::SamlIdentityProviderCreate { + &identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -73,7 +75,7 @@ async fn test_create_a_saml_idp(cptestctx: &ControlPlaneTestContext) { description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Url { + idp_metadata_source: identity_provider::IdpMetadataSource::Url { url: server.url("/descriptor").to_string(), }, @@ -161,7 +163,7 @@ async fn test_create_a_saml_idp_invalid_descriptor_truncated( let client = &cptestctx.external_client; const SILO_NAME: &str = "saml-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; let saml_idp_descriptor = { @@ -182,7 +184,7 @@ async fn test_create_a_saml_idp_invalid_descriptor_truncated( Method::POST, &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), ) - .body(Some(¶ms::SamlIdentityProviderCreate { + .body(Some(&identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -191,7 +193,7 @@ async fn test_create_a_saml_idp_invalid_descriptor_truncated( description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Url { + idp_metadata_source: identity_provider::IdpMetadataSource::Url { url: server.url("/descriptor").to_string(), }, @@ -221,7 +223,7 @@ async fn test_create_a_saml_idp_invalid_descriptor_no_redirect_binding( let client = &cptestctx.external_client; const SILO_NAME: &str = "saml-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; let saml_idp_descriptor = { @@ -255,7 +257,7 @@ async fn test_create_a_saml_idp_invalid_descriptor_no_redirect_binding( Method::POST, &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), ) - .body(Some(¶ms::SamlIdentityProviderCreate { + .body(Some(&identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -264,7 +266,7 @@ async fn test_create_a_saml_idp_invalid_descriptor_no_redirect_binding( description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Url { + idp_metadata_source: identity_provider::IdpMetadataSource::Url { url: server.url("/descriptor").to_string(), }, @@ -295,7 +297,7 @@ async fn test_create_a_saml_idp_metadata_only_encryption_keys( let client = &cptestctx.external_client; const SILO_NAME: &str = "saml-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; let saml_idp_descriptor = @@ -313,7 +315,7 @@ async fn test_create_a_saml_idp_metadata_only_encryption_keys( Method::POST, &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), ) - .body(Some(¶ms::SamlIdentityProviderCreate { + .body(Some(&identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -322,7 +324,7 @@ async fn test_create_a_saml_idp_metadata_only_encryption_keys( description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Url { + idp_metadata_source: identity_provider::IdpMetadataSource::Url { url: server.url("/descriptor").to_string(), }, @@ -352,7 +354,7 @@ async fn test_create_a_saml_idp_metadata_no_keys( let client = &cptestctx.external_client; const SILO_NAME: &str = "saml-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; let saml_idp_descriptor = SAML_IDP_DESCRIPTOR_NO_KEYS.to_string(); @@ -369,7 +371,7 @@ async fn test_create_a_saml_idp_metadata_no_keys( Method::POST, &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), ) - .body(Some(¶ms::SamlIdentityProviderCreate { + .body(Some(&identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -378,7 +380,7 @@ async fn test_create_a_saml_idp_metadata_no_keys( description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Url { + idp_metadata_source: identity_provider::IdpMetadataSource::Url { url: server.url("/descriptor").to_string(), }, @@ -407,7 +409,7 @@ async fn test_create_a_hidden_silo_saml_idp( ) { let client = &cptestctx.external_client; - create_silo(&client, "hidden", false, shared::SiloIdentityMode::SamlJit) + create_silo(&client, "hidden", false, silo::SiloIdentityMode::SamlJit) .await; // Valid IdP descriptor @@ -419,10 +421,10 @@ async fn test_create_a_hidden_silo_saml_idp( .respond_with(status_code(200).body(saml_idp_descriptor)), ); - let silo_saml_idp: views::SamlIdentityProvider = object_create( + let silo_saml_idp: identity_provider::SamlIdentityProvider = object_create( client, "/v1/system/identity-providers/saml?silo=hidden", - ¶ms::SamlIdentityProviderCreate { + &identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -431,7 +433,7 @@ async fn test_create_a_hidden_silo_saml_idp( description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Url { + idp_metadata_source: identity_provider::IdpMetadataSource::Url { url: server.url("/descriptor").to_string(), }, @@ -477,7 +479,7 @@ async fn test_saml_idp_metadata_url_404(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; const SILO_NAME: &str = "saml-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; let server = Server::run(); @@ -492,7 +494,7 @@ async fn test_saml_idp_metadata_url_404(cptestctx: &ControlPlaneTestContext) { Method::POST, &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), ) - .body(Some(¶ms::SamlIdentityProviderCreate { + .body(Some(&identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -501,7 +503,7 @@ async fn test_saml_idp_metadata_url_404(cptestctx: &ControlPlaneTestContext) { description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Url { + idp_metadata_source: identity_provider::IdpMetadataSource::Url { url: server.url("/descriptor").to_string(), }, @@ -531,7 +533,7 @@ async fn test_saml_idp_metadata_url_invalid( let client = &cptestctx.external_client; const SILO_NAME: &str = "saml-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; NexusRequest::new( @@ -540,7 +542,7 @@ async fn test_saml_idp_metadata_url_invalid( Method::POST, &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), ) - .body(Some(¶ms::SamlIdentityProviderCreate { + .body(Some(&identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -549,7 +551,7 @@ async fn test_saml_idp_metadata_url_invalid( description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Url { + idp_metadata_source: identity_provider::IdpMetadataSource::Url { url: "htttps://fake.url".to_string(), }, @@ -592,43 +594,43 @@ async fn test_saml_idp_reject_keypair(cptestctx: &ControlPlaneTestContext) { ); const SILO_NAME: &str = "saml-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; let test_cases = vec![ // Reject signing keypair if the certificate or key is not base64 // encoded - params::DerEncodedKeyPair { + identity_provider::DerEncodedKeyPair { public_cert: "regular string".to_string(), private_key: RSA_KEY_1_PRIVATE.to_string(), }, - params::DerEncodedKeyPair { + identity_provider::DerEncodedKeyPair { public_cert: RSA_KEY_1_PUBLIC.to_string(), private_key: "regular string".to_string(), }, // Reject signing keypair if the certificate or key is base64 encoded // but not valid - params::DerEncodedKeyPair { + identity_provider::DerEncodedKeyPair { public_cert: base64::engine::general_purpose::STANDARD .encode("not a cert"), private_key: RSA_KEY_1_PRIVATE.to_string(), }, - params::DerEncodedKeyPair { + identity_provider::DerEncodedKeyPair { public_cert: RSA_KEY_1_PUBLIC.to_string(), private_key: base64::engine::general_purpose::STANDARD .encode("not a cert"), }, // Reject signing keypair if cert and key are swapped - params::DerEncodedKeyPair { + identity_provider::DerEncodedKeyPair { public_cert: RSA_KEY_1_PRIVATE.to_string(), private_key: RSA_KEY_1_PUBLIC.to_string(), }, // Reject signing keypair if the keys do not match - params::DerEncodedKeyPair { + identity_provider::DerEncodedKeyPair { public_cert: RSA_KEY_1_PUBLIC.to_string(), private_key: RSA_KEY_2_PRIVATE.to_string(), }, - params::DerEncodedKeyPair { + identity_provider::DerEncodedKeyPair { public_cert: RSA_KEY_2_PUBLIC.to_string(), private_key: RSA_KEY_1_PRIVATE.to_string(), }, @@ -644,7 +646,7 @@ async fn test_saml_idp_reject_keypair(cptestctx: &ControlPlaneTestContext) { SILO_NAME ), ) - .body(Some(¶ms::SamlIdentityProviderCreate { + .body(Some(&identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -653,9 +655,10 @@ async fn test_saml_idp_reject_keypair(cptestctx: &ControlPlaneTestContext) { description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Url { - url: server.url("/descriptor").to_string(), - }, + idp_metadata_source: + identity_provider::IdpMetadataSource::Url { + url: server.url("/descriptor").to_string(), + }, idp_entity_id: "entity_id".to_string(), sp_client_id: "client_id".to_string(), @@ -692,7 +695,7 @@ async fn test_saml_idp_rsa_keypair_ok(cptestctx: &ControlPlaneTestContext) { ); const SILO_NAME: &str = "saml-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; NexusRequest::new( @@ -701,7 +704,7 @@ async fn test_saml_idp_rsa_keypair_ok(cptestctx: &ControlPlaneTestContext) { Method::POST, &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), ) - .body(Some(¶ms::SamlIdentityProviderCreate { + .body(Some(&identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -710,7 +713,7 @@ async fn test_saml_idp_rsa_keypair_ok(cptestctx: &ControlPlaneTestContext) { description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Url { + idp_metadata_source: identity_provider::IdpMetadataSource::Url { url: server.url("/descriptor").to_string(), }, @@ -720,7 +723,7 @@ async fn test_saml_idp_rsa_keypair_ok(cptestctx: &ControlPlaneTestContext) { slo_url: "http://slo".to_string(), technical_contact_email: "technical@fake".to_string(), - signing_keypair: Some(params::DerEncodedKeyPair { + signing_keypair: Some(identity_provider::DerEncodedKeyPair { public_cert: RSA_KEY_1_PUBLIC.to_string(), private_key: RSA_KEY_1_PRIVATE.to_string(), }), @@ -1135,38 +1138,40 @@ async fn test_post_saml_response(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; const SILO_NAME: &str = "saml-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; - let _silo_saml_idp: views::SamlIdentityProvider = object_create( - client, - &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), - ¶ms::SamlIdentityProviderCreate { - identity: IdentityMetadataCreateParams { - name: "some-totally-real-saml-provider" - .to_string() - .parse() - .unwrap(), - description: "a demo provider".to_string(), - }, + let _silo_saml_idp: identity_provider::SamlIdentityProvider = + object_create( + client, + &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), + &identity_provider::SamlIdentityProviderCreate { + identity: IdentityMetadataCreateParams { + name: "some-totally-real-saml-provider" + .to_string() + .parse() + .unwrap(), + description: "a demo provider".to_string(), + }, - idp_metadata_source: params::IdpMetadataSource::Base64EncodedXml { - data: base64::engine::general_purpose::STANDARD - .encode(SAML_RESPONSE_IDP_DESCRIPTOR), - }, + idp_metadata_source: + identity_provider::IdpMetadataSource::Base64EncodedXml { + data: base64::engine::general_purpose::STANDARD + .encode(SAML_RESPONSE_IDP_DESCRIPTOR), + }, - idp_entity_id: "https://some.idp.test/oxide_rack/".to_string(), - sp_client_id: "client_id".to_string(), - acs_url: "https://customer.site/oxide_rack/saml".to_string(), - slo_url: "https://customer.site/oxide_rack/saml".to_string(), - technical_contact_email: "technical@fake".to_string(), + idp_entity_id: "https://some.idp.test/oxide_rack/".to_string(), + sp_client_id: "client_id".to_string(), + acs_url: "https://customer.site/oxide_rack/saml".to_string(), + slo_url: "https://customer.site/oxide_rack/saml".to_string(), + technical_contact_email: "technical@fake".to_string(), - signing_keypair: None, + signing_keypair: None, - group_attribute_name: Some("groups".into()), - }, - ) - .await; + group_attribute_name: Some("groups".into()), + }, + ) + .await; let nexus = &cptestctx.server.server_context().nexus; nexus.set_samael_max_issue_delay( @@ -1214,7 +1219,7 @@ async fn test_post_saml_response(cptestctx: &ControlPlaneTestContext) { let session_cookie_value = result.headers["Set-Cookie"].to_str().unwrap().to_string(); - let groups: ResultsPage = NexusRequest::new( + let groups: ResultsPage = NexusRequest::new( RequestBuilder::new(client, Method::GET, "/v1/groups") .header(http::header::COOKIE, session_cookie_value.clone()) .expect_status(Some(StatusCode::OK)), @@ -1237,12 +1242,12 @@ async fn test_post_saml_response(cptestctx: &ControlPlaneTestContext) { .header(http::header::COOKIE, session_cookie_value.clone()) .expect_status(Some(StatusCode::OK)), ) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; assert_eq!(session_me.user.display_name, "some@customer.com"); - let groups: ResultsPage = NexusRequest::new( + let groups: ResultsPage = NexusRequest::new( RequestBuilder::new(client, Method::GET, "/v1/me/groups") .header(http::header::COOKIE, session_cookie_value) .expect_status(Some(StatusCode::OK)), @@ -1267,38 +1272,40 @@ async fn test_post_saml_response_with_relay_state( let client = &cptestctx.external_client; const SILO_NAME: &str = "saml-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; - let _silo_saml_idp: views::SamlIdentityProvider = object_create( - client, - &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), - ¶ms::SamlIdentityProviderCreate { - identity: IdentityMetadataCreateParams { - name: "some-totally-real-saml-provider" - .to_string() - .parse() - .unwrap(), - description: "a demo provider".to_string(), - }, + let _silo_saml_idp: identity_provider::SamlIdentityProvider = + object_create( + client, + &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), + &identity_provider::SamlIdentityProviderCreate { + identity: IdentityMetadataCreateParams { + name: "some-totally-real-saml-provider" + .to_string() + .parse() + .unwrap(), + description: "a demo provider".to_string(), + }, - idp_metadata_source: params::IdpMetadataSource::Base64EncodedXml { - data: base64::engine::general_purpose::STANDARD - .encode(SAML_RESPONSE_IDP_DESCRIPTOR), - }, + idp_metadata_source: + identity_provider::IdpMetadataSource::Base64EncodedXml { + data: base64::engine::general_purpose::STANDARD + .encode(SAML_RESPONSE_IDP_DESCRIPTOR), + }, - idp_entity_id: "https://some.idp.test/oxide_rack/".to_string(), - sp_client_id: "client_id".to_string(), - acs_url: "https://customer.site/oxide_rack/saml".to_string(), - slo_url: "https://customer.site/oxide_rack/saml".to_string(), - technical_contact_email: "technical@fake".to_string(), + idp_entity_id: "https://some.idp.test/oxide_rack/".to_string(), + sp_client_id: "client_id".to_string(), + acs_url: "https://customer.site/oxide_rack/saml".to_string(), + slo_url: "https://customer.site/oxide_rack/saml".to_string(), + technical_contact_email: "technical@fake".to_string(), - signing_keypair: None, + signing_keypair: None, - group_attribute_name: None, - }, - ) - .await; + group_attribute_name: None, + }, + ) + .await; let nexus = &cptestctx.server.server_context().nexus; nexus.set_samael_max_issue_delay( diff --git a/nexus/tests/integration_tests/schema.rs b/nexus/tests/integration_tests/schema.rs index ad27ef4ea1d..6d1a0cf8f3a 100644 --- a/nexus/tests/integration_tests/schema.rs +++ b/nexus/tests/integration_tests/schema.rs @@ -1409,7 +1409,7 @@ fn at_current_101_0_0<'a>(ctx: &'a MigrationContext<'a>) -> BoxFuture<'a, ()> { use async_bb8_diesel::AsyncRunQueryDsl; use nexus_db_model::Instance; use nexus_db_schema::schema::instance::dsl; - use nexus_types::external_api::params; + use nexus_types::external_api::instance; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_uuid_kinds::InstanceUuid; @@ -1417,7 +1417,7 @@ fn at_current_101_0_0<'a>(ctx: &'a MigrationContext<'a>) -> BoxFuture<'a, ()> { .values(Instance::new( InstanceUuid::new_v4(), Uuid::new_v4(), - ¶ms::InstanceCreate { + &instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "hello".parse().unwrap(), description: "hello".to_string(), @@ -1428,7 +1428,7 @@ fn at_current_101_0_0<'a>(ctx: &'a MigrationContext<'a>) -> BoxFuture<'a, ()> { user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], boot_disk: None, cpu_platform: None, diff --git a/nexus/tests/integration_tests/scim.rs b/nexus/tests/integration_tests/scim.rs index 425a7e1055c..a68a6c6ea36 100644 --- a/nexus/tests/integration_tests/scim.rs +++ b/nexus/tests/integration_tests/scim.rs @@ -24,8 +24,11 @@ use nexus_test_utils::resource_helpers::object_create_no_body; use nexus_test_utils::resource_helpers::object_delete; use nexus_test_utils::resource_helpers::object_get; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::views::{self, Silo}; -use nexus_types::external_api::{params, shared}; +use nexus_types::external_api::identity_provider; +use nexus_types::external_api::policy; +use nexus_types::external_api::scim; +use nexus_types::external_api::silo; +use nexus_types::external_api::user; use nexus_types::identity::Asset; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_nexus::TestInterfaces; @@ -44,9 +47,9 @@ async fn test_create_a_saml_scim_silo(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; const SILO_NAME: &str = "saml-scim-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlScim) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlScim) .await; - let silo: Silo = NexusRequest::object_get( + let silo: silo::Silo = NexusRequest::object_get( &client, &format!("/v1/system/silos/{SILO_NAME}"), ) @@ -59,10 +62,10 @@ async fn test_create_a_saml_scim_silo(cptestctx: &ControlPlaneTestContext) { // Assert we can create a SAML IDP for this identity type - let silo_saml_idp: views::SamlIdentityProvider = object_create( + let silo_saml_idp: identity_provider::SamlIdentityProvider = object_create( client, &format!("/v1/system/identity-providers/saml?silo={SILO_NAME}"), - ¶ms::SamlIdentityProviderCreate { + &identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -71,10 +74,11 @@ async fn test_create_a_saml_scim_silo(cptestctx: &ControlPlaneTestContext) { description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Base64EncodedXml { - data: base64::engine::general_purpose::STANDARD - .encode(SAML_IDP_DESCRIPTOR), - }, + idp_metadata_source: + identity_provider::IdpMetadataSource::Base64EncodedXml { + data: base64::engine::general_purpose::STANDARD + .encode(SAML_IDP_DESCRIPTOR), + }, idp_entity_id: "entity_id".to_string(), sp_client_id: "client_id".to_string(), @@ -158,38 +162,40 @@ async fn test_no_jit_for_saml_scim_silos(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; const SILO_NAME: &str = "saml-scim-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlScim) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlScim) .await; - let _silo_saml_idp: views::SamlIdentityProvider = object_create( - client, - &format!("/v1/system/identity-providers/saml?silo={SILO_NAME}"), - ¶ms::SamlIdentityProviderCreate { - identity: IdentityMetadataCreateParams { - name: "some-totally-real-saml-provider" - .to_string() - .parse() - .unwrap(), - description: "a demo provider".to_string(), - }, - - idp_metadata_source: params::IdpMetadataSource::Base64EncodedXml { - data: base64::engine::general_purpose::STANDARD - .encode(SAML_RESPONSE_IDP_DESCRIPTOR), + let _silo_saml_idp: identity_provider::SamlIdentityProvider = + object_create( + client, + &format!("/v1/system/identity-providers/saml?silo={SILO_NAME}"), + &identity_provider::SamlIdentityProviderCreate { + identity: IdentityMetadataCreateParams { + name: "some-totally-real-saml-provider" + .to_string() + .parse() + .unwrap(), + description: "a demo provider".to_string(), + }, + + idp_metadata_source: + identity_provider::IdpMetadataSource::Base64EncodedXml { + data: base64::engine::general_purpose::STANDARD + .encode(SAML_RESPONSE_IDP_DESCRIPTOR), + }, + + idp_entity_id: "https://some.idp.test/oxide_rack/".to_string(), + sp_client_id: "client_id".to_string(), + acs_url: "https://customer.site/oxide_rack/saml".to_string(), + slo_url: "https://customer.site/oxide_rack/saml".to_string(), + technical_contact_email: "technical@fake".to_string(), + + signing_keypair: None, + + group_attribute_name: Some("groups".into()), }, - - idp_entity_id: "https://some.idp.test/oxide_rack/".to_string(), - sp_client_id: "client_id".to_string(), - acs_url: "https://customer.site/oxide_rack/saml".to_string(), - slo_url: "https://customer.site/oxide_rack/saml".to_string(), - technical_contact_email: "technical@fake".to_string(), - - signing_keypair: None, - - group_attribute_name: Some("groups".into()), - }, - ) - .await; + ) + .await; let nexus = &cptestctx.server.server_context().nexus; nexus.set_samael_max_issue_delay( @@ -228,13 +234,13 @@ async fn test_scim_client_token_crud(cptestctx: &ControlPlaneTestContext) { // Create a Silo, then grant the PrivilegedUser the Admin role on it const SILO_NAME: &str = "saml-scim-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlScim) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlScim) .await; grant_iam( client, &format!("/v1/system/silos/{SILO_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, USER_TEST_PRIVILEGED.id(), AuthnMode::PrivilegedUser, ) @@ -242,7 +248,7 @@ async fn test_scim_client_token_crud(cptestctx: &ControlPlaneTestContext) { // Initially, there should be no tokens created during silo create. - let tokens: Vec = + let tokens: Vec = object_get(client, &format!("/v1/system/scim/tokens?silo={SILO_NAME}")) .await; @@ -250,7 +256,7 @@ async fn test_scim_client_token_crud(cptestctx: &ControlPlaneTestContext) { // Fleet admins can create SCIM client tokens - let created_token_1: views::ScimClientBearerTokenValue = + let created_token_1: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={SILO_NAME}"), @@ -259,7 +265,7 @@ async fn test_scim_client_token_crud(cptestctx: &ControlPlaneTestContext) { // Now there's one! - let tokens: Vec = + let tokens: Vec = object_get(client, &format!("/v1/system/scim/tokens?silo={SILO_NAME}")) .await; @@ -268,7 +274,7 @@ async fn test_scim_client_token_crud(cptestctx: &ControlPlaneTestContext) { // Get that specific token - let token: views::ScimClientBearerToken = object_get( + let token: scim::ScimClientBearerToken = object_get( client, &format!( "/v1/system/scim/tokens/{}?silo={SILO_NAME}", @@ -281,7 +287,7 @@ async fn test_scim_client_token_crud(cptestctx: &ControlPlaneTestContext) { // Create a new token - let created_token_2: views::ScimClientBearerTokenValue = + let created_token_2: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={SILO_NAME}"), @@ -290,7 +296,7 @@ async fn test_scim_client_token_crud(cptestctx: &ControlPlaneTestContext) { // Now there's two! - let tokens: Vec = + let tokens: Vec = object_get(client, &format!("/v1/system/scim/tokens?silo={SILO_NAME}")) .await; @@ -300,14 +306,14 @@ async fn test_scim_client_token_crud(cptestctx: &ControlPlaneTestContext) { // Create one more - let created_token_3: views::ScimClientBearerTokenValue = + let created_token_3: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={SILO_NAME}"), ) .await; - let tokens: Vec = + let tokens: Vec = object_get(client, &format!("/v1/system/scim/tokens?silo={SILO_NAME}")) .await; @@ -329,7 +335,7 @@ async fn test_scim_client_token_crud(cptestctx: &ControlPlaneTestContext) { // Check there's two - let tokens: Vec = + let tokens: Vec = object_get(client, &format!("/v1/system/scim/tokens?silo={SILO_NAME}")) .await; @@ -347,16 +353,16 @@ async fn test_scim_client_token_tenancy(cptestctx: &ControlPlaneTestContext) { const SILO_1_NAME: &str = "saml-scim-silo-1"; const SILO_2_NAME: &str = "saml-scim-silo-2"; - create_silo(&client, SILO_1_NAME, true, shared::SiloIdentityMode::SamlScim) + create_silo(&client, SILO_1_NAME, true, silo::SiloIdentityMode::SamlScim) .await; - create_silo(&client, SILO_2_NAME, true, shared::SiloIdentityMode::SamlScim) + create_silo(&client, SILO_2_NAME, true, silo::SiloIdentityMode::SamlScim) .await; grant_iam( client, &format!("/v1/system/silos/{SILO_1_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, USER_TEST_PRIVILEGED.id(), AuthnMode::PrivilegedUser, ) @@ -365,7 +371,7 @@ async fn test_scim_client_token_tenancy(cptestctx: &ControlPlaneTestContext) { grant_iam( client, &format!("/v1/system/silos/{SILO_2_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, USER_TEST_PRIVILEGED.id(), AuthnMode::PrivilegedUser, ) @@ -373,7 +379,7 @@ async fn test_scim_client_token_tenancy(cptestctx: &ControlPlaneTestContext) { // Initially, there should be no tokens created during silo create. - let tokens: Vec = object_get( + let tokens: Vec = object_get( client, &format!("/v1/system/scim/tokens?silo={SILO_1_NAME}"), ) @@ -381,7 +387,7 @@ async fn test_scim_client_token_tenancy(cptestctx: &ControlPlaneTestContext) { assert!(tokens.is_empty()); - let tokens: Vec = object_get( + let tokens: Vec = object_get( client, &format!("/v1/system/scim/tokens?silo={SILO_2_NAME}"), ) @@ -391,7 +397,7 @@ async fn test_scim_client_token_tenancy(cptestctx: &ControlPlaneTestContext) { // Create a token in one of the Silos - let _created_token_1: views::ScimClientBearerTokenValue = + let _created_token_1: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={SILO_1_NAME}"), @@ -400,7 +406,7 @@ async fn test_scim_client_token_tenancy(cptestctx: &ControlPlaneTestContext) { // Now there's one but only in the first Silo - let tokens: Vec = object_get( + let tokens: Vec = object_get( client, &format!("/v1/system/scim/tokens?silo={SILO_1_NAME}"), ) @@ -408,7 +414,7 @@ async fn test_scim_client_token_tenancy(cptestctx: &ControlPlaneTestContext) { assert!(!tokens.is_empty()); - let tokens: Vec = object_get( + let tokens: Vec = object_get( client, &format!("/v1/system/scim/tokens?silo={SILO_2_NAME}"), ) @@ -426,13 +432,13 @@ async fn test_scim_client_token_bearer_auth( // Create a Silo, then grant the PrivilegedUser the Admin role on it const SILO_NAME: &str = "saml-scim-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlScim) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlScim) .await; grant_iam( client, &format!("/v1/system/silos/{SILO_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, USER_TEST_PRIVILEGED.id(), AuthnMode::PrivilegedUser, ) @@ -440,7 +446,7 @@ async fn test_scim_client_token_bearer_auth( // Create a token - let created_token: views::ScimClientBearerTokenValue = + let created_token: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={SILO_NAME}"), @@ -472,13 +478,9 @@ async fn test_scim_client_no_auth_with_expired_token( const SILO_NAME: &str = "saml-scim-silo"; - let silo = create_silo( - &client, - SILO_NAME, - true, - shared::SiloIdentityMode::SamlScim, - ) - .await; + let silo = + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlScim) + .await; // Manually create an expired token @@ -527,13 +529,13 @@ async fn test_scim2_crate_self_test(cptestctx: &ControlPlaneTestContext) { // Create a Silo, then grant the PrivilegedUser the Admin role on it const SILO_NAME: &str = "saml-scim-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlScim) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlScim) .await; grant_iam( client, &format!("/v1/system/silos/{SILO_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, opctx.authn.actor().unwrap().silo_user_id().unwrap(), AuthnMode::PrivilegedUser, ) @@ -541,7 +543,7 @@ async fn test_scim2_crate_self_test(cptestctx: &ControlPlaneTestContext) { // Create a token - let created_token: views::ScimClientBearerTokenValue = + let created_token: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={}", SILO_NAME,), @@ -573,40 +575,42 @@ async fn test_disabling_scim_user(cptestctx: &ControlPlaneTestContext) { // Create the Silo const SILO_NAME: &str = "saml-scim-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlScim) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlScim) .await; // Create a SAML IDP - let _silo_saml_idp: views::SamlIdentityProvider = object_create( - client, - &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), - ¶ms::SamlIdentityProviderCreate { - identity: IdentityMetadataCreateParams { - name: "some-totally-real-saml-provider" - .to_string() - .parse() - .unwrap(), - description: "a demo provider".to_string(), - }, - - idp_metadata_source: params::IdpMetadataSource::Base64EncodedXml { - data: base64::engine::general_purpose::STANDARD - .encode(SAML_RESPONSE_IDP_DESCRIPTOR), + let _silo_saml_idp: identity_provider::SamlIdentityProvider = + object_create( + client, + &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), + &identity_provider::SamlIdentityProviderCreate { + identity: IdentityMetadataCreateParams { + name: "some-totally-real-saml-provider" + .to_string() + .parse() + .unwrap(), + description: "a demo provider".to_string(), + }, + + idp_metadata_source: + identity_provider::IdpMetadataSource::Base64EncodedXml { + data: base64::engine::general_purpose::STANDARD + .encode(SAML_RESPONSE_IDP_DESCRIPTOR), + }, + + idp_entity_id: "https://some.idp.test/oxide_rack/".to_string(), + sp_client_id: "client_id".to_string(), + acs_url: "https://customer.site/oxide_rack/saml".to_string(), + slo_url: "https://customer.site/oxide_rack/saml".to_string(), + technical_contact_email: "technical@fake".to_string(), + + signing_keypair: None, + + group_attribute_name: Some("groups".into()), }, - - idp_entity_id: "https://some.idp.test/oxide_rack/".to_string(), - sp_client_id: "client_id".to_string(), - acs_url: "https://customer.site/oxide_rack/saml".to_string(), - slo_url: "https://customer.site/oxide_rack/saml".to_string(), - technical_contact_email: "technical@fake".to_string(), - - signing_keypair: None, - - group_attribute_name: Some("groups".into()), - }, - ) - .await; + ) + .await; nexus.set_samael_max_issue_delay( chrono::Utc::now() @@ -646,7 +650,7 @@ async fn test_disabling_scim_user(cptestctx: &ControlPlaneTestContext) { grant_iam( client, &format!("/v1/system/silos/{SILO_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, opctx.authn.actor().unwrap().silo_user_id().unwrap(), AuthnMode::PrivilegedUser, ) @@ -654,7 +658,7 @@ async fn test_disabling_scim_user(cptestctx: &ControlPlaneTestContext) { // Create a token - let created_token: views::ScimClientBearerTokenValue = + let created_token: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={}", SILO_NAME,), @@ -715,7 +719,7 @@ async fn test_disabling_scim_user(cptestctx: &ControlPlaneTestContext) { let session_cookie_value = result.headers["Set-Cookie"].to_str().unwrap().to_string(); - let me: views::CurrentUser = NexusRequest::new( + let me: user::CurrentUser = NexusRequest::new( RequestBuilder::new(client, Method::GET, "/v1/me") .header(http::header::COOKIE, session_cookie_value.clone()) .expect_status(Some(StatusCode::OK)), @@ -816,7 +820,7 @@ async fn test_scim_user_search(cptestctx: &ControlPlaneTestContext) { // Create the Silo const SILO_NAME: &str = "saml-scim-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlScim) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlScim) .await; // Grant permissions on this silo for the PrivilegedUser @@ -824,7 +828,7 @@ async fn test_scim_user_search(cptestctx: &ControlPlaneTestContext) { grant_iam( client, &format!("/v1/system/silos/{SILO_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, opctx.authn.actor().unwrap().silo_user_id().unwrap(), AuthnMode::PrivilegedUser, ) @@ -832,7 +836,7 @@ async fn test_scim_user_search(cptestctx: &ControlPlaneTestContext) { // Create a token - let created_token: views::ScimClientBearerTokenValue = + let created_token: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={}", SILO_NAME,), @@ -994,7 +998,7 @@ async fn test_scim_group_search(cptestctx: &ControlPlaneTestContext) { // Create the Silo const SILO_NAME: &str = "saml-scim-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlScim) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlScim) .await; // Grant permissions on this silo for the PrivilegedUser @@ -1002,7 +1006,7 @@ async fn test_scim_group_search(cptestctx: &ControlPlaneTestContext) { grant_iam( client, &format!("/v1/system/silos/{SILO_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, opctx.authn.actor().unwrap().silo_user_id().unwrap(), AuthnMode::PrivilegedUser, ) @@ -1010,7 +1014,7 @@ async fn test_scim_group_search(cptestctx: &ControlPlaneTestContext) { // Create a token - let created_token: views::ScimClientBearerTokenValue = + let created_token: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={}", SILO_NAME,), @@ -1181,7 +1185,7 @@ async fn test_scim_user_unique(cptestctx: &ControlPlaneTestContext) { // Create the Silo const SILO_NAME: &str = "saml-scim-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlScim) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlScim) .await; // Grant permissions on this silo for the PrivilegedUser @@ -1189,7 +1193,7 @@ async fn test_scim_user_unique(cptestctx: &ControlPlaneTestContext) { grant_iam( client, &format!("/v1/system/silos/{SILO_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, opctx.authn.actor().unwrap().silo_user_id().unwrap(), AuthnMode::PrivilegedUser, ) @@ -1197,7 +1201,7 @@ async fn test_scim_user_unique(cptestctx: &ControlPlaneTestContext) { // Create a token - let created_token: views::ScimClientBearerTokenValue = + let created_token: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={}", SILO_NAME,), @@ -1323,7 +1327,7 @@ async fn test_scim_group_unique(cptestctx: &ControlPlaneTestContext) { // Create the Silo const SILO_NAME: &str = "saml-scim-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlScim) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlScim) .await; // Grant permissions on this silo for the PrivilegedUser @@ -1331,7 +1335,7 @@ async fn test_scim_group_unique(cptestctx: &ControlPlaneTestContext) { grant_iam( client, &format!("/v1/system/silos/{SILO_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, opctx.authn.actor().unwrap().silo_user_id().unwrap(), AuthnMode::PrivilegedUser, ) @@ -1339,7 +1343,7 @@ async fn test_scim_group_unique(cptestctx: &ControlPlaneTestContext) { // Create a token - let created_token: views::ScimClientBearerTokenValue = + let created_token: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={}", SILO_NAME,), @@ -1469,42 +1473,44 @@ async fn test_scim_user_admin_group_priv(cptestctx: &ControlPlaneTestContext) { &client, SILO_NAME, true, - shared::SiloIdentityMode::SamlScim, + silo::SiloIdentityMode::SamlScim, Some(String::from("scranton_admins")), ) .await; // Create a SAML IDP - let _silo_saml_idp: views::SamlIdentityProvider = object_create( - client, - &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), - ¶ms::SamlIdentityProviderCreate { - identity: IdentityMetadataCreateParams { - name: "some-totally-real-saml-provider" - .to_string() - .parse() - .unwrap(), - description: "a demo provider".to_string(), - }, - - idp_metadata_source: params::IdpMetadataSource::Base64EncodedXml { - data: base64::engine::general_purpose::STANDARD - .encode(SAML_RESPONSE_IDP_DESCRIPTOR), + let _silo_saml_idp: identity_provider::SamlIdentityProvider = + object_create( + client, + &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), + &identity_provider::SamlIdentityProviderCreate { + identity: IdentityMetadataCreateParams { + name: "some-totally-real-saml-provider" + .to_string() + .parse() + .unwrap(), + description: "a demo provider".to_string(), + }, + + idp_metadata_source: + identity_provider::IdpMetadataSource::Base64EncodedXml { + data: base64::engine::general_purpose::STANDARD + .encode(SAML_RESPONSE_IDP_DESCRIPTOR), + }, + + idp_entity_id: "https://some.idp.test/oxide_rack/".to_string(), + sp_client_id: "client_id".to_string(), + acs_url: "https://customer.site/oxide_rack/saml".to_string(), + slo_url: "https://customer.site/oxide_rack/saml".to_string(), + technical_contact_email: "technical@fake".to_string(), + + signing_keypair: None, + + group_attribute_name: Some("groups".into()), }, - - idp_entity_id: "https://some.idp.test/oxide_rack/".to_string(), - sp_client_id: "client_id".to_string(), - acs_url: "https://customer.site/oxide_rack/saml".to_string(), - slo_url: "https://customer.site/oxide_rack/saml".to_string(), - technical_contact_email: "technical@fake".to_string(), - - signing_keypair: None, - - group_attribute_name: Some("groups".into()), - }, - ) - .await; + ) + .await; nexus.set_samael_max_issue_delay( chrono::Utc::now() @@ -1519,7 +1525,7 @@ async fn test_scim_user_admin_group_priv(cptestctx: &ControlPlaneTestContext) { grant_iam( client, &format!("/v1/system/silos/{SILO_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, opctx.authn.actor().unwrap().silo_user_id().unwrap(), AuthnMode::PrivilegedUser, ) @@ -1527,7 +1533,7 @@ async fn test_scim_user_admin_group_priv(cptestctx: &ControlPlaneTestContext) { // Create a token - let created_token: views::ScimClientBearerTokenValue = + let created_token: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={}", SILO_NAME,), @@ -1593,7 +1599,7 @@ async fn test_scim_user_admin_group_priv(cptestctx: &ControlPlaneTestContext) { // Initially this user should _not_ have the silo admin role, they are not // part of any group. - let me: views::CurrentUser = NexusRequest::new( + let me: user::CurrentUser = NexusRequest::new( RequestBuilder::new(client, Method::GET, "/v1/me") .header(http::header::COOKIE, session_cookie_value.clone()) .expect_status(Some(StatusCode::OK)), @@ -1635,7 +1641,7 @@ async fn test_scim_user_admin_group_priv(cptestctx: &ControlPlaneTestContext) { .parsed_body() .expect("created group"); - let me: views::CurrentUser = NexusRequest::new( + let me: user::CurrentUser = NexusRequest::new( RequestBuilder::new(client, Method::GET, "/v1/me") .header(http::header::COOKIE, session_cookie_value.clone()) .expect_status(Some(StatusCode::OK)), @@ -1690,7 +1696,7 @@ async fn test_scim_user_admin_group_priv(cptestctx: &ControlPlaneTestContext) { .await .expect("expected 200"); - let me: views::CurrentUser = NexusRequest::new( + let me: user::CurrentUser = NexusRequest::new( RequestBuilder::new(client, Method::GET, "/v1/me") .header(http::header::COOKIE, session_cookie_value.clone()) .expect_status(Some(StatusCode::OK)), @@ -1743,7 +1749,7 @@ async fn test_scim_user_admin_group_priv(cptestctx: &ControlPlaneTestContext) { .await .expect("expected 200"); - let me: views::CurrentUser = NexusRequest::new( + let me: user::CurrentUser = NexusRequest::new( RequestBuilder::new(client, Method::GET, "/v1/me") .header(http::header::COOKIE, session_cookie_value.clone()) .expect_status(Some(StatusCode::OK)), @@ -1796,7 +1802,7 @@ async fn test_scim_user_admin_group_priv(cptestctx: &ControlPlaneTestContext) { .await .expect("expected 200"); - let me: views::CurrentUser = NexusRequest::new( + let me: user::CurrentUser = NexusRequest::new( RequestBuilder::new(client, Method::GET, "/v1/me") .header(http::header::COOKIE, session_cookie_value.clone()) .expect_status(Some(StatusCode::OK)), @@ -1850,7 +1856,7 @@ async fn test_scim_user_admin_group_priv(cptestctx: &ControlPlaneTestContext) { .await .expect("expected 200"); - let me: views::CurrentUser = NexusRequest::new( + let me: user::CurrentUser = NexusRequest::new( RequestBuilder::new(client, Method::GET, "/v1/me") .header(http::header::COOKIE, session_cookie_value.clone()) .expect_status(Some(StatusCode::OK)), @@ -1884,7 +1890,7 @@ async fn test_scim_user_admin_group_priv_conflict( &client, SILO_NAME, true, - shared::SiloIdentityMode::SamlScim, + silo::SiloIdentityMode::SamlScim, Some(String::from("assistant_to_assistant_to_regional_manager")), ) .await; @@ -1894,7 +1900,7 @@ async fn test_scim_user_admin_group_priv_conflict( grant_iam( client, &format!("/v1/system/silos/{SILO_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, opctx.authn.actor().unwrap().silo_user_id().unwrap(), AuthnMode::PrivilegedUser, ) @@ -1902,7 +1908,7 @@ async fn test_scim_user_admin_group_priv_conflict( // Create a token - let created_token: views::ScimClientBearerTokenValue = + let created_token: scim::ScimClientBearerTokenValue = object_create_no_body( client, &format!("/v1/system/scim/tokens?silo={}", SILO_NAME,), @@ -1943,7 +1949,7 @@ async fn test_scim_user_admin_group_priv_conflict( grant_iam_for_group( client, &format!("/v1/system/silos/{SILO_NAME}"), - shared::SiloRole::Admin, + policy::SiloRole::Admin, SiloGroupUuid::from_untyped_uuid(group.id.parse().unwrap()), AuthnMode::PrivilegedUser, ) diff --git a/nexus/tests/integration_tests/silo_users.rs b/nexus/tests/integration_tests/silo_users.rs index 564e58a7301..ec33684df98 100644 --- a/nexus/tests/integration_tests/silo_users.rs +++ b/nexus/tests/integration_tests/silo_users.rs @@ -12,7 +12,7 @@ use nexus_test_utils::assert_same_items; use nexus_test_utils::http_testing::{AuthnMode, NexusRequest}; use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::views; +use nexus_types::external_api::user; use nexus_types::identity::Asset; use nexus_types::silo::DEFAULT_SILO_ID; use omicron_common::api::external::LookupType; @@ -36,14 +36,14 @@ async fn test_silo_group_users(cptestctx: &ControlPlaneTestContext) { // we start out with the two default users let users = - objects_list_page_authz::(client, &"/v1/users").await; + objects_list_page_authz::(client, &"/v1/users").await; let user_names: Vec<&str> = users.items.iter().map(|u| u.display_name.as_str()).collect(); assert_same_items(user_names, vec!["privileged", "unprivileged"]); // no groups to start with let groups = - objects_list_page_authz::(client, &"/v1/groups").await; + objects_list_page_authz::(client, &"/v1/groups").await; assert_eq!(groups.items.len(), 0); let authz_silo = authz::Silo::new( @@ -71,7 +71,7 @@ async fn test_silo_group_users(cptestctx: &ControlPlaneTestContext) { // now we have a group let groups = - objects_list_page_authz::(client, &"/v1/groups").await; + objects_list_page_authz::(client, &"/v1/groups").await; assert_eq!(groups.items.len(), 1); let group = groups.items.get(0).unwrap(); @@ -81,7 +81,7 @@ async fn test_silo_group_users(cptestctx: &ControlPlaneTestContext) { let group_url = format!("/v1/groups/{}", group.id); let group = NexusRequest::object_get(&client, &group_url) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; assert_eq!(group.display_name, group_name); @@ -89,7 +89,7 @@ async fn test_silo_group_users(cptestctx: &ControlPlaneTestContext) { // we can now fetch the group's user list and get an empty list of users let group_users = - objects_list_page_authz::(client, &group_users_url).await; + objects_list_page_authz::(client, &group_users_url).await; assert_eq!(group_users.items.len(), 0); @@ -112,7 +112,7 @@ async fn test_silo_group_users(cptestctx: &ControlPlaneTestContext) { .expect("Failed to set user group memberships"); let group_users = - objects_list_page_authz::(client, &group_users_url).await; + objects_list_page_authz::(client, &group_users_url).await; let user_ids = group_users.items.iter().map(|g| g.id).collect(); assert_same_items(user_ids, vec![USER_TEST_UNPRIVILEGED.id()]); diff --git a/nexus/tests/integration_tests/silos.rs b/nexus/tests/integration_tests/silos.rs index a2866c5f506..d48de6e3575 100644 --- a/nexus/tests/integration_tests/silos.rs +++ b/nexus/tests/integration_tests/silos.rs @@ -23,11 +23,12 @@ use nexus_test_utils::resource_helpers::{ projects_list, test_params, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::views::Certificate; -use nexus_types::external_api::views::{ - self, IdentityProvider, Project, SamlIdentityProvider, Silo, -}; -use nexus_types::external_api::{params, shared}; +use nexus_types::external_api::certificate; +use nexus_types::external_api::identity_provider; +use nexus_types::external_api::ip_pool; +use nexus_types::external_api::project; +use nexus_types::external_api::silo; +use nexus_types::external_api::user; use nexus_types::silo::DEFAULT_SILO_ID; use omicron_common::address::{IpRange, Ipv4Range}; use omicron_common::api::external::{ @@ -48,7 +49,7 @@ use hickory_resolver::proto::ProtoErrorKind; use http::StatusCode; use http::method::Method; use httptest::{Expectation, Server, matchers::*, responders::*}; -use nexus_types::external_api::shared::{FleetRole, SiloRole}; +use nexus_types::external_api::policy::{FleetRole, SiloRole}; use std::convert::Infallible; use std::net::Ipv4Addr; use std::time::Duration; @@ -69,14 +70,14 @@ async fn test_silos(cptestctx: &ControlPlaneTestContext) { StatusCode::BAD_REQUEST, Method::POST, "/v1/system/silos", - ¶ms::SiloCreate { + &silo::SiloCreate { identity: IdentityMetadataCreateParams { name: cptestctx.silo_name.clone(), description: "a silo".to_string(), }, - quotas: params::SiloQuotasCreate::empty(), + quotas: silo::SiloQuotasCreate::empty(), discoverable: false, - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: silo::SiloIdentityMode::LocalOnly, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: Default::default(), @@ -95,10 +96,10 @@ async fn test_silos(cptestctx: &ControlPlaneTestContext) { &client, "discoverable", true, - shared::SiloIdentityMode::LocalOnly, + silo::SiloIdentityMode::LocalOnly, ) .await; - create_silo(&client, "hidden", false, shared::SiloIdentityMode::LocalOnly) + create_silo(&client, "hidden", false, silo::SiloIdentityMode::LocalOnly) .await; // Verify that an external DNS name was propagated for these Silos. @@ -109,7 +110,7 @@ async fn test_silos(cptestctx: &ControlPlaneTestContext) { let discoverable_url = "/v1/system/silos/discoverable"; let hidden_url = "/v1/system/silos/hidden"; - let silo: Silo = NexusRequest::object_get(&client, &discoverable_url) + let silo: silo::Silo = NexusRequest::object_get(&client, &discoverable_url) .authn_as(AuthnMode::PrivilegedUser) .execute() .await @@ -118,7 +119,7 @@ async fn test_silos(cptestctx: &ControlPlaneTestContext) { .unwrap(); assert_eq!(silo.identity.name, "discoverable"); - let silo: Silo = NexusRequest::object_get(&client, &hidden_url) + let silo: silo::Silo = NexusRequest::object_get(&client, &hidden_url) .authn_as(AuthnMode::PrivilegedUser) .execute() .await @@ -141,7 +142,9 @@ async fn test_silos(cptestctx: &ControlPlaneTestContext) { // Verify GET /v1/system/silos only returns discoverable silos let silos = - objects_list_page_authz::(client, "/v1/system/silos").await.items; + objects_list_page_authz::(client, "/v1/system/silos") + .await + .items; assert_eq!(silos.len(), 1); assert_eq!(silos[0].identity.name, "discoverable"); @@ -183,7 +186,7 @@ async fn test_silos(cptestctx: &ControlPlaneTestContext) { // default silo project does not show up in our silo let projects_in_our_silo = NexusRequest::object_get(client, "/v1/projects") .authn_as(AuthnMode::SiloUser(new_silo_user_id)) - .execute_and_parse_unwrap::>() + .execute_and_parse_unwrap::>() .await; assert_eq!(projects_in_our_silo.items.len(), 0); @@ -192,7 +195,7 @@ async fn test_silos(cptestctx: &ControlPlaneTestContext) { let new_proj_in_our_silo = NexusRequest::objects_post( client, "/v1/projects", - ¶ms::ProjectCreate { + &project::ProjectCreate { identity: IdentityMetadataCreateParams { name: project_name.parse().unwrap(), description: String::new(), @@ -203,7 +206,7 @@ async fn test_silos(cptestctx: &ControlPlaneTestContext) { .execute() .await .expect("failed to create same-named Project in a different Silo") - .parsed_body::() + .parsed_body::() .expect("failed to parse new Project"); assert_eq!( new_proj_in_default_silo.identity.name, @@ -283,17 +286,17 @@ async fn test_silo_admin_group(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; let nexus = &cptestctx.server.server_context().nexus; - let silo: Silo = object_create( + let silo: silo::Silo = object_create( client, "/v1/system/silos", - ¶ms::SiloCreate { + &silo::SiloCreate { identity: IdentityMetadataCreateParams { name: "silo-name".parse().unwrap(), description: "a silo".to_string(), }, - quotas: params::SiloQuotasCreate::empty(), + quotas: silo::SiloQuotasCreate::empty(), discoverable: false, - identity_mode: shared::SiloIdentityMode::SamlJit, + identity_mode: silo::SiloIdentityMode::SamlJit, admin_group_name: Some("administrator".into()), tls_certificates: vec![], mapped_fleet_roles: Default::default(), @@ -353,7 +356,7 @@ async fn test_silo_admin_group(cptestctx: &ControlPlaneTestContext) { let _org = NexusRequest::objects_post( client, "/v1/projects", - ¶ms::ProjectCreate { + &project::ProjectCreate { identity: IdentityMetadataCreateParams { name: "myproj".parse().unwrap(), description: "some proj".into(), @@ -364,7 +367,7 @@ async fn test_silo_admin_group(cptestctx: &ControlPlaneTestContext) { .execute() .await .expect("failed to create Project") - .parsed_body::() + .parsed_body::() .expect("failed to parse as Project"); } @@ -372,13 +375,14 @@ async fn test_silo_admin_group(cptestctx: &ControlPlaneTestContext) { #[nexus_test] async fn test_listing_identity_providers(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; - create_silo(&client, "test-silo", true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, "test-silo", true, silo::SiloIdentityMode::SamlJit) .await; // List providers - should be none - let providers = objects_list_page_authz::( - client, - "/v1/system/identity-providers?silo=test-silo", + let providers = objects_list_page_authz::< + identity_provider::IdentityProvider, + >( + client, "/v1/system/identity-providers?silo=test-silo" ) .await .items; @@ -395,68 +399,73 @@ async fn test_listing_identity_providers(cptestctx: &ControlPlaneTestContext) { .respond_with(status_code(200).body(saml_idp_descriptor)), ); - let silo_saml_idp_1: SamlIdentityProvider = object_create( - client, - &"/v1/system/identity-providers/saml?silo=test-silo", - ¶ms::SamlIdentityProviderCreate { - identity: IdentityMetadataCreateParams { - name: "some-totally-real-saml-provider" - .to_string() - .parse() - .unwrap(), - description: "a demo provider".to_string(), - }, - - idp_metadata_source: params::IdpMetadataSource::Url { - url: server.url("/descriptor").to_string(), - }, + let silo_saml_idp_1: identity_provider::SamlIdentityProvider = + object_create( + client, + &"/v1/system/identity-providers/saml?silo=test-silo", + &identity_provider::SamlIdentityProviderCreate { + identity: IdentityMetadataCreateParams { + name: "some-totally-real-saml-provider" + .to_string() + .parse() + .unwrap(), + description: "a demo provider".to_string(), + }, - idp_entity_id: "entity_id".to_string(), - sp_client_id: "client_id".to_string(), - acs_url: "http://acs".to_string(), - slo_url: "http://slo".to_string(), - technical_contact_email: "technical@fake".to_string(), + idp_metadata_source: + identity_provider::IdpMetadataSource::Url { + url: server.url("/descriptor").to_string(), + }, - signing_keypair: None, + idp_entity_id: "entity_id".to_string(), + sp_client_id: "client_id".to_string(), + acs_url: "http://acs".to_string(), + slo_url: "http://slo".to_string(), + technical_contact_email: "technical@fake".to_string(), - group_attribute_name: None, - }, - ) - .await; + signing_keypair: None, - let silo_saml_idp_2: SamlIdentityProvider = object_create( - client, - &"/v1/system/identity-providers/saml?silo=test-silo", - ¶ms::SamlIdentityProviderCreate { - identity: IdentityMetadataCreateParams { - name: "another-totally-real-saml-provider" - .to_string() - .parse() - .unwrap(), - description: "a demo provider".to_string(), + group_attribute_name: None, }, + ) + .await; - idp_metadata_source: params::IdpMetadataSource::Url { - url: server.url("/descriptor").to_string(), - }, + let silo_saml_idp_2: identity_provider::SamlIdentityProvider = + object_create( + client, + &"/v1/system/identity-providers/saml?silo=test-silo", + &identity_provider::SamlIdentityProviderCreate { + identity: IdentityMetadataCreateParams { + name: "another-totally-real-saml-provider" + .to_string() + .parse() + .unwrap(), + description: "a demo provider".to_string(), + }, - idp_entity_id: "entity_id".to_string(), - sp_client_id: "client_id".to_string(), - acs_url: "http://acs".to_string(), - slo_url: "http://slo".to_string(), - technical_contact_email: "technical@fake".to_string(), + idp_metadata_source: + identity_provider::IdpMetadataSource::Url { + url: server.url("/descriptor").to_string(), + }, - signing_keypair: None, + idp_entity_id: "entity_id".to_string(), + sp_client_id: "client_id".to_string(), + acs_url: "http://acs".to_string(), + slo_url: "http://slo".to_string(), + technical_contact_email: "technical@fake".to_string(), - group_attribute_name: None, - }, - ) - .await; + signing_keypair: None, + + group_attribute_name: None, + }, + ) + .await; // List providers again - expect 2 - let providers = objects_list_page_authz::( - client, - "/v1/system/identity-providers?silo=test-silo", + let providers = objects_list_page_authz::< + identity_provider::IdentityProvider, + >( + client, "/v1/system/identity-providers?silo=test-silo" ) .await .items; @@ -477,7 +486,7 @@ async fn test_deleting_a_silo_deletes_the_idp( let client = &cptestctx.external_client; const SILO_NAME: &str = "test-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; let saml_idp_descriptor = SAML_IDP_DESCRIPTOR; @@ -488,10 +497,10 @@ async fn test_deleting_a_silo_deletes_the_idp( .respond_with(status_code(200).body(saml_idp_descriptor)), ); - let silo_saml_idp: SamlIdentityProvider = object_create( + let silo_saml_idp: identity_provider::SamlIdentityProvider = object_create( client, &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), - ¶ms::SamlIdentityProviderCreate { + &identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -500,7 +509,7 @@ async fn test_deleting_a_silo_deletes_the_idp( description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Url { + idp_metadata_source: identity_provider::IdpMetadataSource::Url { url: server.url("/descriptor").to_string(), }, @@ -588,13 +597,13 @@ async fn test_saml_idp_metadata_data_valid( ) { let client = &cptestctx.external_client; - create_silo(&client, "blahblah", true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, "blahblah", true, silo::SiloIdentityMode::SamlJit) .await; - let silo_saml_idp: SamlIdentityProvider = object_create( + let silo_saml_idp: identity_provider::SamlIdentityProvider = object_create( client, "/v1/system/identity-providers/saml?silo=blahblah", - ¶ms::SamlIdentityProviderCreate { + &identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -603,10 +612,11 @@ async fn test_saml_idp_metadata_data_valid( description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Base64EncodedXml { - data: base64::engine::general_purpose::STANDARD - .encode(SAML_IDP_DESCRIPTOR), - }, + idp_metadata_source: + identity_provider::IdpMetadataSource::Base64EncodedXml { + data: base64::engine::general_purpose::STANDARD + .encode(SAML_IDP_DESCRIPTOR), + }, idp_entity_id: "entity_id".to_string(), sp_client_id: "client_id".to_string(), @@ -651,7 +661,7 @@ async fn test_saml_idp_metadata_data_truncated( ) { let client = &cptestctx.external_client; - create_silo(&client, "blahblah", true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, "blahblah", true, silo::SiloIdentityMode::SamlJit) .await; NexusRequest::new( @@ -660,7 +670,7 @@ async fn test_saml_idp_metadata_data_truncated( Method::POST, "/v1/system/identity-providers/saml?silo=blahblah", ) - .body(Some(¶ms::SamlIdentityProviderCreate { + .body(Some(&identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -669,14 +679,15 @@ async fn test_saml_idp_metadata_data_truncated( description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Base64EncodedXml { - data: base64::engine::general_purpose::STANDARD.encode({ - let mut saml_idp_descriptor = - SAML_IDP_DESCRIPTOR.to_string(); - saml_idp_descriptor.truncate(100); - saml_idp_descriptor - }), - }, + idp_metadata_source: + identity_provider::IdpMetadataSource::Base64EncodedXml { + data: base64::engine::general_purpose::STANDARD.encode({ + let mut saml_idp_descriptor = + SAML_IDP_DESCRIPTOR.to_string(); + saml_idp_descriptor.truncate(100); + saml_idp_descriptor + }), + }, idp_entity_id: "entity_id".to_string(), sp_client_id: "client_id".to_string(), @@ -704,7 +715,7 @@ async fn test_saml_idp_metadata_data_invalid( let client = &cptestctx.external_client; const SILO_NAME: &str = "saml-silo"; - create_silo(&client, SILO_NAME, true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, SILO_NAME, true, silo::SiloIdentityMode::SamlJit) .await; NexusRequest::new( @@ -713,7 +724,7 @@ async fn test_saml_idp_metadata_data_invalid( Method::POST, &format!("/v1/system/identity-providers/saml?silo={}", SILO_NAME), ) - .body(Some(¶ms::SamlIdentityProviderCreate { + .body(Some(&identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -722,9 +733,10 @@ async fn test_saml_idp_metadata_data_invalid( description: "a demo provider".to_string(), }, - idp_metadata_source: params::IdpMetadataSource::Base64EncodedXml { - data: "bad data".to_string(), - }, + idp_metadata_source: + identity_provider::IdpMetadataSource::Base64EncodedXml { + data: "bad data".to_string(), + }, idp_entity_id: "entity_id".to_string(), sp_client_id: "client_id".to_string(), @@ -745,7 +757,7 @@ async fn test_saml_idp_metadata_data_invalid( } struct TestSiloUserProvisionTypes { - identity_mode: shared::SiloIdentityMode, + identity_mode: silo::SiloIdentityMode, existing_silo_user: bool, expect_user: bool, } @@ -760,42 +772,42 @@ async fn test_silo_user_provision_types(cptestctx: &ControlPlaneTestContext) { // A silo configured with a "ApiOnly" user provision type should fetch a // user if it exists already. TestSiloUserProvisionTypes { - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: silo::SiloIdentityMode::LocalOnly, existing_silo_user: true, expect_user: true, }, // A silo configured with a "ApiOnly" user provision type should not // create a user if one does not exist already. TestSiloUserProvisionTypes { - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: silo::SiloIdentityMode::LocalOnly, existing_silo_user: false, expect_user: false, }, // A silo configured with a "JIT" user provision type should fetch a // user if it exists already. TestSiloUserProvisionTypes { - identity_mode: shared::SiloIdentityMode::SamlJit, + identity_mode: silo::SiloIdentityMode::SamlJit, existing_silo_user: true, expect_user: true, }, // A silo configured with a "JIT" user provision type should create a // user if one does not exist already. TestSiloUserProvisionTypes { - identity_mode: shared::SiloIdentityMode::SamlJit, + identity_mode: silo::SiloIdentityMode::SamlJit, existing_silo_user: false, expect_user: true, }, // A silo configured with a "SCIM" user provision type should fetch a // user if it exists already. TestSiloUserProvisionTypes { - identity_mode: shared::SiloIdentityMode::SamlScim, + identity_mode: silo::SiloIdentityMode::SamlScim, existing_silo_user: true, expect_user: true, }, // A silo configured with a "SCIM" user provision type should not do any // user management except via the SCIM provisioning client. TestSiloUserProvisionTypes { - identity_mode: shared::SiloIdentityMode::SamlScim, + identity_mode: silo::SiloIdentityMode::SamlScim, existing_silo_user: false, expect_user: false, }, @@ -808,11 +820,11 @@ async fn test_silo_user_provision_types(cptestctx: &ControlPlaneTestContext) { if test_case.existing_silo_user { match test_case.identity_mode { - shared::SiloIdentityMode::SamlJit => { + silo::SiloIdentityMode::SamlJit => { create_jit_user(datastore, &silo, "external-id-com").await; } - shared::SiloIdentityMode::LocalOnly => { + silo::SiloIdentityMode::LocalOnly => { create_local_user( client, &silo, @@ -822,7 +834,7 @@ async fn test_silo_user_provision_types(cptestctx: &ControlPlaneTestContext) { .await; } - shared::SiloIdentityMode::SamlScim => { + silo::SiloIdentityMode::SamlScim => { create_scim_user(datastore, &silo, "external-id-com").await; } }; @@ -874,7 +886,7 @@ async fn test_silo_user_fetch_by_external_id( &client, "test-silo", true, - shared::SiloIdentityMode::LocalOnly, + silo::SiloIdentityMode::LocalOnly, ) .await; @@ -930,7 +942,7 @@ async fn test_silo_user_fetch_by_external_id( async fn test_silo_users_list(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; - let initial_silo_users: Vec = + let initial_silo_users: Vec = NexusRequest::iter_collection_authn(client, "/v1/users", "", None) .await .expect("failed to list silo users (1)") @@ -941,12 +953,12 @@ async fn test_silo_users_list(cptestctx: &ControlPlaneTestContext) { assert_eq!( initial_silo_users, vec![ - views::User { + user::User { id: USER_TEST_PRIVILEGED.id(), display_name: USER_TEST_PRIVILEGED.external_id.clone().unwrap(), silo_id: DEFAULT_SILO_ID, }, - views::User { + user::User { id: USER_TEST_UNPRIVILEGED.id(), display_name: USER_TEST_UNPRIVILEGED .external_id @@ -962,14 +974,14 @@ async fn test_silo_users_list(cptestctx: &ControlPlaneTestContext) { let new_silo_user_external_id = "can-we-see-them"; let new_silo_user_id = create_local_user( client, - &views::Silo::try_from(DEFAULT_SILO.clone()).unwrap(), + &silo::Silo::try_from(DEFAULT_SILO.clone()).unwrap(), &new_silo_user_external_id.parse().unwrap(), test_params::UserPassword::LoginDisallowed, ) .await .id; - let mut silo_users: Vec = + let mut silo_users: Vec = NexusRequest::iter_collection_authn(client, "/v1/users", "", Some(1)) .await .expect("failed to list silo users (2)") @@ -978,17 +990,17 @@ async fn test_silo_users_list(cptestctx: &ControlPlaneTestContext) { assert_eq!( silo_users, vec![ - views::User { + user::User { id: new_silo_user_id, display_name: new_silo_user_external_id.into(), silo_id: DEFAULT_SILO_ID, }, - views::User { + user::User { id: USER_TEST_PRIVILEGED.id(), display_name: USER_TEST_PRIVILEGED.external_id.clone().unwrap(), silo_id: DEFAULT_SILO_ID, }, - views::User { + user::User { id: USER_TEST_UNPRIVILEGED.id(), display_name: USER_TEST_UNPRIVILEGED .external_id @@ -1003,7 +1015,7 @@ async fn test_silo_users_list(cptestctx: &ControlPlaneTestContext) { // able to see the users in the first Silo. let silo = - create_silo(client, "silo2", true, shared::SiloIdentityMode::LocalOnly) + create_silo(client, "silo2", true, silo::SiloIdentityMode::LocalOnly) .await; let new_silo_user_name = String::from("some-silo-user"); @@ -1024,7 +1036,7 @@ async fn test_silo_users_list(cptestctx: &ControlPlaneTestContext) { ) .await; - let silo2_users: dropshot::ResultsPage = + let silo2_users: dropshot::ResultsPage = NexusRequest::object_get(client, "/v1/users") .authn_as(AuthnMode::SiloUser(new_silo_user_id)) .execute() @@ -1034,7 +1046,7 @@ async fn test_silo_users_list(cptestctx: &ControlPlaneTestContext) { .unwrap(); assert_eq!( silo2_users.items, - vec![views::User { + vec![user::User { id: new_silo_user_id, display_name: new_silo_user_name, silo_id: silo.identity.id, @@ -1043,7 +1055,7 @@ async fn test_silo_users_list(cptestctx: &ControlPlaneTestContext) { // The "test-privileged" user also shouldn't see the user in this other // Silo. - let mut new_silo_users: Vec = + let mut new_silo_users: Vec = NexusRequest::iter_collection_authn(client, "/v1/users", "", Some(1)) .await .expect("failed to list silo users (2)") @@ -1065,7 +1077,7 @@ async fn test_silo_groups_jit(cptestctx: &ControlPlaneTestContext) { &client, "test-silo", true, - shared::SiloIdentityMode::SamlJit, + silo::SiloIdentityMode::SamlJit, ) .await; @@ -1133,7 +1145,7 @@ async fn test_silo_groups_fixed(cptestctx: &ControlPlaneTestContext) { &client, "test-silo", true, - shared::SiloIdentityMode::LocalOnly, + silo::SiloIdentityMode::LocalOnly, ) .await; @@ -1194,7 +1206,7 @@ async fn test_silo_groups_remove_from_one_group( &client, "test-silo", true, - shared::SiloIdentityMode::SamlJit, + silo::SiloIdentityMode::SamlJit, ) .await; @@ -1307,7 +1319,7 @@ async fn test_silo_groups_remove_from_both_groups( &client, "test-silo", true, - shared::SiloIdentityMode::SamlJit, + silo::SiloIdentityMode::SamlJit, ) .await; @@ -1419,7 +1431,7 @@ async fn test_silo_delete_clean_up_groups(cptestctx: &ControlPlaneTestContext) { &client, "test-silo", true, - shared::SiloIdentityMode::SamlJit, + silo::SiloIdentityMode::SamlJit, ) .await; @@ -1502,7 +1514,7 @@ async fn test_ensure_same_silo_group(cptestctx: &ControlPlaneTestContext) { &client, "test-silo", true, - shared::SiloIdentityMode::SamlJit, + silo::SiloIdentityMode::SamlJit, ) .await; @@ -1605,15 +1617,11 @@ async fn test_silo_user_views(cptestctx: &ControlPlaneTestContext) { // Create the two Silos. let silo1 = - create_silo(client, "silo1", false, shared::SiloIdentityMode::SamlJit) + create_silo(client, "silo1", false, silo::SiloIdentityMode::SamlJit) + .await; + let silo2 = + create_silo(client, "silo2", false, silo::SiloIdentityMode::LocalOnly) .await; - let silo2 = create_silo( - client, - "silo2", - false, - shared::SiloIdentityMode::LocalOnly, - ) - .await; // Create two users in each Silo. We need two so that we can verify that an // ordinary user can see a user other than themselves in each Silo. @@ -1644,7 +1652,7 @@ async fn test_silo_user_views(cptestctx: &ControlPlaneTestContext) { silo2_expected_users.sort_by_key(|u| u.id); let users_by_id = { - let mut users_by_id: BTreeMap = + let mut users_by_id: BTreeMap = BTreeMap::new(); assert_eq!(users_by_id.insert(silo1_user1_id, &silo1_user1), None); assert_eq!(users_by_id.insert(silo1_user2_id, &silo1_user2), None); @@ -1685,8 +1693,8 @@ async fn test_silo_user_views(cptestctx: &ControlPlaneTestContext) { }; struct TestSilo<'a> { - silo: &'a views::Silo, - expected_users: [views::User; 2], + silo: &'a silo::Silo, + expected_users: [user::User; 2], } let test_silo1 = @@ -1732,7 +1740,7 @@ async fn test_silo_user_views(cptestctx: &ControlPlaneTestContext) { // this Silo. if test_response.status == http::StatusCode::OK { let found_users = test_response - .parsed_body::>() + .parsed_body::>() .unwrap() .items; assert_eq!(found_users, test_silo.expected_users); @@ -1765,7 +1773,7 @@ async fn test_silo_user_views(cptestctx: &ControlPlaneTestContext) { // If this succeeded, it must have returned the right user back. if test_response.status == http::StatusCode::OK { let found_user = - test_response.parsed_body::().unwrap(); + test_response.parsed_body::().unwrap(); assert_eq!( found_user.silo_id, test_silo.silo.identity().id @@ -1797,10 +1805,10 @@ async fn test_silo_user_views(cptestctx: &ControlPlaneTestContext) { /// For local-only Silos, use the real API (via `create_local_user()`). async fn create_jit_user( datastore: &db::DataStore, - silo: &views::Silo, + silo: &silo::Silo, external_id: &str, -) -> views::User { - assert_eq!(silo.identity_mode, shared::SiloIdentityMode::SamlJit); +) -> user::User { + assert_eq!(silo.identity_mode, silo::SiloIdentityMode::SamlJit); let silo_id = silo.identity.id; let silo_user_id = SiloUserUuid::new_v4(); let authz_silo = @@ -1818,10 +1826,10 @@ async fn create_jit_user( /// Create a user in a SamlScim Silo for testing async fn create_scim_user( datastore: &db::DataStore, - silo: &views::Silo, + silo: &silo::Silo, user_name: &str, -) -> views::User { - assert_eq!(silo.identity_mode, shared::SiloIdentityMode::SamlScim); +) -> user::User { + assert_eq!(silo.identity_mode, silo::SiloIdentityMode::SamlScim); let silo_id = silo.identity.id; let silo_user_id = SiloUserUuid::new_v4(); let authz_silo = @@ -1848,7 +1856,7 @@ async fn test_jit_silo_constraints(cptestctx: &ControlPlaneTestContext) { let nexus = &cptestctx.server.server_context().nexus; let datastore = nexus.datastore(); let silo = - create_silo(&client, "jit", true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, "jit", true, silo::SiloIdentityMode::SamlJit) .await; // We need one initial user that would in principle have privileges to @@ -1978,13 +1986,9 @@ async fn test_local_silo_constraints(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; // Create a "LocalOnly" Silo with its own admin user. - let silo = create_silo( - &client, - "fixed", - true, - shared::SiloIdentityMode::LocalOnly, - ) - .await; + let silo = + create_silo(&client, "fixed", true, silo::SiloIdentityMode::LocalOnly) + .await; let new_silo_user_id = create_local_user( client, &silo, @@ -2009,7 +2013,7 @@ async fn test_local_silo_constraints(cptestctx: &ControlPlaneTestContext) { StatusCode::BAD_REQUEST, Method::POST, "/v1/system/identity-providers/saml?silo=fixed", - ¶ms::SamlIdentityProviderCreate { + &identity_provider::SamlIdentityProviderCreate { identity: IdentityMetadataCreateParams { name: "some-totally-real-saml-provider" .to_string() @@ -2019,7 +2023,7 @@ async fn test_local_silo_constraints(cptestctx: &ControlPlaneTestContext) { }, idp_metadata_source: - params::IdpMetadataSource::Base64EncodedXml { + identity_provider::IdpMetadataSource::Base64EncodedXml { data: base64::engine::general_purpose::STANDARD .encode(SAML_IDP_DESCRIPTOR), }, @@ -2079,13 +2083,9 @@ async fn test_local_silo_users(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; // Create a "LocalOnly" Silo for testing. - let silo1 = create_silo( - &client, - "silo1", - true, - shared::SiloIdentityMode::LocalOnly, - ) - .await; + let silo1 = + create_silo(&client, "silo1", true, silo::SiloIdentityMode::LocalOnly) + .await; // We'll run through a battery of tests as each of two different users: the // usual "test-privileged" user (which should have full access because @@ -2121,9 +2121,9 @@ async fn test_local_silo_users(cptestctx: &ControlPlaneTestContext) { /// Runs a sequence of tests for create, read, and delete of API-managed users async fn run_user_tests( client: &dropshot::test_util::ClientTestContext, - silo: &views::Silo, + silo: &silo::Silo, authn_mode: &AuthnMode, - existing_users: &[views::User], + existing_users: &[user::User], ) { let url_all_users = format!("/v1/system/users?silo={}", silo.identity.name); let url_local_idp_users = format!( @@ -2139,7 +2139,7 @@ async fn run_user_tests( .execute() .await .expect("failed to list users") - .parsed_body::>() + .parsed_body::>() .unwrap() .items; println!("users: {:?}", users); @@ -2158,7 +2158,7 @@ async fn run_user_tests( .execute() .await .expect("failed to create user") - .parsed_body::() + .parsed_body::() .unwrap(); assert_eq!(user_created.display_name, "a-test-user"); println!("created user: {:?}", user_created); @@ -2173,7 +2173,7 @@ async fn run_user_tests( .execute() .await .expect("failed to fetch user we just created") - .parsed_body::() + .parsed_body::() .unwrap(); assert_eq!(user_created, user_found); @@ -2183,7 +2183,7 @@ async fn run_user_tests( .execute() .await .expect("failed to list users") - .parsed_body::>() + .parsed_body::>() .unwrap() .items; println!("new_users: {:?}", new_users); @@ -2234,7 +2234,7 @@ async fn run_user_tests( .execute() .await .expect("failed to list users") - .parsed_body::>() + .parsed_body::>() .unwrap() .items; println!("last_users: {:?}", last_users); @@ -2366,14 +2366,14 @@ async fn test_silo_authn_policy(cptestctx: &ControlPlaneTestContext) { let silo = NexusRequest::objects_post( client, "/v1/system/silos", - ¶ms::SiloCreate { + &silo::SiloCreate { identity: IdentityMetadataCreateParams { name: silo_name, description: String::new(), }, - quotas: params::SiloQuotasCreate::empty(), + quotas: silo::SiloQuotasCreate::empty(), discoverable: false, - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: silo::SiloIdentityMode::LocalOnly, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: policy, @@ -2383,7 +2383,7 @@ async fn test_silo_authn_policy(cptestctx: &ControlPlaneTestContext) { .execute() .await .unwrap() - .parsed_body::() + .parsed_body::() .unwrap(); // Create an administrator in this Silo. @@ -2443,14 +2443,14 @@ async fn check_fleet_privileges( // confers no Fleet-level roles). const URL_SILOS: &'static str = "/v1/system/silos"; const SILO_NAME: &'static str = "probe-silo"; - let body = params::SiloCreate { + let body = silo::SiloCreate { identity: IdentityMetadataCreateParams { name: SILO_NAME.parse().unwrap(), description: String::new(), }, - quotas: params::SiloQuotasCreate::empty(), + quotas: silo::SiloQuotasCreate::empty(), discoverable: false, - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: silo::SiloIdentityMode::LocalOnly, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: BTreeMap::new(), @@ -2472,14 +2472,14 @@ async fn check_fleet_privileges( NexusRequest::objects_post( client, URL_SILOS, - ¶ms::SiloCreate { + &silo::SiloCreate { identity: IdentityMetadataCreateParams { name: SILO_NAME.parse().unwrap(), description: String::new(), }, - quotas: params::SiloQuotasCreate::empty(), + quotas: silo::SiloQuotasCreate::empty(), discoverable: false, - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: silo::SiloIdentityMode::LocalOnly, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: BTreeMap::new(), @@ -2500,14 +2500,14 @@ async fn check_fleet_privileges( } // Last, see if the user can create a privileged Silo. - let body = params::SiloCreate { + let body = silo::SiloCreate { identity: IdentityMetadataCreateParams { name: SILO_NAME.parse().unwrap(), description: String::new(), }, - quotas: params::SiloQuotasCreate::empty(), + quotas: silo::SiloQuotasCreate::empty(), discoverable: false, - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: silo::SiloIdentityMode::LocalOnly, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: BTreeMap::from([( @@ -2533,14 +2533,14 @@ async fn check_fleet_privileges( NexusRequest::objects_post( client, URL_SILOS, - ¶ms::SiloCreate { + &silo::SiloCreate { identity: IdentityMetadataCreateParams { name: SILO_NAME.parse().unwrap(), description: String::new(), }, - quotas: params::SiloQuotasCreate::empty(), + quotas: silo::SiloQuotasCreate::empty(), discoverable: false, - identity_mode: shared::SiloIdentityMode::LocalOnly, + identity_mode: silo::SiloIdentityMode::LocalOnly, admin_group_name: None, tls_certificates: vec![], mapped_fleet_roles: BTreeMap::new(), @@ -2578,7 +2578,7 @@ async fn test_silo_admin_can_create_certs(cptestctx: &ControlPlaneTestContext) { client, "silo-name", true, - shared::SiloIdentityMode::LocalOnly, + silo::SiloIdentityMode::LocalOnly, ) .await; @@ -2605,17 +2605,17 @@ async fn test_silo_admin_can_create_certs(cptestctx: &ControlPlaneTestContext) { let (cert, key) = (chain.cert_chain_as_pem(), chain.end_cert_private_key_as_pem()); - let cert: Certificate = NexusRequest::objects_post( + let cert: certificate::Certificate = NexusRequest::objects_post( client, certs_url, - ¶ms::CertificateCreate { + &certificate::CertificateCreate { identity: IdentityMetadataCreateParams { name: "test-cert".parse().unwrap(), description: "the test cert".to_string(), }, cert, key, - service: shared::ServiceUsingCertificate::ExternalApi, + service: certificate::ServiceUsingCertificate::ExternalApi, }, ) .authn_as(AuthnMode::SiloUser(new_silo_user_id)) @@ -2632,8 +2632,10 @@ async fn test_silo_admin_can_create_certs(cptestctx: &ControlPlaneTestContext) { .execute() .await .expect("failed to list certificates") - .parsed_body::>() - .expect("failed to parse body as ResultsPage") + .parsed_body::>() + .expect( + "failed to parse body as ResultsPage", + ) .items; assert_eq!(silo_certs.len(), 1); @@ -2649,10 +2651,10 @@ async fn test_silo_delete_cleans_up_ip_pool_links( // Create a silo let silo1 = - create_silo(&client, "silo1", true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, "silo1", true, silo::SiloIdentityMode::SamlJit) .await; let silo2 = - create_silo(&client, "silo2", true, shared::SiloIdentityMode::SamlJit) + create_silo(&client, "silo2", true, silo::SiloIdentityMode::SamlJit) .await; // link pool1 to both, link pool2 to silo1 only @@ -2680,12 +2682,12 @@ async fn test_silo_delete_cleans_up_ip_pool_links( // we want to make sure the links are there before we make sure they're gone let url = "/v1/system/ip-pools/pool1/silos"; let links = - objects_list_page_authz::(client, &url).await; + objects_list_page_authz::(client, &url).await; assert_eq!(links.items.len(), 2); let url = "/v1/system/ip-pools/pool2/silos"; let links = - objects_list_page_authz::(client, &url).await; + objects_list_page_authz::(client, &url).await; assert_eq!(links.items.len(), 1); // Delete the silo @@ -2695,17 +2697,17 @@ async fn test_silo_delete_cleans_up_ip_pool_links( // Now make sure the links are gone let url = "/v1/system/ip-pools/pool1/silos"; let links = - objects_list_page_authz::(client, &url).await; + objects_list_page_authz::(client, &url).await; assert_eq!(links.items.len(), 1); let url = "/v1/system/ip-pools/pool2/silos"; let links = - objects_list_page_authz::(client, &url).await; + objects_list_page_authz::(client, &url).await; assert_eq!(links.items.len(), 0); // but the pools are of course still there let url = "/v1/system/ip-pools"; - let pools = objects_list_page_authz::(client, &url).await; + let pools = objects_list_page_authz::(client, &url).await; assert_eq!(pools.items.len(), 2); assert_eq!(pools.items[0].identity.name, "pool1"); assert_eq!(pools.items[1].identity.name, "pool2"); diff --git a/nexus/tests/integration_tests/sleds.rs b/nexus/tests/integration_tests/sleds.rs index 53ae7d92394..4252be1d6dd 100644 --- a/nexus/tests/integration_tests/sleds.rs +++ b/nexus/tests/integration_tests/sleds.rs @@ -17,8 +17,9 @@ use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils::start_sled_agent; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::views::SledInstance; -use nexus_types::external_api::views::{PhysicalDisk, Sled}; +use nexus_types::external_api::physical_disk::PhysicalDisk; +use nexus_types::external_api::sled::Sled; +use nexus_types::external_api::sled::SledInstance; use omicron_sled_agent::sim; use omicron_test_utils::dev::poll::{CondCheckError, wait_for_condition}; use omicron_uuid_kinds::GenericUuid; diff --git a/nexus/tests/integration_tests/snapshots.rs b/nexus/tests/integration_tests/snapshots.rs index 115605f75c5..e695e8dc654 100644 --- a/nexus/tests/integration_tests/snapshots.rs +++ b/nexus/tests/integration_tests/snapshots.rs @@ -29,8 +29,11 @@ use nexus_test_utils::resource_helpers::create_disk; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils::resource_helpers::object_create; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::disk; +use nexus_types::external_api::image; +use nexus_types::external_api::instance; +use nexus_types::external_api::sled; +use nexus_types::external_api::snapshot; use omicron_common::api::external; use omicron_common::api::external::ByteCount; use omicron_common::api::external::DiskState; @@ -78,14 +81,14 @@ async fn test_snapshot_basic(cptestctx: &ControlPlaneTestContext) { let disks_url = get_disks_url(); // Define a global image - let image_create_params = params::ImageCreate { + let image_create_params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "alpine-edge".parse().unwrap(), description: String::from( "you can boot any image, as long as it's alpine", ), }, - source: params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + source: image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, os: "alpine".to_string(), version: "edge".to_string(), }; @@ -94,19 +97,19 @@ async fn test_snapshot_basic(cptestctx: &ControlPlaneTestContext) { let image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; // Create a disk from this image let disk_size = ByteCount::from_gibibytes_u32(2); let base_disk_name: Name = "base-disk".parse().unwrap(); - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.clone(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: image.identity.id, read_only: false, }, @@ -133,7 +136,7 @@ async fn test_snapshot_basic(cptestctx: &ControlPlaneTestContext) { let instance: Instance = object_create( client, &instances_url, - ¶ms::InstanceCreate { + &instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: format!("instance {:?}", instance_name), @@ -146,9 +149,9 @@ async fn test_snapshot_basic(cptestctx: &ControlPlaneTestContext) { .to_vec(), ssh_public_keys: Some(Vec::new()), network_interfaces: - params::InstanceNetworkInterfaceAttachment::None, - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { name: base_disk_name.clone() }, + instance::InstanceNetworkInterfaceAttachment::None, + boot_disk: Some(instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: base_disk_name.clone() }, )), cpu_platform: None, disks: Vec::new(), @@ -169,10 +172,10 @@ async fn test_snapshot_basic(cptestctx: &ControlPlaneTestContext) { // Issue snapshot request let snapshots_url = format!("/v1/snapshots?project={}", PROJECT_NAME); - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: format!("instance {:?}", instance_name), @@ -194,14 +197,14 @@ async fn test_snapshot_without_instance(cptestctx: &ControlPlaneTestContext) { let disks_url = get_disks_url(); // Define a global image - let image_create_params = params::ImageCreate { + let image_create_params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "alpine-edge".parse().unwrap(), description: String::from( "you can boot any image, as long as it's alpine", ), }, - source: params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + source: image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, os: "alpine".to_string(), version: "edge".to_string(), }; @@ -210,19 +213,19 @@ async fn test_snapshot_without_instance(cptestctx: &ControlPlaneTestContext) { let image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; // Create a disk from this image let disk_size = ByteCount::from_gibibytes_u32(2); let base_disk_name: Name = "base-disk".parse().unwrap(); - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.clone(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: image.identity.id, read_only: false, }, @@ -258,10 +261,10 @@ async fn test_snapshot_without_instance(cptestctx: &ControlPlaneTestContext) { // Issue snapshot request let snapshots_url = format!("/v1/snapshots?project={}", PROJECT_NAME); - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "not-attached".parse().unwrap(), description: "not attached to instance".into(), @@ -296,14 +299,14 @@ async fn test_snapshot_stopped_instance(cptestctx: &ControlPlaneTestContext) { let disks_url = get_disks_url(); // Define a global image - let image_create_params = params::ImageCreate { + let image_create_params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "alpine-edge".parse().unwrap(), description: String::from( "you can boot any image, as long as it's alpine", ), }, - source: params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + source: image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, os: "alpine".to_string(), version: "edge".to_string(), }; @@ -312,19 +315,19 @@ async fn test_snapshot_stopped_instance(cptestctx: &ControlPlaneTestContext) { let image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; // Create a disk from this image let disk_size = ByteCount::from_gibibytes_u32(2); let base_disk_name: Name = "base-disk".parse().unwrap(); - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.clone(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: image.identity.id, read_only: false, }, @@ -351,7 +354,7 @@ async fn test_snapshot_stopped_instance(cptestctx: &ControlPlaneTestContext) { let instance: Instance = object_create( client, &instances_url, - ¶ms::InstanceCreate { + &instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: format!("instance {:?}", instance_name), @@ -364,9 +367,9 @@ async fn test_snapshot_stopped_instance(cptestctx: &ControlPlaneTestContext) { .to_vec(), ssh_public_keys: Some(Vec::new()), network_interfaces: - params::InstanceNetworkInterfaceAttachment::None, - boot_disk: Some(params::InstanceDiskAttachment::Attach( - params::InstanceDiskAttach { name: base_disk_name.clone() }, + instance::InstanceNetworkInterfaceAttachment::None, + boot_disk: Some(instance::InstanceDiskAttachment::Attach( + instance::InstanceDiskAttach { name: base_disk_name.clone() }, )), cpu_platform: None, disks: Vec::new(), @@ -384,10 +387,10 @@ async fn test_snapshot_stopped_instance(cptestctx: &ControlPlaneTestContext) { // Issue snapshot request let snapshots_url = format!("/v1/snapshots?project={}", PROJECT_NAME); - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: format!("instance {:?}", instance_name), @@ -413,14 +416,14 @@ async fn test_delete_snapshot(cptestctx: &ControlPlaneTestContext) { // Create a blank disk let disk_size = ByteCount::from_gibibytes_u32(2); let base_disk_name: Name = "base-disk".parse().unwrap(); - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.clone(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -449,10 +452,10 @@ async fn test_delete_snapshot(cptestctx: &ControlPlaneTestContext) { // Issue snapshot request let snapshots_url = format!("/v1/snapshots?project={}", PROJECT_NAME); - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "not-attached".parse().unwrap(), description: "not attached to instance".into(), @@ -476,14 +479,14 @@ async fn test_delete_snapshot(cptestctx: &ControlPlaneTestContext) { // Create a disk from this snapshot let disk_size = ByteCount::from_gibibytes_u32(2); let snap_disk_name: Name = "snap-disk".parse().unwrap(); - let snap_disk = params::DiskCreate { + let snap_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: snap_disk_name.clone(), description: String::from("snapshot of 'sells rainsticks'"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -620,14 +623,14 @@ async fn test_reject_creating_disk_from_snapshot( // Reject where block size doesn't evenly divide total size let error = NexusRequest::new( RequestBuilder::new(client, Method::POST, &disks_url) - .body(Some(¶ms::DiskCreate { + .body(Some(&disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "bad-disk".parse().unwrap(), description: String::from("bad disk"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Snapshot { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Snapshot { snapshot_id: snapshot.id(), read_only: false, }, @@ -655,14 +658,14 @@ async fn test_reject_creating_disk_from_snapshot( // Reject where size of snapshot is greater than the disk's let error = NexusRequest::new( RequestBuilder::new(client, Method::POST, &disks_url) - .body(Some(¶ms::DiskCreate { + .body(Some(&disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "bad-disk".parse().unwrap(), description: String::from("bad disk"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Snapshot { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Snapshot { snapshot_id: snapshot.id(), read_only: false, }, @@ -691,14 +694,14 @@ async fn test_reject_creating_disk_from_snapshot( // the size let error = NexusRequest::new( RequestBuilder::new(client, Method::POST, &disks_url) - .body(Some(¶ms::DiskCreate { + .body(Some(&disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "bad-disk".parse().unwrap(), description: String::from("bad disk"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Snapshot { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Snapshot { snapshot_id: snapshot.id(), read_only: false, }, @@ -791,14 +794,14 @@ async fn test_reject_creating_disk_from_illegal_snapshot( // this anyway. let error = NexusRequest::new( RequestBuilder::new(client, Method::POST, &disks_url) - .body(Some(¶ms::DiskCreate { + .body(Some(&disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "bad-disk".parse().unwrap(), description: String::from("bad disk"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Snapshot { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Snapshot { snapshot_id: snapshot.id(), read_only: false, }, @@ -883,14 +886,14 @@ async fn test_reject_creating_disk_from_other_project_snapshot( format!("/v1/disks?project={}", second_project.identity.name); let error = NexusRequest::new( RequestBuilder::new(client, Method::POST, &second_disks_url) - .body(Some(¶ms::DiskCreate { + .body(Some(&disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "stolen-disk".parse().unwrap(), description: String::from("stolen disk"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Snapshot { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Snapshot { snapshot_id: snapshot.id(), read_only: false, }, @@ -922,14 +925,14 @@ async fn test_cannot_snapshot_if_no_space(cptestctx: &ControlPlaneTestContext) { let disk_size = ByteCount::try_from(gibibytes * 1024 * 1024 * 1024).unwrap(); let base_disk_name: Name = "base-disk".parse().unwrap(); - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.clone(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -950,7 +953,7 @@ async fn test_cannot_snapshot_if_no_space(cptestctx: &ControlPlaneTestContext) { NexusRequest::new( RequestBuilder::new(client, Method::POST, &snapshots_url) - .body(Some(¶ms::SnapshotCreate { + .body(Some(&snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "not-attached".parse().unwrap(), description: "not attached to instance".into(), @@ -973,14 +976,14 @@ async fn test_snapshot_unwind(cptestctx: &ControlPlaneTestContext) { let disks_url = get_disks_url(); // Define a global image - let image_create_params = params::ImageCreate { + let image_create_params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "alpine-edge".parse().unwrap(), description: String::from( "you can boot any image, as long as it's alpine", ), }, - source: params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + source: image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, os: "alpine".to_string(), version: "edge".to_string(), }; @@ -989,19 +992,19 @@ async fn test_snapshot_unwind(cptestctx: &ControlPlaneTestContext) { let image = NexusRequest::objects_post(client, &images_url, &image_create_params) .authn_as(AuthnMode::PrivilegedUser) - .execute_and_parse_unwrap::() + .execute_and_parse_unwrap::() .await; // Create a disk from this image let disk_size = ByteCount::from_gibibytes_u32(2); let base_disk_name: Name = "base-disk".parse().unwrap(); - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.clone(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: image.identity.id, read_only: false, }, @@ -1037,7 +1040,7 @@ async fn test_snapshot_unwind(cptestctx: &ControlPlaneTestContext) { StatusCode::INTERNAL_SERVER_ERROR, Method::POST, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "snapshot".parse().unwrap(), description: String::from("a snapshot"), @@ -1293,14 +1296,14 @@ async fn test_multiple_deletes_not_sent(cptestctx: &ControlPlaneTestContext) { // Create a blank disk let disk_size = ByteCount::from_gibibytes_u32(2); let base_disk_name: Name = "base-disk".parse().unwrap(); - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.clone(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1321,10 +1324,10 @@ async fn test_multiple_deletes_not_sent(cptestctx: &ControlPlaneTestContext) { // Create three snapshots of this disk let snapshots_url = format!("/v1/snapshots?project={}", PROJECT_NAME); - let snapshot_1: views::Snapshot = object_create( + let snapshot_1: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "snapshot1".parse().unwrap(), description: "not attached to instance".into(), @@ -1334,10 +1337,10 @@ async fn test_multiple_deletes_not_sent(cptestctx: &ControlPlaneTestContext) { ) .await; - let snapshot_2: views::Snapshot = object_create( + let snapshot_2: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "snapshot2".parse().unwrap(), description: "not attached to instance".into(), @@ -1347,10 +1350,10 @@ async fn test_multiple_deletes_not_sent(cptestctx: &ControlPlaneTestContext) { ) .await; - let snapshot_3: views::Snapshot = object_create( + let snapshot_3: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "snapshot3".parse().unwrap(), description: "not attached to instance".into(), @@ -1544,10 +1547,10 @@ async fn test_region_allocation_for_snapshot( let snapshots_url = format!("/v1/snapshots?project={}", PROJECT_NAME); - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "snapshot".parse().unwrap(), description: String::from("a snapshot"), @@ -1588,8 +1591,8 @@ async fn test_region_allocation_for_snapshot( snapshot_id: snapshot.identity.id, }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from( + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from( disk.block_size.to_bytes() as u32, ) .unwrap(), @@ -1646,8 +1649,8 @@ async fn test_region_allocation_for_snapshot( snapshot_id: snapshot.identity.id, }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from( + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from( disk.block_size.to_bytes() as u32, ) .unwrap(), @@ -1676,8 +1679,8 @@ async fn test_region_allocation_for_snapshot( snapshot_id: snapshot.identity.id, }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from( + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from( disk.block_size.to_bytes() as u32, ) .unwrap(), @@ -1714,10 +1717,10 @@ async fn test_snapshot_expunge(cptestctx: &ControlPlaneTestContext) { let snapshots_url = format!("/v1/snapshots?project={}", PROJECT_NAME); - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "snapshot".parse().unwrap(), description: String::from("a snapshot"), @@ -1733,9 +1736,7 @@ async fn test_snapshot_expunge(cptestctx: &ControlPlaneTestContext) { .make_request( Method::POST, "/sleds/expunge", - Some(params::SledSelector { - sled: SLED_AGENT_UUID.parse().unwrap(), - }), + Some(sled::SledSelector { sled: SLED_AGENT_UUID.parse().unwrap() }), StatusCode::OK, ) .await diff --git a/nexus/tests/integration_tests/ssh_keys.rs b/nexus/tests/integration_tests/ssh_keys.rs index 0187a4aa55f..c7b750f687e 100644 --- a/nexus/tests/integration_tests/ssh_keys.rs +++ b/nexus/tests/integration_tests/ssh_keys.rs @@ -6,8 +6,8 @@ use nexus_test_utils::http_testing::{AuthnMode, NexusRequest}; use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::SshKeyCreate; -use nexus_types::external_api::views::SshKey; +use nexus_types::external_api::ssh_key::SshKey; +use nexus_types::external_api::ssh_key::SshKeyCreate; use omicron_common::api::external::IdentityMetadataCreateParams; type ControlPlaneTestContext = diff --git a/nexus/tests/integration_tests/subnet_allocation.rs b/nexus/tests/integration_tests/subnet_allocation.rs index a255a3fd4e8..f45f2eebde1 100644 --- a/nexus/tests/integration_tests/subnet_allocation.rs +++ b/nexus/tests/integration_tests/subnet_allocation.rs @@ -18,8 +18,9 @@ use nexus_test_utils::resource_helpers::create_instance_with; use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::params::PrivateIpStackCreate; +use nexus_types::external_api::instance; +use nexus_types::external_api::instance::PrivateIpStackCreate; +use nexus_types::external_api::vpc; use omicron_common::api::external::{ ByteCount, IdentityMetadataCreateParams, InstanceCpuCount, InstanceNetworkInterface, @@ -38,8 +39,8 @@ async fn create_instance_expect_failure( subnet_name: &str, ) -> HttpErrorResponseBody { let network_interfaces = - params::InstanceNetworkInterfaceAttachment::Create(vec![ - params::InstanceNetworkInterfaceCreate { + instance::InstanceNetworkInterfaceAttachment::Create(vec![ + instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { // We're using the name of the instance purposefully, to // avoid any naming conflicts on the interface. @@ -51,7 +52,7 @@ async fn create_instance_expect_failure( ip_config: PrivateIpStackCreate::auto_ipv4(), }, ]); - let new_instance = params::InstanceCreate { + let new_instance = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: name.parse().unwrap(), description: "".to_string(), @@ -110,7 +111,7 @@ async fn test_subnet_allocation(cptestctx: &ControlPlaneTestContext) { let network_address = Ipv4Addr::new(192, 168, 42, 0); let subnet = Ipv4Net::new(network_address, subnet_size) .expect("Invalid IPv4 network"); - let subnet_create = params::VpcSubnetCreate { + let subnet_create = vpc::VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: subnet_name.parse().unwrap(), description: String::from("a small subnet"), @@ -129,8 +130,8 @@ async fn test_subnet_allocation(cptestctx: &ControlPlaneTestContext) { // The valid addresses for allocation in `subnet` are 192.168.42.5 and // 192.168.42.6. The rest are reserved as described in RFD21. const SUBNET_NAME: &str = "small"; - let nic = params::InstanceNetworkInterfaceAttachment::Create(vec![ - params::InstanceNetworkInterfaceCreate { + let nic = instance::InstanceNetworkInterfaceAttachment::Create(vec![ + instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: "eth0".parse().unwrap(), description: String::from("some iface"), @@ -155,9 +156,9 @@ async fn test_subnet_allocation(cptestctx: &ControlPlaneTestContext) { &format!("i{}", i), &nic, // Disks= - Vec::::new(), + Vec::::new(), // External IPs= - Vec::::new(), + Vec::::new(), true, Default::default(), None, diff --git a/nexus/tests/integration_tests/subnet_pools.rs b/nexus/tests/integration_tests/subnet_pools.rs index 670e067f96c..8bcdb7e00a2 100644 --- a/nexus/tests/integration_tests/subnet_pools.rs +++ b/nexus/tests/integration_tests/subnet_pools.rs @@ -24,18 +24,18 @@ use nexus_test_utils::resource_helpers::grant_iam; use nexus_test_utils::resource_helpers::link_subnet_pool; use nexus_test_utils::resource_helpers::test_params; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::params::SubnetPoolMemberAdd; -use nexus_types::external_api::params::SubnetPoolMemberRemove; -use nexus_types::external_api::params::SubnetPoolUpdate; -use nexus_types::external_api::shared::SiloIdentityMode; -use nexus_types::external_api::shared::SiloRole; -use nexus_types::external_api::views::IpVersion; -use nexus_types::external_api::views::SiloSubnetPool; -use nexus_types::external_api::views::SubnetPool; -use nexus_types::external_api::views::SubnetPoolMember; -use nexus_types::external_api::views::SubnetPoolSiloLink; +use nexus_types::external_api::policy::SiloRole; +use nexus_types::external_api::silo::SiloIdentityMode; +use nexus_types::external_api::subnet_pool; +use nexus_types::external_api::subnet_pool::SiloSubnetPool; +use nexus_types::external_api::subnet_pool::SubnetPool; +use nexus_types::external_api::subnet_pool::SubnetPoolMember; +use nexus_types::external_api::subnet_pool::SubnetPoolMemberAdd; +use nexus_types::external_api::subnet_pool::SubnetPoolMemberRemove; +use nexus_types::external_api::subnet_pool::SubnetPoolSiloLink; +use nexus_types::external_api::subnet_pool::SubnetPoolUpdate; use nexus_types::silo::DEFAULT_SILO_ID; +use omicron_common::address::IpVersion; use omicron_common::api::external::IdentityMetadataUpdateParams; use omicron_common::api::external::Name; use omicron_common::api::external::SimpleIdentityOrName as _; @@ -316,7 +316,7 @@ async fn test_subnet_pool_silo_list(cptestctx: &ControlPlaneTestContext) { let n_to_link = 10; let mut linked_silos = Vec::with_capacity(n_to_link); for silo in silos.iter().take(n_to_link) { - let link_params = params::SubnetPoolLinkSilo { + let link_params = subnet_pool::SubnetPoolLinkSilo { silo: omicron_common::api::external::NameOrId::Id(silo.identity.id), is_default: false, }; @@ -428,7 +428,7 @@ async fn test_silo_subnet_pool_list(cptestctx: &ControlPlaneTestContext) { // Link the first few pools. let n_to_link = 10; let mut linked_pools = Vec::with_capacity(n_to_link); - let link_params = params::SubnetPoolLinkSilo { + let link_params = subnet_pool::SubnetPoolLinkSilo { silo: omicron_common::api::external::NameOrId::Id(silo.identity.id), is_default: false, }; @@ -538,7 +538,7 @@ async fn test_current_silo_subnet_pool_list( create_subnet_pool(client, unlinked_name, IpVersion::V6).await; // Link two of the pools to the silo. - let link_params = params::SubnetPoolLinkSilo { + let link_params = subnet_pool::SubnetPoolLinkSilo { silo: omicron_common::api::external::NameOrId::Id(silo.identity.id), is_default: true, }; @@ -551,7 +551,7 @@ async fn test_current_silo_subnet_pool_list( .execute_and_parse_unwrap::() .await; - let link_params = params::SubnetPoolLinkSilo { + let link_params = subnet_pool::SubnetPoolLinkSilo { silo: omicron_common::api::external::NameOrId::Id(silo.identity.id), is_default: false, }; @@ -674,7 +674,7 @@ async fn test_subnet_pool_silo_link(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; let pool = create_subnet_pool(client, SUBNET_POOL_NAME, IpVersion::V6).await; - let link_params = params::SubnetPoolLinkSilo { + let link_params = subnet_pool::SubnetPoolLinkSilo { silo: omicron_common::api::external::NameOrId::Id(DEFAULT_SILO_ID), is_default: false, }; @@ -693,7 +693,7 @@ async fn test_subnet_pool_silo_link(cptestctx: &ControlPlaneTestContext) { assert!(!link.is_default); // We can make it the default now. - let params = params::SubnetPoolSiloUpdate { is_default: true }; + let params = subnet_pool::SubnetPoolSiloUpdate { is_default: true }; let link = NexusRequest::object_put( client, &format!( @@ -713,7 +713,7 @@ async fn test_subnet_pool_silo_link(cptestctx: &ControlPlaneTestContext) { let new_silo = create_silo(client, "new-guy", false, SiloIdentityMode::LocalOnly) .await; - let link_params = params::SubnetPoolLinkSilo { + let link_params = subnet_pool::SubnetPoolLinkSilo { silo: omicron_common::api::external::NameOrId::Id(new_silo.identity.id), is_default: true, }; @@ -732,7 +732,7 @@ async fn test_subnet_pool_silo_link(cptestctx: &ControlPlaneTestContext) { // We should be able to link another pool to the same silo. let new_pool = create_subnet_pool(client, "new-pool-guy", IpVersion::V6).await; - let link_params = params::SubnetPoolLinkSilo { + let link_params = subnet_pool::SubnetPoolLinkSilo { silo: omicron_common::api::external::NameOrId::Id(new_silo.identity.id), is_default: false, }; @@ -750,7 +750,7 @@ async fn test_subnet_pool_silo_link(cptestctx: &ControlPlaneTestContext) { // But we should not be able to make that the default, since we already have // one of this IP version. - let params = params::SubnetPoolSiloUpdate { is_default: true }; + let params = subnet_pool::SubnetPoolSiloUpdate { is_default: true }; NexusRequest::expect_failure_with_body( client, StatusCode::BAD_REQUEST, @@ -827,7 +827,7 @@ async fn cannot_link_multiple_times(cptestctx: &ControlPlaneTestContext) { create_subnet_pool(client, SUBNET_POOL_NAME, IpVersion::V6).await; // Now link it to the default silo. - let link_params = params::SubnetPoolLinkSilo { + let link_params = subnet_pool::SubnetPoolLinkSilo { silo: omicron_common::api::external::NameOrId::Id(DEFAULT_SILO_ID), is_default: false, }; diff --git a/nexus/tests/integration_tests/support_bundles.rs b/nexus/tests/integration_tests/support_bundles.rs index 11ce5119ede..71f7eefe228 100644 --- a/nexus/tests/integration_tests/support_bundles.rs +++ b/nexus/tests/integration_tests/support_bundles.rs @@ -18,8 +18,8 @@ use nexus_test_utils::http_testing::AuthnMode; use nexus_test_utils::http_testing::NexusRequest; use nexus_test_utils::http_testing::RequestBuilder; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::shared::SupportBundleInfo; -use nexus_types::external_api::shared::SupportBundleState; +use nexus_types::external_api::support_bundle::SupportBundleInfo; +use nexus_types::external_api::support_bundle::SupportBundleState; use nexus_types::internal_api::background::SupportBundleCleanupReport; use nexus_types::internal_api::background::SupportBundleCollectionReport; use nexus_types::internal_api::background::SupportBundleCollectionStep; @@ -156,7 +156,7 @@ async fn bundle_create_with_comment( client: &ClientTestContext, user_comment: Option, ) -> Result { - use nexus_types::external_api::params::SupportBundleCreate; + use nexus_types::external_api::support_bundle::SupportBundleCreate; let create_params = SupportBundleCreate { user_comment }; @@ -178,7 +178,7 @@ async fn bundle_create_expect_fail( expected_status: StatusCode, expected_message: &str, ) -> Result<()> { - use nexus_types::external_api::params::SupportBundleCreate; + use nexus_types::external_api::support_bundle::SupportBundleCreate; let create_params = SupportBundleCreate { user_comment: None }; let error = NexusRequest::new( @@ -306,7 +306,7 @@ async fn bundle_update_comment( id: SupportBundleUuid, comment: Option, ) -> Result { - use nexus_types::external_api::params::SupportBundleUpdate; + use nexus_types::external_api::support_bundle::SupportBundleUpdate; let url = format!("{BUNDLES_URL}/{id}"); let update = SupportBundleUpdate { user_comment: comment }; @@ -785,9 +785,10 @@ async fn test_support_bundle_update_comment( // Test exceeding maximum length (4097 bytes) let too_long_comment = "a".repeat(4097); let url = format!("{BUNDLES_URL}/{}", bundle.id); - let update = nexus_types::external_api::params::SupportBundleUpdate { - user_comment: Some(too_long_comment), - }; + let update = + nexus_types::external_api::support_bundle::SupportBundleUpdate { + user_comment: Some(too_long_comment), + }; let error = NexusRequest::new( RequestBuilder::new(client, Method::PUT, &url) diff --git a/nexus/tests/integration_tests/switch_port.rs b/nexus/tests/integration_tests/switch_port.rs index cb319943641..284e7608a65 100644 --- a/nexus/tests/integration_tests/switch_port.rs +++ b/nexus/tests/integration_tests/switch_port.rs @@ -10,14 +10,14 @@ use http::StatusCode; use http::method::Method; use nexus_test_utils::http_testing::{AuthnMode, NexusRequest, RequestBuilder}; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params::{ +use nexus_types::external_api::networking::{ Address, AddressConfig, AddressLotBlockCreate, AddressLotCreate, BgpAnnounceSetCreate, BgpAnnouncementCreate, BgpConfigCreate, BgpPeerConfig, LinkConfigCreate, LldpLinkConfigCreate, Route, RouteConfig, SwitchInterfaceConfigCreate, SwitchInterfaceKind, SwitchPortApplySettings, SwitchPortSettingsCreate, }; -use nexus_types::external_api::views::Rack; +use nexus_types::external_api::rack::Rack; use omicron_common::api::external::{ self, AddressLotKind, BgpPeer, IdentityMetadataCreateParams, LinkFec, LinkSpeed, NameOrId, SwitchLocation, SwitchPort, SwitchPortSettings, diff --git a/nexus/tests/integration_tests/switches.rs b/nexus/tests/integration_tests/switches.rs index d665d6ff8e4..aaf890bca0a 100644 --- a/nexus/tests/integration_tests/switches.rs +++ b/nexus/tests/integration_tests/switches.rs @@ -6,11 +6,12 @@ use dropshot::test_util::ClientTestContext; use nexus_test_interface::NexusServer; +use nexus_test_utils::SLED_AGENT_UUID; use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils::start_sled_agent; -use nexus_test_utils::SLED_AGENT_UUID; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::views::Sled; +use nexus_types::external_api::sled::Sled; +use nexus_types::external_api::switch::Switch; use nexus_types::internal_api::params as internal_params; use omicron_sled_agent::sim; use std::str::FromStr; diff --git a/nexus/tests/integration_tests/target_release.rs b/nexus/tests/integration_tests/target_release.rs index 0a533cbefad..84c25d4646d 100644 --- a/nexus/tests/integration_tests/target_release.rs +++ b/nexus/tests/integration_tests/target_release.rs @@ -12,8 +12,8 @@ use http::method::Method; use nexus_test_utils::http_testing::AuthnMode; use nexus_test_utils::http_testing::{NexusRequest, RequestBuilder}; use nexus_test_utils::resource_helpers::object_get; -use nexus_types::external_api::params::SetTargetReleaseParams; -use nexus_types::external_api::views; +use nexus_types::external_api::update; +use nexus_types::external_api::update::SetTargetReleaseParams; use semver::Version; use tufaceous_artifact::{ArtifactVersion, KnownArtifactKind}; use tufaceous_lib::assemble::ManifestTweak; @@ -30,7 +30,7 @@ async fn get_set_target_release() -> Result<()> { let logctx = &ctx.logctx; // There is no target release before one has ever been specified - let status: views::UpdateStatus = + let status: update::UpdateStatus = object_get(client, "/v1/system/update/status").await; assert_eq!(status.target_release.0, None); @@ -54,7 +54,7 @@ async fn get_set_target_release() -> Result<()> { { let before = Utc::now(); let system_version = Version::new(1, 0, 0); - let response: views::TufRepoUpload = trust_root + let response: update::TufRepoUpload = trust_root .assemble_repo(&logctx.log, &[]) .await? .into_upload_request(client, StatusCode::OK) @@ -65,7 +65,7 @@ async fn get_set_target_release() -> Result<()> { set_target_release(client, &system_version).await?; - let status: views::UpdateStatus = + let status: update::UpdateStatus = object_get(client, "/v1/system/update/status").await; let target_release = status.target_release.0.unwrap(); @@ -86,7 +86,7 @@ async fn get_set_target_release() -> Result<()> { version: ArtifactVersion::new("non-semver-2").unwrap(), }, ]; - let response: views::TufRepoUpload = trust_root + let response: update::TufRepoUpload = trust_root .assemble_repo(&logctx.log, tweaks) .await? .into_upload_request(client, StatusCode::OK) @@ -97,7 +97,7 @@ async fn get_set_target_release() -> Result<()> { set_target_release(client, &system_version).await?; - let status: views::UpdateStatus = + let status: update::UpdateStatus = object_get(client, "/v1/system/update/status").await; let target_release = status.target_release.0.unwrap(); diff --git a/nexus/tests/integration_tests/unauthorized.rs b/nexus/tests/integration_tests/unauthorized.rs index 198ba064f91..cfe8d46ce50 100644 --- a/nexus/tests/integration_tests/unauthorized.rs +++ b/nexus/tests/integration_tests/unauthorized.rs @@ -497,7 +497,7 @@ static SETUP_REQUESTS: LazyLock> = LazyLock::new(|| { SetupReq::Post { url: &SUPPORT_BUNDLES_URL, body: serde_json::to_value( - &nexus_types::external_api::params::SupportBundleCreate { + &nexus_types::external_api::support_bundle::SupportBundleCreate { user_comment: None, }, ) diff --git a/nexus/tests/integration_tests/updates.rs b/nexus/tests/integration_tests/updates.rs index 3b11655e225..504b8b89d3c 100644 --- a/nexus/tests/integration_tests/updates.rs +++ b/nexus/tests/integration_tests/updates.rs @@ -21,8 +21,9 @@ use nexus_test_utils::resource_helpers::object_get; use nexus_test_utils::resource_helpers::object_get_error; use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::views; -use nexus_types::external_api::views::{TufRepoUpload, TufRepoUploadStatus}; +use nexus_types::external_api::update; +use nexus_types::external_api::update::TufRepoUpload; +use nexus_types::external_api::update::TufRepoUploadStatus; use omicron_common::api::external::TufArtifactMeta; use pretty_assertions::assert_eq; use semver::Version; @@ -353,7 +354,7 @@ async fn test_repo_upload() -> Result<()> { ); // Now get the repository that was just uploaded. - let repo = object_get::( + let repo = object_get::( client, "/v1/system/update/repositories/1.0.0", ) @@ -529,7 +530,7 @@ async fn test_repo_upload() -> Result<()> { ); // Now get the repository that was just uploaded. - let get_repo = object_get::( + let get_repo = object_get::( client, "/v1/system/update/repositories/2.0.0", ) @@ -656,7 +657,7 @@ async fn test_trust_root_operations(cptestctx: &ControlPlaneTestContext) { TestTrustRoot::generate().await.expect("trust root generation failed"); // POST /v1/system/update/trust-roots - let trust_root_view: views::UpdatesTrustRoot = trust_root + let trust_root_view: update::UpdatesTrustRoot = trust_root .to_upload_request(client, StatusCode::CREATED) .execute() .await @@ -667,7 +668,7 @@ async fn test_trust_root_operations(cptestctx: &ControlPlaneTestContext) { // GET /v1/system/update/trust-roots let request = RequestBuilder::new(client, Method::GET, TRUST_ROOTS_URL) .expect_status(Some(StatusCode::OK)); - let response: ResultsPage = + let response: ResultsPage = NexusRequest::new(request) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -681,7 +682,7 @@ async fn test_trust_root_operations(cptestctx: &ControlPlaneTestContext) { let id_url = format!("{TRUST_ROOTS_URL}/{}", trust_root_view.id); let request = RequestBuilder::new(client, Method::GET, &id_url) .expect_status(Some(StatusCode::OK)); - let response: views::UpdatesTrustRoot = NexusRequest::new(request) + let response: update::UpdatesTrustRoot = NexusRequest::new(request) .authn_as(AuthnMode::PrivilegedUser) .execute() .await @@ -700,7 +701,7 @@ async fn test_trust_root_operations(cptestctx: &ControlPlaneTestContext) { .expect("trust root delete failed"); let request = RequestBuilder::new(client, Method::GET, TRUST_ROOTS_URL) .expect_status(Some(StatusCode::OK)); - let response: ResultsPage = + let response: ResultsPage = NexusRequest::new(request) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -721,7 +722,7 @@ async fn test_update_status() -> Result<()> { let logctx = &cptestctx.logctx; // initial status - let status: views::UpdateStatus = + let status: update::UpdateStatus = object_get(client, "/v1/system/update/status").await; assert_eq!(status.target_release.0, None); // does not start suspended because the DB migration initialized the @@ -748,7 +749,7 @@ async fn test_update_status() -> Result<()> { let v1 = Version::new(1, 0, 0); set_target_release(client, &v1).await?; - let status: views::UpdateStatus = + let status: update::UpdateStatus = object_get(client, "/v1/system/update/status").await; assert_eq!(status.target_release.0.unwrap().version, v1); assert!(!status.suspended, "should not be suspended after setting v1"); @@ -779,7 +780,7 @@ async fn test_update_status() -> Result<()> { .await?; set_target_release(client, &v2).await?; - let status: views::UpdateStatus = + let status: update::UpdateStatus = object_get(client, "/v1/system/update/status").await; assert_eq!(status.target_release.0.unwrap().version, v2); @@ -876,7 +877,7 @@ async fn test_repo_list() -> Result<()> { let logctx = &cptestctx.logctx; // Initially, list should be empty - let initial_list: ResultsPage = + let initial_list: ResultsPage = objects_list_page_authz(client, "/v1/system/update/repositories").await; assert_eq!(initial_list.items.len(), 0); @@ -925,7 +926,7 @@ async fn test_repo_list() -> Result<()> { assert_eq!(response3.status, TufRepoUploadStatus::Inserted); // List repositories - should return all 3, ordered by system version (newest first) - let list: ResultsPage = + let list: ResultsPage = objects_list_page_authz(client, "/v1/system/update/repositories").await; assert_eq!(list.items.len(), 3); @@ -947,7 +948,7 @@ async fn test_repo_list() -> Result<()> { } // Request ascending order and expect the versions oldest-first - let ascending_list: ResultsPage = objects_list_page_authz( + let ascending_list: ResultsPage = objects_list_page_authz( client, "/v1/system/update/repositories?sort_by=version_ascending", ) @@ -963,7 +964,7 @@ async fn test_repo_list() -> Result<()> { assert_eq!(ascending_versions, vec!["1.0.0", "2.0.0", "3.0.0"]); // Test pagination by setting a small limit - let paginated_list = objects_list_page_authz::( + let paginated_list = objects_list_page_authz::( client, "/v1/system/update/repositories?limit=2", ) @@ -985,7 +986,7 @@ async fn test_repo_list() -> Result<()> { "/v1/system/update/repositories?limit=2&page_token={}", paginated_list.next_page.clone().expect("expected next page token"), ); - let next_page: ResultsPage = + let next_page: ResultsPage = objects_list_page_authz(client, &next_page_url).await; assert_eq!(next_page.items.len(), 1); assert_eq!(next_page.items[0].system_version.to_string(), "1.0.0"); @@ -993,7 +994,7 @@ async fn test_repo_list() -> Result<()> { // Test filtering out pruned repos // Confirm that GET works for 1.0.0 before pruning - let _repo_before_prune: views::TufRepo = + let _repo_before_prune: update::TufRepo = object_get(client, "/v1/system/update/repositories/1.0.0").await; // Mark the 1.0.0 repo as pruned (use datastore methods since there's no API @@ -1027,7 +1028,7 @@ async fn test_repo_list() -> Result<()> { .await; // List repositories again - the pruned repo should not appear - let list_after_prune: ResultsPage = + let list_after_prune: ResultsPage = objects_list_page_authz(client, "/v1/system/update/repositories").await; assert_eq!(list_after_prune.items.len(), 2); @@ -1066,6 +1067,6 @@ async fn test_request_without_api_version(cptestctx: &ControlPlaneTestContext) { ); let req = NexusRequest::new(req_builder).authn_as(AuthnMode::PrivilegedUser); - let status: views::UpdateStatus = req.execute_and_parse_unwrap().await; + let status: update::UpdateStatus = req.execute_and_parse_unwrap().await; assert_eq!(status.target_release.0, None); } diff --git a/nexus/tests/integration_tests/users_builtin.rs b/nexus/tests/integration_tests/users_builtin.rs index 23a1858593e..0f87e19ca41 100644 --- a/nexus/tests/integration_tests/users_builtin.rs +++ b/nexus/tests/integration_tests/users_builtin.rs @@ -5,7 +5,7 @@ use nexus_db_queries::authn; use nexus_test_utils::http_testing::AuthnMode; use nexus_test_utils::http_testing::NexusRequest; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::views::UserBuiltin; +use nexus_types::external_api::user::UserBuiltin; use omicron_uuid_kinds::GenericUuid; use std::collections::BTreeMap; diff --git a/nexus/tests/integration_tests/utilization.rs b/nexus/tests/integration_tests/utilization.rs index 92133a8961d..d7917d83b19 100644 --- a/nexus/tests/integration_tests/utilization.rs +++ b/nexus/tests/integration_tests/utilization.rs @@ -16,13 +16,13 @@ use nexus_test_utils::resource_helpers::object_put; use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils::resource_helpers::test_params; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::params::SiloQuotasCreate; -use nexus_types::external_api::views::Silo; -use nexus_types::external_api::views::SiloQuotas; -use nexus_types::external_api::views::SiloUtilization; -use nexus_types::external_api::views::Utilization; -use nexus_types::external_api::views::VirtualResourceCounts; +use nexus_types::external_api::disk; +use nexus_types::external_api::instance; +use nexus_types::external_api::project; +use nexus_types::external_api::silo::{ + Silo, SiloQuotas, SiloQuotasCreate, SiloUtilization, Utilization, + VirtualResourceCounts, +}; use omicron_common::api::external::ByteCount; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::InstanceCpuCount; @@ -50,7 +50,7 @@ async fn test_utilization_list(cptestctx: &ControlPlaneTestContext) { let _: SiloQuotas = object_put( client, quotas_url, - ¶ms::SiloQuotasCreate::arbitrarily_high_default(), + &SiloQuotasCreate::arbitrarily_high_default(), ) .await; @@ -88,8 +88,7 @@ async fn test_utilization_list(cptestctx: &ControlPlaneTestContext) { // now we take the quota back off of test-suite-silo and end up empty again let _: SiloQuotas = - object_put(client, quotas_url, ¶ms::SiloQuotasCreate::empty()) - .await; + object_put(client, quotas_url, &SiloQuotasCreate::empty()).await; assert!(util_list(client).await.is_empty()); } @@ -141,15 +140,15 @@ async fn test_utilization_view(cptestctx: &ControlPlaneTestContext) { // provision disk NexusRequest::new( RequestBuilder::new(client, Method::POST, &disk_url) - .body(Some(¶ms::DiskCreate { + .body(Some(&disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "test-disk".parse().unwrap(), description: "".into(), }, size: ByteCount::from_gibibytes_u32(2), - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, })) @@ -210,7 +209,7 @@ async fn create_resources_in_test_suite_silo( NexusRequest::objects_post( client, "/v1/projects", - ¶ms::ProjectCreate { + &project::ProjectCreate { identity: IdentityMetadataCreateParams { name: test_project_name.parse().unwrap(), description: String::new(), @@ -223,7 +222,7 @@ async fn create_resources_in_test_suite_silo( .expect("failed to create project in test-suite-silo"); // Create instance in test-suite-silo as the test user - let instance_params = params::InstanceCreate { + let instance_params = instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: "test-inst".parse().unwrap(), description: "test instance in test-suite-silo".to_string(), @@ -234,7 +233,7 @@ async fn create_resources_in_test_suite_silo( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], disks: vec![], boot_disk: None, diff --git a/nexus/tests/integration_tests/volume_management.rs b/nexus/tests/integration_tests/volume_management.rs index 491a2865446..70ac16882bc 100644 --- a/nexus/tests/integration_tests/volume_management.rs +++ b/nexus/tests/integration_tests/volume_management.rs @@ -51,8 +51,10 @@ use nexus_test_utils::resource_helpers::create_project; use nexus_test_utils::resource_helpers::create_snapshot; use nexus_test_utils::resource_helpers::object_create; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::views; +use nexus_types::external_api::disk; +use nexus_types::external_api::image; +use nexus_types::external_api::sled; +use nexus_types::external_api::snapshot; use nexus_types::identity::Asset; use nexus_types::identity::Resource; use omicron_common::api::external; @@ -110,19 +112,19 @@ async fn create_project_and_pool(client: &ClientTestContext) -> Uuid { project.identity.id } -async fn create_image(client: &ClientTestContext) -> views::Image { +async fn create_image(client: &ClientTestContext) -> image::Image { create_project_and_pool(client).await; // Define a global image - let image_create_params = params::ImageCreate { + let image_create_params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "alpine-edge".parse().unwrap(), description: String::from( "you can boot any image, as long as it's alpine", ), }, - source: params::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, + source: image::ImageSource::YouCanBootAnythingAsLongAsItsAlpine, os: "alpine".to_string(), version: "edge".to_string(), }; @@ -139,18 +141,18 @@ async fn create_image(client: &ClientTestContext) -> views::Image { async fn create_base_disk( client: &ClientTestContext, - image: &views::Image, + image: &image::Image, disks_url: &String, base_disk_name: &Name, ) -> external::Disk { let disk_size = ByteCount::from_gibibytes_u32(2); - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.clone(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: image.identity.id, read_only: false, }, @@ -192,10 +194,10 @@ async fn test_snapshot_then_delete_disk(cptestctx: &ControlPlaneTestContext) { create_base_disk(&client, &image, &disks_url, &base_disk_name).await; // Issue snapshot request - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &get_snapshots_url(), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "a-snapshot".parse().unwrap(), description: "a snapshot!".to_string(), @@ -253,10 +255,10 @@ async fn test_delete_snapshot_then_disk(cptestctx: &ControlPlaneTestContext) { create_base_disk(&client, &image, &disks_url, &base_disk_name).await; // Issue snapshot request - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &get_snapshots_url(), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "a-snapshot".parse().unwrap(), description: "a snapshot!".to_string(), @@ -314,10 +316,10 @@ async fn test_multiple_snapshots(cptestctx: &ControlPlaneTestContext) { // Issue snapshot requests for i in 0..4 { - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &get_snapshots_url(), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: format!("a-snapshot-{}", i).parse().unwrap(), description: "a snapshot!".to_string(), @@ -376,10 +378,10 @@ async fn test_snapshot_prevents_other_disk( create_base_disk(&client, &image, &disks_url, &base_disk_name).await; // Issue snapshot request - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &get_snapshots_url(), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "a-snapshot".parse().unwrap(), description: "a snapshot!".to_string(), @@ -409,13 +411,13 @@ async fn test_snapshot_prevents_other_disk( // means the region wasn't deleted. let disk_size = ByteCount::from_gibibytes_u32(10); let next_disk_name: Name = "next-disk".parse().unwrap(); - let next_disk = params::DiskCreate { + let next_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: next_disk_name.clone(), description: String::from("will fail"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Image { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Image { image_id: image.identity.id, read_only: false, }, @@ -480,14 +482,14 @@ async fn test_multiple_disks_multiple_snapshots_order_1( // Create a blank disk let disk_size = ByteCount::from_gibibytes_u32(2); let first_disk_name: Name = "first-disk".parse().unwrap(); - let first_disk = params::DiskCreate { + let first_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: first_disk_name.clone(), description: String::from("disk 1"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -506,10 +508,10 @@ async fn test_multiple_disks_multiple_snapshots_order_1( .unwrap(); // Issue snapshot request - let first_snapshot: views::Snapshot = object_create( + let first_snapshot: snapshot::Snapshot = object_create( client, &get_snapshots_url(), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "first-snapshot".parse().unwrap(), description: "first snapshot!".to_string(), @@ -524,14 +526,14 @@ async fn test_multiple_disks_multiple_snapshots_order_1( // Create another blank disk let second_disk_name: Name = "second-disk".parse().unwrap(); - let second_disk = params::DiskCreate { + let second_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: second_disk_name.clone(), description: String::from("disk 1"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -550,10 +552,10 @@ async fn test_multiple_disks_multiple_snapshots_order_1( .unwrap(); // Issue snapshot request for the second disk - let second_snapshot: views::Snapshot = object_create( + let second_snapshot: snapshot::Snapshot = object_create( client, &get_snapshots_url(), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "second-snapshot".parse().unwrap(), description: "second snapshot!".to_string(), @@ -619,14 +621,14 @@ async fn test_multiple_disks_multiple_snapshots_order_2( // Create a blank disk let disk_size = ByteCount::from_gibibytes_u32(2); let first_disk_name: Name = "first-disk".parse().unwrap(); - let first_disk = params::DiskCreate { + let first_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: first_disk_name.clone(), description: String::from("disk 1"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -645,10 +647,10 @@ async fn test_multiple_disks_multiple_snapshots_order_2( .unwrap(); // Issue snapshot request - let first_snapshot: views::Snapshot = object_create( + let first_snapshot: snapshot::Snapshot = object_create( client, &get_snapshots_url(), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "first-snapshot".parse().unwrap(), description: "first snapshot!".to_string(), @@ -663,14 +665,14 @@ async fn test_multiple_disks_multiple_snapshots_order_2( // Create another blank disk let second_disk_name: Name = "second-disk".parse().unwrap(); - let second_disk = params::DiskCreate { + let second_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: second_disk_name.clone(), description: String::from("disk 1"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -689,10 +691,10 @@ async fn test_multiple_disks_multiple_snapshots_order_2( .unwrap(); // Issue snapshot request for the second disk - let second_snapshot: views::Snapshot = object_create( + let second_snapshot: snapshot::Snapshot = object_create( client, &get_snapshots_url(), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "second-snapshot".parse().unwrap(), description: "second snapshot!".to_string(), @@ -753,14 +755,14 @@ async fn prepare_for_test_multiple_layers_of_snapshots( // Create a blank disk let disk_size = ByteCount::from_gibibytes_u32(1); let layer_1_disk_name: Name = "layer-1-disk".parse().unwrap(); - let layer_1_disk = params::DiskCreate { + let layer_1_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: layer_1_disk_name.clone(), description: String::from("layer 1"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -779,10 +781,10 @@ async fn prepare_for_test_multiple_layers_of_snapshots( .unwrap(); // Issue snapshot request - let layer_1_snapshot: views::Snapshot = object_create( + let layer_1_snapshot: snapshot::Snapshot = object_create( client, &get_snapshots_url(), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "layer-1-snapshot".parse().unwrap(), description: "layer 1 snapshot!".to_string(), @@ -797,13 +799,13 @@ async fn prepare_for_test_multiple_layers_of_snapshots( // Create a layer 2 disk out of the layer 1 snapshot let layer_2_disk_name: Name = "layer-2-disk".parse().unwrap(); - let layer_2_disk = params::DiskCreate { + let layer_2_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: layer_2_disk_name.clone(), description: String::from("layer 2"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Snapshot { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Snapshot { snapshot_id: layer_1_snapshot.identity.id, read_only: false, }, @@ -824,10 +826,10 @@ async fn prepare_for_test_multiple_layers_of_snapshots( .unwrap(); // Issue snapshot request for the second disk - let layer_2_snapshot: views::Snapshot = object_create( + let layer_2_snapshot: snapshot::Snapshot = object_create( client, &get_snapshots_url(), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "layer-2-snapshot".parse().unwrap(), description: "layer 2 snapshot!".to_string(), @@ -842,13 +844,13 @@ async fn prepare_for_test_multiple_layers_of_snapshots( // Create a layer 3 disk out of the layer 2 snapshot let layer_3_disk_name: Name = "layer-3-disk".parse().unwrap(); - let layer_3_disk = params::DiskCreate { + let layer_3_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: layer_3_disk_name.clone(), description: String::from("layer 3"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Snapshot { + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Snapshot { snapshot_id: layer_2_snapshot.identity.id, read_only: false, }, @@ -869,10 +871,10 @@ async fn prepare_for_test_multiple_layers_of_snapshots( .unwrap(); // Issue snapshot request for the third disk - let layer_3_snapshot: views::Snapshot = object_create( + let layer_3_snapshot: snapshot::Snapshot = object_create( client, &get_snapshots_url(), - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "layer-3-snapshot".parse().unwrap(), description: "layer 3 snapshot!".to_string(), @@ -1046,10 +1048,10 @@ async fn test_create_image_from_snapshot(cptestctx: &ControlPlaneTestContext) { // Issue snapshot request let snapshots_url = format!("/v1/snapshots?project={}", PROJECT_NAME); - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "a-snapshot".parse().unwrap(), description: "a snapshot!".to_string(), @@ -1060,17 +1062,17 @@ async fn test_create_image_from_snapshot(cptestctx: &ControlPlaneTestContext) { .await; // Create an image from the snapshot - let image_create_params = params::ImageCreate { + let image_create_params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "debian-11".parse().unwrap(), description: String::from("debian's cool too"), }, - source: params::ImageSource::Snapshot { id: snapshot.identity.id }, + source: image::ImageSource::Snapshot { id: snapshot.identity.id }, os: "debian".parse().unwrap(), version: "11".into(), }; - let _image: views::Image = + let _image: image::Image = NexusRequest::objects_post(client, "/v1/images", &image_create_params) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -1105,10 +1107,10 @@ async fn test_create_image_from_snapshot_delete( // Issue snapshot request let snapshots_url = format!("/v1/snapshots?project={}", PROJECT_NAME); - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "a-snapshot".parse().unwrap(), description: "a snapshot!".to_string(), @@ -1119,17 +1121,17 @@ async fn test_create_image_from_snapshot_delete( .await; // Create an image from the snapshot - let image_create_params = params::ImageCreate { + let image_create_params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "debian-11".parse().unwrap(), description: String::from("debian's cool too"), }, - source: params::ImageSource::Snapshot { id: snapshot.identity.id }, + source: image::ImageSource::Snapshot { id: snapshot.identity.id }, os: "debian".parse().unwrap(), version: "11".into(), }; - let _image: views::Image = + let _image: image::Image = NexusRequest::objects_post(client, "/v1/images", &image_create_params) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -1198,14 +1200,14 @@ async fn delete_image_test( let disk_size = ByteCount::from_gibibytes_u32(2); let base_disk_name: Name = "base-disk".parse().unwrap(); - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.clone(), description: String::from("all your base disk are belong to us"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -1226,10 +1228,10 @@ async fn delete_image_test( // Issue snapshot request let snapshots_url = format!("/v1/snapshots?project={}", PROJECT_NAME); - let snapshot: views::Snapshot = object_create( + let snapshot: snapshot::Snapshot = object_create( client, &snapshots_url, - ¶ms::SnapshotCreate { + &snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "a-snapshot".parse().unwrap(), description: String::from("you are on the way to destruction"), @@ -1240,19 +1242,19 @@ async fn delete_image_test( .await; // Create an image from the snapshot - let image_create_params = params::ImageCreate { + let image_create_params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "debian-11".parse().unwrap(), description: String::from( "you have no chance to survive make your time", ), }, - source: params::ImageSource::Snapshot { id: snapshot.identity.id }, + source: image::ImageSource::Snapshot { id: snapshot.identity.id }, os: "debian".parse().unwrap(), version: "12".into(), }; - let _image: views::Image = + let _image: image::Image = NexusRequest::objects_post(client, "/v1/images", &image_create_params) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -2495,14 +2497,14 @@ async fn test_disk_create_saga_unwinds_correctly( .set_region_creation_error(true); let disk_size = ByteCount::from_gibibytes_u32(2); - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.clone(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -2541,14 +2543,14 @@ async fn test_snapshot_create_saga_unwinds_correctly( // Create a disk let disk_size = ByteCount::from_gibibytes_u32(2); - let base_disk = params::DiskCreate { + let base_disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: base_disk_name.clone(), description: String::from("sells rainsticks"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: disk_size, @@ -2567,7 +2569,7 @@ async fn test_snapshot_create_saga_unwinds_correctly( .set_region_creation_error(true); // Create a snapshot - let snapshot_create = params::SnapshotCreate { + let snapshot_create = snapshot::SnapshotCreate { identity: IdentityMetadataCreateParams { name: "a-snapshot".parse().unwrap(), description: "a snapshot!".to_string(), @@ -3343,14 +3345,14 @@ async fn test_cte_returns_regions(cptestctx: &ControlPlaneTestContext) { create_project_and_pool(client).await; let disks_url = get_disks_url(); - let disk = params::DiskCreate { + let disk = disk::DiskCreate { identity: IdentityMetadataCreateParams { name: "disk".parse().unwrap(), description: String::from("disk"), }, - disk_backend: params::DiskBackend::Distributed { - disk_source: params::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_backend: disk::DiskBackend::Distributed { + disk_source: disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, }, size: ByteCount::from_gibibytes_u32(2), @@ -3452,8 +3454,8 @@ impl TestReadOnlyRegionReferenceUsage { snapshot_id, }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, size: ByteCount::from_gibibytes_u32(1), }, @@ -4516,8 +4518,8 @@ async fn test_volume_replace_snapshot_respects_accounting( snapshot_id: db_snapshot.id(), }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, size: ByteCount::from_gibibytes_u32(1), }, @@ -5232,12 +5234,12 @@ async fn test_migrate_to_ref_count_with_records_soft_delete_volume( let snapshot = create_snapshot(&client, PROJECT_NAME, "disk", "snapshot").await; - let params = params::ImageCreate { + let params = image::ImageCreate { identity: IdentityMetadataCreateParams { name: "windows98".parse().unwrap(), description: String::from("as soon as we get CSM support!"), }, - source: params::ImageSource::Snapshot { id: snapshot.identity.id }, + source: image::ImageSource::Snapshot { id: snapshot.identity.id }, os: "windows98".to_string(), version: "se".to_string(), }; @@ -5926,8 +5928,8 @@ async fn test_no_zombie_read_only_regions(cptestctx: &ControlPlaneTestContext) { snapshot_id, }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, size: ByteCount::from_gibibytes_u32(1), }, @@ -6111,8 +6113,8 @@ async fn test_no_zombie_read_write_regions( snapshot_id, }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, size: ByteCount::from_gibibytes_u32(1), }, @@ -6282,11 +6284,11 @@ async fn test_proper_region_sled_redundancy( let sleds_found = sleds_list(&client, &sleds_url).await; assert_eq!(sleds_found.len(), 4); for sled in sleds_found { - assert_eq!(sled.state, views::SledState::Active); + assert_eq!(sled.state, sled::SledState::Active); assert!(matches!( sled.policy, - views::SledPolicy::InService { - provision_policy: views::SledProvisionPolicy::Provisionable + sled::SledPolicy::InService { + provision_policy: sled::SledProvisionPolicy::Provisionable }, )); } @@ -6303,8 +6305,8 @@ async fn test_proper_region_sled_redundancy( &opctx, RegionAllocationFor::SnapshotVolume { volume_id, snapshot_id }, RegionAllocationParameters::FromDiskSource { - disk_source: ¶ms::DiskSource::Blank { - block_size: params::BlockSize::try_from(512).unwrap(), + disk_source: &disk::DiskSource::Blank { + block_size: disk::BlockSize::try_from(512).unwrap(), }, size: ByteCount::from_gibibytes_u32(1), }, diff --git a/nexus/tests/integration_tests/vpc_firewall.rs b/nexus/tests/integration_tests/vpc_firewall.rs index bd6bc51d8ed..6afaaf83a40 100644 --- a/nexus/tests/integration_tests/vpc_firewall.rs +++ b/nexus/tests/integration_tests/vpc_firewall.rs @@ -15,7 +15,7 @@ use nexus_test_utils::resource_helpers::{ create_project, create_vpc, object_get, object_put, object_put_error, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::views::Vpc; +use nexus_types::external_api::vpc::Vpc; use omicron_common::api::external::{ IcmpParamRange, IdentityMetadata, L4Port, L4PortRange, ServiceIcmpConfig, VpcFirewallIcmpFilter, VpcFirewallRule, VpcFirewallRuleAction, diff --git a/nexus/tests/integration_tests/vpc_routers.rs b/nexus/tests/integration_tests/vpc_routers.rs index 9317d2e3e14..bb81db3c969 100644 --- a/nexus/tests/integration_tests/vpc_routers.rs +++ b/nexus/tests/integration_tests/vpc_routers.rs @@ -23,14 +23,9 @@ use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils::resource_helpers::{create_project, create_vpc}; use nexus_test_utils::resource_helpers::{object_put, object_put_error}; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::params::InstanceNetworkInterfaceAttachment; -use nexus_types::external_api::params::InstanceNetworkInterfaceCreate; -use nexus_types::external_api::params::PrivateIpStackCreate; -use nexus_types::external_api::params::VpcSubnetUpdate; -use nexus_types::external_api::views::VpcRouter; -use nexus_types::external_api::views::VpcRouterKind; -use nexus_types::external_api::views::VpcSubnet; +use nexus_types::external_api::instance; +use nexus_types::external_api::instance::PrivateIpStackCreate; +use nexus_types::external_api::vpc; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; use omicron_common::api::external::NameOrId; @@ -66,7 +61,7 @@ async fn test_vpc_routers_crud_operations(cptestctx: &ControlPlaneTestContext) { // get routers should have only the system router created w/ the VPC let routers = list_routers(client, &VPC_NAME).await; assert_eq!(routers.len(), 1); - assert_eq!(routers[0].kind, VpcRouterKind::System); + assert_eq!(routers[0].kind, vpc::VpcRouterKind::System); // This router should not be deletable. let system_router_url = format!("/v1/vpc-routers/{}", routers[0].id()); @@ -114,7 +109,7 @@ async fn test_vpc_routers_crud_operations(cptestctx: &ControlPlaneTestContext) { assert_eq!(router.identity.name, router_name); assert_eq!(router.identity.description, "router description"); assert_eq!(router.vpc_id, vpc.identity.id); - assert_eq!(router.kind, VpcRouterKind::Custom); + assert_eq!(router.kind, vpc::VpcRouterKind::Custom); // get router, should be the same let same_router = NexusRequest::object_get(client, &router_url) @@ -134,7 +129,7 @@ async fn test_vpc_routers_crud_operations(cptestctx: &ControlPlaneTestContext) { // creating another router in the same VPC with the same name fails let error: dropshot::HttpErrorResponseBody = NexusRequest::new( RequestBuilder::new(&client, Method::POST, &routers_url) - .body(Some(¶ms::VpcRouterCreate { + .body(Some(&vpc::VpcRouterCreate { identity: IdentityMetadataCreateParams { name: router_name.parse().unwrap(), description: String::from("this is not a router"), @@ -182,7 +177,7 @@ async fn test_vpc_routers_crud_operations(cptestctx: &ControlPlaneTestContext) { create_router(client, PROJECT_NAME, VPC_NAME, router2_name).await; assert_eq!(router2.identity.name, router2_name); assert_eq!(router2.vpc_id, vpc.identity.id); - assert_eq!(router2.kind, VpcRouterKind::Custom); + assert_eq!(router2.kind, vpc::VpcRouterKind::Custom); // routers list should now have two custom and one system let routers = list_routers(client, &VPC_NAME).await; @@ -191,13 +186,13 @@ async fn test_vpc_routers_crud_operations(cptestctx: &ControlPlaneTestContext) { routers_eq(&routers[1], &router2); // update first router - let update_params = params::VpcRouterUpdate { + let update_params = vpc::VpcRouterUpdate { identity: IdentityMetadataUpdateParams { name: Some("new-name".parse().unwrap()), description: Some("another description".to_string()), }, }; - let update: VpcRouter = + let update: vpc::VpcRouter = NexusRequest::object_put(&client, &router_url, Some(&update_params)) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -236,7 +231,7 @@ async fn test_vpc_routers_crud_operations(cptestctx: &ControlPlaneTestContext) { ); // fetching by new name works - let updated_router: VpcRouter = + let updated_router: vpc::VpcRouter = NexusRequest::object_get(&client, &router_url) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -326,13 +321,13 @@ async fn test_vpc_routers_attach_to_subnet( // get routers should have only the system router created w/ the VPC let routers = list_routers(client, VPC_NAME).await; assert_eq!(routers.len(), 1); - assert_eq!(routers[0].kind, VpcRouterKind::System); + assert_eq!(routers[0].kind, vpc::VpcRouterKind::System); // Create a custom router for later use. let router_name = ROUTER_NAMES[0]; let router = create_router(&client, PROJECT_NAME, VPC_NAME, router_name).await; - assert_eq!(router.kind, VpcRouterKind::Custom); + assert_eq!(router.kind, vpc::VpcRouterKind::Custom); // Attaching a system router should fail. let err = object_put_error( @@ -340,7 +335,7 @@ async fn test_vpc_routers_attach_to_subnet( &format!( "/v1/vpc-subnets/{subnet_name}?project={PROJECT_NAME}&vpc={VPC_NAME}" ), - &VpcSubnetUpdate { + &vpc::VpcSubnetUpdate { identity: IdentityMetadataUpdateParams { name: None, description: None, @@ -404,7 +399,7 @@ async fn test_vpc_routers_attach_to_subnet( let err = object_put_error( client, &format!("/v1/vpc-subnets/default?project={PROJECT_NAME}&vpc=vpc1"), - &VpcSubnetUpdate { + &vpc::VpcSubnetUpdate { identity: IdentityMetadataUpdateParams { name: None, description: None, @@ -458,7 +453,9 @@ async fn test_vpc_routers_attach_to_subnet( .await; for subnet in - objects_list_page_authz::(client, &subnets_url).await.items + objects_list_page_authz::(client, &subnets_url) + .await + .items { assert!(subnet.custom_router_id.is_none(), "{subnet:?}"); } @@ -502,8 +499,8 @@ async fn test_vpc_routers_custom_delivered_to_instance( client, PROJECT_NAME, instance_name, - &InstanceNetworkInterfaceAttachment::Create(vec![ - InstanceNetworkInterfaceCreate { + &instance::InstanceNetworkInterfaceAttachment::Create(vec![ + instance::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { name: format!("nic-{i}").parse().unwrap(), description: "".into(), @@ -695,13 +692,13 @@ async fn set_custom_router( subnet_name: &str, vpc_name: &str, custom_router: Option, -) -> VpcSubnet { +) -> vpc::VpcSubnet { object_put( client, &format!( "/v1/vpc-subnets/{subnet_name}?project={PROJECT_NAME}&vpc={vpc_name}" ), - &VpcSubnetUpdate { + &vpc::VpcSubnetUpdate { identity: IdentityMetadataUpdateParams { name: None, description: None, @@ -715,14 +712,15 @@ async fn set_custom_router( async fn list_routers( client: &ClientTestContext, vpc_name: &str, -) -> Vec { +) -> Vec { let routers_url = format!("/v1/vpc-routers?project={}&vpc={}", PROJECT_NAME, vpc_name); - let out = objects_list_page_authz::(client, &routers_url).await; + let out = + objects_list_page_authz::(client, &routers_url).await; out.items } -fn routers_eq(sn1: &VpcRouter, sn2: &VpcRouter) { +fn routers_eq(sn1: &vpc::VpcRouter, sn2: &vpc::VpcRouter) { identity_eq(&sn1.identity, &sn2.identity); assert_eq!(sn1.vpc_id, sn2.vpc_id); } diff --git a/nexus/tests/integration_tests/vpc_subnets.rs b/nexus/tests/integration_tests/vpc_subnets.rs index 8469ed2fb34..f769918cb1b 100644 --- a/nexus/tests/integration_tests/vpc_subnets.rs +++ b/nexus/tests/integration_tests/vpc_subnets.rs @@ -18,7 +18,9 @@ use nexus_test_utils::resource_helpers::{ create_default_ip_pools, create_instance, create_project, create_vpc, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::{params, views::VpcSubnet}; +use nexus_types::external_api::vpc::{ + VpcSubnet, VpcSubnetCreate, VpcSubnetUpdate, +}; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; use omicron_common::api::external::InstanceState; @@ -175,7 +177,7 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { segments[3] = 1; let addr = std::net::Ipv6Addr::from(segments); let other_ipv6_block = Some(Ipv6Net::new(addr, 64).unwrap()); - let new_subnet = params::VpcSubnetCreate { + let new_subnet = VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: subnet_name.parse().unwrap(), description: "it's below the net".to_string(), @@ -227,7 +229,7 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { subnets_eq(&subnets[0], &subnet); // creating another subnet in the same VPC with the same IP ranges fails - let new_subnet = params::VpcSubnetCreate { + let new_subnet = VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: "new-name".parse().unwrap(), description: "it's below the net".to_string(), @@ -255,7 +257,7 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { // creating another subnet in the same VPC with the same name, but different // IP address ranges also fails. - let new_subnet = params::VpcSubnetCreate { + let new_subnet = VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: subnet_name.parse().unwrap(), description: "it's below the net".to_string(), @@ -300,7 +302,7 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { // create second subnet, this time with an autogenerated IPv6 range. let ipv4_block = "192.168.0.0/16".parse().unwrap(); - let new_subnet = params::VpcSubnetCreate { + let new_subnet = VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: subnet2_name.parse().unwrap(), description: "it's also below the net".to_string(), @@ -331,7 +333,7 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { subnets_eq(&subnets[1], &subnet2); // update first subnet - let update_params = params::VpcSubnetUpdate { + let update_params = VpcSubnetUpdate { identity: IdentityMetadataUpdateParams { name: Some("new-name".parse().unwrap()), description: Some("another description".to_string()), diff --git a/nexus/tests/integration_tests/vpcs.rs b/nexus/tests/integration_tests/vpcs.rs index 0db87135a8b..5f1fc37873b 100644 --- a/nexus/tests/integration_tests/vpcs.rs +++ b/nexus/tests/integration_tests/vpcs.rs @@ -16,9 +16,14 @@ use nexus_test_utils::resource_helpers::{ create_vpc_with_error, grant_iam, objects_list_page_authz, test_params, }; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::params; -use nexus_types::external_api::shared::{ProjectRole, SiloRole}; -use nexus_types::external_api::views::{Silo, Vpc}; +use nexus_types::external_api::floating_ip; +use nexus_types::external_api::instance; +use nexus_types::external_api::internet_gateway; +use nexus_types::external_api::ip_pool; +use nexus_types::external_api::policy; +use nexus_types::external_api::project; +use nexus_types::external_api::silo; +use nexus_types::external_api::vpc; use nexus_types::identity::{Asset, Resource}; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::IdentityMetadataUpdateParams; @@ -90,7 +95,7 @@ async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { NexusRequest::new( RequestBuilder::new(client, Method::POST, &vpcs_url) .expect_status(Some(StatusCode::BAD_REQUEST)) - .body(Some(¶ms::VpcCreate { + .body(Some(&vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: "just-rainsticks".parse().unwrap(), description: String::from("vpc description"), @@ -131,7 +136,7 @@ async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { assert_eq!(error.message, "already exists: vpc \"just-rainsticks\""); // creating a VPC with the same name in another project works, though - let vpc2: Vpc = create_vpc(&client, PROJECT_NAME_2, vpc_name).await; + let vpc2: vpc::Vpc = create_vpc(&client, PROJECT_NAME_2, vpc_name).await; assert_eq!(vpc2.identity.name, "just-rainsticks"); // List VPCs again and expect to find the one we just created. @@ -145,7 +150,7 @@ async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { vpcs_eq(&vpcs[1], &vpc); // Update the VPC - let update_params = params::VpcUpdate { + let update_params = vpc::VpcUpdate { identity: IdentityMetadataUpdateParams { name: Some("new-name".parse().unwrap()), description: Some("another description".to_string()), @@ -230,11 +235,14 @@ async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { vpcs_eq(&vpcs[0], &default_vpc); } -async fn vpcs_list(client: &ClientTestContext, vpcs_url: &str) -> Vec { - objects_list_page_authz::(client, vpcs_url).await.items +async fn vpcs_list( + client: &ClientTestContext, + vpcs_url: &str, +) -> Vec { + objects_list_page_authz::(client, vpcs_url).await.items } -async fn vpc_get(client: &ClientTestContext, vpc_url: &str) -> Vpc { +async fn vpc_get(client: &ClientTestContext, vpc_url: &str) -> vpc::Vpc { NexusRequest::object_get(client, vpc_url) .authn_as(AuthnMode::PrivilegedUser) .execute_and_parse_unwrap() @@ -244,15 +252,15 @@ async fn vpc_get(client: &ClientTestContext, vpc_url: &str) -> Vpc { async fn vpc_put( client: &ClientTestContext, vpc_url: &str, - params: params::VpcUpdate, -) -> Vpc { + params: vpc::VpcUpdate, +) -> vpc::Vpc { NexusRequest::object_put(client, vpc_url, Some(¶ms)) .authn_as(AuthnMode::PrivilegedUser) .execute_and_parse_unwrap() .await } -fn vpcs_eq(vpc1: &Vpc, vpc2: &Vpc) { +fn vpcs_eq(vpc1: &vpc::Vpc, vpc2: &vpc::Vpc) { identity_eq(&vpc1.identity, &vpc2.identity); assert_eq!(vpc1.project_id, vpc2.project_id); } @@ -270,7 +278,7 @@ async fn test_vpc_limited_collaborator_role( let vpcs_url = format!("/v1/vpcs?project={}", project_name); use nexus_db_queries::db::fixed_data::silo::DEFAULT_SILO; - let silo: Silo = NexusRequest::object_get( + let silo: silo::Silo = NexusRequest::object_get( client, &format!("/v1/system/silos/{}", DEFAULT_SILO.name()), ) @@ -278,11 +286,11 @@ async fn test_vpc_limited_collaborator_role( .execute_and_parse_unwrap() .await; - // Grant the Project Collaborator role to a local user + // Grant the project::Project Collaborator role to a local user grant_iam( client, &project_url, - ProjectRole::Collaborator, + policy::ProjectRole::Collaborator, USER_TEST_UNPRIVILEGED.id(), AuthnMode::PrivilegedUser, ) @@ -301,7 +309,7 @@ async fn test_vpc_limited_collaborator_role( grant_iam( client, &silo_url, - SiloRole::LimitedCollaborator, + policy::SiloRole::LimitedCollaborator, limited_user.id, AuthnMode::PrivilegedUser, ) @@ -309,10 +317,10 @@ async fn test_vpc_limited_collaborator_role( // Test 1: Verify the PrivilegedUser can create a VPC let vpc_name = "test-vpc"; - let _vpc_priv: Vpc = NexusRequest::objects_post( + let _vpc_priv: vpc::Vpc = NexusRequest::objects_post( client, &vpcs_url, - ¶ms::VpcCreate { + &vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: vpc_name.parse().unwrap(), description: "test vpc".to_string(), @@ -330,10 +338,10 @@ async fn test_vpc_limited_collaborator_role( // Test 2: Unprivileged user with Collaborator role CAN create a VPC let vpc_name2 = "test-vpc-2"; - let vpc: Vpc = NexusRequest::objects_post( + let vpc: vpc::Vpc = NexusRequest::objects_post( client, &vpcs_url, - ¶ms::VpcCreate { + &vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: vpc_name2.parse().unwrap(), description: "test vpc 2".to_string(), @@ -354,12 +362,12 @@ async fn test_vpc_limited_collaborator_role( // Test 3: User with silo.limited-collaborator role CAN read/list VPCs // (inherits project.limited-collaborator → project.viewer) - let vpcs_list: Vec = NexusRequest::object_get(client, &vpcs_url) + let vpcs_list: Vec = NexusRequest::object_get(client, &vpcs_url) .authn_as(AuthnMode::SiloUser(limited_user.id)) .execute() .await .expect("silo.limited-collaborator should be able to list VPCs") - .parsed_body::>() + .parsed_body::>() .unwrap() .items; assert!( @@ -371,7 +379,7 @@ async fn test_vpc_limited_collaborator_role( // (inherits project.limited-collaborator, which cannot modify networking) NexusRequest::new( RequestBuilder::new(client, Method::POST, &vpcs_url) - .body(Some(¶ms::VpcCreate { + .body(Some(&vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: "forbidden-vpc".parse().unwrap(), description: "should not be created".to_string(), @@ -399,7 +407,7 @@ async fn test_limited_collaborator_can_create_instance( create_project(&client, &project_name).await; use nexus_db_queries::db::fixed_data::silo::DEFAULT_SILO; - let silo: Silo = NexusRequest::object_get( + let silo: silo::Silo = NexusRequest::object_get( client, &format!("/v1/system/silos/{}", DEFAULT_SILO.name()), ) @@ -420,7 +428,7 @@ async fn test_limited_collaborator_can_create_instance( grant_iam( client, &silo_url, - SiloRole::LimitedCollaborator, + policy::SiloRole::LimitedCollaborator, limited_user.id, AuthnMode::PrivilegedUser, ) @@ -428,16 +436,16 @@ async fn test_limited_collaborator_can_create_instance( // First verify the project exists and user can see it use dropshot::ResultsPage; - use nexus_types::external_api::views::Project; let projects_url = "/v1/projects"; - let projects: Vec = NexusRequest::object_get(client, projects_url) - .authn_as(AuthnMode::SiloUser(limited_user.id)) - .execute() - .await - .expect("limited-collaborator should be able to list projects") - .parsed_body::>() - .unwrap() - .items; + let projects: Vec = + NexusRequest::object_get(client, projects_url) + .authn_as(AuthnMode::SiloUser(limited_user.id)) + .execute() + .await + .expect("limited-collaborator should be able to list projects") + .parsed_body::>() + .unwrap() + .items; assert!( projects.iter().any(|p| p.identity.name == project_name), @@ -451,7 +459,7 @@ async fn test_limited_collaborator_can_create_instance( let instance: Instance = NexusRequest::objects_post( client, &instances_url, - ¶ms::InstanceCreate { + &instance::InstanceCreate { identity: IdentityMetadataCreateParams { name: instance_name.parse().unwrap(), description: "test instance created by limited-collaborator" @@ -464,7 +472,7 @@ async fn test_limited_collaborator_can_create_instance( user_data: vec![], ssh_public_keys: None, network_interfaces: - params::InstanceNetworkInterfaceAttachment::DefaultIpv4, + instance::InstanceNetworkInterfaceAttachment::DefaultIpv4, external_ips: vec![], multicast_groups: vec![], disks: vec![], @@ -497,7 +505,7 @@ async fn test_limited_collaborator_blocked_from_networking_resources( create_project(&client, &project_name).await; use nexus_db_queries::db::fixed_data::silo::DEFAULT_SILO; - let silo: Silo = NexusRequest::object_get( + let silo: silo::Silo = NexusRequest::object_get( client, &format!("/v1/system/silos/{}", DEFAULT_SILO.name()), ) @@ -518,7 +526,7 @@ async fn test_limited_collaborator_blocked_from_networking_resources( grant_iam( client, &silo_url, - SiloRole::LimitedCollaborator, + policy::SiloRole::LimitedCollaborator, limited_user.id, AuthnMode::PrivilegedUser, ) @@ -528,7 +536,7 @@ async fn test_limited_collaborator_blocked_from_networking_resources( let vpcs_url = format!("/v1/vpcs?project={}", project_name); NexusRequest::new( RequestBuilder::new(client, Method::POST, &vpcs_url) - .body(Some(¶ms::VpcCreate { + .body(Some(&vpc::VpcCreate { identity: IdentityMetadataCreateParams { name: "forbidden-vpc".parse().unwrap(), description: "should not be created".to_string(), @@ -548,7 +556,7 @@ async fn test_limited_collaborator_blocked_from_networking_resources( format!("/v1/vpc-subnets?project={}&vpc=default", project_name); NexusRequest::new( RequestBuilder::new(client, Method::POST, &subnets_url) - .body(Some(¶ms::VpcSubnetCreate { + .body(Some(&vpc::VpcSubnetCreate { identity: IdentityMetadataCreateParams { name: "forbidden-subnet".parse().unwrap(), description: "should not be created".to_string(), @@ -569,7 +577,7 @@ async fn test_limited_collaborator_blocked_from_networking_resources( format!("/v1/vpc-routers?project={}&vpc=default", project_name); NexusRequest::new( RequestBuilder::new(client, Method::POST, &routers_url) - .body(Some(¶ms::VpcRouterCreate { + .body(Some(&vpc::VpcRouterCreate { identity: IdentityMetadataCreateParams { name: "forbidden-router".parse().unwrap(), description: "should not be created".to_string(), @@ -587,7 +595,7 @@ async fn test_limited_collaborator_blocked_from_networking_resources( format!("/v1/internet-gateways?project={}&vpc=default", project_name); NexusRequest::new( RequestBuilder::new(client, Method::POST, &igw_url) - .body(Some(¶ms::InternetGatewayCreate { + .body(Some(&internet_gateway::InternetGatewayCreate { identity: IdentityMetadataCreateParams { name: "forbidden-gateway".parse().unwrap(), description: "should not be created".to_string(), @@ -607,7 +615,7 @@ async fn test_limited_collaborator_blocked_from_networking_resources( let _igw = NexusRequest::objects_post( client, &igw_url, - ¶ms::InternetGatewayCreate { + &internet_gateway::InternetGatewayCreate { identity: IdentityMetadataCreateParams { name: igw_name.parse().unwrap(), description: "test gateway".to_string(), @@ -618,14 +626,14 @@ async fn test_limited_collaborator_blocked_from_networking_resources( .execute() .await .expect("privileged user should create gateway") - .parsed_body::() + .parsed_body::() .unwrap(); let router_name = "test-router"; let _router = NexusRequest::objects_post( client, &routers_url, - ¶ms::VpcRouterCreate { + &vpc::VpcRouterCreate { identity: IdentityMetadataCreateParams { name: router_name.parse().unwrap(), description: "test router".to_string(), @@ -636,7 +644,7 @@ async fn test_limited_collaborator_blocked_from_networking_resources( .execute() .await .expect("privileged user should create router") - .parsed_body::() + .parsed_body::() .unwrap(); // Test 4: Cannot create router route @@ -646,7 +654,7 @@ async fn test_limited_collaborator_blocked_from_networking_resources( ); NexusRequest::new( RequestBuilder::new(client, Method::POST, &routes_url) - .body(Some(¶ms::RouterRouteCreate { + .body(Some(&vpc::RouterRouteCreate { identity: IdentityMetadataCreateParams { name: "forbidden-route".parse().unwrap(), description: "should not be created".to_string(), @@ -668,7 +676,7 @@ async fn test_limited_collaborator_blocked_from_networking_resources( ); NexusRequest::new( RequestBuilder::new(client, Method::POST, &pool_attach_url) - .body(Some(¶ms::InternetGatewayIpPoolCreate { + .body(Some(&internet_gateway::InternetGatewayIpPoolCreate { identity: IdentityMetadataCreateParams { name: "forbidden-pool-attach".parse().unwrap(), description: "should not be created".to_string(), @@ -728,7 +736,7 @@ async fn test_limited_collaborator_can_manage_floating_ips_and_nics( create_project(&client, &project_name).await; use nexus_db_queries::db::fixed_data::silo::DEFAULT_SILO; - let silo: Silo = NexusRequest::object_get( + let silo: silo::Silo = NexusRequest::object_get( client, &format!("/v1/system/silos/{}", DEFAULT_SILO.name()), ) @@ -749,7 +757,7 @@ async fn test_limited_collaborator_can_manage_floating_ips_and_nics( grant_iam( client, &silo_url, - SiloRole::LimitedCollaborator, + policy::SiloRole::LimitedCollaborator, limited_user.id, AuthnMode::PrivilegedUser, ) @@ -758,17 +766,16 @@ async fn test_limited_collaborator_can_manage_floating_ips_and_nics( // Test 1: Limited-collaborator CAN create a floating IP let fips_url = format!("/v1/floating-ips?project={}", project_name); let fip_name = "test-fip"; - use nexus_types::external_api::views::FloatingIp; - let fip: FloatingIp = NexusRequest::objects_post( + let fip: floating_ip::FloatingIp = NexusRequest::objects_post( client, &fips_url, - ¶ms::FloatingIpCreate { + &floating_ip::FloatingIpCreate { identity: IdentityMetadataCreateParams { name: fip_name.parse().unwrap(), description: "test floating ip".to_string(), }, - address_allocator: params::AddressAllocator::Auto { - pool_selector: params::PoolSelector::Explicit { + address_allocator: floating_ip::AddressAllocator::Auto { + pool_selector: ip_pool::PoolSelector::Explicit { pool: v4_pool.identity.name.clone().into(), }, }, @@ -785,10 +792,10 @@ async fn test_limited_collaborator_can_manage_floating_ips_and_nics( // Test 2: Limited-collaborator CAN update a floating IP let fip_url = format!("/v1/floating-ips/{}?project={}", fip_name, project_name); - let updated_fip: FloatingIp = NexusRequest::object_put( + let updated_fip: floating_ip::FloatingIp = NexusRequest::object_put( client, &fip_url, - Some(¶ms::FloatingIpUpdate { + Some(&floating_ip::FloatingIpUpdate { identity: IdentityMetadataUpdateParams { name: Some(fip_name.parse().unwrap()), description: Some("updated description".to_string()), @@ -813,5 +820,5 @@ async fn test_limited_collaborator_can_manage_floating_ips_and_nics( // Note: We don't explicitly test NIC CRUD here because: // - NIC creation is tested in test_limited_collaborator_can_create_instance // (NICs are created as part of instance creation) - // - The key authorization check (Read on VpcSubnet) is what we fixed + // - The key authorization check (Read on vpc::VpcSubnet) is what we fixed } diff --git a/nexus/tests/integration_tests/webhooks.rs b/nexus/tests/integration_tests/webhooks.rs index 33d7cfe235c..eb8d5a41d9e 100644 --- a/nexus/tests/integration_tests/webhooks.rs +++ b/nexus/tests/integration_tests/webhooks.rs @@ -16,7 +16,13 @@ use nexus_test_utils::http_testing::NexusRequest; use nexus_test_utils::http_testing::RequestBuilder; use nexus_test_utils::resource_helpers; use nexus_test_utils_macros::nexus_test; -use nexus_types::external_api::{params, shared, views}; +use nexus_types::external_api::alert::{ + AlertDelivery, AlertDeliveryAttempts, AlertDeliveryId, AlertDeliveryState, + AlertDeliveryTrigger, AlertProbeResult, AlertReceiver, AlertSubscription, + AlertSubscriptionCreate, AlertSubscriptionCreated, WebhookCreate, + WebhookDeliveryAttemptResult, WebhookReceiver, WebhookSecret, + WebhookSecretCreate, WebhookSecrets, +}; use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::NameOrId; use omicron_uuid_kinds::AlertReceiverUuid; @@ -36,12 +42,13 @@ const SECRETS_BASE_PATH: &str = "/v1/webhook-secrets"; async fn webhook_create( ctx: &ControlPlaneTestContext, - params: ¶ms::WebhookCreate, -) -> views::WebhookReceiver { - resource_helpers::object_create::< - params::WebhookCreate, - views::WebhookReceiver, - >(&ctx.external_client, WEBHOOK_RECEIVERS_BASE_PATH, params) + params: &WebhookCreate, +) -> WebhookReceiver { + resource_helpers::object_create::( + &ctx.external_client, + WEBHOOK_RECEIVERS_BASE_PATH, + params, + ) .await } @@ -53,7 +60,7 @@ fn alert_rx_url(name_or_id: impl Into) -> String { async fn alert_rx_get( client: &ClientTestContext, webhook_url: &str, -) -> views::AlertReceiver { +) -> AlertReceiver { alert_rx_get_as(client, webhook_url, AuthnMode::PrivilegedUser).await } @@ -61,7 +68,7 @@ async fn alert_rx_get_as( client: &ClientTestContext, webhook_url: &str, authn_as: AuthnMode, -) -> views::AlertReceiver { +) -> AlertReceiver { NexusRequest::object_get(client, &webhook_url) .authn_as(authn_as) .execute() @@ -71,10 +78,8 @@ async fn alert_rx_get_as( .unwrap() } -async fn alert_rx_list( - client: &ClientTestContext, -) -> Vec { - resource_helpers::objects_list_page_authz::( +async fn alert_rx_list(client: &ClientTestContext) -> Vec { + resource_helpers::objects_list_page_authz::( client, ALERT_RECEIVERS_BASE_PATH, ) @@ -85,7 +90,7 @@ async fn alert_rx_list( async fn webhook_secrets_get( client: &ClientTestContext, webhook_name_or_id: impl Into, -) -> views::WebhookSecrets { +) -> WebhookSecrets { let name_or_id = webhook_name_or_id.into(); NexusRequest::object_get( client, @@ -110,7 +115,7 @@ fn resend_url( async fn alert_deliveries_list( client: &ClientTestContext, webhook_name_or_id: impl Into, -) -> Collection { +) -> Collection { let mut rx_url = alert_rx_url(webhook_name_or_id); rx_url.push_str("/deliveries"); NexusRequest::iter_collection_authn(client, &rx_url, "", None) @@ -122,7 +127,7 @@ async fn alert_delivery_resend( client: &ClientTestContext, webhook_name_or_id: impl Into, alert_id: AlertUuid, -) -> views::AlertDeliveryId { +) -> AlertDeliveryId { let req = RequestBuilder::new( client, http::Method::POST, @@ -161,10 +166,8 @@ async fn webhook_delivery_resend_error( .unwrap() } -fn my_great_webhook_params( - mock: &httpmock::MockServer, -) -> params::WebhookCreate { - params::WebhookCreate { +fn my_great_webhook_params(mock: &httpmock::MockServer) -> WebhookCreate { + WebhookCreate { identity: my_great_webhook_identity(), endpoint: mock .url("/webhooks") @@ -187,12 +190,9 @@ const MY_COOL_SECRET: &str = "my cool secret"; async fn secret_add( ctx: &ControlPlaneTestContext, webhook_id: AlertReceiverUuid, - params: ¶ms::WebhookSecretCreate, -) -> views::WebhookSecret { - resource_helpers::object_create::< - params::WebhookSecretCreate, - views::WebhookSecret, - >( + params: &WebhookSecretCreate, +) -> WebhookSecret { + resource_helpers::object_create::( &ctx.external_client, &format!("{SECRETS_BASE_PATH}/?receiver={webhook_id}"), params, @@ -203,12 +203,12 @@ async fn secret_add( async fn subscription_add( ctx: &ControlPlaneTestContext, webhook_id: AlertReceiverUuid, - subscription: &shared::AlertSubscription, -) -> views::AlertSubscriptionCreated { + subscription: &AlertSubscription, +) -> AlertSubscriptionCreated { resource_helpers::object_create( &ctx.external_client, &format!("{ALERT_RECEIVERS_BASE_PATH}/{webhook_id}/subscriptions"), - ¶ms::AlertSubscriptionCreate { subscription: subscription.clone() }, + &AlertSubscriptionCreate { subscription: subscription.clone() }, ) .await } @@ -216,7 +216,7 @@ async fn subscription_add( async fn subscription_remove( ctx: &ControlPlaneTestContext, webhook_id: AlertReceiverUuid, - subscription: &shared::AlertSubscription, + subscription: &AlertSubscription, ) { resource_helpers::object_delete( &ctx.external_client, @@ -227,7 +227,7 @@ async fn subscription_remove( fn subscription_remove_url( webhook_id: AlertReceiverUuid, - subscription: &shared::AlertSubscription, + subscription: &AlertSubscription, ) -> String { format!( "{ALERT_RECEIVERS_BASE_PATH}/{webhook_id}/subscriptions/{subscription}" @@ -239,7 +239,7 @@ async fn alert_receiver_send_probe( webhook_id: &AlertReceiverUuid, resend: bool, status: http::StatusCode, -) -> views::AlertProbeResult { +) -> AlertProbeResult { let pathparams = if resend { "?resend=true" } else { "" }; let path = format!("{ALERT_RECEIVERS_BASE_PATH}/{webhook_id}/probe{pathparams}"); @@ -258,7 +258,7 @@ async fn alert_receiver_send_probe( } fn is_valid_for_webhook( - webhook: &views::WebhookReceiver, + webhook: &WebhookReceiver, ) -> impl FnOnce(httpmock::When) -> httpmock::When + use<> { let path = webhook.config.endpoint.path().to_string(); let id = webhook.identity.id.to_string(); @@ -323,7 +323,7 @@ fn signature_verifies( } struct ExpectAttempt { - result: views::WebhookDeliveryAttemptResult, + result: WebhookDeliveryAttemptResult, status: Option, } @@ -331,10 +331,10 @@ struct ExpectAttempt { /// such as timestamps, which are variable. #[track_caller] fn expect_delivery_attempts( - actual: &views::AlertDeliveryAttempts, + actual: &AlertDeliveryAttempts, expected: &[ExpectAttempt], ) { - let views::AlertDeliveryAttempts::Webhook(actual) = actual; + let AlertDeliveryAttempts::Webhook(actual) = actual; assert_eq!( actual.len(), expected.len(), @@ -358,7 +358,7 @@ async fn test_webhook_receiver_get(cptestctx: &ControlPlaneTestContext) { // Create a webhook receiver. let created_webhook = webhook_create( &cptestctx, - ¶ms::WebhookCreate { + &WebhookCreate { identity: my_great_webhook_identity(), endpoint: "https://example.com/webhooks" .parse() @@ -390,7 +390,7 @@ async fn test_webhook_receiver_create_delete( // Create a webhook receiver. let created_webhook = webhook_create( &cptestctx, - ¶ms::WebhookCreate { + &WebhookCreate { identity: my_great_webhook_identity(), endpoint: "https://example.com/webhooks" .parse() @@ -423,7 +423,7 @@ async fn test_webhook_receiver_names_are_unique( // Create a webhook receiver. let created_webhook = webhook_create( &cptestctx, - ¶ms::WebhookCreate { + &WebhookCreate { identity: my_great_webhook_identity(), endpoint: "https://example.com/webhooks" .parse() @@ -438,7 +438,7 @@ async fn test_webhook_receiver_names_are_unique( let error = resource_helpers::object_create_error( &client, WEBHOOK_RECEIVERS_BASE_PATH, - ¶ms::WebhookCreate { + &WebhookCreate { identity: my_great_webhook_identity(), endpoint: "https://example.com/more-webhooks" .parse() @@ -462,7 +462,7 @@ async fn test_cannot_subscribe_to_probes(cptestctx: &ControlPlaneTestContext) { let error = resource_helpers::object_create_error( &client, WEBHOOK_RECEIVERS_BASE_PATH, - ¶ms::WebhookCreate { + &WebhookCreate { identity: my_great_webhook_identity(), endpoint: "https://example.com/webhooks" .parse() @@ -569,7 +569,7 @@ async fn test_multiple_secrets(cptestctx: &ControlPlaneTestContext) { // Create a webhook receiver. let webhook = webhook_create( &cptestctx, - ¶ms::WebhookCreate { + &WebhookCreate { identity: IdentityMetadataCreateParams { name: "my-great-webhook".parse().unwrap(), description: String::from("my great webhook"), @@ -605,7 +605,7 @@ async fn test_multiple_secrets(cptestctx: &ControlPlaneTestContext) { secret_add( &cptestctx, rx_id, - ¶ms::WebhookSecretCreate { secret: SECRET2.to_string() }, + &WebhookSecretCreate { secret: SECRET2.to_string() }, ) .await ) @@ -617,7 +617,7 @@ async fn test_multiple_secrets(cptestctx: &ControlPlaneTestContext) { secret_add( &cptestctx, rx_id, - ¶ms::WebhookSecretCreate { secret: SECRET3.to_string() }, + &WebhookSecretCreate { secret: SECRET3.to_string() }, ) .await ) @@ -682,20 +682,19 @@ async fn test_multiple_receivers(cptestctx: &ControlPlaneTestContext) { let bar_alert_id = AlertUuid::new_v4(); let baz_alert_id = AlertUuid::new_v4(); - let assert_webhook_rx_list_matches = - |mut expected: Vec| async move { - let mut actual = alert_rx_list(client).await; - actual.sort_by_key(|rx| rx.identity.id); - expected.sort_by_key(|rx| rx.identity.id); - assert_eq!(expected, actual); - }; + let assert_webhook_rx_list_matches = |mut expected: Vec| async move { + let mut actual = alert_rx_list(client).await; + actual.sort_by_key(|rx| rx.identity.id); + expected.sort_by_key(|rx| rx.identity.id); + assert_eq!(expected, actual); + }; // Create three webhook receivers let srv_bar = httpmock::MockServer::start_async().await; const BAR_SECRET: &str = "this is bar's secret"; let rx_bar = webhook_create( &cptestctx, - ¶ms::WebhookCreate { + &WebhookCreate { identity: IdentityMetadataCreateParams { name: "webhooked-on-phonics".parse().unwrap(), description: String::from("webhooked on phonics"), @@ -732,7 +731,7 @@ async fn test_multiple_receivers(cptestctx: &ControlPlaneTestContext) { const BAZ_SECRET: &str = "this is baz's secret"; let rx_baz = webhook_create( &cptestctx, - ¶ms::WebhookCreate { + &WebhookCreate { identity: IdentityMetadataCreateParams { name: "webhook-line-and-sinker".parse().unwrap(), description: String::from("webhook, line, and sinker"), @@ -773,7 +772,7 @@ async fn test_multiple_receivers(cptestctx: &ControlPlaneTestContext) { const STAR_SECRET: &str = "this is star's secret"; let rx_star = webhook_create( &cptestctx, - ¶ms::WebhookCreate { + &WebhookCreate { identity: IdentityMetadataCreateParams { name: "globulated".parse().unwrap(), description: String::from("this one has globs"), @@ -929,12 +928,12 @@ async fn test_retry_backoff(cptestctx: &ControlPlaneTestContext) { assert_eq!(delivery.receiver_id.into_untyped_uuid(), webhook.identity.id); assert_eq!(delivery.alert_id, id); assert_eq!(delivery.alert_class, "test.foo"); - assert_eq!(delivery.state, views::AlertDeliveryState::Pending); + assert_eq!(delivery.state, AlertDeliveryState::Pending); expect_delivery_attempts( &delivery.attempts, &[ExpectAttempt { status: Some(500), - result: views::WebhookDeliveryAttemptResult::FailedHttpError, + result: WebhookDeliveryAttemptResult::FailedHttpError, }], ); @@ -1003,17 +1002,17 @@ async fn test_retry_backoff(cptestctx: &ControlPlaneTestContext) { assert_eq!(delivery.receiver_id.into_untyped_uuid(), webhook.identity.id); assert_eq!(delivery.alert_id, id); assert_eq!(delivery.alert_class, "test.foo"); - assert_eq!(delivery.state, views::AlertDeliveryState::Pending); + assert_eq!(delivery.state, AlertDeliveryState::Pending); expect_delivery_attempts( &delivery.attempts, &[ ExpectAttempt { status: Some(500), - result: views::WebhookDeliveryAttemptResult::FailedHttpError, + result: WebhookDeliveryAttemptResult::FailedHttpError, }, ExpectAttempt { status: Some(503), - result: views::WebhookDeliveryAttemptResult::FailedHttpError, + result: WebhookDeliveryAttemptResult::FailedHttpError, }, ], ); @@ -1072,21 +1071,21 @@ async fn test_retry_backoff(cptestctx: &ControlPlaneTestContext) { assert_eq!(delivery.receiver_id.into_untyped_uuid(), webhook.identity.id); assert_eq!(delivery.alert_id, id); assert_eq!(delivery.alert_class, "test.foo"); - assert_eq!(delivery.state, views::AlertDeliveryState::Delivered); + assert_eq!(delivery.state, AlertDeliveryState::Delivered); expect_delivery_attempts( &delivery.attempts, &[ ExpectAttempt { status: Some(500), - result: views::WebhookDeliveryAttemptResult::FailedHttpError, + result: WebhookDeliveryAttemptResult::FailedHttpError, }, ExpectAttempt { status: Some(503), - result: views::WebhookDeliveryAttemptResult::FailedHttpError, + result: WebhookDeliveryAttemptResult::FailedHttpError, }, ExpectAttempt { status: Some(200), - result: views::WebhookDeliveryAttemptResult::Succeeded, + result: WebhookDeliveryAttemptResult::Succeeded, }, ], ); @@ -1150,13 +1149,13 @@ async fn test_probe(cptestctx: &ControlPlaneTestContext) { expect_delivery_attempts( &probe1.probe.attempts, &[ExpectAttempt { - result: views::WebhookDeliveryAttemptResult::FailedTimeout, + result: WebhookDeliveryAttemptResult::FailedTimeout, status: None, }], ); assert_eq!(probe1.probe.alert_class, "probe"); - assert_eq!(probe1.probe.trigger, views::AlertDeliveryTrigger::Probe); - assert_eq!(probe1.probe.state, views::AlertDeliveryState::Failed); + assert_eq!(probe1.probe.trigger, AlertDeliveryTrigger::Probe); + assert_eq!(probe1.probe.state, AlertDeliveryState::Failed); assert_eq!( probe1.resends_started, None, "we did not request events be resent" @@ -1195,13 +1194,13 @@ async fn test_probe(cptestctx: &ControlPlaneTestContext) { expect_delivery_attempts( &probe2.probe.attempts, &[ExpectAttempt { - result: views::WebhookDeliveryAttemptResult::FailedHttpError, + result: WebhookDeliveryAttemptResult::FailedHttpError, status: Some(503), }], ); assert_eq!(probe2.probe.alert_class, "probe"); - assert_eq!(probe2.probe.trigger, views::AlertDeliveryTrigger::Probe); - assert_eq!(probe2.probe.state, views::AlertDeliveryState::Failed); + assert_eq!(probe2.probe.trigger, AlertDeliveryTrigger::Probe); + assert_eq!(probe2.probe.state, AlertDeliveryState::Failed); assert_ne!( probe2.probe.id, probe1.probe.id, "a new delivery ID should be assigned to each probe" @@ -1244,13 +1243,13 @@ async fn test_probe(cptestctx: &ControlPlaneTestContext) { expect_delivery_attempts( &probe3.probe.attempts, &[ExpectAttempt { - result: views::WebhookDeliveryAttemptResult::Succeeded, + result: WebhookDeliveryAttemptResult::Succeeded, status: Some(200), }], ); assert_eq!(probe3.probe.alert_class, "probe"); - assert_eq!(probe3.probe.trigger, views::AlertDeliveryTrigger::Probe); - assert_eq!(probe3.probe.state, views::AlertDeliveryState::Delivered); + assert_eq!(probe3.probe.trigger, AlertDeliveryTrigger::Probe); + assert_eq!(probe3.probe.state, AlertDeliveryState::Delivered); assert_ne!( probe3.probe.id, probe1.probe.id, "a new delivery ID should be assigned to each probe" @@ -1409,7 +1408,7 @@ async fn test_probe_resends_failed_deliveries( dbg!(&probe); probe_mock.assert_async().await; probe_mock.delete_async().await; - assert_eq!(probe.probe.state, views::AlertDeliveryState::Delivered); + assert_eq!(probe.probe.state, AlertDeliveryState::Delivered); assert_eq!(probe.resends_started, Some(2)); // Both events should be resent. @@ -1627,7 +1626,7 @@ async fn subscription_add_test( let rx_id = AlertReceiverUuid::from_untyped_uuid(webhook.identity.id); let new_subscription = - new_subscription.parse::().unwrap(); + new_subscription.parse::().unwrap(); dbg!(subscription_add(&cptestctx, rx_id, &new_subscription).await); // The new subscription should be there. @@ -1692,15 +1691,14 @@ async fn subscription_remove_test( let id2 = AlertUuid::new_v4(); let id3 = AlertUuid::new_v4(); - let other_subscription = - "test.foo".parse::().unwrap(); + let other_subscription = "test.foo".parse::().unwrap(); let deleted_subscription = - deleted_subscription.parse::().unwrap(); + deleted_subscription.parse::().unwrap(); // Create a webhook receiver. let webhook = webhook_create( &cptestctx, - ¶ms::WebhookCreate { + &WebhookCreate { subscriptions: vec![ other_subscription.clone(), deleted_subscription.clone(), diff --git a/nexus/types/Cargo.toml b/nexus/types/Cargo.toml index 07abe335cc2..eff3bf9d4cc 100644 --- a/nexus/types/Cargo.toml +++ b/nexus/types/Cargo.toml @@ -58,6 +58,7 @@ trust-quorum-types.workspace = true tufaceous-artifact.workspace = true newtype-uuid.workspace = true +nexus-types-versions.workspace = true update-engine.workspace = true unicode-width.workspace = true uuid.workspace = true diff --git a/nexus/types/src/deployment.rs b/nexus/types/src/deployment.rs index 99f739e2da8..a08cf778ddb 100644 --- a/nexus/types/src/deployment.rs +++ b/nexus/types/src/deployment.rs @@ -12,7 +12,7 @@ //! nexus/db-model, but nexus/reconfigurator/planning does not currently know //! about nexus/db-model and it's convenient to separate these concerns.) -use crate::external_api::views::SledState; +use crate::external_api::sled::SledState; use crate::internal_api::params::DnsConfigParams; use crate::inventory::Collection; pub use crate::inventory::SourceNatConfigGeneric; diff --git a/nexus/types/src/deployment/execution/dns.rs b/nexus/types/src/deployment/execution/dns.rs index a212db79bde..009377fd8d9 100644 --- a/nexus/types/src/deployment/execution/dns.rs +++ b/nexus/types/src/deployment/execution/dns.rs @@ -177,7 +177,7 @@ pub fn blueprint_internal_dns_config( // names. let mut nrepo_depots = 6; for sled in sleds_by_id { - if !sled.policy().matches(SledFilter::TufArtifactReplication) { + if !SledFilter::TufArtifactReplication.matches_policy(sled.policy()) { continue; } diff --git a/nexus/types/src/deployment/execution/utils.rs b/nexus/types/src/deployment/execution/utils.rs index 8e5c235de68..ccdedbac262 100644 --- a/nexus/types/src/deployment/execution/utils.rs +++ b/nexus/types/src/deployment/execution/utils.rs @@ -14,7 +14,7 @@ use sled_agent_types_versions::latest::inventory::SledRole; use crate::{ deployment::{Blueprint, BlueprintZoneType, blueprint_zone_type}, - external_api::views::SledPolicy, + external_api::sled::SledPolicy, }; /// The minimal information needed to represent a sled in the context of diff --git a/nexus/types/src/deployment/planning_input.rs b/nexus/types/src/deployment/planning_input.rs index a2af81e5972..17cdb2ff08c 100644 --- a/nexus/types/src/deployment/planning_input.rs +++ b/nexus/types/src/deployment/planning_input.rs @@ -12,11 +12,11 @@ use super::OmicronZoneExternalIp; use super::OmicronZoneNetworkResources; use super::OmicronZoneNic; use crate::deployment::PlannerConfig; -use crate::external_api::views::PhysicalDiskPolicy; -use crate::external_api::views::PhysicalDiskState; -use crate::external_api::views::SledPolicy; -use crate::external_api::views::SledProvisionPolicy; -use crate::external_api::views::SledState; +use crate::external_api::physical_disk::PhysicalDiskPolicy; +use crate::external_api::physical_disk::PhysicalDiskState; +use crate::external_api::sled::SledPolicy; +use crate::external_api::sled::SledProvisionPolicy; +use crate::external_api::sled::SledState; use chrono::DateTime; use chrono::TimeDelta; use chrono::Utc; @@ -629,62 +629,65 @@ pub enum DiskFilter { } impl DiskFilter { - fn matches_policy_and_state( + /// Returns true if the given policy and state match this filter + pub fn matches_policy_and_state( self, policy: PhysicalDiskPolicy, state: PhysicalDiskState, ) -> bool { - policy.matches(self) && state.matches(self) + self.matches_policy(policy) && self.matches_state(state) } -} -impl PhysicalDiskPolicy { - /// Returns true if self matches the filter - pub fn matches(self, filter: DiskFilter) -> bool { - match self { - PhysicalDiskPolicy::InService => match filter { + /// Returns true if the given policy matches this filter + pub fn matches_policy(self, policy: PhysicalDiskPolicy) -> bool { + match policy { + PhysicalDiskPolicy::InService => match self { DiskFilter::All => true, DiskFilter::InService => true, }, - PhysicalDiskPolicy::Expunged => match filter { + PhysicalDiskPolicy::Expunged => match self { DiskFilter::All => true, DiskFilter::InService => false, }, } } - /// Returns all policies matching the given filter. - /// - /// This is meant for database access, and is generally paired with - /// [`PhysicalDiskState::all_matching`]. See `ApplyPhysicalDiskFilterExt` in - /// nexus-db-model. - pub fn all_matching(filter: DiskFilter) -> impl Iterator { - Self::iter().filter(move |state| state.matches(filter)) - } -} - -impl PhysicalDiskState { - /// Returns true if self matches the filter - pub fn matches(self, filter: DiskFilter) -> bool { - match self { - PhysicalDiskState::Active => match filter { + /// Returns true if the given state matches this filter + pub fn matches_state(self, state: PhysicalDiskState) -> bool { + match state { + PhysicalDiskState::Active => match self { DiskFilter::All => true, DiskFilter::InService => true, }, - PhysicalDiskState::Decommissioned => match filter { + PhysicalDiskState::Decommissioned => match self { DiskFilter::All => true, DiskFilter::InService => false, }, } } - /// Returns all state matching the given filter. + /// Returns all policies matching the given filter. + /// + /// This is meant for database access, and is generally paired with + /// [`DiskFilter::all_matching_states`]. See `ApplyPhysicalDiskFilterExt` in + /// nexus-db-model. + pub fn all_matching_policies( + self, + ) -> impl Iterator { + PhysicalDiskPolicy::iter() + .filter(move |policy| self.matches_policy(*policy)) + } + + /// Returns all states matching the given filter. /// /// This is meant for database access, and is generally paired with - /// [`PhysicalDiskPolicy::all_matching`]. See `ApplyPhysicalDiskFilterExt` in + /// [`DiskFilter::all_matching_policies`]. See `ApplyPhysicalDiskFilterExt` in /// nexus-db-model. - pub fn all_matching(filter: DiskFilter) -> impl Iterator { - Self::iter().filter(move |state| state.matches(filter)) + pub fn all_matching_states( + self, + ) -> impl Iterator { + PhysicalDiskState::iter() + .filter(move |state| self.matches_state(*state)) } } @@ -826,32 +829,30 @@ pub enum SledFilter { } impl SledFilter { - /// Returns true if self matches the provided policy and state. + /// Returns true if the provided policy and state match this filter. pub fn matches_policy_and_state( self, policy: SledPolicy, state: SledState, ) -> bool { - policy.matches(self) && state.matches(self) + self.matches_policy(policy) && self.matches_state(state) } -} -impl SledPolicy { - /// Returns true if self matches the filter. + /// Returns true if the given policy matches this filter. /// /// Any users of this must also compare against the [`SledState`], if /// relevant: a sled filter is fully matched when it matches both the /// policy and the state. See [`SledFilter::matches_policy_and_state`]. - pub fn matches(self, filter: SledFilter) -> bool { + pub fn matches_policy(self, policy: SledPolicy) -> bool { // Some notes: // // # Match style // // This code could be written in three ways: // - // 1. match self { match filter { ... } } - // 2. match filter { match self { ... } } - // 3. match (self, filter) { ... } + // 1. match policy { match self { ... } } + // 2. match self { match policy { ... } } + // 3. match (self, policy) { ... } // // We choose 1 here because we expect many filters and just a few // policies, and 1 is the easiest form to represent that. @@ -863,12 +864,12 @@ impl SledPolicy { // have a policy+state combo where the policy says the sled is in // service but the state is decommissioned, for example, but the two // separate types let us represent that. Code that ANDs - // policy.matches(filter) and state.matches(filter) naturally guards - // against those states. - match self { + // filter.matches_policy(policy) and filter.matches_state(state) + // naturally guards against those states. + match policy { SledPolicy::InService { provision_policy: SledProvisionPolicy::Provisionable, - } => match filter { + } => match self { SledFilter::All => true, SledFilter::Commissioned => true, SledFilter::Decommissioned => false, @@ -883,7 +884,7 @@ impl SledPolicy { }, SledPolicy::InService { provision_policy: SledProvisionPolicy::NonProvisionable, - } => match filter { + } => match self { SledFilter::All => true, SledFilter::Commissioned => true, SledFilter::Decommissioned => false, @@ -896,7 +897,7 @@ impl SledPolicy { SledFilter::TufArtifactReplication => true, SledFilter::SpsUpdatedByReconfigurator => true, }, - SledPolicy::Expunged => match filter { + SledPolicy::Expunged => match self { SledFilter::All => true, SledFilter::Commissioned => true, SledFilter::Decommissioned => true, @@ -912,26 +913,15 @@ impl SledPolicy { } } - /// Returns all policies matching the given filter. - /// - /// This is meant for database access, and is generally paired with - /// [`SledState::all_matching`]. See `ApplySledFilterExt` in - /// nexus-db-model. - pub fn all_matching(filter: SledFilter) -> impl Iterator { - Self::iter().filter(move |policy| policy.matches(filter)) - } -} - -impl SledState { - /// Returns true if self matches the filter. + /// Returns true if the given state matches this filter. /// /// Any users of this must also compare against the [`SledPolicy`], if /// relevant: a sled filter is fully matched when both the policy and the /// state match. See [`SledFilter::matches_policy_and_state`]. - pub fn matches(self, filter: SledFilter) -> bool { - // See `SledFilter::matches` above for some notes. - match self { - SledState::Active => match filter { + pub fn matches_state(self, state: SledState) -> bool { + // See `matches_policy` above for some notes. + match state { + SledState::Active => match self { SledFilter::All => true, SledFilter::Commissioned => true, SledFilter::Decommissioned => false, @@ -944,7 +934,7 @@ impl SledState { SledFilter::TufArtifactReplication => true, SledFilter::SpsUpdatedByReconfigurator => true, }, - SledState::Decommissioned => match filter { + SledState::Decommissioned => match self { SledFilter::All => true, SledFilter::Commissioned => false, SledFilter::Decommissioned => true, @@ -960,13 +950,22 @@ impl SledState { } } - /// Returns all policies matching the given filter. + /// Returns all policies matching this filter. + /// + /// This is meant for database access, and is generally paired with + /// [`SledFilter::all_matching_states`]. See `ApplySledFilterExt` in + /// nexus-db-model. + pub fn all_matching_policies(self) -> impl Iterator { + SledPolicy::iter().filter(move |policy| self.matches_policy(*policy)) + } + + /// Returns all states matching this filter. /// /// This is meant for database access, and is generally paired with - /// [`SledPolicy::all_matching`]. See `ApplySledFilterExt` in + /// [`SledFilter::all_matching_policies`]. See `ApplySledFilterExt` in /// nexus-db-model. - pub fn all_matching(filter: SledFilter) -> impl Iterator { - Self::iter().filter(move |state| state.matches(filter)) + pub fn all_matching_states(self) -> impl Iterator { + SledState::iter().filter(move |state| self.matches_state(*state)) } } diff --git a/nexus/types/src/external_api/affinity.rs b/nexus/types/src/external_api/affinity.rs new file mode 100644 index 00000000000..ddaaf8a6308 --- /dev/null +++ b/nexus/types/src/external_api/affinity.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Affinity and anti-affinity group types. + +pub use nexus_types_versions::latest::affinity::*; diff --git a/nexus/types/src/external_api/alert.rs b/nexus/types/src/external_api/alert.rs new file mode 100644 index 00000000000..accfb7fe7ff --- /dev/null +++ b/nexus/types/src/external_api/alert.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Alert and webhook types. + +pub use nexus_types_versions::latest::alert::*; diff --git a/nexus/types/src/external_api/asset.rs b/nexus/types/src/external_api/asset.rs new file mode 100644 index 00000000000..a6f819b0e1c --- /dev/null +++ b/nexus/types/src/external_api/asset.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Asset types. + +pub use nexus_types_versions::latest::asset::*; diff --git a/nexus/types/src/external_api/audit.rs b/nexus/types/src/external_api/audit.rs new file mode 100644 index 00000000000..bfb2442d0db --- /dev/null +++ b/nexus/types/src/external_api/audit.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Audit log types. + +pub use nexus_types_versions::latest::audit::*; diff --git a/nexus/types/src/external_api/bfd.rs b/nexus/types/src/external_api/bfd.rs new file mode 100644 index 00000000000..6e60fa7ce01 --- /dev/null +++ b/nexus/types/src/external_api/bfd.rs @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! BFD (Bidirectional Forwarding Detection) types. + +pub use nexus_types_versions::latest::bfd::*; +pub use omicron_common::api::external::BfdMode; diff --git a/nexus/types/src/external_api/certificate.rs b/nexus/types/src/external_api/certificate.rs new file mode 100644 index 00000000000..3d17e2d50f3 --- /dev/null +++ b/nexus/types/src/external_api/certificate.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Certificate types. + +pub use nexus_types_versions::latest::certificate::*; diff --git a/nexus/types/src/external_api/console.rs b/nexus/types/src/external_api/console.rs new file mode 100644 index 00000000000..89d172a5d1d --- /dev/null +++ b/nexus/types/src/external_api/console.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Console login types. + +pub use nexus_types_versions::latest::console::*; diff --git a/nexus/types/src/external_api/device.rs b/nexus/types/src/external_api/device.rs new file mode 100644 index 00000000000..dfad70fa6c7 --- /dev/null +++ b/nexus/types/src/external_api/device.rs @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Device authentication types. + +pub use nexus_types_versions::latest::device::*; +pub use nexus_types_versions::latest::device_params::*; diff --git a/nexus/types/src/external_api/disk.rs b/nexus/types/src/external_api/disk.rs new file mode 100644 index 00000000000..f51547c6faa --- /dev/null +++ b/nexus/types/src/external_api/disk.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Disk types. + +pub use nexus_types_versions::latest::disk::*; diff --git a/nexus/types/src/external_api/external_ip.rs b/nexus/types/src/external_api/external_ip.rs new file mode 100644 index 00000000000..d7d663aeebb --- /dev/null +++ b/nexus/types/src/external_api/external_ip.rs @@ -0,0 +1,7 @@ +// 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/. + +//! External IP types. + +pub use nexus_types_versions::latest::external_ip::*; diff --git a/nexus/types/src/external_api/external_subnet.rs b/nexus/types/src/external_api/external_subnet.rs new file mode 100644 index 00000000000..9869d6fc19e --- /dev/null +++ b/nexus/types/src/external_api/external_subnet.rs @@ -0,0 +1,7 @@ +// 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/. + +//! External subnet types. + +pub use nexus_types_versions::latest::external_subnet::*; diff --git a/nexus/types/src/external_api/floating_ip.rs b/nexus/types/src/external_api/floating_ip.rs new file mode 100644 index 00000000000..d79f7316932 --- /dev/null +++ b/nexus/types/src/external_api/floating_ip.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Floating IP types. + +pub use nexus_types_versions::latest::floating_ip::*; diff --git a/nexus/types/src/external_api/hardware.rs b/nexus/types/src/external_api/hardware.rs new file mode 100644 index 00000000000..f009eb64597 --- /dev/null +++ b/nexus/types/src/external_api/hardware.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Hardware types. + +pub use nexus_types_versions::latest::hardware::*; diff --git a/nexus/types/src/external_api/headers.rs b/nexus/types/src/external_api/headers.rs index e928e8f5c6b..afec6eccc45 100644 --- a/nexus/types/src/external_api/headers.rs +++ b/nexus/types/src/external_api/headers.rs @@ -2,15 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use schemars::JsonSchema; -use serde::Deserialize; -use serde::Serialize; +//! HTTP header types. -/// Range request headers -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct RangeRequest { - /// A request to access a portion of the resource, such as `bytes=0-499` - /// - /// See: - pub range: Option, -} +pub use nexus_types_versions::latest::headers::*; diff --git a/nexus/types/src/external_api/identity_provider.rs b/nexus/types/src/external_api/identity_provider.rs new file mode 100644 index 00000000000..96032abd994 --- /dev/null +++ b/nexus/types/src/external_api/identity_provider.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Identity provider types. + +pub use nexus_types_versions::latest::identity_provider::*; diff --git a/nexus/types/src/external_api/image.rs b/nexus/types/src/external_api/image.rs new file mode 100644 index 00000000000..1dddc58fcdf --- /dev/null +++ b/nexus/types/src/external_api/image.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Image types. + +pub use nexus_types_versions::latest::image::*; diff --git a/nexus/types/src/external_api/instance.rs b/nexus/types/src/external_api/instance.rs new file mode 100644 index 00000000000..b0ab0323354 --- /dev/null +++ b/nexus/types/src/external_api/instance.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Instance types. + +pub use nexus_types_versions::latest::instance::*; diff --git a/nexus/types/src/external_api/internet_gateway.rs b/nexus/types/src/external_api/internet_gateway.rs new file mode 100644 index 00000000000..911b2ed1191 --- /dev/null +++ b/nexus/types/src/external_api/internet_gateway.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Internet gateway types. + +pub use nexus_types_versions::latest::internet_gateway::*; diff --git a/nexus/types/src/external_api/ip_pool.rs b/nexus/types/src/external_api/ip_pool.rs new file mode 100644 index 00000000000..dc69b1fb2ad --- /dev/null +++ b/nexus/types/src/external_api/ip_pool.rs @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! IP pool types. + +pub use nexus_types_versions::latest::ip_pool::*; +pub use omicron_common::address::{IpRange, IpVersion, Ipv4Range, Ipv6Range}; diff --git a/nexus/types/src/external_api/metrics.rs b/nexus/types/src/external_api/metrics.rs new file mode 100644 index 00000000000..9f9282a59cd --- /dev/null +++ b/nexus/types/src/external_api/metrics.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Metrics types. + +pub use nexus_types_versions::latest::metrics::*; diff --git a/nexus/types/src/external_api/mod.rs b/nexus/types/src/external_api/mod.rs index 363ddd3f41d..a784bac7989 100644 --- a/nexus/types/src/external_api/mod.rs +++ b/nexus/types/src/external_api/mod.rs @@ -2,7 +2,51 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +//! Types for the Nexus external API. +//! +//! Types are organized by semantic domain. Each module re-exports the latest +//! versions of types from `nexus_types_versions::latest`. + +pub mod affinity; +pub mod alert; +pub mod asset; +pub mod audit; +pub mod bfd; +pub mod certificate; +pub mod console; +pub mod device; +pub mod disk; +pub mod external_ip; +pub mod external_subnet; +pub mod floating_ip; +pub mod hardware; pub mod headers; -pub mod params; -pub mod shared; -pub mod views; +pub mod identity_provider; +pub mod image; +pub mod instance; +pub mod internet_gateway; +pub mod ip_pool; +pub mod metrics; +pub mod multicast; +pub mod networking; +pub mod oxql; +pub mod path_params; +pub mod physical_disk; +pub mod policy; +pub mod probe; +pub mod project; +pub mod rack; +pub mod saml; +pub mod scim; +pub mod silo; +pub mod sled; +pub mod snapshot; +pub mod ssh_key; +pub mod subnet_pool; +pub mod support_bundle; +pub mod switch; +pub mod system; +pub mod timeseries; +pub mod update; +pub mod user; +pub mod vpc; diff --git a/nexus/types/src/external_api/multicast.rs b/nexus/types/src/external_api/multicast.rs new file mode 100644 index 00000000000..659e0ed2517 --- /dev/null +++ b/nexus/types/src/external_api/multicast.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Multicast group types. + +pub use nexus_types_versions::latest::multicast::*; diff --git a/nexus/types/src/external_api/networking.rs b/nexus/types/src/external_api/networking.rs new file mode 100644 index 00000000000..c165f0eb1d7 --- /dev/null +++ b/nexus/types/src/external_api/networking.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Networking types. + +pub use nexus_types_versions::latest::networking::*; diff --git a/nexus/types/src/external_api/oxql.rs b/nexus/types/src/external_api/oxql.rs new file mode 100644 index 00000000000..831bfe21ef7 --- /dev/null +++ b/nexus/types/src/external_api/oxql.rs @@ -0,0 +1,7 @@ +// 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/. + +//! OxQL query types. + +pub use nexus_types_versions::latest::oxql::*; diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs deleted file mode 100644 index b83480ca0a9..00000000000 --- a/nexus/types/src/external_api/params.rs +++ /dev/null @@ -1,4364 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Params define the request bodies of API endpoints for creating or updating -//! resources. - -use crate::external_api::shared; -use base64::Engine; -use chrono::{DateTime, Utc}; -use http::Uri; -use omicron_common::address::{ - ConcreteIp, IPV6_INTERFACE_LOCAL_MULTICAST_SUBNET, - IPV6_LINK_LOCAL_MULTICAST_SUBNET, IPV6_RESERVED_SCOPE_MULTICAST_SUBNET, - MAX_SSM_SOURCE_IPS, -}; -use omicron_common::api::external::{ - AddressLotKind, AffinityPolicy, AllowedSourceIps, BfdMode, BgpPeer, - ByteCount, FailureDomain, Hostname, IdentityMetadataCreateParams, - IdentityMetadataUpdateParams, InstanceAutoRestartPolicy, InstanceCpuCount, - InstanceCpuPlatform, IpVersion, LinkFec, LinkSpeed, MaxPathConfig, Name, - NameOrId, Nullable, PaginationOrder, RouteDestination, RouteTarget, UserId, -}; -use omicron_common::disk::DiskVariant; -use omicron_common::vlan::VlanID; -use omicron_uuid_kinds::*; -use oxnet::{IpNet, Ipv4Net, Ipv6Net}; -use parse_display::Display; -use schemars::JsonSchema; -use semver::Version; -use serde::{ - Deserialize, Deserializer, Serialize, Serializer, - de::{self, Visitor}, -}; -use sled_hardware_types::BaseboardId; -use std::collections::{BTreeMap, BTreeSet, HashSet}; -use std::num::NonZeroU32; -use std::{ - net::{IpAddr, Ipv4Addr, Ipv6Addr}, - str::FromStr, -}; -use url::Url; -use uuid::Uuid; - -macro_rules! path_param { - ($struct:ident, $param:ident, $name:tt) => { - path_param!($struct, $param, $name, NameOrId, "Name or ID of the "); - }; - ($struct:ident, $param:ident, $name:tt, $type:ty, $doc_prefix:tt) => { - #[derive(Serialize, Deserialize, JsonSchema)] - pub struct $struct { - #[doc = $doc_prefix] - #[doc = $name] - pub $param: $type, - } - }; -} - -macro_rules! id_path_param { - ($struct:ident, $param:ident, $name:tt) => { - id_path_param!($struct, $param, $name, Uuid); - }; - - ($struct:ident, $param:ident, $name:tt, $uuid_type:ident) => { - #[derive(Serialize, Deserialize, JsonSchema)] - pub struct $struct { - #[doc = "ID of the "] - #[doc = $name] - #[schemars(with = "Uuid")] - pub $param: $uuid_type, - } - }; -} - -/// The unique hardware ID for a sled -#[derive( - Clone, - Debug, - Serialize, - Deserialize, - JsonSchema, - PartialOrd, - Ord, - PartialEq, - Eq, -)] -pub struct UninitializedSledId { - pub serial: String, - pub part: String, -} - -impl From for BaseboardId { - fn from(value: UninitializedSledId) -> Self { - BaseboardId { part_number: value.part, serial_number: value.serial } - } -} - -#[derive( - Clone, - Debug, - Serialize, - Deserialize, - JsonSchema, - PartialOrd, - Ord, - PartialEq, - Eq, -)] -pub struct RackMembershipAddSledsRequest { - pub sled_ids: BTreeSet, -} - -path_param!(AffinityGroupPath, affinity_group, "affinity group"); -path_param!(AntiAffinityGroupPath, anti_affinity_group, "anti affinity group"); -path_param!( - MulticastGroupPath, - multicast_group, - "multicast group", - MulticastGroupIdentifier, - "Name, ID, or IP address of the " -); -path_param!(ProjectPath, project, "project"); -path_param!(InstancePath, instance, "instance"); -path_param!(NetworkInterfacePath, interface, "network interface"); -path_param!(VpcPath, vpc, "VPC"); -path_param!(SubnetPath, subnet, "subnet"); -path_param!(RouterPath, router, "router"); -path_param!(RoutePath, route, "route"); -path_param!(InternetGatewayPath, gateway, "gateway"); -path_param!(FloatingIpPath, floating_ip, "floating IP"); -path_param!(DiskPath, disk, "disk"); -path_param!(SnapshotPath, snapshot, "snapshot"); -path_param!(ImagePath, image, "image"); -path_param!(SiloPath, silo, "silo"); -path_param!(ProviderPath, provider, "SAML identity provider"); -path_param!(IpPoolPath, pool, "IP pool"); -path_param!(IpAddressPath, address, "IP address"); -path_param!(SshKeyPath, ssh_key, "SSH key"); -path_param!(AddressLotPath, address_lot, "address lot"); -path_param!(ProbePath, probe, "probe"); -path_param!(CertificatePath, certificate, "certificate"); - -id_path_param!(SupportBundlePath, bundle_id, "support bundle"); -id_path_param!(GroupPath, group_id, "group", SiloGroupUuid); -id_path_param!(UserPath, user_id, "user", SiloUserUuid); -id_path_param!(TokenPath, token_id, "token"); -id_path_param!(TufTrustRootPath, trust_root_id, "trust root"); - -// TODO: The hardware resources should be represented by its UUID or a hardware -// ID that can be used to deterministically generate the UUID. -id_path_param!(RackPath, rack_id, "rack"); -id_path_param!(SledPath, sled_id, "sled", SledUuid); -id_path_param!(SwitchPath, switch_id, "switch"); -id_path_param!(PhysicalDiskPath, disk_id, "physical disk"); - -// Internal API parameters -id_path_param!(BlueprintPath, blueprint_id, "blueprint"); - -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct SledSelector { - /// ID of the sled - #[schemars(with = "Uuid")] - pub sled: SledUuid, -} - -/// Parameters for `sled_set_provision_policy`. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct SledProvisionPolicyParams { - /// The provision state. - pub state: super::views::SledProvisionPolicy, -} - -/// Response to `sled_set_provision_policy`. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct SledProvisionPolicyResponse { - /// The old provision state. - pub old_state: super::views::SledProvisionPolicy, - - /// The new provision state. - pub new_state: super::views::SledProvisionPolicy, -} - -pub struct SwitchSelector { - /// ID of the switch - pub switch: Uuid, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct SiloSelector { - /// Name or ID of the silo - pub silo: NameOrId, -} - -impl From for SiloSelector { - fn from(name: Name) -> Self { - SiloSelector { silo: name.into() } - } -} - -#[derive(Serialize, Deserialize, JsonSchema)] -pub struct SupportBundleFilePath { - #[serde(flatten)] - pub bundle: SupportBundlePath, - - /// The file within the bundle to download - pub file: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct SupportBundleCreate { - /// User comment for the support bundle - pub user_comment: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] -pub struct SupportBundleUpdate { - /// User comment for the support bundle - pub user_comment: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct OptionalSiloSelector { - /// Name or ID of the silo - pub silo: Option, -} - -/// Path parameters for Silo User requests -#[derive(Deserialize, JsonSchema)] -pub struct UserParam { - /// The user's internal ID - #[schemars(with = "Uuid")] - pub user_id: SiloUserUuid, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct SamlIdentityProviderSelector { - /// Name or ID of the silo in which the SAML identity provider is associated - pub silo: Option, - /// Name or ID of the SAML identity provider - pub saml_identity_provider: NameOrId, -} - -// The shape of this selector is slightly different than the others given that -// silos users can only be specified via ID and are automatically provided by -// the environment the user is authenticated in -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct SshKeySelector { - /// ID of the silo user - #[schemars(with = "Uuid")] - pub silo_user_id: SiloUserUuid, - /// Name or ID of the SSH key - pub ssh_key: NameOrId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct ProjectSelector { - /// Name or ID of the project - pub project: NameOrId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct OptionalProjectSelector { - /// Name or ID of the project - pub project: Option, -} - -/// Query parameters for ephemeral IP detach endpoint -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct EphemeralIpDetachSelector { - /// Name or ID of the project - pub project: Option, - /// The IP version of the ephemeral IP to detach. - /// - /// Required when the instance has both IPv4 and IPv6 ephemeral IPs. - /// If only one ephemeral IP is attached, this field may be omitted. - pub ip_version: Option, -} - -#[derive(Deserialize, JsonSchema, Clone)] -pub struct FloatingIpSelector { - /// Name or ID of the project, only required if `floating_ip` is provided as a `Name` - pub project: Option, - /// Name or ID of the Floating IP - pub floating_ip: NameOrId, -} - -/// Identifier for a multicast group: can be a Name, UUID, or IP address. -/// -/// This type supports the join-by-IP pattern where users can specify -/// a multicast IP address directly, and the system will auto-discover -/// the pool and find or create the group. -#[derive(Debug, Display, Clone, PartialEq)] -#[display("{0}")] -pub enum MulticastGroupIdentifier { - Id(Uuid), - Name(Name), - Ip(IpAddr), -} - -impl TryFrom for MulticastGroupIdentifier { - type Error = String; - - fn try_from(value: String) -> Result { - if let Ok(id) = Uuid::parse_str(&value) { - Ok(MulticastGroupIdentifier::Id(id)) - } else if let Ok(ip) = value.parse::() { - Ok(MulticastGroupIdentifier::Ip(ip)) - } else { - Ok(MulticastGroupIdentifier::Name(Name::try_from(value)?)) - } - } -} - -impl FromStr for MulticastGroupIdentifier { - type Err = String; - - fn from_str(value: &str) -> Result { - MulticastGroupIdentifier::try_from(String::from(value)) - } -} - -impl From for MulticastGroupIdentifier { - fn from(name: Name) -> Self { - MulticastGroupIdentifier::Name(name) - } -} - -impl From for MulticastGroupIdentifier { - fn from(id: Uuid) -> Self { - MulticastGroupIdentifier::Id(id) - } -} - -impl From for MulticastGroupIdentifier { - fn from(ip: IpAddr) -> Self { - MulticastGroupIdentifier::Ip(ip) - } -} - -impl From for MulticastGroupIdentifier { - fn from(value: NameOrId) -> Self { - match value { - NameOrId::Name(name) => MulticastGroupIdentifier::Name(name), - NameOrId::Id(id) => MulticastGroupIdentifier::Id(id), - } - } -} - -impl Serialize for MulticastGroupIdentifier { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl<'de> Deserialize<'de> for MulticastGroupIdentifier { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - MulticastGroupIdentifier::try_from(s).map_err(de::Error::custom) - } -} - -impl JsonSchema for MulticastGroupIdentifier { - fn schema_name() -> String { - "MulticastGroupIdentifier".to_string() - } - - fn json_schema( - _generator: &mut schemars::r#gen::SchemaGenerator, - ) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), - metadata: Some(Box::new(schemars::schema::Metadata { - title: Some("A multicast group identifier".to_string()), - description: Some( - "Can be a UUID, a name, or an IP address".to_string(), - ), - ..Default::default() - })), - ..Default::default() - } - .into() - } -} - -#[derive(Deserialize, JsonSchema, Clone)] -pub struct MulticastGroupSelector { - /// Name, ID, or IP address of the multicast group (fleet-scoped) - pub multicast_group: MulticastGroupIdentifier, -} - -/// Path parameter for multicast group lookup by IP address. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupIpLookupPath { - /// IP address of the multicast group - pub address: IpAddr, -} - -#[derive(Deserialize, JsonSchema)] -pub struct DiskSelector { - /// Name or ID of the project, only required if `disk` is provided as a `Name` - pub project: Option, - /// Name or ID of the disk - pub disk: NameOrId, -} - -#[derive(Deserialize, JsonSchema)] -pub struct SnapshotSelector { - /// Name or ID of the project, only required if `snapshot` is provided as a `Name` - pub project: Option, - /// Name or ID of the snapshot - pub snapshot: NameOrId, -} - -#[derive(Deserialize, JsonSchema)] -pub struct ImageSelector { - /// Name or ID of the project, only required if `image` is provided as a `Name` - pub project: Option, - /// Name or ID of the image - pub image: NameOrId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct InstanceSelector { - /// Name or ID of the project, only required if `instance` is provided as a `Name` - pub project: Option, - /// Name or ID of the instance - pub instance: NameOrId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct OptionalInstanceSelector { - /// Name or ID of the project, only required if `instance` is provided as a `Name` - pub project: Option, - /// Name or ID of the instance - pub instance: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct InstanceNetworkInterfaceSelector { - /// Name or ID of the project, only required if `instance` is provided as a `Name` - pub project: Option, - /// Name or ID of the instance, only required if `network_interface` is provided as a `Name` - pub instance: Option, - /// Name or ID of the network interface - pub network_interface: NameOrId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct VpcSelector { - /// Name or ID of the project, only required if `vpc` is provided as a `Name` - pub project: Option, - /// Name or ID of the VPC - pub vpc: NameOrId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct OptionalVpcSelector { - /// Name or ID of the project, only required if `vpc` is provided as a `Name` - pub project: Option, - /// Name or ID of the VPC - pub vpc: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct InternetGatewayDeleteSelector { - /// Name or ID of the project, only required if `vpc` is provided as a `Name` - pub project: Option, - /// Name or ID of the VPC - pub vpc: Option, - /// Also delete routes targeting this gateway. - #[serde(default)] - pub cascade: bool, -} - -#[derive(Deserialize, JsonSchema)] -pub struct SubnetSelector { - /// Name or ID of the project, only required if `vpc` is provided as a `Name` - pub project: Option, - /// Name or ID of the VPC, only required if `subnet` is provided as a `Name` - pub vpc: Option, - /// Name or ID of the subnet - pub subnet: NameOrId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct RouterSelector { - /// Name or ID of the project, only required if `vpc` is provided as a `Name` - pub project: Option, - /// Name or ID of the VPC, only required if `router` is provided as a `Name` - pub vpc: Option, - /// Name or ID of the router - pub router: NameOrId, -} - -#[derive(Deserialize, JsonSchema)] -pub struct OptionalRouterSelector { - /// Name or ID of the project, only required if `vpc` is provided as a `Name` - pub project: Option, - /// Name or ID of the VPC, only required if `router` is provided as a `Name` - pub vpc: Option, - /// Name or ID of the router - pub router: Option, -} - -#[derive(Deserialize, JsonSchema)] -pub struct RouteSelector { - /// Name or ID of the project, only required if `vpc` is provided as a `Name` - pub project: Option, - /// Name or ID of the VPC, only required if `router` is provided as a `Name` - pub vpc: Option, - /// Name or ID of the router, only required if `route` is provided as a `Name` - pub router: Option, - /// Name or ID of the route - pub route: NameOrId, -} - -// Internet gateways - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct InternetGatewaySelector { - /// Name or ID of the project, only required if `vpc` is provided as a `Name` - pub project: Option, - /// Name or ID of the VPC, only required if `gateway` is provided as a `Name` - pub vpc: Option, - /// Name or ID of the internet gateway - pub gateway: NameOrId, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct OptionalInternetGatewaySelector { - /// Name or ID of the project, only required if `vpc` is provided as a `Name` - pub project: Option, - /// Name or ID of the VPC, only required if `gateway` is provided as a `Name` - pub vpc: Option, - /// Name or ID of the internet gateway - pub gateway: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct DeleteInternetGatewayElementSelector { - /// Name or ID of the project, only required if `vpc` is provided as a `Name` - pub project: Option, - /// Name or ID of the VPC, only required if `gateway` is provided as a `Name` - pub vpc: Option, - /// Name or ID of the internet gateway - pub gateway: Option, - /// Also delete routes targeting this gateway element. - #[serde(default)] - pub cascade: bool, -} - -#[derive(Deserialize, JsonSchema)] -pub struct InternetGatewayIpPoolSelector { - /// Name or ID of the project, only required if `vpc` is provided as a `Name` - pub project: Option, - /// Name or ID of the VPC, only required if `gateway` is provided as a `Name` - pub vpc: Option, - /// Name or ID of the gateway, only required if `pool` is provided as a `Name` - pub gateway: Option, - /// Name or ID of the pool - pub pool: NameOrId, -} - -#[derive(Deserialize, JsonSchema)] -pub struct InternetGatewayIpAddressSelector { - /// Name or ID of the project, only required if `vpc` is provided as a `Name` - pub project: Option, - /// Name or ID of the VPC, only required if `gateway` is provided as a `Name` - pub vpc: Option, - /// Name or ID of the gateway, only required if `address` is provided as a `Name` - pub gateway: Option, - /// Name or ID of the address - pub address: NameOrId, -} - -// Silos - -/// Create-time parameters for a `Silo` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SiloCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - pub discoverable: bool, - - pub identity_mode: shared::SiloIdentityMode, - - /// If set, this group will be created during Silo creation and granted the - /// "Silo Admin" role. Identity providers can assert that users belong to - /// this group and those users can log in and further initialize the Silo. - /// - /// Note that if configuring a SAML based identity provider, - /// group_attribute_name must be set for users to be considered part of a - /// group. See `SamlIdentityProviderCreate` for more information. - pub admin_group_name: Option, - - /// Initial TLS certificates to be used for the new Silo's console and API - /// endpoints. These should be valid for the Silo's DNS name(s). - pub tls_certificates: Vec, - - /// Limits the amount of provisionable CPU, memory, and storage in the Silo. - /// CPU and memory are only consumed by running instances, while storage is - /// consumed by any disk or snapshot. A value of 0 means that resource is - /// *not* provisionable. - pub quotas: SiloQuotasCreate, - - /// Mapping of which Fleet roles are conferred by each Silo role - /// - /// The default is that no Fleet roles are conferred by any Silo roles - /// unless there's a corresponding entry in this map. - #[serde(default)] - pub mapped_fleet_roles: - BTreeMap>, -} - -/// The amount of provisionable resources for a Silo -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SiloQuotasCreate { - /// The amount of virtual CPUs available for running instances in the Silo - pub cpus: i64, - /// The amount of RAM (in bytes) available for running instances in the Silo - pub memory: ByteCount, - /// The amount of storage (in bytes) available for disks or snapshots - pub storage: ByteCount, -} - -impl SiloQuotasCreate { - /// All quotas set to 0 - pub fn empty() -> Self { - Self { - cpus: 0, - memory: ByteCount::from(0), - storage: ByteCount::from(0), - } - } - - /// An arbitrarily high but identifiable default for quotas - /// that can be used for creating a Silo for testing - /// - /// The only silo that customers will see that this should be set on is the default - /// silo. Ultimately the default silo should only be initialized with an empty quota, - /// but as tests currently relying on it having a quota, we need to set something. - pub fn arbitrarily_high_default() -> Self { - Self { - cpus: 9999999999, - memory: ByteCount::try_from(999999999999999999_u64).unwrap(), - storage: ByteCount::try_from(999999999999999999_u64).unwrap(), - } - } -} - -// This conversion is mostly just useful for tests such that we can reuse -// empty() and arbitrarily_high_default() when testing utilization -impl From for super::views::VirtualResourceCounts { - fn from(quota: SiloQuotasCreate) -> Self { - Self { cpus: quota.cpus, memory: quota.memory, storage: quota.storage } - } -} - -/// Updateable properties of a Silo's resource limits. -/// If a value is omitted it will not be updated. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SiloQuotasUpdate { - /// The amount of virtual CPUs available for running instances in the Silo - pub cpus: Option, - /// The amount of RAM (in bytes) available for running instances in the Silo - pub memory: Option, - /// The amount of storage (in bytes) available for disks or snapshots - pub storage: Option, -} - -// TODO: Unlike quota values, silo settings are nullable, so we need passing -// null to be meaningful here. But it's confusing for it to work that way here -// and differently for quotas. Maybe the best thing would be to make them all -// non-nullable on SiloQuotasUpdate. I vaguely remember the latter being the -// direction we wanted to go in general anyway. Can't find the issue where it -// was discussed. - -/// Updateable properties of a silo's settings. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct SiloAuthSettingsUpdate { - /// Maximum lifetime of a device token in seconds. If set to null, users - /// will be able to create tokens that do not expire. - pub device_token_max_ttl_seconds: Nullable, -} - -/// Create-time parameters for a `User` -#[derive(Clone, Deserialize, JsonSchema)] -pub struct UserCreate { - /// Username used to log in - pub external_id: UserId, - /// How to set the user's login password - pub password: UserPassword, -} - -/// A password used for authenticating a local-only user -#[derive(Clone, Deserialize)] -#[serde(try_from = "String")] -// We store both the raw String and omicron_passwords::Password forms of the -// password. That's because `omicron_passwords::Password` does not support -// getting the String back out (by design), but we may need to do that in order -// to impl Serialize. See the `From for String` impl below. -pub struct Password(omicron_passwords::Password); - -impl FromStr for Password { - type Err = String; - fn from_str(value: &str) -> Result { - Password::try_from(String::from(value)) - } -} - -// Used to impl `Deserialize` -impl TryFrom for Password { - type Error = String; - fn try_from(value: String) -> Result { - let inner = omicron_passwords::Password::new(&value) - .map_err(|e| format!("unsupported password: {:#}", e))?; - // TODO-security If we want to apply password policy rules, this seems - // like the place. We presumably want to also document them in the - // OpenAPI schema below. See omicron#2307. - Ok(Password(inner)) - } -} - -impl JsonSchema for Password { - fn schema_name() -> String { - "Password".to_string() - } - - fn json_schema( - _: &mut schemars::r#gen::SchemaGenerator, - ) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - metadata: Some(Box::new(schemars::schema::Metadata { - title: Some( - "A password used to authenticate a user".to_string(), - ), - // TODO-doc If we apply password strength rules, they should - // presumably be documented here. See omicron#2307. - description: Some( - "Passwords may be subject to additional constraints." - .to_string(), - ), - ..Default::default() - })), - instance_type: Some(schemars::schema::InstanceType::String.into()), - string: Some(Box::new(schemars::schema::StringValidation { - max_length: Some( - u32::try_from(omicron_passwords::MAX_PASSWORD_LENGTH) - .unwrap(), - ), - min_length: None, - pattern: None, - })), - ..Default::default() - } - .into() - } -} - -impl AsRef for Password { - fn as_ref(&self) -> &omicron_passwords::Password { - &self.0 - } -} - -/// Parameters for setting a user's password -#[derive(Clone, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -#[serde(tag = "mode", content = "value")] -pub enum UserPassword { - /// Sets the user's password to the provided value - Password(Password), - /// Invalidates any current password (disabling password authentication) - LoginDisallowed, -} - -/// Credentials for local user login -#[derive(Clone, Deserialize, JsonSchema)] -pub struct UsernamePasswordCredentials { - pub username: UserId, - pub password: Password, -} - -// Silo identity providers - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct DerEncodedKeyPair { - /// Request signing public certificate (base64 encoded DER file) - #[serde(deserialize_with = "x509_cert_from_base64_encoded_der")] - pub public_cert: String, - - /// Request signing RSA private key in PKCS#1 format - /// (base64 encoded DER file) - #[serde(deserialize_with = "key_from_base64_encoded_der")] - pub private_key: String, -} - -struct X509CertVisitor; - -impl Visitor<'_> for X509CertVisitor { - type Value = String; - - fn expecting( - &self, - formatter: &mut std::fmt::Formatter, - ) -> std::fmt::Result { - formatter.write_str("a DER formatted X509 certificate as a string of base64 encoded bytes") - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - let raw_bytes = base64::engine::general_purpose::STANDARD - .decode(&value.as_bytes()) - .map_err(|e| { - de::Error::custom(format!( - "could not base64 decode public_cert: {}", - e - )) - })?; - let _parsed = - openssl::x509::X509::from_der(&raw_bytes).map_err(|e| { - de::Error::custom(format!( - "public_cert is not recognized as a X509 certificate: {}", - e - )) - })?; - - Ok(value.to_string()) - } -} - -fn x509_cert_from_base64_encoded_der<'de, D>( - deserializer: D, -) -> Result -where - D: Deserializer<'de>, -{ - deserializer.deserialize_str(X509CertVisitor) -} - -struct KeyVisitor; - -impl Visitor<'_> for KeyVisitor { - type Value = String; - - fn expecting( - &self, - formatter: &mut std::fmt::Formatter, - ) -> std::fmt::Result { - formatter.write_str( - "a DER formatted key as a string of base64 encoded bytes", - ) - } - - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - let raw_bytes = base64::engine::general_purpose::STANDARD - .decode(&value) - .map_err(|e| { - de::Error::custom(format!( - "could not base64 decode private_key: {}", - e - )) - })?; - - // TODO: samael does not support ECDSA, update to generic PKey type when it does - //let _parsed = openssl::pkey::PKey::private_key_from_der(&raw_bytes) - // .map_err(|e| de::Error::custom(format!("could not base64 decode private_key: {}", e)))?; - - let parsed = openssl::rsa::Rsa::private_key_from_der(&raw_bytes) - .map_err(|e| { - de::Error::custom(format!( - "private_key is not recognized as a RSA private key: {}", - e - )) - })?; - let _parsed = openssl::pkey::PKey::from_rsa(parsed).map_err(|e| { - de::Error::custom(format!( - "private_key is not recognized as a RSA private key: {}", - e - )) - })?; - - Ok(value.to_string()) - } -} - -fn key_from_base64_encoded_der<'de, D>( - deserializer: D, -) -> Result -where - D: Deserializer<'de>, -{ - deserializer.deserialize_str(KeyVisitor) -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum IdpMetadataSource { - Url { url: String }, - Base64EncodedXml { data: String }, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SamlIdentityProviderCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// The source of an identity provider metadata descriptor - pub idp_metadata_source: IdpMetadataSource, - - /// IdP's entity ID - pub idp_entity_id: String, - - /// SP's client ID - pub sp_client_id: String, - - /// Service provider endpoint where the response will be sent - pub acs_url: String, - - /// Service provider endpoint where the IdP should send log out requests - pub slo_url: String, - - /// Customer's technical contact for SAML configuration - pub technical_contact_email: String, - - /// Request signing key pair - #[serde(default)] - #[serde(deserialize_with = "validate_key_pair")] - pub signing_keypair: Option, - - /// If set, SAML attributes with this name will be considered to denote a - /// user's group membership, where the attribute value(s) should be a - /// comma-separated list of group names. - pub group_attribute_name: Option, -} - -/// sign some junk data and validate it with the key pair -fn sign_junk_data(key_pair: &DerEncodedKeyPair) -> Result<(), anyhow::Error> { - let private_key = { - let raw_bytes = base64::engine::general_purpose::STANDARD - .decode(&key_pair.private_key)?; - // TODO: samael does not support ECDSA, update to generic PKey type when it does - //let parsed = openssl::pkey::PKey::private_key_from_der(&raw_bytes)?; - let parsed = openssl::rsa::Rsa::private_key_from_der(&raw_bytes)?; - let parsed = openssl::pkey::PKey::from_rsa(parsed)?; - parsed - }; - - let public_key = { - let raw_bytes = base64::engine::general_purpose::STANDARD - .decode(&key_pair.public_cert)?; - let parsed = openssl::x509::X509::from_der(&raw_bytes)?; - parsed.public_key()? - }; - - let mut signer = openssl::sign::Signer::new( - openssl::hash::MessageDigest::sha256(), - &private_key.as_ref(), - )?; - - let some_junk_data = b"this is some junk data"; - - signer.update(some_junk_data)?; - let signature = signer.sign_to_vec()?; - - let mut verifier = openssl::sign::Verifier::new( - openssl::hash::MessageDigest::sha256(), - &public_key, - )?; - - verifier.update(some_junk_data)?; - - if !verifier.verify(&signature)? { - anyhow::bail!("signature validation failed!"); - } - - Ok(()) -} - -fn validate_key_pair<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let v = Option::::deserialize(deserializer)?; - - if let Some(ref key_pair) = v { - if let Err(e) = sign_junk_data(&key_pair) { - return Err(de::Error::custom(format!( - "data signed with key not verified with certificate! {}", - e - ))); - } - } - - Ok(v) -} - -// AFFINITY GROUPS - -/// Create-time parameters for an `AffinityGroup` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AffinityGroupCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - pub policy: AffinityPolicy, - pub failure_domain: FailureDomain, -} - -/// Updateable properties of an `AffinityGroup` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AffinityGroupUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct AffinityInstanceGroupMemberPath { - pub affinity_group: NameOrId, - pub instance: NameOrId, -} - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -pub struct AntiAffinityInstanceGroupMemberPath { - pub anti_affinity_group: NameOrId, - pub instance: NameOrId, -} - -/// Create-time parameters for an `AntiAffinityGroup` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AntiAffinityGroupCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - pub policy: AffinityPolicy, - pub failure_domain: FailureDomain, -} - -/// Updateable properties of an `AntiAffinityGroup` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AntiAffinityGroupUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, -} - -#[derive(Deserialize, JsonSchema, Clone)] -pub struct AffinityGroupSelector { - /// Name or ID of the project, only required if `affinity_group` is provided as a `Name` - pub project: Option, - /// Name or ID of the Affinity Group - pub affinity_group: NameOrId, -} - -#[derive(Deserialize, JsonSchema, Clone)] -pub struct AntiAffinityGroupSelector { - /// Name or ID of the project, only required if `anti_affinity_group` is provided as a `Name` - pub project: Option, - /// Name or ID of the Anti Affinity Group - pub anti_affinity_group: NameOrId, -} - -// PROJECTS - -/// Create-time parameters for a `Project` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct ProjectCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, -} - -/// Updateable properties of a `Project` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct ProjectUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, -} - -// NETWORK INTERFACES - -/// Create-time parameters for an `InstanceNetworkInterface` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceNetworkInterfaceCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// The VPC in which to create the interface. - pub vpc_name: Name, - /// The VPC Subnet in which to create the interface. - pub subnet_name: Name, - /// The IP stack configuration for this interface. - /// - /// If not provided, a default configuration will be used, which creates a - /// dual-stack IPv4 / IPv6 interface. - #[serde(default = "PrivateIpStackCreate::auto_dual_stack")] - pub ip_config: PrivateIpStackCreate, -} - -/// Parameters for updating an `InstanceNetworkInterface` -/// -/// Note that modifying IP addresses for an interface is not yet supported, a -/// new interface must be created instead. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceNetworkInterfaceUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, - - /// Make a secondary interface the instance's primary interface. - /// - /// If applied to a secondary interface, that interface will become the - /// primary on the next reboot of the instance. Note that this may have - /// implications for routing between instances, as the new primary interface - /// will be on a distinct subnet from the previous primary interface. - /// - /// Note that this can only be used to select a new primary interface for an - /// instance. Requests to change the primary interface into a secondary will - /// return an error. - // TODO-completeness TODO-doc When we get there, this should note that a - // change in the primary interface will result in changes to the DNS records - // for the instance, though not the name. - #[serde(default)] - pub primary: bool, - - /// A set of additional networks that this interface may send and receive - /// traffic on - #[serde(default)] - pub transit_ips: Vec, -} - -/// How a VPC-private IP address is assigned to a network interface. -// -// NOTE: This type is used as a layer of indirection for generating the JSON -// Schema we want for `IpAssignment`. In particular, we use most of its -// contents, but let the real type set the _name_ of the schema based on the -// `ConcreteIp` type being used. -#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema, Serialize)] -#[serde(rename_all = "snake_case", tag = "type", content = "value")] -enum IpAssignmentShadow { - /// Automatically assign an IP address from the VPC Subnet. - #[default] - Auto, - /// Explicitly assign a specific address, if available. - Explicit(T), -} - -trait IpAssignmentSchema { - fn ip_assignment_schema_name() -> String; -} - -impl IpAssignmentSchema for Ipv4Addr { - fn ip_assignment_schema_name() -> String { - String::from("Ipv4Assignment") - } -} - -impl IpAssignmentSchema for Ipv6Addr { - fn ip_assignment_schema_name() -> String { - String::from("Ipv6Assignment") - } -} - -/// How a VPC-private IP address is assigned to a network interface. -#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] -#[serde(rename_all = "snake_case", tag = "type", content = "value")] -pub enum IpAssignment { - /// Automatically assign an IP address from the VPC Subnet. - #[default] - Auto, - /// Explicitly assign a specific address, if available. - Explicit(T), -} - -impl JsonSchema for IpAssignment -where - T: ConcreteIp + IpAssignmentSchema, -{ - fn schema_name() -> String { - ::ip_assignment_schema_name() - } - - fn json_schema( - generator: &mut schemars::r#gen::SchemaGenerator, - ) -> schemars::schema::Schema { - IpAssignmentShadow::::json_schema(generator) - } -} - -impl From for IpAssignment { - fn from(ip: T) -> Self { - Self::Explicit(ip) - } -} - -/// How to assign an IPv4 address. -pub type Ipv4Assignment = IpAssignment; - -/// How to assign an IPv6 address. -pub type Ipv6Assignment = IpAssignment; - -/// Configuration for a network interface's IPv4 addressing. -#[derive(Clone, Debug, Default, Deserialize, JsonSchema, Serialize)] -pub struct PrivateIpv4StackCreate { - /// The VPC-private address to assign to the interface. - pub ip: Ipv4Assignment, - /// Additional IP networks the interface can send / receive on. - #[serde(default)] - pub transit_ips: Vec, -} - -/// Configuration for a network interface's IPv6 addressing. -#[derive(Clone, Debug, Default, Deserialize, JsonSchema, Serialize)] -pub struct PrivateIpv6StackCreate { - /// The VPC-private address to assign to the interface. - pub ip: Ipv6Assignment, - /// Additional IP networks the interface can send / receive on. - #[serde(default)] - pub transit_ips: Vec, -} - -/// Create parameters for a network interface's IP stack. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -#[serde(rename_all = "snake_case", tag = "type", content = "value")] -pub enum PrivateIpStackCreate { - /// The interface has only an IPv4 stack. - V4(PrivateIpv4StackCreate), - /// The interface has only an IPv6 stack. - V6(PrivateIpv6StackCreate), - /// The interface has both an IPv4 and IPv6 stack. - DualStack { v4: PrivateIpv4StackCreate, v6: PrivateIpv6StackCreate }, -} - -impl PrivateIpStackCreate { - /// Construct an IPv4 configuration with no transit IPs. - pub fn from_ipv4(addr: std::net::Ipv4Addr) -> Self { - PrivateIpStackCreate::V4(PrivateIpv4StackCreate { - ip: Ipv4Assignment::Explicit(addr), - transit_ips: vec![], - }) - } - - /// Construct an IP configuration with only an automatic IPv4 address. - pub fn auto_ipv4() -> Self { - PrivateIpStackCreate::V4(PrivateIpv4StackCreate::default()) - } - - /// Return the IPv4 create parameters, if they exist. - pub fn as_ipv4_create(&self) -> Option<&PrivateIpv4StackCreate> { - match self { - PrivateIpStackCreate::V4(v4) - | PrivateIpStackCreate::DualStack { v4, .. } => Some(v4), - PrivateIpStackCreate::V6(_) => None, - } - } - - /// Return the IPv4 address assignment. - pub fn ipv4_assignment(&self) -> Option<&Ipv4Assignment> { - self.as_ipv4_create().map(|c| &c.ip) - } - - /// Return the IPv4 address explicitly requested, if one exists. - pub fn ipv4_addr(&self) -> Option<&std::net::Ipv4Addr> { - self.ipv4_assignment().and_then(|assignment| match assignment { - IpAssignment::Auto => None, - IpAssignment::Explicit(addr) => Some(addr), - }) - } - - /// Return the IPv4 transit IPs, if they exist. - pub fn ipv4_transit_ips(&self) -> Option<&[Ipv4Net]> { - self.as_ipv4_create().map(|c| c.transit_ips.as_slice()) - } - - /// Construct an IPv6 configuration with no transit IPs. - pub fn from_ipv6(addr: std::net::Ipv6Addr) -> Self { - PrivateIpStackCreate::V6(PrivateIpv6StackCreate { - ip: Ipv6Assignment::Explicit(addr), - transit_ips: vec![], - }) - } - - /// Construct an IP configuration with only an automatic IPv6 address. - pub fn auto_ipv6() -> Self { - PrivateIpStackCreate::V6(PrivateIpv6StackCreate::default()) - } - - /// Return the IPv6 create parameters, if they exist. - pub fn as_ipv6_create(&self) -> Option<&PrivateIpv6StackCreate> { - match self { - PrivateIpStackCreate::V6(v6) - | PrivateIpStackCreate::DualStack { v6, .. } => Some(v6), - PrivateIpStackCreate::V4(_) => None, - } - } - - /// Return the IPv6 address assignment. - pub fn ipv6_assignment(&self) -> Option<&Ipv6Assignment> { - self.as_ipv6_create().map(|c| &c.ip) - } - - /// Return the IPv6 address explicitly requested, if one exists. - pub fn ipv6_addr(&self) -> Option<&std::net::Ipv6Addr> { - self.ipv6_assignment().and_then(|assignment| match assignment { - IpAssignment::Auto => None, - IpAssignment::Explicit(addr) => Some(addr), - }) - } - - /// Return the IPv6 transit IPs, if they exist. - pub fn ipv6_transit_ips(&self) -> Option<&[Ipv6Net]> { - self.as_ipv6_create().map(|c| c.transit_ips.as_slice()) - } - - /// Return the transit IPs requested in this configuration. - pub fn transit_ips(&self) -> Vec { - self.ipv4_transit_ips() - .unwrap_or_default() - .iter() - .copied() - .map(Into::into) - .chain( - self.ipv6_transit_ips() - .unwrap_or_default() - .iter() - .copied() - .map(Into::into), - ) - .collect() - } - - /// Construct a dual-stack IP configuration with explicit IP addresses. - pub fn new_dual_stack( - ipv4: std::net::Ipv4Addr, - ipv6: std::net::Ipv6Addr, - ) -> Self { - PrivateIpStackCreate::DualStack { - v4: PrivateIpv4StackCreate { - ip: Ipv4Assignment::Explicit(ipv4), - transit_ips: Vec::new(), - }, - v6: PrivateIpv6StackCreate { - ip: Ipv6Assignment::Explicit(ipv6), - transit_ips: Vec::new(), - }, - } - } - - /// Construct an IP configuration with both IPv4 / IPv6 addresses and no - /// transit IPs. - pub fn auto_dual_stack() -> Self { - PrivateIpStackCreate::DualStack { - v4: PrivateIpv4StackCreate::default(), - v6: PrivateIpv6StackCreate::default(), - } - } - - /// Return true if this config has any transit IPs - pub fn has_transit_ips(&self) -> bool { - match self { - PrivateIpStackCreate::V4(PrivateIpv4StackCreate { - transit_ips, - .. - }) => !transit_ips.is_empty(), - PrivateIpStackCreate::V6(PrivateIpv6StackCreate { - transit_ips, - .. - }) => !transit_ips.is_empty(), - PrivateIpStackCreate::DualStack { - v4: PrivateIpv4StackCreate { transit_ips: ipv4_addrs, .. }, - v6: PrivateIpv6StackCreate { transit_ips: ipv6_addrs, .. }, - } => !ipv4_addrs.is_empty() || !ipv6_addrs.is_empty(), - } - } - - /// Return true if this IP configuration has an IPv4 stack. - pub fn has_ipv4_stack(&self) -> bool { - self.ipv4_assignment().is_some() - } - - /// Return true if this IP configuration has an IPv6 stack. - pub fn has_ipv6_stack(&self) -> bool { - self.ipv6_assignment().is_some() - } -} - -// CERTIFICATES - -/// Create-time parameters for a `Certificate` -#[derive(Clone, Deserialize, Serialize, JsonSchema)] -pub struct CertificateCreate { - /// Common identifying metadata - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// PEM-formatted string containing public certificate chain - pub cert: String, - /// PEM-formatted string containing private key - pub key: String, - /// The service using this certificate - pub service: shared::ServiceUsingCertificate, -} - -impl std::fmt::Debug for CertificateCreate { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CertificateCreate") - .field("identity", &self.identity) - .field("cert", &self.cert) - .field("key", &"") - .finish() - } -} - -// IP POOLS - -/// Create-time parameters for an `IpPool`. -/// -/// For multicast pools, all ranges must be either Any-Source Multicast (ASM) -/// or Source-Specific Multicast (SSM), but not both. Mixing ASM and SSM -/// ranges in the same pool is not allowed. -/// -/// ASM: IPv4 addresses outside 232.0.0.0/8, IPv6 addresses with flag field != 3 -/// SSM: IPv4 addresses in 232.0.0.0/8, IPv6 addresses with flag field = 3 -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct IpPoolCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// The IP version of the pool. - /// - /// The default is IPv4. - #[serde(default = "IpVersion::v4")] - pub ip_version: IpVersion, - /// Type of IP pool (defaults to Unicast) - #[serde(default)] - pub pool_type: shared::IpPoolType, -} - -impl IpPoolCreate { - /// Create parameters for a unicast IP pool (the default) - pub fn new( - identity: IdentityMetadataCreateParams, - ip_version: IpVersion, - ) -> Self { - Self { identity, ip_version, pool_type: shared::IpPoolType::Unicast } - } - - /// Create parameters for a multicast IP pool - pub fn new_multicast( - identity: IdentityMetadataCreateParams, - ip_version: IpVersion, - ) -> Self { - Self { identity, ip_version, pool_type: shared::IpPoolType::Multicast } - } -} - -/// Parameters for updating an IP Pool -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct IpPoolUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct IpPoolSiloPath { - pub pool: NameOrId, - pub silo: NameOrId, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct IpPoolLinkSilo { - pub silo: NameOrId, - /// When a pool is the default for a silo, floating IPs and instance - /// ephemeral IPs will come from that pool when no other pool is specified. - /// - /// A silo can have at most one default pool per combination of pool type - /// (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 - /// default pools total. - pub is_default: bool, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct IpPoolSiloUpdate { - /// When a pool is the default for a silo, floating IPs and instance - /// ephemeral IPs will come from that pool when no other pool is specified. - /// - /// A silo can have at most one default pool per combination of pool type - /// (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 - /// default pools total. When a pool is made default, an existing default - /// of the same type and version will remain linked but will no longer be - /// the default. - pub is_default: bool, -} - -// Subnet Pools - -path_param!(SubnetPoolPath, pool, "subnet pool"); - -/// Path parameters for subnet pool silo operations -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SubnetPoolSiloPath { - /// Name or ID of the subnet pool - pub pool: NameOrId, - /// Name or ID of the silo - pub silo: NameOrId, -} - -/// Create a subnet pool -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SubnetPoolCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// The IP version for this pool (IPv4 or IPv6). All subnets in the pool - /// must match this version. - pub ip_version: IpVersion, -} - -/// Update a subnet pool -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SubnetPoolUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, -} - -/// Add a member (subnet) to a subnet pool -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SubnetPoolMemberAdd { - /// The subnet to add to the pool - pub subnet: IpNet, - /// Minimum prefix length for allocations from this subnet; a smaller prefix - /// means larger allocations are allowed (e.g. a /16 prefix yields larger - /// subnet allocations than a /24 prefix). - /// - /// Valid values: 0-32 for IPv4, 0-128 for IPv6. - /// Default if not specified is equal to the subnet's prefix length. - pub min_prefix_length: Option, - /// Maximum prefix length for allocations from this subnet; a larger prefix - /// means smaller allocations are allowed (e.g. a /24 prefix yields smaller - /// subnet allocations than a /16 prefix). - /// - /// Valid values: 0-32 for IPv4, 0-128 for IPv6. - /// Default if not specified is 32 for IPv4 and 128 for IPv6. - pub max_prefix_length: Option, -} - -/// Remove a subnet from a pool -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SubnetPoolMemberRemove { - /// The subnet to remove from the pool. Must match an existing entry exactly. - pub subnet: IpNet, -} - -/// Link a subnet pool to a silo -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SubnetPoolLinkSilo { - /// The silo to link - pub silo: NameOrId, - /// Whether this is the default subnet pool for the silo. When true, - /// external subnet allocations that don't specify a pool use this one. - pub is_default: bool, -} - -/// Update a subnet pool's silo link -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SubnetPoolSiloUpdate { - /// Whether this is the default subnet pool for the silo - pub is_default: bool, -} - -// External Subnets - -path_param!(ExternalSubnetPath, external_subnet, "external subnet"); - -/// Selector for looking up an external subnet -#[derive(Deserialize, JsonSchema, Clone)] -pub struct ExternalSubnetSelector { - /// Name or ID of the project (required if `external_subnet` is a Name) - pub project: Option, - /// Name or ID of the external subnet - pub external_subnet: NameOrId, -} - -/// Specify how to allocate an external subnet. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum ExternalSubnetAllocator { - /// Reserve a specific subnet. - Explicit { - /// The subnet CIDR to reserve. Must be available in the pool. - subnet: IpNet, - }, - /// Automatically allocate a subnet with the specified prefix length. - Auto { - /// The prefix length for the allocated subnet (e.g., 24 for a /24). - prefix_len: u8, - /// Pool selection. - /// - /// If omitted, this field uses the silo's default pool. If the - /// silo has default pools for both IPv4 and IPv6, the request will - /// fail unless `ip_version` is specified in the pool selector. - #[serde(default)] - pool_selector: PoolSelector, - }, -} - -/// Create an external subnet -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct ExternalSubnetCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// Subnet allocation method. - pub allocator: ExternalSubnetAllocator, -} - -/// Update an external subnet -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct ExternalSubnetUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, -} - -/// Attach an external subnet to an instance -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct ExternalSubnetAttach { - /// Name or ID of the instance to attach to - pub instance: NameOrId, -} - -// Floating IPs - -/// Specify how to allocate a floating IP address. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum AddressAllocator { - /// Reserve a specific IP address. The pool is inferred from the address - /// since IP pools cannot have overlapping ranges. - Explicit { - /// The IP address to reserve. - ip: IpAddr, - }, - /// Automatically allocate an IP address from a pool. - Auto { - /// Pool selection. - /// - /// If omitted, the silo's default pool is used. If the silo has - /// default pools for both IPv4 and IPv6, the request will fail - /// unless `ip_version` is specified. - #[serde(default)] - pool_selector: PoolSelector, - }, -} - -impl Default for AddressAllocator { - fn default() -> Self { - AddressAllocator::Auto { pool_selector: PoolSelector::default() } - } -} - -/// Parameters for creating a new floating IP address for instances. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct FloatingIpCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// IP address allocation method. - #[serde(default)] - pub address_allocator: AddressAllocator, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct FloatingIpUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, -} - -/// The type of resource that a floating IP is attached to -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum FloatingIpParentKind { - Instance, -} - -/// Parameters for attaching a floating IP address to another resource -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct FloatingIpAttach { - /// Name or ID of the resource that this IP address should be attached to - pub parent: NameOrId, - - /// The type of `parent`'s resource - pub kind: FloatingIpParentKind, -} - -// INSTANCES - -/// Describes an attachment of an `InstanceNetworkInterface` to an `Instance`, -/// at the time the instance is created. -// NOTE: VPC's are an organizing concept for networking resources, not for -// instances. It's true that all networking resources for an instance must -// belong to a single VPC, but we don't consider instances to be "scoped" to a -// VPC in the same way that they are scoped to projects, for example. -// -// This is slightly different than some other cloud providers, such as AWS, -// which use VPCs as both a networking concept, and a container more similar to -// our concept of a project. One example for why this is useful is that "moving" -// an instance to a new VPC can be done by detaching any interfaces in the -// original VPC and attaching interfaces in the new VPC. -// -// This type then requires the VPC identifiers, exactly because instances are -// _not_ scoped to a VPC, and so the VPC and/or VPC Subnet names are not present -// in the path of endpoints handling instance operations. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", content = "params", rename_all = "snake_case")] -#[derive(Default)] -pub enum InstanceNetworkInterfaceAttachment { - /// Create one or more `InstanceNetworkInterface`s for the `Instance`. - /// - /// If more than one interface is provided, then the first will be - /// designated the primary interface for the instance. - Create(Vec), - - /// Create a single primary interface with an automatically-assigned IPv4 - /// address. - /// - /// The IP will be pulled from the Project's default VPC / VPC Subnet. - DefaultIpv4, - - /// Create a single primary interface with an automatically-assigned IPv6 - /// address. - /// - /// The IP will be pulled from the Project's default VPC / VPC Subnet. - DefaultIpv6, - - /// Create a single primary interface with automatically-assigned IPv4 and - /// IPv6 addresses. - /// - /// The IPs will be pulled from the Project's default VPC / VPC Subnet. - #[default] - DefaultDualStack, - - /// No network interfaces at all will be created for the instance. - None, -} - -/// Describe the instance's disks at creation time -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum InstanceDiskAttachment { - /// During instance creation, create and attach disks - Create(DiskCreate), - - /// During instance creation, attach this disk - Attach(InstanceDiskAttach), -} - -impl InstanceDiskAttachment { - /// Get the name of the disk described by this attachment. - pub fn name(&self) -> Name { - match self { - Self::Create(create) => create.identity.name.clone(), - Self::Attach(InstanceDiskAttach { name }) => name.clone(), - } - } -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceDiskAttach { - /// A disk name to attach - pub name: Name, -} - -/// Specify which IP or external subnet pool to allocate from. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum PoolSelector { - /// Use the specified pool by name or ID. - Explicit { - /// The pool to allocate from. - pool: NameOrId, - }, - /// Use the default pool for the silo. - Auto { - /// IP version to use when multiple default pools exist. - /// Required if both IPv4 and IPv6 default pools are configured. - #[serde(default)] - ip_version: Option, - }, -} - -impl Default for PoolSelector { - fn default() -> Self { - PoolSelector::Auto { ip_version: None } - } -} - -/// Parameters for creating an external IP address for instances. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum ExternalIpCreate { - /// An IP address providing both inbound and outbound access. The address is - /// automatically assigned from a pool. - Ephemeral { - /// Pool to allocate from. - #[serde(default)] - pool_selector: PoolSelector, - }, - /// An IP address providing both inbound and outbound access. The address is - /// an existing floating IP object assigned to the current project. - /// - /// The floating IP must not be in use by another instance or service. - Floating { floating_ip: NameOrId }, -} - -/// Parameters for creating an ephemeral IP address for an instance. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct EphemeralIpCreate { - /// Pool to allocate from. - #[serde(default)] - pub pool_selector: PoolSelector, -} - -/// Parameters for detaching an external IP from an instance. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum ExternalIpDetach { - Ephemeral { - /// The IP version of the ephemeral IP to detach. - /// - /// Required when the instance has both IPv4 and IPv6 ephemeral IPs. - /// If only one ephemeral IP is attached, this field may be omitted. - #[serde(default)] - ip_version: Option, - }, - Floating { - floating_ip: NameOrId, - }, -} - -/// Create-time parameters for an `Instance` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// The number of vCPUs to be allocated to the instance - pub ncpus: InstanceCpuCount, - /// The amount of RAM (in bytes) to be allocated to the instance - pub memory: ByteCount, - /// The hostname to be assigned to the instance - pub hostname: Hostname, - - /// User data for instance initialization systems (such as cloud-init). - /// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / - /// characters with padding). Maximum 32 KiB unencoded data. - // While serde happily accepts #[serde(with = "")] as a shorthand for - // specifying `serialize_with` and `deserialize_with`, schemars requires the - // argument to `with` to be a type rather than merely a path prefix (i.e. a - // mod or type). It's admittedly a bit tricky for schemars to address; - // unlike `serialize` or `deserialize`, `JsonSchema` requires several - // functions working together. It's unfortunate that schemars has this - // built-in incompatibility, exacerbated by its glacial rate of progress - // and immunity to offers of help. - #[serde(default, with = "UserData")] - pub user_data: Vec, - - /// The network interfaces to be created for this instance. - #[serde(default)] - pub network_interfaces: InstanceNetworkInterfaceAttachment, - - /// The external IP addresses provided to this instance. - /// - /// By default, all instances have outbound connectivity, but no inbound - /// connectivity. These external addresses can be used to provide a fixed, - /// known IP address for making inbound connections to the instance. - #[serde(default)] - pub external_ips: Vec, - - /// Multicast groups this instance should join at creation. - /// - /// Groups can be specified by name, UUID, or IP address. Non-existent - /// groups are created automatically. - #[serde(default)] - pub multicast_groups: Vec, - - /// A list of disks to be attached to the instance. - /// - /// Disk attachments of type "create" will be created, while those of type - /// "attach" must already exist. - /// - /// The order of this list does not guarantee a boot order for the instance. - /// Use the boot_disk attribute to specify a boot disk. When boot_disk is - /// specified it will count against the disk attachment limit. - #[serde(default)] - pub disks: Vec, - - /// The disk the instance is configured to boot from. - /// - /// This disk can either be attached if it already exists or created along - /// with the instance. - /// - /// Specifying a boot disk is optional but recommended to ensure predictable - /// boot behavior. The boot disk can be set during instance creation or - /// later if the instance is stopped. The boot disk counts against the disk - /// attachment limit. - /// - /// An instance that does not have a boot disk set will use the boot - /// options specified in its UEFI settings, which are controlled by both the - /// instance's UEFI firmware and the guest operating system. Boot options - /// can change as disks are attached and detached, which may result in an - /// instance that only boots to the EFI shell until a boot disk is set. - #[serde(default)] - pub boot_disk: Option, - - /// An allowlist of SSH public keys to be transferred to the instance via - /// cloud-init during instance creation. - /// - /// If not provided, all SSH public keys from the user's profile will be sent. - /// If an empty list is provided, no public keys will be transmitted to the - /// instance. - pub ssh_public_keys: Option>, - - /// Should this instance be started upon creation; true by default. - #[serde(default = "bool_true")] - pub start: bool, - - /// The auto-restart policy for this instance. - /// - /// This policy determines whether the instance should be automatically - /// restarted by the control plane on failure. If this is `null`, no - /// auto-restart policy will be explicitly configured for this instance, and - /// the control plane will select the default policy when determining - /// whether the instance can be automatically restarted. - /// - /// Currently, the global default auto-restart policy is "best-effort", so - /// instances with `null` auto-restart policies will be automatically - /// restarted. However, in the future, the default policy may be - /// configurable through other mechanisms, such as on a per-project basis. - /// In that case, any configured default policy will be used if this is - /// `null`. - #[serde(default)] - pub auto_restart_policy: Option, - - /// Anti-affinity groups to which this instance should be added. - #[serde(default)] - pub anti_affinity_groups: Vec, - - /// The CPU platform to be used for this instance. If this is `null`, the - /// instance requires no particular CPU platform; when it is started the - /// instance will have the most general CPU platform supported by the sled - /// it is initially placed on. - #[serde(default)] - pub cpu_platform: Option, -} - -/// Parameters of an `Instance` that can be reconfigured after creation. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceUpdate { - /// The number of vCPUs to be allocated to the instance - pub ncpus: InstanceCpuCount, - - /// The amount of RAM (in bytes) to be allocated to the instance - pub memory: ByteCount, - - /// The disk the instance is configured to boot from. - /// - /// Setting a boot disk is optional but recommended to ensure predictable - /// boot behavior. The boot disk can be set during instance creation or - /// later if the instance is stopped. The boot disk counts against the disk - /// attachment limit. - /// - /// An instance that does not have a boot disk set will use the boot - /// options specified in its UEFI settings, which are controlled by both the - /// instance's UEFI firmware and the guest operating system. Boot options - /// can change as disks are attached and detached, which may result in an - /// instance that only boots to the EFI shell until a boot disk is set. - pub boot_disk: Nullable, - - /// The auto-restart policy for this instance. - /// - /// This policy determines whether the instance should be automatically - /// restarted by the control plane on failure. If this is `null`, any - /// explicitly configured auto-restart policy will be unset, and - /// the control plane will select the default policy when determining - /// whether the instance can be automatically restarted. - /// - /// Currently, the global default auto-restart policy is "best-effort", so - /// instances with `null` auto-restart policies will be automatically - /// restarted. However, in the future, the default policy may be - /// configurable through other mechanisms, such as on a per-project basis. - /// In that case, any configured default policy will be used if this is - /// `null`. - pub auto_restart_policy: Nullable, - - /// The CPU platform to be used for this instance. If this is `null`, the - /// instance requires no particular CPU platform; when it is started the - /// instance will have the most general CPU platform supported by the sled - /// it is initially placed on. - pub cpu_platform: Nullable, - - /// Multicast groups this instance should join. - /// - /// When specified, this replaces the instance's current multicast group - /// membership with the new set of groups. The instance will leave any - /// groups not listed here and join any new groups that are specified. - /// - /// Each entry can specify the group by name, UUID, or IP address, along with - /// optional source IP filtering for SSM (Source-Specific Multicast). When - /// a group doesn't exist, it will be implicitly created using the default - /// multicast pool (or you can specify `ip_version` to disambiguate if needed). - /// - /// If not provided, the instance's multicast group membership will not - /// be changed. - #[serde(default)] - pub multicast_groups: Option>, -} - -#[inline] -pub fn bool_true() -> bool { - true -} - -// If you change this, also update the error message in -// `UserData::deserialize()` below. -pub const MAX_USER_DATA_BYTES: usize = 32 * 1024; // 32 KiB - -pub struct UserData; -impl UserData { - pub fn serialize( - data: &Vec, - serializer: S, - ) -> Result - where - S: Serializer, - { - base64::engine::general_purpose::STANDARD - .encode(data) - .serialize(serializer) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - match base64::engine::general_purpose::STANDARD - .decode(::deserialize(deserializer)?) - { - Ok(buf) => { - // if you change this, also update the stress test in crate::cidata - if buf.len() > MAX_USER_DATA_BYTES { - Err(::invalid_length( - buf.len(), - &"less than 32 KiB", - )) - } else { - Ok(buf) - } - } - Err(_) => Err(::invalid_value( - serde::de::Unexpected::Other("invalid base64 string"), - &"a valid base64 string", - )), - } - } -} - -impl JsonSchema for UserData { - fn schema_name() -> String { - "String".to_string() - } - - fn json_schema( - _: &mut schemars::r#gen::SchemaGenerator, - ) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - instance_type: Some(schemars::schema::InstanceType::String.into()), - format: Some("byte".to_string()), - ..Default::default() - } - .into() - } - - fn is_referenceable() -> bool { - false - } -} - -/// Forwarded to a propolis server to request the contents of an Instance's serial console. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct InstanceSerialConsoleRequest { - /// Name or ID of the project, only required if `instance` is provided as a `Name` - pub project: Option, - /// Character index in the serial buffer from which to read, counting the bytes output since - /// instance start. If this is not provided, `most_recent` must be provided, and if this *is* - /// provided, `most_recent` must *not* be provided. - pub from_start: Option, - /// Character index in the serial buffer from which to read, counting *backward* from the most - /// recently buffered data retrieved from the instance. (See note on `from_start` about mutual - /// exclusivity) - pub most_recent: Option, - /// Maximum number of bytes of buffered serial console contents to return. If the requested - /// range runs to the end of the available buffer, the data returned will be shorter than - /// `max_bytes`. - pub max_bytes: Option, -} - -/// Forwarded to a propolis server to request the contents of an Instance's serial console. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct InstanceSerialConsoleStreamRequest { - /// Name or ID of the project, only required if `instance` is provided as a `Name` - pub project: Option, - /// Character index in the serial buffer from which to read, counting *backward* from the most - /// recently buffered data retrieved from the instance. - pub most_recent: Option, -} - -/// Contents of an Instance's serial console buffer. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceSerialConsoleData { - /// The bytes starting from the requested offset up to either the end of the buffer or the - /// request's `max_bytes`. Provided as a u8 array rather than a string, as it may not be UTF-8. - pub data: Vec, - /// The absolute offset since boot (suitable for use as `byte_offset` in a subsequent request) - /// of the last byte returned in `data`. - pub last_byte_offset: u64, -} - -// VPCS - -/// Create-time parameters for a `Vpc` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct VpcCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// The IPv6 prefix for this VPC - /// - /// All IPv6 subnets created from this VPC must be taken from this range, - /// which should be a Unique Local Address in the range `fd00::/48`. The - /// default VPC Subnet will have the first `/64` range from this prefix. - pub ipv6_prefix: Option, - - pub dns_name: Name, -} - -/// Updateable properties of a `Vpc` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct VpcUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, - pub dns_name: Option, -} - -/// Create-time parameters for a `VpcSubnet` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct VpcSubnetCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// The IPv4 address range for this subnet. - /// - /// It must be allocated from an RFC 1918 private address range, and must - /// not overlap with any other existing subnet in the VPC. - pub ipv4_block: Ipv4Net, - - /// The IPv6 address range for this subnet. - /// - /// It must be allocated from the RFC 4193 Unique Local Address range, with - /// the prefix equal to the parent VPC's prefix. A random `/64` block will - /// be assigned if one is not provided. It must not overlap with any - /// existing subnet in the VPC. - pub ipv6_block: Option, - - /// An optional router, used to direct packets sent from hosts in this subnet - /// to any destination address. - /// - /// Custom routers apply in addition to the VPC-wide *system* router, and have - /// higher priority than the system router for an otherwise - /// equal-prefix-length match. - pub custom_router: Option, -} - -/// Updateable properties of a `VpcSubnet` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct VpcSubnetUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, - - /// An optional router, used to direct packets sent from hosts in this subnet - /// to any destination address. - pub custom_router: Option, -} - -// VPC ROUTERS - -/// Create-time parameters for a `VpcRouter` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct VpcRouterCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, -} - -/// Updateable properties of a `VpcRouter` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct VpcRouterUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, -} - -// VPC ROUTER ROUTES - -/// Create-time parameters for a `RouterRoute` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct RouterRouteCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// The location that matched packets should be forwarded to. - pub target: RouteTarget, - /// Selects which traffic this routing rule will apply to. - pub destination: RouteDestination, -} - -/// Updateable properties of a `RouterRoute` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct RouterRouteUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, - /// The location that matched packets should be forwarded to. - pub target: RouteTarget, - /// Selects which traffic this routing rule will apply to. - pub destination: RouteDestination, -} - -// INTERNET GATEWAYS - -/// Create-time parameters for an `InternetGateway` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InternetGatewayCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InternetGatewayIpPoolCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - pub ip_pool: NameOrId, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InternetGatewayIpAddressCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - pub address: IpAddr, -} - -// DISKS - -#[derive(Display, Serialize, Deserialize, JsonSchema)] -#[display(style = "snake_case")] -#[serde(rename_all = "snake_case")] -pub enum DiskMetricName { - Activated, - Flush, - Read, - ReadBytes, - Write, - WriteBytes, -} - -#[derive(Serialize, Deserialize, JsonSchema)] -pub struct DiskMetricsPath { - pub disk: NameOrId, - pub metric: DiskMetricName, -} - -#[derive(Copy, Clone, Debug, Deserialize, Serialize)] -#[serde(try_from = "u32")] // invoke the try_from validation routine below -pub struct BlockSize(pub u32); - -impl TryFrom for BlockSize { - type Error = anyhow::Error; - fn try_from(x: u32) -> Result { - if ![512, 2048, 4096].contains(&x) { - anyhow::bail!("invalid block size {}", x); - } - - Ok(BlockSize(x)) - } -} - -impl Into for BlockSize { - fn into(self) -> ByteCount { - ByteCount::from(self.0) - } -} - -impl From for u64 { - fn from(bs: BlockSize) -> u64 { - u64::from(bs.0) - } -} - -impl JsonSchema for BlockSize { - fn schema_name() -> String { - "BlockSize".to_string() - } - - fn json_schema( - _: &mut schemars::r#gen::SchemaGenerator, - ) -> schemars::schema::Schema { - schemars::schema::Schema::Object(schemars::schema::SchemaObject { - metadata: Some(Box::new(schemars::schema::Metadata { - id: None, - title: Some("Disk block size in bytes".to_string()), - ..Default::default() - })), - instance_type: Some(schemars::schema::InstanceType::Integer.into()), - enum_values: Some(vec![ - serde_json::json!(512), - serde_json::json!(2048), - serde_json::json!(4096), - ]), - ..Default::default() - }) - } -} - -/// Describes the form factor of physical disks. -#[derive( - Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq, -)] -#[serde(rename_all = "snake_case")] -pub enum PhysicalDiskKind { - M2, - U2, -} - -impl From for PhysicalDiskKind { - fn from(dv: DiskVariant) -> Self { - match dv { - DiskVariant::M2 => PhysicalDiskKind::M2, - DiskVariant::U2 => PhysicalDiskKind::U2, - } - } -} - -/// Different sources for a Distributed Disk -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum DiskSource { - /// Create a blank disk - Blank { - /// Size of blocks for this disk. Valid values are: 512, 2048, or 4096. - block_size: BlockSize, - }, - - /// Create a disk from a disk snapshot - Snapshot { - snapshot_id: Uuid, - /// If `true`, the disk created from this snapshot will be read-only. - #[serde(default)] - read_only: bool, - }, - - /// Create a disk from an image - Image { - image_id: Uuid, - /// If `true`, the disk created from this image will be read-only. - #[serde(default)] - read_only: bool, - }, - - /// Create a blank disk that will accept bulk writes or pull blocks from an - /// external source. - ImportingBlocks { block_size: BlockSize }, -} - -/// The source of a `Disk`'s blocks -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum DiskBackend { - Local {}, - - Distributed { - /// The initial source for this disk - disk_source: DiskSource, - }, -} - -/// Create-time parameters for a `Disk` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct DiskCreate { - /// The common identifying metadata for the disk - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// The source for this `Disk`'s blocks - pub disk_backend: DiskBackend, - - /// The total size of the Disk (in bytes) - pub size: ByteCount, -} - -// equivalent to crucible_pantry_client::types::ExpectedDigest -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ExpectedDigest { - Sha256(String), -} - -/// Parameters for importing blocks with a bulk write -// equivalent to crucible_pantry_client::types::BulkWriteRequest -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct ImportBlocksBulkWrite { - pub offset: u64, - pub base64_encoded_data: String, -} - -/// Parameters for finalizing a disk -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct FinalizeDisk { - /// If specified a snapshot of the disk will be created with the given name - /// during finalization. If not specified, a snapshot for the disk will - /// _not_ be created. A snapshot can be manually created once the disk - /// transitions into the `Detached` state. - pub snapshot_name: Option, -} - -/// Select an address lot by an optional name or id. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct AddressLotSelector { - /// Name or id of the address lot to select - pub address_lot: NameOrId, -} - -/// Parameters for creating an address lot. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AddressLotCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// The kind of address lot to create. - pub kind: AddressLotKind, - /// The blocks to add along with the new address lot. - pub blocks: Vec, -} - -/// Parameters for creating an address lot block. First and last addresses are -/// inclusive. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AddressLotBlockCreate { - /// The first address in the lot (inclusive). - pub first_address: IpAddr, - /// The last address in the lot (inclusive). - pub last_address: IpAddr, -} - -impl From for AddressLotBlockCreate { - fn from(ipnet: IpNet) -> AddressLotBlockCreate { - match ipnet { - IpNet::V4(ipv4_net) => AddressLotBlockCreate { - first_address: ipv4_net.first_addr().into(), - last_address: ipv4_net.last_addr().into(), - }, - IpNet::V6(ipv6_net) => AddressLotBlockCreate { - first_address: ipv6_net.first_addr().into(), - last_address: ipv6_net.last_addr().into(), - }, - } - } -} - -/// Parameters for creating a loopback address on a particular rack switch. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct LoopbackAddressCreate { - /// The name or id of the address lot this loopback address will pull an - /// address from. - pub address_lot: NameOrId, - - /// The rack containing the switch this loopback address will be configured on. - pub rack_id: Uuid, - - // TODO: #3604 Consider using `SwitchLocation` type instead of `Name` for `LoopbackAddressCreate.switch_location` - /// The location of the switch within the rack this loopback address will be - /// configured on. - pub switch_location: Name, - - /// The address to create. - pub address: IpAddr, - - /// The subnet mask to use for the address. - pub mask: u8, - - /// Address is an anycast address. - /// - /// This allows the address to be assigned to multiple locations simultaneously. - pub anycast: bool, -} - -#[derive(Serialize, Deserialize, JsonSchema)] -pub struct LoopbackAddressPath { - /// The rack to use when selecting the loopback address. - pub rack_id: Uuid, - - /// The switch location to use when selecting the loopback address. - pub switch_location: Name, - - /// The IP address and subnet mask to use when selecting the loopback - /// address. - pub address: IpAddr, - - /// The IP address and subnet mask to use when selecting the loopback - /// address. - pub subnet_mask: u8, -} - -/// Parameters for creating a port settings group. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SwtichPortSettingsGroupCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// Switch port settings to associate with the settings group being created. - pub settings: SwitchPortSettingsCreate, -} - -/// Parameters for creating switch port settings. Switch port settings are the -/// central data structure for setting up external networking. Switch port -/// settings include link, interface, route, address and dynamic network -/// protocol configuration. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SwitchPortSettingsCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - pub port_config: SwitchPortConfigCreate, - - #[serde(default)] - pub groups: Vec, - - /// Link configurations. - pub links: Vec, - - /// Interface configurations. - #[serde(default)] - pub interfaces: Vec, - - /// Route configurations. - #[serde(default)] - pub routes: Vec, - - /// BGP peer configurations. - #[serde(default)] - pub bgp_peers: Vec, - - /// Address configurations. - pub addresses: Vec, -} - -impl SwitchPortSettingsCreate { - pub fn new(identity: IdentityMetadataCreateParams) -> Self { - Self { - identity, - port_config: SwitchPortConfigCreate { - geometry: SwitchPortGeometry::Qsfp28x1, - }, - groups: Vec::new(), - links: Vec::new(), - interfaces: Vec::new(), - routes: Vec::new(), - bgp_peers: Vec::new(), - addresses: Vec::new(), - } - } -} - -/// Physical switch port configuration. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub struct SwitchPortConfigCreate { - /// Link geometry for the switch port. - pub geometry: SwitchPortGeometry, -} - -/// The link geometry associated with a switch port. -#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum SwitchPortGeometry { - /// The port contains a single QSFP28 link with four lanes. - Qsfp28x1, - - /// The port contains two QSFP28 links each with two lanes. - Qsfp28x2, - - /// The port contains four SFP28 links each with one lane. - Sfp28x4, -} - -/// Switch link configuration. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct LinkConfigCreate { - /// Link name. On ports that are not broken out, this is always phy0. - /// On a 2x breakout the options are phy0 and phy1, on 4x - /// phy0-phy3, etc. - pub link_name: Name, - - /// Maximum transmission unit for the link. - pub mtu: u16, - - /// The link-layer discovery protocol (LLDP) configuration for the link. - pub lldp: LldpLinkConfigCreate, - - /// The requested forward-error correction method. If this is not - /// specified, the standard FEC for the underlying media will be applied - /// if it can be determined. - pub fec: Option, - - /// The speed of the link. - pub speed: LinkSpeed, - - /// Whether or not to set autonegotiation. - pub autoneg: bool, - - /// Optional tx_eq settings. - pub tx_eq: Option, -} - -/// Per-port tx-eq overrides. This can be used to fine-tune the transceiver -/// equalization settings to improve signal integrity. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] -pub struct TxEqConfig { - /// Pre-cursor tap1 - pub pre1: Option, - /// Pre-cursor tap2 - pub pre2: Option, - /// Main tap - pub main: Option, - /// Post-cursor tap2 - pub post2: Option, - /// Post-cursor tap1 - pub post1: Option, -} - -impl From for TxEqConfig { - fn from( - x: omicron_common::api::internal::shared::TxEqConfig, - ) -> TxEqConfig { - TxEqConfig { - pre1: x.pre1, - pre2: x.pre2, - main: x.main, - post2: x.post2, - post1: x.post1, - } - } -} - -/// The LLDP configuration associated with a port. -#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)] -pub struct LldpLinkConfigCreate { - /// Whether or not LLDP is enabled. - pub enabled: bool, - - /// The LLDP link name TLV. - pub link_name: Option, - - /// The LLDP link description TLV. - pub link_description: Option, - - /// The LLDP chassis identifier TLV. - pub chassis_id: Option, - - /// The LLDP system name TLV. - pub system_name: Option, - - /// The LLDP system description TLV. - pub system_description: Option, - - /// The LLDP management IP TLV. - pub management_ip: Option, -} - -impl PartialEq - for omicron_common::api::external::LldpLinkConfig -{ - fn eq(&self, other: &LldpLinkConfigCreate) -> bool { - self.enabled == other.enabled - && self.link_name == other.link_name - && self.link_description == other.link_description - && self.chassis_id == other.chassis_id - && self.system_name == other.system_name - && self.system_description == other.system_description - && self.management_ip == other.management_ip - } -} - -impl PartialEq - for LldpLinkConfigCreate -{ - fn eq( - &self, - other: &omicron_common::api::external::LldpLinkConfig, - ) -> bool { - self.enabled == other.enabled - && self.link_name == other.link_name - && self.link_description == other.link_description - && self.chassis_id == other.chassis_id - && self.system_name == other.system_name - && self.system_description == other.system_description - && self.management_ip == other.management_ip - } -} - -/// A layer-3 switch interface configuration. When IPv6 is enabled, a link local -/// address will be created for the interface. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SwitchInterfaceConfigCreate { - /// Link name. On ports that are not broken out, this is always phy0. - /// On a 2x breakout the options are phy0 and phy1, on 4x - /// phy0-phy3, etc. - pub link_name: Name, - - /// Whether or not IPv6 is enabled. - pub v6_enabled: bool, - - /// What kind of switch interface this configuration represents. - pub kind: SwitchInterfaceKind, -} - -/// Indicates the kind for a switch interface. -#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum SwitchInterfaceKind { - /// Primary interfaces are associated with physical links. There is exactly - /// one primary interface per physical link. - Primary, - - /// VLAN interfaces allow physical interfaces to be multiplexed onto - /// multiple logical links, each distinguished by a 12-bit 802.1Q Ethernet - /// tag. - Vlan(SwitchVlanInterface), - - /// Loopback interfaces are anchors for IP addresses that are not specific - /// to any particular port. - Loopback, -} - -/// Configuration data associated with a switch VLAN interface. The VID -/// indicates a VLAN identifier. Must be between 1 and 4096. -#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SwitchVlanInterface { - /// The virtual network id (VID) that distinguishes this interface and is - /// used for producing and consuming 802.1Q Ethernet tags. This field has a - /// maximum value of 4095 as 802.1Q tags are twelve bits. - pub vid: u16, -} - -/// Route configuration data associated with a switch port configuration. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct RouteConfig { - /// Link name. On ports that are not broken out, this is always phy0. - /// On a 2x breakout the options are phy0 and phy1, on 4x - /// phy0-phy3, etc. - pub link_name: Name, - - /// The set of routes assigned to a switch port. - pub routes: Vec, -} - -/// A route to a destination network through a gateway address. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Route { - /// The route destination. - pub dst: IpNet, - - /// The route gateway. - pub gw: IpAddr, - - /// VLAN id the gateway is reachable over. - pub vid: Option, - - /// Route RIB priority. Higher priority indicates precedence within and across - /// protocols. - pub rib_priority: Option, -} - -/// Select a BGP config by a name or id. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct BgpConfigSelector { - /// A name or id to use when selecting BGP config. - pub name_or_id: NameOrId, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct BgpPeerConfig { - /// Link that the peer is reachable on. - /// On ports that are not broken out, this is always phy0. - /// On a 2x breakout the options are phy0 and phy1, on 4x - /// phy0-phy3, etc. - pub link_name: Name, - - pub peers: Vec, -} - -/// Parameters for creating a named set of BGP announcements. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct BgpAnnounceSetCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// The announcements in this set. - pub announcement: Vec, -} - -/// Select a BGP announce set by a name or id. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct BgpAnnounceSetSelector { - /// Name or ID of the announce set - pub announce_set: NameOrId, -} - -/// List BGP announce set with an optional name or id. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct BgpAnnounceListSelector { - /// Name or ID of the announce set - pub announce_set: Option, -} - -/// Selector used for querying imported BGP routes. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct BgpRouteSelector { - /// The ASN to filter on. Required. - pub asn: u32, -} - -/// A BGP announcement tied to a particular address lot block. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct BgpAnnouncementCreate { - /// Address lot this announcement is drawn from. - pub address_lot_block: NameOrId, - - /// The network being announced. - pub network: IpNet, -} - -/// Parameters for creating a BGP configuration. This includes an autonomous -/// system number (ASN) and a virtual routing and forwarding (VRF) identifier. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct BgpConfigCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// The autonomous system number of this BGP configuration. - pub asn: u32, - - pub bgp_announce_set_id: NameOrId, - - /// Optional virtual routing and forwarding identifier for this BGP - /// configuration. - pub vrf: Option, - - // Dynamic BGP policy is not yet available so we skip adding it to the API - /// A shaper program to apply to outgoing open and update messages. - #[serde(skip)] - pub shaper: Option, - /// A checker program to apply to incoming open and update messages. - #[serde(skip)] - pub checker: Option, - - /// Maximum number of paths to use when multiple "best paths" exist - #[serde(default)] - pub max_paths: MaxPathConfig, -} - -/// Select a BGP status information by BGP config id. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct BgpStatusSelector { - /// A name or id of the BGP configuration to get status for - pub name_or_id: NameOrId, -} - -/// Information about a bidirectional forwarding detection (BFD) session. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct BfdSessionEnable { - /// Address the Oxide switch will listen on for BFD traffic. If `None` then - /// the unspecified address (0.0.0.0 or ::) is used. - pub local: Option, - - /// Address of the remote peer to establish a BFD session with. - pub remote: IpAddr, - - /// The negotiated Control packet transmission interval, multiplied by this - /// variable, will be the Detection Time for this session (as seen by the - /// remote system) - pub detection_threshold: u8, - - /// The minimum interval, in microseconds, between received BFD - /// Control packets that this system requires - pub required_rx: u64, - - /// The switch to enable this session on. Must be `switch0` or `switch1`. - pub switch: Name, - - /// Select either single-hop (RFC 5881) or multi-hop (RFC 5883) - pub mode: BfdMode, -} - -/// Information needed to disable a BFD session -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct BfdSessionDisable { - /// Address of the remote peer to disable a BFD session for. - pub remote: IpAddr, - - /// The switch to enable this session on. Must be `switch0` or `switch1`. - pub switch: Name, -} - -/// A set of addresses associated with a port configuration. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AddressConfig { - /// Link to assign the addresses to. - /// On ports that are not broken out, this is always phy0. - /// On a 2x breakout the options are phy0 and phy1, on 4x - /// phy0-phy3, etc. - pub link_name: Name, - - /// The set of addresses assigned to the port configuration. - pub addresses: Vec
, -} - -/// An address tied to an address lot. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Address { - /// The address lot this address is drawn from. - pub address_lot: NameOrId, - - /// The address and prefix length of this address. - pub address: IpNet, - - /// Optional VLAN ID for this address - pub vlan_id: Option, -} - -/// Select a port settings object by an optional name or id. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct SwitchPortSettingsSelector { - /// An optional name or id to use when selecting port settings. - pub port_settings: Option, -} - -/// Select a port settings info object by name or id. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct SwitchPortSettingsInfoSelector { - /// A name or id to use when selecting switch port settings info objects. - pub port: NameOrId, -} - -/// Select a switch port by name. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct SwitchPortPathSelector { - /// A name to use when selecting switch ports. - pub port: Name, -} - -/// Select switch ports by rack id and location. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct SwitchPortSelector { - /// A rack id to use when selecting switch ports. - pub rack_id: Uuid, - - /// A switch location to use when selecting switch ports. - pub switch_location: Name, -} - -/// Select switch port interfaces by id. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct SwitchPortPageSelector { - /// An optional switch port id to use when listing switch ports. - pub switch_port_id: Option, -} - -/// Parameters for applying settings to switch ports. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct SwitchPortApplySettings { - /// A name or id to use when applying switch port settings. - pub port_settings: NameOrId, -} - -/// Select an LLDP endpoint by rack/switch/port -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct LldpPortPathSelector { - /// A rack id to use when selecting switch ports. - pub rack_id: Uuid, - - /// A switch location to use when selecting switch ports. - pub switch_location: Name, - - /// A name to use when selecting switch ports. - pub port: Name, -} - -// IMAGES - -/// The source of the underlying image. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum ImageSource { - Snapshot { - id: Uuid, - }, - - /// Boot the Alpine ISO that ships with the Propolis zone. Intended for - /// development purposes only. - #[schemars(skip)] // keep it out of the OpenAPI schema - YouCanBootAnythingAsLongAsItsAlpine, -} - -/// OS image distribution -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Distribution { - /// The name of the distribution (e.g. "alpine" or "ubuntu") - pub name: Name, - /// The version of the distribution (e.g. "3.10" or "18.04") - pub version: String, -} - -/// Create-time parameters for an `Image` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct ImageCreate { - /// Common identifying metadata - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// The family of the operating system (e.g. Debian, Ubuntu, etc.) - pub os: String, - - /// The version of the operating system (e.g. 18.04, 20.04, etc.) - pub version: String, - - /// The source of the image's contents. - pub source: ImageSource, -} - -// SNAPSHOTS - -/// Create-time parameters for a `Snapshot` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SnapshotCreate { - /// Common identifying metadata - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// The disk to be snapshotted - pub disk: NameOrId, -} - -// USERS AND GROUPS - -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct OptionalGroupSelector { - #[schemars(with = "Option")] - pub group: Option, -} - -// BUILT-IN USERS -// -// These cannot be created via the external API, but we use the same interfaces -// for creating them internally as we use for types that can be created in the -// external API. - -/// Create-time parameters for a `UserBuiltin` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct UserBuiltinCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, -} - -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] -pub struct UserBuiltinSelector { - pub user: NameOrId, -} - -// SSH PUBLIC KEYS -// -// The SSH key mangement endpoints are currently under `/v1/me`, -// and so have an implicit silo user ID which must be passed seperately -// to the creation routine. Note that this disagrees with RFD 44. - -/// Create-time parameters for an `SshKey` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SshKeyCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// SSH public key, e.g., `"ssh-ed25519 AAAAC3NzaC..."` - pub public_key: String, -} - -// METRICS - -#[derive(Display, Deserialize, JsonSchema)] -#[display(style = "snake_case")] -#[serde(rename_all = "snake_case")] -pub enum SystemMetricName { - VirtualDiskSpaceProvisioned, - CpusProvisioned, - RamProvisioned, -} - -#[derive(Deserialize, JsonSchema)] -pub struct SystemMetricsPathParam { - pub metric_name: SystemMetricName, -} - -/// Query parameters common to resource metrics endpoints. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct ResourceMetrics { - /// An inclusive start time of metrics. - pub start_time: DateTime, - /// An exclusive end time of metrics. - pub end_time: DateTime, - /// Query result order - pub order: Option, -} - -// SYSTEM UPDATE - -/// Parameters for PUT requests for `/v1/system/update/repositories`. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct UpdatesPutRepositoryParams { - /// The name of the uploaded file. - pub file_name: String, -} - -/// Parameters for GET requests for `/v1/system/update/repositories`. -#[derive(Clone, Debug, Deserialize, JsonSchema)] -pub struct UpdatesGetRepositoryParams { - /// The version to get. - pub system_version: Version, -} - -/// Parameters for PUT requests to `/v1/system/update/target-release`. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct SetTargetReleaseParams { - /// Version of the system software to make the target release. - pub system_version: Version, -} - -// Probes - -/// Create time parameters for probes. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct ProbeCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - #[schemars(with = "Uuid")] - pub sled: SledUuid, - /// Pool to allocate from. - #[serde(default)] - pub pool_selector: PoolSelector, -} - -/// List probes with an optional name or id. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct ProbeListSelector { - /// A name or id to use when selecting a probe. - pub name_or_id: Option, -} - -/// A timeseries query string, written in the Oximeter query language. -#[derive(Deserialize, JsonSchema, Serialize)] -pub struct TimeseriesQuery { - /// A timeseries query string, written in the Oximeter query language. - pub query: String, - /// Whether to include query summaries in the response. Note: we omit this - /// field from the generated docs, since it is not intended for consumption - /// by customers. - #[serde(default)] - #[schemars(skip)] - pub include_summaries: bool, -} - -// Allowed source IPs - -/// Parameters for updating allowed source IPs -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct AllowListUpdate { - /// The new list of allowed source IPs. - pub allowed_ips: AllowedSourceIps, -} - -// Console API - -#[derive(Deserialize, JsonSchema)] -pub struct RestPathParam { - pub path: Vec, -} - -#[derive(Deserialize, JsonSchema)] -pub struct LoginToProviderPathParam { - pub silo_name: Name, - pub provider_name: Name, -} - -#[derive(Serialize, Deserialize, JsonSchema)] -pub struct LoginUrlQuery { - pub redirect_uri: Option, -} - -#[derive(Deserialize, JsonSchema)] -pub struct LoginPath { - pub silo_name: Name, -} - -#[derive(Deserialize, JsonSchema)] -pub struct RackMembershipConfigPathParams { - pub rack_id: Uuid, -} - -/// This is meant as a security feature. We want to ensure we never redirect to -/// a URI on a different host. -#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, Display)] -#[serde(try_from = "String")] -#[display("{0}")] -pub struct RelativeUri(String); - -impl FromStr for RelativeUri { - type Err = String; - - fn from_str(s: &str) -> Result { - Self::try_from(s.to_string()) - } -} - -impl TryFrom for RelativeUri { - type Error = String; - - fn try_from(uri: Uri) -> Result { - if uri.host().is_none() && uri.scheme().is_none() { - Ok(Self(uri.to_string())) - } else { - Err(format!("\"{}\" is not a relative URI", uri)) - } - } -} - -impl TryFrom for RelativeUri { - type Error = String; - - fn try_from(s: String) -> Result { - s.parse::() - .map_err(|_| format!("\"{}\" is not a relative URI", s)) - .and_then(|uri| Self::try_from(uri)) - } -} - -// Device auth - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct DeviceAuthRequest { - pub client_id: Uuid, - /// Optional lifetime for the access token in seconds. - /// - /// This value will be validated during the confirmation step. If not - /// specified, it defaults to the silo's max TTL, which can be seen at - /// `/v1/auth-settings`. If specified, must not exceed the silo's max TTL. - /// - /// Some special logic applies when authenticating the confirmation request - /// with an existing device token: the requested TTL must not produce an - /// expiration time later than the authenticating token's expiration. If no - /// TTL is specified, the expiration will be the lesser of the silo max and - /// the authenticating token's expiration time. To get the longest allowed - /// lifetime, omit the TTL and authenticate with a web console session. - pub ttl_seconds: Option, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct DeviceAuthVerify { - pub user_code: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct DeviceAccessTokenRequest { - pub grant_type: String, - pub device_code: String, - pub client_id: Uuid, -} - -// Alerts - -/// Query params for listing alert classes. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AlertClassFilter { - /// An optional glob pattern for filtering alert class names. - /// - /// If provided, only alert classes which match this glob pattern will be - /// included in the response. - pub filter: Option, -} - -#[derive(Deserialize, JsonSchema)] -pub struct AlertSelector { - /// UUID of the alert - pub alert_id: Uuid, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AlertSubscriptionSelector { - /// The webhook receiver that the subscription is attached to. - #[serde(flatten)] - pub receiver: AlertReceiverSelector, - /// The event class subscription itself. - pub subscription: shared::AlertSubscription, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AlertClassPage { - /// The last webhook event class returned by a previous page. - pub last_seen: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AlertReceiverSelector { - /// The name or ID of the webhook receiver. - pub receiver: NameOrId, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct WebhookCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// The URL that webhook notification requests should be sent to - pub endpoint: Url, - - /// A non-empty list of secret keys used to sign webhook payloads. - pub secrets: Vec, - - /// A list of webhook event class subscriptions. - /// - /// If this list is empty or is not included in the request body, the - /// webhook will not be subscribed to any events. - #[serde(default)] - pub subscriptions: Vec, -} - -/// Parameters to update a webhook configuration. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct WebhookReceiverUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, - - /// The URL that webhook notification requests should be sent to - pub endpoint: Option, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AlertSubscriptionCreate { - /// The event class pattern to subscribe to. - pub subscription: shared::AlertSubscription, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct WebhookSecretCreate { - /// The value of the shared secret key. - pub secret: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct WebhookSecretSelector { - /// ID of the secret. - pub secret_id: Uuid, -} - -#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AlertDeliveryStateFilter { - /// If true, include deliveries which are currently in progress. - /// - /// If any of the "pending", "failed", or "delivered" query parameters are - /// set to true, only deliveries matching those state(s) will be included in - /// the response. If NO state filter parameters are set, then all deliveries - /// are included. - /// - /// A delivery is considered "pending" if it has not yet been sent at all, - /// or if a delivery attempt has failed but the delivery has retries - /// remaining. - pub pending: Option, - /// If true, include deliveries which have failed permanently. - /// - /// If any of the "pending", "failed", or "delivered" query parameters are - /// set to true, only deliveries matching those state(s) will be included in - /// the response. If NO state filter parameters are set, then all deliveries - /// are included. - /// - /// A delivery fails permanently when the retry limit of three total - /// attempts is reached without a successful delivery. - pub failed: Option, - /// If true, include deliveries which have succeeded. - /// - /// If any of the "pending", "failed", or "delivered" query parameters are - /// set to true, only deliveries matching those state(s) will be included in - /// the response. If NO state filter parameters are set, then all deliveries - /// are included. - pub delivered: Option, -} - -impl Default for AlertDeliveryStateFilter { - fn default() -> Self { - Self::ALL - } -} - -impl AlertDeliveryStateFilter { - pub const ALL: Self = - Self { pending: Some(true), failed: Some(true), delivered: Some(true) }; - - pub fn include_pending(&self) -> bool { - self.pending == Some(true) || self.is_all_none() - } - - pub fn include_failed(&self) -> bool { - self.failed == Some(true) || self.is_all_none() - } - - pub fn include_delivered(&self) -> bool { - self.delivered == Some(true) || self.is_all_none() - } - - pub fn include_all(&self) -> bool { - self.is_all_none() - || (self.pending == Some(true) - && self.failed == Some(true) - && self.delivered == Some(true)) - } - - fn is_all_none(&self) -> bool { - self.pending.is_none() - && self.failed.is_none() - && self.delivered.is_none() - } -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AlertReceiverProbe { - /// If true, resend all events that have not been delivered successfully if - /// the probe request succeeds. - #[serde(default)] - pub resend: bool, -} - -/// Audit log has its own pagination scheme because it paginates by timestamp. -#[derive(Deserialize, JsonSchema, Serialize, PartialEq, Debug, Clone)] -pub struct AuditLog { - /// Required, inclusive - pub start_time: DateTime, - /// Exclusive - pub end_time: Option>, -} - -/// Create-time parameters for a multicast group. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupCreate { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - /// The multicast IP address to allocate. If not provided, one will be - /// allocated from the default pool. - #[serde(default, deserialize_with = "validate_multicast_ip_param")] - pub multicast_ip: Option, - /// Source IP addresses for Source-Specific Multicast (SSM). - /// - /// If not provided, uses default behavior (Any-Source Multicast). - /// An empty list explicitly allows any source (Any-Source Multicast). - /// A non-empty list restricts to specific sources (SSM). - #[serde(default, deserialize_with = "validate_source_ips_param")] - pub source_ips: Option>, - /// Name or ID of the IP pool to allocate from. If not provided, uses the - /// default multicast pool. - #[serde(default)] - pub pool: Option, - /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. - /// Tags packets leaving the rack to traverse VLAN-segmented upstream networks. - /// - /// Valid range: 2-4094 (VLAN IDs 0-1 are reserved by IEEE 802.1Q standard). - #[serde(default, deserialize_with = "validate_mvlan_option")] - pub mvlan: Option, -} - -/// Update-time parameters for a multicast group. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupUpdate { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, - #[serde( - default, - deserialize_with = "validate_source_ips_param", - skip_serializing_if = "Option::is_none" - )] - pub source_ips: Option>, - /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. - /// Set to null to clear the MVLAN. Valid range: 2-4094 when provided. - /// Omit the field to leave mvlan unchanged. - #[serde( - default, - deserialize_with = "validate_mvlan_option_nullable", - skip_serializing_if = "Option::is_none" - )] - pub mvlan: Option>, -} - -/// Parameters for adding an instance to a multicast group. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupMemberAdd { - /// Name or ID of the instance to add to the multicast group - pub instance: NameOrId, - /// Source IPs for source-filtered multicast (SSM). Optional for ASM groups, - /// required for SSM groups (232.0.0.0/8, ff3x::/32). - #[serde(default, deserialize_with = "validate_source_ips_param")] - pub source_ips: Option>, -} - -/// Dendrite requires VLAN IDs >= 2 (rejects 0 and 1) -/// -/// Valid range is 2-4094 -fn validate_mvlan(vlan_id: VlanID) -> Result { - let value: u16 = vlan_id.into(); - if value >= 2 { - Ok(vlan_id) - } else { - Err(format!( - "invalid mvlan: {value} (must be >= 2, VLAN IDs 0-1 are reserved)" - )) - } -} - -fn validate_mvlan_option<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - let opt = Option::::deserialize(deserializer)?; - match opt { - Some(v) => { - validate_mvlan(v).map(Some).map_err(serde::de::Error::custom) - } - None => Ok(None), - } -} - -fn validate_mvlan_option_nullable<'de, D>( - deserializer: D, -) -> Result>, D::Error> -where - D: serde::Deserializer<'de>, -{ - // Deserialize as Nullable directly, which handles null properly - // When field has null value, Nullable deserializer returns Nullable(None) - // We always wrap in Some because if field is present, we got here - let nullable = Nullable::::deserialize(deserializer)?; - match nullable.0 { - Some(v) => validate_mvlan(v) - .map(|vv| Some(Nullable(Some(vv)))) - .map_err(serde::de::Error::custom), - None => Ok(Some(Nullable(None))), // Explicit null to clear - } -} - -/// Parameters for removing an instance from a multicast group. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupMemberRemove { - /// Name or ID of the instance to remove from the multicast group - pub instance: NameOrId, -} - -/// Path parameters for multicast group member operations. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupMemberPath { - /// Name, ID, or IP address of the multicast group - pub multicast_group: MulticastGroupIdentifier, - /// Name or ID of the instance - pub instance: NameOrId, -} - -/// Path parameters for instance multicast group operations. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InstanceMulticastGroupPath { - /// Name or ID of the instance - pub instance: NameOrId, - /// Name, ID, or IP address of the multicast group - pub multicast_group: MulticastGroupIdentifier, -} - -/// Parameters for joining an instance to a multicast group. -/// -/// When joining by IP address, the pool containing the multicast IP is -/// auto-discovered from all linked multicast pools. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, Default)] -pub struct InstanceMulticastGroupJoin { - /// Source IPs for source-filtered multicast (SSM). Optional for ASM groups, - /// required for SSM groups (232.0.0.0/8, ff3x::/32). - #[serde(default, deserialize_with = "validate_source_ips_param")] - pub source_ips: Option>, - - /// IP version for pool selection when creating a group by name. - /// Required if both IPv4 and IPv6 default multicast pools are linked. - #[serde(default)] - pub ip_version: Option, -} - -/// Specification for joining a multicast group with optional source filtering. -/// -/// Used in `InstanceCreate` and `InstanceUpdate` to specify multicast group -/// membership along with per-member source IP configuration. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct MulticastGroupJoinSpec { - /// The multicast group to join, specified by name, UUID, or IP address. - pub group: MulticastGroupIdentifier, - - /// Source IPs for source-filtered multicast (SSM). Optional for ASM groups, - /// required for SSM groups (232.0.0.0/8, ff3x::/32). - #[serde(default, deserialize_with = "validate_source_ips_param")] - pub source_ips: Option>, - - /// IP version for pool selection when creating a group by name. - /// Required if both IPv4 and IPv6 default multicast pools are linked. - #[serde(default)] - pub ip_version: Option, -} - -/// Validate that an IP address is suitable for use as a SSM source. -/// -/// For specifics, follow-up on RFC 4607: -/// -pub fn validate_source_ip(ip: IpAddr) -> Result<(), String> { - match ip { - IpAddr::V4(ipv4) => validate_ipv4_source(ipv4), - IpAddr::V6(ipv6) => validate_ipv6_source(ipv6), - } -} - -/// Validate that an IPv4 address is suitable for use as a multicast source. -fn validate_ipv4_source(addr: Ipv4Addr) -> Result<(), String> { - // Must be a unicast address - if !is_unicast_v4(&addr) { - return Err(format!("{} is not a unicast address", addr)); - } - - // Exclude problematic addresses (mostly align with Dendrite, but block link-local) - if addr.is_loopback() - || addr.is_broadcast() - || addr.is_unspecified() - || addr.is_link_local() - { - return Err(format!("{} is a special-use address", addr)); - } - - Ok(()) -} - -/// Validate that an IPv6 address is suitable for use as a multicast source. -fn validate_ipv6_source(addr: Ipv6Addr) -> Result<(), String> { - // Must be a unicast address - if !is_unicast_v6(&addr) { - return Err(format!("{} is not a unicast address", addr)); - } - - // Exclude problematic addresses (align with Dendrite validation, but block link-local) - if addr.is_loopback() - || addr.is_unspecified() - || ((addr.segments()[0] & 0xffc0) == 0xfe80) - // fe80::/10 link-local - { - return Err(format!("{} is a special-use address", addr)); - } - - Ok(()) -} - -/// Validate that an IP address is a proper multicast address for API validation. -pub fn validate_multicast_ip(ip: IpAddr) -> Result<(), String> { - match ip { - IpAddr::V4(ipv4) => validate_ipv4_multicast(ipv4), - IpAddr::V6(ipv6) => validate_ipv6_multicast(ipv6), - } -} - -// IPv4 link-local multicast range reserved for local network control. -const RESERVED_IPV4_MULTICAST_LINK_LOCAL: Ipv4Addr = - Ipv4Addr::new(224, 0, 0, 0); -const RESERVED_IPV4_MULTICAST_LINK_LOCAL_PREFIX: u8 = 24; - -/// Validates IPv4 multicast addresses. -fn validate_ipv4_multicast(addr: Ipv4Addr) -> Result<(), String> { - // Verify this is actually a multicast address - if !addr.is_multicast() { - return Err(format!("{} is not a multicast address", addr)); - } - - // Block link-local multicast (224.0.0.0/24) as it's reserved for local network control - let link_local = Ipv4Net::new( - RESERVED_IPV4_MULTICAST_LINK_LOCAL, - RESERVED_IPV4_MULTICAST_LINK_LOCAL_PREFIX, - ) - .unwrap(); - if link_local.contains(addr) { - return Err(format!( - "{addr} is in the link-local multicast range (224.0.0.0/24)" - )); - } - - Ok(()) -} - -/// Validates IPv6 multicast addresses. -fn validate_ipv6_multicast(addr: Ipv6Addr) -> Result<(), String> { - if !addr.is_multicast() { - return Err(format!("{addr} is not a multicast address")); - } - - // Check reserved subnets - if IPV6_RESERVED_SCOPE_MULTICAST_SUBNET.contains(addr) { - return Err(format!( - "{addr} is in the reserved-scope multicast range (ff00::/16)" - )); - } - if IPV6_INTERFACE_LOCAL_MULTICAST_SUBNET.contains(addr) { - return Err(format!( - "{addr} is in the interface-local multicast range (ff01::/16)" - )); - } - if IPV6_LINK_LOCAL_MULTICAST_SUBNET.contains(addr) { - return Err(format!( - "{addr} is in the link-local multicast range (ff02::/16)" - )); - } - - Ok(()) -} - -/// Deserializer for validating multicast IP addresses. -fn validate_multicast_ip_param<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let ip_opt = Option::::deserialize(deserializer)?; - if let Some(ip) = ip_opt { - validate_multicast_ip(ip).map_err(|e| de::Error::custom(e))?; - } - Ok(ip_opt) -} - -/// Deserializer for validating source IP addresses. -fn validate_source_ips_param<'de, D>( - deserializer: D, -) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - let ips_opt = Option::>::deserialize(deserializer)?; - if let Some(ips) = ips_opt { - // Validate each IP and deduplicate - let mut seen = HashSet::new(); - for ip in &ips { - validate_source_ip(*ip).map_err(de::Error::custom)?; - seen.insert(*ip); - } - - // Check max limit after deduplication - if seen.len() > MAX_SSM_SOURCE_IPS { - return Err(de::Error::custom(format!( - "too many source IPs: {} exceeds maximum of {MAX_SSM_SOURCE_IPS} per RFC 3376", - seen.len(), - ))); - } - - // Return deduplicated list preserving original order - let mut deduped = Vec::with_capacity(seen.len()); - let mut added = HashSet::new(); - for ip in ips { - if added.insert(ip) { - deduped.push(ip); - } - } - Ok(Some(deduped)) - } else { - Ok(None) - } -} - -const fn is_unicast_v4(ip: &Ipv4Addr) -> bool { - !ip.is_multicast() -} - -const fn is_unicast_v6(ip: &Ipv6Addr) -> bool { - !ip.is_multicast() -} - -// SCIM - -#[derive(Deserialize, JsonSchema)] -pub struct ScimV2TokenPathParam { - pub token_id: Uuid, -} - -#[derive(Deserialize, JsonSchema)] -pub struct ScimV2UserPathParam { - pub user_id: String, -} - -#[derive(Deserialize, JsonSchema)] -pub struct ScimV2GroupPathParam { - pub group_id: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct RackMembershipVersionParam { - pub version: Option, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_validate_multicast_ip_v4() { - // Valid IPv4 multicast addresses - assert!( - validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(224, 1, 0, 1))) - .is_ok() - ); - assert!( - validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(225, 2, 3, 4))) - .is_ok() - ); - assert!( - validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(231, 5, 6, 7))) - .is_ok() - ); - assert!( - validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(233, 1, 1, 1))) - .is_ok() - ); // GLOP addressing - allowed - assert!( - validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(239, 1, 1, 1))) - .is_ok() - ); // Admin-scoped - allowed - - // Invalid IPv4 multicast addresses - reserved ranges - assert!( - validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(224, 0, 0, 1))) - .is_err() - ); // Link-local control - assert!( - validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(224, 0, 0, 255))) - .is_err() - ); // Link-local control - - // Non-multicast addresses - assert!( - validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))) - .is_err() - ); - assert!( - validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))) - .is_err() - ); - } - - #[test] - fn test_validate_multicast_ip_v6() { - // Valid IPv6 multicast addresses - assert!( - validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( - 0xff0e, 0, 0, 0, 0, 0, 0, 1 - ))) - .is_ok() - ); // Global scope - assert!( - validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( - 0xff0d, 0, 0, 0, 0, 0, 0, 1 - ))) - .is_ok() - ); // Site-local scope - assert!( - validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( - 0xff05, 0, 0, 0, 0, 0, 0, 1 - ))) - .is_ok() - ); // Site-local admin scope - allowed - assert!( - validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( - 0xff08, 0, 0, 0, 0, 0, 0, 1 - ))) - .is_ok() - ); // Org-local admin scope - allowed - - // Invalid IPv6 multicast addresses - reserved ranges - assert!( - validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( - 0xff01, 0, 0, 0, 0, 0, 0, 1 - ))) - .is_err() - ); // Interface-local - assert!( - validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( - 0xff02, 0, 0, 0, 0, 0, 0, 1 - ))) - .is_err() - ); // Link-local - - // Admin-local (ff04::/16) is allowed for on-premises deployments. - // Collision avoidance is handled by the mapping function which sets - // a collision-avoidance bit to separate external and underlay spaces. - assert!( - validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( - 0xff04, 0, 0, 0, 0, 0, 0, 1 - ))) - .is_ok() - ); - - // Non-multicast addresses - assert!( - validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( - 0x2001, 0xdb8, 0, 0, 0, 0, 0, 1 - ))) - .is_err() - ); - } - - #[test] - fn test_validate_source_ip_v4() { - // Valid IPv4 source addresses - assert!( - validate_source_ip(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))) - .is_ok() - ); - assert!( - validate_source_ip(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))).is_ok() - ); - assert!( - validate_source_ip(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 1))) - .is_ok() - ); // TEST-NET-3 - - // Invalid IPv4 source addresses - assert!( - validate_source_ip(IpAddr::V4(Ipv4Addr::new(224, 1, 1, 1))) - .is_err() - ); // Multicast - assert!( - validate_source_ip(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))).is_err() - ); // Unspecified - assert!( - validate_source_ip(IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255))) - .is_err() - ); // Broadcast - assert!( - validate_source_ip(IpAddr::V4(Ipv4Addr::new(169, 254, 1, 1))) - .is_err() - ); // Link-local - } - - #[test] - fn test_validate_source_ip_v6() { - // Valid IPv6 source addresses - assert!( - validate_source_ip(IpAddr::V6(Ipv6Addr::new( - 0x2001, 0xdb8, 0, 0, 0, 0, 0, 1 - ))) - .is_ok() - ); - assert!( - validate_source_ip(IpAddr::V6(Ipv6Addr::new( - 0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888 - ))) - .is_ok() - ); - - // Invalid IPv6 source addresses - assert!( - validate_source_ip(IpAddr::V6(Ipv6Addr::new( - 0xff0e, 0, 0, 0, 0, 0, 0, 1 - ))) - .is_err() - ); // Multicast - assert!( - validate_source_ip(IpAddr::V6(Ipv6Addr::new( - 0, 0, 0, 0, 0, 0, 0, 0 - ))) - .is_err() - ); // Unspecified - assert!( - validate_source_ip(IpAddr::V6(Ipv6Addr::new( - 0, 0, 0, 0, 0, 0, 0, 1 - ))) - .is_err() - ); // Loopback - } - - #[test] - fn test_multicast_group_create_deserialization_with_all_fields() { - let json = r#"{ - "name": "test-group", - "description": "Test multicast group", - "multicast_ip": "224.1.2.3", - "source_ips": ["10.0.0.1", "10.0.0.2"], - "pool": "default", - "mvlan": 10 - }"#; - - let result: Result = - serde_json::from_str(json); - assert!(result.is_ok()); - let params = result.unwrap(); - assert_eq!(params.identity.name.as_str(), "test-group"); - assert_eq!( - params.multicast_ip, - Some(IpAddr::V4(Ipv4Addr::new(224, 1, 2, 3))) - ); - assert_eq!( - params.source_ips, - Some(vec![ - IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), - IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)) - ]) - ); - } - - #[test] - fn test_multicast_group_create_deserialization_without_optional_fields() { - // This is the critical test - multicast_ip, source_ips, pool, and mvlan are all optional - let json = r#"{ - "name": "test-group", - "description": "Test multicast group" - }"#; - - let result: Result = - serde_json::from_str(json); - assert!( - result.is_ok(), - "Failed to deserialize without optional fields: {:?}", - result.err() - ); - let params = result.unwrap(); - assert_eq!(params.identity.name.as_str(), "test-group"); - assert_eq!(params.multicast_ip, None); - assert_eq!(params.source_ips, None); - assert_eq!(params.pool, None); - assert_eq!(params.mvlan, None); - } - - #[test] - fn test_multicast_group_create_deserialization_with_empty_source_ips() { - let json = r#"{ - "name": "test-group", - "description": "Test multicast group", - "multicast_ip": "224.1.2.3", - "source_ips": [] - }"#; - - let result: Result = - serde_json::from_str(json); - assert!(result.is_ok()); - let params = result.unwrap(); - assert_eq!(params.source_ips, Some(vec![])); - } - - #[test] - fn test_multicast_group_create_deserialization_invalid_multicast_ip() { - // Non-multicast IP should be rejected - let json = r#"{ - "name": "test-group", - "description": "Test multicast group", - "multicast_ip": "192.168.1.1" - }"#; - - let result: Result = - serde_json::from_str(json); - assert!(result.is_err()); - } - - #[test] - fn test_multicast_group_create_deserialization_invalid_source_ip() { - // Multicast address in source_ips should be rejected - let json = r#"{ - "name": "test-group", - "description": "Test multicast group", - "multicast_ip": "224.1.2.3", - "source_ips": ["224.0.0.1"] - }"#; - - let result: Result = - serde_json::from_str(json); - assert!(result.is_err()); - } - - #[test] - fn test_multicast_group_create_deserialization_only_multicast_ip() { - // Test with only multicast_ip, no source_ips - let json = r#"{ - "name": "test-group", - "description": "Test multicast group", - "multicast_ip": "224.1.2.3" - }"#; - - let result: Result = - serde_json::from_str(json); - assert!(result.is_ok()); - let params = result.unwrap(); - assert_eq!( - params.multicast_ip, - Some(IpAddr::V4(Ipv4Addr::new(224, 1, 2, 3))) - ); - assert_eq!(params.source_ips, None); - } - - #[test] - fn test_multicast_group_create_deserialization_only_source_ips() { - // Test with only source_ips, no multicast_ip (will be auto-allocated) - let json = r#"{ - "name": "test-group", - "description": "Test multicast group", - "source_ips": ["10.0.0.1"] - }"#; - - let result: Result = - serde_json::from_str(json); - assert!(result.is_ok()); - let params = result.unwrap(); - assert_eq!(params.multicast_ip, None); - assert_eq!( - params.source_ips, - Some(vec![IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))]) - ); - } - - #[test] - fn test_multicast_group_create_deserialization_explicit_null_fields() { - // Test with explicit null values for optional fields - // This is what the CLI sends when fields are not provided - let json = r#"{ - "name": "test-group", - "description": "Test multicast group", - "multicast_ip": null, - "source_ips": null, - "pool": null, - "mvlan": null - }"#; - - let result: Result = - serde_json::from_str(json); - assert!( - result.is_ok(), - "Failed to deserialize with explicit null fields: {:?}", - result.err() - ); - let params = result.unwrap(); - assert_eq!(params.multicast_ip, None); - assert_eq!(params.source_ips, None); - assert_eq!(params.pool, None); - assert_eq!(params.mvlan, None); - } - - #[test] - fn test_multicast_group_create_deserialization_mixed_null_and_values() { - // Test with some nulls and some values - let json = r#"{ - "name": "test-group", - "description": "Test multicast group", - "multicast_ip": "224.1.2.3", - "source_ips": [], - "pool": null, - "mvlan": 30 - }"#; - - let result: Result = - serde_json::from_str(json); - assert!(result.is_ok()); - let params = result.unwrap(); - assert_eq!( - params.multicast_ip, - Some(IpAddr::V4(Ipv4Addr::new(224, 1, 2, 3))) - ); - assert_eq!(params.source_ips, Some(vec![])); - assert_eq!(params.pool, None); - assert_eq!(params.mvlan, Some(VlanID::new(30).unwrap())); - } - - #[test] - fn test_multicast_group_update_deserialization_omit_all_fields() { - // When fields are omitted, they should be None (no change) - let json = r#"{ - "name": "test-group" - }"#; - - let result: Result = - serde_json::from_str(json); - assert!( - result.is_ok(), - "Failed to deserialize update with omitted fields: {:?}", - result.err() - ); - let params = result.unwrap(); - assert_eq!(params.source_ips, None); - assert_eq!(params.mvlan, None); - } - - #[test] - fn test_multicast_group_update_deserialization_explicit_null_mvlan() { - // When mvlan is explicitly null, it should be Some(Nullable(None)) (clearing the field) - let json = r#"{ - "name": "test-group", - "mvlan": null - }"#; - - let result: Result = - serde_json::from_str(json); - assert!( - result.is_ok(), - "Failed to deserialize update with null mvlan: {:?}", - result.err() - ); - let params = result.unwrap(); - assert_eq!(params.mvlan, Some(Nullable(None))); - } - - #[test] - fn test_multicast_group_update_deserialization_set_mvlan() { - // When mvlan has a value, it should be Some(Nullable(Some(value))) - let json = r#"{ - "name": "test-group", - "mvlan": 100 - }"#; - - let result: Result = - serde_json::from_str(json); - assert!(result.is_ok()); - let params = result.unwrap(); - assert_eq!( - params.mvlan, - Some(Nullable(Some(VlanID::new(100).unwrap()))) - ); - } - - #[test] - fn test_multicast_group_update_deserialization_update_source_ips() { - // Test updating source_ips - let json = r#"{ - "name": "test-group", - "source_ips": ["10.0.0.5", "10.0.0.6"] - }"#; - - let result: Result = - serde_json::from_str(json); - assert!(result.is_ok()); - let params = result.unwrap(); - assert_eq!( - params.source_ips, - Some(vec![ - IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5)), - IpAddr::V4(Ipv4Addr::new(10, 0, 0, 6)) - ]) - ); - } - - #[test] - fn test_multicast_group_update_deserialization_clear_source_ips() { - // Empty array should clear source_ips (Any-Source Multicast) - let json = r#"{ - "name": "test-group", - "source_ips": [] - }"#; - - let result: Result = - serde_json::from_str(json); - assert!(result.is_ok()); - let params = result.unwrap(); - assert_eq!(params.source_ips, Some(vec![])); - } - - #[test] - fn test_multicast_group_update_deserialization_invalid_mvlan() { - // VLAN ID 1 should be rejected (reserved) - let json = r#"{ - "name": "test-group", - "mvlan": 1 - }"#; - - let result: Result = - serde_json::from_str(json); - assert!(result.is_err(), "Should reject reserved VLAN ID 1"); - } - - #[test] - fn test_address_allocator_explicit_requires_ip() { - // Explicit requires an IP address - pool-only is not valid - let json = r#"{"type": "explicit", "pool": "my-pool"}"#; - let result: Result = serde_json::from_str(json); - assert!(result.is_err(), "explicit without ip should fail"); - } - - #[test] - fn test_address_allocator_explicit_with_ip() { - let json = r#"{"type": "explicit", "ip": "10.0.0.1"}"#; - let result: Result = serde_json::from_str(json); - assert!(result.is_ok(), "explicit with ip should be valid"); - match result.unwrap() { - AddressAllocator::Explicit { ip } => { - assert_eq!(ip, "10.0.0.1".parse::().unwrap()); - } - _ => panic!("Expected Explicit variant"), - } - } - - #[test] - fn test_address_allocator_explicit_missing_ip() { - let json = r#"{"type": "explicit"}"#; - let result: Result = serde_json::from_str(json); - assert!(result.is_err(), "explicit without ip should fail"); - } - - #[test] - fn test_address_allocator_auto_with_explicit_pool() { - // To allocate from a specific pool, use Auto with explicit pool_selector - let json = r#"{"type": "auto", "pool_selector": {"type": "explicit", "pool": "my-pool"}}"#; - let result: Result = serde_json::from_str(json); - assert!( - result.is_ok(), - "auto with explicit pool_selector should be valid" - ); - match result.unwrap() { - AddressAllocator::Auto { pool_selector } => { - assert!(matches!(pool_selector, PoolSelector::Explicit { .. })); - } - _ => panic!("Expected Auto variant"), - } - } - - #[test] - fn test_address_allocator_auto_with_auto_pool() { - // Auto-allocate from default pool - let json = r#"{"type": "auto", "pool_selector": {"type": "auto"}}"#; - let result: Result = serde_json::from_str(json); - assert!(result.is_ok(), "auto with auto pool_selector should be valid"); - } - - #[test] - fn test_address_allocator_auto_default() { - // Default pool_selector when omitted - let json = r#"{"type": "auto"}"#; - let result: Result = serde_json::from_str(json); - assert!( - result.is_ok(), - "auto without pool_selector should use default" - ); - } -} diff --git a/nexus/types/src/external_api/path_params.rs b/nexus/types/src/external_api/path_params.rs new file mode 100644 index 00000000000..915e1fbf081 --- /dev/null +++ b/nexus/types/src/external_api/path_params.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Path parameter types. + +pub use nexus_types_versions::latest::path_params::*; diff --git a/nexus/types/src/external_api/physical_disk.rs b/nexus/types/src/external_api/physical_disk.rs new file mode 100644 index 00000000000..61007b6cde0 --- /dev/null +++ b/nexus/types/src/external_api/physical_disk.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Physical disk types. + +pub use nexus_types_versions::latest::physical_disk::*; diff --git a/nexus/types/src/external_api/policy.rs b/nexus/types/src/external_api/policy.rs new file mode 100644 index 00000000000..4213d9e1225 --- /dev/null +++ b/nexus/types/src/external_api/policy.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Policy and role types. + +pub use nexus_types_versions::latest::policy::*; diff --git a/nexus/types/src/external_api/probe.rs b/nexus/types/src/external_api/probe.rs new file mode 100644 index 00000000000..8134306fbac --- /dev/null +++ b/nexus/types/src/external_api/probe.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Probe types. + +pub use nexus_types_versions::latest::probe::*; diff --git a/nexus/types/src/external_api/project.rs b/nexus/types/src/external_api/project.rs new file mode 100644 index 00000000000..b3f5778d831 --- /dev/null +++ b/nexus/types/src/external_api/project.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Project types. + +pub use nexus_types_versions::latest::project::*; diff --git a/nexus/types/src/external_api/rack.rs b/nexus/types/src/external_api/rack.rs new file mode 100644 index 00000000000..c59a6020e27 --- /dev/null +++ b/nexus/types/src/external_api/rack.rs @@ -0,0 +1,51 @@ +// 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/. + +//! Rack types. + +pub use nexus_types_versions::latest::rack::*; + +use crate::trust_quorum::{TrustQuorumConfig, TrustQuorumMemberState}; +use omicron_uuid_kinds::GenericUuid; + +impl From for RackMembershipStatus { + fn from(value: TrustQuorumConfig) -> Self { + // `Unacked` means that a member has not received and acked a `Prepare` + // yet. `Prepared` means that a member has acknowledged the prepare but + // not the commit. `Committed` is when the member starts participating + // in the new group. + // + // Since we don't want to expose trust quorum specific knowledge to + // the operator, and they really only want to know when the membership + // change has started to take effect, we say that any member that hasn't + // yet committed is unacknowledged. + let unacknowledged_members = value + .members + .iter() + .filter_map(|(id, data)| match data.state { + TrustQuorumMemberState::Unacked + | TrustQuorumMemberState::Prepared => Some(id.clone()), + TrustQuorumMemberState::Committed => None, + }) + .collect(); + let state = if value.state.is_committed() { + RackMembershipChangeState::Committed + } else if value.state.is_aborted() { + RackMembershipChangeState::Aborted + } else { + RackMembershipChangeState::InProgress + }; + + Self { + rack_id: value.rack_id.into_untyped_uuid(), + version: RackMembershipVersion(value.epoch.0), + state, + members: value.members.keys().cloned().collect(), + unacknowledged_members, + time_created: value.time_created, + time_committed: value.time_committed, + time_aborted: value.time_aborted, + } + } +} diff --git a/nexus/types/src/external_api/saml.rs b/nexus/types/src/external_api/saml.rs new file mode 100644 index 00000000000..e982831a7f9 --- /dev/null +++ b/nexus/types/src/external_api/saml.rs @@ -0,0 +1,7 @@ +// 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/. + +//! SAML types. + +pub use nexus_types_versions::latest::saml::*; diff --git a/nexus/types/src/external_api/scim.rs b/nexus/types/src/external_api/scim.rs new file mode 100644 index 00000000000..510aa61a8c1 --- /dev/null +++ b/nexus/types/src/external_api/scim.rs @@ -0,0 +1,7 @@ +// 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/. + +//! SCIM types. + +pub use nexus_types_versions::latest::scim::*; diff --git a/nexus/types/src/external_api/shared.rs b/nexus/types/src/external_api/shared.rs deleted file mode 100644 index fc79ac2e836..00000000000 --- a/nexus/types/src/external_api/shared.rs +++ /dev/null @@ -1,775 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Types that are used as both views and params - -use std::net::IpAddr; - -use super::params::RelativeUri; -use anyhow::Context; -use chrono::DateTime; -use chrono::Utc; -use omicron_common::api::external::Name; -use omicron_common::api::internal::shared::NetworkInterface; -use omicron_uuid_kinds::GenericUuid; -use omicron_uuid_kinds::SiloGroupUuid; -use omicron_uuid_kinds::SiloUserUuid; -use omicron_uuid_kinds::SledUuid; -use omicron_uuid_kinds::SupportBundleUuid; -use parse_display::FromStr; -use schemars::JsonSchema; -use serde::Deserialize; -use serde::Deserializer; -use serde::Serialize; -use serde::de::Error as _; -use sled_hardware_types::BaseboardId; -use slog_error_chain::InlineErrorChain; -use strum::EnumIter; -use uuid::Uuid; - -pub use omicron_common::address::{IpRange, IpVersion, Ipv4Range, Ipv6Range}; -pub use omicron_common::api::external::BfdMode; - -/// Maximum number of role assignments allowed on any one resource -// Today's implementation assumes a relatively small number of role assignments -// per resource. Things should work if we bump this up, but we'll want to look -// into scalability improvements (e.g., use pagination for fetching and updating -// the role assignments, and consider the impact on authz checks as well). -// -// Most importantly: by keeping this low to start with, it's impossible for -// customers to develop a dependency on a huge number of role assignments. That -// maximizes our flexibility in the future. -// -// TODO This should be runtime-configurable. But it doesn't belong in the Nexus -// configuration file, since it's a constraint on database objects more than it -// is Nexus. We should have some kinds of config that lives in the database. -pub const MAX_ROLE_ASSIGNMENTS_PER_RESOURCE: usize = 64; - -/// Policy for a particular resource -/// -/// Note that the Policy only describes access granted explicitly for this -/// resource. The policies of parent resources can also cause a user to have -/// access to this resource. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] -#[schemars(rename = "{AllowedRoles}Policy")] -pub struct Policy { - /// Roles directly assigned on this resource - #[serde(deserialize_with = "role_assignments_deserialize")] - pub role_assignments: Vec>, -} - -fn role_assignments_deserialize<'de, D, R>( - d: D, -) -> Result>, D::Error> -where - D: Deserializer<'de>, - R: serde::de::DeserializeOwned, -{ - let v = Vec::<_>::deserialize(d)?; - if v.len() > MAX_ROLE_ASSIGNMENTS_PER_RESOURCE { - return Err(D::Error::invalid_length( - v.len(), - &format!( - "a list of at most {} role assignments", - MAX_ROLE_ASSIGNMENTS_PER_RESOURCE - ) - .as_str(), - )); - } - Ok(v) -} - -/// Describes the assignment of a particular role on a particular resource to a -/// particular identity (user, group, etc.) -/// -/// The resource is not part of this structure. Rather, `RoleAssignment`s are -/// put into a `Policy` and that Policy is applied to a particular resource. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] -#[schemars(rename = "{AllowedRoles}RoleAssignment")] -pub struct RoleAssignment { - pub identity_type: IdentityType, - pub identity_id: Uuid, - pub role_name: AllowedRoles, -} - -impl RoleAssignment { - pub fn for_silo_user( - silo_user_id: SiloUserUuid, - role_name: AllowedRoles, - ) -> Self { - Self { - identity_type: IdentityType::SiloUser, - identity_id: silo_user_id.into_untyped_uuid(), - role_name, - } - } - - pub fn for_silo_group( - silo_group_id: SiloGroupUuid, - role_name: AllowedRoles, - ) -> Self { - Self { - identity_type: IdentityType::SiloGroup, - identity_id: silo_group_id.into_untyped_uuid(), - role_name, - } - } -} - -#[derive( - Clone, - Copy, - Debug, - Deserialize, - EnumIter, - Eq, - Ord, - PartialEq, - PartialOrd, - Serialize, - JsonSchema, -)] -#[serde(rename_all = "snake_case")] -pub enum FleetRole { - Admin, - Collaborator, - Viewer, - // There are other Fleet roles, but they are not externally-visible and so - // they do not show up in this enum. -} - -#[derive( - Clone, - Copy, - Debug, - Deserialize, - EnumIter, - Eq, - FromStr, - Ord, - PartialOrd, - PartialEq, - Serialize, - JsonSchema, -)] -#[serde(rename_all = "snake_case")] -pub enum SiloRole { - Admin, - Collaborator, - LimitedCollaborator, - Viewer, -} - -#[derive( - Clone, - Copy, - Debug, - Deserialize, - EnumIter, - Eq, - FromStr, - PartialEq, - Serialize, - JsonSchema, -)] -#[serde(rename_all = "snake_case")] -pub enum ProjectRole { - Admin, - Collaborator, - LimitedCollaborator, - Viewer, -} - -/// Describes what kind of identity is described by an id -// This is a subset of the identity types that might be found in the database -// because we do not expose some (e.g., built-in users) externally. -#[derive( - Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema, -)] -#[serde(rename_all = "snake_case")] -pub enum IdentityType { - SiloUser, - SiloGroup, -} - -/// Describes how identities are managed and users are authenticated in this -/// Silo -#[derive( - Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema, -)] -#[serde(rename_all = "snake_case")] -pub enum SiloIdentityMode { - /// Users are authenticated with SAML using an external authentication - /// provider. The system updates information about users and groups only - /// during successful authentication (i.e,. "JIT provisioning" of users and - /// groups). - SamlJit, - - /// The system is the source of truth about users. There is no linkage to - /// an external authentication provider or identity provider. - // NOTE: authentication for these users is not supported yet at all. It - // will eventually be password-based. - LocalOnly, - - /// Users are authenticated with SAML using an external authentication - /// provider. Users and groups are managed with SCIM API calls, likely from - /// the same authentication provider. - SamlScim, -} - -impl SiloIdentityMode { - pub fn authentication_mode(&self) -> AuthenticationMode { - match self { - SiloIdentityMode::LocalOnly => AuthenticationMode::Local, - SiloIdentityMode::SamlJit => AuthenticationMode::Saml, - SiloIdentityMode::SamlScim => AuthenticationMode::Saml, - } - } - - pub fn user_provision_type(&self) -> UserProvisionType { - match self { - SiloIdentityMode::LocalOnly => UserProvisionType::ApiOnly, - SiloIdentityMode::SamlJit => UserProvisionType::Jit, - SiloIdentityMode::SamlScim => UserProvisionType::Scim, - } - } -} - -/// How users are authenticated in this Silo -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum AuthenticationMode { - /// Authentication is via SAML using an external authentication provider - Saml, - - /// Authentication is local to the Oxide system - Local, -} - -/// How users will be provisioned in a silo during authentication. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum UserProvisionType { - /// Identities are managed directly by explicit calls to the external API. - /// They are not synchronized from any external identity provider nor - /// automatically created or updated when a user logs in. - ApiOnly, - - /// Users and groups are created or updated during authentication using - /// information provided by the authentication provider - Jit, - - /// Users and groups are managed by SCIM - Scim, -} - -/// The service intended to use this certificate. -#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ServiceUsingCertificate { - /// This certificate is intended for access to the external API. - ExternalApi, -} - -/// The kind of an external IP address for an instance -#[derive( - Debug, Clone, Copy, Deserialize, Eq, Serialize, JsonSchema, PartialEq, -)] -#[serde(rename_all = "snake_case")] -pub enum IpKind { - SNat, - Ephemeral, - Floating, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum UpdateableComponentType { - BootloaderForRot, - BootloaderForSp, - BootloaderForHostProc, - HubrisForPscRot, - HubrisForPscSp, - HubrisForSidecarRot, - HubrisForSidecarSp, - HubrisForGimletRot, - HubrisForGimletSp, - HeliosHostPhase1, - HeliosHostPhase2, - HostOmicron, -} - -/// Wrapper type for TUF root roles to prevent misuse. -/// -/// The format of TUF root roles is an implementation detail and Nexus should -/// generally treat them as opaque JSON blobs without inspecting any fields -/// or, especially, adding any data to them. This value should be created only -/// through Serde deserialization. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)] -#[serde(transparent)] -pub struct TufSignedRootRole(serde_json::Value); - -impl TufSignedRootRole { - pub fn to_bytes(&self) -> Vec { - self.0.to_string().into_bytes() - } -} - -// We'd like to use `#[serde(try_from = ..)]` here but it conflicts with -// `#[serde(transparent)]`, which we're using for the Serialize derive. -impl<'de> Deserialize<'de> for TufSignedRootRole { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - use tough::schema::{Root, Signed}; - - let value = serde_json::Value::deserialize(deserializer)?; - // Verify that this appears to be a valid, self-signed TUF root role. - let root = - >::deserialize(&value).map_err(D::Error::custom)?; - match root.signed.verify_role(&root) { - Ok(()) => Ok(Self(value)), - Err(err) => Err(D::Error::custom(format!( - "Unable to verify root role: {}", - InlineErrorChain::new(&err) - ))), - } - } -} - -/// Properties that uniquely identify an Oxide hardware component -#[derive( - Clone, - Debug, - Serialize, - Deserialize, - JsonSchema, - PartialOrd, - Ord, - PartialEq, - Eq, -)] -pub struct Baseboard { - pub serial: String, - pub part: String, - pub revision: u32, -} - -impl From for BaseboardId { - fn from(value: crate::external_api::shared::Baseboard) -> Self { - BaseboardId { part_number: value.part, serial_number: value.serial } - } -} - -/// A sled that has not been added to an initialized rack yet -#[derive( - Clone, - Debug, - Serialize, - Deserialize, - JsonSchema, - PartialOrd, - Ord, - PartialEq, - Eq, -)] -pub struct UninitializedSled { - pub baseboard: Baseboard, - pub rack_id: Uuid, - pub cubby: u16, -} - -#[derive( - Clone, - Debug, - Serialize, - Deserialize, - JsonSchema, - PartialOrd, - Ord, - PartialEq, - Eq, -)] -#[serde(rename_all = "snake_case")] -pub enum BfdState { - /// A stable down state. Non-responsive to incoming messages. - AdminDown = 0, - - /// The initial state. - Down = 1, - - /// The peer has detected a remote peer in the down state. - Init = 2, - - /// The peer has detected a remote peer in the up or init state while in the - /// init state. - Up = 3, -} - -#[derive( - Clone, - Debug, - Serialize, - Deserialize, - JsonSchema, - PartialOrd, - Ord, - PartialEq, - Eq, -)] -pub struct BfdStatus { - pub peer: IpAddr, - pub state: BfdState, - pub switch: Name, - pub local: Option, - pub detection_threshold: u8, - pub required_rx: u64, - pub mode: BfdMode, -} - -/// Opaque object representing link state. -/// -/// The contents of this object are not yet stable. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SwitchLinkState { - link: serde_json::Value, - monitors: Option, -} - -impl SwitchLinkState { - pub fn new( - link: serde_json::Value, - monitors: Option, - ) -> Self { - Self { link, monitors } - } -} - -impl JsonSchema for SwitchLinkState { - fn json_schema( - r#gen: &mut schemars::r#gen::SchemaGenerator, - ) -> schemars::schema::Schema { - let obj = schemars::schema::Schema::Object( - schemars::schema::SchemaObject::default(), - ); - r#gen.definitions_mut().insert(Self::schema_name(), obj.clone()); - obj - } - - fn schema_name() -> String { - "SwitchLinkState".to_owned() - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(try_from = "String")] -#[serde(into = "String")] -pub struct AlertSubscription(String); - -impl AlertSubscription { - const PATTERN: &str = - r"^([a-zA-Z0-9_]+|\*|\*\*)(\.([a-zA-Z0-9_]+|\*|\*\*))*$"; - - fn is_valid(s: &str) -> Result<(), anyhow::Error> { - static REGEX: std::sync::LazyLock = - std::sync::LazyLock::new(|| { - regex::Regex::new(AlertSubscription::PATTERN).expect( - "AlertSubscription validation regex should be valid", - ) - }); - if REGEX.is_match(s) { - Ok(()) - } else { - Err(anyhow::anyhow!( - "alert subscription {s:?} does not match the pattern {}", - AlertSubscription::PATTERN - )) - } - } - - #[inline] - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl TryFrom for AlertSubscription { - type Error = anyhow::Error; - fn try_from(s: String) -> Result { - Self::is_valid(&s)?; - Ok(Self(s)) - } -} - -impl std::str::FromStr for AlertSubscription { - type Err = anyhow::Error; - fn from_str(s: &str) -> Result { - Self::is_valid(s)?; - Ok(Self(s.to_string())) - } -} - -impl From for String { - fn from(AlertSubscription(s): AlertSubscription) -> Self { - s - } -} - -impl AsRef for AlertSubscription { - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl JsonSchema for AlertSubscription { - fn schema_name() -> String { - "AlertSubscription".to_string() - } - - fn json_schema( - _: &mut schemars::r#gen::SchemaGenerator, - ) -> schemars::schema::Schema { - schemars::schema::SchemaObject { - metadata: Some(Box::new(schemars::schema::Metadata { - title: Some("A webhook event class subscription".to_string()), - description: Some( - "A webhook event class subscription matches either a single event class exactly, or a glob pattern including wildcards that may match multiple event classes" - .to_string(), - ), - ..Default::default() - })), - instance_type: Some(schemars::schema::InstanceType::String.into()), - string: Some(Box::new(schemars::schema::StringValidation { - max_length: None, - min_length: None, - pattern: Some(AlertSubscription::PATTERN.to_string()), - })), - ..Default::default() - } - .into() - } -} - -impl std::fmt::Display for AlertSubscription { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(self.as_str()) - } -} - -#[cfg(test)] -mod test { - use super::AlertSubscription; - use super::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE; - use super::Policy; - use serde::Deserialize; - - #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] - #[serde(rename_all = "kebab-case")] - pub enum DummyRoles { - Bogus, - } - - #[test] - fn test_policy_parsing() { - // Success case (edge case: max number of role assignments) - let role_assignment = serde_json::json!({ - "identity_type": "silo_user", - "identity_id": "75ec4a39-67cf-4549-9e74-44b92947c37c", - "role_name": "bogus" - }); - const MAX: usize = MAX_ROLE_ASSIGNMENTS_PER_RESOURCE; - let okay_input = - serde_json::Value::Array(vec![role_assignment.clone(); MAX]); - let policy: Policy = - serde_json::from_value(serde_json::json!({ - "role_assignments": okay_input - })) - .expect("unexpectedly failed with okay input"); - assert_eq!(policy.role_assignments[0].role_name, DummyRoles::Bogus); - - // Failure case: too many role assignments - let bad_input = - serde_json::Value::Array(vec![role_assignment; MAX + 1]); - let error = - serde_json::from_value::>(serde_json::json!({ - "role_assignments": bad_input - })) - .expect_err("unexpectedly succeeded with too many items"); - assert_eq!( - error.to_string(), - "invalid length 65, expected a list of at most 64 role assignments" - ); - } - - #[test] - fn test_webhook_subscription_validation() { - let successes = [ - "foo.bar", - "foo.bar.baz", - "foo_bar.baz", - "foo_1.bar_200.**", - "1.2.3", - "foo.**", - "foo.*.baz", - "foo.**.baz", - "foo.bar.**", - "foo.*.baz.**", - "**.foo", - "**.foo.bar.*", - "**.foo.bar.*.baz", - "*.foo.bar.*", - "*.foo", - "*", - "*.*", - ]; - let failures = [ - "", - "f*o.bar", - "**foo.bar", - "foo.**bar", - "foo.*bar*", - "*foo*", - "f**.bar", - "foo.***", - "***", - "$.foo.bar", - "foo.%bar", - "foo.[barbaz]", - ]; - for s in successes { - match s.parse::() { - Ok(_) => {} - Err(e) => panic!( - "expected string {s:?} to be a valid webhook subscription: {e}" - ), - } - } - - for s in failures { - match s.parse::() { - Ok(_) => panic!( - "expected string {s:?} to NOT be a valid webhook subscription" - ), - Err(_) => {} - } - } - } -} - -#[derive( - Debug, Clone, Copy, JsonSchema, Serialize, Deserialize, Eq, PartialEq, -)] -#[serde(rename_all = "snake_case")] -pub enum SupportBundleState { - /// Support Bundle still actively being collected. - /// - /// This is the initial state for a Support Bundle, and it will - /// automatically transition to either "Failing" or "Active". - /// - /// If a user no longer wants to access a Support Bundle, they can - /// request cancellation, which will transition to the "Destroying" state. - Collecting, - - /// Support Bundle is being destroyed. - /// - /// Once backing storage has been freed, this bundle is destroyed. - Destroying, - - /// Support Bundle was not created successfully, or was created and has lost - /// backing storage. - /// - /// The record of the bundle still exists for readability, but the only - /// valid operation on these bundles is to destroy them. - Failed, - - /// Support Bundle has been processed, and is ready for usage. - Active, -} - -#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] -pub struct SupportBundleInfo { - #[schemars(with = "Uuid")] - pub id: SupportBundleUuid, - pub time_created: DateTime, - pub reason_for_creation: String, - pub reason_for_failure: Option, - pub user_comment: Option, - pub state: SupportBundleState, -} - -#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] -pub struct ProbeInfo { - pub id: Uuid, - pub name: Name, - #[schemars(with = "Uuid")] - pub sled: SledUuid, - 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: ProbeExternalIpKind, -} - -#[derive(Debug, Clone, Copy, JsonSchema, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum ProbeExternalIpKind { - Snat, - Floating, - Ephemeral, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct RelayState { - pub redirect_uri: Option, -} - -impl RelayState { - pub fn to_encoded(&self) -> Result { - Ok(base64::Engine::encode( - &base64::engine::general_purpose::STANDARD, - serde_json::to_string(&self).context("encoding relay state")?, - )) - } - - pub fn from_encoded(encoded: String) -> Result { - serde_json::from_str( - &String::from_utf8( - base64::Engine::decode( - &base64::engine::general_purpose::STANDARD, - encoded, - ) - .context("base64 decoding relay state")?, - ) - .context("creating relay state string")?, - ) - .context("json from relay state string") - } -} - -/// Type of IP pool. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -#[serde(rename_all = "snake_case")] -#[derive(Default)] -pub enum IpPoolType { - /// Unicast IP pool for standard IP allocations. - #[default] - Unicast, - /// Multicast IP pool for multicast group allocations. - /// - /// All ranges in a multicast pool must be either ASM or SSM (not mixed). - Multicast, -} - -/// A unique, monotonically increasing number representing the set of active -/// sleds in a rack at a given point in time. -// -// Maps to `trust_quorum_types::types::Epoch` under the hood -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct RackMembershipVersion(pub u64); diff --git a/nexus/types/src/external_api/silo.rs b/nexus/types/src/external_api/silo.rs new file mode 100644 index 00000000000..15f069f9287 --- /dev/null +++ b/nexus/types/src/external_api/silo.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Silo types. + +pub use nexus_types_versions::latest::silo::*; diff --git a/nexus/types/src/external_api/sled.rs b/nexus/types/src/external_api/sled.rs new file mode 100644 index 00000000000..a93ff4e4957 --- /dev/null +++ b/nexus/types/src/external_api/sled.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Sled types. + +pub use nexus_types_versions::latest::sled::*; diff --git a/nexus/types/src/external_api/snapshot.rs b/nexus/types/src/external_api/snapshot.rs new file mode 100644 index 00000000000..e78a4c70f4f --- /dev/null +++ b/nexus/types/src/external_api/snapshot.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Snapshot types. + +pub use nexus_types_versions::latest::snapshot::*; diff --git a/nexus/types/src/external_api/ssh_key.rs b/nexus/types/src/external_api/ssh_key.rs new file mode 100644 index 00000000000..fc4580bd1ab --- /dev/null +++ b/nexus/types/src/external_api/ssh_key.rs @@ -0,0 +1,7 @@ +// 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/. + +//! SSH key types. + +pub use nexus_types_versions::latest::ssh_key::*; diff --git a/nexus/types/src/external_api/subnet_pool.rs b/nexus/types/src/external_api/subnet_pool.rs new file mode 100644 index 00000000000..b055c9234ce --- /dev/null +++ b/nexus/types/src/external_api/subnet_pool.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Subnet pool types. + +pub use nexus_types_versions::latest::subnet_pool::*; diff --git a/nexus/types/src/external_api/support_bundle.rs b/nexus/types/src/external_api/support_bundle.rs new file mode 100644 index 00000000000..1ec9d2de73a --- /dev/null +++ b/nexus/types/src/external_api/support_bundle.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Support bundle types. + +pub use nexus_types_versions::latest::support_bundle::*; diff --git a/nexus/types/src/external_api/switch.rs b/nexus/types/src/external_api/switch.rs new file mode 100644 index 00000000000..7707f28d1f6 --- /dev/null +++ b/nexus/types/src/external_api/switch.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Switch types. + +pub use nexus_types_versions::latest::switch::*; diff --git a/nexus/types/src/external_api/system.rs b/nexus/types/src/external_api/system.rs new file mode 100644 index 00000000000..1556f243977 --- /dev/null +++ b/nexus/types/src/external_api/system.rs @@ -0,0 +1,8 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! System types. + +pub use nexus_types_versions::latest::system::*; +pub use omicron_common::address::IpVersion; diff --git a/nexus/types/src/external_api/timeseries.rs b/nexus/types/src/external_api/timeseries.rs new file mode 100644 index 00000000000..8e70084ad3e --- /dev/null +++ b/nexus/types/src/external_api/timeseries.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Timeseries types. + +pub use nexus_types_versions::latest::timeseries::*; diff --git a/nexus/types/src/external_api/update.rs b/nexus/types/src/external_api/update.rs new file mode 100644 index 00000000000..e56661465f0 --- /dev/null +++ b/nexus/types/src/external_api/update.rs @@ -0,0 +1,7 @@ +// 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/. + +//! Update types. + +pub use nexus_types_versions::latest::update::*; diff --git a/nexus/types/src/external_api/user.rs b/nexus/types/src/external_api/user.rs new file mode 100644 index 00000000000..d293e7b3e31 --- /dev/null +++ b/nexus/types/src/external_api/user.rs @@ -0,0 +1,7 @@ +// 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/. + +//! User types. + +pub use nexus_types_versions::latest::user::*; diff --git a/nexus/types/src/external_api/views.rs b/nexus/types/src/external_api/views.rs deleted file mode 100644 index d1ad3f942c8..00000000000 --- a/nexus/types/src/external_api/views.rs +++ /dev/null @@ -1,2056 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -//! Views are response bodies, most of which are public lenses onto DB models. - -use crate::external_api::shared::{ - self, Baseboard, IpKind, IpRange, RackMembershipVersion, - ServiceUsingCertificate, TufSignedRootRole, -}; -use crate::identity::AssetIdentityMetadata; -use crate::trust_quorum::{TrustQuorumConfig, TrustQuorumMemberState}; -use api_identity::ObjectIdentity; -use chrono::DateTime; -use chrono::Utc; -use daft::Diffable; -pub use omicron_common::api::external::IpVersion; -use omicron_common::api::external::{ - AffinityPolicy, AllowedSourceIps as ExternalAllowedSourceIps, ByteCount, - Digest, Error, FailureDomain, IdentityMetadata, InstanceState, Name, - Nullable, ObjectIdentity, SimpleIdentity, SimpleIdentityOrName, -}; -use omicron_common::vlan::VlanID; -use omicron_uuid_kinds::*; -use oxnet::{IpNet, Ipv4Net, Ipv6Net}; -use schemars::JsonSchema; -use semver::Version; -use serde::{Deserialize, Serialize}; -use sled_hardware_types::BaseboardId; -use std::collections::BTreeMap; -use std::collections::BTreeSet; -use std::fmt; -use std::net::IpAddr; -use std::sync::LazyLock; -use strum::{EnumIter, IntoEnumIterator}; -use tufaceous_artifact::ArtifactHash; -use url::Url; -use uuid::Uuid; - -use super::params::PhysicalDiskKind; - -// SILOS - -/// View of a Silo -/// -/// A Silo is the highest level unit of isolation. -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Silo { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// A silo where discoverable is false can be retrieved only by its id - it - /// will not be part of the "list all silos" output. - pub discoverable: bool, - - /// How users and groups are managed in this Silo - pub identity_mode: shared::SiloIdentityMode, - - /// Mapping of which Fleet roles are conferred by each Silo role - /// - /// The default is that no Fleet roles are conferred by any Silo roles - /// unless there's a corresponding entry in this map. - pub mapped_fleet_roles: - BTreeMap>, - - /// Optionally, silos can have a group name that is automatically granted - /// the silo admin role. - pub admin_group_name: Option, -} - -/// A collection of resource counts used to describe capacity and utilization -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema)] -pub struct VirtualResourceCounts { - /// Number of virtual CPUs - pub cpus: i64, - /// Amount of memory in bytes - pub memory: ByteCount, - /// Amount of disk storage in bytes - pub storage: ByteCount, -} - -/// A collection of resource counts used to set the virtual capacity of a silo -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SiloQuotas { - pub silo_id: Uuid, - #[serde(flatten)] - pub limits: VirtualResourceCounts, -} - -// For the eyes of end users -/// View of the current silo's resource utilization and capacity -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Utilization { - /// Accounts for resources allocated to running instances or - /// storage allocated via disks or snapshots. - /// - /// Note that CPU and memory resources associated with stopped - /// instances are not counted here, whereas associated disks will - /// still be counted. - pub provisioned: VirtualResourceCounts, - /// The total amount of resources that can be provisioned in this silo. - /// Actions that would exceed this limit will fail. - pub capacity: VirtualResourceCounts, -} - -// For the eyes of an operator -/// View of a silo's resource utilization and capacity -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SiloUtilization { - pub silo_id: Uuid, - pub silo_name: Name, - /// Accounts for the total resources allocated by the silo, - /// including CPU and memory for running instances and storage - /// for disks and snapshots. - /// - /// Note that CPU and memory resources associated with stopped - /// instances are not counted here. - pub provisioned: VirtualResourceCounts, - /// Accounts for the total amount of resources reserved for - /// silos via their quotas. - pub allocated: VirtualResourceCounts, -} - -// We want to be able to paginate SiloUtilization by NameOrId -// but we can't derive ObjectIdentity because this isn't a typical asset. -// Instead we implement this new simple identity trait which is used under the -// hood by the pagination code. -impl SimpleIdentityOrName for SiloUtilization { - fn id(&self) -> Uuid { - self.silo_id - } - fn name(&self) -> &Name { - &self.silo_name - } -} - -/// View of silo authentication settings -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SiloAuthSettings { - pub silo_id: Uuid, - /// Maximum lifetime of a device token in seconds. If set to null, users - /// will be able to create tokens that do not expire. - pub device_token_max_ttl_seconds: Option, -} - -// AFFINITY GROUPS - -/// View of an Affinity Group -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AffinityGroup { - #[serde(flatten)] - pub identity: IdentityMetadata, - pub project_id: Uuid, - pub policy: AffinityPolicy, - pub failure_domain: FailureDomain, -} - -/// View of an Anti-Affinity Group -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AntiAffinityGroup { - #[serde(flatten)] - pub identity: IdentityMetadata, - pub project_id: Uuid, - pub policy: AffinityPolicy, - pub failure_domain: FailureDomain, -} - -// IDENTITY PROVIDER - -#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum IdentityProviderType { - /// SAML identity provider - Saml, -} - -/// View of an Identity Provider -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct IdentityProvider { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// Identity provider type - pub provider_type: IdentityProviderType, -} - -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SamlIdentityProvider { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// IdP's entity id - pub idp_entity_id: String, - - /// SP's client id - pub sp_client_id: String, - - /// Service provider endpoint where the response will be sent - pub acs_url: String, - - /// Service provider endpoint where the idp should send log out requests - pub slo_url: String, - - /// Customer's technical contact for saml configuration - pub technical_contact_email: String, - - /// Optional request signing public certificate (base64 encoded der file) - pub public_cert: Option, - - /// If set, attributes with this name will be considered to denote a user's - /// group membership, where the values will be the group names. - pub group_attribute_name: Option, -} - -// PROJECTS - -/// View of a Project -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Project { - // TODO-correctness is flattening here (and in all the other types) the - // intent in RFD 4? - #[serde(flatten)] - pub identity: IdentityMetadata, - // Important: Silo ID does not get presented to user -} - -// CERTIFICATES - -/// View of a Certificate -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Certificate { - #[serde(flatten)] - pub identity: IdentityMetadata, - /// The service using this certificate - pub service: ServiceUsingCertificate, - /// PEM-formatted string containing public certificate chain - pub cert: String, -} - -// IMAGES - -/// View of an image -/// -/// If `project_id` is present then the image is only visible inside that -/// project. If it's not present then the image is visible to all projects in -/// the silo. -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Image { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// ID of the parent project if the image is a project image - pub project_id: Option, - - /// The family of the operating system like Debian, Ubuntu, etc. - pub os: String, - - /// Version of the operating system - pub version: String, - - /// Hash of the image contents, if applicable - pub digest: Option, - - /// Size of blocks in bytes - pub block_size: ByteCount, - - /// Total size in bytes - pub size: ByteCount, -} - -// SNAPSHOTS - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum SnapshotState { - Creating, - Ready, - Faulted, - Destroyed, -} - -/// View of a Snapshot -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Snapshot { - #[serde(flatten)] - pub identity: IdentityMetadata, - - pub project_id: Uuid, - pub disk_id: Uuid, - - pub state: SnapshotState, - - pub size: ByteCount, -} - -// VPCs - -/// View of a VPC -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Vpc { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// ID for the project containing this VPC - pub project_id: Uuid, - - /// ID for the system router where subnet default routes are registered - pub system_router_id: Uuid, - - /// The unique local IPv6 address range for subnets in this VPC - pub ipv6_prefix: Ipv6Net, - - // TODO-design should this be optional? - /// The name used for the VPC in DNS. - pub dns_name: Name, -} - -/// A VPC subnet represents a logical grouping for instances that -/// allows network traffic between them, within an IPv4 subnetwork -/// or optionally an IPv6 subnetwork. -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct VpcSubnet { - /// Common identifying metadata - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// The VPC to which the subnet belongs. - pub vpc_id: Uuid, - - /// The IPv4 subnet CIDR block. - pub ipv4_block: Ipv4Net, - - /// The IPv6 subnet CIDR block. - pub ipv6_block: Ipv6Net, - - /// ID for an attached custom router. - pub custom_router_id: Option, -} - -#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum VpcRouterKind { - System, - Custom, -} - -/// A VPC router defines a series of rules that indicate where traffic -/// should be sent depending on its destination. -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct VpcRouter { - /// Common identifying metadata - #[serde(flatten)] - pub identity: IdentityMetadata, - - pub kind: VpcRouterKind, - - /// The VPC to which the router belongs. - pub vpc_id: Uuid, -} - -// INTERNET GATEWAYS - -/// An internet gateway provides a path between VPC networks and external -/// networks. -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InternetGateway { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// The VPC to which the gateway belongs. - pub vpc_id: Uuid, -} - -/// An IP pool that is attached to an internet gateway -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InternetGatewayIpPool { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// The associated internet gateway. - pub internet_gateway_id: Uuid, - - /// The associated IP pool. - pub ip_pool_id: Uuid, -} - -/// An IP address that is attached to an internet gateway -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct InternetGatewayIpAddress { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// The associated internet gateway - pub internet_gateway_id: Uuid, - - /// The associated IP address - pub address: IpAddr, -} - -// IP POOLS - -/// A collection of IP ranges. If a pool is linked to a silo, IP addresses from -/// the pool can be allocated within that silo. -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct IpPool { - #[serde(flatten)] - pub identity: IdentityMetadata, - /// The IP version for the pool. - pub ip_version: IpVersion, - /// Type of IP pool (unicast or multicast). - pub pool_type: shared::IpPoolType, -} - -/// The utilization of IP addresses in a pool. -/// -/// Note that both the count of remaining addresses and the total capacity are -/// integers, reported as floating point numbers. This accommodates allocations -/// larger than a 64-bit integer, which is common with IPv6 address spaces. With -/// very large IP Pools (> 2**53 addresses), integer precision will be lost, in -/// exchange for representing the entire range. In such a case the pool still -/// has many available addresses. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct IpPoolUtilization { - /// The number of remaining addresses in the pool. - pub remaining: f64, - /// The total number of addresses in the pool. - pub capacity: f64, -} - -/// An IP pool in the context of a silo -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SiloIpPool { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// When a pool is the default for a silo, floating IPs and instance - /// ephemeral IPs will come from that pool when no other pool is specified. - /// - /// A silo can have at most one default pool per combination of pool type - /// (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 - /// default pools total. - pub is_default: bool, - - /// The IP version for the pool. - pub ip_version: IpVersion, - - /// Type of IP pool (unicast or multicast). - pub pool_type: shared::IpPoolType, -} - -/// A link between an IP pool and a silo that allows one to allocate IPs from -/// the pool within the silo -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct IpPoolSiloLink { - pub ip_pool_id: Uuid, - pub silo_id: Uuid, - /// When a pool is the default for a silo, floating IPs and instance - /// ephemeral IPs will come from that pool when no other pool is specified. - /// - /// A silo can have at most one default pool per combination of pool type - /// (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 - /// default pools total. - pub is_default: bool, -} - -#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema)] -pub struct IpPoolRange { - pub id: Uuid, - pub ip_pool_id: Uuid, - pub time_created: DateTime, - pub range: IpRange, -} - -// SUBNET POOLS - -/// A pool of subnets for external subnet allocation -#[derive( - ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, -)] -pub struct SubnetPool { - #[serde(flatten)] - pub identity: IdentityMetadata, - /// The IP version for this pool - pub ip_version: IpVersion, -} - -/// A subnet pool in the context of a silo -#[derive( - ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, -)] -pub struct SiloSubnetPool { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// When a pool is the default for a silo, external subnet allocations will - /// come from that pool when no other pool is specified. - /// - /// A silo can have at most one default pool per IP version (IPv4 or IPv6), - /// allowing up to 2 default pools total. - pub is_default: bool, - - /// The IP version for the pool. - pub ip_version: IpVersion, -} - -/// A member (subnet) within a subnet pool -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct SubnetPoolMember { - /// ID of the pool member - pub id: Uuid, - /// Time the pool member was created. - pub time_created: DateTime, - /// ID of the parent subnet pool - pub subnet_pool_id: Uuid, - /// The subnet CIDR - pub subnet: IpNet, - /// Minimum prefix length for allocations from this subnet; a smaller prefix - /// means larger allocations are allowed (e.g. a /16 prefix yields larger - /// subnet allocations than a /24 prefix). - pub min_prefix_length: u8, - /// Maximum prefix length for allocations from this subnet; a larger prefix - /// means smaller allocations are allowed (e.g. a /24 prefix yields smaller - /// subnet allocations than a /16 prefix). - pub max_prefix_length: u8, -} - -/// A link between a subnet pool and a silo -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct SubnetPoolSiloLink { - pub subnet_pool_id: Uuid, - pub silo_id: Uuid, - pub is_default: bool, -} - -/// Utilization information for a subnet pool -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SubnetPoolUtilization { - /// Number of addresses allocated from this pool - pub allocated: f64, - /// Total capacity of this pool in addresses - pub capacity: f64, -} - -// EXTERNAL SUBNETS - -/// An external subnet allocated from a subnet pool -#[derive( - ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, -)] -pub struct ExternalSubnet { - #[serde(flatten)] - pub identity: IdentityMetadata, - /// The allocated subnet CIDR - pub subnet: IpNet, - /// The project this subnet belongs to - pub project_id: Uuid, - /// The subnet pool this was allocated from - pub subnet_pool_id: Uuid, - /// The subnet pool member this subnet corresponds to - pub subnet_pool_member_id: Uuid, - /// The instance this subnet is attached to, if any - pub instance_id: Option, -} - -// INSTANCE EXTERNAL IP ADDRESSES - -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema)] -#[serde(tag = "kind", rename_all = "snake_case")] -pub enum ExternalIp { - #[serde(rename = "snat")] - SNat(SNatIp), - Ephemeral { - ip: IpAddr, - ip_pool_id: Uuid, - }, - Floating(FloatingIp), -} - -impl ExternalIp { - pub fn ip(&self) -> IpAddr { - match self { - Self::SNat(snat) => snat.ip, - Self::Ephemeral { ip, .. } => *ip, - Self::Floating(float) => float.ip, - } - } - - pub fn kind(&self) -> IpKind { - match self { - Self::SNat(_) => IpKind::SNat, - Self::Ephemeral { .. } => IpKind::Ephemeral, - Self::Floating(_) => IpKind::Floating, - } - } -} - -/// A source NAT IP address. -/// -/// SNAT addresses are ephemeral addresses used only for outbound connectivity. -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema)] -pub struct SNatIp { - /// The IP address. - pub ip: IpAddr, - /// The first usable port within the IP address. - pub first_port: u16, - /// The last usable port within the IP address. - pub last_port: u16, - /// ID of the IP Pool from which the address is taken. - pub ip_pool_id: Uuid, -} - -/// A Floating IP is a well-known IP address which can be attached -/// and detached from instances. -#[derive( - ObjectIdentity, Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema, -)] -#[serde(rename_all = "snake_case")] -pub struct FloatingIp { - #[serde(flatten)] - pub identity: IdentityMetadata, - /// The IP address held by this resource. - pub ip: IpAddr, - /// The ID of the IP pool this resource belongs to. - pub ip_pool_id: Uuid, - /// The project this resource exists within. - pub project_id: Uuid, - /// The ID of the instance that this Floating IP is attached to, - /// if it is presently in use. - pub instance_id: Option, -} - -impl From for ExternalIp { - fn from(value: FloatingIp) -> Self { - ExternalIp::Floating(value) - } -} - -impl TryFrom for FloatingIp { - type Error = Error; - - fn try_from(value: ExternalIp) -> Result { - match value { - ExternalIp::SNat(_) | ExternalIp::Ephemeral { .. } => { - Err(Error::internal_error( - "tried to convert an SNAT or ephemeral IP into a floating IP", - )) - } - ExternalIp::Floating(v) => Ok(v), - } - } -} - -// MULTICAST GROUPS - -/// View of a Multicast Group -#[derive( - ObjectIdentity, Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema, -)] -pub struct MulticastGroup { - #[serde(flatten)] - pub identity: IdentityMetadata, - /// The multicast IP address held by this resource. - pub multicast_ip: IpAddr, - /// Union of all member source IP addresses (computed, read-only). - /// - /// This field shows the combined source IPs across all group members. - /// Individual members may subscribe to different sources; this union - /// reflects all sources that any member is subscribed to. - /// Empty array means no members have source filtering enabled. - pub source_ips: Vec, - /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. - /// None means no VLAN tagging on egress. - // TODO(multicast): Remove mvlan field - being deprecated from multicast groups - pub mvlan: Option, - /// The ID of the IP pool this resource belongs to. - pub ip_pool_id: Uuid, - /// Current state of the multicast group. - pub state: String, -} - -/// View of a Multicast Group Member (instance belonging to a multicast group) -#[derive( - ObjectIdentity, Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema, -)] -pub struct MulticastGroupMember { - #[serde(flatten)] - pub identity: IdentityMetadata, - /// The ID of the multicast group this member belongs to. - pub multicast_group_id: Uuid, - /// The multicast IP address of the group this member belongs to. - pub multicast_ip: IpAddr, - /// The ID of the instance that is a member of this group. - pub instance_id: Uuid, - /// Source IP addresses for this member's multicast subscription. - /// - /// - **ASM**: Sources are optional. Empty array means any source is allowed. - /// Non-empty array enables source filtering (IGMPv3/MLDv2). - /// - **SSM**: Sources are required for SSM addresses (232/8, ff3x::/32). - pub source_ips: Vec, - /// Current state of the multicast group membership. - pub state: String, -} - -// RACKS - -/// View of a Rack -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Rack { - #[serde(flatten)] - pub identity: AssetIdentityMetadata, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum RackMembershipChangeState { - InProgress, - Committed, - Aborted, -} - -/// Status of the rack membership uniquely identified by the (rack_id, version) -/// pair -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct RackMembershipStatus { - pub rack_id: Uuid, - /// Version that uniquely identifies the rack membership at a given point - /// in time - pub version: RackMembershipVersion, - pub state: RackMembershipChangeState, - /// All members of the rack for this version - pub members: BTreeSet, - /// All members that have not yet confirmed this membership version - pub unacknowledged_members: BTreeSet, - pub time_created: DateTime, - pub time_committed: Option>, - pub time_aborted: Option>, -} - -impl From for RackMembershipStatus { - fn from(value: TrustQuorumConfig) -> Self { - // `Unacked` means that a member has not received and acked a `Prepare` - // yet. `Prepared` means that a member has acknowledged the prepare but - // not the commit. `Committed` is when the member starts participating - // in the new group. - // - // Since we don't want to expose trust quorum specific knowledge to - // the operator, and they really only want to know when the membership - // change has started to take effect, we say that any member that hasn't - // yet committed is unacknowledged. - let unacknowledged_members = value - .members - .iter() - .filter_map(|(id, data)| match data.state { - TrustQuorumMemberState::Unacked - | TrustQuorumMemberState::Prepared => Some(id.clone()), - TrustQuorumMemberState::Committed => None, - }) - .collect(); - let state = if value.state.is_committed() { - RackMembershipChangeState::Committed - } else if value.state.is_aborted() { - RackMembershipChangeState::Aborted - } else { - RackMembershipChangeState::InProgress - }; - - Self { - rack_id: value.rack_id.into_untyped_uuid(), - version: RackMembershipVersion(value.epoch.0), - state, - members: value.members.keys().cloned().collect(), - unacknowledged_members, - time_created: value.time_created, - time_committed: value.time_committed, - time_aborted: value.time_aborted, - } - } -} - -// FRUs - -// SLEDS - -/// The unique ID of a sled. -#[derive(Clone, Debug, Serialize, JsonSchema)] -pub struct SledId { - #[schemars(with = "Uuid")] - pub id: SledUuid, -} - -/// An operator's view of a Sled. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Sled { - #[serde(flatten)] - pub identity: AssetIdentityMetadata, - pub baseboard: Baseboard, - /// The rack to which this Sled is currently attached - pub rack_id: Uuid, - /// The operator-defined policy of a sled. - pub policy: SledPolicy, - /// The current state of the sled. - pub state: SledState, - /// The number of hardware threads which can execute on this sled - pub usable_hardware_threads: u32, - /// Amount of RAM which may be used by the Sled's OS - pub usable_physical_ram: ByteCount, -} - -/// The operator-defined provision policy of a sled. -/// -/// This controls whether new resources are going to be provisioned on this -/// sled. -#[derive( - Copy, - Clone, - Debug, - Deserialize, - Serialize, - JsonSchema, - PartialEq, - Eq, - EnumIter, -)] -#[serde(rename_all = "snake_case")] -pub enum SledProvisionPolicy { - /// New resources will be provisioned on this sled. - Provisionable, - - /// New resources will not be provisioned on this sled. However, if the - /// sled is currently in service, existing resources will continue to be on - /// this sled unless manually migrated off. - NonProvisionable, -} - -impl SledProvisionPolicy { - /// Returns the opposite of the current provision state. - pub const fn invert(self) -> Self { - match self { - Self::Provisionable => Self::NonProvisionable, - Self::NonProvisionable => Self::Provisionable, - } - } -} - -/// The operator-defined policy of a sled. -#[derive( - Copy, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, -)] -#[serde(rename_all = "snake_case", tag = "kind")] -pub enum SledPolicy { - /// The operator has indicated that the sled is in-service. - InService { - /// Determines whether new resources can be provisioned onto the sled. - provision_policy: SledProvisionPolicy, - }, - - /// The operator has indicated that the sled has been permanently removed - /// from service. - /// - /// This is a terminal state: once a particular sled ID is expunged, it - /// will never return to service. (The actual hardware may be reused, but - /// it will be treated as a brand-new sled.) - /// - /// An expunged sled is always non-provisionable. - Expunged, - // NOTE: if you add a new value here, be sure to add it to - // the `IntoEnumIterator` impl below! -} - -// Can't automatically derive strum::EnumIter because that doesn't provide a -// way to iterate over nested enums. -impl IntoEnumIterator for SledPolicy { - type Iterator = std::array::IntoIter; - - fn iter() -> Self::Iterator { - [ - Self::InService { - provision_policy: SledProvisionPolicy::Provisionable, - }, - Self::InService { - provision_policy: SledProvisionPolicy::NonProvisionable, - }, - Self::Expunged, - ] - .into_iter() - } -} - -impl SledPolicy { - /// Creates a new `SledPolicy` that is in-service and provisionable. - pub fn provisionable() -> Self { - Self::InService { provision_policy: SledProvisionPolicy::Provisionable } - } - - /// Returns the provision policy, if the sled is in service. - pub fn provision_policy(&self) -> Option { - match self { - Self::InService { provision_policy } => Some(*provision_policy), - Self::Expunged => None, - } - } - - /// Returns true if the sled can be decommissioned with this policy - /// - /// This is a method here, rather than being a variant on `SledFilter`, - /// because the "decommissionable" condition only has meaning for policies, - /// not states. - pub fn is_decommissionable(&self) -> bool { - // This should be kept in sync with `all_decommissionable` below. - match self { - Self::InService { .. } => false, - Self::Expunged => true, - } - } - - /// Returns all the possible policies a sled can have for it to be - /// decommissioned. - /// - /// This is a method here, rather than being a variant on `SledFilter`, - /// because the "decommissionable" condition only has meaning for policies, - /// not states. - pub fn all_decommissionable() -> &'static [Self] { - &[Self::Expunged] - } -} - -impl fmt::Display for SledPolicy { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SledPolicy::InService { - provision_policy: SledProvisionPolicy::Provisionable, - } => write!(f, "in service"), - SledPolicy::InService { - provision_policy: SledProvisionPolicy::NonProvisionable, - } => write!(f, "not provisionable"), - SledPolicy::Expunged => write!(f, "expunged"), - } - } -} - -/// The current state of the sled. -#[derive( - Copy, - Clone, - Debug, - Deserialize, - Serialize, - JsonSchema, - PartialEq, - Eq, - EnumIter, - Diffable, -)] -#[serde(rename_all = "snake_case")] -pub enum SledState { - /// The sled is currently active, and has resources allocated on it. - Active, - - /// The sled has been permanently removed from service. - /// - /// This is a terminal state: once a particular sled ID is decommissioned, - /// it will never return to service. (The actual hardware may be reused, - /// but it will be treated as a brand-new sled.) - Decommissioned, -} - -impl fmt::Display for SledState { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SledState::Active => write!(f, "active"), - SledState::Decommissioned => write!(f, "decommissioned"), - } - } -} - -/// An operator's view of an instance running on a given sled -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct SledInstance { - #[serde(flatten)] - pub identity: AssetIdentityMetadata, - pub active_sled_id: Uuid, - pub migration_id: Option, - pub name: Name, - pub silo_name: Name, - pub project_name: Name, - pub state: InstanceState, - pub ncpus: i64, - pub memory: i64, -} - -// SWITCHES - -/// An operator's view of a Switch. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Switch { - #[serde(flatten)] - pub identity: AssetIdentityMetadata, - pub baseboard: Baseboard, - /// The rack to which this Switch is currently attached - pub rack_id: Uuid, -} - -// PHYSICAL DISKS - -/// View of a Physical Disk -/// -/// Physical disks reside in a particular sled and are used to store both -/// Instance Disk data as well as internal metadata. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] -pub struct PhysicalDisk { - #[serde(flatten)] - pub identity: AssetIdentityMetadata, - - /// The operator-defined policy for a physical disk. - pub policy: PhysicalDiskPolicy, - /// The current state Nexus believes the disk to be in. - pub state: PhysicalDiskState, - - /// The sled to which this disk is attached, if any. - #[schemars(with = "Option")] - pub sled_id: Option, - - pub vendor: String, - pub serial: String, - pub model: String, - - pub form_factor: PhysicalDiskKind, -} - -/// The operator-defined policy of a physical disk. -#[derive( - Copy, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, -)] -#[serde(rename_all = "snake_case", tag = "kind")] -pub enum PhysicalDiskPolicy { - /// The operator has indicated that the disk is in-service. - InService, - - /// The operator has indicated that the disk has been permanently removed - /// from service. - /// - /// This is a terminal state: once a particular disk ID is expunged, it - /// will never return to service. (The actual hardware may be reused, but - /// it will be treated as a brand-new disk.) - /// - /// An expunged disk is always non-provisionable. - Expunged, - // NOTE: if you add a new value here, be sure to add it to - // the `IntoEnumIterator` impl below! -} - -// Can't automatically derive strum::EnumIter because that doesn't provide a -// way to iterate over nested enums. -impl IntoEnumIterator for PhysicalDiskPolicy { - type Iterator = std::array::IntoIter; - - fn iter() -> Self::Iterator { - [Self::InService, Self::Expunged].into_iter() - } -} - -impl PhysicalDiskPolicy { - /// Creates a new `PhysicalDiskPolicy` that is in-service. - pub fn in_service() -> Self { - Self::InService - } - - /// Returns true if the disk can be decommissioned in this state. - pub fn is_decommissionable(&self) -> bool { - // This should be kept in sync with decommissionable_states below. - match self { - Self::InService => false, - Self::Expunged => true, - } - } -} - -impl fmt::Display for PhysicalDiskPolicy { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PhysicalDiskPolicy::InService => write!(f, "in service"), - PhysicalDiskPolicy::Expunged => write!(f, "expunged"), - } - } -} - -/// The current state of the disk, as determined by Nexus. -#[derive( - Copy, - Clone, - Debug, - Deserialize, - Serialize, - JsonSchema, - PartialEq, - Eq, - EnumIter, - Diffable, -)] -#[serde(rename_all = "snake_case")] -pub enum PhysicalDiskState { - /// The disk is currently active, and has resources allocated on it. - Active, - - /// The disk has been permanently removed from service. - /// - /// This is a terminal state: once a particular disk ID is decommissioned, - /// it will never return to service. (The actual hardware may be reused, - /// but it will be treated as a brand-new disk.) - Decommissioned, -} - -impl fmt::Display for PhysicalDiskState { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PhysicalDiskState::Active => write!(f, "active"), - PhysicalDiskState::Decommissioned => write!(f, "decommissioned"), - } - } -} - -// SILO USERS - -/// View of a User -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] -pub struct User { - #[schemars(with = "Uuid")] - pub id: SiloUserUuid, - - /** Human-readable name that can identify the user */ - pub display_name: String, - - /** Uuid of the silo to which this user belongs */ - pub silo_id: Uuid, -} - -// SESSION - -// Add silo name to User because the console needs to display it -/// Info about the current user -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] -pub struct CurrentUser { - #[serde(flatten)] - pub user: User, - /** Name of the silo to which this user belongs. */ - pub silo_name: Name, - /** - * Whether this user has the viewer role on the fleet. Used by the web - * console to determine whether to show system-level UI. - */ - pub fleet_viewer: bool, - /** - * Whether this user has the admin role on their silo. Used by the web - * console to determine whether to show admin-only UI elements. - */ - pub silo_admin: bool, -} - -// SILO GROUPS - -/// View of a Group -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] -pub struct Group { - #[schemars(with = "Uuid")] - pub id: SiloGroupUuid, - - /// Human-readable name that can identify the group - pub display_name: String, - - /// Uuid of the silo to which this group belongs - pub silo_id: Uuid, -} - -// BUILT-IN USERS - -/// View of a Built-in User -/// -/// Built-in users are identities internal to the system, used when the control -/// plane performs actions autonomously -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct UserBuiltin { - // TODO-correctness is flattening here (and in all the other types) the - // intent in RFD 4? - #[serde(flatten)] - pub identity: IdentityMetadata, -} - -// SSH KEYS - -/// View of an SSH Key -#[derive( - ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, -)] -pub struct SshKey { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// The user to whom this key belongs - #[schemars(with = "Uuid")] - pub silo_user_id: SiloUserUuid, - - /// SSH public key, e.g., `"ssh-ed25519 AAAAC3NzaC..."` - pub public_key: String, -} - -/// View of a device access token -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct DeviceAccessToken { - /// A unique, immutable, system-controlled identifier for the token. - /// - /// Note that this ID is not the bearer token itself, which starts with - /// "oxide-token-". - pub id: Uuid, - pub time_created: DateTime, - - /// Expiration timestamp. A null value means the token does not automatically expire. - pub time_expires: Option>, -} - -impl SimpleIdentity for DeviceAccessToken { - fn id(&self) -> Uuid { - self.id - } -} - -/// View of a console session -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct ConsoleSession { - /// A unique, immutable, system-controlled identifier for the session - pub id: Uuid, - pub time_created: DateTime, - pub time_last_used: DateTime, -} - -impl SimpleIdentity for ConsoleSession { - fn id(&self) -> Uuid { - self.id - } -} - -// OAUTH 2.0 DEVICE AUTHORIZATION REQUESTS & TOKENS - -/// Response to an initial device authorization request. -/// See RFC 8628 §3.2 (Device Authorization Response). -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct DeviceAuthResponse { - /// The device verification code - pub device_code: String, - - /// The end-user verification code - pub user_code: String, - - /// The end-user verification URI on the authorization server. - /// The URI should be short and easy to remember as end users - /// may be asked to manually type it into their user agent. - pub verification_uri: String, - - /// The lifetime in seconds of the `device_code` and `user_code` - pub expires_in: u16, -} - -/// Successful access token grant. See RFC 6749 §5.1. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct DeviceAccessTokenGrant { - /// The access token issued to the client - pub access_token: String, - - /// The type of the token issued, as described in RFC 6749 §7.1. - pub token_type: DeviceAccessTokenType, - - /// A unique, immutable, system-controlled identifier for the token - pub token_id: Uuid, - - /// Expiration timestamp. A null value means the token does not automatically expire. - pub time_expires: Option>, -} - -/// The kind of token granted. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum DeviceAccessTokenType { - Bearer, -} - -// SYSTEM HEALTH - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum PingStatus { - Ok, -} - -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] -pub struct Ping { - /// Whether the external API is reachable. Will always be Ok if the endpoint - /// returns anything at all. - pub status: PingStatus, -} - -// ALLOWED SOURCE IPS - -/// Allowlist of IPs or subnets that can make requests to user-facing services. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct AllowList { - /// Time the list was created. - pub time_created: DateTime, - /// Time the list was last modified. - pub time_modified: DateTime, - /// The allowlist of IPs or subnets. - pub allowed_ips: ExternalAllowedSourceIps, -} - -// OxQL QUERIES - -/// A table represents one or more timeseries with the same schema. -/// -/// A table is the result of an OxQL query. It contains a name, usually the name -/// of the timeseries schema from which the data is derived, and any number of -/// timeseries, which contain the actual data. -// -// # Motivation -// -// This struct is derived from [`oxql_types::Table`] but presents timeseries data as a `Vec` -// rather than a map keyed by [`TimeseriesKey`]. This provides a cleaner JSON -// representation for external consumers, as these numeric keys are ephemeral -// identifiers that have no meaning to API consumers. Key ordering is retained -// as this is contructed from the already sorted values present in [`Table`]. -// -// When serializing a [`Table`] to JSON, the `BTreeMap` -// structure produces output with numeric keys like: -// ```json -// { -// "timeseries": { -// "2352746367989923131": { ... }, -// "3940108470521992408": { ... } -// } -// } -// ``` -// -// The `Table` view instead serializes timeseries as an array: -// ```json -// { -// "timeseries": [ { ... }, { ... } ] -// } -// ``` -#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] -pub struct OxqlTable { - /// The name of the table. - pub name: String, - /// The set of timeseries in the table, ordered by key. - pub timeseries: Vec, -} - -impl From for OxqlTable { - fn from(table: oxql_types::Table) -> Self { - OxqlTable { - name: table.name.clone(), - timeseries: table.into_iter().collect(), - } - } -} - -/// Basic metadata about the resource usage of a query. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct OxqlQuerySummary { - /// The database-assigned query ID. - pub id: Uuid, - /// The raw query. - pub query: String, - /// The total duration of the query (network plus execution). - pub elapsed_ms: usize, - /// Summary of the data read and written. - pub io_summary: oxql_types::IoSummary, -} - -impl From for OxqlQuerySummary { - fn from(query_summary: oxql_types::QuerySummary) -> Self { - OxqlQuerySummary { - id: query_summary.id, - query: query_summary.query, - elapsed_ms: query_summary.elapsed.as_millis() as usize, - io_summary: query_summary.io_summary, - } - } -} - -/// The result of a successful OxQL query. -#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] -pub struct OxqlQueryResult { - /// Tables resulting from the query, each containing timeseries. - pub tables: Vec, - /// Summaries of queries run against ClickHouse. Note: we omit this field - /// from the generated docs, since it is not intended for consumption by - /// customers. - #[schemars(skip)] - pub query_summaries: Option>, -} - -// ALERTS - -/// An alert class. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AlertClass { - /// The name of the alert class. - pub name: String, - - /// A description of what this alert class represents. - pub description: String, -} - -/// The configuration for an alert receiver. -#[derive( - ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, -)] -pub struct AlertReceiver { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// The list of alert classes to which this receiver is subscribed. - pub subscriptions: Vec, - - /// Configuration specific to the kind of alert receiver that this is. - pub kind: AlertReceiverKind, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct AlertSubscriptionCreated { - /// The new subscription added to the receiver. - pub subscription: shared::AlertSubscription, -} - -/// The possible alert delivery mechanisms for an alert receiver. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -#[serde(rename_all = "snake_case", tag = "kind")] -pub enum AlertReceiverKind { - Webhook(WebhookReceiverConfig), -} - -/// The configuration for a webhook alert receiver. -#[derive( - ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, -)] -pub struct WebhookReceiver { - #[serde(flatten)] - pub identity: IdentityMetadata, - - /// The list of alert classes to which this receiver is subscribed. - pub subscriptions: Vec, - - #[serde(flatten)] - pub config: WebhookReceiverConfig, -} - -impl From for AlertReceiver { - fn from( - WebhookReceiver { identity, subscriptions, config }: WebhookReceiver, - ) -> Self { - Self { - identity, - subscriptions, - kind: AlertReceiverKind::Webhook(config), - } - } -} - -impl PartialEq for AlertReceiver { - fn eq(&self, other: &WebhookReceiver) -> bool { - // Will become refutable if/when more variants are added... - #[allow(irrefutable_let_patterns)] - let AlertReceiverKind::Webhook(ref config) = self.kind else { - return false; - }; - self.identity == other.identity - && self.subscriptions == other.subscriptions - && config == &other.config - } -} - -impl PartialEq for WebhookReceiver { - fn eq(&self, other: &AlertReceiver) -> bool { - // Will become refutable if/when more variants are added... - #[allow(irrefutable_let_patterns)] - let AlertReceiverKind::Webhook(ref config) = other.kind else { - return false; - }; - self.identity == other.identity - && self.subscriptions == other.subscriptions - && &self.config == config - } -} - -/// Webhook-specific alert receiver configuration. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct WebhookReceiverConfig { - /// The URL that webhook notification requests are sent to. - pub endpoint: Url, - /// A list containing the IDs of the secret keys used to sign payloads sent - /// to this receiver. - pub secrets: Vec, -} - -/// A list of the IDs of secrets associated with a webhook receiver. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct WebhookSecrets { - pub secrets: Vec, -} - -/// A view of a shared secret key assigned to a webhook receiver. -/// -/// Once a secret is created, the value of the secret is not available in the -/// API, as it must remain secret. Instead, secrets are referenced by their -/// unique IDs assigned when they are created. -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] -pub struct WebhookSecret { - /// The public unique ID of the secret. - pub id: Uuid, - - /// The UTC timestamp at which this secret was created. - pub time_created: DateTime, -} - -/// A delivery of a webhook event. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] -pub struct AlertDelivery { - /// The UUID of this delivery attempt. - pub id: Uuid, - - /// The UUID of the alert receiver that this event was delivered to. - #[schemars(with = "Uuid")] - pub receiver_id: AlertReceiverUuid, - - /// The event class. - pub alert_class: String, - - /// The UUID of the event. - #[schemars(with = "Uuid")] - pub alert_id: AlertUuid, - - /// The state of this delivery. - pub state: AlertDeliveryState, - - /// Why this delivery was performed. - pub trigger: AlertDeliveryTrigger, - - /// Individual attempts to deliver this webhook event, and their outcomes. - pub attempts: AlertDeliveryAttempts, - - /// The time at which this delivery began (i.e. the event was dispatched to - /// the receiver). - pub time_started: DateTime, -} - -/// The state of a webhook delivery attempt. -#[derive( - Copy, - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - JsonSchema, - strum::VariantArray, -)] -#[serde(rename_all = "snake_case")] -pub enum AlertDeliveryState { - /// The webhook event has not yet been delivered successfully. - /// - /// Either no delivery attempts have yet been performed, or the delivery has - /// failed at least once but has retries remaining. - Pending, - /// The webhook event has been delivered successfully. - Delivered, - /// The webhook delivery attempt has failed permanently and will not be - /// retried again. - Failed, -} - -impl AlertDeliveryState { - pub const ALL: &[Self] = ::VARIANTS; - - pub fn as_str(&self) -> &'static str { - match self { - Self::Pending => "pending", - Self::Delivered => "delivered", - Self::Failed => "failed", - } - } -} - -impl fmt::Display for AlertDeliveryState { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -impl std::str::FromStr for AlertDeliveryState { - type Err = Error; - fn from_str(s: &str) -> Result { - static EXPECTED_ONE_OF: LazyLock = - LazyLock::new(expected_one_of::); - - for &v in Self::ALL { - if s.trim().eq_ignore_ascii_case(v.as_str()) { - return Ok(v); - } - } - Err(Error::invalid_value("AlertDeliveryState", &*EXPECTED_ONE_OF)) - } -} - -/// The reason an alert was delivered -#[derive( - Copy, - Clone, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - JsonSchema, - strum::VariantArray, -)] -#[serde(rename_all = "snake_case")] -pub enum AlertDeliveryTrigger { - /// Delivery was triggered by the alert itself. - Alert, - /// Delivery was triggered by a request to resend the alert. - Resend, - /// This delivery is a liveness probe. - Probe, -} - -impl AlertDeliveryTrigger { - pub fn as_str(&self) -> &'static str { - match self { - Self::Alert => "alert", - Self::Resend => "resend", - Self::Probe => "probe", - } - } -} - -impl fmt::Display for AlertDeliveryTrigger { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -impl std::str::FromStr for AlertDeliveryTrigger { - type Err = Error; - fn from_str(s: &str) -> Result { - static EXPECTED_ONE_OF: LazyLock = - LazyLock::new(expected_one_of::); - - for &v in ::VARIANTS { - if s.trim().eq_ignore_ascii_case(v.as_str()) { - return Ok(v); - } - } - Err(Error::invalid_value("AlertDeliveryTrigger", &*EXPECTED_ONE_OF)) - } -} - -/// A list of attempts to deliver an alert to a receiver. -/// -/// The type of the delivery attempt model depends on the receiver type, as it -/// may contain information specific to that delivery mechanism. For example, -/// webhook delivery attempts contain the HTTP status code of the webhook -/// request. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum AlertDeliveryAttempts { - /// A list of attempts to deliver an alert to a webhook receiver. - Webhook(Vec), -} - -/// An individual delivery attempt for a webhook event. -/// -/// This represents a single HTTP request that was sent to the receiver, and its -/// outcome. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] -pub struct WebhookDeliveryAttempt { - /// The time at which the webhook delivery was attempted. - pub time_sent: DateTime, - - /// The attempt number. - pub attempt: usize, - - /// The outcome of this delivery attempt: either the event was delivered - /// successfully, or the request failed for one of several reasons. - pub result: WebhookDeliveryAttemptResult, - - pub response: Option, -} - -#[derive( - Clone, - Debug, - PartialEq, - Eq, - Deserialize, - Serialize, - JsonSchema, - strum::VariantArray, -)] -#[serde(rename_all = "snake_case")] -pub enum WebhookDeliveryAttemptResult { - /// The webhook event has been delivered successfully. - Succeeded, - /// A webhook request was sent to the endpoint, and it - /// returned a HTTP error status code indicating an error. - FailedHttpError, - /// The webhook request could not be sent to the receiver endpoint. - FailedUnreachable, - /// A connection to the receiver endpoint was successfully established, but - /// no response was received within the delivery timeout. - FailedTimeout, -} - -impl WebhookDeliveryAttemptResult { - pub const ALL: &[Self] = ::VARIANTS; - pub const ALL_FAILED: &[Self] = - &[Self::FailedHttpError, Self::FailedUnreachable, Self::FailedTimeout]; - - pub fn as_str(&self) -> &'static str { - match self { - Self::Succeeded => "succeeded", - Self::FailedHttpError => "failed_http_error", - Self::FailedTimeout => "failed_timeout", - Self::FailedUnreachable => "failed_unreachable", - } - } - - /// Returns `true` if this `WebhookDeliveryAttemptResult` represents a failure - pub fn is_failed(&self) -> bool { - *self != Self::Succeeded - } -} - -impl fmt::Display for WebhookDeliveryAttemptResult { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -/// The response received from a webhook receiver endpoint. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, JsonSchema)] -pub struct WebhookDeliveryResponse { - /// The HTTP status code returned from the webhook endpoint. - pub status: u16, - /// The response time of the webhook endpoint, in milliseconds. - pub duration_ms: usize, -} - -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, JsonSchema)] -pub struct AlertDeliveryId { - pub delivery_id: Uuid, -} - -/// Data describing the result of an alert receiver liveness probe attempt. -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, JsonSchema)] -pub struct AlertProbeResult { - /// The outcome of the probe delivery. - pub probe: AlertDelivery, - /// If the probe request succeeded, and resending failed deliveries on - /// success was requested, the number of new delivery attempts started. - /// Otherwise, if the probe did not succeed, or resending failed deliveries - /// was not requested, this is null. - /// - /// Note that this may be 0, if there were no events found which had not - /// been delivered successfully to this receiver. - pub resends_started: Option, -} - -// UPDATE - -/// View of a system software target release -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] -pub struct TargetRelease { - /// Time this was set as the target release - pub time_requested: DateTime, - - /// The specified release of the rack's system software - pub version: Version, -} - -/// Trusted root role used by the update system to verify update repositories. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] -pub struct UpdatesTrustRoot { - /// The UUID of this trusted root role. - pub id: Uuid, - /// Time the trusted root role was added. - pub time_created: DateTime, - /// The trusted root role itself, a JSON document as described by The Update - /// Framework. - pub root_role: TufSignedRootRole, -} - -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct UpdateStatus { - /// Current target release of the system software - /// - /// This may not correspond to the actual system software running - /// at the time of request; it is instead the release that the system - /// should be moving towards as a goal state. The system asynchronously - /// updates software to match this target release. - /// - /// Will only be null if a target release has never been set. In that case, - /// the system is not automatically attempting to manage software versions. - pub target_release: Nullable, - - /// Count of components running each release version - /// - /// Keys will be either: - /// - /// * Semver-like release version strings - /// * "install dataset", representing the initial rack software before - /// any updates - /// * "unknown", which means there is no TUF repo uploaded that matches - /// the software running on the component) - pub components_by_release_version: BTreeMap, - - /// Time of most recent update planning activity - /// - /// This is intended as a rough indicator of the last time something - /// happened in the update planner. - pub time_last_step_planned: DateTime, - - /// Whether automatic update is suspended due to manual update activity - /// - /// After a manual support procedure that changes the system software, - /// automatic update activity is suspended to avoid undoing the change. To - /// resume automatic update, first upload the TUF repository matching the - /// manually applied update, then set that as the target release. - pub suspended: bool, -} - -/// Metadata about a TUF repository -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] -pub struct TufRepo { - /// The hash of the repository - // This is a slight abuse of `ArtifactHash`, since that's the hash of - // individual artifacts within the repository. However, we use it here for - // convenience. - pub hash: ArtifactHash, - - /// The system version for this repository - /// - /// The system version is a top-level version number applied to all the - /// software in the repository. - pub system_version: Version, - - /// The file name of the repository, as reported by the client that uploaded - /// it - /// - /// This is intended for debugging. The file name may not match any - /// particular pattern, and even if it does, it may not be accurate since - /// it's just what the client reported. - // (e.g., with wicket, we read the file contents from stdin so we don't know - // the correct file name). - pub file_name: String, - - /// Time the repository was uploaded - pub time_created: DateTime, -} - -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] -pub struct TufRepoUpload { - pub repo: TufRepo, - pub status: TufRepoUploadStatus, -} - -/// Whether the uploaded TUF repo already existed or was new and had to be -/// inserted. Part of `TufRepoUpload`. -#[derive( - Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema, -)] -#[serde(rename_all = "snake_case")] -pub enum TufRepoUploadStatus { - /// The repository already existed in the database - AlreadyExists, - - /// The repository did not exist, and was inserted into the database - Inserted, -} - -fn expected_one_of() -> String { - use std::fmt::Write; - let mut msg = "expected one of:".to_string(); - let mut variants = T::VARIANTS.iter().peekable(); - while let Some(variant) = variants.next() { - if variants.peek().is_some() { - write!(&mut msg, " '{variant}',").unwrap(); - } else { - write!(&mut msg, " or '{variant}'").unwrap(); - } - } - msg -} - -// SCIM - -/// The POST response is the only time the generated bearer token is returned to -/// the client. -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct ScimClientBearerTokenValue { - pub id: Uuid, - pub time_created: DateTime, - pub time_expires: Option>, - pub bearer_token: String, -} - -#[derive(Deserialize, Serialize, JsonSchema)] -pub struct ScimClientBearerToken { - pub id: Uuid, - pub time_created: DateTime, - pub time_expires: Option>, -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_expected_one_of() { - // Test this using an enum that we declare here, so that the test - // needn't be updated if the types which actually use this helper - // change. - #[derive(Debug, strum::VariantArray)] - enum Test { - Foo, - Bar, - Baz, - } - - impl fmt::Display for Test { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(self, f) - } - } - - assert_eq!( - expected_one_of::(), - "expected one of: 'Foo', 'Bar', or 'Baz'" - ); - } -} - -#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] -#[serde(tag = "kind", rename_all = "snake_case")] -pub enum AuditLogEntryActor { - UserBuiltin { - #[schemars(with = "Uuid")] - user_builtin_id: BuiltInUserUuid, - }, - - SiloUser { - #[schemars(with = "Uuid")] - silo_user_id: SiloUserUuid, - - silo_id: Uuid, - }, - - Scim { - silo_id: Uuid, - }, - - Unauthenticated, -} - -/// Authentication method used for a request -#[derive( - Debug, Clone, Copy, Deserialize, Serialize, JsonSchema, PartialEq, Eq, -)] -#[serde(rename_all = "snake_case")] -pub enum AuthMethod { - /// Console session cookie - SessionCookie, - /// Device access token (OAuth 2.0 device authorization flow) - AccessToken, - /// SCIM client bearer token - ScimToken, - /// Spoof authentication (test only) - #[schemars(skip)] - Spoof, -} - -/// Result of an audit log entry -#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] -#[serde(tag = "kind", rename_all = "snake_case")] -pub enum AuditLogEntryResult { - /// The operation completed successfully - Success { - /// HTTP status code - http_status_code: u16, - }, - /// The operation failed - Error { - /// HTTP status code - http_status_code: u16, - error_code: Option, - error_message: String, - }, - // Note that the DB model result kind analogous to Unknown is called Timeout - // -- The name "Timeout" feels useful to write down for the DB but also - // feels like too much of an implementation detail to expose to the user -- - // it makes it sounds like the operation timed out rather than the audit log - // entry itself. - /// After the logged operation completed, our attempt to write the result - /// to the audit log failed, so it was automatically marked completed later - /// by a background job. This does not imply that the operation itself timed - /// out or failed, only our attempts to log its result. - Unknown, -} - -/// Audit log entry -#[derive(Debug, Deserialize, Serialize, JsonSchema)] -pub struct AuditLogEntry { - /// Unique identifier for the audit log entry - pub id: Uuid, - - /// When the request was received - pub time_started: DateTime, - - /// Request ID for tracing requests through the system - pub request_id: String, - /// URI of the request, truncated to 512 characters. Will only include host - /// and scheme for HTTP/2 requests. For HTTP/1.1, the URI will consist of - /// only the path and query. - pub request_uri: String, - /// API endpoint ID, e.g., `project_create` - pub operation_id: String, - /// IP address that made the request - pub source_ip: IpAddr, - /// User agent string from the request, truncated to 256 characters. - pub user_agent: Option, - - pub actor: AuditLogEntryActor, - - /// How the user authenticated the request (access token, session, or SCIM - /// token). Null for unauthenticated requests like login attempts. - pub auth_method: Option, - - /// ID of the credential used for authentication. Null for unauthenticated - /// requests. The value of `auth_method` indicates what kind of credential - /// it is (access token, session, or SCIM token). - pub credential_id: Option, - - // Fields that are optional because they get filled in after the action completes - /// Time operation completed - pub time_completed: DateTime, - - /// Result of the operation - pub result: AuditLogEntryResult, -} diff --git a/nexus/types/src/external_api/vpc.rs b/nexus/types/src/external_api/vpc.rs new file mode 100644 index 00000000000..0a2a968d5f3 --- /dev/null +++ b/nexus/types/src/external_api/vpc.rs @@ -0,0 +1,7 @@ +// 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/. + +//! VPC types. + +pub use nexus_types_versions::latest::vpc::*; diff --git a/nexus/types/src/identity.rs b/nexus/types/src/identity.rs index d505c549e9a..724a7fd7f19 100644 --- a/nexus/types/src/identity.rs +++ b/nexus/types/src/identity.rs @@ -10,9 +10,6 @@ use chrono::{DateTime, Utc}; use omicron_common::api::external::IdentityMetadata; use omicron_common::api::external::Name; use omicron_uuid_kinds::GenericUuid; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; /// Identity-related accessors for resources. /// @@ -44,17 +41,8 @@ pub trait Resource { } } -/// Identity-related metadata that's included in "asset" public API objects -/// (which generally have no name or description) -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] -pub struct AssetIdentityMetadata { - /// unique, immutable, system-controlled identifier for each resource - pub id: Uuid, - /// timestamp when this resource was created - pub time_created: DateTime, - /// timestamp when this resource was last modified - pub time_modified: DateTime, -} +// Re-export AssetIdentityMetadata from the versions crate +pub use nexus_types_versions::latest::asset::AssetIdentityMetadata; /// Identity-related accessors for assets. /// diff --git a/nexus/types/src/internal_api/background.rs b/nexus/types/src/internal_api/background.rs index 9fdf5c3c87a..894cf62d9a5 100644 --- a/nexus/types/src/internal_api/background.rs +++ b/nexus/types/src/internal_api/background.rs @@ -3,7 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use crate::deployment::PlanningReport; -use crate::external_api::views; +use crate::external_api::alert; use chrono::DateTime; use chrono::Utc; use gateway_types::component::SpType; @@ -833,7 +833,7 @@ pub struct WebhookDeliveryFailure { pub delivery_id: WebhookDeliveryUuid, pub alert_id: AlertUuid, pub attempt: usize, - pub result: views::WebhookDeliveryAttemptResult, + pub result: alert::WebhookDeliveryAttemptResult, pub response_status: Option, pub response_duration: Option, } diff --git a/nexus/types/src/internal_api/params.rs b/nexus/types/src/internal_api/params.rs index 596c107ef62..f78f9684dc5 100644 --- a/nexus/types/src/internal_api/params.rs +++ b/nexus/types/src/internal_api/params.rs @@ -5,9 +5,9 @@ //! Params define the request bodies of API endpoints for creating or updating resources. use crate::deployment::Blueprint; -use crate::external_api::params::PhysicalDiskKind; -use crate::external_api::shared::Baseboard; -use crate::external_api::shared::IpRange; +use crate::external_api::hardware::Baseboard; +use crate::external_api::physical_disk::PhysicalDiskKind; +use omicron_common::address::IpRange; use omicron_common::api::external::ByteCount; use omicron_common::api::external::Generation; use omicron_common::api::external::MacAddr; diff --git a/nexus/types/src/inventory.rs b/nexus/types/src/inventory.rs index 91e8af11229..3f8d14dabd6 100644 --- a/nexus/types/src/inventory.rs +++ b/nexus/types/src/inventory.rs @@ -9,7 +9,7 @@ //! nexus/inventory does not currently know about nexus/db-model and it's //! convenient to separate these concerns.) -use crate::external_api::params::PhysicalDiskKind; +use crate::external_api::physical_disk::PhysicalDiskKind; use chrono::DateTime; use chrono::Utc; use clickhouse_admin_types::keeper::ClickhouseKeeperClusterMembership; diff --git a/nexus/types/versions/Cargo.toml b/nexus/types/versions/Cargo.toml new file mode 100644 index 00000000000..2e27a6d603d --- /dev/null +++ b/nexus/types/versions/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "nexus-types-versions" +version = "0.1.0" +edition.workspace = true +license = "MPL-2.0" + +[lints] +workspace = true + +[dependencies] +anyhow.workspace = true +api_identity.workspace = true +base64.workspace = true +chrono.workspace = true +daft.workspace = true +dropshot.workspace = true +http.workspace = true +omicron-common.workspace = true +omicron-passwords.workspace = true +omicron-uuid-kinds.workspace = true +omicron-workspace-hack.workspace = true +openssl.workspace = true +oxnet.workspace = true +oxql-types.workspace = true +parse-display.workspace = true +regex.workspace = true +schemars.workspace = true +semver.workspace = true +sled-hardware-types.workspace = true +serde.workspace = true +serde_json.workspace = true +slog-error-chain.workspace = true +strum.workspace = true +tough.workspace = true +tufaceous-artifact.workspace = true +url.workspace = true +uuid.workspace = true diff --git a/nexus/types/versions/src/audit_log_credential_id/audit.rs b/nexus/types/versions/src/audit_log_credential_id/audit.rs new file mode 100644 index 00000000000..ee5a40d0d71 --- /dev/null +++ b/nexus/types/versions/src/audit_log_credential_id/audit.rs @@ -0,0 +1,70 @@ +// 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/. + +//! Audit log types for version AUDIT_LOG_CREDENTIAL_ID. + +use chrono::{DateTime, Utc}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; +use uuid::Uuid; + +use crate::v2025_11_20_00::audit::{AuditLogEntryActor, AuditLogEntryResult}; + +/// Authentication method used for a request +#[derive( + Debug, Clone, Copy, Deserialize, Serialize, JsonSchema, PartialEq, Eq, +)] +#[serde(rename_all = "snake_case")] +pub enum AuthMethod { + /// Console session cookie + SessionCookie, + /// Device access token (OAuth 2.0 device authorization flow) + AccessToken, + /// SCIM client bearer token + ScimToken, + /// Spoof authentication (test only) + #[schemars(skip)] + Spoof, +} + +/// Audit log entry +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct AuditLogEntry { + /// Unique identifier for the audit log entry + pub id: Uuid, + + /// When the request was received + pub time_started: DateTime, + + /// Request ID for tracing requests through the system + pub request_id: String, + /// URI of the request, truncated to 512 characters. Will only include + /// host and scheme for HTTP/2 requests. For HTTP/1.1, the URI will + /// consist of only the path and query. + pub request_uri: String, + /// API endpoint ID, e.g., `project_create` + pub operation_id: String, + /// IP address that made the request + pub source_ip: IpAddr, + /// User agent string from the request, truncated to 256 characters. + pub user_agent: Option, + + pub actor: AuditLogEntryActor, + + /// How the user authenticated the request (access token, session, or SCIM + /// token). Null for unauthenticated requests like login attempts. + pub auth_method: Option, + + /// ID of the credential used for authentication. Null for unauthenticated + /// requests. The value of `auth_method` indicates what kind of credential + /// it is (access token, session, or SCIM token). + pub credential_id: Option, + + /// Time operation completed + pub time_completed: DateTime, + + /// Result of the operation + pub result: AuditLogEntryResult, +} diff --git a/nexus/types/versions/src/audit_log_credential_id/mod.rs b/nexus/types/versions/src/audit_log_credential_id/mod.rs new file mode 100644 index 00000000000..a26b1a4397e --- /dev/null +++ b/nexus/types/versions/src/audit_log_credential_id/mod.rs @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `AUDIT_LOG_CREDENTIAL_ID` of the Nexus external API. +//! +//! Adds `AuthMethod` enum and `credential_id` to audit log entries. + +pub mod audit; diff --git a/nexus/types/versions/src/bgp_peer_collision_state/mod.rs b/nexus/types/versions/src/bgp_peer_collision_state/mod.rs new file mode 100644 index 00000000000..5f5e2f31ac0 --- /dev/null +++ b/nexus/types/versions/src/bgp_peer_collision_state/mod.rs @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `BGP_PEER_COLLISION_STATE` of the Nexus external API. +//! +//! This version (2025_12_12_00) adds the `ConnectionCollision` state to +//! `BgpPeerState`, but does not yet include the `peer_id` field on +//! `BgpPeerStatus`. + +pub mod networking; diff --git a/nexus/types/versions/src/bgp_peer_collision_state/networking.rs b/nexus/types/versions/src/bgp_peer_collision_state/networking.rs new file mode 100644 index 00000000000..9fe83ab0340 --- /dev/null +++ b/nexus/types/versions/src/bgp_peer_collision_state/networking.rs @@ -0,0 +1,98 @@ +// 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/. + +//! Networking types for the `BGP_PEER_COLLISION_STATE` version. +//! +//! This version adds `ConnectionCollision` to `BgpPeerState` but does not +//! include the `peer_id` field on `BgpPeerStatus`. + +use omicron_common::api::external; +use omicron_common::api::external::SwitchLocation; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; + +/// The current status of a BGP peer (with `ConnectionCollision` state, +/// without `peer_id`). +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct BgpPeerStatus { + /// IP address of the peer. + pub addr: IpAddr, + + /// Local autonomous system number. + pub local_asn: u32, + + /// Remote autonomous system number. + pub remote_asn: u32, + + /// State of the peer. + pub state: BgpPeerState, + + /// Time of last state change. + pub state_duration_millis: u64, + + /// Switch with the peer session. + pub switch: SwitchLocation, +} + +/// The current state of a BGP peer (includes `ConnectionCollision`). +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum BgpPeerState { + /// Initial state. Refuse all incoming BGP connections. No resources + /// allocated to peer. + Idle, + + /// Waiting for the TCP connection to be completed. + Connect, + + /// Trying to acquire peer by listening for and accepting a TCP connection. + Active, + + /// Waiting for open message from peer. + OpenSent, + + /// Waiting for keepalive or notification from peer. + OpenConfirm, + + /// There is an ongoing Connection Collision that hasn't yet been resolved. + ConnectionCollision, + + /// Synchronizing with peer. + SessionSetup, + + /// Session established. Able to exchange update, notification and keepalive + /// messages with peers. + Established, +} + +impl From for BgpPeerStatus { + fn from(new: external::BgpPeerStatus) -> Self { + BgpPeerStatus { + addr: new.addr, + local_asn: new.local_asn, + remote_asn: new.remote_asn, + state: match new.state { + external::BgpPeerState::Idle => BgpPeerState::Idle, + external::BgpPeerState::Connect => BgpPeerState::Connect, + external::BgpPeerState::Active => BgpPeerState::Active, + external::BgpPeerState::OpenSent => BgpPeerState::OpenSent, + external::BgpPeerState::OpenConfirm => { + BgpPeerState::OpenConfirm + } + external::BgpPeerState::ConnectionCollision => { + BgpPeerState::ConnectionCollision + } + external::BgpPeerState::SessionSetup => { + BgpPeerState::SessionSetup + } + external::BgpPeerState::Established => { + BgpPeerState::Established + } + }, + state_duration_millis: new.state_duration_millis, + switch: new.switch, + } + } +} diff --git a/nexus/types/versions/src/bgp_unnumbered_peers/mod.rs b/nexus/types/versions/src/bgp_unnumbered_peers/mod.rs new file mode 100644 index 00000000000..13513c9f203 --- /dev/null +++ b/nexus/types/versions/src/bgp_unnumbered_peers/mod.rs @@ -0,0 +1,16 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `BGP_UNNUMBERED_PEERS` of the Nexus external API. +//! +//! This version (2026_02_13_01) adds support for BGP unnumbered peers: +//! - `BgpPeer.addr` becomes optional (unnumbered sessions). +//! - `BgpPeer.router_lifetime` is added for IPv6 router advertisement +//! lifetime. +//! - `BgpConfigCreate` gains a `max_paths` field for BGP multipath. +//! - `BgpPeerStatus` gains a `peer_id` field. +//! - `BgpImported` replaces the IPv4-only `BgpImportedRouteIpv4`. +//! - `BgpExported` becomes per-route instead of a HashMap. + +pub mod networking; diff --git a/nexus/types/versions/src/bgp_unnumbered_peers/networking.rs b/nexus/types/versions/src/bgp_unnumbered_peers/networking.rs new file mode 100644 index 00000000000..3a3d7d57282 --- /dev/null +++ b/nexus/types/versions/src/bgp_unnumbered_peers/networking.rs @@ -0,0 +1,154 @@ +// 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/. + +//! Networking types for the `BGP_UNNUMBERED_PEERS` version. +//! +//! This version: +//! - Adds `max_paths` to `BgpConfigCreate`. +//! - Uses `external::BgpPeer` (optional `addr`, `router_lifetime`) in +//! `BgpPeerConfig`. +//! - Updates `SwitchPortSettingsCreate` to use the new `BgpPeerConfig`. + +use omicron_common::api::external::{ + self, IdentityMetadataCreateParams, MaxPathConfig, Name, NameOrId, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Parameters for creating a BGP configuration. This includes an autonomous +/// system number (ASN) and a virtual routing and forwarding (VRF) identifier. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct BgpConfigCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// The autonomous system number of this BGP configuration. + pub asn: u32, + + pub bgp_announce_set_id: NameOrId, + + /// Optional virtual routing and forwarding identifier for this BGP + /// configuration. + pub vrf: Option, + + // Dynamic BGP policy is not yet available so we skip adding it to the API. + /// A shaper program to apply to outgoing open and update messages. + #[serde(skip)] + pub shaper: Option, + /// A checker program to apply to incoming open and update messages. + #[serde(skip)] + pub checker: Option, + + /// Maximum number of paths to use when multiple "best paths" exist + #[serde(default)] + pub max_paths: MaxPathConfig, +} + +impl From + for BgpConfigCreate +{ + fn from(old: crate::v2025_11_20_00::networking::BgpConfigCreate) -> Self { + BgpConfigCreate { + identity: old.identity, + asn: old.asn, + bgp_announce_set_id: old.bgp_announce_set_id, + vrf: old.vrf, + shaper: old.shaper, + checker: old.checker, + max_paths: Default::default(), + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct BgpPeerConfig { + /// Link that the peer is reachable on. + /// On ports that are not broken out, this is always phy0. + /// On a 2x breakout the options are phy0 and phy1, on 4x + /// phy0-phy3, etc. + pub link_name: Name, + + pub peers: Vec, +} + +impl From for BgpPeerConfig { + fn from(old: crate::v2025_11_20_00::networking::BgpPeerConfig) -> Self { + BgpPeerConfig { + link_name: old.link_name, + peers: old.peers.into_iter().map(Into::into).collect(), + } + } +} + +/// Parameters for creating switch port settings. Switch port settings are the +/// central data structure for setting up external networking. Switch port +/// settings include link, interface, route, address and dynamic network +/// protocol configuration. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SwitchPortSettingsCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + pub port_config: crate::v2025_11_20_00::networking::SwitchPortConfigCreate, + + #[serde(default)] + pub groups: Vec, + + /// Link configurations. + pub links: Vec, + + /// Interface configurations. + #[serde(default)] + pub interfaces: + Vec, + + /// Route configurations. + #[serde(default)] + pub routes: Vec, + + /// BGP peer configurations. + #[serde(default)] + pub bgp_peers: Vec, + + /// Address configurations. + pub addresses: Vec, +} + +impl SwitchPortSettingsCreate { + pub fn new(identity: IdentityMetadataCreateParams) -> Self { + Self { + identity, + port_config: + crate::v2025_11_20_00::networking::SwitchPortConfigCreate { + geometry: + crate::v2025_11_20_00::networking::SwitchPortGeometry::Qsfp28x1, + }, + groups: Vec::new(), + links: Vec::new(), + interfaces: Vec::new(), + routes: Vec::new(), + bgp_peers: Vec::new(), + addresses: Vec::new(), + } + } +} + +impl From + for SwitchPortSettingsCreate +{ + fn from( + old: crate::v2025_11_20_00::networking::SwitchPortSettingsCreate, + ) -> Self { + SwitchPortSettingsCreate { + identity: old.identity, + port_config: old.port_config, + groups: old.groups, + links: old.links, + interfaces: old.interfaces, + routes: old.routes, + bgp_peers: old.bgp_peers.into_iter().map(Into::into).collect(), + addresses: old.addresses, + } + } +} diff --git a/nexus/types/versions/src/dual_stack_ephemeral_ip/instance.rs b/nexus/types/versions/src/dual_stack_ephemeral_ip/instance.rs new file mode 100644 index 00000000000..c09cdc86209 --- /dev/null +++ b/nexus/types/versions/src/dual_stack_ephemeral_ip/instance.rs @@ -0,0 +1,39 @@ +// 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/. + +//! Instance types for version DUAL_STACK_EPHEMERAL_IP. + +use omicron_common::address::IpVersion; +use omicron_common::api::external::NameOrId; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Query parameters for ephemeral IP detach operations. +#[derive(Deserialize, JsonSchema, Clone)] +pub struct EphemeralIpDetachSelector { + /// Name or ID of the project + pub project: Option, + /// The IP version of the ephemeral IP to detach. + /// + /// Required when the instance has both IPv4 and IPv6 ephemeral IPs. + /// If only one ephemeral IP is attached, this field may be omitted. + pub ip_version: Option, +} + +/// Parameters for detaching an external IP from an instance. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum ExternalIpDetach { + Ephemeral { + /// The IP version of the ephemeral IP to detach. + /// + /// Required when the instance has both IPv4 and IPv6 ephemeral IPs. + /// If only one ephemeral IP is attached, this field may be omitted. + #[serde(default)] + ip_version: Option, + }, + Floating { + floating_ip: NameOrId, + }, +} diff --git a/nexus/types/versions/src/dual_stack_ephemeral_ip/mod.rs b/nexus/types/versions/src/dual_stack_ephemeral_ip/mod.rs new file mode 100644 index 00000000000..d841195dd5d --- /dev/null +++ b/nexus/types/versions/src/dual_stack_ephemeral_ip/mod.rs @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `DUAL_STACK_EPHEMERAL_IP` of the Nexus external API. +//! +//! Adds `ip_version` to `ExternalIpDetach::Ephemeral` for dual-stack support. + +pub mod instance; diff --git a/nexus/types/versions/src/dual_stack_nics/instance.rs b/nexus/types/versions/src/dual_stack_nics/instance.rs new file mode 100644 index 00000000000..45edaec096d --- /dev/null +++ b/nexus/types/versions/src/dual_stack_nics/instance.rs @@ -0,0 +1,342 @@ +// 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/. + +//! Instance types for version DUAL_STACK_NICS. +//! +//! This version introduces dual-stack network interface support. + +use omicron_common::address::ConcreteIp; +use omicron_common::api::external::{ + ByteCount, Error, Hostname, IdentityMetadataCreateParams, + InstanceAutoRestartPolicy, InstanceCpuCount, InstanceCpuPlatform, Name, + NameOrId, +}; +use oxnet::{Ipv4Net, Ipv6Net}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::{Ipv4Addr, Ipv6Addr}; + +use crate::v2025_11_20_00::instance::{UserData, bool_true}; +use crate::v2025_12_03_00::instance::InstanceDiskAttachment; +use crate::v2025_12_23_00; + +// Shadow type for JsonSchema generation +/// How a VPC-private IP address is assigned to a network interface. +#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema, Serialize)] +#[serde(rename_all = "snake_case", tag = "type", content = "value")] +enum IpAssignmentShadow { + /// Automatically assign an IP address from the VPC Subnet. + #[default] + Auto, + /// Explicitly assign a specific address, if available. + Explicit(T), +} + +trait IpAssignmentSchema { + fn ip_assignment_schema_name() -> String; +} + +impl IpAssignmentSchema for Ipv4Addr { + fn ip_assignment_schema_name() -> String { + String::from("Ipv4Assignment") + } +} + +impl IpAssignmentSchema for Ipv6Addr { + fn ip_assignment_schema_name() -> String { + String::from("Ipv6Assignment") + } +} + +/// How a VPC-private IP address is assigned to a network interface. +#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "snake_case", tag = "type", content = "value")] +pub enum IpAssignment { + /// Automatically assign an IP address from the VPC Subnet. + #[default] + Auto, + /// Explicitly assign a specific address, if available. + Explicit(T), +} + +impl JsonSchema for IpAssignment +where + T: ConcreteIp + IpAssignmentSchema, +{ + fn schema_name() -> String { + ::ip_assignment_schema_name() + } + + fn json_schema( + generator: &mut schemars::r#gen::SchemaGenerator, + ) -> schemars::schema::Schema { + IpAssignmentShadow::::json_schema(generator) + } +} + +impl From for IpAssignment { + fn from(ip: T) -> Self { + Self::Explicit(ip) + } +} + +/// How to assign an IPv4 address. +pub type Ipv4Assignment = IpAssignment; + +/// How to assign an IPv6 address. +pub type Ipv6Assignment = IpAssignment; + +/// Configuration for a network interface's IPv4 addressing. +#[derive(Clone, Debug, Default, Deserialize, JsonSchema, Serialize)] +pub struct PrivateIpv4StackCreate { + /// The VPC-private address to assign to the interface. + pub ip: Ipv4Assignment, + /// Additional IP networks the interface can send / receive on. + #[serde(default)] + pub transit_ips: Vec, +} + +/// Configuration for a network interface's IPv6 addressing. +#[derive(Clone, Debug, Default, Deserialize, JsonSchema, Serialize)] +pub struct PrivateIpv6StackCreate { + /// The VPC-private address to assign to the interface. + pub ip: Ipv6Assignment, + /// Additional IP networks the interface can send / receive on. + #[serde(default)] + pub transit_ips: Vec, +} + +/// Create parameters for a network interface's IP stack. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +#[serde(rename_all = "snake_case", tag = "type", content = "value")] +pub enum PrivateIpStackCreate { + /// The interface has only an IPv4 stack. + V4(PrivateIpv4StackCreate), + /// The interface has only an IPv6 stack. + V6(PrivateIpv6StackCreate), + /// The interface has both an IPv4 and IPv6 stack. + DualStack { v4: PrivateIpv4StackCreate, v6: PrivateIpv6StackCreate }, +} + +impl PrivateIpStackCreate { + /// Construct an IP configuration with only an automatic IPv4 address. + pub fn auto_ipv4() -> Self { + PrivateIpStackCreate::V4(PrivateIpv4StackCreate::default()) + } + + /// Construct an IP configuration with both IPv4 / IPv6 addresses. + pub fn auto_dual_stack() -> Self { + PrivateIpStackCreate::DualStack { + v4: PrivateIpv4StackCreate::default(), + v6: PrivateIpv6StackCreate::default(), + } + } +} + +/// Describes an attachment of an `InstanceNetworkInterface` to an `Instance`, +/// at the time the instance is created. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", content = "params", rename_all = "snake_case")] +#[derive(Default)] +pub enum InstanceNetworkInterfaceAttachment { + /// Create one or more `InstanceNetworkInterface`s for the `Instance`. + /// + /// If more than one interface is provided, then the first will be + /// designated the primary interface for the instance. + Create(Vec), + + /// Create a single primary interface with an automatically-assigned IPv4 + /// address. + /// + /// The IP will be pulled from the Project's default VPC / VPC Subnet. + DefaultIpv4, + + /// Create a single primary interface with an automatically-assigned IPv6 + /// address. + /// + /// The IP will be pulled from the Project's default VPC / VPC Subnet. + DefaultIpv6, + + /// Create a single primary interface with automatically-assigned IPv4 and + /// IPv6 addresses. + /// + /// The IPs will be pulled from the Project's default VPC / VPC Subnet. + #[default] + DefaultDualStack, + + /// No network interfaces at all will be created for the instance. + None, +} + +impl TryFrom + for InstanceNetworkInterfaceAttachment +{ + type Error = Error; + + fn try_from( + value: v2025_12_23_00::instance::InstanceNetworkInterfaceAttachment, + ) -> Result { + match value { + v2025_12_23_00::instance::InstanceNetworkInterfaceAttachment::Create( + nics, + ) => nics + .into_iter() + .map(TryInto::try_into) + .collect::>() + .map(Self::Create), + v2025_12_23_00::instance::InstanceNetworkInterfaceAttachment::Default => { + Ok(Self::DefaultIpv4) + } + v2025_12_23_00::instance::InstanceNetworkInterfaceAttachment::None => { + Ok(Self::None) + } + } + } +} + +/// Create-time parameters for an `InstanceNetworkInterface` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceNetworkInterfaceCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The VPC in which to create the interface. + pub vpc_name: Name, + /// The VPC Subnet in which to create the interface. + pub subnet_name: Name, + /// The IP stack configuration for this interface. + /// + /// If not provided, a default configuration will be used, which creates + /// a dual-stack IPv4 / IPv6 interface. + #[serde(default = "PrivateIpStackCreate::auto_dual_stack")] + pub ip_config: PrivateIpStackCreate, +} + +impl TryFrom + for InstanceNetworkInterfaceCreate +{ + type Error = Error; + + fn try_from( + value: v2025_12_23_00::instance::InstanceNetworkInterfaceCreate, + ) -> Result { + use oxnet::IpNet; + + let mut ipv4_transit_ips = Vec::new(); + let mut ipv6_transit_ips = Vec::new(); + for net in value.transit_ips { + match net { + IpNet::V4(ipv4) => ipv4_transit_ips.push(ipv4), + IpNet::V6(ipv6) => ipv6_transit_ips.push(ipv6), + } + } + if !ipv4_transit_ips.is_empty() && !ipv6_transit_ips.is_empty() { + return Err(Error::invalid_request( + "Cannot specify both IPv4 and IPv6 transit IPs", + )); + } + let ip_config = match value.ip { + None => { + if !ipv4_transit_ips.is_empty() { + PrivateIpStackCreate::V4(PrivateIpv4StackCreate { + ip: IpAssignment::Auto, + transit_ips: ipv4_transit_ips, + }) + } else if !ipv6_transit_ips.is_empty() { + PrivateIpStackCreate::V6(PrivateIpv6StackCreate { + ip: IpAssignment::Auto, + transit_ips: ipv6_transit_ips, + }) + } else { + PrivateIpStackCreate::auto_ipv4() + } + } + Some(std::net::IpAddr::V4(ipv4)) => { + if !ipv6_transit_ips.is_empty() { + return Err(Error::invalid_request( + "Cannot specify IPv6 transit IPs with an IPv4 address", + )); + } + PrivateIpStackCreate::V4(PrivateIpv4StackCreate { + ip: IpAssignment::Explicit(ipv4), + transit_ips: ipv4_transit_ips, + }) + } + Some(std::net::IpAddr::V6(ipv6)) => { + if !ipv4_transit_ips.is_empty() { + return Err(Error::invalid_request( + "Cannot specify IPv4 transit IPs with an IPv6 address", + )); + } + PrivateIpStackCreate::V6(PrivateIpv6StackCreate { + ip: IpAssignment::Explicit(ipv6), + transit_ips: ipv6_transit_ips, + }) + } + }; + Ok(Self { + identity: value.identity, + vpc_name: value.vpc_name, + subnet_name: value.subnet_name, + ip_config, + }) + } +} + +/// Create-time parameters for an `Instance` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + pub ncpus: InstanceCpuCount, + pub memory: ByteCount, + pub hostname: Hostname, + #[serde(default, with = "UserData")] + pub user_data: Vec, + #[serde(default)] + pub network_interfaces: InstanceNetworkInterfaceAttachment, + #[serde(default)] + pub external_ips: Vec, + #[serde(default)] + pub multicast_groups: Vec, + #[serde(default)] + pub disks: Vec, + #[serde(default)] + pub boot_disk: Option, + pub ssh_public_keys: Option>, + #[serde(default = "bool_true")] + pub start: bool, + #[serde(default)] + pub auto_restart_policy: Option, + #[serde(default)] + pub anti_affinity_groups: Vec, + #[serde(default)] + pub cpu_platform: Option, +} + +impl TryFrom for InstanceCreate { + type Error = Error; + + fn try_from( + old: v2025_12_23_00::instance::InstanceCreate, + ) -> Result { + let network_interfaces = old.network_interfaces.try_into()?; + Ok(InstanceCreate { + identity: old.identity, + ncpus: old.ncpus, + memory: old.memory, + hostname: old.hostname, + user_data: old.user_data, + network_interfaces, + external_ips: old.external_ips, + multicast_groups: old.multicast_groups, + disks: old.disks, + boot_disk: old.boot_disk, + ssh_public_keys: old.ssh_public_keys, + start: old.start, + auto_restart_policy: old.auto_restart_policy, + anti_affinity_groups: old.anti_affinity_groups, + cpu_platform: old.cpu_platform, + }) + } +} diff --git a/nexus/types/versions/src/dual_stack_nics/mod.rs b/nexus/types/versions/src/dual_stack_nics/mod.rs new file mode 100644 index 00000000000..56a6366fad1 --- /dev/null +++ b/nexus/types/versions/src/dual_stack_nics/mod.rs @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `DUAL_STACK_NICS` of the Nexus external API. +//! +//! This version (2026_01_03_00) introduces dual-stack network interface support, +//! allowing instances to have both IPv4 and IPv6 addresses on a single NIC. +//! It also adds `ip_version` support for IP pool selection. + +pub mod instance; +pub mod probe; diff --git a/nexus/types/versions/src/dual_stack_nics/probe.rs b/nexus/types/versions/src/dual_stack_nics/probe.rs new file mode 100644 index 00000000000..7abdc1b83b4 --- /dev/null +++ b/nexus/types/versions/src/dual_stack_nics/probe.rs @@ -0,0 +1,41 @@ +// 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/. + +//! Probe types for version DUAL_STACK_NICS. + +use omicron_common::api::external; +use omicron_common::api::external::Name; +use omicron_common::api::internal::shared::network_interface::NetworkInterface; +use omicron_uuid_kinds::SledUuid; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::v2025_11_20_00::probe::ProbeExternalIp; + +#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] +pub struct ProbeInfo { + pub id: Uuid, + pub name: Name, + #[schemars(with = "Uuid")] + pub sled: SledUuid, + pub external_ips: Vec, + pub interface: NetworkInterface, +} + +impl TryFrom for crate::v2025_11_20_00::probe::ProbeInfo { + type Error = external::Error; + + fn try_from( + new: ProbeInfo, + ) -> Result { + Ok(crate::v2025_11_20_00::probe::ProbeInfo { + id: new.id, + name: new.name, + sled: new.sled, + external_ips: new.external_ips, + interface: new.interface.try_into()?, + }) + } +} diff --git a/nexus/types/versions/src/external_subnet_attachment/external_subnet.rs b/nexus/types/versions/src/external_subnet_attachment/external_subnet.rs new file mode 100644 index 00000000000..890cda3324d --- /dev/null +++ b/nexus/types/versions/src/external_subnet_attachment/external_subnet.rs @@ -0,0 +1,119 @@ +// 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/. + +//! External subnet types for version EXTERNAL_SUBNET_ATTACHMENT. +//! +//! This is the earliest version where external subnet endpoints exist. Types +//! here include both wire-format shims and first definitions of unchanged +//! types. +//! +//! Wire-format shims: +//! - `ExternalSubnetAllocator::Explicit` accepts a `pool` field that was +//! later removed. We accept it on the wire but reject requests where it is +//! set. +//! - `ExternalSubnetCreate` wraps the above allocator. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + IdentityMetadata, IdentityMetadataCreateParams, + IdentityMetadataUpdateParams, NameOrId, ObjectIdentity, +}; +use oxnet::IpNet; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::v2026_01_05_00::ip_pool::PoolSelector; + +// -- Path params / selectors -- + +/// Path parameters for external subnet operations. +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct ExternalSubnetPath { + /// Name or ID of the external subnet + pub external_subnet: NameOrId, +} + +/// Selector for looking up an external subnet. +#[derive(Deserialize, JsonSchema, Clone)] +pub struct ExternalSubnetSelector { + /// Name or ID of the project (required if `external_subnet` is a Name). + pub project: Option, + /// Name or ID of the external subnet + pub external_subnet: NameOrId, +} + +// -- Create/update params -- + +/// Specify how to allocate an external subnet. +/// +/// This version of the type accepts a `pool` field on the `explicit` variant. +/// Setting `pool` is rejected; it exists only for wire-format compatibility. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum ExternalSubnetAllocator { + /// Reserve a specific subnet. + Explicit { + /// The subnet CIDR to reserve. Must be available in the pool. + subnet: IpNet, + /// The pool containing this subnet. If not specified, the default + /// subnet pool for the subnet's IP version is used. + pool: Option, + }, + /// Automatically allocate a subnet with the specified prefix length. + Auto { + /// The prefix length for the allocated subnet (e.g., 24 for a /24). + prefix_len: u8, + /// Pool selection. + /// + /// If omitted, this field uses the silo's default pool. If the + /// silo has default pools for both IPv4 and IPv6, the request will + /// fail unless `ip_version` is specified in the pool selector. + #[serde(default)] + pool_selector: PoolSelector, + }, +} + +/// Create an external subnet +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct ExternalSubnetCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// Subnet allocation method. + pub allocator: ExternalSubnetAllocator, +} + +/// Update an external subnet +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct ExternalSubnetUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, +} + +/// Attach an external subnet to an instance +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct ExternalSubnetAttach { + /// Name or ID of the instance to attach to + pub instance: NameOrId, +} + +/// An external subnet allocated from a subnet pool +#[derive( + ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, +)] +pub struct ExternalSubnet { + #[serde(flatten)] + pub identity: IdentityMetadata, + /// The allocated subnet CIDR + pub subnet: IpNet, + /// The project this subnet belongs to + pub project_id: Uuid, + /// The subnet pool this was allocated from + pub subnet_pool_id: Uuid, + /// The subnet pool member this subnet corresponds to + pub subnet_pool_member_id: Uuid, + /// The instance this subnet is attached to, if any + pub instance_id: Option, +} diff --git a/nexus/types/versions/src/external_subnet_attachment/mod.rs b/nexus/types/versions/src/external_subnet_attachment/mod.rs new file mode 100644 index 00000000000..81dfcd145a6 --- /dev/null +++ b/nexus/types/versions/src/external_subnet_attachment/mod.rs @@ -0,0 +1,17 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `EXTERNAL_SUBNET_ATTACHMENT` of the Nexus external API. +//! +//! This version introduced subnet pool and external subnet creation endpoints. +//! The types here are wire-format shims that accept extra fields which were +//! later removed: +//! +//! - `SubnetPoolCreate` accepted a `pool_type` field (validated to be Unicast). +//! - `SubnetPoolMemberAdd` accepted an `identity` field (silently discarded). +//! - `ExternalSubnetAllocator::Explicit` accepted a `pool` field (validated to +//! be None). + +pub mod external_subnet; +pub mod subnet_pool; diff --git a/nexus/types/versions/src/external_subnet_attachment/subnet_pool.rs b/nexus/types/versions/src/external_subnet_attachment/subnet_pool.rs new file mode 100644 index 00000000000..bc5dd1c4b43 --- /dev/null +++ b/nexus/types/versions/src/external_subnet_attachment/subnet_pool.rs @@ -0,0 +1,179 @@ +// 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/. + +//! Subnet pool types for version EXTERNAL_SUBNET_ATTACHMENT. +//! +//! This is the earliest version where subnet pool endpoints exist. Types +//! defined here are either wire-format shims (request types with fields that +//! were later removed) or the first definitions of types that are unchanged +//! in later versions. +//! +//! Wire-format shims: +//! - `SubnetPoolCreate` accepts a `pool_type` field (validated to be Unicast). +//! - `SubnetPoolMemberAdd` accepts an `identity` field (silently discarded). +//! +//! View-type shims (for EXTERNAL_SUBNET_ATTACHMENT..REMOVE_SUBNET_POOL_POOL_TYPE): +//! - `SubnetPool` includes a `pool_type` field. +//! - `SubnetPoolMember` includes identity metadata. + +use api_identity::ObjectIdentity; +use omicron_common::address::IpVersion; +use omicron_common::api::external::{ + IdentityMetadata, IdentityMetadataCreateParams, + IdentityMetadataUpdateParams, NameOrId, ObjectIdentity, +}; +use oxnet::IpNet; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::v2025_11_20_00::ip_pool::IpPoolType; + +// -- Path params -- + +/// Path parameters for subnet pool operations. +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct SubnetPoolPath { + /// Name or ID of the subnet pool + pub pool: NameOrId, +} + +/// Path parameters for subnet pool silo operations. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SubnetPoolSiloPath { + /// Name or ID of the subnet pool + pub pool: NameOrId, + /// Name or ID of the silo + pub silo: NameOrId, +} + +// -- Create/update params -- + +/// Create a subnet pool +/// +/// This version of the type accepts a `pool_type` field. The only valid +/// value is `unicast`; any other value is rejected. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SubnetPoolCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The IP version for this pool (IPv4 or IPv6). All subnets in the pool + /// must match this version. + pub ip_version: IpVersion, + /// Type of subnet pool (defaults to Unicast). + #[serde(default)] + pub pool_type: IpPoolType, +} + +/// Update a subnet pool +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SubnetPoolUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, +} + +/// Add a member (subnet) to a subnet pool +/// +/// This version of the type accepts an `identity` field (flattened as +/// `name` / `description`). The field is silently discarded. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SubnetPoolMemberAdd { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The subnet to add to the pool + pub subnet: IpNet, + /// Minimum prefix length for allocations from this subnet; a smaller prefix + /// means larger allocations are allowed (e.g. a /16 prefix yields larger + /// subnet allocations than a /24 prefix). + /// + /// Valid values: 0-32 for IPv4, 0-128 for IPv6. + /// Default if not specified is equal to the subnet's prefix length. + pub min_prefix_length: Option, + /// Maximum prefix length for allocations from this subnet; a larger prefix + /// means smaller allocations are allowed (e.g. a /24 prefix yields smaller + /// subnet allocations than a /16 prefix). + /// + /// Valid values: 0-32 for IPv4, 0-128 for IPv6. + /// Default if not specified is 32 for IPv4 and 128 for IPv6. + pub max_prefix_length: Option, +} + +/// Remove a subnet from a pool +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SubnetPoolMemberRemove { + /// The subnet to remove from the pool. Must match an existing entry exactly. + pub subnet: IpNet, +} + +/// Link a subnet pool to a silo +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SubnetPoolLinkSilo { + /// The silo to link + pub silo: NameOrId, + /// Whether this is the default subnet pool for the silo. When true, + /// external subnet allocations that don't specify a pool use this one. + pub is_default: bool, +} + +/// Update a subnet pool's silo link +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SubnetPoolSiloUpdate { + /// Whether this is the default subnet pool for the silo + pub is_default: bool, +} + +// -- View types -- + +/// A pool of subnets for external subnet allocation +/// +/// This version includes a `pool_type` field that was removed in +/// `REMOVE_SUBNET_POOL_POOL_TYPE`. +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SubnetPool { + #[serde(flatten)] + pub identity: IdentityMetadata, + /// The IP version for this pool + pub ip_version: IpVersion, + /// Type of subnet pool (unicast or multicast). + pub pool_type: IpPoolType, +} + +/// A member (subnet) within a subnet pool +/// +/// This version includes identity metadata that was removed in +/// `REMOVE_SUBNET_POOL_POOL_TYPE`. +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SubnetPoolMember { + #[serde(flatten)] + pub identity: IdentityMetadata, + /// ID of the parent subnet pool + pub subnet_pool_id: Uuid, + /// The subnet CIDR + pub subnet: IpNet, + /// Minimum prefix length for allocations from this subnet; a smaller prefix + /// means larger allocations are allowed (e.g. a /16 prefix yields larger + /// subnet allocations than a /24 prefix). + pub min_prefix_length: u8, + /// Maximum prefix length for allocations from this subnet; a larger prefix + /// means smaller allocations are allowed (e.g. a /24 prefix yields smaller + /// subnet allocations than a /16 prefix). + pub max_prefix_length: u8, +} + +/// A link between a subnet pool and a silo +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SubnetPoolSiloLink { + pub subnet_pool_id: Uuid, + pub silo_id: Uuid, + pub is_default: bool, +} + +/// Utilization information for a subnet pool +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SubnetPoolUtilization { + /// Number of addresses allocated from this pool + pub allocated: f64, + /// Total capacity of this pool in addresses + pub capacity: f64, +} diff --git a/nexus/types/versions/src/floating_ip_allocator_update/external_subnet.rs b/nexus/types/versions/src/floating_ip_allocator_update/external_subnet.rs new file mode 100644 index 00000000000..7795c8080b4 --- /dev/null +++ b/nexus/types/versions/src/floating_ip_allocator_update/external_subnet.rs @@ -0,0 +1,94 @@ +// 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/. + +//! External subnet types for version FLOATING_IP_ALLOCATOR_UPDATE. +//! +//! This version removes the `pool` field from +//! `ExternalSubnetAllocator::Explicit`. + +use omicron_common::api::external::{Error, IdentityMetadataCreateParams}; +use oxnet::IpNet; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::v2026_01_05_00::ip_pool::PoolSelector; + +/// Specify how to allocate an external subnet. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum ExternalSubnetAllocator { + /// Reserve a specific subnet. + Explicit { + /// The subnet CIDR to reserve. Must be available in the pool. + subnet: IpNet, + }, + /// Automatically allocate a subnet with the specified prefix length. + Auto { + /// The prefix length for the allocated subnet (e.g., 24 for a /24). + prefix_len: u8, + /// Pool selection. + /// + /// If omitted, this field uses the silo's default pool. If the + /// silo has default pools for both IPv4 and IPv6, the request will + /// fail unless `ip_version` is specified in the pool selector. + #[serde(default)] + pool_selector: PoolSelector, + }, +} + +/// Create an external subnet +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct ExternalSubnetCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// Subnet allocation method. + pub allocator: ExternalSubnetAllocator, +} + +// Conversion from the prior version (EXTERNAL_SUBNET_ATTACHMENT), which +// accepted a `pool` field on ExternalSubnetAllocator::Explicit that was +// later removed. +impl TryFrom + for ExternalSubnetAllocator +{ + type Error = Error; + + fn try_from( + value: crate::v2026_01_16_01::external_subnet::ExternalSubnetAllocator, + ) -> Result { + match value { + crate::v2026_01_16_01::external_subnet::ExternalSubnetAllocator::Explicit { + subnet, + pool, + } => { + if pool.is_some() { + return Err(Error::invalid_request( + "May not specify both an IP subnet and a Subnet Pool", + )); + } + Ok(Self::Explicit { subnet }) + } + crate::v2026_01_16_01::external_subnet::ExternalSubnetAllocator::Auto { + prefix_len, + pool_selector, + } => Ok(Self::Auto { prefix_len, pool_selector }), + } + } +} + +impl TryFrom + for ExternalSubnetCreate +{ + type Error = Error; + + fn try_from( + value: crate::v2026_01_16_01::external_subnet::ExternalSubnetCreate, + ) -> Result { + let crate::v2026_01_16_01::external_subnet::ExternalSubnetCreate { + identity, + allocator, + } = value; + allocator.try_into().map(|allocator| Self { identity, allocator }) + } +} diff --git a/nexus/types/versions/src/floating_ip_allocator_update/floating_ip.rs b/nexus/types/versions/src/floating_ip_allocator_update/floating_ip.rs new file mode 100644 index 00000000000..ffbd56cc4d5 --- /dev/null +++ b/nexus/types/versions/src/floating_ip_allocator_update/floating_ip.rs @@ -0,0 +1,84 @@ +// 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/. + +//! Floating IP types for version FLOATING_IP_ALLOCATOR_UPDATE. + +use omicron_common::api::external::IdentityMetadataCreateParams; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; + +use crate::v2026_01_05_00::ip_pool::PoolSelector; + +/// Specify how to allocate a floating IP address. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum AddressAllocator { + /// Reserve a specific IP address. The pool is inferred from the address + /// since IP pools cannot have overlapping ranges. + Explicit { + /// The IP address to reserve. + ip: IpAddr, + }, + /// Automatically allocate an IP address from a pool. + Auto { + /// Pool selection. + /// + /// If omitted, the silo's default pool is used. If the silo has + /// default pools for both IPv4 and IPv6, the request will fail + /// unless `ip_version` is specified. + #[serde(default)] + pool_selector: PoolSelector, + }, +} + +impl Default for AddressAllocator { + fn default() -> Self { + AddressAllocator::Auto { pool_selector: PoolSelector::default() } + } +} + +impl From + for AddressAllocator +{ + fn from( + value: crate::v2026_01_16_00::floating_ip::AddressAllocator, + ) -> Self { + match value { + // Pool field is dropped since the IP uniquely identifies + // the pool. + crate::v2026_01_16_00::floating_ip::AddressAllocator::Explicit { + ip, + pool: _, + } => AddressAllocator::Explicit { ip }, + crate::v2026_01_16_00::floating_ip::AddressAllocator::Auto { + pool_selector, + } => AddressAllocator::Auto { pool_selector }, + } + } +} + +/// Parameters for creating a new floating IP address for instances. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct FloatingIpCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// IP address allocation method. + #[serde(default)] + pub address_allocator: AddressAllocator, +} + +impl From + for FloatingIpCreate +{ + fn from( + value: crate::v2026_01_16_00::floating_ip::FloatingIpCreate, + ) -> Self { + Self { + identity: value.identity, + address_allocator: value.address_allocator.into(), + } + } +} diff --git a/nexus/types/versions/src/floating_ip_allocator_update/mod.rs b/nexus/types/versions/src/floating_ip_allocator_update/mod.rs new file mode 100644 index 00000000000..2f40c1814f0 --- /dev/null +++ b/nexus/types/versions/src/floating_ip_allocator_update/mod.rs @@ -0,0 +1,12 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `FLOATING_IP_ALLOCATOR_UPDATE` of the Nexus external API. +//! +//! Renames `AddressSelector` to `AddressAllocator` and removes `pool` from +//! `Explicit` variant. Introduces subnet pool and external subnet types. + +pub mod external_subnet; +pub mod floating_ip; +pub mod subnet_pool; diff --git a/nexus/types/versions/src/floating_ip_allocator_update/subnet_pool.rs b/nexus/types/versions/src/floating_ip_allocator_update/subnet_pool.rs new file mode 100644 index 00000000000..1775c1195ec --- /dev/null +++ b/nexus/types/versions/src/floating_ip_allocator_update/subnet_pool.rs @@ -0,0 +1,186 @@ +// 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/. + +//! Subnet pool types for version FLOATING_IP_ALLOCATOR_UPDATE. +//! +//! This version removes `pool_type` from `SubnetPool` and identity metadata +//! from `SubnetPoolMember`. It also removes `pool_type` from +//! `SubnetPoolCreate` and `identity` from `SubnetPoolMemberAdd`. + +use api_identity::ObjectIdentity; +use chrono::{DateTime, Utc}; +use omicron_common::address::IpVersion; +use omicron_common::api::external::{ + Error, IdentityMetadata, IdentityMetadataCreateParams, ObjectIdentity, +}; +use oxnet::IpNet; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::v2025_11_20_00::ip_pool::IpPoolType; + +// -- Create/update params -- + +/// Create a subnet pool +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SubnetPoolCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The IP version for this pool (IPv4 or IPv6). All subnets in the pool + /// must match this version. + pub ip_version: IpVersion, +} + +/// Add a member (subnet) to a subnet pool +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SubnetPoolMemberAdd { + /// The subnet to add to the pool + pub subnet: IpNet, + /// Minimum prefix length for allocations from this subnet; a smaller prefix + /// means larger allocations are allowed (e.g. a /16 prefix yields larger + /// subnet allocations than a /24 prefix). + /// + /// Valid values: 0-32 for IPv4, 0-128 for IPv6. + /// Default if not specified is equal to the subnet's prefix length. + pub min_prefix_length: Option, + /// Maximum prefix length for allocations from this subnet; a larger prefix + /// means smaller allocations are allowed (e.g. a /24 prefix yields smaller + /// subnet allocations than a /16 prefix). + /// + /// Valid values: 0-32 for IPv4, 0-128 for IPv6. + /// Default if not specified is 32 for IPv4 and 128 for IPv6. + pub max_prefix_length: Option, +} + +// Conversion from the prior version (EXTERNAL_SUBNET_ATTACHMENT), which +// accepted a `pool_type` field on SubnetPoolCreate (only `unicast` was +// valid) and an `identity` field on SubnetPoolMemberAdd (silently +// discarded). +impl TryFrom + for SubnetPoolCreate +{ + type Error = Error; + + fn try_from( + value: crate::v2026_01_16_01::subnet_pool::SubnetPoolCreate, + ) -> Result { + let crate::v2026_01_16_01::subnet_pool::SubnetPoolCreate { + identity, + ip_version, + pool_type, + } = value; + if pool_type != crate::v2025_11_20_00::ip_pool::IpPoolType::Unicast { + return Err(Error::invalid_request( + "Subnet Pools must have pool type unicast", + )); + } + Ok(Self { identity, ip_version }) + } +} + +impl From + for SubnetPoolMemberAdd +{ + fn from( + value: crate::v2026_01_16_01::subnet_pool::SubnetPoolMemberAdd, + ) -> Self { + let crate::v2026_01_16_01::subnet_pool::SubnetPoolMemberAdd { + subnet, + min_prefix_length, + max_prefix_length, + // Silently discard the identity field. + .. + } = value; + Self { subnet, min_prefix_length, max_prefix_length } + } +} + +// -- View types -- + +/// A pool of subnets for external subnet allocation +#[derive( + ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, +)] +pub struct SubnetPool { + #[serde(flatten)] + pub identity: IdentityMetadata, + /// The IP version for this pool + pub ip_version: IpVersion, +} + +// Response conversion: add back pool_type for the older API version. +impl From for crate::v2026_01_16_01::subnet_pool::SubnetPool { + fn from(value: SubnetPool) -> Self { + // The older version included pool_type. The only valid value was + // unicast, so we always emit that. + Self { + identity: value.identity, + ip_version: value.ip_version, + pool_type: IpPoolType::Unicast, + } + } +} + +/// A subnet pool in the context of a silo +#[derive( + ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, +)] +pub struct SiloSubnetPool { + #[serde(flatten)] + pub identity: IdentityMetadata, + /// When a pool is the default for a silo, external subnet allocations will + /// come from that pool when no other pool is specified. + /// + /// A silo can have at most one default pool per IP version (IPv4 or IPv6), + /// allowing up to 2 default pools total. + pub is_default: bool, + /// The IP version for the pool. + pub ip_version: IpVersion, +} + +/// A member (subnet) within a subnet pool +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SubnetPoolMember { + /// ID of the pool member + pub id: Uuid, + /// Time the pool member was created. + pub time_created: DateTime, + /// ID of the parent subnet pool + pub subnet_pool_id: Uuid, + /// The subnet CIDR + pub subnet: IpNet, + /// Minimum prefix length for allocations from this subnet; a smaller prefix + /// means larger allocations are allowed (e.g. a /16 prefix yields larger + /// subnet allocations than a /24 prefix). + pub min_prefix_length: u8, + /// Maximum prefix length for allocations from this subnet; a larger prefix + /// means smaller allocations are allowed (e.g. a /24 prefix yields smaller + /// subnet allocations than a /16 prefix). + pub max_prefix_length: u8, +} + +// Response conversion: add back identity metadata for the older API version. +impl From + for crate::v2026_01_16_01::subnet_pool::SubnetPoolMember +{ + fn from(value: SubnetPoolMember) -> Self { + // The older version included identity metadata. The name was not + // user-controllable, so we provide a dummy name. Since members + // cannot be updated, the modification time equals the creation time. + Self { + identity: IdentityMetadata { + id: value.id, + name: "unused".parse().expect("parsed \"unused\" as a Name"), + description: String::new(), + time_created: value.time_created, + time_modified: value.time_created, + }, + subnet_pool_id: value.subnet_pool_id, + subnet: value.subnet, + min_prefix_length: value.min_prefix_length, + max_prefix_length: value.max_prefix_length, + } + } +} diff --git a/nexus/types/versions/src/impls/alert.rs b/nexus/types/versions/src/impls/alert.rs new file mode 100644 index 00000000000..bea870dedc1 --- /dev/null +++ b/nexus/types/versions/src/impls/alert.rs @@ -0,0 +1,270 @@ +// 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/. + +//! Functional code for alert types. + +use crate::latest::alert::{ + AlertDeliveryState, AlertDeliveryStateFilter, AlertDeliveryTrigger, + AlertSubscription, WebhookDeliveryAttemptResult, +}; +use omicron_common::api::external::Error; +use std::fmt; +use std::sync::LazyLock; + +impl AlertSubscription { + pub(crate) fn is_valid(s: &str) -> Result<(), anyhow::Error> { + static REGEX: std::sync::LazyLock = + std::sync::LazyLock::new(|| { + regex::Regex::new(AlertSubscription::PATTERN).expect( + "AlertSubscription validation regex should be valid", + ) + }); + if REGEX.is_match(s) { + Ok(()) + } else { + Err(anyhow::anyhow!( + "alert subscription {s:?} does not match the pattern {}", + AlertSubscription::PATTERN + )) + } + } + + #[inline] + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl TryFrom for AlertSubscription { + type Error = anyhow::Error; + fn try_from(s: String) -> Result { + Self::is_valid(&s)?; + Ok(Self(s)) + } +} + +impl std::str::FromStr for AlertSubscription { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result { + Self::is_valid(s)?; + Ok(Self(s.to_string())) + } +} + +impl From for String { + fn from(AlertSubscription(s): AlertSubscription) -> Self { + s + } +} + +impl AsRef for AlertSubscription { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl fmt::Display for AlertSubscription { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl AlertDeliveryState { + pub const ALL: &[Self] = ::VARIANTS; + + pub fn as_str(&self) -> &'static str { + match self { + Self::Pending => "pending", + Self::Delivered => "delivered", + Self::Failed => "failed", + } + } +} + +impl fmt::Display for AlertDeliveryState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl std::str::FromStr for AlertDeliveryState { + type Err = Error; + fn from_str(s: &str) -> Result { + static EXPECTED_ONE_OF: LazyLock = + LazyLock::new(expected_one_of::); + + for &v in Self::ALL { + if s.trim().eq_ignore_ascii_case(v.as_str()) { + return Ok(v); + } + } + Err(Error::invalid_value("AlertDeliveryState", &*EXPECTED_ONE_OF)) + } +} + +impl AlertDeliveryTrigger { + pub fn as_str(&self) -> &'static str { + match self { + Self::Alert => "alert", + Self::Resend => "resend", + Self::Probe => "probe", + } + } +} + +impl fmt::Display for AlertDeliveryTrigger { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl std::str::FromStr for AlertDeliveryTrigger { + type Err = Error; + fn from_str(s: &str) -> Result { + static EXPECTED_ONE_OF: LazyLock = + LazyLock::new(expected_one_of::); + + for &v in ::VARIANTS { + if s.trim().eq_ignore_ascii_case(v.as_str()) { + return Ok(v); + } + } + Err(Error::invalid_value("AlertDeliveryTrigger", &*EXPECTED_ONE_OF)) + } +} + +impl WebhookDeliveryAttemptResult { + pub const ALL: &[Self] = ::VARIANTS; + pub const ALL_FAILED: &[Self] = + &[Self::FailedHttpError, Self::FailedUnreachable, Self::FailedTimeout]; + + pub fn as_str(&self) -> &'static str { + match self { + Self::Succeeded => "succeeded", + Self::FailedHttpError => "failed_http_error", + Self::FailedTimeout => "failed_timeout", + Self::FailedUnreachable => "failed_unreachable", + } + } + + /// Returns `true` if this `WebhookDeliveryAttemptResult` represents a failure + pub fn is_failed(&self) -> bool { + *self != Self::Succeeded + } +} + +impl fmt::Display for WebhookDeliveryAttemptResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl Default for AlertDeliveryStateFilter { + fn default() -> Self { + Self::ALL + } +} + +impl AlertDeliveryStateFilter { + pub const ALL: Self = + Self { pending: Some(true), failed: Some(true), delivered: Some(true) }; + + pub fn include_pending(&self) -> bool { + self.pending == Some(true) || self.is_all_none() + } + + pub fn include_failed(&self) -> bool { + self.failed == Some(true) || self.is_all_none() + } + + pub fn include_delivered(&self) -> bool { + self.delivered == Some(true) || self.is_all_none() + } + + pub fn include_all(&self) -> bool { + self.is_all_none() + || (self.pending == Some(true) + && self.failed == Some(true) + && self.delivered == Some(true)) + } + + fn is_all_none(&self) -> bool { + self.pending.is_none() + && self.failed.is_none() + && self.delivered.is_none() + } +} + +fn expected_one_of() -> String { + use std::fmt::Write; + let mut msg = "expected one of:".to_string(); + let mut variants = T::VARIANTS.iter().peekable(); + while let Some(variant) = variants.next() { + if variants.peek().is_some() { + write!(&mut msg, " '{variant}',").unwrap(); + } else { + write!(&mut msg, " or '{variant}'").unwrap(); + } + } + msg +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_webhook_subscription_validation() { + let successes = [ + "foo.bar", + "foo.bar.baz", + "foo_bar.baz", + "foo_1.bar_200.**", + "1.2.3", + "foo.**", + "foo.*.baz", + "foo.**.baz", + "foo.bar.**", + "foo.*.baz.**", + "**.foo", + "**.foo.bar.*", + "**.foo.bar.*.baz", + "*.foo.bar.*", + "*.foo", + "*", + "*.*", + ]; + let failures = [ + "", + "f*o.bar", + "**foo.bar", + "foo.**bar", + "foo.*bar*", + "*foo*", + "f**.bar", + "foo.***", + "***", + "$.foo.bar", + "foo.%bar", + "foo.[barbaz]", + ]; + for s in successes { + match s.parse::() { + Ok(_) => {} + Err(e) => panic!( + "expected string {s:?} to be a valid webhook subscription: {e}" + ), + } + } + + for s in failures { + match s.parse::() { + Ok(_) => panic!( + "expected string {s:?} to NOT be a valid webhook subscription" + ), + Err(_) => {} + } + } + } +} diff --git a/nexus/types/versions/src/impls/disk.rs b/nexus/types/versions/src/impls/disk.rs new file mode 100644 index 00000000000..bf1acec44f6 --- /dev/null +++ b/nexus/types/versions/src/impls/disk.rs @@ -0,0 +1,31 @@ +// 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/. + +//! Functional code for disk types. + +use crate::latest::disk::BlockSize; +use omicron_common::api::external::ByteCount; + +impl TryFrom for BlockSize { + type Error = anyhow::Error; + fn try_from(x: u32) -> Result { + if ![512, 2048, 4096].contains(&x) { + anyhow::bail!("invalid block size {}", x); + } + + Ok(BlockSize(x)) + } +} + +impl From for ByteCount { + fn from(bs: BlockSize) -> ByteCount { + ByteCount::from(bs.0) + } +} + +impl From for u64 { + fn from(bs: BlockSize) -> u64 { + u64::from(bs.0) + } +} diff --git a/nexus/types/versions/src/impls/hardware.rs b/nexus/types/versions/src/impls/hardware.rs new file mode 100644 index 00000000000..ac077926013 --- /dev/null +++ b/nexus/types/versions/src/impls/hardware.rs @@ -0,0 +1,14 @@ +// 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/. + +//! Functional code for hardware types. + +use crate::latest::hardware::UninitializedSledId; +use sled_hardware_types::BaseboardId; + +impl From for BaseboardId { + fn from(value: UninitializedSledId) -> Self { + BaseboardId { part_number: value.part, serial_number: value.serial } + } +} diff --git a/nexus/types/versions/src/impls/instance.rs b/nexus/types/versions/src/impls/instance.rs new file mode 100644 index 00000000000..8f626f1e71e --- /dev/null +++ b/nexus/types/versions/src/impls/instance.rs @@ -0,0 +1,164 @@ +// 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/. + +//! Functional code for instance-related types. + +use crate::latest::instance::{ + InstanceDiskAttach, InstanceDiskAttachment, IpAssignment, Ipv4Assignment, + Ipv6Assignment, PrivateIpStackCreate, PrivateIpv4StackCreate, + PrivateIpv6StackCreate, +}; +use omicron_common::api::external::Name; +use oxnet::IpNet; +use oxnet::Ipv4Net; +use oxnet::Ipv6Net; + +impl InstanceDiskAttachment { + /// Get the name of the disk described by this attachment. + pub fn name(&self) -> Name { + match self { + Self::Create(create) => create.identity.name.clone(), + Self::Attach(InstanceDiskAttach { name }) => name.clone(), + } + } +} + +impl PrivateIpStackCreate { + /// Construct an IPv4 configuration with no transit IPs. + pub fn from_ipv4(addr: std::net::Ipv4Addr) -> Self { + PrivateIpStackCreate::V4(PrivateIpv4StackCreate { + ip: Ipv4Assignment::Explicit(addr), + transit_ips: vec![], + }) + } + + /// Return the IPv4 create parameters, if they exist. + pub fn as_ipv4_create(&self) -> Option<&PrivateIpv4StackCreate> { + match self { + PrivateIpStackCreate::V4(v4) + | PrivateIpStackCreate::DualStack { v4, .. } => Some(v4), + PrivateIpStackCreate::V6(_) => None, + } + } + + /// Return the IPv4 address assignment. + pub fn ipv4_assignment(&self) -> Option<&Ipv4Assignment> { + self.as_ipv4_create().map(|c| &c.ip) + } + + /// Return the IPv4 address explicitly requested, if one exists. + pub fn ipv4_addr(&self) -> Option<&std::net::Ipv4Addr> { + self.ipv4_assignment().and_then(|assignment| match assignment { + IpAssignment::Auto => None, + IpAssignment::Explicit(addr) => Some(addr), + }) + } + + /// Return the IPv4 transit IPs, if they exist. + pub fn ipv4_transit_ips(&self) -> Option<&[Ipv4Net]> { + self.as_ipv4_create().map(|c| c.transit_ips.as_slice()) + } + + /// Construct an IPv6 configuration with no transit IPs. + pub fn from_ipv6(addr: std::net::Ipv6Addr) -> Self { + PrivateIpStackCreate::V6(PrivateIpv6StackCreate { + ip: Ipv6Assignment::Explicit(addr), + transit_ips: vec![], + }) + } + + /// Construct an IP configuration with only an automatic IPv6 address. + pub fn auto_ipv6() -> Self { + PrivateIpStackCreate::V6(PrivateIpv6StackCreate::default()) + } + + /// Return the IPv6 create parameters, if they exist. + pub fn as_ipv6_create(&self) -> Option<&PrivateIpv6StackCreate> { + match self { + PrivateIpStackCreate::V6(v6) + | PrivateIpStackCreate::DualStack { v6, .. } => Some(v6), + PrivateIpStackCreate::V4(_) => None, + } + } + + /// Return the IPv6 address assignment. + pub fn ipv6_assignment(&self) -> Option<&Ipv6Assignment> { + self.as_ipv6_create().map(|c| &c.ip) + } + + /// Return the IPv6 address explicitly requested, if one exists. + pub fn ipv6_addr(&self) -> Option<&std::net::Ipv6Addr> { + self.ipv6_assignment().and_then(|assignment| match assignment { + IpAssignment::Auto => None, + IpAssignment::Explicit(addr) => Some(addr), + }) + } + + /// Return the IPv6 transit IPs, if they exist. + pub fn ipv6_transit_ips(&self) -> Option<&[Ipv6Net]> { + self.as_ipv6_create().map(|c| c.transit_ips.as_slice()) + } + + /// Return the transit IPs requested in this configuration. + pub fn transit_ips(&self) -> Vec { + self.ipv4_transit_ips() + .unwrap_or_default() + .iter() + .copied() + .map(Into::into) + .chain( + self.ipv6_transit_ips() + .unwrap_or_default() + .iter() + .copied() + .map(Into::into), + ) + .collect() + } + + /// Construct a dual-stack IP configuration with explicit IP addresses. + pub fn new_dual_stack( + ipv4: std::net::Ipv4Addr, + ipv6: std::net::Ipv6Addr, + ) -> Self { + PrivateIpStackCreate::DualStack { + v4: PrivateIpv4StackCreate { + ip: Ipv4Assignment::Explicit(ipv4), + transit_ips: Vec::new(), + }, + v6: PrivateIpv6StackCreate { + ip: Ipv6Assignment::Explicit(ipv6), + transit_ips: Vec::new(), + }, + } + } + + /// Return true if this config has any transit IPs. + pub fn has_transit_ips(&self) -> bool { + match self { + PrivateIpStackCreate::V4(PrivateIpv4StackCreate { + transit_ips, + .. + }) => !transit_ips.is_empty(), + PrivateIpStackCreate::V6(PrivateIpv6StackCreate { + transit_ips, + .. + }) => !transit_ips.is_empty(), + PrivateIpStackCreate::DualStack { + v4: PrivateIpv4StackCreate { transit_ips: ipv4_addrs, .. }, + v6: PrivateIpv6StackCreate { transit_ips: ipv6_addrs, .. }, + } => !ipv4_addrs.is_empty() || !ipv6_addrs.is_empty(), + } + } + + /// Return true if this IP configuration has an IPv4 stack. + pub fn has_ipv4_stack(&self) -> bool { + self.ipv4_assignment().is_some() + } + + /// Return true if this IP configuration has an IPv6 stack. + pub fn has_ipv6_stack(&self) -> bool { + self.ipv6_assignment().is_some() + } +} diff --git a/nexus/types/versions/src/impls/mod.rs b/nexus/types/versions/src/impls/mod.rs new file mode 100644 index 00000000000..56a1b3b8f2f --- /dev/null +++ b/nexus/types/versions/src/impls/mod.rs @@ -0,0 +1,26 @@ +// 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/. + +//! Functional code for the latest versions of types. +//! +//! This module contains inherent methods, trait implementations (e.g., +//! `Display`, `FromStr`), and other functional code attached to versioned +//! types. Per RFD 619, such code must be implemented on the latest versions +//! of each type and live in this module. +//! +//! Within this module, types are referred to using `latest::` identifiers. + +mod alert; +mod disk; +mod hardware; +mod instance; +pub(crate) mod multicast; +mod networking; +mod physical_disk; +mod policy; +mod saml; +mod silo; +mod sled; +mod switch; +mod user; diff --git a/nexus/types/versions/src/impls/multicast.rs b/nexus/types/versions/src/impls/multicast.rs new file mode 100644 index 00000000000..ab50c8f894d --- /dev/null +++ b/nexus/types/versions/src/impls/multicast.rs @@ -0,0 +1,611 @@ +// 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/. + +//! Functional code for multicast types. + +use oxnet::{Ipv4Net, Ipv6Net}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +/// Validate that an IP address is suitable for use as a SSM source. +/// +/// For specifics, follow-up on RFC 4607: +/// +pub fn validate_source_ip(ip: IpAddr) -> Result<(), String> { + match ip { + IpAddr::V4(ipv4) => validate_ipv4_source(ipv4), + IpAddr::V6(ipv6) => validate_ipv6_source(ipv6), + } +} + +/// Validate that an IPv4 address is suitable for use as a multicast source. +fn validate_ipv4_source(addr: Ipv4Addr) -> Result<(), String> { + // Must be a unicast address + if !is_unicast_v4(&addr) { + return Err(format!("{} is not a unicast address", addr)); + } + + // Exclude problematic addresses (mostly align with Dendrite, but block link-local) + if addr.is_loopback() + || addr.is_broadcast() + || addr.is_unspecified() + || addr.is_link_local() + { + return Err(format!("{} is a special-use address", addr)); + } + + Ok(()) +} + +/// Validate that an IPv6 address is suitable for use as a multicast source. +fn validate_ipv6_source(addr: Ipv6Addr) -> Result<(), String> { + // Must be a unicast address + if !is_unicast_v6(&addr) { + return Err(format!("{} is not a unicast address", addr)); + } + + // Exclude problematic addresses (align with Dendrite validation, but block link-local) + if addr.is_loopback() + || addr.is_unspecified() + || ((addr.segments()[0] & 0xffc0) == 0xfe80) + // fe80::/10 link-local + { + return Err(format!("{} is a special-use address", addr)); + } + + Ok(()) +} + +/// Validate that an IP address is a proper multicast address for API validation. +pub fn validate_multicast_ip(ip: IpAddr) -> Result<(), String> { + match ip { + IpAddr::V4(ipv4) => validate_ipv4_multicast(ipv4), + IpAddr::V6(ipv6) => validate_ipv6_multicast(ipv6), + } +} + +// IPv4 link-local multicast range reserved for local network control. +const RESERVED_IPV4_MULTICAST_LINK_LOCAL: Ipv4Addr = + Ipv4Addr::new(224, 0, 0, 0); +const RESERVED_IPV4_MULTICAST_LINK_LOCAL_PREFIX: u8 = 24; + +/// Validates IPv4 multicast addresses. +fn validate_ipv4_multicast(addr: Ipv4Addr) -> Result<(), String> { + // Verify this is actually a multicast address + if !addr.is_multicast() { + return Err(format!("{} is not a multicast address", addr)); + } + + // Block link-local multicast (224.0.0.0/24) as it's reserved for local network control + let link_local = Ipv4Net::new( + RESERVED_IPV4_MULTICAST_LINK_LOCAL, + RESERVED_IPV4_MULTICAST_LINK_LOCAL_PREFIX, + ) + .unwrap(); + if link_local.contains(addr) { + return Err(format!( + "{addr} is in the link-local multicast range (224.0.0.0/24)" + )); + } + + Ok(()) +} + +/// Validates IPv6 multicast addresses. +fn validate_ipv6_multicast(addr: Ipv6Addr) -> Result<(), String> { + if !addr.is_multicast() { + return Err(format!("{addr} is not a multicast address")); + } + + // Define reserved IPv6 multicast subnets using oxnet + let reserved_subnets = [ + // Interface-local scope (ff01::/16) + Ipv6Net::new(Ipv6Addr::new(0xff01, 0, 0, 0, 0, 0, 0, 0), 16).unwrap(), + // Link-local scope (ff02::/16) + Ipv6Net::new(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0), 16).unwrap(), + ]; + + // Check reserved subnets + for subnet in &reserved_subnets { + if subnet.contains(addr) { + return Err(format!( + "{} is in the reserved multicast subnet {}", + addr, subnet + )); + } + } + + // Note: Admin-local scope (ff04::/16) is allowed for on-premises deployments. + // Collision avoidance with underlay addresses is handled by the mapping + // function which sets a collision-avoidance bit in the underlay space. + + Ok(()) +} + +const fn is_unicast_v4(ip: &Ipv4Addr) -> bool { + !ip.is_multicast() +} + +const fn is_unicast_v6(ip: &Ipv6Addr) -> bool { + !ip.is_multicast() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::latest::multicast::{ + MulticastGroupCreate, MulticastGroupUpdate, + }; + use omicron_common::api::external::Nullable; + use omicron_common::vlan::VlanID; + + #[test] + fn test_validate_multicast_ip_v4() { + // Valid IPv4 multicast addresses + assert!( + validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(224, 1, 0, 1))) + .is_ok() + ); + assert!( + validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(225, 2, 3, 4))) + .is_ok() + ); + assert!( + validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(231, 5, 6, 7))) + .is_ok() + ); + assert!( + validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(233, 1, 1, 1))) + .is_ok() + ); // GLOP addressing - allowed + assert!( + validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(239, 1, 1, 1))) + .is_ok() + ); // Admin-scoped - allowed + + // Invalid IPv4 multicast addresses - reserved ranges + assert!( + validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(224, 0, 0, 1))) + .is_err() + ); // Link-local control + assert!( + validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(224, 0, 0, 255))) + .is_err() + ); // Link-local control + + // Non-multicast addresses + assert!( + validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))) + .is_err() + ); + assert!( + validate_multicast_ip(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))) + .is_err() + ); + } + + #[test] + fn test_validate_multicast_ip_v6() { + // Valid IPv6 multicast addresses + assert!( + validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( + 0xff0e, 0, 0, 0, 0, 0, 0, 1 + ))) + .is_ok() + ); // Global scope + assert!( + validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( + 0xff0d, 0, 0, 0, 0, 0, 0, 1 + ))) + .is_ok() + ); // Site-local scope + assert!( + validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( + 0xff05, 0, 0, 0, 0, 0, 0, 1 + ))) + .is_ok() + ); // Site-local admin scope - allowed + assert!( + validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( + 0xff08, 0, 0, 0, 0, 0, 0, 1 + ))) + .is_ok() + ); // Org-local admin scope - allowed + + // Invalid IPv6 multicast addresses - reserved ranges + assert!( + validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( + 0xff01, 0, 0, 0, 0, 0, 0, 1 + ))) + .is_err() + ); // Interface-local + assert!( + validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( + 0xff02, 0, 0, 0, 0, 0, 0, 1 + ))) + .is_err() + ); // Link-local + + // Admin-local (ff04::/16) is allowed for on-premises deployments. + // Collision avoidance is handled by the mapping function which sets + // a collision-avoidance bit to separate external and underlay spaces. + assert!( + validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( + 0xff04, 0, 0, 0, 0, 0, 0, 1 + ))) + .is_ok() + ); + + // Non-multicast addresses + assert!( + validate_multicast_ip(IpAddr::V6(Ipv6Addr::new( + 0x2001, 0xdb8, 0, 0, 0, 0, 0, 1 + ))) + .is_err() + ); + } + + #[test] + fn test_validate_source_ip_v4() { + // Valid IPv4 source addresses + assert!( + validate_source_ip(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))) + .is_ok() + ); + assert!( + validate_source_ip(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))).is_ok() + ); + assert!( + validate_source_ip(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 1))) + .is_ok() + ); // TEST-NET-3 + + // Invalid IPv4 source addresses + assert!( + validate_source_ip(IpAddr::V4(Ipv4Addr::new(224, 1, 1, 1))) + .is_err() + ); // Multicast + assert!( + validate_source_ip(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))).is_err() + ); // Unspecified + assert!( + validate_source_ip(IpAddr::V4(Ipv4Addr::new(255, 255, 255, 255))) + .is_err() + ); // Broadcast + assert!( + validate_source_ip(IpAddr::V4(Ipv4Addr::new(169, 254, 1, 1))) + .is_err() + ); // Link-local + } + + #[test] + fn test_validate_source_ip_v6() { + // Valid IPv6 source addresses + assert!( + validate_source_ip(IpAddr::V6(Ipv6Addr::new( + 0x2001, 0xdb8, 0, 0, 0, 0, 0, 1 + ))) + .is_ok() + ); + assert!( + validate_source_ip(IpAddr::V6(Ipv6Addr::new( + 0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888 + ))) + .is_ok() + ); + + // Invalid IPv6 source addresses + assert!( + validate_source_ip(IpAddr::V6(Ipv6Addr::new( + 0xff0e, 0, 0, 0, 0, 0, 0, 1 + ))) + .is_err() + ); // Multicast + assert!( + validate_source_ip(IpAddr::V6(Ipv6Addr::new( + 0, 0, 0, 0, 0, 0, 0, 0 + ))) + .is_err() + ); // Unspecified + assert!( + validate_source_ip(IpAddr::V6(Ipv6Addr::new( + 0, 0, 0, 0, 0, 0, 0, 1 + ))) + .is_err() + ); // Loopback + } + + #[test] + fn test_multicast_group_create_deserialization_with_all_fields() { + let json = r#"{ + "name": "test-group", + "description": "Test multicast group", + "multicast_ip": "224.1.2.3", + "source_ips": ["10.0.0.1", "10.0.0.2"], + "pool": "default", + "mvlan": 10 + }"#; + + let result: Result = + serde_json::from_str(json); + assert!(result.is_ok()); + let params = result.unwrap(); + assert_eq!(params.identity.name.as_str(), "test-group"); + assert_eq!( + params.multicast_ip, + Some(IpAddr::V4(Ipv4Addr::new(224, 1, 2, 3))) + ); + assert_eq!( + params.source_ips, + Some(vec![ + IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), + IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)) + ]) + ); + } + + #[test] + fn test_multicast_group_create_deserialization_without_optional_fields() { + // This is the critical test - multicast_ip, source_ips, pool, and mvlan are all optional + let json = r#"{ + "name": "test-group", + "description": "Test multicast group" + }"#; + + let result: Result = + serde_json::from_str(json); + assert!( + result.is_ok(), + "Failed to deserialize without optional fields: {:?}", + result.err() + ); + let params = result.unwrap(); + assert_eq!(params.identity.name.as_str(), "test-group"); + assert_eq!(params.multicast_ip, None); + assert_eq!(params.source_ips, None); + assert_eq!(params.pool, None); + assert_eq!(params.mvlan, None); + } + + #[test] + fn test_multicast_group_create_deserialization_with_empty_source_ips() { + let json = r#"{ + "name": "test-group", + "description": "Test multicast group", + "multicast_ip": "224.1.2.3", + "source_ips": [] + }"#; + + let result: Result = + serde_json::from_str(json); + assert!(result.is_ok()); + let params = result.unwrap(); + assert_eq!(params.source_ips, Some(vec![])); + } + + #[test] + fn test_multicast_group_create_deserialization_invalid_multicast_ip() { + // Non-multicast IP should be rejected + let json = r#"{ + "name": "test-group", + "description": "Test multicast group", + "multicast_ip": "192.168.1.1" + }"#; + + let result: Result = + serde_json::from_str(json); + assert!(result.is_err()); + } + + #[test] + fn test_multicast_group_create_deserialization_invalid_source_ip() { + // Multicast address in source_ips should be rejected + let json = r#"{ + "name": "test-group", + "description": "Test multicast group", + "multicast_ip": "224.1.2.3", + "source_ips": ["224.0.0.1"] + }"#; + + let result: Result = + serde_json::from_str(json); + assert!(result.is_err()); + } + + #[test] + fn test_multicast_group_create_deserialization_only_multicast_ip() { + // Test with only multicast_ip, no source_ips + let json = r#"{ + "name": "test-group", + "description": "Test multicast group", + "multicast_ip": "224.1.2.3" + }"#; + + let result: Result = + serde_json::from_str(json); + assert!(result.is_ok()); + let params = result.unwrap(); + assert_eq!( + params.multicast_ip, + Some(IpAddr::V4(Ipv4Addr::new(224, 1, 2, 3))) + ); + assert_eq!(params.source_ips, None); + } + + #[test] + fn test_multicast_group_create_deserialization_only_source_ips() { + // Test with only source_ips, no multicast_ip (will be auto-allocated) + let json = r#"{ + "name": "test-group", + "description": "Test multicast group", + "source_ips": ["10.0.0.1"] + }"#; + + let result: Result = + serde_json::from_str(json); + assert!(result.is_ok()); + let params = result.unwrap(); + assert_eq!(params.multicast_ip, None); + assert_eq!( + params.source_ips, + Some(vec![IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))]) + ); + } + + #[test] + fn test_multicast_group_create_deserialization_explicit_null_fields() { + // Test with explicit null values for optional fields + // This is what the CLI sends when fields are not provided + let json = r#"{ + "name": "test-group", + "description": "Test multicast group", + "multicast_ip": null, + "source_ips": null, + "pool": null, + "mvlan": null + }"#; + + let result: Result = + serde_json::from_str(json); + assert!( + result.is_ok(), + "Failed to deserialize with explicit null fields: {:?}", + result.err() + ); + let params = result.unwrap(); + assert_eq!(params.multicast_ip, None); + assert_eq!(params.source_ips, None); + assert_eq!(params.pool, None); + assert_eq!(params.mvlan, None); + } + + #[test] + fn test_multicast_group_create_deserialization_mixed_null_and_values() { + // Test with some nulls and some values + let json = r#"{ + "name": "test-group", + "description": "Test multicast group", + "multicast_ip": "224.1.2.3", + "source_ips": [], + "pool": null, + "mvlan": 30 + }"#; + + let result: Result = + serde_json::from_str(json); + assert!(result.is_ok()); + let params = result.unwrap(); + assert_eq!( + params.multicast_ip, + Some(IpAddr::V4(Ipv4Addr::new(224, 1, 2, 3))) + ); + assert_eq!(params.source_ips, Some(vec![])); + assert_eq!(params.pool, None); + assert_eq!(params.mvlan, Some(VlanID::new(30).unwrap())); + } + + #[test] + fn test_multicast_group_update_deserialization_omit_all_fields() { + // When fields are omitted, they should be None (no change) + let json = r#"{ + "name": "test-group" + }"#; + + let result: Result = + serde_json::from_str(json); + assert!( + result.is_ok(), + "Failed to deserialize update with omitted fields: {:?}", + result.err() + ); + let params = result.unwrap(); + assert_eq!(params.source_ips, None); + assert_eq!(params.mvlan, None); + } + + #[test] + fn test_multicast_group_update_deserialization_explicit_null_mvlan() { + // When mvlan is explicitly null, it should be Some(Nullable(None)) (clearing the field) + let json = r#"{ + "name": "test-group", + "mvlan": null + }"#; + + let result: Result = + serde_json::from_str(json); + assert!( + result.is_ok(), + "Failed to deserialize update with null mvlan: {:?}", + result.err() + ); + let params = result.unwrap(); + assert_eq!(params.mvlan, Some(Nullable(None))); + } + + #[test] + fn test_multicast_group_update_deserialization_set_mvlan() { + // When mvlan has a value, it should be Some(Nullable(Some(value))) + let json = r#"{ + "name": "test-group", + "mvlan": 100 + }"#; + + let result: Result = + serde_json::from_str(json); + assert!(result.is_ok()); + let params = result.unwrap(); + assert_eq!( + params.mvlan, + Some(Nullable(Some(VlanID::new(100).unwrap()))) + ); + } + + #[test] + fn test_multicast_group_update_deserialization_update_source_ips() { + // Test updating source_ips + let json = r#"{ + "name": "test-group", + "source_ips": ["10.0.0.5", "10.0.0.6"] + }"#; + + let result: Result = + serde_json::from_str(json); + assert!(result.is_ok()); + let params = result.unwrap(); + assert_eq!( + params.source_ips, + Some(vec![ + IpAddr::V4(Ipv4Addr::new(10, 0, 0, 5)), + IpAddr::V4(Ipv4Addr::new(10, 0, 0, 6)) + ]) + ); + } + + #[test] + fn test_multicast_group_update_deserialization_clear_source_ips() { + // Empty array should clear source_ips (Any-Source Multicast) + let json = r#"{ + "name": "test-group", + "source_ips": [] + }"#; + + let result: Result = + serde_json::from_str(json); + assert!(result.is_ok()); + let params = result.unwrap(); + assert_eq!(params.source_ips, Some(vec![])); + } + + #[test] + fn test_multicast_group_update_deserialization_invalid_mvlan() { + // VLAN ID 1 should be rejected (reserved) + let json = r#"{ + "name": "test-group", + "mvlan": 1 + }"#; + + let result: Result = + serde_json::from_str(json); + assert!(result.is_err(), "Should reject reserved VLAN ID 1"); + } +} diff --git a/nexus/types/versions/src/impls/networking.rs b/nexus/types/versions/src/impls/networking.rs new file mode 100644 index 00000000000..79ed92ab854 --- /dev/null +++ b/nexus/types/versions/src/impls/networking.rs @@ -0,0 +1,21 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use crate::latest; +use oxnet::IpNet; + +impl From for latest::networking::AddressLotBlockCreate { + fn from(ipnet: IpNet) -> Self { + match ipnet { + IpNet::V4(v4) => Self { + first_address: v4.first_addr().into(), + last_address: v4.last_addr().into(), + }, + IpNet::V6(v6) => Self { + first_address: v6.first_addr().into(), + last_address: v6.last_addr().into(), + }, + } + } +} diff --git a/nexus/types/versions/src/impls/physical_disk.rs b/nexus/types/versions/src/impls/physical_disk.rs new file mode 100644 index 00000000000..0fc1ae215a4 --- /dev/null +++ b/nexus/types/versions/src/impls/physical_disk.rs @@ -0,0 +1,42 @@ +// 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/. + +//! Functional code for physical disk types. + +use crate::latest::physical_disk::{PhysicalDiskPolicy, PhysicalDiskState}; +use std::fmt; + +impl PhysicalDiskPolicy { + /// Creates a new `PhysicalDiskPolicy` that is in-service. + pub fn in_service() -> Self { + Self::InService + } + + /// Returns true if the disk can be decommissioned in this state. + pub fn is_decommissionable(&self) -> bool { + // This should be kept in sync with decommissionable_states below. + match self { + Self::InService => false, + Self::Expunged => true, + } + } +} + +impl fmt::Display for PhysicalDiskPolicy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PhysicalDiskPolicy::InService => write!(f, "in service"), + PhysicalDiskPolicy::Expunged => write!(f, "expunged"), + } + } +} + +impl fmt::Display for PhysicalDiskState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PhysicalDiskState::Active => write!(f, "active"), + PhysicalDiskState::Decommissioned => write!(f, "decommissioned"), + } + } +} diff --git a/nexus/types/versions/src/impls/policy.rs b/nexus/types/versions/src/impls/policy.rs new file mode 100644 index 00000000000..9dd2ab91174 --- /dev/null +++ b/nexus/types/versions/src/impls/policy.rs @@ -0,0 +1,49 @@ +// 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/. + +//! Functional code for policy types. + +#[cfg(test)] +mod tests { + use crate::latest::policy::{MAX_ROLE_ASSIGNMENTS_PER_RESOURCE, Policy}; + use serde::Deserialize; + + #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] + #[serde(rename_all = "kebab-case")] + pub enum DummyRoles { + Bogus, + } + + #[test] + fn test_policy_parsing() { + // Success case (edge case: max number of role assignments) + let role_assignment = serde_json::json!({ + "identity_type": "silo_user", + "identity_id": "75ec4a39-67cf-4549-9e74-44b92947c37c", + "role_name": "bogus" + }); + const MAX: usize = MAX_ROLE_ASSIGNMENTS_PER_RESOURCE; + let okay_input = + serde_json::Value::Array(vec![role_assignment.clone(); MAX]); + let policy: Policy = + serde_json::from_value(serde_json::json!({ + "role_assignments": okay_input + })) + .expect("unexpectedly failed with okay input"); + assert_eq!(policy.role_assignments[0].role_name, DummyRoles::Bogus); + + // Failure case: too many role assignments + let bad_input = + serde_json::Value::Array(vec![role_assignment; MAX + 1]); + let error = + serde_json::from_value::>(serde_json::json!({ + "role_assignments": bad_input + })) + .expect_err("unexpectedly succeeded with too many items"); + assert_eq!( + error.to_string(), + "invalid length 65, expected a list of at most 64 role assignments" + ); + } +} diff --git a/nexus/types/versions/src/impls/saml.rs b/nexus/types/versions/src/impls/saml.rs new file mode 100644 index 00000000000..2f6552533be --- /dev/null +++ b/nexus/types/versions/src/impls/saml.rs @@ -0,0 +1,63 @@ +// 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/. + +//! Functional code for SAML types. + +use crate::latest::saml::{RelativeUri, RelayState}; +use anyhow::Context; +use http::Uri; +use std::str::FromStr; + +impl FromStr for RelativeUri { + type Err = String; + + fn from_str(s: &str) -> Result { + Self::try_from(s.to_string()) + } +} + +impl TryFrom for RelativeUri { + type Error = String; + + fn try_from(uri: Uri) -> Result { + if uri.host().is_none() && uri.scheme().is_none() { + Ok(Self(uri.to_string())) + } else { + Err(format!("\"{}\" is not a relative URI", uri)) + } + } +} + +impl TryFrom for RelativeUri { + type Error = String; + + fn try_from(s: String) -> Result { + s.parse::() + .map_err(|_| format!("\"{}\" is not a relative URI", s)) + .and_then(|uri| Self::try_from(uri)) + } +} + +impl RelayState { + pub fn to_encoded(&self) -> Result { + Ok(base64::Engine::encode( + &base64::engine::general_purpose::STANDARD, + serde_json::to_string(&self).context("encoding relay state")?, + )) + } + + pub fn from_encoded(encoded: String) -> Result { + serde_json::from_str( + &String::from_utf8( + base64::Engine::decode( + &base64::engine::general_purpose::STANDARD, + encoded, + ) + .context("base64 decoding relay state")?, + ) + .context("creating relay state string")?, + ) + .context("json from relay state string") + } +} diff --git a/nexus/types/versions/src/impls/silo.rs b/nexus/types/versions/src/impls/silo.rs new file mode 100644 index 00000000000..11ed407e8f8 --- /dev/null +++ b/nexus/types/versions/src/impls/silo.rs @@ -0,0 +1,74 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Functional code for silo types. + +use crate::latest::silo::{ + AuthenticationMode, SiloIdentityMode, SiloQuotasCreate, SiloUtilization, + UserProvisionType, VirtualResourceCounts, +}; +use omicron_common::api::external::{ByteCount, Name, SimpleIdentityOrName}; +use uuid::Uuid; + +impl SiloIdentityMode { + pub fn authentication_mode(&self) -> AuthenticationMode { + match self { + SiloIdentityMode::LocalOnly => AuthenticationMode::Local, + SiloIdentityMode::SamlJit => AuthenticationMode::Saml, + SiloIdentityMode::SamlScim => AuthenticationMode::Saml, + } + } + + pub fn user_provision_type(&self) -> UserProvisionType { + match self { + SiloIdentityMode::LocalOnly => UserProvisionType::ApiOnly, + SiloIdentityMode::SamlJit => UserProvisionType::Jit, + SiloIdentityMode::SamlScim => UserProvisionType::Scim, + } + } +} + +// We want to be able to paginate SiloUtilization by NameOrId +// but we can't derive ObjectIdentity because this isn't a typical asset. +// Instead we implement this new simple identity trait which is used under the +// hood by the pagination code. +impl SimpleIdentityOrName for SiloUtilization { + fn id(&self) -> Uuid { + self.silo_id + } + fn name(&self) -> &Name { + &self.silo_name + } +} + +impl SiloQuotasCreate { + /// All quotas set to 0 + pub fn empty() -> Self { + Self { + cpus: 0, + memory: ByteCount::from(0), + storage: ByteCount::from(0), + } + } + + /// An arbitrarily high but identifiable default for quotas + /// that can be used for creating a Silo for testing + /// + /// The only silo that customers will see that this should be set on is the default + /// silo. Ultimately the default silo should only be initialized with an empty quota, + /// but as tests currently relying on it having a quota, we need to set something. + pub fn arbitrarily_high_default() -> Self { + Self { + cpus: 9999999999, + memory: ByteCount::try_from(999999999999999999_u64).unwrap(), + storage: ByteCount::try_from(999999999999999999_u64).unwrap(), + } + } +} + +impl From for VirtualResourceCounts { + fn from(quota: SiloQuotasCreate) -> Self { + Self { cpus: quota.cpus, memory: quota.memory, storage: quota.storage } + } +} diff --git a/nexus/types/versions/src/impls/sled.rs b/nexus/types/versions/src/impls/sled.rs new file mode 100644 index 00000000000..c7df0c9553f --- /dev/null +++ b/nexus/types/versions/src/impls/sled.rs @@ -0,0 +1,99 @@ +// 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/. + +//! Functional code for sled types. + +use crate::latest::sled::{SledPolicy, SledProvisionPolicy, SledState}; +use std::fmt; +use strum::IntoEnumIterator; + +impl SledProvisionPolicy { + /// Returns the opposite of the current provision state. + pub const fn invert(self) -> Self { + match self { + Self::Provisionable => Self::NonProvisionable, + Self::NonProvisionable => Self::Provisionable, + } + } +} + +// Can't automatically derive strum::EnumIter because that doesn't provide a +// way to iterate over nested enums. +impl IntoEnumIterator for SledPolicy { + type Iterator = std::array::IntoIter; + + fn iter() -> Self::Iterator { + [ + Self::InService { + provision_policy: SledProvisionPolicy::Provisionable, + }, + Self::InService { + provision_policy: SledProvisionPolicy::NonProvisionable, + }, + Self::Expunged, + ] + .into_iter() + } +} + +impl SledPolicy { + /// Creates a new `SledPolicy` that is in-service and provisionable. + pub fn provisionable() -> Self { + Self::InService { provision_policy: SledProvisionPolicy::Provisionable } + } + + /// Returns the provision policy, if the sled is in service. + pub fn provision_policy(&self) -> Option { + match self { + Self::InService { provision_policy } => Some(*provision_policy), + Self::Expunged => None, + } + } + + /// Returns true if the sled can be decommissioned with this policy + /// + /// This is a method here, rather than being a variant on `SledFilter`, + /// because the "decommissionable" condition only has meaning for policies, + /// not states. + pub fn is_decommissionable(&self) -> bool { + // This should be kept in sync with `all_decommissionable` below. + match self { + Self::InService { .. } => false, + Self::Expunged => true, + } + } + + /// Returns all the possible policies a sled can have for it to be + /// decommissioned. + /// + /// This is a method here, rather than being a variant on `SledFilter`, + /// because the "decommissionable" condition only has meaning for policies, + /// not states. + pub fn all_decommissionable() -> &'static [Self] { + &[Self::Expunged] + } +} + +impl fmt::Display for SledPolicy { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SledPolicy::InService { + provision_policy: SledProvisionPolicy::Provisionable, + } => write!(f, "in service"), + SledPolicy::InService { + provision_policy: SledProvisionPolicy::NonProvisionable, + } => write!(f, "not provisionable"), + SledPolicy::Expunged => write!(f, "expunged"), + } + } +} + +impl fmt::Display for SledState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SledState::Active => write!(f, "active"), + SledState::Decommissioned => write!(f, "decommissioned"), + } + } +} diff --git a/nexus/types/versions/src/impls/switch.rs b/nexus/types/versions/src/impls/switch.rs new file mode 100644 index 00000000000..fbfdd4dbe0c --- /dev/null +++ b/nexus/types/versions/src/impls/switch.rs @@ -0,0 +1,16 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Functional code for switch types. + +use crate::latest::switch::SwitchLinkState; + +impl SwitchLinkState { + pub fn new( + link: serde_json::Value, + monitors: Option, + ) -> Self { + Self { link, monitors } + } +} diff --git a/nexus/types/versions/src/impls/user.rs b/nexus/types/versions/src/impls/user.rs new file mode 100644 index 00000000000..8774aec2844 --- /dev/null +++ b/nexus/types/versions/src/impls/user.rs @@ -0,0 +1,30 @@ +// 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/. + +//! Functional code for user types. + +use crate::latest::user::Password; +use std::str::FromStr; + +impl FromStr for Password { + type Err = String; + fn from_str(value: &str) -> Result { + Password::try_from(String::from(value)) + } +} + +impl TryFrom for Password { + type Error = String; + fn try_from(value: String) -> Result { + let inner = omicron_passwords::Password::new(&value) + .map_err(|e| format!("unsupported password: {:#}", e))?; + Ok(Password(inner)) + } +} + +impl AsRef for Password { + fn as_ref(&self) -> &omicron_passwords::Password { + &self.0 + } +} diff --git a/nexus/types/versions/src/initial/affinity.rs b/nexus/types/versions/src/initial/affinity.rs new file mode 100644 index 00000000000..c3e1859c2b3 --- /dev/null +++ b/nexus/types/versions/src/initial/affinity.rs @@ -0,0 +1,97 @@ +// 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/. + +//! Affinity and anti-affinity group types for version INITIAL. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + AffinityPolicy, FailureDomain, IdentityMetadata, + IdentityMetadataCreateParams, IdentityMetadataUpdateParams, NameOrId, + ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// View of an Affinity Group +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AffinityGroup { + #[serde(flatten)] + pub identity: IdentityMetadata, + pub project_id: Uuid, + pub policy: AffinityPolicy, + pub failure_domain: FailureDomain, +} + +/// View of an Anti-Affinity Group +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AntiAffinityGroup { + #[serde(flatten)] + pub identity: IdentityMetadata, + pub project_id: Uuid, + pub policy: AffinityPolicy, + pub failure_domain: FailureDomain, +} + +/// Create-time parameters for an `AffinityGroup` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AffinityGroupCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + pub policy: AffinityPolicy, + pub failure_domain: FailureDomain, +} + +/// Updateable properties of an `AffinityGroup` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AffinityGroupUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, +} + +/// Create-time parameters for an `AntiAffinityGroup` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AntiAffinityGroupCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + pub policy: AffinityPolicy, + pub failure_domain: FailureDomain, +} + +/// Updateable properties of an `AntiAffinityGroup` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AntiAffinityGroupUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, +} + +#[derive(Deserialize, JsonSchema, Clone)] +pub struct AffinityGroupSelector { + /// Name or ID of the project, only required if `affinity_group` is provided as a `Name` + pub project: Option, + /// Name or ID of the Affinity Group + pub affinity_group: NameOrId, +} + +#[derive(Deserialize, JsonSchema, Clone)] +pub struct AntiAffinityGroupSelector { + /// Name or ID of the project, only required if `anti_affinity_group` is provided as a `Name` + pub project: Option, + /// Name or ID of the Anti Affinity Group + pub anti_affinity_group: NameOrId, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct AffinityInstanceGroupMemberPath { + pub affinity_group: NameOrId, + pub instance: NameOrId, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct AntiAffinityInstanceGroupMemberPath { + pub anti_affinity_group: NameOrId, + pub instance: NameOrId, +} diff --git a/nexus/types/versions/src/initial/alert.rs b/nexus/types/versions/src/initial/alert.rs new file mode 100644 index 00000000000..fe5f3635c7c --- /dev/null +++ b/nexus/types/versions/src/initial/alert.rs @@ -0,0 +1,470 @@ +// 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/. + +//! Alert/webhook types for version INITIAL. + +use api_identity::ObjectIdentity; +use chrono::{DateTime, Utc}; +use omicron_common::api::external::{IdentityMetadata, ObjectIdentity}; +use omicron_uuid_kinds::*; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use url::Url; +use uuid::Uuid; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(try_from = "String")] +#[serde(into = "String")] +pub struct AlertSubscription(pub(crate) String); + +impl AlertSubscription { + pub(crate) const PATTERN: &str = + r"^([a-zA-Z0-9_]+|\*|\*\*)(\.([a-zA-Z0-9_]+|\*|\*\*))*$"; +} + +impl schemars::JsonSchema for AlertSubscription { + fn schema_name() -> String { + "AlertSubscription".to_string() + } + + fn json_schema( + _: &mut schemars::r#gen::SchemaGenerator, + ) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + metadata: Some(Box::new(schemars::schema::Metadata { + title: Some("A webhook event class subscription".to_string()), + description: Some( + "A webhook event class subscription matches either a single event class exactly, or a glob pattern including wildcards that may match multiple event classes" + .to_string(), + ), + ..Default::default() + })), + instance_type: Some(schemars::schema::InstanceType::String.into()), + string: Some(Box::new(schemars::schema::StringValidation { + max_length: None, + min_length: None, + pattern: Some(AlertSubscription::PATTERN.to_string()), + })), + ..Default::default() + } + .into() + } +} + +// ALERTS + +/// An alert class. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AlertClass { + /// The name of the alert class. + pub name: String, + + /// A description of what this alert class represents. + pub description: String, +} + +/// The configuration for an alert receiver. +#[derive( + ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, +)] +pub struct AlertReceiver { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// The list of alert classes to which this receiver is subscribed. + pub subscriptions: Vec, + + /// Configuration specific to the kind of alert receiver that this is. + pub kind: AlertReceiverKind, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AlertSubscriptionCreated { + /// The new subscription added to the receiver. + pub subscription: AlertSubscription, +} + +/// The possible alert delivery mechanisms for an alert receiver. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +#[serde(rename_all = "snake_case", tag = "kind")] +pub enum AlertReceiverKind { + Webhook(WebhookReceiverConfig), +} + +/// The configuration for a webhook alert receiver. +#[derive( + ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, +)] +pub struct WebhookReceiver { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// The list of alert classes to which this receiver is subscribed. + pub subscriptions: Vec, + + #[serde(flatten)] + pub config: WebhookReceiverConfig, +} + +impl From for AlertReceiver { + fn from( + WebhookReceiver { identity, subscriptions, config }: WebhookReceiver, + ) -> Self { + Self { + identity, + subscriptions, + kind: AlertReceiverKind::Webhook(config), + } + } +} + +impl PartialEq for AlertReceiver { + fn eq(&self, other: &WebhookReceiver) -> bool { + // Will become refutable if/when more variants are added... + #[allow(irrefutable_let_patterns)] + let AlertReceiverKind::Webhook(ref config) = self.kind else { + return false; + }; + self.identity == other.identity + && self.subscriptions == other.subscriptions + && config == &other.config + } +} + +impl PartialEq for WebhookReceiver { + fn eq(&self, other: &AlertReceiver) -> bool { + // Will become refutable if/when more variants are added... + #[allow(irrefutable_let_patterns)] + let AlertReceiverKind::Webhook(ref config) = other.kind else { + return false; + }; + self.identity == other.identity + && self.subscriptions == other.subscriptions + && &self.config == config + } +} + +/// Webhook-specific alert receiver configuration. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct WebhookReceiverConfig { + /// The URL that webhook notification requests are sent to. + pub endpoint: Url, + /// A list containing the IDs of the secret keys used to sign payloads sent + /// to this receiver. + pub secrets: Vec, +} + +/// A list of the IDs of secrets associated with a webhook receiver. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct WebhookSecrets { + pub secrets: Vec, +} + +/// A view of a shared secret key assigned to a webhook receiver. +/// +/// Once a secret is created, the value of the secret is not available in the +/// API, as it must remain secret. Instead, secrets are referenced by their +/// unique IDs assigned when they are created. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct WebhookSecret { + /// The public unique ID of the secret. + pub id: Uuid, + + /// The UTC timestamp at which this secret was created. + pub time_created: DateTime, +} + +/// A delivery of a webhook event. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +pub struct AlertDelivery { + /// The UUID of this delivery attempt. + pub id: Uuid, + + /// The UUID of the alert receiver that this event was delivered to. + #[schemars(with = "Uuid")] + pub receiver_id: AlertReceiverUuid, + + /// The event class. + pub alert_class: String, + + /// The UUID of the event. + #[schemars(with = "Uuid")] + pub alert_id: AlertUuid, + + /// The state of this delivery. + pub state: AlertDeliveryState, + + /// Why this delivery was performed. + pub trigger: AlertDeliveryTrigger, + + /// Individual attempts to deliver this webhook event, and their outcomes. + pub attempts: AlertDeliveryAttempts, + + /// The time at which this delivery began (i.e. the event was dispatched to + /// the receiver). + pub time_started: DateTime, +} + +/// The state of a webhook delivery attempt. +#[derive( + Copy, + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + JsonSchema, + strum::VariantArray, +)] +#[serde(rename_all = "snake_case")] +pub enum AlertDeliveryState { + /// The webhook event has not yet been delivered successfully. + /// + /// Either no delivery attempts have yet been performed, or the delivery has + /// failed at least once but has retries remaining. + Pending, + /// The webhook event has been delivered successfully. + Delivered, + /// The webhook delivery attempt has failed permanently and will not be + /// retried again. + Failed, +} + +/// The reason an alert was delivered +#[derive( + Copy, + Clone, + Debug, + Eq, + PartialEq, + Deserialize, + Serialize, + JsonSchema, + strum::VariantArray, +)] +#[serde(rename_all = "snake_case")] +pub enum AlertDeliveryTrigger { + /// Delivery was triggered by the alert itself. + Alert, + /// Delivery was triggered by a request to resend the alert. + Resend, + /// This delivery is a liveness probe. + Probe, +} + +/// A list of attempts to deliver an alert to a receiver. +/// +/// The type of the delivery attempt model depends on the receiver type, as it +/// may contain information specific to that delivery mechanism. For example, +/// webhook delivery attempts contain the HTTP status code of the webhook +/// request. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AlertDeliveryAttempts { + /// A list of attempts to deliver an alert to a webhook receiver. + Webhook(Vec), +} + +/// An individual delivery attempt for a webhook event. +/// +/// This represents a single HTTP request that was sent to the receiver, and its +/// outcome. +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +pub struct WebhookDeliveryAttempt { + /// The time at which the webhook delivery was attempted. + pub time_sent: DateTime, + + /// The attempt number. + pub attempt: usize, + + /// The outcome of this delivery attempt: either the event was delivered + /// successfully, or the request failed for one of several reasons. + pub result: WebhookDeliveryAttemptResult, + + pub response: Option, +} + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Deserialize, + Serialize, + JsonSchema, + strum::VariantArray, +)] +#[serde(rename_all = "snake_case")] +pub enum WebhookDeliveryAttemptResult { + /// The webhook event has been delivered successfully. + Succeeded, + /// A webhook request was sent to the endpoint, and it + /// returned a HTTP error status code indicating an error. + FailedHttpError, + /// The webhook request could not be sent to the receiver endpoint. + FailedUnreachable, + /// A connection to the receiver endpoint was successfully established, but + /// no response was received within the delivery timeout. + FailedTimeout, +} + +/// The response received from a webhook receiver endpoint. +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, JsonSchema)] +pub struct WebhookDeliveryResponse { + /// The HTTP status code returned from the webhook endpoint. + pub status: u16, + /// The response time of the webhook endpoint, in milliseconds. + pub duration_ms: usize, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, JsonSchema)] +pub struct AlertDeliveryId { + pub delivery_id: Uuid, +} + +/// Data describing the result of an alert receiver liveness probe attempt. +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize, JsonSchema)] +pub struct AlertProbeResult { + /// The outcome of the probe delivery. + pub probe: AlertDelivery, + /// If the probe request succeeded, and resending failed deliveries on + /// success was requested, the number of new delivery attempts started. + /// Otherwise, if the probe did not succeed, or resending failed deliveries + /// was not requested, this is null. + /// + /// Note that this may be 0, if there were no events found which had not + /// been delivered successfully to this receiver. + pub resends_started: Option, +} + +// ALERT PARAMS + +use omicron_common::api::external::{ + IdentityMetadataCreateParams, IdentityMetadataUpdateParams, NameOrId, +}; + +/// Query params for listing alert classes. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AlertClassFilter { + /// An optional glob pattern for filtering alert class names. + /// + /// If provided, only alert classes which match this glob pattern will be + /// included in the response. + pub filter: Option, +} + +#[derive(Deserialize, JsonSchema)] +pub struct AlertSelector { + /// UUID of the alert + pub alert_id: Uuid, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AlertSubscriptionSelector { + /// The webhook receiver that the subscription is attached to. + #[serde(flatten)] + pub receiver: AlertReceiverSelector, + /// The event class subscription itself. + pub subscription: AlertSubscription, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AlertClassPage { + /// The last webhook event class returned by a previous page. + pub last_seen: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AlertReceiverSelector { + /// The name or ID of the webhook receiver. + pub receiver: NameOrId, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct WebhookCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// The URL that webhook notification requests should be sent to + pub endpoint: Url, + + /// A non-empty list of secret keys used to sign webhook payloads. + pub secrets: Vec, + + /// A list of webhook event class subscriptions. + /// + /// If this list is empty or is not included in the request body, the + /// webhook will not be subscribed to any events. + #[serde(default)] + pub subscriptions: Vec, +} + +/// Parameters to update a webhook configuration. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct WebhookReceiverUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, + + /// The URL that webhook notification requests should be sent to + pub endpoint: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AlertSubscriptionCreate { + /// The event class pattern to subscribe to. + pub subscription: AlertSubscription, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct WebhookSecretCreate { + /// The value of the shared secret key. + pub secret: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct WebhookSecretSelector { + /// ID of the secret. + pub secret_id: Uuid, +} + +#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AlertDeliveryStateFilter { + /// If true, include deliveries which are currently in progress. + /// + /// If any of the "pending", "failed", or "delivered" query parameters are + /// set to true, only deliveries matching those state(s) will be included in + /// the response. If NO state filter parameters are set, then all deliveries + /// are included. + /// + /// A delivery is considered "pending" if it has not yet been sent at all, + /// or if a delivery attempt has failed but the delivery has retries + /// remaining. + pub pending: Option, + /// If true, include deliveries which have failed permanently. + /// + /// If any of the "pending", "failed", or "delivered" query parameters are + /// set to true, only deliveries matching those state(s) will be included in + /// the response. If NO state filter parameters are set, then all deliveries + /// are included. + /// + /// A delivery fails permanently when the retry limit of three total + /// attempts is reached without a successful delivery. + pub failed: Option, + /// If true, include deliveries which have succeeded. + /// + /// If any of the "pending", "failed", or "delivered" query parameters are + /// set to true, only deliveries matching those state(s) will be included in + /// the response. If NO state filter parameters are set, then all deliveries + /// are included. + pub delivered: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AlertReceiverProbe { + /// If true, resend all events that have not been delivered successfully if + /// the probe request succeeds. + #[serde(default)] + pub resend: bool, +} diff --git a/nexus/types/versions/src/initial/asset.rs b/nexus/types/versions/src/initial/asset.rs new file mode 100644 index 00000000000..64de56f39f9 --- /dev/null +++ b/nexus/types/versions/src/initial/asset.rs @@ -0,0 +1,22 @@ +// 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/. + +//! Asset identity types for version INITIAL. + +use chrono::{DateTime, Utc}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// Identity-related metadata that's included in "asset" public API objects +/// (which generally have no name or description) +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] +pub struct AssetIdentityMetadata { + /// unique, immutable, system-controlled identifier for each resource + pub id: Uuid, + /// timestamp when this resource was created + pub time_created: DateTime, + /// timestamp when this resource was last modified + pub time_modified: DateTime, +} diff --git a/nexus/types/versions/src/initial/audit.rs b/nexus/types/versions/src/initial/audit.rs new file mode 100644 index 00000000000..2dd091622c6 --- /dev/null +++ b/nexus/types/versions/src/initial/audit.rs @@ -0,0 +1,110 @@ +// 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/. + +//! Audit log types for version INITIAL. + +use chrono::{DateTime, Utc}; +use omicron_uuid_kinds::*; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; +use uuid::Uuid; + +#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum AuditLogEntryActor { + UserBuiltin { + #[schemars(with = "Uuid")] + user_builtin_id: BuiltInUserUuid, + }, + + SiloUser { + #[schemars(with = "Uuid")] + silo_user_id: SiloUserUuid, + + silo_id: Uuid, + }, + + Scim { + silo_id: Uuid, + }, + + Unauthenticated, +} + +/// Result of an audit log entry +#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum AuditLogEntryResult { + /// The operation completed successfully + Success { + /// HTTP status code + http_status_code: u16, + }, + /// The operation failed + Error { + /// HTTP status code + http_status_code: u16, + error_code: Option, + error_message: String, + }, + // Note that the DB model result kind analogous to Unknown is called Timeout + // -- The name "Timeout" feels useful to write down for the DB but also + // feels like too much of an implementation detail to expose to the user -- + // it makes it sounds like the operation timed out rather than the audit log + // entry itself. + /// After the logged operation completed, our attempt to write the result + /// to the audit log failed, so it was automatically marked completed later + /// by a background job. This does not imply that the operation itself timed + /// out or failed, only our attempts to log its result. + Unknown, +} + +/// Audit log entry +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct AuditLogEntry { + /// Unique identifier for the audit log entry + pub id: Uuid, + + /// When the request was received + pub time_started: DateTime, + + /// Request ID for tracing requests through the system + pub request_id: String, + /// URI of the request, truncated to 512 characters. Will only include host + /// and scheme for HTTP/2 requests. For HTTP/1.1, the URI will consist of + /// only the path and query. + pub request_uri: String, + /// API endpoint ID, e.g., `project_create` + pub operation_id: String, + /// IP address that made the request + pub source_ip: IpAddr, + /// User agent string from the request, truncated to 256 characters. + pub user_agent: Option, + + pub actor: AuditLogEntryActor, + + /// How the user authenticated the request. Possible values are + /// "session_cookie" and "access_token". Optional because it will not be + /// defined on unauthenticated requests like login attempts. + pub auth_method: Option, + + // Fields that are optional because they get filled in after the action completes + /// Time operation completed + pub time_completed: DateTime, + + /// Result of the operation + pub result: AuditLogEntryResult, +} + +// AUDIT PARAMS + +/// Audit log has its own pagination scheme because it paginates by timestamp. +#[derive(Deserialize, JsonSchema, Serialize, PartialEq, Debug, Clone)] +pub struct AuditLogParams { + /// Required, inclusive + pub start_time: DateTime, + /// Exclusive + pub end_time: Option>, +} diff --git a/nexus/types/versions/src/initial/bfd.rs b/nexus/types/versions/src/initial/bfd.rs new file mode 100644 index 00000000000..2270323b230 --- /dev/null +++ b/nexus/types/versions/src/initial/bfd.rs @@ -0,0 +1,61 @@ +// 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/. + +//! BFD (Bidirectional Forwarding Detection) types for the Nexus external API. + +use std::net::IpAddr; + +use omicron_common::api::external::BfdMode; +use omicron_common::api::external::Name; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; + +#[derive( + Clone, + Debug, + Serialize, + Deserialize, + JsonSchema, + PartialOrd, + Ord, + PartialEq, + Eq, +)] +#[serde(rename_all = "snake_case")] +pub enum BfdState { + /// A stable down state. Non-responsive to incoming messages. + AdminDown = 0, + + /// The initial state. + Down = 1, + + /// The peer has detected a remote peer in the down state. + Init = 2, + + /// The peer has detected a remote peer in the up or init state while in the + /// init state. + Up = 3, +} + +#[derive( + Clone, + Debug, + Serialize, + Deserialize, + JsonSchema, + PartialOrd, + Ord, + PartialEq, + Eq, +)] +pub struct BfdStatus { + pub peer: IpAddr, + pub state: BfdState, + pub switch: Name, + pub local: Option, + pub detection_threshold: u8, + pub required_rx: u64, + pub mode: BfdMode, +} diff --git a/nexus/types/versions/src/initial/certificate.rs b/nexus/types/versions/src/initial/certificate.rs new file mode 100644 index 00000000000..2160acbd5bd --- /dev/null +++ b/nexus/types/versions/src/initial/certificate.rs @@ -0,0 +1,55 @@ +// 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/. + +//! Certificate types for version INITIAL. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + IdentityMetadata, IdentityMetadataCreateParams, ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// The service intended to use this certificate. +#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum ServiceUsingCertificate { + /// This certificate is intended for access to the external API. + ExternalApi, +} + +/// View of a Certificate +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Certificate { + #[serde(flatten)] + pub identity: IdentityMetadata, + /// The service using this certificate + pub service: ServiceUsingCertificate, + /// PEM-formatted string containing public certificate chain + pub cert: String, +} + +/// Create-time parameters for a `Certificate` +#[derive(Clone, Deserialize, Serialize, JsonSchema)] +pub struct CertificateCreate { + /// common identifying metadata + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// PEM-formatted string containing public certificate chain + pub cert: String, + /// PEM-formatted string containing private key + pub key: String, + /// The service using this certificate + pub service: ServiceUsingCertificate, +} + +impl std::fmt::Debug for CertificateCreate { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CertificateCreate") + .field("identity", &self.identity) + .field("cert", &self.cert) + .field("key", &"") + .finish() + } +} diff --git a/nexus/types/versions/src/initial/console.rs b/nexus/types/versions/src/initial/console.rs new file mode 100644 index 00000000000..54ec1a179b1 --- /dev/null +++ b/nexus/types/versions/src/initial/console.rs @@ -0,0 +1,31 @@ +// 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/. + +//! Console API parameters for the Nexus external API. + +use super::saml::RelativeUri; +use omicron_common::api::external::Name; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, JsonSchema)] +pub struct RestPathParam { + pub path: Vec, +} + +#[derive(Deserialize, JsonSchema)] +pub struct LoginToProviderPathParam { + pub silo_name: Name, + pub provider_name: Name, +} + +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct LoginUrlQuery { + pub redirect_uri: Option, +} + +#[derive(Deserialize, JsonSchema)] +pub struct LoginPath { + pub silo_name: Name, +} diff --git a/nexus/types/versions/src/initial/device.rs b/nexus/types/versions/src/initial/device.rs new file mode 100644 index 00000000000..038c82b7e82 --- /dev/null +++ b/nexus/types/versions/src/initial/device.rs @@ -0,0 +1,90 @@ +// 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/. + +//! Device and session types for version INITIAL. + +use chrono::{DateTime, Utc}; +use omicron_common::api::external::SimpleIdentity; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// View of a device access token +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct DeviceAccessToken { + /// A unique, immutable, system-controlled identifier for the token. + /// + /// Note that this ID is not the bearer token itself, which starts with + /// "oxide-token-". + pub id: Uuid, + pub time_created: DateTime, + + /// Expiration timestamp. A null value means the token does not automatically expire. + pub time_expires: Option>, +} + +impl SimpleIdentity for DeviceAccessToken { + fn id(&self) -> Uuid { + self.id + } +} + +/// View of a console session +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct ConsoleSession { + /// A unique, immutable, system-controlled identifier for the session + pub id: Uuid, + pub time_created: DateTime, + pub time_last_used: DateTime, +} + +impl SimpleIdentity for ConsoleSession { + fn id(&self) -> Uuid { + self.id + } +} + +// OAUTH 2.0 DEVICE AUTHORIZATION REQUESTS & TOKENS + +/// Response to an initial device authorization request. +/// See RFC 8628 §3.2 (Device Authorization Response). +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct DeviceAuthResponse { + /// The device verification code + pub device_code: String, + + /// The end-user verification code + pub user_code: String, + + /// The end-user verification URI on the authorization server. + /// The URI should be short and easy to remember as end users + /// may be asked to manually type it into their user agent. + pub verification_uri: String, + + /// The lifetime in seconds of the `device_code` and `user_code` + pub expires_in: u16, +} + +/// Successful access token grant. See RFC 6749 §5.1. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct DeviceAccessTokenGrant { + /// The access token issued to the client + pub access_token: String, + + /// The type of the token issued, as described in RFC 6749 §7.1. + pub token_type: DeviceAccessTokenType, + + /// A unique, immutable, system-controlled identifier for the token + pub token_id: Uuid, + + /// Expiration timestamp. A null value means the token does not automatically expire. + pub time_expires: Option>, +} + +/// The kind of token granted. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum DeviceAccessTokenType { + Bearer, +} diff --git a/nexus/types/versions/src/initial/device_params.rs b/nexus/types/versions/src/initial/device_params.rs new file mode 100644 index 00000000000..7f6e7000d43 --- /dev/null +++ b/nexus/types/versions/src/initial/device_params.rs @@ -0,0 +1,40 @@ +// 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/. + +//! Device authentication parameters for the Nexus external API. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::num::NonZeroU32; +use uuid::Uuid; + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct DeviceAuthRequest { + pub client_id: Uuid, + /// Optional lifetime for the access token in seconds. + /// + /// This value will be validated during the confirmation step. If not + /// specified, it defaults to the silo's max TTL, which can be seen at + /// `/v1/auth-settings`. If specified, must not exceed the silo's max TTL. + /// + /// Some special logic applies when authenticating the confirmation request + /// with an existing device token: the requested TTL must not produce an + /// expiration time later than the authenticating token's expiration. If no + /// TTL is specified, the expiration will be the lesser of the silo max and + /// the authenticating token's expiration time. To get the longest allowed + /// lifetime, omit the TTL and authenticate with a web console session. + pub ttl_seconds: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct DeviceAuthVerify { + pub user_code: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct DeviceAccessTokenRequest { + pub grant_type: String, + pub device_code: String, + pub client_id: Uuid, +} diff --git a/nexus/types/versions/src/initial/disk.rs b/nexus/types/versions/src/initial/disk.rs new file mode 100644 index 00000000000..7148a7fedf4 --- /dev/null +++ b/nexus/types/versions/src/initial/disk.rs @@ -0,0 +1,153 @@ +// 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/. + +//! Disk types for version INITIAL. + +use omicron_common::api::external::{ + ByteCount, DiskState, IdentityMetadata, IdentityMetadataCreateParams, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Copy, Clone, Debug, Deserialize, Serialize)] +#[serde(try_from = "u32")] +pub struct BlockSize(pub u32); + +impl schemars::JsonSchema for BlockSize { + fn schema_name() -> String { + "BlockSize".to_string() + } + + fn json_schema( + _: &mut schemars::r#gen::SchemaGenerator, + ) -> schemars::schema::Schema { + schemars::schema::Schema::Object(schemars::schema::SchemaObject { + metadata: Some(Box::new(schemars::schema::Metadata { + id: None, + title: Some("Disk block size in bytes".to_string()), + ..Default::default() + })), + instance_type: Some(schemars::schema::InstanceType::Integer.into()), + enum_values: Some(vec![ + serde_json::json!(512), + serde_json::json!(2048), + serde_json::json!(4096), + ]), + ..Default::default() + }) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum DiskType { + Crucible, +} + +/// View of a Disk +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Disk { + #[serde(flatten)] + pub identity: IdentityMetadata, + pub project_id: Uuid, + /// ID of snapshot from which disk was created, if any + pub snapshot_id: Option, + /// ID of image from which disk was created, if any + pub image_id: Option, + pub size: ByteCount, + pub block_size: ByteCount, + pub state: DiskState, + pub device_path: String, + pub disk_type: DiskType, +} + +/// Different sources for a disk +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum DiskSource { + /// Create a blank disk + Blank { + /// Size of blocks for this disk. Valid values are: 512, 2048, or 4096. + block_size: BlockSize, + }, + + /// Create a disk from a disk snapshot + Snapshot { snapshot_id: Uuid }, + + /// Create a disk from an image + Image { image_id: Uuid }, + + /// Create a blank disk that will accept bulk writes or pull blocks from an + /// external source. + ImportingBlocks { block_size: BlockSize }, +} + +/// Create-time parameters for a `Disk` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct DiskCreate { + /// The common identifying metadata for the disk + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// The initial source for this disk + pub disk_source: DiskSource, + + /// The total size of the Disk (in bytes) + pub size: ByteCount, +} + +use omicron_common::api::external::{Name, NameOrId}; +use parse_display::Display; + +#[derive(Deserialize, JsonSchema)] +pub struct DiskSelector { + /// Name or ID of the project, only required if `disk` is provided as a `Name` + pub project: Option, + /// Name or ID of the disk + pub disk: NameOrId, +} + +#[derive(Display, Serialize, Deserialize, JsonSchema)] +#[display(style = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum DiskMetricName { + Activated, + Flush, + Read, + ReadBytes, + Write, + WriteBytes, +} + +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct DiskMetricsPath { + pub disk: NameOrId, + pub metric: DiskMetricName, +} + +// equivalent to crucible_pantry_client::types::ExpectedDigest +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExpectedDigest { + Sha256(String), +} + +/// Parameters for importing blocks with a bulk write +// equivalent to crucible_pantry_client::types::BulkWriteRequest +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct ImportBlocksBulkWrite { + pub offset: u64, + pub base64_encoded_data: String, +} + +/// Parameters for finalizing a disk +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct FinalizeDisk { + /// If specified a snapshot of the disk will be created with the given name + /// during finalization. If not specified, a snapshot for the disk will + /// _not_ be created. A snapshot can be manually created once the disk + /// transitions into the `Detached` state. + pub snapshot_name: Option, +} diff --git a/nexus/types/versions/src/initial/external_ip.rs b/nexus/types/versions/src/initial/external_ip.rs new file mode 100644 index 00000000000..d6cbd1269b0 --- /dev/null +++ b/nexus/types/versions/src/initial/external_ip.rs @@ -0,0 +1,90 @@ +// 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/. + +//! External IP types for version INITIAL. + +use omicron_common::api::external::Error; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; +use uuid::Uuid; + +use super::floating_ip::FloatingIp; + +/// The kind of an external IP address for an instance +#[derive( + Debug, Clone, Copy, Deserialize, Eq, Serialize, JsonSchema, PartialEq, +)] +#[serde(rename_all = "snake_case")] +pub enum IpKind { + SNat, + Ephemeral, + Floating, +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum ExternalIp { + #[serde(rename = "snat")] + SNat(SNatIp), + Ephemeral { + ip: IpAddr, + ip_pool_id: Uuid, + }, + Floating(FloatingIp), +} + +impl ExternalIp { + pub fn ip(&self) -> IpAddr { + match self { + Self::SNat(snat) => snat.ip, + Self::Ephemeral { ip, .. } => *ip, + Self::Floating(float) => float.ip, + } + } + + pub fn kind(&self) -> IpKind { + match self { + Self::SNat(_) => IpKind::SNat, + Self::Ephemeral { .. } => IpKind::Ephemeral, + Self::Floating(_) => IpKind::Floating, + } + } +} + +/// A source NAT IP address. +/// +/// SNAT addresses are ephemeral addresses used only for outbound connectivity. +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema)] +pub struct SNatIp { + /// The IP address. + pub ip: IpAddr, + /// The first usable port within the IP address. + pub first_port: u16, + /// The last usable port within the IP address. + pub last_port: u16, + /// ID of the IP Pool from which the address is taken. + pub ip_pool_id: Uuid, +} + +impl From for ExternalIp { + fn from(value: FloatingIp) -> Self { + ExternalIp::Floating(value) + } +} + +impl TryFrom for FloatingIp { + type Error = Error; + + fn try_from(value: ExternalIp) -> Result { + match value { + ExternalIp::SNat(_) | ExternalIp::Ephemeral { .. } => { + Err(Error::internal_error( + "tried to convert an SNAT or ephemeral IP into a floating IP", + )) + } + ExternalIp::Floating(v) => Ok(v), + } + } +} diff --git a/nexus/types/versions/src/initial/floating_ip.rs b/nexus/types/versions/src/initial/floating_ip.rs new file mode 100644 index 00000000000..b2a43d173b6 --- /dev/null +++ b/nexus/types/versions/src/initial/floating_ip.rs @@ -0,0 +1,82 @@ +// 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/. + +//! Floating IP types for version INITIAL. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + IdentityMetadata, IdentityMetadataCreateParams, + IdentityMetadataUpdateParams, NameOrId, ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; +use uuid::Uuid; + +/// A Floating IP is a well-known IP address which can be attached +/// and detached from instances. +#[derive( + ObjectIdentity, Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub struct FloatingIp { + #[serde(flatten)] + pub identity: IdentityMetadata, + /// The IP address held by this resource. + pub ip: IpAddr, + /// The ID of the IP pool this resource belongs to. + pub ip_pool_id: Uuid, + /// The project this resource exists within. + pub project_id: Uuid, + /// The ID of the instance that this Floating IP is attached to, + /// if it is presently in use. + pub instance_id: Option, +} + +#[derive(Deserialize, JsonSchema, Clone)] +pub struct FloatingIpSelector { + /// Name or ID of the project, only required if `floating_ip` is provided as a `Name` + pub project: Option, + /// Name or ID of the Floating IP + pub floating_ip: NameOrId, +} + +/// Parameters for creating a new floating IP address for instances. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct FloatingIpCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// An IP address to reserve for use as a floating IP. This field is + /// optional: when not set, an address will be automatically chosen from + /// `pool`. If set, then the IP must be available in the resolved `pool`. + pub ip: Option, + + /// The parent IP pool that a floating IP is pulled from. If unset, the + /// default pool is selected. + pub pool: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct FloatingIpUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, +} + +/// The type of resource that a floating IP is attached to +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum FloatingIpParentKind { + Instance, +} + +/// Parameters for attaching a floating IP address to another resource +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct FloatingIpAttach { + /// Name or ID of the resource that this IP address should be attached to + pub parent: NameOrId, + + /// The type of `parent`'s resource + pub kind: FloatingIpParentKind, +} diff --git a/nexus/types/versions/src/initial/hardware.rs b/nexus/types/versions/src/initial/hardware.rs new file mode 100644 index 00000000000..689a26f5dfb --- /dev/null +++ b/nexus/types/versions/src/initial/hardware.rs @@ -0,0 +1,80 @@ +// 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/. + +//! Hardware types for the Nexus external API. + +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use uuid::Uuid; + +/// Properties that uniquely identify an Oxide hardware component +#[derive( + Clone, + Debug, + Serialize, + Deserialize, + JsonSchema, + PartialOrd, + Ord, + PartialEq, + Eq, +)] +pub struct Baseboard { + pub serial: String, + pub part: String, + pub revision: u32, +} + +/// A sled that has not been added to an initialized rack yet +#[derive( + Clone, + Debug, + Serialize, + Deserialize, + JsonSchema, + PartialOrd, + Ord, + PartialEq, + Eq, +)] +pub struct UninitializedSled { + pub baseboard: Baseboard, + pub rack_id: Uuid, + pub cubby: u16, +} + +/// The unique hardware ID for a sled +#[derive( + Clone, + Debug, + Serialize, + Deserialize, + JsonSchema, + PartialOrd, + Ord, + PartialEq, + Eq, +)] +pub struct UninitializedSledId { + pub serial: String, + pub part: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum UpdateableComponentType { + BootloaderForRot, + BootloaderForSp, + BootloaderForHostProc, + HubrisForPscRot, + HubrisForPscSp, + HubrisForSidecarRot, + HubrisForSidecarSp, + HubrisForGimletRot, + HubrisForGimletSp, + HeliosHostPhase1, + HeliosHostPhase2, + HostOmicron, +} diff --git a/nexus/types/versions/src/initial/headers.rs b/nexus/types/versions/src/initial/headers.rs new file mode 100644 index 00000000000..027f51bff0a --- /dev/null +++ b/nexus/types/versions/src/initial/headers.rs @@ -0,0 +1,18 @@ +// 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/. + +//! HTTP header types for the Nexus external API. + +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; + +/// Range request headers +#[derive(Debug, Deserialize, Serialize, JsonSchema)] +pub struct RangeRequest { + /// A request to access a portion of the resource, such as `bytes=0-499` + /// + /// See: + pub range: Option, +} diff --git a/nexus/types/versions/src/initial/identity_provider.rs b/nexus/types/versions/src/initial/identity_provider.rs new file mode 100644 index 00000000000..774f812003d --- /dev/null +++ b/nexus/types/versions/src/initial/identity_provider.rs @@ -0,0 +1,281 @@ +// 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/. + +//! Identity provider types for version INITIAL. + +use api_identity::ObjectIdentity; +use base64::Engine; +use omicron_common::api::external::{ + IdentityMetadata, IdentityMetadataCreateParams, NameOrId, ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::de::{self, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum IdentityProviderType { + /// SAML identity provider + Saml, +} + +/// View of an Identity Provider +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct IdentityProvider { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// Identity provider type + pub provider_type: IdentityProviderType, +} + +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SamlIdentityProvider { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// IdP's entity id + pub idp_entity_id: String, + + /// SP's client id + pub sp_client_id: String, + + /// Service provider endpoint where the response will be sent + pub acs_url: String, + + /// Service provider endpoint where the idp should send log out requests + pub slo_url: String, + + /// Customer's technical contact for saml configuration + pub technical_contact_email: String, + + /// Optional request signing public certificate (base64 encoded der file) + pub public_cert: Option, + + /// If set, attributes with this name will be considered to denote a user's + /// group membership, where the values will be the group names. + pub group_attribute_name: Option, +} + +// Params + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct SamlIdentityProviderSelector { + /// Name or ID of the silo in which the SAML identity provider is associated + pub silo: Option, + /// Name or ID of the SAML identity provider + pub saml_identity_provider: NameOrId, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct DerEncodedKeyPair { + /// Request signing public certificate (base64 encoded DER file) + #[serde(deserialize_with = "x509_cert_from_base64_encoded_der")] + pub public_cert: String, + + /// Request signing RSA private key in PKCS#1 format (base64 encoded DER file) + #[serde(deserialize_with = "key_from_base64_encoded_der")] + pub private_key: String, +} + +struct X509CertVisitor; + +impl Visitor<'_> for X509CertVisitor { + type Value = String; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("a DER formatted X509 certificate as a string of base64 encoded bytes") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + let raw_bytes = base64::engine::general_purpose::STANDARD + .decode(&value.as_bytes()) + .map_err(|e| { + de::Error::custom(format!( + "could not base64 decode public_cert: {}", + e + )) + })?; + let _parsed = + openssl::x509::X509::from_der(&raw_bytes).map_err(|e| { + de::Error::custom(format!( + "public_cert is not recognized as a X509 certificate: {}", + e + )) + })?; + + Ok(value.to_string()) + } +} + +fn x509_cert_from_base64_encoded_der<'de, D>( + deserializer: D, +) -> Result +where + D: Deserializer<'de>, +{ + deserializer.deserialize_str(X509CertVisitor) +} + +struct KeyVisitor; + +impl Visitor<'_> for KeyVisitor { + type Value = String; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str( + "a DER formatted key as a string of base64 encoded bytes", + ) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + let raw_bytes = base64::engine::general_purpose::STANDARD + .decode(&value) + .map_err(|e| { + de::Error::custom(format!( + "could not base64 decode private_key: {}", + e + )) + })?; + + let parsed = openssl::rsa::Rsa::private_key_from_der(&raw_bytes) + .map_err(|e| { + de::Error::custom(format!( + "private_key is not recognized as a RSA private key: {}", + e + )) + })?; + let _parsed = openssl::pkey::PKey::from_rsa(parsed).map_err(|e| { + de::Error::custom(format!( + "private_key is not recognized as a RSA private key: {}", + e + )) + })?; + + Ok(value.to_string()) + } +} + +fn key_from_base64_encoded_der<'de, D>( + deserializer: D, +) -> Result +where + D: Deserializer<'de>, +{ + deserializer.deserialize_str(KeyVisitor) +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum IdpMetadataSource { + Url { url: String }, + Base64EncodedXml { data: String }, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SamlIdentityProviderCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// The source of an identity provider metadata descriptor + pub idp_metadata_source: IdpMetadataSource, + + /// IdP's entity ID + pub idp_entity_id: String, + + /// SP's client ID + pub sp_client_id: String, + + /// Service provider endpoint where the response will be sent + pub acs_url: String, + + /// Service provider endpoint where the IdP should send log out requests + pub slo_url: String, + + /// Customer's technical contact for SAML configuration + pub technical_contact_email: String, + + /// Request signing key pair + #[serde(default)] + #[serde(deserialize_with = "validate_key_pair")] + pub signing_keypair: Option, + + /// If set, SAML attributes with this name will be considered to denote a + /// user's group membership, where the attribute value(s) should be a + /// comma-separated list of group names. + pub group_attribute_name: Option, +} + +/// sign some junk data and validate it with the key pair +fn sign_junk_data(key_pair: &DerEncodedKeyPair) -> Result<(), anyhow::Error> { + let private_key = { + let raw_bytes = base64::engine::general_purpose::STANDARD + .decode(&key_pair.private_key)?; + let parsed = openssl::rsa::Rsa::private_key_from_der(&raw_bytes)?; + let parsed = openssl::pkey::PKey::from_rsa(parsed)?; + parsed + }; + + let public_key = { + let raw_bytes = base64::engine::general_purpose::STANDARD + .decode(&key_pair.public_cert)?; + let parsed = openssl::x509::X509::from_der(&raw_bytes)?; + parsed.public_key()? + }; + + let mut signer = openssl::sign::Signer::new( + openssl::hash::MessageDigest::sha256(), + &private_key.as_ref(), + )?; + + let some_junk_data = b"this is some junk data"; + + signer.update(some_junk_data)?; + let signature = signer.sign_to_vec()?; + + let mut verifier = openssl::sign::Verifier::new( + openssl::hash::MessageDigest::sha256(), + &public_key, + )?; + + verifier.update(some_junk_data)?; + + if !verifier.verify(&signature)? { + anyhow::bail!("signature validation failed!"); + } + + Ok(()) +} + +fn validate_key_pair<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let v = Option::::deserialize(deserializer)?; + + if let Some(ref key_pair) = v { + if let Err(e) = sign_junk_data(&key_pair) { + return Err(de::Error::custom(format!( + "data signed with key not verified with certificate! {}", + e + ))); + } + } + + Ok(v) +} diff --git a/nexus/types/versions/src/initial/image.rs b/nexus/types/versions/src/initial/image.rs new file mode 100644 index 00000000000..c61904ccfea --- /dev/null +++ b/nexus/types/versions/src/initial/image.rs @@ -0,0 +1,91 @@ +// 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/. + +//! Image types for version INITIAL. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + ByteCount, Digest, IdentityMetadata, IdentityMetadataCreateParams, Name, + NameOrId, ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Deserialize, JsonSchema)] +pub struct ImageSelector { + /// Name or ID of the project, only required if `image` is provided as a `Name` + pub project: Option, + /// Name or ID of the image + pub image: NameOrId, +} + +/// View of an image +/// +/// If `project_id` is present then the image is only visible inside that +/// project. If it's not present then the image is visible to all projects in +/// the silo. +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Image { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// ID of the parent project if the image is a project image + pub project_id: Option, + + /// The family of the operating system like Debian, Ubuntu, etc. + pub os: String, + + /// Version of the operating system + pub version: String, + + /// Hash of the image contents, if applicable + pub digest: Option, + + /// Size of blocks in bytes + pub block_size: ByteCount, + + /// Total size in bytes + pub size: ByteCount, +} + +/// The source of the underlying image. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum ImageSource { + Snapshot { + id: Uuid, + }, + + /// Boot the Alpine ISO that ships with the Propolis zone. Intended for + /// development purposes only. + #[schemars(skip)] // keep it out of the OpenAPI schema + YouCanBootAnythingAsLongAsItsAlpine, +} + +/// OS image distribution +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Distribution { + /// The name of the distribution (e.g. "alpine" or "ubuntu") + pub name: Name, + /// The version of the distribution (e.g. "3.10" or "18.04") + pub version: String, +} + +/// Create-time parameters for an `Image` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct ImageCreate { + /// common identifying metadata + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// The family of the operating system (e.g. Debian, Ubuntu, etc.) + pub os: String, + + /// The version of the operating system (e.g. 18.04, 20.04, etc.) + pub version: String, + + /// The source of the image's contents. + pub source: ImageSource, +} diff --git a/nexus/types/versions/src/initial/instance.rs b/nexus/types/versions/src/initial/instance.rs new file mode 100644 index 00000000000..0997c63561b --- /dev/null +++ b/nexus/types/versions/src/initial/instance.rs @@ -0,0 +1,434 @@ +// 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/. + +//! Instance types for version INITIAL. + +use std::net::IpAddr; + +use omicron_common::api::external::{ + ByteCount, Hostname, IdentityMetadataCreateParams, + InstanceAutoRestartPolicy, InstanceCpuCount, InstanceCpuPlatform, Name, + NameOrId, +}; +use oxnet::IpNet; +use schemars::JsonSchema; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use super::disk::DiskCreate; + +#[inline] +pub fn bool_true() -> bool { + true +} + +pub const MAX_USER_DATA_BYTES: usize = 32 * 1024; // 32 KiB + +pub struct UserData; +impl UserData { + pub fn serialize( + data: &Vec, + serializer: S, + ) -> Result + where + S: Serializer, + { + use base64::prelude::*; + BASE64_STANDARD.encode(data).serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + use base64::prelude::*; + match BASE64_STANDARD.decode(::deserialize(deserializer)?) { + Ok(buf) => { + if buf.len() > MAX_USER_DATA_BYTES { + Err(::invalid_length( + buf.len(), + &"less than 32 KiB", + )) + } else { + Ok(buf) + } + } + Err(_) => Err(::invalid_value( + serde::de::Unexpected::Other("invalid base64 string"), + &"a valid base64 string", + )), + } + } +} + +impl JsonSchema for UserData { + fn schema_name() -> String { + "String".to_string() + } + + fn json_schema( + _: &mut schemars::r#gen::SchemaGenerator, + ) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + instance_type: Some(schemars::schema::InstanceType::String.into()), + format: Some("byte".to_string()), + ..Default::default() + } + .into() + } + + fn is_referenceable() -> bool { + false + } +} + +/// Create-time parameters for an `InstanceNetworkInterface` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceNetworkInterfaceCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The VPC in which to create the interface. + pub vpc_name: Name, + /// The VPC Subnet in which to create the interface. + pub subnet_name: Name, + /// The IP address for the interface. One will be auto-assigned if not provided. + pub ip: Option, + /// A set of additional networks that this interface may send and + /// receive traffic on. + #[serde(default)] + pub transit_ips: Vec, +} + +/// Describes an attachment of an `InstanceNetworkInterface` to an `Instance`, +/// at the time the instance is created. +#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", content = "params", rename_all = "snake_case")] +pub enum InstanceNetworkInterfaceAttachment { + /// Create one or more `InstanceNetworkInterface`s for the `Instance`. + /// + /// If more than one interface is provided, then the first will be + /// designated the primary interface for the instance. + Create(Vec), + + /// The default networking configuration for an instance is to create a + /// single primary interface with an automatically-assigned IP address. The + /// IP will be pulled from the Project's default VPC / VPC Subnet. + #[default] + Default, + + /// No network interfaces at all will be created for the instance. + None, +} + +/// Parameters for creating an external IP address for instances. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum ExternalIpCreate { + /// An IP address providing both inbound and outbound access. The address is + /// automatically assigned from the provided IP pool or the default IP pool + /// if not specified. + Ephemeral { pool: Option }, + /// An IP address providing both inbound and outbound access. The address is + /// an existing floating IP object assigned to the current project. + /// + /// The floating IP must not be in use by another instance or service. + Floating { floating_ip: NameOrId }, +} + +/// During instance creation, attach this disk +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceDiskAttach { + /// A disk name to attach + pub name: Name, +} + +/// Describe the instance's disks at creation time +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum InstanceDiskAttachment { + /// During instance creation, create and attach disks + Create(DiskCreate), + + /// During instance creation, attach this disk + Attach(InstanceDiskAttach), +} + +/// Create-time parameters for an `Instance` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The number of vCPUs to be allocated to the instance + pub ncpus: InstanceCpuCount, + /// The amount of RAM (in bytes) to be allocated to the instance + pub memory: ByteCount, + /// The hostname to be assigned to the instance + pub hostname: Hostname, + + /// User data for instance initialization systems (such as cloud-init). + /// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / + /// characters with padding). Maximum 32 KiB unencoded data. + #[serde(default, with = "UserData")] + pub user_data: Vec, + + /// The network interfaces to be created for this instance. + #[serde(default)] + pub network_interfaces: InstanceNetworkInterfaceAttachment, + + /// The external IP addresses provided to this instance. + /// + /// By default, all instances have outbound connectivity, but no inbound + /// connectivity. These external addresses can be used to provide a fixed, + /// known IP address for making inbound connections to the instance. + #[serde(default)] + pub external_ips: Vec, + + /// The multicast groups this instance should join. + /// + /// The instance will be automatically added as a member of the specified + /// multicast groups during creation, enabling it to send and receive + /// multicast traffic for those groups. + #[serde(default)] + pub multicast_groups: Vec, + + /// A list of disks to be attached to the instance. + /// + /// Disk attachments of type "create" will be created, while those of type + /// "attach" must already exist. + /// + /// The order of this list does not guarantee a boot order for the instance. + /// Use the boot_disk attribute to specify a boot disk. When boot_disk is + /// specified it will count against the disk attachment limit. + #[serde(default)] + pub disks: Vec, + + /// The disk the instance is configured to boot from. + /// + /// This disk can either be attached if it already exists or created along + /// with the instance. + /// + /// Specifying a boot disk is optional but recommended to ensure predictable + /// boot behavior. The boot disk can be set during instance creation or + /// later if the instance is stopped. The boot disk counts against the disk + /// attachment limit. + /// + /// An instance that does not have a boot disk set will use the boot + /// options specified in its UEFI settings, which are controlled by both the + /// instance's UEFI firmware and the guest operating system. Boot options + /// can change as disks are attached and detached, which may result in an + /// instance that only boots to the EFI shell until a boot disk is set. + #[serde(default)] + pub boot_disk: Option, + + /// An allowlist of SSH public keys to be transferred to the instance via + /// cloud-init during instance creation. + /// + /// If not provided, all SSH public keys from the user's profile will be sent. + /// If an empty list is provided, no public keys will be transmitted to the + /// instance. + pub ssh_public_keys: Option>, + + /// Should this instance be started upon creation; true by default. + #[serde(default = "bool_true")] + pub start: bool, + + /// The auto-restart policy for this instance. + /// + /// This policy determines whether the instance should be automatically + /// restarted by the control plane on failure. If this is `null`, no + /// auto-restart policy will be explicitly configured for this instance, and + /// the control plane will select the default policy when determining + /// whether the instance can be automatically restarted. + /// + /// Currently, the global default auto-restart policy is "best-effort", so + /// instances with `null` auto-restart policies will be automatically + /// restarted. However, in the future, the default policy may be + /// configurable through other mechanisms, such as on a per-project basis. + /// In that case, any configured default policy will be used if this is + /// `null`. + #[serde(default)] + pub auto_restart_policy: Option, + + /// Anti-Affinity groups which this instance should be added. + #[serde(default)] + pub anti_affinity_groups: Vec, + + /// The CPU platform to be used for this instance. If this is `null`, the + /// instance requires no particular CPU platform; when it is started the + /// instance will have the most general CPU platform supported by the sled + /// it is initially placed on. + #[serde(default)] + pub cpu_platform: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct InstanceSelector { + /// Name or ID of the project, only required if `instance` is provided as a `Name` + pub project: Option, + /// Name or ID of the instance + pub instance: NameOrId, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct OptionalInstanceSelector { + /// Name or ID of the project, only required if `instance` is provided as a `Name` + pub project: Option, + /// Name or ID of the instance + pub instance: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct InstanceNetworkInterfaceSelector { + /// Name or ID of the project, only required if `instance` is provided as a `Name` + pub project: Option, + /// Name or ID of the instance, only required if `network_interface` is provided as a `Name` + pub instance: Option, + /// Name or ID of the network interface + pub network_interface: NameOrId, +} + +/// Parameters for updating an `InstanceNetworkInterface` +/// +/// Note that modifying IP addresses for an interface is not yet supported, a +/// new interface must be created instead. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceNetworkInterfaceUpdate { + #[serde(flatten)] + pub identity: omicron_common::api::external::IdentityMetadataUpdateParams, + + /// Make a secondary interface the instance's primary interface. + /// + /// If applied to a secondary interface, that interface will become the + /// primary on the next reboot of the instance. Note that this may have + /// implications for routing between instances, as the new primary interface + /// will be on a distinct subnet from the previous primary interface. + /// + /// Note that this can only be used to select a new primary interface for an + /// instance. Requests to change the primary interface into a secondary will + /// return an error. + // TODO-completeness TODO-doc When we get there, this should note that a + // change in the primary interface will result in changes to the DNS records + // for the instance, though not the name. + #[serde(default)] + pub primary: bool, + + /// A set of additional networks that this interface may send and receive traffic on + #[serde(default)] + pub transit_ips: Vec, +} + +/// Parameters for creating an ephemeral IP address for an instance. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub struct EphemeralIpCreate { + /// Name or ID of the IP pool used to allocate an address. If unspecified, + /// the default IP pool will be used. + pub pool: Option, +} + +/// Parameters for detaching an external IP from an instance. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum ExternalIpDetach { + Ephemeral, + Floating { floating_ip: NameOrId }, +} + +/// Parameters of an `Instance` that can be reconfigured after creation. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceUpdate { + /// The number of vCPUs to be allocated to the instance + pub ncpus: InstanceCpuCount, + + /// The amount of RAM (in bytes) to be allocated to the instance + pub memory: ByteCount, + + /// The disk the instance is configured to boot from. + /// + /// Setting a boot disk is optional but recommended to ensure predictable + /// boot behavior. The boot disk can be set during instance creation or + /// later if the instance is stopped. The boot disk counts against the disk + /// attachment limit. + /// + /// An instance that does not have a boot disk set will use the boot + /// options specified in its UEFI settings, which are controlled by both the + /// instance's UEFI firmware and the guest operating system. Boot options + /// can change as disks are attached and detached, which may result in an + /// instance that only boots to the EFI shell until a boot disk is set. + pub boot_disk: omicron_common::api::external::Nullable, + + /// The auto-restart policy for this instance. + /// + /// This policy determines whether the instance should be automatically + /// restarted by the control plane on failure. If this is `null`, any + /// explicitly configured auto-restart policy will be unset, and + /// the control plane will select the default policy when determining + /// whether the instance can be automatically restarted. + /// + /// Currently, the global default auto-restart policy is "best-effort", so + /// instances with `null` auto-restart policies will be automatically + /// restarted. However, in the future, the default policy may be + /// configurable through other mechanisms, such as on a per-project basis. + /// In that case, any configured default policy will be used if this is + /// `null`. + pub auto_restart_policy: + omicron_common::api::external::Nullable, + + /// The CPU platform to be used for this instance. If this is `null`, the + /// instance requires no particular CPU platform; when it is started the + /// instance will have the most general CPU platform supported by the sled + /// it is initially placed on. + pub cpu_platform: + omicron_common::api::external::Nullable, + + /// Multicast groups this instance should join. + /// + /// When specified, this replaces the instance's current multicast group + /// membership with the new set of groups. The instance will leave any + /// groups not listed here and join any new groups that are specified. + /// + /// If not provided (None), the instance's multicast group membership + /// will not be changed. + #[serde(default)] + pub multicast_groups: Option>, +} + +/// Forwarded to a propolis server to request the contents of an Instance's serial console. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct InstanceSerialConsoleRequest { + /// Name or ID of the project, only required if `instance` is provided as a `Name` + pub project: Option, + /// Character index in the serial buffer from which to read, counting the bytes output since + /// instance start. If this is not provided, `most_recent` must be provided, and if this *is* + /// provided, `most_recent` must *not* be provided. + pub from_start: Option, + /// Character index in the serial buffer from which to read, counting *backward* from the most + /// recently buffered data retrieved from the instance. (See note on `from_start` about mutual + /// exclusivity) + pub most_recent: Option, + /// Maximum number of bytes of buffered serial console contents to return. If the requested + /// range runs to the end of the available buffer, the data returned will be shorter than + /// `max_bytes`. + pub max_bytes: Option, +} + +/// Forwarded to a propolis server to request the contents of an Instance's serial console. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct InstanceSerialConsoleStreamRequest { + /// Name or ID of the project, only required if `instance` is provided as a `Name` + pub project: Option, + /// Character index in the serial buffer from which to read, counting *backward* from the most + /// recently buffered data retrieved from the instance. + pub most_recent: Option, +} + +/// Contents of an Instance's serial console buffer. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceSerialConsoleData { + /// The bytes starting from the requested offset up to either the end of the buffer or the + /// request's `max_bytes`. Provided as a u8 array rather than a string, as it may not be UTF-8. + pub data: Vec, + /// The absolute offset since boot (suitable for use as `byte_offset` in a subsequent request) + /// of the last byte returned in `data`. + pub last_byte_offset: u64, +} diff --git a/nexus/types/versions/src/initial/internet_gateway.rs b/nexus/types/versions/src/initial/internet_gateway.rs new file mode 100644 index 00000000000..df9a9954c70 --- /dev/null +++ b/nexus/types/versions/src/initial/internet_gateway.rs @@ -0,0 +1,140 @@ +// 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/. + +//! Internet gateway types for version INITIAL. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + IdentityMetadata, IdentityMetadataCreateParams, NameOrId, ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; +use uuid::Uuid; + +/// An internet gateway provides a path between VPC networks and external +/// networks. +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InternetGateway { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// The VPC to which the gateway belongs. + pub vpc_id: Uuid, +} + +/// An IP pool that is attached to an internet gateway +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InternetGatewayIpPool { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// The associated internet gateway. + pub internet_gateway_id: Uuid, + + /// The associated IP pool. + pub ip_pool_id: Uuid, +} + +/// An IP address that is attached to an internet gateway +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InternetGatewayIpAddress { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// The associated internet gateway + pub internet_gateway_id: Uuid, + + /// The associated IP address + pub address: IpAddr, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct InternetGatewayDeleteSelector { + /// Name or ID of the project, only required if `vpc` is provided as a `Name` + pub project: Option, + /// Name or ID of the VPC + pub vpc: Option, + /// Also delete routes targeting this gateway. + #[serde(default)] + pub cascade: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct InternetGatewaySelector { + /// Name or ID of the project, only required if `vpc` is provided as a `Name` + pub project: Option, + /// Name or ID of the VPC, only required if `gateway` is provided as a `Name` + pub vpc: Option, + /// Name or ID of the internet gateway + pub gateway: NameOrId, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct OptionalInternetGatewaySelector { + /// Name or ID of the project, only required if `vpc` is provided as a `Name` + pub project: Option, + /// Name or ID of the VPC, only required if `gateway` is provided as a `Name` + pub vpc: Option, + /// Name or ID of the internet gateway + pub gateway: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct DeleteInternetGatewayElementSelector { + /// Name or ID of the project, only required if `vpc` is provided as a `Name` + pub project: Option, + /// Name or ID of the VPC, only required if `gateway` is provided as a `Name` + pub vpc: Option, + /// Name or ID of the internet gateway + pub gateway: Option, + /// Also delete routes targeting this gateway element. + #[serde(default)] + pub cascade: bool, +} + +#[derive(Deserialize, JsonSchema)] +pub struct InternetGatewayIpPoolSelector { + /// Name or ID of the project, only required if `vpc` is provided as a `Name` + pub project: Option, + /// Name or ID of the VPC, only required if `gateway` is provided as a `Name` + pub vpc: Option, + /// Name or ID of the gateway, only required if `pool` is provided as a `Name` + pub gateway: Option, + /// Name or ID of the pool + pub pool: NameOrId, +} + +#[derive(Deserialize, JsonSchema)] +pub struct InternetGatewayIpAddressSelector { + /// Name or ID of the project, only required if `vpc` is provided as a `Name` + pub project: Option, + /// Name or ID of the VPC, only required if `gateway` is provided as a `Name` + pub vpc: Option, + /// Name or ID of the gateway, only required if `address` is provided as a `Name` + pub gateway: Option, + /// Name or ID of the address + pub address: NameOrId, +} + +/// Create-time parameters for an `InternetGateway` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InternetGatewayCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InternetGatewayIpPoolCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + pub ip_pool: NameOrId, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InternetGatewayIpAddressCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + pub address: IpAddr, +} diff --git a/nexus/types/versions/src/initial/ip_pool.rs b/nexus/types/versions/src/initial/ip_pool.rs new file mode 100644 index 00000000000..29173a23f0c --- /dev/null +++ b/nexus/types/versions/src/initial/ip_pool.rs @@ -0,0 +1,166 @@ +// 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/. + +//! IP pool types for version INITIAL. + +use api_identity::ObjectIdentity; +use chrono::{DateTime, Utc}; +use omicron_common::address::IpRange; +use omicron_common::api::external::{ + IdentityMetadata, IdentityMetadataCreateParams, + IdentityMetadataUpdateParams, IpVersion, NameOrId, ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// Type of IP pool. +#[derive( + Clone, Copy, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, +)] +#[serde(rename_all = "snake_case")] +pub enum IpPoolType { + /// Unicast IP pool for standard IP allocations. + #[default] + Unicast, + /// Multicast IP pool for multicast group allocations. + /// + /// All ranges in a multicast pool must be either ASM or SSM (not mixed). + Multicast, +} + +/// A collection of IP ranges. If a pool is linked to a silo, IP addresses from +/// the pool can be allocated within that silo. +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct IpPool { + #[serde(flatten)] + pub identity: IdentityMetadata, + /// The IP version for the pool. + pub ip_version: IpVersion, + /// Type of IP pool (unicast or multicast). + pub pool_type: IpPoolType, +} + +/// The utilization of IP addresses in a pool. +/// +/// Note that both the count of remaining addresses and the total capacity are +/// integers, reported as floating point numbers. This accommodates allocations +/// larger than a 64-bit integer, which is common with IPv6 address spaces. With +/// very large IP Pools (> 2**53 addresses), integer precision will be lost, in +/// exchange for representing the entire range. In such a case the pool still +/// has many available addresses. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct IpPoolUtilization { + /// The number of remaining addresses in the pool. + pub remaining: f64, + /// The total number of addresses in the pool. + pub capacity: f64, +} + +/// An IP pool in the context of a silo +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SiloIpPool { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// When a pool is the default for a silo, floating IPs and instance + /// ephemeral IPs will come from that pool when no other pool is specified. + /// There can be at most one default for a given silo. + pub is_default: bool, +} + +/// A link between an IP pool and a silo that allows one to allocate IPs from +/// the pool within the silo +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct IpPoolSiloLink { + pub ip_pool_id: Uuid, + pub silo_id: Uuid, + /// When a pool is the default for a silo, floating IPs and instance + /// ephemeral IPs will come from that pool when no other pool is specified. + /// + /// A silo can have at most one default pool per combination of pool type + /// (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 + /// default pools total. + pub is_default: bool, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, JsonSchema)] +pub struct IpPoolRange { + pub id: Uuid, + pub ip_pool_id: Uuid, + pub time_created: DateTime, + pub range: IpRange, +} + +/// Create-time parameters for an `IpPool`. +/// +/// For multicast pools, all ranges must be either Any-Source Multicast (ASM) +/// or Source-Specific Multicast (SSM), but not both. Mixing ASM and SSM +/// ranges in the same pool is not allowed. +/// +/// ASM: IPv4 addresses outside 232.0.0.0/8, IPv6 addresses with flag field != 3 +/// SSM: IPv4 addresses in 232.0.0.0/8, IPv6 addresses with flag field = 3 +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct IpPoolCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The IP version of the pool. + /// + /// The default is IPv4. + #[serde(default = "IpVersion::v4")] + pub ip_version: IpVersion, + /// Type of IP pool (defaults to Unicast) + #[serde(default)] + pub pool_type: IpPoolType, +} + +impl IpPoolCreate { + /// Create parameters for a unicast IP pool (the default) + pub fn new( + identity: IdentityMetadataCreateParams, + ip_version: IpVersion, + ) -> Self { + Self { identity, ip_version, pool_type: IpPoolType::Unicast } + } + + /// Create parameters for a multicast IP pool + pub fn new_multicast( + identity: IdentityMetadataCreateParams, + ip_version: IpVersion, + ) -> Self { + Self { identity, ip_version, pool_type: IpPoolType::Multicast } + } +} + +/// Parameters for updating an IP Pool +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct IpPoolUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct IpPoolSiloPath { + pub pool: NameOrId, + pub silo: NameOrId, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct IpPoolLinkSilo { + pub silo: NameOrId, + /// When a pool is the default for a silo, floating IPs and instance + /// ephemeral IPs will come from that pool when no other pool is specified. + /// There can be at most one default for a given silo. + pub is_default: bool, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct IpPoolSiloUpdate { + /// When a pool is the default for a silo, floating IPs and instance + /// ephemeral IPs will come from that pool when no other pool is specified. + /// There can be at most one default for a given silo, so when a pool is + /// made default, an existing default will remain linked but will no longer + /// be the default. + pub is_default: bool, +} diff --git a/nexus/types/versions/src/initial/metrics.rs b/nexus/types/versions/src/initial/metrics.rs new file mode 100644 index 00000000000..19afc65c88f --- /dev/null +++ b/nexus/types/versions/src/initial/metrics.rs @@ -0,0 +1,36 @@ +// 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/. + +//! Metrics types for the Nexus external API. + +use chrono::{DateTime, Utc}; +use omicron_common::api::external::PaginationOrder; +use parse_display::Display; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Display, Deserialize, JsonSchema)] +#[display(style = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum SystemMetricName { + VirtualDiskSpaceProvisioned, + CpusProvisioned, + RamProvisioned, +} + +#[derive(Deserialize, JsonSchema)] +pub struct SystemMetricsPathParam { + pub metric_name: SystemMetricName, +} + +/// Query parameters common to resource metrics endpoints. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct ResourceMetrics { + /// An inclusive start time of metrics. + pub start_time: DateTime, + /// An exclusive end time of metrics. + pub end_time: DateTime, + /// Query result order + pub order: Option, +} diff --git a/nexus/types/versions/src/initial/mod.rs b/nexus/types/versions/src/initial/mod.rs new file mode 100644 index 00000000000..503885c74a6 --- /dev/null +++ b/nexus/types/versions/src/initial/mod.rs @@ -0,0 +1,51 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `INITIAL` of the Nexus external API. +//! +//! This was the first server-side versioned release of the Nexus external API +//! (version 2025_11_20_00). + +pub mod affinity; +pub mod alert; +pub mod asset; +pub mod audit; +pub mod bfd; +pub mod certificate; +pub mod console; +pub mod device; +pub mod device_params; +pub mod disk; +pub mod external_ip; +pub mod floating_ip; +pub mod hardware; +pub mod headers; +pub mod identity_provider; +pub mod image; +pub mod instance; +pub mod internet_gateway; +pub mod ip_pool; +pub mod metrics; +pub mod multicast; +pub mod networking; +pub mod oxql; +pub mod path_params; +pub mod physical_disk; +pub mod policy; +pub mod probe; +pub mod project; +pub mod rack; +pub mod saml; +pub mod scim; +pub mod silo; +pub mod sled; +pub mod snapshot; +pub mod ssh_key; +pub mod support_bundle; +pub mod switch; +pub mod system; +pub mod timeseries; +pub mod update; +pub mod user; +pub mod vpc; diff --git a/nexus/types/versions/src/initial/multicast.rs b/nexus/types/versions/src/initial/multicast.rs new file mode 100644 index 00000000000..80c7bdf3dd0 --- /dev/null +++ b/nexus/types/versions/src/initial/multicast.rs @@ -0,0 +1,230 @@ +// 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/. + +//! Multicast group types for version INITIAL. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + IdentityMetadata, NameOrId, ObjectIdentity, +}; +use omicron_common::vlan::VlanID; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; +use uuid::Uuid; + +/// View of a Multicast Group +#[derive( + ObjectIdentity, Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema, +)] +pub struct MulticastGroup { + #[serde(flatten)] + pub identity: IdentityMetadata, + /// The multicast IP address held by this resource. + pub multicast_ip: IpAddr, + /// Source IP addresses for Source-Specific Multicast (SSM). + /// Empty array means any source is allowed. + pub source_ips: Vec, + /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. + /// None means no VLAN tagging on egress. + pub mvlan: Option, + /// The ID of the IP pool this resource belongs to. + pub ip_pool_id: Uuid, + /// Current state of the multicast group. + pub state: String, +} + +/// View of a Multicast Group Member (instance belonging to a multicast group) +#[derive( + ObjectIdentity, Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema, +)] +pub struct MulticastGroupMember { + #[serde(flatten)] + pub identity: IdentityMetadata, + /// The ID of the multicast group this member belongs to. + pub multicast_group_id: Uuid, + /// The ID of the instance that is a member of this group. + pub instance_id: Uuid, + /// Current state of the multicast group membership. + pub state: String, +} + +#[derive(Deserialize, JsonSchema, Clone)] +pub struct MulticastGroupSelector { + /// Name or ID of the multicast group (fleet-scoped) + pub multicast_group: NameOrId, +} + +use omicron_common::api::external::{ + IdentityMetadataCreateParams, IdentityMetadataUpdateParams, Nullable, +}; +use serde::de; + +/// Path parameter for multicast group lookup by IP address. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupIpLookupPath { + /// IP address of the multicast group + pub address: IpAddr, +} + +/// Create-time parameters for a multicast group. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The multicast IP address to allocate. If None, one will be allocated + /// from the default pool. + #[serde(default, deserialize_with = "validate_multicast_ip_param")] + pub multicast_ip: Option, + /// Source IP addresses for Source-Specific Multicast (SSM). + /// + /// None uses default behavior (Any-Source Multicast). + /// Empty list explicitly allows any source (Any-Source Multicast). + /// Non-empty list restricts to specific sources (SSM). + #[serde(default, deserialize_with = "validate_source_ips_param")] + pub source_ips: Option>, + /// Name or ID of the IP pool to allocate from. If None, uses the default + /// multicast pool. + #[serde(default)] + pub pool: Option, + /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. + /// Tags packets leaving the rack to traverse VLAN-segmented upstream networks. + /// + /// Valid range: 2-4094 (VLAN IDs 0-1 are reserved by IEEE 802.1Q standard). + #[serde(default, deserialize_with = "validate_mvlan_option")] + pub mvlan: Option, +} + +/// Update-time parameters for a multicast group. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, + #[serde( + default, + deserialize_with = "validate_source_ips_param", + skip_serializing_if = "Option::is_none" + )] + pub source_ips: Option>, + /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. + /// Set to null to clear the MVLAN. Valid range: 2-4094 when provided. + /// Omit the field to leave mvlan unchanged. + #[serde( + default, + deserialize_with = "validate_mvlan_option_nullable", + skip_serializing_if = "Option::is_none" + )] + pub mvlan: Option>, +} + +/// Parameters for adding an instance to a multicast group. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupMemberAdd { + /// Name or ID of the instance to add to the multicast group + pub instance: NameOrId, +} + +/// Parameters for removing an instance from a multicast group. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupMemberRemove { + /// Name or ID of the instance to remove from the multicast group + pub instance: NameOrId, +} + +/// Path parameters for multicast group member operations. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupMemberPath { + /// Name or ID of the multicast group + pub multicast_group: NameOrId, + /// Name or ID of the instance + pub instance: NameOrId, +} + +/// Path parameters for instance multicast group operations. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceMulticastGroupPath { + /// Name or ID of the instance + pub instance: NameOrId, + /// Name or ID of the multicast group + pub multicast_group: NameOrId, +} + +/// Dendrite requires VLAN IDs >= 2 (rejects 0 and 1) +/// +/// Valid range is 2-4094 +pub(crate) fn validate_mvlan(vlan_id: VlanID) -> Result { + let value: u16 = vlan_id.into(); + if value >= 2 { + Ok(vlan_id) + } else { + Err(format!( + "invalid mvlan: {value} (must be >= 2, VLAN IDs 0-1 are reserved)" + )) + } +} + +pub(crate) fn validate_mvlan_option<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let opt = Option::::deserialize(deserializer)?; + match opt { + Some(v) => { + validate_mvlan(v).map(Some).map_err(serde::de::Error::custom) + } + None => Ok(None), + } +} + +pub(crate) fn validate_mvlan_option_nullable<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: serde::Deserializer<'de>, +{ + // Deserialize as Nullable directly, which handles null properly + // When field has null value, Nullable deserializer returns Nullable(None) + // We always wrap in Some because if field is present, we got here + let nullable = Nullable::::deserialize(deserializer)?; + match nullable.0 { + Some(v) => validate_mvlan(v) + .map(|vv| Some(Nullable(Some(vv)))) + .map_err(serde::de::Error::custom), + None => Ok(Some(Nullable(None))), // Explicit null to clear + } +} + +use crate::impls::multicast::{validate_multicast_ip, validate_source_ip}; + +/// Deserializer for validating multicast IP addresses. +pub(crate) fn validate_multicast_ip_param<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let ip_opt = Option::::deserialize(deserializer)?; + if let Some(ip) = ip_opt { + validate_multicast_ip(ip).map_err(|e| de::Error::custom(e))?; + } + Ok(ip_opt) +} + +/// Deserializer for validating source IP addresses. +pub(crate) fn validate_source_ips_param<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: serde::Deserializer<'de>, +{ + let ips_opt = Option::>::deserialize(deserializer)?; + if let Some(ref ips) = ips_opt { + for ip in ips { + validate_source_ip(*ip).map_err(|e| de::Error::custom(e))?; + } + } + Ok(ips_opt) +} diff --git a/nexus/types/versions/src/initial/networking.rs b/nexus/types/versions/src/initial/networking.rs new file mode 100644 index 00000000000..121f425d934 --- /dev/null +++ b/nexus/types/versions/src/initial/networking.rs @@ -0,0 +1,952 @@ +// 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/. + +//! Networking types for the Nexus external API. +//! +//! This includes address lot, switch port, BGP, BFD, and routing configuration +//! types. + +use api_identity::ObjectIdentity; +use omicron_common::api::external; +use omicron_common::api::external::{ + AddressLotKind, BfdMode, IdentityMetadata, IdentityMetadataCreateParams, + ImportExportPolicy, LinkFec, LinkSpeed, Name, NameOrId, ObjectIdentity, + SwitchLocation, +}; +use oxnet::IpNet; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::collections::hash_map::Entry; +use std::net::{IpAddr, Ipv4Addr}; +use uuid::Uuid; + +// ADDRESS LOT + +/// Select an address lot by an optional name or id. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct AddressLotSelector { + /// Name or id of the address lot to select + pub address_lot: NameOrId, +} + +/// Parameters for creating an address lot. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AddressLotCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The kind of address lot to create. + pub kind: AddressLotKind, + /// The blocks to add along with the new address lot. + pub blocks: Vec, +} + +/// Parameters for creating an address lot block. First and last addresses are +/// inclusive. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AddressLotBlockCreate { + /// The first address in the lot (inclusive). + pub first_address: IpAddr, + /// The last address in the lot (inclusive). + pub last_address: IpAddr, +} + +/// Parameters for creating a loopback address on a particular rack switch. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct LoopbackAddressCreate { + /// The name or id of the address lot this loopback address will pull an + /// address from. + pub address_lot: NameOrId, + + /// The rack containing the switch this loopback address will be configured on. + pub rack_id: Uuid, + + // TODO: #3604 Consider using `SwitchLocation` type instead of `Name` for `LoopbackAddressCreate.switch_location` + /// The location of the switch within the rack this loopback address will be + /// configured on. + pub switch_location: Name, + + /// The address to create. + pub address: IpAddr, + + /// The subnet mask to use for the address. + pub mask: u8, + + /// Address is an anycast address. + /// + /// This allows the address to be assigned to multiple locations simultaneously. + pub anycast: bool, +} + +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct LoopbackAddressPath { + /// The rack to use when selecting the loopback address. + pub rack_id: Uuid, + + /// The switch location to use when selecting the loopback address. + pub switch_location: Name, + + /// The IP address and subnet mask to use when selecting the loopback + /// address. + pub address: IpAddr, + + /// The IP address and subnet mask to use when selecting the loopback + /// address. + pub subnet_mask: u8, +} + +// SWITCH PORT SETTINGS + +/// Parameters for creating a port settings group. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SwtichPortSettingsGroupCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// Switch port settings to associate with the settings group being created. + pub settings: SwitchPortSettingsCreate, +} + +/// Parameters for creating switch port settings. Switch port settings are the +/// central data structure for setting up external networking. Switch port +/// settings include link, interface, route, address and dynamic network +/// protocol configuration. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SwitchPortSettingsCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + pub port_config: SwitchPortConfigCreate, + + #[serde(default)] + pub groups: Vec, + + /// Link configurations. + pub links: Vec, + + /// Interface configurations. + #[serde(default)] + pub interfaces: Vec, + + /// Route configurations. + #[serde(default)] + pub routes: Vec, + + /// BGP peer configurations. + #[serde(default)] + pub bgp_peers: Vec, + + /// Address configurations. + pub addresses: Vec, +} + +impl SwitchPortSettingsCreate { + pub fn new(identity: IdentityMetadataCreateParams) -> Self { + Self { + identity, + port_config: SwitchPortConfigCreate { + geometry: SwitchPortGeometry::Qsfp28x1, + }, + groups: Vec::new(), + links: Vec::new(), + interfaces: Vec::new(), + routes: Vec::new(), + bgp_peers: Vec::new(), + addresses: Vec::new(), + } + } +} + +/// Physical switch port configuration. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct SwitchPortConfigCreate { + /// Link geometry for the switch port. + pub geometry: SwitchPortGeometry, +} + +/// The link geometry associated with a switch port. +#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SwitchPortGeometry { + /// The port contains a single QSFP28 link with four lanes. + Qsfp28x1, + + /// The port contains two QSFP28 links each with two lanes. + Qsfp28x2, + + /// The port contains four SFP28 links each with one lane. + Sfp28x4, +} + +/// Switch link configuration. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct LinkConfigCreate { + /// Link name. On ports that are not broken out, this is always phy0. + /// On a 2x breakout the options are phy0 and phy1, on 4x + /// phy0-phy3, etc. + pub link_name: Name, + + /// Maximum transmission unit for the link. + pub mtu: u16, + + /// The link-layer discovery protocol (LLDP) configuration for the link. + pub lldp: LldpLinkConfigCreate, + + /// The requested forward-error correction method. If this is not + /// specified, the standard FEC for the underlying media will be applied + /// if it can be determined. + pub fec: Option, + + /// The speed of the link. + pub speed: LinkSpeed, + + /// Whether or not to set autonegotiation. + pub autoneg: bool, + + /// Optional tx_eq settings. + pub tx_eq: Option, +} + +/// Per-port tx-eq overrides. This can be used to fine-tune the transceiver +/// equalization settings to improve signal integrity. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct TxEqConfig { + /// Pre-cursor tap1 + pub pre1: Option, + /// Pre-cursor tap2 + pub pre2: Option, + /// Main tap + pub main: Option, + /// Post-cursor tap2 + pub post2: Option, + /// Post-cursor tap1 + pub post1: Option, +} + +impl From for TxEqConfig { + fn from( + x: omicron_common::api::internal::shared::TxEqConfig, + ) -> TxEqConfig { + TxEqConfig { + pre1: x.pre1, + pre2: x.pre2, + main: x.main, + post2: x.post2, + post1: x.post1, + } + } +} + +/// The LLDP configuration associated with a port. +#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)] +pub struct LldpLinkConfigCreate { + /// Whether or not LLDP is enabled. + pub enabled: bool, + + /// The LLDP link name TLV. + pub link_name: Option, + + /// The LLDP link description TLV. + pub link_description: Option, + + /// The LLDP chassis identifier TLV. + pub chassis_id: Option, + + /// The LLDP system name TLV. + pub system_name: Option, + + /// The LLDP system description TLV. + pub system_description: Option, + + /// The LLDP management IP TLV. + pub management_ip: Option, +} + +impl PartialEq + for omicron_common::api::external::LldpLinkConfig +{ + fn eq(&self, other: &LldpLinkConfigCreate) -> bool { + self.enabled == other.enabled + && self.link_name == other.link_name + && self.link_description == other.link_description + && self.chassis_id == other.chassis_id + && self.system_name == other.system_name + && self.system_description == other.system_description + && self.management_ip == other.management_ip + } +} + +impl PartialEq + for LldpLinkConfigCreate +{ + fn eq( + &self, + other: &omicron_common::api::external::LldpLinkConfig, + ) -> bool { + self.enabled == other.enabled + && self.link_name == other.link_name + && self.link_description == other.link_description + && self.chassis_id == other.chassis_id + && self.system_name == other.system_name + && self.system_description == other.system_description + && self.management_ip == other.management_ip + } +} + +/// A layer-3 switch interface configuration. When IPv6 is enabled, a link local +/// address will be created for the interface. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SwitchInterfaceConfigCreate { + /// Link name. On ports that are not broken out, this is always phy0. + /// On a 2x breakout the options are phy0 and phy1, on 4x + /// phy0-phy3, etc. + pub link_name: Name, + + /// Whether or not IPv6 is enabled. + pub v6_enabled: bool, + + /// What kind of switch interface this configuration represents. + pub kind: SwitchInterfaceKind, +} + +/// Indicates the kind for a switch interface. +#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum SwitchInterfaceKind { + /// Primary interfaces are associated with physical links. There is exactly + /// one primary interface per physical link. + Primary, + + /// VLAN interfaces allow physical interfaces to be multiplexed onto + /// multiple logical links, each distinguished by a 12-bit 802.1Q Ethernet + /// tag. + Vlan(SwitchVlanInterface), + + /// Loopback interfaces are anchors for IP addresses that are not specific + /// to any particular port. + Loopback, +} + +/// Configuration data associated with a switch VLAN interface. The VID +/// indicates a VLAN identifier. Must be between 1 and 4096. +#[derive(Copy, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SwitchVlanInterface { + /// The virtual network id (VID) that distinguishes this interface and is + /// used for producing and consuming 802.1Q Ethernet tags. This field has a + /// maximum value of 4095 as 802.1Q tags are twelve bits. + pub vid: u16, +} + +/// Route configuration data associated with a switch port configuration. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct RouteConfig { + /// Link name. On ports that are not broken out, this is always phy0. + /// On a 2x breakout the options are phy0 and phy1, on 4x + /// phy0-phy3, etc. + pub link_name: Name, + + /// The set of routes assigned to a switch port. + pub routes: Vec, +} + +/// A route to a destination network through a gateway address. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Route { + /// The route destination. + pub dst: IpNet, + + /// The route gateway. + pub gw: IpAddr, + + /// VLAN id the gateway is reachable over. + pub vid: Option, + + /// Route RIB priority. Higher priority indicates precedence within and across + /// protocols. + pub rib_priority: Option, +} + +// BGP + +/// Select a BGP config by a name or id. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct BgpConfigSelector { + /// A name or id to use when selecting BGP config. + pub name_or_id: NameOrId, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct BgpPeerConfig { + /// Link that the peer is reachable on. + /// On ports that are not broken out, this is always phy0. + /// On a 2x breakout the options are phy0 and phy1, on 4x + /// phy0-phy3, etc. + pub link_name: Name, + + pub peers: Vec, +} + +// BGP PEER (old shape: `addr` required, no `router_lifetime`) + +/// A BGP peer configuration for an interface. Includes the set of announcements +/// that will be advertised to the peer identified by `addr`. The `bgp_config` +/// parameter is a reference to global BGP parameters. The `interface_name` +/// indicates what interface the peer should be contacted on. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct BgpPeer { + /// The global BGP configuration used for establishing a session with this + /// peer. + pub bgp_config: NameOrId, + + /// The name of interface to peer on. This is relative to the port + /// configuration this BGP peer configuration is a part of. For example this + /// value could be phy0 to refer to a primary physical interface. Or it + /// could be vlan47 to refer to a VLAN interface. + pub interface_name: Name, + + /// The address of the host to peer with. + pub addr: IpAddr, + + /// How long to hold peer connections between keepalives (seconds). + pub hold_time: u32, + + /// How long to hold a peer in idle before attempting a new session + /// (seconds). + pub idle_hold_time: u32, + + /// How long to delay sending an open request after establishing a TCP + /// session (seconds). + pub delay_open: u32, + + /// How long to to wait between TCP connection retries (seconds). + pub connect_retry: u32, + + /// How often to send keepalive requests (seconds). + pub keepalive: u32, + + /// Require that a peer has a specified ASN. + pub remote_asn: Option, + + /// Require messages from a peer have a minimum IP time to live field. + pub min_ttl: Option, + + /// Use the given key for TCP-MD5 authentication with the peer. + pub md5_auth_key: Option, + + /// Apply the provided multi-exit discriminator (MED) updates sent to the peer. + pub multi_exit_discriminator: Option, + + /// Include the provided communities in updates sent to the peer. + pub communities: Vec, + + /// Apply a local preference to routes received from this peer. + pub local_pref: Option, + + /// Enforce that the first AS in paths received from this peer is the peer's AS. + pub enforce_first_as: bool, + + /// Define import policy for a peer. + pub allowed_import: ImportExportPolicy, + + /// Define export policy for a peer. + pub allowed_export: ImportExportPolicy, + + /// Associate a VLAN ID with a peer. + pub vlan_id: Option, +} + +// TODO: per RFD 619, these conversion impls between initial types and +// `omicron_common::api::external` types should live in the later version +// module that introduced the shape change (e.g. `bgp_unnumbered_peers`). +// They currently live here because `omicron-common-versions` does not yet +// exist; once it does, move these conversions out of the initial module. +impl From for external::BgpPeer { + fn from(old: BgpPeer) -> external::BgpPeer { + external::BgpPeer { + bgp_config: old.bgp_config, + interface_name: old.interface_name, + addr: Some(old.addr), + hold_time: old.hold_time, + idle_hold_time: old.idle_hold_time, + delay_open: old.delay_open, + connect_retry: old.connect_retry, + keepalive: old.keepalive, + remote_asn: old.remote_asn, + min_ttl: old.min_ttl, + md5_auth_key: old.md5_auth_key, + multi_exit_discriminator: old.multi_exit_discriminator, + communities: old.communities, + local_pref: old.local_pref, + enforce_first_as: old.enforce_first_as, + allowed_import: old.allowed_import, + allowed_export: old.allowed_export, + vlan_id: old.vlan_id, + router_lifetime: 0, + } + } +} + +impl TryFrom for BgpPeer { + type Error = external::Error; + + fn try_from(new: external::BgpPeer) -> Result { + let addr = new.addr.ok_or_else(|| { + external::Error::invalid_request( + "BGP peer has no address configured, but the API version \ + in use requires an address. Update your client to use \ + BGP unnumbered peers.", + ) + })?; + Ok(BgpPeer { + bgp_config: new.bgp_config, + interface_name: new.interface_name, + addr, + hold_time: new.hold_time, + idle_hold_time: new.idle_hold_time, + delay_open: new.delay_open, + connect_retry: new.connect_retry, + keepalive: new.keepalive, + remote_asn: new.remote_asn, + min_ttl: new.min_ttl, + md5_auth_key: new.md5_auth_key, + multi_exit_discriminator: new.multi_exit_discriminator, + communities: new.communities, + local_pref: new.local_pref, + enforce_first_as: new.enforce_first_as, + allowed_import: new.allowed_import, + allowed_export: new.allowed_export, + vlan_id: new.vlan_id, + }) + } +} + +/// Parameters for creating a named set of BGP announcements. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct BgpAnnounceSetCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// The announcements in this set. + pub announcement: Vec, +} + +/// Select a BGP announce set by a name or id. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct BgpAnnounceSetSelector { + /// Name or ID of the announce set + pub announce_set: NameOrId, +} + +/// List BGP announce set with an optional name or id. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct BgpAnnounceListSelector { + /// Name or ID of the announce set + pub announce_set: Option, +} + +/// Selector used for querying imported BGP routes. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct BgpRouteSelector { + /// The ASN to filter on. Required. + pub asn: u32, +} + +/// A BGP announcement tied to a particular address lot block. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct BgpAnnouncementCreate { + /// Address lot this announcement is drawn from. + pub address_lot_block: NameOrId, + + /// The network being announced. + pub network: IpNet, +} + +/// Parameters for creating a BGP configuration. This includes and autonomous +/// system number (ASN) and a virtual routing and forwarding (VRF) identifier. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct BgpConfigCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// The autonomous system number of this BGP configuration. + pub asn: u32, + + pub bgp_announce_set_id: NameOrId, + + /// Optional virtual routing and forwarding identifier for this BGP + /// configuration. + pub vrf: Option, + + // Dynamic BGP policy is not yet available so we skip adding it to the API + /// A shaper program to apply to outgoing open and update messages. + #[serde(skip)] + pub shaper: Option, + /// A checker program to apply to incoming open and update messages. + #[serde(skip)] + pub checker: Option, +} + +/// Select a BGP status information by BGP config id. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct BgpStatusSelector { + /// A name or id of the BGP configuration to get status for + pub name_or_id: NameOrId, +} + +// BFD + +/// Information about a bidirectional forwarding detection (BFD) session. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct BfdSessionEnable { + /// Address the Oxide switch will listen on for BFD traffic. If `None` then + /// the unspecified address (0.0.0.0 or ::) is used. + pub local: Option, + + /// Address of the remote peer to establish a BFD session with. + pub remote: IpAddr, + + /// The negotiated Control packet transmission interval, multiplied by this + /// variable, will be the Detection Time for this session (as seen by the + /// remote system) + pub detection_threshold: u8, + + /// The minimum interval, in microseconds, between received BFD + /// Control packets that this system requires + pub required_rx: u64, + + /// The switch to enable this session on. Must be `switch0` or `switch1`. + pub switch: Name, + + /// Select either single-hop (RFC 5881) or multi-hop (RFC 5883) + pub mode: BfdMode, +} + +/// Information needed to disable a BFD session +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct BfdSessionDisable { + /// Address of the remote peer to disable a BFD session for. + pub remote: IpAddr, + + /// The switch to enable this session on. Must be `switch0` or `switch1`. + pub switch: Name, +} + +/// A set of addresses associated with a port configuration. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct AddressConfig { + /// Link to assign the addresses to. + /// On ports that are not broken out, this is always phy0. + /// On a 2x breakout the options are phy0 and phy1, on 4x + /// phy0-phy3, etc. + pub link_name: Name, + + /// The set of addresses assigned to the port configuration. + pub addresses: Vec
, +} + +/// An address tied to an address lot. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Address { + /// The address lot this address is drawn from. + pub address_lot: NameOrId, + + /// The address and prefix length of this address. + pub address: IpNet, + + /// Optional VLAN ID for this address + pub vlan_id: Option, +} + +/// Select a port settings object by an optional name or id. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SwitchPortSettingsSelector { + /// An optional name or id to use when selecting port settings. + pub port_settings: Option, +} + +/// Select a port settings info object by name or id. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SwitchPortSettingsInfoSelector { + /// A name or id to use when selecting switch port settings info objects. + pub port: NameOrId, +} + +/// Select a switch port by name. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SwitchPortPathSelector { + /// A name to use when selecting switch ports. + pub port: Name, +} + +/// Select switch ports by rack id and location. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SwitchPortSelector { + /// A rack id to use when selecting switch ports. + pub rack_id: Uuid, + + /// A switch location to use when selecting switch ports. + pub switch_location: Name, +} + +/// Select switch port interfaces by id. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SwitchPortPageSelector { + /// An optional switch port id to use when listing switch ports. + pub switch_port_id: Option, +} + +/// Parameters for applying settings to switch ports. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct SwitchPortApplySettings { + /// A name or id to use when applying switch port settings. + pub port_settings: NameOrId, +} + +/// Select an LLDP endpoint by rack/switch/port +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct LldpPortPathSelector { + /// A rack id to use when selecting switch ports. + pub rack_id: Uuid, + + /// A switch location to use when selecting switch ports. + pub switch_location: Name, + + /// A name to use when selecting switch ports. + pub port: Name, +} + +// BGP STATUS + +/// The current status of a BGP peer. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct BgpPeerStatus { + /// IP address of the peer. + pub addr: IpAddr, + + /// Local autonomous system number. + pub local_asn: u32, + + /// Remote autonomous system number. + pub remote_asn: u32, + + /// State of the peer. + pub state: BgpPeerState, + + /// Time of last state change. + pub state_duration_millis: u64, + + /// Switch with the peer session. + pub switch: external::SwitchLocation, +} + +/// The current state of a BGP peer. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum BgpPeerState { + /// Initial state. Refuse all incoming BGP connections. No resources + /// allocated to peer. + Idle, + + /// Waiting for the TCP connection to be completed. + Connect, + + /// Trying to acquire peer by listening for and accepting a TCP connection. + Active, + + /// Waiting for open message from peer. + OpenSent, + + /// Waiting for keepalive or notification from peer. + OpenConfirm, + + /// Synchronizing with peer. + SessionSetup, + + /// Session established. Able to exchange update, notification and keepalive + /// messages with peers. + Established, +} + +// BGP IMPORTED ROUTES (old IPv4-only type) + +/// A route imported from a BGP peer. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)] +pub struct BgpImportedRouteIpv4 { + /// The destination network prefix. + pub prefix: oxnet::Ipv4Net, + + /// The nexthop the prefix is reachable through. + pub nexthop: Ipv4Addr, + + /// BGP identifier of the originating router. + pub id: u32, + + /// Switch the route is imported into. + pub switch: SwitchLocation, +} + +// TODO: these conversion impls between initial types and +// `omicron_common::api::external` types should live in the later version +// module that introduced the shape change. They currently live here because +// `omicron-common-versions` does not yet exist. +impl TryFrom for BgpImportedRouteIpv4 { + type Error = String; + + fn try_from(value: external::BgpImported) -> Result { + let external::BgpImported { prefix, nexthop, id, switch } = value; + + let prefix = match prefix { + oxnet::IpNet::V4(ipv4_net) => Ok(ipv4_net), + oxnet::IpNet::V6(ipv6_net) => { + Err(format!("prefix must be Ipv4Net but it is {ipv6_net}")) + } + }?; + + let nexthop = match nexthop { + IpAddr::V4(ipv4_addr) => Ok(ipv4_addr), + IpAddr::V6(ipv6_addr) => { + Err(format!("nexthop must be Ipv4Addr but it is {ipv6_addr}")) + } + }?; + + Ok(Self { prefix, nexthop, id, switch }) + } +} + +// BGP EXPORTED (old HashMap-based type) + +/// BGP exported routes indexed by peer address. +#[derive( + Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq, Default, +)] +pub struct BgpExported { + /// Exported routes indexed by peer address. + pub exports: HashMap>, +} + +// TODO: see above comment on `TryFrom`. +impl From> for BgpExported { + fn from(values: Vec) -> Self { + let mut out = Self::default(); + + for export in values { + let oxnet::IpNet::V4(net) = export.prefix else { + continue; + }; + match out.exports.entry(export.peer_id) { + Entry::Occupied(mut occupied_entry) => { + occupied_entry.get_mut().push(net); + } + Entry::Vacant(vacant_entry) => { + vacant_entry.insert(vec![net]); + } + } + } + + out + } +} + +// BGP CONFIG (old version without max_paths) + +/// A base BGP configuration. +#[derive( + ObjectIdentity, Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq, +)] +pub struct BgpConfig { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// The autonomous system number of this BGP configuration. + pub asn: u32, + + /// Optional virtual routing and forwarding identifier for this BGP + /// configuration. + pub vrf: Option, +} + +// TODO: these conversion impls between initial types and +// `omicron_common::api::external` types should live in the later version +// module that introduced the shape change. They currently live here because +// `omicron-common-versions` does not yet exist. +impl From for BgpConfig { + fn from(new: external::BgpConfig) -> Self { + BgpConfig { identity: new.identity, asn: new.asn, vrf: new.vrf } + } +} + +impl From for external::BgpConfig { + fn from(old: BgpConfig) -> external::BgpConfig { + external::BgpConfig { + identity: old.identity, + asn: old.asn, + vrf: old.vrf, + max_paths: Default::default(), + } + } +} + +// SWITCH PORT SETTINGS (old response type with required BgpPeer.addr) + +/// Switch port settings (old version with required BgpPeer.addr). +// TODO: several fields below embed `external::*` types directly from +// `omicron-common`, which means their serialized shape is not truly frozen. +// Once `omicron-common-versions` exists, replace these with version-local +// copies of the types to ensure the initial version's wire format is +// immutable. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SwitchPortSettings { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// Switch port settings included from other switch port settings groups. + pub groups: Vec, + + /// Layer 1 physical port settings. + pub port: external::SwitchPortConfig, + + /// Layer 2 link settings. + pub links: Vec, + + /// Layer 3 interface settings. + pub interfaces: Vec, + + /// Vlan interface settings. + pub vlan_interfaces: Vec, + + /// IP route settings. + pub routes: Vec, + + /// BGP peer settings. + pub bgp_peers: Vec, + + /// Layer 3 IP address settings. + pub addresses: Vec, +} + +// TODO: this conversion impl should move out of the initial module once +// `omicron-common-versions` exists. See comment on `BgpPeer` above. +impl TryFrom for SwitchPortSettings { + type Error = external::Error; + + fn try_from( + new: external::SwitchPortSettings, + ) -> Result { + Ok(SwitchPortSettings { + identity: new.identity, + groups: new.groups, + port: new.port, + links: new.links, + interfaces: new.interfaces, + vlan_interfaces: new.vlan_interfaces, + routes: new.routes, + bgp_peers: new + .bgp_peers + .into_iter() + .map(BgpPeer::try_from) + .collect::, _>>()?, + addresses: new.addresses, + }) + } +} diff --git a/nexus/types/versions/src/initial/oxql.rs b/nexus/types/versions/src/initial/oxql.rs new file mode 100644 index 00000000000..44bc2ecfba3 --- /dev/null +++ b/nexus/types/versions/src/initial/oxql.rs @@ -0,0 +1,95 @@ +// 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/. + +//! OxQL query types for version INITIAL. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +// OxQL QUERIES + +/// A table represents one or more timeseries with the same schema. +/// +/// A table is the result of an OxQL query. It contains a name, usually the name +/// of the timeseries schema from which the data is derived, and any number of +/// timeseries, which contain the actual data. +// +// # Motivation +// +// This struct is derived from [`oxql_types::Table`] but presents timeseries data as a `Vec` +// rather than a map keyed by [`TimeseriesKey`]. This provides a cleaner JSON +// representation for external consumers, as these numeric keys are ephemeral +// identifiers that have no meaning to API consumers. Key ordering is retained +// as this is contructed from the already sorted values present in [`Table`]. +// +// When serializing a [`Table`] to JSON, the `BTreeMap` +// structure produces output with numeric keys like: +// ```json +// { +// "timeseries": { +// "2352746367989923131": { ... }, +// "3940108470521992408": { ... } +// } +// } +// ``` +// +// The `Table` view instead serializes timeseries as an array: +// ```json +// { +// "timeseries": [ { ... }, { ... } ] +// } +// ``` +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] +pub struct OxqlTable { + /// The name of the table. + pub name: String, + /// The set of timeseries in the table, ordered by key. + pub timeseries: Vec, +} + +impl From for OxqlTable { + fn from(table: oxql_types::Table) -> Self { + OxqlTable { + name: table.name.clone(), + timeseries: table.into_iter().collect(), + } + } +} + +/// Basic metadata about the resource usage of a query. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct OxqlQuerySummary { + /// The database-assigned query ID. + pub id: Uuid, + /// The raw query. + pub query: String, + /// The total duration of the query (network plus execution). + pub elapsed_ms: usize, + /// Summary of the data read and written. + pub io_summary: oxql_types::IoSummary, +} + +impl From for OxqlQuerySummary { + fn from(query_summary: oxql_types::QuerySummary) -> Self { + OxqlQuerySummary { + id: query_summary.id, + query: query_summary.query, + elapsed_ms: query_summary.elapsed.as_millis() as usize, + io_summary: query_summary.io_summary, + } + } +} + +/// The result of a successful OxQL query. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct OxqlQueryResult { + /// Tables resulting from the query, each containing timeseries. + pub tables: Vec, + /// Summaries of queries run against ClickHouse. Note: we omit this field + /// from the generated docs, since it is not intended for consumption by + /// customers. + #[schemars(skip)] + pub query_summaries: Option>, +} diff --git a/nexus/types/versions/src/initial/path_params.rs b/nexus/types/versions/src/initial/path_params.rs new file mode 100644 index 00000000000..34f8ec5042d --- /dev/null +++ b/nexus/types/versions/src/initial/path_params.rs @@ -0,0 +1,77 @@ +// 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/. + +//! Path parameter types for version INITIAL. + +use omicron_common::api::external::NameOrId; +use omicron_uuid_kinds::*; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +macro_rules! path_param { + ($struct:ident, $param:ident, $name:tt) => { + #[derive(Serialize, Deserialize, JsonSchema)] + pub struct $struct { + #[doc = "Name or ID of the "] + #[doc = $name] + pub $param: NameOrId, + } + }; +} + +macro_rules! id_path_param { + ($struct:ident, $param:ident, $name:tt) => { + id_path_param!($struct, $param, $name, Uuid); + }; + + ($struct:ident, $param:ident, $name:tt, $uuid_type:ident) => { + #[derive(Serialize, Deserialize, JsonSchema)] + pub struct $struct { + #[doc = "ID of the "] + #[doc = $name] + #[schemars(with = "Uuid")] + pub $param: $uuid_type, + } + }; +} + +path_param!(AffinityGroupPath, affinity_group, "affinity group"); +path_param!(AntiAffinityGroupPath, anti_affinity_group, "anti affinity group"); +path_param!(MulticastGroupPath, multicast_group, "multicast group"); +path_param!(ProjectPath, project, "project"); +path_param!(InstancePath, instance, "instance"); +path_param!(NetworkInterfacePath, interface, "network interface"); +path_param!(VpcPath, vpc, "VPC"); +path_param!(SubnetPath, subnet, "subnet"); +path_param!(RouterPath, router, "router"); +path_param!(RoutePath, route, "route"); +path_param!(InternetGatewayPath, gateway, "gateway"); +path_param!(FloatingIpPath, floating_ip, "floating IP"); +path_param!(DiskPath, disk, "disk"); +path_param!(SnapshotPath, snapshot, "snapshot"); +path_param!(ImagePath, image, "image"); +path_param!(SiloPath, silo, "silo"); +path_param!(ProviderPath, provider, "SAML identity provider"); +path_param!(IpPoolPath, pool, "IP pool"); +path_param!(IpAddressPath, address, "IP address"); +path_param!(SshKeyPath, ssh_key, "SSH key"); +path_param!(AddressLotPath, address_lot, "address lot"); +path_param!(ProbePath, probe, "probe"); +path_param!(CertificatePath, certificate, "certificate"); + +id_path_param!(GroupPath, group_id, "group", SiloGroupUuid); +id_path_param!(UserPath, user_id, "user", SiloUserUuid); +id_path_param!(TokenPath, token_id, "token"); +id_path_param!(TufTrustRootPath, trust_root_id, "trust root"); + +// TODO: The hardware resources should be represented by its UUID or a hardware +// ID that can be used to deterministically generate the UUID. +id_path_param!(RackPath, rack_id, "rack"); +id_path_param!(SledPath, sled_id, "sled", SledUuid); +id_path_param!(SwitchPath, switch_id, "switch"); +id_path_param!(PhysicalDiskPath, disk_id, "physical disk"); + +// Internal API parameters +id_path_param!(BlueprintPath, blueprint_id, "blueprint"); diff --git a/nexus/types/versions/src/initial/physical_disk.rs b/nexus/types/versions/src/initial/physical_disk.rs new file mode 100644 index 00000000000..24389016738 --- /dev/null +++ b/nexus/types/versions/src/initial/physical_disk.rs @@ -0,0 +1,112 @@ +// 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/. + +//! Physical disk types for version INITIAL. + +use super::asset::AssetIdentityMetadata; +use daft::Diffable; +use omicron_common::disk::DiskVariant; +use omicron_uuid_kinds::SledUuid; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use strum::EnumIter; +use uuid::Uuid; + +/// Describes the form factor of physical disks. +#[derive( + Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq, +)] +#[serde(rename_all = "snake_case")] +pub enum PhysicalDiskKind { + M2, + U2, +} + +impl From for PhysicalDiskKind { + fn from(dv: DiskVariant) -> Self { + match dv { + DiskVariant::M2 => PhysicalDiskKind::M2, + DiskVariant::U2 => PhysicalDiskKind::U2, + } + } +} + +/// View of a Physical Disk +/// +/// Physical disks reside in a particular sled and are used to store both +/// Instance Disk data as well as internal metadata. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] +pub struct PhysicalDisk { + #[serde(flatten)] + pub identity: AssetIdentityMetadata, + + /// The operator-defined policy for a physical disk. + pub policy: PhysicalDiskPolicy, + /// The current state Nexus believes the disk to be in. + pub state: PhysicalDiskState, + + /// The sled to which this disk is attached, if any. + #[schemars(with = "Option")] + pub sled_id: Option, + + pub vendor: String, + pub serial: String, + pub model: String, + + pub form_factor: PhysicalDiskKind, +} + +/// The operator-defined policy of a physical disk. +#[derive( + Copy, + Clone, + Debug, + Deserialize, + Serialize, + JsonSchema, + PartialEq, + Eq, + EnumIter, +)] +#[serde(rename_all = "snake_case", tag = "kind")] +pub enum PhysicalDiskPolicy { + /// The operator has indicated that the disk is in-service. + InService, + + /// The operator has indicated that the disk has been permanently removed + /// from service. + /// + /// This is a terminal state: once a particular disk ID is expunged, it + /// will never return to service. (The actual hardware may be reused, but + /// it will be treated as a brand-new disk.) + /// + /// An expunged disk is always non-provisionable. + Expunged, +} + +/// The current state of the disk, as determined by Nexus. +#[derive( + Copy, + Clone, + Debug, + Deserialize, + Serialize, + JsonSchema, + PartialEq, + Eq, + EnumIter, + Diffable, +)] +#[serde(rename_all = "snake_case")] +pub enum PhysicalDiskState { + /// The disk is currently active, and has resources allocated on it. + Active, + + /// The disk has been permanently removed from service. + /// + /// This is a terminal state: once a particular disk ID is decommissioned, + /// it will never return to service. (The actual hardware may be reused, + /// but it will be treated as a brand-new disk.) + Decommissioned, +} diff --git a/nexus/types/versions/src/initial/policy.rs b/nexus/types/versions/src/initial/policy.rs new file mode 100644 index 00000000000..aec88465b42 --- /dev/null +++ b/nexus/types/versions/src/initial/policy.rs @@ -0,0 +1,179 @@ +// 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/. + +//! Policy and role types for the Nexus external API. + +use omicron_uuid_kinds::GenericUuid; +use omicron_uuid_kinds::SiloGroupUuid; +use omicron_uuid_kinds::SiloUserUuid; +use parse_display::FromStr; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Deserializer; +use serde::Serialize; +use serde::de::Error as _; +use strum::EnumIter; +use uuid::Uuid; + +/// Maximum number of role assignments allowed on any one resource +// Today's implementation assumes a relatively small number of role assignments +// per resource. Things should work if we bump this up, but we'll want to look +// into scalability improvements (e.g., use pagination for fetching and updating +// the role assignments, and consider the impact on authz checks as well). +// +// Most importantly: by keeping this low to start with, it's impossible for +// customers to develop a dependency on a huge number of role assignments. That +// maximizes our flexibility in the future. +// +// TODO This should be runtime-configurable. But it doesn't belong in the Nexus +// configuration file, since it's a constraint on database objects more than it +// is Nexus. We should have some kinds of config that lives in the database. +pub const MAX_ROLE_ASSIGNMENTS_PER_RESOURCE: usize = 64; + +/// Policy for a particular resource +/// +/// Note that the Policy only describes access granted explicitly for this +/// resource. The policies of parent resources can also cause a user to have +/// access to this resource. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] +#[schemars(rename = "{AllowedRoles}Policy")] +pub struct Policy { + /// Roles directly assigned on this resource + #[serde(deserialize_with = "role_assignments_deserialize")] + pub role_assignments: Vec>, +} + +fn role_assignments_deserialize<'de, D, R>( + d: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, + R: serde::de::DeserializeOwned, +{ + let v = Vec::<_>::deserialize(d)?; + if v.len() > MAX_ROLE_ASSIGNMENTS_PER_RESOURCE { + return Err(D::Error::invalid_length( + v.len(), + &format!( + "a list of at most {} role assignments", + MAX_ROLE_ASSIGNMENTS_PER_RESOURCE + ) + .as_str(), + )); + } + Ok(v) +} + +/// Describes the assignment of a particular role on a particular resource to a +/// particular identity (user, group, etc.) +/// +/// The resource is not part of this structure. Rather, `RoleAssignment`s are +/// put into a `Policy` and that Policy is applied to a particular resource. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] +#[schemars(rename = "{AllowedRoles}RoleAssignment")] +pub struct RoleAssignment { + pub identity_type: IdentityType, + pub identity_id: Uuid, + pub role_name: AllowedRoles, +} + +impl RoleAssignment { + pub fn for_silo_user( + silo_user_id: SiloUserUuid, + role_name: AllowedRoles, + ) -> Self { + Self { + identity_type: IdentityType::SiloUser, + identity_id: silo_user_id.into_untyped_uuid(), + role_name, + } + } + + pub fn for_silo_group( + silo_group_id: SiloGroupUuid, + role_name: AllowedRoles, + ) -> Self { + Self { + identity_type: IdentityType::SiloGroup, + identity_id: silo_group_id.into_untyped_uuid(), + role_name, + } + } +} + +#[derive( + Clone, + Copy, + Debug, + Deserialize, + EnumIter, + Eq, + Ord, + PartialEq, + PartialOrd, + Serialize, + JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum FleetRole { + Admin, + Collaborator, + Viewer, + // There are other Fleet roles, but they are not externally-visible and so + // they do not show up in this enum. +} + +#[derive( + Clone, + Copy, + Debug, + Deserialize, + EnumIter, + Eq, + FromStr, + Ord, + PartialOrd, + PartialEq, + Serialize, + JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum SiloRole { + Admin, + Collaborator, + LimitedCollaborator, + Viewer, +} + +#[derive( + Clone, + Copy, + Debug, + Deserialize, + EnumIter, + Eq, + FromStr, + PartialEq, + Serialize, + JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum ProjectRole { + Admin, + Collaborator, + LimitedCollaborator, + Viewer, +} + +/// Describes what kind of identity is described by an id +// This is a subset of the identity types that might be found in the database +// because we do not expose some (e.g., built-in users) externally. +#[derive( + Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum IdentityType { + SiloUser, + SiloGroup, +} diff --git a/nexus/types/versions/src/initial/probe.rs b/nexus/types/versions/src/initial/probe.rs new file mode 100644 index 00000000000..310801500f5 --- /dev/null +++ b/nexus/types/versions/src/initial/probe.rs @@ -0,0 +1,64 @@ +// 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/. + +//! Probe types for version INITIAL. + +use std::net::IpAddr; + +use omicron_common::api::external::{ + IdentityMetadataCreateParams, Name, NameOrId, +}; +use omicron_common::api::internal::shared::network_interface::v1::NetworkInterface as NetworkInterfaceV1; +use omicron_uuid_kinds::SledUuid; +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, + #[schemars(with = "Uuid")] + pub sled: SledUuid, + pub external_ips: Vec, + // NOTE: This type currently appears in both the external and internal APIs. + // It's not used in the internal API anymore, and we've not yet expanded the + // external API to support dual-stack NICs. When we do, this whole type + // needs a new version in the external API, and the internal API needs to + // continue to refer to this original version. + pub interface: NetworkInterfaceV1, +} + +#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] +pub struct ProbeExternalIp { + pub ip: IpAddr, + pub first_port: u16, + pub last_port: u16, + pub kind: ProbeExternalIpKind, +} + +#[derive(Debug, Clone, Copy, JsonSchema, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ProbeExternalIpKind { + Snat, + Floating, + Ephemeral, +} + +/// Create time parameters for probes. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct ProbeCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + #[schemars(with = "Uuid")] + pub sled: SledUuid, + pub ip_pool: Option, +} + +/// List probes with an optional name or id. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq)] +pub struct ProbeListSelector { + /// A name or id to use when selecting a probe. + pub name_or_id: Option, +} diff --git a/nexus/types/versions/src/initial/project.rs b/nexus/types/versions/src/initial/project.rs new file mode 100644 index 00000000000..0956bb0b6c1 --- /dev/null +++ b/nexus/types/versions/src/initial/project.rs @@ -0,0 +1,49 @@ +// 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/. + +//! Project types for version INITIAL. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + IdentityMetadata, IdentityMetadataCreateParams, + IdentityMetadataUpdateParams, NameOrId, ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct ProjectSelector { + /// Name or ID of the project + pub project: NameOrId, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct OptionalProjectSelector { + /// Name or ID of the project + pub project: Option, +} + +/// View of a Project +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Project { + // TODO-correctness is flattening here (and in all the other types) the + // intent in RFD 4? + #[serde(flatten)] + pub identity: IdentityMetadata, + // Important: Silo ID does not get presented to user +} + +/// Create-time parameters for a `Project` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct ProjectCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, +} + +/// Updateable properties of a `Project` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct ProjectUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, +} diff --git a/nexus/types/versions/src/initial/rack.rs b/nexus/types/versions/src/initial/rack.rs new file mode 100644 index 00000000000..d363a8b0a76 --- /dev/null +++ b/nexus/types/versions/src/initial/rack.rs @@ -0,0 +1,16 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Rack types for version INITIAL. + +use super::asset::AssetIdentityMetadata; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// View of a Rack +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Rack { + #[serde(flatten)] + pub identity: AssetIdentityMetadata, +} diff --git a/nexus/types/versions/src/initial/saml.rs b/nexus/types/versions/src/initial/saml.rs new file mode 100644 index 00000000000..4eae534691d --- /dev/null +++ b/nexus/types/versions/src/initial/saml.rs @@ -0,0 +1,21 @@ +// 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/. + +//! SAML-related types for version INITIAL. + +use parse_display::Display; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// This is meant as a security feature. We want to ensure we never redirect to +/// a URI on a different host. +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, Display)] +#[serde(try_from = "String")] +#[display("{0}")] +pub struct RelativeUri(pub(crate) String); + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct RelayState { + pub redirect_uri: Option, +} diff --git a/nexus/types/versions/src/initial/scim.rs b/nexus/types/versions/src/initial/scim.rs new file mode 100644 index 00000000000..9b695f38a24 --- /dev/null +++ b/nexus/types/versions/src/initial/scim.rs @@ -0,0 +1,46 @@ +// 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/. + +//! SCIM client types for version INITIAL. + +use chrono::{DateTime, Utc}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +// SCIM + +/// The POST response is the only time the generated bearer token is returned to +/// the client. +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct ScimClientBearerTokenValue { + pub id: Uuid, + pub time_created: DateTime, + pub time_expires: Option>, + pub bearer_token: String, +} + +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct ScimClientBearerToken { + pub id: Uuid, + pub time_created: DateTime, + pub time_expires: Option>, +} + +// SCIM PARAMS + +#[derive(Deserialize, JsonSchema)] +pub struct ScimV2TokenPathParam { + pub token_id: Uuid, +} + +#[derive(Deserialize, JsonSchema)] +pub struct ScimV2UserPathParam { + pub user_id: String, +} + +#[derive(Deserialize, JsonSchema)] +pub struct ScimV2GroupPathParam { + pub group_id: String, +} diff --git a/nexus/types/versions/src/initial/silo.rs b/nexus/types/versions/src/initial/silo.rs new file mode 100644 index 00000000000..bea8b555081 --- /dev/null +++ b/nexus/types/versions/src/initial/silo.rs @@ -0,0 +1,247 @@ +// 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/. + +//! Silo-related types for the Nexus external API. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + ByteCount, IdentityMetadata, IdentityMetadataCreateParams, Name, NameOrId, + Nullable, ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use std::collections::{BTreeMap, BTreeSet}; +use std::num::NonZeroU32; +use uuid::Uuid; + +use super::certificate::CertificateCreate; +use super::policy::{FleetRole, SiloRole}; + +/// Describes how identities are managed and users are authenticated in this +/// Silo +#[derive( + Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum SiloIdentityMode { + /// Users are authenticated with SAML using an external authentication + /// provider. The system updates information about users and groups only + /// during successful authentication (i.e,. "JIT provisioning" of users and + /// groups). + SamlJit, + + /// The system is the source of truth about users. There is no linkage to + /// an external authentication provider or identity provider. + // NOTE: authentication for these users is not supported yet at all. It + // will eventually be password-based. + LocalOnly, + + /// Users are authenticated with SAML using an external authentication + /// provider. Users and groups are managed with SCIM API calls, likely from + /// the same authentication provider. + SamlScim, +} + +/// How users are authenticated in this Silo +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum AuthenticationMode { + /// Authentication is via SAML using an external authentication provider + Saml, + + /// Authentication is local to the Oxide system + Local, +} + +/// How users will be provisioned in a silo during authentication. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum UserProvisionType { + /// Identities are managed directly by explicit calls to the external API. + /// They are not synchronized from any external identity provider nor + /// automatically created or updated when a user logs in. + ApiOnly, + + /// Users and groups are created or updated during authentication using + /// information provided by the authentication provider + Jit, + + /// Users and groups are managed by SCIM + Scim, +} + +// View types + +/// View of a Silo +/// +/// A Silo is the highest level unit of isolation. +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Silo { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// A silo where discoverable is false can be retrieved only by its id - it + /// will not be part of the "list all silos" output. + pub discoverable: bool, + + /// How users and groups are managed in this Silo + pub identity_mode: SiloIdentityMode, + + /// Mapping of which Fleet roles are conferred by each Silo role + /// + /// The default is that no Fleet roles are conferred by any Silo roles + /// unless there's a corresponding entry in this map. + pub mapped_fleet_roles: BTreeMap>, + + /// Optionally, silos can have a group name that is automatically granted + /// the silo admin role. + pub admin_group_name: Option, +} + +/// A collection of resource counts used to describe capacity and utilization +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema)] +pub struct VirtualResourceCounts { + /// Number of virtual CPUs + pub cpus: i64, + /// Amount of memory in bytes + pub memory: ByteCount, + /// Amount of disk storage in bytes + pub storage: ByteCount, +} + +/// A collection of resource counts used to set the virtual capacity of a silo +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SiloQuotas { + pub silo_id: Uuid, + #[serde(flatten)] + pub limits: VirtualResourceCounts, +} + +/// View of the current silo's resource utilization and capacity +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Utilization { + /// Accounts for resources allocated to running instances or storage + /// allocated via disks or snapshots. + /// + /// Note that CPU and memory resources associated with stopped instances + /// are not counted here, whereas associated disks will still be counted. + pub provisioned: VirtualResourceCounts, + /// The total amount of resources that can be provisioned in this silo. + /// Actions that would exceed this limit will fail. + pub capacity: VirtualResourceCounts, +} + +/// View of a silo's resource utilization and capacity +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SiloUtilization { + pub silo_id: Uuid, + pub silo_name: Name, + /// Accounts for the total resources allocated by the silo, including CPU + /// and memory for running instances and storage for disks and snapshots. + /// + /// Note that CPU and memory resources associated with stopped instances + /// are not counted here. + pub provisioned: VirtualResourceCounts, + /// Accounts for the total amount of resources reserved for silos via + /// their quotas. + pub allocated: VirtualResourceCounts, +} + +/// View of silo authentication settings +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SiloAuthSettings { + pub silo_id: Uuid, + /// Maximum lifetime of a device token in seconds. If set to null, users + /// will be able to create tokens that do not expire. + pub device_token_max_ttl_seconds: Option, +} + +// Params + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct SiloSelector { + /// Name or ID of the silo + pub silo: NameOrId, +} + +impl From for SiloSelector { + fn from(name: Name) -> Self { + SiloSelector { silo: name.into() } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct OptionalSiloSelector { + /// Name or ID of the silo + pub silo: Option, +} + +/// Create-time parameters for a `Silo` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SiloCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + pub discoverable: bool, + + pub identity_mode: SiloIdentityMode, + + /// If set, this group will be created during Silo creation and granted the + /// "Silo Admin" role. Identity providers can assert that users belong to + /// this group and those users can log in and further initialize the Silo. + /// + /// Note that if configuring a SAML based identity provider, + /// group_attribute_name must be set for users to be considered part of a + /// group. See `SamlIdentityProviderCreate` for more information. + pub admin_group_name: Option, + + /// Initial TLS certificates to be used for the new Silo's console and API + /// endpoints. These should be valid for the Silo's DNS name(s). + pub tls_certificates: Vec, + + /// Limits the amount of provisionable CPU, memory, and storage in the Silo. + /// CPU and memory are only consumed by running instances, while storage is + /// consumed by any disk or snapshot. A value of 0 means that resource is + /// *not* provisionable. + pub quotas: SiloQuotasCreate, + + /// Mapping of which Fleet roles are conferred by each Silo role + /// + /// The default is that no Fleet roles are conferred by any Silo roles + /// unless there's a corresponding entry in this map. + #[serde(default)] + pub mapped_fleet_roles: BTreeMap>, +} + +/// The amount of provisionable resources for a Silo +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SiloQuotasCreate { + /// The amount of virtual CPUs available for running instances in the Silo + pub cpus: i64, + /// The amount of RAM (in bytes) available for running instances in the Silo + pub memory: ByteCount, + /// The amount of storage (in bytes) available for disks or snapshots + pub storage: ByteCount, +} + +/// Updateable properties of a Silo's resource limits. +/// If a value is omitted it will not be updated. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SiloQuotasUpdate { + /// The amount of virtual CPUs available for running instances in the Silo + pub cpus: Option, + /// The amount of RAM (in bytes) available for running instances in the Silo + pub memory: Option, + /// The amount of storage (in bytes) available for disks or snapshots + pub storage: Option, +} + +/// Updateable properties of a silo's settings. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct SiloAuthSettingsUpdate { + /// Maximum lifetime of a device token in seconds. If set to null, users + /// will be able to create tokens that do not expire. + pub device_token_max_ttl_seconds: Nullable, +} diff --git a/nexus/types/versions/src/initial/sled.rs b/nexus/types/versions/src/initial/sled.rs new file mode 100644 index 00000000000..bf08b7174ef --- /dev/null +++ b/nexus/types/versions/src/initial/sled.rs @@ -0,0 +1,162 @@ +// 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/. + +//! Sled types for version INITIAL. + +use super::asset::AssetIdentityMetadata; +use super::hardware::Baseboard; +use daft::Diffable; +use omicron_common::api::external::{ByteCount, InstanceState, Name}; +use omicron_uuid_kinds::SledUuid; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use strum::EnumIter; +use uuid::Uuid; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct SledSelector { + /// ID of the sled + #[schemars(with = "Uuid")] + pub sled: SledUuid, +} + +/// Parameters for `sled_set_provision_policy`. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct SledProvisionPolicyParams { + /// The provision state. + pub state: SledProvisionPolicy, +} + +/// Response to `sled_set_provision_policy`. +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct SledProvisionPolicyResponse { + /// The old provision state. + pub old_state: SledProvisionPolicy, + + /// The new provision state. + pub new_state: SledProvisionPolicy, +} + +pub struct SwitchSelector { + /// ID of the switch + pub switch: Uuid, +} + +/// The unique ID of a sled. +#[derive(Clone, Debug, Serialize, JsonSchema)] +pub struct SledId { + #[schemars(with = "Uuid")] + pub id: SledUuid, +} + +/// An operator's view of a Sled. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Sled { + #[serde(flatten)] + pub identity: AssetIdentityMetadata, + pub baseboard: Baseboard, + /// The rack to which this Sled is currently attached + pub rack_id: Uuid, + /// The operator-defined policy of a sled. + pub policy: SledPolicy, + /// The current state of the sled. + pub state: SledState, + /// The number of hardware threads which can execute on this sled + pub usable_hardware_threads: u32, + /// Amount of RAM which may be used by the Sled's OS + pub usable_physical_ram: ByteCount, +} + +/// The operator-defined provision policy of a sled. +/// +/// This controls whether new resources are going to be provisioned on this +/// sled. +#[derive( + Copy, + Clone, + Debug, + Deserialize, + Serialize, + JsonSchema, + PartialEq, + Eq, + EnumIter, +)] +#[serde(rename_all = "snake_case")] +pub enum SledProvisionPolicy { + /// New resources will be provisioned on this sled. + Provisionable, + + /// New resources will not be provisioned on this sled. However, if the + /// sled is currently in service, existing resources will continue to be on + /// this sled unless manually migrated off. + NonProvisionable, +} + +/// The operator-defined policy of a sled. +#[derive( + Copy, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, Eq, +)] +#[serde(rename_all = "snake_case", tag = "kind")] +pub enum SledPolicy { + /// The operator has indicated that the sled is in-service. + InService { + /// Determines whether new resources can be provisioned onto the sled. + provision_policy: SledProvisionPolicy, + }, + + /// The operator has indicated that the sled has been permanently removed + /// from service. + /// + /// This is a terminal state: once a particular sled ID is expunged, it + /// will never return to service. (The actual hardware may be reused, but + /// it will be treated as a brand-new sled.) + /// + /// An expunged sled is always non-provisionable. + Expunged, + // + // NOTE: If you add another variant here, be sure to update `impl + // IntoEnumIterator for SledPolicy` in `impls/sled.rs`with the new variant. +} + +/// The current state of the sled. +#[derive( + Copy, + Clone, + Debug, + Deserialize, + Serialize, + JsonSchema, + PartialEq, + Eq, + EnumIter, + Diffable, +)] +#[serde(rename_all = "snake_case")] +pub enum SledState { + /// The sled is currently active, and has resources allocated on it. + Active, + + /// The sled has been permanently removed from service. + /// + /// This is a terminal state: once a particular sled ID is decommissioned, + /// it will never return to service. (The actual hardware may be reused, + /// but it will be treated as a brand-new sled.) + Decommissioned, +} + +/// An operator's view of an instance running on a given sled +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SledInstance { + #[serde(flatten)] + pub identity: AssetIdentityMetadata, + pub active_sled_id: Uuid, + pub migration_id: Option, + pub name: Name, + pub silo_name: Name, + pub project_name: Name, + pub state: InstanceState, + pub ncpus: i64, + pub memory: i64, +} diff --git a/nexus/types/versions/src/initial/snapshot.rs b/nexus/types/versions/src/initial/snapshot.rs new file mode 100644 index 00000000000..4d7e987dc6e --- /dev/null +++ b/nexus/types/versions/src/initial/snapshot.rs @@ -0,0 +1,56 @@ +// 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/. + +//! Snapshot types for version INITIAL. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + ByteCount, IdentityMetadata, IdentityMetadataCreateParams, NameOrId, + ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Deserialize, JsonSchema)] +pub struct SnapshotSelector { + /// Name or ID of the project, only required if `snapshot` is provided as a `Name` + pub project: Option, + /// Name or ID of the snapshot + pub snapshot: NameOrId, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SnapshotState { + Creating, + Ready, + Faulted, + Destroyed, +} + +/// View of a Snapshot +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Snapshot { + #[serde(flatten)] + pub identity: IdentityMetadata, + + pub project_id: Uuid, + pub disk_id: Uuid, + + pub state: SnapshotState, + + pub size: ByteCount, +} + +/// Create-time parameters for a `Snapshot` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SnapshotCreate { + /// common identifying metadata + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// The disk to be snapshotted + pub disk: NameOrId, +} diff --git a/nexus/types/versions/src/initial/ssh_key.rs b/nexus/types/versions/src/initial/ssh_key.rs new file mode 100644 index 00000000000..f9fc43a31ad --- /dev/null +++ b/nexus/types/versions/src/initial/ssh_key.rs @@ -0,0 +1,51 @@ +// 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/. + +//! SSH key types for version INITIAL. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + IdentityMetadata, IdentityMetadataCreateParams, NameOrId, ObjectIdentity, +}; +use omicron_uuid_kinds::SiloUserUuid; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// View of an SSH Key +#[derive( + ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema, PartialEq, +)] +pub struct SshKey { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// The user to whom this key belongs + #[schemars(with = "Uuid")] + pub silo_user_id: SiloUserUuid, + + /// SSH public key, e.g., `"ssh-ed25519 AAAAC3NzaC..."` + pub public_key: String, +} + +// Params + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct SshKeySelector { + /// ID of the silo user + #[schemars(with = "Uuid")] + pub silo_user_id: SiloUserUuid, + /// Name or ID of the SSH key + pub ssh_key: NameOrId, +} + +/// Create-time parameters for an `SshKey` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct SshKeyCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// SSH public key, e.g., `"ssh-ed25519 AAAAC3NzaC..."` + pub public_key: String, +} diff --git a/nexus/types/versions/src/initial/support_bundle.rs b/nexus/types/versions/src/initial/support_bundle.rs new file mode 100644 index 00000000000..c3269349b01 --- /dev/null +++ b/nexus/types/versions/src/initial/support_bundle.rs @@ -0,0 +1,82 @@ +// 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/. + +//! Support bundle types for the Nexus external API. + +use chrono::DateTime; +use chrono::Utc; +use omicron_uuid_kinds::SupportBundleUuid; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use uuid::Uuid; + +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct SupportBundlePath { + #[doc = "ID of the "] + #[doc = "support bundle"] + pub bundle_id: Uuid, +} + +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct SupportBundleFilePath { + #[serde(flatten)] + pub bundle: SupportBundlePath, + + /// The file within the bundle to download + pub file: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SupportBundleCreate { + /// User comment for the support bundle + pub user_comment: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct SupportBundleUpdate { + /// User comment for the support bundle + pub user_comment: Option, +} + +#[derive( + Debug, Clone, Copy, JsonSchema, Serialize, Deserialize, Eq, PartialEq, +)] +#[serde(rename_all = "snake_case")] +pub enum SupportBundleState { + /// Support Bundle still actively being collected. + /// + /// This is the initial state for a Support Bundle, and it will + /// automatically transition to either "Failing" or "Active". + /// + /// If a user no longer wants to access a Support Bundle, they can + /// request cancellation, which will transition to the "Destroying" state. + Collecting, + + /// Support Bundle is being destroyed. + /// + /// Once backing storage has been freed, this bundle is destroyed. + Destroying, + + /// Support Bundle was not created successfully, or was created and has lost + /// backing storage. + /// + /// The record of the bundle still exists for readability, but the only + /// valid operation on these bundles is to destroy them. + Failed, + + /// Support Bundle has been processed, and is ready for usage. + Active, +} + +#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)] +pub struct SupportBundleInfo { + #[schemars(with = "Uuid")] + pub id: SupportBundleUuid, + pub time_created: DateTime, + pub reason_for_creation: String, + pub reason_for_failure: Option, + pub user_comment: Option, + pub state: SupportBundleState, +} diff --git a/nexus/types/versions/src/initial/switch.rs b/nexus/types/versions/src/initial/switch.rs new file mode 100644 index 00000000000..a728b02dc23 --- /dev/null +++ b/nexus/types/versions/src/initial/switch.rs @@ -0,0 +1,45 @@ +// 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/. + +//! Switch types for version INITIAL. + +use super::asset::AssetIdentityMetadata; +use super::hardware::Baseboard; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// Opaque object representing link state. The contents of this object are not +/// yet stable. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct SwitchLinkState { + pub(crate) link: serde_json::Value, + pub(crate) monitors: Option, +} + +impl schemars::JsonSchema for SwitchLinkState { + fn json_schema( + r#gen: &mut schemars::r#gen::SchemaGenerator, + ) -> schemars::schema::Schema { + let obj = schemars::schema::Schema::Object( + schemars::schema::SchemaObject::default(), + ); + r#gen.definitions_mut().insert(Self::schema_name(), obj.clone()); + obj + } + + fn schema_name() -> String { + "SwitchLinkState".to_owned() + } +} + +/// An operator's view of a Switch. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Switch { + #[serde(flatten)] + pub identity: AssetIdentityMetadata, + pub baseboard: Baseboard, + /// The rack to which this Switch is currently attached + pub rack_id: Uuid, +} diff --git a/nexus/types/versions/src/initial/system.rs b/nexus/types/versions/src/initial/system.rs new file mode 100644 index 00000000000..8f9aa92d512 --- /dev/null +++ b/nexus/types/versions/src/initial/system.rs @@ -0,0 +1,47 @@ +// 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/. + +//! System health types for version INITIAL. + +use chrono::{DateTime, Utc}; +use omicron_common::api::external::AllowedSourceIps as ExternalAllowedSourceIps; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +// SYSTEM HEALTH + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum PingStatus { + Ok, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct Ping { + /// Whether the external API is reachable. Will always be Ok if the endpoint + /// returns anything at all. + pub status: PingStatus, +} + +// ALLOWED SOURCE IPS + +/// Allowlist of IPs or subnets that can make requests to user-facing services. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct AllowList { + /// Time the list was created. + pub time_created: DateTime, + /// Time the list was last modified. + pub time_modified: DateTime, + /// The allowlist of IPs or subnets. + pub allowed_ips: ExternalAllowedSourceIps, +} + +// SYSTEM PARAMS + +/// Parameters for updating allowed source IPs +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct AllowListUpdate { + /// The new list of allowed source IPs. + pub allowed_ips: ExternalAllowedSourceIps, +} diff --git a/nexus/types/versions/src/initial/timeseries.rs b/nexus/types/versions/src/initial/timeseries.rs new file mode 100644 index 00000000000..d8d3734c090 --- /dev/null +++ b/nexus/types/versions/src/initial/timeseries.rs @@ -0,0 +1,21 @@ +// 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/. + +//! Timeseries query parameters for the Nexus external API. + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// A timeseries query string, written in the Oximeter query language. +#[derive(Deserialize, JsonSchema, Serialize)] +pub struct TimeseriesQuery { + /// A timeseries query string, written in the Oximeter query language. + pub query: String, + /// Whether to include query summaries in the response. Note: we omit this + /// field from the generated docs, since it is not intended for consumption + /// by customers. + #[serde(default)] + #[schemars(skip)] + pub include_summaries: bool, +} diff --git a/nexus/types/versions/src/initial/update.rs b/nexus/types/versions/src/initial/update.rs new file mode 100644 index 00000000000..450380c99f1 --- /dev/null +++ b/nexus/types/versions/src/initial/update.rs @@ -0,0 +1,185 @@ +// 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/. + +//! Update-related types for version INITIAL. + +use chrono::{DateTime, Utc}; +use omicron_common::api::external::Nullable; +use schemars::JsonSchema; +use semver::Version; +use serde::{Deserialize, Deserializer, Serialize, de::Error as _}; +use slog_error_chain::InlineErrorChain; +use std::collections::BTreeMap; +use tufaceous_artifact::ArtifactHash; +use uuid::Uuid; + +/// Wrapper type for TUF root roles to prevent misuse. +/// +/// The format of TUF root roles is an implementation detail and Nexus should +/// generally treat them as opaque JSON blobs without inspecting any fields +/// or, especially, adding any data to them. This value should be created only +/// through Serde deserialization. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)] +#[serde(transparent)] +pub struct TufSignedRootRole(serde_json::Value); + +impl TufSignedRootRole { + pub fn to_bytes(&self) -> Vec { + self.0.to_string().into_bytes() + } +} + +// We'd like to use `#[serde(try_from = ..)]` here but it conflicts with +// `#[serde(transparent)]`, which we're using for the Serialize derive. +impl<'de> Deserialize<'de> for TufSignedRootRole { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + use tough::schema::{Root, Signed}; + + let value = serde_json::Value::deserialize(deserializer)?; + // Verify that this appears to be a valid, self-signed TUF root role. + let root = + >::deserialize(&value).map_err(D::Error::custom)?; + match root.signed.verify_role(&root) { + Ok(()) => Ok(Self(value)), + Err(err) => Err(D::Error::custom(format!( + "Unable to verify root role: {}", + InlineErrorChain::new(&err) + ))), + } + } +} + +/// View of a system software target release +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] +pub struct TargetRelease { + /// Time this was set as the target release + pub time_requested: DateTime, + + /// The specified release of the rack's system software + pub version: Version, +} + +/// Trusted root role used by the update system to verify update repositories. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] +pub struct UpdatesTrustRoot { + /// The UUID of this trusted root role. + pub id: Uuid, + /// Time the trusted root role was added. + pub time_created: DateTime, + /// The trusted root role itself, a JSON document as described by The Update + /// Framework. + pub root_role: TufSignedRootRole, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct UpdateStatus { + /// Current target release of the system software + /// + /// This may not correspond to the actual system software running + /// at the time of request; it is instead the release that the system + /// should be moving towards as a goal state. The system asynchronously + /// updates software to match this target release. + /// + /// Will only be null if a target release has never been set. In that case, + /// the system is not automatically attempting to manage software versions. + pub target_release: Nullable, + + /// Count of components running each release version + /// + /// Keys will be either: + /// + /// * Semver-like release version strings + /// * "install dataset", representing the initial rack software before + /// any updates + /// * "unknown", which means there is no TUF repo uploaded that matches + /// the software running on the component) + pub components_by_release_version: BTreeMap, + + /// Time of most recent update planning activity + /// + /// This is intended as a rough indicator of the last time something + /// happened in the update planner. + pub time_last_step_planned: DateTime, + + /// Whether automatic update is suspended due to manual update activity + /// + /// After a manual support procedure that changes the system software, + /// automatic update activity is suspended to avoid undoing the change. To + /// resume automatic update, first upload the TUF repository matching the + /// manually applied update, then set that as the target release. + pub suspended: bool, +} + +/// Metadata about a TUF repository +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +pub struct TufRepo { + /// The hash of the repository + // This is a slight abuse of `ArtifactHash`, since that's the hash of + // individual artifacts within the repository. However, we use it here for + // convenience. + pub hash: ArtifactHash, + + /// The system version for this repository + /// + /// The system version is a top-level version number applied to all the + /// software in the repository. + pub system_version: Version, + + /// The file name of the repository, as reported by the client that uploaded + /// it + /// + /// This is intended for debugging. The file name may not match any + /// particular pattern, and even if it does, it may not be accurate since + /// it's just what the client reported. + // (e.g., with wicket, we read the file contents from stdin so we don't know + // the correct file name). + pub file_name: String, + + /// Time the repository was uploaded + pub time_created: DateTime, +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +pub struct TufRepoUpload { + pub repo: TufRepo, + pub status: TufRepoUploadStatus, +} + +/// Whether the uploaded TUF repo already existed or was new and had to be +/// inserted. Part of `TufRepoUpload`. +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema, +)] +#[serde(rename_all = "snake_case")] +pub enum TufRepoUploadStatus { + /// The repository already existed in the database + AlreadyExists, + + /// The repository did not exist, and was inserted into the database + Inserted, +} + +/// Parameters for PUT requests for `/v1/system/update/repositories`. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct UpdatesPutRepositoryParams { + /// The name of the uploaded file. + pub file_name: String, +} + +/// Parameters for GET requests for `/v1/system/update/repositories`. +#[derive(Clone, Debug, Deserialize, JsonSchema)] +pub struct UpdatesGetRepositoryParams { + /// The version to get. + pub system_version: Version, +} + +/// Parameters for PUT requests to `/v1/system/update/target-release`. +#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] +pub struct SetTargetReleaseParams { + /// Version of the system software to make the target release. + pub system_version: Version, +} diff --git a/nexus/types/versions/src/initial/user.rs b/nexus/types/versions/src/initial/user.rs new file mode 100644 index 00000000000..f3cc355a846 --- /dev/null +++ b/nexus/types/versions/src/initial/user.rs @@ -0,0 +1,163 @@ +// 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/. + +//! User types for version INITIAL. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + IdentityMetadata, IdentityMetadataCreateParams, Name, NameOrId, + ObjectIdentity, UserId, +}; +use omicron_uuid_kinds::{SiloGroupUuid, SiloUserUuid}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// View of a User +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] +pub struct User { + #[schemars(with = "Uuid")] + pub id: SiloUserUuid, + + /// Human-readable name that can identify the user + pub display_name: String, + + /// Uuid of the silo to which this user belongs + pub silo_id: Uuid, +} + +/// Info about the current user +// Add silo name to User because the console needs to display it +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] +pub struct CurrentUser { + #[serde(flatten)] + pub user: User, + /// Name of the silo to which this user belongs. + pub silo_name: Name, + /// Whether this user has the viewer role on the fleet. Used by the web + /// console to determine whether to show system-level UI. + pub fleet_viewer: bool, + /// Whether this user has the admin role on their silo. Used by the web + /// console to determine whether to show admin-only UI elements. + pub silo_admin: bool, +} + +/// View of a Group +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, JsonSchema)] +pub struct Group { + #[schemars(with = "Uuid")] + pub id: SiloGroupUuid, + + /// Human-readable name that can identify the group + pub display_name: String, + + /// Uuid of the silo to which this group belongs + pub silo_id: Uuid, +} + +/// View of a Built-in User +/// +/// Built-in users are identities internal to the system, used when the control +/// plane performs actions autonomously +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct UserBuiltin { + // TODO-correctness is flattening here (and in all the other types) the + // intent in RFD 4? + #[serde(flatten)] + pub identity: IdentityMetadata, +} + +// Params + +/// Path parameters for Silo User requests +#[derive(Deserialize, JsonSchema)] +pub struct UserParam { + /// The user's internal ID + #[schemars(with = "Uuid")] + pub user_id: SiloUserUuid, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct OptionalGroupSelector { + #[schemars(with = "Option")] + pub group: Option, +} + +/// Create-time parameters for a `User` +#[derive(Clone, Deserialize, JsonSchema)] +pub struct UserCreate { + /// Username used to log in + pub external_id: UserId, + /// How to set the user's login password + pub password: UserPassword, +} + +/// A password used for authenticating a local-only user +#[derive(Clone, Deserialize)] +#[serde(try_from = "String")] +pub struct Password(pub(crate) omicron_passwords::Password); + +impl schemars::JsonSchema for Password { + fn schema_name() -> String { + "Password".to_string() + } + + fn json_schema( + _: &mut schemars::r#gen::SchemaGenerator, + ) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + metadata: Some(Box::new(schemars::schema::Metadata { + title: Some( + "A password used to authenticate a user".to_string(), + ), + description: Some( + "Passwords may be subject to additional constraints." + .to_string(), + ), + ..Default::default() + })), + instance_type: Some(schemars::schema::InstanceType::String.into()), + string: Some(Box::new(schemars::schema::StringValidation { + max_length: Some( + u32::try_from(omicron_passwords::MAX_PASSWORD_LENGTH) + .unwrap(), + ), + min_length: None, + pattern: None, + })), + ..Default::default() + } + .into() + } +} + +/// Parameters for setting a user's password +#[derive(Clone, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "mode", content = "value")] +pub enum UserPassword { + /// Sets the user's password to the provided value + Password(Password), + /// Invalidates any current password (disabling password authentication) + LoginDisallowed, +} + +/// Credentials for local user login +#[derive(Clone, Deserialize, JsonSchema)] +pub struct UsernamePasswordCredentials { + pub username: UserId, + pub password: Password, +} + +/// Create-time parameters for a `UserBuiltin` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct UserBuiltinCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct UserBuiltinSelector { + pub user: NameOrId, +} diff --git a/nexus/types/versions/src/initial/vpc.rs b/nexus/types/versions/src/initial/vpc.rs new file mode 100644 index 00000000000..9732fddc9e1 --- /dev/null +++ b/nexus/types/versions/src/initial/vpc.rs @@ -0,0 +1,236 @@ +// 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/. + +//! VPC types for version INITIAL. + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + IdentityMetadata, IdentityMetadataCreateParams, + IdentityMetadataUpdateParams, Name, NameOrId, ObjectIdentity, + RouteDestination, RouteTarget, +}; +use oxnet::{Ipv4Net, Ipv6Net}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct VpcSelector { + /// Name or ID of the project, only required if `vpc` is provided as a `Name` + pub project: Option, + /// Name or ID of the VPC + pub vpc: NameOrId, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct OptionalVpcSelector { + /// Name or ID of the project, only required if `vpc` is provided as a `Name` + pub project: Option, + /// Name or ID of the VPC + pub vpc: Option, +} + +#[derive(Deserialize, JsonSchema)] +pub struct SubnetSelector { + /// Name or ID of the project, only required if `vpc` is provided as a `Name` + pub project: Option, + /// Name or ID of the VPC, only required if `subnet` is provided as a `Name` + pub vpc: Option, + /// Name or ID of the subnet + pub subnet: NameOrId, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct RouterSelector { + /// Name or ID of the project, only required if `vpc` is provided as a `Name` + pub project: Option, + /// Name or ID of the VPC, only required if `router` is provided as a `Name` + pub vpc: Option, + /// Name or ID of the router + pub router: NameOrId, +} + +#[derive(Deserialize, JsonSchema)] +pub struct OptionalRouterSelector { + /// Name or ID of the project, only required if `vpc` is provided as a `Name` + pub project: Option, + /// Name or ID of the VPC, only required if `router` is provided as a `Name` + pub vpc: Option, + /// Name or ID of the router + pub router: Option, +} + +#[derive(Deserialize, JsonSchema)] +pub struct RouteSelector { + /// Name or ID of the project, only required if `vpc` is provided as a `Name` + pub project: Option, + /// Name or ID of the VPC, only required if `router` is provided as a `Name` + pub vpc: Option, + /// Name or ID of the router, only required if `route` is provided as a `Name` + pub router: Option, + /// Name or ID of the route + pub route: NameOrId, +} + +/// View of a VPC +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Vpc { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// ID for the project containing this VPC + pub project_id: Uuid, + + /// ID for the system router where subnet default routes are registered + pub system_router_id: Uuid, + + /// The unique local IPv6 address range for subnets in this VPC + pub ipv6_prefix: Ipv6Net, + + // TODO-design should this be optional? + /// The name used for the VPC in DNS. + pub dns_name: Name, +} + +/// A VPC subnet represents a logical grouping for instances that allows network traffic between +/// them, within an IPv4 subnetwork or optionally an IPv6 subnetwork. +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct VpcSubnet { + /// common identifying metadata + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// The VPC to which the subnet belongs. + pub vpc_id: Uuid, + + /// The IPv4 subnet CIDR block. + pub ipv4_block: Ipv4Net, + + /// The IPv6 subnet CIDR block. + pub ipv6_block: Ipv6Net, + + /// ID for an attached custom router. + pub custom_router_id: Option, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum VpcRouterKind { + System, + Custom, +} + +/// A VPC router defines a series of rules that indicate where traffic +/// should be sent depending on its destination. +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct VpcRouter { + /// common identifying metadata + #[serde(flatten)] + pub identity: IdentityMetadata, + + pub kind: VpcRouterKind, + + /// The VPC to which the router belongs. + pub vpc_id: Uuid, +} + +/// Create-time parameters for a `Vpc` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct VpcCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// The IPv6 prefix for this VPC + /// + /// All IPv6 subnets created from this VPC must be taken from this range, + /// which should be a Unique Local Address in the range `fd00::/48`. The + /// default VPC Subnet will have the first `/64` range from this prefix. + pub ipv6_prefix: Option, + + pub dns_name: Name, +} + +/// Updateable properties of a `Vpc` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct VpcUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, + pub dns_name: Option, +} + +/// Create-time parameters for a `VpcSubnet` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct VpcSubnetCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// The IPv4 address range for this subnet. + /// + /// It must be allocated from an RFC 1918 private address range, and must + /// not overlap with any other existing subnet in the VPC. + pub ipv4_block: Ipv4Net, + + /// The IPv6 address range for this subnet. + /// + /// It must be allocated from the RFC 4193 Unique Local Address range, with + /// the prefix equal to the parent VPC's prefix. A random `/64` block will + /// be assigned if one is not provided. It must not overlap with any + /// existing subnet in the VPC. + pub ipv6_block: Option, + + /// An optional router, used to direct packets sent from hosts in this subnet + /// to any destination address. + /// + /// Custom routers apply in addition to the VPC-wide *system* router, and have + /// higher priority than the system router for an otherwise + /// equal-prefix-length match. + pub custom_router: Option, +} + +/// Updateable properties of a `VpcSubnet` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct VpcSubnetUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, + + /// An optional router, used to direct packets sent from hosts in this subnet + /// to any destination address. + pub custom_router: Option, +} + +/// Create-time parameters for a `VpcRouter` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct VpcRouterCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, +} + +/// Updateable properties of a `VpcRouter` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct VpcRouterUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, +} + +/// Create-time parameters for a `RouterRoute` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct RouterRouteCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The location that matched packets should be forwarded to. + pub target: RouteTarget, + /// Selects which traffic this routing rule will apply to. + pub destination: RouteDestination, +} + +/// Updateable properties of a `RouterRoute` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct RouterRouteUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, + /// The location that matched packets should be forwarded to. + pub target: RouteTarget, + /// Selects which traffic this routing rule will apply to. + pub destination: RouteDestination, +} diff --git a/nexus/types/versions/src/instances_external_subnets/disk.rs b/nexus/types/versions/src/instances_external_subnets/disk.rs new file mode 100644 index 00000000000..926814525ee --- /dev/null +++ b/nexus/types/versions/src/instances_external_subnets/disk.rs @@ -0,0 +1,88 @@ +// 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/. + +//! Disk types for version INSTANCES_EXTERNAL_SUBNETS. +//! +//! The `Disk` view type for this version range does not include a `read_only` +//! field and uses the new `DiskType` from `omicron_common` (no old `Crucible` +//! variant). + +use api_identity::ObjectIdentity; +use omicron_common::api::external::{ + ByteCount, DiskState, DiskType, IdentityMetadata, ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +/// View of a Disk. +#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct Disk { + #[serde(flatten)] + pub identity: IdentityMetadata, + pub project_id: Uuid, + /// ID of snapshot from which disk was created, if any. + pub snapshot_id: Option, + /// ID of image from which disk was created, if any. + pub image_id: Option, + pub size: ByteCount, + pub block_size: ByteCount, + pub state: DiskState, + pub device_path: String, + pub disk_type: DiskType, +} + +// -- View type conversions -- + +impl From for Disk { + fn from(new: omicron_common::api::external::Disk) -> Self { + let omicron_common::api::external::Disk { + identity, + project_id, + snapshot_id, + image_id, + size, + block_size, + state, + device_path, + disk_type, + read_only: _, // read_only does not exist in this version. + } = new; + Self { + identity, + project_id, + snapshot_id, + image_id, + size, + block_size, + state, + device_path, + disk_type, + } + } +} + +impl TryFrom for Disk { + type Error = dropshot::HttpError; + + fn try_from( + old: crate::v2025_11_20_00::disk::Disk, + ) -> Result { + Ok(Disk { + identity: old.identity, + project_id: old.project_id, + snapshot_id: old.snapshot_id, + image_id: old.image_id, + size: old.size, + block_size: old.block_size, + state: old.state, + device_path: old.device_path, + disk_type: match old.disk_type { + crate::v2025_11_20_00::disk::DiskType::Crucible => { + DiskType::Distributed + } + }, + }) + } +} diff --git a/nexus/types/versions/src/instances_external_subnets/mod.rs b/nexus/types/versions/src/instances_external_subnets/mod.rs new file mode 100644 index 00000000000..b598f4bed8c --- /dev/null +++ b/nexus/types/versions/src/instances_external_subnets/mod.rs @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `INSTANCES_EXTERNAL_SUBNETS` of the Nexus external API. +//! +//! The `Disk` view type for this version uses the new `DiskType` from +//! `omicron_common` (without the old `Crucible` variant) but does not +//! include a `read_only` field. + +pub mod disk; diff --git a/nexus/types/versions/src/ip_version_and_multiple_default_pools/floating_ip.rs b/nexus/types/versions/src/ip_version_and_multiple_default_pools/floating_ip.rs new file mode 100644 index 00000000000..a73992297b4 --- /dev/null +++ b/nexus/types/versions/src/ip_version_and_multiple_default_pools/floating_ip.rs @@ -0,0 +1,43 @@ +// 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/. + +//! Floating IP types for version IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS. +//! +//! This version adds `ip_version` to floating IP creation for pool selection. + +use omicron_common::api::external::{ + IdentityMetadataCreateParams, IpVersion, NameOrId, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; + +use crate::v2025_11_20_00; + +/// Parameters for creating a new floating IP address for instances. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct FloatingIpCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// An IP address to reserve for use as a floating IP. + pub ip: Option, + /// The parent IP pool that a floating IP is pulled from. + pub pool: Option, + /// The IP version preference for address allocation. + #[serde(default)] + pub ip_version: Option, +} + +impl From for FloatingIpCreate { + fn from( + old: v2025_11_20_00::floating_ip::FloatingIpCreate, + ) -> FloatingIpCreate { + FloatingIpCreate { + identity: old.identity, + ip: old.ip, + pool: old.pool, + ip_version: None, + } + } +} diff --git a/nexus/types/versions/src/ip_version_and_multiple_default_pools/instance.rs b/nexus/types/versions/src/ip_version_and_multiple_default_pools/instance.rs new file mode 100644 index 00000000000..2d817d530f3 --- /dev/null +++ b/nexus/types/versions/src/ip_version_and_multiple_default_pools/instance.rs @@ -0,0 +1,207 @@ +// 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/. + +//! Instance types for version IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS. +//! +//! This version has old-style network interfaces with single IP + transit_ips, +//! and adds `ip_version` to ExternalIpCreate and EphemeralIpCreate. + +use omicron_common::address::IpVersion; +use omicron_common::api::external::{ + ByteCount, Hostname, IdentityMetadataCreateParams, + InstanceAutoRestartPolicy, InstanceCpuCount, InstanceCpuPlatform, Name, + NameOrId, +}; +use oxnet::IpNet; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; + +use crate::v2025_11_20_00; +use crate::v2025_11_20_00::instance::{UserData, bool_true}; +use crate::v2025_12_03_00; +use crate::v2025_12_03_00::instance::InstanceDiskAttachment; + +/// Describes an attachment of an `InstanceNetworkInterface` to an `Instance`. +#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", content = "params", rename_all = "snake_case")] +pub enum InstanceNetworkInterfaceAttachment { + /// Create one or more `InstanceNetworkInterface`s for the `Instance`. + Create(Vec), + + /// The default networking configuration for an instance is to create a + /// single primary interface with an automatically-assigned IP address. + #[default] + Default, + + /// No network interfaces at all will be created for the instance. + None, +} + +impl From + for InstanceNetworkInterfaceAttachment +{ + fn from( + old: v2025_11_20_00::instance::InstanceNetworkInterfaceAttachment, + ) -> Self { + match old { + v2025_11_20_00::instance::InstanceNetworkInterfaceAttachment::Create( + nics, + ) => InstanceNetworkInterfaceAttachment::Create( + nics.into_iter().map(Into::into).collect(), + ), + v2025_11_20_00::instance::InstanceNetworkInterfaceAttachment::Default => { + InstanceNetworkInterfaceAttachment::Default + } + v2025_11_20_00::instance::InstanceNetworkInterfaceAttachment::None => { + InstanceNetworkInterfaceAttachment::None + } + } + } +} + +/// Create-time parameters for an `InstanceNetworkInterface` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceNetworkInterfaceCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The VPC in which to create the interface. + pub vpc_name: Name, + /// The VPC Subnet in which to create the interface. + pub subnet_name: Name, + /// The IP address for the interface. One will be auto-assigned if not provided. + pub ip: Option, + /// A set of additional networks that this interface may send and + /// receive traffic on. + #[serde(default)] + pub transit_ips: Vec, +} + +impl From + for InstanceNetworkInterfaceCreate +{ + fn from( + old: v2025_11_20_00::instance::InstanceNetworkInterfaceCreate, + ) -> Self { + InstanceNetworkInterfaceCreate { + identity: old.identity, + vpc_name: old.vpc_name, + subnet_name: old.subnet_name, + ip: old.ip, + transit_ips: old.transit_ips, + } + } +} + +/// The type of IP address to attach to an instance during creation. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum ExternalIpCreate { + /// An IP address providing both inbound and outbound access. The address is + /// automatically assigned from the provided IP pool or the default IP pool + /// if not specified. + Ephemeral { + /// Name or ID of the IP pool to use. + pool: Option, + /// IP version to use when allocating from the default pool. Only used + /// when `pool` is not specified. Required if multiple default pools of + /// different IP versions exist. Allocation fails if no pool of the + /// requested version is available. + #[serde(default)] + ip_version: Option, + }, + /// A floating IP address. + Floating { floating_ip: NameOrId }, +} + +impl From for ExternalIpCreate { + fn from( + old: v2025_11_20_00::instance::ExternalIpCreate, + ) -> ExternalIpCreate { + match old { + v2025_11_20_00::instance::ExternalIpCreate::Ephemeral { pool } => { + ExternalIpCreate::Ephemeral { pool, ip_version: None } + } + v2025_11_20_00::instance::ExternalIpCreate::Floating { + floating_ip, + } => ExternalIpCreate::Floating { floating_ip }, + } + } +} + +/// Parameters for creating an ephemeral IP address for an instance. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct EphemeralIpCreate { + /// Name or ID of the IP pool used to allocate an address. + pub pool: Option, + /// IP version to use when allocating from the default pool. Only used + /// when `pool` is not specified. + #[serde(default)] + pub ip_version: Option, +} + +impl From for EphemeralIpCreate { + fn from( + old: v2025_11_20_00::instance::EphemeralIpCreate, + ) -> EphemeralIpCreate { + EphemeralIpCreate { pool: old.pool, ip_version: None } + } +} + +/// Create-time parameters for an `Instance` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + pub ncpus: InstanceCpuCount, + pub memory: ByteCount, + pub hostname: Hostname, + #[serde(default, with = "UserData")] + pub user_data: Vec, + #[serde(default)] + pub network_interfaces: InstanceNetworkInterfaceAttachment, + #[serde(default)] + pub external_ips: Vec, + #[serde(default)] + pub multicast_groups: Vec, + #[serde(default)] + pub disks: Vec, + #[serde(default)] + pub boot_disk: Option, + pub ssh_public_keys: Option>, + #[serde(default = "bool_true")] + pub start: bool, + #[serde(default)] + pub auto_restart_policy: Option, + #[serde(default)] + pub anti_affinity_groups: Vec, + #[serde(default)] + pub cpu_platform: Option, +} + +impl From for InstanceCreate { + fn from(old: v2025_12_03_00::instance::InstanceCreate) -> Self { + InstanceCreate { + identity: old.identity, + ncpus: old.ncpus, + memory: old.memory, + hostname: old.hostname, + user_data: old.user_data, + network_interfaces: old.network_interfaces.into(), + external_ips: old + .external_ips + .into_iter() + .map(Into::into) + .collect(), + multicast_groups: old.multicast_groups, + disks: old.disks, + boot_disk: old.boot_disk, + ssh_public_keys: old.ssh_public_keys, + start: old.start, + auto_restart_policy: old.auto_restart_policy, + anti_affinity_groups: old.anti_affinity_groups, + cpu_platform: old.cpu_platform, + } + } +} diff --git a/nexus/types/versions/src/ip_version_and_multiple_default_pools/mod.rs b/nexus/types/versions/src/ip_version_and_multiple_default_pools/mod.rs new file mode 100644 index 00000000000..17c846531eb --- /dev/null +++ b/nexus/types/versions/src/ip_version_and_multiple_default_pools/mod.rs @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `IP_VERSION_AND_MULTIPLE_DEFAULT_POOLS` of the Nexus external API. +//! +//! This version (2025_12_23_00) adds `ip_version` to floating IP and instance +//! IP creation types. + +pub mod floating_ip; +pub mod instance; diff --git a/nexus/types/versions/src/latest.rs b/nexus/types/versions/src/latest.rs new file mode 100644 index 00000000000..3625785359b --- /dev/null +++ b/nexus/types/versions/src/latest.rs @@ -0,0 +1,546 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Re-exports of the latest versions of all published types. +//! +//! This module provides floating identifiers that always point to the +//! current version of each type. Business logic should use these re-exports +//! rather than versioned identifiers. + +pub mod affinity { + pub use crate::v2025_11_20_00::affinity::AffinityGroup; + pub use crate::v2025_11_20_00::affinity::AffinityGroupCreate; + pub use crate::v2025_11_20_00::affinity::AffinityGroupSelector; + pub use crate::v2025_11_20_00::affinity::AffinityGroupUpdate; + pub use crate::v2025_11_20_00::affinity::AffinityInstanceGroupMemberPath; + pub use crate::v2025_11_20_00::affinity::AntiAffinityGroup; + pub use crate::v2025_11_20_00::affinity::AntiAffinityGroupCreate; + pub use crate::v2025_11_20_00::affinity::AntiAffinityGroupSelector; + pub use crate::v2025_11_20_00::affinity::AntiAffinityGroupUpdate; + pub use crate::v2025_11_20_00::affinity::AntiAffinityInstanceGroupMemberPath; +} + +pub mod alert { + pub use crate::v2025_11_20_00::alert::AlertClass; + pub use crate::v2025_11_20_00::alert::AlertClassFilter; + pub use crate::v2025_11_20_00::alert::AlertClassPage; + pub use crate::v2025_11_20_00::alert::AlertDelivery; + pub use crate::v2025_11_20_00::alert::AlertDeliveryAttempts; + pub use crate::v2025_11_20_00::alert::AlertDeliveryId; + pub use crate::v2025_11_20_00::alert::AlertDeliveryState; + pub use crate::v2025_11_20_00::alert::AlertDeliveryStateFilter; + pub use crate::v2025_11_20_00::alert::AlertDeliveryTrigger; + pub use crate::v2025_11_20_00::alert::AlertProbeResult; + pub use crate::v2025_11_20_00::alert::AlertReceiver; + pub use crate::v2025_11_20_00::alert::AlertReceiverKind; + pub use crate::v2025_11_20_00::alert::AlertReceiverProbe; + pub use crate::v2025_11_20_00::alert::AlertReceiverSelector; + pub use crate::v2025_11_20_00::alert::AlertSelector; + pub use crate::v2025_11_20_00::alert::AlertSubscription; + pub use crate::v2025_11_20_00::alert::AlertSubscriptionCreate; + pub use crate::v2025_11_20_00::alert::AlertSubscriptionCreated; + pub use crate::v2025_11_20_00::alert::AlertSubscriptionSelector; + pub use crate::v2025_11_20_00::alert::WebhookCreate; + pub use crate::v2025_11_20_00::alert::WebhookDeliveryAttempt; + pub use crate::v2025_11_20_00::alert::WebhookDeliveryAttemptResult; + pub use crate::v2025_11_20_00::alert::WebhookDeliveryResponse; + pub use crate::v2025_11_20_00::alert::WebhookReceiver; + pub use crate::v2025_11_20_00::alert::WebhookReceiverConfig; + pub use crate::v2025_11_20_00::alert::WebhookReceiverUpdate; + pub use crate::v2025_11_20_00::alert::WebhookSecret; + pub use crate::v2025_11_20_00::alert::WebhookSecretCreate; + pub use crate::v2025_11_20_00::alert::WebhookSecretSelector; + pub use crate::v2025_11_20_00::alert::WebhookSecrets; +} + +pub mod audit { + pub use crate::v2025_11_20_00::audit::AuditLogEntryActor; + pub use crate::v2025_11_20_00::audit::AuditLogEntryResult; + pub use crate::v2025_11_20_00::audit::AuditLogParams; + + pub use crate::v2026_01_15_01::audit::AuditLogEntry; + pub use crate::v2026_01_15_01::audit::AuthMethod; +} + +pub mod bfd { + pub use crate::v2025_11_20_00::bfd::BfdState; + pub use crate::v2025_11_20_00::bfd::BfdStatus; +} + +pub mod device { + pub use crate::v2025_11_20_00::device::ConsoleSession; + pub use crate::v2025_11_20_00::device::DeviceAccessToken; + pub use crate::v2025_11_20_00::device::DeviceAccessTokenGrant; + pub use crate::v2025_11_20_00::device::DeviceAccessTokenType; + pub use crate::v2025_11_20_00::device::DeviceAuthResponse; +} + +pub mod certificate { + pub use crate::v2025_11_20_00::certificate::Certificate; + pub use crate::v2025_11_20_00::certificate::CertificateCreate; + pub use crate::v2025_11_20_00::certificate::ServiceUsingCertificate; +} + +pub mod console { + pub use crate::v2025_11_20_00::console::LoginPath; + pub use crate::v2025_11_20_00::console::LoginToProviderPathParam; + pub use crate::v2025_11_20_00::console::LoginUrlQuery; + pub use crate::v2025_11_20_00::console::RestPathParam; +} + +pub mod device_params { + pub use crate::v2025_11_20_00::device_params::DeviceAccessTokenRequest; + pub use crate::v2025_11_20_00::device_params::DeviceAuthRequest; + pub use crate::v2025_11_20_00::device_params::DeviceAuthVerify; +} + +pub mod disk { + // View types from omicron-common (latest version). + pub use omicron_common::api::external::{Disk, DiskType}; + + // Request types unchanged since INITIAL. + pub use crate::v2025_11_20_00::disk::{ + BlockSize, DiskMetricName, DiskMetricsPath, DiskSelector, + ExpectedDigest, FinalizeDisk, ImportBlocksBulkWrite, + }; + + // Request types from READ_ONLY_DISKS_NULLABLE. + pub use crate::v2026_01_31_00::disk::{ + DiskBackend, DiskCreate, DiskSource, + }; +} + +pub mod external_ip { + pub use crate::v2025_11_20_00::external_ip::ExternalIp; + pub use crate::v2025_11_20_00::external_ip::IpKind; + pub use crate::v2025_11_20_00::external_ip::SNatIp; +} + +pub mod floating_ip { + pub use crate::v2025_11_20_00::floating_ip::FloatingIp; + pub use crate::v2025_11_20_00::floating_ip::FloatingIpAttach; + pub use crate::v2025_11_20_00::floating_ip::FloatingIpParentKind; + pub use crate::v2025_11_20_00::floating_ip::FloatingIpSelector; + pub use crate::v2025_11_20_00::floating_ip::FloatingIpUpdate; + + pub use crate::v2026_01_22_00::floating_ip::AddressAllocator; + pub use crate::v2026_01_22_00::floating_ip::FloatingIpCreate; +} + +pub mod hardware { + pub use crate::v2025_11_20_00::hardware::Baseboard; + pub use crate::v2025_11_20_00::hardware::UninitializedSled; + pub use crate::v2025_11_20_00::hardware::UninitializedSledId; + pub use crate::v2025_11_20_00::hardware::UpdateableComponentType; +} + +pub mod headers { + pub use crate::v2025_11_20_00::headers::RangeRequest; +} + +pub mod image { + pub use crate::v2025_11_20_00::image::Distribution; + pub use crate::v2025_11_20_00::image::Image; + pub use crate::v2025_11_20_00::image::ImageCreate; + pub use crate::v2025_11_20_00::image::ImageSelector; + pub use crate::v2025_11_20_00::image::ImageSource; +} + +pub mod instance { + pub use crate::v2025_11_20_00::instance::InstanceDiskAttach; + pub use crate::v2025_11_20_00::instance::InstanceNetworkInterfaceSelector; + pub use crate::v2025_11_20_00::instance::InstanceNetworkInterfaceUpdate; + pub use crate::v2025_11_20_00::instance::InstanceSelector; + pub use crate::v2025_11_20_00::instance::InstanceSerialConsoleData; + pub use crate::v2025_11_20_00::instance::InstanceSerialConsoleRequest; + pub use crate::v2025_11_20_00::instance::InstanceSerialConsoleStreamRequest; + pub use crate::v2025_11_20_00::instance::MAX_USER_DATA_BYTES; + pub use crate::v2025_11_20_00::instance::OptionalInstanceSelector; + pub use crate::v2025_11_20_00::instance::UserData; + pub use crate::v2025_11_20_00::instance::bool_true; + + pub use crate::v2026_01_03_00::instance::InstanceNetworkInterfaceAttachment; + pub use crate::v2026_01_03_00::instance::InstanceNetworkInterfaceCreate; + pub use crate::v2026_01_03_00::instance::IpAssignment; + pub use crate::v2026_01_03_00::instance::Ipv4Assignment; + pub use crate::v2026_01_03_00::instance::Ipv6Assignment; + pub use crate::v2026_01_03_00::instance::PrivateIpStackCreate; + pub use crate::v2026_01_03_00::instance::PrivateIpv4StackCreate; + pub use crate::v2026_01_03_00::instance::PrivateIpv6StackCreate; + + pub use crate::v2026_01_05_00::instance::EphemeralIpCreate; + pub use crate::v2026_01_05_00::instance::ExternalIpCreate; + + pub use crate::v2026_01_08_00::instance::InstanceUpdate; + + pub use crate::v2026_01_23_00::instance::EphemeralIpDetachSelector; + pub use crate::v2026_01_23_00::instance::ExternalIpDetach; + + pub use crate::v2026_01_31_00::instance::InstanceCreate; + pub use crate::v2026_01_31_00::instance::InstanceDiskAttachment; +} + +pub mod internet_gateway { + pub use crate::v2025_11_20_00::internet_gateway::DeleteInternetGatewayElementSelector; + pub use crate::v2025_11_20_00::internet_gateway::InternetGateway; + pub use crate::v2025_11_20_00::internet_gateway::InternetGatewayCreate; + pub use crate::v2025_11_20_00::internet_gateway::InternetGatewayDeleteSelector; + pub use crate::v2025_11_20_00::internet_gateway::InternetGatewayIpAddress; + pub use crate::v2025_11_20_00::internet_gateway::InternetGatewayIpAddressCreate; + pub use crate::v2025_11_20_00::internet_gateway::InternetGatewayIpAddressSelector; + pub use crate::v2025_11_20_00::internet_gateway::InternetGatewayIpPool; + pub use crate::v2025_11_20_00::internet_gateway::InternetGatewayIpPoolCreate; + pub use crate::v2025_11_20_00::internet_gateway::InternetGatewayIpPoolSelector; + pub use crate::v2025_11_20_00::internet_gateway::InternetGatewaySelector; + pub use crate::v2025_11_20_00::internet_gateway::OptionalInternetGatewaySelector; +} + +pub mod ip_pool { + pub use crate::v2025_11_20_00::ip_pool::IpPool; + pub use crate::v2025_11_20_00::ip_pool::IpPoolCreate; + pub use crate::v2025_11_20_00::ip_pool::IpPoolRange; + pub use crate::v2025_11_20_00::ip_pool::IpPoolSiloLink; + pub use crate::v2025_11_20_00::ip_pool::IpPoolSiloPath; + pub use crate::v2025_11_20_00::ip_pool::IpPoolType; + pub use crate::v2025_11_20_00::ip_pool::IpPoolUpdate; + pub use crate::v2025_11_20_00::ip_pool::IpPoolUtilization; + + pub use crate::v2026_01_01_00::ip_pool::SiloIpPool; + + pub use crate::v2026_01_05_00::ip_pool::IpPoolLinkSilo; + pub use crate::v2026_01_05_00::ip_pool::IpPoolSiloUpdate; + pub use crate::v2026_01_05_00::ip_pool::PoolSelector; +} + +pub mod metrics { + pub use crate::v2025_11_20_00::metrics::ResourceMetrics; + pub use crate::v2025_11_20_00::metrics::SystemMetricName; + pub use crate::v2025_11_20_00::metrics::SystemMetricsPathParam; +} + +pub mod multicast { + pub use crate::v2025_11_20_00::multicast::MulticastGroupCreate; + pub use crate::v2025_11_20_00::multicast::MulticastGroupIpLookupPath; + pub use crate::v2025_11_20_00::multicast::MulticastGroupMemberRemove; + pub use crate::v2025_11_20_00::multicast::MulticastGroupUpdate; + + pub use crate::v2026_01_08_00::multicast::InstanceMulticastGroupJoin; + pub use crate::v2026_01_08_00::multicast::InstanceMulticastGroupPath; + pub use crate::v2026_01_08_00::multicast::MulticastGroup; + pub use crate::v2026_01_08_00::multicast::MulticastGroupIdentifier; + pub use crate::v2026_01_08_00::multicast::MulticastGroupJoinSpec; + pub use crate::v2026_01_08_00::multicast::MulticastGroupMember; + pub use crate::v2026_01_08_00::multicast::MulticastGroupMemberAdd; + pub use crate::v2026_01_08_00::multicast::MulticastGroupMemberPath; + pub use crate::v2026_01_08_00::multicast::MulticastGroupPath; + pub use crate::v2026_01_08_00::multicast::MulticastGroupSelector; + + pub use crate::impls::multicast::validate_multicast_ip; + pub use crate::impls::multicast::validate_source_ip; +} + +pub mod networking { + pub use crate::v2025_11_20_00::networking::Address; + pub use crate::v2025_11_20_00::networking::AddressConfig; + pub use crate::v2025_11_20_00::networking::AddressLotBlockCreate; + pub use crate::v2025_11_20_00::networking::AddressLotCreate; + pub use crate::v2025_11_20_00::networking::AddressLotSelector; + pub use crate::v2025_11_20_00::networking::BfdSessionDisable; + pub use crate::v2025_11_20_00::networking::BfdSessionEnable; + pub use crate::v2025_11_20_00::networking::BgpAnnounceListSelector; + pub use crate::v2025_11_20_00::networking::BgpAnnounceSetCreate; + pub use crate::v2025_11_20_00::networking::BgpAnnounceSetSelector; + pub use crate::v2025_11_20_00::networking::BgpAnnouncementCreate; + pub use crate::v2025_11_20_00::networking::BgpConfigSelector; + pub use crate::v2025_11_20_00::networking::BgpRouteSelector; + pub use crate::v2025_11_20_00::networking::BgpStatusSelector; + pub use crate::v2025_11_20_00::networking::LinkConfigCreate; + pub use crate::v2025_11_20_00::networking::LldpLinkConfigCreate; + pub use crate::v2025_11_20_00::networking::LldpPortPathSelector; + pub use crate::v2025_11_20_00::networking::LoopbackAddressCreate; + pub use crate::v2025_11_20_00::networking::LoopbackAddressPath; + pub use crate::v2025_11_20_00::networking::Route; + pub use crate::v2025_11_20_00::networking::RouteConfig; + pub use crate::v2025_11_20_00::networking::SwitchInterfaceConfigCreate; + pub use crate::v2025_11_20_00::networking::SwitchInterfaceKind; + pub use crate::v2025_11_20_00::networking::SwitchPortApplySettings; + pub use crate::v2025_11_20_00::networking::SwitchPortConfigCreate; + pub use crate::v2025_11_20_00::networking::SwitchPortGeometry; + pub use crate::v2025_11_20_00::networking::SwitchPortPageSelector; + pub use crate::v2025_11_20_00::networking::SwitchPortPathSelector; + pub use crate::v2025_11_20_00::networking::SwitchPortSelector; + pub use crate::v2025_11_20_00::networking::SwitchPortSettingsInfoSelector; + pub use crate::v2025_11_20_00::networking::SwitchPortSettingsSelector; + pub use crate::v2025_11_20_00::networking::SwitchVlanInterface; + pub use crate::v2025_11_20_00::networking::SwtichPortSettingsGroupCreate; + pub use crate::v2025_11_20_00::networking::TxEqConfig; + + pub use crate::v2026_02_13_01::networking::BgpConfigCreate; + pub use crate::v2026_02_13_01::networking::BgpPeerConfig; + pub use crate::v2026_02_13_01::networking::SwitchPortSettingsCreate; +} + +pub mod oxql { + pub use crate::v2025_11_20_00::oxql::OxqlQueryResult; + pub use crate::v2025_11_20_00::oxql::OxqlQuerySummary; + pub use crate::v2025_11_20_00::oxql::OxqlTable; +} + +pub mod policy { + pub use crate::v2025_11_20_00::policy::FleetRole; + pub use crate::v2025_11_20_00::policy::IdentityType; + pub use crate::v2025_11_20_00::policy::MAX_ROLE_ASSIGNMENTS_PER_RESOURCE; + pub use crate::v2025_11_20_00::policy::Policy; + pub use crate::v2025_11_20_00::policy::ProjectRole; + pub use crate::v2025_11_20_00::policy::RoleAssignment; + pub use crate::v2025_11_20_00::policy::SiloRole; +} + +pub mod probe { + pub use crate::v2025_11_20_00::probe::ProbeExternalIp; + pub use crate::v2025_11_20_00::probe::ProbeExternalIpKind; + pub use crate::v2025_11_20_00::probe::ProbeListSelector; + + pub use crate::v2026_01_03_00::probe::ProbeInfo; + + pub use crate::v2026_01_05_00::probe::ProbeCreate; +} + +pub mod project { + pub use crate::v2025_11_20_00::project::OptionalProjectSelector; + pub use crate::v2025_11_20_00::project::Project; + pub use crate::v2025_11_20_00::project::ProjectCreate; + pub use crate::v2025_11_20_00::project::ProjectSelector; + pub use crate::v2025_11_20_00::project::ProjectUpdate; +} + +pub mod saml { + pub use crate::v2025_11_20_00::saml::RelativeUri; + pub use crate::v2025_11_20_00::saml::RelayState; +} + +pub mod scim { + pub use crate::v2025_11_20_00::scim::ScimClientBearerToken; + pub use crate::v2025_11_20_00::scim::ScimClientBearerTokenValue; + pub use crate::v2025_11_20_00::scim::ScimV2GroupPathParam; + pub use crate::v2025_11_20_00::scim::ScimV2TokenPathParam; + pub use crate::v2025_11_20_00::scim::ScimV2UserPathParam; +} + +pub mod silo { + pub use crate::v2025_11_20_00::silo::AuthenticationMode; + pub use crate::v2025_11_20_00::silo::OptionalSiloSelector; + pub use crate::v2025_11_20_00::silo::Silo; + pub use crate::v2025_11_20_00::silo::SiloAuthSettings; + pub use crate::v2025_11_20_00::silo::SiloAuthSettingsUpdate; + pub use crate::v2025_11_20_00::silo::SiloCreate; + pub use crate::v2025_11_20_00::silo::SiloIdentityMode; + pub use crate::v2025_11_20_00::silo::SiloQuotas; + pub use crate::v2025_11_20_00::silo::SiloQuotasCreate; + pub use crate::v2025_11_20_00::silo::SiloQuotasUpdate; + pub use crate::v2025_11_20_00::silo::SiloSelector; + pub use crate::v2025_11_20_00::silo::SiloUtilization; + pub use crate::v2025_11_20_00::silo::UserProvisionType; + pub use crate::v2025_11_20_00::silo::Utilization; + pub use crate::v2025_11_20_00::silo::VirtualResourceCounts; +} + +pub mod snapshot { + pub use crate::v2025_11_20_00::snapshot::Snapshot; + pub use crate::v2025_11_20_00::snapshot::SnapshotCreate; + pub use crate::v2025_11_20_00::snapshot::SnapshotSelector; + pub use crate::v2025_11_20_00::snapshot::SnapshotState; +} + +pub mod support_bundle { + pub use crate::v2025_11_20_00::support_bundle::SupportBundleCreate; + pub use crate::v2025_11_20_00::support_bundle::SupportBundleFilePath; + pub use crate::v2025_11_20_00::support_bundle::SupportBundleInfo; + pub use crate::v2025_11_20_00::support_bundle::SupportBundlePath; + pub use crate::v2025_11_20_00::support_bundle::SupportBundleState; + pub use crate::v2025_11_20_00::support_bundle::SupportBundleUpdate; +} + +pub mod switch { + pub use crate::v2025_11_20_00::switch::Switch; + pub use crate::v2025_11_20_00::switch::SwitchLinkState; +} + +pub mod system { + pub use crate::v2025_11_20_00::system::AllowList; + pub use crate::v2025_11_20_00::system::AllowListUpdate; + pub use crate::v2025_11_20_00::system::Ping; + pub use crate::v2025_11_20_00::system::PingStatus; +} + +pub mod timeseries { + pub use crate::v2025_11_20_00::timeseries::TimeseriesQuery; +} + +pub mod update { + pub use crate::v2025_11_20_00::update::SetTargetReleaseParams; + pub use crate::v2025_11_20_00::update::TargetRelease; + pub use crate::v2025_11_20_00::update::TufRepo; + pub use crate::v2025_11_20_00::update::TufRepoUpload; + pub use crate::v2025_11_20_00::update::TufRepoUploadStatus; + pub use crate::v2025_11_20_00::update::TufSignedRootRole; + pub use crate::v2025_11_20_00::update::UpdateStatus; + pub use crate::v2025_11_20_00::update::UpdatesGetRepositoryParams; + pub use crate::v2025_11_20_00::update::UpdatesPutRepositoryParams; + pub use crate::v2025_11_20_00::update::UpdatesTrustRoot; +} + +pub mod vpc { + pub use crate::v2025_11_20_00::vpc::OptionalRouterSelector; + pub use crate::v2025_11_20_00::vpc::OptionalVpcSelector; + pub use crate::v2025_11_20_00::vpc::RouteSelector; + pub use crate::v2025_11_20_00::vpc::RouterRouteCreate; + pub use crate::v2025_11_20_00::vpc::RouterRouteUpdate; + pub use crate::v2025_11_20_00::vpc::RouterSelector; + pub use crate::v2025_11_20_00::vpc::SubnetSelector; + pub use crate::v2025_11_20_00::vpc::Vpc; + pub use crate::v2025_11_20_00::vpc::VpcCreate; + pub use crate::v2025_11_20_00::vpc::VpcRouter; + pub use crate::v2025_11_20_00::vpc::VpcRouterCreate; + pub use crate::v2025_11_20_00::vpc::VpcRouterKind; + pub use crate::v2025_11_20_00::vpc::VpcRouterUpdate; + pub use crate::v2025_11_20_00::vpc::VpcSelector; + pub use crate::v2025_11_20_00::vpc::VpcSubnet; + pub use crate::v2025_11_20_00::vpc::VpcSubnetCreate; + pub use crate::v2025_11_20_00::vpc::VpcSubnetUpdate; + pub use crate::v2025_11_20_00::vpc::VpcUpdate; +} + +pub mod asset { + pub use crate::v2025_11_20_00::asset::AssetIdentityMetadata; +} + +pub mod identity_provider { + pub use crate::v2025_11_20_00::identity_provider::DerEncodedKeyPair; + pub use crate::v2025_11_20_00::identity_provider::IdentityProvider; + pub use crate::v2025_11_20_00::identity_provider::IdentityProviderType; + pub use crate::v2025_11_20_00::identity_provider::IdpMetadataSource; + pub use crate::v2025_11_20_00::identity_provider::SamlIdentityProvider; + pub use crate::v2025_11_20_00::identity_provider::SamlIdentityProviderCreate; + pub use crate::v2025_11_20_00::identity_provider::SamlIdentityProviderSelector; +} + +pub mod physical_disk { + pub use crate::v2025_11_20_00::physical_disk::PhysicalDisk; + pub use crate::v2025_11_20_00::physical_disk::PhysicalDiskKind; + pub use crate::v2025_11_20_00::physical_disk::PhysicalDiskPolicy; + pub use crate::v2025_11_20_00::physical_disk::PhysicalDiskState; +} + +pub mod rack { + pub use crate::v2025_11_20_00::rack::Rack; + + pub use crate::v2026_01_21_00::rack::RackMembershipAddSledsRequest; + pub use crate::v2026_01_21_00::rack::RackMembershipChangeState; + pub use crate::v2026_01_21_00::rack::RackMembershipConfigPathParams; + pub use crate::v2026_01_21_00::rack::RackMembershipStatus; + pub use crate::v2026_01_21_00::rack::RackMembershipVersion; + pub use crate::v2026_01_21_00::rack::RackMembershipVersionParam; +} + +pub mod subnet_pool { + // Types unchanged since EXTERNAL_SUBNET_ATTACHMENT. + pub use crate::v2026_01_16_01::subnet_pool::SubnetPoolLinkSilo; + pub use crate::v2026_01_16_01::subnet_pool::SubnetPoolMemberRemove; + pub use crate::v2026_01_16_01::subnet_pool::SubnetPoolPath; + pub use crate::v2026_01_16_01::subnet_pool::SubnetPoolSiloLink; + pub use crate::v2026_01_16_01::subnet_pool::SubnetPoolSiloPath; + pub use crate::v2026_01_16_01::subnet_pool::SubnetPoolSiloUpdate; + pub use crate::v2026_01_16_01::subnet_pool::SubnetPoolUpdate; + pub use crate::v2026_01_16_01::subnet_pool::SubnetPoolUtilization; + + // View types from FLOATING_IP_ALLOCATOR_UPDATE (pool_type removed). + pub use crate::v2026_01_22_00::subnet_pool::SiloSubnetPool; + pub use crate::v2026_01_22_00::subnet_pool::SubnetPool; + pub use crate::v2026_01_22_00::subnet_pool::SubnetPoolMember; + // Create/update types from FLOATING_IP_ALLOCATOR_UPDATE. + pub use crate::v2026_01_22_00::subnet_pool::SubnetPoolCreate; + pub use crate::v2026_01_22_00::subnet_pool::SubnetPoolMemberAdd; +} + +pub mod external_subnet { + // Types unchanged since EXTERNAL_SUBNET_ATTACHMENT. + pub use crate::v2026_01_16_01::external_subnet::ExternalSubnet; + pub use crate::v2026_01_16_01::external_subnet::ExternalSubnetAttach; + pub use crate::v2026_01_16_01::external_subnet::ExternalSubnetPath; + pub use crate::v2026_01_16_01::external_subnet::ExternalSubnetSelector; + pub use crate::v2026_01_16_01::external_subnet::ExternalSubnetUpdate; + + // Create types from FLOATING_IP_ALLOCATOR_UPDATE (pool field removed). + pub use crate::v2026_01_22_00::external_subnet::ExternalSubnetAllocator; + pub use crate::v2026_01_22_00::external_subnet::ExternalSubnetCreate; +} + +pub mod sled { + pub use crate::v2025_11_20_00::sled::Sled; + pub use crate::v2025_11_20_00::sled::SledId; + pub use crate::v2025_11_20_00::sled::SledInstance; + pub use crate::v2025_11_20_00::sled::SledPolicy; + pub use crate::v2025_11_20_00::sled::SledProvisionPolicy; + pub use crate::v2025_11_20_00::sled::SledProvisionPolicyParams; + pub use crate::v2025_11_20_00::sled::SledProvisionPolicyResponse; + pub use crate::v2025_11_20_00::sled::SledSelector; + pub use crate::v2025_11_20_00::sled::SledState; + pub use crate::v2025_11_20_00::sled::SwitchSelector; +} + +pub mod ssh_key { + pub use crate::v2025_11_20_00::ssh_key::SshKey; + pub use crate::v2025_11_20_00::ssh_key::SshKeyCreate; + pub use crate::v2025_11_20_00::ssh_key::SshKeySelector; +} + +pub mod user { + pub use crate::v2025_11_20_00::user::CurrentUser; + pub use crate::v2025_11_20_00::user::Group; + pub use crate::v2025_11_20_00::user::OptionalGroupSelector; + pub use crate::v2025_11_20_00::user::Password; + pub use crate::v2025_11_20_00::user::User; + pub use crate::v2025_11_20_00::user::UserBuiltin; + pub use crate::v2025_11_20_00::user::UserBuiltinCreate; + pub use crate::v2025_11_20_00::user::UserBuiltinSelector; + pub use crate::v2025_11_20_00::user::UserCreate; + pub use crate::v2025_11_20_00::user::UserParam; + pub use crate::v2025_11_20_00::user::UserPassword; + pub use crate::v2025_11_20_00::user::UsernamePasswordCredentials; +} + +pub mod path_params { + pub use crate::v2025_11_20_00::path_params::AddressLotPath; + pub use crate::v2025_11_20_00::path_params::AffinityGroupPath; + pub use crate::v2025_11_20_00::path_params::AntiAffinityGroupPath; + pub use crate::v2025_11_20_00::path_params::BlueprintPath; + pub use crate::v2025_11_20_00::path_params::CertificatePath; + pub use crate::v2025_11_20_00::path_params::DiskPath; + pub use crate::v2025_11_20_00::path_params::FloatingIpPath; + pub use crate::v2025_11_20_00::path_params::GroupPath; + pub use crate::v2025_11_20_00::path_params::ImagePath; + pub use crate::v2025_11_20_00::path_params::InstancePath; + pub use crate::v2025_11_20_00::path_params::InternetGatewayPath; + pub use crate::v2025_11_20_00::path_params::IpAddressPath; + pub use crate::v2025_11_20_00::path_params::IpPoolPath; + pub use crate::v2025_11_20_00::path_params::NetworkInterfacePath; + pub use crate::v2025_11_20_00::path_params::PhysicalDiskPath; + pub use crate::v2025_11_20_00::path_params::ProbePath; + pub use crate::v2025_11_20_00::path_params::ProjectPath; + pub use crate::v2025_11_20_00::path_params::ProviderPath; + pub use crate::v2025_11_20_00::path_params::RackPath; + pub use crate::v2025_11_20_00::path_params::RoutePath; + pub use crate::v2025_11_20_00::path_params::RouterPath; + pub use crate::v2025_11_20_00::path_params::SiloPath; + pub use crate::v2025_11_20_00::path_params::SledPath; + pub use crate::v2025_11_20_00::path_params::SnapshotPath; + pub use crate::v2025_11_20_00::path_params::SshKeyPath; + pub use crate::v2025_11_20_00::path_params::SubnetPath; + pub use crate::v2025_11_20_00::path_params::SwitchPath; + pub use crate::v2025_11_20_00::path_params::TokenPath; + pub use crate::v2025_11_20_00::path_params::TufTrustRootPath; + pub use crate::v2025_11_20_00::path_params::UserPath; + pub use crate::v2025_11_20_00::path_params::VpcPath; +} diff --git a/nexus/types/versions/src/lib.rs b/nexus/types/versions/src/lib.rs new file mode 100644 index 00000000000..27565ca95a8 --- /dev/null +++ b/nexus/types/versions/src/lib.rs @@ -0,0 +1,69 @@ +// 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/. + +//! Versioned types for the Nexus external API. +//! +//! # Adding a new API version +//! +//! When adding a new API version N with added or changed types: +//! +//! 1. Create `/mod.rs`, where `` is the lowercase +//! form of the new version's identifier, as defined in the API trait's +//! `api_versions!` macro. +//! +//! 2. Add to the end of this list: +//! +//! ```rust,ignore +//! #[path = "/mod.rs"] +//! pub mod vN; +//! ``` +//! +//! 3. Add your types to the new module, mirroring the module structure from +//! earlier versions. +//! +//! 4. Update `latest.rs` with new and updated types from the new version. +//! +//! For more information, see the [detailed guide] and [RFD 619]. +//! +//! [detailed guide]: https://github.com/oxidecomputer/dropshot-api-manager/blob/main/guides/new-version.md +//! [RFD 619]: https://rfd.shared.oxide.computer/rfd/619 + +mod impls; +pub mod latest; +#[path = "initial/mod.rs"] +pub mod v2025_11_20_00; +#[path = "local_storage/mod.rs"] +pub mod v2025_12_03_00; +#[path = "bgp_peer_collision_state/mod.rs"] +pub mod v2025_12_12_00; +#[path = "ip_version_and_multiple_default_pools/mod.rs"] +pub mod v2025_12_23_00; +#[path = "silo_project_ip_version_and_pool_type/mod.rs"] +pub mod v2026_01_01_00; +#[path = "dual_stack_nics/mod.rs"] +pub mod v2026_01_03_00; +#[path = "pool_selection_enums/mod.rs"] +pub mod v2026_01_05_00; +#[path = "multicast_implicit_lifecycle_updates/mod.rs"] +pub mod v2026_01_08_00; +#[path = "audit_log_credential_id/mod.rs"] +pub mod v2026_01_15_01; +#[path = "rename_address_selector_to_address_allocator/mod.rs"] +pub mod v2026_01_16_00; +#[path = "external_subnet_attachment/mod.rs"] +pub mod v2026_01_16_01; +#[path = "trust_quorum_add_sleds_and_get_latest_config/mod.rs"] +pub mod v2026_01_21_00; +#[path = "floating_ip_allocator_update/mod.rs"] +pub mod v2026_01_22_00; +#[path = "dual_stack_ephemeral_ip/mod.rs"] +pub mod v2026_01_23_00; +#[path = "instances_external_subnets/mod.rs"] +pub mod v2026_01_30_00; +#[path = "read_only_disks/mod.rs"] +pub mod v2026_01_30_01; +#[path = "read_only_disks_nullable/mod.rs"] +pub mod v2026_01_31_00; +#[path = "bgp_unnumbered_peers/mod.rs"] +pub mod v2026_02_13_01; diff --git a/nexus/types/versions/src/local_storage/disk.rs b/nexus/types/versions/src/local_storage/disk.rs new file mode 100644 index 00000000000..605ae0e9178 --- /dev/null +++ b/nexus/types/versions/src/local_storage/disk.rs @@ -0,0 +1,159 @@ +// 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/. + +//! Disk types for version LOCAL_STORAGE. + +use omicron_common::api::external::{ByteCount, IdentityMetadataCreateParams}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::v2025_11_20_00; +use crate::v2025_11_20_00::disk::BlockSize; + +/// Different sources for a Distributed Disk +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum DiskSource { + /// Create a blank disk + Blank { + /// size of blocks for this Disk. valid values are: 512, 2048, or 4096 + block_size: BlockSize, + }, + + /// Create a disk from a disk snapshot + Snapshot { snapshot_id: Uuid }, + + /// Create a disk from an image + Image { image_id: Uuid }, + + /// Create a blank disk that will accept bulk writes or pull blocks from an + /// external source. + ImportingBlocks { block_size: BlockSize }, +} + +impl From for DiskSource { + fn from(old: v2025_11_20_00::disk::DiskSource) -> Self { + match old { + v2025_11_20_00::disk::DiskSource::Blank { block_size } => { + DiskSource::Blank { block_size } + } + v2025_11_20_00::disk::DiskSource::Snapshot { snapshot_id } => { + DiskSource::Snapshot { snapshot_id } + } + v2025_11_20_00::disk::DiskSource::Image { image_id } => { + DiskSource::Image { image_id } + } + v2025_11_20_00::disk::DiskSource::ImportingBlocks { + block_size, + } => DiskSource::ImportingBlocks { block_size }, + } + } +} + +/// The source of a `Disk`'s blocks +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum DiskBackend { + Local {}, + + Distributed { + /// The initial source for this disk + disk_source: DiskSource, + }, +} + +/// Create-time parameters for a `Disk` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct DiskCreate { + /// The common identifying metadata for the disk + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// The source for this `Disk`'s blocks + pub disk_backend: DiskBackend, + + /// The total size of the Disk (in bytes) + pub size: ByteCount, +} + +impl From for DiskCreate { + fn from(old: v2025_11_20_00::disk::DiskCreate) -> Self { + DiskCreate { + identity: old.identity, + disk_backend: DiskBackend::Distributed { + disk_source: old.disk_source.into(), + }, + size: old.size, + } + } +} + +// Response type conversion: new Disk (from omicron-common) to old v2025_11_20_00 Disk +impl TryFrom + for v2025_11_20_00::disk::Disk +{ + type Error = dropshot::HttpError; + + fn try_from( + new: omicron_common::api::external::Disk, + ) -> Result { + Ok(v2025_11_20_00::disk::Disk { + identity: new.identity, + project_id: new.project_id, + snapshot_id: new.snapshot_id, + image_id: new.image_id, + size: new.size, + block_size: new.block_size, + state: new.state, + device_path: new.device_path, + disk_type: match new.disk_type { + omicron_common::api::external::DiskType::Distributed => { + v2025_11_20_00::disk::DiskType::Crucible + } + _ => { + return Err(dropshot::HttpError::for_client_error( + Some(String::from("Not Acceptable")), + dropshot::ClientErrorStatusCode::NOT_ACCEPTABLE, + String::from( + "disk type variant not supported for client version", + ), + )); + } + }, + }) + } +} + +// Forward conversion for request types: old DiskType to new DiskType +impl From + for omicron_common::api::external::DiskType +{ + fn from(old: v2025_11_20_00::disk::DiskType) -> Self { + match old { + v2025_11_20_00::disk::DiskType::Crucible => { + omicron_common::api::external::DiskType::Distributed + } + } + } +} + +// Forward conversion: old Disk to new Disk +impl From for omicron_common::api::external::Disk { + fn from(old: v2025_11_20_00::disk::Disk) -> Self { + omicron_common::api::external::Disk { + identity: old.identity, + project_id: old.project_id, + snapshot_id: old.snapshot_id, + image_id: old.image_id, + size: old.size, + block_size: old.block_size, + state: old.state, + device_path: old.device_path, + disk_type: old.disk_type.into(), + // Old Disk type predates read-only disks; default to false. + read_only: false, + } + } +} diff --git a/nexus/external-api/src/v2026013000.rs b/nexus/types/versions/src/local_storage/instance.rs similarity index 51% rename from nexus/external-api/src/v2026013000.rs rename to nexus/types/versions/src/local_storage/instance.rs index d9674da3e2b..1b80b28b86f 100644 --- a/nexus/external-api/src/v2026013000.rs +++ b/nexus/types/versions/src/local_storage/instance.rs @@ -2,114 +2,22 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -//! Types from API version 2026013000 (`REMOVE_SUBNET_POOL_POOL_TYPE`) that changed -//! in version 2026013001 (`READ_ONLY_DISKS`). +//! Instance types for version LOCAL_STORAGE. -use crate::v2025112000; -use api_identity::ObjectIdentity; -use nexus_types::external_api::params; -use omicron_common::api::external; -use omicron_common::api::external::ByteCount; -use omicron_common::api::external::DiskState; -use omicron_common::api::external::DiskType; -use omicron_common::api::external::IdentityMetadata; -use omicron_common::api::external::IdentityMetadataCreateParams; -use omicron_common::api::external::ObjectIdentity; +use omicron_common::api::external::{ + ByteCount, Hostname, IdentityMetadataCreateParams, + InstanceAutoRestartPolicy, InstanceCpuCount, InstanceCpuPlatform, Name, + NameOrId, +}; use schemars::JsonSchema; -use serde::Deserialize; -use serde::Serialize; -use uuid::Uuid; +use serde::{Deserialize, Serialize}; -/// Create-time parameters for a `Disk` -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct DiskCreate { - /// The common identifying metadata for the disk - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - - /// The source for this `Disk`'s blocks - pub disk_backend: DiskBackend, - - /// The total size of the Disk (in bytes) - pub size: ByteCount, -} - -impl From for params::DiskCreate { - fn from(old: DiskCreate) -> Self { - let DiskCreate { identity, disk_backend, size } = old; - params::DiskCreate { identity, disk_backend: disk_backend.into(), size } - } -} - -/// The source of a `Disk`'s blocks -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum DiskBackend { - Local {}, - - Distributed { - /// The initial source for this disk - disk_source: v2025112000::DiskSource, - }, -} - -impl From for params::DiskBackend { - fn from(old: DiskBackend) -> Self { - match old { - DiskBackend::Local {} => params::DiskBackend::Local {}, - DiskBackend::Distributed { disk_source } => { - params::DiskBackend::Distributed { - disk_source: disk_source.into(), - } - } - } - } -} - -/// View of a Disk -#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct Disk { - #[serde(flatten)] - pub identity: IdentityMetadata, - pub project_id: Uuid, - /// ID of snapshot from which disk was created, if any - pub snapshot_id: Option, - /// ID of image from which disk was created, if any - pub image_id: Option, - pub size: ByteCount, - pub block_size: ByteCount, - pub state: DiskState, - pub device_path: String, - pub disk_type: DiskType, -} - -impl From for Disk { - fn from(new: external::Disk) -> Self { - let external::Disk { - identity, - project_id, - snapshot_id, - image_id, - size, - block_size, - state, - device_path, - disk_type, - read_only: _, // read_only doth not exist in v2026013000 - } = new; - Self { - identity, - project_id, - snapshot_id, - image_id, - size, - block_size, - state, - device_path, - disk_type, - } - } -} +use super::disk::DiskCreate; +use crate::v2025_11_20_00; +use crate::v2025_11_20_00::instance::{ + ExternalIpCreate, InstanceDiskAttach, InstanceNetworkInterfaceAttachment, + UserData, bool_true, +}; /// Describe the instance's disks at creation time #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] @@ -119,19 +27,30 @@ pub enum InstanceDiskAttachment { Create(DiskCreate), /// During instance creation, attach this disk - Attach(params::InstanceDiskAttach), + Attach(InstanceDiskAttach), } -impl From for params::InstanceDiskAttachment { - fn from(old: InstanceDiskAttachment) -> params::InstanceDiskAttachment { - match old { - InstanceDiskAttachment::Create(create) => { - params::InstanceDiskAttachment::Create(create.into()) - } +impl InstanceDiskAttachment { + /// Get the name of the disk described by this attachment. + pub fn name(&self) -> Name { + match self { + Self::Create(create) => create.identity.name.clone(), + Self::Attach(attach) => attach.name.clone(), + } + } +} - InstanceDiskAttachment::Attach(attach) => { - params::InstanceDiskAttachment::Attach(attach) - } +impl From + for InstanceDiskAttachment +{ + fn from(old: v2025_11_20_00::instance::InstanceDiskAttachment) -> Self { + match old { + v2025_11_20_00::instance::InstanceDiskAttachment::Create( + create, + ) => InstanceDiskAttachment::Create(create.into()), + v2025_11_20_00::instance::InstanceDiskAttachment::Attach( + attach, + ) => InstanceDiskAttachment::Attach(attach), } } } @@ -140,47 +59,39 @@ impl From for params::InstanceDiskAttachment { #[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct InstanceCreate { #[serde(flatten)] - pub identity: external::IdentityMetadataCreateParams, + pub identity: IdentityMetadataCreateParams, /// The number of vCPUs to be allocated to the instance - pub ncpus: external::InstanceCpuCount, + pub ncpus: InstanceCpuCount, /// The amount of RAM (in bytes) to be allocated to the instance - pub memory: external::ByteCount, + pub memory: ByteCount, /// The hostname to be assigned to the instance - pub hostname: external::Hostname, + pub hostname: Hostname, /// User data for instance initialization systems (such as cloud-init). /// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / /// characters with padding). Maximum 32 KiB unencoded data. - // While serde happily accepts #[serde(with = "")] as a shorthand for - // specifying `serialize_with` and `deserialize_with`, schemars requires the - // argument to `with` to be a type rather than merely a path prefix (i.e. a - // mod or type). It's admittedly a bit tricky for schemars to address; - // unlike `serialize` or `deserialize`, `JsonSchema` requires several - // functions working together. It's unfortunate that schemars has this - // built-in incompatibility, exacerbated by its glacial rate of progress - // and immunity to offers of help. - #[serde(default, with = "params::UserData")] + #[serde(default, with = "UserData")] pub user_data: Vec, /// The network interfaces to be created for this instance. #[serde(default)] - pub network_interfaces: params::InstanceNetworkInterfaceAttachment, + pub network_interfaces: InstanceNetworkInterfaceAttachment, /// The external IP addresses provided to this instance. /// /// By default, all instances have outbound connectivity, but no inbound /// connectivity. These external addresses can be used to provide a fixed, /// known IP address for making inbound connections to the instance. - // Delegates through v2025121200 → params::ExternalIpCreate #[serde(default)] - pub external_ips: Vec, + pub external_ips: Vec, - /// Multicast groups this instance should join at creation. + /// The multicast groups this instance should join. /// - /// Groups can be specified by name, UUID, or IP address. Non-existent - /// groups are created automatically. + /// The instance will be automatically added as a member of the specified + /// multicast groups during creation, enabling it to send and receive + /// multicast traffic for those groups. #[serde(default)] - pub multicast_groups: Vec, + pub multicast_groups: Vec, /// A list of disks to be attached to the instance. /// @@ -217,10 +128,10 @@ pub struct InstanceCreate { /// If not provided, all SSH public keys from the user's profile will be sent. /// If an empty list is provided, no public keys will be transmitted to the /// instance. - pub ssh_public_keys: Option>, + pub ssh_public_keys: Option>, /// Should this instance be started upon creation; true by default. - #[serde(default = "params::bool_true")] + #[serde(default = "bool_true")] pub start: bool, /// The auto-restart policy for this instance. @@ -238,23 +149,23 @@ pub struct InstanceCreate { /// In that case, any configured default policy will be used if this is /// `null`. #[serde(default)] - pub auto_restart_policy: Option, + pub auto_restart_policy: Option, /// Anti-Affinity groups which this instance should be added. #[serde(default)] - pub anti_affinity_groups: Vec, + pub anti_affinity_groups: Vec, /// The CPU platform to be used for this instance. If this is `null`, the /// instance requires no particular CPU platform; when it is started the /// instance will have the most general CPU platform supported by the sled /// it is initially placed on. #[serde(default)] - pub cpu_platform: Option, + pub cpu_platform: Option, } -impl From for params::InstanceCreate { - fn from(old: InstanceCreate) -> params::InstanceCreate { - params::InstanceCreate { +impl From for InstanceCreate { + fn from(old: v2025_11_20_00::instance::InstanceCreate) -> Self { + InstanceCreate { identity: old.identity, ncpus: old.ncpus, memory: old.memory, diff --git a/nexus/types/versions/src/local_storage/mod.rs b/nexus/types/versions/src/local_storage/mod.rs new file mode 100644 index 00000000000..a519360ace2 --- /dev/null +++ b/nexus/types/versions/src/local_storage/mod.rs @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `LOCAL_STORAGE` of the Nexus external API. +//! +//! This version (2025_12_03_00) adds support for local storage disks. + +pub mod disk; +pub mod instance; diff --git a/nexus/types/versions/src/multicast_implicit_lifecycle_updates/instance.rs b/nexus/types/versions/src/multicast_implicit_lifecycle_updates/instance.rs new file mode 100644 index 00000000000..cd1d1d33b72 --- /dev/null +++ b/nexus/types/versions/src/multicast_implicit_lifecycle_updates/instance.rs @@ -0,0 +1,198 @@ +// 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/. + +//! Instance types for version MULTICAST_IMPLICIT_LIFECYCLE_UPDATES. +//! +//! This version changes multicast groups from `Vec` to +//! `Vec`. It is also the earliest version where +//! `InstanceCreate` uses `MulticastGroupJoinSpec`, so `InstanceCreate` and +//! `InstanceDiskAttachment` are defined here. + +use omicron_common::api::external::{ + ByteCount, Hostname, IdentityMetadataCreateParams, + InstanceAutoRestartPolicy, InstanceCpuCount, InstanceCpuPlatform, NameOrId, + Nullable, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::v2025_11_20_00::instance::InstanceDiskAttach; +use crate::v2025_11_20_00::instance::{UserData, bool_true}; +use crate::v2026_01_03_00::instance::InstanceNetworkInterfaceAttachment; +use crate::v2026_01_05_00::instance::ExternalIpCreate; + +use super::multicast::MulticastGroupJoinSpec; + +/// Parameters of an `Instance` that can be reconfigured after creation. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceUpdate { + /// The number of vCPUs to be allocated to the instance + pub ncpus: InstanceCpuCount, + + /// The amount of RAM (in bytes) to be allocated to the instance + pub memory: ByteCount, + + /// The disk the instance is configured to boot from. + /// + /// Setting a boot disk is optional but recommended to ensure predictable + /// boot behavior. The boot disk can be set during instance creation or + /// later if the instance is stopped. The boot disk counts against the + /// disk attachment limit. + /// + /// An instance that does not have a boot disk set will use the boot + /// options specified in its UEFI settings, which are controlled by both + /// the instance's UEFI firmware and the guest operating system. Boot + /// options can change as disks are attached and detached, which may + /// result in an instance that only boots to the EFI shell until a boot + /// disk is set. + pub boot_disk: Nullable, + + /// The auto-restart policy for this instance. + /// + /// This policy determines whether the instance should be automatically + /// restarted by the control plane on failure. If this is `null`, any + /// explicitly configured auto-restart policy will be unset, and the + /// control plane will select the default policy when determining whether + /// the instance can be automatically restarted. + /// + /// Currently, the global default auto-restart policy is "best-effort", + /// so instances with `null` auto-restart policies will be automatically + /// restarted. However, in the future, the default policy may be + /// configurable through other mechanisms, such as on a per-project + /// basis. In that case, any configured default policy will be used if + /// this is `null`. + pub auto_restart_policy: Nullable, + + /// The CPU platform to be used for this instance. If this is `null`, + /// the instance requires no particular CPU platform; when it is started + /// the instance will have the most general CPU platform supported by + /// the sled it is initially placed on. + pub cpu_platform: Nullable, + + /// Multicast groups this instance should join. + /// + /// When specified, this replaces the instance's current multicast group + /// membership with the new set of groups. The instance will leave any + /// groups not listed here and join any new groups that are specified. + /// + /// Each entry can specify the group by name, UUID, or IP address, along with + /// optional source IP filtering for SSM (Source-Specific Multicast). When + /// a group doesn't exist, it will be implicitly created using the default + /// multicast pool (or you can specify `ip_version` to disambiguate if needed). + /// + /// If not provided, the instance's multicast group membership will not + /// be changed. + #[serde(default)] + pub multicast_groups: Option>, +} + +impl From for InstanceUpdate { + fn from(old: crate::v2025_11_20_00::instance::InstanceUpdate) -> Self { + Self { + ncpus: old.ncpus, + memory: old.memory, + boot_disk: old.boot_disk, + auto_restart_policy: old.auto_restart_policy, + cpu_platform: old.cpu_platform, + multicast_groups: old.multicast_groups.map(|groups| { + groups + .into_iter() + .map(|g| MulticastGroupJoinSpec { + group: g.into(), + source_ips: None, + ip_version: None, + }) + .collect() + }), + } + } +} + +/// Describe the instance's disks at creation time. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum InstanceDiskAttachment { + /// During instance creation, create and attach disks. + Create(crate::v2025_12_03_00::disk::DiskCreate), + /// During instance creation, attach this disk. + Attach(InstanceDiskAttach), +} + +impl From + for InstanceDiskAttachment +{ + fn from( + old: crate::v2025_12_03_00::instance::InstanceDiskAttachment, + ) -> Self { + match old { + crate::v2025_12_03_00::instance::InstanceDiskAttachment::Create( + create, + ) => Self::Create(create), + crate::v2025_12_03_00::instance::InstanceDiskAttachment::Attach( + attach, + ) => Self::Attach(attach), + } + } +} + +/// Create-time parameters for an `Instance`. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + pub ncpus: InstanceCpuCount, + pub memory: ByteCount, + pub hostname: Hostname, + #[serde(default, with = "UserData")] + pub user_data: Vec, + #[serde(default)] + pub network_interfaces: InstanceNetworkInterfaceAttachment, + #[serde(default)] + pub external_ips: Vec, + #[serde(default)] + pub multicast_groups: Vec, + #[serde(default)] + pub disks: Vec, + #[serde(default)] + pub boot_disk: Option, + pub ssh_public_keys: Option>, + #[serde(default = "bool_true")] + pub start: bool, + #[serde(default)] + pub auto_restart_policy: Option, + #[serde(default)] + pub anti_affinity_groups: Vec, + #[serde(default)] + pub cpu_platform: Option, +} + +impl From for InstanceCreate { + fn from(old: crate::v2026_01_05_00::instance::InstanceCreate) -> Self { + Self { + identity: old.identity, + ncpus: old.ncpus, + memory: old.memory, + hostname: old.hostname, + user_data: old.user_data, + network_interfaces: old.network_interfaces, + external_ips: old.external_ips, + multicast_groups: old + .multicast_groups + .into_iter() + .map(|g| MulticastGroupJoinSpec { + group: g.into(), + source_ips: None, + ip_version: None, + }) + .collect(), + disks: old.disks.into_iter().map(Into::into).collect(), + boot_disk: old.boot_disk.map(Into::into), + ssh_public_keys: old.ssh_public_keys, + start: old.start, + auto_restart_policy: old.auto_restart_policy, + anti_affinity_groups: old.anti_affinity_groups, + cpu_platform: old.cpu_platform, + } + } +} diff --git a/nexus/types/versions/src/multicast_implicit_lifecycle_updates/mod.rs b/nexus/types/versions/src/multicast_implicit_lifecycle_updates/mod.rs new file mode 100644 index 00000000000..1f90f84b591 --- /dev/null +++ b/nexus/types/versions/src/multicast_implicit_lifecycle_updates/mod.rs @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `MULTICAST_IMPLICIT_LIFECYCLE_UPDATES` of the Nexus external API. +//! +//! This version changes multicast group paths and member operations to use +//! `MulticastGroupIdentifier` (supporting name, UUID, or IP address) instead +//! of `NameOrId`. It also introduces `MulticastGroupJoinSpec` for inline +//! multicast group membership in instance create/update, and adds per-member +//! source IP filtering. + +pub mod instance; +pub mod multicast; diff --git a/nexus/types/versions/src/multicast_implicit_lifecycle_updates/multicast.rs b/nexus/types/versions/src/multicast_implicit_lifecycle_updates/multicast.rs new file mode 100644 index 00000000000..b2d8c7c2ffd --- /dev/null +++ b/nexus/types/versions/src/multicast_implicit_lifecycle_updates/multicast.rs @@ -0,0 +1,339 @@ +// 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/. + +//! Multicast group types for version MULTICAST_IMPLICIT_LIFECYCLE_UPDATES. + +use api_identity::ObjectIdentity; +use omicron_common::address::IpVersion; +use omicron_common::api::external::{ + IdentityMetadata, NameOrId, ObjectIdentity, +}; +use omicron_common::vlan::VlanID; +use parse_display::Display; +use schemars::JsonSchema; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de}; +use std::net::IpAddr; +use std::str::FromStr; +use uuid::Uuid; + +// Re-use validators from initial module. +use crate::v2025_11_20_00::multicast::validate_source_ips_param; + +use omicron_common::api::external::Name; + +/// Identifier for a multicast group: can be a Name, UUID, or IP address. +/// +/// This type supports the join-by-IP pattern where users can specify +/// a multicast IP address directly, and the system will auto-discover +/// the pool and find or create the group. +#[derive(Debug, Display, Clone, PartialEq)] +#[display("{0}")] +pub enum MulticastGroupIdentifier { + Id(Uuid), + Name(Name), + Ip(IpAddr), +} + +impl TryFrom for MulticastGroupIdentifier { + type Error = String; + + fn try_from(value: String) -> Result { + if let Ok(id) = Uuid::parse_str(&value) { + Ok(MulticastGroupIdentifier::Id(id)) + } else if let Ok(ip) = value.parse::() { + Ok(MulticastGroupIdentifier::Ip(ip)) + } else { + Ok(MulticastGroupIdentifier::Name(Name::try_from(value)?)) + } + } +} + +impl FromStr for MulticastGroupIdentifier { + type Err = String; + + fn from_str(value: &str) -> Result { + MulticastGroupIdentifier::try_from(String::from(value)) + } +} + +impl From for MulticastGroupIdentifier { + fn from(name: Name) -> Self { + MulticastGroupIdentifier::Name(name) + } +} + +impl From for MulticastGroupIdentifier { + fn from(id: Uuid) -> Self { + MulticastGroupIdentifier::Id(id) + } +} + +impl From for MulticastGroupIdentifier { + fn from(ip: IpAddr) -> Self { + MulticastGroupIdentifier::Ip(ip) + } +} + +impl From for MulticastGroupIdentifier { + fn from(value: NameOrId) -> Self { + match value { + NameOrId::Name(name) => MulticastGroupIdentifier::Name(name), + NameOrId::Id(id) => MulticastGroupIdentifier::Id(id), + } + } +} + +impl Serialize for MulticastGroupIdentifier { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for MulticastGroupIdentifier { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + MulticastGroupIdentifier::try_from(s).map_err(de::Error::custom) + } +} + +impl JsonSchema for MulticastGroupIdentifier { + fn schema_name() -> String { + "MulticastGroupIdentifier".to_string() + } + + fn json_schema( + _generator: &mut schemars::r#gen::SchemaGenerator, + ) -> schemars::schema::Schema { + schemars::schema::SchemaObject { + instance_type: Some(schemars::schema::InstanceType::String.into()), + metadata: Some(Box::new(schemars::schema::Metadata { + title: Some("A multicast group identifier".to_string()), + description: Some( + "Can be a UUID, a name, or an IP address".to_string(), + ), + ..Default::default() + })), + ..Default::default() + } + .into() + } +} + +#[derive(Deserialize, JsonSchema, Clone)] +pub struct MulticastGroupSelector { + /// Name, ID, or IP address of the multicast group (fleet-scoped) + pub multicast_group: MulticastGroupIdentifier, +} + +/// Specification for joining a multicast group with optional source filtering. +/// +/// Used in `InstanceCreate` and `InstanceUpdate` to specify multicast group +/// membership along with per-member source IP configuration. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupJoinSpec { + /// The multicast group to join, specified by name, UUID, or IP address. + pub group: MulticastGroupIdentifier, + + /// Source IPs for source-filtered multicast (SSM). Optional for ASM groups, + /// required for SSM groups (232.0.0.0/8, ff3x::/32). + #[serde(default, deserialize_with = "validate_source_ips_param")] + pub source_ips: Option>, + + /// IP version for pool selection when creating a group by name. + /// Required if both IPv4 and IPv6 default multicast pools are linked. + #[serde(default)] + pub ip_version: Option, +} + +/// Parameters for joining an instance to a multicast group. +/// +/// When joining by IP address, the pool containing the multicast IP is +/// auto-discovered from all linked multicast pools. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, Default)] +pub struct InstanceMulticastGroupJoin { + /// Source IPs for source-filtered multicast (SSM). Optional for ASM groups, + /// required for SSM groups (232.0.0.0/8, ff3x::/32). + #[serde(default, deserialize_with = "validate_source_ips_param")] + pub source_ips: Option>, + + /// IP version for pool selection when creating a group by name. + /// Required if both IPv4 and IPv6 default multicast pools are linked. + #[serde(default)] + pub ip_version: Option, +} + +/// Path parameters for multicast group operations. +#[derive(Serialize, Deserialize, JsonSchema)] +pub struct MulticastGroupPath { + /// Name, ID, or IP address of the multicast group + pub multicast_group: MulticastGroupIdentifier, +} + +/// Parameters for adding an instance to a multicast group. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupMemberAdd { + /// Name or ID of the instance to add to the multicast group + pub instance: NameOrId, + /// Source IPs for source-filtered multicast (SSM). Optional for ASM groups, + /// required for SSM groups (232.0.0.0/8, ff3x::/32). + #[serde(default, deserialize_with = "validate_source_ips_param")] + pub source_ips: Option>, +} + +/// Path parameters for multicast group member operations. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct MulticastGroupMemberPath { + /// Name, ID, or IP address of the multicast group + pub multicast_group: MulticastGroupIdentifier, + /// Name or ID of the instance + pub instance: NameOrId, +} + +/// Path parameters for instance multicast group operations. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceMulticastGroupPath { + /// Name or ID of the instance + pub instance: NameOrId, + /// Name, ID, or IP address of the multicast group + pub multicast_group: MulticastGroupIdentifier, +} + +/// View of a Multicast Group +#[derive( + ObjectIdentity, Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema, +)] +pub struct MulticastGroup { + #[serde(flatten)] + pub identity: IdentityMetadata, + /// The multicast IP address held by this resource. + pub multicast_ip: IpAddr, + /// Union of all member source IP addresses (computed, read-only). + /// + /// This field shows the combined source IPs across all group members. + /// Individual members may subscribe to different sources; this union + /// reflects all sources that any member is subscribed to. + /// Empty array means no members have source filtering enabled. + pub source_ips: Vec, + /// Multicast VLAN (MVLAN) for egress multicast traffic to upstream networks. + /// None means no VLAN tagging on egress. + pub mvlan: Option, + /// The ID of the IP pool this resource belongs to. + pub ip_pool_id: Uuid, + /// Current state of the multicast group. + pub state: String, +} + +/// View of a Multicast Group Member (instance belonging to a multicast group) +#[derive( + ObjectIdentity, Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema, +)] +pub struct MulticastGroupMember { + #[serde(flatten)] + pub identity: IdentityMetadata, + /// The ID of the multicast group this member belongs to. + pub multicast_group_id: Uuid, + /// The multicast IP address of the group this member belongs to. + pub multicast_ip: IpAddr, + /// The ID of the instance that is a member of this group. + pub instance_id: Uuid, + /// Source IP addresses for this member's multicast subscription. + /// + /// - **ASM**: Sources are optional. Empty array means any source is allowed. + /// Non-empty array enables source filtering (IGMPv3/MLDv2). + /// - **SSM**: Sources are required for SSM addresses (232/8, ff3x::/32). + pub source_ips: Vec, + /// Current state of the multicast group membership. + pub state: String, +} + +// -- Conversions between v2026_01_08_00 and v2025_11_20_00 multicast types -- + +impl From for crate::v2025_11_20_00::multicast::MulticastGroup { + fn from(new: MulticastGroup) -> Self { + Self { + identity: new.identity, + multicast_ip: new.multicast_ip, + source_ips: new.source_ips, + mvlan: new.mvlan, + ip_pool_id: new.ip_pool_id, + state: new.state, + } + } +} + +impl From + for crate::v2025_11_20_00::multicast::MulticastGroupMember +{ + fn from(new: MulticastGroupMember) -> Self { + // Drop multicast_ip and source_ips which were added in this version. + Self { + identity: new.identity, + multicast_group_id: new.multicast_group_id, + instance_id: new.instance_id, + state: new.state, + } + } +} + +impl From + for MulticastGroupPath +{ + fn from( + old: crate::v2025_11_20_00::path_params::MulticastGroupPath, + ) -> Self { + Self { multicast_group: old.multicast_group.into() } + } +} + +impl From + for MulticastGroupPath +{ + fn from( + old: crate::v2025_11_20_00::multicast::MulticastGroupIpLookupPath, + ) -> Self { + Self { multicast_group: old.address.into() } + } +} + +impl From + for MulticastGroupMemberPath +{ + fn from( + old: crate::v2025_11_20_00::multicast::MulticastGroupMemberPath, + ) -> Self { + Self { + multicast_group: old.multicast_group.into(), + instance: old.instance, + } + } +} + +impl From + for InstanceMulticastGroupPath +{ + fn from( + old: crate::v2025_11_20_00::multicast::InstanceMulticastGroupPath, + ) -> Self { + Self { + instance: old.instance, + multicast_group: old.multicast_group.into(), + } + } +} + +impl From + for MulticastGroupMemberAdd +{ + fn from( + old: crate::v2025_11_20_00::multicast::MulticastGroupMemberAdd, + ) -> Self { + Self { instance: old.instance, source_ips: None } + } +} diff --git a/nexus/types/versions/src/pool_selection_enums/floating_ip.rs b/nexus/types/versions/src/pool_selection_enums/floating_ip.rs new file mode 100644 index 00000000000..3737bec2683 --- /dev/null +++ b/nexus/types/versions/src/pool_selection_enums/floating_ip.rs @@ -0,0 +1,99 @@ +// 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/. + +//! Floating IP types for version POOL_SELECTION_ENUMS. +//! +//! This version introduces `AddressSelector`, a tagged enum for type-safe +//! IP address allocation that makes invalid states unrepresentable. + +use omicron_common::api::external::{IdentityMetadataCreateParams, NameOrId}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; + +use super::ip_pool::PoolSelector; +use crate::v2025_12_23_00; + +/// Specify how to allocate a floating IP address. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum AddressSelector { + /// Reserve a specific IP address. + Explicit { + /// The IP address to reserve. Must be available in the pool. + ip: IpAddr, + /// The pool containing this address. If not specified, the default + /// pool for the address's IP version is used. + pool: Option, + }, + /// Automatically allocate an IP address from a specified pool. + Auto { + /// Pool selection. + /// + /// If omitted, this field uses the silo's default pool. If the + /// silo has default pools for both IPv4 and IPv6, the request will + /// fail unless `ip_version` is specified in the pool selector. + #[serde(default)] + pool_selector: PoolSelector, + }, +} + +impl Default for AddressSelector { + fn default() -> Self { + AddressSelector::Auto { pool_selector: PoolSelector::default() } + } +} + +/// Parameters for creating a new floating IP address for instances. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct FloatingIpCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// IP address allocation method. + #[serde(default)] + pub address_selector: AddressSelector, +} + +impl TryFrom + for FloatingIpCreate +{ + type Error = omicron_common::api::external::Error; + + fn try_from( + old: v2025_12_23_00::floating_ip::FloatingIpCreate, + ) -> Result { + let address_selector = match (old.ip, old.pool, old.ip_version) { + // Explicit IP address provided -> ip_version must not be set + (Some(ip), pool, None) => AddressSelector::Explicit { ip, pool }, + // Explicit IP and ip_version is an invalid combination + (Some(_), _, Some(_)) => { + return Err( + omicron_common::api::external::Error::invalid_request( + "cannot specify both `ip` and `ip_version`; \ + the IP version is determined by the explicit IP address", + ), + ); + } + // No explicit IP, but named pool specified -> ip_version must not be set + (None, Some(pool), None) => AddressSelector::Auto { + pool_selector: PoolSelector::Explicit { pool }, + }, + // Named pool and ip_version is an invalid combination + (None, Some(_), Some(_)) => { + return Err( + omicron_common::api::external::Error::invalid_request( + "cannot specify both `pool` and `ip_version`; \ + `ip_version` is only used when allocating from the default pool", + ), + ); + } + // Allocate from default pool with optional IP version preference + (None, None, ip_version) => AddressSelector::Auto { + pool_selector: PoolSelector::Auto { ip_version }, + }, + }; + Ok(FloatingIpCreate { identity: old.identity, address_selector }) + } +} diff --git a/nexus/types/versions/src/pool_selection_enums/instance.rs b/nexus/types/versions/src/pool_selection_enums/instance.rs new file mode 100644 index 00000000000..6422e0e580d --- /dev/null +++ b/nexus/types/versions/src/pool_selection_enums/instance.rs @@ -0,0 +1,222 @@ +// 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/. + +//! Instance types for version POOL_SELECTION_ENUMS. +//! +//! This version changes IP pool selection to use `PoolSelector` enums +//! instead of flat pool/ip_version fields. + +use omicron_common::api::external::{ + ByteCount, Hostname, IdentityMetadataCreateParams, + InstanceAutoRestartPolicy, InstanceCpuCount, InstanceCpuPlatform, NameOrId, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use super::ip_pool::PoolSelector; +use crate::v2025_11_20_00::instance::{UserData, bool_true}; +use crate::v2025_12_03_00::instance::InstanceDiskAttachment; +use crate::v2025_12_23_00; +use crate::v2026_01_03_00; +use crate::v2026_01_03_00::instance::InstanceNetworkInterfaceAttachment; + +/// Parameters for creating an external IP address for instances. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum ExternalIpCreate { + /// An IP address providing both inbound and outbound access. The address is + /// automatically assigned from a pool. + Ephemeral { + /// Pool to allocate from. + #[serde(default)] + pool_selector: PoolSelector, + }, + /// An IP address providing both inbound and outbound access. The address is + /// an existing floating IP object assigned to the current project. + /// + /// The floating IP must not be in use by another instance or service. + Floating { floating_ip: NameOrId }, +} + +impl TryFrom for ExternalIpCreate { + type Error = omicron_common::api::external::Error; + + fn try_from( + old: v2025_12_23_00::instance::ExternalIpCreate, + ) -> Result { + match old { + v2025_12_23_00::instance::ExternalIpCreate::Ephemeral { + pool, + ip_version, + } => { + let pool_selector = (pool, ip_version).try_into()?; + Ok(ExternalIpCreate::Ephemeral { pool_selector }) + } + v2025_12_23_00::instance::ExternalIpCreate::Floating { + floating_ip, + } => Ok(ExternalIpCreate::Floating { floating_ip }), + } + } +} + +/// Parameters for creating an ephemeral IP address for an instance. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct EphemeralIpCreate { + /// Pool to allocate from. + #[serde(default)] + pub pool_selector: PoolSelector, +} + +impl TryFrom + for EphemeralIpCreate +{ + type Error = omicron_common::api::external::Error; + + fn try_from( + old: v2025_12_23_00::instance::EphemeralIpCreate, + ) -> Result { + let pool_selector = (old.pool, old.ip_version).try_into()?; + Ok(EphemeralIpCreate { pool_selector }) + } +} + +/// Create-time parameters for an `Instance` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The number of vCPUs to be allocated to the instance + pub ncpus: InstanceCpuCount, + /// The amount of RAM (in bytes) to be allocated to the instance + pub memory: ByteCount, + /// The hostname to be assigned to the instance + pub hostname: Hostname, + + /// User data for instance initialization systems (such as cloud-init). + /// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / + /// characters with padding). Maximum 32 KiB unencoded data. + #[serde(default, with = "UserData")] + pub user_data: Vec, + + /// The network interfaces to be created for this instance. + #[serde(default)] + pub network_interfaces: InstanceNetworkInterfaceAttachment, + + /// The external IP addresses provided to this instance. + /// + /// By default, all instances have outbound connectivity, but no inbound + /// connectivity. These external addresses can be used to provide a fixed, + /// known IP address for making inbound connections to the instance. + #[serde(default)] + pub external_ips: Vec, + + /// The multicast groups this instance should join. + /// + /// The instance will be automatically added as a member of the specified + /// multicast groups during creation, enabling it to send and receive + /// multicast traffic for those groups. + #[serde(default)] + pub multicast_groups: Vec, + + /// A list of disks to be attached to the instance. + /// + /// Disk attachments of type "create" will be created, while those of type + /// "attach" must already exist. + /// + /// The order of this list does not guarantee a boot order for the instance. + /// Use the boot_disk attribute to specify a boot disk. When boot_disk is + /// specified it will count against the disk attachment limit. + #[serde(default)] + pub disks: Vec, + + /// The disk the instance is configured to boot from. + /// + /// This disk can either be attached if it already exists or created along + /// with the instance. + /// + /// Specifying a boot disk is optional but recommended to ensure predictable + /// boot behavior. The boot disk can be set during instance creation or + /// later if the instance is stopped. The boot disk counts against the disk + /// attachment limit. + /// + /// An instance that does not have a boot disk set will use the boot + /// options specified in its UEFI settings, which are controlled by both the + /// instance's UEFI firmware and the guest operating system. Boot options + /// can change as disks are attached and detached, which may result in an + /// instance that only boots to the EFI shell until a boot disk is set. + #[serde(default)] + pub boot_disk: Option, + + /// An allowlist of SSH public keys to be transferred to the instance via + /// cloud-init during instance creation. + /// + /// If not provided, all SSH public keys from the user's profile will be sent. + /// If an empty list is provided, no public keys will be transmitted to the + /// instance. + pub ssh_public_keys: Option>, + + /// Should this instance be started upon creation; true by default. + #[serde(default = "bool_true")] + pub start: bool, + + /// The auto-restart policy for this instance. + /// + /// This policy determines whether the instance should be automatically + /// restarted by the control plane on failure. If this is `null`, no + /// auto-restart policy will be explicitly configured for this instance, and + /// the control plane will select the default policy when determining + /// whether the instance can be automatically restarted. + /// + /// Currently, the global default auto-restart policy is "best-effort", so + /// instances with `null` auto-restart policies will be automatically + /// restarted. However, in the future, the default policy may be + /// configurable through other mechanisms, such as on a per-project basis. + /// In that case, any configured default policy will be used if this is + /// `null`. + #[serde(default)] + pub auto_restart_policy: Option, + + /// Anti-Affinity groups which this instance should be added. + #[serde(default)] + pub anti_affinity_groups: Vec, + + /// The CPU platform to be used for this instance. If this is `null`, the + /// instance requires no particular CPU platform; when it is started the + /// instance will have the most general CPU platform supported by the sled + /// it is initially placed on. + #[serde(default)] + pub cpu_platform: Option, +} + +impl TryFrom for InstanceCreate { + type Error = omicron_common::api::external::Error; + + fn try_from( + old: v2026_01_03_00::instance::InstanceCreate, + ) -> Result { + let external_ips: Vec = old + .external_ips + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?; + + Ok(InstanceCreate { + identity: old.identity, + ncpus: old.ncpus, + memory: old.memory, + hostname: old.hostname, + user_data: old.user_data, + network_interfaces: old.network_interfaces, + external_ips, + multicast_groups: old.multicast_groups, + disks: old.disks, + boot_disk: old.boot_disk, + ssh_public_keys: old.ssh_public_keys, + start: old.start, + auto_restart_policy: old.auto_restart_policy, + anti_affinity_groups: old.anti_affinity_groups, + cpu_platform: old.cpu_platform, + }) + } +} diff --git a/nexus/types/versions/src/pool_selection_enums/ip_pool.rs b/nexus/types/versions/src/pool_selection_enums/ip_pool.rs new file mode 100644 index 00000000000..d1b62f0c472 --- /dev/null +++ b/nexus/types/versions/src/pool_selection_enums/ip_pool.rs @@ -0,0 +1,98 @@ +// 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/. + +//! IP pool types for version POOL_SELECTION_ENUMS. +//! +//! This version introduces `PoolSelector`, a tagged enum for type-safe pool +//! selection that makes invalid states unrepresentable. + +use omicron_common::api::external::{IpVersion, NameOrId}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Specify which IP or external subnet pool to allocate from. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum PoolSelector { + /// Use the specified pool by name or ID. + Explicit { + /// The pool to allocate from. + pool: NameOrId, + }, + /// Use the default pool for the silo. + Auto { + /// IP version to use when multiple default pools exist. + /// Required if both IPv4 and IPv6 default pools are configured. + #[serde(default)] + ip_version: Option, + }, +} + +impl Default for PoolSelector { + fn default() -> Self { + PoolSelector::Auto { ip_version: None } + } +} + +// Conversion from v2026_01_03_00's flat pool/ip_version fields. +impl TryFrom<(Option, Option)> for PoolSelector { + type Error = omicron_common::api::external::Error; + + fn try_from( + (pool, ip_version): (Option, Option), + ) -> Result { + match (pool, ip_version) { + // Named pool specified -> ip_version must not be set + (Some(pool), None) => Ok(PoolSelector::Explicit { pool }), + // Named pool & ip_version is an invalid combination + (Some(_), Some(_)) => { + Err(omicron_common::api::external::Error::invalid_request( + "cannot specify both `pool` and `ip_version`; \ + `ip_version` is only used when allocating from the default pool", + )) + } + // Default pool with optional ip_version preference + (None, ip_version) => Ok(PoolSelector::Auto { ip_version }), + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct IpPoolSiloUpdate { + /// When a pool is the default for a silo, floating IPs and instance + /// ephemeral IPs will come from that pool when no other pool is specified. + /// + /// A silo can have at most one default pool per combination of pool type + /// (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 + /// default pools total. When a pool is made default, an existing default + /// of the same type and version will remain linked but will no longer be + /// the default. + pub is_default: bool, +} + +impl From + for IpPoolSiloUpdate +{ + fn from(old: crate::v2025_11_20_00::ip_pool::IpPoolSiloUpdate) -> Self { + IpPoolSiloUpdate { is_default: old.is_default } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct IpPoolLinkSilo { + pub silo: NameOrId, + /// When a pool is the default for a silo, floating IPs and instance + /// ephemeral IPs will come from that pool when no other pool is specified. + /// + /// A silo can have at most one default pool per combination of pool type + /// (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 + /// default pools total. + pub is_default: bool, +} + +impl From for IpPoolLinkSilo { + fn from(old: crate::v2025_11_20_00::ip_pool::IpPoolLinkSilo) -> Self { + IpPoolLinkSilo { silo: old.silo, is_default: old.is_default } + } +} diff --git a/nexus/types/versions/src/pool_selection_enums/mod.rs b/nexus/types/versions/src/pool_selection_enums/mod.rs new file mode 100644 index 00000000000..3e35749f86a --- /dev/null +++ b/nexus/types/versions/src/pool_selection_enums/mod.rs @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `POOL_SELECTION_ENUMS` of the Nexus external API. +//! +//! This version (2026_01_05_00) refactors IP pool selection to use tagged enums +//! (`PoolSelector` and `AddressSelector`) that make invalid states +//! unrepresentable. + +pub mod floating_ip; +pub mod instance; +pub mod ip_pool; +pub mod probe; diff --git a/nexus/types/versions/src/pool_selection_enums/probe.rs b/nexus/types/versions/src/pool_selection_enums/probe.rs new file mode 100644 index 00000000000..ec380797a29 --- /dev/null +++ b/nexus/types/versions/src/pool_selection_enums/probe.rs @@ -0,0 +1,39 @@ +// 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/. + +//! Probe types for version POOL_SELECTION_ENUMS. +//! +//! This version changes probe creation to use `PoolSelector` instead of a flat +//! `ip_pool` field. + +use omicron_common::api::external::IdentityMetadataCreateParams; +use omicron_uuid_kinds::SledUuid; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use super::ip_pool::PoolSelector; +use crate::v2025_11_20_00; + +/// Create time parameters for probes. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct ProbeCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + #[schemars(with = "Uuid")] + pub sled: SledUuid, + /// Pool to allocate from. + #[serde(default)] + pub pool_selector: PoolSelector, +} + +impl From for ProbeCreate { + fn from(old: v2025_11_20_00::probe::ProbeCreate) -> ProbeCreate { + let pool_selector = match old.ip_pool { + Some(pool) => PoolSelector::Explicit { pool }, + None => PoolSelector::Auto { ip_version: None }, + }; + ProbeCreate { identity: old.identity, sled: old.sled, pool_selector } + } +} diff --git a/nexus/types/versions/src/read_only_disks/disk.rs b/nexus/types/versions/src/read_only_disks/disk.rs new file mode 100644 index 00000000000..5a8276de77d --- /dev/null +++ b/nexus/types/versions/src/read_only_disks/disk.rs @@ -0,0 +1,110 @@ +// 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/. + +//! Disk types for version READ_ONLY_DISKS. +//! +//! These are identical to the READ_ONLY_DISKS_NULLABLE types except that +//! `read_only` on `DiskSource::Snapshot` and `DiskSource::Image` is required +//! (no `#[serde(default)]`). + +use omicron_common::api::external::{ByteCount, IdentityMetadataCreateParams}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::v2025_11_20_00::disk::BlockSize; + +/// Different sources for a Distributed Disk +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum DiskSource { + /// Create a blank disk + Blank { + /// size of blocks for this Disk. valid values are: 512, 2048, or 4096 + block_size: BlockSize, + }, + /// Create a disk from a disk snapshot + Snapshot { + snapshot_id: Uuid, + /// If `true`, the disk created from this snapshot will be read-only. + read_only: bool, + }, + /// Create a disk from an image + Image { + image_id: Uuid, + /// If `true`, the disk created from this image will be read-only. + read_only: bool, + }, + /// Create a blank disk that will accept bulk writes or pull blocks from an + /// external source. + ImportingBlocks { block_size: BlockSize }, +} + +/// The source of a `Disk`'s blocks +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum DiskBackend { + Local {}, + Distributed { + /// The initial source for this disk + disk_source: DiskSource, + }, +} + +/// Create-time parameters for a `Disk` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct DiskCreate { + /// The common identifying metadata for the disk + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The source for this `Disk`'s blocks + pub disk_backend: DiskBackend, + /// Total size of the Disk in bytes + pub size: ByteCount, +} + +// -- Conversions from the previous version (LOCAL_STORAGE) -- +// The prior version's DiskCreate uses v2025_12_03_00 disk types (no read_only). +// We add read_only=false as the default. + +impl From for DiskBackend { + fn from(old: crate::v2025_12_03_00::disk::DiskBackend) -> Self { + match old { + crate::v2025_12_03_00::disk::DiskBackend::Local {} => { + Self::Local {} + } + crate::v2025_12_03_00::disk::DiskBackend::Distributed { + disk_source, + } => { + // Convert old DiskSource (no read_only) to new DiskSource + // (with read_only), defaulting to false. + let disk_source = match disk_source { + crate::v2025_12_03_00::disk::DiskSource::Blank { + block_size, + } => DiskSource::Blank { block_size }, + crate::v2025_12_03_00::disk::DiskSource::Snapshot { + snapshot_id, + } => DiskSource::Snapshot { snapshot_id, read_only: false }, + crate::v2025_12_03_00::disk::DiskSource::Image { + image_id, + } => DiskSource::Image { image_id, read_only: false }, + crate::v2025_12_03_00::disk::DiskSource::ImportingBlocks { + block_size, + } => DiskSource::ImportingBlocks { block_size }, + }; + Self::Distributed { disk_source } + } + } + } +} + +impl From for DiskCreate { + fn from(old: crate::v2025_12_03_00::disk::DiskCreate) -> Self { + Self { + identity: old.identity, + disk_backend: old.disk_backend.into(), + size: old.size, + } + } +} diff --git a/nexus/types/versions/src/read_only_disks/instance.rs b/nexus/types/versions/src/read_only_disks/instance.rs new file mode 100644 index 00000000000..10face04985 --- /dev/null +++ b/nexus/types/versions/src/read_only_disks/instance.rs @@ -0,0 +1,100 @@ +// 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/. + +//! Instance types for version READ_ONLY_DISKS. + +use omicron_common::api::external::{ + ByteCount, Hostname, IdentityMetadataCreateParams, + InstanceAutoRestartPolicy, InstanceCpuCount, InstanceCpuPlatform, NameOrId, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::v2025_11_20_00::instance::InstanceDiskAttach; +use crate::v2025_11_20_00::instance::{UserData, bool_true}; +use crate::v2026_01_03_00::instance::InstanceNetworkInterfaceAttachment; +use crate::v2026_01_05_00::instance::ExternalIpCreate; +use crate::v2026_01_08_00::multicast::MulticastGroupJoinSpec; + +use super::disk::DiskCreate; + +/// Describe the instance's disks at creation time +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum InstanceDiskAttachment { + /// During instance creation, create and attach disks + Create(DiskCreate), + /// During instance creation, attach this disk + Attach(InstanceDiskAttach), +} + +impl From + for InstanceDiskAttachment +{ + fn from( + old: crate::v2026_01_08_00::instance::InstanceDiskAttachment, + ) -> Self { + match old { + crate::v2026_01_08_00::instance::InstanceDiskAttachment::Create( + create, + ) => Self::Create(create.into()), + crate::v2026_01_08_00::instance::InstanceDiskAttachment::Attach( + attach, + ) => Self::Attach(attach), + } + } +} + +/// Create-time parameters for an `Instance` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + pub ncpus: InstanceCpuCount, + pub memory: ByteCount, + pub hostname: Hostname, + #[serde(default, with = "UserData")] + pub user_data: Vec, + #[serde(default)] + pub network_interfaces: InstanceNetworkInterfaceAttachment, + #[serde(default)] + pub external_ips: Vec, + #[serde(default)] + pub multicast_groups: Vec, + #[serde(default)] + pub disks: Vec, + #[serde(default)] + pub boot_disk: Option, + pub ssh_public_keys: Option>, + #[serde(default = "bool_true")] + pub start: bool, + #[serde(default)] + pub auto_restart_policy: Option, + #[serde(default)] + pub anti_affinity_groups: Vec, + #[serde(default)] + pub cpu_platform: Option, +} + +impl From for InstanceCreate { + fn from(old: crate::v2026_01_08_00::instance::InstanceCreate) -> Self { + Self { + identity: old.identity, + ncpus: old.ncpus, + memory: old.memory, + hostname: old.hostname, + user_data: old.user_data, + network_interfaces: old.network_interfaces, + external_ips: old.external_ips, + multicast_groups: old.multicast_groups, + disks: old.disks.into_iter().map(Into::into).collect(), + boot_disk: old.boot_disk.map(Into::into), + ssh_public_keys: old.ssh_public_keys, + start: old.start, + auto_restart_policy: old.auto_restart_policy, + anti_affinity_groups: old.anti_affinity_groups, + cpu_platform: old.cpu_platform, + } + } +} diff --git a/nexus/types/versions/src/read_only_disks/mod.rs b/nexus/types/versions/src/read_only_disks/mod.rs new file mode 100644 index 00000000000..53153dd4664 --- /dev/null +++ b/nexus/types/versions/src/read_only_disks/mod.rs @@ -0,0 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `READ_ONLY_DISKS` of the Nexus external API. +//! +//! This version introduces the `read_only` field on `DiskSource::Snapshot` +//! and `DiskSource::Image` as a required field. The subsequent version, +//! `READ_ONLY_DISKS_NULLABLE`, makes `read_only` optional via +//! `#[serde(default)]`. + +pub mod disk; +pub mod instance; diff --git a/nexus/types/versions/src/read_only_disks_nullable/disk.rs b/nexus/types/versions/src/read_only_disks_nullable/disk.rs new file mode 100644 index 00000000000..fda647461a8 --- /dev/null +++ b/nexus/types/versions/src/read_only_disks_nullable/disk.rs @@ -0,0 +1,111 @@ +// 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/. + +//! Disk types for version READ_ONLY_DISKS_NULLABLE. + +use omicron_common::api::external::IdentityMetadataCreateParams; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::v2025_11_20_00::disk::BlockSize; +use omicron_common::api::external::ByteCount; + +/// Different sources for a Distributed Disk +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum DiskSource { + /// Create a blank disk + Blank { + /// Size of blocks for this disk. Valid values are: 512, 2048, or 4096. + block_size: BlockSize, + }, + /// Create a disk from a disk snapshot + Snapshot { + snapshot_id: Uuid, + /// If `true`, the disk created from this snapshot will be read-only. + #[serde(default)] + read_only: bool, + }, + /// Create a disk from an image + Image { + image_id: Uuid, + /// If `true`, the disk created from this image will be read-only. + #[serde(default)] + read_only: bool, + }, + /// Create a blank disk that will accept bulk writes or pull blocks from an + /// external source. + ImportingBlocks { block_size: BlockSize }, +} + +/// The source of a `Disk`'s blocks +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum DiskBackend { + Local {}, + Distributed { + /// The initial source for this disk + disk_source: DiskSource, + }, +} + +/// Create-time parameters for a `Disk` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct DiskCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The source for this `Disk`'s blocks + pub disk_backend: DiskBackend, + /// The total size of the Disk (in bytes) + pub size: ByteCount, +} + +// -- Conversions from the previous version (READ_ONLY_DISKS) -- + +impl From for DiskSource { + fn from(old: crate::v2026_01_30_01::disk::DiskSource) -> Self { + // The types are structurally identical; only `#[serde(default)]` + // differs between versions. + match old { + crate::v2026_01_30_01::disk::DiskSource::Blank { block_size } => { + Self::Blank { block_size } + } + crate::v2026_01_30_01::disk::DiskSource::Snapshot { + snapshot_id, + read_only, + } => Self::Snapshot { snapshot_id, read_only }, + crate::v2026_01_30_01::disk::DiskSource::Image { + image_id, + read_only, + } => Self::Image { image_id, read_only }, + crate::v2026_01_30_01::disk::DiskSource::ImportingBlocks { + block_size, + } => Self::ImportingBlocks { block_size }, + } + } +} + +impl From for DiskBackend { + fn from(old: crate::v2026_01_30_01::disk::DiskBackend) -> Self { + match old { + crate::v2026_01_30_01::disk::DiskBackend::Local {} => { + Self::Local {} + } + crate::v2026_01_30_01::disk::DiskBackend::Distributed { + disk_source, + } => Self::Distributed { disk_source: disk_source.into() }, + } + } +} + +impl From for DiskCreate { + fn from(old: crate::v2026_01_30_01::disk::DiskCreate) -> Self { + Self { + identity: old.identity, + disk_backend: old.disk_backend.into(), + size: old.size, + } + } +} diff --git a/nexus/types/versions/src/read_only_disks_nullable/instance.rs b/nexus/types/versions/src/read_only_disks_nullable/instance.rs new file mode 100644 index 00000000000..2286b59afaa --- /dev/null +++ b/nexus/types/versions/src/read_only_disks_nullable/instance.rs @@ -0,0 +1,167 @@ +// 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/. + +//! Instance types for version READ_ONLY_DISKS_NULLABLE. + +use omicron_common::api::external::{ + ByteCount, Hostname, IdentityMetadataCreateParams, + InstanceAutoRestartPolicy, InstanceCpuCount, InstanceCpuPlatform, NameOrId, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::v2025_11_20_00::instance::{ + InstanceDiskAttach, UserData, bool_true, +}; +use crate::v2026_01_03_00::instance::InstanceNetworkInterfaceAttachment; +use crate::v2026_01_05_00::instance::ExternalIpCreate; +use crate::v2026_01_08_00::multicast::MulticastGroupJoinSpec; + +use super::disk::DiskCreate; + +/// Describe the instance's disks at creation time +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum InstanceDiskAttachment { + /// During instance creation, create and attach disks + Create(DiskCreate), + /// During instance creation, attach this disk + Attach(InstanceDiskAttach), +} + +impl From + for InstanceDiskAttachment +{ + fn from( + old: crate::v2026_01_30_01::instance::InstanceDiskAttachment, + ) -> Self { + match old { + crate::v2026_01_30_01::instance::InstanceDiskAttachment::Create( + create, + ) => Self::Create(create.into()), + crate::v2026_01_30_01::instance::InstanceDiskAttachment::Attach( + attach, + ) => Self::Attach(attach), + } + } +} + +/// Create-time parameters for an `Instance` +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct InstanceCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + /// The number of vCPUs to be allocated to the instance + pub ncpus: InstanceCpuCount, + /// The amount of RAM (in bytes) to be allocated to the instance + pub memory: ByteCount, + /// The hostname to be assigned to the instance + pub hostname: Hostname, + /// User data for instance initialization systems (such as cloud-init). + /// Must be a Base64-encoded string, as specified in RFC 4648 § 4 (+ and / + /// characters with padding). Maximum 32 KiB unencoded data. + #[serde(default, with = "UserData")] + pub user_data: Vec, + /// The network interfaces to be created for this instance. + #[serde(default)] + pub network_interfaces: InstanceNetworkInterfaceAttachment, + /// The external IP addresses provided to this instance. + /// + /// By default, all instances have outbound connectivity, but no inbound + /// connectivity. These external addresses can be used to provide a fixed, + /// known IP address for making inbound connections to the instance. + #[serde(default)] + pub external_ips: Vec, + /// Multicast groups this instance should join at creation. + /// + /// Groups can be specified by name, UUID, or IP address. Non-existent + /// groups are created automatically. + #[serde(default)] + pub multicast_groups: Vec, + /// A list of disks to be attached to the instance. + /// + /// Disk attachments of type "create" will be created, while those of type + /// "attach" must already exist. + /// + /// The order of this list does not guarantee a boot order for the + /// instance. Use the boot_disk attribute to specify a boot disk. When + /// boot_disk is specified it will count against the disk attachment limit. + #[serde(default)] + pub disks: Vec, + /// The disk the instance is configured to boot from. + /// + /// This disk can either be attached if it already exists or created along + /// with the instance. + /// + /// Specifying a boot disk is optional but recommended to ensure + /// predictable boot behavior. The boot disk can be set during instance + /// creation or later if the instance is stopped. The boot disk counts + /// against the disk attachment limit. + /// + /// An instance that does not have a boot disk set will use the boot + /// options specified in its UEFI settings, which are controlled by both + /// the instance's UEFI firmware and the guest operating system. Boot + /// options can change as disks are attached and detached, which may + /// result in an instance that only boots to the EFI shell until a boot + /// disk is set. + #[serde(default)] + pub boot_disk: Option, + /// An allowlist of SSH public keys to be transferred to the instance via + /// cloud-init during instance creation. + /// + /// If not provided, all SSH public keys from the user's profile will be + /// sent. If an empty list is provided, no public keys will be transmitted + /// to the instance. + pub ssh_public_keys: Option>, + /// Should this instance be started upon creation; true by default. + #[serde(default = "bool_true")] + pub start: bool, + /// The auto-restart policy for this instance. + /// + /// This policy determines whether the instance should be automatically + /// restarted by the control plane on failure. If this is `null`, no + /// auto-restart policy will be explicitly configured for this instance, + /// and the control plane will select the default policy when determining + /// whether the instance can be automatically restarted. + /// + /// Currently, the global default auto-restart policy is "best-effort", + /// so instances with `null` auto-restart policies will be automatically + /// restarted. However, in the future, the default policy may be + /// configurable through other mechanisms, such as on a per-project + /// basis. In that case, any configured default policy will be used if + /// this is `null`. + #[serde(default)] + pub auto_restart_policy: Option, + /// Anti-affinity groups to which this instance should be added. + #[serde(default)] + pub anti_affinity_groups: Vec, + /// The CPU platform to be used for this instance. If this is `null`, the + /// instance requires no particular CPU platform; when it is started the + /// instance will have the most general CPU platform supported by the sled + /// it is initially placed on. + #[serde(default)] + pub cpu_platform: Option, +} + +impl From for InstanceCreate { + fn from(old: crate::v2026_01_30_01::instance::InstanceCreate) -> Self { + Self { + identity: old.identity, + ncpus: old.ncpus, + memory: old.memory, + hostname: old.hostname, + user_data: old.user_data, + network_interfaces: old.network_interfaces, + external_ips: old.external_ips, + multicast_groups: old.multicast_groups, + disks: old.disks.into_iter().map(Into::into).collect(), + boot_disk: old.boot_disk.map(Into::into), + ssh_public_keys: old.ssh_public_keys, + start: old.start, + auto_restart_policy: old.auto_restart_policy, + anti_affinity_groups: old.anti_affinity_groups, + cpu_platform: old.cpu_platform, + } + } +} diff --git a/nexus/types/versions/src/read_only_disks_nullable/mod.rs b/nexus/types/versions/src/read_only_disks_nullable/mod.rs new file mode 100644 index 00000000000..9c4fdb94bf0 --- /dev/null +++ b/nexus/types/versions/src/read_only_disks_nullable/mod.rs @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `READ_ONLY_DISKS_NULLABLE` of the Nexus external API. +//! +//! Makes `read_only` on `DiskSource` variants use `#[serde(default)]`, +//! allowing it to be omitted (defaulting to `false`). + +pub mod disk; +pub mod instance; diff --git a/nexus/types/versions/src/rename_address_selector_to_address_allocator/floating_ip.rs b/nexus/types/versions/src/rename_address_selector_to_address_allocator/floating_ip.rs new file mode 100644 index 00000000000..20621868021 --- /dev/null +++ b/nexus/types/versions/src/rename_address_selector_to_address_allocator/floating_ip.rs @@ -0,0 +1,89 @@ +// 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/. + +//! Floating IP types for version RENAME_ADDRESS_SELECTOR_TO_ADDRESS_ALLOCATOR. +//! +//! This version renames `AddressSelector` to `AddressAllocator` and +//! `address_selector` to `address_allocator` in `FloatingIpCreate`. The +//! `Explicit` variant still has both `ip` and optional `pool` fields; +//! the `pool` field is dropped in FLOATING_IP_ALLOCATOR_UPDATE. + +use omicron_common::api::external::{IdentityMetadataCreateParams, NameOrId}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::net::IpAddr; + +use crate::v2026_01_05_00::ip_pool::PoolSelector; + +/// Specify how to allocate a floating IP address. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum AddressAllocator { + /// Reserve a specific IP address. + Explicit { + /// The IP address to reserve. Must be available in the pool. + ip: IpAddr, + /// The pool containing this address. If not specified, the default + /// pool for the address's IP version is used. + pool: Option, + }, + /// Automatically allocate an IP address from a specified pool. + Auto { + /// Pool selection. + /// + /// If omitted, this field uses the silo's default pool. If the + /// silo has default pools for both IPv4 and IPv6, the request will + /// fail unless `ip_version` is specified in the pool selector. + #[serde(default)] + pool_selector: PoolSelector, + }, +} + +impl Default for AddressAllocator { + fn default() -> Self { + AddressAllocator::Auto { pool_selector: PoolSelector::default() } + } +} + +impl From + for AddressAllocator +{ + fn from( + value: crate::v2026_01_05_00::floating_ip::AddressSelector, + ) -> Self { + match value { + crate::v2026_01_05_00::floating_ip::AddressSelector::Explicit { + ip, + pool, + } => AddressAllocator::Explicit { ip, pool }, + crate::v2026_01_05_00::floating_ip::AddressSelector::Auto { + pool_selector, + } => AddressAllocator::Auto { pool_selector }, + } + } +} + +/// Parameters for creating a new floating IP address for instances. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct FloatingIpCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + + /// IP address allocation method. + #[serde(default)] + pub address_allocator: AddressAllocator, +} + +impl From + for FloatingIpCreate +{ + fn from( + value: crate::v2026_01_05_00::floating_ip::FloatingIpCreate, + ) -> Self { + Self { + identity: value.identity, + address_allocator: value.address_selector.into(), + } + } +} diff --git a/nexus/types/versions/src/rename_address_selector_to_address_allocator/mod.rs b/nexus/types/versions/src/rename_address_selector_to_address_allocator/mod.rs new file mode 100644 index 00000000000..7d9e037310d --- /dev/null +++ b/nexus/types/versions/src/rename_address_selector_to_address_allocator/mod.rs @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `RENAME_ADDRESS_SELECTOR_TO_ADDRESS_ALLOCATOR` of the Nexus +//! external API. +//! +//! Renames `AddressSelector` to `AddressAllocator` and +//! `address_selector` to `address_allocator` in `FloatingIpCreate`. + +pub mod floating_ip; diff --git a/nexus/types/versions/src/silo_project_ip_version_and_pool_type/ip_pool.rs b/nexus/types/versions/src/silo_project_ip_version_and_pool_type/ip_pool.rs new file mode 100644 index 00000000000..e131597e649 --- /dev/null +++ b/nexus/types/versions/src/silo_project_ip_version_and_pool_type/ip_pool.rs @@ -0,0 +1,52 @@ +// 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/. + +//! IP pool types for version SILO_PROJECT_IP_VERSION_AND_POOL_TYPE. +//! +//! This version adds `ip_version` and `pool_type` fields to SiloIpPool. + +use omicron_common::api::external::{ + IdentityMetadata, IpVersion, ObjectIdentity, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::v2025_11_20_00::ip_pool::IpPoolType; + +/// An IP pool in the context of a silo +#[derive( + api_identity::ObjectIdentity, + Clone, + Debug, + Deserialize, + Serialize, + JsonSchema, +)] +pub struct SiloIpPool { + #[serde(flatten)] + pub identity: IdentityMetadata, + + /// When a pool is the default for a silo, floating IPs and instance + /// ephemeral IPs will come from that pool when no other pool is specified. + /// + /// A silo can have at most one default pool per combination of pool type + /// (unicast or multicast) and IP version (IPv4 or IPv6), allowing up to 4 + /// default pools total. + pub is_default: bool, + + /// The IP version for the pool. + pub ip_version: IpVersion, + + /// Type of IP pool (unicast or multicast). + pub pool_type: IpPoolType, +} + +impl From for crate::v2025_11_20_00::ip_pool::SiloIpPool { + fn from(new: SiloIpPool) -> crate::v2025_11_20_00::ip_pool::SiloIpPool { + crate::v2025_11_20_00::ip_pool::SiloIpPool { + identity: new.identity, + is_default: new.is_default, + } + } +} diff --git a/nexus/types/versions/src/silo_project_ip_version_and_pool_type/mod.rs b/nexus/types/versions/src/silo_project_ip_version_and_pool_type/mod.rs new file mode 100644 index 00000000000..5b442457bc0 --- /dev/null +++ b/nexus/types/versions/src/silo_project_ip_version_and_pool_type/mod.rs @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `SILO_PROJECT_IP_VERSION_AND_POOL_TYPE` of the Nexus external API. +//! +//! This version (2026_01_01_00) adds `ip_version` and `pool_type` fields to +//! SiloIpPool responses, and uses old-style single-IP network interfaces. + +pub mod ip_pool; diff --git a/nexus/types/versions/src/trust_quorum_add_sleds_and_get_latest_config/mod.rs b/nexus/types/versions/src/trust_quorum_add_sleds_and_get_latest_config/mod.rs new file mode 100644 index 00000000000..0b668ddd351 --- /dev/null +++ b/nexus/types/versions/src/trust_quorum_add_sleds_and_get_latest_config/mod.rs @@ -0,0 +1,9 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! Version `TRUST_QUORUM_ADD_SLEDS_AND_GET_LATEST_CONFIG` of the Nexus external API. +//! +//! Adds rack membership management types. + +pub mod rack; diff --git a/nexus/types/versions/src/trust_quorum_add_sleds_and_get_latest_config/rack.rs b/nexus/types/versions/src/trust_quorum_add_sleds_and_get_latest_config/rack.rs new file mode 100644 index 00000000000..57c672f9582 --- /dev/null +++ b/nexus/types/versions/src/trust_quorum_add_sleds_and_get_latest_config/rack.rs @@ -0,0 +1,66 @@ +// 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/. + +//! Rack membership types for version TRUST_QUORUM_ADD_SLEDS_AND_GET_LATEST_CONFIG. + +use chrono::{DateTime, Utc}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use sled_hardware_types::BaseboardId; +use std::collections::BTreeSet; +use uuid::Uuid; + +/// A unique, monotonically increasing number representing the set of active +/// sleds in a rack at a given point in time. +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct RackMembershipVersion(pub u64); + +#[derive( + Clone, + Debug, + Serialize, + Deserialize, + JsonSchema, + PartialOrd, + Ord, + PartialEq, + Eq, +)] +pub struct RackMembershipAddSledsRequest { + pub sled_ids: BTreeSet, +} + +#[derive(Deserialize, JsonSchema)] +pub struct RackMembershipConfigPathParams { + pub rack_id: Uuid, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct RackMembershipVersionParam { + pub version: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum RackMembershipChangeState { + InProgress, + Committed, + Aborted, +} + +/// Status of the rack membership uniquely identified by the (rack_id, version) pair +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct RackMembershipStatus { + pub rack_id: Uuid, + /// Version that uniquely identifies the rack membership at a given point in time + pub version: RackMembershipVersion, + pub state: RackMembershipChangeState, + /// All members of the rack for this version + pub members: BTreeSet, + /// All members that have not yet confirmed this membership version + pub unacknowledged_members: BTreeSet, + pub time_created: DateTime, + pub time_committed: Option>, + pub time_aborted: Option>, +} diff --git a/sled-agent/src/rack_setup/plan/service.rs b/sled-agent/src/rack_setup/plan/service.rs index f6b1bd3c8fb..c1a86f59490 100644 --- a/sled-agent/src/rack_setup/plan/service.rs +++ b/sled-agent/src/rack_setup/plan/service.rs @@ -26,7 +26,7 @@ use nexus_types::deployment::{ OmicronZoneExternalSnatIp, OximeterReadMode, PendingMgsUpdates, blueprint_zone_type, }; -use nexus_types::external_api::views::SledState; +use nexus_types::external_api::sled::SledState; use omicron_common::address::{ DENDRITE_PORT, DNS_HTTP_PORT, DNS_PORT, Ipv6Subnet, MGD_PORT, MGS_PORT, NEXUS_INTERNAL_PORT, NEXUS_LOCKSTEP_PORT, NTP_PORT, NUM_SOURCE_NAT_PORTS,