Skip to main content

nv_redfish/chassis/
item.rs

1// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::hardware_id::HardwareIdRef;
17use crate::hardware_id::Manufacturer as HardwareIdManufacturer;
18use crate::hardware_id::Model as HardwareIdModel;
19use crate::hardware_id::PartNumber as HardwareIdPartNumber;
20use crate::hardware_id::SerialNumber as HardwareIdSerialNumber;
21use crate::patch_support::JsonValue;
22use crate::patch_support::Payload;
23use crate::patch_support::ReadPatchFn;
24use crate::schema::redfish::chassis::Chassis as ChassisSchema;
25use crate::Error;
26use crate::NvBmc;
27use crate::Resource;
28use crate::ResourceSchema;
29use crate::ServiceRoot;
30use nv_redfish_core::bmc::Bmc;
31use nv_redfish_core::NavProperty;
32use std::sync::Arc;
33
34#[cfg(feature = "assembly")]
35use crate::assembly::Assembly;
36#[cfg(feature = "network-adapters")]
37use crate::chassis::NetworkAdapter;
38#[cfg(feature = "network-adapters")]
39use crate::chassis::NetworkAdapterCollection;
40#[cfg(feature = "power")]
41use crate::chassis::Power;
42#[cfg(feature = "power-supplies")]
43use crate::chassis::PowerSupply;
44#[cfg(feature = "thermal")]
45use crate::chassis::Thermal;
46#[cfg(feature = "log-services")]
47use crate::log_service::LogService;
48#[cfg(feature = "oem-nvidia-baseboard")]
49use crate::oem::nvidia::baseboard::NvidiaCbcChassis;
50#[cfg(feature = "pcie-devices")]
51use crate::pcie_device::PcieDeviceCollection;
52#[cfg(feature = "sensors")]
53use crate::schema::redfish::sensor::Sensor as SchemaSensor;
54#[cfg(feature = "sensors")]
55use crate::sensor::extract_environment_sensors;
56#[cfg(feature = "sensors")]
57use crate::sensor::SensorRef;
58
59#[doc(hidden)]
60pub enum ChassisTag {}
61
62/// Chassis manufacturer.
63pub type Manufacturer<T> = HardwareIdManufacturer<T, ChassisTag>;
64
65/// Chassis model.
66pub type Model<T> = HardwareIdModel<T, ChassisTag>;
67
68/// Chassis part number.
69pub type PartNumber<T> = HardwareIdPartNumber<T, ChassisTag>;
70
71/// Chassis serial number.
72pub type SerialNumber<T> = HardwareIdSerialNumber<T, ChassisTag>;
73
74pub struct Config {
75    read_patch_fn: Option<ReadPatchFn>,
76}
77
78impl Config {
79    pub fn new<B: Bmc>(root: &ServiceRoot<B>) -> Self {
80        let mut patches = Vec::new();
81        if root.bug_invalid_contained_by_fields() {
82            patches.push(remove_invalid_contained_by_fields);
83        }
84        let read_patch_fn = if patches.is_empty() {
85            None
86        } else {
87            let read_patch_fn: ReadPatchFn =
88                Arc::new(move |v| patches.iter().fold(v, |acc, f| f(acc)));
89            Some(read_patch_fn)
90        };
91        Self { read_patch_fn }
92    }
93}
94
95/// Represents a chassis in the BMC.
96///
97/// Provides access to chassis information and sub-resources such as power supplies.
98pub struct Chassis<B: Bmc> {
99    #[allow(dead_code)] // used if any feature enabled.
100    bmc: NvBmc<B>,
101    data: Arc<ChassisSchema>,
102    #[allow(dead_code)] // used when assembly feature enabled.
103    config: Arc<Config>,
104}
105
106impl<B: Bmc> Chassis<B> {
107    /// Create a new chassis handle.
108    pub(crate) async fn new(
109        bmc: &NvBmc<B>,
110        nav: &NavProperty<ChassisSchema>,
111        config: Arc<Config>,
112    ) -> Result<Self, Error<B>> {
113        if let Some(read_patch_fn) = &config.read_patch_fn {
114            Payload::get(bmc.as_ref(), nav, read_patch_fn.as_ref()).await
115        } else {
116            nav.get(bmc.as_ref()).await.map_err(Error::Bmc)
117        }
118        .map(|data| Self {
119            bmc: bmc.clone(),
120            data,
121            config,
122        })
123    }
124
125    /// Get the raw schema data for this chassis.
126    ///
127    /// Returns an `Arc` to the underlying schema, allowing cheap cloning
128    /// and sharing of the data.
129    #[must_use]
130    pub fn raw(&self) -> Arc<ChassisSchema> {
131        self.data.clone()
132    }
133
134    /// Get hardware identifier of the network adpater.
135    #[must_use]
136    pub fn hardware_id(&self) -> HardwareIdRef<'_, ChassisTag> {
137        HardwareIdRef {
138            manufacturer: self
139                .data
140                .manufacturer
141                .as_ref()
142                .and_then(Option::as_ref)
143                .map(Manufacturer::new),
144            model: self
145                .data
146                .model
147                .as_ref()
148                .and_then(Option::as_ref)
149                .map(Model::new),
150            part_number: self
151                .data
152                .part_number
153                .as_ref()
154                .and_then(Option::as_ref)
155                .map(PartNumber::new),
156            serial_number: self
157                .data
158                .serial_number
159                .as_ref()
160                .and_then(Option::as_ref)
161                .map(SerialNumber::new),
162        }
163    }
164
165    /// Get assembly of this chassis
166    ///
167    /// # Errors
168    ///
169    /// Returns an error if fetching assembly data fails.
170    #[cfg(feature = "assembly")]
171    pub async fn assembly(&self) -> Result<Assembly<B>, Error<B>> {
172        let assembly_ref = self
173            .data
174            .assembly
175            .as_ref()
176            .ok_or(Error::AssemblyNotAvailable)?;
177        Assembly::new(&self.bmc, assembly_ref).await
178    }
179
180    /// Get power supplies from this chassis.
181    ///
182    /// Attempts to fetch power supplies from `PowerSubsystem` (modern API)
183    /// with fallback to Power resource (deprecated API).
184    ///
185    /// # Errors
186    ///
187    /// Returns an error if fetching power supply data fails.
188    #[cfg(feature = "power-supplies")]
189    pub async fn power_supplies(&self) -> Result<Vec<PowerSupply<B>>, Error<B>> {
190        if let Some(ps) = &self.data.power_subsystem {
191            let ps = ps.get(self.bmc.as_ref()).await.map_err(Error::Bmc)?;
192            if let Some(supplies) = &ps.power_supplies {
193                let supplies = &self.bmc.expand_property(supplies).await?.members;
194                let mut power_supplies = Vec::with_capacity(supplies.len());
195                for power_supply in supplies {
196                    power_supplies.push(PowerSupply::new(&self.bmc, power_supply).await?);
197                }
198                return Ok(power_supplies);
199            }
200        }
201
202        Ok(Vec::new())
203    }
204
205    /// Get legacy Power resource (for older BMCs).
206    ///
207    /// Returns the deprecated `Chassis/Power` resource if available.
208    /// For modern BMCs, prefer using direct sensor links via `HasSensors`
209    /// or the modern `PowerSubsystem` API.
210    ///
211    /// # Errors
212    ///
213    /// Returns an error if fetching power data fails.
214    #[cfg(feature = "power")]
215    pub async fn power(&self) -> Result<Option<Power<B>>, Error<B>> {
216        if let Some(power_ref) = &self.data.power {
217            Ok(Some(Power::new(&self.bmc, power_ref).await?))
218        } else {
219            Ok(None)
220        }
221    }
222
223    /// Get legacy Thermal resource (for older BMCs).
224    ///
225    /// Returns the deprecated `Chassis/Thermal` resource if available.
226    /// For modern BMCs, prefer using direct sensor links via `HasSensors`
227    /// or the modern `ThermalSubsystem` API.
228    ///
229    /// # Errors
230    ///
231    /// Returns an error if fetching thermal data fails.
232    #[cfg(feature = "thermal")]
233    pub async fn thermal(&self) -> Result<Option<Thermal<B>>, Error<B>> {
234        if let Some(thermal_ref) = &self.data.thermal {
235            Thermal::new(&self.bmc, thermal_ref).await.map(Some)
236        } else {
237            Ok(None)
238        }
239    }
240
241    /// Get network adapter resources
242    ///
243    /// Returns the `Chassis/NetworkAdapter` resources if available.
244    ///
245    /// # Errors
246    ///
247    /// Returns an error if fetching network adapters data fails.
248    #[cfg(feature = "network-adapters")]
249    pub async fn network_adapters(&self) -> Result<Vec<NetworkAdapter<B>>, Error<B>> {
250        let network_adapters_collection_ref = &self
251            .data
252            .network_adapters
253            .as_ref()
254            .ok_or(Error::NetworkAdaptersNotAvailable)?;
255        NetworkAdapterCollection::new(&self.bmc, network_adapters_collection_ref)
256            .await?
257            .members()
258            .await
259    }
260
261    /// Get log services for this chassis.
262    ///
263    /// # Errors
264    ///
265    /// Returns an error if:
266    /// - The chassis does not have log services
267    /// - Fetching log service data fails
268    #[cfg(feature = "log-services")]
269    pub async fn log_services(&self) -> Result<Vec<LogService<B>>, Error<B>> {
270        let log_services_ref = self
271            .data
272            .log_services
273            .as_ref()
274            .ok_or(Error::LogServiceNotAvailable)?;
275
276        let log_services_collection = log_services_ref
277            .get(self.bmc.as_ref())
278            .await
279            .map_err(Error::Bmc)?;
280
281        let mut log_services = Vec::new();
282        for m in &log_services_collection.members {
283            log_services.push(LogService::new(&self.bmc, m).await?);
284        }
285
286        Ok(log_services)
287    }
288
289    /// Get the environment sensors for this chassis.
290    ///
291    /// Returns a vector of `Sensor<B>` obtained from environment metrics, if available.
292    ///
293    /// # Errors
294    ///
295    /// Returns an error if get of environment metrics failed.
296    #[cfg(feature = "sensors")]
297    pub async fn environment_sensors(&self) -> Result<Vec<SensorRef<B>>, Error<B>> {
298        let sensor_refs = if let Some(env_ref) = &self.data.environment_metrics {
299            extract_environment_sensors(env_ref, self.bmc.as_ref()).await?
300        } else {
301            Vec::new()
302        };
303
304        Ok(sensor_refs
305            .into_iter()
306            .map(|r| SensorRef::new(self.bmc.clone(), r))
307            .collect())
308    }
309
310    /// Get the sensors collection for this chassis.
311    ///
312    /// Returns all available sensors associated with the chassis.
313    ///
314    /// # Errors
315    ///
316    /// Returns an error if:
317    /// - The chassis does not have sensors
318    /// - Fetching sensors data fails
319    #[cfg(feature = "sensors")]
320    pub async fn sensors(&self) -> Result<Vec<SensorRef<B>>, Error<B>> {
321        if let Some(sensors_collection) = &self.data.sensors {
322            let sc = sensors_collection
323                .get(self.bmc.as_ref())
324                .await
325                .map_err(Error::Bmc)?;
326            let mut sensor_data = Vec::with_capacity(sc.members.len());
327            for sensor in &sc.members {
328                sensor_data.push(SensorRef::new(
329                    self.bmc.clone(),
330                    NavProperty::<SchemaSensor>::new_reference(sensor.id().clone()),
331                ));
332            }
333            Ok(sensor_data)
334        } else {
335            Err(Error::SensorsNotAvailable)
336        }
337    }
338
339    /// Get `PCIe` devices for this computer system.
340    ///
341    /// # Errors
342    ///
343    /// Returns an error if:
344    /// - The systems does not have / provide pcie devices
345    /// - Fetching pcie devices data fails
346    #[cfg(feature = "pcie-devices")]
347    pub async fn pcie_devices(&self) -> Result<PcieDeviceCollection<B>, crate::Error<B>> {
348        let p = self
349            .data
350            .pcie_devices
351            .as_ref()
352            .ok_or(crate::Error::PcieDevicesNotAvailable)?;
353        PcieDeviceCollection::new(&self.bmc, p).await
354    }
355
356    /// NVIDIA Bluefield OEM extension
357    ///
358    /// # Errors
359    ///
360    /// Returns an error if:
361    /// - `Error::NvidiaChassisNotAvailable` if the systems does not have / provide NVIDIA OEM extension
362    /// - Fetching data fails
363    #[cfg(feature = "oem-nvidia-baseboard")]
364    pub fn oem_nvidia_baseboard_cbc(&self) -> Result<NvidiaCbcChassis<B>, Error<B>> {
365        self.data
366            .base
367            .base
368            .oem
369            .as_ref()
370            .ok_or(Error::NvidiaCbcChassisNotAvailable)
371            .and_then(NvidiaCbcChassis::new)
372    }
373}
374
375impl<B: Bmc> Resource for Chassis<B> {
376    fn resource_ref(&self) -> &ResourceSchema {
377        &self.data.as_ref().base
378    }
379}
380
381fn remove_invalid_contained_by_fields(mut v: JsonValue) -> JsonValue {
382    if let JsonValue::Object(ref mut obj) = v {
383        if let Some(JsonValue::Object(ref mut links_obj)) = obj.get_mut("Links") {
384            if let Some(JsonValue::Object(ref mut contained_by_obj)) =
385                links_obj.get_mut("ContainedBy")
386            {
387                contained_by_obj.retain(|k, _| k == "@odata.id");
388            }
389        }
390    }
391    v
392}