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 nv_redfish_core::{Bmc, NavProperty, ODataId};
19use tagged_types::TaggedType;
20
21#[cfg(feature = "accounts")]
22use crate::account::AccountService;
23#[cfg(feature = "accounts")]
24use crate::account::SlotDefinedConfig as SlotDefinedUserAccountsConfig;
25#[cfg(feature = "chassis")]
26use crate::chassis::ChassisCollection;
27#[cfg(feature = "computer-systems")]
28use crate::computer_system::SystemCollection;
29#[cfg(feature = "event-service")]
30use crate::event_service::EventService;
31#[cfg(feature = "managers")]
32use crate::manager::ManagerCollection;
33use crate::schema::redfish::service_root::ServiceRoot as SchemaServiceRoot;
34#[cfg(feature = "telemetry-service")]
35use crate::telemetry_service::TelemetryService;
36#[cfg(feature = "update-service")]
37use crate::update_service::UpdateService;
38use crate::{Error, NvBmc, ProtocolFeatures, Resource, ResourceSchema};
39
40/// The vendor or manufacturer associated with Redfish service.
41pub type Vendor<T> = TaggedType<T, VendorTag>;
42#[doc(hidden)]
43#[derive(tagged_types::Tag)]
44#[implement(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
45#[transparent(Debug, Display, Serialize, Deserialize)]
46#[capability(inner_access, cloned)]
47pub enum VendorTag {}
48
49/// The product associated with Redfish service..
50pub type Product<T> = TaggedType<T, ProductTag>;
51#[doc(hidden)]
52#[derive(tagged_types::Tag)]
53#[implement(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
54#[transparent(Debug, Display, Serialize, Deserialize)]
55#[capability(inner_access, cloned)]
56pub enum ProductTag {}
57
58/// Represents `ServiceRoot` in the BMC model.
59#[derive(Clone)]
60pub struct ServiceRoot<B: Bmc> {
61    /// Content of the root.
62    pub root: Arc<SchemaServiceRoot>,
63    #[allow(dead_code)] // feature-enabled field
64    bmc: NvBmc<B>,
65}
66
67impl<B: Bmc> ServiceRoot<B> {
68    /// Create a new service root.
69    ///
70    /// # Errors
71    ///
72    /// Returns error if retrieving the root path via Redfish fails.
73    pub async fn new(bmc: Arc<B>) -> Result<Self, Error<B>> {
74        let root = NavProperty::<SchemaServiceRoot>::new_reference(ODataId::service_root())
75            .get(bmc.as_ref())
76            .await
77            .map_err(Error::Bmc)?;
78        let mut protocol_features = root
79            .protocol_features_supported
80            .as_ref()
81            .map(ProtocolFeatures::new)
82            .unwrap_or_default();
83
84        if Self::expand_is_not_working_properly(&root) {
85            protocol_features.expand.expand_all = false;
86            protocol_features.expand.no_links = false;
87        }
88
89        let bmc = NvBmc::new(bmc, protocol_features);
90        Ok(Self { root, bmc })
91    }
92
93    /// The vendor or manufacturer associated with this Redfish service.
94    pub fn vendor(&self) -> Option<Vendor<&str>> {
95        self.root
96            .vendor
97            .as_ref()
98            .and_then(Option::as_ref)
99            .map(String::as_str)
100            .map(Vendor::new)
101    }
102
103    /// The product associated with this Redfish service.
104    pub fn product(&self) -> Option<Product<&str>> {
105        self.root
106            .product
107            .as_ref()
108            .and_then(Option::as_ref)
109            .map(String::as_str)
110            .map(Product::new)
111    }
112
113    /// Get the account service belonging to the BMC.
114    ///
115    /// Returns `Ok(None)` when the BMC does not expose AccountService.
116    ///
117    /// # Errors
118    ///
119    /// Returns error if retrieving account service data fails.
120    #[cfg(feature = "accounts")]
121    pub async fn account_service(&self) -> Result<Option<AccountService<B>>, Error<B>> {
122        AccountService::new(&self.bmc, self).await
123    }
124
125    /// Get chassis collection in BMC
126    ///
127    /// Returns `Ok(None)` when the BMC does not expose Chassis.
128    ///
129    /// # Errors
130    ///
131    /// Returns error if retrieving chassis collection data fails.
132    #[cfg(feature = "chassis")]
133    pub async fn chassis(&self) -> Result<Option<ChassisCollection<B>>, Error<B>> {
134        ChassisCollection::new(&self.bmc, self).await
135    }
136
137    /// Get computer system collection in BMC
138    ///
139    /// Returns `Ok(None)` when the BMC does not expose Systems.
140    ///
141    /// # Errors
142    ///
143    /// Returns error if retrieving system collection data fails.
144    #[cfg(feature = "computer-systems")]
145    pub async fn systems(&self) -> Result<Option<SystemCollection<B>>, Error<B>> {
146        SystemCollection::new(&self.bmc, self).await
147    }
148
149    /// Get update service in BMC
150    ///
151    /// Returns `Ok(None)` when the BMC does not expose UpdateService.
152    ///
153    /// # Errors
154    ///
155    /// Returns error if retrieving update service data fails.
156    #[cfg(feature = "update-service")]
157    pub async fn update_service(&self) -> Result<Option<UpdateService<B>>, Error<B>> {
158        UpdateService::new(&self.bmc, self).await
159    }
160
161    /// Get event service in BMC
162    ///
163    /// Returns `Ok(None)` when the BMC does not expose EventService.
164    ///
165    /// # Errors
166    ///
167    /// Returns error if retrieving event service data fails.
168    #[cfg(feature = "event-service")]
169    pub async fn event_service(&self) -> Result<Option<EventService<B>>, Error<B>> {
170        EventService::new(&self.bmc, self).await
171    }
172
173    /// Get telemetry service in BMC
174    ///
175    /// Returns `Ok(None)` when the BMC does not expose TelemetryService.
176    ///
177    /// # Errors
178    ///
179    /// Returns error if retrieving telemetry service data fails.
180    #[cfg(feature = "telemetry-service")]
181    pub async fn telemetry_service(&self) -> Result<Option<TelemetryService<B>>, Error<B>> {
182        TelemetryService::new(&self.bmc, self).await
183    }
184
185    /// Get manager collection in BMC
186    ///
187    /// Returns `Ok(None)` when the BMC does not expose Managers.
188    ///
189    /// # Errors
190    ///
191    /// Returns error if retrieving manager collection data fails.
192    #[cfg(feature = "managers")]
193    pub async fn managers(&self) -> Result<Option<ManagerCollection<B>>, Error<B>> {
194        ManagerCollection::new(&self.bmc, self).await
195    }
196}
197
198// Known Redfish implementation bug checks.
199impl<B: Bmc> ServiceRoot<B> {
200    // Account type is required according to schema specification
201    // (marked with Redfish.Required annotation) but some vendors
202    // ignores this flag. A workaround for this bug is supported by
203    // `nv-redfish`.
204    #[cfg(feature = "accounts")]
205    pub(crate) fn bug_no_account_type_in_accounts(&self) -> bool {
206        self.root
207            .vendor
208            .as_ref()
209            .and_then(Option::as_ref)
210            .is_some_and(|v| v == "HPE")
211    }
212
213    // In some implementations BMC cannot create / delete Redfish
214    // accounts but have pre-created accounts (slots). Workflow is as
215    // following: to "create" new account user should update
216    // precreated account with new parameters and enable it. To delete
217    // account user should just disable it.
218    #[cfg(feature = "accounts")]
219    pub(crate) fn slot_defined_user_accounts(&self) -> Option<SlotDefinedUserAccountsConfig> {
220        if self
221            .root
222            .vendor
223            .as_ref()
224            .and_then(Option::as_ref)
225            .is_some_and(|v| v == "Dell")
226        {
227            Some(SlotDefinedUserAccountsConfig {
228                min_slot: Some(3),
229                hide_disabled: true,
230                disable_account_on_delete: true,
231            })
232        } else {
233            None
234        }
235    }
236
237    // In some implementations BMC ReleaseDate is incorrectly set to
238    // 00:00:00Z in FirmwareInventory (which is
239    // SoftwareInventoryCollection).
240    #[cfg(feature = "update-service")]
241    pub(crate) fn fw_inventory_wrong_release_date(&self) -> bool {
242        self.root
243            .vendor
244            .as_ref()
245            .and_then(Option::as_ref)
246            .is_some_and(|v| v == "Dell")
247    }
248
249    /// In some cases there is addtional fields in Links.ContainedBy in
250    /// Chassis resource, this flag aims to patch this invalid links
251    #[cfg(feature = "chassis")]
252    pub(crate) fn bug_invalid_contained_by_fields(&self) -> bool {
253        Self::is_ami_viking(&self.root)
254    }
255
256    /// Missing navigation properties in root object.
257    #[cfg(any(
258        feature = "chassis",
259        feature = "computer-systems",
260        feature = "managers",
261        feature = "update-service",
262    ))]
263    pub(crate) fn bug_missing_root_nav_properties(&self) -> bool {
264        Self::is_ami_viking(&self.root)
265    }
266
267    /// Missing chassis type property in Chassis resource. This
268    /// property is Required in according to specification but some
269    /// systems doesn't provide it.
270    #[cfg(feature = "chassis")]
271    pub(crate) fn bug_missing_chassis_type_field(&self) -> bool {
272        Self::is_ami_viking(&self.root)
273    }
274
275    /// Missing Name property in Chassis resource. This property is
276    /// required in any resource.
277    #[cfg(feature = "chassis")]
278    pub(crate) fn bug_missing_chassis_name_field(&self) -> bool {
279        Self::is_ami_viking(&self.root)
280    }
281
282    /// Missing Name property in Chassis resource. This property is
283    /// required in any resource.
284    #[cfg(feature = "update-service")]
285    pub(crate) fn bug_missing_update_service_name_field(&self) -> bool {
286        Self::is_ami_viking(&self.root)
287    }
288
289    /// In some implementations BMC ReleaseDate is incorrectly set to
290    /// "0000-00-00T00:00:00+00:00" in ComputerSystem/LastResetTime
291    /// This prevents ComputerSystem to be correctly parsed because
292    /// this is invalid Edm.DateTimeOffset.
293    #[cfg(feature = "computer-systems")]
294    pub(crate) fn computer_systems_wrong_last_reset_time(&self) -> bool {
295        self.root
296            .vendor
297            .as_ref()
298            .and_then(Option::as_ref)
299            .is_some_and(|v| v == "Dell")
300    }
301
302    /// In some implementations, Event records in SSE payload do not include
303    /// `MemberId`.
304    #[cfg(feature = "event-service")]
305    pub(crate) fn event_service_sse_no_member_id(&self) -> bool {
306        self.root
307            .vendor
308            .as_ref()
309            .and_then(Option::as_ref)
310            .is_some_and(|v| v == "NVIDIA")
311    }
312
313    /// In some implementations, Event records in SSE payload use compact
314    /// timezone offsets in `EventTimestamp` (for example, `-0600`).
315    #[cfg(feature = "event-service")]
316    pub(crate) fn event_service_sse_wrong_timestamp_offset(&self) -> bool {
317        self.root
318            .vendor
319            .as_ref()
320            .and_then(Option::as_ref)
321            .is_some_and(|v| v == "Dell")
322    }
323
324    /// In some implementations, Event records in SSE payload use unsupported
325    /// values in `EventType`.
326    #[cfg(feature = "event-service")]
327    pub(crate) fn event_service_sse_wrong_event_type(&self) -> bool {
328        self.root
329            .vendor
330            .as_ref()
331            .and_then(Option::as_ref)
332            .is_some_and(|v| v == "NVIDIA")
333    }
334
335    /// SSE payload does not include `@odata.id`.
336    #[cfg(feature = "event-service")]
337    pub(crate) fn event_service_sse_no_odata_id(&self) -> bool {
338        self.root.vendor.as_ref().and_then(Option::as_ref).is_some()
339    }
340
341    /// In some cases we expand is not working according to spec,
342    /// if it is the case for specific chassis, we would disable
343    /// expand api
344    fn expand_is_not_working_properly(root: &SchemaServiceRoot) -> bool {
345        Self::is_ami_viking(root)
346    }
347
348    fn is_ami_viking(root: &SchemaServiceRoot) -> bool {
349        root.vendor
350            .as_ref()
351            .and_then(Option::as_ref)
352            .is_some_and(|v| v == "AMI")
353            && root
354                .redfish_version
355                .as_ref()
356                .is_some_and(|version| version == "1.11.0")
357    }
358}
359
360impl<B: Bmc> Resource for ServiceRoot<B> {
361    fn resource_ref(&self) -> &ResourceSchema {
362        &self.root.as_ref().base
363    }
364}