Skip to content

Commit

Permalink
create default internet gateway on vpc create
Browse files Browse the repository at this point in the history
  • Loading branch information
rcgoodfellow committed Sep 2, 2024
1 parent c89e8f1 commit c875f44
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 11 deletions.
143 changes: 137 additions & 6 deletions nexus/src/app/sagas/vpc_create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ use super::NexusSaga;
use super::ACTION_GENERATE_ID;
use crate::app::sagas::declare_saga_actions;
use crate::external_api::params;
use nexus_db_model::InternetGatewayIpPool;
use nexus_db_queries::db::queries::vpc_subnet::InsertVpcSubnetError;
use nexus_db_queries::{authn, authz, db};
use nexus_defaults as defaults;
use omicron_common::api::external;
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::RouterRouteKind;
use oxnet::IpNet;
use serde::Deserialize;
use serde::Serialize;
Expand Down Expand Up @@ -45,6 +43,10 @@ declare_saga_actions! {
+ svc_create_router
- svc_create_router_undo
}
VPC_CREATE_GATEWAY -> "gateway" {
+ svc_create_gateway
- svc_create_gateway_undo
}
VPC_CREATE_V4_ROUTE -> "route4" {
+ svc_create_v4_route
- svc_create_v4_route_undo
Expand Down Expand Up @@ -98,12 +100,18 @@ pub fn create_dag(
"GenerateDefaultSubnetId",
ACTION_GENERATE_ID.as_ref(),
));
builder.append(Node::action(
"default_internet_gateway_id",
"GenerateDefaultInternetGatewayId",
ACTION_GENERATE_ID.as_ref(),
));
builder.append(vpc_create_vpc_action());
builder.append(vpc_create_router_action());
builder.append(vpc_create_v4_route_action());
builder.append(vpc_create_v6_route_action());
builder.append(vpc_create_subnet_action());
builder.append(vpc_update_firewall_action());
builder.append(vpc_create_gateway_action());
builder.append(vpc_notify_sleds_action());

