mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-06-28 08:17:37 +00:00
runk: Refactor container builder
Refactor the container builder code (`InitContainer` and `ActivatedContainer`) to make it easier to understand and to maintain. The details: 1. Separate the existing `builder.rs` into an `init_builder.rs` and `activated_builder.rs` to make them easy to read and maintain. 2. Move the `create_linux_container` function from the `builder.rs` to `container.rs` because it is shared by the both files. 3. Some validation functions such as `validate_spec` from `builder.rs` to `utils.rs` because they will be also used by other components as utilities in the future. Fixes: #5033 Signed-off-by: Manabu Sugimoto <Manabu.Sugimoto@sony.com>
This commit is contained in:
parent
630eada0d3
commit
968c2f6e8e
322
src/tools/runk/libcontainer/src/activated_builder.rs
Normal file
322
src/tools/runk/libcontainer/src/activated_builder.rs
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use crate::container::{create_linux_container, Container, ContainerLauncher};
|
||||||
|
use crate::status::Status;
|
||||||
|
use crate::utils::validate_spec;
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use derive_builder::Builder;
|
||||||
|
use oci::{ContainerState, Process as OCIProcess, Spec};
|
||||||
|
use rustjail::container::update_namespaces;
|
||||||
|
use slog::{debug, Logger};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// Used for exec command. It will prepare the options for joining an existing container.
|
||||||
|
#[derive(Default, Builder, Debug, Clone)]
|
||||||
|
#[builder(build_fn(validate = "Self::validate"))]
|
||||||
|
pub struct ActivatedContainer {
|
||||||
|
pub id: String,
|
||||||
|
pub root: PathBuf,
|
||||||
|
pub console_socket: Option<PathBuf>,
|
||||||
|
pub pid_file: Option<PathBuf>,
|
||||||
|
pub tty: bool,
|
||||||
|
pub cwd: Option<PathBuf>,
|
||||||
|
pub env: Vec<(String, String)>,
|
||||||
|
pub no_new_privs: bool,
|
||||||
|
pub args: Vec<String>,
|
||||||
|
pub process: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActivatedContainerBuilder {
|
||||||
|
/// pre-validate before building ActivatedContainer
|
||||||
|
fn validate(&self) -> Result<(), String> {
|
||||||
|
// ensure container exists
|
||||||
|
let id = self.id.as_ref().unwrap();
|
||||||
|
let root = self.root.as_ref().unwrap();
|
||||||
|
let status_path = Status::get_dir_path(root, id);
|
||||||
|
if !status_path.exists() {
|
||||||
|
return Err(format!(
|
||||||
|
"container {} does not exist at path {:?}",
|
||||||
|
id, root
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure argv will not be empty in process exec phase later
|
||||||
|
let process = self.process.as_ref().unwrap();
|
||||||
|
let args = self.args.as_ref().unwrap();
|
||||||
|
if process.is_none() && args.is_empty() {
|
||||||
|
return Err("process and args cannot be all empty".to_string());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActivatedContainer {
|
||||||
|
/// Create ContainerLauncher that can be used to spawn a process in an existing container.
|
||||||
|
/// This reads the spec from status file of an existing container and adapts it with given process file
|
||||||
|
/// or other options like args, env, etc. It also changes the namespace in spec to join the container.
|
||||||
|
pub fn create_launcher(self, logger: &Logger) -> Result<ContainerLauncher> {
|
||||||
|
debug!(
|
||||||
|
logger,
|
||||||
|
"enter ActivatedContainer::create_launcher {:?}", self
|
||||||
|
);
|
||||||
|
let container = Container::load(&self.root, &self.id)?;
|
||||||
|
|
||||||
|
// If state is Created or Running, we can execute the process.
|
||||||
|
if container.state != ContainerState::Created && container.state != ContainerState::Running
|
||||||
|
{
|
||||||
|
return Err(anyhow!(
|
||||||
|
"cannot exec in a stopped or paused container, state: {:?}",
|
||||||
|
container.state
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut config = container.status.config;
|
||||||
|
let spec = config.spec.as_mut().unwrap();
|
||||||
|
self.adapt_exec_spec(spec, container.status.pid, logger)?;
|
||||||
|
debug!(logger, "adapted spec: {:?}", spec);
|
||||||
|
validate_spec(spec, &self.console_socket)?;
|
||||||
|
|
||||||
|
debug!(logger, "create LinuxContainer with config: {:?}", config);
|
||||||
|
// Maybe we should move some properties from status into LinuxContainer,
|
||||||
|
// like pid, process_start_time, created, cgroup_manager, etc. But it works now.
|
||||||
|
let runner =
|
||||||
|
create_linux_container(&self.id, &self.root, config, self.console_socket, logger)?;
|
||||||
|
|
||||||
|
Ok(ContainerLauncher::new(
|
||||||
|
&self.id,
|
||||||
|
&container.status.bundle,
|
||||||
|
&self.root,
|
||||||
|
false,
|
||||||
|
runner,
|
||||||
|
self.pid_file,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adapt spec to execute a new process which will join the container.
|
||||||
|
fn adapt_exec_spec(&self, spec: &mut Spec, pid: i32, logger: &Logger) -> Result<()> {
|
||||||
|
// If with --process, load process from file.
|
||||||
|
// Otherwise, update process with args and other options.
|
||||||
|
if let Some(process_path) = self.process.as_ref() {
|
||||||
|
spec.process = Some(Self::get_process(process_path)?);
|
||||||
|
} else if let Some(process) = spec.process.as_mut() {
|
||||||
|
self.update_process(process)?;
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("process is empty in spec"));
|
||||||
|
};
|
||||||
|
// Exec process will join the container's namespaces
|
||||||
|
update_namespaces(logger, spec, pid)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update process with args and other options.
|
||||||
|
fn update_process(&self, process: &mut OCIProcess) -> Result<()> {
|
||||||
|
process.args = self.args.clone();
|
||||||
|
process.no_new_privileges = self.no_new_privs;
|
||||||
|
process.terminal = self.tty;
|
||||||
|
if let Some(cwd) = self.cwd.as_ref() {
|
||||||
|
process.cwd = cwd.as_path().display().to_string();
|
||||||
|
}
|
||||||
|
process
|
||||||
|
.env
|
||||||
|
.extend(self.env.iter().map(|kv| format!("{}={}", kv.0, kv.1)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read and parse OCI Process from path
|
||||||
|
fn get_process(process_path: &Path) -> Result<OCIProcess> {
|
||||||
|
let f = File::open(process_path)?;
|
||||||
|
Ok(serde_json::from_reader(f)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::status::Status;
|
||||||
|
use crate::utils::test_utils::*;
|
||||||
|
use nix::unistd::getpid;
|
||||||
|
use oci::{Linux, LinuxNamespace, User};
|
||||||
|
use rustjail::container::TYPETONAME;
|
||||||
|
use scopeguard::defer;
|
||||||
|
use slog::o;
|
||||||
|
use std::{
|
||||||
|
fs::{create_dir_all, File},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
use test_utils::skip_if_not_root;
|
||||||
|
|
||||||
|
fn create_activated_dirs(root: &Path, id: &str, bundle: &Path) {
|
||||||
|
Status::create_dir(root, id).unwrap();
|
||||||
|
create_dir_all(bundle.join(TEST_ROOTFS_PATH)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_activated_container_validate() {
|
||||||
|
let root = tempdir().unwrap();
|
||||||
|
let id = TEST_CONTAINER_ID.to_string();
|
||||||
|
Status::create_dir(root.path(), &id).unwrap();
|
||||||
|
let result = ActivatedContainerBuilder::default()
|
||||||
|
.id(id)
|
||||||
|
.root(root.into_path())
|
||||||
|
.console_socket(None)
|
||||||
|
.pid_file(None)
|
||||||
|
.tty(false)
|
||||||
|
.cwd(None)
|
||||||
|
.env(Vec::new())
|
||||||
|
.no_new_privs(false)
|
||||||
|
.process(None)
|
||||||
|
.args(vec!["sleep".to_string(), "10".to_string()])
|
||||||
|
.build();
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_activated_container_create() {
|
||||||
|
// create cgroup directory needs root permission
|
||||||
|
skip_if_not_root!();
|
||||||
|
let logger = slog::Logger::root(slog::Discard, o!());
|
||||||
|
let bundle_dir = tempdir().unwrap();
|
||||||
|
let root = tempdir().unwrap();
|
||||||
|
// Since tests are executed concurrently, container_id must be unique in tests with cgroup.
|
||||||
|
// Or the cgroup directory may be removed by other tests in advance.
|
||||||
|
let id = "test_activated_container_create".to_string();
|
||||||
|
create_activated_dirs(root.path(), &id, bundle_dir.path());
|
||||||
|
let pid = getpid().as_raw();
|
||||||
|
|
||||||
|
let mut spec = create_dummy_spec();
|
||||||
|
spec.root.as_mut().unwrap().path = bundle_dir
|
||||||
|
.path()
|
||||||
|
.join(TEST_ROOTFS_PATH)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let status = create_custom_dummy_status(&id, pid, root.path(), &spec);
|
||||||
|
status.save().unwrap();
|
||||||
|
|
||||||
|
// create empty cgroup directory to avoid is_pause failing
|
||||||
|
let cgroup = create_dummy_cgroup(Path::new(id.as_str()));
|
||||||
|
defer!(cgroup.delete().unwrap());
|
||||||
|
|
||||||
|
let result = ActivatedContainerBuilder::default()
|
||||||
|
.id(id)
|
||||||
|
.root(root.into_path())
|
||||||
|
.console_socket(Some(PathBuf::from(TEST_CONSOLE_SOCKET_PATH)))
|
||||||
|
.pid_file(Some(PathBuf::from(TEST_PID_FILE_PATH)))
|
||||||
|
.tty(true)
|
||||||
|
.cwd(Some(PathBuf::from(TEST_BUNDLE_PATH)))
|
||||||
|
.env(vec![
|
||||||
|
("K1".to_string(), "V1".to_string()),
|
||||||
|
("K2".to_string(), "V2".to_string()),
|
||||||
|
])
|
||||||
|
.no_new_privs(true)
|
||||||
|
.process(None)
|
||||||
|
.args(vec!["sleep".to_string(), "10".to_string()])
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let linux = Linux {
|
||||||
|
namespaces: TYPETONAME
|
||||||
|
.iter()
|
||||||
|
.filter(|&(_, &name)| name != "user")
|
||||||
|
.map(|ns| LinuxNamespace {
|
||||||
|
r#type: ns.0.to_string(),
|
||||||
|
path: format!("/proc/{}/ns/{}", pid, ns.1),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
spec.linux = Some(linux);
|
||||||
|
spec.process = Some(OCIProcess {
|
||||||
|
terminal: result.tty,
|
||||||
|
console_size: None,
|
||||||
|
user: User::default(),
|
||||||
|
args: result.args.clone(),
|
||||||
|
cwd: result.cwd.clone().unwrap().to_string_lossy().to_string(),
|
||||||
|
env: vec![
|
||||||
|
"PATH=/bin:/usr/bin".to_string(),
|
||||||
|
"K1=V1".to_string(),
|
||||||
|
"K2=V2".to_string(),
|
||||||
|
],
|
||||||
|
capabilities: None,
|
||||||
|
rlimits: Vec::new(),
|
||||||
|
no_new_privileges: result.no_new_privs,
|
||||||
|
apparmor_profile: "".to_string(),
|
||||||
|
oom_score_adj: None,
|
||||||
|
selinux_label: "".to_string(),
|
||||||
|
});
|
||||||
|
let launcher = result.clone().create_launcher(&logger).unwrap();
|
||||||
|
assert!(!launcher.init);
|
||||||
|
assert_eq!(launcher.runner.config.spec.unwrap(), spec);
|
||||||
|
assert_eq!(
|
||||||
|
launcher.runner.console_socket,
|
||||||
|
result.console_socket.unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(launcher.pid_file, result.pid_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_activated_container_create_with_process() {
|
||||||
|
// create cgroup directory needs root permission
|
||||||
|
skip_if_not_root!();
|
||||||
|
let bundle_dir = tempdir().unwrap();
|
||||||
|
let process_file = bundle_dir.path().join(TEST_PROCESS_FILE_NAME);
|
||||||
|
let process_template = OCIProcess {
|
||||||
|
args: vec!["sleep".to_string(), "10".to_string()],
|
||||||
|
cwd: "/".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let file = File::create(process_file.clone()).unwrap();
|
||||||
|
serde_json::to_writer(&file, &process_template).unwrap();
|
||||||
|
|
||||||
|
let logger = slog::Logger::root(slog::Discard, o!());
|
||||||
|
let root = tempdir().unwrap();
|
||||||
|
// Since tests are executed concurrently, container_id must be unique in tests with cgroup.
|
||||||
|
// Or the cgroup directory may be removed by other tests in advance.
|
||||||
|
let id = "test_activated_container_create_with_process".to_string();
|
||||||
|
let pid = getpid().as_raw();
|
||||||
|
let mut spec = create_dummy_spec();
|
||||||
|
spec.root.as_mut().unwrap().path = bundle_dir
|
||||||
|
.path()
|
||||||
|
.join(TEST_ROOTFS_PATH)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
create_activated_dirs(root.path(), &id, bundle_dir.path());
|
||||||
|
|
||||||
|
let status = create_custom_dummy_status(&id, pid, root.path(), &spec);
|
||||||
|
status.save().unwrap();
|
||||||
|
// create empty cgroup directory to avoid is_pause failing
|
||||||
|
let cgroup = create_dummy_cgroup(Path::new(id.as_str()));
|
||||||
|
defer!(cgroup.delete().unwrap());
|
||||||
|
|
||||||
|
let launcher = ActivatedContainerBuilder::default()
|
||||||
|
.id(id)
|
||||||
|
.root(root.into_path())
|
||||||
|
.console_socket(None)
|
||||||
|
.pid_file(None)
|
||||||
|
.tty(true)
|
||||||
|
.cwd(Some(PathBuf::from(TEST_BUNDLE_PATH)))
|
||||||
|
.env(vec![
|
||||||
|
("K1".to_string(), "V1".to_string()),
|
||||||
|
("K2".to_string(), "V2".to_string()),
|
||||||
|
])
|
||||||
|
.no_new_privs(true)
|
||||||
|
.process(Some(process_file))
|
||||||
|
.args(vec!["sleep".to_string(), "10".to_string()])
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.create_launcher(&logger)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(!launcher.init);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
launcher.runner.config.spec.unwrap().process.unwrap(),
|
||||||
|
process_template
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,657 +0,0 @@
|
|||||||
// Copyright 2021-2022 Sony Group Corporation
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
use crate::container::{get_config_path, Container, ContainerLauncher};
|
|
||||||
use crate::utils::validate_process_spec;
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use derive_builder::Builder;
|
|
||||||
use oci::{ContainerState, Process as OCIProcess, Spec};
|
|
||||||
use rustjail::container::update_namespaces;
|
|
||||||
use rustjail::{container::LinuxContainer, specconv::CreateOpts};
|
|
||||||
use slog::{debug, Logger};
|
|
||||||
use std::fs::File;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
/// Used for create and run commands. It will prepare the options used for creating a new container.
|
|
||||||
#[derive(Default, Builder, Debug, Clone)]
|
|
||||||
#[builder(build_fn(validate = "Self::validate"))]
|
|
||||||
pub struct InitContainer {
|
|
||||||
id: String,
|
|
||||||
bundle: PathBuf,
|
|
||||||
root: PathBuf,
|
|
||||||
console_socket: Option<PathBuf>,
|
|
||||||
pid_file: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InitContainerBuilder {
|
|
||||||
/// Pre-validate before building InitContainer
|
|
||||||
fn validate(&self) -> Result<(), String> {
|
|
||||||
// ensure container hasn't already been created
|
|
||||||
let id = self.id.as_ref().unwrap();
|
|
||||||
let root = self.root.as_ref().unwrap();
|
|
||||||
let path = root.join(id);
|
|
||||||
if path.as_path().exists() {
|
|
||||||
return Err(format!(
|
|
||||||
"container {} already exists at path {:?}",
|
|
||||||
id, root
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InitContainer {
|
|
||||||
/// Create ContainerLauncher that can be used to launch a new container.
|
|
||||||
/// It will read the spec under bundle path.
|
|
||||||
pub fn create_launcher(self, logger: &Logger) -> Result<ContainerLauncher> {
|
|
||||||
debug!(logger, "enter InitContainer::create_launcher {:?}", self);
|
|
||||||
let bundle_canon = self.bundle.canonicalize()?;
|
|
||||||
let config_path = get_config_path(&bundle_canon);
|
|
||||||
let mut spec = Spec::load(
|
|
||||||
config_path
|
|
||||||
.to_str()
|
|
||||||
.ok_or_else(|| anyhow!("invalid config path"))?,
|
|
||||||
)?;
|
|
||||||
// Only absolute rootfs path is valid when creating LinuxContainer later.
|
|
||||||
canonicalize_spec_root(&mut spec, &bundle_canon)?;
|
|
||||||
debug!(logger, "load spec from config file: {:?}", spec);
|
|
||||||
validate_spec(&spec, &self.console_socket)?;
|
|
||||||
|
|
||||||
let config = CreateOpts {
|
|
||||||
cgroup_name: "".to_string(),
|
|
||||||
use_systemd_cgroup: false,
|
|
||||||
// TODO: liboci-cli does not support --no-pivot option for create and run command.
|
|
||||||
// After liboci-cli supports the option, we will change the following code.
|
|
||||||
// no_pivot_root: self.no_pivot,
|
|
||||||
no_pivot_root: false,
|
|
||||||
no_new_keyring: false,
|
|
||||||
spec: Some(spec),
|
|
||||||
rootless_euid: false,
|
|
||||||
rootless_cgroup: false,
|
|
||||||
};
|
|
||||||
debug!(logger, "create LinuxContainer with config: {:?}", config);
|
|
||||||
let container =
|
|
||||||
create_linux_container(&self.id, &self.root, config, self.console_socket, logger)?;
|
|
||||||
|
|
||||||
Ok(ContainerLauncher::new(
|
|
||||||
&self.id,
|
|
||||||
&bundle_canon,
|
|
||||||
&self.root,
|
|
||||||
true,
|
|
||||||
container,
|
|
||||||
self.pid_file,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used for exec command. It will prepare the options for joining an existing container.
|
|
||||||
#[derive(Default, Builder, Debug, Clone)]
|
|
||||||
#[builder(build_fn(validate = "Self::validate"))]
|
|
||||||
pub struct ActivatedContainer {
|
|
||||||
pub id: String,
|
|
||||||
pub root: PathBuf,
|
|
||||||
pub console_socket: Option<PathBuf>,
|
|
||||||
pub pid_file: Option<PathBuf>,
|
|
||||||
pub tty: bool,
|
|
||||||
pub cwd: Option<PathBuf>,
|
|
||||||
pub env: Vec<(String, String)>,
|
|
||||||
pub no_new_privs: bool,
|
|
||||||
pub args: Vec<String>,
|
|
||||||
pub process: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActivatedContainerBuilder {
|
|
||||||
/// pre-validate before building ActivatedContainer
|
|
||||||
fn validate(&self) -> Result<(), String> {
|
|
||||||
// ensure container exists
|
|
||||||
let id = self.id.as_ref().unwrap();
|
|
||||||
let root = self.root.as_ref().unwrap();
|
|
||||||
let path = root.join(id);
|
|
||||||
if !path.as_path().exists() {
|
|
||||||
return Err(format!(
|
|
||||||
"container {} does not exist at path {:?}",
|
|
||||||
id, root
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure argv will not be empty in process exec phase later
|
|
||||||
let process = self.process.as_ref().unwrap();
|
|
||||||
let args = self.args.as_ref().unwrap();
|
|
||||||
if process.is_none() && args.is_empty() {
|
|
||||||
return Err("process and args cannot be all empty".to_string());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActivatedContainer {
|
|
||||||
/// Create ContainerLauncher that can be used to spawn a process in an existing container.
|
|
||||||
/// It reads the spec from status file of an existing container, and adapted it with given process file
|
|
||||||
/// or other options like args, env, etc. It also changes the namespace in spec to join the container.
|
|
||||||
pub fn create_launcher(self, logger: &Logger) -> Result<ContainerLauncher> {
|
|
||||||
debug!(
|
|
||||||
logger,
|
|
||||||
"enter ActivatedContainer::create_launcher {:?}", self
|
|
||||||
);
|
|
||||||
let container = Container::load(&self.root, &self.id)?;
|
|
||||||
|
|
||||||
// If state is Created or Running, we can execute the process.
|
|
||||||
if container.state != ContainerState::Created && container.state != ContainerState::Running
|
|
||||||
{
|
|
||||||
return Err(anyhow!(
|
|
||||||
"cannot exec in a stopped or paused container, state: {:?}",
|
|
||||||
container.state
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut config = container.status.config;
|
|
||||||
let spec = config.spec.as_mut().unwrap();
|
|
||||||
self.adapt_exec_spec(spec, container.status.pid, logger)?;
|
|
||||||
debug!(logger, "adapted spec: {:?}", spec);
|
|
||||||
validate_spec(spec, &self.console_socket)?;
|
|
||||||
|
|
||||||
debug!(logger, "create LinuxContainer with config: {:?}", config);
|
|
||||||
// Maybe we should move some properties from status into LinuxContainer,
|
|
||||||
// like pid, process_start_time, created, cgroup_manager, etc. But it works now.
|
|
||||||
let runner =
|
|
||||||
create_linux_container(&self.id, &self.root, config, self.console_socket, logger)?;
|
|
||||||
|
|
||||||
Ok(ContainerLauncher::new(
|
|
||||||
&self.id,
|
|
||||||
&container.status.bundle,
|
|
||||||
&self.root,
|
|
||||||
false,
|
|
||||||
runner,
|
|
||||||
self.pid_file,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adapt spec to execute a new process which will join the container.
|
|
||||||
fn adapt_exec_spec(&self, spec: &mut Spec, pid: i32, logger: &Logger) -> Result<()> {
|
|
||||||
// If with --process, load process from file.
|
|
||||||
// Otherwise, update process with args and other options.
|
|
||||||
if let Some(process_path) = self.process.as_ref() {
|
|
||||||
spec.process = Some(Self::get_process(process_path)?);
|
|
||||||
} else if let Some(process) = spec.process.as_mut() {
|
|
||||||
self.update_process(process)?;
|
|
||||||
} else {
|
|
||||||
return Err(anyhow!("process is empty in spec"));
|
|
||||||
};
|
|
||||||
// Exec process will join the container's namespaces
|
|
||||||
update_namespaces(logger, spec, pid)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update process with args and other options.
|
|
||||||
fn update_process(&self, process: &mut OCIProcess) -> Result<()> {
|
|
||||||
process.args = self.args.clone();
|
|
||||||
process.no_new_privileges = self.no_new_privs;
|
|
||||||
process.terminal = self.tty;
|
|
||||||
if let Some(cwd) = self.cwd.as_ref() {
|
|
||||||
process.cwd = cwd.as_path().display().to_string();
|
|
||||||
}
|
|
||||||
process
|
|
||||||
.env
|
|
||||||
.extend(self.env.iter().map(|kv| format!("{}={}", kv.0, kv.1)));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read and parse OCI Process from path
|
|
||||||
fn get_process(process_path: &Path) -> Result<OCIProcess> {
|
|
||||||
let f = File::open(process_path)?;
|
|
||||||
Ok(serde_json::from_reader(f)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If root in spec is a relative path, make it absolute.
|
|
||||||
fn canonicalize_spec_root(spec: &mut Spec, bundle_canon: &Path) -> Result<()> {
|
|
||||||
let mut spec_root = spec
|
|
||||||
.root
|
|
||||||
.as_mut()
|
|
||||||
.ok_or_else(|| anyhow!("root config was not present in the spec file"))?;
|
|
||||||
let rootfs_path = Path::new(&spec_root.path);
|
|
||||||
if !rootfs_path.is_absolute() {
|
|
||||||
spec_root.path = bundle_canon
|
|
||||||
.join(rootfs_path)
|
|
||||||
.canonicalize()?
|
|
||||||
.to_str()
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.ok_or_else(|| anyhow!("failed to convert a rootfs path into a canonical path"))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_linux_container(
|
|
||||||
id: &str,
|
|
||||||
root: &Path,
|
|
||||||
config: CreateOpts,
|
|
||||||
console_socket: Option<PathBuf>,
|
|
||||||
logger: &Logger,
|
|
||||||
) -> Result<LinuxContainer> {
|
|
||||||
let mut container = LinuxContainer::new(
|
|
||||||
id,
|
|
||||||
root.to_str()
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.ok_or_else(|| anyhow!("failed to convert bundle path"))?
|
|
||||||
.as_str(),
|
|
||||||
config,
|
|
||||||
logger,
|
|
||||||
)?;
|
|
||||||
if let Some(socket_path) = console_socket.as_ref() {
|
|
||||||
container.set_console_socket(socket_path)?;
|
|
||||||
}
|
|
||||||
Ok(container)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether spec is valid. Now runk only support detach mode.
|
|
||||||
pub fn validate_spec(spec: &Spec, console_socket: &Option<PathBuf>) -> Result<()> {
|
|
||||||
validate_process_spec(&spec.process)?;
|
|
||||||
if let Some(process) = spec.process.as_ref() {
|
|
||||||
// runk always launches containers with detached mode, so users have to
|
|
||||||
// use a console socket with run or create operation when a terminal is used.
|
|
||||||
if process.terminal && console_socket.is_none() {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"cannot allocate a pseudo-TTY without setting a console socket"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::container::CONFIG_FILE_NAME;
|
|
||||||
use crate::status::Status;
|
|
||||||
use crate::utils::test_utils::*;
|
|
||||||
use chrono::DateTime;
|
|
||||||
use nix::unistd::getpid;
|
|
||||||
use oci::{self, Root, Spec};
|
|
||||||
use oci::{Linux, LinuxNamespace, User};
|
|
||||||
use rustjail::container::TYPETONAME;
|
|
||||||
use scopeguard::defer;
|
|
||||||
use slog::o;
|
|
||||||
use std::fs::create_dir;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
use std::{
|
|
||||||
fs::{create_dir_all, File},
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
use tempfile::tempdir;
|
|
||||||
use test_utils::skip_if_not_root;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct TestData {
|
|
||||||
id: String,
|
|
||||||
bundle: PathBuf,
|
|
||||||
root: PathBuf,
|
|
||||||
console_socket: Option<PathBuf>,
|
|
||||||
pid_file: Option<PathBuf>,
|
|
||||||
config: CreateOpts,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_init_container_validate() {
|
|
||||||
let root = tempdir().unwrap();
|
|
||||||
let id = "test".to_string();
|
|
||||||
Status::create_dir(root.path(), id.as_str()).unwrap();
|
|
||||||
let result = InitContainerBuilder::default()
|
|
||||||
.id(id)
|
|
||||||
.root(root.path().to_path_buf())
|
|
||||||
.bundle(PathBuf::from("test"))
|
|
||||||
.pid_file(None)
|
|
||||||
.console_socket(None)
|
|
||||||
.build();
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_init_container_create_launcher() {
|
|
||||||
let logger = slog::Logger::root(slog::Discard, o!());
|
|
||||||
let root_dir = tempdir().unwrap();
|
|
||||||
let bundle_dir = tempdir().unwrap();
|
|
||||||
// create dummy rootfs
|
|
||||||
create_dir(bundle_dir.path().join("rootfs")).unwrap();
|
|
||||||
let config_file = bundle_dir.path().join(CONFIG_FILE_NAME);
|
|
||||||
let mut spec = create_dummy_spec();
|
|
||||||
let file = File::create(config_file).unwrap();
|
|
||||||
serde_json::to_writer(&file, &spec).unwrap();
|
|
||||||
|
|
||||||
spec.root.as_mut().unwrap().path = bundle_dir
|
|
||||||
.path()
|
|
||||||
.join(TEST_ROOTFS_PATH)
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
let test_data = TestData {
|
|
||||||
// Since tests are executed concurrently, container_id must be unique in tests with cgroup.
|
|
||||||
// Or the cgroup directory may be removed by other tests in advance.
|
|
||||||
id: String::from("test_init_container_create_launcher"),
|
|
||||||
bundle: bundle_dir.path().to_path_buf(),
|
|
||||||
root: root_dir.into_path(),
|
|
||||||
console_socket: Some(PathBuf::from("test")),
|
|
||||||
config: CreateOpts {
|
|
||||||
spec: Some(spec),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
pid_file: Some(PathBuf::from("test")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let launcher = InitContainerBuilder::default()
|
|
||||||
.id(test_data.id.clone())
|
|
||||||
.bundle(test_data.bundle.clone())
|
|
||||||
.root(test_data.root.clone())
|
|
||||||
.console_socket(test_data.console_socket.clone())
|
|
||||||
.pid_file(test_data.pid_file.clone())
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
.create_launcher(&logger)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// LinuxContainer doesn't impl PartialEq, so we need to compare the fields manually.
|
|
||||||
assert!(launcher.init);
|
|
||||||
assert_eq!(launcher.bundle, test_data.bundle);
|
|
||||||
assert_eq!(launcher.state_root, test_data.root);
|
|
||||||
assert_eq!(launcher.pid_file, test_data.pid_file);
|
|
||||||
assert_eq!(launcher.runner.id, test_data.id);
|
|
||||||
assert_eq!(launcher.runner.config.spec, test_data.config.spec);
|
|
||||||
assert_eq!(
|
|
||||||
Some(launcher.runner.console_socket),
|
|
||||||
test_data.console_socket
|
|
||||||
);
|
|
||||||
// If it is run by root, create_launcher will create cgroup dirs successfully. So we need to do some cleanup stuff.
|
|
||||||
if nix::unistd::Uid::effective().is_root() {
|
|
||||||
clean_up_cgroup(Path::new(&test_data.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_init_container_tty_err() {
|
|
||||||
let logger = slog::Logger::root(slog::Discard, o!());
|
|
||||||
let bundle_dir = tempdir().unwrap();
|
|
||||||
let config_file = bundle_dir.path().join(CONFIG_FILE_NAME);
|
|
||||||
|
|
||||||
let mut spec = oci::Spec {
|
|
||||||
process: Some(oci::Process::default()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
spec.process.as_mut().unwrap().terminal = true;
|
|
||||||
|
|
||||||
let file = File::create(config_file).unwrap();
|
|
||||||
serde_json::to_writer(&file, &spec).unwrap();
|
|
||||||
|
|
||||||
let test_data = TestData {
|
|
||||||
id: String::from("test"),
|
|
||||||
bundle: bundle_dir.into_path(),
|
|
||||||
root: tempdir().unwrap().into_path(),
|
|
||||||
console_socket: None,
|
|
||||||
config: CreateOpts {
|
|
||||||
spec: Some(spec),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
pid_file: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = InitContainerBuilder::default()
|
|
||||||
.id(test_data.id.clone())
|
|
||||||
.bundle(test_data.bundle.clone())
|
|
||||||
.root(test_data.root.clone())
|
|
||||||
.console_socket(test_data.console_socket.clone())
|
|
||||||
.pid_file(test_data.pid_file)
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
.create_launcher(&logger);
|
|
||||||
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_canonicalize_spec_root() {
|
|
||||||
let gen_spec = |p: &str| -> Spec {
|
|
||||||
Spec {
|
|
||||||
root: Some(Root {
|
|
||||||
path: p.to_string(),
|
|
||||||
readonly: false,
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let rootfs_name = TEST_ROOTFS_PATH;
|
|
||||||
let temp_dir = tempdir().unwrap();
|
|
||||||
let bundle_dir = temp_dir.path();
|
|
||||||
let abs_root = bundle_dir.join(rootfs_name);
|
|
||||||
create_dir_all(abs_root.clone()).unwrap();
|
|
||||||
let mut spec = gen_spec(abs_root.to_str().unwrap());
|
|
||||||
assert!(canonicalize_spec_root(&mut spec, bundle_dir).is_ok());
|
|
||||||
assert_eq!(spec.root.unwrap().path, abs_root.to_str().unwrap());
|
|
||||||
let mut spec = gen_spec(rootfs_name);
|
|
||||||
assert!(canonicalize_spec_root(&mut spec, bundle_dir).is_ok());
|
|
||||||
assert_eq!(spec.root.unwrap().path, abs_root.to_str().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_dummy_spec() -> Spec {
|
|
||||||
let linux = oci::Linux {
|
|
||||||
namespaces: TYPETONAME
|
|
||||||
.iter()
|
|
||||||
.filter(|&(_, &name)| name != "user")
|
|
||||||
.map(|ns| LinuxNamespace {
|
|
||||||
r#type: ns.0.to_string(),
|
|
||||||
path: "".to_string(),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
Spec {
|
|
||||||
version: "1.0".to_string(),
|
|
||||||
process: Some(OCIProcess {
|
|
||||||
args: vec!["sleep".to_string(), "10".to_string()],
|
|
||||||
env: vec!["PATH=/bin:/usr/bin".to_string()],
|
|
||||||
cwd: "/".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
hostname: "runk".to_string(),
|
|
||||||
root: Some(Root {
|
|
||||||
path: TEST_ROOTFS_PATH.to_string(),
|
|
||||||
readonly: false,
|
|
||||||
}),
|
|
||||||
linux: Some(linux),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_dummy_status(id: &str, pid: i32, root: &Path, spec: &Spec) -> Status {
|
|
||||||
let start_time = procfs::process::Process::new(pid)
|
|
||||||
.unwrap()
|
|
||||||
.stat()
|
|
||||||
.unwrap()
|
|
||||||
.starttime;
|
|
||||||
Status {
|
|
||||||
oci_version: spec.version.clone(),
|
|
||||||
id: id.to_string(),
|
|
||||||
pid,
|
|
||||||
root: root.to_path_buf(),
|
|
||||||
bundle: PathBuf::from("/tmp"),
|
|
||||||
rootfs: TEST_ROOTFS_PATH.to_string(),
|
|
||||||
process_start_time: start_time,
|
|
||||||
created: DateTime::from(SystemTime::now()),
|
|
||||||
cgroup_manager: serde_json::from_str(TEST_CGM_DATA).unwrap(),
|
|
||||||
config: CreateOpts {
|
|
||||||
spec: Some(spec.clone()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_activated_dirs(root: &Path, id: &str, bundle: &Path) {
|
|
||||||
Status::create_dir(root, id).unwrap();
|
|
||||||
create_dir_all(bundle.join(TEST_ROOTFS_PATH)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_activated_container_validate() {
|
|
||||||
let root = tempdir().unwrap();
|
|
||||||
let id = "test".to_string();
|
|
||||||
Status::create_dir(root.path(), &id).unwrap();
|
|
||||||
let result = ActivatedContainerBuilder::default()
|
|
||||||
.id(id)
|
|
||||||
.root(root.into_path())
|
|
||||||
.console_socket(None)
|
|
||||||
.pid_file(None)
|
|
||||||
.tty(false)
|
|
||||||
.cwd(None)
|
|
||||||
.env(Vec::new())
|
|
||||||
.no_new_privs(false)
|
|
||||||
.process(None)
|
|
||||||
.args(vec!["sleep".to_string(), "10".to_string()])
|
|
||||||
.build();
|
|
||||||
assert!(result.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_activated_container_create() {
|
|
||||||
// create cgroup directory needs root permission
|
|
||||||
skip_if_not_root!();
|
|
||||||
let logger = slog::Logger::root(slog::Discard, o!());
|
|
||||||
let bundle_dir = tempdir().unwrap();
|
|
||||||
let root = tempdir().unwrap();
|
|
||||||
// Since tests are executed concurrently, container_id must be unique in tests with cgroup.
|
|
||||||
// Or the cgroup directory may be removed by other tests in advance.
|
|
||||||
let id = "test_activated_container_create".to_string();
|
|
||||||
create_activated_dirs(root.path(), &id, bundle_dir.path());
|
|
||||||
let pid = getpid().as_raw();
|
|
||||||
|
|
||||||
let mut spec = create_dummy_spec();
|
|
||||||
spec.root.as_mut().unwrap().path = bundle_dir
|
|
||||||
.path()
|
|
||||||
.join(TEST_ROOTFS_PATH)
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let status = create_dummy_status(&id, pid, root.path(), &spec);
|
|
||||||
status.save().unwrap();
|
|
||||||
|
|
||||||
// create empty cgroup directory to avoid is_pause failing
|
|
||||||
let cgroup = create_dummy_cgroup(Path::new(id.as_str()));
|
|
||||||
defer!(cgroup.delete().unwrap());
|
|
||||||
|
|
||||||
let result = ActivatedContainerBuilder::default()
|
|
||||||
.id(id)
|
|
||||||
.root(root.into_path())
|
|
||||||
.console_socket(Some(PathBuf::from("/var/run/test.sock")))
|
|
||||||
.pid_file(Some(PathBuf::from("test")))
|
|
||||||
.tty(true)
|
|
||||||
.cwd(Some(PathBuf::from("/tmp")))
|
|
||||||
.env(vec![
|
|
||||||
("K1".to_string(), "V1".to_string()),
|
|
||||||
("K2".to_string(), "V2".to_string()),
|
|
||||||
])
|
|
||||||
.no_new_privs(true)
|
|
||||||
.process(None)
|
|
||||||
.args(vec!["sleep".to_string(), "10".to_string()])
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let linux = Linux {
|
|
||||||
namespaces: TYPETONAME
|
|
||||||
.iter()
|
|
||||||
.filter(|&(_, &name)| name != "user")
|
|
||||||
.map(|ns| LinuxNamespace {
|
|
||||||
r#type: ns.0.to_string(),
|
|
||||||
path: format!("/proc/{}/ns/{}", pid, ns.1),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
spec.linux = Some(linux);
|
|
||||||
spec.process = Some(OCIProcess {
|
|
||||||
terminal: result.tty,
|
|
||||||
console_size: None,
|
|
||||||
user: User::default(),
|
|
||||||
args: result.args.clone(),
|
|
||||||
cwd: result.cwd.clone().unwrap().to_string_lossy().to_string(),
|
|
||||||
env: vec![
|
|
||||||
"PATH=/bin:/usr/bin".to_string(),
|
|
||||||
"K1=V1".to_string(),
|
|
||||||
"K2=V2".to_string(),
|
|
||||||
],
|
|
||||||
capabilities: None,
|
|
||||||
rlimits: Vec::new(),
|
|
||||||
no_new_privileges: result.no_new_privs,
|
|
||||||
apparmor_profile: "".to_string(),
|
|
||||||
oom_score_adj: None,
|
|
||||||
selinux_label: "".to_string(),
|
|
||||||
});
|
|
||||||
let launcher = result.clone().create_launcher(&logger).unwrap();
|
|
||||||
assert!(!launcher.init);
|
|
||||||
assert_eq!(launcher.runner.config.spec.unwrap(), spec);
|
|
||||||
assert_eq!(
|
|
||||||
launcher.runner.console_socket,
|
|
||||||
result.console_socket.unwrap()
|
|
||||||
);
|
|
||||||
assert_eq!(launcher.pid_file, result.pid_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_activated_container_create_with_process() {
|
|
||||||
// create cgroup directory needs root permission
|
|
||||||
skip_if_not_root!();
|
|
||||||
const PROCESS_FILE_NAME: &str = "process.json";
|
|
||||||
let bundle_dir = tempdir().unwrap();
|
|
||||||
let process_file = bundle_dir.path().join(PROCESS_FILE_NAME);
|
|
||||||
let process_template = OCIProcess {
|
|
||||||
args: vec!["sleep".to_string(), "10".to_string()],
|
|
||||||
cwd: "/".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let file = File::create(process_file.clone()).unwrap();
|
|
||||||
serde_json::to_writer(&file, &process_template).unwrap();
|
|
||||||
|
|
||||||
let logger = slog::Logger::root(slog::Discard, o!());
|
|
||||||
let root = tempdir().unwrap();
|
|
||||||
// Since tests are executed concurrently, container_id must be unique in tests with cgroup.
|
|
||||||
// Or the cgroup directory may be removed by other tests in advance.
|
|
||||||
let id = "test_activated_container_create_with_process".to_string();
|
|
||||||
let pid = getpid().as_raw();
|
|
||||||
let mut spec = create_dummy_spec();
|
|
||||||
spec.root.as_mut().unwrap().path = bundle_dir
|
|
||||||
.path()
|
|
||||||
.join(TEST_ROOTFS_PATH)
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
create_activated_dirs(root.path(), &id, bundle_dir.path());
|
|
||||||
|
|
||||||
let status = create_dummy_status(&id, pid, root.path(), &spec);
|
|
||||||
status.save().unwrap();
|
|
||||||
// create empty cgroup directory to avoid is_pause failing
|
|
||||||
let cgroup = create_dummy_cgroup(Path::new(id.as_str()));
|
|
||||||
defer!(cgroup.delete().unwrap());
|
|
||||||
|
|
||||||
let launcher = ActivatedContainerBuilder::default()
|
|
||||||
.id(id)
|
|
||||||
.root(root.into_path())
|
|
||||||
.console_socket(None)
|
|
||||||
.pid_file(None)
|
|
||||||
.tty(true)
|
|
||||||
.cwd(Some(PathBuf::from("/tmp")))
|
|
||||||
.env(vec![
|
|
||||||
("K1".to_string(), "V1".to_string()),
|
|
||||||
("K2".to_string(), "V2".to_string()),
|
|
||||||
])
|
|
||||||
.no_new_privs(true)
|
|
||||||
.process(Some(process_file))
|
|
||||||
.args(vec!["sleep".to_string(), "10".to_string()])
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
.create_launcher(&logger)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(!launcher.init);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
launcher.runner.config.spec.unwrap().process.unwrap(),
|
|
||||||
process_template
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,6 +20,7 @@ use procfs;
|
|||||||
use rustjail::{
|
use rustjail::{
|
||||||
container::{self, BaseContainer, LinuxContainer, EXEC_FIFO_FILENAME},
|
container::{self, BaseContainer, LinuxContainer, EXEC_FIFO_FILENAME},
|
||||||
process::{Process, ProcessOperations},
|
process::{Process, ProcessOperations},
|
||||||
|
specconv::CreateOpts,
|
||||||
};
|
};
|
||||||
use scopeguard::defer;
|
use scopeguard::defer;
|
||||||
use slog::{debug, Logger};
|
use slog::{debug, Logger};
|
||||||
@ -333,6 +334,28 @@ impl ContainerLauncher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_linux_container(
|
||||||
|
id: &str,
|
||||||
|
root: &Path,
|
||||||
|
config: CreateOpts,
|
||||||
|
console_socket: Option<PathBuf>,
|
||||||
|
logger: &Logger,
|
||||||
|
) -> Result<LinuxContainer> {
|
||||||
|
let mut container = LinuxContainer::new(
|
||||||
|
id,
|
||||||
|
root.to_str()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.ok_or_else(|| anyhow!("failed to convert bundle path"))?
|
||||||
|
.as_str(),
|
||||||
|
config,
|
||||||
|
logger,
|
||||||
|
)?;
|
||||||
|
if let Some(socket_path) = console_socket.as_ref() {
|
||||||
|
container.set_console_socket(socket_path)?;
|
||||||
|
}
|
||||||
|
Ok(container)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_config_path<P: AsRef<Path>>(bundle: P) -> PathBuf {
|
pub fn get_config_path<P: AsRef<Path>>(bundle: P) -> PathBuf {
|
||||||
bundle.as_ref().join(CONFIG_FILE_NAME)
|
bundle.as_ref().join(CONFIG_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
211
src/tools/runk/libcontainer/src/init_builder.rs
Normal file
211
src/tools/runk/libcontainer/src/init_builder.rs
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use crate::container::{create_linux_container, get_config_path, ContainerLauncher};
|
||||||
|
use crate::status::Status;
|
||||||
|
use crate::utils::{canonicalize_spec_root, validate_spec};
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use derive_builder::Builder;
|
||||||
|
use oci::Spec;
|
||||||
|
use rustjail::specconv::CreateOpts;
|
||||||
|
use slog::{debug, Logger};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Used for create and run commands. It will prepare the options used for creating a new container.
|
||||||
|
#[derive(Default, Builder, Debug, Clone)]
|
||||||
|
#[builder(build_fn(validate = "Self::validate"))]
|
||||||
|
pub struct InitContainer {
|
||||||
|
id: String,
|
||||||
|
bundle: PathBuf,
|
||||||
|
root: PathBuf,
|
||||||
|
console_socket: Option<PathBuf>,
|
||||||
|
pid_file: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InitContainerBuilder {
|
||||||
|
/// pre-validate before building InitContainer
|
||||||
|
fn validate(&self) -> Result<(), String> {
|
||||||
|
// ensure container hasn't already been created
|
||||||
|
let id = self.id.as_ref().unwrap();
|
||||||
|
let root = self.root.as_ref().unwrap();
|
||||||
|
let status_path = Status::get_dir_path(root, id);
|
||||||
|
if status_path.exists() {
|
||||||
|
return Err(format!(
|
||||||
|
"container {} already exists at path {:?}",
|
||||||
|
id, root
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InitContainer {
|
||||||
|
/// Create ContainerLauncher that can be used to launch a new container.
|
||||||
|
/// It will read the spec under bundle path.
|
||||||
|
pub fn create_launcher(self, logger: &Logger) -> Result<ContainerLauncher> {
|
||||||
|
debug!(logger, "enter InitContainer::create_launcher {:?}", self);
|
||||||
|
let bundle_canon = self.bundle.canonicalize()?;
|
||||||
|
let config_path = get_config_path(&bundle_canon);
|
||||||
|
let mut spec = Spec::load(
|
||||||
|
config_path
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| anyhow!("invalid config path"))?,
|
||||||
|
)?;
|
||||||
|
// Only absolute rootfs path is valid when creating LinuxContainer later.
|
||||||
|
canonicalize_spec_root(&mut spec, &bundle_canon)?;
|
||||||
|
debug!(logger, "load spec from config file: {:?}", spec);
|
||||||
|
validate_spec(&spec, &self.console_socket)?;
|
||||||
|
|
||||||
|
let config = CreateOpts {
|
||||||
|
cgroup_name: "".to_string(),
|
||||||
|
use_systemd_cgroup: false,
|
||||||
|
// TODO: liboci-cli does not support --no-pivot option for create and run command.
|
||||||
|
// After liboci-cli supports the option, we will change the following code.
|
||||||
|
// no_pivot_root: self.no_pivot,
|
||||||
|
no_pivot_root: false,
|
||||||
|
no_new_keyring: false,
|
||||||
|
spec: Some(spec),
|
||||||
|
rootless_euid: false,
|
||||||
|
rootless_cgroup: false,
|
||||||
|
};
|
||||||
|
debug!(logger, "create LinuxContainer with config: {:?}", config);
|
||||||
|
let container =
|
||||||
|
create_linux_container(&self.id, &self.root, config, self.console_socket, logger)?;
|
||||||
|
|
||||||
|
Ok(ContainerLauncher::new(
|
||||||
|
&self.id,
|
||||||
|
&bundle_canon,
|
||||||
|
&self.root,
|
||||||
|
true,
|
||||||
|
container,
|
||||||
|
self.pid_file,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::container::CONFIG_FILE_NAME;
|
||||||
|
use crate::utils::test_utils::*;
|
||||||
|
use slog::o;
|
||||||
|
use std::fs::{create_dir, File};
|
||||||
|
use std::path::Path;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_init_container_validate() {
|
||||||
|
let root = tempdir().unwrap();
|
||||||
|
let id = TEST_CONTAINER_ID.to_string();
|
||||||
|
Status::create_dir(root.path(), id.as_str()).unwrap();
|
||||||
|
let result = InitContainerBuilder::default()
|
||||||
|
.id(id)
|
||||||
|
.root(root.path().to_path_buf())
|
||||||
|
.bundle(PathBuf::from(TEST_BUNDLE_PATH))
|
||||||
|
.pid_file(None)
|
||||||
|
.console_socket(None)
|
||||||
|
.build();
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_init_container_create_launcher() {
|
||||||
|
let logger = slog::Logger::root(slog::Discard, o!());
|
||||||
|
let root_dir = tempdir().unwrap();
|
||||||
|
let bundle_dir = tempdir().unwrap();
|
||||||
|
// create dummy rootfs
|
||||||
|
create_dir(bundle_dir.path().join(TEST_ROOTFS_PATH)).unwrap();
|
||||||
|
let config_file = bundle_dir.path().join(CONFIG_FILE_NAME);
|
||||||
|
let mut spec = create_dummy_spec();
|
||||||
|
let file = File::create(config_file).unwrap();
|
||||||
|
serde_json::to_writer(&file, &spec).unwrap();
|
||||||
|
|
||||||
|
spec.root.as_mut().unwrap().path = bundle_dir
|
||||||
|
.path()
|
||||||
|
.join(TEST_ROOTFS_PATH)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
let test_data = TestContainerData {
|
||||||
|
// Since tests are executed concurrently, container_id must be unique in tests with cgroup.
|
||||||
|
// Or the cgroup directory may be removed by other tests in advance.
|
||||||
|
id: String::from("test_init_container_create_launcher"),
|
||||||
|
bundle: bundle_dir.path().to_path_buf(),
|
||||||
|
root: root_dir.into_path(),
|
||||||
|
console_socket: Some(PathBuf::from(TEST_CONSOLE_SOCKET_PATH)),
|
||||||
|
config: CreateOpts {
|
||||||
|
spec: Some(spec),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
pid_file: Some(PathBuf::from(TEST_PID_FILE_PATH)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let launcher = InitContainerBuilder::default()
|
||||||
|
.id(test_data.id.clone())
|
||||||
|
.bundle(test_data.bundle.clone())
|
||||||
|
.root(test_data.root.clone())
|
||||||
|
.console_socket(test_data.console_socket.clone())
|
||||||
|
.pid_file(test_data.pid_file.clone())
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.create_launcher(&logger)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// LinuxContainer doesn't impl PartialEq, so we need to compare the fields manually.
|
||||||
|
assert!(launcher.init);
|
||||||
|
assert_eq!(launcher.bundle, test_data.bundle);
|
||||||
|
assert_eq!(launcher.state_root, test_data.root);
|
||||||
|
assert_eq!(launcher.pid_file, test_data.pid_file);
|
||||||
|
assert_eq!(launcher.runner.id, test_data.id);
|
||||||
|
assert_eq!(launcher.runner.config.spec, test_data.config.spec);
|
||||||
|
assert_eq!(
|
||||||
|
Some(launcher.runner.console_socket),
|
||||||
|
test_data.console_socket
|
||||||
|
);
|
||||||
|
// If it is run by root, create_launcher will create cgroup dirs successfully. So we need to do some cleanup stuff.
|
||||||
|
if nix::unistd::Uid::effective().is_root() {
|
||||||
|
clean_up_cgroup(Path::new(&test_data.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_init_container_tty_err() {
|
||||||
|
let logger = slog::Logger::root(slog::Discard, o!());
|
||||||
|
let bundle_dir = tempdir().unwrap();
|
||||||
|
let config_file = bundle_dir.path().join(CONFIG_FILE_NAME);
|
||||||
|
|
||||||
|
let mut spec = oci::Spec {
|
||||||
|
process: Some(oci::Process::default()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
spec.process.as_mut().unwrap().terminal = true;
|
||||||
|
|
||||||
|
let file = File::create(config_file).unwrap();
|
||||||
|
serde_json::to_writer(&file, &spec).unwrap();
|
||||||
|
|
||||||
|
let test_data = TestContainerData {
|
||||||
|
id: String::from(TEST_CONTAINER_ID),
|
||||||
|
bundle: bundle_dir.into_path(),
|
||||||
|
root: tempdir().unwrap().into_path(),
|
||||||
|
console_socket: None,
|
||||||
|
config: CreateOpts {
|
||||||
|
spec: Some(spec),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
pid_file: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = InitContainerBuilder::default()
|
||||||
|
.id(test_data.id.clone())
|
||||||
|
.bundle(test_data.bundle.clone())
|
||||||
|
.root(test_data.root.clone())
|
||||||
|
.console_socket(test_data.console_socket.clone())
|
||||||
|
.pid_file(test_data.pid_file)
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.create_launcher(&logger);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,9 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
pub mod builder;
|
pub mod activated_builder;
|
||||||
pub mod cgroup;
|
pub mod cgroup;
|
||||||
pub mod container;
|
pub mod container;
|
||||||
|
pub mod init_builder;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use nix::sys::stat::Mode;
|
use nix::sys::stat::Mode;
|
||||||
use oci::Process;
|
use oci::{Process, Spec};
|
||||||
use std::{
|
use std::{
|
||||||
fs::{DirBuilder, File},
|
fs::{DirBuilder, File},
|
||||||
io::{prelude::*, BufReader},
|
io::{prelude::*, BufReader},
|
||||||
os::unix::fs::DirBuilderExt,
|
os::unix::fs::DirBuilderExt,
|
||||||
path::Path,
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn lines_from_file<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
|
pub fn lines_from_file<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
|
||||||
@ -34,6 +34,39 @@ pub fn create_dir_with_mode<P: AsRef<Path>>(path: P, mode: Mode, recursive: bool
|
|||||||
.create(path)?)
|
.create(path)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If root in spec is a relative path, make it absolute.
|
||||||
|
pub fn canonicalize_spec_root(spec: &mut Spec, bundle_canon: &Path) -> Result<()> {
|
||||||
|
let mut spec_root = spec
|
||||||
|
.root
|
||||||
|
.as_mut()
|
||||||
|
.ok_or_else(|| anyhow!("root config was not present in the spec file"))?;
|
||||||
|
let rootfs_path = Path::new(&spec_root.path);
|
||||||
|
if !rootfs_path.is_absolute() {
|
||||||
|
spec_root.path = bundle_canon
|
||||||
|
.join(rootfs_path)
|
||||||
|
.canonicalize()?
|
||||||
|
.to_str()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.ok_or_else(|| anyhow!("failed to convert a rootfs path into a canonical path"))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check whether spec is valid. Now runk only support detach mode.
|
||||||
|
pub fn validate_spec(spec: &Spec, console_socket: &Option<PathBuf>) -> Result<()> {
|
||||||
|
validate_process_spec(&spec.process)?;
|
||||||
|
if let Some(process) = spec.process.as_ref() {
|
||||||
|
// runk always launches containers with detached mode, so users have to
|
||||||
|
// use a console socket with run or create operation when a terminal is used.
|
||||||
|
if process.terminal && console_socket.is_none() {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"cannot allocate a pseudo-TTY without setting a console socket"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// Validate process just like runc, https://github.com/opencontainers/runc/pull/623
|
// Validate process just like runc, https://github.com/opencontainers/runc/pull/623
|
||||||
pub fn validate_process_spec(process: &Option<Process>) -> Result<()> {
|
pub fn validate_process_spec(process: &Option<Process>) -> Result<()> {
|
||||||
let process = process
|
let process = process
|
||||||
@ -56,19 +89,25 @@ pub fn validate_process_spec(process: &Option<Process>) -> Result<()> {
|
|||||||
pub(crate) mod test_utils {
|
pub(crate) mod test_utils {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::status::Status;
|
use crate::status::Status;
|
||||||
|
use chrono::DateTime;
|
||||||
use nix::unistd::getpid;
|
use nix::unistd::getpid;
|
||||||
use oci::Process;
|
use oci::{ContainerState, LinuxNamespace, Process, Root, Spec, State as OCIState};
|
||||||
use oci::State as OCIState;
|
use rustjail::{
|
||||||
use oci::{ContainerState, Root, Spec};
|
cgroups::fs::Manager as CgroupManager, container::TYPETONAME, specconv::CreateOpts,
|
||||||
use rustjail::cgroups::fs::Manager as CgroupManager;
|
};
|
||||||
use rustjail::specconv::CreateOpts;
|
use std::{fs::create_dir_all, path::Path, time::SystemTime};
|
||||||
use std::path::Path;
|
use tempfile::tempdir;
|
||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
pub const TEST_CONTAINER_ID: &str = "test";
|
pub const TEST_CONTAINER_ID: &str = "test";
|
||||||
pub const TEST_STATE_ROOT_PATH: &str = "/state";
|
pub const TEST_STATE_ROOT_PATH: &str = "/state";
|
||||||
pub const TEST_BUNDLE_PATH: &str = "/bundle";
|
pub const TEST_BUNDLE_PATH: &str = "/bundle";
|
||||||
pub const TEST_ANNOTATION: &str = "test";
|
pub const TEST_ROOTFS_PATH: &str = "rootfs";
|
||||||
|
pub const TEST_ANNOTATION: &str = "test-annotation";
|
||||||
|
pub const TEST_CONSOLE_SOCKET_PATH: &str = "/test-console-sock";
|
||||||
|
pub const TEST_PROCESS_FILE_NAME: &str = "process.json";
|
||||||
|
pub const TEST_PID_FILE_PATH: &str = "/test-pid";
|
||||||
|
pub const TEST_HOST_NAME: &str = "test-host";
|
||||||
|
pub const TEST_OCI_SPEC_VERSION: &str = "1.0.2";
|
||||||
pub const TEST_CGM_DATA: &str = r#"{
|
pub const TEST_CGM_DATA: &str = r#"{
|
||||||
"paths": {
|
"paths": {
|
||||||
"devices": "/sys/fs/cgroup/devices"
|
"devices": "/sys/fs/cgroup/devices"
|
||||||
@ -78,7 +117,46 @@ pub(crate) mod test_utils {
|
|||||||
},
|
},
|
||||||
"cpath": "test"
|
"cpath": "test"
|
||||||
}"#;
|
}"#;
|
||||||
pub const TEST_ROOTFS_PATH: &str = "rootfs";
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TestContainerData {
|
||||||
|
pub id: String,
|
||||||
|
pub bundle: PathBuf,
|
||||||
|
pub root: PathBuf,
|
||||||
|
pub console_socket: Option<PathBuf>,
|
||||||
|
pub pid_file: Option<PathBuf>,
|
||||||
|
pub config: CreateOpts,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_dummy_spec() -> Spec {
|
||||||
|
let linux = oci::Linux {
|
||||||
|
namespaces: TYPETONAME
|
||||||
|
.iter()
|
||||||
|
.filter(|&(_, &name)| name != "user")
|
||||||
|
.map(|ns| LinuxNamespace {
|
||||||
|
r#type: ns.0.to_string(),
|
||||||
|
path: "".to_string(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
Spec {
|
||||||
|
version: TEST_OCI_SPEC_VERSION.to_string(),
|
||||||
|
process: Some(Process {
|
||||||
|
args: vec!["sleep".to_string(), "10".to_string()],
|
||||||
|
env: vec!["PATH=/bin:/usr/bin".to_string()],
|
||||||
|
cwd: "/".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
hostname: TEST_HOST_NAME.to_string(),
|
||||||
|
root: Some(Root {
|
||||||
|
path: TEST_ROOTFS_PATH.to_string(),
|
||||||
|
readonly: false,
|
||||||
|
}),
|
||||||
|
linux: Some(linux),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_dummy_opts() -> CreateOpts {
|
pub fn create_dummy_opts() -> CreateOpts {
|
||||||
let spec = Spec {
|
let spec = Spec {
|
||||||
@ -98,7 +176,7 @@ pub(crate) mod test_utils {
|
|||||||
|
|
||||||
pub fn create_dummy_oci_state() -> OCIState {
|
pub fn create_dummy_oci_state() -> OCIState {
|
||||||
OCIState {
|
OCIState {
|
||||||
version: "1.0.0".to_string(),
|
version: TEST_OCI_SPEC_VERSION.to_string(),
|
||||||
id: TEST_CONTAINER_ID.to_string(),
|
id: TEST_CONTAINER_ID.to_string(),
|
||||||
status: ContainerState::Running,
|
status: ContainerState::Running,
|
||||||
pid: getpid().as_raw(),
|
pid: getpid().as_raw(),
|
||||||
@ -133,6 +211,29 @@ pub(crate) mod test_utils {
|
|||||||
status
|
status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_custom_dummy_status(id: &str, pid: i32, root: &Path, spec: &Spec) -> Status {
|
||||||
|
let start_time = procfs::process::Process::new(pid)
|
||||||
|
.unwrap()
|
||||||
|
.stat()
|
||||||
|
.unwrap()
|
||||||
|
.starttime;
|
||||||
|
Status {
|
||||||
|
oci_version: spec.version.clone(),
|
||||||
|
id: id.to_string(),
|
||||||
|
pid,
|
||||||
|
root: root.to_path_buf(),
|
||||||
|
bundle: PathBuf::from(TEST_BUNDLE_PATH),
|
||||||
|
rootfs: TEST_ROOTFS_PATH.to_string(),
|
||||||
|
process_start_time: start_time,
|
||||||
|
created: DateTime::from(SystemTime::now()),
|
||||||
|
cgroup_manager: serde_json::from_str(TEST_CGM_DATA).unwrap(),
|
||||||
|
config: CreateOpts {
|
||||||
|
spec: Some(spec.clone()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_dummy_cgroup(cpath: &Path) -> cgroups::Cgroup {
|
pub fn create_dummy_cgroup(cpath: &Path) -> cgroups::Cgroup {
|
||||||
cgroups::Cgroup::new(cgroups::hierarchies::auto(), cpath)
|
cgroups::Cgroup::new(cgroups::hierarchies::auto(), cpath)
|
||||||
}
|
}
|
||||||
@ -142,6 +243,31 @@ pub(crate) mod test_utils {
|
|||||||
cgroup.delete().unwrap();
|
cgroup.delete().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_canonicalize_spec_root() {
|
||||||
|
let gen_spec = |p: &str| -> Spec {
|
||||||
|
Spec {
|
||||||
|
root: Some(Root {
|
||||||
|
path: p.to_string(),
|
||||||
|
readonly: false,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rootfs_name = TEST_ROOTFS_PATH;
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let bundle_dir = temp_dir.path();
|
||||||
|
let abs_root = bundle_dir.join(rootfs_name);
|
||||||
|
create_dir_all(abs_root.clone()).unwrap();
|
||||||
|
let mut spec = gen_spec(abs_root.to_str().unwrap());
|
||||||
|
assert!(canonicalize_spec_root(&mut spec, bundle_dir).is_ok());
|
||||||
|
assert_eq!(spec.root.unwrap().path, abs_root.to_str().unwrap());
|
||||||
|
let mut spec = gen_spec(rootfs_name);
|
||||||
|
assert!(canonicalize_spec_root(&mut spec, bundle_dir).is_ok());
|
||||||
|
assert_eq!(spec.root.unwrap().path, abs_root.to_str().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_validate_process_spec() {
|
pub fn test_validate_process_spec() {
|
||||||
let valid_process = Process {
|
let valid_process = Process {
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use libcontainer::{builder::InitContainerBuilder, container::ContainerAction};
|
use libcontainer::{container::ContainerAction, init_builder::InitContainerBuilder};
|
||||||
|
|
||||||
use liboci_cli::Create;
|
use liboci_cli::Create;
|
||||||
use slog::{info, Logger};
|
use slog::{info, Logger};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use libcontainer::builder::ActivatedContainerBuilder;
|
use libcontainer::activated_builder::ActivatedContainerBuilder;
|
||||||
use libcontainer::container::ContainerAction;
|
use libcontainer::container::ContainerAction;
|
||||||
use liboci_cli::Exec;
|
use liboci_cli::Exec;
|
||||||
use slog::{info, Logger};
|
use slog::{info, Logger};
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use libcontainer::{builder::InitContainerBuilder, container::ContainerAction};
|
use libcontainer::{container::ContainerAction, init_builder::InitContainerBuilder};
|
||||||
use liboci_cli::Run;
|
use liboci_cli::Run;
|
||||||
use slog::{info, Logger};
|
use slog::{info, Logger};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
Loading…
Reference in New Issue
Block a user