Skip to content

Fix: VeraRubin host BMC displays boot order in a different format#112

Open
hakhondzadeh wants to merge 4 commits into
NVIDIA:mainfrom
hakhondzadeh:VR_bootOrder
Open

Fix: VeraRubin host BMC displays boot order in a different format#112
hakhondzadeh wants to merge 4 commits into
NVIDIA:mainfrom
hakhondzadeh:VR_bootOrder

Conversation

@hakhondzadeh

@hakhondzadeh hakhondzadeh commented Jul 24, 2026

Copy link
Copy Markdown

Redfish path redfish/v1/Systems/System_0 for a Vera Rubin host bmc, show boot orders in this format:
"BootOrder": [ "Boot0019: Ubuntu", "Boot0008: UEFI HTTPv4 (MAC:AC3AE2634C82)",
While previously, we saw them in this format:

 "BootOrder": [
            "Boot0002",
            "Boot0001",
            "Boot0000"
        ],

This PR fixes this issue by removing the suffix of the boot order. Also, when patching a boot order, the name is added to keep the formatting consistent.

Unit tests were also added.

Vera Rubin host BMC firmware reports BootOrder strings like
"Boot0019: Ubuntu" while BootOption resources live at bare Ids.
Strip the display-name suffix before GET and preserve firmware format
when reordering boot options.
Add boot_order_prepend_first helper with unit tests for the PATCH path,
simplify vera_rubin boot-order reordering, and fix stale GB200 error text.
@hakhondzadeh hakhondzadeh changed the title Vr boot order Fix: VeraRubin host BMC displays boot order in a different format Jul 24, 2026
@krish-nvidia

krish-nvidia commented Jul 24, 2026

Copy link
Copy Markdown

Could we handle the Vera Rubin boot order quirk directly in set_boot_order_dpu_first()? I don't want vendor specific things bleeding out into the standard model (as well as vendor specific unit tests). I have a reference implementation below that you can drop in which follows how we set the boot order for other vendors.

fn set_boot_order_dpu_first<'a>(
    &'a self,
    boot_interface: crate::BootInterfaceRef<'a>,
) -> crate::RedfishFuture<'a, Result<Option<String>, RedfishError>> {
    Box::pin(async move {
        let address = crate::resolve_boot_interface_mac(self, boot_interface).await?;
        let mac_address = address.replace(':', "").to_uppercase();
        let boot_option_name =
            format!("{} (MAC:{})", BootOptionName::Http.to_string(), mac_address);
        let system = self.s.get_system().await?;
        let boot_options_id =
            system
                .boot
                .boot_options
                .clone()
                .ok_or_else(|| RedfishError::MissingKey {
                    key: "boot.boot_options".to_string(),
                    url: system.odata.odata_id.clone(),
                })?;
        let boot_options: Vec<BootOption> = self
            .get_collection(boot_options_id)
            .await
            .and_then(|collection| collection.try_get::<BootOption>())?
            .members;
        let target = boot_options
            .iter()
            .find(|option| option.display_name.starts_with(&boot_option_name))
            .ok_or_else(|| RedfishError::GenericError {
                error: format!("Could not find boot option matching {boot_option_name}"),
            })?;
        let mut boot_order = system.boot.boot_order;
        let target_index = boot_order
            .iter()
            .position(|entry| {
                entry
                    .split_once(": ")
                    .map_or(entry.as_str(), |(reference, _)| reference)
                    == target.boot_option_reference
            })
            .ok_or_else(|| RedfishError::GenericError {
                error: format!(
                    "Boot option {} is not present in BootOrder",
                    target.boot_option_reference
                ),
            })?;
        // Preserve the exact entry returned by Vera Rubin firmware.
        let target_entry = boot_order.remove(target_index);
        boot_order.insert(0, target_entry);
        self.change_boot_order(boot_order).await?;
        Ok(None)
    })
}
async fn get_expected_and_actual_first_boot_option(
    &self,
    boot_interface_mac: &str,
) -> Result<(Option<String>, Option<String>), RedfishError> {
    let mac_address = boot_interface_mac.replace(':', "").to_uppercase();
    let boot_option_name =
        format!("{} (MAC:{})", BootOptionName::Http.to_string(), mac_address);
    let boot_order = self.s.get_system().await?.boot.boot_order;
    let actual_first_boot_option = if let Some(first) = boot_order.first() {
        let reference = first
            .split_once(": ")
            .map_or(first.as_str(), |(reference, _)| reference);
        Some(self.s.get_boot_option(reference).await?.display_name)
    } else {
        None
    };
    let mut expected_first_boot_option = None;
    for entry in &boot_order {
        let reference = entry
            .split_once(": ")
            .map_or(entry.as_str(), |(reference, _)| reference);
        let option = self.s.get_boot_option(reference).await?;
        if option.display_name.starts_with(&boot_option_name) {
            expected_first_boot_option = Some(option.display_name);
            break;
        }
    }
    Ok((
        expected_first_boot_option,
        actual_first_boot_option,
    ))
}

