Skip to main content

nv_redfish/
control.rs

1// SPDX-FileCopyrightText: Copyright (c) 2026 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//! Control resources.
17//!
18//! # Example
19//!
20//! ```ignore
21//! use nv_redfish::control::ControlUpdate;
22//! use nv_redfish_core::ModificationResponse;
23//!
24//! let Some(power_limit) = chassis.environment_power_limit_control().await? else {
25//!     return Ok(());
26//! };
27//!
28//! let update = ControlUpdate::builder().with_set_point(700.0).build();
29//!
30//! match power_limit.update(&update).await? {
31//!     ModificationResponse::Entity(updated) => {
32//!         let _updated_control = updated.raw();
33//!     }
34//!     ModificationResponse::Task(task) => {
35//!         let _task = task;
36//!     }
37//!     ModificationResponse::Empty => {}
38//! }
39//! ```
40
41use std::sync::Arc;
42
43use crate::schema::control::Control as ControlSchema;
44#[cfg(feature = "chassis")]
45use crate::schema::control_collection::ControlCollection as ControlCollectionSchema;
46use crate::Error;
47use crate::NvBmc;
48use crate::Resource;
49use crate::ResourceSchema;
50
51use nv_redfish_core::Bmc;
52use nv_redfish_core::EntityTypeRef as _;
53use nv_redfish_core::ModificationResponse;
54use nv_redfish_core::NavProperty;
55
56#[cfg(any(
57    feature = "chassis",
58    feature = "memory",
59    feature = "storages",
60    feature = "processors"
61))]
62use crate::schema::environment_metrics::EnvironmentMetrics;
63
64#[cfg(any(
65    feature = "chassis",
66    feature = "memory",
67    feature = "storages",
68    feature = "processors"
69))]
70use crate::core::ODataId;
71
72pub use crate::schema::control::ControlMode;
73pub use crate::schema::control::ControlType;
74pub use crate::schema::control::ControlUpdate;
75pub use crate::schema::control::ImplementationType;
76pub use crate::schema::control::SetPointType;
77
78/// Control collection.
79///
80/// This wraps the collection resource and its member links. Per-control
81/// properties such as set point and allowable range are stored on each
82/// [`Control`] returned by [`Self::members`].
83///
84/// # Example
85///
86/// ```ignore
87/// let controls = control_collection.members().await?;
88///
89/// for control in controls {
90///     let _control = control.raw();
91/// }
92/// ```
93#[cfg(feature = "chassis")]
94pub struct ControlCollection<B: Bmc> {
95    bmc: NvBmc<B>,
96    collection: Arc<ControlCollectionSchema>,
97}
98
99#[cfg(feature = "chassis")]
100impl<B: Bmc> ControlCollection<B> {
101    pub(crate) async fn new(
102        bmc: &NvBmc<B>,
103        nav: &NavProperty<ControlCollectionSchema>,
104    ) -> Result<Self, Error<B>> {
105        // Read the collection from the BMC so members can be fetched later.
106        nav.get(bmc.as_ref())
107            .await
108            .map_err(Error::Bmc)
109            .map(|collection| Self {
110                bmc: bmc.clone(),
111                collection,
112            })
113    }
114
115    /// Get the raw control collection schema data.
116    #[must_use]
117    pub fn raw(&self) -> Arc<ControlCollectionSchema> {
118        self.collection.clone()
119    }
120
121    /// List all controls in this collection.
122    ///
123    /// # Errors
124    ///
125    /// Returns an error if fetching a control fails.
126    pub async fn members(&self) -> Result<Vec<Control<B>>, Error<B>> {
127        let mut controls = Vec::with_capacity(self.collection.members.len());
128
129        for control in &self.collection.members {
130            controls.push(Control::new(&self.bmc, control).await?);
131        }
132
133        Ok(controls)
134    }
135}
136
137/// Control entity wrapper.
138///
139/// The raw schema data contains the target BMC's reported control properties,
140/// including set point, units, allowable range, and related metadata when the
141/// service provides them.
142pub struct Control<B: Bmc> {
143    bmc: NvBmc<B>,
144    data: Arc<ControlSchema>,
145}
146
147impl<B: Bmc> Control<B> {
148    pub(crate) async fn new(
149        bmc: &NvBmc<B>,
150        nav: &NavProperty<ControlSchema>,
151    ) -> Result<Self, Error<B>> {
152        nav.get(bmc.as_ref())
153            .await
154            .map_err(Error::Bmc)
155            .map(|data| Self {
156                bmc: bmc.clone(),
157                data,
158            })
159    }
160
161    /// Get the raw control schema data.
162    #[must_use]
163    pub fn raw(&self) -> Arc<ControlSchema> {
164        self.data.clone()
165    }
166
167    /// Update this control.
168    ///
169    /// # Example
170    ///
171    /// ```ignore
172    /// use nv_redfish::control::ControlUpdate;
173    /// use nv_redfish::core::ModificationResponse;
174    ///
175    /// let update = ControlUpdate::builder().with_set_point(700.0).build();
176    ///
177    /// match power_limit.update(&update).await? {
178    ///     ModificationResponse::Entity(updated) => {
179    ///         let _updated_control = updated.raw();
180    ///     }
181    ///     ModificationResponse::Task(task) => {
182    ///         let _update_task = task;
183    ///     }
184    ///     ModificationResponse::Empty => {}
185    /// }
186    /// ```
187    ///
188    /// # Errors
189    ///
190    /// Returns an error if updating the control fails.
191    pub async fn update(
192        &self,
193        update: &ControlUpdate,
194    ) -> Result<ModificationResponse<Self>, Error<B>> {
195        self.bmc
196            .as_ref()
197            .update::<_, NavProperty<ControlSchema>>(self.data.odata_id(), self.data.etag(), update)
198            .await
199            .map_err(Error::Bmc)?
200            .try_map_entity_async(|nav| async move { Self::new(&self.bmc, &nav).await })
201            .await
202    }
203}
204
205impl<B: Bmc> Resource for Control<B> {
206    fn resource_ref(&self) -> &ResourceSchema {
207        &self.data.as_ref().base
208    }
209}
210
211#[cfg(any(
212    feature = "chassis",
213    feature = "memory",
214    feature = "storages",
215    feature = "processors"
216))]
217pub(crate) async fn extract_environment_power_limit_control<B: Bmc>(
218    bmc: &NvBmc<B>,
219    metrics_ref: &NavProperty<EnvironmentMetrics>,
220) -> Result<Option<Control<B>>, Error<B>> {
221    let metrics = metrics_ref.get(bmc.as_ref()).await.map_err(Error::Bmc)?;
222
223    let Some(Some(uri)) = metrics
224        .power_limit_watts
225        .as_ref()
226        .and_then(|control| control.data_source_uri.as_ref())
227    else {
228        return Ok(None);
229    };
230
231    let control_ref = NavProperty::<ControlSchema>::new_reference(ODataId::from(uri.clone()));
232
233    Control::new(bmc, &control_ref).await.map(Some)
234}