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