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