Skip to content

Commit 208de58

Browse files
committed
feat(zero-dpu): Allow flat VPC's to not belong to a network segment
In an environment without DPU's, we don't get dynamic network isolation, and the expectation is that there is a single, flat network address space, modeled as a single network segment. In this case, we still want to model VPC's for things like NVLink partitions, where tenants will have the understanding that the network is not isolated between VPC's. To make this work, a network segment can no longer be the "link" that links an instance to a VPC. Before, an instance interface maps to a machine interface, which maps to a network segment, which maps to a VPC. But if multiple VPC's are all sharing a network segment, this link doesn't exist (namely, network_segments.vpc_id will be null), and so we need the instance address itself to store the VPC ID on it. This change adds a vpc_id column to instance_addresses, so that we know the VPC of any given address without having to consult the network_segments table. It also changes the AllocateInstance API so that you can pass a `auto_config` object which allows specifying a VPC ID as part of the allocation request. (This replaces the `auto: bool` field, such that auto is implied if `auto_config` is set.)
1 parent 3e99dd2 commit 208de58

46 files changed

Lines changed: 1162 additions & 373 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/admin-cli/src/instance/allocate/args.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
*/
1717

1818
use carbide_uuid::machine::MachineId;
19-
use carbide_uuid::vpc::VpcPrefixId;
19+
use carbide_uuid::vpc::{VpcId, VpcPrefixId};
2020
use clap::{ArgGroup, Parser};
2121
use rpc::forge::{InstanceOperatingSystemConfig, InstanceSpxConfig};
2222

2323
#[derive(Parser, Debug)]
24-
#[clap(group(ArgGroup::new("selector").required(true).args(&["subnet", "vpc_prefix_id"])))]
24+
#[clap(group(ArgGroup::new("selector").required(true).multiple(true).args(&["subnet", "vpc_prefix_id", "flat_vpc_id"])))]
2525
#[command(after_long_help = "\
2626
EXAMPLES:
2727
@@ -101,8 +101,11 @@ pub struct Args {
101101
#[clap(short, long, help = "The VPC prefix to assign to a PF")]
102102
pub vpc_prefix_id: Vec<VpcPrefixId>,
103103

104-
#[clap(long, help = "Allocate a zero-dpu host")]
105-
pub zero_dpu: bool,
104+
#[clap(
105+
long,
106+
help = "Create an instance in the given \"flat\" VPC, for machines without DPUs"
107+
)]
108+
pub flat_vpc_id: Option<VpcId>,
106109

107110
#[clap(long, help = "The VPC prefix to assign to a VF")]
108111
pub vf_vpc_prefix_id: Vec<VpcPrefixId>,

