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
128 changes: 95 additions & 33 deletions crates/api-model/src/site_explorer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,28 +626,28 @@ impl ExploredDpu {
.report
.create_temporary_dmi_data(serial_number, vendor, model);

let chassis_map = self
.report
.chassis
.iter()
.map(|x| (x.id.as_str(), x))
.collect::<HashMap<_, _>>();
let inventory_map = self.report.get_inventory_map();

let dpu_data = DpuData {
factory_mac_address: self
.host_pf_mac_address
.ok_or(ModelError::MissingArgument("Missing base mac"))?
.to_string(),
part_number: chassis_map
.get("Card1")
.and_then(|value| value.part_number.as_ref())
.unwrap_or(&"".to_string())
part_number: self
.report
.chassis
.iter()
.filter(|chassis| is_dpu_product_chassis_id(&chassis.id))
.find_map(chassis_part_number)
.unwrap_or("")
.to_string(),
part_description: chassis_map
.get("Card1")
.and_then(|value| value.model.as_ref())
.unwrap_or(&"".to_string())
part_description: self
.report
.chassis
.iter()
.filter(|chassis| is_dpu_product_chassis_id(&chassis.id))
.find_map(chassis_model)
.unwrap_or("")
.to_string(),
firmware_version: inventory_map
.get("DPU_NIC")
Expand Down Expand Up @@ -837,8 +837,8 @@ impl EndpointExplorationReport {
// `Bluefield_BMC` on some trays, `BlueField_BMC_0` on others).
self.chassis
.iter()
.find(|chassis| is_dpu_product_chassis_id(&chassis.id))
.and_then(chassis_part_number)
.filter(|chassis| is_dpu_product_chassis_id(&chassis.id))
.find_map(chassis_part_number)
})
}

Expand Down Expand Up @@ -877,7 +877,7 @@ impl EndpointExplorationReport {
if !self
.systems
.first()
.map(|system| is_bluefield_system_id(&system.id))
.map(is_bluefield_system)
.unwrap_or(false)
{
return None;
Expand All @@ -890,8 +890,14 @@ impl EndpointExplorationReport {
.collect::<HashMap<_, _>>();
let model = chassis_map
.get("Card1")
.and_then(|value| value.model.as_ref())
.unwrap_or(&"".to_string())
.and_then(|value| chassis_model(value))
.or_else(|| {
self.chassis
.iter()
.filter(|chassis| is_dpu_product_chassis_id(&chassis.id))
.find_map(chassis_model)
})
.unwrap_or("")
.to_string();
match model.to_lowercase() {
value if value.contains("bluefield 2") => Some(DpuModel::BlueField2),
Expand Down Expand Up @@ -942,13 +948,18 @@ impl EndpointExplorationReport {
.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
// on the product BMC chassis; use its known legacy/new IDs 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())
.filter(|chassis| is_dpu_product_chassis_id(&chassis.id))
.find_map(|chassis| {
chassis
.serial_number
.as_deref()
.map(str::trim)
.filter(|serial| !serial.is_empty())
})
})?
})
}
Expand Down Expand Up @@ -1752,18 +1763,25 @@ pub fn is_bf4_dpu_part_number(part_number: &str) -> bool {
|| normalized_part_number.starts_with("900-9d4a4")
}

/// Whether a DPU BMC chassis member carries the card product identity (part/serial).
/// Whether a DPU BMC chassis member carries the card product identity
/// (part/model/serial).
///
/// Older Redfish reports publish this identity on `Card1`; newer BF4 firmware may
/// instead publish it on the integrated BMC chassis (`Bluefield_BMC` or
/// `BlueField_BMC_0`). These IDs are expected to be mutually exclusive as product
/// identity sources in real reports, so callers can select the first matching
/// chassis.
fn is_dpu_product_chassis_id(id: &str) -> bool {
matches!(id, "Bluefield_BMC" | "BlueField_BMC_0")
matches!(id, "Card1" | "Bluefield_BMC" | "BlueField_BMC_0")
}

