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::schema::chassis::Chassis as ChassisSchema;
27use crate::Error;
28use crate::NvBmc;
29use crate::Resource;
30use crate::ResourceSchema;
31use nv_redfish_core::bmc::Bmc;
32use nv_redfish_core::NavProperty;
33use std::future::Future;
34use std::sync::Arc;
35
36#[cfg(feature = "assembly")]
37use crate::assembly::Assembly;
38#[cfg(feature = "network-adapters")]
39use crate::chassis::NetworkAdapter;
40#[cfg(feature = "network-adapters")]
41use crate::chassis::NetworkAdapterCollection;
42#[cfg(feature = "power")]
43use crate::chassis::Power;
44#[cfg(feature = "power-supplies")]
45use crate::chassis::PowerSupply;
46#[cfg(feature = "thermal")]
47use crate::chassis::Thermal;
48#[cfg(feature = "controls")]
49use crate::control::extract_environment_power_limit_control;
50#[cfg(feature = "controls")]
51use crate::control::Control;
52#[cfg(feature = "controls")]
53use crate::control::ControlCollection;
54#[cfg(feature = "log-services")]
55use crate::log_service::LogService;
56#[cfg(all(feature = "oem-liteon", feature = "power-supplies"))]
57use crate::oem::liteon;
58#[cfg(feature = "oem-nvidia-baseboard")]
59use crate::oem::nvidia::baseboard::NvidiaCbcChassis;
60#[cfg(feature = "pcie-devices")]
61use crate::pcie_device::PcieDeviceCollection;
62#[cfg(feature = "sensors")]
63use crate::schema::sensor::Sensor as SchemaSensor;
64#[cfg(feature = "sensors")]
65use crate::sensor::extract_environment_sensors;
66#[cfg(feature = "sensors")]
67use crate::sensor::SensorLink;
68#[cfg(feature = "oem-nvidia-baseboard")]
69use std::convert::identity;
70
71#[doc(hidden)]
72pub enum ChassisTag {}
73
74pub type Manufacturer<T> = HardwareIdManufacturer<T, ChassisTag>;
76
77pub type Model<T> = HardwareIdModel<T, ChassisTag>;
79
80pub type PartNumber<T> = HardwareIdPartNumber<T, ChassisTag>;
82
83pub type SerialNumber<T> = HardwareIdSerialNumber<T, ChassisTag>;
85
86pub struct Config {
87 pub read_patch_fn: Option<ReadPatchFn>,
88}
89
90impl Config {
91 pub fn new(quirks: &BmcQuirks) -> Self {
92 let mut patches = Vec::new();
93 if quirks.bug_invalid_contained_by_fields() {
94 patches.push(remove_invalid_contained_by_fields as fn(JsonValue) -> JsonValue);
95 }
96 if quirks.bug_missing_chassis_type_field() {
97 patches.push(add_default_chassis_type);
98 }
99 if quirks.bug_missing_chassis_name_field() {
100 patches.push(add_default_chassis_name);
101 }
102 if quirks.bug_empty_uuid_field() {
103 patches.push(normalize_empty_uuid_field);
104 }
105 let read_patch_fn = (!patches.is_empty())
106 .then(|| Arc::new(move |v| patches.iter().fold(v, |acc, f| f(acc))) as ReadPatchFn);
107 Self { read_patch_fn }
108 }
109}
110
111pub struct Chassis<B: Bmc> {
115 #[allow(dead_code)] bmc: NvBmc<B>,
117 data: Arc<ChassisSchema>,
118 #[allow(dead_code)] config: Arc<Config>,
120}
121
122impl<B: Bmc> Chassis<B> {
123 pub(crate) async fn new(
125 bmc: &NvBmc<B>,
126 nav: &NavProperty<ChassisSchema>,
127 ) -> Result<Self, Error<B>> {
128 let config = Config::new(&bmc.quirks);
129 if let Some(read_patch_fn) = &config.read_patch_fn {
130 Payload::get(bmc.as_ref(), nav, read_patch_fn.as_ref()).await
131 } else {
132 nav.get(bmc.as_ref()).await.map_err(Error::Bmc)
133 }
134 .map(|data| Self {
135 bmc: bmc.clone(),
136 data,
137 config: config.into(),
138 })
139 }
140
141 #[must_use]
146 pub fn raw(&self) -> Arc<ChassisSchema> {
147 self.data.clone()
148 }
149
150 #[must_use]
152 pub fn hardware_id(&self) -> HardwareIdRef<'_, ChassisTag> {
153 HardwareIdRef {
154 manufacturer: self
155 .data
156 .manufacturer
157 .as_ref()
158 .and_then(Option::as_deref)
159 .map(Manufacturer::new),
160 model: self
161 .data
162 .model
163 .as_ref()
164 .and_then(Option::as_deref)
165 .map(Model::new),
166 part_number: self
167 .data
168 .part_number
169 .as_ref()
170 .and_then(Option::as_deref)
171 .map(PartNumber::new),
172 serial_number: self
173 .data
174 .serial_number
175 .as_ref()
176 .and_then(Option::as_deref)
177 .map(SerialNumber::new),
178 }
179 }
180
181 #[cfg(feature = "assembly")]
189 pub async fn assembly(&self) -> Result<Option<Assembly<B>>, Error<B>> {
190 if let Some(assembly_ref) = &self.data.assembly {
191 Assembly::new(&self.bmc, assembly_ref).await.map(Some)
192 } else {
193 Ok(None)
194 }
195 }
196
197 #[cfg(feature = "power-supplies")]
206 pub async fn power_supplies(&self) -> Result<Vec<PowerSupply<B>>, Error<B>> {
207 if let Some(ps) = &self.data.power_subsystem {
208 let ps = ps.get(self.bmc.as_ref()).await.map_err(Error::Bmc)?;
209 if let Some(supplies) = &ps.power_supplies {
210 let supplies = &self.bmc.expand_property(supplies).await?.members;
211 let mut power_supplies = Vec::with_capacity(supplies.len());
212 for power_supply in supplies {
213 power_supplies.push(PowerSupply::new(&self.bmc, power_supply).await?);
214 }
215 return Ok(power_supplies);
216 }
217 }
218
219 Ok(Vec::new())
220 }
221
222 #[cfg(all(feature = "oem-liteon", feature = "power-supplies"))]
228 pub async fn oem_liteon_power_supply_links(
229 &self,
230 ) -> Result<Option<Vec<liteon::power_supply::LiteonPowerSupplyLink<B>>>, Error<B>> {
231 liteon::power_supply::chassis_fetch_links(&self.bmc, self).await
232 }
233
234 #[cfg(feature = "power")]
244 pub async fn power(&self) -> Result<Option<Power<B>>, Error<B>> {
245 if let Some(power_ref) = &self.data.power {
246 Ok(Some(Power::new(&self.bmc, power_ref).await?))
247 } else {
248 Ok(None)
249 }
250 }
251
252 #[cfg(feature = "controls")]
260 pub async fn controls(&self) -> Result<Option<Vec<Control<B>>>, Error<B>> {
261 let Some(controls_ref) = &self.data.controls else {
262 return Ok(None);
263 };
264
265 ControlCollection::new(&self.bmc, controls_ref)
266 .await?
267 .members()
268 .await
269 .map(Some)
270 }
271
272 #[cfg(feature = "thermal")]
282 pub async fn thermal(&self) -> Result<Option<Thermal<B>>, Error<B>> {
283 if let Some(thermal_ref) = &self.data.thermal {
284 Thermal::new(&self.bmc, thermal_ref).await.map(Some)
285 } else {
286 Ok(None)
287 }
288 }
289
290 #[cfg(feature = "network-adapters")]
299 pub async fn network_adapters(&self) -> Result<Option<Vec<NetworkAdapter<B>>>, Error<B>> {
300 if let Some(network_adapters_collection_ref) = &self.data.network_adapters {
301 NetworkAdapterCollection::new(&self.bmc, network_adapters_collection_ref)
302 .await?
303 .members()
304 .await
305 .map(Some)
306 } else {
307 Ok(None)
308 }
309 }
310
311 #[cfg(feature = "log-services")]
319 pub async fn log_services(&self) -> Result<Option<Vec<LogService<B>>>, Error<B>> {
320 if let Some(log_services_ref) = &self.data.log_services {
321 let log_services_collection = log_services_ref
322 .get(self.bmc.as_ref())
323 .await
324 .map_err(Error::Bmc)?;
325
326 let mut log_services = Vec::new();
327 for m in &log_services_collection.members {
328 log_services.push(LogService::new(&self.bmc, m).await?);
329 }
330
331 Ok(Some(log_services))
332 } else {
333 Ok(None)
334 }
335 }
336
337 #[cfg(feature = "sensors")]
345 pub async fn environment_sensor_links(&self) -> Result<Vec<SensorLink<B>>, Error<B>> {
346 let sensor_refs = if let Some(env_ref) = &self.data.environment_metrics {
347 extract_environment_sensors(env_ref, self.bmc.as_ref()).await?
348 } else {
349 Vec::new()
350 };
351
352 Ok(sensor_refs
353 .into_iter()
354 .map(|r| SensorLink::new(&self.bmc, r))
355 .collect())
356 }
357
358 #[cfg(feature = "controls")]
366 pub async fn environment_power_limit_control(&self) -> Result<Option<Control<B>>, Error<B>> {
367 let Some(env_ref) = &self.data.environment_metrics else {
368 return Ok(None);
369 };
370
371 extract_environment_power_limit_control(&self.bmc, env_ref).await
372 }
373
374 #[cfg(feature = "sensors")]
383 pub async fn sensor_links(&self) -> Result<Option<Vec<SensorLink<B>>>, Error<B>> {
384 if let Some(sensors_collection) = &self.data.sensors {
385 let sc = sensors_collection
386 .get(self.bmc.as_ref())
387 .await
388 .map_err(Error::Bmc)?;
389 let mut sensor_data = Vec::with_capacity(sc.members.len());
390 for sensor in &sc.members {
391 sensor_data.push(SensorLink::new(
392 &self.bmc,
393 NavProperty::<SchemaSensor>::new_reference(sensor.id().clone()),
394 ));
395 }
396 Ok(Some(sensor_data))
397 } else {
398 Ok(None)
399 }
400 }
401
402 #[cfg(feature = "pcie-devices")]
410 pub async fn pcie_devices(&self) -> Result<Option<PcieDeviceCollection<B>>, crate::Error<B>> {
411 if let Some(p) = &self.data.pcie_devices {
412 PcieDeviceCollection::new(&self.bmc, p).await.map(Some)
413 } else {
414 Ok(None)
415 }
416 }
417
418 #[cfg(feature = "oem-nvidia-baseboard")]
426 pub fn oem_nvidia_baseboard_cbc(&self) -> Result<Option<NvidiaCbcChassis<B>>, Error<B>> {
427 self.data
428 .base
429 .base
430 .oem
431 .as_ref()
432 .map(NvidiaCbcChassis::new)
433 .transpose()
434 .map(|v| v.and_then(identity))
435 }
436}
437
438impl<B: Bmc> Resource for Chassis<B> {
439 fn resource_ref(&self) -> &ResourceSchema {
440 &self.data.as_ref().base
441 }
442}
443
444impl<B: Bmc> FromLink<B> for Chassis<B> {
445 type Schema = ChassisSchema;
446
447 fn from_link(
448 bmc: &NvBmc<B>,
449 nav: &NavProperty<Self::Schema>,
450 ) -> impl Future<Output = Result<Self, Error<B>>> + Send {
451 Self::new(bmc, nav)
452 }
453}
454
455fn remove_invalid_contained_by_fields(mut v: JsonValue) -> JsonValue {
456 if let JsonValue::Object(ref mut obj) = v {
457 if let Some(JsonValue::Object(ref mut links_obj)) = obj.get_mut("Links") {
458 if let Some(JsonValue::Object(ref mut contained_by_obj)) =
459 links_obj.get_mut("ContainedBy")
460 {
461 contained_by_obj.retain(|k, _| k == "@odata.id");
462 }
463 }
464 }
465 v
466}
467
468fn add_default_chassis_type(v: JsonValue) -> JsonValue {
469 if let JsonValue::Object(mut obj) = v {
470 obj.entry("ChassisType")
471 .or_insert(JsonValue::String("Other".into()));
472 JsonValue::Object(obj)
473 } else {
474 v
475 }
476}
477
478fn add_default_chassis_name(v: JsonValue) -> JsonValue {
479 if let JsonValue::Object(mut obj) = v {
480 obj.entry("Name")
481 .or_insert(JsonValue::String("Unnamed chassis".into()));
482 JsonValue::Object(obj)
483 } else {
484 v
485 }
486}
487
488fn normalize_empty_uuid_field(mut v: JsonValue) -> JsonValue {
489 if let JsonValue::Object(ref mut obj) = v {
490 if let Some(uuid) = obj.get_mut("UUID") {
491 let is_empty = uuid.as_str().is_some_and(str::is_empty);
492 if is_empty {
493 *uuid = JsonValue::Null;
494 }
495 }
496 }
497 v
498}