crates/admin-cli/src/instance/allocate/cmd.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ pub async fn allocate(
6767
api_client,
6868
&mut machine_ids,
6969
min_interface_count,
70-
allocate_request.zero_dpu,
70+
allocate_request.flat_vpc_id,
7171
)
7272
.await
7373
else {
@@ -109,7 +109,7 @@ pub async fn allocate(
109109
api_client,
110110
&mut machine_ids,
111111
min_interface_count,
112-
allocate_request.zero_dpu,
112+
allocate_request.flat_vpc_id,
113113
)
114114
.await
115115
else {

crates/admin-cli/src/instance/show/cmd.rs

Lines changed: 119 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -178,99 +178,125 @@ async fn convert_instance_to_nice_format(
178178

179179
let width = 25;
180180
writeln!(&mut lines, "INTERFACES:")?;
181-
let if_configs = instance
181+
let network_config = instance
182182
.config
183183
.as_ref()
184-
.and_then(|config| config.network.as_ref())
184+
.and_then(|config| config.network.as_ref());
185+
let if_configs = network_config
185186
.map(|config| config.interfaces.as_slice())
186187
.unwrap_or_default();
188+
let auto_network = network_config.is_some_and(|config| config.auto_config.is_some());
187189
let if_status = instance
188190
.status
189191
.as_ref()
190192
.and_then(|status| status.network.as_ref())
191193
.map(|status| status.interfaces.as_slice())
192194
.unwrap_or_default();
193195

194-
if if_configs.is_empty() || if_status.is_empty() {
196+
if if_status.is_empty() {
195197
writeln!(&mut lines, "\tEMPTY")?;
196-
} else if if_configs.len() != if_status.len() {
198+
} else if !auto_network && if_configs.len() != if_status.len() {
197199
writeln!(&mut lines, "\tLENGTH MISMATCH")?;
198200
} else {
199-
for (i, interface) in if_configs.iter().enumerate() {
200-
let status = &if_status[i];
201-
202-
let vpc = if let Some(network_segment_id) = interface.network_segment_id {
203-
get_vpc_for_interface_network_segment(api_client, network_segment_id).await?
204-
} else {
205-
None
206-
};
207-
208-
let data: &[(&str, Cow<str>)] = &[
209-
(
210-
"FUNCTION_TYPE",
211-
forgerpc::InterfaceFunctionType::try_from(interface.function_type)
212-
.ok()
213-
.map(|ty| format!("{ty:?}").into())
214-
.unwrap_or_else(|| "INVALID".into()),
215-
),
216-
(
217-
"VF ID",
218-
status
219-
.virtual_function_id
220-
.map(|id| id.to_string().into())
221-
.unwrap_or_default(),
222-
),
223-
(
224-
"SEGMENT ID",
225-
interface
226-
.network_segment_id
227-
.unwrap_or_default()
228-
.to_string()
201+
let vpcs: Vec<Vpc> = if auto_network {
202+
futures::future::join_all(
203+
if_status
204+
.iter()
205+
.filter_map(|s| s.vpc_id)
206+
.map(|vpc_id| get_vpc_by_id(api_client, vpc_id)),
207+
)
208+
.await
209+
.into_iter()
210+
.collect::<Result<Vec<_>, _>>()?
211+
.into_iter()
212+
} else {
213+
futures::future::join_all(if_configs.iter().filter_map(|c| c.network_segment_id).map(
214+
|segment_id| async move {
215+
get_vpc_for_interface_network_segment(api_client, segment_id).await
216+
},
217+
))
218+
.await
219+
.into_iter()
220+
.collect::<Result<Vec<_>, _>>()?
221+
.into_iter()
222+
}
223+
.flatten()
224+
.collect();
225+
if !auto_network && if_configs.len() != if_status.len() {
226+
writeln!(&mut lines, "\tLENGTH MISMATCH")?;
227+
} else {
228+
for (idx, status) in if_status.iter().enumerate() {
229+
let vpc = vpcs.get(idx);
230+
let if_config = if_configs.get(idx);
231+
let data: &[(&str, Cow<str>)] = &[
232+
(
233+
"FUNCTION_TYPE",
234+
format!(
235+
"{:?}",
236+
match status.virtual_function_id {
237+
Some(_) => forgerpc::InterfaceFunctionType::Virtual,
238+
None => forgerpc::InterfaceFunctionType::Physical,
239+
}
240+
)
229241
.into(),
230-
),
231-
(
232-
"VPC PREFIX ID",
233-
match &interface.network_details {
234-
Some(forgerpc::instance_interface_config::NetworkDetails::SegmentId(_)) => {
235-
"Segment Based Allocation".into()
236-
}
237-
Some(forgerpc::instance_interface_config::NetworkDetails::VpcPrefixId(
238-
x,
239-
)) => x.to_string().into(),
240-
None => "NA".into(),
241-
},
242-
),
243-
(
244-
"MAC ADDR",
245-
status
246-
.mac_address
247-
.as_ref()
248-
.map(|s| s.as_str().into())
249-
.unwrap_or_default(),
250-
),
251-
("ADDRESSES", status.addresses.as_slice().join(", ").into()),
252-
(
253-
"VPC ID",
254-
vpc.as_ref()
255-
.map(|v| v.id.unwrap_or_default().to_string().into())
256-
.unwrap_or("<not found>".into()),
257-
),
258-
(
259-
"VPC NAME",
260-
vpc.as_ref()
261-
.and_then(|v| v.metadata.as_ref())
262-
.map(|v| Cow::Borrowed(v.name.as_str()))
263-
.unwrap_or("<not found>".into()),
264-
),
265-
];
266-
267-
for (key, value) in data {
268-
writeln!(&mut lines, "\t{key:<width$}: {value}")?;
242+
),
243+
(
244+
"VF ID",
245+
status
246+
.virtual_function_id
247+
.map(|id| id.to_string().into())
248+
.unwrap_or_default(),
249+
),
250+
(
251+
"SEGMENT ID",
252+
if_config
253+
.and_then(|c| c.network_segment_id)
254+
.unwrap_or_default()
255+
.to_string()
256+
.into(),
257+
),
258+
(
259+
"VPC PREFIX ID",
260+
match if_config.and_then(|c| c.network_details.as_ref()) {
261+
Some(
262+
forgerpc::instance_interface_config::NetworkDetails::SegmentId(_),
263+
) => "Segment Based Allocation".into(),
264+
Some(
265+
forgerpc::instance_interface_config::NetworkDetails::VpcPrefixId(x),
266+
) => x.to_string().into(),
267+
None => "NA".into(),
268+
},
269+
),
270+
(
271+
"MAC ADDR",
272+
status
273+
.mac_address
274+
.as_ref()
275+
.map(|s| s.as_str().into())
276+
.unwrap_or_default(),
277+
),
278+
("ADDRESSES", status.addresses.as_slice().join(", ").into()),
279+
(
280+
"VPC ID",
281+
vpc.map(|v| v.id.unwrap_or_default().to_string().into())
282+
.unwrap_or("<not found>".into()),
283+
),
284+
(
285+
"VPC NAME",
286+
vpc.and_then(|v| v.metadata.as_ref())
287+
.map(|v| Cow::Borrowed(v.name.as_str()))
288+
.unwrap_or("<not found>".into()),
289+
),
290+
];
291+
292+
for (key, value) in data {
293+
writeln!(&mut lines, "\t{key:<width$}: {value}")?;
294+
}
295+
writeln!(
296+
&mut lines,
297+
"\t--------------------------------------------------"
298+
)?;
269299
}
270-
writeln!(
271-
&mut lines,
272-
"\t--------------------------------------------------"
273-
)?;
274300
}
275301
}
276302

@@ -555,7 +581,6 @@ pub async fn handle_show(args: Args, ctx: &mut RuntimeContext) -> CarbideCliResu
555581
Ok(())
556582
}
557583

558-
#[allow(deprecated)]
559584
async fn get_vpc_for_interface_network_segment(
560585
api_client: &ApiClient,
561586
network_segment_id: NetworkSegmentId,
@@ -565,10 +590,10 @@ async fn get_vpc_for_interface_network_segment(
565590
.await?;
566591

567592
if !network_segments.network_segments.is_empty()
568-
&& let Some(vpc_id) = network_segments
569-
.network_segments
570-
.first()
571-
.and_then(|s| s.config.as_ref().and_then(|c| c.vpc_id).or(s.vpc_id))
593+
&& let Some(vpc_id) = network_segments.network_segments.first().and_then(|s| {
594+
#[allow(deprecated)]
595+
s.config.as_ref().and_then(|c| c.vpc_id).or(s.vpc_id)
596+
})
572597
{
573598
let vpc_ids: Vec<VpcId> = vec![vpc_id];
574599
Ok(api_client
@@ -582,3 +607,15 @@ async fn get_vpc_for_interface_network_segment(
582607
Ok(None)
583608
}
584609
}
610+
611+
async fn get_vpc_by_id(api_client: &ApiClient, vpc_id: VpcId) -> CarbideCliResult<Option<Vpc>> {
612+
Ok(api_client
613+
.0
614+
.find_vpcs_by_ids(VpcsByIdsRequest {
615+
vpc_ids: vec![vpc_id],
616+
})
617+
.await?
618+
.vpcs
619+
.into_iter()
620+
.next())
621+
}

crates/admin-cli/src/machine/show/cmd.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use std::fmt::Write;
2121
use ::rpc::admin_cli::OutputFormat;
2222
use ::rpc::forge as forgerpc;
2323
use carbide_uuid::machine::MachineId;
24+
use carbide_uuid::vpc::VpcId;
2425
use prettytable::{Table, row};
2526
use rpc::Machine;
2627

@@ -428,7 +429,7 @@ pub async fn get_next_free_machine(
428429
api_client: &ApiClient,
429430
machine_ids: &mut VecDeque<MachineId>,
430431
min_interface_count: usize,
431-
zero_dpu: bool,
432+
flat_vpc_id: Option<VpcId>,
432433
) -> Option<Machine> {
433434
while let Some(id) = machine_ids.pop_front() {
434435
tracing::debug!("Checking {}", id);
@@ -437,7 +438,7 @@ pub async fn get_next_free_machine(
437438
tracing::debug!("Machine is not ready");
438439
continue;
439440
}
440-
if zero_dpu {
441+
if flat_vpc_id.is_some() {
441442
return Some(machine);
442443
}
443444
if let Some(discovery_info) = &machine.discovery_info {

0 commit comments

Comments
 (0)