@krish-nvidia

Copy link
Copy Markdown

Also, I don't see a fix for this issue other than changing the model in the error message?

Details: BMC vendor does not support this operation: GB200 doesn't have Systems EthernetInterface BlueField_NIC_0_Port_0.

Checked the VR BMC and it does exist:

{
    "@odata.id": "/redfish/v1/Systems/System_0/EthernetInterfaces",
    "@odata.type": "#EthernetInterfaceCollection.EthernetInterfaceCollection",
    "Description": "Collection of EthernetInterfaces of the host",
    "Members": [
        {
            "@odata.id": "/redfish/v1/Systems/System_0/EthernetInterfaces/BlueField_NIC_0_Port_0"
        },

@bcavnvidia

bcavnvidia commented Jul 24, 2026

Copy link
Copy Markdown

/redfish/v1/Systems/System_0/EthernetInterfaces/BlueField_NIC_0_Port_0
Also, I don't see a fix for this issue other than changing the model in the error message?

Details: BMC vendor does not support this operation: GB200 doesn't have Systems EthernetInterface BlueField_NIC_0_Port_0.

Checked the VR BMC and it does exist:

{
    "@odata.id": "/redfish/v1/Systems/System_0/EthernetInterfaces",
    "@odata.type": "#EthernetInterfaceCollection.EthernetInterfaceCollection",
    "Description": "Collection of EthernetInterfaces of the host",
    "Members": [
        {
            "@odata.id": "/redfish/v1/Systems/System_0/EthernetInterfaces/BlueField_NIC_0_Port_0"
        },

Seems like just minor clean-up. I think this is just house-keeping. Current version of libredfish only had the boot-order issue, not NIC location issue. Nvm, I see what you meant. It's currently short-circuiting to error, but the VR has the that path.

@hakhondzadeh

hakhondzadeh commented Jul 24, 2026

Copy link
Copy Markdown
Author

/redfish/v1/Systems/System_0/EthernetInterfaces/BlueField_NIC_0_Port_0
Also, I don't see a fix for this issue other than changing the model in the error message?

Details: BMC vendor does not support this operation: GB200 doesn't have Systems EthernetInterface BlueField_NIC_0_Port_0.

Checked the VR BMC and it does exist:

{
    "@odata.id": "/redfish/v1/Systems/System_0/EthernetInterfaces",
    "@odata.type": "#EthernetInterfaceCollection.EthernetInterfaceCollection",
    "Description": "Collection of EthernetInterfaces of the host",
    "Members": [
        {
            "@odata.id": "/redfish/v1/Systems/System_0/EthernetInterfaces/BlueField_NIC_0_Port_0"
        },

Seems like just minor clean-up. I think this is just house-keeping. Current version of libredfish only had the boot-order issue, not NIC location issue.

There is no functional NIC fix in this PR; just fixed the string and changed GB200 to VR so to avoid confusion later. Implementing real System EthernetInterface support for Vera Rubin is separate work

@hakhondzadeh

Copy link
Copy Markdown
Author

Could we handle the Vera Rubin boot order quirk directly in set_boot_order_dpu_first()? I don't want vendor specific things bleeding out into the standard model (as well as vendor specific unit tests). I have a reference implementation below that you can drop in which follows how we set the boot order for other vendors.

fn set_boot_order_dpu_first<'a>(
    &'a self,
    boot_interface: crate::BootInterfaceRef<'a>,
) -> crate::RedfishFuture<'a, Result<Option<String>, RedfishError>> {
    Box::pin(async move {
        let address = crate::resolve_boot_interface_mac(self, boot_interface).await?;
        let mac_address = address.replace(':', "").to_uppercase();
        let boot_option_name =
            format!("{} (MAC:{})", BootOptionName::Http.to_string(), mac_address);
        let system = self.s.get_system().await?;
        let boot_options_id =
            system
                .boot
                .boot_options
                .clone()
                .ok_or_else(|| RedfishError::MissingKey {
                    key: "boot.boot_options".to_string(),
                    url: system.odata.odata_id.clone(),
                })?;
        let boot_options: Vec<BootOption> = self
            .get_collection(boot_options_id)
            .await
            .and_then(|collection| collection.try_get::<BootOption>())?
            .members;
        let target = boot_options
            .iter()
            .find(|option| option.display_name.starts_with(&boot_option_name))
            .ok_or_else(|| RedfishError::GenericError {
                error: format!("Could not find boot option matching {boot_option_name}"),
            })?;
        let mut boot_order = system.boot.boot_order;
        let target_index = boot_order
            .iter()
            .position(|entry| {
                entry
                    .split_once(": ")
                    .map_or(entry.as_str(), |(reference, _)| reference)
                    == target.boot_option_reference
            })
            .ok_or_else(|| RedfishError::GenericError {
                error: format!(
                    "Boot option {} is not present in BootOrder",
                    target.boot_option_reference
                ),
            })?;
        // Preserve the exact entry returned by Vera Rubin firmware.
        let target_entry = boot_order.remove(target_index);
        boot_order.insert(0, target_entry);
        self.change_boot_order(boot_order).await?;
        Ok(None)
    })
}
async fn get_expected_and_actual_first_boot_option(
    &self,
    boot_interface_mac: &str,
) -> Result<(Option<String>, Option<String>), RedfishError> {
    let mac_address = boot_interface_mac.replace(':', "").to_uppercase();
    let boot_option_name =
        format!("{} (MAC:{})", BootOptionName::Http.to_string(), mac_address);
    let boot_order = self.s.get_system().await?.boot.boot_order;
    let actual_first_boot_option = if let Some(first) = boot_order.first() {
        let reference = first
            .split_once(": ")
            .map_or(first.as_str(), |(reference, _)| reference);
        Some(self.s.get_boot_option(reference).await?.display_name)
    } else {
        None
    };
    let mut expected_first_boot_option = None;
    for entry in &boot_order {
        let reference = entry
            .split_once(": ")
            .map_or(entry.as_str(), |(reference, _)| reference);
        let option = self.s.get_boot_option(reference).await?;
        if option.display_name.starts_with(&boot_option_name) {
            expected_first_boot_option = Some(option.display_name);
            break;
        }
    }
    Ok((
        expected_first_boot_option,
        actual_first_boot_option,
    ))
}

Helper functions was intentionally added to model/boot.rs because it’s not really Vera Rubin–specific. Newer firmware on other GPU platforms could easily do the same thing. When that happens, a shared helper avoids copying the same parsing into every nvidia_*.rs file.
However, I changed to your approach. If another platform starts emitting "Boot####: DisplayName" in BootOrder, we can promote more of the reorder logic into boot.rs then. For now, with only VR showing this on the hosts we debugged, keeping promote in the vendor module works.

Comment thread src/nvidia_vera_rubin.rs Outdated
.members;
let target = boot_options
.iter()
.find(|option| option.display_name.starts_with(&boot_option_name))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might need exact match. Live data looked like this:

  ...: UEFI HTTPv4 (MAC:XXXXXXXXXXXX)
  ...: UEFI HTTPv4 (MAC:XXXXXXXXXXXX) 2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh true, do you know what the 2 option is? I'm assuming we just need to pick one without the 2.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, thanks

@krish-nvidia

Copy link
Copy Markdown

There is no functional NIC fix in this PR; just fixed the string and changed GB200 to VR so to avoid confusion later. Implementing real System EthernetInterface support for Vera Rubin is separate work

Correct me if I'm wrong, but the host won't move past the set-boot-order state because it first calls is_boot_order_setup(), which calls resolve_boot_interface_mac(), which then calls Vera Rubin's get_system_ethernet_interface() implementation. That currently returns NotSupported. The fix should be to delegate to the standard implementation instead, since I verified that the standard Redfish path exists and works:

fn get_system_ethernet_interfaces<'a>(
    &'a self,
) -> crate::RedfishFuture<'a, Result<Vec<String>, RedfishError>> {
    Box::pin(async move { self.s.get_system_ethernet_interfaces().await })
}
fn get_system_ethernet_interface<'a>(
    &'a self,
    id: &'a str,
) -> crate::RedfishFuture<'a, Result<crate::EthernetInterface, RedfishError>> {
    Box::pin(async move { self.s.get_system_ethernet_interface(id).await })
}

