diff --git a/redfish/src/chassis/item.rs b/redfish/src/chassis/item.rs index e87b59c4..396d2933 100644 --- a/redfish/src/chassis/item.rs +++ b/redfish/src/chassis/item.rs @@ -23,12 +23,14 @@ use crate::hardware_id::SerialNumber as HardwareIdSerialNumber; use crate::patch_support::JsonValue; use crate::patch_support::Payload; use crate::patch_support::ReadPatchFn; +use crate::resource::ResetType; use crate::schema::chassis::Chassis as ChassisSchema; use crate::Error; use crate::NvBmc; use crate::Resource; use crate::ResourceSchema; use nv_redfish_core::bmc::Bmc; +use nv_redfish_core::ModificationResponse; use nv_redfish_core::NavProperty; use std::future::Future; use std::sync::Arc; @@ -147,6 +149,35 @@ impl Chassis { self.data.clone() } + /// Reset this chassis. + /// + /// # Errors + /// + /// Returns an error if the chassis does not support the `Reset` action or + /// if invoking the action fails. + pub async fn reset( + &self, + reset_type: Option, + ) -> Result, Error> + where + B::Error: nv_redfish_core::ActionError, + { + let actions = self + .data + .actions + .as_ref() + .ok_or(Error::ActionNotAvailable)?; + + if actions.reset.is_none() { + return Err(Error::ActionNotAvailable); + } + + actions + .reset(self.bmc.as_ref(), reset_type) + .await + .map_err(Error::Bmc) + } + /// Get hardware identifier of the network adpater. #[must_use] pub fn hardware_id(&self) -> HardwareIdRef<'_, ChassisTag> { diff --git a/redfish/src/chassis/power_supply.rs b/redfish/src/chassis/power_supply.rs index a83453a3..c93edb09 100644 --- a/redfish/src/chassis/power_supply.rs +++ b/redfish/src/chassis/power_supply.rs @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::resource::ResetType; use crate::schema::power_supply::PowerSupply as PowerSupplySchema; use crate::schema::power_supply_metrics::PowerSupplyMetrics; use crate::Error; @@ -20,6 +21,7 @@ use crate::NvBmc; use crate::Resource; use crate::ResourceSchema; use nv_redfish_core::Bmc; +use nv_redfish_core::ModificationResponse; use nv_redfish_core::NavProperty; use std::sync::Arc; @@ -60,6 +62,35 @@ impl PowerSupply { self.data.clone() } + /// Reset this power supply. + /// + /// # Errors + /// + /// Returns an error if the power supply does not support the `Reset` + /// action or if invoking the action fails. + pub async fn reset( + &self, + reset_type: Option, + ) -> Result, Error> + where + B::Error: nv_redfish_core::ActionError, + { + let actions = self + .data + .actions + .as_ref() + .ok_or(Error::ActionNotAvailable)?; + + if actions.reset.is_none() { + return Err(Error::ActionNotAvailable); + } + + actions + .reset(self.bmc.as_ref(), reset_type) + .await + .map_err(Error::Bmc) + } + /// Get power supply metrics. /// /// Returns the power supply's performance and state metrics if available. diff --git a/redfish/src/computer_system/item.rs b/redfish/src/computer_system/item.rs index 0c3b644e..70d7f409 100644 --- a/redfish/src/computer_system/item.rs +++ b/redfish/src/computer_system/item.rs @@ -26,6 +26,7 @@ use crate::hardware_id::SerialNumber as HardwareIdSerialNumber; use crate::patch_support::Payload; use crate::patch_support::ReadPatchFn; use crate::resource::PowerState; +use crate::resource::ResetType; use crate::schema::computer_system::ComputerSystem as ComputerSystemSchema; use crate::Error; use crate::NvBmc; @@ -187,6 +188,35 @@ impl ComputerSystem { self.data.power_state.and_then(identity) } + /// Reset this computer system. + /// + /// # Errors + /// + /// Returns an error if the system does not support the `Reset` action or + /// if invoking the action fails. + pub async fn reset( + &self, + reset_type: Option, + ) -> Result, Error> + where + B::Error: nv_redfish_core::ActionError, + { + let actions = self + .data + .actions + .as_ref() + .ok_or(Error::ActionNotAvailable)?; + + if actions.reset.is_none() { + return Err(Error::ActionNotAvailable); + } + + actions + .reset(self.bmc.as_ref(), reset_type) + .await + .map_err(Error::Bmc) + } + /// An array of `BootOptionReference` strings that represent the persistent boot order for with this /// computer system. #[must_use] diff --git a/redfish/src/manager/item.rs b/redfish/src/manager/item.rs index 90c71a9c..731952a7 100644 --- a/redfish/src/manager/item.rs +++ b/redfish/src/manager/item.rs @@ -13,12 +13,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::resource::ResetType; use crate::schema::manager::Manager as ManagerSchema; +use crate::schema::manager::ResetToDefaultsType as ManagerResetToDefaultsType; use crate::Error; use crate::NvBmc; use crate::Resource; use crate::ResourceSchema; use nv_redfish_core::Bmc; +use nv_redfish_core::ModificationResponse; use nv_redfish_core::NavProperty; use std::sync::Arc; @@ -72,6 +75,64 @@ impl Manager { self.data.clone() } + /// Reset this manager. + /// + /// # Errors + /// + /// Returns an error if the manager does not support the `Reset` action or + /// if invoking the action fails. + pub async fn reset( + &self, + reset_type: Option, + ) -> Result, Error> + where + B::Error: nv_redfish_core::ActionError, + { + let actions = self + .data + .actions + .as_ref() + .ok_or(Error::ActionNotAvailable)?; + + if actions.reset.is_none() { + return Err(Error::ActionNotAvailable); + } + + actions + .reset(self.bmc.as_ref(), reset_type) + .await + .map_err(Error::Bmc) + } + + /// Reset this manager's settings to defaults. + /// + /// # Errors + /// + /// Returns an error if the manager does not support the `ResetToDefaults` + /// action or if invoking the action fails. + pub async fn reset_to_defaults( + &self, + reset_type: ManagerResetToDefaultsType, + ) -> Result, Error> + where + B::Error: nv_redfish_core::ActionError, + { + let actions = self + .data + .actions + .as_ref() + .ok_or(Error::ActionNotAvailable)?; + + if actions.reset_to_defaults.is_none() { + return Err(Error::ActionNotAvailable); + } + + actions + .reset_to_defaults(self.bmc.as_ref(), Some(reset_type)) + .await + .map_err(Error::Bmc) + } + /// Get ethernet interfaces for this manager. /// /// Returns `Ok(None)` when the ethernet interfaces link is absent. diff --git a/redfish/src/manager/mod.rs b/redfish/src/manager/mod.rs index 3f40e516..ee3ae94b 100644 --- a/redfish/src/manager/mod.rs +++ b/redfish/src/manager/mod.rs @@ -36,6 +36,9 @@ use std::sync::Arc; pub use item::Manager; +#[doc(inline)] +pub use crate::schema::manager::ResetToDefaultsType as ManagerResetToDefaultsType; + /// Manager collection. /// /// Provides functions to access collection members. diff --git a/redfish/src/resource.rs b/redfish/src/resource.rs index 830dfb34..7ce91f99 100644 --- a/redfish/src/resource.rs +++ b/redfish/src/resource.rs @@ -39,6 +39,14 @@ pub use crate::schema::resource::State; #[cfg(feature = "computer-systems")] pub use crate::schema::resource::PowerState; +#[doc(inline)] +#[cfg(any( + feature = "chassis", + feature = "computer-systems", + feature = "managers" +))] +pub use crate::schema::resource::ResetType; + /// Redfish resource identifier. pub type ResourceId = TaggedType; /// Reference to Redfish resource identifier. diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 710602da..326e98f7 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -88,3 +88,27 @@ pub fn anonymous_1_9_service_root(root_id: &ODataId, fields: Value) -> Value { }); json_merge([&base, &fields]) } + +/// Build a Redfish `Actions` payload containing one action target. +pub fn redfish_action_payload(action: &str, target: &str) -> Value { + let mut action_body = serde_json::Map::new(); + action_body.insert("target".into(), json!(target)); + + let mut actions = serde_json::Map::new(); + actions.insert(format!("#{action}"), Value::Object(action_body)); + + let mut payload = serde_json::Map::new(); + payload.insert("Actions".into(), Value::Object(actions)); + Value::Object(payload) +} + +/// Build a Redfish payload with an empty `Actions` object. +pub fn redfish_empty_actions_payload() -> Value { + json!({ "Actions": {} }) +} + +/// Expect a Redfish reset action request with an empty action response. +pub fn expect_redfish_reset_action(bmc: &Bmc, target: &str, reset_type: Option<&str>) { + let request = reset_type.map_or_else(|| json!({}), |value| json!({ "ResetType": value })); + bmc.expect(Expect::action(target, request, json!(null))); +} diff --git a/tests/tests/test-chassis.rs b/tests/tests/test-chassis.rs index 35617885..f1f248cb 100644 --- a/tests/tests/test-chassis.rs +++ b/tests/tests/test-chassis.rs @@ -14,13 +14,19 @@ // limitations under the License. //! Integration tests for Chassis collection workaround behavior. +use nv_redfish::chassis::Chassis; +use nv_redfish::chassis::PowerSupply; use nv_redfish::control::ControlUpdate; +use nv_redfish::resource::ResetType; use nv_redfish::ServiceRoot; use nv_redfish_core::ModificationResponse; use nv_redfish_core::ODataId; use nv_redfish_tests::ami_viking_service_root; use nv_redfish_tests::anonymous_1_9_service_root; +use nv_redfish_tests::expect_redfish_reset_action; use nv_redfish_tests::json_merge; +use nv_redfish_tests::redfish_action_payload; +use nv_redfish_tests::redfish_empty_actions_payload; use nv_redfish_tests::Bmc; use nv_redfish_tests::Expect; use nv_redfish_tests::ODATA_ID; @@ -33,6 +39,93 @@ use tokio::test; const CHASSIS_COLLECTION_DATA_TYPE: &str = "#ChassisCollection.ChassisCollection"; const CHASSIS_DATA_TYPE: &str = "#Chassis.v1_23_0.Chassis"; +const POWER_SUBSYSTEM_DATA_TYPE: &str = "#PowerSubsystem.v1_1_0.PowerSubsystem"; +const POWER_SUPPLY_COLLECTION_DATA_TYPE: &str = "#PowerSupplyCollection.PowerSupplyCollection"; +const POWER_SUPPLY_DATA_TYPE: &str = "#PowerSupply.v1_5_0.PowerSupply"; + +#[test] +async fn reset_invokes_chassis_reset_action() -> Result<(), Box> { + let bmc = Arc::new(Bmc::default()); + let ids = ids(); + let action_target = format!("{}/Actions/Chassis.Reset", ids.chassis_id); + let chassis = get_chassis( + bmc.clone(), + &ids, + chassis_payload( + &ids, + redfish_action_payload("Chassis.Reset", &action_target), + ), + ) + .await?; + + expect_redfish_reset_action(&bmc, &action_target, Some("ForceRestart")); + chassis.reset(Some(ResetType::ForceRestart)).await?; + + expect_redfish_reset_action(&bmc, &action_target, None); + chassis.reset(None).await?; + + Ok(()) +} + +#[test] +async fn reset_returns_action_not_available_when_chassis_reset_is_absent( +) -> Result<(), Box> { + let bmc = Arc::new(Bmc::default()); + let ids = ids(); + let chassis = get_chassis( + bmc.clone(), + &ids, + chassis_payload(&ids, redfish_empty_actions_payload()), + ) + .await?; + + assert!(matches!( + chassis.reset(Some(ResetType::ForceRestart)).await, + Err(nv_redfish::Error::ActionNotAvailable) + )); + + Ok(()) +} + +#[test] +async fn reset_invokes_power_supply_reset_action() -> Result<(), Box> { + let bmc = Arc::new(Bmc::default()); + let ids = ids(); + let power_ids = power_supply_ids(&ids); + let action_target = format!("{}/Actions/PowerSupply.Reset", power_ids.power_supply_id); + let power_supply = get_power_supply( + bmc.clone(), + &ids, + &power_ids, + redfish_action_payload("PowerSupply.Reset", &action_target), + ) + .await?; + + expect_redfish_reset_action(&bmc, &action_target, Some("GracefulRestart")); + power_supply.reset(Some(ResetType::GracefulRestart)).await?; + + expect_redfish_reset_action(&bmc, &action_target, None); + power_supply.reset(None).await?; + + Ok(()) +} + +#[test] +async fn reset_returns_action_not_available_when_power_supply_reset_is_absent( +) -> Result<(), Box> { + let bmc = Arc::new(Bmc::default()); + let ids = ids(); + let power_ids = power_supply_ids(&ids); + let power_supply = + get_power_supply(bmc, &ids, &power_ids, redfish_empty_actions_payload()).await?; + + assert!(matches!( + power_supply.reset(Some(ResetType::GracefulRestart)).await, + Err(nv_redfish::Error::ActionNotAvailable) + )); + + Ok(()) +} #[test] async fn ami_viking_missing_root_chassis_nav_workaround() -> Result<(), Box> { @@ -544,6 +637,10 @@ fn valid_chassis_payload(ids: &Ids) -> Value { }) } +fn chassis_payload(ids: &Ids, fields: Value) -> Value { + json_merge([&valid_chassis_payload(ids), &fields]) +} + fn control_payload(control_id: &str, set_point: f64) -> Value { json!({ ODATA_ID: control_id, @@ -559,6 +656,123 @@ fn control_payload(control_id: &str, set_point: f64) -> Value { }) } +async fn get_chassis( + bmc: Arc, + ids: &Ids, + payload: Value, +) -> Result, Box> { + let root = expect_anonymous_1_9_service_root( + bmc.clone(), + ids, + json!({ + "Chassis": { ODATA_ID: &ids.chassis_collection_id } + }), + ) + .await?; + expect_chassis_collection(bmc.clone(), ids); + let collection = root.chassis().await?.unwrap(); + expect_chassis_get(bmc, ids, payload); + let mut members = collection.members().await?; + members.pop().ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "missing chassis").into() + }) +} + +async fn get_power_supply( + bmc: Arc, + ids: &Ids, + power_ids: &PowerSupplyIds, + power_supply_fields: Value, +) -> Result, Box> { + let chassis = get_chassis( + bmc.clone(), + ids, + chassis_payload( + ids, + json!({ + "PowerSubsystem": { + ODATA_ID: &power_ids.power_subsystem_id + } + }), + ), + ) + .await?; + + expect_power_supply( + bmc, + power_ids, + power_supply_payload(power_ids, power_supply_fields), + ); + let mut power_supplies = chassis.power_supplies().await?; + power_supplies.pop().ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "missing power supply").into() + }) +} + +struct PowerSupplyIds { + power_subsystem_id: String, + power_supply_collection_id: String, + power_supply_id: String, +} + +fn power_supply_ids(ids: &Ids) -> PowerSupplyIds { + let power_subsystem_id = format!("{}/PowerSubsystem", ids.chassis_id); + let power_supply_collection_id = format!("{power_subsystem_id}/PowerSupplies"); + let power_supply_id = format!("{power_supply_collection_id}/1"); + PowerSupplyIds { + power_subsystem_id, + power_supply_collection_id, + power_supply_id, + } +} + +fn expect_power_supply(bmc: Arc, ids: &PowerSupplyIds, payload: Value) { + bmc.expect(Expect::get( + &ids.power_subsystem_id, + json!({ + ODATA_ID: &ids.power_subsystem_id, + ODATA_TYPE: POWER_SUBSYSTEM_DATA_TYPE, + "Id": "PowerSubsystem", + "Name": "Power Subsystem", + "PowerSupplies": { + ODATA_ID: &ids.power_supply_collection_id + } + }), + )); + bmc.expect(Expect::get( + &ids.power_supply_collection_id, + json!({ + ODATA_ID: &ids.power_supply_collection_id, + ODATA_TYPE: POWER_SUPPLY_COLLECTION_DATA_TYPE, + "Id": "PowerSupplies", + "Name": "Power Supply Collection", + "Members": [ + { + ODATA_ID: &ids.power_supply_id + } + ] + }), + )); + bmc.expect(Expect::get(&ids.power_supply_id, payload)); +} + +fn power_supply_payload(ids: &PowerSupplyIds, fields: Value) -> Value { + let base = json!({ + ODATA_ID: &ids.power_supply_id, + ODATA_TYPE: POWER_SUPPLY_DATA_TYPE, + "Id": "1", + "Name": "Power Supply 1", + "Manufacturer": "NVIDIA", + "Model": "PSU-1", + "PowerState": true, + "Status": { + "Health": "OK", + "State": "Enabled" + } + }); + json_merge([&base, &fields]) +} + struct Ids { root_id: ODataId, chassis_collection_id: String, diff --git a/tests/tests/test-computer-system.rs b/tests/tests/test-computer-system.rs index bfa2217a..3a86b704 100644 --- a/tests/tests/test-computer-system.rs +++ b/tests/tests/test-computer-system.rs @@ -14,13 +14,18 @@ // limitations under the License. //! Integration tests for Computer System resources. +use nv_redfish::computer_system::ComputerSystem; use nv_redfish::computer_system::SystemCollection; +use nv_redfish::resource::ResetType; use nv_redfish::Resource; use nv_redfish::ServiceRoot; use nv_redfish_core::ODataId; use nv_redfish_tests::ami_viking_service_root; use nv_redfish_tests::anonymous_1_9_service_root; +use nv_redfish_tests::expect_redfish_reset_action; use nv_redfish_tests::json_merge; +use nv_redfish_tests::redfish_action_payload; +use nv_redfish_tests::redfish_empty_actions_payload; use nv_redfish_tests::Bmc; use nv_redfish_tests::Expect; use nv_redfish_tests::ODATA_ID; @@ -35,6 +40,50 @@ const SERVICE_ROOT_DATA_TYPE: &str = "#ServiceRoot.v1_13_0.ServiceRoot"; const SYSTEM_COLLECTION_DATA_TYPE: &str = "#ComputerSystemCollection.ComputerSystemCollection"; const SYSTEM_DATA_TYPE: &str = "#ComputerSystem.v1_20_0.ComputerSystem"; +#[test] +async fn reset_invokes_computer_system_reset_action() -> Result<(), Box> { + let bmc = Arc::new(Bmc::default()); + let ids = computer_system_ids(); + let action_target = format!("{}/Actions/ComputerSystem.Reset", ids.system_id); + let system = get_system( + bmc.clone(), + &ids, + computer_system( + &ids, + redfish_action_payload("ComputerSystem.Reset", &action_target), + ), + ) + .await?; + + expect_redfish_reset_action(&bmc, &action_target, Some("GracefulRestart")); + system.reset(Some(ResetType::GracefulRestart)).await?; + + expect_redfish_reset_action(&bmc, &action_target, None); + system.reset(None).await?; + + Ok(()) +} + +#[test] +async fn reset_returns_action_not_available_when_computer_system_reset_is_absent( +) -> Result<(), Box> { + let bmc = Arc::new(Bmc::default()); + let ids = computer_system_ids(); + let system = get_system( + bmc.clone(), + &ids, + computer_system(&ids, redfish_empty_actions_payload()), + ) + .await?; + + assert!(matches!( + system.reset(Some(ResetType::GracefulRestart)).await, + Err(nv_redfish::Error::ActionNotAvailable) + )); + + Ok(()) +} + #[test] async fn dell_wrong_last_reset_time_workaround() -> Result<(), Box> { let bmc = Arc::new(Bmc::default()); @@ -377,3 +426,15 @@ fn computer_system(ids: &ComputerSystemIds, fields: Value) -> Value { }); json_merge([&base, &fields]) } + +async fn get_system( + bmc: Arc, + ids: &ComputerSystemIds, + member: Value, +) -> Result, Box> { + let systems = get_systems(bmc, ids, "NVIDIA", vec![member]).await?; + let mut members = systems.members().await?; + members.pop().ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "missing computer system").into() + }) +} diff --git a/tests/tests/test-manager.rs b/tests/tests/test-manager.rs index a07827fe..4ed93df4 100644 --- a/tests/tests/test-manager.rs +++ b/tests/tests/test-manager.rs @@ -14,16 +14,24 @@ // limitations under the License. //! Integration tests for Manager collection behavior. +use nv_redfish::manager::Manager; +use nv_redfish::manager::ManagerResetToDefaultsType; +use nv_redfish::resource::ResetType; use nv_redfish::Resource; use nv_redfish::ServiceRoot; use nv_redfish_core::ODataId; use nv_redfish_tests::ami_viking_service_root; use nv_redfish_tests::anonymous_1_9_service_root; +use nv_redfish_tests::expect_redfish_reset_action; +use nv_redfish_tests::json_merge; +use nv_redfish_tests::redfish_action_payload; +use nv_redfish_tests::redfish_empty_actions_payload; use nv_redfish_tests::Bmc; use nv_redfish_tests::Expect; use nv_redfish_tests::ODATA_ID; use nv_redfish_tests::ODATA_TYPE; use serde_json::json; +use serde_json::Value; use std::error::Error as StdError; use std::sync::Arc; use tokio::test; @@ -31,6 +39,80 @@ use tokio::test; const MANAGER_COLLECTION_DATA_TYPE: &str = "#ManagerCollection.ManagerCollection"; const MANAGER_DATA_TYPE: &str = "#Manager.v1_16_0.Manager"; +#[test] +async fn reset_invokes_manager_reset_action() -> Result<(), Box> { + let bmc = Arc::new(Bmc::default()); + let ids = ids(); + let action_target = format!("{}/Actions/Manager.Reset", ids.manager_id); + let manager = get_manager( + bmc.clone(), + &ids, + manager_payload_with_fields( + &ids, + redfish_action_payload("Manager.Reset", &action_target), + ), + ) + .await?; + + expect_redfish_reset_action(&bmc, &action_target, Some("ForceRestart")); + manager.reset(Some(ResetType::ForceRestart)).await?; + + expect_redfish_reset_action(&bmc, &action_target, None); + manager.reset(None).await?; + + Ok(()) +} + +#[test] +async fn reset_to_defaults_invokes_manager_reset_to_defaults_action( +) -> Result<(), Box> { + let bmc = Arc::new(Bmc::default()); + let ids = ids(); + let action_target = format!("{}/Actions/Manager.ResetToDefaults", ids.manager_id); + let manager = get_manager( + bmc.clone(), + &ids, + manager_payload_with_fields( + &ids, + redfish_action_payload("Manager.ResetToDefaults", &action_target), + ), + ) + .await?; + + expect_redfish_reset_action(&bmc, &action_target, Some("ResetAll")); + manager + .reset_to_defaults(ManagerResetToDefaultsType::ResetAll) + .await?; + + Ok(()) +} + +#[test] +async fn reset_helpers_return_action_not_available_when_manager_actions_are_absent( +) -> Result<(), Box> { + let bmc = Arc::new(Bmc::default()); + let ids = ids(); + let manager = get_manager( + bmc.clone(), + &ids, + manager_payload_with_fields(&ids, redfish_empty_actions_payload()), + ) + .await?; + + assert!(matches!( + manager.reset(Some(ResetType::ForceRestart)).await, + Err(nv_redfish::Error::ActionNotAvailable) + )); + assert!(matches!( + manager + .reset_to_defaults(ManagerResetToDefaultsType::ResetAll) + .await, + Err(nv_redfish::Error::ActionNotAvailable) + )); + + Ok(()) +} + #[test] async fn ami_viking_missing_root_managers_nav_workaround() -> Result<(), Box> { let bmc = Arc::new(Bmc::default()); @@ -194,20 +276,25 @@ fn manager_payload(ids: &Ids) -> serde_json::Value { manager_payload_with_state(ids, "Enabled") } -fn manager_payload_with_state(ids: &Ids, state: &str) -> serde_json::Value { - json!({ +fn manager_payload_with_state(ids: &Ids, state: &str) -> Value { + manager_payload_with_fields(ids, json!({ "Status": { "State": state } })) +} + +fn manager_payload_with_fields(ids: &Ids, fields: Value) -> Value { + let base = json!({ ODATA_ID: &ids.manager_id, ODATA_TYPE: MANAGER_DATA_TYPE, "Id": "1", "Name": "Manager", - "Status": { "State": state } - }) + "Status": { "State": "Enabled" } + }); + json_merge([&base, &fields]) } async fn expect_anonymous_1_9_service_root( bmc: Arc, ids: &Ids, - fields: serde_json::Value, + fields: Value, ) -> Result, Box> { bmc.expect(Expect::get( &ids.root_id, @@ -216,7 +303,7 @@ async fn expect_anonymous_1_9_service_root( ServiceRoot::new(bmc).await.map_err(Into::into) } -fn manager_payload_with_id(id: &str) -> serde_json::Value { +fn manager_payload_with_id(id: &str) -> Value { let name = id.rsplit('/').next().unwrap_or("Manager"); json!({ ODATA_ID: id, @@ -226,3 +313,34 @@ fn manager_payload_with_id(id: &str) -> serde_json::Value { "Status": { "State": "Enabled" } }) } + +async fn get_manager( + bmc: Arc, + ids: &Ids, + member: Value, +) -> Result, Box> { + let root = expect_anonymous_1_9_service_root( + bmc.clone(), + ids, + json!({ + "Managers": { ODATA_ID: &ids.managers_id } + }), + ) + .await?; + bmc.expect(Expect::get( + &ids.managers_id, + json!({ + ODATA_ID: &ids.managers_id, + ODATA_TYPE: MANAGER_COLLECTION_DATA_TYPE, + "Id": "Managers", + "Name": "Manager Collection", + "Members": [member] + }), + )); + + let collection = root.managers().await?.unwrap(); + let mut members = collection.members().await?; + members.pop().ok_or_else(|| { + std::io::Error::new(std::io::ErrorKind::InvalidData, "missing manager").into() + }) +}