Skip to content

Commit b7c97fa

Browse files
committed
feat: add set_boot_override trait method with HttpBootUri support
This PR adds a new `BootOverride` struct and `Redfish::set_boot_override` trait method that exposes full Redfish Boot override support: - `target` -- e.g. (`UefiHttp`, `Hdd`, etc...) - `enabled` -- e.g. (`Once`, `Continuous`, `Disabled`) - `mode` -- e.g. (`UEFI`, `Legacy`) - `http_boot_uri` -- (an optional `ipxe.efi` URI, which allows us to override DHCP option 67). The `boot_once`/`boot_first` paths now route through it internally, so existing callers keep their current behavior. The purpose of this is for upcoming feature work: when `http_boot_uri` is set together with `target = UefiHttp`, the BMC pins the boot URL, and the host will UEFI HTTP boot from it *without* needing DHCP option 67 (which we currently set with `carbide-dhcp`). When `http_boot_uri` is None, the firmware falls back to option 67 per the UEFI HTTP Boot specification. The idea is we can start controlling the `ipxe.efi` boot URL via Redfish and not need to have `carbide-dhcp` populate it via option 67, reducing depdencencies on `carbide-dhcp` as the DHCP server. Vendor support implemented (via standard Boot resource `PATCH`) for: `nvidia_gh200`, `nvidia_gbx00`, `nvidia_dpu`, `nvidia_viking`, `nvidia_gbswitch`, `hpe`, `supermicro`, `ami`. Returning `NotSupported` (planned as follow-up PRs) for: `dell`, `lenovo`. Both BMCs require a vendor-specific BIOS-attribute path (`HttpDev1Uri` on iDRAC, XCC equivalent on Lenovo) -- they don't expose the standard `Boot.HttpBootUri` property. ...and then `standard` and `liteon_powershelf` will just stay `NotSupported`. It currently returns `Result<Option<String>, RedfishError>` -- `Some(job_id)` is reserved for vendors that schedule the change via a BIOS settings job (e.g. future `dell` + `lenovo` impls); current vendors apply immediately and return `None`. Also fixes a stray trailing space in `Systems/{id}/Settings ` and `Systems/{id}/SD ` URLs across the NVIDIA family vendors. Tests added. Signed-off-by: Chet Nichols III <chetn@nvidia.com>
1 parent 6ab9486 commit b7c97fa

15 files changed

Lines changed: 587 additions & 318 deletions

src/ami.rs

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ use crate::{
2727
jsonmap,
2828
model::{
2929
account_service::ManagerAccount,
30-
boot::{BootSourceOverrideEnabled, BootSourceOverrideTarget},
30+
boot::{
31+
BootOverride, BootSourceOverrideEnabled, BootSourceOverrideMode,
32+
BootSourceOverrideTarget,
33+
},
3134
certificate::Certificate,
3235
chassis::{Assembly, Chassis, NetworkAdapter},
3336
component_integrity::ComponentIntegrities,
@@ -586,8 +589,17 @@ impl Redfish for Bmc {
586589
Boot::HardDisk => BootSourceOverrideTarget::Hdd,
587590
Boot::UefiHttp => BootSourceOverrideTarget::UefiHttp,
588591
};
589-
self.set_boot_override(override_target, BootSourceOverrideEnabled::Once)
590-
.await
592+
Redfish::set_boot_override(
593+
self,
594+
BootOverride {
595+
target: override_target,
596+
enabled: BootSourceOverrideEnabled::Once,
597+
mode: None,
598+
http_boot_uri: None,
599+
},
600+
)
601+
.await?;
602+
Ok(())
591603
})
592604
}
593605

@@ -605,6 +617,39 @@ impl Redfish for Bmc {
605617
})
606618
}
607619

