From 906d0e83e75936fa09151629f7063b71a8e0e563 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Mon, 14 Oct 2024 14:41:38 -0500 Subject: [PATCH] add transit_ips to NIC create params --- nexus/db-model/src/network_interface.rs | 7 +++++ nexus/db-queries/src/db/datastore/vpc.rs | 1 + .../src/db/queries/network_interface.rs | 27 +++++++++++++++++++ nexus/src/app/network_interface.rs | 1 + nexus/src/app/sagas/instance_create.rs | 3 +++ nexus/tests/integration_tests/endpoints.rs | 1 + nexus/tests/integration_tests/instances.rs | 21 +++++++++++++++ .../integration_tests/internet_gateway.rs | 1 + .../integration_tests/subnet_allocation.rs | 2 ++ nexus/tests/integration_tests/vpc_routers.rs | 1 + nexus/types/src/external_api/params.rs | 5 +++- openapi/nexus.json | 10 ++++++- 12 files changed, 78 insertions(+), 2 deletions(-) diff --git a/nexus/db-model/src/network_interface.rs b/nexus/db-model/src/network_interface.rs index a764d25e4b3..02a7b026413 100644 --- a/nexus/db-model/src/network_interface.rs +++ b/nexus/db-model/src/network_interface.rs @@ -321,6 +321,7 @@ pub struct IncompleteNetworkInterface { pub kind: NetworkInterfaceKind, pub parent_id: Uuid, pub subnet: VpcSubnet, + pub transit_ips: Vec, pub ip: Option, pub mac: Option, pub slot: Option, @@ -334,6 +335,7 @@ impl IncompleteNetworkInterface { parent_id: Uuid, subnet: VpcSubnet, identity: external::IdentityMetadataCreateParams, + transit_ips: Vec, ip: Option, mac: Option, slot: Option, @@ -380,6 +382,7 @@ impl IncompleteNetworkInterface { kind, parent_id, subnet, + transit_ips: transit_ips.into_iter().map(Into::into).collect(), ip, mac, slot, @@ -391,6 +394,7 @@ impl IncompleteNetworkInterface { instance_id: InstanceUuid, subnet: VpcSubnet, identity: external::IdentityMetadataCreateParams, + transit_ips: Vec, ip: Option, ) -> Result { Self::new( @@ -399,6 +403,7 @@ impl IncompleteNetworkInterface { instance_id.into_untyped_uuid(), subnet, identity, + transit_ips, ip, None, None, @@ -420,6 +425,7 @@ impl IncompleteNetworkInterface { service_id, subnet, identity, + vec![], Some(ip), Some(mac), Some(slot), @@ -440,6 +446,7 @@ impl IncompleteNetworkInterface { probe_id, subnet, identity, + vec![], ip, mac, None, diff --git a/nexus/db-queries/src/db/datastore/vpc.rs b/nexus/db-queries/src/db/datastore/vpc.rs index efc44fa2483..83b55fdb7ac 100644 --- a/nexus/db-queries/src/db/datastore/vpc.rs +++ b/nexus/db-queries/src/db/datastore/vpc.rs @@ -3918,6 +3918,7 @@ mod tests { name: "nic".parse().unwrap(), description: "A NIC...".into(), }, + vec![], None, ) .unwrap(), diff --git a/nexus/db-queries/src/db/queries/network_interface.rs b/nexus/db-queries/src/db/queries/network_interface.rs index bfc4715b318..7ce203e515a 100644 --- a/nexus/db-queries/src/db/queries/network_interface.rs +++ b/nexus/db-queries/src/db/queries/network_interface.rs @@ -1146,6 +1146,13 @@ impl QueryFragment for InsertQuery { out.push_identifier(dsl::parent_id::NAME)?; out.push_sql(", "); + out.push_bind_param::, Vec>( + &self.interface.transit_ips, + )?; + out.push_sql(" AS "); + out.push_identifier(dsl::transit_ips::NAME)?; + out.push_sql(", "); + // Helper function to push a subquery selecting something from the CTE. fn select_from_cte( mut out: AstPass, @@ -1239,6 +1246,8 @@ impl QueryFragment for InsertQueryValues { out.push_sql(", "); out.push_identifier(dsl::parent_id::NAME)?; out.push_sql(", "); + out.push_identifier(dsl::transit_ips::NAME)?; + out.push_sql(", "); out.push_identifier(dsl::vpc_id::NAME)?; out.push_sql(", "); out.push_identifier(dsl::subnet_id::NAME)?; @@ -2145,6 +2154,7 @@ mod tests { name: "interface-a".parse().unwrap(), description: String::from("description"), }, + vec![], Some(requested_ip), ) .unwrap(); @@ -2174,6 +2184,7 @@ mod tests { name: "interface-a".parse().unwrap(), description: String::from("description"), }, + vec![], Some(requested_ip), ) .unwrap(); @@ -2206,6 +2217,7 @@ mod tests { name: "interface-b".parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2244,6 +2256,7 @@ mod tests { name: format!("interface-{}", i).parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2288,6 +2301,7 @@ mod tests { name: "interface-c".parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2307,6 +2321,7 @@ mod tests { name: "interface-c".parse().unwrap(), description: String::from("description"), }, + vec![], Some(inserted_interface.ip.ip()), ) .unwrap(); @@ -2555,6 +2570,7 @@ mod tests { name: "interface-c".parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2574,6 +2590,7 @@ mod tests { name: "interface-c".parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2605,6 +2622,7 @@ mod tests { name: "interface-c".parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2621,6 +2639,7 @@ mod tests { name: "interface-d".parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2649,6 +2668,7 @@ mod tests { name: "interface-c".parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2691,6 +2711,7 @@ mod tests { name: "interface-c".parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2709,6 +2730,7 @@ mod tests { name: "interface-a".parse().unwrap(), description: String::from("description"), }, + vec![], addr, ) .unwrap(); @@ -2748,6 +2770,7 @@ mod tests { name: "interface-c".parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2777,6 +2800,7 @@ mod tests { name: "interface-d".parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2810,6 +2834,7 @@ mod tests { name: format!("if{}", i).parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2878,6 +2903,7 @@ mod tests { name: format!("interface-{}", slot).parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); @@ -2913,6 +2939,7 @@ mod tests { name: "interface-8".parse().unwrap(), description: String::from("description"), }, + vec![], None, ) .unwrap(); diff --git a/nexus/src/app/network_interface.rs b/nexus/src/app/network_interface.rs index 7ce35aa4154..f8870d245d0 100644 --- a/nexus/src/app/network_interface.rs +++ b/nexus/src/app/network_interface.rs @@ -91,6 +91,7 @@ impl super::Nexus { InstanceUuid::from_untyped_uuid(authz_instance.id()), db_subnet, params.identity.clone(), + params.transit_ips.clone(), params.ip, )?; self.db_datastore diff --git a/nexus/src/app/sagas/instance_create.rs b/nexus/src/app/sagas/instance_create.rs index df3064d96ad..49caf320498 100644 --- a/nexus/src/app/sagas/instance_create.rs +++ b/nexus/src/app/sagas/instance_create.rs @@ -525,6 +525,7 @@ async fn create_custom_network_interface( instance_id, db_subnet.clone(), interface_params.identity.clone(), + interface_params.transit_ips.clone(), interface_params.ip, ) .map_err(ActionError::action_failed)?; @@ -598,6 +599,7 @@ async fn create_default_primary_network_interface( vpc_name: default_name.clone(), subnet_name: default_name.clone(), ip: None, // Request an IP address allocation + transit_ips: vec![], }; // Lookup authz objects, used in the call to actually create the NIC. @@ -619,6 +621,7 @@ async fn create_default_primary_network_interface( instance_id, db_subnet.clone(), interface_params.identity.clone(), + interface_params.transit_ips.clone(), interface_params.ip, ) .map_err(ActionError::action_failed)?; diff --git a/nexus/tests/integration_tests/endpoints.rs b/nexus/tests/integration_tests/endpoints.rs index a606a0c94c2..d17884cc3c7 100644 --- a/nexus/tests/integration_tests/endpoints.rs +++ b/nexus/tests/integration_tests/endpoints.rs @@ -689,6 +689,7 @@ pub static DEMO_INSTANCE_NIC_CREATE: LazyLock< vpc_name: DEMO_VPC_NAME.clone(), subnet_name: DEMO_VPC_SUBNET_NAME.clone(), ip: None, + transit_ips: vec![], }); pub static DEMO_INSTANCE_NIC_PUT: LazyLock< params::InstanceNetworkInterfaceUpdate, diff --git a/nexus/tests/integration_tests/instances.rs b/nexus/tests/integration_tests/instances.rs index c45b4a41554..1cae4a649e7 100644 --- a/nexus/tests/integration_tests/instances.rs +++ b/nexus/tests/integration_tests/instances.rs @@ -2113,6 +2113,7 @@ async fn test_instance_create_saga_removes_instance_database_record( vpc_name: default_name.clone(), subnet_name: default_name.clone(), ip: Some(requested_address), + transit_ips: vec![], }; let interface_params = params::InstanceNetworkInterfaceAttachment::Create(vec![ @@ -2192,6 +2193,7 @@ async fn test_instance_create_saga_removes_instance_database_record( vpc_name: default_name.clone(), subnet_name: default_name.clone(), ip: Some(requested_address), + transit_ips: vec![], }; let interface_params = params::InstanceNetworkInterfaceAttachment::Create(vec![ @@ -2234,6 +2236,7 @@ async fn test_instance_with_single_explicit_ip_address( vpc_name: default_name.clone(), subnet_name: default_name.clone(), ip: Some(requested_address), + transit_ips: vec![], }; let interface_params = params::InstanceNetworkInterfaceAttachment::Create(vec![ @@ -2342,6 +2345,7 @@ async fn test_instance_with_new_custom_network_interfaces( vpc_name: default_name.clone(), subnet_name: default_name.clone(), ip: None, + transit_ips: vec![], }; let if1_params = params::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { @@ -2351,6 +2355,7 @@ async fn test_instance_with_new_custom_network_interfaces( vpc_name: default_name.clone(), subnet_name: non_default_subnet_name.clone(), ip: None, + transit_ips: vec![], }; let interface_params = params::InstanceNetworkInterfaceAttachment::Create(vec![ @@ -2529,6 +2534,7 @@ async fn test_instance_create_delete_network_interface( vpc_name: "default".parse().unwrap(), subnet_name: "default".parse().unwrap(), ip: Some("172.30.0.10".parse().unwrap()), + transit_ips: vec![], }, params::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { @@ -2538,6 +2544,7 @@ async fn test_instance_create_delete_network_interface( vpc_name: "default".parse().unwrap(), subnet_name: secondary_subnet.identity.name.clone(), ip: Some("172.31.0.11".parse().unwrap()), + transit_ips: vec!["10.0.0.0/24".parse().unwrap()], }, ]; @@ -2591,6 +2598,15 @@ async fn test_instance_create_delete_network_interface( i == 0, "Only the first interface should be primary" ); + if i == 1 { + assert!( + iface.transit_ips.len() == 1 + && iface.transit_ips[0].to_string() == "10.0.0.0/24", + "Only the second interface has a transit IP" + ); + } else { + assert!(iface.transit_ips.is_empty()) + } interfaces.push(iface); } @@ -2611,6 +2627,7 @@ async fn test_instance_create_delete_network_interface( assert_eq!(iface0.subnet_id, iface1.subnet_id); assert_eq!(iface0.ip, iface1.ip); assert_eq!(iface0.primary, iface1.primary); + assert_eq!(iface0.transit_ips, iface1.transit_ips); } // Verify we cannot delete either interface while the instance is running @@ -2764,6 +2781,7 @@ async fn test_instance_update_network_interfaces( vpc_name: "default".parse().unwrap(), subnet_name: "default".parse().unwrap(), ip: Some("172.30.0.10".parse().unwrap()), + transit_ips: vec![], }, params::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { @@ -2773,6 +2791,7 @@ async fn test_instance_update_network_interfaces( vpc_name: "default".parse().unwrap(), subnet_name: secondary_subnet.identity.name.clone(), ip: Some("172.31.0.11".parse().unwrap()), + transit_ips: vec![], }, ]; @@ -3335,6 +3354,7 @@ async fn test_instance_with_multiple_nics_unwinds_completely( vpc_name: default_name.clone(), subnet_name: default_name.clone(), ip: Some("172.30.0.6".parse().unwrap()), + transit_ips: vec![], }; let if1_params = params::InstanceNetworkInterfaceCreate { identity: IdentityMetadataCreateParams { @@ -3344,6 +3364,7 @@ async fn test_instance_with_multiple_nics_unwinds_completely( vpc_name: default_name.clone(), subnet_name: default_name.clone(), ip: Some("172.30.0.7".parse().unwrap()), + transit_ips: vec![], }; let interface_params = params::InstanceNetworkInterfaceAttachment::Create(vec![ diff --git a/nexus/tests/integration_tests/internet_gateway.rs b/nexus/tests/integration_tests/internet_gateway.rs index c7f9a40cfc8..dbefe200aef 100644 --- a/nexus/tests/integration_tests/internet_gateway.rs +++ b/nexus/tests/integration_tests/internet_gateway.rs @@ -372,6 +372,7 @@ async fn test_setup(c: &ClientTestContext) { ip: None, subnet_name: "default".parse().unwrap(), vpc_name: VPC_NAME.parse().unwrap(), + transit_ips: vec![], }, ]); let _inst = create_instance_with( diff --git a/nexus/tests/integration_tests/subnet_allocation.rs b/nexus/tests/integration_tests/subnet_allocation.rs index f17f78cc2b8..e8816970c46 100644 --- a/nexus/tests/integration_tests/subnet_allocation.rs +++ b/nexus/tests/integration_tests/subnet_allocation.rs @@ -47,6 +47,7 @@ async fn create_instance_expect_failure( vpc_name: "default".parse().unwrap(), subnet_name: subnet_name.parse().unwrap(), ip: None, + transit_ips: vec![], }, ]); let new_instance = params::InstanceCreate { @@ -132,6 +133,7 @@ async fn test_subnet_allocation(cptestctx: &ControlPlaneTestContext) { vpc_name: "default".parse().unwrap(), subnet_name: "small".parse().unwrap(), ip: None, + transit_ips: vec![], }, ]); diff --git a/nexus/tests/integration_tests/vpc_routers.rs b/nexus/tests/integration_tests/vpc_routers.rs index 205b2fde689..2b3056265d7 100644 --- a/nexus/tests/integration_tests/vpc_routers.rs +++ b/nexus/tests/integration_tests/vpc_routers.rs @@ -510,6 +510,7 @@ async fn test_vpc_routers_custom_delivered_to_instance( vpc_name: vpc.name().clone(), subnet_name: subnet_name.parse().unwrap(), ip: Some(format!("192.168.{i}.10").parse().unwrap()), + transit_ips: vec![], }, ]), vec![], diff --git a/nexus/types/src/external_api/params.rs b/nexus/types/src/external_api/params.rs index 90f5e171d5f..c974419d100 100644 --- a/nexus/types/src/external_api/params.rs +++ b/nexus/types/src/external_api/params.rs @@ -902,6 +902,9 @@ pub struct InstanceNetworkInterfaceCreate { pub subnet_name: Name, /// The IP address for the interface. One will be auto-assigned if not provided. pub ip: Option, + /// A set of additional networks that this interface may send and + /// receive traffic on + pub transit_ips: Vec, } /// Parameters for updating an `InstanceNetworkInterface` @@ -930,7 +933,7 @@ pub struct InstanceNetworkInterfaceUpdate { pub primary: bool, /// A set of additional networks that this interface may send and - /// receive traffic on. + /// receive traffic on #[serde(default)] pub transit_ips: Vec, } diff --git a/openapi/nexus.json b/openapi/nexus.json index 43485176b82..ca406a1dec0 100644 --- a/openapi/nexus.json +++ b/openapi/nexus.json @@ -18224,6 +18224,13 @@ } ] }, + "transit_ips": { + "description": "A set of additional networks that this interface may send and receive traffic on", + "type": "array", + "items": { + "$ref": "#/components/schemas/IpNet" + } + }, "vpc_name": { "description": "The VPC in which to create the interface.", "allOf": [ @@ -18237,6 +18244,7 @@ "description", "name", "subnet_name", + "transit_ips", "vpc_name" ] }, @@ -18283,7 +18291,7 @@ "type": "boolean" }, "transit_ips": { - "description": "A set of additional networks that this interface may send and receive traffic on.", + "description": "A set of additional networks that this interface may send and receive traffic on", "default": [], "type": "array", "items": {