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}