620+
/// AMI requires patching `/Systems/{id}` (NOT `/SD`) with an `If-Match` header.
621+
fn set_boot_override<'a>(
622+
&'a self,
623+
settings: BootOverride,
624+
) -> crate::RedfishFuture<'a, Result<Option<String>, RedfishError>> {
625+
Box::pin(async move {
626+
let mut boot_data: HashMap<String, serde_json::Value> = HashMap::new();
627+
boot_data.insert(
628+
"BootSourceOverrideTarget".to_string(),
629+
settings.target.to_string().into(),
630+
);
631+
boot_data.insert(
632+
"BootSourceOverrideEnabled".to_string(),
633+
settings.enabled.to_string().into(),
634+
);
635+
// AMI BMCs default to UEFI mode when the caller doesn't specify one.
636+
let mode = settings.mode.unwrap_or(BootSourceOverrideMode::UEFI);
637+
boot_data.insert(
638+
"BootSourceOverrideMode".to_string(),
639+
mode.to_string().into(),
640+
);
641+
if let Some(uri) = settings.http_boot_uri {
642+
boot_data.insert("HttpBootUri".to_string(), uri.into());
643+
}
644+
let url = format!("Systems/{}", self.s.system_id());
645+
self.s
646+
.client
647+
.patch_with_if_match(&url, HashMap::from([("Boot", boot_data)]))
648+
.await?;
649+
Ok(None)
650+
})
651+
}
652+
608653
/// AMI BMC requires If-Match header for boot order changes
609654
fn change_boot_order<'a>(
610655
&'a self,
@@ -1124,28 +1169,6 @@ impl Redfish for Bmc {
11241169
}
11251170

