Skip to content

Commit

Permalink
cooling_device: add persistency
Browse files Browse the repository at this point in the history
Given the persistency cannot deal with dynamic memory that well, we
allocated space to store up to 10 cooling device speeds.
  • Loading branch information
svenrademakers committed Jul 25, 2024
1 parent 05b8e0c commit a51b316
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 30 deletions.
33 changes: 9 additions & 24 deletions src/api/legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -541,42 +540,28 @@ async fn get_usb_mode(bmc: &BmcApplication) -> impl Into<LegacyResponse> {
)
}

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<LegacyResponse> {
let info = get_cooling_state().await;
json!(info)
async fn get_cooling_info() -> LegacyResult<serde_json::Value> {
let info = BmcApplication::get_cooling_devices().await?;
Ok(json!(info))
}

async fn handle_flash_status(flash: web::Data<StreamingDataService>) -> LegacyResult<String> {
Expand Down
61 changes: 59 additions & 2 deletions src/app/bmc_application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64, u64>;

/// 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
Expand All @@ -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)]
Expand Down Expand Up @@ -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?;
Expand Down Expand Up @@ -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::<u8>(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<()> {
Expand All @@ -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::<CoolingMap>(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::<UsbConfig>(USB_CONFIG).await,
Expand Down Expand Up @@ -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::<CoolingMap>(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<Vec<CoolingDevice>> {
Ok(get_cooling_state().await)
}
}
21 changes: 17 additions & 4 deletions src/app/cooling_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -35,7 +35,7 @@ pub async fn get_cooling_state() -> Vec<CoolingDevice> {
}

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('-', ' ')) {

Check failure on line 38 in src/app/cooling_device.rs

View workflow job for this annotation

GitHub Actions / cargo-test

mismatched types

Check failure on line 38 in src/app/cooling_device.rs

View workflow job for this annotation

GitHub Actions / clippy

mismatched types

error[E0308]: mismatched types --> src/app/cooling_device.rs:38:84 | 38 | if let Some(name) = is_system_fan(&device_path).map(|n| n.replace('-', ' ')) { | ------- ^^^ expected `&str`, found `char` | | | arguments to this method are incorrect | note: method defined here --> /rustc/129f3b9964af4d4a709d1383930ade12dfe7c081/library/alloc/src/str.rs:271:12 help: if you meant to write a string literal, use double quotes | 38 | if let Some(name) = is_system_fan(&device_path).map(|n| n.replace('-', " ")) { | ~ ~
device_name = name;
}

Expand Down Expand Up @@ -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(())
}

0 comments on commit a51b316

Please sign in to comment.