Skip to main content

nv_redfish/computer_system/
mod.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
16//! Computer System entities and collections.
17//!
18//! This module provides types for working with Redfish ComputerSystem resources
19//! and their sub-resources like processors, storage, memory, and drives.
20
21mod item;
22
23#[cfg(feature = "bios")]
24pub mod bios;
25#[cfg(feature = "boot-options")]
26pub mod boot_option;
27#[cfg(feature = "storages")]
28pub mod drive;
29#[cfg(feature = "memory")]
30pub mod memory;
31#[cfg(feature = "processors")]
32pub mod processor;
33#[cfg(feature = "secure-boot")]
34pub mod secure_boot;
35#[cfg(feature = "storages")]
36pub mod storage;
37
38use crate::patch_support::CollectionWithPatch;
39use crate::patch_support::FilterFn;
40use crate::patch_support::JsonValue;
41use crate::patch_support::ReadPatchFn;
42use crate::resource::Resource as _;
43use crate::schema::computer_system::ComputerSystem as ComputerSystemSchema;
44use crate::schema::computer_system_collection::ComputerSystemCollection as ComputerSystemCollectionSchema;
45use crate::schema::resource::ResourceCollection;
46use crate::Error;
47use crate::NvBmc;
48use crate::ServiceRoot;
49use nv_redfish_core::Bmc;
50use nv_redfish_core::NavProperty;
51use std::convert::identity;
52use std::sync::Arc;
53
54#[doc(inline)]
55pub use item::BootOptionReference;
56#[doc(inline)]
57pub use item::ComputerSystem;
58
59#[doc(inline)]
60#[cfg(feature = "bios")]
61pub use bios::Bios;
62#[doc(inline)]
63#[cfg(feature = "boot-options")]
64pub use boot_option::BootOption;
65#[doc(inline)]
66#[cfg(feature = "boot-options")]
67pub use boot_option::BootOptionCollection;
68#[doc(inline)]
69#[cfg(feature = "storages")]
70pub use drive::Drive;
71#[doc(inline)]
72#[cfg(feature = "memory")]
73pub use memory::Memory;
74#[doc(inline)]
75#[cfg(feature = "processors")]
76pub use processor::Processor;
77#[doc(inline)]
78#[cfg(feature = "secure-boot")]
79pub use secure_boot::SecureBoot;
80#[doc(inline)]
81#[cfg(feature = "secure-boot")]
82pub use secure_boot::SecureBootCurrentBootType;
83#[doc(inline)]
84#[cfg(feature = "storages")]
85pub use storage::Storage;
86
87/// Computer system collection.
88///
89/// Provides functions to access collection members.
90pub struct SystemCollection<B: Bmc> {
91    bmc: NvBmc<B>,
92    collection: Arc<ComputerSystemCollectionSchema>,
93    read_patch_fn: Option<ReadPatchFn>,
94}
95
96impl<B: Bmc> SystemCollection<B> {
97    pub(crate) async fn new(
98        bmc: &NvBmc<B>,
99        root: &ServiceRoot<B>,
100    ) -> Result<Option<Self>, Error<B>> {
101        let mut patches = Vec::new();
102        let mut filters = Vec::new();
103        if let Some(odata_id_filter) = bmc.quirks.filter_computer_system_odata_ids() {
104            filters.push(Box::new(move |js: &JsonValue| {
105                js.get("@odata.id")
106                    .and_then(|v| v.as_str())
107                    .map(odata_id_filter)
108                    .is_some_and(identity)
109            }));
110        }
111        if bmc.quirks.computer_systems_wrong_last_reset_time() {
112            patches.push(computer_systems_wrong_last_reset_time as fn(JsonValue) -> JsonValue);
113        }
114        if bmc.quirks.bug_empty_uuid_field() {
115            patches.push(normalize_empty_uuid_field);
116        }
117        if bmc.quirks.vera_rubin_composite_boot_order_entries() {
118            patches.push(normalize_vera_rubin_composite_boot_order);
119        }
120        let read_patch_fn = (!patches.is_empty())
121            .then(|| Arc::new(move |v| patches.iter().fold(v, |acc, f| f(acc))) as ReadPatchFn);
122        let filters_fn = (!filters.is_empty())
123            .then(move || Arc::new(move |v: &JsonValue| filters.iter().any(|f| f(v))) as FilterFn);
124
125        if let Some(collection_ref) = &root.root.systems {
126            Self::expand_collection(
127                bmc,
128                collection_ref,
129                read_patch_fn.as_ref(),
130                filters_fn.as_ref(),
131            )
132            .await
133            .map(Some)
134        } else if bmc.quirks.bug_missing_root_nav_properties() {
135            bmc.expand_property(&NavProperty::new_reference(
136                format!("{}/Systems", root.odata_id()).into(),
137            ))
138            .await
139            .map(Some)
140        } else {
141            Ok(None)
142        }
143        .map(|c| {
144            c.map(|collection| Self {
145                bmc: bmc.clone(),
146                collection,
147                read_patch_fn,
148            })
149        })
150    }
151
152    /// List all computer systems available in this BMC.
153    ///
154    /// # Errors
155    ///
156    /// Returns an error if fetching system data fails.
157    pub async fn members(&self) -> Result<Vec<ComputerSystem<B>>, Error<B>> {
158        let mut members = Vec::new();
159        for m in &self.collection.members {
160            members.push(ComputerSystem::new(&self.bmc, m, self.read_patch_fn.as_ref()).await?);
161        }
162        Ok(members)
163    }
164}
165
166impl<B: Bmc> CollectionWithPatch<ComputerSystemCollectionSchema, ComputerSystemSchema, B>
167    for SystemCollection<B>
168{
169    fn convert_patched(
170        base: ResourceCollection,
171        members: Vec<NavProperty<ComputerSystemSchema>>,
172    ) -> ComputerSystemCollectionSchema {
173        ComputerSystemCollectionSchema { base, members }
174    }
175}
176
177// `LastResetTime` is marked as `edm.DateTimeOffset`, but some systems
178// puts "0000-00-00T00:00:00+00:00" as LastResetTime that is not
179// conform to ABNF of the DateTimeOffset. We delete such fields...
180fn computer_systems_wrong_last_reset_time(v: JsonValue) -> JsonValue {
181    if let JsonValue::Object(mut obj) = v {
182        if let Some(JsonValue::String(date)) = obj.get("LastResetTime") {
183            if date.starts_with("0000-00-00") {
184                obj.remove("LastResetTime");
185            }
186        }
187        JsonValue::Object(obj)
188    } else {
189        v
190    }
191}
192
193fn normalize_empty_uuid_field(mut v: JsonValue) -> JsonValue {
194    if let JsonValue::Object(ref mut obj) = v {
195        if let Some(uuid) = obj.get_mut("UUID") {
196            let is_empty = uuid.as_str().is_some_and(str::is_empty);
197            if is_empty {
198                *uuid = JsonValue::Null;
199            }
200        }
201    }
202    v
203}
204
205/// Vera Rubin firmware reports composite `BootOrder` entries such as
206/// `"Boot0019: Ubuntu"` while boot option resources use the bare reference.
207fn normalize_vera_rubin_composite_boot_order(mut v: JsonValue) -> JsonValue {
208    if let JsonValue::Object(ref mut obj) = v {
209        if let Some(JsonValue::Object(ref mut boot)) = obj.get_mut("Boot") {
210            if let Some(JsonValue::Array(ref mut boot_order)) = boot.get_mut("BootOrder") {
211                for entry in boot_order.iter_mut() {
212                    if let JsonValue::String(entry) = entry {
213                        *entry = vera_rubin_boot_order_entry_reference(entry).to_string();
214                    }
215                }
216            }
217        }
218    }
219    v
220}
221
222fn vera_rubin_boot_order_entry_reference(entry: &str) -> &str {
223    entry
224        .split_once(": ")
225        .map_or(entry, |(reference, _)| reference)
226}
227
228#[cfg(test)]
229mod vera_rubin_boot_order_tests {
230    use super::*;
231    use serde_json::json;
232
233    #[test]
234    fn vera_rubin_boot_order_entry_reference_strips_display_name_suffix() {
235        assert_eq!(
236            vera_rubin_boot_order_entry_reference("Boot0019: Ubuntu"),
237            "Boot0019"
238        );
239        assert_eq!(
240            vera_rubin_boot_order_entry_reference("Boot0010: UEFI HTTPv4 (MAC:AA)"),
241            "Boot0010"
242        );
243        assert_eq!(
244            vera_rubin_boot_order_entry_reference("Boot0010"),
245            "Boot0010"
246        );
247    }
248
249    #[test]
250    fn normalize_vera_rubin_composite_boot_order_patches_boot_order_array() {
251        let patched = normalize_vera_rubin_composite_boot_order(json!({
252            "Boot": {
253                "BootOrder": [
254                    "Boot0019: Ubuntu",
255                    "Boot0010: UEFI HTTPv4 (MAC:AA)"
256                ]
257            }
258        }));
259        assert_eq!(
260            patched,
261            json!({
262                "Boot": {
263                    "BootOrder": ["Boot0019", "Boot0010"]
264                }
265            })
266        );
267    }
268}