From 6577426f80231cc384019a55267b2523b660591b Mon Sep 17 00:00:00 2001 From: Evgeny Shevchenko Date: Fri, 3 Jul 2026 20:13:36 +0200 Subject: [PATCH 1/2] fix: pick the correct manager/system on multi-system, multi-chassis hosts Match the system by presence of a Bios resource and select the manager whose Links.ManagerForServers references it, instead of blindly taking the first entry. Adds ManagerLinks, get_manager_with_id, is_bios_attributes. Signed-off-by: Evgeny Shevchenko --- src/model/manager.rs | 10 ++++++++++ src/network.rs | 46 ++++++++++++++++++++++++++++++++++++++++++-- src/standard.rs | 25 ++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/model/manager.rs b/src/model/manager.rs index ceb354f59..65cd2f1ad 100644 --- a/src/model/manager.rs +++ b/src/model/manager.rs @@ -59,6 +59,16 @@ pub struct Manager { #[serde(rename = "UUID")] pub uuid: Option, pub oem: Option, + pub links: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[serde(rename_all = "PascalCase")] +pub struct ManagerLinks { + #[serde(default)] + pub manager_for_servers: Vec, + #[serde(default)] + pub manager_for_chassis: Vec, } #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/src/network.rs b/src/network.rs index a3d2b76b4..99843877b 100644 --- a/src/network.rs +++ b/src/network.rs @@ -211,7 +211,7 @@ impl RedfishClientPool { }; let managers = s.get_managers().await?; - let manager_id = managers.first().ok_or_else(|| RedfishError::GenericError { + let mut manager_id = managers.first().ok_or_else(|| RedfishError::GenericError { error: "No managers found in service root".to_string(), })?; let chassis = s.get_chassis_all().await?; @@ -228,15 +228,57 @@ impl RedfishClientPool { // member blindly targets the wrong system (no BIOS/boot). Falling // back to the first member preserves behavior for every platform // that does not expose `System_0` (e.g. Viking's `DGX`). - let system_id = systems + let at_least_one_system_id = systems .iter() .find(|id| *id == "System_0") .or_else(|| systems.first()) .ok_or_else(|| RedfishError::GenericError { error: "No systems found in service root".to_string(), })?; + + //Find another system with BIOS section + let mut system_with_bios = None; + for system_member in &systems { + // Treat any error as "no BIOS here": we already have + // `at_least_one_system_id` as a fallback, and this also handles the + // test mockup, which drops the connection instead of returning 404 + // when the Bios section is empty. + if let Ok(has_bios) = s.is_bios_attributes(system_member).await { + if has_bios { + system_with_bios = Some(system_member); + break; + } + } + } + let system_id = system_with_bios.unwrap_or(at_least_one_system_id); + // call set_system_id always before calling set_vendor s.set_system_id(system_id)?; + + // Try to find manager with ManagerForServers related to System + for m in &managers { + if let Some(links) = s.get_manager_with_id(m).await?.links { + let manager_for_servers: Result, RedfishError> = links + .manager_for_servers + .iter() + .map(|d| { + d.odata_id + .trim_matches('/') + .split('/') + .next_back() + .map(|s| s.to_string()) + .ok_or_else(|| RedfishError::GenericError { + error: format!("Invalid odata_id format: {}", d.odata_id), + }) + }) + .collect(); + let manager_for_servers = manager_for_servers?; + if manager_for_servers.contains(system_id) { + manager_id = m; + break; + } + } + } } s.set_manager_id(manager_id)?; diff --git a/src/standard.rs b/src/standard.rs index 3c7af86d0..26cf82946 100644 --- a/src/standard.rs +++ b/src/standard.rs @@ -1405,6 +1405,17 @@ impl RedfishStandard { Ok(b) } + pub fn get_manager_with_id<'a>( + &'a self, + manager_id: &'a str, + ) -> crate::RedfishFuture<'a, Result> { + Box::pin(async move { + let (_, manager): (_, Manager) = + self.client.get(&format!("Managers/{}", manager_id)).await?; + Ok(manager) + }) + } + pub async fn fetch_bmc_event_log( &self, url: String, @@ -1507,6 +1518,20 @@ impl RedfishStandard { }) } + pub async fn is_bios_attributes(&self, system_id: &str) -> Result { + let url = format!("Systems/{}/Bios", system_id); + let result = self.client.get::(&url).await; + match result { + Ok((_code, _data)) => Ok(true), + Err(RedfishError::HTTPErrorCode { status_code, .. }) + if status_code == StatusCode::NOT_FOUND => + { + Ok(false) + } + Err(e) => Err(e), + } + } + pub async fn factory_reset_bios(&self) -> Result<(), RedfishError> { let url = format!("Systems/{}/Bios/Actions/Bios.ResetBios", self.system_id()); self.client From 0d6aeb756f49c1fd1c3fa3cc43c1120dbc08cb82 Mon Sep 17 00:00:00 2001 From: Evgeny Shevchenko Date: Fri, 24 Jul 2026 14:22:22 +0200 Subject: [PATCH 2/2] Resolve managing Manager from System Links.ManagedBy - model: add `bios: Option` to ComputerSystem so BIOS presence can be read straight off the system resource. - standard: replace is_bios_attributes() (a separate Systems/{id}/Bios GET + 404 check) with if_system_has_bios(), which fetches the ComputerSystem once and returns it when it carries a Bios link. - network: pick the BIOS-bearing system, take manager_id from its Links.ManagedBy[0] (falling back to the first manager), and drop the per-manager get_manager_with_id scan. Signed-off-by: Evgeny Shevchenko --- src/model/system.rs | 1 + src/network.rs | 52 ++++++++++++++++----------------------------- src/standard.rs | 22 ++++++++----------- 3 files changed, 28 insertions(+), 47 deletions(-) diff --git a/src/model/system.rs b/src/model/system.rs index f9d3cd84c..134be2c17 100644 --- a/src/model/system.rs +++ b/src/model/system.rs @@ -179,6 +179,7 @@ pub struct ComputerSystem { pub asset_tag: Option, #[serde(default)] // Some viking ComputerSystem has no Boot property; so use the default pub boot: Boot, + pub bios: Option, pub bios_version: Option, pub ethernet_interfaces: Option, pub id: String, diff --git a/src/network.rs b/src/network.rs index 99843877b..5438f19b9 100644 --- a/src/network.rs +++ b/src/network.rs @@ -213,7 +213,7 @@ impl RedfishClientPool { let managers = s.get_managers().await?; let mut manager_id = managers.first().ok_or_else(|| RedfishError::GenericError { error: "No managers found in service root".to_string(), - })?; + })?.clone(); let chassis = s.get_chassis_all().await?; // Delta power shelves expose no `/Systems` resource (a real query 404s) @@ -237,51 +237,35 @@ impl RedfishClientPool { })?; //Find another system with BIOS section - let mut system_with_bios = None; + let mut system_with_bios: Option = None; for system_member in &systems { // Treat any error as "no BIOS here": we already have // `at_least_one_system_id` as a fallback, and this also handles the // test mockup, which drops the connection instead of returning 404 // when the Bios section is empty. - if let Ok(has_bios) = s.is_bios_attributes(system_member).await { - if has_bios { - system_with_bios = Some(system_member); - break; - } + system_with_bios = s.if_system_has_bios(system_member).await; + if system_with_bios.is_some() { + break; } + } - let system_id = system_with_bios.unwrap_or(at_least_one_system_id); + let manager_from_system = system_with_bios + .as_ref() + .and_then(|swb| swb.links.as_ref()) + .and_then(|links| links.managed_by.as_ref()) + .and_then(|mb| mb.get(0)) + .and_then(|d| d.odata_id.trim_matches('/').split('/').next_back()) + .map(|m| m.to_string()); + manager_id = manager_from_system.unwrap_or(manager_id); + + let system_id = system_with_bios.map(|swb| swb.id.to_owned()).unwrap_or(at_least_one_system_id.to_owned()); // call set_system_id always before calling set_vendor - s.set_system_id(system_id)?; + s.set_system_id(&system_id)?; - // Try to find manager with ManagerForServers related to System - for m in &managers { - if let Some(links) = s.get_manager_with_id(m).await?.links { - let manager_for_servers: Result, RedfishError> = links - .manager_for_servers - .iter() - .map(|d| { - d.odata_id - .trim_matches('/') - .split('/') - .next_back() - .map(|s| s.to_string()) - .ok_or_else(|| RedfishError::GenericError { - error: format!("Invalid odata_id format: {}", d.odata_id), - }) - }) - .collect(); - let manager_for_servers = manager_for_servers?; - if manager_for_servers.contains(system_id) { - manager_id = m; - break; - } - } - } } - s.set_manager_id(manager_id)?; + s.set_manager_id(&manager_id)?; s.set_service_root(service_root.clone())?; // Resolve placeholder/ambiguous vendors that can only be settled from diff --git a/src/standard.rs b/src/standard.rs index 26cf82946..768f25cdb 100644 --- a/src/standard.rs +++ b/src/standard.rs @@ -40,7 +40,9 @@ use crate::model::{job::Job, oem::nvidia_dpu::NicMode}; use crate::model::{ manager_network_protocol::ManagerNetworkProtocol, update_service::TransferProtocolType, }; -use crate::model::{power, thermal, BootOption, InvalidValueError, Manager, Managers, ODataId}; +use crate::model::{ + power, thermal, BootOption, ComputerSystem, InvalidValueError, Manager, Managers, ODataId, +}; use crate::model::{power::Power, update_service::UpdateService}; use crate::model::{secure_boot::SecureBoot, sensor::GPUSensors}; use crate::model::{sel::LogEntry, ManagerResetType}; @@ -1518,18 +1520,12 @@ impl RedfishStandard { }) } - pub async fn is_bios_attributes(&self, system_id: &str) -> Result { - let url = format!("Systems/{}/Bios", system_id); - let result = self.client.get::(&url).await; - match result { - Ok((_code, _data)) => Ok(true), - Err(RedfishError::HTTPErrorCode { status_code, .. }) - if status_code == StatusCode::NOT_FOUND => - { - Ok(false) - } - Err(e) => Err(e), - } + pub async fn if_system_has_bios(&self, system_id: &str) -> Option { + self.client + .get::(&format!("Systems/{system_id}")) + .await + .map_or(None, |(_code, cs)| Some(cs)) + .and_then(|cs| if cs.bios.is_some() { Some(cs) } else { None }) } pub async fn factory_reset_bios(&self) -> Result<(), RedfishError> {