From a51b316528a60489eac10ddb643850210d3021d6 Mon Sep 17 00:00:00 2001 From: Sven Rademakers Date: Thu, 25 Jul 2024 10:35:40 +0100 Subject: [PATCH] cooling_device: add persistency Given the persistency cannot deal with dynamic memory that well, we allocated space to store up to 10 cooling device speeds. --- src/api/legacy.rs | 33 ++++++--------------- src/app/bmc_application.rs | 61 ++++++++++++++++++++++++++++++++++++-- src/app/cooling_device.rs | 21 ++++++++++--- 3 files changed, 85 insertions(+), 30 deletions(-) diff --git a/src/api/legacy.rs b/src/api/legacy.rs index 8a4ade0..2c8c9af 100644 --- a/src/api/legacy.rs +++ b/src/api/legacy.rs @@ -19,7 +19,6 @@ use crate::app::bmc_application::{BmcApplication, UsbConfig}; use crate::app::bmc_info::{ get_fs_stat, get_ipv4_address, get_mac_address, get_net_interfaces, get_storage_info, }; -use crate::app::cooling_device::{get_cooling_state, set_cooling_state}; use crate::app::transfer_action::InitializeTransfer; use crate::app::transfer_action::UpgradeCommand; use crate::hal::{NodeId, UsbMode, UsbRoute}; @@ -187,7 +186,7 @@ async fn api_entry( ("usb_node1", false) => get_node1_usb_mode(bmc).await, ("info", false) => get_info().await.into(), ("cooling", false) => get_cooling_info().await.into(), - ("cooling", true) => set_cooling_info(query).await.into(), + ("cooling", true) => set_cooling_info(bmc, query).await.into(), ("about", false) => get_about().await.into(), _ => ( StatusCode::BAD_REQUEST, @@ -541,42 +540,28 @@ async fn get_usb_mode(bmc: &BmcApplication) -> impl Into { ) } -async fn set_cooling_info(query: Query) -> LegacyResult<()> { - let device_str = query +async fn set_cooling_info(bmc: &BmcApplication, query: Query) -> LegacyResult<()> { + let device = query .get("device") .ok_or(LegacyResponse::bad_request("Missing `device` parameter"))?; let speed_str = query .get("speed") .ok_or(LegacyResponse::bad_request("Missing `speed` parameter"))?; - // get the target device - let devices = get_cooling_state().await; - let device = devices - .iter() - .find(|d| &d.device == device_str) - .ok_or(LegacyResponse::bad_request("Device not found"))?; - // check if the speed is a valid number within the range of the device - let speed = match c_ulong::from_str(speed_str) { - Ok(s) if s <= device.max_speed => s, - _ => { - return Err(LegacyResponse::bad_request(format!( - "Parameter `speed` must be a number between 0-{}", - device.max_speed - ))) - } - }; + let speed = c_ulong::from_str(speed_str) + .map_err(|_| LegacyResponse::bad_request("`speed` parameter is not a number"))?; // set the speed - set_cooling_state(&device.device, &speed) + bmc.set_cooling_speed(device, speed) .await .context("set Cooling state") .map_err(Into::into) } -async fn get_cooling_info() -> impl Into { - let info = get_cooling_state().await; - json!(info) +async fn get_cooling_info() -> LegacyResult { + let info = BmcApplication::get_cooling_devices().await?; + Ok(json!(info)) } async fn handle_flash_status(flash: web::Data) -> LegacyResult { diff --git a/src/app/bmc_application.rs b/src/app/bmc_application.rs index 30a2f1c..48e8c46 100644 --- a/src/app/bmc_application.rs +++ b/src/app/bmc_application.rs @@ -26,15 +26,20 @@ use crate::{ use anyhow::{bail, ensure, Context}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use std::ffi::c_ulong; +use std::hash::{DefaultHasher, Hash, Hasher}; use std::path::PathBuf; use std::process::Command; use std::time::Duration; use tokio::fs::OpenOptions; use tokio::io::{AsyncRead, AsyncSeek, AsyncSeekExt, AsyncWrite, AsyncWriteExt}; -use tracing::info; use tracing::{debug, trace}; +use tracing::{info, warn}; + +use super::cooling_device::{get_cooling_state, set_cooling_state, CoolingDevice}; pub type NodeInfos = [NodeInfo; 4]; +type CoolingMap = HashMap; /// Stores which slots are actually used. This information is used to determine /// for instance, which nodes need to be powered on, when such command is given @@ -44,6 +49,7 @@ pub const USB_CONFIG: &str = "usb_config"; /// Stores information about nodes: name alias, time since powered on, and others. See [NodeInfo]. pub const NODE_INFO_KEY: &str = "node_info"; pub const NODE1_USB_MODE: &str = "node1_usb"; +pub const COOLING_DEVICES: &str = "cooling_devices"; /// Describes the different configuration the USB bus can be setup #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] @@ -84,6 +90,7 @@ impl BmcApplication { .register_key(USB_CONFIG, &UsbConfig::UsbA(NodeId::Node1)) .register_key(NODE_INFO_KEY, &NodeInfos::default()) .register_key(NODE1_USB_MODE, &false) + .register_key(COOLING_DEVICES, &CoolingMap::with_capacity(10)) .write_timeout(database_write_timeout) .build() .await?; @@ -135,7 +142,8 @@ impl BmcApplication { async fn initialize(&self) -> anyhow::Result<()> { self.initialize_usb_mode().await?; let power_state = self.app_db.try_get::(ACTIVATED_NODES_KEY).await?; - self.activate_slot(power_state, 0b1111).await + self.activate_slot(power_state, 0b1111).await?; + self.initialize_cooling().await } async fn initialize_usb_mode(&self) -> anyhow::Result<()> { @@ -148,6 +156,29 @@ impl BmcApplication { self.configure_usb(config).await.context("USB configure") } + async fn initialize_cooling(&self) -> anyhow::Result<()> { + let store = self.app_db.get::(COOLING_DEVICES).await; + let devices = get_cooling_state().await; + + let mut set_devices = Vec::new(); + for dev in devices { + let mut hasher = DefaultHasher::new(); + dev.device.hash(&mut hasher); + let hash = hasher.finish(); + + if let Some(speed) = store.get(&hash) { + set_cooling_state(&dev.device, speed).await?; + set_devices.push((hash, *speed)); + } + } + + // cleanup storage + let map: CoolingMap = HashMap::from_iter(set_devices.into_iter()); + self.app_db.set(COOLING_DEVICES, map).await; + + Ok(()) + } + pub async fn get_usb_mode(&self) -> (UsbConfig, String) { ( self.app_db.get::(USB_CONFIG).await, @@ -383,4 +414,30 @@ impl BmcApplication { Ok(node_infos) } + + pub async fn set_cooling_speed(&self, device: &str, speed: c_ulong) -> anyhow::Result<()> { + let res = set_cooling_state(device, &speed).await; + + if res.is_ok() { + let mut cooling = self.app_db.get::(COOLING_DEVICES).await; + if cooling.len() < cooling.capacity() { + let mut hasher = DefaultHasher::new(); + device.hash(&mut hasher); + let value = cooling.entry(hasher.finish()).or_default(); + *value = speed; + self.app_db.set(COOLING_DEVICES, cooling).await; + } else { + warn!( + "cooling devices persistency full, no room to persist speed of '{}'", + device + ); + } + } + + res + } + + pub async fn get_cooling_devices() -> anyhow::Result> { + Ok(get_cooling_state().await) + } } diff --git a/src/app/cooling_device.rs b/src/app/cooling_device.rs index 75aea4e..2f1014b 100644 --- a/src/app/cooling_device.rs +++ b/src/app/cooling_device.rs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{ffi::c_ulong, fs, io, path::Path}; - +use anyhow::{anyhow, bail}; use serde::Serialize; +use std::{ffi::c_ulong, fs, io, path::Path}; use tracing::{instrument, warn}; #[derive(Debug, Serialize)] @@ -35,7 +35,7 @@ pub async fn get_cooling_state() -> Vec { } let device_path = device.path(); - if let Some(name) = is_system_fan(&device_path).map(|n| n.replace("_", " ")) { + if let Some(name) = is_system_fan(&device_path).map(|n| n.replace('-', ' ')) { device_name = name; } @@ -105,7 +105,20 @@ pub async fn set_cooling_state(device: &str, speed: &c_ulong) -> anyhow::Result< .join(device) .join("cur_state"); - tokio::fs::write(device_path, speed.to_string()).await?; + let devices = get_cooling_state().await; + let device = devices + .iter() + .find(|d| d.device == device) + .ok_or(anyhow!("cooling device: `{}` does not exist", device))?; + + if speed > &device.max_speed { + bail!( + "given speed '{}' exceeds maximum speed of '{}'", + speed, + device.max_speed + ); + } + tokio::fs::write(device_path, speed.to_string()).await?; Ok(()) }