Skip to content

Commit

Permalink
Merge pull request #93 from duduainankai/add-freezer
Browse files Browse the repository at this point in the history
Add cgroup v1 freezer controller
  • Loading branch information
Furisto authored Jun 18, 2021
2 parents b7d6390 + 6a38a5d commit 58e33bb
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 2 deletions.
8 changes: 8 additions & 0 deletions oci_spec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ pub struct LinuxResources {
#[serde(default)]
pub hugepage_limits: Vec<LinuxHugepageLimit>,
pub network: Option<LinuxNetwork>,
pub freezer: Option<FreezerState>,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
Expand Down Expand Up @@ -556,6 +557,13 @@ pub enum LinuxSeccompOperator {
ScmpCmpMaskedEq = 7,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub enum FreezerState {
Undefined,
Frozen,
Thawed,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Linux {
Expand Down
3 changes: 3 additions & 0 deletions src/cgroups/v1/controller_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub enum ControllerType {
Blkio,
NetworkPriority,
NetworkClassifier,
Freezer,
}

impl Display for ControllerType {
Expand All @@ -26,6 +27,7 @@ impl Display for ControllerType {
Self::Blkio => "blkio",
Self::NetworkPriority => "net_prio",
Self::NetworkClassifier => "net_cls",
Self::Freezer => "freezer",
};

write!(f, "{}", print)
Expand All @@ -43,4 +45,5 @@ pub const CONTROLLERS: &[ControllerType] = &[
ControllerType::Blkio,
ControllerType::NetworkPriority,
ControllerType::NetworkClassifier,
ControllerType::Freezer,
];
248 changes: 248 additions & 0 deletions src/cgroups/v1/freezer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
use std::io::prelude::*;
use std::{
fs::{create_dir_all, OpenOptions},
path::Path,
thread, time,
};

use anyhow::{Result, *};
use nix::unistd::Pid;

use crate::cgroups::common::{self, CGROUP_PROCS};
use crate::cgroups::v1::Controller;
use oci_spec::{FreezerState, LinuxResources};

const CGROUP_FREEZER_STATE: &str = "freezer.state";
const FREEZER_STATE_THAWED: &str = "THAWED";
const FREEZER_STATE_FROZEN: &str = "FROZEN";
const FREEZER_STATE_FREEZING: &str = "FREEZING";

pub struct Freezer {}

impl Controller for Freezer {
fn apply(linux_resources: &LinuxResources, cgroup_root: &Path, pid: Pid) -> Result<()> {
log::debug!("Apply Freezer cgroup config");
create_dir_all(&cgroup_root)?;

if let Some(freezer_state) = linux_resources.freezer {
Self::apply(freezer_state, cgroup_root)?;
}

common::write_cgroup_file(cgroup_root.join(CGROUP_PROCS), pid)?;
Ok(())
}
}

impl Freezer {
fn apply(freezer_state: FreezerState, cgroup_root: &Path) -> Result<()> {
match freezer_state {
FreezerState::Undefined => {}
FreezerState::Thawed => {
common::write_cgroup_file(
cgroup_root.join(CGROUP_FREEZER_STATE),
FREEZER_STATE_THAWED,
)?;
}
FreezerState::Frozen => {
let r = || -> Result<()> {
// We should do our best to retry if FREEZING is seen until it becomes FROZEN.
// Add sleep between retries occasionally helped when system is extremely slow.
// see:
// https://github.com/opencontainers/runc/blob/b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7/libcontainer/cgroups/fs/freezer.go#L42
for i in 0..1000 {
if i % 50 == 49 {
let _ = common::write_cgroup_file(
cgroup_root.join(CGROUP_FREEZER_STATE),
FREEZER_STATE_THAWED,
);
thread::sleep(time::Duration::from_millis(10));
}

common::write_cgroup_file(
cgroup_root.join(CGROUP_FREEZER_STATE),
FREEZER_STATE_FROZEN,
)?;

if i % 25 == 24 {
thread::sleep(time::Duration::from_millis(10));
}

let r = Self::read_freezer_state(cgroup_root)?;
match r.trim() {
FREEZER_STATE_FREEZING => {
continue;
}
FREEZER_STATE_FROZEN => {
if i > 1 {
log::debug!("frozen after {} retries", i)
}
return Ok(());
}
_ => {
// should not reach here.
bail!("unexpected state {} while freezing", r.trim());
}
}
}
bail!("unbale to freeze");
}();

if r.is_err() {
// Freezing failed, and it is bad and dangerous to leave the cgroup in FROZEN or
// FREEZING, so try to thaw it back.
let _ = common::write_cgroup_file(
cgroup_root.join(CGROUP_FREEZER_STATE),
FREEZER_STATE_THAWED,
);
}
return r;
}
}
Ok(())
}

fn read_freezer_state(cgroup_root: &Path) -> Result<String> {
let path = cgroup_root.join(CGROUP_FREEZER_STATE);
let mut content = String::new();
OpenOptions::new()
.create(false)
.read(true)
.open(path)?
.read_to_string(&mut content)?;
Ok(content)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::cgroups::test::{create_temp_dir, set_fixture};
use oci_spec::FreezerState;

#[test]
fn test_set_freezer_state() {
let tmp =
create_temp_dir("test_set_freezer_state").expect("create temp directory for test");
set_fixture(&tmp, CGROUP_FREEZER_STATE, "").expect("Set fixure for freezer state");

// set Frozen 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_FREEZER_STATE)).expect("Read to string");
assert_eq!(FREEZER_STATE_FROZEN, 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_FREEZER_STATE)).expect("Read to string");
assert_eq!(FREEZER_STATE_THAWED, state_content);
}

// set Undefined state.
{
let old_state_content =
std::fs::read_to_string(tmp.join(CGROUP_FREEZER_STATE)).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_FREEZER_STATE)).expect("Read to string");
assert_eq!(old_state_content, state_content);
}
}

#[test]
fn test_apply() {
let tmp =
create_temp_dir("test_set_freezer_state").expect("create temp directory for test");
set_fixture(&tmp, CGROUP_FREEZER_STATE, "").expect("Set fixure for freezer state");
set_fixture(&tmp, CGROUP_PROCS, "").expect("set fixture for proc file");

// set Thawed state.
{
let linux_resources = LinuxResources {
devices: vec![],
disable_oom_killer: false,
oom_score_adj: None,
memory: None,
cpu: None,
pids: None,
block_io: None,
hugepage_limits: vec![],
network: None,
freezer: Some(FreezerState::Thawed),
};

let pid = Pid::from_raw(1000);
let _ =
<Freezer as Controller>::apply(&linux_resources, &tmp, pid).expect("freezer apply");
let state_content =
std::fs::read_to_string(tmp.join(CGROUP_FREEZER_STATE)).expect("Read to string");
assert_eq!(FREEZER_STATE_THAWED, state_content);
let pid_content =
std::fs::read_to_string(tmp.join(CGROUP_PROCS)).expect("Read to string");
assert_eq!(pid_content, "1000");
}

// set Frozen state.
{
let linux_resources = LinuxResources {
devices: vec![],
disable_oom_killer: false,
oom_score_adj: None,
memory: None,
cpu: None,
pids: None,
block_io: None,
hugepage_limits: vec![],
network: None,
freezer: Some(FreezerState::Frozen),
};

let pid = Pid::from_raw(1001);
let _ =
<Freezer as Controller>::apply(&linux_resources, &tmp, pid).expect("freezer apply");
let state_content =
std::fs::read_to_string(tmp.join(CGROUP_FREEZER_STATE)).expect("Read to string");
assert_eq!(FREEZER_STATE_FROZEN, state_content);
let pid_content =
std::fs::read_to_string(tmp.join(CGROUP_PROCS)).expect("Read to string");
assert_eq!(pid_content, "1001");
}

// set Undefined state.
{
let linux_resources = LinuxResources {
devices: vec![],
disable_oom_killer: false,
oom_score_adj: None,
memory: None,
cpu: None,
pids: None,
block_io: None,
hugepage_limits: vec![],
network: None,
freezer: Some(FreezerState::Undefined),
};

let old_state_content =
std::fs::read_to_string(tmp.join(CGROUP_FREEZER_STATE)).expect("Read to string");
let pid = Pid::from_raw(1002);
let _ =
<Freezer as Controller>::apply(&linux_resources, &tmp, pid).expect("freezer apply");
let state_content =
std::fs::read_to_string(tmp.join(CGROUP_FREEZER_STATE)).expect("Read to string");
assert_eq!(old_state_content, state_content);
let pid_content =
std::fs::read_to_string(tmp.join(CGROUP_PROCS)).expect("Read to string");
assert_eq!(pid_content, "1002");
}
}
}
6 changes: 4 additions & 2 deletions src/cgroups/v1/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use procfs::process::Process;

use super::{
blkio::Blkio, controller_type::CONTROLLERS, cpu::Cpu, cpuacct::CpuAcct, cpuset::CpuSet,
devices::Devices, hugetlb::Hugetlb, memory::Memory, network_classifier::NetworkClassifier,
network_priority::NetworkPriority, pids::Pids, util, Controller,
devices::Devices, freezer::Freezer, hugetlb::Hugetlb, memory::Memory,
network_classifier::NetworkClassifier, network_priority::NetworkPriority, pids::Pids, util,
Controller,
};

use crate::cgroups::common::CGROUP_PROCS;
Expand Down Expand Up @@ -70,6 +71,7 @@ impl CgroupManager for Manager {
"blkio" => Blkio::apply(linux_resources, &subsys.1, pid)?,
"net_prio" => NetworkPriority::apply(linux_resources, &subsys.1, pid)?,
"net_cls" => NetworkClassifier::apply(linux_resources, &subsys.1, pid)?,
"freezer" => Freezer::apply(linux_resources, &subsys.1, pid)?,
_ => unreachable!("every subsystem should have an associated controller"),
}
}
Expand Down
1 change: 1 addition & 0 deletions src/cgroups/v1/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ mod tests {
block_io: None,
hugepage_limits: vec![],
network: None,
freezer: None,
};

let pid = Pid::from_raw(pid_int);
Expand Down
1 change: 1 addition & 0 deletions src/cgroups/v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod cpu;
mod cpuacct;
mod cpuset;
mod devices;
mod freezer;
mod hugetlb;
pub mod manager;
mod memory;
Expand Down

0 comments on commit 58e33bb

Please sign in to comment.