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 ce2ef23
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 29 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)
}
}
19 changes: 16 additions & 3 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 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 ce2ef23

Please sign in to comment.