Skip to content

Commit d781f9c

Browse files
authored
Merge branch 'main' into fix/setup-sigpipe-head
2 parents ad52d81 + 611e96b commit d781f9c

137 files changed

Lines changed: 5362 additions & 1214 deletions

File tree

Some content is hidden

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

.github/actions/setup-mkosi-environment/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ inputs:
44
rust-version:
55
description: 'Rust toolchain version to install'
66
required: false
7-
default: '1.90.0'
7+
default: '1.96.0'
88
arch:
99
description: 'Target architecture (x86_64 or aarch64)'
1010
required: false

.github/workflows/build-boot-artifacts.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ jobs:
140140
if: inputs.build_type == 'ephemeral'
141141
uses: ./.github/actions/setup-mkosi-environment
142142
with:
143-
rust-version: '1.90.0'
143+
rust-version: '1.96.0'
144144
arch: ${{ inputs.arch }}
145145

146146
# Setup Docker for container-based builds

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ authors = ["NVIDIA Carbide Engineering <carbide-dev@exchange.nvidia.com>"]
2626

2727
[workspace.dependencies]
2828
clap = { version = "4", features = ["derive", "env"] }
29-
libredfish = { git = "https://github.com/NVIDIA/libredfish.git", tag = "v0.44.10" }
29+
libredfish = { git = "https://github.com/NVIDIA/libredfish.git", tag = "v0.44.11" }
3030
librms = { git = "https://github.com/NVIDIA/nv-rms-client.git", tag = "v0.9.0-rc1" }
3131
ansi-to-html = "0.2.2"
3232

