diff --git a/common/src/api/external/mod.rs b/common/src/api/external/mod.rs index 42dd4594224..5c4799fd926 100644 --- a/common/src/api/external/mod.rs +++ b/common/src/api/external/mod.rs @@ -1557,24 +1557,6 @@ pub struct RouterRoute { pub destination: RouteDestination, } -/// Create-time parameters for a [`RouterRoute`] -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct RouterRouteCreateParams { - #[serde(flatten)] - pub identity: IdentityMetadataCreateParams, - pub target: RouteTarget, - pub destination: RouteDestination, -} - -/// Updateable properties of a [`RouterRoute`] -#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] -pub struct RouterRouteUpdateParams { - #[serde(flatten)] - pub identity: IdentityMetadataUpdateParams, - pub target: RouteTarget, - pub destination: RouteDestination, -} - /// A single rule in a VPC firewall #[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)] pub struct VpcFirewallRule { diff --git a/nexus/db-model/src/vpc_route.rs b/nexus/db-model/src/vpc_route.rs index c8316bef14b..39c918eeecb 100644 --- a/nexus/db-model/src/vpc_route.rs +++ b/nexus/db-model/src/vpc_route.rs @@ -11,6 +11,7 @@ use diesel::deserialize::{self, FromSql}; use diesel::pg::Pg; use diesel::serialize::{self, ToSql}; use diesel::sql_types; +use nexus_types::external_api::params; use nexus_types::identity::Resource; use omicron_common::api::external; use std::io::Write; @@ -115,7 +116,7 @@ impl RouterRoute { route_id: Uuid, vpc_router_id: Uuid, kind: external::RouterRouteKind, - params: external::RouterRouteCreateParams, + params: params::RouterRouteCreate, ) -> Self { let identity = RouterRouteIdentity::new(route_id, params.identity); Self { @@ -150,8 +151,8 @@ pub struct RouterRouteUpdate { pub destination: RouteDestination, } -impl From for RouterRouteUpdate { - fn from(params: external::RouterRouteUpdateParams) -> Self { +impl From for RouterRouteUpdate { + fn from(params: params::RouterRouteUpdate) -> Self { Self { name: params.identity.name.map(Name), description: params.identity.description, diff --git a/nexus/src/app/instance.rs b/nexus/src/app/instance.rs index 8bc2d1f4d07..4338c0d1ddc 100644 --- a/nexus/src/app/instance.rs +++ b/nexus/src/app/instance.rs @@ -10,14 +10,12 @@ use super::MAX_NICS_PER_INSTANCE; use crate::app::sagas; use crate::authn; use crate::authz; -use crate::authz::ApiResource; use crate::cidata::InstanceCiData; use crate::context::OpContext; use crate::db; use crate::db::identity::Resource; use crate::db::lookup; use crate::db::lookup::LookupPath; -use crate::db::queries::network_interface; use crate::external_api::params; use futures::future::Fuse; use futures::{FutureExt, SinkExt, StreamExt}; @@ -799,220 +797,6 @@ impl super::Nexus { Ok(disk) } - /// Create a network interface attached to the provided instance. - // TODO-performance: Add a version of this that accepts the instance ID - // directly. This will avoid all the internal database lookups in the event - // that we create many NICs for the same instance, such as in a saga. - pub async fn instance_create_network_interface( - &self, - opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, - params: ¶ms::NetworkInterfaceCreate, - ) -> CreateResult { - let (.., authz_project, authz_instance) = - LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .instance_name(instance_name) - .lookup_for(authz::Action::Modify) - .await?; - - // NOTE: We need to lookup the VPC and VPC Subnet, since we need both - // IDs for creating the network interface. - // - // TODO-correctness: There are additional races here. The VPC and VPC - // Subnet could both be deleted between the time we fetch them and - // actually insert the record for the interface. The solution is likely - // to make both objects implement `DatastoreCollection` for their - // children, and then use `VpcSubnet::insert_resource` inside the - // `instance_create_network_interface` method. See - // https://github.com/oxidecomputer/omicron/issues/738. - let vpc_name = db::model::Name(params.vpc_name.clone()); - let subnet_name = db::model::Name(params.subnet_name.clone()); - let (.., authz_vpc, authz_subnet, db_subnet) = - LookupPath::new(opctx, &self.db_datastore) - .project_id(authz_project.id()) - .vpc_name(&vpc_name) - .vpc_subnet_name(&subnet_name) - .fetch() - .await?; - let interface_id = Uuid::new_v4(); - let interface = db::model::IncompleteNetworkInterface::new( - interface_id, - authz_instance.id(), - authz_vpc.id(), - db_subnet, - params.identity.clone(), - params.ip, - )?; - self.db_datastore - .instance_create_network_interface( - opctx, - &authz_subnet, - &authz_instance, - interface, - ) - .await - .map_err(|e| { - debug!( - self.log, - "failed to create network interface"; - "instance_id" => ?authz_instance.id(), - "interface_id" => ?interface_id, - "error" => ?e, - ); - if matches!( - e, - network_interface::InsertError::InstanceNotFound(_) - ) { - // Return the not-found message of the authz interface - // object, so that the message reflects how the caller - // originally looked it up - authz_instance.not_found() - } else { - // Convert other errors into an appropriate client error - network_interface::InsertError::into_external(e) - } - }) - } - - /// Lists network interfaces attached to the instance. - pub async fn instance_list_network_interfaces( - &self, - opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, - pagparams: &DataPageParams<'_, Name>, - ) -> ListResultVec { - let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .instance_name(instance_name) - .lookup_for(authz::Action::ListChildren) - .await?; - self.db_datastore - .instance_list_network_interfaces(opctx, &authz_instance, pagparams) - .await - } - - /// Fetch a network interface attached to the given instance. - pub async fn network_interface_fetch( - &self, - opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, - interface_name: &Name, - ) -> LookupResult { - let (.., db_interface) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .instance_name(instance_name) - .network_interface_name(interface_name) - .fetch() - .await?; - Ok(db_interface) - } - - pub async fn network_interface_fetch_by_id( - &self, - opctx: &OpContext, - interface_id: &Uuid, - ) -> LookupResult { - let (.., db_interface) = LookupPath::new(opctx, &self.db_datastore) - .network_interface_id(*interface_id) - .fetch() - .await?; - Ok(db_interface) - } - - /// Update a network interface for the given instance. - pub async fn network_interface_update( - &self, - opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, - interface_name: &Name, - updates: params::NetworkInterfaceUpdate, - ) -> UpdateResult { - let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .instance_name(instance_name) - .lookup_for(authz::Action::Modify) - .await?; - let (.., authz_interface) = LookupPath::new(opctx, &self.db_datastore) - .instance_id(authz_instance.id()) - .network_interface_name(interface_name) - .lookup_for(authz::Action::Modify) - .await?; - self.db_datastore - .instance_update_network_interface( - opctx, - &authz_instance, - &authz_interface, - db::model::NetworkInterfaceUpdate::from(updates), - ) - .await - } - - /// Delete a network interface from the provided instance. - /// - /// Note that the primary interface for an instance cannot be deleted if - /// there are any secondary interfaces. - pub async fn instance_delete_network_interface( - &self, - opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - instance_name: &Name, - interface_name: &Name, - ) -> DeleteResult { - let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .instance_name(instance_name) - .lookup_for(authz::Action::Modify) - .await?; - let (.., authz_interface) = LookupPath::new(opctx, &self.db_datastore) - .instance_id(authz_instance.id()) - .network_interface_name(interface_name) - .lookup_for(authz::Action::Delete) - .await?; - self.db_datastore - .instance_delete_network_interface( - opctx, - &authz_instance, - &authz_interface, - ) - .await - .map_err(|e| { - debug!( - self.log, - "failed to delete network interface"; - "instance_id" => ?authz_instance.id(), - "interface_id" => ?authz_interface.id(), - "error" => ?e, - ); - if matches!( - e, - network_interface::DeleteError::InstanceNotFound(_) - ) { - // Return the not-found message of the authz interface - // object, so that the message reflects how the caller - // originally looked it up - authz_instance.not_found() - } else { - // Convert other errors into an appropriate client error - network_interface::DeleteError::into_external(e) - } - }) - } - /// Invoked by a sled agent to publish an updated runtime state for an /// Instance. pub async fn notify_instance_updated( diff --git a/nexus/src/app/mod.rs b/nexus/src/app/mod.rs index fa814f70e32..29af787dab4 100644 --- a/nexus/src/app/mod.rs +++ b/nexus/src/app/mod.rs @@ -34,6 +34,7 @@ mod image; mod instance; mod ip_pool; mod metrics; +mod network_interface; mod organization; mod oximeter; mod project; diff --git a/nexus/src/app/network_interface.rs b/nexus/src/app/network_interface.rs new file mode 100644 index 00000000000..d023e60e20d --- /dev/null +++ b/nexus/src/app/network_interface.rs @@ -0,0 +1,210 @@ +use crate::authz::ApiResource; +use crate::db::queries::network_interface; +use nexus_db_model::Name; +use nexus_types::external_api::params; +use omicron_common::api::external::CreateResult; +use omicron_common::api::external::DeleteResult; +use omicron_common::api::external::Error; +use omicron_common::api::external::ListResultVec; +use omicron_common::api::external::LookupResult; +use omicron_common::api::external::NameOrId; + +use omicron_common::api::external::http_pagination::PaginatedBy; +use omicron_common::api::external::UpdateResult; +use ref_cast::RefCast; +use uuid::Uuid; + +use crate::authz; +use crate::db; +use crate::{ + context::OpContext, + db::lookup::{self, LookupPath}, +}; + +impl super::Nexus { + pub fn network_interface_lookup<'a>( + &'a self, + opctx: &'a OpContext, + network_interface_selector: &'a params::NetworkInterfaceSelector, + ) -> LookupResult> { + match network_interface_selector { + params::NetworkInterfaceSelector { + instance_selector: None, + network_interface: NameOrId::Id(id), + } => { + let network_interface = + LookupPath::new(opctx, &self.db_datastore) + .network_interface_id(*id); + Ok(network_interface) + } + params::NetworkInterfaceSelector { + instance_selector: Some(instance_selector), + network_interface: NameOrId::Name(name), + } => { + let network_interface = self + .instance_lookup(opctx, instance_selector)? + .network_interface_name(Name::ref_cast(name)); + Ok(network_interface) + } + params::NetworkInterfaceSelector { + instance_selector: Some(_), + network_interface: NameOrId::Id(_), + } => { + Err(Error::invalid_request( + "when providing network_interface as an id, instance_selector should not be specified" + )) + } + _ => { + Err(Error::invalid_request( + "network_interface should either be a UUID or instance should be specified" + )) + } + } + } + + /// Create a network interface attached to the provided instance. + // TODO-performance: Add a version of this that accepts the instance ID + // directly. This will avoid all the internal database lookups in the event + // that we create many NICs for the same instance, such as in a saga. + pub async fn network_interface_create( + &self, + opctx: &OpContext, + instance_lookup: &lookup::Instance<'_>, + params: ¶ms::NetworkInterfaceCreate, + ) -> CreateResult { + let (.., authz_project, authz_instance) = + instance_lookup.lookup_for(authz::Action::Modify).await?; + + // NOTE: We need to lookup the VPC and VPC Subnet, since we need both + // IDs for creating the network interface. + // + // TODO-correctness: There are additional races here. The VPC and VPC + // Subnet could both be deleted between the time we fetch them and + // actually insert the record for the interface. The solution is likely + // to make both objects implement `DatastoreCollection` for their + // children, and then use `VpcSubnet::insert_resource` inside the + // `instance_create_network_interface` method. See + // https://github.com/oxidecomputer/omicron/issues/738. + let vpc_name = db::model::Name(params.vpc_name.clone()); + let subnet_name = db::model::Name(params.subnet_name.clone()); + let (.., authz_vpc, authz_subnet, db_subnet) = + LookupPath::new(opctx, &self.db_datastore) + .project_id(authz_project.id()) + .vpc_name(&vpc_name) + .vpc_subnet_name(&subnet_name) + .fetch() + .await?; + let interface_id = Uuid::new_v4(); + let interface = db::model::IncompleteNetworkInterface::new( + interface_id, + authz_instance.id(), + authz_vpc.id(), + db_subnet, + params.identity.clone(), + params.ip, + )?; + self.db_datastore + .instance_create_network_interface( + opctx, + &authz_subnet, + &authz_instance, + interface, + ) + .await + .map_err(|e| { + debug!( + self.log, + "failed to create network interface"; + "instance_id" => ?authz_instance.id(), + "interface_id" => ?interface_id, + "error" => ?e, + ); + if matches!( + e, + network_interface::InsertError::InstanceNotFound(_) + ) { + // Return the not-found message of the authz interface + // object, so that the message reflects how the caller + // originally looked it up + authz_instance.not_found() + } else { + // Convert other errors into an appropriate client error + network_interface::InsertError::into_external(e) + } + }) + } + + /// Lists network interfaces attached to the instance. + pub async fn network_interface_list( + &self, + opctx: &OpContext, + instance_lookup: &lookup::Instance<'_>, + pagparams: &PaginatedBy<'_>, + ) -> ListResultVec { + let (.., authz_instance) = + instance_lookup.lookup_for(authz::Action::ListChildren).await?; + self.db_datastore + .instance_list_network_interfaces(opctx, &authz_instance, pagparams) + .await + } + + /// Update a network interface for the given instance. + pub async fn network_interface_update( + &self, + opctx: &OpContext, + network_interface_lookup: &lookup::NetworkInterface<'_>, + updates: params::NetworkInterfaceUpdate, + ) -> UpdateResult { + let (.., authz_instance, authz_interface) = + network_interface_lookup.lookup_for(authz::Action::Modify).await?; + self.db_datastore + .instance_update_network_interface( + opctx, + &authz_instance, + &authz_interface, + db::model::NetworkInterfaceUpdate::from(updates), + ) + .await + } + + /// Delete a network interface from the provided instance. + /// + /// Note that the primary interface for an instance cannot be deleted if + /// there are any secondary interfaces. + pub async fn network_interface_delete( + &self, + opctx: &OpContext, + network_interface_lookup: &lookup::NetworkInterface<'_>, + ) -> DeleteResult { + let (.., authz_instance, authz_interface) = + network_interface_lookup.lookup_for(authz::Action::Delete).await?; + self.db_datastore + .instance_delete_network_interface( + opctx, + &authz_instance, + &authz_interface, + ) + .await + .map_err(|e| { + debug!( + self.log, + "failed to delete network interface"; + "instance_id" => ?authz_instance.id(), + "interface_id" => ?authz_interface.id(), + "error" => ?e, + ); + if matches!( + e, + network_interface::DeleteError::InstanceNotFound(_) + ) { + // Return the not-found message of the authz interface + // object, so that the message reflects how the caller + // originally looked it up + authz_instance.not_found() + } else { + // Convert other errors into an appropriate client error + network_interface::DeleteError::into_external(e) + } + }) + } +} diff --git a/nexus/src/app/sagas/vpc_create.rs b/nexus/src/app/sagas/vpc_create.rs index a816b8c0b87..44bbaee309d 100644 --- a/nexus/src/app/sagas/vpc_create.rs +++ b/nexus/src/app/sagas/vpc_create.rs @@ -17,7 +17,6 @@ use omicron_common::api::external::IdentityMetadataCreateParams; use omicron_common::api::external::LookupType; use omicron_common::api::external::RouteDestination; use omicron_common::api::external::RouteTarget; -use omicron_common::api::external::RouterRouteCreateParams; use omicron_common::api::external::RouterRouteKind; use serde::Deserialize; use serde::Serialize; @@ -221,7 +220,7 @@ async fn svc_create_route( default_route_id, system_router_id, RouterRouteKind::Default, - RouterRouteCreateParams { + params::RouterRouteCreate { identity: IdentityMetadataCreateParams { name: "default".parse().unwrap(), description: "The default route of a vpc".to_string(), diff --git a/nexus/src/app/vpc.rs b/nexus/src/app/vpc.rs index a739a624e6e..3fc64efc5c7 100644 --- a/nexus/src/app/vpc.rs +++ b/nexus/src/app/vpc.rs @@ -17,16 +17,18 @@ use crate::db::model::Name; use crate::external_api::params; use nexus_defaults as defaults; use omicron_common::api::external; +use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::CreateResult; -use omicron_common::api::external::DataPageParams; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; use omicron_common::api::external::InternalContext; use omicron_common::api::external::ListResultVec; use omicron_common::api::external::LookupResult; use omicron_common::api::external::LookupType; +use omicron_common::api::external::NameOrId; use omicron_common::api::external::UpdateResult; use omicron_common::api::external::VpcFirewallRuleUpdateParams; +use ref_cast::RefCast; use sled_agent_client::types::IpNet; use sled_agent_client::types::NetworkInterface; @@ -39,6 +41,42 @@ use uuid::Uuid; impl super::Nexus { // VPCs + + pub fn vpc_lookup<'a>( + &'a self, + opctx: &'a OpContext, + vpc_selector: &'a params::VpcSelector, + ) -> LookupResult> { + match vpc_selector { + params::VpcSelector { + vpc: NameOrId::Id(id), + project_selector: None, + } => { + let vpc = + LookupPath::new(opctx, &self.db_datastore).vpc_id(*id); + Ok(vpc) + } + params::VpcSelector { + vpc: NameOrId::Name(name), + project_selector: Some(selector), + } => { + let vpc = self + .project_lookup(opctx, selector)? + .vpc_name(Name::ref_cast(name)); + Ok(vpc) + } + params::VpcSelector { + vpc: NameOrId::Id(_), + project_selector: Some(_), + } => Err(Error::invalid_request( + "when providing vpc as an ID, project should not be specified", + )), + _ => Err(Error::invalid_request( + "vpc should either be an ID or project should be specified", + )), + } + } + pub async fn project_create_vpc( self: &Arc, opctx: &OpContext, @@ -68,65 +106,25 @@ impl super::Nexus { Ok(db_vpc) } - pub async fn project_list_vpcs( + pub async fn vpc_list( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - pagparams: &DataPageParams<'_, Name>, + project_lookup: &lookup::Project<'_>, + pagparams: &PaginatedBy<'_>, ) -> ListResultVec { - let (.., authz_project) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .lookup_for(authz::Action::ListChildren) - .await?; - self.db_datastore - .project_list_vpcs(&opctx, &authz_project, pagparams) - .await - } - - pub async fn vpc_fetch( - &self, - opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - ) -> LookupResult { - let (.., db_vpc) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .fetch() - .await?; - Ok(db_vpc) - } - - pub async fn vpc_fetch_by_id( - &self, - opctx: &OpContext, - vpc_id: &Uuid, - ) -> LookupResult { - let (.., db_vpc) = LookupPath::new(opctx, &self.db_datastore) - .vpc_id(*vpc_id) - .fetch() - .await?; - Ok(db_vpc) + let (.., authz_project) = + project_lookup.lookup_for(authz::Action::ListChildren).await?; + self.db_datastore.vpc_list(&opctx, &authz_project, pagparams).await } pub async fn project_update_vpc( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, + vpc_lookup: &lookup::Vpc<'_>, params: ¶ms::VpcUpdate, ) -> UpdateResult { - let (.., authz_vpc) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .lookup_for(authz::Action::Modify) - .await?; + let (.., authz_vpc) = + vpc_lookup.lookup_for(authz::Action::Modify).await?; self.db_datastore .project_update_vpc(opctx, &authz_vpc, params.clone().into()) .await @@ -135,17 +133,9 @@ impl super::Nexus { pub async fn project_delete_vpc( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, + vpc_lookup: &lookup::Vpc<'_>, ) -> DeleteResult { - let (.., authz_vpc, db_vpc) = - LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .fetch() - .await?; + let (.., authz_vpc, db_vpc) = vpc_lookup.fetch().await?; let authz_vpc_router = authz::VpcRouter::new( authz_vpc.clone(), diff --git a/nexus/src/app/vpc_router.rs b/nexus/src/app/vpc_router.rs index 9af86c6ed24..f6a8f36d320 100644 --- a/nexus/src/app/vpc_router.rs +++ b/nexus/src/app/vpc_router.rs @@ -7,42 +7,71 @@ use crate::authz; use crate::context::OpContext; use crate::db; +use crate::db::lookup; use crate::db::lookup::LookupPath; use crate::db::model::Name; use crate::db::model::RouterRoute; use crate::db::model::VpcRouter; use crate::db::model::VpcRouterKind; use crate::external_api::params; +use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::CreateResult; -use omicron_common::api::external::DataPageParams; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; use omicron_common::api::external::ListResultVec; use omicron_common::api::external::LookupResult; -use omicron_common::api::external::RouterRouteCreateParams; +use omicron_common::api::external::NameOrId; use omicron_common::api::external::RouterRouteKind; -use omicron_common::api::external::RouterRouteUpdateParams; use omicron_common::api::external::UpdateResult; +use ref_cast::RefCast; use uuid::Uuid; impl super::Nexus { // Routers + pub fn vpc_router_lookup<'a>( + &'a self, + opctx: &'a OpContext, + router_selector: &'a params::RouterSelector, + ) -> LookupResult> { + match router_selector { + params::RouterSelector { + router: NameOrId::Id(id), + vpc_selector: None, + } => { + let router = LookupPath::new(opctx, &self.db_datastore) + .vpc_router_id(*id); + Ok(router) + } + params::RouterSelector { + router: NameOrId::Name(name), + vpc_selector: Some(vpc_selector), + } => { + let router = self + .vpc_lookup(opctx, vpc_selector)? + .vpc_router_name(Name::ref_cast(name)); + Ok(router) + } + params::RouterSelector { + router: NameOrId::Id(_), + vpc_selector: Some(_), + } => Err(Error::invalid_request( + "when providing vpc_router as an ID, vpc should not be specified", + )), + _ => Err(Error::invalid_request( + "vpc_router should either be an ID or vpc should be specified", + )), + } + } pub async fn vpc_create_router( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, + vpc_lookup: &lookup::Vpc<'_>, kind: &VpcRouterKind, params: ¶ms::VpcRouterCreate, ) -> CreateResult { - let (.., authz_vpc) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .lookup_for(authz::Action::CreateChild) - .await?; + let (.., authz_vpc) = + vpc_lookup.lookup_for(authz::Action::CreateChild).await?; let id = Uuid::new_v4(); let router = db::model::VpcRouter::new( id, @@ -57,73 +86,29 @@ impl super::Nexus { Ok(router) } - pub async fn vpc_list_routers( + pub async fn vpc_router_list( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - pagparams: &DataPageParams<'_, Name>, + vpc_lookup: &lookup::Vpc<'_>, + pagparams: &PaginatedBy<'_>, ) -> ListResultVec { - let (.., authz_vpc) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .lookup_for(authz::Action::ListChildren) - .await?; + let (.., authz_vpc) = + vpc_lookup.lookup_for(authz::Action::ListChildren).await?; let routers = self .db_datastore - .vpc_list_routers(opctx, &authz_vpc, pagparams) + .vpc_router_list(opctx, &authz_vpc, pagparams) .await?; Ok(routers) } - pub async fn vpc_router_fetch( - &self, - opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - router_name: &Name, - ) -> LookupResult { - let (.., db_router) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .vpc_router_name(router_name) - .fetch() - .await?; - Ok(db_router) - } - - pub async fn vpc_router_fetch_by_id( - &self, - opctx: &OpContext, - vpc_router_id: &Uuid, - ) -> LookupResult { - let (.., db_router) = LookupPath::new(opctx, &self.db_datastore) - .vpc_router_id(*vpc_router_id) - .fetch() - .await?; - Ok(db_router) - } - pub async fn vpc_update_router( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - router_name: &Name, + vpc_router_lookup: &lookup::VpcRouter<'_>, params: ¶ms::VpcRouterUpdate, ) -> UpdateResult { - let (.., authz_router) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .vpc_router_name(router_name) - .lookup_for(authz::Action::Modify) - .await?; + let (.., authz_router) = + vpc_router_lookup.lookup_for(authz::Action::Modify).await?; self.db_datastore .vpc_update_router(opctx, &authz_router, params.clone().into()) .await @@ -135,19 +120,10 @@ impl super::Nexus { pub async fn vpc_delete_router( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - router_name: &Name, + vpc_router_lookup: &lookup::VpcRouter<'_>, ) -> DeleteResult { let (.., authz_router, db_router) = - LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .vpc_router_name(router_name) - .fetch() - .await?; + vpc_router_lookup.fetch_for(authz::Action::Delete).await?; // TODO-performance shouldn't this check be part of the "update" // database query? This shouldn't affect correctness, assuming that a // router kind cannot be changed, but it might be able to save us a @@ -162,24 +138,50 @@ impl super::Nexus { // Routes - #[allow(clippy::too_many_arguments)] + pub fn vpc_router_route_lookup<'a>( + &'a self, + opctx: &'a OpContext, + route_selector: &'a params::RouteSelector, + ) -> LookupResult> { + match route_selector { + params::RouteSelector { + route: NameOrId::Id(id), + router_selector: None, + } => { + let route = LookupPath::new(opctx, &self.db_datastore) + .router_route_id(*id); + Ok(route) + } + params::RouteSelector { + route: NameOrId::Name(name), + router_selector: Some(selector), + } => { + let route = self + .vpc_router_lookup(opctx, selector)? + .router_route_name(Name::ref_cast(name)); + Ok(route) + } + params::RouteSelector { + route: NameOrId::Id(_), + router_selector: Some(_), + } => Err(Error::invalid_request( + "when providing subnet as an ID, vpc should not be specified", + )), + _ => Err(Error::invalid_request( + "router should either be an ID or vpc should be specified", + )), + } + } + pub async fn router_create_route( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - router_name: &Name, + router_lookup: &lookup::VpcRouter<'_>, kind: &RouterRouteKind, - params: &RouterRouteCreateParams, + params: ¶ms::RouterRouteCreate, ) -> CreateResult { - let (.., authz_router) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .vpc_router_name(router_name) - .lookup_for(authz::Action::CreateChild) - .await?; + let (.., authz_router) = + router_lookup.lookup_for(authz::Action::CreateChild).await?; let id = Uuid::new_v4(); let route = db::model::RouterRoute::new( id, @@ -194,79 +196,27 @@ impl super::Nexus { Ok(route) } - pub async fn router_list_routes( + pub async fn vpc_router_route_list( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - router_name: &Name, - pagparams: &DataPageParams<'_, Name>, + vpc_router_lookup: &lookup::VpcRouter<'_>, + pagparams: &PaginatedBy<'_>, ) -> ListResultVec { - let (.., authz_router) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .vpc_router_name(router_name) - .lookup_for(authz::Action::ListChildren) - .await?; + let (.., authz_router) = + vpc_router_lookup.lookup_for(authz::Action::ListChildren).await?; self.db_datastore - .router_list_routes(opctx, &authz_router, pagparams) + .vpc_router_route_list(opctx, &authz_router, pagparams) .await } - pub async fn route_fetch( - &self, - opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - router_name: &Name, - route_name: &Name, - ) -> LookupResult { - let (.., db_route) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .vpc_router_name(router_name) - .router_route_name(route_name) - .fetch() - .await?; - Ok(db_route) - } - - pub async fn route_fetch_by_id( - &self, - opctx: &OpContext, - route_id: &Uuid, - ) -> LookupResult { - let (.., db_route) = LookupPath::new(opctx, &self.db_datastore) - .router_route_id(*route_id) - .fetch() - .await?; - Ok(db_route) - } - - #[allow(clippy::too_many_arguments)] pub async fn router_update_route( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - router_name: &Name, - route_name: &Name, - params: &RouterRouteUpdateParams, + route_lookup: &lookup::RouterRoute<'_>, + params: ¶ms::RouterRouteUpdate, ) -> UpdateResult { - let (.., authz_route, db_route) = - LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .vpc_router_name(router_name) - .router_route_name(route_name) - .fetch() - .await?; + let (.., vpc, _, authz_route, db_route) = + route_lookup.fetch_for(authz::Action::Modify).await?; // TODO: Write a test for this once there's a way to test it (i.e. // subnets automatically register to the system router table) match db_route.kind.0 { @@ -276,7 +226,8 @@ impl super::Nexus { internal_message: format!( "routes of type {} from the system table of VPC {:?} \ are not modifiable", - db_route.kind.0, vpc_name + db_route.kind.0, + vpc.id() ), }) } @@ -289,21 +240,10 @@ impl super::Nexus { pub async fn router_delete_route( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - router_name: &Name, - route_name: &Name, + route_lookup: &lookup::RouterRoute<'_>, ) -> DeleteResult { let (.., authz_route, db_route) = - LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .vpc_router_name(router_name) - .router_route_name(route_name) - .fetch() - .await?; + route_lookup.fetch_for(authz::Action::Delete).await?; // Only custom routes can be deleted // TODO Shouldn't this constraint be checked by the database query? diff --git a/nexus/src/app/vpc_subnet.rs b/nexus/src/app/vpc_subnet.rs index 7e5d9840de3..0f5419580e4 100644 --- a/nexus/src/app/vpc_subnet.rs +++ b/nexus/src/app/vpc_subnet.rs @@ -8,39 +8,70 @@ use crate::authz; use crate::context::OpContext; use crate::db; use crate::db::identity::Resource; +use crate::db::lookup; use crate::db::lookup::LookupPath; use crate::db::model::Name; use crate::db::model::VpcSubnet; use crate::db::queries::vpc_subnet::SubnetError; use crate::external_api::params; use omicron_common::api::external; +use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::DeleteResult; +use omicron_common::api::external::Error; use omicron_common::api::external::ListResultVec; use omicron_common::api::external::LookupResult; +use omicron_common::api::external::NameOrId; use omicron_common::api::external::UpdateResult; use omicron_common::nexus_config::MIN_VPC_IPV4_SUBNET_PREFIX; +use ref_cast::RefCast; use uuid::Uuid; impl super::Nexus { + pub fn vpc_subnet_lookup<'a>( + &'a self, + opctx: &'a OpContext, + subnet_selector: &'a params::SubnetSelector, + ) -> LookupResult> { + match subnet_selector { + params::SubnetSelector { + subnet: NameOrId::Id(id), + vpc_selector: None, + } => { + let subnet = LookupPath::new(opctx, &self.db_datastore) + .vpc_subnet_id(*id); + Ok(subnet) + } + params::SubnetSelector { + subnet: NameOrId::Name(name), + vpc_selector: Some(selector), + } => { + let subnet = self + .vpc_lookup(opctx, selector)? + .vpc_subnet_name(Name::ref_cast(name)); + Ok(subnet) + } + params::SubnetSelector { + subnet: NameOrId::Id(_), + vpc_selector: Some(_), + } => Err(Error::invalid_request( + "when providing subnet as an ID, vpc should not be specified", + )), + _ => Err(Error::invalid_request( + "subnet should either be an ID or vpc should be specified", + )), + } + } // TODO: When a subnet is created it should add a route entry into the VPC's // system router pub async fn vpc_create_subnet( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, + vpc_lookup: &lookup::Vpc<'_>, params: ¶ms::VpcSubnetCreate, ) -> CreateResult { - let (.., authz_vpc, db_vpc) = - LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .fetch() - .await?; + let (.., authz_vpc, db_vpc) = vpc_lookup.fetch().await?; // Validate IPv4 range if !params.ipv4_block.network().is_private() { @@ -184,69 +215,25 @@ impl super::Nexus { } } - pub async fn vpc_list_subnets( + pub async fn vpc_subnet_list( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - pagparams: &DataPageParams<'_, Name>, + vpc_lookup: &lookup::Vpc<'_>, + pagparams: &PaginatedBy<'_>, ) -> ListResultVec { - let (.., authz_vpc) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .lookup_for(authz::Action::ListChildren) - .await?; - self.db_datastore.vpc_list_subnets(opctx, &authz_vpc, pagparams).await - } - - pub async fn vpc_subnet_fetch( - &self, - opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - subnet_name: &Name, - ) -> LookupResult { - let (.., db_vpc) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .vpc_subnet_name(subnet_name) - .fetch() - .await?; - Ok(db_vpc) - } - - pub async fn vpc_subnet_fetch_by_id( - &self, - opctx: &OpContext, - vpc_subnet_id: &Uuid, - ) -> LookupResult { - let (.., db_vpc) = LookupPath::new(opctx, &self.db_datastore) - .vpc_subnet_id(*vpc_subnet_id) - .fetch() - .await?; - Ok(db_vpc) + let (.., authz_vpc) = + vpc_lookup.lookup_for(authz::Action::ListChildren).await?; + self.db_datastore.vpc_subnet_list(opctx, &authz_vpc, pagparams).await } pub async fn vpc_update_subnet( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - subnet_name: &Name, + vpc_subnet_lookup: &lookup::VpcSubnet<'_>, params: ¶ms::VpcSubnetUpdate, ) -> UpdateResult { - let (.., authz_subnet) = LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .vpc_subnet_name(subnet_name) - .lookup_for(authz::Action::Modify) - .await?; + let (.., authz_subnet) = + vpc_subnet_lookup.lookup_for(authz::Action::Modify).await?; self.db_datastore .vpc_update_subnet(&opctx, &authz_subnet, params.clone().into()) .await @@ -257,19 +244,10 @@ impl super::Nexus { pub async fn vpc_delete_subnet( &self, opctx: &OpContext, - organization_name: &Name, - project_name: &Name, - vpc_name: &Name, - subnet_name: &Name, + vpc_subnet_lookup: &lookup::VpcSubnet<'_>, ) -> DeleteResult { let (.., authz_subnet, db_subnet) = - LookupPath::new(opctx, &self.db_datastore) - .organization_name(organization_name) - .project_name(project_name) - .vpc_name(vpc_name) - .vpc_subnet_name(subnet_name) - .fetch_for(authz::Action::Delete) - .await?; + vpc_subnet_lookup.fetch_for(authz::Action::Delete).await?; self.db_datastore .vpc_delete_subnet(opctx, &db_subnet, &authz_subnet) .await diff --git a/nexus/src/db/datastore/network_interface.rs b/nexus/src/db/datastore/network_interface.rs index c49ea2dc7da..a1ca240c069 100644 --- a/nexus/src/db/datastore/network_interface.rs +++ b/nexus/src/db/datastore/network_interface.rs @@ -27,13 +27,14 @@ use async_bb8_diesel::AsyncRunQueryDsl; use chrono::Utc; use diesel::prelude::*; use omicron_common::api::external; -use omicron_common::api::external::DataPageParams; +use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::DeleteResult; use omicron_common::api::external::Error; use omicron_common::api::external::ListResultVec; use omicron_common::api::external::LookupType; use omicron_common::api::external::ResourceType; use omicron_common::api::external::UpdateResult; +use ref_cast::RefCast; use sled_agent_client::types as sled_client_types; /// OPTE requires information that's currently split across the network @@ -286,18 +287,27 @@ impl DataStore { &self, opctx: &OpContext, authz_instance: &authz::Instance, - pagparams: &DataPageParams<'_, Name>, + pagparams: &PaginatedBy<'_>, ) -> ListResultVec { opctx.authorize(authz::Action::ListChildren, authz_instance).await?; use db::schema::network_interface::dsl; - paginated(dsl::network_interface, dsl::name, &pagparams) - .filter(dsl::time_deleted.is_null()) - .filter(dsl::instance_id.eq(authz_instance.id())) - .select(NetworkInterface::as_select()) - .load_async::(self.pool_authorized(opctx).await?) - .await - .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) + match pagparams { + PaginatedBy::Id(pagparams) => { + paginated(dsl::network_interface, dsl::id, &pagparams) + } + PaginatedBy::Name(pagparams) => paginated( + dsl::network_interface, + dsl::name, + &pagparams.map_name(|n| Name::ref_cast(n)), + ), + } + .filter(dsl::time_deleted.is_null()) + .filter(dsl::instance_id.eq(authz_instance.id())) + .select(NetworkInterface::as_select()) + .load_async::(self.pool_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) } /// Update a network interface associated with a given instance. diff --git a/nexus/src/db/datastore/vpc.rs b/nexus/src/db/datastore/vpc.rs index ba951d7730d..5f70faa8be1 100644 --- a/nexus/src/db/datastore/vpc.rs +++ b/nexus/src/db/datastore/vpc.rs @@ -40,6 +40,7 @@ use async_bb8_diesel::AsyncRunQueryDsl; use chrono::Utc; use diesel::prelude::*; use ipnetwork::IpNetwork; +use omicron_common::api::external::http_pagination::PaginatedBy; use omicron_common::api::external::CreateResult; use omicron_common::api::external::DataPageParams; use omicron_common::api::external::DeleteResult; @@ -49,26 +50,36 @@ use omicron_common::api::external::LookupResult; use omicron_common::api::external::LookupType; use omicron_common::api::external::ResourceType; use omicron_common::api::external::UpdateResult; +use ref_cast::RefCast; use std::collections::BTreeMap; use uuid::Uuid; impl DataStore { - pub async fn project_list_vpcs( + pub async fn vpc_list( &self, opctx: &OpContext, authz_project: &authz::Project, - pagparams: &DataPageParams<'_, Name>, + pagparams: &PaginatedBy<'_>, ) -> ListResultVec { opctx.authorize(authz::Action::ListChildren, authz_project).await?; use db::schema::vpc::dsl; - paginated(dsl::vpc, dsl::name, &pagparams) - .filter(dsl::time_deleted.is_null()) - .filter(dsl::project_id.eq(authz_project.id())) - .select(Vpc::as_select()) - .load_async(self.pool_authorized(opctx).await?) - .await - .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) + match pagparams { + PaginatedBy::Id(pagparams) => { + paginated(dsl::vpc, dsl::id, &pagparams) + } + PaginatedBy::Name(pagparams) => paginated( + dsl::vpc, + dsl::name, + &pagparams.map_name(|n| Name::ref_cast(n)), + ), + } + .filter(dsl::time_deleted.is_null()) + .filter(dsl::project_id.eq(authz_project.id())) + .select(Vpc::as_select()) + .load_async(self.pool_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) } pub async fn project_create_vpc( @@ -359,22 +370,31 @@ impl DataStore { .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) } - pub async fn vpc_list_subnets( + pub async fn vpc_subnet_list( &self, opctx: &OpContext, authz_vpc: &authz::Vpc, - pagparams: &DataPageParams<'_, Name>, + pagparams: &PaginatedBy<'_>, ) -> ListResultVec { opctx.authorize(authz::Action::ListChildren, authz_vpc).await?; use db::schema::vpc_subnet::dsl; - paginated(dsl::vpc_subnet, dsl::name, &pagparams) - .filter(dsl::time_deleted.is_null()) - .filter(dsl::vpc_id.eq(authz_vpc.id())) - .select(VpcSubnet::as_select()) - .load_async(self.pool_authorized(opctx).await?) - .await - .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) + match pagparams { + PaginatedBy::Id(pagparams) => { + paginated(dsl::vpc_subnet, dsl::id, &pagparams) + } + PaginatedBy::Name(pagparams) => paginated( + dsl::vpc_subnet, + dsl::name, + &pagparams.map_name(|n| Name::ref_cast(n)), + ), + } + .filter(dsl::time_deleted.is_null()) + .filter(dsl::vpc_id.eq(authz_vpc.id())) + .select(VpcSubnet::as_select()) + .load_async(self.pool_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) } /// Insert a VPC Subnet, checking for unique IP address ranges. @@ -517,24 +537,31 @@ impl DataStore { .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) } - pub async fn vpc_list_routers( + pub async fn vpc_router_list( &self, opctx: &OpContext, authz_vpc: &authz::Vpc, - pagparams: &DataPageParams<'_, Name>, + pagparams: &PaginatedBy<'_>, ) -> ListResultVec { opctx.authorize(authz::Action::ListChildren, authz_vpc).await?; use db::schema::vpc_router::dsl; - paginated(dsl::vpc_router, dsl::name, pagparams) - .filter(dsl::time_deleted.is_null()) - .filter(dsl::vpc_id.eq(authz_vpc.id())) - .select(VpcRouter::as_select()) - .load_async::( - self.pool_authorized(opctx).await?, - ) - .await - .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) + match pagparams { + PaginatedBy::Id(pagparams) => { + paginated(dsl::vpc_router, dsl::id, pagparams) + } + PaginatedBy::Name(pagparams) => paginated( + dsl::vpc_router, + dsl::name, + &pagparams.map_name(|n| Name::ref_cast(n)), + ), + } + .filter(dsl::time_deleted.is_null()) + .filter(dsl::vpc_id.eq(authz_vpc.id())) + .select(VpcRouter::as_select()) + .load_async::(self.pool_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) } pub async fn vpc_create_router( @@ -621,24 +648,33 @@ impl DataStore { }) } - pub async fn router_list_routes( + pub async fn vpc_router_route_list( &self, opctx: &OpContext, authz_router: &authz::VpcRouter, - pagparams: &DataPageParams<'_, Name>, + pagparams: &PaginatedBy<'_>, ) -> ListResultVec { opctx.authorize(authz::Action::ListChildren, authz_router).await?; use db::schema::router_route::dsl; - paginated(dsl::router_route, dsl::name, pagparams) - .filter(dsl::time_deleted.is_null()) - .filter(dsl::vpc_router_id.eq(authz_router.id())) - .select(RouterRoute::as_select()) - .load_async::( - self.pool_authorized(opctx).await?, - ) - .await - .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) + match pagparams { + PaginatedBy::Id(pagparams) => { + paginated(dsl::router_route, dsl::id, pagparams) + } + PaginatedBy::Name(pagparams) => paginated( + dsl::router_route, + dsl::name, + &pagparams.map_name(|n| Name::ref_cast(n)), + ), + } + .filter(dsl::time_deleted.is_null()) + .filter(dsl::vpc_router_id.eq(authz_router.id())) + .select(RouterRoute::as_select()) + .load_async::( + self.pool_authorized(opctx).await?, + ) + .await + .map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server)) } pub async fn router_create_route( diff --git a/nexus/src/external_api/http_entrypoints.rs b/nexus/src/external_api/http_entrypoints.rs index bc9158a2fc6..f1d14099b5f 100644 --- a/nexus/src/external_api/http_entrypoints.rs +++ b/nexus/src/external_api/http_entrypoints.rs @@ -61,9 +61,7 @@ use omicron_common::api::external::InternalContext; use omicron_common::api::external::NameOrId; use omicron_common::api::external::NetworkInterface; use omicron_common::api::external::RouterRoute; -use omicron_common::api::external::RouterRouteCreateParams; use omicron_common::api::external::RouterRouteKind; -use omicron_common::api::external::RouterRouteUpdateParams; use omicron_common::api::external::Saga; use omicron_common::api::external::VpcFirewallRuleUpdateParams; use omicron_common::api::external::VpcFirewallRules; @@ -212,6 +210,12 @@ pub fn external_api() -> NexusApiDescription { api.register(vpc_update)?; api.register(vpc_delete)?; + api.register(vpc_list_v1)?; + api.register(vpc_create_v1)?; + api.register(vpc_view_v1)?; + api.register(vpc_update_v1)?; + api.register(vpc_delete_v1)?; + api.register(vpc_subnet_list)?; api.register(vpc_subnet_view)?; api.register(vpc_subnet_view_by_id)?; @@ -220,6 +224,13 @@ pub fn external_api() -> NexusApiDescription { api.register(vpc_subnet_update)?; api.register(vpc_subnet_list_network_interfaces)?; + api.register(vpc_subnet_list_v1)?; + api.register(vpc_subnet_view_v1)?; + api.register(vpc_subnet_create_v1)?; + api.register(vpc_subnet_delete_v1)?; + api.register(vpc_subnet_update_v1)?; + // api.register(vpc_subnet_list_network_interfaces_v1)?; + api.register(instance_network_interface_create)?; api.register(instance_network_interface_list)?; api.register(instance_network_interface_view)?; @@ -227,6 +238,12 @@ pub fn external_api() -> NexusApiDescription { api.register(instance_network_interface_update)?; api.register(instance_network_interface_delete)?; + api.register(instance_network_interface_create_v1)?; + api.register(instance_network_interface_list_v1)?; + api.register(instance_network_interface_view_v1)?; + api.register(instance_network_interface_update_v1)?; + api.register(instance_network_interface_delete_v1)?; + api.register(instance_external_ip_list)?; api.register(vpc_router_list)?; @@ -236,6 +253,12 @@ pub fn external_api() -> NexusApiDescription { api.register(vpc_router_delete)?; api.register(vpc_router_update)?; + api.register(vpc_router_list_v1)?; + api.register(vpc_router_view_v1)?; + api.register(vpc_router_create_v1)?; + api.register(vpc_router_delete_v1)?; + api.register(vpc_router_update_v1)?; + api.register(vpc_router_route_list)?; api.register(vpc_router_route_view)?; api.register(vpc_router_route_view_by_id)?; @@ -243,6 +266,12 @@ pub fn external_api() -> NexusApiDescription { api.register(vpc_router_route_delete)?; api.register(vpc_router_route_update)?; + api.register(vpc_router_route_list_v1)?; + api.register(vpc_router_route_view_v1)?; + api.register(vpc_router_route_create_v1)?; + api.register(vpc_router_route_delete_v1)?; + api.register(vpc_router_route_update_v1)?; + api.register(vpc_firewall_rules_view)?; api.register(vpc_firewall_rules_update)?; @@ -4175,10 +4204,40 @@ async fn image_delete( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } -/* - * VPCs - */ - +/// List network interfaces +#[endpoint { + method = GET, + path = "/v1/network-interfaces", + tags = ["instances"], +}] +async fn instance_network_interface_list_v1( + rqctx: RequestContext>, + query_params: Query>, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.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 instance_lookup = + nexus.instance_lookup(&opctx, &scan_params.selector)?; + let interfaces = nexus + .network_interface_list(&opctx, &instance_lookup, &paginated_by) + .await? + .into_iter() + .map(|d| d.into()) + .collect(); + Ok(HttpResponseOk(ScanByNameOrId::results_page( + &query, + interfaces, + &marker_for_name_or_id, + )?)) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} /// List network interfaces #[endpoint { method = GET, @@ -4191,22 +4250,23 @@ async fn instance_network_interface_list( path_params: Path, ) -> Result>, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let query = query_params.into_inner(); - let path = path_params.into_inner(); - let organization_name = &path.organization_name; - let project_name = &path.project_name; - let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let path = path_params.into_inner(); + let instance_selector = params::InstanceSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + path.instance_name.into(), + ); + let instance_lookup = + nexus.instance_lookup(&opctx, &instance_selector)?; let interfaces = nexus - .instance_list_network_interfaces( + .network_interface_list( &opctx, - &organization_name, - &project_name, - &instance_name, - &data_page_params_for(&rqctx, &query)? - .map_name(|n| Name::ref_cast(n)), + &instance_lookup, + &PaginatedBy::Name(data_page_params_for(&rqctx, &query)?), ) .await? .into_iter() @@ -4222,10 +4282,41 @@ async fn instance_network_interface_list( } /// Create a network interface +#[endpoint { + method = POST, + path = "/v1/network-interfaces", + tags = ["instances"], +}] +async fn instance_network_interface_create_v1( + rqctx: RequestContext>, + query_params: Query, + interface_params: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let instance_lookup = nexus.instance_lookup(&opctx, &query)?; + let iface = nexus + .network_interface_create( + &opctx, + &instance_lookup, + &interface_params.into_inner(), + ) + .await?; + Ok(HttpResponseCreated(iface.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Create a network interface +/// Use `POST /v1/network-interfaces` instead #[endpoint { method = POST, path = "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces", tags = ["instances"], + deprecated = true }] async fn instance_network_interface_create( rqctx: RequestContext>, @@ -4233,19 +4324,21 @@ async fn instance_network_interface_create( interface_params: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let organization_name = &path.organization_name; - let project_name = &path.project_name; - let instance_name = &path.instance_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let instance_selector = params::InstanceSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + path.instance_name.into(), + ); + let instance_lookup = + nexus.instance_lookup(&opctx, &instance_selector)?; let iface = nexus - .instance_create_network_interface( + .network_interface_create( &opctx, - &organization_name, - &project_name, - &instance_name, + &instance_lookup, &interface_params.into_inner(), ) .await?; @@ -4262,6 +4355,40 @@ pub struct NetworkInterfacePathParam { pub interface_name: Name, } +/// Delete a network interface +/// +/// Note that the primary interface for an instance cannot be deleted if there +/// are any secondary interfaces. A new primary interface must be designated +/// first. The primary interface can be deleted if there are no secondary +/// interfaces. +#[endpoint { + method = DELETE, + path = "/v1/network-interfaces/{interface}", + tags = ["instances"], +}] +async fn instance_network_interface_delete_v1( + rqctx: RequestContext>, + path_params: Path, + query_params: Query, +) -> Result { + let apictx = rqctx.context(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let interface_selector = params::NetworkInterfaceSelector { + instance_selector: query.instance_selector, + network_interface: path.interface, + }; + let interface_lookup = + nexus.network_interface_lookup(&opctx, &interface_selector)?; + nexus.network_interface_delete(&opctx, &interface_lookup).await?; + Ok(HttpResponseDeleted()) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// Delete a network interface /// /// Note that the primary interface for an instance cannot be deleted if there @@ -4278,57 +4405,82 @@ async fn instance_network_interface_delete( path_params: Path, ) -> Result { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let organization_name = &path.organization_name; - let project_name = &path.project_name; - let instance_name = &path.instance_name; - let interface_name = &path.interface_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - nexus - .instance_delete_network_interface( - &opctx, - organization_name, - project_name, - instance_name, - interface_name, - ) - .await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let interface_selector = params::NetworkInterfaceSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.instance_name.into()), + path.interface_name.into(), + ); + let interface_lookup = + nexus.network_interface_lookup(&opctx, &interface_selector)?; + nexus.network_interface_delete(&opctx, &interface_lookup).await?; Ok(HttpResponseDeleted()) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } /// Fetch a network interface +#[endpoint { + method = GET, + path = "/v1/network-interfaces/{interface}", + tags = ["instances"], +}] +async fn instance_network_interface_view_v1( + rqctx: RequestContext>, + path_params: Path, + query_params: Query, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let interface_selector = params::NetworkInterfaceSelector { + instance_selector: query.instance_selector, + network_interface: path.interface, + }; + let (.., interface) = nexus + .network_interface_lookup(&opctx, &interface_selector)? + .fetch() + .await?; + Ok(HttpResponseOk(interface.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Fetch a network interface +/// Use `GET /v1/network-interfaces/{interface}` instead #[endpoint { method = GET, path = "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name}", tags = ["instances"], + deprecated = true }] async fn instance_network_interface_view( rqctx: RequestContext>, path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let organization_name = &path.organization_name; - let project_name = &path.project_name; - let instance_name = &path.instance_name; - let interface_name = &path.interface_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let interface = nexus - .network_interface_fetch( - &opctx, - organization_name, - project_name, - instance_name, - interface_name, - ) + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let interface_selector = params::NetworkInterfaceSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.instance_name.into()), + path.interface_name.into(), + ); + let (.., interface) = nexus + .network_interface_lookup(&opctx, &interface_selector)? + .fetch() .await?; - Ok(HttpResponseOk(NetworkInterface::from(interface))) + Ok(HttpResponseOk(interface.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } @@ -4344,14 +4496,19 @@ async fn instance_network_interface_view_by_id( path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let id = &path.id; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let network_interface = - nexus.network_interface_fetch_by_id(&opctx, id).await?; - Ok(HttpResponseOk(network_interface.into())) + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let interface_selector = params::NetworkInterfaceSelector { + instance_selector: None, + network_interface: path.id.into(), + }; + let (.., interface) = nexus + .network_interface_lookup(&opctx, &interface_selector)? + .fetch() + .await?; + Ok(HttpResponseOk(interface.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } @@ -4359,31 +4516,32 @@ async fn instance_network_interface_view_by_id( /// Update a network interface #[endpoint { method = PUT, - path = "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name}", + path = "/v1/network-interfaces/{interface}", tags = ["instances"], }] -async fn instance_network_interface_update( +async fn instance_network_interface_update_v1( rqctx: RequestContext>, - path_params: Path, + path_params: Path, + query_params: Query, updated_iface: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let organization_name = &path.organization_name; - let project_name = &path.project_name; - let instance_name = &path.instance_name; - let interface_name = &path.interface_name; - let updated_iface = updated_iface.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let updated_iface = updated_iface.into_inner(); + let network_interface_selector = params::NetworkInterfaceSelector { + instance_selector: query.instance_selector, + network_interface: path.interface, + }; + let network_interface_lookup = nexus + .network_interface_lookup(&opctx, &network_interface_selector)?; let interface = nexus .network_interface_update( &opctx, - organization_name, - project_name, - instance_name, - interface_name, + &network_interface_lookup, updated_iface, ) .await?; @@ -4392,6 +4550,41 @@ async fn instance_network_interface_update( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +/// Update a network interface +/// Use `PUT /v1/network-interfaces/{interface}` instead +#[endpoint { + method = PUT, + path = "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name}", + tags = ["instances"], + deprecated = true +}] +async fn instance_network_interface_update( + rqctx: RequestContext>, + path_params: Path, + updated_iface: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let updated_iface = updated_iface.into_inner(); + let interface_selector = params::NetworkInterfaceSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.instance_name.into()), + path.interface_name.into(), + ); + let interface_lookup = + nexus.network_interface_lookup(&opctx, &interface_selector)?; + let interface = nexus + .network_interface_update(&opctx, &interface_lookup, updated_iface) + .await?; + Ok(HttpResponseOk(interface.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + // External IP addresses for instances /// List external IP addresses @@ -4722,45 +4915,170 @@ async fn snapshot_delete( /// List VPCs #[endpoint { method = GET, - path = "/organizations/{organization_name}/projects/{project_name}/vpcs", + path = "/v1/vpcs", tags = ["vpcs"], }] -async fn vpc_list( +async fn vpc_list_v1( rqctx: RequestContext>, - query_params: Query, - path_params: Path, + query_params: Query>, ) -> Result>, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let query = query_params.into_inner(); - let path = path_params.into_inner(); - let organization_name = &path.organization_name; - let project_name = &path.project_name; let handler = async { + let nexus = &apictx.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 = OpContext::for_external_api(&rqctx).await?; + let project_lookup = + nexus.project_lookup(&opctx, &scan_params.selector)?; let vpcs = nexus - .project_list_vpcs( - &opctx, - &organization_name, - &project_name, - &data_page_params_for(&rqctx, &query)? - .map_name(|n| Name::ref_cast(n)), - ) + .vpc_list(&opctx, &project_lookup, &paginated_by) .await? .into_iter() .map(|p| p.into()) .collect(); - Ok(HttpResponseOk(ScanByName::results_page( + Ok(HttpResponseOk(ScanByNameOrId::results_page( &query, vpcs, - &marker_for_name, + &marker_for_name_or_id, )?)) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } -/// Path parameters for VPC requests +/// List VPCs +/// Use `GET /v1/vpcs` instead +#[endpoint { + method = GET, + path = "/organizations/{organization_name}/projects/{project_name}/vpcs", + tags = ["vpcs"], + deprecated = true, +}] +async fn vpc_list( + rqctx: RequestContext>, + query_params: Query, + path_params: Path, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let path = path_params.into_inner(); + let project_selector = params::ProjectSelector::new( + Some(path.organization_name.into()), + path.project_name.into(), + ); + let opctx = OpContext::for_external_api(&rqctx).await?; + let project_lookup = nexus.project_lookup(&opctx, &project_selector)?; + let vpcs = nexus + .vpc_list( + &opctx, + &project_lookup, + &PaginatedBy::Name(data_page_params_for(&rqctx, &query)?), + ) + .await? + .into_iter() + .map(|p| p.into()) + .collect(); + + Ok(HttpResponseOk(ScanByName::results_page( + &query, + vpcs, + &marker_for_name, + )?)) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Create a VPC +#[endpoint { + method = POST, + path = "/v1/vpcs", + tags = ["vpcs"], +}] +async fn vpc_create_v1( + rqctx: RequestContext>, + query_params: Query, + body: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let new_vpc_params = body.into_inner(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let project_lookup = nexus.project_lookup(&opctx, &query)?; + let vpc = nexus + .project_create_vpc(&opctx, &project_lookup, &new_vpc_params) + .await?; + Ok(HttpResponseCreated(vpc.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Create a VPC +/// Use `POST /v1/vpcs` instead +#[endpoint { + method = POST, + path = "/organizations/{organization_name}/projects/{project_name}/vpcs", + tags = ["vpcs"], + deprecated = true, +}] +async fn vpc_create( + rqctx: RequestContext>, + path_params: Path, + new_vpc: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let new_vpc_params = &new_vpc.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let project_selector = params::ProjectSelector::new( + Some(path.organization_name.into()), + path.project_name.into(), + ); + let project_lookup = nexus.project_lookup(&opctx, &project_selector)?; + let vpc = nexus + .project_create_vpc(&opctx, &project_lookup, &new_vpc_params) + .await?; + Ok(HttpResponseCreated(vpc.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Fetch a VPC +#[endpoint { + method = GET, + path = "/v1/vpcs/{vpc}", + tags = ["vpcs"], +}] +async fn vpc_view_v1( + rqctx: RequestContext>, + path_params: Path, + query_params: Query, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let vpc_selector = params::VpcSelector { + project_selector: query.project_selector, + vpc: path.vpc, + }; + let (.., vpc) = + nexus.vpc_lookup(&opctx, &vpc_selector)?.fetch().await?; + Ok(HttpResponseOk(vpc.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Path parameters for VPC requests #[derive(Deserialize, JsonSchema)] struct VpcPathParam { organization_name: Name, @@ -4769,10 +5087,12 @@ struct VpcPathParam { } /// Fetch a VPC +/// Use `GET /v1/vpcs/{vpc}` instead #[endpoint { method = GET, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}", tags = ["vpcs"], + deprecated = true, }] async fn vpc_view( rqctx: RequestContext>, @@ -4781,24 +5101,27 @@ async fn vpc_view( let apictx = rqctx.context(); let nexus = &apictx.nexus; let path = path_params.into_inner(); - let organization_name = &path.organization_name; - let project_name = &path.project_name; - let vpc_name = &path.vpc_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let vpc = nexus - .vpc_fetch(&opctx, &organization_name, &project_name, &vpc_name) - .await?; + let vpc_selector = params::VpcSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + path.vpc_name.into(), + ); + let (.., vpc) = + nexus.vpc_lookup(&opctx, &vpc_selector)?.fetch().await?; Ok(HttpResponseOk(vpc.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } /// Fetch a VPC +/// Use `GET /v1/vpcs/{id}` instead #[endpoint { method = GET, path = "/by-id/vpcs/{id}", tags = ["vpcs"], + deprecated = true, }] async fn vpc_view_by_id( rqctx: RequestContext>, @@ -4807,50 +5130,55 @@ async fn vpc_view_by_id( let apictx = rqctx.context(); let nexus = &apictx.nexus; let path = path_params.into_inner(); - let id = &path.id; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let vpc = nexus.vpc_fetch_by_id(&opctx, id).await?; + let vpc_selector = params::VpcSelector::new(None, None, path.id.into()); + let (.., vpc) = + nexus.vpc_lookup(&opctx, &vpc_selector)?.fetch().await?; Ok(HttpResponseOk(vpc.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } -/// Create a VPC +/// Update a VPC #[endpoint { - method = POST, - path = "/organizations/{organization_name}/projects/{project_name}/vpcs", + method = PUT, + path = "/v1/vpcs/{vpc}", tags = ["vpcs"], }] -async fn vpc_create( +async fn vpc_update_v1( rqctx: RequestContext>, - path_params: Path, - new_vpc: TypedBody, -) -> Result, HttpError> { + path_params: Path, + query_params: Query, + updated_vpc: TypedBody, +) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let new_vpc_params = &new_vpc.into_inner(); let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let updated_vpc_params = &updated_vpc.into_inner(); let opctx = OpContext::for_external_api(&rqctx).await?; - let project_selector = params::ProjectSelector::new( - Some(path.organization_name.into()), - path.project_name.into(), - ); - let project_lookup = nexus.project_lookup(&opctx, &project_selector)?; + let vpc_selector = params::VpcSelector { + project_selector: query.project_selector, + vpc: path.vpc, + }; + let vpc_lookup = nexus.vpc_lookup(&opctx, &vpc_selector)?; let vpc = nexus - .project_create_vpc(&opctx, &project_lookup, &new_vpc_params) + .project_update_vpc(&opctx, &vpc_lookup, &updated_vpc_params) .await?; - Ok(HttpResponseCreated(vpc.into())) + Ok(HttpResponseOk(vpc.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } /// Update a VPC +/// Use `PUT /v1/vpcs/{vpc}` instead #[endpoint { method = PUT, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}", tags = ["vpcs"], + deprecated = true, }] async fn vpc_update( rqctx: RequestContext>, @@ -4862,14 +5190,14 @@ async fn vpc_update( let path = path_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; + let vpc_selector = params::VpcSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + path.vpc_name.into(), + ); + let vpc_lookup = nexus.vpc_lookup(&opctx, &vpc_selector)?; let newvpc = nexus - .project_update_vpc( - &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &updated_vpc.into_inner(), - ) + .project_update_vpc(&opctx, &vpc_lookup, &updated_vpc.into_inner()) .await?; Ok(HttpResponseOk(newvpc.into())) }; @@ -4877,10 +5205,40 @@ async fn vpc_update( } /// Delete a VPC +#[endpoint { + method = DELETE, + path = "/v1/vpcs/{vpc}", + tags = ["vpcs"], +}] +async fn vpc_delete_v1( + rqctx: RequestContext>, + path_params: Path, + query_params: Query, +) -> Result { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let vpc_selector = params::VpcSelector { + project_selector: query.project_selector, + vpc: path.vpc, + }; + let vpc_lookup = nexus.vpc_lookup(&opctx, &vpc_selector)?; + nexus.project_delete_vpc(&opctx, &vpc_lookup).await?; + Ok(HttpResponseDeleted()) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Delete a VPC +/// Use `DELETE /v1/vpcs/{vpc}` instead #[endpoint { method = DELETE, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}", tags = ["vpcs"], + deprecated = true, }] async fn vpc_delete( rqctx: RequestContext>, @@ -4889,29 +5247,61 @@ async fn vpc_delete( let apictx = rqctx.context(); let nexus = &apictx.nexus; let path = path_params.into_inner(); - let organization_name = &path.organization_name; - let project_name = &path.project_name; - let vpc_name = &path.vpc_name; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - nexus - .project_delete_vpc( - &opctx, - &organization_name, - &project_name, - &vpc_name, - ) - .await?; + let vpc_selector = params::VpcSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + path.vpc_name.into(), + ); + let vpc_lookup = nexus.vpc_lookup(&opctx, &vpc_selector)?; + nexus.project_delete_vpc(&opctx, &vpc_lookup).await?; Ok(HttpResponseDeleted()) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +/// Fetch a subnet +#[endpoint { + method = GET, + path = "/v1/vpc-subnets", + tags = ["vpcs"], +}] +async fn vpc_subnet_list_v1( + rqctx: RequestContext>, + query_params: Query>, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.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 = OpContext::for_external_api(&rqctx).await?; + let vpc_lookup = nexus.vpc_lookup(&opctx, &scan_params.selector)?; + let subnets = nexus + .vpc_subnet_list(&opctx, &vpc_lookup, &paginated_by) + .await? + .into_iter() + .map(|vpc| vpc.into()) + .collect(); + Ok(HttpResponseOk(ScanByNameOrId::results_page( + &query, + subnets, + &marker_for_name_or_id, + )?)) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// List subnets +/// Use `GET /v1/vpc-subnets` instead #[endpoint { method = GET, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets", tags = ["vpcs"], + deprecated = true, }] async fn vpc_subnet_list( rqctx: RequestContext>, @@ -4919,29 +5309,117 @@ async fn vpc_subnet_list( path_params: Path, ) -> Result>, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let query = query_params.into_inner(); - let path = path_params.into_inner(); let handler = async { + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let path = path_params.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let vpc_selector = params::VpcSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + path.vpc_name.into(), + ); + let vpc_lookup = nexus.vpc_lookup(&opctx, &vpc_selector)?; + let vpcs = nexus + .vpc_subnet_list( + &opctx, + &vpc_lookup, + &PaginatedBy::Name(data_page_params_for(&rqctx, &query)?), + ) + .await? + .into_iter() + .map(|vpc| vpc.into()) + .collect(); + Ok(HttpResponseOk(ScanByName::results_page( + &query, + vpcs, + &marker_for_name, + )?)) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Create a subnet +#[endpoint { + method = POST, + path = "/v1/vpc-subnets", + tags = ["vpcs"], +}] +async fn vpc_subnet_create_v1( + rqctx: RequestContext>, + query_params: Query, + create_params: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let create = create_params.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let vpc_lookup = nexus.vpc_lookup(&opctx, &query)?; + let subnet = + nexus.vpc_create_subnet(&opctx, &vpc_lookup, &create).await?; + Ok(HttpResponseCreated(subnet.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Create a subnet +/// Use `POST /v1/vpc-subnets` instead +#[endpoint { + method = POST, + path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets", + tags = ["vpcs"], + deprecated = true +}] +async fn vpc_subnet_create( + rqctx: RequestContext>, + path_params: Path, + create_params: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let vpc_selector = params::VpcSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + path.vpc_name.into(), + ); + let vpc_lookup = nexus.vpc_lookup(&opctx, &vpc_selector)?; + let subnet = nexus + .vpc_create_subnet(&opctx, &vpc_lookup, &create_params.into_inner()) + .await?; + Ok(HttpResponseCreated(subnet.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Fetch a subnet +#[endpoint { + method = GET, + path = "/v1/vpc-subnets/{subnet}", + tags = ["vpcs"], +}] +async fn vpc_subnet_view_v1( + rqctx: RequestContext>, + path_params: Path, + query_params: Query, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); let opctx = OpContext::for_external_api(&rqctx).await?; - let vpcs = nexus - .vpc_list_subnets( - &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &data_page_params_for(&rqctx, &query)? - .map_name(|n| Name::ref_cast(n)), - ) - .await? - .into_iter() - .map(|vpc| vpc.into()) - .collect(); - Ok(HttpResponseOk(ScanByName::results_page( - &query, - vpcs, - &marker_for_name, - )?)) + let subnet_selector = params::SubnetSelector { + vpc_selector: Some(query), + subnet: path.subnet, + }; + let (.., subnet) = + nexus.vpc_subnet_lookup(&opctx, &subnet_selector)?.fetch().await?; + Ok(HttpResponseOk(subnet.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } @@ -4956,39 +5434,42 @@ struct VpcSubnetPathParam { } /// Fetch a subnet +/// Use `GET /v1/vpc-subnets/{subnet}` instead #[endpoint { method = GET, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}", tags = ["vpcs"], + deprecated = true }] async fn vpc_subnet_view( rqctx: RequestContext>, path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let subnet = nexus - .vpc_subnet_fetch( - &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &path.subnet_name, - ) - .await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let subnet_selector = params::SubnetSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.vpc_name.into()), + path.subnet_name.into(), + ); + let (.., subnet) = + nexus.vpc_subnet_lookup(&opctx, &subnet_selector)?.fetch().await?; Ok(HttpResponseOk(subnet.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } /// Fetch a subnet by id +/// Use `GET /v1/vpc-subnets/{id}` instead #[endpoint { method = GET, path = "/by-id/vpc-subnets/{id}", tags = ["vpcs"], + deprecated = true }] async fn vpc_subnet_view_by_id( rqctx: RequestContext>, @@ -4997,79 +5478,119 @@ async fn vpc_subnet_view_by_id( let apictx = rqctx.context(); let nexus = &apictx.nexus; let path = path_params.into_inner(); - let id = &path.id; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let subnet = nexus.vpc_subnet_fetch_by_id(&opctx, id).await?; + let subnet_selector = params::SubnetSelector { + subnet: path.id.into(), + vpc_selector: None, + }; + let (.., subnet) = + nexus.vpc_subnet_lookup(&opctx, &subnet_selector)?.fetch().await?; Ok(HttpResponseOk(subnet.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } -/// Create a subnet +/// Delete a subnet #[endpoint { - method = POST, - path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets", + method = DELETE, + path = "/v1/vpc-subnets/{subnet}", tags = ["vpcs"], }] -async fn vpc_subnet_create( +async fn vpc_subnet_delete_v1( rqctx: RequestContext>, - path_params: Path, - create_params: TypedBody, -) -> Result, HttpError> { + path_params: Path, + query_params: Query, +) -> Result { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); let opctx = OpContext::for_external_api(&rqctx).await?; - let subnet = nexus - .vpc_create_subnet( - &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &create_params.into_inner(), - ) - .await?; - Ok(HttpResponseCreated(subnet.into())) + let subnet_selector = params::SubnetSelector { + vpc_selector: query.vpc_selector, + subnet: path.subnet, + }; + let subnet_lookup = + nexus.vpc_subnet_lookup(&opctx, &subnet_selector)?; + nexus.vpc_delete_subnet(&opctx, &subnet_lookup).await?; + Ok(HttpResponseDeleted()) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } /// Delete a subnet +/// Use `DELETE /v1/vpc-subnets/{subnet}` instead #[endpoint { method = DELETE, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}", tags = ["vpcs"], + deprecated = true }] async fn vpc_subnet_delete( rqctx: RequestContext>, path_params: Path, ) -> Result { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); let opctx = OpContext::for_external_api(&rqctx).await?; - nexus - .vpc_delete_subnet( - &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &path.subnet_name, - ) - .await?; + let subnet_selector = params::SubnetSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.vpc_name.into()), + path.subnet_name.into(), + ); + let subnet_lookup = + nexus.vpc_subnet_lookup(&opctx, &subnet_selector)?; + nexus.vpc_delete_subnet(&opctx, &subnet_lookup).await?; Ok(HttpResponseDeleted()) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } /// Update a subnet +#[endpoint { + method = PUT, + path = "/v1/vpc-subnets/{subnet}", + tags = ["vpcs"], +}] +async fn vpc_subnet_update_v1( + rqctx: RequestContext>, + path_params: Path, + query_params: Query, + subnet_params: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let subnet_params = subnet_params.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let subnet_selector = params::SubnetSelector { + vpc_selector: query.vpc_selector, + subnet: path.subnet, + }; + let subnet_lookup = + nexus.vpc_subnet_lookup(&opctx, &subnet_selector)?; + let subnet = nexus + .vpc_update_subnet(&opctx, &subnet_lookup, &subnet_params) + .await?; + Ok(HttpResponseOk(subnet.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Update a subnet +/// Use `PUT /v1/vpc-subnets/{subnet}` instead #[endpoint { method = PUT, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}", tags = ["vpcs"], + deprecated = true }] async fn vpc_subnet_update( rqctx: RequestContext>, @@ -5077,17 +5598,22 @@ async fn vpc_subnet_update( subnet_params: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); let opctx = OpContext::for_external_api(&rqctx).await?; + let subnet_selector = params::SubnetSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.vpc_name.into()), + path.subnet_name.into(), + ); + let subnet_lookup = + nexus.vpc_subnet_lookup(&opctx, &subnet_selector)?; let subnet = nexus .vpc_update_subnet( &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &path.subnet_name, + &subnet_lookup, &subnet_params.into_inner(), ) .await?; @@ -5209,10 +5735,46 @@ async fn vpc_firewall_rules_update( // VPC Routers /// List routers +#[endpoint { + method = GET, + path = "/v1/vpc-routers", + tags = ["vpcs"], +}] +async fn vpc_router_list_v1( + rqctx: RequestContext>, + query_params: Query>, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.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 vpc_lookup = nexus.vpc_lookup(&opctx, &scan_params.selector)?; + let routers = nexus + .vpc_router_list(&opctx, &vpc_lookup, &paginated_by) + .await? + .into_iter() + .map(|s| s.into()) + .collect(); + Ok(HttpResponseOk(ScanByNameOrId::results_page( + &query, + routers, + &marker_for_name_or_id, + )?)) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// List routers +/// Use `GET /v1/vpc-routers` instead #[endpoint { method = GET, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers", tags = ["vpcs"], + deprecated = true, }] async fn vpc_router_list( rqctx: RequestContext>, @@ -5220,19 +5782,22 @@ async fn vpc_router_list( path_params: Path, ) -> Result>, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let query = query_params.into_inner(); - let path = path_params.into_inner(); let handler = async { + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let path = path_params.into_inner(); let opctx = OpContext::for_external_api(&rqctx).await?; + let vpc_selector = params::VpcSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + path.vpc_name.into(), + ); + let vpc_lookup = nexus.vpc_lookup(&opctx, &vpc_selector)?; let routers = nexus - .vpc_list_routers( + .vpc_router_list( &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &data_page_params_for(&rqctx, &query)? - .map_name(|n| Name::ref_cast(n)), + &vpc_lookup, + &PaginatedBy::Name(data_page_params_for(&rqctx, &query)?), ) .await? .into_iter() @@ -5247,6 +5812,34 @@ async fn vpc_router_list( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +/// Get a router +#[endpoint { + method = GET, + path = "/v1/vpc-routers/{router}", + tags = ["vpcs"], +}] +async fn vpc_router_view_v1( + rqctx: RequestContext>, + path_params: Path, + query_params: Query, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let router_selector = params::RouterSelector { + vpc_selector: Some(query), + router: path.router, + }; + let (.., vpc_router) = + nexus.vpc_router_lookup(&opctx, &router_selector)?.fetch().await?; + Ok(HttpResponseOk(vpc_router.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// Path parameters for VPC Router requests #[derive(Deserialize, JsonSchema)] struct VpcRouterPathParam { @@ -5257,29 +5850,32 @@ struct VpcRouterPathParam { } /// Get a router +/// Use `GET /v1/vpc-routers/{router}` instead #[endpoint { method = GET, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}", tags = ["vpcs"], + deprecated = true, }] async fn vpc_router_view( rqctx: RequestContext>, path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let vpc_router = nexus - .vpc_router_fetch( - &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &path.router_name, - ) - .await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let router_selector = params::RouterSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.vpc_name.into()), + path.router_name.into(), + ); + let router_lookup = + nexus.vpc_router_lookup(&opctx, &router_selector)?; + let (.., vpc_router) = router_lookup.fetch().await?; + Ok(HttpResponseOk(vpc_router.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await @@ -5296,22 +5892,60 @@ async fn vpc_router_view_by_id( path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let id = &path.id; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let router = nexus.vpc_router_fetch_by_id(&opctx, id).await?; - Ok(HttpResponseOk(router.into())) + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let router_selector = params::RouterSelector { + vpc_selector: None, + router: path.id.into(), + }; + let router_lookup = + nexus.vpc_router_lookup(&opctx, &router_selector)?; + let (.., vpc_router) = router_lookup.fetch().await?; + Ok(HttpResponseOk(vpc_router.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Create a VPC router +#[endpoint { + method = POST, + path = "/v1/vpc-routers", + tags = ["vpcs"], +}] +async fn vpc_router_create_v1( + rqctx: RequestContext>, + query_params: Query, + create_params: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let create = create_params.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let vpc_lookup = nexus.vpc_lookup(&opctx, &query)?; + let router = nexus + .vpc_create_router( + &opctx, + &vpc_lookup, + &db::model::VpcRouterKind::Custom, + &create, + ) + .await?; + Ok(HttpResponseCreated(router.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } /// Create a router +/// Use `POST /v1/vpc-routers` instead #[endpoint { method = POST, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers", tags = ["vpcs"], + deprecated = true, }] async fn vpc_router_create( rqctx: RequestContext>, @@ -5319,59 +5953,128 @@ async fn vpc_router_create( create_params: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let vpc_selector = params::VpcSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + path.vpc_name.into(), + ); + let vpc_lookup = nexus.vpc_lookup(&opctx, &vpc_selector)?; + let router = nexus + .vpc_create_router( + &opctx, + &vpc_lookup, + &db::model::VpcRouterKind::Custom, + &create_params.into_inner(), + ) + .await?; + Ok(HttpResponseCreated(router.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Delete a router +#[endpoint { + method = DELETE, + path = "/v1/vpc-routers/{router}", + tags = ["vpcs"], +}] +async fn vpc_router_delete_v1( + rqctx: RequestContext>, + path_params: Path, + query_params: Query, +) -> Result { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); let opctx = OpContext::for_external_api(&rqctx).await?; - let router = nexus - .vpc_create_router( - &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &db::model::VpcRouterKind::Custom, - &create_params.into_inner(), - ) - .await?; - Ok(HttpResponseCreated(router.into())) + let router_selector = params::RouterSelector { + vpc_selector: query.vpc_selector, + router: path.router, + }; + let router_lookup = + nexus.vpc_router_lookup(&opctx, &router_selector)?; + nexus.vpc_delete_router(&opctx, &router_lookup).await?; + Ok(HttpResponseDeleted()) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } /// Delete a router +/// Use `DELETE /v1/vpc-routers/{router}` instead #[endpoint { method = DELETE, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}", tags = ["vpcs"], + deprecated = true, }] async fn vpc_router_delete( rqctx: RequestContext>, path_params: Path, ) -> Result { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); let opctx = OpContext::for_external_api(&rqctx).await?; - nexus - .vpc_delete_router( - &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &path.router_name, - ) - .await?; + let router_selector = params::RouterSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.vpc_name.into()), + path.router_name.into(), + ); + let router_lookup = + nexus.vpc_router_lookup(&opctx, &router_selector)?; + nexus.vpc_delete_router(&opctx, &router_lookup).await?; Ok(HttpResponseDeleted()) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +/// Update a router +#[endpoint { + method = PUT, + path = "/v1/vpc-routers/{router}", + tags = ["vpcs"], +}] +async fn vpc_router_update_v1( + rqctx: RequestContext>, + path_params: Path, + query_params: Query, + router_params: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let router_params = router_params.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let router_selector = params::RouterSelector { + vpc_selector: query.vpc_selector, + router: path.router, + }; + let router_lookup = + nexus.vpc_router_lookup(&opctx, &router_selector)?; + let router = nexus + .vpc_update_router(&opctx, &router_lookup, &router_params) + .await?; + Ok(HttpResponseOk(router.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// Update a router #[endpoint { method = PUT, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}", tags = ["vpcs"], + deprecated = true, }] async fn vpc_router_update( rqctx: RequestContext>, @@ -5379,17 +6082,22 @@ async fn vpc_router_update( router_params: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); let opctx = OpContext::for_external_api(&rqctx).await?; + let router_selector = params::RouterSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.vpc_name.into()), + path.router_name.into(), + ); + let router_lookup = + nexus.vpc_router_lookup(&opctx, &router_selector)?; let router = nexus .vpc_update_router( &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &path.router_name, + &router_lookup, &router_params.into_inner(), ) .await?; @@ -5398,15 +6106,54 @@ async fn vpc_router_update( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +// List routes +/// +/// List the routes associated with a router in a particular VPC. +#[endpoint { + method = GET, + path = "/v1/vpc-router-routes", + tags = ["vpcs"], +}] +async fn vpc_router_route_list_v1( + rqctx: RequestContext>, + query_params: Query>, +) -> Result>, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.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 router_lookup = + nexus.vpc_router_lookup(&opctx, &scan_params.selector)?; + let routes = nexus + .vpc_router_route_list(&opctx, &router_lookup, &paginated_by) + .await? + .into_iter() + .map(|route| route.into()) + .collect(); + Ok(HttpResponseOk(ScanByNameOrId::results_page( + &query, + routes, + &marker_for_name_or_id, + )?)) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + // Vpc Router Routes /// List routes /// /// List the routes associated with a router in a particular VPC. +/// Use `GET /v1/vpc-router-routes` instead. #[endpoint { method = GET, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes", tags = ["vpcs"], + deprecated = true }] async fn vpc_router_route_list( rqctx: RequestContext>, @@ -5414,20 +6161,24 @@ async fn vpc_router_route_list( path_params: Path, ) -> Result>, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let query = query_params.into_inner(); - let path = path_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let path = path_params.into_inner(); + let router_selector = params::RouterSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.vpc_name.into()), + path.router_name.into(), + ); + let router_lookup = + nexus.vpc_router_lookup(&opctx, &router_selector)?; let routes = nexus - .router_list_routes( + .vpc_router_route_list( &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &path.router_name, - &data_page_params_for(&rqctx, &query)? - .map_name(|n| Name::ref_cast(n)), + &router_lookup, + &PaginatedBy::Name(data_page_params_for(&rqctx, &query)?), ) .await? .into_iter() @@ -5442,6 +6193,36 @@ async fn vpc_router_route_list( apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +/// Fetch a route +#[endpoint { + method = GET, + path = "/v1/vpc-router-routes/{route}", + tags = ["vpcs"], +}] +async fn vpc_router_route_view_v1( + rqctx: RequestContext>, + path_params: Path, + query_params: Query, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let route_selector = params::RouteSelector { + router_selector: Some(query), + route: path.route, + }; + let (.., route) = nexus + .vpc_router_route_lookup(&opctx, &route_selector)? + .fetch() + .await?; + Ok(HttpResponseOk(route.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + /// Path parameters for Router Route requests #[derive(Deserialize, JsonSchema)] struct RouterRoutePathParam { @@ -5453,29 +6234,32 @@ struct RouterRoutePathParam { } /// Fetch a route +/// Use `GET /v1/vpc-router-routes/{route}` instead #[endpoint { method = GET, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}", tags = ["vpcs"], + deprecated = true }] async fn vpc_router_route_view( rqctx: RequestContext>, path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let route = nexus - .route_fetch( - &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &path.router_name, - &path.route_name, - ) + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let route_selector = params::RouteSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.vpc_name.into()), + Some(path.router_name.into()), + path.route_name.into(), + ); + let (.., route) = nexus + .vpc_router_route_lookup(&opctx, &route_selector)? + .fetch() .await?; Ok(HttpResponseOk(route.into())) }; @@ -5483,50 +6267,96 @@ async fn vpc_router_route_view( } /// Fetch a route by id +/// Use `GET /v1/vpc-router-routes/{route}` instead #[endpoint { method = GET, path = "/by-id/vpc-router-routes/{id}", - tags = ["vpcs"] + tags = ["vpcs"], + deprecated = true }] async fn vpc_router_route_view_by_id( rqctx: RequestContext>, path_params: Path, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); - let id = &path.id; let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let route = nexus.route_fetch_by_id(&opctx, id).await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let route_selector = params::RouteSelector { + router_selector: None, + route: path.id.into(), + }; + let (.., route) = nexus + .vpc_router_route_lookup(&opctx, &route_selector)? + .fetch() + .await?; Ok(HttpResponseOk(route.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } /// Create a router +#[endpoint { + method = POST, + path = "/v1/vpc-router-routes", + tags = ["vpcs"], +}] +async fn vpc_router_route_create_v1( + rqctx: RequestContext>, + query_params: Query, + create_params: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.nexus; + let query = query_params.into_inner(); + let create = create_params.into_inner(); + let router_lookup = nexus.vpc_router_lookup(&opctx, &query)?; + let route = nexus + .router_create_route( + &opctx, + &router_lookup, + &RouterRouteKind::Custom, + &create, + ) + .await?; + Ok(HttpResponseCreated(route.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Create a router +/// Use `POST /v1/vpc-router-routes` instead #[endpoint { method = POST, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes", tags = ["vpcs"], + deprecated = true }] async fn vpc_router_route_create( rqctx: RequestContext>, path_params: Path, - create_params: TypedBody, + create_params: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let router_selector = params::RouterSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.vpc_name.into()), + path.router_name.into(), + ); + let router_lookup = + nexus.vpc_router_lookup(&opctx, &router_selector)?; let route = nexus .router_create_route( &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &path.router_name, + &router_lookup, &RouterRouteKind::Custom, &create_params.into_inner(), ) @@ -5537,63 +6367,133 @@ async fn vpc_router_route_create( } /// Delete a route +#[endpoint { + method = DELETE, + path = "/v1/vpc-router-routes/{route}", + tags = ["vpcs"], +}] +async fn vpc_router_route_delete_v1( + rqctx: RequestContext>, + path_params: Path, + query_params: Query, +) -> Result { + let apictx = rqctx.context(); + let handler = async { + let opctx = OpContext::for_external_api(&rqctx).await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let route_selector = params::RouteSelector { + router_selector: query.router_selector, + route: path.route, + }; + let route_lookup = + nexus.vpc_router_route_lookup(&opctx, &route_selector)?; + nexus.router_delete_route(&opctx, &route_lookup).await?; + Ok(HttpResponseDeleted()) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} + +/// Delete a route +/// Use `DELETE /v1/vpc-router-routes/{route}` instead #[endpoint { method = DELETE, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}", tags = ["vpcs"], + deprecated = true }] async fn vpc_router_route_delete( rqctx: RequestContext>, path_params: Path, ) -> Result { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - nexus - .router_delete_route( - &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &path.router_name, - &path.route_name, - ) - .await?; + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let route_selector = params::RouteSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.vpc_name.into()), + Some(path.router_name.into()), + path.route_name.into(), + ); + let route_lookup = + nexus.vpc_router_route_lookup(&opctx, &route_selector)?; + nexus.router_delete_route(&opctx, &route_lookup).await?; Ok(HttpResponseDeleted()) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } +/// Update a route +#[endpoint { + method = PUT, + path = "/v1/vpc-router-routes/{route}", + tags = ["vpcs"], +}] +async fn vpc_router_route_update_v1( + rqctx: RequestContext>, + path_params: Path, + query_params: Query, + router_params: TypedBody, +) -> Result, HttpError> { + let apictx = rqctx.context(); + let handler = async { + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let query = query_params.into_inner(); + let router_params = router_params.into_inner(); + let opctx = OpContext::for_external_api(&rqctx).await?; + let route_selector = params::RouteSelector { + router_selector: query.router_selector, + route: path.route, + }; + let route_lookup = + nexus.vpc_router_route_lookup(&opctx, &route_selector)?; + let route = nexus + .router_update_route(&opctx, &route_lookup, &router_params) + .await?; + Ok(HttpResponseOk(route.into())) + }; + apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await +} /// Update a route +/// Use `PUT /v1/vpc-router-routes/{route}` instead #[endpoint { method = PUT, path = "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}", tags = ["vpcs"], + deprecated = true }] async fn vpc_router_route_update( rqctx: RequestContext>, path_params: Path, - router_params: TypedBody, + router_params: TypedBody, ) -> Result, HttpError> { let apictx = rqctx.context(); - let nexus = &apictx.nexus; - let path = path_params.into_inner(); let handler = async { let opctx = OpContext::for_external_api(&rqctx).await?; - let router_route = nexus + let nexus = &apictx.nexus; + let path = path_params.into_inner(); + let route_selector = params::RouteSelector::new( + Some(path.organization_name.into()), + Some(path.project_name.into()), + Some(path.vpc_name.into()), + Some(path.router_name.into()), + path.route_name.into(), + ); + let route_lookup = + nexus.vpc_router_route_lookup(&opctx, &route_selector)?; + let route = nexus .router_update_route( &opctx, - &path.organization_name, - &path.project_name, - &path.vpc_name, - &path.router_name, - &path.route_name, + &route_lookup, &router_params.into_inner(), ) .await?; - Ok(HttpResponseOk(router_route.into())) + Ok(HttpResponseOk(route.into())) }; apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await } diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index d5aaf33f374..7eda7fb3466 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -23,8 +23,6 @@ use omicron_common::api::external::Ipv4Net; use omicron_common::api::external::Name; use omicron_common::api::external::RouteDestination; use omicron_common::api::external::RouteTarget; -use omicron_common::api::external::RouterRouteCreateParams; -use omicron_common::api::external::RouterRouteUpdateParams; use omicron_common::api::external::SemverVersion; use omicron_common::api::external::VpcFirewallRuleUpdateParams; use omicron_nexus::authn; @@ -114,14 +112,14 @@ lazy_static! { pub static ref DEMO_PROJECT_POLICY_URL: String = format!("/v1/projects/{}/policy?organization={}", *DEMO_PROJECT_NAME, *DEMO_ORG_NAME); pub static ref DEMO_PROJECT_URL_DISKS: String = - format!("/organizations/{}/projects/{}/disks", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); + format!("/v1/disks?organization={}&project={}", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); pub static ref DEMO_PROJECT_URL_IMAGES: String = format!("/organizations/{}/projects/{}/images", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); pub static ref DEMO_PROJECT_URL_INSTANCES: String = format!("/v1/instances?organization={}&project={}", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); pub static ref DEMO_PROJECT_URL_SNAPSHOTS: String = format!("/v1/snapshots?organization={}&project={}", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); pub static ref DEMO_PROJECT_URL_VPCS: String = - format!("/organizations/{}/projects/{}/vpcs", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); + format!("/v1/vpcs?organization={}&project={}", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); pub static ref DEMO_PROJECT_CREATE: params::ProjectCreate = params::ProjectCreate { identity: IdentityMetadataCreateParams { @@ -133,13 +131,13 @@ lazy_static! { // VPC used for testing pub static ref DEMO_VPC_NAME: Name = "demo-vpc".parse().unwrap(); pub static ref DEMO_VPC_URL: String = - format!("/organizations/{}/projects/{}/vpcs/{}", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_VPC_NAME); + format!("/v1/vpcs/{}?organization={}&project={}", *DEMO_VPC_NAME, *DEMO_ORG_NAME, *DEMO_PROJECT_NAME); pub static ref DEMO_VPC_URL_FIREWALL_RULES: String = format!("/organizations/{}/projects/{}/vpcs/{}/firewall/rules", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_VPC_NAME); pub static ref DEMO_VPC_URL_ROUTERS: String = - format!("/organizations/{}/projects/{}/vpcs/{}/routers", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_VPC_NAME); + format!("/v1/vpc-routers?organization={}&project={}&vpc={}", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_VPC_NAME); pub static ref DEMO_VPC_URL_SUBNETS: String = - format!("/organizations/{}/projects/{}/vpcs/{}/subnets", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_VPC_NAME); + format!("/v1/vpc-subnets?organization={}&project={}&vpc={}", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_VPC_NAME); pub static ref DEMO_VPC_CREATE: params::VpcCreate = params::VpcCreate { identity: IdentityMetadataCreateParams { @@ -154,9 +152,9 @@ lazy_static! { pub static ref DEMO_VPC_SUBNET_NAME: Name = "demo-vpc-subnet".parse().unwrap(); pub static ref DEMO_VPC_SUBNET_URL: String = - format!("{}/{}", *DEMO_VPC_URL_SUBNETS, *DEMO_VPC_SUBNET_NAME); + format!("/v1/vpc-subnets/{}?organization={}&project={}&vpc={}", *DEMO_VPC_SUBNET_NAME, *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_VPC_NAME); pub static ref DEMO_VPC_SUBNET_INTERFACES_URL: String = - format!("{}/network-interfaces", *DEMO_VPC_SUBNET_URL); + format!("/organizations/{}/projects/{}/vpcs/{}/subnets/{}/network-interfaces", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_VPC_NAME, *DEMO_VPC_SUBNET_NAME); pub static ref DEMO_VPC_SUBNET_CREATE: params::VpcSubnetCreate = params::VpcSubnetCreate { identity: IdentityMetadataCreateParams { @@ -171,9 +169,9 @@ lazy_static! { pub static ref DEMO_VPC_ROUTER_NAME: Name = "demo-vpc-router".parse().unwrap(); pub static ref DEMO_VPC_ROUTER_URL: String = - format!("{}/{}", *DEMO_VPC_URL_ROUTERS, *DEMO_VPC_ROUTER_NAME); + format!("/v1/vpc-routers/{}?organization={}&project={}&vpc={}", *DEMO_VPC_ROUTER_NAME, *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_VPC_NAME); pub static ref DEMO_VPC_ROUTER_URL_ROUTES: String = - format!("{}/routes", *DEMO_VPC_ROUTER_URL); + format!("/v1/vpc-router-routes?organization={}&project={}&vpc={}&router={}", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_VPC_NAME, *DEMO_VPC_ROUTER_NAME); pub static ref DEMO_VPC_ROUTER_CREATE: params::VpcRouterCreate = params::VpcRouterCreate { identity: IdentityMetadataCreateParams { @@ -186,9 +184,9 @@ lazy_static! { pub static ref DEMO_ROUTER_ROUTE_NAME: Name = "demo-router-route".parse().unwrap(); pub static ref DEMO_ROUTER_ROUTE_URL: String = - format!("{}/{}", *DEMO_VPC_ROUTER_URL_ROUTES, *DEMO_ROUTER_ROUTE_NAME); - pub static ref DEMO_ROUTER_ROUTE_CREATE: RouterRouteCreateParams = - RouterRouteCreateParams { + format!("/v1/vpc-router-routes/{}?organization={}&project={}&vpc={}&router={}", *DEMO_ROUTER_ROUTE_NAME, *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_VPC_NAME, *DEMO_VPC_ROUTER_NAME); + pub static ref DEMO_ROUTER_ROUTE_CREATE: params::RouterRouteCreate = + params::RouterRouteCreate { identity: IdentityMetadataCreateParams { name: DEMO_ROUTER_ROUTE_NAME.clone(), description: String::from(""), @@ -259,7 +257,7 @@ lazy_static! { // To be migrated... pub static ref DEMO_INSTANCE_NICS_URL: String = - format!("/organizations/{}/projects/{}/instances/{}/network-interfaces", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_INSTANCE_NAME); + format!("/v1/network-interfaces?organization={}&project={}&instance={}", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_INSTANCE_NAME); pub static ref DEMO_INSTANCE_EXTERNAL_IPS_URL: String = format!("/organizations/{}/projects/{}/instances/{}/external-ips", *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_INSTANCE_NAME); pub static ref DEMO_INSTANCE_CREATE: params::InstanceCreate = @@ -285,7 +283,7 @@ lazy_static! { pub static ref DEMO_INSTANCE_NIC_NAME: Name = nexus_defaults::DEFAULT_PRIMARY_NIC_NAME.parse().unwrap(); pub static ref DEMO_INSTANCE_NIC_URL: String = - format!("{}/{}", *DEMO_INSTANCE_NICS_URL, *DEMO_INSTANCE_NIC_NAME); + format!("/v1/network-interfaces/{}?organization={}&project={}&instance={}", *DEMO_INSTANCE_NIC_NAME, *DEMO_ORG_NAME, *DEMO_PROJECT_NAME, *DEMO_INSTANCE_NAME); pub static ref DEMO_INSTANCE_NIC_CREATE: params::NetworkInterfaceCreate = params::NetworkInterfaceCreate { identity: IdentityMetadataCreateParams { @@ -1140,7 +1138,7 @@ lazy_static! { allowed_methods: vec![ AllowedMethod::Get, AllowedMethod::Put( - serde_json::to_value(&RouterRouteUpdateParams { + serde_json::to_value(¶ms::RouterRouteUpdate { identity: IdentityMetadataUpdateParams { name: None, description: Some("different".to_string()) @@ -1369,15 +1367,6 @@ lazy_static! { ], }, - VerifyEndpoint { - url: "/by-id/network-interfaces/{id}", - visibility: Visibility::Protected, - unprivileged_access: UnprivilegedAccess::None, - allowed_methods: vec![ - AllowedMethod::Get, - ], - }, - VerifyEndpoint { url: &DEMO_INSTANCE_NIC_URL, visibility: Visibility::Protected, diff --git a/nexus/tests/integration_tests/router_routes.rs b/nexus/tests/integration_tests/router_routes.rs index 5ef1a5af71a..21af569440c 100644 --- a/nexus/tests/integration_tests/router_routes.rs +++ b/nexus/tests/integration_tests/router_routes.rs @@ -11,9 +11,9 @@ use nexus_test_utils::resource_helpers::objects_list_page_authz; use nexus_test_utils_macros::nexus_test; use omicron_common::api::external::{ IdentityMetadataCreateParams, IdentityMetadataUpdateParams, - RouteDestination, RouteTarget, RouterRoute, RouterRouteCreateParams, - RouterRouteKind, RouterRouteUpdateParams, + RouteDestination, RouteTarget, RouterRoute, RouterRouteKind, }; +use omicron_nexus::external_api::params; use std::net::IpAddr; use std::net::Ipv4Addr; @@ -110,7 +110,7 @@ async fn test_router_routes(cptestctx: &ControlPlaneTestContext) { let route_created: RouterRoute = NexusRequest::objects_post( client, get_routes_url(router_name).as_str(), - &RouterRouteCreateParams { + ¶ms::RouterRouteCreate { identity: IdentityMetadataCreateParams { name: route_name.parse().unwrap(), description: "It's a route, what else can I say?".to_string(), @@ -151,7 +151,7 @@ async fn test_router_routes(cptestctx: &ControlPlaneTestContext) { NexusRequest::object_put( client, route_url.as_str(), - Some(&RouterRouteUpdateParams { + Some(¶ms::RouterRouteUpdate { identity: IdentityMetadataUpdateParams { name: Some(route_name.parse().unwrap()), description: None, diff --git a/nexus/tests/integration_tests/vpc_subnets.rs b/nexus/tests/integration_tests/vpc_subnets.rs index 9e0485b11e1..bc922bd3fda 100644 --- a/nexus/tests/integration_tests/vpc_subnets.rs +++ b/nexus/tests/integration_tests/vpc_subnets.rs @@ -43,11 +43,14 @@ async fn test_delete_vpc_subnet_with_interfaces_fails( let _ = create_project(&client, org_name, project_name).await; populate_ip_pool(client, "default", None).await; - let vpcs_url = - format!("/organizations/{}/projects/{}/vpcs", org_name, project_name); - let vpc_url = format!("{vpcs_url}/default"); - let subnets_url = format!("{vpc_url}/subnets"); - let subnet_url = format!("{subnets_url}/default"); + let subnets_url = format!( + "/v1/vpc-subnets?organization={}&project={}&vpc=default", + org_name, project_name + ); + let subnet_url = format!( + "/v1/vpc-subnets/default?organization={}&project={}&vpc=default", + org_name, project_name + ); // get subnets should return the default subnet let subnets = @@ -57,7 +60,7 @@ async fn test_delete_vpc_subnet_with_interfaces_fails( // Create an instance in the default VPC and VPC Subnet. Verify that we // cannot delete the subnet until the instance is gone. let instance_url = format!( - "/organizations/{org_name}/projects/{project_name}/instances/{instance_name}" + "/v1/instances/{instance_name}?organization={org_name}&project={project_name}" ); let instance = create_instance(client, &org_name, project_name, instance_name).await; @@ -92,7 +95,10 @@ async fn test_delete_vpc_subnet_with_interfaces_fails( // Now deleting the subnet should succeed NexusRequest::object_delete( &client, - &format!("{}/{}", subnets_url, subnets[0].identity.name), + &format!( + "/v1/vpc-subnets/{}?organization={org_name}&project={project_name}&vpc=default", + subnets[0].identity.name + ), ) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -111,16 +117,16 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { let org_name = "test-org"; create_organization(&client, &org_name).await; let project_name = "springfield-squidport"; - let vpcs_url = - format!("/organizations/{}/projects/{}/vpcs", org_name, project_name); let _ = create_project(&client, org_name, project_name).await; // Create a VPC. let vpc_name = "vpc1"; let vpc = create_vpc(&client, org_name, project_name, vpc_name).await; - let vpc_url = format!("{}/{}", vpcs_url, vpc_name); - let subnets_url = format!("{}/subnets", vpc_url); + let subnets_url = format!( + "/v1/vpc-subnets?organization={}&project={}&vpc={}", + org_name, project_name, vpc_name + ); // get subnets should return the default subnet let subnets = @@ -130,7 +136,10 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { // delete default subnet NexusRequest::object_delete( &client, - &format!("{}/{}", subnets_url, subnets[0].identity.name), + &format!( + "/v1/vpc-subnets/{}?organization={}&project={}&vpc={}", + subnets[0].identity.name, org_name, project_name, vpc_name + ), ) .authn_as(AuthnMode::PrivilegedUser) .execute() @@ -143,7 +152,10 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { assert_eq!(subnets.len(), 0); let subnet_name = "subnet1"; - let subnet_url = format!("{}/{}", subnets_url, subnet_name); + let subnet_url = format!( + "/v1/vpc-subnets/{}?organization={}&project={}&vpc={}", + subnet_name, org_name, project_name, vpc_name + ); // fetching a particular subnet should 404 let error: dropshot::HttpErrorResponseBody = NexusRequest::expect_failure( @@ -261,7 +273,10 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { assert_eq!(error.message, "already exists: vpc-subnet \"subnet1\""); let subnet2_name = "subnet2"; - let subnet2_url = format!("{}/{}", subnets_url, subnet2_name); + let subnet2_url = format!( + "/v1/vpc-subnets/{}?organization={}&project={}&vpc={}", + subnet2_name, org_name, project_name, vpc_name + ); // second subnet 404s before it's created let error: dropshot::HttpErrorResponseBody = NexusRequest::expect_failure( @@ -337,7 +352,10 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { .unwrap(); assert_eq!(error.message, "not found: vpc-subnet with name \"subnet1\""); - let subnet_url = format!("{}/{}", subnets_url, "new-name"); + let subnet_url = format!( + "/v1/vpc-subnets/new-name?organization={}&project={}&vpc={}", + org_name, project_name, vpc_name + ); // fetching by new name works let updated_subnet: VpcSubnet = @@ -405,7 +423,7 @@ async fn test_vpc_subnets(cptestctx: &ControlPlaneTestContext) { let subnet_same_name: VpcSubnet = NexusRequest::objects_post( client, - format!("{}/{}/subnets", vpcs_url, vpc2_name).as_str(), + format!("/v1/vpc-subnets?organization={org_name}&project={project_name}&vpc={vpc2_name}").as_str(), &new_subnet, ) .authn_as(AuthnMode::PrivilegedUser) diff --git a/nexus/tests/integration_tests/vpcs.rs b/nexus/tests/integration_tests/vpcs.rs index 5e6ffaca928..5023f50ec10 100644 --- a/nexus/tests/integration_tests/vpcs.rs +++ b/nexus/tests/integration_tests/vpcs.rs @@ -23,20 +23,34 @@ use omicron_nexus::external_api::{params, views::Vpc}; type ControlPlaneTestContext = nexus_test_utils::ControlPlaneTestContext; +static ORG_NAME: &str = "test-org"; +static PROJECT_NAME: &str = "springfield-squidport"; +static PROJECT_NAME_2: &str = "peeky-park"; + +fn get_vpc_url(vpc_name: &str) -> String { + format!( + "/v1/vpcs/{vpc_name}?organization={}&project={}", + ORG_NAME, PROJECT_NAME + ) +} + +fn get_subnet_url(vpc_name: &str, subnet_name: &str) -> String { + format!( + "/v1/vpc-subnets/{subnet_name}?organization={}&project={}&vpc={}", + ORG_NAME, PROJECT_NAME, vpc_name + ) +} + #[nexus_test] async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { let client = &cptestctx.external_client; // Create a project that we'll use for testing. - let org_name = "test-org"; - create_organization(&client, &org_name).await; - let project_name = "springfield-squidport"; + create_organization(&client, &ORG_NAME).await; let vpcs_url = - format!("/organizations/{}/projects/{}/vpcs", org_name, project_name); - let _ = create_project(&client, &org_name, &project_name).await; - - let project_name2 = "pokemon"; - let _ = create_project(&client, &org_name, &project_name2).await; + format!("/v1/vpcs?organization={}&project={}", ORG_NAME, PROJECT_NAME); + let _ = create_project(&client, &ORG_NAME, &PROJECT_NAME).await; + let _ = create_project(&client, &ORG_NAME, &PROJECT_NAME_2).await; // List vpcs. We see the default VPC, and nothing else. let mut vpcs = vpcs_list(&client, &vpcs_url).await; @@ -46,7 +60,7 @@ async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { let default_vpc = vpcs.remove(0); // Make sure we get a 404 if we fetch or delete one. - let vpc_url = format!("{}/just-rainsticks", vpcs_url); + let vpc_url = get_vpc_url("just-rainsticks"); for method in &[Method::GET, Method::DELETE] { let error: HttpErrorResponseBody = NexusRequest::expect_failure( client, @@ -88,7 +102,7 @@ async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { // Create a VPC. let vpc_name = "just-rainsticks"; - let vpc = create_vpc(&client, org_name, project_name, vpc_name).await; + let vpc = create_vpc(&client, ORG_NAME, PROJECT_NAME, vpc_name).await; assert_eq!(vpc.identity.name, "just-rainsticks"); assert_eq!(vpc.identity.description, "vpc description"); assert_eq!(vpc.dns_name, "abc"); @@ -105,8 +119,8 @@ async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { // Attempt to create a second VPC with a conflicting name. let error = create_vpc_with_error( &client, - org_name, - project_name, + ORG_NAME, + PROJECT_NAME, vpc_name, StatusCode::BAD_REQUEST, ) @@ -115,7 +129,7 @@ async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { // creating a VPC with the same name in another project works, though let vpc2: Vpc = - create_vpc(&client, org_name, project_name2, vpc_name).await; + create_vpc(&client, ORG_NAME, 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. @@ -157,7 +171,7 @@ async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { assert_eq!(error.message, "not found: vpc with name \"just-rainsticks\""); // new url with new name - let vpc_url = format!("{}/new-name", vpcs_url); + let vpc_url = get_vpc_url("new-name"); // Fetch the VPC again. It should have the updated properties. let vpc = vpc_get(&client, &vpc_url).await; @@ -181,7 +195,7 @@ async fn test_vpcs(cptestctx: &ControlPlaneTestContext) { assert_eq!(error.message, "VPC cannot be deleted while VPC Subnets exist",); // Delete the default VPC Subnet and VPC. - let default_subnet_url = format!("{vpc_url}/subnets/default"); + let default_subnet_url = get_subnet_url("new-name", "default"); NexusRequest::object_delete(client, &default_subnet_url) .authn_as(AuthnMode::PrivilegedUser) .execute() diff --git a/nexus/tests/output/nexus_tags.txt b/nexus/tests/output/nexus_tags.txt index 0b5ca9513e5..fcc6773c166 100644 --- a/nexus/tests/output/nexus_tags.txt +++ b/nexus/tests/output/nexus_tags.txt @@ -47,11 +47,16 @@ instance_list_v1 /v1/instances instance_migrate /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/migrate instance_migrate_v1 /v1/instances/{instance}/migrate instance_network_interface_create /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces +instance_network_interface_create_v1 /v1/network-interfaces instance_network_interface_delete /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name} +instance_network_interface_delete_v1 /v1/network-interfaces/{interface} instance_network_interface_list /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces +instance_network_interface_list_v1 /v1/network-interfaces instance_network_interface_update /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name} +instance_network_interface_update_v1 /v1/network-interfaces/{interface} instance_network_interface_view /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name} instance_network_interface_view_by_id /by-id/network-interfaces/{id} +instance_network_interface_view_v1 /v1/network-interfaces/{interface} instance_reboot /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/reboot instance_reboot_v1 /v1/instances/{instance}/reboot instance_serial_console /organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/serial-console @@ -228,30 +233,50 @@ update_deployments_list /v1/system/update/deployments API operations found with tag "vpcs" OPERATION ID URL PATH vpc_create /organizations/{organization_name}/projects/{project_name}/vpcs +vpc_create_v1 /v1/vpcs vpc_delete /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name} +vpc_delete_v1 /v1/vpcs/{vpc} vpc_firewall_rules_update /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/firewall/rules vpc_firewall_rules_view /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/firewall/rules vpc_list /organizations/{organization_name}/projects/{project_name}/vpcs +vpc_list_v1 /v1/vpcs vpc_router_create /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers +vpc_router_create_v1 /v1/vpc-routers vpc_router_delete /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name} +vpc_router_delete_v1 /v1/vpc-routers/{router} vpc_router_list /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers +vpc_router_list_v1 /v1/vpc-routers vpc_router_route_create /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes +vpc_router_route_create_v1 /v1/vpc-router-routes vpc_router_route_delete /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name} +vpc_router_route_delete_v1 /v1/vpc-router-routes/{route} vpc_router_route_list /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes +vpc_router_route_list_v1 /v1/vpc-router-routes vpc_router_route_update /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name} +vpc_router_route_update_v1 /v1/vpc-router-routes/{route} vpc_router_route_view /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name} vpc_router_route_view_by_id /by-id/vpc-router-routes/{id} +vpc_router_route_view_v1 /v1/vpc-router-routes/{route} vpc_router_update /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name} +vpc_router_update_v1 /v1/vpc-routers/{router} vpc_router_view /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name} vpc_router_view_by_id /by-id/vpc-routers/{id} +vpc_router_view_v1 /v1/vpc-routers/{router} vpc_subnet_create /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets +vpc_subnet_create_v1 /v1/vpc-subnets vpc_subnet_delete /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name} +vpc_subnet_delete_v1 /v1/vpc-subnets/{subnet} vpc_subnet_list /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets vpc_subnet_list_network_interfaces /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}/network-interfaces +vpc_subnet_list_v1 /v1/vpc-subnets vpc_subnet_update /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name} +vpc_subnet_update_v1 /v1/vpc-subnets/{subnet} vpc_subnet_view /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name} vpc_subnet_view_by_id /by-id/vpc-subnets/{id} +vpc_subnet_view_v1 /v1/vpc-subnets/{subnet} vpc_update /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name} +vpc_update_v1 /v1/vpcs/{vpc} vpc_view /organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name} vpc_view_by_id /by-id/vpcs/{id} +vpc_view_v1 /v1/vpcs/{vpc} diff --git a/nexus/tests/output/uncovered-authz-endpoints.txt b/nexus/tests/output/uncovered-authz-endpoints.txt index 56aebbfa55b..748fb93672c 100644 --- a/nexus/tests/output/uncovered-authz-endpoints.txt +++ b/nexus/tests/output/uncovered-authz-endpoints.txt @@ -3,10 +3,16 @@ organization_delete (delete "/organizations/{organization_n project_delete (delete "/organizations/{organization_name}/projects/{project_name}") disk_delete (delete "/organizations/{organization_name}/projects/{project_name}/disks/{disk_name}") instance_delete (delete "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}") +instance_network_interface_delete (delete "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name}") snapshot_delete (delete "/organizations/{organization_name}/projects/{project_name}/snapshots/{snapshot_name}") +vpc_delete (delete "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}") +vpc_router_delete (delete "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}") +vpc_router_route_delete (delete "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}") +vpc_subnet_delete (delete "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}") certificate_delete (delete "/system/certificates/{certificate}") disk_view_by_id (get "/by-id/disks/{id}") instance_view_by_id (get "/by-id/instances/{id}") +instance_network_interface_view_by_id (get "/by-id/network-interfaces/{id}") organization_view_by_id (get "/by-id/organizations/{id}") project_view_by_id (get "/by-id/projects/{id}") snapshot_view_by_id (get "/by-id/snapshots/{id}") @@ -21,11 +27,21 @@ disk_view (get "/organizations/{organization_n instance_list (get "/organizations/{organization_name}/projects/{project_name}/instances") instance_view (get "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}") instance_disk_list (get "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks") +instance_network_interface_list (get "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces") +instance_network_interface_view (get "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name}") instance_serial_console (get "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/serial-console") instance_serial_console_stream (get "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/serial-console/stream") project_policy_view (get "/organizations/{organization_name}/projects/{project_name}/policy") snapshot_list (get "/organizations/{organization_name}/projects/{project_name}/snapshots") snapshot_view (get "/organizations/{organization_name}/projects/{project_name}/snapshots/{snapshot_name}") +vpc_list (get "/organizations/{organization_name}/projects/{project_name}/vpcs") +vpc_view (get "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}") +vpc_router_list (get "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers") +vpc_router_view (get "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}") +vpc_router_route_list (get "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes") +vpc_router_route_view (get "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}") +vpc_subnet_list (get "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets") +vpc_subnet_view (get "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}") policy_view (get "/policy") certificate_list (get "/system/certificates") certificate_view (get "/system/certificates/{certificate}") @@ -52,14 +68,24 @@ instance_create (post "/organizations/{organization_n instance_disk_attach (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks/attach") instance_disk_detach (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/disks/detach") instance_migrate (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/migrate") +instance_network_interface_create (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces") instance_reboot (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/reboot") instance_start (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/start") instance_stop (post "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/stop") snapshot_create (post "/organizations/{organization_name}/projects/{project_name}/snapshots") +vpc_create (post "/organizations/{organization_name}/projects/{project_name}/vpcs") +vpc_router_create (post "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers") +vpc_router_route_create (post "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes") +vpc_subnet_create (post "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets") certificate_create (post "/system/certificates") organization_update (put "/organizations/{organization_name}") organization_policy_update (put "/organizations/{organization_name}/policy") project_update (put "/organizations/{organization_name}/projects/{project_name}") +instance_network_interface_update (put "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name}") project_policy_update (put "/organizations/{organization_name}/projects/{project_name}/policy") +vpc_update (put "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}") +vpc_router_update (put "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}") +vpc_router_route_update (put "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}") +vpc_subnet_update (put "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}") policy_update (put "/policy") system_policy_update (put "/system/policy") diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index bfdd7140fab..fb3aafe6b9a 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -9,7 +9,8 @@ use base64::Engine; use chrono::{DateTime, Utc}; use omicron_common::api::external::{ ByteCount, IdentityMetadataCreateParams, IdentityMetadataUpdateParams, - InstanceCpuCount, Ipv4Net, Ipv6Net, Name, NameOrId, SemverVersion, + InstanceCpuCount, Ipv4Net, Ipv6Net, Name, NameOrId, RouteDestination, + RouteTarget, SemverVersion, }; use schemars::JsonSchema; use serde::{ @@ -35,6 +36,31 @@ pub struct InstancePath { pub instance: NameOrId, } +#[derive(Deserialize, JsonSchema)] +pub struct NetworkInterfacePath { + pub interface: NameOrId, +} + +#[derive(Deserialize, JsonSchema)] +pub struct VpcPath { + pub vpc: NameOrId, +} + +#[derive(Deserialize, JsonSchema)] +pub struct SubnetPath { + pub subnet: NameOrId, +} + +#[derive(Deserialize, JsonSchema)] +pub struct RouterPath { + pub router: NameOrId, +} + +#[derive(Deserialize, JsonSchema)] +pub struct RoutePath { + pub route: NameOrId, +} + #[derive(Serialize, Deserialize, JsonSchema)] pub struct DiskPath { pub disk: NameOrId, @@ -128,7 +154,7 @@ impl SnapshotSelector { } } -#[derive(Deserialize, JsonSchema)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] pub struct InstanceSelector { #[serde(flatten)] pub project_selector: Option, @@ -150,6 +176,141 @@ impl InstanceSelector { } } +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct OptionalInstanceSelector { + #[serde(flatten)] + pub instance_selector: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct NetworkInterfaceSelector { + #[serde(flatten)] + pub instance_selector: Option, + pub network_interface: NameOrId, +} + +// TODO-v1: delete this post migration +impl NetworkInterfaceSelector { + pub fn new( + organization: Option, + project: Option, + instance: Option, + network_interface: NameOrId, + ) -> Self { + NetworkInterfaceSelector { + instance_selector: instance.map(|instance| { + InstanceSelector::new(organization, project, instance) + }), + network_interface, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct VpcSelector { + #[serde(flatten)] + pub project_selector: Option, + pub vpc: NameOrId, +} + +#[derive(Deserialize, JsonSchema)] +pub struct OptionalVpcSelector { + #[serde(flatten)] + pub vpc_selector: Option, +} + +// TODO-v1: delete this post migration +impl VpcSelector { + pub fn new( + organization: Option, + project: Option, + vpc: NameOrId, + ) -> Self { + VpcSelector { + project_selector: project + .map(|p| ProjectSelector::new(organization, p)), + vpc, + } + } +} + +#[derive(Deserialize, JsonSchema)] +pub struct SubnetSelector { + #[serde(flatten)] + pub vpc_selector: Option, + pub subnet: NameOrId, +} + +// TODO-v1: delete this post migration +impl SubnetSelector { + pub fn new( + organization: Option, + project: Option, + vpc: Option, + subnet: NameOrId, + ) -> Self { + SubnetSelector { + vpc_selector: vpc + .map(|vpc| VpcSelector::new(organization, project, vpc)), + subnet, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct RouterSelector { + #[serde(flatten)] + pub vpc_selector: Option, + pub router: NameOrId, +} + +// TODO-v1: delete this post migration +impl RouterSelector { + pub fn new( + organization: Option, + project: Option, + vpc: Option, + router: NameOrId, + ) -> Self { + RouterSelector { + vpc_selector: vpc + .map(|vpc| VpcSelector::new(organization, project, vpc)), + router, + } + } +} + +#[derive(Deserialize, JsonSchema)] +pub struct OptionalRouterSelector { + #[serde(flatten)] + pub router_selector: Option, +} + +#[derive(Deserialize, JsonSchema)] +pub struct RouteSelector { + #[serde(flatten)] + pub router_selector: Option, + pub route: NameOrId, +} + +// TODO-v1: delete this post migration +impl RouteSelector { + pub fn new( + organization: Option, + project: Option, + vpc: Option, + router: Option, + route: NameOrId, + ) -> Self { + RouteSelector { + router_selector: router.map(|router| { + RouterSelector::new(organization, project, vpc, router) + }), + route, + } + } +} + // Silos /// Create-time parameters for a [`Silo`](crate::external_api::views::Silo) @@ -951,6 +1112,26 @@ pub struct VpcRouterUpdate { pub identity: IdentityMetadataUpdateParams, } +// VPC ROUTER ROUTES + +/// Create-time parameters for a [`omicron_common::api::external::RouterRoute`] +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct RouterRouteCreate { + #[serde(flatten)] + pub identity: IdentityMetadataCreateParams, + pub target: RouteTarget, + pub destination: RouteDestination, +} + +/// Updateable properties of a [`omicron_common::api::external::RouterRoute`] +#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)] +pub struct RouterRouteUpdate { + #[serde(flatten)] + pub identity: IdentityMetadataUpdateParams, + pub target: RouteTarget, + pub destination: RouteDestination, +} + // DISKS pub const MIN_DISK_SIZE_BYTES: u32 = 1 << 30; // 1 GiB diff --git a/openapi/nexus.json b/openapi/nexus.json index 968f6b46831..3b5cbe8bbc8 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -290,6 +290,7 @@ "vpcs" ], "summary": "Fetch a route by id", + "description": "Use `GET /v1/vpc-router-routes/{route}` instead", "operationId": "vpc_router_route_view_by_id", "parameters": [ { @@ -319,7 +320,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/by-id/vpc-routers/{id}": { @@ -366,6 +368,7 @@ "vpcs" ], "summary": "Fetch a subnet by id", + "description": "Use `GET /v1/vpc-subnets/{id}` instead", "operationId": "vpc_subnet_view_by_id", "parameters": [ { @@ -395,7 +398,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/by-id/vpcs/{id}": { @@ -404,6 +408,7 @@ "vpcs" ], "summary": "Fetch a VPC", + "description": "Use `GET /v1/vpcs/{id}` instead", "operationId": "vpc_view_by_id", "parameters": [ { @@ -433,7 +438,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/device/auth": { @@ -2565,6 +2571,7 @@ "instances" ], "summary": "Create a network interface", + "description": "Use `POST /v1/network-interfaces` instead", "operationId": "instance_network_interface_create", "parameters": [ { @@ -2619,7 +2626,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/organizations/{organization_name}/projects/{project_name}/instances/{instance_name}/network-interfaces/{interface_name}": { @@ -2628,6 +2636,7 @@ "instances" ], "summary": "Fetch a network interface", + "description": "Use `GET /v1/network-interfaces/{interface}` instead", "operationId": "instance_network_interface_view", "parameters": [ { @@ -2680,13 +2689,15 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true }, "put": { "tags": [ "instances" ], "summary": "Update a network interface", + "description": "Use `PUT /v1/network-interfaces/{interface}` instead", "operationId": "instance_network_interface_update", "parameters": [ { @@ -2749,7 +2760,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true }, "delete": { "tags": [ @@ -3451,6 +3463,7 @@ "vpcs" ], "summary": "List VPCs", + "description": "Use `GET /v1/vpcs` instead", "operationId": "vpc_list", "parameters": [ { @@ -3517,6 +3530,7 @@ "$ref": "#/components/responses/Error" } }, + "deprecated": true, "x-dropshot-pagination": true }, "post": { @@ -3524,6 +3538,7 @@ "vpcs" ], "summary": "Create a VPC", + "description": "Use `POST /v1/vpcs` instead", "operationId": "vpc_create", "parameters": [ { @@ -3572,7 +3587,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}": { @@ -3581,6 +3597,7 @@ "vpcs" ], "summary": "Fetch a VPC", + "description": "Use `GET /v1/vpcs/{vpc}` instead", "operationId": "vpc_view", "parameters": [ { @@ -3625,13 +3642,15 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true }, "put": { "tags": [ "vpcs" ], "summary": "Update a VPC", + "description": "Use `PUT /v1/vpcs/{vpc}` instead", "operationId": "vpc_update", "parameters": [ { @@ -3686,13 +3705,15 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true }, "delete": { "tags": [ "vpcs" ], "summary": "Delete a VPC", + "description": "Use `DELETE /v1/vpcs/{vpc}` instead", "operationId": "vpc_delete", "parameters": [ { @@ -3730,7 +3751,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/firewall/rules": { @@ -3853,6 +3875,7 @@ "vpcs" ], "summary": "List routers", + "description": "Use `GET /v1/vpc-routers` instead", "operationId": "vpc_router_list", "parameters": [ { @@ -3925,6 +3948,7 @@ "$ref": "#/components/responses/Error" } }, + "deprecated": true, "x-dropshot-pagination": true }, "post": { @@ -3932,6 +3956,7 @@ "vpcs" ], "summary": "Create a router", + "description": "Use `POST /v1/vpc-routers` instead", "operationId": "vpc_router_create", "parameters": [ { @@ -3986,7 +4011,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}": { @@ -3995,6 +4021,7 @@ "vpcs" ], "summary": "Get a router", + "description": "Use `GET /v1/vpc-routers/{router}` instead", "operationId": "vpc_router_view", "parameters": [ { @@ -4047,7 +4074,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true }, "put": { "tags": [ @@ -4116,13 +4144,15 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true }, "delete": { "tags": [ "vpcs" ], "summary": "Delete a router", + "description": "Use `DELETE /v1/vpc-routers/{router}` instead", "operationId": "vpc_router_delete", "parameters": [ { @@ -4168,7 +4198,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes": { @@ -4177,7 +4208,7 @@ "vpcs" ], "summary": "List routes", - "description": "List the routes associated with a router in a particular VPC.", + "description": "List the routes associated with a router in a particular VPC. Use `GET /v1/vpc-router-routes` instead.", "operationId": "vpc_router_route_list", "parameters": [ { @@ -4258,6 +4289,7 @@ "$ref": "#/components/responses/Error" } }, + "deprecated": true, "x-dropshot-pagination": true }, "post": { @@ -4265,6 +4297,7 @@ "vpcs" ], "summary": "Create a router", + "description": "Use `POST /v1/vpc-router-routes` instead", "operationId": "vpc_router_route_create", "parameters": [ { @@ -4304,7 +4337,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RouterRouteCreateParams" + "$ref": "#/components/schemas/RouterRouteCreate" } } }, @@ -4327,7 +4360,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/routers/{router_name}/routes/{route_name}": { @@ -4336,6 +4370,7 @@ "vpcs" ], "summary": "Fetch a route", + "description": "Use `GET /v1/vpc-router-routes/{route}` instead", "operationId": "vpc_router_route_view", "parameters": [ { @@ -4396,13 +4431,15 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true }, "put": { "tags": [ "vpcs" ], "summary": "Update a route", + "description": "Use `PUT /v1/vpc-router-routes/{route}` instead", "operationId": "vpc_router_route_update", "parameters": [ { @@ -4450,7 +4487,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RouterRouteUpdateParams" + "$ref": "#/components/schemas/RouterRouteUpdate" } } }, @@ -4473,13 +4510,15 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true }, "delete": { "tags": [ "vpcs" ], "summary": "Delete a route", + "description": "Use `DELETE /v1/vpc-router-routes/{route}` instead", "operationId": "vpc_router_route_delete", "parameters": [ { @@ -4533,7 +4572,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets": { @@ -4542,6 +4582,7 @@ "vpcs" ], "summary": "List subnets", + "description": "Use `GET /v1/vpc-subnets` instead", "operationId": "vpc_subnet_list", "parameters": [ { @@ -4614,6 +4655,7 @@ "$ref": "#/components/responses/Error" } }, + "deprecated": true, "x-dropshot-pagination": true }, "post": { @@ -4621,6 +4663,7 @@ "vpcs" ], "summary": "Create a subnet", + "description": "Use `POST /v1/vpc-subnets` instead", "operationId": "vpc_subnet_create", "parameters": [ { @@ -4675,7 +4718,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}": { @@ -4684,6 +4728,7 @@ "vpcs" ], "summary": "Fetch a subnet", + "description": "Use `GET /v1/vpc-subnets/{subnet}` instead", "operationId": "vpc_subnet_view", "parameters": [ { @@ -4736,13 +4781,15 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true }, "put": { "tags": [ "vpcs" ], "summary": "Update a subnet", + "description": "Use `PUT /v1/vpc-subnets/{subnet}` instead", "operationId": "vpc_subnet_update", "parameters": [ { @@ -4805,13 +4852,15 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true }, "delete": { "tags": [ "vpcs" ], "summary": "Delete a subnet", + "description": "Use `DELETE /v1/vpc-subnets/{subnet}` instead", "operationId": "vpc_subnet_delete", "parameters": [ { @@ -4857,7 +4906,8 @@ "5XX": { "$ref": "#/components/responses/Error" } - } + }, + "deprecated": true } }, "/organizations/{organization_name}/projects/{project_name}/vpcs/{vpc_name}/subnets/{subnet_name}/network-interfaces": { @@ -8699,14 +8749,21 @@ } } }, - "/v1/organizations": { + "/v1/network-interfaces": { "get": { "tags": [ - "organizations" + "instances" ], - "summary": "List organizations", - "operationId": "organization_list_v1", + "summary": "List network interfaces", + "operationId": "instance_network_interface_list_v1", "parameters": [ + { + "in": "query", + "name": "instance", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, { "in": "query", "name": "limit", @@ -8718,6 +8775,13 @@ "minimum": 1 } }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, { "in": "query", "name": "page_token", @@ -8727,6 +8791,13 @@ "type": "string" } }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, { "in": "query", "name": "sort_by", @@ -8741,7 +8812,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OrganizationResultsPage" + "$ref": "#/components/schemas/NetworkInterfaceResultsPage" } } } @@ -8757,15 +8828,39 @@ }, "post": { "tags": [ - "organizations" + "instances" + ], + "summary": "Create a network interface", + "operationId": "instance_network_interface_create_v1", + "parameters": [ + { + "in": "query", + "name": "instance", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } ], - "summary": "Create an organization", - "operationId": "organization_create_v1", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OrganizationCreate" + "$ref": "#/components/schemas/NetworkInterfaceCreate" } } }, @@ -8777,7 +8872,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Organization" + "$ref": "#/components/schemas/NetworkInterface" } } } @@ -8791,21 +8886,42 @@ } } }, - "/v1/organizations/{organization}": { + "/v1/network-interfaces/{interface}": { "get": { "tags": [ - "organizations" + "instances" ], - "summary": "Fetch an organization", - "operationId": "organization_view_v1", + "summary": "Fetch a network interface", + "operationId": "instance_network_interface_view_v1", "parameters": [ { "in": "path", - "name": "organization", + "name": "interface", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" } + }, + { + "in": "query", + "name": "instance", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } } ], "responses": { @@ -8814,7 +8930,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Organization" + "$ref": "#/components/schemas/NetworkInterface" } } } @@ -8829,25 +8945,46 @@ }, "put": { "tags": [ - "organizations" + "instances" ], - "summary": "Update an organization", - "operationId": "organization_update_v1", + "summary": "Update a network interface", + "operationId": "instance_network_interface_update_v1", "parameters": [ { "in": "path", - "name": "organization", + "name": "interface", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" } + }, + { + "in": "query", + "name": "instance", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } } ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OrganizationUpdate" + "$ref": "#/components/schemas/NetworkInterfaceUpdate" } } }, @@ -8859,7 +8996,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Organization" + "$ref": "#/components/schemas/NetworkInterface" } } } @@ -8874,18 +9011,40 @@ }, "delete": { "tags": [ - "organizations" + "instances" ], - "summary": "Delete an organization", - "operationId": "organization_delete_v1", + "summary": "Delete a network interface", + "description": "Note that the primary interface for an instance cannot be deleted if there are any secondary interfaces. A new primary interface must be designated first. The primary interface can be deleted if there are no secondary interfaces.", + "operationId": "instance_network_interface_delete_v1", "parameters": [ { "in": "path", - "name": "organization", + "name": "interface", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" } + }, + { + "in": "query", + "name": "instance", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } } ], "responses": { @@ -8901,20 +9060,39 @@ } } }, - "/v1/organizations/{organization}/policy": { + "/v1/organizations": { "get": { "tags": [ "organizations" ], - "summary": "Fetch an organization's IAM policy", - "operationId": "organization_policy_view_v1", + "summary": "List organizations", + "operationId": "organization_list_v1", "parameters": [ { - "in": "path", - "name": "organization", - "required": true, + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", "schema": { - "$ref": "#/components/schemas/NameOrId" + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/NameOrIdSortMode" } } ], @@ -8924,7 +9102,190 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OrganizationRolePolicy" + "$ref": "#/components/schemas/OrganizationResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": true + }, + "post": { + "tags": [ + "organizations" + ], + "summary": "Create an organization", + "operationId": "organization_create_v1", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Organization" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/organizations/{organization}": { + "get": { + "tags": [ + "organizations" + ], + "summary": "Fetch an organization", + "operationId": "organization_view_v1", + "parameters": [ + { + "in": "path", + "name": "organization", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Organization" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "tags": [ + "organizations" + ], + "summary": "Update an organization", + "operationId": "organization_update_v1", + "parameters": [ + { + "in": "path", + "name": "organization", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Organization" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "organizations" + ], + "summary": "Delete an organization", + "operationId": "organization_delete_v1", + "parameters": [ + { + "in": "path", + "name": "organization", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/organizations/{organization}/policy": { + "get": { + "tags": [ + "organizations" + ], + "summary": "Fetch an organization's IAM policy", + "operationId": "organization_policy_view_v1", + "parameters": [ + { + "in": "path", + "name": "organization", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrganizationRolePolicy" } } } @@ -10619,6 +10980,1249 @@ } } } + }, + "/v1/vpc-router-routes": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "List the routes associated with a router in a particular VPC.", + "operationId": "vpc_router_route_list_v1", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "router", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/NameOrIdSortMode" + } + }, + { + "in": "query", + "name": "vpc", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RouterRouteResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": true + }, + "post": { + "tags": [ + "vpcs" + ], + "summary": "Create a router", + "operationId": "vpc_router_route_create_v1", + "parameters": [ + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "router", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RouterRouteCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RouterRoute" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/vpc-router-routes/{route}": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "Fetch a route", + "operationId": "vpc_router_route_view_v1", + "parameters": [ + { + "in": "path", + "name": "route", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "router", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RouterRoute" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "tags": [ + "vpcs" + ], + "summary": "Update a route", + "operationId": "vpc_router_route_update_v1", + "parameters": [ + { + "in": "path", + "name": "route", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "router", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RouterRouteUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RouterRoute" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "vpcs" + ], + "summary": "Delete a route", + "operationId": "vpc_router_route_delete_v1", + "parameters": [ + { + "in": "path", + "name": "route", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "router", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/vpc-routers": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "List routers", + "operationId": "vpc_router_list_v1", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/NameOrIdSortMode" + } + }, + { + "in": "query", + "name": "vpc", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcRouterResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": true + }, + "post": { + "tags": [ + "vpcs" + ], + "summary": "Create a VPC router", + "operationId": "vpc_router_create_v1", + "parameters": [ + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcRouterCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcRouter" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/vpc-routers/{router}": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "Get a router", + "operationId": "vpc_router_view_v1", + "parameters": [ + { + "in": "path", + "name": "router", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcRouter" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "tags": [ + "vpcs" + ], + "summary": "Update a router", + "operationId": "vpc_router_update_v1", + "parameters": [ + { + "in": "path", + "name": "router", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcRouterUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcRouter" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "vpcs" + ], + "summary": "Delete a router", + "operationId": "vpc_router_delete_v1", + "parameters": [ + { + "in": "path", + "name": "router", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/vpc-subnets": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "Fetch a subnet", + "operationId": "vpc_subnet_list_v1", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/NameOrIdSortMode" + } + }, + { + "in": "query", + "name": "vpc", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcSubnetResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": true + }, + "post": { + "tags": [ + "vpcs" + ], + "summary": "Create a subnet", + "operationId": "vpc_subnet_create_v1", + "parameters": [ + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcSubnetCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcSubnet" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/vpc-subnets/{subnet}": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "Fetch a subnet", + "operationId": "vpc_subnet_view_v1", + "parameters": [ + { + "in": "path", + "name": "subnet", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcSubnet" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "tags": [ + "vpcs" + ], + "summary": "Update a subnet", + "operationId": "vpc_subnet_update_v1", + "parameters": [ + { + "in": "path", + "name": "subnet", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcSubnetUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcSubnet" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "vpcs" + ], + "summary": "Delete a subnet", + "operationId": "vpc_subnet_delete_v1", + "parameters": [ + { + "in": "path", + "name": "subnet", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/vpcs": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "List VPCs", + "operationId": "vpc_list_v1", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/NameOrIdSortMode" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": true + }, + "post": { + "tags": [ + "vpcs" + ], + "summary": "Create a VPC", + "operationId": "vpc_create_v1", + "parameters": [ + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Vpc" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/vpcs/{vpc}": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "Fetch a VPC", + "operationId": "vpc_view_v1", + "parameters": [ + { + "in": "path", + "name": "vpc", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Vpc" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "tags": [ + "vpcs" + ], + "summary": "Update a VPC", + "operationId": "vpc_update_v1", + "parameters": [ + { + "in": "path", + "name": "vpc", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Vpc" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "vpcs" + ], + "summary": "Delete a VPC", + "operationId": "vpc_delete_v1", + "parameters": [ + { + "in": "path", + "name": "vpc", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "organization", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } } }, "components": { @@ -14066,8 +15670,8 @@ "vpc_router_id" ] }, - "RouterRouteCreateParams": { - "description": "Create-time parameters for a [`RouterRoute`]", + "RouterRouteCreate": { + "description": "Create-time parameters for a [`omicron_common::api::external::RouterRoute`]", "type": "object", "properties": { "description": { @@ -14144,8 +15748,8 @@ "items" ] }, - "RouterRouteUpdateParams": { - "description": "Updateable properties of a [`RouterRoute`]", + "RouterRouteUpdate": { + "description": "Updateable properties of a [`omicron_common::api::external::RouterRoute`]", "type": "object", "properties": { "description": {