Skip to content

Commit cb7b856

Browse files
authored
Add transit_ips to NIC create body (#8851)
Closes #6588 As threatened in product eng sync, here is Claude with some aggressive steering on my end. It cost nearly $10, by far the most I have ever spent in Claude Code. Hard to say whether it was worth it. It seems pretty good and I did almost nothing.
1 parent 43178a1 commit cb7b856

File tree

12 files changed

+126
-2
lines changed

12 files changed

+126
-2
lines changed

nexus/db-model/src/network_interface.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ pub struct IncompleteNetworkInterface {
321321
pub ip: Option<std::net::IpAddr>,
322322
pub mac: Option<external::MacAddr>,
323323
pub slot: Option<u8>,
324+
pub transit_ips: Vec<IpNetwork>,
324325
}
325326

326327
impl IncompleteNetworkInterface {
@@ -334,6 +335,7 @@ impl IncompleteNetworkInterface {
334335
ip: Option<std::net::IpAddr>,
335336
mac: Option<external::MacAddr>,
336337
slot: Option<u8>,
338+
transit_ips: Vec<IpNetwork>,
337339
) -> Result<Self, external::Error> {
338340
if let Some(ip) = ip {
339341
subnet.check_requestable_addr(ip)?;
@@ -380,6 +382,7 @@ impl IncompleteNetworkInterface {
380382
ip,
381383
mac,
382384
slot,
385+
transit_ips,
383386
})
384387
}
385388

@@ -389,6 +392,7 @@ impl IncompleteNetworkInterface {
389392
subnet: VpcSubnet,
390393
identity: external::IdentityMetadataCreateParams,
391394
ip: Option<std::net::IpAddr>,
395+
transit_ips: Vec<IpNetwork>,
392396
) -> Result<Self, external::Error> {
393397
Self::new(
394398
interface_id,
@@ -399,6 +403,7 @@ impl IncompleteNetworkInterface {
399403
ip,
400404
None,
401405
None,
406+
transit_ips,
402407
)
403408
}
404409

@@ -420,6 +425,7 @@ impl IncompleteNetworkInterface {
420425
Some(ip),
421426
Some(mac),
422427
Some(slot),
428+
vec![], // Service interfaces don't use transit_ips
423429
)
424430
}
425431

@@ -440,6 +446,7 @@ impl IncompleteNetworkInterface {
440446
ip,
441447
mac,
442448
None,
449+
vec![], // Probe interfaces don't use transit_ips
443450
)
444451
}
445452
}

nexus/db-queries/src/db/datastore/vpc.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4018,6 +4018,7 @@ mod tests {
40184018
description: "A NIC...".into(),
40194019
},
40204020
None,
4021+
vec![],
40214022
)
40224023
.unwrap(),
40234024
)

nexus/db-queries/src/db/queries/network_interface.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,7 @@ fn push_interface_validation_cte<'a>(
926926
/// <time_created> AS time_created, <time_modified> AS time_modified,
927927
/// NULL AS time_deleted, <instance_id> AS instance_id, <vpc_id> AS vpc_id,
928928
/// <subnet_id> AS subnet_id, <mac> AS mac, <maybe IP allocation subquery>,
929-
/// <slot> AS slot, <is_primary> AS is_primary
929+
/// <slot> AS slot, <is_primary> AS is_primary, <transit_ips> AS transit_ips
930930
/// ```
931931
///
932932
/// Instance validation
@@ -1204,8 +1204,20 @@ impl QueryFragment<Pg> for InsertQuery {
12041204
} else {
12051205
select_from_cte(out.reborrow(), dsl::slot::NAME)?;
12061206
}
1207+
out.push_sql(" AS ");
1208+
out.push_identifier(dsl::slot::NAME)?;
12071209
out.push_sql(", ");
1210+
12081211
select_from_cte(out.reborrow(), dsl::is_primary::NAME)?;
1212+
out.push_sql(" AS ");
1213+
out.push_identifier(dsl::is_primary::NAME)?;
1214+
out.push_sql(", ");
1215+
1216+
out.push_bind_param::<sql_types::Array<sql_types::Inet>, Vec<IpNetwork>>(
1217+
&self.interface.transit_ips,
1218+
)?;
1219+
out.push_sql(" AS ");
1220+
out.push_identifier(dsl::transit_ips::NAME)?;
12091221

