nv_redfish/chassis/
item.rs1use crate::bmc_quirks::BmcQuirks;
17use crate::entity_link::FromLink;
18use crate::hardware_id::HardwareIdRef;
19use crate::hardware_id::Manufacturer as HardwareIdManufacturer;
20use crate::hardware_id::Model as HardwareIdModel;
21use crate::hardware_id::PartNumber as HardwareIdPartNumber;
22use crate::hardware_id::SerialNumber as HardwareIdSerialNumber;
23use crate::patch_support::JsonValue;
24use crate::patch_support::Payload;
25use crate::patch_support::ReadPatchFn;
26use crate::resource::ResetType;
27use crate::schema::chassis::Chassis as ChassisSchema;
28use crate::Error;
29use crate::NvBmc;
30use crate::Resource;
31use crate::ResourceSchema;
32use nv_redfish_core::bmc::Bmc;
33use nv_redfish_core::ModificationResponse;
34use nv_redfish_core::NavProperty;
35use std::future::Future;
36use std::sync::Arc;
37
38#[cfg(feature = "assembly")]
39use crate::assembly::Assembly;
40#[cfg(feature = "network-adapters")]
41use crate::chassis::NetworkAdapter;
42#[cfg(feature = "network-adapters")]
43use crate::chassis::NetworkAdapterCollection;
44#[cfg(feature = "power")]
45use crate::chassis::Power;
46#[cfg(feature = "power-supplies")]
47use crate::chassis::PowerSupply;
48#[cfg(feature = "thermal")]
49use crate::chassis::Thermal;
50#[cfg(feature = "controls")]
51use crate::control::extract_environment_power_limit_control;
52#[cfg(feature = "controls")]
53use crate::control::Control;
54#[cfg(feature = "controls")]
55use crate::control::ControlCollection;
56#[cfg(feature = "log-services")]
57use crate::log_service::LogService;
58#[cfg(all(feature = "oem-liteon", feature = "power-supplies"))]
59use crate::oem::liteon;
60#[cfg(feature = "oem-nvidia")]
61use crate::oem::nvidia::NvidiaCbcChassis;
62#[cfg(feature = "pcie-devices")]
63use crate::pcie_device::PcieDeviceCollection;
64#[cfg(feature = "sensors")]
65use crate::schema::sensor::Sensor as SchemaSensor;
66#[cfg(feature = "sensors")]
67use crate::sensor::extract_environment_sensors;
68#[cfg(feature = "sensors")]
69use crate::sensor::SensorLink;
70#[cfg(feature = "oem-nvidia")]
71use std::convert::identity;
72
73#[doc(hidden)]
74pub enum ChassisTag {}
75
76pub type Manufacturer<T> = HardwareIdManufacturer<T, ChassisTag>;
78
79pub type Model<T> = HardwareIdModel<T, ChassisTag>;
81
82pub type PartNumber<T> = HardwareIdPartNumber<T, ChassisTag>;
84
85pub type SerialNumber<T> = HardwareIdSerialNumber<T, ChassisTag>;
87
88pub struct Config {
89 pub read_patch_fn: Option<ReadPatchFn>,
90}
91
92impl Config {
93 pub fn new(quirks: &BmcQuirks) -> Self {
94 let mut patches = Vec::new();
95 if quirks.bug_invalid_contained_by_fields() {
96 patches.push(remove_invalid_contained_by_fields as fn(JsonValue) -> JsonValue);
97 }
98 if quirks.bug_missing_chassis_type_field() {
99 patches.push(add_default_chassis_type);
100 }
101 if quirks.bug_missing_chassis_name_field() {
102 patches.push(add_default_chassis_name);
103 }
104 if quirks.bug_empty_uuid_field() {
105 patches.push(normalize_empty_uuid_field);
106 }
107 let read_patch_fn = (!patches.is_empty())
108 .then(|| Arc::new(move |v| patches.iter().fold(v, |acc, f| f(acc))) as ReadPatchFn);
109 Self { read_patch_fn }
110 }
111}
112
113pub struct Chassis<B: Bmc> {
117 #[allow(dead_code)] bmc: NvBmc<B>,
119 data: Arc<ChassisSchema>,
120 #[allow(dead_code)] config: Arc<Config>,
122}
123
124impl<B: Bmc> Chassis<B> {
125 pub(crate) async fn new(
127 bmc: &NvBmc<B>,
128 nav: &NavProperty<ChassisSchema>,
129 ) -> Result<Self, Error<B>> {
130 let config = Config::new(&bmc.quirks);
131 if let Some(read_patch_fn) = &config.read_patch_fn {
132 Payload::get(bmc.as_ref(), nav, read_patch_fn.as_ref()).await
133 } else {
134 nav.get(bmc.as_ref()).await.map_err(Error::Bmc)
135 }
136 .map(|data| Self {
137 bmc: bmc.clone(),
138 data,
139 config: config.into(),
140 })
141 }
142
143 #[must_use]
148 pub fn raw(&self) -> Arc<ChassisSchema> {
149 self.data.clone()
150 }
151
152 pub async fn reset(
159 &self,
160 reset_type: Option<ResetType>,
161 ) -> Result<ModificationResponse<()>, Error<B>>
162 where
163 B::Error: nv_redfish_core::ActionError,
164 {
165 let actions = self
166 .data
167 .actions
168 .as_ref()
169 .ok_or(Error::ActionNotAvailable)?;
170
171 if actions.reset.is_none() {
172 return Err(Error::ActionNotAvailable);
173 }
174
175 actions
176 .reset(self.bmc.as_ref(), reset_type)
177 .await
178 .map_err(Error::Bmc)
179 }
180
181 #[must_use]
183 pub fn hardware_id(&self) -> HardwareIdRef<'_, ChassisTag> {
184 HardwareIdRef {
185 manufacturer: self
186 .data
187 .manufacturer
188 .as_ref()
189 .and_then(Option::as_deref)
190 .map(Manufacturer::new),
191 model: self
192 .data
193 .model
194 .as_ref()
195 .and_then(Option::as_deref)
196 .map(Model::new),
197 part_number: self
198 .data
199 .part_number
200 .as_ref()
201 .and_then(Option::as_deref)
202 .map(PartNumber::new),
203 serial_number: self
204 .data
205 .serial_number
206 .as_ref()
207 .and_then(Option::as_deref)
208 .map(SerialNumber::new),
209 }
210 }
211
212 #[cfg(feature = "assembly")]
220 pub async fn assembly(&self) -> Result<Option<Assembly<B>>, Error<B>> {
221 if let Some(assembly_ref) = &self.data.assembly {
222 Assembly::new(&self.bmc, assembly_ref).await.map(Some)
223 } else {
224 Ok(None)
225 }
226 }
227
228 #[cfg(feature = "power-supplies")]
237 pub async fn power_supplies(&self) -> Result<Vec<PowerSupply<B>>, Error<B>> {
238 if let Some(ps) = &self.data.power_subsystem {
239 let ps = ps.get(self.bmc.as_ref()).await.map_err(Error::Bmc)?;
240 if let Some(supplies) = &ps.power_supplies {
241 let supplies = &self.bmc.expand_property(supplies).await?.members;
242 let mut power_supplies = Vec::with_capacity(supplies.len());
243 for power_supply in supplies {
244 power_supplies.push(PowerSupply::new(&self.bmc, power_supply).await?);
245 }
246 return Ok(power_supplies);
247 }
248 }
249
250 Ok(Vec::new())
251 }
252
253 #[cfg(all(feature = "oem-liteon", feature = "power-supplies"))]
259 pub async fn oem_liteon_power_supply_links(
260 &self,
261 ) -> Result<Option<Vec<liteon::power_supply::LiteonPowerSupplyLink<B>>>, Error<B>> {
262 liteon::power_supply::chassis_fetch_links(&self.bmc, self).await
263 }
264
265 #[cfg(feature = "power")]
275 pub async fn power(&self) -> Result<Option<Power<B>>, Error<B>> {
276 if let Some(power_ref) = &self.data.power {
277 Ok(Some(Power::new(&self.bmc, power_ref).await?))
278 } else {
279 Ok(None)
280 }
281 }
282
283 #[cfg(feature = "controls")]
291 pub async fn controls(&self) -> Result<Option<Vec<Control<B>>>, Error<B>> {
292 let Some(controls_ref) = &self.data.controls else {
293 return Ok(None);
294 };
295
296 ControlCollection::new(&self.bmc, controls_ref)
297 .await?
298 .members()
299 .await
300 .map(Some)
301 }
302
303 #[cfg(feature = "thermal")]
313 pub async fn thermal(&self) -> Result<Option<Thermal<B>>, Error<B>> {
314 if let Some(thermal_ref) = &self.data.thermal {
315 Thermal::new(&self.bmc, thermal_ref).await.map(Some)
316 } else {
317 Ok(None)
318 }
319 }
320
321 #[cfg(feature = "network-adapters")]
330 pub async fn network_adapters(&self) -> Result<Option<Vec<NetworkAdapter<B>>>, Error<B>> {
331 if let Some(network_adapters_collection_ref) = &self.data.network_adapters {
332 NetworkAdapterCollection::new(&self.bmc, network_adapters_collection_ref)
333 .await?
334 .members()
335 .await
336 .map(Some)
337 } else {
338 Ok(None)
339 }
340 }
341
342 #[cfg(feature = "log-services")]
350 pub async fn log_services(&self) -> Result<Option<Vec<LogService<B>>>, Error<B>> {
351 if let Some(log_services_ref) = &self.data.log_services {
352 let log_services_collection = log_services_ref
353 .get(self.bmc.as_ref())
354 .await
355 .map_err(Error::Bmc)?;
356
357 let mut log_services = Vec::new();
358 for m in &log_services_collection.members {
359 log_services.push(LogService::new(&self.bmc, m).await?);
360 }
361
362 Ok(Some(log_services))
363 } else {
364 Ok(None)
365 }
366 }
367
368 #[cfg(feature = "sensors")]
376 pub async fn environment_sensor_links(&self) -> Result<Vec<SensorLink<B>>, Error<B>> {
377 let sensor_refs = if let Some(env_ref) = &self.data.environment_metrics {
378 extract_environment_sensors(env_ref, self.bmc.as_ref()).await?
379 } else {
380 Vec::new()
381 };
382
383 Ok(sensor_refs
384 .into_iter()
385 .map(|r| SensorLink::new(&self.bmc, r))
386 .collect())
387 }
388
389 #[cfg(feature = "controls")]
397 pub async fn environment_power_limit_control(&self) -> Result<Option<Control<B>>, Error<B>> {
398 let Some(env_ref) = &self.data.environment_metrics else {
399 return Ok(None);
400 };
401
402 extract_environment_power_limit_control(&self.bmc, env_ref).await
403 }
404
405 #[cfg(feature = "sensors")]
414 pub async fn sensor_links(&self) -> Result<Option<Vec<SensorLink<B>>>, Error<B>> {
415 if let Some(sensors_collection) = &self.data.sensors {
416 let sc = sensors_collection
417 .get(self.bmc.as_ref())
418 .await
419 .map_err(Error::Bmc)?;
420 let mut sensor_data = Vec::with_capacity(sc.members.len());
421 for sensor in &sc.members {
422 sensor_data.push(SensorLink::new(
423 &self.bmc,
424 NavProperty::<SchemaSensor>::new_reference(sensor.id().clone()),
425 ));
426 }
427 Ok(Some(sensor_data))
428 } else {
429 Ok(None)
430 }
431 }
432
433 #[cfg(feature = "pcie-devices")]
441 pub async fn pcie_devices(&self) -> Result<Option<PcieDeviceCollection<B>>, crate::Error<B>> {
442 if let Some(p) = &self.data.pcie_devices {
443 PcieDeviceCollection::new(&self.bmc, p).await.map(Some)
444 } else {
445 Ok(None)
446 }
447 }
448
449 #[cfg(feature = "oem-nvidia")]
457 pub fn oem_nvidia_cbc(&self) -> Result<Option<NvidiaCbcChassis<B>>, Error<B>> {
458 self.data
459 .base
460 .base
461 .oem
462 .as_ref()
463 .map(NvidiaCbcChassis::new)
464 .transpose()
465 .map(|v| v.and_then(identity))
466 }
467}
468
469impl<B: Bmc> Resource for Chassis<B> {
470 fn resource_ref(&self) -> &ResourceSchema {
471 &self.data.as_ref().base
472 }
473}
474
475impl<B: Bmc> FromLink<B> for Chassis<B> {
476 type Schema = ChassisSchema;
477
478 fn from_link(
479 bmc: &NvBmc<B>,
480 nav: &NavProperty<Self::Schema>,
481 ) -> impl Future<Output = Result<Self, Error<B>>> + Send {
482 Self::new(bmc, nav)
483 }
484}
485
486fn remove_invalid_contained_by_fields(mut v: JsonValue) -> JsonValue {
487 if let JsonValue::Object(ref mut obj) = v {
488 if let Some(JsonValue::Object(ref mut links_obj)) = obj.get_mut("Links") {
489 if let Some(JsonValue::Object(ref mut contained_by_obj)) =
490 links_obj.get_mut("ContainedBy")
491 {
492 contained_by_obj.retain(|k, _| k == "@odata.id");
493 }
494 }
495 }
496 v
497}
498
499fn add_default_chassis_type(v: JsonValue) -> JsonValue {
500 if let JsonValue::Object(mut obj) = v {
501 obj.entry("ChassisType")
502 .or_insert(JsonValue::String("Other".into()));
503 JsonValue::Object(obj)
504 } else {
505 v
506 }
507}
508
509fn add_default_chassis_name(v: JsonValue) -> JsonValue {
510 if let JsonValue::Object(mut obj) = v {
511 obj.entry("Name")
512 .or_insert(JsonValue::String("Unnamed chassis".into()));
513 JsonValue::Object(obj)
514 } else {
515 v
516 }
517}
518
519fn normalize_empty_uuid_field(mut v: JsonValue) -> JsonValue {
520 if let JsonValue::Object(ref mut obj) = v {
521 if let Some(uuid) = obj.get_mut("UUID") {
522 let is_empty = uuid.as_str().is_some_and(str::is_empty);
523 if is_empty {
524 *uuid = JsonValue::Null;
525 }
526 }
527 }
528 v
529}