diff --git a/nexus/db-model/src/internet_gateway.rs b/nexus/db-model/src/internet_gateway.rs index 175ae682066..9c5389bfcec 100644 --- a/nexus/db-model/src/internet_gateway.rs +++ b/nexus/db-model/src/internet_gateway.rs @@ -2,9 +2,10 @@ use super::Generation; use crate::schema::{ internet_gateway, internet_gateway_ip_address, internet_gateway_ip_pool, }; +use crate::DatastoreCollectionConfig; use db_macros::Resource; use ipnetwork::IpNetwork; -use nexus_types::external_api::views; +use nexus_types::external_api::{params, views}; use nexus_types::identity::Resource; use uuid::Uuid; @@ -19,6 +20,18 @@ pub struct InternetGateway { pub resolved_version: i64, } +impl InternetGateway { + pub fn new( + gateway_id: Uuid, + vpc_id: Uuid, + params: params::InternetGatewayCreate, + ) -> Self { + let identity = + InternetGatewayIdentity::new(gateway_id, params.identity); + Self { identity, vpc_id, rcgen: Generation::new(), resolved_version: 0 } + } +} + impl From for views::InternetGateway { fn from(value: InternetGateway) -> Self { Self { identity: value.identity(), vpc_id: value.vpc_id } @@ -29,11 +42,24 @@ impl From for views::InternetGateway { #[diesel(table_name = internet_gateway_ip_pool)] pub struct InternetGatewayIpPool { #[diesel(embed)] - pub identity: InternetGatewayIpPoolIdentity, + identity: InternetGatewayIpPoolIdentity, + pub internet_gateway_id: Uuid, pub ip_pool_id: Uuid, } +impl InternetGatewayIpPool { + pub fn new( + pool_id: Uuid, + internet_gateway_id: Uuid, + params: params::InternetGatewayIpPoolCreate, + ) -> Self { + let identity = + InternetGatewayIpPoolIdentity::new(pool_id, params.identity); + Self { identity, internet_gateway_id, ip_pool_id: params.ip_pool_id } + } +} + impl From for views::InternetGatewayIpPool { fn from(value: InternetGatewayIpPool) -> Self { Self { @@ -48,11 +74,28 @@ impl From for views::InternetGatewayIpPool { #[diesel(table_name = internet_gateway_ip_address)] pub struct InternetGatewayIpAddress { #[diesel(embed)] - pub identity: InternetGatewayIpAddressIdentity, + identity: InternetGatewayIpAddressIdentity, + pub internet_gateway_id: Uuid, pub address: IpNetwork, } +impl InternetGatewayIpAddress { + pub fn new( + pool_id: Uuid, + internet_gateway_id: Uuid, + params: params::InternetGatewayIpAddressCreate, + ) -> Self { + let identity = + InternetGatewayIpAddressIdentity::new(pool_id, params.identity); + Self { + identity, + internet_gateway_id, + address: IpNetwork::from(params.address), + } + } +} + impl From for views::InternetGatewayIpAddress { fn from(value: InternetGatewayIpAddress) -> Self { Self { @@ -62,3 +105,19 @@ impl From for views::InternetGatewayIpAddress { } } } + +impl DatastoreCollectionConfig for InternetGateway { + type CollectionId = Uuid; + type GenerationNumberColumn = internet_gateway::dsl::rcgen; + type CollectionTimeDeletedColumn = internet_gateway::dsl::time_deleted; + type CollectionIdColumn = + internet_gateway_ip_pool::dsl::internet_gateway_id; +} + +impl DatastoreCollectionConfig for InternetGateway { + type CollectionId = Uuid; + type GenerationNumberColumn = internet_gateway::dsl::rcgen; + type CollectionTimeDeletedColumn = internet_gateway::dsl::time_deleted; + type CollectionIdColumn = + internet_gateway_ip_address::dsl::internet_gateway_id; +} diff --git a/nexus/db-model/src/schema.rs b/nexus/db-model/src/schema.rs index ad2b9a8eca5..adb88bb1bed 100644 --- a/nexus/db-model/src/schema.rs +++ b/nexus/db-model/src/schema.rs @@ -1158,7 +1158,7 @@ table! { } table! { - internet_gateway_ip_pool(internet_gateway_id, ip_pool_id) { + internet_gateway_ip_pool(id) { id -> Uuid, name -> Text, description -> Text, @@ -1171,7 +1171,7 @@ table! { } table! { - internet_gateway_ip_address(internet_gateway_id, address) { + internet_gateway_ip_address(id) { id -> Uuid, name -> Text, description -> Text, diff --git a/nexus/db-queries/src/db/datastore/vpc.rs b/nexus/db-queries/src/db/datastore/vpc.rs index b21a7cf6aa0..cbf70adec31 100644 --- a/nexus/db-queries/src/db/datastore/vpc.rs +++ b/nexus/db-queries/src/db/datastore/vpc.rs @@ -51,6 +51,9 @@ use diesel::result::Error as DieselError; use futures::stream::{self, StreamExt}; use ipnetwork::IpNetwork; use nexus_db_fixed_data::vpc::SERVICES_VPC_ID; +use nexus_db_model::InternetGateway; +use nexus_db_model::InternetGatewayIpAddress; +use nexus_db_model::InternetGatewayIpPool; use nexus_types::deployment::BlueprintZoneFilter; use nexus_types::deployment::SledFilter; use omicron_common::api::external::http_pagination::PaginatedBy; @@ -1082,6 +1085,93 @@ impl DataStore { .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) } + pub async fn internet_gateway_list( + &self, + opctx: &OpContext, + authz_vpc: &authz::Vpc, + pagparams: &PaginatedBy<'_>, + ) -> ListResultVec { + opctx.authorize(authz::Action::ListChildren, authz_vpc).await?; + + use db::schema::internet_gateway::dsl; + match pagparams { + PaginatedBy::Id(pagparams) => { + paginated(dsl::internet_gateway, dsl::id, pagparams) + } + PaginatedBy::Name(pagparams) => paginated( + dsl::internet_gateway, + 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(InternetGateway::as_select()) + .load_async::( + &*self.pool_connection_authorized(opctx).await?, + ) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) + } + + pub async fn internet_gateway_list_ip_pools( + &self, + opctx: &OpContext, + authz_igw: &authz::InternetGateway, + pagparams: &PaginatedBy<'_>, + ) -> ListResultVec { + opctx.authorize(authz::Action::ListChildren, authz_igw).await?; + + use db::schema::internet_gateway_ip_pool::dsl; + match pagparams { + PaginatedBy::Id(pagparams) => { + paginated(dsl::internet_gateway_ip_pool, dsl::id, pagparams) + } + PaginatedBy::Name(pagparams) => paginated( + dsl::internet_gateway_ip_pool, + dsl::name, + &pagparams.map_name(|n| Name::ref_cast(n)), + ), + } + .filter(dsl::time_deleted.is_null()) + .filter(dsl::internet_gateway_id.eq(authz_igw.id())) + .select(InternetGatewayIpPool::as_select()) + .load_async::( + &*self.pool_connection_authorized(opctx).await?, + ) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) + } + + pub async fn internet_gateway_list_ip_addresses( + &self, + opctx: &OpContext, + authz_igw: &authz::InternetGateway, + pagparams: &PaginatedBy<'_>, + ) -> ListResultVec { + opctx.authorize(authz::Action::ListChildren, authz_igw).await?; + + use db::schema::internet_gateway_ip_address::dsl; + match pagparams { + PaginatedBy::Id(pagparams) => { + paginated(dsl::internet_gateway_ip_address, dsl::id, pagparams) + } + PaginatedBy::Name(pagparams) => paginated( + dsl::internet_gateway_ip_address, + dsl::name, + &pagparams.map_name(|n| Name::ref_cast(n)), + ), + } + .filter(dsl::time_deleted.is_null()) + .filter(dsl::internet_gateway_id.eq(authz_igw.id())) + .select(InternetGatewayIpAddress::as_select()) + .load_async::( + &*self.pool_connection_authorized(opctx).await?, + ) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server)) + } + pub async fn vpc_create_router( &self, opctx: &OpContext, @@ -1118,6 +1208,42 @@ impl DataStore { )) } + pub async fn vpc_create_internet_gateway( + &self, + opctx: &OpContext, + authz_vpc: &authz::Vpc, + igw: InternetGateway, + ) -> CreateResult<(authz::InternetGateway, InternetGateway)> { + opctx.authorize(authz::Action::CreateChild, authz_vpc).await?; + + use db::schema::internet_gateway::dsl; + let name = igw.name().clone(); + let igw = diesel::insert_into(dsl::internet_gateway) + .values(igw) + .on_conflict(dsl::id) + .do_nothing() + .returning(InternetGateway::as_returning()) + .get_result_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| { + public_error_from_diesel( + e, + ErrorHandler::Conflict( + ResourceType::InternetGateway, + name.as_str(), + ), + ) + })?; + Ok(( + authz::InternetGateway::new( + authz_vpc.clone(), + igw.id(), + LookupType::ById(igw.id()), + ), + igw, + )) + } + pub async fn vpc_delete_router( &self, opctx: &OpContext, @@ -1168,6 +1294,53 @@ impl DataStore { Ok(()) } + pub async fn vpc_delete_internet_gateway( + &self, + opctx: &OpContext, + authz_igw: &authz::InternetGateway, + ) -> DeleteResult { + opctx.authorize(authz::Action::Delete, authz_igw).await?; + + use db::schema::internet_gateway::dsl; + let now = Utc::now(); + diesel::update(dsl::internet_gateway) + .filter(dsl::time_deleted.is_null()) + .filter(dsl::id.eq(authz_igw.id())) + .set(dsl::time_deleted.eq(now)) + .execute_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| { + public_error_from_diesel( + e, + ErrorHandler::NotFoundByResource(authz_igw), + ) + })?; + + // Delete ip pool associations + use db::schema::internet_gateway_ip_pool::dsl as pool; + let now = Utc::now(); + diesel::update(pool::internet_gateway_ip_pool) + .filter(pool::time_deleted.is_null()) + .filter(pool::internet_gateway_id.eq(authz_igw.id())) + .set(pool::time_deleted.eq(now)) + .execute_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + // Delete ip address associations + use db::schema::internet_gateway_ip_address::dsl as addr; + let now = Utc::now(); + diesel::update(addr::internet_gateway_ip_address) + .filter(addr::time_deleted.is_null()) + .filter(addr::internet_gateway_id.eq(authz_igw.id())) + .set(addr::time_deleted.eq(now)) + .execute_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))?; + + Ok(()) + } + pub async fn vpc_update_router( &self, opctx: &OpContext, @@ -1266,6 +1439,76 @@ impl DataStore { }) } + pub async fn internet_gateway_attach_ip_pool( + &self, + opctx: &OpContext, + authz_igw: &authz::InternetGateway, + igwip: InternetGatewayIpPool, + ) -> CreateResult { + use db::schema::internet_gateway_ip_pool::dsl; + opctx.authorize(authz::Action::CreateChild, authz_igw).await?; + + let igw_id = igwip.internet_gateway_id; + let name = igwip.name().clone(); + + InternetGateway::insert_resource( + igw_id, + diesel::insert_into(dsl::internet_gateway_ip_pool).values(igwip), + ) + .insert_and_get_result_async( + &*self.pool_connection_authorized(opctx).await?, + ) + .await + .map_err(|e| match e { + AsyncInsertError::CollectionNotFound => Error::ObjectNotFound { + type_name: ResourceType::InternetGateway, + lookup_type: LookupType::ById(igw_id), + }, + AsyncInsertError::DatabaseError(e) => public_error_from_diesel( + e, + ErrorHandler::Conflict( + ResourceType::InternetGatewayIpPool, + name.as_str(), + ), + ), + }) + } + + pub async fn internet_gateway_attach_ip_address( + &self, + opctx: &OpContext, + authz_igw: &authz::InternetGateway, + igwip: InternetGatewayIpAddress, + ) -> CreateResult { + use db::schema::internet_gateway_ip_address::dsl; + opctx.authorize(authz::Action::CreateChild, authz_igw).await?; + + let igw_id = igwip.internet_gateway_id; + let name = igwip.name().clone(); + + InternetGateway::insert_resource( + igw_id, + diesel::insert_into(dsl::internet_gateway_ip_address).values(igwip), + ) + .insert_and_get_result_async( + &*self.pool_connection_authorized(opctx).await?, + ) + .await + .map_err(|e| match e { + AsyncInsertError::CollectionNotFound => Error::ObjectNotFound { + type_name: ResourceType::InternetGateway, + lookup_type: LookupType::ById(igw_id), + }, + AsyncInsertError::DatabaseError(e) => public_error_from_diesel( + e, + ErrorHandler::Conflict( + ResourceType::InternetGatewayIpAddress, + name.as_str(), + ), + ), + }) + } + pub async fn router_delete_route( &self, opctx: &OpContext, @@ -1290,6 +1533,54 @@ impl DataStore { Ok(()) } + pub async fn internet_gateway_detach_ip_pool( + &self, + opctx: &OpContext, + authz_pool: &authz::InternetGatewayIpPool, + ) -> DeleteResult { + opctx.authorize(authz::Action::Delete, authz_pool).await?; + + use db::schema::internet_gateway_ip_pool::dsl; + let now = Utc::now(); + diesel::update(dsl::internet_gateway_ip_pool) + .filter(dsl::time_deleted.is_null()) + .filter(dsl::id.eq(authz_pool.id())) + .set(dsl::time_deleted.eq(now)) + .execute_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| { + public_error_from_diesel( + e, + ErrorHandler::NotFoundByResource(authz_pool), + ) + })?; + Ok(()) + } + + pub async fn internet_gateway_detach_ip_address( + &self, + opctx: &OpContext, + authz_addr: &authz::InternetGatewayIpAddress, + ) -> DeleteResult { + opctx.authorize(authz::Action::Delete, authz_addr).await?; + + use db::schema::internet_gateway_ip_address::dsl; + let now = Utc::now(); + diesel::update(dsl::internet_gateway_ip_address) + .filter(dsl::time_deleted.is_null()) + .filter(dsl::id.eq(authz_addr.id())) + .set(dsl::time_deleted.eq(now)) + .execute_async(&*self.pool_connection_authorized(opctx).await?) + .await + .map_err(|e| { + public_error_from_diesel( + e, + ErrorHandler::NotFoundByResource(authz_addr), + ) + })?; + Ok(()) + } + pub async fn router_update_route( &self, opctx: &OpContext, diff --git a/nexus/db-queries/src/db/lookup.rs b/nexus/db-queries/src/db/lookup.rs index 0b09b1bb33a..bc1368820ce 100644 --- a/nexus/db-queries/src/db/lookup.rs +++ b/nexus/db-queries/src/db/lookup.rs @@ -227,11 +227,32 @@ impl<'a> LookupPath<'a> { VpcRouter::PrimaryKey(Root { lookup_root: self }, id) } + /// Select a resource of type InternetGateway, identified by its id + pub fn internet_gateway_id(self, id: Uuid) -> InternetGateway<'a> { + InternetGateway::PrimaryKey(Root { lookup_root: self }, id) + } + /// Select a resource of type RouterRoute, identified by its id pub fn router_route_id(self, id: Uuid) -> RouterRoute<'a> { RouterRoute::PrimaryKey(Root { lookup_root: self }, id) } + /// Select a resource of type InternetGatewayIpPool, identified by its id + pub fn internet_gateway_ip_pool_id( + self, + id: Uuid, + ) -> InternetGatewayIpPool<'a> { + InternetGatewayIpPool::PrimaryKey(Root { lookup_root: self }, id) + } + + /// Select a resource of type InternetGatewayIpAddress, identified by its id + pub fn internet_gateway_ip_address_id( + self, + id: Uuid, + ) -> InternetGatewayIpAddress<'a> { + InternetGatewayIpAddress::PrimaryKey(Root { lookup_root: self }, id) + } + /// Select a resource of type FloatingIp, identified by its id pub fn floating_ip_id(self, id: Uuid) -> FloatingIp<'a> { FloatingIp::PrimaryKey(Root { lookup_root: self }, id) @@ -686,7 +707,7 @@ lookup_resource! { lookup_resource! { name = "Vpc", ancestors = [ "Silo", "Project" ], - children = [ "VpcRouter", "VpcSubnet" ], + children = [ "VpcRouter", "VpcSubnet", "InternetGateway" ], lookup_by_name = true, soft_deletes = true, primary_key_columns = [ { column_name = "id", rust_type = Uuid } ] @@ -722,7 +743,7 @@ lookup_resource! { lookup_resource! { name = "InternetGateway", ancestors = [ "Silo", "Project", "Vpc" ], - children = [ ], + children = [ "InternetGatewayIpPool", "InternetGatewayIpAddress" ], lookup_by_name = true, soft_deletes = true, primary_key_columns = [ { column_name = "id", rust_type = Uuid } ] diff --git a/nexus/src/app/internet_gateway.rs b/nexus/src/app/internet_gateway.rs index f1f4c0fc6f1..dd11084d1eb 100644 --- a/nexus/src/app/internet_gateway.rs +++ b/nexus/src/app/internet_gateway.rs @@ -5,116 +5,311 @@ //! Internet gateways use crate::external_api::params; +use nexus_auth::authz; use nexus_auth::context::OpContext; use nexus_db_queries::db; use nexus_db_queries::db::lookup; +use nexus_db_queries::db::lookup::LookupPath; use omicron_common::api::external::http_pagination::PaginatedBy; 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 uuid::Uuid; impl super::Nexus { //Internet gateways pub fn internet_gateway_lookup<'a>( &'a self, - _opctx: &'a OpContext, - _igw_selector: params::InternetGatewaySelector, + opctx: &'a OpContext, + igw_selector: params::InternetGatewaySelector, ) -> LookupResult> { - todo!(); + match igw_selector { + params::InternetGatewaySelector { + gateway: NameOrId::Id(id), + vpc: None, + project: None + } => { + let gw = LookupPath::new(opctx, &self.db_datastore) + .internet_gateway_id(id); + Ok(gw) + } + params::InternetGatewaySelector { + gateway: NameOrId::Name(name), + vpc: Some(vpc), + project + } => { + let gw = self + .vpc_lookup(opctx, params::VpcSelector { project, vpc })? + .internet_gateway_name_owned(name.into()); + Ok(gw) + } + params::InternetGatewaySelector { + gateway: NameOrId::Id(_), + .. + } => Err(Error::invalid_request( + "when providing gateway as an ID vpc and project should not be specified", + )), + _ => Err(Error::invalid_request( + "gateway should either be an ID or vpc should be specified", + )), + } } pub(crate) async fn internet_gateway_create( &self, - _opctx: &OpContext, - _vpc_lookup: &lookup::Vpc<'_>, - _params: ¶ms::InternetGatewayCreate, + opctx: &OpContext, + vpc_lookup: &lookup::Vpc<'_>, + params: ¶ms::InternetGatewayCreate, ) -> CreateResult { - todo!(); + let (.., authz_vpc) = + vpc_lookup.lookup_for(authz::Action::CreateChild).await?; + let id = Uuid::new_v4(); + let router = + db::model::InternetGateway::new(id, authz_vpc.id(), params.clone()); + let (_, router) = self + .db_datastore + .vpc_create_internet_gateway(&opctx, &authz_vpc, router) + .await?; + + Ok(router) } pub(crate) async fn internet_gateway_list( &self, - _opctx: &OpContext, - _vpc_lookup: &lookup::Vpc<'_>, - _pagparams: &PaginatedBy<'_>, + opctx: &OpContext, + vpc_lookup: &lookup::Vpc<'_>, + pagparams: &PaginatedBy<'_>, ) -> ListResultVec { - todo!(); + let (.., authz_vpc) = + vpc_lookup.lookup_for(authz::Action::ListChildren).await?; + let igws = self + .db_datastore + .internet_gateway_list(opctx, &authz_vpc, pagparams) + .await?; + Ok(igws) } pub(crate) async fn internet_gateway_delete( &self, - _opctx: &OpContext, - _lookup: &lookup::InternetGateway<'_>, + opctx: &OpContext, + lookup: &lookup::InternetGateway<'_>, ) -> DeleteResult { - todo!(); + let (.., authz_router, _db_igw) = + lookup.fetch_for(authz::Action::Delete).await?; + let out = self + .db_datastore + .vpc_delete_internet_gateway(opctx, &authz_router) + .await?; + + self.vpc_needed_notify_sleds(); + + Ok(out) } pub(crate) async fn internet_gateway_ip_pool_list( &self, - _opctx: &OpContext, - _gateway_lookup: &lookup::InternetGateway<'_>, - _pagparams: &PaginatedBy<'_>, + opctx: &OpContext, + gateway_lookup: &lookup::InternetGateway<'_>, + pagparams: &PaginatedBy<'_>, ) -> ListResultVec { - todo!(); + let (.., authz_vpc) = + gateway_lookup.lookup_for(authz::Action::ListChildren).await?; + let pools = self + .db_datastore + .internet_gateway_list_ip_pools(opctx, &authz_vpc, pagparams) + .await?; + Ok(pools) } pub fn internet_gateway_ip_pool_lookup<'a>( &'a self, - _opctx: &'a OpContext, - _route_selector: params::InternetGatewayIpPoolSelector, + opctx: &'a OpContext, + pool_selector: params::InternetGatewayIpPoolSelector, ) -> LookupResult> { - todo!(); + match pool_selector { + params::InternetGatewayIpPoolSelector { + pool: NameOrId::Id(id), + gateway: None, + vpc: None, + project: None, + } => { + let route = LookupPath::new(opctx, &self.db_datastore) + .internet_gateway_ip_pool_id(id); + Ok(route) + } + params::InternetGatewayIpPoolSelector { + pool: NameOrId::Name(name), + gateway: Some(gateway), + vpc, + project, + } => { + let route = self + .internet_gateway_lookup( + opctx, + params::InternetGatewaySelector { project, vpc, gateway }, + )? + .internet_gateway_ip_pool_name_owned(name.into()); + Ok(route) + } + params::InternetGatewayIpPoolSelector { + pool: NameOrId::Id(_), + .. + } => Err(Error::invalid_request( + "when providing pool as an ID gateway, subnet, vpc, and project should not be specified", + )), + _ => Err(Error::invalid_request( + "pool should either be an ID or gateway should be specified", + )), + } } pub(crate) async fn internet_gateway_ip_pool_attach( &self, - _opctx: &OpContext, - _lookup: &lookup::InternetGateway<'_>, - _params: ¶ms::InternetGatewayIpPoolCreate, + opctx: &OpContext, + lookup: &lookup::InternetGateway<'_>, + params: ¶ms::InternetGatewayIpPoolCreate, ) -> CreateResult { - todo!(); + let (.., authz_igw, _db_pool) = + lookup.fetch_for(authz::Action::CreateChild).await?; + + let id = Uuid::new_v4(); + let route = db::model::InternetGatewayIpPool::new( + id, + authz_igw.id(), + params.clone(), + ); + let route = self + .db_datastore + .internet_gateway_attach_ip_pool(&opctx, &authz_igw, route) + .await?; + + //TODO trigger igw rpw + //self.vpc_igw_increment_rpw_version(opctx, &authz_igw).await?; + + Ok(route) } pub(crate) async fn internet_gateway_ip_pool_detach( &self, - _opctx: &OpContext, - _route_lookup: &lookup::InternetGatewayIpPool<'_>, + opctx: &OpContext, + lookup: &lookup::InternetGatewayIpPool<'_>, ) -> DeleteResult { - todo!(); - } + let (.., _authz_igw, authz_pool, _db_pool) = + lookup.fetch_for(authz::Action::Delete).await?; - pub fn internet_gateway_ip_address_lookup<'a>( - &'a self, - _opctx: &'a OpContext, - _route_selector: params::InternetGatewayIpAddressSelector, - ) -> LookupResult> { - todo!(); + let out = self + .db_datastore + .internet_gateway_detach_ip_pool(opctx, &authz_pool) + .await?; + + //TODO trigger igw rpw + //self.vpc_igw_increment_rpw_version(opctx, &authz_igw).await?; + + Ok(out) } pub(crate) async fn internet_gateway_ip_address_list( &self, - _opctx: &OpContext, - _gateway_lookup: &lookup::InternetGateway<'_>, - _pagparams: &PaginatedBy<'_>, + opctx: &OpContext, + gateway_lookup: &lookup::InternetGateway<'_>, + pagparams: &PaginatedBy<'_>, ) -> ListResultVec { - todo!(); + let (.., authz_vpc) = + gateway_lookup.lookup_for(authz::Action::ListChildren).await?; + let pools = self + .db_datastore + .internet_gateway_list_ip_addresses(opctx, &authz_vpc, pagparams) + .await?; + Ok(pools) + } + + pub fn internet_gateway_ip_address_lookup<'a>( + &'a self, + opctx: &'a OpContext, + address_selector: params::InternetGatewayIpAddressSelector, + ) -> LookupResult> { + match address_selector { + params::InternetGatewayIpAddressSelector { + address: NameOrId::Id(id), + gateway: None, + vpc: None, + project: None, + } => { + let route = LookupPath::new(opctx, &self.db_datastore) + .internet_gateway_ip_address_id(id); + Ok(route) + } + params::InternetGatewayIpAddressSelector { + address: NameOrId::Name(name), + gateway: Some(gateway), + vpc, + project, + } => { + let route = self + .internet_gateway_lookup( + opctx, + params::InternetGatewaySelector { project, vpc, gateway }, + )? + .internet_gateway_ip_address_name_owned(name.into()); + Ok(route) + } + params::InternetGatewayIpAddressSelector { + address: NameOrId::Id(_), + .. + } => Err(Error::invalid_request( + "when providing address as an ID gateway, subnet, vpc, and project should not be specified", + )), + _ => Err(Error::invalid_request( + "address should either be an ID or gateway should be specified", + )), + } } pub(crate) async fn internet_gateway_ip_address_attach( &self, - _opctx: &OpContext, - _lookup: &lookup::InternetGateway<'_>, - _params: ¶ms::InternetGatewayIpAddressCreate, + opctx: &OpContext, + lookup: &lookup::InternetGateway<'_>, + params: ¶ms::InternetGatewayIpAddressCreate, ) -> CreateResult { - todo!(); + let (.., authz_igw, _db_addr) = + lookup.fetch_for(authz::Action::CreateChild).await?; + + let id = Uuid::new_v4(); + let route = db::model::InternetGatewayIpAddress::new( + id, + authz_igw.id(), + params.clone(), + ); + let route = self + .db_datastore + .internet_gateway_attach_ip_address(&opctx, &authz_igw, route) + .await?; + + //TODO trigger igw rpw + //self.vpc_igw_increment_rpw_version(opctx, &authz_igw).await?; + + Ok(route) } pub(crate) async fn internet_gateway_ip_address_detach( &self, - _opctx: &OpContext, - _route_lookup: &lookup::InternetGatewayIpAddress<'_>, + opctx: &OpContext, + lookup: &lookup::InternetGatewayIpAddress<'_>, ) -> DeleteResult { - todo!(); + let (.., _authz_igw, authz_addr, _db_addr) = + lookup.fetch_for(authz::Action::Delete).await?; + + let out = self + .db_datastore + .internet_gateway_detach_ip_address(opctx, &authz_addr) + .await?; + + //TODO trigger igw rpw + //self.vpc_igw_increment_rpw_version(opctx, &authz_igw).await?; + + Ok(out) } }