11261171
impl Bmc {
1127-
/// AMI requires patching to /Systems/{id} (NOT /SD) with If-Match header
1128-
async fn set_boot_override(
1129-
&self,
1130-
override_target: BootSourceOverrideTarget,
1131-
override_enabled: BootSourceOverrideEnabled,
1132-
) -> Result<(), RedfishError> {
1133-
let boot_data = HashMap::from([
1134-
("BootSourceOverrideMode".to_string(), "UEFI".to_string()),
1135-
(
1136-
"BootSourceOverrideEnabled".to_string(),
1137-
override_enabled.to_string(),
1138-
),
1139-
(
1140-
"BootSourceOverrideTarget".to_string(),
1141-
override_target.to_string(),
1142-
),
1143-
]);
1144-
let data = HashMap::from([("Boot", boot_data)]);
1145-
let url = format!("Systems/{}", self.s.system_id());
1146-
self.s.client.patch_with_if_match(&url, data).await
1147-
}
1148-
11491172
async fn get_system_and_boot_options(
11501173
&self,
11511174
) -> Result<(ComputerSystem, Vec<BootOption>), RedfishError> {

src/dell.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use crate::{
3131
jsonmap,
3232
model::{
3333
account_service::ManagerAccount,
34+
boot::BootOverride,
3435
certificate::Certificate,
3536
chassis::{Assembly, Chassis, NetworkAdapter},
3637
component_integrity::ComponentIntegrities,
@@ -603,6 +604,21 @@ impl Redfish for Bmc {
603604
})
604605
}
605606

607+
/// Not yet implemented on Dell iDRAC. Dell does not expose the standard
608+
/// Redfish `Boot.HttpBootUri` property; setting an HTTP boot URI on Dell
609+
/// requires PATCHing the `HttpDev1Uri` and related BIOS attributes via
610+
/// `/Systems/{id}/Bios/Settings`. Planned as a follow-up PR.
611+
fn set_boot_override<'a>(
612+
&'a self,
613+
_settings: BootOverride,
614+
) -> crate::RedfishFuture<'a, Result<Option<String>, RedfishError>> {
615+
Box::pin(async move {
616+
Err(RedfishError::NotSupported(
617+
"No Dell set_boot_override implementation".to_string(),
618+
))
619+
})
620+
}
621+
606622
fn clear_tpm<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> {
607623
Box::pin(async move {
608624
self.delete_job_queue().await?;

src/hpe.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use serde_json::Value;
2727
use crate::{
2828
model::{
2929
account_service::ManagerAccount,
30+
boot::{BootOverride, BootSourceOverrideMode},
3031
certificate::Certificate,
3132
chassis::{Assembly, Chassis, NetworkAdapter},
3233
component_integrity::ComponentIntegrities,
@@ -475,6 +476,38 @@ impl Redfish for Bmc {
475476
Box::pin(async move { self.boot_first(target).await })
476477
}
477478

479+
fn set_boot_override<'a>(
480+
&'a self,
481+
settings: BootOverride,
482+
) -> crate::RedfishFuture<'a, Result<Option<String>, RedfishError>> {
483+
Box::pin(async move {
484+
let mut boot_data: HashMap<String, serde_json::Value> = HashMap::new();
485+
boot_data.insert(
486+
"BootSourceOverrideTarget".to_string(),
487+
settings.target.to_string().into(),
488+
);
489+
boot_data.insert(
490+
"BootSourceOverrideEnabled".to_string(),
491+
settings.enabled.to_string().into(),
492+
);
493+
// HPE iLO defaults to UEFI mode when the caller doesn't specify one.
494+
let mode = settings.mode.unwrap_or(BootSourceOverrideMode::UEFI);
495+
boot_data.insert(
496+
"BootSourceOverrideMode".to_string(),
497+
mode.to_string().into(),
498+
);
499+
if let Some(uri) = settings.http_boot_uri {
500+
boot_data.insert("HttpBootUri".to_string(), uri.into());
501+
}
502+
let url = format!("Systems/{}", self.s.system_id());
503+
self.s
504+
.client
505+
.patch(&url, HashMap::from([("Boot", boot_data)]))
506+
.await?;
507+
Ok(None)
508+
})
509+
}
510+
478511
fn clear_tpm<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> {
479512
Box::pin(async move {
480513
let tpm = hpe::TpmAttributes {

src/lenovo.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use tokio::time::sleep;
3434
use tracing::debug;
3535

3636
use crate::model::account_service::ManagerAccount;
37+
use crate::model::boot::BootOverride;
3738
use crate::model::certificate::Certificate;
3839
use crate::model::component_integrity::ComponentIntegrities;
3940
use crate::model::oem::lenovo::{BootSettings, FrontPanelUSB, LenovoBootOrder};
@@ -586,6 +587,21 @@ impl Redfish for Bmc {
586587
})
587588
}
588589

590+
/// Not yet implemented on Lenovo XCC. Like Dell, Lenovo does not expose
591+
/// the standard Redfish `Boot.HttpBootUri` property; setting an HTTP boot
592+
/// URI on Lenovo requires a vendor-specific BIOS attribute path. Planned
593+
/// as a follow-up PR.
594+
fn set_boot_override<'a>(
595+
&'a self,
596+
_settings: BootOverride,
597+
) -> crate::RedfishFuture<'a, Result<Option<String>, RedfishError>> {
598+
Box::pin(async move {
599+
Err(RedfishError::NotSupported(
600+
"No Lenovo set_boot_override implementation".to_string(),
601+
))
602+
})
603+
}
604+
589605
fn clear_tpm<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> {
590606
Box::pin(async move {
591607
let mut body = HashMap::new();

src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ use std::{collections::HashMap, fmt, future::Future, path::Path, pin::Pin, time:
2323

2424
pub mod model;
2525
use model::account_service::ManagerAccount;
26+
pub use model::boot::{
27+
BootOverride, BootSourceOverrideEnabled, BootSourceOverrideMode, BootSourceOverrideTarget,
28+
};
2629
pub use model::chassis::{Assembly, Chassis, NetworkAdapter};
2730
pub use model::ethernet_interface::EthernetInterface;
2831
pub use model::network_device_function::NetworkDeviceFunction;
@@ -284,6 +287,27 @@ pub trait Redfish: Send + Sync + 'static {
284287
/// Change boot order putting this target first
285288
fn boot_first<'a>(&'a self, target: Boot) -> RedfishFuture<'a, Result<(), RedfishError>>;
286289

290+
/// Set a boot source override, optionally including an HTTP boot URI.
291+
///
292+
/// This is a lower-level alternative to [`Redfish::boot_once`] /
293+
/// [`Redfish::boot_first`] that exposes the full Redfish `Boot` override
294+
/// shape: `target`, `enabled` (`Once`/`Continuous`/`Disabled`), `mode`
295+
/// (`UEFI`/`Legacy`), and `http_boot_uri`.
296+
///
297+
/// When `target` is `UefiHttp` and `http_boot_uri` is `Some`, the BMC pins
298+
/// the boot URL — the host will UEFI-HTTP-boot from that URI on the next
299+
/// applicable boot without needing DHCP option 67. If `http_boot_uri` is
300+
/// `None`, the firmware falls back to DHCP option 67 per the UEFI HTTP
301+
/// Boot specification.
302+
///
303+
/// Returns an optional job ID. Vendors that route the change through a
304+
/// BIOS settings job schedule it to apply on next reboot and return the
305+
/// job ID. Vendors that apply the change immediately return `None`.
306+
fn set_boot_override<'a>(
307+
&'a self,
308+
settings: BootOverride,
309+
) -> RedfishFuture<'a, Result<Option<String>, RedfishError>>;
310+
287311
/// Change boot order by setting boot array.
288312
fn change_boot_order<'a>(
289313
&'a self,

src/liteon_powershelf.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{collections::HashMap, path::Path, time::Duration};
44
use tokio::fs::File;
55

66
use crate::model::account_service::ManagerAccount;
7+
use crate::model::boot::BootOverride;
78
use crate::model::certificate::Certificate;
89
use crate::model::component_integrity::ComponentIntegrities;
910
use crate::model::oem::nvidia_dpu::{HostPrivilegeLevel, NicMode};
@@ -342,6 +343,17 @@ impl Redfish for Bmc {
342343
})
343344
}
344345

346+
fn set_boot_override<'a>(
347+
&'a self,
348+
_settings: BootOverride,
349+
) -> crate::RedfishFuture<'a, Result<Option<String>, RedfishError>> {
350+
Box::pin(async move {
351+
Err(RedfishError::NotSupported(
352+
"Lite-on powershelf does not support boot source overrides".to_string(),
353+
))
354+
})
355+
}
356+
345357
fn clear_tpm<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> {
346358
Box::pin(async move { self.s.clear_tpm().await })
347359
}

src/model/boot.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,27 @@ pub enum BootSourceOverrideMode {
113113
InvalidValue,
114114
}
115115

116+
impl fmt::Display for BootSourceOverrideMode {
117+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118+
fmt::Debug::fmt(self, f)
119+
}
120+
}
121+
122+
/// Settings for a Redfish boot source override, applied via
123+
/// [`Redfish::set_boot_override`](crate::Redfish::set_boot_override).
124+
///
125+
/// `target` and `enabled` are required. `mode` is typically `UEFI` for modern
126+
/// systems and can be left `None` to keep the current mode unchanged. `http_boot_uri`
127+
/// only applies when `target` is `UefiHttp`; if `None`, the firmware obtains the
128+
/// boot URL from DHCP option 67 as specified by the UEFI HTTP Boot specification.
129+
#[derive(Debug, Clone)]
130+
pub struct BootOverride {
131+
pub target: BootSourceOverrideTarget,
132+
pub enabled: BootSourceOverrideEnabled,
133+
pub mode: Option<BootSourceOverrideMode>,
134+
pub http_boot_uri: Option<String>,
135+
}
136+
116137
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
117138
pub enum TrustedModuleRequiredToBoot {
118139
Disabled,

0 commit comments

Comments
 (0)