@bcavnvidia

bcavnvidia commented Jul 24, 2026

Copy link
Copy Markdown

There is no functional NIC fix in this PR; just fixed the string and changed GB200 to VR so to avoid confusion later. Implementing real System EthernetInterface support for Vera Rubin is separate work

Correct me if I'm wrong, but the host won't move past the set-boot-order state because it first calls is_boot_order_setup(), which calls resolve_boot_interface_mac(), which then calls Vera Rubin's get_system_ethernet_interface() implementation. That currently returns NotSupported. The fix should be to delegate to the standard implementation instead, since I verified that the standard Redfish path exists and works:

fn get_system_ethernet_interfaces<'a>(
    &'a self,
) -> crate::RedfishFuture<'a, Result<Vec<String>, RedfishError>> {
    Box::pin(async move { self.s.get_system_ethernet_interfaces().await })
}
fn get_system_ethernet_interface<'a>(
    &'a self,
    id: &'a str,
) -> crate::RedfishFuture<'a, Result<crate::EthernetInterface, RedfishError>> {
    Box::pin(async move { self.s.get_system_ethernet_interface(id).await })
}

I think the changes here avoid the get_system_ethernet_interface call #110

EDIT:
Traced:

  • (core) e3705ccc0+ with libredfish v0.45+: guaranteed to send Mac or Pair once.
  • VeraRubin then resolves either directly from the supplied MAC without calling get_system_ethernet_interface().