12101222
Ok(())
12111223
}
@@ -1260,6 +1272,8 @@ impl QueryFragment<Pg> for InsertQueryValues {
12601272
out.push_identifier(dsl::slot::NAME)?;
12611273
out.push_sql(", ");
12621274
out.push_identifier(dsl::is_primary::NAME)?;
1275+
out.push_sql(", ");
1276+
out.push_identifier(dsl::transit_ips::NAME)?;
12631277
out.push_sql(") ");
12641278
self.0.walk_ast(out)
12651279
}
@@ -2191,6 +2205,7 @@ mod tests {
21912205
description: String::from("description"),
21922206
},
21932207
Some(requested_ip),
2208+
vec![],
21942209
)
21952210
.unwrap();
21962211
let err = context.datastore()
@@ -2220,6 +2235,7 @@ mod tests {
22202235
description: String::from("description"),
22212236
},
22222237
Some(requested_ip),
2238+
vec![],
22232239
)
22242240
.unwrap();
22252241
let inserted_interface = context
@@ -2252,6 +2268,7 @@ mod tests {
22522268
description: String::from("description"),
22532269
},
22542270
None,
2271+
vec![],
22552272
)
22562273
.unwrap();
22572274
let err = context.datastore()
@@ -2290,6 +2307,7 @@ mod tests {
22902307
description: String::from("description"),
22912308
},
22922309
None,
2310+
vec![],
22932311
)
22942312
.unwrap();
22952313
let inserted_interface = context
@@ -2334,6 +2352,7 @@ mod tests {
23342352
description: String::from("description"),
23352353
},
23362354
None,
2355+
vec![],
23372356
)
23382357
.unwrap();
23392358
let inserted_interface = context
@@ -2353,6 +2372,7 @@ mod tests {
23532372
description: String::from("description"),
23542373
},
23552374
Some(inserted_interface.ip.ip()),
2375+
vec![],
23562376
)
23572377
.unwrap();
23582378
let result = context
@@ -2601,6 +2621,7 @@ mod tests {
26012621
description: String::from("description"),
26022622
},
26032623
None,
2624+
vec![],
26042625
)
26052626
.unwrap();
26062627
let _ = context
@@ -2620,6 +2641,7 @@ mod tests {
26202641
description: String::from("description"),
26212642
},
26222643
None,
2644+
vec![],
26232645
)
26242646
.unwrap();
26252647
let result = context
@@ -2651,6 +2673,7 @@ mod tests {
26512673
description: String::from("description"),
26522674
},
26532675
None,
2676+
vec![],
26542677
)
26552678
.unwrap();
26562679
let _ = context
@@ -2667,6 +2690,7 @@ mod tests {
26672690
description: String::from("description"),
26682691
},
26692692
None,
2693+
vec![],
26702694
)
26712695
.unwrap();
26722696
let result = context
@@ -2695,6 +2719,7 @@ mod tests {
26952719
description: String::from("description"),
26962720
},
26972721
None,
2722+
vec![],
26982723
)
26992724
.unwrap();
27002725
let _ = context
@@ -2737,6 +2762,7 @@ mod tests {
27372762
description: String::from("description"),
27382763
},
27392764
None,
2765+
vec![],
27402766
)
27412767
.unwrap();
27422768
let _ = context
@@ -2755,6 +2781,7 @@ mod tests {
27552781
description: String::from("description"),
27562782
},
27572783
addr,
2784+
vec![],
27582785
)
27592786
.unwrap();
27602787
let result = context
@@ -2794,6 +2821,7 @@ mod tests {
27942821
description: String::from("description"),
27952822
},
27962823
None,
2824+
vec![],
27972825
)
27982826
.unwrap();
27992827
let _ = context
@@ -2823,6 +2851,7 @@ mod tests {
28232851
description: String::from("description"),
28242852
},
28252853
None,
2854+
vec![],
28262855
)
28272856
.unwrap();
28282857
let result = context
@@ -2856,6 +2885,7 @@ mod tests {
28562885
description: String::from("description"),
28572886
},
28582887
None,
2888+
vec![],
28592889
)
28602890
.unwrap();
28612891
let result = context
@@ -2900,6 +2930,7 @@ mod tests {
29002930
"The random MAC address {:?} is not a valid {} address",
29012931
inserted.mac, kind,
29022932
);
2933+
assert_eq!(inserted.transit_ips, incomplete.transit_ips);
29032934
}
29042935