Makefile.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ BUILD_CONTAINER_AARCH64_URL = { value = "urm.nvidia.com/swngc-ngcc-docker-local/
4646
#
4747
# Make sure to update the RUST_NIGHTLY variable in
4848
# dev/docker/Dockerfile.build-container-x86_64 if you change this.
49-
RUST_NIGHTLY = "nightly-2026-05-27"
49+
RUST_NIGHTLY = "nightly-2026-06-16"
5050

5151
[tasks.book]
5252
workspace = false
@@ -203,7 +203,7 @@ echo "Image carbide-build-x86_64 built. Run: cargo make cargo-docker -- build -p
203203

204204
[tasks.build-cargo-docker-image-minimal]
205205
category = "Build"
206-
description = "Build minimal Cargo Docker image (Rust 1.90 + protoc). Quick (~2-5 min). Required once before cargo-docker-minimal for workspace builds (e.g. carbide-rpc)."
206+
description = "Build minimal Cargo Docker image (Rust 1.96 + protoc). Quick (~2-5 min). Required once before cargo-docker-minimal for workspace builds (e.g. carbide-rpc)."
207207
workspace = false
208208
script = '''
209209
set -e
@@ -213,7 +213,7 @@ echo "Image carbide-build-minimal built. Run: cargo make cargo-docker-minimal --
213213

214214
[tasks.build-pxe-build-container]
215215
category = "Build"
216-
description = "Build the PXE artifact build container image (Ubuntu 24.04 + Rust 1.90 + mkosi deps). Required once before pxe-docker-x86."
216+
description = "Build the PXE artifact build container image (Ubuntu 24.04 + Rust 1.96 + mkosi deps). Required once before pxe-docker-x86."
217217
workspace = false
218218
script = '''
219219
set -e
@@ -301,7 +301,7 @@ fi
301301

302302
[tasks.cargo-docker-minimal]
303303
category = "Build"
304-
description = "Run Cargo in minimal Linux image (Rust 1.90 + protoc). Requires build-cargo-docker-image-minimal once (~2-5 min). For DB tests set DATABASE_URL (use host.docker.internal, not localhost). Runs as root and chowns /code afterward so target/ stays writable on macOS/Colima."
304+
description = "Run Cargo in minimal Linux image (Rust 1.96 + protoc). Requires build-cargo-docker-image-minimal once (~2-5 min). For DB tests set DATABASE_URL (use host.docker.internal, not localhost). Runs as root and chowns /code afterward so target/ stays writable on macOS/Colima."
305305
workspace = false
306306
dependencies = ["check-cargo-home-set"]
307307
script = '''

crates/admin-cli/src/browse/nmxc/args.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ use rpc::forge as forgerpc;
2626
#[clap(rename_all = "kebab-case")]
2727
pub enum NmxcOperationArg {
2828
ComputeNodeInfoList,
29+
SwitchNodeInfoList,
2930
GpuInfo,
3031
GpuInfoList,
32+
PartitionInfoList,
33+
GetDomainProperties,
3134
}
3235

3336
impl From<NmxcOperationArg> for forgerpc::NmxcBrowseOperation {
@@ -36,8 +39,15 @@ impl From<NmxcOperationArg> for forgerpc::NmxcBrowseOperation {
3639
NmxcOperationArg::ComputeNodeInfoList => {
3740
forgerpc::NmxcBrowseOperation::ComputeNodeInfoList
3841
}
42+
NmxcOperationArg::SwitchNodeInfoList => {
43+
forgerpc::NmxcBrowseOperation::SwitchNodeInfoList
44+
}
3945
NmxcOperationArg::GpuInfo => forgerpc::NmxcBrowseOperation::GpuInfo,
4046
NmxcOperationArg::GpuInfoList => forgerpc::NmxcBrowseOperation::GpuInfoList,
47+
NmxcOperationArg::PartitionInfoList => forgerpc::NmxcBrowseOperation::PartitionInfoList,
48+
NmxcOperationArg::GetDomainProperties => {
49+
forgerpc::NmxcBrowseOperation::GetDomainProperties
50+
}
4151
}
4252
}
4353
}
@@ -52,9 +62,18 @@ List the GPUs on a chassis via NMX-C:
5262
List the compute nodes on a chassis:
5363
$ nico-admin-cli browse nmxc --chassis-serial 1234567890 --operation compute-node-info-list
5464
65+
List the switch nodes on a chassis:
66+
$ nico-admin-cli browse nmxc --chassis-serial 1234567890 --operation switch-node-info-list
67+
5568
Get info for a specific GPU UID:
5669
$ nico-admin-cli browse nmxc --chassis-serial 1234567890 --operation gpu-info --gpu-uid 42
5770
71+
List NMX-C partitions:
72+
$ nico-admin-cli browse nmxc --chassis-serial 1234567890 --operation partition-info-list
73+
74+
Get NMX-C domain properties:
75+
$ nico-admin-cli browse nmxc --chassis-serial 1234567890 --operation get-domain-properties
76+
5877
")]
5978
pub struct Args {
6079
#[clap(long, help = "Chassis serial number")]

crates/agent/src/dhcp_server_grpc_client.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub mod proto {
2121

2222
use carbide_rpc_utils::dhcp::{
2323
DhcpConfig as ModelDhcpConfig, HostConfig as ModelHostConfig,
24-
InterfaceInfo as ModelInterfaceInfo,
24+
InterfaceInfo as ModelInterfaceInfo, InterfaceInfoV6 as ModelInterfaceInfoV6,
2525
};
2626
use carbide_uuid::machine::MachineInterfaceId;
2727
use proto::dhcp_server_control_client::DhcpServerControlClient;
@@ -47,6 +47,28 @@ impl From<ModelDhcpConfig> for proto::DhcpConfig {
4747
.collect(),
4848
carbide_provisioning_server_ipv4: c.carbide_provisioning_server_ipv4.to_string(),
4949
carbide_dhcp_server: c.carbide_dhcp_server.to_string(),
50+
carbide_nameservers_v6: c
51+
.carbide_nameservers_v6
52+
.iter()
53+
.map(|ip| ip.to_string())
54+
.collect(),
55+
carbide_ntpservers_v6: c
56+
.carbide_ntpservers_v6
57+
.iter()
58+
.map(|ip| ip.to_string())
59+
.collect(),
60+
carbide_dhcp_server_v6: c.carbide_dhcp_server_v6.map(|ip| ip.to_string()),
61+
dhcpv6_preferred_lifetime_secs: c.dhcpv6_preferred_lifetime_secs,
62+
dhcpv6_valid_lifetime_secs: c.dhcpv6_valid_lifetime_secs,
63+
}
64+
}
65+
}
66+
67+
impl From<ModelInterfaceInfoV6> for proto::InterfaceInfoV6 {
68+
fn from(i: ModelInterfaceInfoV6) -> Self {
69+
proto::InterfaceInfoV6 {
70+
address: i.address.map(|ip| ip.to_string()),
71+
prefix: i.prefix,
5072
}
5173
}
5274
}
@@ -60,6 +82,7 @@ impl From<ModelInterfaceInfo> for proto::InterfaceInfo {
6082
fqdn: i.fqdn,
6183
booturl: i.booturl,
6284
mtu: i.mtu,
85+
ipv6: i.ipv6.map(Into::into),
6386
}
6487
}
6588
}

crates/agent/src/ethernet_virtualization.rs

Lines changed: 101 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,52 @@ pub struct ServiceAddresses {
138138
pub nameservers: Vec<IpAddr>,
139139
}
140140

141+
fn build_dhcp_ntp_servers(
142+
nc: &rpc::ManagedHostNetworkConfigResponse,
143+
service_addrs: &ServiceAddresses,
144+
) -> Vec<Ipv4Addr> {
145+
// Start with the NTP servers from the service addresses, which is read from carbide-ntp.forge.
146+
let mut ntp_servers = service_addrs
147+
.ntpservers
148+
.iter()
149+
.filter_map(|x| match x {
150+
IpAddr::V4(x) => Some(*x),
151+
_ => None,
152+
})
153+
.collect::<Vec<Ipv4Addr>>();
154+
155+
// If the site has configured NTP servers, use them instead.
156+
if !nc.ntp_servers.is_empty() {
157+
let site_ntp_servers: Vec<Ipv4Addr> = nc.ntp_servers
158+
.iter()
159+
.filter_map(|s| match IpAddr::from_str(s) {
160+
Ok(IpAddr::V4(ip)) => Some(ip),
161+
Ok(IpAddr::V6(_)) => {
162+
tracing::debug!(
163+
ntp_server = %s,
164+
"IPv6 NTP server from ManagedHostNetworkConfigResponse is ignored for DHCPv4 config"
165+
);
166+
None
167+
}
168+
Err(e) => {
169+
tracing::debug!(
170+
ntp_server = %s,
171+
error = %e,
172+
"Invalid NTP server IP from ManagedHostNetworkConfigResponse, ignoring"
173+
);
174+
None
175+
}
176+
})
177+
.collect();
178+
179+
if !site_ntp_servers.is_empty() {
180+
ntp_servers = site_ntp_servers;
181+
}
182+
}
183+
184+
ntp_servers
185+
}
186+
141187
/// How we tell HBN to notice the new file we wrote
142188
#[derive(Debug)]
143189
struct PostAction {
@@ -1029,14 +1075,7 @@ async fn update_dhcp_via_grpc(
10291075
})
10301076
.collect::<Vec<Ipv4Addr>>();
10311077

1032-
let ntpservers_v4 = service_addrs
1033-
.ntpservers
1034-
.iter()
1035-
.filter_map(|x| match x {
1036-
IpAddr::V4(x) => Some(*x),
1037-
_ => None,
1038-
})
1039-
.collect::<Vec<Ipv4Addr>>();
1078+
let ntpservers_v4 = build_dhcp_ntp_servers(network_config, service_addrs);
10401079

10411080
let pxe_ip_v4 = service_addrs
10421081
.pxe_ips
@@ -1445,14 +1484,7 @@ fn write_dhcp_v4_server_config(
14451484
})
14461485
.collect::<Vec<Ipv4Addr>>();
14471486

1448-
let ntpservers_v4 = service_addrs
1449-
.ntpservers
1450-
.iter()
1451-
.filter_map(|x| match x {
1452-
IpAddr::V4(x) => Some(*x),
1453-
_ => None,
1454-
})
1455-
.collect::<Vec<Ipv4Addr>>();
1487+
let ntpservers_v4 = build_dhcp_ntp_servers(nc, service_addrs);
14561488

14571489
let pxe_ip_v4 = service_addrs
14581490
.pxe_ips
@@ -1911,6 +1943,54 @@ mod tests {
19111943
carbide_host_support::init_logging("nico-dpu-agent").unwrap();
19121944
}
19131945

1946+
#[test]
1947+
fn test_build_dhcp_ntp_servers() {
1948+
let service_addrs = ServiceAddresses {
1949+
pxe_ips: vec![],
1950+
ntpservers: vec![IpAddr::from([192, 0, 2, 20])],
1951+
nameservers: vec![],
1952+
};
1953+
let nc = rpc::ManagedHostNetworkConfigResponse {
1954+
ntp_servers: vec!["198.51.100.1".to_string(), "198.51.100.2".to_string()],
1955+
..Default::default()
1956+
};
1957+
1958+
let out = build_dhcp_ntp_servers(&nc, &service_addrs);
1959+
assert_eq!(
1960+
out,
1961+
vec![
1962+
Ipv4Addr::from([198, 51, 100, 1]),
1963+
Ipv4Addr::from([198, 51, 100, 2])
1964+
]
1965+
);
1966+
}
1967+
1968+
#[test]
1969+
fn test_build_dhcp_ntp_servers_fallback() {
1970+
let service_addrs = ServiceAddresses {
1971+
pxe_ips: vec![],
1972+
ntpservers: vec![IpAddr::from([192, 0, 2, 20])],
1973+
nameservers: vec![],
1974+
};
1975+
1976+
let empty_nc = rpc::ManagedHostNetworkConfigResponse::default();
1977+
1978+
assert_eq!(
1979+
build_dhcp_ntp_servers(&empty_nc, &service_addrs),
1980+
vec![Ipv4Addr::from([192, 0, 2, 20])]
1981+
);
1982+
1983+
let invalid_nc = rpc::ManagedHostNetworkConfigResponse {
1984+
ntp_servers: vec!["not-an-ip".to_string(), "2001:db8::1".to_string()],
1985+
..Default::default()
1986+
};
1987+
1988+
assert_eq!(
1989+
build_dhcp_ntp_servers(&invalid_nc, &service_addrs),
1990+
vec![Ipv4Addr::from([192, 0, 2, 20])]
1991+
);
1992+
}
1993+
19141994
#[test]
19151995
fn test_hostname() -> Result<(), Box<dyn std::error::Error>> {
19161996
let syscall_h = super::hostname()?;
@@ -2935,6 +3015,7 @@ mod tests {
29353015

29363016
// yes it's in there twice I dunno either
29373017
dhcp_servers: vec!["10.217.5.197".to_string(), "10.217.5.197".to_string()],
3018+
ntp_servers: vec![],
29383019
vni_device: "vxlan48".to_string(),
29393020

29403021
managed_host_config: Some(netconf),
@@ -3382,6 +3463,7 @@ mod tests {
33823463
rebinding_time_secs: 432000,
33833464
carbide_api_url: None,
33843465
carbide_dhcp_server: Ipv4Addr::from([10, 217, 5, 39]),
3466+
..Default::default()
33853467
};
33863468

33873469
let mut network_config = rpc::ManagedHostNetworkConfigResponse {
@@ -3421,6 +3503,7 @@ mod tests {
34213503

34223504
// yes it's in there twice I dunno either
34233505
dhcp_servers: vec!["10.217.5.197".to_string(), "10.217.5.197".to_string()],
3506+
ntp_servers: vec![],
34243507
vni_device: "vxlan48".to_string(),
34253508

34263509
managed_host_config: Some(netconf),
@@ -3574,6 +3657,7 @@ mod tests {
35743657
rebinding_time_secs: 432000,
35753658
carbide_api_url: None,
35763659
carbide_dhcp_server: Ipv4Addr::from([10, 217, 5, 39]),
3660+
..Default::default()
35773661
};
35783662
let dhcp_contents = super::read_limited(g.path())?;
35793663
assert!(dhcp_contents.contains("vlan196"));
@@ -3616,6 +3700,7 @@ mod tests {
36163700
routing_profile: None,
36173701
traffic_intercept_config: None,
36183702
dhcp_servers: vec![],
3703+
ntp_servers: vec![],
36193704
vni_device: "vxlan48".to_string(),
36203705
managed_host_config: Some(netconf),
36213706
managed_host_config_version: "V1-T1".to_string(),

crates/agent/src/tests/full.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,7 @@ async fn handle_netconf(AxumState(state): AxumState<Arc<Mutex<State>>>) -> impl
885885
}),
886886

887887
dhcp_servers: vec!["127.0.0.1".to_string()],
888+
ntp_servers: vec![],
888889
vni_device: "".to_string(),
889890

890891
managed_host_config: Some(rpc::forge::ManagedHostNetworkConfig {

crates/api-core/src/cfg/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ applicable.
2020
| `ib_config` | `Option<IBFabricConfig>` || InfiniBand fabric configuration (see [IBFabricConfig](#ibfabricconfig)). |
2121
| `asn` | `u32` | **required** | Autonomous System Number, fixed per environment. Used by nico-dpu-agent for `frr.conf` BGP routing. |
2222
| `dhcp_servers` | `Vec<Ipv4Addr>` | `[]` | DHCP server addresses announced to DPUs during network provisioning. |
23+
| `ntp_servers` | `Vec<Ipv4Addr>` | `[]` | Site-level NTP server IPs used for BMC time configuration and DHCP NTP Server configuration. |
2324
| `route_servers` | `Vec<String>` | `[]` | Route server IPs for L2VPN Ethernet Virtual network support. |
2425
| `enable_route_servers` | `bool` | `false` | Enables route server injection into DPU FRR configs for L2VPN. |
2526
| `deny_prefixes` | `Vec<Ipv4Network>` | `[]` | IPv4 CIDR prefixes that tenant instances are blocked from reaching. Generates iptables DROP rules and nvue ACL policies. |
@@ -33,7 +34,7 @@ applicable.
3334
| `listen_mode` | `ListenMode` | `Tls` | Transport mode: `plaintext_http1`, `plaintext_http2`, or `tls`. |
3435
| `auth` | `Option<AuthConfig>` || Authentication/authorization settings (see [AuthConfig](#authconfig)). |
3536
| `pools` | `Option<HashMap<String, ResourcePoolDef>>` || Resource pools that allocate IPs, VNIs, etc. Required but `Option` for partial-config merging. |
36-
| `networks` | `Option<HashMap<String, NetworkDefinition>>` || Networks created at startup. Alternative: `CreateNetworkSegment` gRPC. |
37+
| `networks` | `Option<HashMap<String, NetworkDefinition>>` || Networks created at startup. Alternative: `CreateNetworkSegment` gRPC. `NetworkDefinition` supports dual-stack seed-time segments with optional `prefix_v6` and `dhcpv6_link_address`; config edits do not retrofit prefixes onto an already-seeded segment because seed definitions are snapshotted on first create. |
3738
| `dpu_ipmi_tool_impl` | `Option<String>` || IPMI tool implementation for DPU power control (`"prod"` or `"fake"`). |
3839
| `dpu_ipmi_reboot_attempts` | `Option<u32>` || Retry count when IPMI errors during DPU reboot. |
3940
| `bmc_session_lockout_threshold` | `u32` | `3` | Consecutive BMC HTTP 401/403 responses before session-token login attempts stop for that BMC. |

0 commit comments

Comments
 (0)