The fix should be to delegate to the standard implementation instead, since I verified that the standard Redfish path exists and works

Still fine to do it for sure, but it seems no reason to lump it into this PR. Though, we're going to need a version bump either way.

@krish-nvidia

Copy link
Copy Markdown

I think the changes here avoid the get_system_ethernet_interface call #110

Lesson learned to update my local libredfish checkout before starting to review new changes 🤦‍♂️ Thanks for the explanation!

@hakhondzadeh

Copy link
Copy Markdown
Author

There is no functional NIC fix in this PR; just fixed the string and changed GB200 to VR so to avoid confusion later. Implementing real System EthernetInterface support for Vera Rubin is separate work

Correct me if I'm wrong, but the host won't move past the set-boot-order state because it first calls is_boot_order_setup(), which calls resolve_boot_interface_mac(), which then calls Vera Rubin's get_system_ethernet_interface() implementation. That currently returns NotSupported. The fix should be to delegate to the standard implementation instead, since I verified that the standard Redfish path exists and works:

fn get_system_ethernet_interfaces<'a>(
    &'a self,
) -> crate::RedfishFuture<'a, Result<Vec<String>, RedfishError>> {
    Box::pin(async move { self.s.get_system_ethernet_interfaces().await })
}
fn get_system_ethernet_interface<'a>(
    &'a self,
    id: &'a str,
) -> crate::RedfishFuture<'a, Result<crate::EthernetInterface, RedfishError>> {
    Box::pin(async move { self.s.get_system_ethernet_interface(id).await })
}

This PR fixes the boot URL. NIC interfaces can be done in a separate PR. thanks for pointing it out

@hakhondzadeh
hakhondzadeh requested a review from bcavnvidia July 24, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants