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 = "log-services")]
49use crate::log_service::LogService;
50#[cfg(all(feature = "oem-liteon", feature = "power-supplies"))]
51use crate::oem::liteon;
52#[cfg(feature = "oem-nvidia-baseboard")]
53use crate::oem::nvidia::baseboard::NvidiaCbcChassis;
54#[cfg(feature = "pcie-devices")]
55use crate::pcie_device::PcieDeviceCollection;
56#[cfg(feature = "sensors")]
57use crate::schema::sensor::Sensor as SchemaSensor;
58#[cfg(feature = "sensors")]
59use crate::sensor::extract_environment_sensors;
60#[cfg(feature = "sensors")]
61use crate::sensor::SensorLink;
62#[cfg(feature = "oem-nvidia-baseboard")]
63use std::convert::identity;
64
65#[doc(hidden)]
66pub enum ChassisTag {}
67
68pub type Manufacturer<T> = HardwareIdManufacturer<T, ChassisTag>;
70
71pub type Model<T> = HardwareIdModel<T, ChassisTag>;
73
74pub type PartNumber<T> = HardwareIdPartNumber<T, ChassisTag>;
76
77pub type SerialNumber<T> = HardwareIdSerialNumber<T, ChassisTag>;
79
80pub struct Config {
81 pub read_patch_fn: Option<ReadPatchFn>,
82}
83
84impl Config {
85 pub fn new(quirks: &BmcQuirks) -> Self {
86 let mut patches = Vec::new();
87 if quirks.bug_invalid_contained_by_fields() {
88 patches.push(remove_invalid_contained_by_fields as fn(JsonValue) -> JsonValue);
89 }
90 if quirks.bug_missing_chassis_type_field() {
91 patches.push(add_default_chassis_type);
92 }
93 if quirks.bug_missing_chassis_name_field() {
94 patches.push(add_default_chassis_name);
95 }
96 if quirks.bug_empty_chassis_uuid_field() {
97 patches.push(normalize_empty_uuid_field);
98 }
99 let read_patch_fn = (!patches.is_empty())
100 .then(|| Arc::new(move |v| patches.iter().fold(v, |acc, f| f(acc))) as ReadPatchFn);
101 Self { read_patch_fn }
102 }
103}
104
105pub struct Chassis<B: Bmc> {
109 #[allow(dead_code)] bmc: NvBmc<B>,
111 data: Arc<ChassisSchema>,
112 #[allow(dead_code)] config: Arc<Config>,
114}
115
116impl<B: Bmc> Chassis<B> {
117 pub(crate) async fn new(
119 bmc: &NvBmc<B>,
120 nav: &NavProperty<ChassisSchema>,
121 ) -> Result<Self, Error<B>> {
122 let config = Config::new(&bmc.quirks);
123 if let Some(read_patch_fn) = &config.read_patch_fn {
124 Payload::get(bmc.as_ref(), nav, read_patch_fn.as_ref()).await
125 } else {
126 nav.get(bmc.as_ref()).await.map_err(Error::Bmc)
127 }
128 .map(|data| Self {
129 bmc: bmc.clone(),
130 data,
131 config: config.into(),
132 })
133 }
134
135 #[must_use]
140 pub fn raw(&self) -> Arc<ChassisSchema> {
141 self.data.clone()
142 }
143
144 #[must_use]
146 pub fn hardware_id(&self) -> HardwareIdRef<'_, ChassisTag> {
147 HardwareIdRef {
148 manufacturer: self
149 .data
150 .manufacturer
151 .as_ref()
152 .and_then(Option::as_deref)
153 .map(Manufacturer::new),
154 model: self
155 .data
156 .model
157 .as_ref()
158 .and_then(Option::as_deref)
159 .map(Model::new),
160 part_number: self
161 .data
162 .part_number
163 .as_ref()
164 .and_then(Option::as_deref)
165 .map(PartNumber::new),
166 serial_number: self
167 .data
168 .serial_number
169 .as_ref()
170 .and_then(Option::as_deref)
171 .map(SerialNumber::new),
172 }
173 }
174
175 #[cfg(feature = "assembly")]
183 pub async fn assembly(&self) -> Result<Option<Assembly<B>>, Error<B>> {
184 if let Some(assembly_ref) = &self.data.assembly {
185 Assembly::new(&self.bmc, assembly_ref).await.map(Some)
186 } else {
187 Ok(None)
188 }
189 }
190
191 #[cfg(feature = "power-supplies")]
200 pub async fn power_supplies(&self) -> Result<Vec<PowerSupply<B>>, Error<B>> {
201 if let Some(ps) = &self.data.power_subsystem {
202 let ps = ps.get(self.bmc.as_ref()).await.map_err(Error::Bmc)?;
203 if let Some(supplies) = &ps.power_supplies {
204 let supplies = &self.bmc.expand_property(supplies).await?.members;
205 let mut power_supplies = Vec::with_capacity(supplies.len());
206 for power_supply in supplies {
207 power_supplies.push(PowerSupply::new(&self.bmc, power_supply).await?);
208 }
209 return Ok(power_supplies);
210 }
211 }
212
213 Ok(Vec::new())
214 }
215
216 #[cfg(all(feature = "oem-liteon", feature = "power-supplies"))]
222 pub async fn oem_liteon_power_supply_links(
223 &self,
224 ) -> Result<Option<Vec<liteon::power_supply::LiteonPowerSupplyLink<B>>>, Error<B>> {
225 liteon::power_supply::chassis_fetch_links(&self.bmc, self).await
226 }
227
228 #[cfg(feature = "power")]
238 pub async fn power(&self) -> Result<Option<Power<B>>, Error<B>> {
239 if let Some(power_ref) = &self.data.power {
240 Ok(Some(Power::new(&self.bmc, power_ref).await?))
241 } else {
242 Ok(None)
243 }
244 }
245
246 #[cfg(feature = "thermal")]
256 pub async fn thermal(&self) -> Result<Option<Thermal<B>>, Error<B>> {
257 if let Some(thermal_ref) = &self.data.thermal {
258 Thermal::new(&self.bmc, thermal_ref).await.map(Some)
259 } else {
260 Ok(None)
261 }
262 }
263
264 #[cfg(feature = "network-adapters")]
273 pub async fn network_adapters(&self) -> Result<Option<Vec<NetworkAdapter<B>>>, Error<B>> {
274 if let Some(network_adapters_collection_ref) = &self.data.network_adapters {
275 NetworkAdapterCollection::new(&self.bmc, network_adapters_collection_ref)
276 .await?
277 .members()
278 .await
279 .map(Some)
280 } else {
281 Ok(None)
282 }
283 }
284
285 #[cfg(feature = "log-services")]
293 pub async fn log_services(&self) -> Result<Option<Vec<LogService<B>>>, Error<B>> {
294 if let Some(log_services_ref) = &self.data.log_services {
295 let log_services_collection = log_services_ref
296 .get(self.bmc.as_ref())
297 .await
298 .map_err(Error::Bmc)?;
299
300 let mut log_services = Vec::new();
301 for m in &log_services_collection.members {
302 log_services.push(LogService::new(&self.bmc, m).await?);
303 }
304
305 Ok(Some(log_services))
306 } else {
307 Ok(None)
308 }
309 }
310
311 #[cfg(feature = "sensors")]
319 pub async fn environment_sensor_links(&self) -> Result<Vec<SensorLink<B>>, Error<B>> {
320 let sensor_refs = if let Some(env_ref) = &self.data.environment_metrics {
321 extract_environment_sensors(env_ref, self.bmc.as_ref()).await?
322 } else {
323 Vec::new()
324 };
325
326 Ok(sensor_refs
327 .into_iter()
328 .map(|r| SensorLink::new(&self.bmc, r))
329 .collect())
330 }
331
332 #[cfg(feature = "sensors")]
341 pub async fn sensor_links(&self) -> Result<Option<Vec<SensorLink<B>>>, Error<B>> {
342 if let Some(sensors_collection) = &self.data.sensors {
343 let sc = sensors_collection
344 .get(self.bmc.as_ref())
345 .await
346 .map_err(Error::Bmc)?;
347 let mut sensor_data = Vec::with_capacity(sc.members.len());
348 for sensor in &sc.members {
349 sensor_data.push(SensorLink::new(
350 &self.bmc,
351 NavProperty::<SchemaSensor>::new_reference(sensor.id().clone()),
352 ));
353 }
354 Ok(Some(sensor_data))
355 } else {
356 Ok(None)
357 }
358 }
359
360 #[cfg(feature = "pcie-devices")]
368 pub async fn pcie_devices(&self) -> Result<Option<PcieDeviceCollection<B>>, crate::Error<B>> {
369 if let Some(p) = &self.data.pcie_devices {
370 PcieDeviceCollection::new(&self.bmc, p).await.map(Some)
371 } else {
372 Ok(None)
373 }
374 }
375
376 #[cfg(feature = "oem-nvidia-baseboard")]
384 pub fn oem_nvidia_baseboard_cbc(&self) -> Result<Option<NvidiaCbcChassis<B>>, Error<B>> {
385 self.data
386 .base
387 .base
388 .oem
389 .as_ref()
390 .map(NvidiaCbcChassis::new)
391 .transpose()
392 .map(|v| v.and_then(identity))
393 }
394}
395
396impl<B: Bmc> Resource for Chassis<B> {
397 fn resource_ref(&self) -> &ResourceSchema {
398 &self.data.as_ref().base
399 }
400}
401
402impl<B: Bmc> FromLink<B> for Chassis<B> {
403 type Schema = ChassisSchema;
404
405 fn from_link(
406 bmc: &NvBmc<B>,
407 nav: &NavProperty<Self::Schema>,
408 ) -> impl Future<Output = Result<Self, Error<B>>> + Send {
409 Self::new(bmc, nav)
410 }
411}
412
413fn remove_invalid_contained_by_fields(mut v: JsonValue) -> JsonValue {
414 if let JsonValue::Object(ref mut obj) = v {
415 if let Some(JsonValue::Object(ref mut links_obj)) = obj.get_mut("Links") {
416 if let Some(JsonValue::Object(ref mut contained_by_obj)) =
417 links_obj.get_mut("ContainedBy")
418 {
419 contained_by_obj.retain(|k, _| k == "@odata.id");
420 }
421 }
422 }
423 v
424}
425
426fn add_default_chassis_type(v: JsonValue) -> JsonValue {
427 if let JsonValue::Object(mut obj) = v {
428 obj.entry("ChassisType")
429 .or_insert(JsonValue::String("Other".into()));
430 JsonValue::Object(obj)
431 } else {
432 v
433 }
434}
435
436fn add_default_chassis_name(v: JsonValue) -> JsonValue {
437 if let JsonValue::Object(mut obj) = v {
438 obj.entry("Name")
439 .or_insert(JsonValue::String("Unnamed chassis".into()));
440 JsonValue::Object(obj)
441 } else {
442 v
443 }
444}
445
446fn normalize_empty_uuid_field(mut v: JsonValue) -> JsonValue {
447 if let JsonValue::Object(ref mut obj) = v {
448 if let Some(uuid) = obj.get_mut("UUID") {
449 let is_empty = uuid.as_str().is_some_and(str::is_empty);
450 if is_empty {
451 *uuid = JsonValue::Null;
452 }
453 }
454 }
455 v
456}