Skip to main content

nv_redfish/
service_root.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 std::sync::Arc;
17
18use crate::bmc_quirks::BmcQuirks;
19use crate::core::Bmc;
20use crate::core::NavProperty;
21use crate::core::ODataId;
22use crate::schema::service_root::ServiceRoot as SchemaServiceRoot;
23use crate::Error;
24use crate::NvBmc;
25use crate::ProtocolFeatures;
26use crate::Resource;
27use crate::ResourceSchema;
28
29use tagged_types::TaggedType;
30
31#[cfg(feature = "accounts")]
32use crate::account::AccountService;
33#[cfg(feature = "chassis")]
34use crate::chassis::ChassisCollection;
35#[cfg(feature = "chassis")]
36use crate::chassis::ChassisLink;
37#[cfg(feature = "computer-systems")]
38use crate::computer_system::SystemCollection;
39#[cfg(feature = "event-service")]
40use crate::event_service::EventService;
41#[cfg(feature = "managers")]
42use crate::manager::ManagerCollection;
43#[cfg(feature = "oem-ami")]
44use crate::oem::ami::AmiServiceRoot;
45#[cfg(feature = "oem-hpe")]
46use crate::oem::hpe::HpeiLoServiceExt;
47#[cfg(feature = "power-equipment")]
48use crate::power_equipment::PowerEquipment;
49#[cfg(feature = "session-service")]
50use crate::session_service::SessionService;
51#[cfg(feature = "task-service")]
52use crate::task_service::TaskService;
53#[cfg(feature = "telemetry-service")]
54use crate::telemetry_service::TelemetryService;
55#[cfg(feature = "update-service")]
56use crate::update_service::UpdateService;
57
58/// The vendor or manufacturer associated with Redfish service.
59pub type Vendor<T> = TaggedType<T, VendorTag>;
60#[doc(hidden)]
61#[derive(tagged_types::Tag)]
62#[implement(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
63#[transparent(Debug, Display, Serialize, Deserialize)]
64#[capability(inner_access, cloned)]
65pub enum VendorTag {}
66
67/// The product associated with Redfish service..
68pub type Product<T> = TaggedType<T, ProductTag>;
69#[doc(hidden)]
70#[derive(tagged_types::Tag)]
71#[implement(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
72#[transparent(Debug, Display, Serialize, Deserialize)]
73#[capability(inner_access, cloned)]
74pub enum ProductTag {}
75
76/// The version of Redfish schema.
77pub type RedfishVersion<'a> = TaggedType<&'a str, RedfishVersionTag>;
78#[doc(hidden)]
79#[derive(tagged_types::Tag)]
80#[implement(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
81#[transparent(Debug, Display, Serialize, Deserialize)]
82#[capability(inner_access, cloned)]
83pub enum RedfishVersionTag {}
84
85/// Represents `ServiceRoot` in the BMC model.
86pub struct ServiceRoot<B: Bmc> {
87    /// Content of the root.
88    pub root: Arc<SchemaServiceRoot>,
89    #[allow(dead_code)] // feature-enabled field
90    bmc: NvBmc<B>,
91}
92
93// Implement Clone manually to avoid requiring B: Clone; cloning only
94// needs Arc/NvBmc clones.
95impl<B: Bmc> Clone for ServiceRoot<B> {
96    fn clone(&self) -> Self {
97        Self {
98            root: self.root.clone(),
99            bmc: self.bmc.clone(),
100        }
101    }
102}
103
104impl<B: Bmc> ServiceRoot<B> {
105    /// Create a new service root.
106    ///
107    /// # Errors
108    ///
109    /// Returns error if retrieving the root path via Redfish fails.
110    pub async fn new(bmc: Arc<B>) -> Result<Self, Error<B>> {
111        let root = NavProperty::<SchemaServiceRoot>::new_reference(ODataId::service_root())
112            .get(bmc.as_ref())
113            .await
114            .map_err(Error::Bmc)?;
115        let quirks = BmcQuirks::new(&root);
116        let mut protocol_features = root
117            .protocol_features_supported
118            .as_ref()
119            .map(ProtocolFeatures::new)
120            .unwrap_or_default();
121
122        if quirks.expand_is_not_working_properly() {
123            protocol_features.expand.expand_all = false;
124            protocol_features.expand.no_links = false;
125        }
126
127        let bmc = NvBmc::new(bmc, protocol_features, quirks);
128        Ok(Self { root, bmc })
129    }
130
131    /// Replace BMC in this root.
132    #[must_use]
133    pub fn replace_bmc(self, bmc: Arc<B>) -> Self {
134        let root = self.root;
135        let bmc = self.bmc.replace_bmc(bmc);
136        Self { root, bmc }
137    }
138
139    /// Restrict usage of expand.
140    #[must_use]
141    pub fn restrict_expand(self) -> Self {
142        let root = self.root;
143        let bmc = self.bmc.restrict_expand();
144        Self { root, bmc }
145    }
146
147    /// The vendor or manufacturer associated with this Redfish service.
148    pub fn vendor(&self) -> Option<Vendor<&str>> {
149        self.root
150            .vendor
151            .as_ref()
152            .and_then(Option::as_ref)
153            .map(String::as_str)
154            .map(Vendor::new)
155    }
156
157    /// The product associated with this Redfish service.
158    pub fn product(&self) -> Option<Product<&str>> {
159        self.root
160            .product
161            .as_ref()
162            .and_then(Option::as_ref)
163            .map(String::as_str)
164            .map(Product::new)
165    }
166
167    /// The vendor or manufacturer associated with this Redfish service.
168    pub fn redfish_version(&self) -> Option<RedfishVersion<'_>> {
169        self.root
170            .redfish_version
171            .as_deref()
172            .map(RedfishVersion::new)
173    }
174
175    /// Get the account service belonging to the BMC.
176    ///
177    /// Returns `Ok(None)` when the BMC does not expose AccountService.
178    ///
179    /// # Errors
180    ///
181    /// Returns error if retrieving account service data fails.
182    #[cfg(feature = "accounts")]
183    pub async fn account_service(&self) -> Result<Option<AccountService<B>>, Error<B>> {
184        AccountService::new(&self.bmc, self).await
185    }
186
187    /// Get chassis collection in BMC
188    ///
189    /// Returns `Ok(None)` when the BMC does not expose Chassis.
190    ///
191    /// # Errors
192    ///
193    /// Returns error if retrieving chassis collection data fails.
194    #[cfg(feature = "chassis")]
195    pub async fn chassis(&self) -> Result<Option<ChassisCollection<B>>, Error<B>> {
196        ChassisCollection::new(&self.bmc, self).await
197    }
198
199    /// Get chassis links
200    ///
201    /// Returns `Ok(None)` when the BMC does not expose Chassis.
202    ///
203    /// # Errors
204    ///
205    /// Returns error if retrieving chassis collection data fails.
206    #[cfg(feature = "chassis")]
207    pub async fn chassis_links(&self) -> Result<Option<Vec<ChassisLink<B>>>, Error<B>> {
208        if let Some(collection) = &self.root.chassis {
209            let collection = collection
210                .get(self.bmc.as_ref())
211                .await
212                .map_err(Error::Bmc)?;
213            Ok(Some(
214                collection
215                    .members
216                    .iter()
217                    .map(|member| {
218                        ChassisLink::new(&self.bmc, NavProperty::new_reference(member.id().clone()))
219                    })
220                    .collect(),
221            ))
222        } else {
223            Ok(None)
224        }
225    }
226
227    /// Get computer system collection in BMC
228    ///
229    /// Returns `Ok(None)` when the BMC does not expose Systems.
230    ///
231    /// # Errors
232    ///
233    /// Returns error if retrieving system collection data fails.
234    #[cfg(feature = "computer-systems")]
235    pub async fn systems(&self) -> Result<Option<SystemCollection<B>>, Error<B>> {
236        SystemCollection::new(&self.bmc, self).await
237    }
238
239    /// Get update service in BMC
240    ///
241    /// Returns `Ok(None)` when the BMC does not expose UpdateService.
242    ///
243    /// # Errors
244    ///
245    /// Returns error if retrieving update service data fails.
246    #[cfg(feature = "update-service")]
247    pub async fn update_service(&self) -> Result<Option<UpdateService<B>>, Error<B>> {
248        UpdateService::new(&self.bmc, self).await
249    }
250
251    /// Get task service in BMC
252    ///
253    /// Returns `Ok(None)` when the BMC does not expose TaskService.
254    ///
255    /// # Errors
256    ///
257    /// Returns error if retrieving task service data fails.
258    #[cfg(feature = "task-service")]
259    pub async fn task_service(&self) -> Result<Option<TaskService<B>>, Error<B>> {
260        TaskService::new(&self.bmc, self).await
261    }
262
263    /// Get event service in BMC
264    ///
265    /// Returns `Ok(None)` when the BMC does not expose EventService.
266    ///
267    /// # Errors
268    ///
269    /// Returns error if retrieving event service data fails.
270    #[cfg(feature = "event-service")]
271    pub async fn event_service(&self) -> Result<Option<EventService<B>>, Error<B>> {
272        EventService::new(&self.bmc, self).await
273    }
274
275    /// Get telemetry service in BMC
276    ///
277    /// Returns `Ok(None)` when the BMC does not expose TelemetryService.
278    ///
279    /// # Errors
280    ///
281    /// Returns error if retrieving telemetry service data fails.
282    #[cfg(feature = "telemetry-service")]
283    pub async fn telemetry_service(&self) -> Result<Option<TelemetryService<B>>, Error<B>> {
284        TelemetryService::new(&self.bmc, self).await
285    }
286
287    /// Get session service in BMC
288    ///
289    /// Returns `Ok(None)` when the BMC does not expose SessionService.
290    ///
291    /// # Errors
292    ///
293    /// Returns error if retrieving session service data fails.
294    #[cfg(feature = "session-service")]
295    pub async fn session_service(&self) -> Result<Option<SessionService<B>>, Error<B>> {
296        SessionService::new(&self.bmc, self).await
297    }
298
299    /// Get manager collection in BMC
300    ///
301    /// Returns `Ok(None)` when the BMC does not expose Managers.
302    ///
303    /// # Errors
304    ///
305    /// Returns error if retrieving manager collection data fails.
306    #[cfg(feature = "managers")]
307    pub async fn managers(&self) -> Result<Option<ManagerCollection<B>>, Error<B>> {
308        ManagerCollection::new(&self.bmc, self).await
309    }
310
311    /// Get power equipment in BMC
312    ///
313    /// Returns `Ok(None)` when the BMC does not expose PowerEquipment.
314    ///
315    /// # Errors
316    ///
317    /// Returns error if retrieving power equipment data fails.
318    #[cfg(feature = "power-equipment")]
319    pub async fn power_equipment(&self) -> Result<Option<PowerEquipment<B>>, Error<B>> {
320        PowerEquipment::new(&self.bmc, self).await
321    }
322
323    /// Get HPE OEM extension in service root
324    ///
325    /// Returns `Ok(None)` when the BMC does not expose HPE extension.
326    ///
327    /// # Errors
328    ///
329    /// Returns error if retrieving manager collection data fails.
330    #[cfg(feature = "oem-hpe")]
331    pub fn oem_hpe_ilo_service_ext(&self) -> Result<Option<HpeiLoServiceExt<B>>, Error<B>> {
332        HpeiLoServiceExt::new(&self.root)
333    }
334
335    /// Get AMI OEM extension in service root
336    ///
337    /// Returns `Ok(None)` when the BMC does not expose AMI extension.
338    ///
339    /// # Errors
340    ///
341    /// Returns error if retrieving service root data fails.
342    #[cfg(feature = "oem-ami")]
343    pub fn oem_ami_service_root(&self) -> Result<Option<AmiServiceRoot<B>>, Error<B>> {
344        AmiServiceRoot::new(&self.bmc, &self.root)
345    }
346}
347
348impl<B: Bmc> Resource for ServiceRoot<B> {
349    fn resource_ref(&self) -> &ResourceSchema {
350        &self.root.as_ref().base
351    }
352}