mirror of
https://github.com/containers/youki
synced 2024-05-12 02:26:12 +02:00
Compare commits
5 Commits
abdd1cf667
...
4ac5b5d67c
Author | SHA1 | Date | |
---|---|---|---|
MarceloSpessoto | 4ac5b5d67c | ||
Yashodhan | 601df9ecd3 | ||
Marcelo Mendes Spessoto Junior | f04e246cee | ||
Marcelo Mendes Spessoto Junior | dc4993e045 | ||
Marcelo Mendes Spessoto Junior | 95d919ea0c |
|
@ -1,5 +1,6 @@
|
|||
[build]
|
||||
default-target = "x86_64-unknown-linux-gnu"
|
||||
env.passthrough = ["XDG_RUNTIME_DIR"]
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
dockerfile = "cross/Dockerfile.gnu"
|
||||
|
|
|
@ -26,4 +26,11 @@ pub trait SystemdClient {
|
|||
fn systemd_version(&self) -> Result<u32, SystemdClientError>;
|
||||
|
||||
fn control_cgroup_root(&self) -> Result<PathBuf, SystemdClientError>;
|
||||
|
||||
fn add_process_to_unit(
|
||||
&self,
|
||||
unit_name: &str,
|
||||
subcgroup: &str,
|
||||
pid: u32,
|
||||
) -> Result<(), SystemdClientError>;
|
||||
}
|
||||
|
|
|
@ -453,6 +453,10 @@ impl SystemdClient for DbusConnection {
|
|||
let cgroup_root = proxy.control_group()?;
|
||||
Ok(PathBuf::from(&cgroup_root))
|
||||
}
|
||||
fn add_process_to_unit(&self, unit_name: &str, subcgroup: &str, pid: u32) -> Result<()> {
|
||||
let proxy = self.create_proxy();
|
||||
proxy.attach_process(unit_name, subcgroup, pid)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -239,4 +239,11 @@ impl<'conn> Proxy<'conn> {
|
|||
v => panic!("control group expected string variant, got {:?} instead", v),
|
||||
}
|
||||
}
|
||||
pub fn attach_process(&self, name: &str, cgroup: &str, pid: u32) -> Result<()> {
|
||||
self.method_call::<_, ()>(
|
||||
"org.freedesktop.systemd1.Manager",
|
||||
"AttachProcessesToUnit",
|
||||
Some((name, cgroup, vec![pid])),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -353,6 +353,12 @@ impl CgroupManager for Manager {
|
|||
if pid.as_raw() == -1 {
|
||||
return Ok(());
|
||||
}
|
||||
if self.client.transient_unit_exists(&self.unit_name) {
|
||||
tracing::debug!("Transient unit {:?} already exists", self.unit_name);
|
||||
self.client
|
||||
.add_process_to_unit(&self.unit_name, "", pid.as_raw() as u32)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::debug!("Starting {:?}", self.unit_name);
|
||||
self.client.start_transient_unit(
|
||||
|
@ -430,8 +436,11 @@ mod tests {
|
|||
use anyhow::{Context, Result};
|
||||
|
||||
use super::*;
|
||||
use crate::systemd::dbus_native::{
|
||||
client::SystemdClient, serialize::Variant, utils::SystemdClientError,
|
||||
use crate::{
|
||||
common::DEFAULT_CGROUP_ROOT,
|
||||
systemd::dbus_native::{
|
||||
client::SystemdClient, serialize::Variant, utils::SystemdClientError,
|
||||
},
|
||||
};
|
||||
|
||||
struct TestSystemdClient {}
|
||||
|
@ -474,6 +483,15 @@ mod tests {
|
|||
fn control_cgroup_root(&self) -> Result<PathBuf, SystemdClientError> {
|
||||
Ok(PathBuf::from("/"))
|
||||
}
|
||||
|
||||
fn add_process_to_unit(
|
||||
&self,
|
||||
_unit_name: &str,
|
||||
_subcgroup: &str,
|
||||
_pid: u32,
|
||||
) -> Result<(), SystemdClientError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -528,4 +546,36 @@ mod tests {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn test_task_addition() {
|
||||
let manager = Manager::new(
|
||||
DEFAULT_CGROUP_ROOT.into(),
|
||||
":youki:test".into(),
|
||||
"youki_test_container".into(),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
let mut p1 = std::process::Command::new("sleep")
|
||||
.arg("1s")
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let p1_id = nix::unistd::Pid::from_raw(p1.id() as i32);
|
||||
let mut p2 = std::process::Command::new("sleep")
|
||||
.arg("1s")
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let p2_id = nix::unistd::Pid::from_raw(p2.id() as i32);
|
||||
manager.add_task(p1_id).unwrap();
|
||||
manager.add_task(p2_id).unwrap();
|
||||
let all_pids = manager.get_all_pids().unwrap();
|
||||
assert!(all_pids.contains(&p1_id));
|
||||
assert!(all_pids.contains(&p2_id));
|
||||
// wait till both processes are finished so we can cleanup the cgroup
|
||||
let _ = p1.wait();
|
||||
let _ = p2.wait();
|
||||
manager.remove().unwrap();
|
||||
// the remove call above should remove the dir, we just do this again
|
||||
// for contingency, and thus ignore the result
|
||||
let _ = fs::remove_dir(&manager.full_path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,6 +151,10 @@ impl CgroupManager for Manager {
|
|||
type Error = V2ManagerError;
|
||||
|
||||
fn add_task(&self, pid: Pid) -> Result<(), Self::Error> {
|
||||
if self.full_path.exists() {
|
||||
common::write_cgroup_file(self.full_path.join(CGROUP_PROCS), pid)?;
|
||||
return Ok(());
|
||||
}
|
||||
self.create_unified_cgroup(pid)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ pub struct YoukiConfig {
|
|||
}
|
||||
|
||||
impl<'a> YoukiConfig {
|
||||
pub fn from_spec(spec: &'a Spec, container_id: &str, new_user_ns: bool) -> Result<Self> {
|
||||
pub fn from_spec(spec: &'a Spec, container_id: &str) -> Result<Self> {
|
||||
Ok(YoukiConfig {
|
||||
hooks: spec.hooks().clone(),
|
||||
cgroup_path: utils::get_cgroup_path(
|
||||
|
@ -56,7 +56,6 @@ impl<'a> YoukiConfig {
|
|||
.ok_or(ConfigError::MissingLinux)?
|
||||
.cgroups_path(),
|
||||
container_id,
|
||||
new_user_ns,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
@ -106,10 +105,13 @@ mod tests {
|
|||
fn test_config_from_spec() -> Result<()> {
|
||||
let container_id = "sample";
|
||||
let spec = Spec::default();
|
||||
let config = YoukiConfig::from_spec(&spec, container_id, false)?;
|
||||
let config = YoukiConfig::from_spec(&spec, container_id)?;
|
||||
assert_eq!(&config.hooks, spec.hooks());
|
||||
dbg!(&config.cgroup_path);
|
||||
assert_eq!(config.cgroup_path, PathBuf::from(container_id));
|
||||
assert_eq!(
|
||||
config.cgroup_path,
|
||||
PathBuf::from(format!(":youki:{container_id}"))
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -118,7 +120,7 @@ mod tests {
|
|||
let container_id = "sample";
|
||||
let tmp = tempfile::tempdir().expect("create temp dir");
|
||||
let spec = Spec::default();
|
||||
let config = YoukiConfig::from_spec(&spec, container_id, false)?;
|
||||
let config = YoukiConfig::from_spec(&spec, container_id)?;
|
||||
config.save(&tmp)?;
|
||||
let act = YoukiConfig::load(&tmp)?;
|
||||
assert_eq!(act, config);
|
||||
|
|
|
@ -68,11 +68,7 @@ impl ContainerBuilderImpl {
|
|||
|
||||
fn run_container(&mut self) -> Result<Pid, LibcontainerError> {
|
||||
let linux = self.spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;
|
||||
let cgroups_path = utils::get_cgroup_path(
|
||||
linux.cgroups_path(),
|
||||
&self.container_id,
|
||||
self.user_ns_config.is_some(),
|
||||
);
|
||||
let cgroups_path = utils::get_cgroup_path(linux.cgroups_path(), &self.container_id);
|
||||
let cgroup_config = libcgroups::common::CgroupConfig {
|
||||
cgroup_path: cgroups_path,
|
||||
systemd_cgroup: self.use_systemd || self.user_ns_config.is_some(),
|
||||
|
@ -186,11 +182,7 @@ impl ContainerBuilderImpl {
|
|||
|
||||
fn cleanup_container(&self) -> Result<(), LibcontainerError> {
|
||||
let linux = self.spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;
|
||||
let cgroups_path = utils::get_cgroup_path(
|
||||
linux.cgroups_path(),
|
||||
&self.container_id,
|
||||
self.user_ns_config.is_some(),
|
||||
);
|
||||
let cgroups_path = utils::get_cgroup_path(linux.cgroups_path(), &self.container_id);
|
||||
let cmanager =
|
||||
libcgroups::common::create_cgroup_manager(libcgroups::common::CgroupConfig {
|
||||
cgroup_path: cgroups_path,
|
||||
|
|
|
@ -332,8 +332,7 @@ mod tests {
|
|||
let tmp_dir = tempfile::tempdir().unwrap();
|
||||
use oci_spec::runtime::Spec;
|
||||
let spec = Spec::default();
|
||||
let config =
|
||||
YoukiConfig::from_spec(&spec, "123", false).context("convert spec to config")?;
|
||||
let config = YoukiConfig::from_spec(&spec, "123").context("convert spec to config")?;
|
||||
config.save(tmp_dir.path()).context("save config")?;
|
||||
|
||||
let container = Container {
|
||||
|
|
|
@ -88,7 +88,7 @@ impl InitContainerBuilder {
|
|||
|
||||
let user_ns_config = UserNamespaceConfig::new(&spec)?;
|
||||
|
||||
let config = YoukiConfig::from_spec(&spec, container.id(), user_ns_config.is_some())?;
|
||||
let config = YoukiConfig::from_spec(&spec, container.id())?;
|
||||
config.save(&container_dir).map_err(|err| {
|
||||
tracing::error!(?container_dir, "failed to save config: {}", err);
|
||||
err
|
||||
|
|
|
@ -327,9 +327,17 @@ impl TenantContainerBuilder {
|
|||
|
||||
let init_process = procfs::process::Process::new(container_pid.as_raw())?;
|
||||
let ns = self.get_namespaces(init_process.namespaces()?.0)?;
|
||||
let linux = LinuxBuilder::default().namespaces(ns).build()?;
|
||||
|
||||
// it should never be the case that linux is not present in spec
|
||||
let spec_linux = spec.linux().as_ref().unwrap();
|
||||
let mut linux_builder = LinuxBuilder::default().namespaces(ns);
|
||||
|
||||
if let Some(ref cgroup_path) = spec_linux.cgroups_path() {
|
||||
linux_builder = linux_builder.cgroups_path(cgroup_path.clone());
|
||||
}
|
||||
let linux = linux_builder.build()?;
|
||||
spec.set_process(Some(process)).set_linux(Some(linux));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -147,17 +147,10 @@ pub fn get_user_home(uid: u32) -> Option<PathBuf> {
|
|||
}
|
||||
|
||||
/// If None, it will generate a default path for cgroups.
|
||||
pub fn get_cgroup_path(
|
||||
cgroups_path: &Option<PathBuf>,
|
||||
container_id: &str,
|
||||
new_user_ns: bool,
|
||||
) -> PathBuf {
|
||||
pub fn get_cgroup_path(cgroups_path: &Option<PathBuf>, container_id: &str) -> PathBuf {
|
||||
match cgroups_path {
|
||||
Some(cpath) => cpath.clone(),
|
||||
None => match new_user_ns {
|
||||
false => PathBuf::from(container_id),
|
||||
true => PathBuf::from(format!(":youki:{container_id}")),
|
||||
},
|
||||
None => PathBuf::from(format!(":youki:{container_id}")),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,11 +316,11 @@ mod tests {
|
|||
fn test_get_cgroup_path() {
|
||||
let cid = "sample_container_id";
|
||||
assert_eq!(
|
||||
get_cgroup_path(&None, cid, false),
|
||||
PathBuf::from("sample_container_id")
|
||||
get_cgroup_path(&None, cid),
|
||||
PathBuf::from(":youki:sample_container_id")
|
||||
);
|
||||
assert_eq!(
|
||||
get_cgroup_path(&Some(PathBuf::from("/youki")), cid, false),
|
||||
get_cgroup_path(&Some(PathBuf::from("/youki")), cid),
|
||||
PathBuf::from("/youki")
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,3 +13,6 @@ RUN dpkg --add-architecture ${CROSS_DEB_ARCH} && \
|
|||
zlib1g-dev:${CROSS_DEB_ARCH} \
|
||||
# dependencies to build wasmedge-sys
|
||||
libzstd-dev:${CROSS_DEB_ARCH}
|
||||
|
||||
COPY hack/busctl.sh /bin/busctl
|
||||
RUN chmod +x /bin/busctl
|
||||
|
|
|
@ -22,6 +22,9 @@ ENV LIBSECCOMP_LIB_PATH="${CROSS_SYSROOT}/lib"
|
|||
ENV WASMEDGE_DEP_STDCXX_LINK_TYPE="static"
|
||||
ENV WASMEDGE_DEP_STDCXX_LIB_PATH="${CROSS_SYSROOT}/lib"
|
||||
|
||||
COPY hack/busctl.sh /bin/busctl
|
||||
RUN chmod +x /bin/busctl
|
||||
|
||||
# wasmedge-sys (through llvm) needs some symbols defined in libgcc
|
||||
RUN mkdir /.cargo && cat <<'EOF' > /.cargo/config.toml
|
||||
[target.'cfg(target_env = "musl")']
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
|
||||
# This hack script is the dummy busctl command used when running tests with cross containers.
|
||||
|
||||
# The issue is that we cannot run systemd or dbus inside the test container without a lot
|
||||
# of hacks. For one specific test - test_task_addition, we need to check that the task
|
||||
# addition via systemd manager works. We mount the host dbus socket in the test container, so
|
||||
# dbus calls work, but for the initial authentication, we use busctl which needs dbus and systemd
|
||||
# to be present and running. So instead of doing all that, we simply run the container with the
|
||||
# actual test running user's uid/gid and here we echo the only relevant line from busctl's
|
||||
# output, using id to get the uid. This is a hack, but less complex than actually setting up
|
||||
# and running the systemd+dbus inside the container.
|
||||
|
||||
echo "OwnerUID=$(id -u)"
|
|
@ -56,7 +56,11 @@ if [ "$CARGO" == "cross" ]; then
|
|||
|
||||
# mount run to have access to dbus socket.
|
||||
# mount /tmp so as shared for test_make_parent_mount_private
|
||||
export CROSS_CONTAINER_OPTS="--privileged -v/run:/run --mount=type=bind,source=/tmp,destination=/tmp,bind-propagation=shared"
|
||||
# Then there are few "hacks" specificallt for test_task_addition
|
||||
# run with user same as the invoking user, so that the dbus is connected with correct user
|
||||
# we want pid ns of host, because we will be connecting to the host dbus, and it needs task pid from host
|
||||
# finally we need to mount the cgroup as read-only, as we need that to check if the tasks are correctly added
|
||||
export CROSS_CONTAINER_OPTS="--privileged --user `id -u`:`id -g` --pid=host -v /sys/fs/cgroup:/sys/fs/cgroup:ro -v/run:/run --mount=type=bind,source=/tmp,destination=/tmp,bind-propagation=shared"
|
||||
fi
|
||||
|
||||
if [ "$1" == "--print-target-dir" ]; then
|
||||
|
|
|
@ -99,6 +99,7 @@ fn main() -> Result<()> {
|
|||
let cgroup_v1_memory = cgroups::memory::get_test_group();
|
||||
let cgroup_v1_network = cgroups::network::get_test_group();
|
||||
let cgroup_v1_blkio = cgroups::blkio::get_test_group();
|
||||
let cgroup_v1_relative_blkio = cgroups::relative_blkio::get_test_group();
|
||||
let seccomp = get_seccomp_test();
|
||||
let seccomp_notify = get_seccomp_notify_test();
|
||||
let ro_paths = get_ro_paths_test();
|
||||
|
@ -122,6 +123,7 @@ fn main() -> Result<()> {
|
|||
tm.add_test_group(Box::new(cgroup_v1_memory));
|
||||
tm.add_test_group(Box::new(cgroup_v1_network));
|
||||
tm.add_test_group(Box::new(cgroup_v1_blkio));
|
||||
tm.add_test_group(Box::new(cgroup_v1_relative_blkio));
|
||||
tm.add_test_group(Box::new(seccomp));
|
||||
tm.add_test_group(Box::new(seccomp_notify));
|
||||
tm.add_test_group(Box::new(ro_paths));
|
||||
|
|
|
@ -8,6 +8,7 @@ pub mod cpu;
|
|||
pub mod memory;
|
||||
pub mod network;
|
||||
pub mod pids;
|
||||
pub mod relative_blkio;
|
||||
|
||||
pub fn cleanup_v1() -> Result<()> {
|
||||
for subsystem in list_subsystem_mount_points()? {
|
||||
|
|
|
@ -0,0 +1,529 @@
|
|||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use oci_spec::runtime::{
|
||||
LinuxBlockIo, LinuxBlockIoBuilder, LinuxBuilder, LinuxResourcesBuilder,
|
||||
LinuxThrottleDeviceBuilder, LinuxWeightDeviceBuilder, Spec, SpecBuilder,
|
||||
};
|
||||
use test_framework::{test_result, ConditionalTest, TestGroup, TestResult};
|
||||
|
||||
use crate::utils::{
|
||||
test_outside_container,
|
||||
test_utils::{check_container_created, CGROUP_ROOT},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct WeightDevice {
|
||||
major: i64,
|
||||
minor: i64,
|
||||
weight: Option<u16>,
|
||||
leaf_weight: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ThrottleDevice {
|
||||
major: i64,
|
||||
minor: i64,
|
||||
rate: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct BlockIO {
|
||||
weight: u16,
|
||||
leaf_weight: u16,
|
||||
weight_devices: Vec<WeightDevice>,
|
||||
throttle_read_bps_devices: Vec<ThrottleDevice>,
|
||||
throttle_write_bps_devices: Vec<ThrottleDevice>,
|
||||
throttle_read_iops_devices: Vec<ThrottleDevice>,
|
||||
throttle_write_iops_devices: Vec<ThrottleDevice>,
|
||||
}
|
||||
|
||||
fn can_run() -> bool {
|
||||
Path::new("/sys/fs/cgroup/blkio").exists()
|
||||
}
|
||||
|
||||
fn supports_weight() -> bool {
|
||||
Path::new("/sys/fs/cgroup/blkio/blkio.weight").exists()
|
||||
}
|
||||
|
||||
fn supports_weight_devices() -> bool {
|
||||
Path::new("/sys/fs/cgroup/blkio/blkio.weight_devices").exists()
|
||||
}
|
||||
|
||||
fn supports_throttle_bps() -> bool {
|
||||
Path::new("/sys/fs/cgroup/blkio/blkio.throttle.read_bps_device").exists()
|
||||
}
|
||||
|
||||
fn supports_throttle_iops() -> bool {
|
||||
Path::new("/sys/fs/cgroup/blkio/blkio.throttle.read_iops_device").exists()
|
||||
}
|
||||
|
||||
fn parse_device_data<'a>(device_type: &'static str, line: &'a str) -> Result<(i64, i64, &'a str)> {
|
||||
let (device_id, value) = line
|
||||
.split_once(' ')
|
||||
.with_context(|| format!("invalid {device_type} device format : found {line}"))?;
|
||||
let (major_str, minor_str) = device_id.split_once(':').with_context(|| {
|
||||
format!("invalid major-minor number format for {device_type} device : found {device_id}")
|
||||
})?;
|
||||
|
||||
let major: i64 = major_str.parse().with_context(|| {
|
||||
format!("Error in parsing {device_type} device major number : found {major_str}")
|
||||
})?;
|
||||
let minor: i64 = minor_str.parse().with_context(|| {
|
||||
format!("Error in parsing {device_type} device minor number : found {minor_str}")
|
||||
})?;
|
||||
|
||||
Ok((major, minor, value))
|
||||
}
|
||||
|
||||
fn create_spec(cgroup_name: &str, block_io: LinuxBlockIo) -> Result<Spec> {
|
||||
let spec = SpecBuilder::default()
|
||||
.linux(
|
||||
LinuxBuilder::default()
|
||||
.cgroups_path(Path::new("testdir/runtime-test/container").join(cgroup_name))
|
||||
.resources(
|
||||
LinuxResourcesBuilder::default()
|
||||
.block_io(block_io)
|
||||
.build()
|
||||
.context("failed to build resource spec")?,
|
||||
)
|
||||
.build()
|
||||
.context("failed to build linux spec")?,
|
||||
)
|
||||
.build()
|
||||
.context("failed to build spec")?;
|
||||
|
||||
Ok(spec)
|
||||
}
|
||||
|
||||
fn get_blkio_data(path: &Path) -> Result<BlockIO> {
|
||||
let mut device = BlockIO::default();
|
||||
|
||||
if supports_weight() {
|
||||
let weight_path = path.join("blkio.weight");
|
||||
let weight_string = fs::read_to_string(&weight_path)
|
||||
.with_context(|| format!("error in reading block io weight from {weight_path:?}"))?;
|
||||
device.weight = weight_string
|
||||
.parse()
|
||||
.with_context(|| format!("error in parsing block io weight : found {weight_string}"))?;
|
||||
|
||||
let leaf_weight_path = path.join("blkio.leaf_weight");
|
||||
let leaf_weight_string = fs::read_to_string(&leaf_weight_path).with_context(|| {
|
||||
format!("error in reading block io leaf weight from {leaf_weight_path:?}")
|
||||
})?;
|
||||
device.leaf_weight = leaf_weight_string.parse().with_context(|| {
|
||||
format!("error in parsing block io weight : found {leaf_weight_string}")
|
||||
})?;
|
||||
}
|
||||
|
||||
if supports_weight_devices() {
|
||||
let device_weight_path = path.join("blkio.weight_device");
|
||||
let device_weight_string = fs::read_to_string(&device_weight_path).with_context(|| {
|
||||
format!("error in reading block io weight device from {device_weight_path:?}")
|
||||
})?;
|
||||
let mut weight_devices = Vec::new();
|
||||
for line in device_weight_string.lines() {
|
||||
let (major, minor, weight_str) = parse_device_data("weight", line)?;
|
||||
weight_devices.push(WeightDevice {
|
||||
major,
|
||||
minor,
|
||||
weight: Some(weight_str.parse().with_context(|| {
|
||||
format!("error in parsing weight of weight device, found {weight_str}")
|
||||
})?),
|
||||
leaf_weight: None,
|
||||
});
|
||||
}
|
||||
|
||||
let device_leaf_weight_path = path.join("blkio.leaf_weight_device");
|
||||
let device_leaf_weight_string =
|
||||
fs::read_to_string(&device_leaf_weight_path).with_context(|| {
|
||||
format!(
|
||||
"error in reading block io leaf weight device from {device_leaf_weight_path:?}"
|
||||
)
|
||||
})?;
|
||||
|
||||
for line in device_leaf_weight_string.lines() {
|
||||
let (major, minor, weight_str) = parse_device_data("weight", line)?;
|
||||
let leaf_weight: u16 = weight_str.parse().with_context(|| {
|
||||
format!("error in parsing leaf weight of weight device : found {weight_str}")
|
||||
})?;
|
||||
let mut found = false;
|
||||
for dev in &mut weight_devices {
|
||||
if dev.major == major && dev.minor == minor {
|
||||
dev.leaf_weight = Some(leaf_weight);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
weight_devices.push(WeightDevice {
|
||||
major,
|
||||
minor,
|
||||
weight: None,
|
||||
leaf_weight: Some(leaf_weight),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
device.weight_devices = weight_devices;
|
||||
}
|
||||
|
||||
if supports_throttle_bps() {
|
||||
let throttle_read_bps_path = path.join("blkio.throttle.read_bps_device");
|
||||
let throttle_read_bps_string =
|
||||
fs::read_to_string(&throttle_read_bps_path).with_context(|| {
|
||||
format!(
|
||||
"error in reading block io read bps device from {throttle_read_bps_path:?}"
|
||||
)
|
||||
})?;
|
||||
let mut throttle_devices = Vec::new();
|
||||
for line in throttle_read_bps_string.lines() {
|
||||
let (major, minor, rate_str) = parse_device_data("throttle read bps", line)?;
|
||||
throttle_devices.push(ThrottleDevice {
|
||||
major,
|
||||
minor,
|
||||
rate: rate_str.parse().with_context(|| {
|
||||
format!("error in parsing throttle read bps rate : found {rate_str}")
|
||||
})?,
|
||||
});
|
||||
}
|
||||
device.throttle_read_bps_devices = throttle_devices;
|
||||
|
||||
let throttle_write_bps_path = path.join("blkio.throttle.write_bps_device");
|
||||
let throttle_write_bps_string =
|
||||
fs::read_to_string(&throttle_write_bps_path).with_context(|| {
|
||||
format!(
|
||||
"error in reading block io write bps device from {throttle_write_bps_path:?}"
|
||||
)
|
||||
})?;
|
||||
let mut throttle_devices = Vec::new();
|
||||
for line in throttle_write_bps_string.lines() {
|
||||
let (major, minor, rate_str) = parse_device_data("throttle write bps", line)?;
|
||||
throttle_devices.push(ThrottleDevice {
|
||||
major,
|
||||
minor,
|
||||
rate: rate_str.parse().with_context(|| {
|
||||
format!("error in parsing throttle write bps rate : found {rate_str}")
|
||||
})?,
|
||||
});
|
||||
}
|
||||
device.throttle_write_bps_devices = throttle_devices;
|
||||
}
|
||||
|
||||
if supports_throttle_iops() {
|
||||
let throttle_read_iops_path = path.join("blkio.throttle.read_iops_device");
|
||||
let throttle_read_iops_string =
|
||||
fs::read_to_string(&throttle_read_iops_path).with_context(|| {
|
||||
format!(
|
||||
"error in reading block io read iops device from {throttle_read_iops_path:?}"
|
||||
)
|
||||
})?;
|
||||
let mut throttle_devices = Vec::new();
|
||||
for line in throttle_read_iops_string.lines() {
|
||||
let (major, minor, rate_str) = parse_device_data("throttle read iops", line)?;
|
||||
throttle_devices.push(ThrottleDevice {
|
||||
major,
|
||||
minor,
|
||||
rate: rate_str.parse().with_context(|| {
|
||||
format!("error in parsing throttle read iops rate : found {rate_str}")
|
||||
})?,
|
||||
});
|
||||
}
|
||||
device.throttle_read_iops_devices = throttle_devices;
|
||||
|
||||
let throttle_write_iops_path = path.join("blkio.throttle.write_iops_device");
|
||||
let throttle_write_iops_string = fs::read_to_string(&throttle_write_iops_path)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"error in reading block io write iops device from {throttle_write_iops_path:?}"
|
||||
)
|
||||
})?;
|
||||
let mut throttle_devices = Vec::new();
|
||||
for line in throttle_write_iops_string.lines() {
|
||||
let (major, minor, rate_str) = parse_device_data("throttle write iop", line)?;
|
||||
throttle_devices.push(ThrottleDevice {
|
||||
major,
|
||||
minor,
|
||||
rate: rate_str.parse().with_context(|| {
|
||||
format!("error in parsing throttle write iops rate : found {rate_str}")
|
||||
})?,
|
||||
});
|
||||
}
|
||||
device.throttle_write_iops_devices = throttle_devices;
|
||||
}
|
||||
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
fn validate_block_io(cgroup_name: &str, spec: &Spec) -> Result<()> {
|
||||
let cgroup_path = PathBuf::from(CGROUP_ROOT)
|
||||
.join("blkio/runtime-test")
|
||||
.join(cgroup_name);
|
||||
let block_io = get_blkio_data(&cgroup_path)?;
|
||||
|
||||
let resources = spec.linux().as_ref().unwrap().resources().as_ref().unwrap();
|
||||
let spec_block_io = resources.block_io().as_ref().unwrap();
|
||||
if supports_weight() {
|
||||
if spec_block_io.weight().is_none() {
|
||||
bail!("spec block io weight is none");
|
||||
}
|
||||
if spec_block_io.weight().unwrap() != block_io.weight {
|
||||
bail!(
|
||||
"block io weight is set incorrectly, expected {}, actual {}",
|
||||
spec_block_io.weight().unwrap(),
|
||||
block_io.weight,
|
||||
);
|
||||
}
|
||||
if spec_block_io.leaf_weight().is_none() {
|
||||
bail!("spec block io leaf weight is none");
|
||||
}
|
||||
if spec_block_io.leaf_weight().unwrap() != block_io.leaf_weight {
|
||||
bail!(
|
||||
"block io leaf weight is set incorrectly, expected {}, actual {}",
|
||||
spec_block_io.leaf_weight().unwrap(),
|
||||
block_io.leaf_weight,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if supports_weight_devices() {
|
||||
for spec_device in spec_block_io.weight_device().as_ref().unwrap() {
|
||||
let spec_major = spec_device.major();
|
||||
let spec_minor = spec_device.minor();
|
||||
let mut found = false;
|
||||
for device in &block_io.weight_devices {
|
||||
if device.major == spec_major && device.minor == spec_minor {
|
||||
found = true;
|
||||
if device.weight != spec_device.weight() {
|
||||
bail!(
|
||||
"blkio weight is set incorrectly for device {}:{}, expected {:?}, found {:?}",
|
||||
spec_major,
|
||||
spec_minor,
|
||||
spec_device.weight(),
|
||||
device.weight
|
||||
);
|
||||
}
|
||||
if device.leaf_weight != spec_device.leaf_weight() {
|
||||
bail!(
|
||||
"blkio leaf weight is set incorrectly for device {}:{}, expected {:?}, found {:?}",
|
||||
spec_major,
|
||||
spec_minor,
|
||||
spec_device.leaf_weight(),
|
||||
device.leaf_weight
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
bail!(
|
||||
"blkio weight device {}:{} not found, exists in spec",
|
||||
spec_major,
|
||||
spec_minor
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if supports_throttle_bps() {
|
||||
for spec_device in spec_block_io.throttle_read_bps_device().as_ref().unwrap() {
|
||||
let spec_major = spec_device.major();
|
||||
let spec_minor = spec_device.minor();
|
||||
let mut found = false;
|
||||
for device in &block_io.throttle_read_bps_devices {
|
||||
if device.major == spec_major && device.minor == spec_minor {
|
||||
found = true;
|
||||
if device.rate != spec_device.rate() {
|
||||
bail!(
|
||||
"blkio throttle read bps rate is set incorrectly for device {}:{}, expected {}, found {}",
|
||||
spec_major,
|
||||
spec_minor,
|
||||
spec_device.rate(),
|
||||
device.rate
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
bail!(
|
||||
"blkio throttle read bps device {}:{} not found, exists in spec",
|
||||
spec_major,
|
||||
spec_minor
|
||||
);
|
||||
}
|
||||
}
|
||||
for spec_device in spec_block_io.throttle_write_bps_device().as_ref().unwrap() {
|
||||
let spec_major = spec_device.major();
|
||||
let spec_minor = spec_device.minor();
|
||||
let mut found = false;
|
||||
for device in &block_io.throttle_write_bps_devices {
|
||||
if device.major == spec_major && device.minor == spec_minor {
|
||||
found = true;
|
||||
if device.rate != spec_device.rate() {
|
||||
bail!(
|
||||
"blkio throttle write bps rate is set incorrectly for device {}:{}, expected {}, found {}",
|
||||
spec_major,
|
||||
spec_minor,
|
||||
spec_device.rate(),
|
||||
device.rate
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
bail!(
|
||||
"blkio throttle write bps device {}:{} not found, exists in spec",
|
||||
spec_major,
|
||||
spec_minor
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if supports_throttle_iops() {
|
||||
for spec_device in spec_block_io.throttle_read_iops_device().as_ref().unwrap() {
|
||||
let spec_major = spec_device.major();
|
||||
let spec_minor = spec_device.minor();
|
||||
let mut found = false;
|
||||
for device in &block_io.throttle_read_iops_devices {
|
||||
if device.major == spec_major && device.minor == spec_minor {
|
||||
found = true;
|
||||
if device.rate != spec_device.rate() {
|
||||
bail!(
|
||||
"blkio throttle read iops rate is set incorrectly for device {}:{}, expected {}, found {}",
|
||||
spec_major,
|
||||
spec_minor,
|
||||
spec_device.rate(),
|
||||
device.rate
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
bail!(
|
||||
"blkio throttle read iops device {}:{} not found, exists in spec",
|
||||
spec_major,
|
||||
spec_minor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for spec_device in spec_block_io.throttle_write_iops_device().as_ref().unwrap() {
|
||||
let spec_major = spec_device.major();
|
||||
let spec_minor = spec_device.minor();
|
||||
let mut found = false;
|
||||
for device in &block_io.throttle_write_iops_devices {
|
||||
if device.major == spec_major && device.minor == spec_minor {
|
||||
found = true;
|
||||
if device.rate != spec_device.rate() {
|
||||
bail!(
|
||||
"blkio throttle write iops rate is set incorrectly for device {}:{}, expected {}, found {}",
|
||||
spec_major,
|
||||
spec_minor,
|
||||
spec_device.rate(),
|
||||
device.rate
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
bail!(
|
||||
"blkio throttle write iops device {}:{} not found, exists in spec",
|
||||
spec_major,
|
||||
spec_minor
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_relative_blkio(test_name: &str, rate: u64, empty: bool) -> TestResult {
|
||||
let weight: u16 = 500;
|
||||
let leaf_weight: u16 = 300;
|
||||
let major: i64 = 8;
|
||||
let minor: i64 = 0;
|
||||
|
||||
let mut block_io_builder = LinuxBlockIoBuilder::default();
|
||||
|
||||
if !empty && supports_weight() {
|
||||
block_io_builder = block_io_builder.weight(weight).leaf_weight(leaf_weight)
|
||||
}
|
||||
if supports_weight_devices() {
|
||||
block_io_builder = block_io_builder.weight_device(vec![
|
||||
LinuxWeightDeviceBuilder::default()
|
||||
.major(major)
|
||||
.minor(minor)
|
||||
.weight(weight)
|
||||
.build()
|
||||
.unwrap(),
|
||||
LinuxWeightDeviceBuilder::default()
|
||||
.major(major)
|
||||
.minor(minor)
|
||||
.leaf_weight(leaf_weight)
|
||||
.build()
|
||||
.unwrap(),
|
||||
])
|
||||
}
|
||||
if supports_throttle_bps() {
|
||||
block_io_builder = block_io_builder
|
||||
.throttle_read_bps_device(vec![LinuxThrottleDeviceBuilder::default()
|
||||
.major(major)
|
||||
.minor(minor)
|
||||
.rate(rate)
|
||||
.build()
|
||||
.unwrap()])
|
||||
.throttle_write_bps_device(vec![LinuxThrottleDeviceBuilder::default()
|
||||
.major(major)
|
||||
.minor(minor)
|
||||
.rate(rate)
|
||||
.build()
|
||||
.unwrap()])
|
||||
}
|
||||
if supports_throttle_iops() {
|
||||
block_io_builder = block_io_builder
|
||||
.throttle_read_iops_device(vec![LinuxThrottleDeviceBuilder::default()
|
||||
.major(major)
|
||||
.minor(minor)
|
||||
.rate(rate)
|
||||
.build()
|
||||
.unwrap()])
|
||||
.throttle_write_iops_device(vec![LinuxThrottleDeviceBuilder::default()
|
||||
.major(major)
|
||||
.minor(minor)
|
||||
.rate(rate)
|
||||
.build()
|
||||
.unwrap()]);
|
||||
}
|
||||
let spec = create_spec(
|
||||
test_name,
|
||||
block_io_builder
|
||||
.build()
|
||||
.context("failed to build block io spec")
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
test_outside_container(spec.clone(), &|data| {
|
||||
test_result!(check_container_created(&data));
|
||||
test_result!(validate_block_io(test_name, &spec));
|
||||
TestResult::Passed
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_test_group() -> TestGroup {
|
||||
let mut test_group = TestGroup::new("cgroup_v1_relative_blkio");
|
||||
let non_empty_100kb = ConditionalTest::new(
|
||||
"non_empty_100kb",
|
||||
Box::new(can_run),
|
||||
Box::new(|| test_relative_blkio("non_empty_100kb", 102400, false)),
|
||||
);
|
||||
test_group.add(vec![Box::new(non_empty_100kb)]);
|
||||
test_group
|
||||
}
|
|
@ -10,9 +10,35 @@ podman rm --force --ignore create-test # remove if existing
|
|||
podman create --runtime $runtime --name create-test hello-world
|
||||
log=$(podman start -a create-test)
|
||||
echo $log | grep "This message shows that your installation appears to be working correctly"
|
||||
podman rm create-test
|
||||
podman rm --force --ignore create-test
|
||||
|
||||
rand=$(head -c 10 /dev/random | base64)
|
||||
|
||||
log=$(podman run --runtime $runtime fedora echo "$rand")
|
||||
echo $log | grep $rand
|
||||
echo $log | grep $rand
|
||||
|
||||
podman kill exec-test || true # ignore failure for killing
|
||||
podman rm --force --ignore exec-test
|
||||
podman run -d --runtime $runtime --name exec-test busybox sleep 10m
|
||||
|
||||
rand=$(head -c 10 /dev/random | base64)
|
||||
|
||||
log=$(podman exec --runtime $runtime exec-test echo "$rand")
|
||||
echo $log | grep $rand
|
||||
|
||||
CGROUP_SUB_PATH=$(podman inspect exec-test | jq .[0].State.CgroupPath | tr -d "\"")
|
||||
CGROUP_PATH="/sys/fs/cgroup$CGROUP_SUB_PATH/cgroup.procs"
|
||||
|
||||
# assert we have exactly one process in the cgroup
|
||||
test $(cat $CGROUP_PATH | wc -l) -eq 1
|
||||
# assert pid match
|
||||
test $(cat $CGROUP_PATH) -eq $(podman inspect exec-test | jq .[0].State.Pid)
|
||||
|
||||
podman exec -d --runtime $runtime exec-test sleep 5m
|
||||
|
||||
# we cannot exactly check the pid of tenant here, instead just check that there are
|
||||
# two processes in the same cgroup now
|
||||
test $(cat $CGROUP_PATH | wc -l) -eq 2
|
||||
|
||||
podman kill exec-test
|
||||
podman rm --force --ignore exec-test
|
||||
|
|
Loading…
Reference in New Issue