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