/// Whether a Redfish ComputerSystem id identifies a BlueField DPU system.
///
/// Firmware is inconsistent: older dumps expose `/redfish/v1/Systems/Bluefield`
/// while newer BF4 firmware exposes `/redfish/v1/Systems/BlueField_0`. Accept
/// both so DPU detection is not silently skipped.
pub fn is_bluefield_system_id(id: &str) -> bool {
matches!(id, "Bluefield" | "BlueField_0")
pub fn is_bluefield_system(system: &ComputerSystem) -> bool {
matches!(system.id.as_str(), "Bluefield" | "BlueField_0")
Comment thread
hakhondzadeh marked this conversation as resolved.
}

fn chassis_part_number(chassis: &Chassis) -> Option<&str> {
Expand All @@ -1774,6 +1792,14 @@ fn chassis_part_number(chassis: &Chassis) -> Option<&str> {
.filter(|part_number| !part_number.is_empty())
}

fn chassis_model(chassis: &Chassis) -> Option<&str> {
chassis
.model
.as_deref()
.map(str::trim)
.filter(|model| !model.is_empty())
}

// returns true if the passed in string is a BlueField part number
pub fn is_bluefield_part_number(part_number: &str) -> bool {
let normalized_part_number = part_number.trim().to_lowercase();
Expand Down Expand Up @@ -1962,14 +1988,18 @@ impl EndpointExplorationReport {
.or_else(|| {
// BF4 Redfish does not currently expose the product serial or
// DPU/NIC mode on the system object. The stable product serial
// lives on the Bluefield_BMC chassis and matches the serial the
// lives on the product BMC chassis and matches the serial the
// host BMC reports for the PCIe/network-adapter device.
self.chassis
.iter()
.find(|chassis| chassis.id == "Bluefield_BMC")
.and_then(|chassis| chassis.serial_number.as_deref())
.map(str::trim)
.filter(|serial| !serial.is_empty())
.filter(|chassis| is_dpu_product_chassis_id(&chassis.id))
.find_map(|chassis| {
chassis
.serial_number
.as_deref()
.map(str::trim)
.filter(|serial| !serial.is_empty())
})
})
}
}
Expand Down Expand Up @@ -2082,7 +2112,11 @@ mod explored_mlx_device_tests {
) -> EndpointExplorationReport {
EndpointExplorationReport {
systems: vec![ComputerSystem {
id: "Bluefield".to_string(),
id: if bmc_chassis_id == "BlueField_BMC_0" {
"BlueField_0".to_string()
} else {
"Bluefield".to_string()
},
..Default::default()
}],
chassis: vec![
Expand Down Expand Up @@ -2174,6 +2208,34 @@ mod explored_mlx_device_tests {
);
}

#[test]
fn recognizes_legacy_and_new_bluefield_system_ids() {
let system = |id: &str| ComputerSystem {
id: id.to_string(),
..Default::default()
};
assert!(is_bluefield_system(&system("Bluefield")));
assert!(is_bluefield_system(&system("BlueField_0")));
assert!(!is_bluefield_system(&system("Bluefield_0")));
}

#[test]
fn new_bf4_ids_use_bmc_chassis_for_identity_and_pairing() {
const SERIAL: &str = "MT2610604VN4";
let mut report = dpu_report_with_bf4_bmc_chassis("BlueField_BMC_0", "900-9D4A4-00CB-TS4");
let bmc_chassis = report
.chassis
.iter_mut()
.find(|chassis| chassis.id == "BlueField_BMC_0")
.unwrap();
bmc_chassis.serial_number = Some(SERIAL.to_string());
bmc_chassis.model = Some("B4240".to_string());

assert_eq!(report.identify_dpu(), Some(DpuModel::Unknown));
assert_eq!(report.machine_id_serial_number(), Some(SERIAL));
assert_eq!(report.dpu_pairing_serial_number(), Some(SERIAL));
}

#[test]
fn is_bf4_dpu_part_number_matches_vera_rubin_sku() {
assert!(is_bf4_dpu_part_number("900-9D4B4-CWAA-TSA"));
Expand Down
13 changes: 5 additions & 8 deletions crates/bmc-explorer/src/computer_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ pub struct Config<'a, B: Bmc> {
// This is expected to be fixed in BMC firmware 24.10-39, which adds
// internal retries.
pub retry_404_on_eth_interfaces: bool,
// Collect boot options (if set to false then assume that they are empty).
pub need_boot_options: bool,
pub explore: &'a ExploreConfig<'a, B>,
}

Expand All @@ -75,11 +73,10 @@ impl<B: Bmc> ExploredComputerSystem<B> {
system: ComputerSystem<B>,
config: &Config<'_, B>,
) -> Result<Self, Error<B>> {
let boot_options = if config.need_boot_options
&& let Some(collection) = system
.boot_options()
.await
.map_err(Error::nv_redfish("boot options"))?
let boot_options = if let Some(collection) = system
.boot_options()
.await
.map_err(Error::nv_redfish("boot options"))?
{
collection
.members()
Expand Down Expand Up @@ -190,7 +187,7 @@ impl<B: Bmc> ExploredComputerSystem<B> {
// This part processes dpu case and do two things such as
// 1. update system serial_number in case it is empty using chassis serial_number
// 2. format serial_number data using the same rules as in fetch_chassis()
if serial_number.is_none() {
if serial_number.is_none() && !chassis.is_bluefield4() {
serial_number = chassis.dpu_card1_serial_number()?;
}

Expand Down
13 changes: 7 additions & 6 deletions crates/bmc-explorer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ use nv_redfish::oem::lenovo::computer_system::{FpMode, PortSwitchingTo};
use nv_redfish::oem::lenovo::manager::KcsState;
use nv_redfish::oem::lenovo::security_service::FwRollbackState;
use nv_redfish::oem::supermicro::Privilege as SupermicroPrivilege;
use nv_redfish::resource::ResourceNameRef;
use nv_redfish::resource::{ResourceIdRef, ResourceNameRef};
use nv_redfish::service_root::{Product, Vendor};
use nv_redfish::{Bmc, Resource, ServiceRoot};

Expand All @@ -62,6 +62,10 @@ pub enum ErrorClass {

pub type ErrorClassifier<'a, B> = &'a (dyn Fn(&<B as Bmc>::Error) -> Option<ErrorClass> + Sync);

fn is_bluefield_system_id(id: ResourceIdRef<'_>) -> bool {
matches!(id.into_inner(), "Bluefield" | "BlueField_0")
}

pub struct Config<'a, B: Bmc> {
pub boot_interface_mac: Option<MacAddress>,
pub error_classifier: ErrorClassifier<'a, B>,
Expand Down Expand Up @@ -140,14 +144,11 @@ pub async fn nv_generate_exploration_report<B: Bmc>(
.next()
.ok_or_else(Error::bmc_not_provided("at least one manager"))?;

let is_bluefield_system = system.id().into_inner() == "Bluefield";
let is_bluefield_system = is_bluefield_system_id(system.id());
let system_explore_config = computer_system::Config {
need_oem_nvidia_bluefield: is_bluefield_system,
ignore_500_on_bios_fetch: is_bluefield_system,
retry_404_on_eth_interfaces: is_bluefield_system,
// BlueField-4 returns null in the Members field in
// BootOptions. This is a workaround for this bug.
need_boot_options: !explored_chassis.is_bluefield4(),
explore: config,
};
let explored_system = ExploredComputerSystem::explore(system, &system_explore_config).await?;
Expand Down Expand Up @@ -323,7 +324,7 @@ pub(crate) fn hw_type<B: Bmc>(
"Lenovo" if oem_id != Some("Ami") => Some(hw::HwType::Lenovo),
"Supermicro" => Some(hw::HwType::Supermicro),
"HPE" => Some(hw::HwType::Hpe),
"Nvidia" if system.id().into_inner() == "Bluefield" => Some(hw::HwType::Bluefield),
"Nvidia" if is_bluefield_system_id(system.id()) => Some(hw::HwType::Bluefield),
"NVIDIA" if root.product() == Some(Product::new("VR NVL72")) => {
Some(hw::HwType::VeraRubin)
}
Expand Down
5 changes: 2 additions & 3 deletions crates/bmc-explorer/src/test_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use nv_redfish::{Bmc, Resource, ServiceRoot};

use crate::chassis::ExploredChassisCollection;
use crate::computer_system::{self, ExploredComputerSystem};
use crate::{Config, Error, build_chassis_explore_config, hw, hw_type};
use crate::{Config, Error, build_chassis_explore_config, hw, hw_type, is_bluefield_system_id};

/// Resolve the [`hw::HwType`] for an endpoint, running only the chassis +
/// computer-system exploration that detection depends on.
Expand Down Expand Up @@ -67,12 +67,11 @@ pub async fn detect_hw_type<B: Bmc>(
let other_system_with_bios = systems_iter.find(|system| system.raw().bios.is_some());
let system = other_system_with_bios.unwrap_or(first_system);

let is_bluefield_system = system.id().into_inner() == "Bluefield";
let is_bluefield_system = is_bluefield_system_id(system.id());
let system_explore_config = computer_system::Config {
need_oem_nvidia_bluefield: is_bluefield_system,
ignore_500_on_bios_fetch: is_bluefield_system,
retry_404_on_eth_interfaces: is_bluefield_system,
need_boot_options: !explored_chassis.is_bluefield4(),
explore: config,
};
let explored_system = ExploredComputerSystem::explore(system, &system_explore_config).await?;
Expand Down
30 changes: 19 additions & 11 deletions crates/bmc-explorer/tests/bluefield4_explore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,37 @@ async fn explore_bluefield4_and_generate_machine_id_from_bluefield_bmc_chassis_s
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_eq!(system.id, "BlueField_0");
assert!(
system.serial_number.is_none(),
"BF4 Redfish reports the usable serial on chassis, not system"
);

let chassis_ids: Vec<&str> = report
assert_eq!(
report.managers.first().map(|manager| manager.id.as_str()),
Some("BlueField_BMC_0")
);

let mut 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:?}"
chassis_ids.sort_unstable();
assert_eq!(
chassis_ids,
[
"BlueField_0",
"BlueField_BMC_0",
"BlueField_ERoT_BMC_0",
"BlueField_ERoT_CPU_0",
"BlueField_IRoT_NIC_0",
]
);
let bmc_chassis_serial = report
.chassis
.iter()
.find(|chassis| chassis.id == "Bluefield_BMC")
.find(|chassis| chassis.id == "BlueField_BMC_0")
.and_then(|chassis| chassis.serial_number.as_deref());
assert_eq!(bmc_chassis_serial, Some("MT2610604VN4"));

Expand Down Expand Up @@ -98,7 +106,7 @@ async fn explore_b4240v_and_generate_machine_id() {
assert_eq!(report.endpoint_type, EndpointType::Bmc);
assert_eq!(report.vendor, Some(bmc_vendor::BMCVendor::Nvidia));
assert!(report.chassis.iter().any(|chassis| {
chassis.id == "Bluefield_BMC" && chassis.model.as_deref() == Some("B4240V")
chassis.id == "BlueField_BMC_0" && chassis.model.as_deref() == Some("B4240V")
}));

let machine_id = report
Expand Down
2 changes: 1 addition & 1 deletion crates/bmc-mock/src/hw/bluefield3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ impl Bluefield3<'_> {
serial_number: Some(self.product_serial_number.to_string().into()),
boot_order_mode: redfish::computer_system::BootOrderMode::ViaSettings,
callbacks: Some(callbacks),
boot_options: Some(boot_options.into()),
boot_options: Some(boot_options),
bios_mode: redfish::computer_system::BiosMode::Generic,
oem: redfish::computer_system::Oem::NvidiaBluefield,
base_bios: Some(
Expand Down
Loading
Loading