Skip to main content

nv_redfish/computer_system/
item.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::core::Bmc;
17use crate::core::EntityTypeRef as _;
18use crate::core::ModificationResponse;
19use crate::core::NavProperty;
20use crate::core::RedfishSettings as _;
21use crate::hardware_id::HardwareIdRef;
22use crate::hardware_id::Manufacturer as HardwareIdManufacturer;
23use crate::hardware_id::Model as HardwareIdModel;
24use crate::hardware_id::PartNumber as HardwareIdPartNumber;
25use crate::hardware_id::SerialNumber as HardwareIdSerialNumber;
26use crate::patch_support::Payload;
27use crate::patch_support::ReadPatchFn;
28use crate::resource::PowerState;
29use crate::schema::redfish::computer_system::ComputerSystem as ComputerSystemSchema;
30use crate::Error;
31use crate::NvBmc;
32use crate::Resource;
33use crate::ResourceSchema;
34
35use serde::Serialize;
36use std::convert::identity;
37use std::sync::Arc;
38use tagged_types::TaggedType;
39
40#[cfg(feature = "bios")]
41use crate::computer_system::Bios;
42#[cfg(feature = "boot-options")]
43use crate::computer_system::BootOptionCollection;
44#[cfg(feature = "memory")]
45use crate::computer_system::Memory;
46#[cfg(feature = "processors")]
47use crate::computer_system::Processor;
48#[cfg(feature = "secure-boot")]
49use crate::computer_system::SecureBoot;
50#[cfg(feature = "storages")]
51use crate::computer_system::Storage;
52#[cfg(feature = "ethernet-interfaces")]
53use crate::ethernet_interface::EthernetInterfaceCollection;
54#[cfg(feature = "log-services")]
55use crate::log_service::LogService;
56#[cfg(feature = "oem-lenovo")]
57use crate::oem::lenovo::computer_system::LenovoComputerSystem;
58#[cfg(feature = "oem-nvidia-bluefield")]
59use crate::oem::nvidia::bluefield::nvidia_computer_system::NvidiaComputerSystem;
60
61#[doc(hidden)]
62pub enum ComputerSystemTag {}
63
64/// Computer system manufacturer.
65pub type Manufacturer<T> = HardwareIdManufacturer<T, ComputerSystemTag>;
66
67/// Computer system model.
68pub type Model<T> = HardwareIdModel<T, ComputerSystemTag>;
69
70/// Computer system part number.
71pub type PartNumber<T> = HardwareIdPartNumber<T, ComputerSystemTag>;
72
73/// Computer system serial number.
74pub type SerialNumber<T> = HardwareIdSerialNumber<T, ComputerSystemTag>;
75
76/// Computer system SKU.
77pub type Sku<T> = TaggedType<T, ComputerSystemSkuTag>;
78#[doc(hidden)]
79#[derive(tagged_types::Tag)]
80#[implement(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
81#[transparent(Debug, Display, FromStr, Serialize, Deserialize)]
82#[capability(inner_access, cloned)]
83pub enum ComputerSystemSkuTag {}
84
85/// `BootOptionReference` type represent boot order of the `ComputerSystem`.
86pub type BootOptionReference<T> = TaggedType<T, BootOptionReferenceTag>;
87#[doc(hidden)]
88#[derive(tagged_types::Tag)]
89#[implement(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
90#[transparent(Debug, Display, FromStr, Serialize, Deserialize)]
91#[capability(inner_access, cloned)]
92pub enum BootOptionReferenceTag {}
93
94#[derive(Serialize)]
95struct BootPatch {
96    #[serde(rename = "BootOrder")]
97    boot_order: Vec<BootOptionReference<String>>,
98}
99
100#[derive(Serialize)]
101struct ComputerSystemBootOrderUpdate {
102    #[serde(rename = "Boot")]
103    boot: BootPatch,
104}
105
106/// Represents a computer system in the BMC.
107///
108/// Provides access to system information and sub-resources such as processors.
109pub struct ComputerSystem<B: Bmc> {
110    #[allow(dead_code)] // feature-enabled...
111    bmc: NvBmc<B>,
112    data: Arc<ComputerSystemSchema>,
113}
114
115impl<B: Bmc> ComputerSystem<B> {
116    /// Create a new computer system handle.
117    pub(crate) async fn new(
118        bmc: &NvBmc<B>,
119        nav: &NavProperty<ComputerSystemSchema>,
120        read_patch_fn: Option<&ReadPatchFn>,
121    ) -> Result<Self, Error<B>> {
122        if let Some(read_patch_fn) = read_patch_fn {
123            Payload::get(bmc.as_ref(), nav, read_patch_fn.as_ref()).await
124        } else {
125            nav.get(bmc.as_ref()).await.map_err(Error::Bmc)
126        }
127        .map(|data| Self {
128            bmc: bmc.clone(),
129            data,
130        })
131    }
132
133    /// Get the raw schema data for this computer system.
134    ///
135    /// Returns an `Arc` to the underlying schema, allowing cheap cloning
136    /// and sharing of the data.
137    #[must_use]
138    pub fn raw(&self) -> Arc<ComputerSystemSchema> {
139        self.data.clone()
140    }
141
142    /// Get hardware identifier of the network adpater.
143    #[must_use]
144    pub fn hardware_id(&self) -> HardwareIdRef<'_, ComputerSystemTag> {
145        HardwareIdRef {
146            manufacturer: self
147                .data
148                .manufacturer
149                .as_ref()
150                .and_then(Option::as_deref)
151                .map(Manufacturer::new),
152            model: self
153                .data
154                .model
155                .as_ref()
156                .and_then(Option::as_deref)
157                .map(Model::new),
158            part_number: self
159                .data
160                .part_number
161                .as_ref()
162                .and_then(Option::as_deref)
163                .map(PartNumber::new),
164            serial_number: self
165                .data
166                .serial_number
167                .as_ref()
168                .and_then(Option::as_deref)
169                .map(SerialNumber::new),
170        }
171    }
172
173    /// The manufacturer SKU for this system.
174    #[must_use]
175    pub fn sku(&self) -> Option<Sku<&str>> {
176        self.data
177            .sku
178            .as_ref()
179            .and_then(Option::as_ref)
180            .map(String::as_str)
181            .map(Sku::new)
182    }
183
184    /// Power state of this system.
185    #[must_use]
186    pub fn power_state(&self) -> Option<PowerState> {
187        self.data.power_state.and_then(identity)
188    }
189
190    /// An array of `BootOptionReference` strings that represent the persistent boot order for with this
191    /// computer system.
192    #[must_use]
193    pub fn boot_order(&self) -> Option<Vec<BootOptionReference<&str>>> {
194        self.data
195            .as_ref()
196            .boot
197            .as_ref()
198            .and_then(|boot| boot.boot_order.as_ref().and_then(Option::as_ref))
199            .map(|v| {
200                v.iter()
201                    .map(String::as_str)
202                    .map(BootOptionReference::new)
203                    .collect::<Vec<_>>()
204            })
205    }
206
207    /// Update the persistent boot order for this computer system.
208    ///
209    /// # Errors
210    ///
211    /// Returns an error if updating the system fails.
212    pub async fn set_boot_order(
213        &self,
214        boot_order: Vec<BootOptionReference<String>>,
215    ) -> Result<Option<Self>, Error<B>> {
216        let update = ComputerSystemBootOrderUpdate {
217            boot: BootPatch { boot_order },
218        };
219
220        let settings = self.data.settings_object();
221
222        let update_odata = settings
223            .as_ref()
224            .map_or_else(|| self.data.odata_id(), |settings| settings.odata_id());
225
226        match self
227            .bmc
228            .as_ref()
229            .update::<_, NavProperty<ComputerSystemSchema>>(update_odata, None, &update)
230            .await
231            .map_err(Error::Bmc)?
232        {
233            ModificationResponse::Entity(nav) => {
234                let data = nav.get(self.bmc.as_ref()).await.map_err(Error::Bmc)?;
235                Ok(Some(Self {
236                    bmc: self.bmc.clone(),
237                    data,
238                }))
239            }
240            ModificationResponse::Task(_) | ModificationResponse::Empty => Ok(None),
241        }
242    }
243
244    /// Bios associated with this system.
245    ///
246    /// Fetches the BIOS settings. Returns `Ok(None)` when the BIOS link is absent.
247    ///
248    /// # Errors
249    ///
250    /// Returns an error if fetching BIOS data fails.
251    #[cfg(feature = "bios")]
252    pub async fn bios(&self) -> Result<Option<Bios<B>>, Error<B>> {
253        if let Some(bios_ref) = &self.data.bios {
254            Bios::new(&self.bmc, bios_ref).await.map(Some)
255        } else {
256            Ok(None)
257        }
258    }
259
260    /// Get processors associated with this system.
261    ///
262    /// Fetches the processor collection and returns a list of [`Processor`] handles.
263    /// Returns `Ok(None)` when the processors link is absent.
264    ///
265    /// # Errors
266    ///
267    /// Returns an error if fetching processor data fails.
268    #[cfg(feature = "processors")]
269    pub async fn processors(&self) -> Result<Option<Vec<Processor<B>>>, Error<B>> {
270        if let Some(processors_ref) = &self.data.processors {
271            let processors_collection = self.bmc.expand_property(processors_ref).await?;
272
273            let mut processors = Vec::new();
274            for m in &processors_collection.members {
275                processors.push(Processor::new(&self.bmc, m).await?);
276            }
277
278            Ok(Some(processors))
279        } else {
280            Ok(None)
281        }
282    }
283
284    /// Get secure boot resource associated with this system.
285    ///
286    /// Returns `Ok(None)` when the secure boot link is absent.
287    ///
288    /// # Errors
289    ///
290    /// Returns an error if fetching secure boot data fails.
291    #[cfg(feature = "secure-boot")]
292    pub async fn secure_boot(&self) -> Result<Option<SecureBoot<B>>, Error<B>> {
293        if let Some(secure_boot_ref) = &self.data.secure_boot {
294            SecureBoot::new(&self.bmc, secure_boot_ref).await.map(Some)
295        } else {
296            Ok(None)
297        }
298    }
299
300    /// Get storage controllers associated with this system.
301    ///
302    /// Fetches the storage collection and returns a list of [`Storage`] handles.
303    /// Returns `Ok(None)` when the storage link is absent.
304    ///
305    /// # Errors
306    ///
307    /// Returns an error if fetching storage data fails.
308    #[cfg(feature = "storages")]
309    pub async fn storage_controllers(&self) -> Result<Option<Vec<Storage<B>>>, Error<B>> {
310        if let Some(storage_ref) = &self.data.storage {
311            let storage_collection = self.bmc.expand_property(storage_ref).await?;
312
313            let mut storage_controllers = Vec::new();
314            for m in &storage_collection.members {
315                storage_controllers.push(Storage::new(&self.bmc, m).await?);
316            }
317
318            Ok(Some(storage_controllers))
319        } else {
320            Ok(None)
321        }
322    }
323
324    /// Get memory modules associated with this system.
325    ///
326    /// Fetches the memory collection and returns a list of [`Memory`] handles.
327    /// Returns `Ok(None)` when the memory link is absent.
328    ///
329    /// # Errors
330    ///
331    /// Returns an error if fetching memory data fails.
332    #[cfg(feature = "memory")]
333    pub async fn memory_modules(&self) -> Result<Option<Vec<Memory<B>>>, Error<B>> {
334        if let Some(memory_ref) = &self.data.memory {
335            let memory_collection = self.bmc.expand_property(memory_ref).await?;
336
337            let mut memory_modules = Vec::new();
338            for m in &memory_collection.members {
339                memory_modules.push(Memory::new(&self.bmc, m).await?);
340            }
341
342            Ok(Some(memory_modules))
343        } else {
344            Ok(None)
345        }
346    }
347
348    /// Get log services for this computer system.
349    ///
350    /// Returns `Ok(None)` when the log services link is absent.
351    ///
352    /// # Errors
353    ///
354    /// Returns an error if fetching log service data fails.
355    #[cfg(feature = "log-services")]
356    pub async fn log_services(&self) -> Result<Option<Vec<LogService<B>>>, Error<B>> {
357        if let Some(log_services_ref) = &self.data.log_services {
358            let log_services_collection = log_services_ref
359                .get(self.bmc.as_ref())
360                .await
361                .map_err(Error::Bmc)?;
362
363            let mut log_services = Vec::new();
364            for m in &log_services_collection.members {
365                log_services.push(LogService::new(&self.bmc, m).await?);
366            }
367
368            Ok(Some(log_services))
369        } else {
370            Ok(None)
371        }
372    }
373
374    /// Get ethernet interfaces for this computer system.
375    ///
376    /// Returns `Ok(None)` when the ethernet interfaces link is absent.
377    ///
378    /// # Errors
379    ///
380    /// Returns an error if fetching ethernet interface data fails.
381    #[cfg(feature = "ethernet-interfaces")]
382    pub async fn ethernet_interfaces(
383        &self,
384    ) -> Result<Option<EthernetInterfaceCollection<B>>, Error<B>> {
385        if let Some(p) = &self.data.ethernet_interfaces {
386            EthernetInterfaceCollection::new(&self.bmc, p)
387                .await
388                .map(Some)
389        } else {
390            Ok(None)
391        }
392    }
393
394    /// Get collection of the UEFI boot options associated with this computer system.
395    ///
396    /// Returns `Ok(None)` when boot options are not exposed.
397    ///
398    /// # Errors
399    ///
400    /// Returns an error if fetching boot options data fails.
401    #[cfg(feature = "boot-options")]
402    pub async fn boot_options(&self) -> Result<Option<BootOptionCollection<B>>, Error<B>> {
403        if let Some(p) = &self
404            .data
405            .boot
406            .as_ref()
407            .and_then(|v| v.boot_options.as_ref())
408        {
409            BootOptionCollection::new(&self.bmc, p).await.map(Some)
410        } else {
411            Ok(None)
412        }
413    }
414
415    /// NVIDIA Bluefield OEM extension
416    ///
417    /// Returns `Ok(None)` when the system does not include NVIDIA OEM extension data.
418    ///
419    /// # Errors
420    ///
421    /// Returns an error if NVIDIA OEM data parsing/fetching fails.
422    #[cfg(feature = "oem-nvidia-bluefield")]
423    pub async fn oem_nvidia_bluefield(&self) -> Result<Option<NvidiaComputerSystem<B>>, Error<B>> {
424        if let Some(oem) = self.data.base.base.oem.as_ref() {
425            NvidiaComputerSystem::new(&self.bmc, oem).await
426        } else {
427            Ok(None)
428        }
429    }
430
431    /// Lenovo OEM extension
432    ///
433    /// Returns `Ok(None)` when the system does not include Lenovo OEM extension data.
434    ///
435    /// # Errors
436    ///
437    /// Returns an error if Lenovo OEM data parsing fails.
438    #[cfg(feature = "oem-lenovo")]
439    pub fn oem_lenovo(&self) -> Result<Option<LenovoComputerSystem<B>>, Error<B>> {
440        LenovoComputerSystem::new(&self.bmc, &self.data)
441    }
442}
443
444impl<B: Bmc> Resource for ComputerSystem<B> {
445    fn resource_ref(&self) -> &ResourceSchema {
446        &self.data.as_ref().base
447    }
448}