Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions crates/api-model/src/site_explorer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -929,17 +929,32 @@ impl EndpointExplorationReport {
}
}

fn machine_id_serial_number(&self) -> Option<&str> {
self.systems
.first()
.and_then(|system| system.serial_number.as_deref().map(str::trim))
.filter(|sn| !sn.is_empty())
.or_else(|| {
self.is_dpu().then(|| {
// BF4 reports no system serial in Redfish. The stable product serial is
// on the Bluefield_BMC chassis; use that explicit chassis ID instead of
// depending on chassis collection order or unrelated component serials.
self.chassis
.iter()
.find(|chassis| chassis.id == "Bluefield_BMC")
.and_then(|chassis| chassis.serial_number.as_deref().map(str::trim))
.filter(|serial| !serial.trim().is_empty())
})?
})
}

/// Tries to generate and store a MachineId for the discovered endpoint if
/// enough data for generation is available
pub fn generate_machine_id(
&mut self,
force_predicted_host: bool,
) -> ModelResult<Option<&MachineId>> {
if let Some(serial_number) = self
.systems
.first()
.and_then(|system| system.serial_number.as_ref())
{
if let Some(serial_number) = self.machine_id_serial_number() {
let vendor = self
.systems
.first()
Expand Down
89 changes: 89 additions & 0 deletions crates/bmc-explorer/tests/bluefield4_explore.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
mod common;

use bmc_explorer::nv_generate_exploration_report;
use bmc_mock::{DpuMachineInfo, DpuSettings, HostHardwareType, test_support};
use mac_address::MacAddress;
use model::site_explorer::EndpointType;
use tokio::test;

#[test]
async fn explore_bluefield4_and_generate_machine_id_from_bluefield_bmc_chassis_serial() {
let h = test_support::dell_poweredge_r760_bluefield4_bmc(DpuMachineInfo {
hw_type: HostHardwareType::DellPowerEdgeR760Bf4,
bmc_mac_address: MacAddress::new([0x02, 0x00, 0x00, 0xbf, 0x04, 0x01]),
host_mac_address: MacAddress::new([0x02, 0x00, 0x00, 0xbf, 0x04, 0x02]),
oob_mac_address: MacAddress::new([0x02, 0x00, 0x00, 0xbf, 0x04, 0x03]),
serial: "MT2610604VN4".to_string(),
settings: DpuSettings::default(),
})
.await;
let mut report = nv_generate_exploration_report(h.service_root, &common::explorer_config())
.await
.unwrap();

assert_eq!(report.endpoint_type, EndpointType::Bmc);
assert_eq!(report.vendor, Some(bmc_vendor::BMCVendor::Nvidia));

let system = report.systems.first().expect("systems must be present");
assert_eq!(system.id, "Bluefield");
assert!(
system.serial_number.is_none(),
"BF4 Redfish reports the usable serial on chassis, not system"
);

let chassis_ids: Vec<&str> = report
.chassis
.iter()
.map(|chassis| chassis.id.as_str())
.collect();
assert!(
chassis_ids.contains(&"Bluefield_BMC"),
"Bluefield_BMC chassis must be present: {chassis_ids:?}"
);
assert!(
chassis_ids.contains(&"Card1"),
"Card1 chassis must be present: {chassis_ids:?}"
);
let bmc_chassis_serial = report
.chassis
.iter()
.find(|chassis| chassis.id == "Bluefield_BMC")
.and_then(|chassis| chassis.serial_number.as_deref());
assert_eq!(bmc_chassis_serial, Some("MT2610604VN4"));

assert!(
report
.service
.iter()
.any(|service| service.id == "FirmwareInventory"),
"firmware inventory service must be present"
);

let machine_id = *report
.generate_machine_id(false)
.expect("BF4 report should have enough collected data for machine ID")
.expect("BF4 report should generate a DPU machine ID");

assert!(machine_id.machine_type().is_dpu());
assert_eq!(
machine_id.to_string(),
"fm100dsje1vlqbfpt0vn3hkuijsm07hpd78ctlfhrje2q8ssnj20ke32rdg"
);
assert_eq!(report.machine_id, Some(machine_id));
}
11 changes: 11 additions & 0 deletions crates/bmc-mock/src/test_support/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,17 @@ pub async fn dell_poweredge_r750_bluefield3_bmc(settings: DpuSettings) -> TestBm
.await
}

pub async fn dell_poweredge_r760_bluefield4_bmc(dpu: DpuMachineInfo) -> TestBmcHandle {
let machine_info = MachineInfo::Dpu(dpu);
test_bmc(machine_router(
&machine_info,
Arc::new(NoopCallbacks),
"test-dpu-id".to_string(),
false,
))
.await
}

pub async fn generic_ami_bmc() -> TestBmcHandle {
test_bmc(machine_router(
&host_info(HostHardwareType::GenericAmi),
Expand Down
Loading