Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ resolver = "2"
members = ["crates/*", "tests/contest/*", "tools/*"]
exclude = ["experiment/seccomp", "experiment/selinux"]

# NOTE: temp for developing
[patch.crates-io]
oci-spec = { git = "https://github.com/youki-dev/oci-spec-rs", branch = "main" }

[workspace.dependencies]
anyhow = "1.0.102"
caps = "0.5.6"
Expand Down
22 changes: 16 additions & 6 deletions crates/libcontainer/src/container/builder_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use super::{Container, ContainerStatus};
use crate::error::{CreateContainerError, LibcontainerError, MissingSpecError};
use crate::notify_socket::NotifyListener;
use crate::process::args::{ContainerArgs, ContainerType};
use crate::process::intel_rdt::delete_resctrl_subdirectory;
use crate::process::intel_rdt::{
delete_resctrl_monitoring_subdirectory, delete_resctrl_subdirectory,
};
use crate::process::{self};
use crate::syscall::syscall::SyscallType;
use crate::user_ns::UserNamespaceConfig;
Expand Down Expand Up @@ -192,7 +194,7 @@ impl ContainerBuilderImpl {
pid_file: self.pid_file.to_owned(),
};

let (init_pid, need_to_clean_up_intel_rdt_dir) =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation behind changing the return value here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My motivation for changing the return value to pass intel_rdt_dir and intel_rdt_monitoring_dir and storing these paths directly in state.json was to closely mirror how runc implements this.

runc explicitly stores both IntelRdtPath and IntelRdtMonPath directly in its container state object, and relies on those saved paths when it needs to clean up the directories during teardown.

You can see runc storing the paths in the state here:
https://github.com/opencontainers/runc/blob/main/libcontainer/container_linux.go#L79

