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