Ok(builder.build()?)
Expand Down Expand Up @@ -280,14 +288,16 @@ async fn svc_create_route(
let route = db::model::RouterRoute::new(
route_id,
system_router_id,
RouterRouteKind::Default,
external::RouterRouteKind::Default,
params::RouterRouteCreate {
identity: IdentityMetadataCreateParams {
name: name.parse().unwrap(),
description: "The default route of a vpc".to_string(),
},
target: RouteTarget::InternetGateway("outbound".parse().unwrap()),
destination: RouteDestination::IpNet(default_net),
target: external::RouteTarget::InternetGateway(
"default".parse().unwrap(),
),
destination: external::RouteDestination::IpNet(default_net),
},
);

Expand Down Expand Up @@ -460,6 +470,92 @@ async fn svc_update_firewall_undo(
Ok(())
}

async fn svc_create_gateway(
sagactx: NexusActionContext,
) -> Result<authz::InternetGateway, ActionError> {
let osagactx = sagactx.user_data();
let params = sagactx.saga_params::<Params>()?;
let opctx = crate::context::op_context_for_saga_action(
&sagactx,
&params.serialized_authn,
);
let vpc_id = sagactx.lookup::<Uuid>("vpc_id")?;
let default_igw_id =
sagactx.lookup::<Uuid>("default_internet_gateway_id")?;
let (authz_vpc, _) =
sagactx.lookup::<(authz::Vpc, db::model::Vpc)>("vpc")?;

let igw = db::model::InternetGateway::new(
default_igw_id,
vpc_id,
params::InternetGatewayCreate {
identity: IdentityMetadataCreateParams {
name: "default".parse().unwrap(),
description: "Automatically created default VPC gateway".into(),
},
},
);

let (authz_igw, _) = osagactx
.datastore()
.vpc_create_internet_gateway(&opctx, &authz_vpc, igw)
.await
.map_err(ActionError::action_failed)?;

match osagactx.datastore().ip_pools_fetch_default(&opctx).await {
Ok((authz_ip_pool, _db_ip_pool)) => {
// Attach the default IP pool to the default gateway.
// Failure of this saga takes out the gateway with a cascading delete and
// thus this ip pool.
osagactx
.datastore()
.internet_gateway_attach_ip_pool(
&opctx,
&authz_igw,
InternetGatewayIpPool::new(
Uuid::new_v4(),
authz_ip_pool.id(),
authz_igw.id(),
IdentityMetadataCreateParams {
name: "default".parse().unwrap(),
description:
"Automatically attached default IP pool".into(),
},
),
)
.await
.map_err(ActionError::action_failed)?;
}
Err(e) => {
warn!(
opctx.log,
"Default ip pool lookup failed: {e}. \
Default gateway has no ip pool association",
);
}
};

Ok(authz_igw)
}

async fn svc_create_gateway_undo(
sagactx: NexusActionContext,
) -> Result<(), anyhow::Error> {
let osagactx = sagactx.user_data();
let params = sagactx.saga_params::<Params>()?;
let opctx = crate::context::op_context_for_saga_action(
&sagactx,
&params.serialized_authn,
);
let authz_igw = sagactx.lookup::<authz::InternetGateway>("gateway")?;

osagactx
.datastore()
.vpc_delete_internet_gateway(&opctx, &authz_igw, true)
.await?;
Ok(())
}

async fn svc_notify_sleds(
sagactx: NexusActionContext,
) -> Result<(), ActionError> {
Expand Down Expand Up @@ -623,6 +719,19 @@ pub(crate) mod test {
.await
.expect("Failed to delete system router");

// Default gateway
let (.., authz_igw, _igw) = LookupPath::new(&opctx, &datastore)
.project_id(project_id)
.vpc_name(&default_name.clone().into())
.internet_gateway_name(&default_name.clone().into())
.fetch()
.await
.expect("Failed to fetch default gateway");
datastore
.vpc_delete_internet_gateway(&opctx, &authz_igw, true)
.await
.expect("Failed to delete default gateway");

// Default VPC & Firewall Rules
let (.., authz_vpc, vpc) = LookupPath::new(&opctx, &datastore)
.project_id(project_id)
Expand All @@ -645,6 +754,7 @@ pub(crate) mod test {
assert!(no_routers_exist(datastore).await);
assert!(no_routes_exist(datastore).await);
assert!(no_subnets_exist(datastore).await);
assert!(no_gateways_exist(datastore).await);
assert!(no_firewall_rules_exist(datastore).await);
}

Expand Down Expand Up @@ -690,6 +800,27 @@ pub(crate) mod test {
.is_none()
}

async fn no_gateways_exist(datastore: &DataStore) -> bool {
use nexus_db_queries::db::model::InternetGateway;
use nexus_db_queries::db::schema::internet_gateway::dsl;

dsl::internet_gateway
.filter(dsl::time_deleted.is_null())
// ignore built-in services VPC
.filter(dsl::vpc_id.ne(*SERVICES_VPC_ID))
.select(InternetGateway::as_select())
.first_async::<InternetGateway>(
&*datastore.pool_connection_for_tests().await.unwrap(),
)
.await
.optional()
.unwrap()
.map(|router| {
eprintln!("Internet gateway exists: {router:?}");
})
.is_none()
}

async fn no_routes_exist(datastore: &DataStore) -> bool {
use nexus_db_queries::db::model::RouterRoute;
use nexus_db_queries::db::schema::router_route::dsl;
Expand Down
10 changes: 5 additions & 5 deletions nexus/tests/integration_tests/internet_gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ async fn test_internet_gateway_basic_crud(ctx: &ControlPlaneTestContext) {
let c = &ctx.external_client;
test_setup(c).await;

// should start with zero gateways
// should start with just default gateway
let igws = list_internet_gateways(c, PROJECT_NAME, VPC_NAME).await;
assert_eq!(igws.len(), 0, "should start with zero internet gateways");
assert_eq!(igws.len(), 1, "should start with zero internet gateways");
expect_igw_not_found(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await;

// create an internet gateway
let gw = create_internet_gateway(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await;
let igws = list_internet_gateways(c, PROJECT_NAME, VPC_NAME).await;
assert_eq!(igws.len(), 1, "should now have one internet gateway");
assert_eq!(igws.len(), 2, "should now have two internet gateways");

// should be able to get the gateway just created
let same_igw = get_igw(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await;
Expand Down Expand Up @@ -136,7 +136,7 @@ async fn test_internet_gateway_basic_crud(ctx: &ControlPlaneTestContext) {
let igw_addrs =
list_internet_gateway_ip_pools(c, PROJECT_NAME, VPC_NAME, IGW_NAME)
.await;
assert_eq!(igw_addrs.len(), 0, "should now have zero attached ip pool");
assert_eq!(igw_addrs.len(), 0, "should now have zero attached ip pools");

// detach an ip address
detach_ip_address_from_igw(
Expand All @@ -160,7 +160,7 @@ async fn test_internet_gateway_basic_crud(ctx: &ControlPlaneTestContext) {
// delete internet gateay
delete_internet_gateway(c, PROJECT_NAME, VPC_NAME, IGW_NAME, false).await;
let igws = list_internet_gateways(c, PROJECT_NAME, VPC_NAME).await;
assert_eq!(igws.len(), 0, "should now have zero internet gateways");
assert_eq!(igws.len(), 1, "should now just have default gateway");

// looking for gateway should return 404
expect_igw_not_found(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await;
Expand Down

0 comments on commit c875f44

Please sign in to comment.