Skip to content

Commit 45cb7a1

Browse files
authored
fix(site-explorer): generate BF4 machine IDs from chassis serial (#2903)
Use the BlueField_BMC chassis serial as a DPU fallback when the Redfish system serial is absent, and add BF4 mock coverage for machine ID generation. ## Related issues ## Type of Change - [ ] **Add** - New feature or capability - [ ] **Change** - Changes in existing functionality - [x] **Fix** - Bug fixes - [ ] **Remove** - Removed features or deprecated functionality - [ ] **Internal** - Internal changes (refactoring, tests, docs, etc.) ## Breaking Changes - [ ] **This PR contains breaking changes** ## Testing - [x] Unit tests added/updated - [ ] Integration tests added/updated - [ ] Manual testing performed - [ ] No testing required (docs, internal refactor, etc.) ## Additional Notes Signed-off-by: Dmitry Porokh <dporokh@nvidia.com>
1 parent afd8949 commit 45cb7a1

3 files changed

Lines changed: 120 additions & 5 deletions

File tree

crates/api-model/src/site_explorer/mod.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -929,17 +929,32 @@ impl EndpointExplorationReport {
929929
}
930930
}
931931

932+
fn machine_id_serial_number(&self) -> Option<&str> {
933+
self.systems
934+
.first()
935+
.and_then(|system| system.serial_number.as_deref().map(str::trim))
936+
.filter(|sn| !sn.is_empty())
937+
.or_else(|| {
938+
self.is_dpu().then(|| {
939+
// BF4 reports no system serial in Redfish. The stable product serial is
940+
// on the Bluefield_BMC chassis; use that explicit chassis ID instead of
941+
// depending on chassis collection order or unrelated component serials.
942+
self.chassis
943+
.iter()
944+
.find(|chassis| chassis.id == "Bluefield_BMC")
945+
.and_then(|chassis| chassis.serial_number.as_deref().map(str::trim))
946+
.filter(|serial| !serial.trim().is_empty())
947+
})?
948+
})
949+
}
950+
932951
/// Tries to generate and store a MachineId for the discovered endpoint if
933952
/// enough data for generation is available
934953
pub fn generate_machine_id(
935954
&mut self,
936955
force_predicted_host: bool,
937956
) -> ModelResult<Option<&MachineId>> {
938-
if let Some(serial_number) = self
939-
.systems
940-
.first()
941-
.and_then(|system| system.serial_number.as_ref())
942-
{
957+
if let Some(serial_number) = self.machine_id_serial_number() {
943958
let vendor = self
944959
.systems
945960
.first()
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
mod common;
18+
19+
use bmc_explorer::nv_generate_exploration_report;
20+
use bmc_mock::{DpuMachineInfo, DpuSettings, HostHardwareType, test_support};
21+
use mac_address::MacAddress;
22+
use model::site_explorer::EndpointType;
23+
use tokio::test;
24+
25+
#[test]
26+
async fn explore_bluefield4_and_generate_machine_id_from_bluefield_bmc_chassis_serial() {
27+
let h = test_support::dell_poweredge_r760_bluefield4_bmc(DpuMachineInfo {
28+
hw_type: HostHardwareType::DellPowerEdgeR760Bf4,
29+
bmc_mac_address: MacAddress::new([0x02, 0x00, 0x00, 0xbf, 0x04, 0x01]),
30+
host_mac_address: MacAddress::new([0x02, 0x00, 0x00, 0xbf, 0x04, 0x02]),
31+
oob_mac_address: MacAddress::new([0x02, 0x00, 0x00, 0xbf, 0x04, 0x03]),
32+
serial: "MT2610604VN4".to_string(),
33+
settings: DpuSettings::default(),
34+
})
35+
.await;
36+
let mut report = nv_generate_exploration_report(h.service_root, &common::explorer_config())
37+
.await
38+
.unwrap();
39+
40+
assert_eq!(report.endpoint_type, EndpointType::Bmc);
41+
assert_eq!(report.vendor, Some(bmc_vendor::BMCVendor::Nvidia));
42+
43+
let system = report.systems.first().expect("systems must be present");
44+
assert_eq!(system.id, "Bluefield");
45+
assert!(
46+
system.serial_number.is_none(),
47+
"BF4 Redfish reports the usable serial on chassis, not system"
48+
);
49+
50+
let chassis_ids: Vec<&str> = report
51+
.chassis
52+
.iter()
53+
.map(|chassis| chassis.id.as_str())
54+
.collect();
55+
assert!(
56+
chassis_ids.contains(&"Bluefield_BMC"),
57+
"Bluefield_BMC chassis must be present: {chassis_ids:?}"
58+
);
59+
assert!(
60+
chassis_ids.contains(&"Card1"),
61+
"Card1 chassis must be present: {chassis_ids:?}"
62+
);
63+
let bmc_chassis_serial = report
64+
.chassis
65+
.iter()
66+
.find(|chassis| chassis.id == "Bluefield_BMC")
67+
.and_then(|chassis| chassis.serial_number.as_deref());
68+
assert_eq!(bmc_chassis_serial, Some("MT2610604VN4"));
69+
70+
assert!(
71+
report
72+
.service
73+
.iter()
74+
.any(|service| service.id == "FirmwareInventory"),
75+
"firmware inventory service must be present"
76+
);
77+
78+
let machine_id = *report
79+
.generate_machine_id(false)
80+
.expect("BF4 report should have enough collected data for machine ID")
81+
.expect("BF4 report should generate a DPU machine ID");
82+
83+
assert!(machine_id.machine_type().is_dpu());
84+
assert_eq!(
85+
machine_id.to_string(),
86+
"fm100dsje1vlqbfpt0vn3hkuijsm07hpd78ctlfhrje2q8ssnj20ke32rdg"
87+
);
88+
assert_eq!(report.machine_id, Some(machine_id));
89+
}

crates/bmc-mock/src/test_support/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,17 @@ pub async fn dell_poweredge_r750_bluefield3_bmc(settings: DpuSettings) -> TestBm
199199
.await
200200
}
201201

202+
pub async fn dell_poweredge_r760_bluefield4_bmc(dpu: DpuMachineInfo) -> TestBmcHandle {
203+
let machine_info = MachineInfo::Dpu(dpu);
204+
test_bmc(machine_router(
205+
&machine_info,
206+
Arc::new(NoopCallbacks),
207+
"test-dpu-id".to_string(),
208+
false,
209+
))
210+
.await
211+
}
212+
202213
pub async fn generic_ami_bmc() -> TestBmcHandle {
203214
test_bmc(machine_router(
204215
&host_info(HostHardwareType::GenericAmi),

0 commit comments

Comments
 (0)