From c89e8f10c5b5eb4ccd48645229e05babec949aa2 Mon Sep 17 00:00:00 2001 From: Ryan Goodfellow Date: Mon, 2 Sep 2024 09:44:07 -0700 Subject: [PATCH] more testing --- nexus/test-utils/src/resource_helpers.rs | 10 +- .../integration_tests/internet_gateway.rs | 489 +++++++++++++----- 2 files changed, 358 insertions(+), 141 deletions(-) diff --git a/nexus/test-utils/src/resource_helpers.rs b/nexus/test-utils/src/resource_helpers.rs index 1da2596c466..6e60a5fe11a 100644 --- a/nexus/test-utils/src/resource_helpers.rs +++ b/nexus/test-utils/src/resource_helpers.rs @@ -749,12 +749,13 @@ pub async fn delete_internet_gateway( project_name: &str, vpc_name: &str, internet_gateway_name: &str, + cascade: bool, ) { NexusRequest::object_delete( &client, format!( - "/v1/internet-gateways/{}?project={}&vpc={}", - &internet_gateway_name, &project_name, &vpc_name + "/v1/internet-gateways/{}?project={}&vpc={}&cascade={}", + &internet_gateway_name, &project_name, &vpc_name, cascade ) .as_str(), ) @@ -859,10 +860,11 @@ pub async fn detach_ip_address_from_igw( vpc_name: &str, igw_name: &str, attachment_name: &str, + cascade: bool, ) { let url = format!( - "/v1/internet-gateway-ip-addresses/{}?project={}&vpc={}&gateway={}", - attachment_name, project_name, vpc_name, igw_name, + "/v1/internet-gateway-ip-addresses/{}?project={}&vpc={}&gateway={}&cascade={}", + attachment_name, project_name, vpc_name, igw_name, cascade ); NexusRequest::object_delete(&client, url.as_str()) diff --git a/nexus/tests/integration_tests/internet_gateway.rs b/nexus/tests/integration_tests/internet_gateway.rs index e89ef6d383f..86777b9d787 100644 --- a/nexus/tests/integration_tests/internet_gateway.rs +++ b/nexus/tests/integration_tests/internet_gateway.rs @@ -34,105 +34,28 @@ use omicron_common::{ type ControlPlaneTestContext = nexus_test_utils::ControlPlaneTestContext; +const PROJECT_NAME: &str = "delta-quadrant"; +const VPC_NAME: &str = "dominion"; +const IGW_NAME: &str = "wormhole"; +const IP_POOL_NAME: &str = "ds9"; +const IP_POOL_ATTACHMENT_NAME: &str = "runabout"; +const IP_ADDRESS_ATTACHMENT_NAME: &str = "defiant"; +const IP_ADDRESS_ATTACHMENT: &str = "198.51.100.47"; +const IP_ADDRESS_ATTACHMENT_FROM_POOL: &str = "203.0.113.1"; +const INSTANCE_NAME: &str = "odo"; +const FLOATING_IP_NAME: &str = "floater"; +const ROUTER_NAME: &str = "deepspace"; +const ROUTE_NAME: &str = "subspace"; + #[nexus_test] async fn test_internet_gateway_basic_crud(ctx: &ControlPlaneTestContext) { - const PROJECT_NAME: &str = "delta-quadrant"; - const VPC_NAME: &str = "dominion"; - const IGW_NAME: &str = "wormhole"; - const IP_POOL_NAME: &str = "ds9"; - const IP_POOL_ATTACHMENT_NAME: &str = "runabout"; - const IP_ADDRESS_ATTACHMENT_NAME: &str = "defiant"; - const IP_ADDRESS_ATTACHMENT: &str = "198.51.100.47"; - const INSTANCE_NAME: &str = "odo"; - const FLOATING_IP_NAME: &str = "floater"; - const ROUTER_NAME: &str = "deepspace"; - const ROUTE_NAME: &str = "subspace"; - let c = &ctx.external_client; - - // create a project and vpc to test with - let _proj = create_project(&c, PROJECT_NAME).await; - let _vpc = create_vpc(&c, PROJECT_NAME, VPC_NAME).await; - let _pool = create_ip_pool( - c, - IP_POOL_NAME, - Some(IpRange::V4(Ipv4Range { - first: "203.0.113.1".parse().unwrap(), - last: "203.0.113.254".parse().unwrap(), - })), - ) - .await; - link_ip_pool(&c, IP_POOL_NAME, &DEFAULT_SILO.id(), true).await; - let _floater = create_floating_ip( - c, - FLOATING_IP_NAME, - PROJECT_NAME, - None, - Some(IP_POOL_NAME), - ) - .await; - let nic_attach = InstanceNetworkInterfaceAttachment::Create(vec![ - InstanceNetworkInterfaceCreate { - identity: IdentityMetadataCreateParams { - description: String::from("description"), - name: "noname".parse().unwrap(), - }, - ip: None, - subnet_name: "default".parse().unwrap(), - vpc_name: VPC_NAME.parse().unwrap(), - }, - ]); - let _inst = create_instance_with( - c, - PROJECT_NAME, - INSTANCE_NAME, - &nic_attach, - Vec::new(), - vec![ExternalIpCreate::Floating { - floating_ip: NameOrId::Name(FLOATING_IP_NAME.parse().unwrap()), - }], - true, - ) - .await; - - let _router = create_router(c, PROJECT_NAME, VPC_NAME, ROUTER_NAME).await; - let route = create_route( - c, - PROJECT_NAME, - VPC_NAME, - ROUTER_NAME, - ROUTE_NAME, - RouteDestination::IpNet("0.0.0.0/0".parse().unwrap()), - RouteTarget::InternetGateway(IGW_NAME.parse().unwrap()), - ) - .await; - println!("{}", route.target); + test_setup(c).await; // should start with zero gateways let igws = list_internet_gateways(c, PROJECT_NAME, VPC_NAME).await; assert_eq!(igws.len(), 0, "should start with zero internet gateways"); - - // check 404 response - let url = format!( - "/v1/internet-gateways/{}?project={}&vpc={}", - IGW_NAME, PROJECT_NAME, VPC_NAME - ); - let error: dropshot::HttpErrorResponseBody = NexusRequest::expect_failure( - c, - StatusCode::NOT_FOUND, - Method::GET, - &url, - ) - .authn_as(AuthnMode::PrivilegedUser) - .execute() - .await - .unwrap() - .parsed_body() - .unwrap(); - assert_eq!( - error.message, - format!("not found: internet-gateway with name \"{IGW_NAME}\"") - ); + 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; @@ -140,14 +63,8 @@ async fn test_internet_gateway_basic_crud(ctx: &ControlPlaneTestContext) { assert_eq!(igws.len(), 1, "should now have one internet gateway"); // should be able to get the gateway just created - let same_gw: InternetGateway = NexusRequest::object_get(c, &url) - .authn_as(AuthnMode::PrivilegedUser) - .execute() - .await - .unwrap() - .parsed_body() - .unwrap(); - assert_eq!(&gw.identity, &same_gw.identity); + let same_igw = get_igw(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await; + assert_eq!(&gw.identity, &same_igw.identity); // a new igw should have zero ip pools let igw_pools = @@ -177,40 +94,18 @@ async fn test_internet_gateway_basic_crud(ctx: &ControlPlaneTestContext) { assert_eq!(igw_pools.len(), 1, "should now have one attached ip pool"); // ensure we cannot delete the IP gateway without cascading - let url = format!( - "/v1/internet-gateways/{}?project={}&vpc={}", - IGW_NAME, PROJECT_NAME, VPC_NAME - ); - let _error: dropshot::HttpErrorResponseBody = NexusRequest::expect_failure( - c, - StatusCode::BAD_REQUEST, - Method::DELETE, - &url, - ) - .authn_as(AuthnMode::PrivilegedUser) - .execute() - .await - .unwrap() - .parsed_body() - .unwrap(); + expect_igw_delete_fail(c, PROJECT_NAME, VPC_NAME, IGW_NAME, false).await; - // ensure we cannot delete the IP gateway pool without cascading - let url = format!( - "/v1/internet-gateway-ip-pools/{}?project={}&vpc={}&gateway={}", - IP_POOL_ATTACHMENT_NAME, PROJECT_NAME, VPC_NAME, IGW_NAME - ); - let _error: dropshot::HttpErrorResponseBody = NexusRequest::expect_failure( + // ensure we cannot detach the igw ip pool without cascading + expect_igw_ip_pool_detach_fail( c, - StatusCode::BAD_REQUEST, - Method::DELETE, - &url, + PROJECT_NAME, + VPC_NAME, + IGW_NAME, + IP_POOL_ATTACHMENT_NAME, + false, ) - .authn_as(AuthnMode::PrivilegedUser) - .execute() - .await - .unwrap() - .parsed_body() - .unwrap(); + .await; // attach an ip address attach_ip_address_to_igw( @@ -250,6 +145,7 @@ async fn test_internet_gateway_basic_crud(ctx: &ControlPlaneTestContext) { VPC_NAME, IGW_NAME, IP_ADDRESS_ATTACHMENT_NAME, + false, ) .await; let igw_addrs = @@ -262,18 +158,163 @@ async fn test_internet_gateway_basic_crud(ctx: &ControlPlaneTestContext) { ); // delete internet gateay - delete_internet_gateway(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await; + 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"); + + // looking for gateway should return 404 + expect_igw_not_found(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await; + + // looking for gateway pools should return 404 + expect_igw_pools_not_found(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await; + + // looking for gateway addresses should return 404 + expect_igw_addresses_not_found(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await; } -/* #[nexus_test] -async fn test_internet_gateway_pool_delete_cascade( - ctx: &ControlPlaneTestContext, -) { +async fn test_internet_gateway_address_detach(ctx: &ControlPlaneTestContext) { + let c = &ctx.external_client; + test_setup(c).await; + + create_internet_gateway(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await; + attach_ip_address_to_igw( + c, + PROJECT_NAME, + VPC_NAME, + IGW_NAME, + IP_ADDRESS_ATTACHMENT_FROM_POOL.parse().unwrap(), + IP_ADDRESS_ATTACHMENT_NAME, + ) + .await; + + // ensure we cannot detach the igw address without cascading + expect_igw_ip_address_detach_fail( + c, + PROJECT_NAME, + VPC_NAME, + IGW_NAME, + IP_ADDRESS_ATTACHMENT_NAME, + false, + ) + .await; + + // ensure that we can detach the igw address with cascading + detach_ip_address_from_igw( + c, + PROJECT_NAME, + VPC_NAME, + IGW_NAME, + IP_ADDRESS_ATTACHMENT_NAME, + true, + ) + .await; + + // should be no addresses attached to the igw + let igw_addrs = + list_internet_gateway_ip_addresses(c, PROJECT_NAME, VPC_NAME, IGW_NAME) + .await; + assert_eq!( + igw_addrs.len(), + 0, + "should now have zero attached ip addresses" + ); +} + +#[nexus_test] +async fn test_internet_gateway_delete_cascade(ctx: &ControlPlaneTestContext) { + let c = &ctx.external_client; + test_setup(c).await; + + create_internet_gateway(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await; + attach_ip_address_to_igw( + c, + PROJECT_NAME, + VPC_NAME, + IGW_NAME, + IP_ADDRESS_ATTACHMENT_FROM_POOL.parse().unwrap(), + IP_ADDRESS_ATTACHMENT_NAME, + ) + .await; + attach_ip_pool_to_igw( + c, + PROJECT_NAME, + VPC_NAME, + IGW_NAME, + IP_POOL_NAME, + IP_POOL_ATTACHMENT_NAME, + ) + .await; + + delete_internet_gateway(c, PROJECT_NAME, VPC_NAME, IGW_NAME, true).await; + + // looking for gateway should return 404 + expect_igw_not_found(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await; + // looking for gateway pools should return 404 + expect_igw_pools_not_found(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await; + // looking for gateway addresses should return 404 + expect_igw_addresses_not_found(c, PROJECT_NAME, VPC_NAME, IGW_NAME).await; +} + +async fn test_setup(c: &ClientTestContext) { + // create a project and vpc to test with + let _proj = create_project(&c, PROJECT_NAME).await; + let _vpc = create_vpc(&c, PROJECT_NAME, VPC_NAME).await; + let _pool = create_ip_pool( + c, + IP_POOL_NAME, + Some(IpRange::V4(Ipv4Range { + first: "203.0.113.1".parse().unwrap(), + last: "203.0.113.254".parse().unwrap(), + })), + ) + .await; + link_ip_pool(&c, IP_POOL_NAME, &DEFAULT_SILO.id(), true).await; + let _floater = create_floating_ip( + c, + FLOATING_IP_NAME, + PROJECT_NAME, + None, + Some(IP_POOL_NAME), + ) + .await; + let nic_attach = InstanceNetworkInterfaceAttachment::Create(vec![ + InstanceNetworkInterfaceCreate { + identity: IdentityMetadataCreateParams { + description: String::from("description"), + name: "noname".parse().unwrap(), + }, + ip: None, + subnet_name: "default".parse().unwrap(), + vpc_name: VPC_NAME.parse().unwrap(), + }, + ]); + let _inst = create_instance_with( + c, + PROJECT_NAME, + INSTANCE_NAME, + &nic_attach, + Vec::new(), + vec![ExternalIpCreate::Floating { + floating_ip: NameOrId::Name(FLOATING_IP_NAME.parse().unwrap()), + }], + true, + ) + .await; + + let _router = create_router(c, PROJECT_NAME, VPC_NAME, ROUTER_NAME).await; + let route = create_route( + c, + PROJECT_NAME, + VPC_NAME, + ROUTER_NAME, + ROUTE_NAME, + RouteDestination::IpNet("0.0.0.0/0".parse().unwrap()), + RouteTarget::InternetGateway(IGW_NAME.parse().unwrap()), + ) + .await; + println!("{}", route.target); } -*/ async fn list_internet_gateways( client: &ClientTestContext, @@ -317,3 +358,177 @@ async fn list_internet_gateway_ip_addresses( objects_list_page_authz::(client, &url).await; out.items } + +async fn expect_igw_not_found( + client: &ClientTestContext, + project_name: &str, + vpc_name: &str, + igw_name: &str, +) { + // check 404 response + let url = format!( + "/v1/internet-gateways/{}?project={}&vpc={}", + igw_name, project_name, vpc_name + ); + let error: dropshot::HttpErrorResponseBody = NexusRequest::expect_failure( + client, + StatusCode::NOT_FOUND, + Method::GET, + &url, + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .unwrap() + .parsed_body() + .unwrap(); + assert_eq!( + error.message, + format!("not found: internet-gateway with name \"{IGW_NAME}\"") + ); +} + +async fn get_igw( + client: &ClientTestContext, + project_name: &str, + vpc_name: &str, + igw_name: &str, +) -> InternetGateway { + // check 404 response + let url = format!( + "/v1/internet-gateways/{}?project={}&vpc={}", + igw_name, project_name, vpc_name + ); + NexusRequest::object_get(client, &url) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .unwrap() + .parsed_body() + .unwrap() +} + +async fn expect_igw_delete_fail( + client: &ClientTestContext, + project_name: &str, + vpc_name: &str, + igw_name: &str, + cascade: bool, +) { + let url = format!( + "/v1/internet-gateways/{}?project={}&vpc={}&cascade={}", + igw_name, project_name, vpc_name, cascade + ); + let _error: dropshot::HttpErrorResponseBody = NexusRequest::expect_failure( + client, + StatusCode::BAD_REQUEST, + Method::DELETE, + &url, + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .unwrap() + .parsed_body() + .unwrap(); +} + +async fn expect_igw_ip_pool_detach_fail( + client: &ClientTestContext, + project_name: &str, + vpc_name: &str, + igw_name: &str, + ip_pool_attachment_name: &str, + cascade: bool, +) { + let url = format!( + "/v1/internet-gateway-ip-pools/{}?project={}&vpc={}&gateway={}&cascade={}", + ip_pool_attachment_name, project_name, vpc_name, igw_name, cascade + ); + let _error: dropshot::HttpErrorResponseBody = NexusRequest::expect_failure( + client, + StatusCode::BAD_REQUEST, + Method::DELETE, + &url, + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .unwrap() + .parsed_body() + .unwrap(); +} + +async fn expect_igw_ip_address_detach_fail( + client: &ClientTestContext, + project_name: &str, + vpc_name: &str, + igw_name: &str, + ip_address_attachment_name: &str, + cascade: bool, +) { + let url = format!( + "/v1/internet-gateway-ip-addresses/{}?project={}&vpc={}&gateway={}&cascade={}", + ip_address_attachment_name, project_name, vpc_name, igw_name, cascade + ); + let _error: dropshot::HttpErrorResponseBody = NexusRequest::expect_failure( + client, + StatusCode::BAD_REQUEST, + Method::DELETE, + &url, + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .unwrap() + .parsed_body() + .unwrap(); +} + +async fn expect_igw_pools_not_found( + client: &ClientTestContext, + project_name: &str, + vpc_name: &str, + igw_name: &str, +) { + let url = format!( + "/v1/internet-gateway-ip-pools?project={}&vpc={}&gateway={}", + project_name, vpc_name, igw_name, + ); + let _error: dropshot::HttpErrorResponseBody = NexusRequest::expect_failure( + client, + StatusCode::NOT_FOUND, + Method::GET, + &url, + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .unwrap() + .parsed_body() + .unwrap(); +} + +async fn expect_igw_addresses_not_found( + client: &ClientTestContext, + project_name: &str, + vpc_name: &str, + igw_name: &str, +) { + let url = format!( + "/v1/internet-gateway-ip-addresses?project={}&vpc={}&gateway={}", + project_name, vpc_name, igw_name, + ); + let _error: dropshot::HttpErrorResponseBody = NexusRequest::expect_failure( + client, + StatusCode::NOT_FOUND, + Method::GET, + &url, + ) + .authn_as(AuthnMode::PrivilegedUser) + .execute() + .await + .unwrap() + .parsed_body() + .unwrap(); +}