mirror of
https://github.com/containers/youki
synced 2024-05-21 15:06:07 +02:00
193 lines
6.0 KiB
Rust
193 lines
6.0 KiB
Rust
use anyhow::{bail, Result};
|
|
use std::{
|
|
fs::OpenOptions,
|
|
io::{BufRead, BufReader, Read, Seek, SeekFrom, Write},
|
|
path::Path,
|
|
str, thread,
|
|
time::Duration,
|
|
};
|
|
|
|
use oci_spec::{FreezerState, LinuxResources};
|
|
|
|
use super::controller::Controller;
|
|
|
|
const CGROUP_FREEZE: &str = "cgroup.freeze";
|
|
const CGROUP_EVENTS: &str = "cgroup.events";
|
|
|
|
pub struct Freezer {}
|
|
|
|
impl Controller for Freezer {
|
|
fn apply(linux_resources: &LinuxResources, cgroup_path: &Path) -> Result<()> {
|
|
if let Some(freezer_state) = linux_resources.freezer {
|
|
Self::apply(freezer_state, cgroup_path)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Freezer {
|
|
fn apply(freezer_state: FreezerState, path: &Path) -> Result<()> {
|
|
let state_str = match freezer_state {
|
|
FreezerState::Undefined => return Ok(()),
|
|
FreezerState::Frozen => "1",
|
|
FreezerState::Thawed => "0",
|
|
};
|
|
|
|
match OpenOptions::new()
|
|
.create(false)
|
|
.write(true)
|
|
.open(path.join(CGROUP_FREEZE))
|
|
{
|
|
Err(e) => {
|
|
if let FreezerState::Frozen = freezer_state {
|
|
bail!("freezer not supported {}", e);
|
|
}
|
|
return Ok(());
|
|
}
|
|
Ok(mut file) => file.write_all(state_str.as_bytes())?,
|
|
};
|
|
|
|
// confirm that the cgroup did actually change states.
|
|
let actual_state = Self::read_freezer_state(path)?;
|
|
if !actual_state.eq(&freezer_state) {
|
|
bail!(
|
|
"expected \"cgroup.freeze\" to be in state {:?} but was in {:?}",
|
|
freezer_state,
|
|
actual_state
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn read_freezer_state(path: &Path) -> Result<FreezerState> {
|
|
let mut buf = [0; 1];
|
|
OpenOptions::new()
|
|
.create(false)
|
|
.read(true)
|
|
.open(path.join(CGROUP_FREEZE))?
|
|
.read_exact(&mut buf)?;
|
|
|
|
let state = str::from_utf8(&buf)?;
|
|
match state {
|
|
"0" => Ok(FreezerState::Thawed),
|
|
"1" => Self::wait_frozen(path),
|
|
_ => bail!("unknown \"cgroup.freeze\" state: {}", state),
|
|
}
|
|
}
|
|
|
|
// wait_frozen polls cgroup.events until it sees "frozen 1" in it.
|
|
fn wait_frozen(path: &Path) -> Result<FreezerState> {
|
|
let f = OpenOptions::new()
|
|
.create(false)
|
|
.read(true)
|
|
.open(path.join(CGROUP_EVENTS))?;
|
|
let mut f = BufReader::new(f);
|
|
|
|
let wait_time = Duration::from_millis(10);
|
|
let max_iter = 1000;
|
|
let mut iter = 0;
|
|
let mut line = String::new();
|
|
|
|
loop {
|
|
if iter == max_iter {
|
|
bail!(
|
|
"timeout of {} ms reached waiting for the cgroup to freeze",
|
|
wait_time.as_millis() * max_iter
|
|
);
|
|
}
|
|
line.clear();
|
|
let num_bytes = f.read_line(&mut line)?;
|
|
if num_bytes == 0 {
|
|
break;
|
|
}
|
|
if line.starts_with("frozen ") {
|
|
if line.starts_with("frozen 1") {
|
|
if iter > 1 {
|
|
log::debug!("frozen after {} retries", iter)
|
|
}
|
|
return Ok(FreezerState::Frozen);
|
|
}
|
|
iter += 1;
|
|
thread::sleep(wait_time);
|
|
f.seek(SeekFrom::Start(0))?;
|
|
}
|
|
}
|
|
|
|
Ok(FreezerState::Undefined)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::test::{create_temp_dir, set_fixture};
|
|
use oci_spec::FreezerState;
|
|
use std::sync::Arc;
|
|
|
|
#[test]
|
|
fn test_set_freezer_state() {
|
|
let tmp = Arc::new(
|
|
create_temp_dir("test_set_freezer_state").expect("create temp directory for test"),
|
|
);
|
|
set_fixture(&tmp, CGROUP_FREEZE, "").expect("Set fixure for freezer state");
|
|
set_fixture(&tmp, CGROUP_EVENTS, "populated 0\nfrozen 0")
|
|
.expect("Set fixure for freezer state");
|
|
|
|
// set Frozen state.
|
|
{
|
|
// use another thread to update events file async.
|
|
let p = Arc::clone(&tmp);
|
|
thread::spawn(move || {
|
|
thread::sleep(Duration::from_millis(100));
|
|
set_fixture(&p, CGROUP_EVENTS, "populated 0\nfrozen 1")
|
|
.expect("Set fixure for freezer state");
|
|
});
|
|
let freezer_state = FreezerState::Frozen;
|
|
Freezer::apply(freezer_state, &tmp).expect("Set freezer state");
|
|
|
|
let state_content =
|
|
std::fs::read_to_string(tmp.join(CGROUP_FREEZE)).expect("Read to string");
|
|
assert_eq!("1", state_content);
|
|
}
|
|
|
|
// set Thawed state.
|
|
{
|
|
let freezer_state = FreezerState::Thawed;
|
|
Freezer::apply(freezer_state, &tmp).expect("Set freezer state");
|
|
|
|
let state_content =
|
|
std::fs::read_to_string(tmp.join(CGROUP_FREEZE)).expect("Read to string");
|
|
assert_eq!("0", state_content);
|
|
}
|
|
|
|
// set Undefined state.
|
|
{
|
|
let old_state_content =
|
|
std::fs::read_to_string(tmp.join(CGROUP_FREEZE)).expect("Read to string");
|
|
let freezer_state = FreezerState::Undefined;
|
|
Freezer::apply(freezer_state, &tmp).expect("Set freezer state");
|
|
|
|
let state_content =
|
|
std::fs::read_to_string(tmp.join(CGROUP_FREEZE)).expect("Read to string");
|
|
assert_eq!(old_state_content, state_content);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_set_freezer_state_error() {
|
|
let tmp = create_temp_dir("test_set_freezer_state_error")
|
|
.expect("create temp directory for test");
|
|
set_fixture(&tmp, CGROUP_FREEZE, "").expect("Set fixure for freezer state");
|
|
set_fixture(&tmp, CGROUP_EVENTS, "").expect("Set fixure for freezer state");
|
|
|
|
// events file does not contain "frozen 1"
|
|
{
|
|
let freezer_state = FreezerState::Frozen;
|
|
let r = Freezer::apply(freezer_state, &tmp);
|
|
assert!(r.is_err());
|
|
}
|
|
}
|
|
}
|