29052936
// Test that we fail to insert an interface if there are no available slots
@@ -2924,6 +2955,7 @@ mod tests {
29242955
description: String::from("description"),
29252956
},
29262957
None,
2958+
vec![],
29272959
)
29282960
.unwrap();
29292961
let inserted_interface = context
@@ -2959,6 +2991,7 @@ mod tests {
29592991
description: String::from("description"),
29602992
},
29612993
None,
2994+
vec![],
29622995
)
29632996
.unwrap();
29642997
let result = context
@@ -2998,6 +3031,7 @@ mod tests {
29983031
description: String::from("description"),
29993032
},
30003033
None,
3034+
vec![],
30013035
)
30023036
.unwrap();
30033037
let intf = context
@@ -3025,6 +3059,7 @@ mod tests {
30253059
description: String::from("description"),
30263060
},
30273061
None,
3062+
vec![],
30283063
)
30293064
.unwrap();
30303065
let intf = context
@@ -3056,6 +3091,7 @@ mod tests {
30563091
description: String::from("description"),
30573092
},
30583093
None,
3094+
vec![],
30593095
)
30603096
.unwrap();
30613097
let intf = context
@@ -3099,6 +3135,7 @@ mod tests {
30993135
description: String::from("description"),
31003136
},
31013137
Some(IpAddr::V4(addr)),
3138+
vec![],
31023139
)
31033140
.unwrap();
31043141
let _ = context
@@ -3119,6 +3156,7 @@ mod tests {
31193156
description: String::from("description"),
31203157
},
31213158
None,
3159+
vec![],
31223160
)
31233161
.unwrap();
31243162

@@ -3173,4 +3211,45 @@ mod tests {
31733211
"fd00::ffff:ffff:ffff:fffe".parse::<IpAddr>().unwrap(),
31743212
);
31753213
}
3214+
3215+
#[tokio::test]
3216+
async fn test_insert_with_transit_ips() {
3217+
let context = TestContext::new("test_insert_with_transit_ips", 2).await;
3218+
let instance = context.create_stopped_instance().await;
3219+
let instance_id = InstanceUuid::from_untyped_uuid(instance.id());
3220+
3221+
// Create transit IPs to test with
3222+
let transit_ips = vec![
3223+
"10.0.0.0/24".parse().unwrap(),
3224+
"192.168.1.0/24".parse().unwrap(),
3225+
"172.16.0.0/16".parse().unwrap(),
3226+
];
3227+
3228+
let interface = IncompleteNetworkInterface::new_instance(
3229+
Uuid::new_v4(),
3230+
instance_id,
3231+
context.net1.subnets[0].clone(),
3232+
IdentityMetadataCreateParams {
3233+
name: "interface-with-transit".parse().unwrap(),
3234+
description: String::from("Test interface with transit IPs"),
3235+
},
3236+
None, // Auto-assign IP
3237+
transit_ips.clone(),
3238+
)
3239+
.unwrap();
3240+
3241+
let inserted_interface = context
3242+
.datastore()
3243+
.instance_create_network_interface_raw(
3244+
context.opctx(),
3245+
interface.clone(),
3246+
)
3247+
.await
3248+
.expect("Failed to insert interface with transit IPs");
3249+
3250+
// Verify the basic interface properties
3251+
assert_interfaces_eq(&interface, &inserted_interface.clone().into());
3252+
3253+
context.success().await;
3254+
}
31763255
}

nexus/src/app/network_interface.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ impl super::Nexus {
9393
db_subnet,
9494
params.identity.clone(),
9595
params.ip,
96+
params.transit_ips.iter().map(|ip| (*ip).into()).collect(),
9697
)?;
9798
self.db_datastore
9899
.instance_create_network_interface(

nexus/src/app/sagas/instance_create.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,7 @@ async fn create_custom_network_interface(
610610
db_subnet.clone(),
611611
interface_params.identity.clone(),
612612
interface_params.ip,
613+
interface_params.transit_ips.iter().map(|ip| (*ip).into()).collect(),
613614
)
614615
.map_err(ActionError::action_failed)?;
615616
datastore
@@ -681,7 +682,8 @@ async fn create_default_primary_network_interface(
681682
},
682683
vpc_name: default_name.clone(),
683684
subnet_name: default_name.clone(),
684-
ip: None, // Request an IP address allocation
685+
ip: None, // Request an IP address allocation
686+
transit_ips: vec![], // Default interfaces don't use transit IPs
685687
};
686688

687689
// Lookup authz objects, used in the call to actually create the NIC.
@@ -704,6 +706,7 @@ async fn create_default_primary_network_interface(
704706
db_subnet.clone(),
705707
interface_params.identity.clone(),
706708
interface_params.ip,
709+
interface_params.transit_ips.iter().map(|ip| (*ip).into()).collect(),
707710
)
708711
.map_err(ActionError::action_failed)?;
709712
datastore

nexus/tests/integration_tests/endpoints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,7 @@ pub static DEMO_INSTANCE_NIC_CREATE: LazyLock<
709709
vpc_name: DEMO_VPC_NAME.clone(),
710710
subnet_name: DEMO_VPC_SUBNET_NAME.clone(),
711711
ip: None,
712+
transit_ips: vec![],
712713
});
713714
pub static DEMO_INSTANCE_NIC_PUT: LazyLock<
714715
params::InstanceNetworkInterfaceUpdate,

0 commit comments

Comments
 (0)