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 = "managers")]
30use crate::manager::ManagerCollection;
31use crate::schema::redfish::service_root::ServiceRoot as SchemaServiceRoot;
32#[cfg(feature = "update-service")]
33use crate::update_service::UpdateService;
34use crate::{Error, NvBmc, ProtocolFeatures, Resource, ResourceSchema};
35
36/// The vendor or manufacturer associated with Redfish service.
37pub type Vendor<T> = TaggedType<T, VendorTag>;
38#[doc(hidden)]
39#[derive(tagged_types::Tag)]
40#[implement(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
41#[transparent(Debug, Display, Serialize, Deserialize)]
42#[capability(inner_access, cloned)]
43pub enum VendorTag {}
44
45/// The product associated with Redfish service..
46pub type Product<T> = TaggedType<T, ProductTag>;
47#[doc(hidden)]
48#[derive(tagged_types::Tag)]
49#[implement(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
50#[transparent(Debug, Display, Serialize, Deserialize)]
51#[capability(inner_access, cloned)]
52pub enum ProductTag {}
53
54/// Represents `ServiceRoot` in the BMC model.
55#[derive(Clone)]
56pub struct ServiceRoot<B: Bmc> {
57    /// Content of the root.
58    pub root: Arc<SchemaServiceRoot>,
59    #[allow(dead_code)] // feature-enabled field
60    bmc: NvBmc<B>,
61}
62
63impl<B: Bmc> ServiceRoot<B> {
64    /// Create a new service root.
65    ///
66    /// # Errors
67    ///
68    /// Returns error if retrieving the root path via Redfish fails.
69    pub async fn new(bmc: Arc<B>) -> Result<Self, Error<B>> {
70        let root = NavProperty::<SchemaServiceRoot>::new_reference(ODataId::service_root())
71            .get(bmc.as_ref())
72            .await
73            .map_err(Error::Bmc)?;
74        let mut protocol_features = root
75            .protocol_features_supported
76            .as_ref()
77            .map(ProtocolFeatures::new)
78            .unwrap_or_default();
79
80        if Self::expand_is_not_working_properly(&root) {
81            protocol_features.expand.expand_all = false;
82            protocol_features.expand.no_links = false;
83        }
84
85        let bmc = NvBmc::new(bmc, protocol_features);
86        Ok(Self { root, bmc })
87    }
88
89    /// The vendor or manufacturer associated with this Redfish service.
90    pub fn vendor(&self) -> Option<Vendor<&String>> {
91        self.root
92            .vendor
93            .as_ref()
94            .and_then(Option::as_ref)
95            .map(Vendor::new)
96    }
97
98    /// The product associated with this Redfish service.
99    pub fn product(&self) -> Option<Product<&String>> {
100        self.root
101            .product
102            .as_ref()
103            .and_then(Option::as_ref)
104            .map(Product::new)
105    }
106
107    /// Get the account service belonging to the BMC.
108    ///
109    /// # Errors
110    ///
111    /// Returns error if retrieving account service data fails.
112    #[cfg(feature = "accounts")]
113    pub async fn account_service(&self) -> Result<AccountService<B>, Error<B>> {
114        AccountService::new(&self.bmc, self).await
115    }
116
117    /// Get chassis collection in BMC
118    ///
119    /// # Errors
120    ///
121    /// Returns error if chassis list is not avaiable in BMC
122    #[cfg(feature = "chassis")]
123    pub async fn chassis(&self) -> Result<ChassisCollection<B>, Error<B>> {
124        ChassisCollection::new(&self.bmc, self).await
125    }
126
127    /// Get computer system collection in BMC
128    ///
129    /// # Errors
130    ///
131    /// Returns error if system list is not available in BMC
132    #[cfg(feature = "computer-systems")]
133    pub async fn systems(&self) -> Result<SystemCollection<B>, Error<B>> {
134        SystemCollection::new(&self.bmc, self).await
135    }
136
137    /// Get update service in BMC
138    ///
139    /// # Errors
140    ///
141    /// Returns error if update service is not available in BMC
142    #[cfg(feature = "update-service")]
143    pub async fn update_service(&self) -> Result<UpdateService<B>, Error<B>> {
144        UpdateService::new(&self.bmc, self).await
145    }
146
147    /// Get manager collection in BMC
148    ///
149    /// # Errors
150    ///
151    /// Returns error if manager list is not available in BMC
152    #[cfg(feature = "managers")]
153    pub async fn managers(&self) -> Result<ManagerCollection<B>, Error<B>> {
154        ManagerCollection::new(&self.bmc, self).await
155    }
156}
157
158// Known Redfish implementation bug checks.
159impl<B: Bmc> ServiceRoot<B> {
160    // Account type is required according to schema specification
161    // (marked with Redfish.Required annotation) but some vendors
162    // ignores this flag. A workaround for this bug is supported by
163    // `nv-redfish`.
164    #[cfg(feature = "accounts")]
165    pub(crate) fn bug_no_account_type_in_accounts(&self) -> bool {
166        self.root
167            .vendor
168            .as_ref()
169            .and_then(Option::as_ref)
170            .is_some_and(|v| v == "HPE")
171    }
172
173    // In some implementations BMC cannot create / delete Redfish
174    // accounts but have pre-created accounts (slots). Workflow is as
175    // following: to "create" new account user should update
176    // precreated account with new parameters and enable it. To delete
177    // account user should just disable it.
178    #[cfg(feature = "accounts")]
179    pub(crate) fn slot_defined_user_accounts(&self) -> Option<SlotDefinedUserAccountsConfig> {
180        if self
181            .root
182            .vendor
183            .as_ref()
184            .and_then(Option::as_ref)
185            .is_some_and(|v| v == "Dell")
186        {
187            Some(SlotDefinedUserAccountsConfig {
188                min_slot: Some(3),
189                hide_disabled: true,
190                disable_account_on_delete: true,
191            })
192        } else {
193            None
194        }
195    }
196
197    // In some implementations BMC ReleaseDate is incorrectly set to
198    // 00:00:00Z in FirmwareInventory (which is
199    // SoftwareInventoryCollection).
200    #[cfg(feature = "update-service")]
201    pub(crate) fn fw_inventory_wrong_release_date(&self) -> bool {
202        self.root
203            .vendor
204            .as_ref()
205            .and_then(Option::as_ref)
206            .is_some_and(|v| v == "Dell")
207    }
208
209    /// In some cases thre is addtional fields in Links.ContainedBy in
210    /// Chassis resource, this flag aims to patch this invalid links
211    #[cfg(feature = "chassis")]
212    pub(crate) fn bug_invalid_contained_by_fields(&self) -> bool {
213        self.root
214            .vendor
215            .as_ref()
216            .and_then(Option::as_ref)
217            .is_some_and(|v| v == "AMI")
218            && self
219                .root
220                .redfish_version
221                .as_ref()
222                .is_some_and(|version| version == "1.11.0")
223    }
224
225    /// In some implementations BMC ReleaseDate is incorrectly set to
226    /// "0000-00-00T00:00:00+00:00" in ComputerSystem/LastResetTime
227    /// This prevents ComputerSystem to be correctly parsed because
228    /// this is invalid Edm.DateTimeOffset.
229    #[cfg(feature = "computer-systems")]
230    pub(crate) fn computer_systems_wrong_last_reset_time(&self) -> bool {
231        self.root
232            .vendor
233            .as_ref()
234            .and_then(Option::as_ref)
235            .is_some_and(|v| v == "Dell")
236    }
237
238    /// In some implementations Assembly/Assemblies doens't
239    /// "@odata.type". This is spec violation but we support patching
240    /// such systems.
241    #[cfg(feature = "assembly")]
242    pub(crate) fn assembly_assemblies_without_odata_type(&self) -> bool {
243        self.root
244            .vendor
245            .as_ref()
246            .and_then(Option::as_ref)
247            .is_some_and(|v| v == "WIWYNN")
248    }
249
250    /// In some cases we expand is not working according to spec,
251    /// if it is the case for specific chassis, we would disable
252    /// expand api
253    fn expand_is_not_working_properly(root: &SchemaServiceRoot) -> bool {
254        root.vendor
255            .as_ref()
256            .and_then(Option::as_ref)
257            .is_some_and(|v| v == "AMI")
258            && root
259                .redfish_version
260                .as_ref()
261                .is_some_and(|version| version == "1.11.0")
262    }
263}
264
265impl<B: Bmc> Resource for ServiceRoot<B> {
266    fn resource_ref(&self) -> &ResourceSchema {
267        &self.root.as_ref().base
268    }
269}