let (init_pid, intel_rdt_dir, intel_rdt_monitoring_dir) =
process::container_main_process::container_main_process(&container_args).map_err(
|err| {
tracing::error!("failed to run container process {}", err);
Expand All @@ -206,7 +208,8 @@ impl ContainerBuilderImpl {
.set_status(ContainerStatus::Created)
.set_creator(nix::unistd::geteuid().as_raw())
.set_pid(init_pid.as_raw())
.set_clean_up_intel_rdt_directory(need_to_clean_up_intel_rdt_dir)
.set_intel_rdt_dir(intel_rdt_dir)
.set_intel_rdt_monitoring_dir(intel_rdt_monitoring_dir)
.save()?;
}

Expand All @@ -231,9 +234,16 @@ impl ContainerBuilderImpl {
}

if let Some(container) = &self.container {
if let Some(true) = container.clean_up_intel_rdt_subdirectory() {
if let Err(e) = delete_resctrl_subdirectory(container.id()) {
tracing::error!(id = ?container.id(), error = ?e, "failed to delete resctrl subdirectory");
if let Some(path) = container.intel_rdt_monitoring_dir() {
if let Err(e) = delete_resctrl_monitoring_subdirectory(path) {
tracing::error!(path = ?path, error = ?e, "failed to delete resctrl monitoring subdirectory");
errors.push(e.to_string());
}
}

if let Some(path) = container.intel_rdt_dir() {
if let Err(e) = delete_resctrl_subdirectory(path) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it throws an error when the file doesn't exist, it might be better not to return an error for ENOENT.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will update the logic to handle the ErrorKind::NotFound case gracefully so that if the directory is already gone, it silently succeeds instead of throwing an error during teardown.

tracing::error!(path = ?path, error = ?e, "failed to delete resctrl subdirectory");
errors.push(e.to_string());
}
}
Expand Down
17 changes: 13 additions & 4 deletions crates/libcontainer/src/container/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,22 @@ impl Container {
self
}

pub fn set_clean_up_intel_rdt_directory(&mut self, clean_up: bool) -> &mut Self {
self.state.clean_up_intel_rdt_subdirectory = Some(clean_up);
pub fn set_intel_rdt_dir(&mut self, dir: Option<PathBuf>) -> &mut Self {
self.state.intel_rdt_dir = dir;
self
}

pub fn clean_up_intel_rdt_subdirectory(&self) -> Option<bool> {
self.state.clean_up_intel_rdt_subdirectory
pub fn intel_rdt_dir(&self) -> Option<&PathBuf> {
self.state.intel_rdt_dir.as_ref()
}

pub fn set_intel_rdt_monitoring_dir(&mut self, dir: Option<PathBuf>) -> &mut Self {
self.state.intel_rdt_monitoring_dir = dir;
self
}

pub fn intel_rdt_monitoring_dir(&self) -> Option<&PathBuf> {
self.state.intel_rdt_monitoring_dir.as_ref()
}

pub fn status(&self) -> ContainerStatus {
Expand Down
81 changes: 48 additions & 33 deletions crates/libcontainer/src/container/container_delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use super::{Container, ContainerStatus};
use crate::config::YoukiConfig;
use crate::error::LibcontainerError;
use crate::hooks;
use crate::process::intel_rdt::delete_resctrl_subdirectory;
use crate::process::intel_rdt::{
delete_resctrl_monitoring_subdirectory, delete_resctrl_subdirectory,
};

impl Container {
/// Deletes the container
Expand Down Expand Up @@ -69,41 +71,12 @@ impl Container {
// Once reached here, the container is verified that it can be deleted.
debug_assert!(self.status().can_delete());

if let Some(true) = &self.clean_up_intel_rdt_subdirectory() {
if let Err(err) = delete_resctrl_subdirectory(self.id()) {
tracing::warn!(
"failed to delete resctrl subdirectory due to: {err:?}, continue to delete"
);
}
}

let mut config_opt = None;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation behind changing config_opt? Personally, I don't see the necessity for this change.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The motivation behind introducing config_opt was simply to decouple the Intel RDT cleanup logic from the YoukiConfig processing block.

I wanted to group the RDT teardown operations clearly before the cgroup and hooks teardown, similar to runc's teardown order.

if self.root.exists() {
match YoukiConfig::load(&self.root) {
Ok(config) => {
tracing::debug!("config: {:?}", config);

// remove the cgroup created for the container
// check https://man7.org/linux/man-pages/man7/cgroups.7.html
// creating and removing cgroups section for more information on cgroups
let cmanager = libcgroups::common::create_cgroup_manager(
libcgroups::common::CgroupConfig {
cgroup_path: config.cgroup_path.to_owned(),
systemd_cgroup: self.systemd(),
container_name: self.id().to_string(),
},
)?;
cmanager.remove().map_err(|err| {
tracing::error!(cgroup_path = ?config.cgroup_path, "failed to remove cgroup due to: {err:?}");
err
})?;

if let Some(hooks) = config.hooks.as_ref() {
hooks::run_hooks(hooks.poststop().as_ref(), Some(&self.state), None, None)
.map_err(|err| {
tracing::error!(err = ?err, "failed to run post stop hooks");
err
})?;
}
tracing::debug!("config: {config:?}");
config_opt = Some(config);
}
Err(err) => {
// There is a brief window where the container state is
Expand All @@ -115,7 +88,49 @@ impl Container {
);
}
}
}

if let Some(path) = self.intel_rdt_monitoring_dir() {
if let Err(err) = delete_resctrl_monitoring_subdirectory(path) {
tracing::warn!(
"failed to delete resctrl monitoring subdirectory due to: {err:?}, continue to delete"
);
}
}

if let Some(path) = self.intel_rdt_dir() {
if let Err(err) = delete_resctrl_subdirectory(path) {
tracing::warn!(
"failed to delete resctrl subdirectory due to: {err:?}, continue to delete"
);
}
}

if let Some(config) = config_opt {
// remove the cgroup created for the container
// check https://man7.org/linux/man-pages/man7/cgroups.7.html
// creating and removing cgroups section for more information on cgroups
let cmanager =
libcgroups::common::create_cgroup_manager(libcgroups::common::CgroupConfig {
cgroup_path: config.cgroup_path.to_owned(),
systemd_cgroup: self.systemd(),
container_name: self.id().to_string(),
})?;
cmanager.remove().map_err(|err| {
tracing::error!(cgroup_path = ?config.cgroup_path, "failed to remove cgroup due to: {err:?}");
err
})?;

if let Some(hooks) = config.hooks.as_ref() {
hooks::run_hooks(hooks.poststop().as_ref(), Some(&self.state), None, None)
.map_err(|err| {
tracing::error!(err = ?err, "failed to run post stop hooks");
err
})?;
}
}

if self.root.exists() {
// remove the directory storing container state
tracing::debug!("remove dir {:?}", self.root);
fs::remove_dir_all(&self.root).map_err(|err| {
Expand Down
13 changes: 10 additions & 3 deletions crates/libcontainer/src/container/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,14 @@ pub struct State {
pub creator: Option<u32>,
// Specifies if systemd should be used to manage cgroups
pub use_systemd: bool,
// Specifies if the Intel RDT subdirectory needs be cleaned up.
pub clean_up_intel_rdt_subdirectory: Option<bool>,
// Specifies the Intel RDT subdirectory path that needs to be cleaned up.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic here implies that this path exists when a deletion is required. Could you rename it to something that makes this intent clearer? Also, what was the motivation behind using a path instead of a bool as the state's responsibility?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To answer the second part first regarding the motivation for using a path instead of a boolean flag:

This was done to match runc's approach. runc stores the explicit filesystem paths (IntelRdtPath and IntelRdtMonPath) directly in its linuxContainer state structure.

Since youki aims for compatibility, saving the explicit paths avoids complex dynamic reconstruction of those paths during container deletion (which requires re-evaluating the spec, closID, etc.).

Regarding the naming: I just followed how runc named them (intel_rdt_path and intel_rdt_mon_path).

pub intel_rdt_dir: Option<PathBuf>,
/// Indicates the dedicated monitoring group subdirectory (`mon_groups/<container_id>`)
/// within the resctrl filesystem that needs to be cleaned up.
/// As per OCI runtime-spec v1.3.0, if `enable_monitoring` is true, the runtime MUST
/// create this group and MUST remove it when the container is deleted.
#[serde(skip_serializing_if = "Option::is_none")]
pub intel_rdt_monitoring_dir: Option<PathBuf>,
}

impl State {
Expand All @@ -142,7 +148,8 @@ impl State {
created: None,
creator: None,
use_systemd: false,
clean_up_intel_rdt_subdirectory: None,
intel_rdt_dir: None,
intel_rdt_monitoring_dir: None,
}
}

Expand Down
14 changes: 9 additions & 5 deletions crates/libcontainer/src/process/container_main_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ pub enum ProcessError {

type Result<T> = std::result::Result<T, ProcessError>;

pub fn container_main_process(container_args: &ContainerArgs) -> Result<(Pid, bool)> {
pub fn container_main_process(
container_args: &ContainerArgs,
) -> Result<(Pid, Option<PathBuf>, Option<PathBuf>)> {
// We use a set of channels to communicate between parent and child process.
// Each channel is uni-directional. Because we will pass these channel to
// cloned process, we have to be deligent about closing any unused channel.
Expand Down Expand Up @@ -132,16 +134,18 @@ pub fn container_main_process(container_args: &ContainerArgs) -> Result<(Pid, bo
// The intermediate process will send the init pid once it forks the init
// process. The intermediate process should exit after this point.
let init_pid = main_receiver.wait_for_intermediate_ready()?;
let mut need_to_clean_up_intel_rdt_subdirectory = false;
let mut intel_rdt_dir = None;
let mut intel_rdt_monitoring_dir = None;

if let Some(linux) = container_args.spec.linux() {
if let Some(intel_rdt) = linux.intel_rdt() {
let container_id = container_args
.container
.as_ref()
.map(|container| container.id());
need_to_clean_up_intel_rdt_subdirectory =
setup_intel_rdt(container_id, &init_pid, intel_rdt)?;
let (dir, mon_dir) = setup_intel_rdt(container_id, &init_pid, intel_rdt)?;
intel_rdt_dir = dir;
intel_rdt_monitoring_dir = mon_dir;
}
}

Expand Down Expand Up @@ -278,7 +282,7 @@ pub fn container_main_process(container_args: &ContainerArgs) -> Result<(Pid, bo
Err(err) => return Err(ProcessError::WaitIntermediateProcess(err)),
};

Ok((init_pid, need_to_clean_up_intel_rdt_subdirectory))
Ok((init_pid, intel_rdt_dir, intel_rdt_monitoring_dir))
}

fn setup_mapping(config: &UserNamespaceConfig, pid: Pid) -> Result<()> {
Expand Down
Loading
Loading