tools: Remove runk

The runk tool hasn't been supported for a few years, with no maintainers
since ManaSugi stopped being involved in the project and the CI was
disabled in 2024.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
This commit is contained in:
Fabiano Fidêncio
2026-01-15 20:53:27 +01:00
parent f68c25de6a
commit 96e1fb4ca6
38 changed files with 0 additions and 7278 deletions

View File

@@ -12,7 +12,6 @@ updates:
- "/src/tools/agent-ctl"
- "/src/tools/genpolicy"
- "/src/tools/kata-ctl"
- "/src/tools/runk"
- "/src/tools/trace-forwarder"
schedule:
interval: "daily"

View File

@@ -163,42 +163,6 @@ jobs:
timeout-minutes: 10
run: bash tests/integration/nydus/gha-run.sh run
run-runk:
name: run-runk
# Skip runk tests as we have no maintainers. TODO: Decide when to remove altogether
if: false
runs-on: ubuntu-22.04
env:
CONTAINERD_VERSION: lts
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ inputs.commit-hash }}
fetch-depth: 0
persist-credentials: false
- name: Rebase atop of the latest target branch
run: |
./tests/git-helper.sh "rebase-atop-of-the-latest-target-branch"
env:
TARGET_BRANCH: ${{ inputs.target-branch }}
- name: Install dependencies
run: bash tests/integration/runk/gha-run.sh install-dependencies
- name: get-kata-tarball
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: kata-static-tarball-amd64${{ inputs.tarball-suffix }}
path: kata-artifacts
- name: Install kata
run: bash tests/integration/runk/gha-run.sh install-kata kata-artifacts
- name: Run runk tests
timeout-minutes: 10
run: bash tests/integration/runk/gha-run.sh run
run-tracing:
name: run-tracing
strategy:

View File

@@ -1,54 +0,0 @@
name: CI | Run runk tests
on:
workflow_call:
inputs:
tarball-suffix:
required: false
type: string
commit-hash:
required: false
type: string
target-branch:
required: false
type: string
default: ""
permissions: {}
jobs:
run-runk:
name: run-runk
# Skip runk tests as we have no maintainers. TODO: Decide when to remove altogether
if: false
runs-on: ubuntu-22.04
env:
CONTAINERD_VERSION: lts
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ inputs.commit-hash }}
fetch-depth: 0
persist-credentials: false
- name: Rebase atop of the latest target branch
run: |
./tests/git-helper.sh "rebase-atop-of-the-latest-target-branch"
env:
TARGET_BRANCH: ${{ inputs.target-branch }}
- name: Install dependencies
run: bash tests/integration/runk/gha-run.sh install-dependencies
env:
GH_TOKEN: ${{ github.token }}
- name: get-kata-tarball
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: kata-static-tarball-amd64${{ inputs.tarball-suffix }}
path: kata-artifacts
- name: Install kata
run: bash tests/integration/runk/gha-run.sh install-kata kata-artifacts
- name: Run runk tests
run: bash tests/integration/runk/gha-run.sh run

View File

@@ -18,7 +18,6 @@ TOOLS =
TOOLS += agent-ctl
TOOLS += kata-ctl
TOOLS += log-parser
TOOLS += runk
TOOLS += trace-forwarder
STANDARD_TARGETS = build check clean install static-checks-build test vendor

View File

@@ -139,7 +139,6 @@ The table below lists the remaining parts of the project:
| [`agent-ctl`](src/tools/agent-ctl) | utility | Tool that provides low-level access for testing the agent. |
| [`kata-ctl`](src/tools/kata-ctl) | utility | Tool that provides advanced commands and debug facilities. |
| [`trace-forwarder`](src/tools/trace-forwarder) | utility | Agent tracing helper. |
| [`runk`](src/tools/runk) | utility | Standard OCI container runtime based on the agent. |
| [`ci`](.github/workflows) | CI | Continuous Integration configuration files and scripts. |
| [`ocp-ci`](ci/openshift-ci/README.md) | CI | Continuous Integration configuration for the OpenShift pipelines. |
| [`katacontainers.io`](https://github.com/kata-containers/www.katacontainers.io) | Source for the [`katacontainers.io`](https://www.katacontainers.io) site. |

View File

@@ -1 +0,0 @@
/vendor/

3943
src/tools/runk/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +0,0 @@
[package]
name = "runk"
version = "0.0.1"
authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>"]
description = "runk: Kata OCI container runtime based on Kata agent"
license = "Apache-2.0"
edition = "2018"
[dependencies]
libcontainer = { path = "./libcontainer" }
rustjail = { path = "../../agent/rustjail", features = [
"standard-oci-runtime",
] }
runtime-spec = { path = "../../libs/runtime-spec" }
oci-spec = { version = "0.8.1", features = ["runtime"] }
logging = { path = "../../libs/logging" }
liboci-cli = "0.5.3"
clap = { version = "4.5.40", features = ["derive", "cargo"] }
libc = "0.2.108"
nix = "0.23.0"
anyhow = "1.0.52"
slog = "2.7.0"
chrono = { version = "0.4.19", features = ["serde"] }
slog-async = "2.7.0"
tokio = { version = "1.44.2", features = ["full"] }
serde = { version = "1.0.133", features = ["derive"] }
serde_json = "1.0.74"
uzers = "0.12.1"
tabwriter = "1.2.1"
[features]
seccomp = ["rustjail/seccomp"]
[dev-dependencies]
tempfile = "3.19.1"
[workspace]
members = ["libcontainer"]

View File

@@ -1,67 +0,0 @@
# Copyright 2021-2022 Sony Group Corporation
#
# SPDX-License-Identifier: Apache-2.0
#
# LIBC=musl|gnu (default: gnu)
LIBC ?= gnu
include ../../../utils.mk
TARGET = runk
TARGET_PATH = target/$(TRIPLE)/$(BUILD_TYPE)/$(TARGET)
AGENT_SOURCE_PATH = ../../agent
EXTRA_RUSTFEATURES :=
# Define if runk enables seccomp support (default: yes)
SECCOMP := yes
# BINDIR is a directory for installing executable programs
BINDIR := /usr/local/bin
ifeq ($(SECCOMP),yes)
override EXTRA_RUSTFEATURES += seccomp
endif
ifneq ($(EXTRA_RUSTFEATURES),)
override EXTRA_RUSTFEATURES := --features "$(EXTRA_RUSTFEATURES)"
endif
.DEFAULT_GOAL := default
default: build
build:
@RUSTFLAGS="$(EXTRA_RUSTFLAGS) --deny warnings" cargo build --target $(TRIPLE) --$(BUILD_TYPE) $(EXTRA_RUSTFEATURES)
static-checks-build:
@echo "INFO: static-checks-build do nothing.."
install:
install -D $(TARGET_PATH) $(BINDIR)/$(TARGET)
clean:
cargo clean
vendor:
cargo vendor
test: test-runk test-agent
test-runk:
cargo test --all --target $(TRIPLE) $(EXTRA_RUSTFEATURES) -- --nocapture
test-agent:
make test -C $(AGENT_SOURCE_PATH) STANDARD_OCI_RUNTIME=yes
check: standard_rust_check
.PHONY: \
build \
install \
clean \
clippy \
format \
vendor \
test \
check \

View File

@@ -1,352 +0,0 @@
# runk
## Overview
> **Warnings:**
> `runk` is currently an experimental tool.
> Only continue if you are using a non-critical system.
`runk` is a standard OCI container runtime written in Rust based on a modified version of
the [Kata Container agent](https://github.com/kata-containers/kata-containers/tree/main/src/agent), `kata-agent`.
`runk` conforms to the [OCI Container Runtime specifications](https://github.com/opencontainers/runtime-spec).
Unlike the [Kata Container runtime](https://github.com/kata-containers/kata-containers/tree/main/src/agent#features),
`kata-runtime`, `runk` spawns and runs containers on the host machine directly.
The user can run `runk` in the same way as the existing container runtimes such as `runc`,
the most used implementation of the OCI runtime specs.
## Why does `runk` exist?
The `kata-agent` is a process running inside a virtual machine (VM) as a supervisor for managing containers
and processes running within those containers.
In other words, the `kata-agent` is a kind of "low-level" container runtime inside VM because the agent
spawns and runs containers according to the OCI runtime specs.
However, the `kata-agent` does not have the OCI Command-Line Interface (CLI) that is defined in the
[runtime spec](https://github.com/opencontainers/runtime-spec/blob/main/runtime.md).
The `kata-runtime` provides the CLI part of the Kata Containers runtime component,
but the `kata-runtime` is a container runtime for creating hardware-virtualized containers running on the host.
`runk` is a Rust-based standard OCI container runtime that manages normal containers,
not hardware-virtualized containers.
`runk` aims to become one of the alternatives to existing OCI compliant container runtimes.
The `kata-agent` has most of the [features](https://github.com/kata-containers/kata-containers/tree/main/src/agent#features)
needed for the container runtime and delivers high performance with a low memory footprint owing to the
implementation by Rust language.
Therefore, `runk` leverages the mechanism of the `kata-agent` to avoid reinventing the wheel.
## Performance
`runk` is faster than `runc` and has a lower memory footprint.
This table shows the average of the elapsed time and the memory footprint (maximum resident set size)
for running sequentially 100 containers, the containers run `/bin/true` using `run` command with
[detached mode](https://github.com/opencontainers/runc/blob/main/docs/terminals.md#detached)
on 12 CPU cores (`3.8 GHz AMD Ryzen 9 3900X`) and 32 GiB of RAM.
`runk` always runs containers with detached mode currently.
Evaluation Results:
| | `runk` (v0.0.1) | `runc` (v1.0.3) | `crun` (v1.4.2) |
|-----------------------|---------------|---------------|---------------|
| time [ms] | 39.83 | 50.39 | 38.41 |
| memory footprint [MB] | 4.013 | 10.78 | 1.738 |
## Status of `runk`
We drafted the initial code here, and any contributions to `runk` and [`kata-agent`](https://github.com/kata-containers/kata-containers/tree/main/src/agent)
are welcome.
Regarding features compared to `runc`, see the `Status of runk` section in the [issue](https://github.com/kata-containers/kata-containers/issues/2784).
## Building
In order to enable seccomp support, you need to install the `libseccomp` library on
your platform.
> e.g. `libseccomp-dev` for Ubuntu, or `libseccomp-devel` for CentOS
You can build `runk`:
```bash
$ cd runk
$ make
```
If you want to build a statically linked binary of `runk`, set the environment
variables for the [`libseccomp` crate](https://github.com/libseccomp-rs/libseccomp-rs) and
set the `LIBC` to `musl`:
```bash
$ export LIBSECCOMP_LINK_TYPE=static
$ export LIBSECCOMP_LIB_PATH="the path of the directory containing libseccomp.a"
$ export LIBC=musl
$ make
```
> **Note**:
>
> - If the compilation fails when `runk` tries to link the `libseccomp` library statically
> against `musl`, you will need to build the `libseccomp` manually with `-U_FORTIFY_SOURCE`.
> For the details, see [our script](https://github.com/kata-containers/kata-containers/blob/main/ci/install_libseccomp.sh)
> to install the `libseccomp` for the agent.
> - On `ppc64le` and `s390x`, `glibc` should be used even if `LIBC=musl` is specified.
> - If you do not want to enable seccomp support, run `make SECCOMP=no`.
To install `runk` into default directory for executable program (`/usr/local/bin`):
```bash
$ sudo -E make install
```
## Using `runk` directly
Please note that `runk` is a low level tool not developed with an end user in mind.
It is mostly employed by other higher-level container software like `containerd`.
If you still want to use `runk` directly, here's how.
### Prerequisites
It is necessary to create an OCI bundle to use the tool. The simplest method is:
``` bash
$ bundle_dir="bundle"
$ rootfs_dir="$bundle_dir/rootfs"
$ image="busybox"
$ mkdir -p "$rootfs_dir" && (cd "$bundle_dir" && runk spec)
$ sudo docker export $(sudo docker create "$image") | tar -C "$rootfs_dir" -xf -
```
> **Note:**
> If you use the unmodified `runk spec` template, this should give a `sh` session inside the container.
> However, if you use `runk` directly and run a container with the unmodified template,
> `runk` cannot launch the `sh` session because `runk` does not support terminal handling yet.
> You need to edit the process field in the `config.json` should look like this below
> with `"terminal": false` and `"args": ["sleep", "10"]`.
```json
"process": {
"terminal": false,
"user": {
"uid": 0,
"gid": 0
},
"args": [
"sleep",
"10"
],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"cwd": "/",
[...]
}
```
If you want to launch the `sh` session inside the container, you need to run `runk` from `containerd`.
Please refer to the [Using `runk` from containerd](#using-runk-from-containerd) section
### Running a container
Now you can go through the [lifecycle operations](https://github.com/opencontainers/runtime-spec/blob/main/runtime.md)
in your shell.
You need to run `runk` as `root` because `runk` does not have the rootless feature which is the ability
to run containers without root privileges.
```bash
$ cd $bundle_dir
# Create a container
$ sudo runk create test
# View the container is created and in the "created" state
$ sudo runk state test
# Start the process inside the container
$ sudo runk start test
# After 10 seconds view that the container has exited and is now in the "stopped" state
$ sudo runk state test
# Now delete the container
$ sudo runk delete test
```
## Using `runk` from `Docker`
`runk` can run containers using [`Docker`](https://github.com/docker).
First, install `Docker` from package by following the
[`Docker` installation instructions](https://docs.docker.com/engine/install/).
### Running a container with `Docker` command line
Start the docker daemon:
```bash
$ sudo dockerd --experimental --add-runtime="runk=/usr/local/bin/runk"
```
> **Note:**
> Before starting the `dockerd`, you need to stop the normal docker daemon
> running on your environment (i.e., `systemctl stop docker`).
Launch a container in a different terminal:
```bash
$ sudo docker run -it --rm --runtime runk busybox sh
/ #
```
## Using `runk` from `Podman`
`runk` can run containers using [`Podman`](https://github.com/containers/podman).
First, install `Podman` from source code or package by following the
[`Podman` installation instructions](https://podman.io/getting-started/installation).
### Running a container with `Podman` command line
```bash
$ sudo podman --runtime /usr/local/bin/runk run -it --rm busybox sh
/ #
```
> **Note:**
> `runk` does not support some commands except
> [OCI standard operations](https://github.com/opencontainers/runtime-spec/blob/main/runtime.md#operations)
> yet, so those commands do not work in `Docker/Podman`. Regarding commands currently
> implemented in `runk`, see the [Status of `runk`](#status-of-runk) section.
## Using `runk` from `containerd`
`runk` can run containers with the containerd runtime handler support on `containerd`.
### Prerequisites for `runk` with containerd
* `containerd` v1.2.4 or above
* `cri-tools`
> **Note:**
> [`cri-tools`](https://github.com/kubernetes-sigs/cri-tools) is a set of tools for CRI
> used for development and testing.
Install `cri-tools` from source code:
```bash
$ go get github.com/kubernetes-sigs/cri-tools
$ pushd $GOPATH/src/github.com/kubernetes-sigs/cri-tools
$ make
$ sudo -E make install
$ popd
```
Write the `crictl` configuration file:
``` bash
$ cat <<EOF | sudo tee /etc/crictl.yaml
runtime-endpoint: unix:///run/containerd/containerd.sock
EOF
```
### Configure `containerd` to use `runk`
Update `/etc/containerd/config.toml`:
```bash
$ cat <<EOF | sudo tee /etc/containerd/config.toml
version = 2
[plugins."io.containerd.runtime.v1.linux"]
shim_debug = true
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runk]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runk.options]
BinaryName = "/usr/local/bin/runk"
EOF
```
Restart `containerd`:
```bash
$ sudo systemctl restart containerd
```
### Running a container with `crictl` command line
You can run containers in `runk` via containerd's CRI.
Pull the `busybox` image:
``` bash
$ sudo crictl pull busybox
```
Create the sandbox configuration:
``` bash
$ cat <<EOF | tee sandbox.json
{
"metadata": {
"name": "busybox-sandbox",
"namespace": "default",
"attempt": 1,
"uid": "hdishd83djaidwnduwk28bcsb"
},
"log_directory": "/tmp",
"linux": {
}
}
EOF
```
Create the container configuration:
``` bash
$ cat <<EOF | tee container.json
{
"metadata": {
"name": "busybox"
},
"image": {
"image": "docker.io/busybox"
},
"command": [
"sh"
],
"envs": [
{
"key": "PATH",
"value": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
},
{
"key": "TERM",
"value": "xterm"
}
],
"log_path": "busybox.0.log",
"stdin": true,
"stdin_once": true,
"tty": true
}
EOF
```
With the `crictl` command line of `cri-tools`, you can specify runtime class with `-r` or `--runtime` flag.
Launch a sandbox and container using the `crictl`:
```bash
# Run a container inside a sandbox
$ sudo crictl run -r runk container.json sandbox.json
f492eee753887ba3dfbba9022028975380739aba1269df431d097b73b23c3871
# Attach to the running container
$ sudo crictl attach --stdin --tty f492eee753887ba3dfbba9022028975380739aba1269df431d097b73b23c3871
/ #
```

View File

@@ -1,32 +0,0 @@
[package]
name = "libcontainer"
version = "0.0.1"
authors = ["The Kata Containers community <kata-dev@lists.katacontainers.io>"]
description = "Library for runk container"
license = "Apache-2.0"
edition = "2018"
[dependencies]
rustjail = { path = "../../../agent/rustjail", features = [
"standard-oci-runtime",
] }
runtime-spec = { path = "../../../libs/runtime-spec" }
oci-spec = { version = "0.8.1", features = ["runtime"] }
kata-sys-util = { path = "../../../libs/kata-sys-util" }
logging = { path = "../../../libs/logging" }
derive_builder = "0.10.2"
libc = "0.2.108"
nix = "0.23.0"
anyhow = "1.0.52"
slog = "2.7.0"
chrono = { version = "0.4.19", features = ["serde"] }
serde = { version = "1.0.133", features = ["derive"] }
serde_json = "1.0.74"
scopeguard = "1.1.0"
cgroups = { package = "cgroups-rs", git = "https://github.com/kata-containers/cgroups-rs", rev = "v0.3.5" }
procfs = "0.14.0"
[dev-dependencies]
tempfile = "3.19.1"
test-utils = { path = "../../../libs/test-utils" }
protocols = { path = "../../../libs/protocols" }

View File

@@ -1,336 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use crate::container::{load_linux_container, Container, ContainerLauncher};
use crate::status::Status;
use crate::utils::validate_spec;
use anyhow::{anyhow, Result};
use derive_builder::Builder;
use oci::{Process as OCIProcess, Spec};
use oci_spec::runtime as oci;
use runtime_spec::ContainerState;
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 mut 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 spec = container
.status
.config
.spec
.as_mut()
.ok_or_else(|| anyhow!("spec config was not present"))?;
self.adapt_exec_spec(spec, container.status.pid, logger)?;
debug!(logger, "adapted spec: {:?}", spec);
validate_spec(spec, &self.console_socket)?;
debug!(
logger,
"load LinuxContainer with config: {:?}", &container.status.config
);
let runner = load_linux_container(&container.status, 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.set_process(Some(Self::get_process(process_path)?));
} else if let Some(process) = spec.process_mut().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.set_args(Some(self.args.clone()));
process.set_no_new_privileges(Some(self.no_new_privs));
process.set_terminal(Some(self.tty));
if let Some(cwd) = self.cwd.as_ref() {
process.set_cwd(cwd.as_path().to_path_buf());
}
if let Some(process_env) = process.env_mut() {
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_spec::runtime::{LinuxBuilder, LinuxNamespaceBuilder, ProcessBuilder, 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_mut()
.as_mut()
.unwrap()
.set_path(bundle_dir.path().join(TEST_ROOTFS_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 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 = LinuxBuilder::default()
.namespaces(
TYPETONAME
.iter()
.filter(|&(_, &name)| name != "user")
.map(|ns| {
LinuxNamespaceBuilder::default()
.typ(ns.0.clone())
.path(PathBuf::from(&format!("/proc/{}/ns/{}", pid, ns.1)))
.build()
.unwrap()
})
.collect::<Vec<_>>(),
)
.build()
.unwrap();
spec.set_linux(Some(linux));
let process = ProcessBuilder::default()
.terminal(result.tty)
.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(),
])
.no_new_privileges(result.no_new_privs)
.build()
.unwrap();
spec.set_process(Some(process));
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 mut process_template = OCIProcess::default();
process_template.set_args(Some(vec!["sleep".to_string(), "10".to_string()]));
process_template.set_cwd(PathBuf::from("/"));
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_mut()
.as_mut()
.unwrap()
.set_path(bundle_dir.path().join(TEST_ROOTFS_PATH));
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(Some(PathBuf::from(TEST_CONSOLE_SOCKET_PATH)))
.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()
.clone()
.unwrap(),
process_template
);
}
}

View File

@@ -1,77 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::anyhow;
use anyhow::Result;
use cgroups;
use cgroups::freezer::{FreezerController, FreezerState};
use std::{thread, time};
// Try to remove the provided cgroups path five times with increasing delay between tries.
// If after all there are not removed cgroups, an appropriate error will be returned.
pub fn remove_cgroup_dir(cgroup: &cgroups::Cgroup) -> Result<()> {
let mut retries = 5;
let mut delay = time::Duration::from_millis(10);
while retries != 0 {
if retries != 5 {
delay *= 2;
thread::sleep(delay);
}
if cgroup.delete().is_ok() {
return Ok(());
}
retries -= 1;
}
Err(anyhow!("failed to remove cgroups paths"))
}
// Make sure we get a stable freezer state, so retry if the cgroup is still undergoing freezing.
pub fn get_freezer_state(freezer: &FreezerController) -> Result<FreezerState> {
let mut retries = 10;
while retries != 0 {
let state = freezer.state()?;
match state {
FreezerState::Thawed => return Ok(FreezerState::Thawed),
FreezerState::Frozen => return Ok(FreezerState::Frozen),
FreezerState::Freezing => {
// sleep for 10 ms, wait for the cgroup to finish freezing
thread::sleep(time::Duration::from_millis(10));
retries -= 1;
}
}
}
Ok(FreezerState::Freezing)
}
// check whether freezer state is frozen
pub fn is_paused(cgroup: &cgroups::Cgroup) -> Result<bool> {
let freezer_controller: &FreezerController = cgroup
.controller_of()
.ok_or_else(|| anyhow!("failed to get freezer controller"))?;
let freezer_state = get_freezer_state(freezer_controller)?;
match freezer_state {
FreezerState::Frozen => Ok(true),
_ => Ok(false),
}
}
pub fn freeze(cgroup: &cgroups::Cgroup, state: FreezerState) -> Result<()> {
let freezer_controller: &FreezerController = cgroup
.controller_of()
.ok_or_else(|| anyhow!("failed to get freezer controller"))?;
match state {
FreezerState::Frozen => {
freezer_controller.freeze()?;
}
FreezerState::Thawed => {
freezer_controller.thaw()?;
}
_ => return Err(anyhow!("invalid freezer state")),
}
Ok(())
}

View File

@@ -1,437 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use crate::cgroup::{freeze, remove_cgroup_dir};
use crate::status::{self, get_current_container_state, Status};
use anyhow::{anyhow, Result};
use cgroups;
use cgroups::freezer::FreezerState;
use cgroups::hierarchies::is_cgroup2_unified_mode;
use nix::sys::signal::kill;
use nix::{
sys::signal::Signal,
sys::signal::SIGKILL,
unistd::{chdir, unlink, Pid},
};
use procfs;
use runtime_spec::{ContainerState, State as OCIState};
use rustjail::cgroups::fs::Manager as CgroupManager;
use rustjail::{
container::{BaseContainer, LinuxContainer, EXEC_FIFO_FILENAME},
process::{Process, ProcessOperations},
specconv::CreateOpts,
};
use scopeguard::defer;
use slog::{debug, info, Logger};
use std::{
env::current_dir,
fs,
path::{Path, PathBuf},
};
use kata_sys_util::hooks::HookStates;
pub const CONFIG_FILE_NAME: &str = "config.json";
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ContainerAction {
Create,
Start,
Run,
}
#[derive(Debug)]
pub struct Container {
pub status: Status,
pub state: ContainerState,
pub cgroup: cgroups::Cgroup,
}
// Container represents a container that is created by the container runtime.
impl Container {
pub fn load(state_root: &Path, id: &str) -> Result<Self> {
let status = Status::load(state_root, id)?;
let spec = status
.config
.spec
.as_ref()
.ok_or_else(|| anyhow!("spec config was not present"))?;
let linux = spec
.linux()
.as_ref()
.ok_or_else(|| anyhow!("linux config was not present"))?;
let cpath = if linux.cgroups_path().is_none() {
id.to_string()
} else {
linux
.cgroups_path()
.clone()
.unwrap_or_default()
.display()
.to_string()
.trim_start_matches('/')
.to_string()
};
let cgroup = cgroups::Cgroup::load(cgroups::hierarchies::auto(), cpath);
let state = get_current_container_state(&status, &cgroup)?;
Ok(Self {
status,
state,
cgroup,
})
}
pub fn processes(&self) -> Result<Vec<Pid>> {
let pids = self.cgroup.tasks();
let result = pids.iter().map(|x| Pid::from_raw(x.pid as i32)).collect();
Ok(result)
}
pub fn kill(&self, signal: Signal, all: bool) -> Result<()> {
if all {
let pids = self.processes()?;
for pid in pids {
if !status::is_process_running(pid)? {
continue;
}
kill(pid, signal)?;
}
} else {
// If --all option is not specified and the container is stopped,
// kill operation generates an error in accordance with the OCI runtime spec.
if self.state == ContainerState::Stopped {
return Err(anyhow!(
"container {} can't be killed because it is {:?}",
self.status.id,
self.state
)
// This error message mustn't be chagned because the containerd integration tests
// expect that OCI container runtimes return the message.
// Ref. https://github.com/containerd/containerd/blob/release/1.7/pkg/process/utils.go#L135
.context("container not running"));
}
let pid = Pid::from_raw(self.status.pid);
if status::is_process_running(pid)? {
kill(pid, signal)?;
}
}
// For cgroup v1, killing a process in a frozen cgroup does nothing until it's thawed.
// Only thaw the cgroup for SIGKILL.
// Ref: https://github.com/opencontainers/runc/pull/3217
if !is_cgroup2_unified_mode() && self.state == ContainerState::Paused && signal == SIGKILL {
freeze(&self.cgroup, FreezerState::Thawed)?;
}
Ok(())
}
pub async fn delete(&self, force: bool, logger: &Logger) -> Result<()> {
let status = &self.status;
let spec = status
.config
.spec
.as_ref()
.ok_or_else(|| anyhow!("spec config was not present in the status"))?;
let oci_state = OCIState {
version: status.oci_version.clone(),
id: status.id.clone(),
status: self.state,
pid: status.pid,
bundle: status
.bundle
.to_str()
.ok_or_else(|| anyhow!("invalid bundle path"))?
.to_string(),
annotations: spec.annotations().clone().unwrap_or_default(),
};
if let Some(hooks) = spec.hooks().as_ref() {
info!(&logger, "Poststop Hooks");
let mut poststop_hookstates = HookStates::new();
poststop_hookstates.execute_hooks(
&hooks.poststop().clone().unwrap_or_default(),
Some(oci_state.clone()),
)?;
}
match oci_state.status {
ContainerState::Stopped => {
self.destroy()?;
}
ContainerState::Created => {
// Kill an init process
self.kill(SIGKILL, false)?;
self.destroy()?;
}
_ => {
if force {
self.kill(SIGKILL, true)?;
self.destroy()?;
} else {
return Err(anyhow!(
"cannot delete container {} that is not stopped",
&status.id
));
}
}
}
Ok(())
}
pub fn pause(&self) -> Result<()> {
if self.state != ContainerState::Running && self.state != ContainerState::Created {
return Err(anyhow!(
"failed to pause container: current status is: {:?}",
self.state
));
}
freeze(&self.cgroup, FreezerState::Frozen)?;
Ok(())
}
pub fn resume(&self) -> Result<()> {
if self.state != ContainerState::Paused {
return Err(anyhow!(
"failed to resume container: current status is: {:?}",
self.state
));
}
freeze(&self.cgroup, FreezerState::Thawed)?;
Ok(())
}
pub fn destroy(&self) -> Result<()> {
remove_cgroup_dir(&self.cgroup)?;
self.status.remove_dir()
}
}
/// Used to run a process. If init is set, it will create a container and run the process in it.
/// If init is not set, it will run the process in an existing container.
#[derive(Debug)]
pub struct ContainerLauncher {
pub id: String,
pub bundle: PathBuf,
pub state_root: PathBuf,
pub init: bool,
pub runner: LinuxContainer,
pub pid_file: Option<PathBuf>,
}
impl ContainerLauncher {
pub fn new(
id: &str,
bundle: &Path,
state_root: &Path,
init: bool,
runner: LinuxContainer,
pid_file: Option<PathBuf>,
) -> Self {
ContainerLauncher {
id: id.to_string(),
bundle: bundle.to_path_buf(),
state_root: state_root.to_path_buf(),
init,
runner,
pid_file,
}
}
/// Launch a process. For init containers, we will create a container. For non-init, it will join an existing container.
pub async fn launch(&mut self, action: ContainerAction, logger: &Logger) -> Result<()> {
if self.init {
self.spawn_container(action, logger).await?;
} else {
if action == ContainerAction::Create {
return Err(anyhow!(
"ContainerAction::Create is used for init-container only"
));
}
self.spawn_process(action, logger).await?;
}
if let Some(pid_file) = self.pid_file.as_ref() {
fs::write(
pid_file,
format!("{}", self.runner.get_process(self.id.as_str())?.pid()),
)?;
}
Ok(())
}
/// Create the container by invoking runner to spawn the first process and save status.
async fn spawn_container(&mut self, action: ContainerAction, logger: &Logger) -> Result<()> {
// State root path root/id has been created in LinuxContainer::new(),
// so we don't have to create it again.
// Spawn a new process in the container by using the agent's codes.
self.spawn_process(action, logger).await?;
let status = self.get_status()?;
status.save()?;
debug!(logger, "saved status is {:?}", status);
// Clean up the fifo file created by LinuxContainer, which is used for block the created process.
if action == ContainerAction::Run || action == ContainerAction::Start {
let fifo_path = get_fifo_path(&status);
if fifo_path.exists() {
unlink(&fifo_path)?;
}
}
Ok(())
}
/// Generate rustjail::Process from OCI::Process
fn get_process(&self, logger: &Logger) -> Result<Process> {
let spec = self.runner.config.spec.as_ref().unwrap();
if spec.process().is_some() {
Ok(Process::new(
logger,
spec.process().as_ref().unwrap(),
// rustjail::LinuxContainer use the exec_id to identify processes in a container,
// so we can get the spawned process by ctr.get_process(exec_id) later.
// Since LinuxContainer is temporarily created to spawn one process in each runk invocation,
// we can use arbitrary string as the exec_id. Here we choose the container id.
&self.id,
self.init,
0,
None,
)?)
} else {
Err(anyhow!("no process configuration"))
}
}
/// Spawn a new process in the container by invoking runner.
async fn spawn_process(&mut self, action: ContainerAction, logger: &Logger) -> Result<()> {
// Agent will chdir to bundle_path before creating LinuxContainer. Just do the same as agent.
let current_dir = current_dir()?;
chdir(&self.bundle)?;
defer! {
chdir(&current_dir).unwrap();
}
let process = self.get_process(logger)?;
match action {
ContainerAction::Create => {
self.runner.start(process).await?;
}
ContainerAction::Start => {
self.runner.exec().await?;
}
ContainerAction::Run => {
self.runner.run(process).await?;
}
}
Ok(())
}
/// Generate runk specified Status
fn get_status(&self) -> Result<Status> {
let oci_state = self.runner.oci_state()?;
// read start time from /proc/<pid>/stat
let proc = procfs::process::Process::new(self.runner.init_process_pid)?;
let process_start_time = proc.stat()?.starttime;
Status::new(
&self.state_root,
&self.bundle,
oci_state,
process_start_time,
self.runner.created,
self.runner
.cgroup_manager
.as_ref()
.as_any()?
.downcast_ref::<CgroupManager>()
.unwrap()
.clone(),
self.runner.config.clone(),
)
}
}
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(),
None,
config,
logger,
)?;
if let Some(socket_path) = console_socket.as_ref() {
container.set_console_socket(socket_path)?;
}
Ok(container)
}
// Load rustjail's Linux container.
// "uid_map_path" and "gid_map_path" are always empty, so they are not set.
pub fn load_linux_container(
status: &Status,
console_socket: Option<PathBuf>,
logger: &Logger,
) -> Result<LinuxContainer> {
let mut container = LinuxContainer::new(
&status.id,
&status
.root
.to_str()
.map(|s| s.to_string())
.ok_or_else(|| anyhow!("failed to convert a root path"))?,
None,
status.config.clone(),
logger,
)?;
if let Some(socket_path) = console_socket.as_ref() {
container.set_console_socket(socket_path)?;
}
container.init_process_pid = status.pid;
container.init_process_start_time = status.process_start_time;
container.created = status.created.into();
Ok(container)
}
pub fn get_config_path<P: AsRef<Path>>(bundle: P) -> PathBuf {
bundle.as_ref().join(CONFIG_FILE_NAME)
}
pub fn get_fifo_path(status: &Status) -> PathBuf {
status.root.join(&status.id).join(EXEC_FIFO_FILENAME)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::test_utils::*;
use rustjail::container::EXEC_FIFO_FILENAME;
use std::path::PathBuf;
#[test]
fn test_get_config_path() {
let test_data = PathBuf::from(TEST_BUNDLE_PATH).join(CONFIG_FILE_NAME);
assert_eq!(get_config_path(TEST_BUNDLE_PATH), test_data);
}
#[test]
fn test_get_fifo_path() {
let test_data = PathBuf::from(TEST_STATE_ROOT_PATH)
.join(TEST_CONTAINER_ID)
.join(EXEC_FIFO_FILENAME);
let status = create_dummy_status();
assert_eq!(get_fifo_path(&status), test_data);
}
}

View File

@@ -1,140 +0,0 @@
// Copyright 2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use crate::container::{load_linux_container, Container, ContainerLauncher};
use anyhow::{anyhow, Result};
use derive_builder::Builder;
use runtime_spec::ContainerState;
use slog::{debug, Logger};
use std::path::PathBuf;
/// Used for start command. It will prepare the options used for starting a new container.
#[derive(Default, Builder, Debug, Clone)]
#[builder(build_fn(validate = "Self::validate"))]
pub struct CreatedContainer {
id: String,
root: PathBuf,
}
impl CreatedContainerBuilder {
/// pre-validate before building CreatedContainer
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", id));
}
Ok(())
}
}
impl CreatedContainer {
/// Create ContainerLauncher that can be used to start a process from an existing init container.
/// It reads the spec from status file of the init container.
pub fn create_launcher(self, logger: &Logger) -> Result<ContainerLauncher> {
debug!(logger, "enter CreatedContainer::create_launcher {:?}", self);
let container = Container::load(&self.root, &self.id)?;
if container.state != ContainerState::Created {
return Err(anyhow!(
"cannot start a container in the {:?} state",
container.state
));
}
let config = container.status.config.clone();
debug!(
logger,
"Prepare LinuxContainer for starting with config: {:?}", config
);
let runner = load_linux_container(&container.status, None, logger)?;
Ok(ContainerLauncher::new(
&self.id,
&container.status.bundle,
&self.root,
true,
runner,
None,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::status::Status;
use crate::utils::test_utils::*;
use nix::sys::stat::Mode;
use nix::unistd::{self, getpid};
use rustjail::container::EXEC_FIFO_FILENAME;
use scopeguard::defer;
use slog::o;
use std::fs::create_dir_all;
use std::path::Path;
use tempfile::tempdir;
use test_utils::skip_if_not_root;
fn create_created_container_dirs(root: &Path, id: &str, bundle: &Path) {
Status::create_dir(root, id).unwrap();
let fifo = root.join(id).join(EXEC_FIFO_FILENAME);
unistd::mkfifo(&fifo, Mode::from_bits(0o644).unwrap()).unwrap();
create_dir_all(bundle.join(TEST_ROOTFS_PATH)).unwrap();
}
#[test]
fn test_created_container_validate() {
let root = tempdir().unwrap();
let id = TEST_CONTAINER_ID.to_string();
let result = CreatedContainerBuilder::default()
.id(id)
.root(root.path().to_path_buf())
.build();
assert!(result.is_err());
}
#[test]
fn test_created_container_create_launcher() {
// 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_created_container_create".to_string();
create_created_container_dirs(root.path(), &id, bundle_dir.path());
let pid = getpid().as_raw();
let mut spec = create_dummy_spec();
spec.root_mut()
.as_mut()
.unwrap()
.set_path(bundle_dir.path().join(TEST_ROOTFS_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 = CreatedContainerBuilder::default()
.id(id.clone())
.root(root.into_path())
.build()
.unwrap()
.create_launcher(&logger)
.unwrap();
assert!(launcher.init);
assert_eq!(launcher.runner.config.spec.unwrap(), spec);
assert_eq!(launcher.runner.id, id);
}
}

View File

@@ -1,215 +0,0 @@
// 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::runtime::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,
container_name: "".to_string(),
};
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 oci_spec::runtime::Process;
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() {
#[cfg(all(target_arch = "powerpc64", target_endian = "little"))]
skip_if_not_root!();
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_mut()
.as_mut()
.unwrap()
.set_path(bundle_dir.path().join(TEST_ROOTFS_PATH));
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 = Spec::default();
spec.set_process(Some(Process::default()));
spec.process_mut()
.as_mut()
.unwrap()
.set_terminal(Some(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());
}
}

View File

@@ -1,12 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
pub mod activated_builder;
pub mod cgroup;
pub mod container;
pub mod created_builder;
pub mod init_builder;
pub mod status;
pub mod utils;

View File

@@ -1,236 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use crate::cgroup::is_paused;
use crate::container::get_fifo_path;
use crate::utils::*;
use anyhow::{anyhow, Result};
use chrono::{DateTime, Utc};
use libc::pid_t;
use nix::{
errno::Errno,
sys::{signal::kill, stat::Mode},
unistd::Pid,
};
use procfs::process::ProcState;
use runtime_spec::{ContainerState, State as OCIState};
use rustjail::{cgroups::fs::Manager as CgroupManager, specconv::CreateOpts};
use serde::{Deserialize, Serialize};
use std::{
fs::{self, File, OpenOptions},
path::{Path, PathBuf},
time::SystemTime,
};
const STATUS_FILE: &str = "status.json";
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Status {
pub oci_version: String,
pub id: String,
pub pid: pid_t,
pub root: PathBuf,
pub bundle: PathBuf,
pub rootfs: String,
pub process_start_time: u64,
pub created: DateTime<Utc>,
// Methods of Manager traits in rustjail are invisible, and CgroupManager.cgroup can't be serialized.
// So it is cumbersome to manage cgroups by this field. Instead, we use cgroups-rs::cgroup directly in Container to manager cgroups.
// Another solution is making some methods public outside rustjail and adding getter/setter for CgroupManager.cgroup.
// Temporarily keep this field for compatibility.
pub cgroup_manager: CgroupManager,
pub config: CreateOpts,
}
impl Status {
pub fn new(
root: &Path,
bundle: &Path,
oci_state: OCIState,
process_start_time: u64,
created_time: SystemTime,
cgroup_mg: CgroupManager,
config: CreateOpts,
) -> Result<Self> {
let created = DateTime::from(created_time);
let rootfs = config
.clone()
.spec
.ok_or_else(|| anyhow!("spec config was not present"))?
.root()
.as_ref()
.ok_or_else(|| anyhow!("root config was not present in the spec"))?
.path()
.clone();
Ok(Self {
oci_version: oci_state.version,
id: oci_state.id,
pid: oci_state.pid,
root: root.to_path_buf(),
bundle: bundle.to_path_buf(),
rootfs: rootfs.display().to_string(),
process_start_time,
created,
cgroup_manager: cgroup_mg,
config,
})
}
pub fn save(&self) -> Result<()> {
let state_file_path = Self::get_file_path(&self.root, &self.id);
if !&self.root.exists() {
create_dir_with_mode(&self.root, Mode::S_IRWXU, true)?;
}
let file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(state_file_path)?;
serde_json::to_writer(&file, self)?;
Ok(())
}
pub fn load(state_root: &Path, id: &str) -> Result<Self> {
let state_file_path = Self::get_file_path(state_root, id);
if !state_file_path.exists() {
return Err(anyhow!("container \"{}\" does not exist", id));
}
let file = File::open(&state_file_path)?;
let state: Self = serde_json::from_reader(&file)?;
Ok(state)
}
pub fn create_dir(state_root: &Path, id: &str) -> Result<()> {
let state_dir_path = Self::get_dir_path(state_root, id);
if !state_dir_path.exists() {
create_dir_with_mode(state_dir_path, Mode::S_IRWXU, true)?;
} else {
return Err(anyhow!("container with id exists: \"{}\"", id));
}
Ok(())
}
pub fn remove_dir(&self) -> Result<()> {
let state_dir_path = Self::get_dir_path(&self.root, &self.id);
fs::remove_dir_all(state_dir_path)?;
Ok(())
}
pub fn get_dir_path(state_root: &Path, id: &str) -> PathBuf {
state_root.join(id)
}
pub fn get_file_path(state_root: &Path, id: &str) -> PathBuf {
state_root.join(id).join(STATUS_FILE)
}
}
pub fn is_process_running(pid: Pid) -> Result<bool> {
match kill(pid, None) {
Err(errno) => {
if errno != Errno::ESRCH {
return Err(anyhow!("failed to kill process {}: {:?}", pid, errno));
}
Ok(false)
}
Ok(()) => Ok(true),
}
}
// Returns the current state of a container. It will read cgroupfs and procfs to determine the state.
// https://github.com/opencontainers/runc/blob/86d6898f3052acba1ebcf83aa2eae3f6cc5fb471/libcontainer/container_linux.go#L1953
pub fn get_current_container_state(
status: &Status,
cgroup: &cgroups::Cgroup,
) -> Result<ContainerState> {
if is_paused(cgroup)? {
return Ok(ContainerState::Paused);
}
let proc = procfs::process::Process::new(status.pid);
// if reading /proc/<pid> occurs error, then the process is not running
if proc.is_err() {
return Ok(ContainerState::Stopped);
}
let proc_stat = proc.unwrap().stat()?;
// if start time is not equal, then the pid is reused, and the process is not running
if proc_stat.starttime != status.process_start_time {
return Ok(ContainerState::Stopped);
}
match proc_stat.state()? {
ProcState::Zombie | ProcState::Dead => Ok(ContainerState::Stopped),
_ => {
let fifo = get_fifo_path(status);
if fifo.exists() {
return Ok(ContainerState::Created);
}
Ok(ContainerState::Running)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::test_utils::*;
use ::test_utils::skip_if_not_root;
use chrono::{DateTime, Utc};
use nix::unistd::getpid;
use runtime_spec::ContainerState;
use rustjail::cgroups::fs::Manager as CgroupManager;
use scopeguard::defer;
use std::path::Path;
use std::time::SystemTime;
#[test]
fn test_status() {
let cgm: CgroupManager = serde_json::from_str(TEST_CGM_DATA).unwrap();
let oci_state = create_dummy_oci_state();
let created = SystemTime::now();
let status = Status::new(
Path::new(TEST_STATE_ROOT_PATH),
Path::new(TEST_BUNDLE_PATH),
oci_state.clone(),
1,
created,
cgm,
create_dummy_opts(),
)
.unwrap();
assert_eq!(status.id, oci_state.id);
assert_eq!(status.pid, oci_state.pid);
assert_eq!(status.process_start_time, 1);
assert_eq!(status.created, DateTime::<Utc>::from(created));
}
#[test]
fn test_is_process_running() {
let pid = getpid();
let ret = is_process_running(pid).unwrap();
assert!(ret);
}
#[test]
fn test_get_current_container_state() {
skip_if_not_root!();
let mut status = create_dummy_status();
status.id = "test_get_current_container_state".to_string();
// crete a dummy cgroup to make sure is_pause doesn't return error
let cgroup = create_dummy_cgroup(Path::new(&status.id));
defer!(cgroup.delete().unwrap());
let state = get_current_container_state(&status, &cgroup).unwrap();
assert_eq!(state, ContainerState::Running);
}
}

View File

@@ -1,294 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::{anyhow, Result};
use nix::sys::stat::Mode;
use oci_spec::runtime::{Process, Spec};
use std::{
fs::{DirBuilder, File},
io::{prelude::*, BufReader},
os::unix::fs::DirBuilderExt,
path::{Path, PathBuf},
};
pub fn lines_from_file<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
let file = File::open(&path)?;
let buf = BufReader::new(file);
Ok(buf
.lines()
.map(|v| v.expect("could not parse line"))
.collect())
}
pub fn create_dir_with_mode<P: AsRef<Path>>(path: P, mode: Mode, recursive: bool) -> Result<()> {
let path = path.as_ref();
if path.exists() {
return Err(anyhow!("{} already exists", path.display()));
}
Ok(DirBuilder::new()
.recursive(recursive)
.mode(mode.bits())
.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 spec_root = spec
.root_mut()
.as_mut()
.ok_or_else(|| anyhow!("root config was not present in the spec file"))?;
let rootfs_path = &spec_root.path();
if !rootfs_path.is_absolute() {
let bundle_canon_path = bundle_canon.join(rootfs_path).canonicalize()?;
spec_root.set_path(bundle_canon_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().is_some() && 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
pub fn validate_process_spec(process: &Option<Process>) -> Result<()> {
let process = process
.as_ref()
.ok_or_else(|| anyhow!("process property must not be empty"))?;
if process.cwd().as_os_str().is_empty() {
return Err(anyhow!("cwd property must not be empty"));
}
let cwd = process.cwd();
if !cwd.is_absolute() {
return Err(anyhow!("cwd must be an absolute path"));
}
if process.args().is_none() {
return Err(anyhow!("args must not be empty"));
}
Ok(())
}
#[cfg(test)]
pub(crate) mod test_utils {
use super::*;
use crate::status::Status;
use chrono::DateTime;
use nix::unistd::getpid;
use oci::{LinuxBuilder, LinuxNamespaceBuilder, Process, Root, Spec};
use oci_spec::runtime as oci;
use runtime_spec::{ContainerState, State as OCIState};
use rustjail::{
cgroups::fs::Manager as CgroupManager, container::TYPETONAME, specconv::CreateOpts,
};
use std::{fs::create_dir_all, path::Path, time::SystemTime};
use tempfile::tempdir;
pub const TEST_CONTAINER_ID: &str = "test";
pub const TEST_STATE_ROOT_PATH: &str = "/state";
pub const TEST_BUNDLE_PATH: &str = "/bundle";
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#"{
"paths": {
"devices": "/sys/fs/cgroup/devices"
},
"mounts": {
"devices": "/sys/fs/cgroup/devices"
},
"cpath": "test"
}"#;
#[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 = LinuxBuilder::default()
.namespaces(
TYPETONAME
.iter()
.filter(|&(_, &name)| name != "user")
.map(|ns| {
LinuxNamespaceBuilder::default()
.typ(ns.0.clone())
.path(PathBuf::from(""))
.build()
.unwrap()
})
.collect::<Vec<_>>(),
)
.build()
.unwrap();
let mut process = Process::default();
process.set_args(Some(vec!["sleep".to_string(), "10".to_string()]));
process.set_env(Some(vec!["PATH=/bin:/usr/bin".to_string()]));
process.set_cwd(PathBuf::from("/"));
let mut root = Root::default();
root.set_path(PathBuf::from(TEST_ROOTFS_PATH));
root.set_readonly(Some(false));
let mut spec = Spec::default();
spec.set_version(TEST_OCI_SPEC_VERSION.to_string());
spec.set_process(Some(process));
spec.set_hostname(Some(TEST_HOST_NAME.to_string()));
spec.set_root(Some(root));
spec.set_linux(Some(linux));
spec
}
pub fn create_dummy_opts() -> CreateOpts {
let mut spec = Spec::default();
spec.set_root(Some(Root::default()));
CreateOpts {
cgroup_name: "".to_string(),
use_systemd_cgroup: false,
no_pivot_root: false,
no_new_keyring: false,
spec: Some(spec),
rootless_euid: false,
rootless_cgroup: false,
container_name: "".to_string(),
}
}
pub fn create_dummy_oci_state() -> OCIState {
OCIState {
version: TEST_OCI_SPEC_VERSION.to_string(),
id: TEST_CONTAINER_ID.to_string(),
status: ContainerState::Running,
pid: getpid().as_raw(),
bundle: TEST_BUNDLE_PATH.to_string(),
annotations: [(TEST_ANNOTATION.to_string(), TEST_ANNOTATION.to_string())]
.iter()
.cloned()
.collect(),
}
}
pub fn create_dummy_status() -> Status {
let cgm: CgroupManager = serde_json::from_str(TEST_CGM_DATA).unwrap();
let oci_state = create_dummy_oci_state();
let created = SystemTime::now();
let start_time = procfs::process::Process::new(oci_state.pid)
.unwrap()
.stat()
.unwrap()
.starttime;
let status = Status::new(
Path::new(TEST_STATE_ROOT_PATH),
Path::new(TEST_BUNDLE_PATH),
oci_state,
start_time,
created,
cgm,
create_dummy_opts(),
)
.unwrap();
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 {
cgroups::Cgroup::new(cgroups::hierarchies::auto(), cpath).unwrap()
}
pub fn clean_up_cgroup(cpath: &Path) {
let cgroup = cgroups::Cgroup::load(cgroups::hierarchies::auto(), cpath);
cgroup.delete().unwrap();
}
#[test]
fn test_canonicalize_spec_root() {
let gen_spec = |p: &str| -> Spec {
let mut root = Root::default();
root.set_path(PathBuf::from(p));
root.set_readonly(Some(false));
let mut spec = Spec::default();
spec.set_root(Some(root));
spec
};
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_mut().clone().unwrap().path(), &abs_root);
let mut spec = gen_spec(rootfs_name);
assert!(canonicalize_spec_root(&mut spec, bundle_dir).is_ok());
assert_eq!(spec.root().clone().unwrap().path(), &abs_root);
}
#[test]
pub fn test_validate_process_spec() {
let mut valid_process = Process::default();
valid_process.set_args(Some(vec!["test".to_string()]));
valid_process.set_cwd(PathBuf::from("/"));
assert!(validate_process_spec(&None).is_err());
assert!(validate_process_spec(&Some(valid_process.clone())).is_ok());
let mut invalid_process = valid_process.clone();
invalid_process.set_args(None);
assert!(validate_process_spec(&Some(invalid_process)).is_err());
let mut invalid_process = valid_process.clone();
invalid_process.set_cwd(PathBuf::from(""));
assert!(validate_process_spec(&Some(invalid_process)).is_err());
let mut invalid_process = valid_process;
invalid_process.set_cwd(PathBuf::from("test/"));
assert!(validate_process_spec(&Some(invalid_process)).is_err());
}
}

View File

@@ -1,28 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::Result;
use libcontainer::{container::ContainerAction, init_builder::InitContainerBuilder};
use liboci_cli::Create;
use slog::{info, Logger};
use std::path::Path;
pub async fn run(opts: Create, root: &Path, logger: &Logger) -> Result<()> {
let mut launcher = InitContainerBuilder::default()
.id(opts.container_id)
.bundle(opts.bundle)
.root(root.to_path_buf())
.console_socket(opts.console_socket)
.pid_file(opts.pid_file)
.build()?
.create_launcher(logger)?;
launcher.launch(ContainerAction::Create, logger).await?;
info!(&logger, "create command finished successfully");
Ok(())
}

View File

@@ -1,30 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::{anyhow, Result};
use libcontainer::{container::Container, status::Status};
use liboci_cli::Delete;
use slog::{info, Logger};
use std::{fs, path::Path};
pub async fn run(opts: Delete, root: &Path, logger: &Logger) -> Result<()> {
let container_id = &opts.container_id;
let status_dir = Status::get_dir_path(root, container_id);
if !status_dir.exists() {
return Err(anyhow!("container {} does not exist", container_id));
}
let container = if let Ok(value) = Container::load(root, container_id) {
value
} else {
fs::remove_dir_all(status_dir)?;
return Ok(());
};
container.delete(opts.force, logger).await?;
info!(&logger, "delete command finished successfully");
Ok(())
}

View File

@@ -1,32 +0,0 @@
// Copyright 2021-2022 Kata Contributors
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::Result;
use libcontainer::activated_builder::ActivatedContainerBuilder;
use libcontainer::container::ContainerAction;
use liboci_cli::Exec;
use slog::{info, Logger};
use std::path::Path;
pub async fn run(opts: Exec, root: &Path, logger: &Logger) -> Result<()> {
let mut launcher = ActivatedContainerBuilder::default()
.id(opts.container_id)
.root(root.to_path_buf())
.console_socket(opts.console_socket)
.pid_file(opts.pid_file)
.tty(opts.tty)
.cwd(opts.cwd)
.env(opts.env)
.no_new_privs(opts.no_new_privs)
.process(opts.process)
.args(opts.command)
.build()?
.create_launcher(logger)?;
launcher.launch(ContainerAction::Run, logger).await?;
info!(&logger, "exec command finished successfully");
Ok(())
}

View File

@@ -1,58 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::Result;
use libcontainer::container::Container;
use liboci_cli::Kill;
use nix::sys::signal::Signal;
use slog::{info, Logger};
use std::{convert::TryFrom, path::Path, str::FromStr};
pub fn run(opts: Kill, state_root: &Path, logger: &Logger) -> Result<()> {
let container_id = &opts.container_id;
let container = Container::load(state_root, container_id)?;
let sig = parse_signal(&opts.signal)?;
let all = opts.all;
container.kill(sig, all)?;
info!(&logger, "kill command finished successfully");
Ok(())
}
fn parse_signal(signal: &str) -> Result<Signal> {
if let Ok(num) = signal.parse::<i32>() {
return Ok(Signal::try_from(num)?);
}
let mut signal_upper = signal.to_uppercase();
if !signal_upper.starts_with("SIG") {
signal_upper = "SIG".to_string() + &signal_upper;
}
Ok(Signal::from_str(&signal_upper)?)
}
#[cfg(test)]
mod tests {
use super::*;
use nix::sys::signal::Signal;
#[test]
fn test_parse_signal() {
assert_eq!(Signal::SIGHUP, parse_signal("1").unwrap());
assert_eq!(Signal::SIGHUP, parse_signal("sighup").unwrap());
assert_eq!(Signal::SIGHUP, parse_signal("hup").unwrap());
assert_eq!(Signal::SIGHUP, parse_signal("SIGHUP").unwrap());
assert_eq!(Signal::SIGHUP, parse_signal("HUP").unwrap());
assert_eq!(Signal::SIGKILL, parse_signal("9").unwrap());
assert_eq!(Signal::SIGKILL, parse_signal("sigkill").unwrap());
assert_eq!(Signal::SIGKILL, parse_signal("kill").unwrap());
assert_eq!(Signal::SIGKILL, parse_signal("SIGKILL").unwrap());
assert_eq!(Signal::SIGKILL, parse_signal("KILL").unwrap());
}
}

View File

@@ -1,68 +0,0 @@
// Copyright 2021-2022 Kata Contributors
//
// SPDX-License-Identifier: Apache-2.0
//
use super::state::get_container_state_name;
use anyhow::Result;
use libcontainer::container::Container;
use liboci_cli::List;
use runtime_spec::ContainerState;
use slog::{info, Logger};
use std::fmt::Write as _;
use std::{fs, os::unix::prelude::MetadataExt, path::Path};
use std::{io, io::Write};
use tabwriter::TabWriter;
use uzers::get_user_by_uid;
pub fn run(_: List, root: &Path, logger: &Logger) -> Result<()> {
let mut content = String::new();
for entry in fs::read_dir(root)? {
let entry = entry?;
// Possibly race with other command of runk, so continue loop when any error occurs below
let metadata = match entry.metadata() {
Ok(metadata) => metadata,
Err(_) => continue,
};
if !metadata.is_dir() {
continue;
}
let container_id = match entry.file_name().into_string() {
Ok(id) => id,
Err(_) => continue,
};
let container = match Container::load(root, &container_id) {
Ok(container) => container,
Err(_) => continue,
};
let state = container.state;
// Just like runc, pid of stopped container is 0
let pid = match state {
ContainerState::Stopped => 0,
_ => container.status.pid,
};
// May replace get_user_by_uid with getpwuid(3)
let owner = match get_user_by_uid(metadata.uid()) {
Some(user) => String::from(user.name().to_string_lossy()),
None => format!("#{}", metadata.uid()),
};
let _ = writeln!(
content,
"{}\t{}\t{}\t{}\t{}\t{}",
container_id,
pid,
get_container_state_name(state),
container.status.bundle.display(),
container.status.created,
owner
);
}
let mut tab_writer = TabWriter::new(io::stdout());
writeln!(&mut tab_writer, "ID\tPID\tSTATUS\tBUNDLE\tCREATED\tOWNER")?;
write!(&mut tab_writer, "{}", content)?;
tab_writer.flush()?;
info!(&logger, "list command finished successfully");
Ok(())
}

View File

@@ -1,17 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
pub mod create;
pub mod delete;
pub mod exec;
pub mod kill;
pub mod list;
pub mod pause;
pub mod ps;
pub mod resume;
pub mod run;
pub mod spec;
pub mod start;
pub mod state;

View File

@@ -1,18 +0,0 @@
// Copyright 2021-2022 Kata Contributors
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::Result;
use libcontainer::container::Container;
use liboci_cli::Pause;
use slog::{info, Logger};
use std::path::Path;
pub fn run(opts: Pause, root: &Path, logger: &Logger) -> Result<()> {
let container = Container::load(root, &opts.container_id)?;
container.pause()?;
info!(&logger, "pause command finished successfully");
Ok(())
}

View File

@@ -1,63 +0,0 @@
// Copyright 2021-2022 Kata Contributors
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::anyhow;
use anyhow::Result;
use libcontainer::container::Container;
use liboci_cli::Ps;
use slog::{info, Logger};
use std::path::Path;
use std::process::Command;
use std::str;
pub fn run(opts: Ps, root: &Path, logger: &Logger) -> Result<()> {
let container = Container::load(root, opts.container_id.as_str())?;
let pids = container
.processes()?
.iter()
.map(|pid| pid.as_raw())
.collect::<Vec<_>>();
match opts.format.as_str() {
"json" => println!("{}", serde_json::to_string(&pids)?),
"table" => {
let ps_options = if opts.ps_options.is_empty() {
vec!["-ef".to_string()]
} else {
opts.ps_options
};
let output = Command::new("ps").args(ps_options).output()?;
if !output.status.success() {
return Err(anyhow!("{}", std::str::from_utf8(&output.stderr)?));
}
let lines = str::from_utf8(&output.stdout)?.lines().collect::<Vec<_>>();
if lines.is_empty() {
return Err(anyhow!("no processes found"));
}
let pid_index = lines[0]
.split_whitespace()
.position(|field| field == "PID")
.ok_or_else(|| anyhow!("could't find PID field in ps output"))?;
println!("{}", lines[0]);
for &line in &lines[1..] {
if line.is_empty() {
continue;
}
let fields = line.split_whitespace().collect::<Vec<_>>();
if pid_index >= fields.len() {
continue;
}
let pid: i32 = fields[pid_index].parse()?;
if pids.contains(&pid) {
println!("{}", line);
}
}
}
_ => return Err(anyhow!("unknown format: {}", opts.format)),
}
info!(&logger, "ps command finished successfully");
Ok(())
}

View File

@@ -1,18 +0,0 @@
// Copyright 2021-2022 Kata Contributors
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::Result;
use libcontainer::container::Container;
use liboci_cli::Resume;
use slog::{info, Logger};
use std::path::Path;
pub fn run(opts: Resume, root: &Path, logger: &Logger) -> Result<()> {
let container = Container::load(root, &opts.container_id)?;
container.resume()?;
info!(&logger, "pause command finished successfully");
Ok(())
}

View File

@@ -1,27 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::Result;
use libcontainer::{container::ContainerAction, init_builder::InitContainerBuilder};
use liboci_cli::Run;
use slog::{info, Logger};
use std::path::Path;
pub async fn run(opts: Run, root: &Path, logger: &Logger) -> Result<()> {
let mut launcher = InitContainerBuilder::default()
.id(opts.container_id)
.bundle(opts.bundle)
.root(root.to_path_buf())
.console_socket(opts.console_socket)
.pid_file(opts.pid_file)
.build()?
.create_launcher(logger)?;
launcher.launch(ContainerAction::Run, logger).await?;
info!(&logger, "run command finished successfully");
Ok(())
}

View File

@@ -1,207 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
//use crate::container::get_config_path;
use anyhow::Result;
use libcontainer::container::CONFIG_FILE_NAME;
use liboci_cli::Spec;
use slog::{info, Logger};
use std::{fs::File, io::Write, path::Path};
pub const DEFAULT_SPEC: &str = r#"{
"ociVersion": "1.0.2-dev",
"process": {
"terminal": true,
"user": {
"uid": 0,
"gid": 0
},
"args": [
"sh"
],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"cwd": "/",
"capabilities": {
"bounding": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"effective": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"inheritable": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"permitted": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"ambient": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
]
},
"rlimits": [
{
"type": "RLIMIT_NOFILE",
"hard": 1024,
"soft": 1024
}
],
"noNewPrivileges": true
},
"root": {
"path": "rootfs",
"readonly": true
},
"hostname": "runk",
"mounts": [
{
"destination": "/proc",
"type": "proc",
"source": "proc"
},
{
"destination": "/dev",
"type": "tmpfs",
"source": "tmpfs",
"options": [
"nosuid",
"strictatime",
"mode=755",
"size=65536k"
]
},
{
"destination": "/dev/pts",
"type": "devpts",
"source": "devpts",
"options": [
"nosuid",
"noexec",
"newinstance",
"ptmxmode=0666",
"mode=0620",
"gid=5"
]
},
{
"destination": "/dev/shm",
"type": "tmpfs",
"source": "shm",
"options": [
"nosuid",
"noexec",
"nodev",
"mode=1777",
"size=65536k"
]
},
{
"destination": "/dev/mqueue",
"type": "mqueue",
"source": "mqueue",
"options": [
"nosuid",
"noexec",
"nodev"
]
},
{
"destination": "/sys",
"type": "sysfs",
"source": "sysfs",
"options": [
"nosuid",
"noexec",
"nodev",
"ro"
]
},
{
"destination": "/sys/fs/cgroup",
"type": "cgroup",
"source": "cgroup",
"options": [
"nosuid",
"noexec",
"nodev",
"relatime",
"ro"
]
}
],
"linux": {
"resources": {
"devices": [
{
"allow": false,
"access": "rwm"
}
]
},
"namespaces": [
{
"type": "pid"
},
{
"type": "network"
},
{
"type": "ipc"
},
{
"type": "uts"
},
{
"type": "mount"
}
],
"maskedPaths": [
"/proc/acpi",
"/proc/asound",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/sys/firmware",
"/proc/scsi"
],
"readonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
}
}"#;
pub fn run(_opts: Spec, logger: &Logger) -> Result<()> {
// TODO: liboci-cli does not support --bundle option for spec command.
// After liboci-cli supports the option, we will change the following code.
// let config_path = get_config_path(&opts.bundle);
let config_path = Path::new(".").join(CONFIG_FILE_NAME);
let config_data = DEFAULT_SPEC;
let mut file = File::create(config_path)?;
file.write_all(config_data.as_bytes())?;
info!(&logger, "spec command finished successfully");
Ok(())
}

View File

@@ -1,24 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::Result;
use libcontainer::{container::ContainerAction, created_builder::CreatedContainerBuilder};
use liboci_cli::Start;
use slog::{info, Logger};
use std::path::Path;
pub async fn run(opts: Start, root: &Path, logger: &Logger) -> Result<()> {
let mut launcher = CreatedContainerBuilder::default()
.id(opts.container_id)
.root(root.to_path_buf())
.build()?
.create_launcher(logger)?;
launcher.launch(ContainerAction::Start, logger).await?;
info!(&logger, "start command finished successfully");
Ok(())
}

View File

@@ -1,78 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::Result;
use chrono::{DateTime, Utc};
use libcontainer::{container::Container, status::Status};
use liboci_cli::State;
use runtime_spec::ContainerState;
use serde::{Deserialize, Serialize};
use slog::{info, Logger};
use std::path::{Path, PathBuf};
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeState {
pub oci_version: String,
pub id: String,
pub pid: i32,
pub status: String,
pub bundle: PathBuf,
pub created: DateTime<Utc>,
}
impl RuntimeState {
pub fn new(status: Status, state: ContainerState) -> Self {
Self {
oci_version: status.oci_version,
id: status.id,
pid: status.pid,
status: get_container_state_name(state),
bundle: status.bundle,
created: status.created,
}
}
}
pub fn run(opts: State, state_root: &Path, logger: &Logger) -> Result<()> {
let container = Container::load(state_root, &opts.container_id)?;
let oci_state = RuntimeState::new(container.status, container.state);
let json_state = &serde_json::to_string_pretty(&oci_state)?;
println!("{}", json_state);
info!(&logger, "state command finished successfully");
Ok(())
}
pub fn get_container_state_name(state: ContainerState) -> String {
match state {
ContainerState::Creating => "creating",
ContainerState::Created => "created",
ContainerState::Running => "running",
ContainerState::Stopped => "stopped",
ContainerState::Paused => "paused",
}
.into()
}
#[cfg(test)]
mod tests {
use super::*;
use runtime_spec::ContainerState;
#[test]
fn test_get_container_state_name() {
assert_eq!(
"creating",
get_container_state_name(ContainerState::Creating)
);
assert_eq!("created", get_container_state_name(ContainerState::Created));
assert_eq!("running", get_container_state_name(ContainerState::Running));
assert_eq!("stopped", get_container_state_name(ContainerState::Stopped));
assert_eq!("paused", get_container_state_name(ContainerState::Paused));
}
}

View File

@@ -1,133 +0,0 @@
// Copyright 2021-2022 Sony Group Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::{anyhow, Result};
use clap::{crate_description, crate_name, Parser};
use liboci_cli::{CommonCmd, GlobalOpts};
use liboci_cli::{Create, Delete, Kill, Start, State};
use slog::{o, Logger};
use slog_async::AsyncGuard;
use std::{
fs::OpenOptions,
path::{Path, PathBuf},
process::exit,
};
const DEFAULT_ROOT_DIR: &str = "/run/runk";
const DEFAULT_LOG_LEVEL: slog::Level = slog::Level::Info;
mod commands;
#[derive(Parser, Debug)]
enum SubCommand {
#[clap(flatten)]
Standard(StandardCmd),
#[clap(flatten)]
Common(CommonCmd),
/// Launch an init process (do not call it outside of runk)
Init {},
}
// Copy from https://github.com/containers/youki/blob/v0.0.3/crates/liboci-cli/src/lib.rs#L38-L44
#[derive(Parser, Debug)]
pub enum StandardCmd {
Create(Create),
Start(Start),
State(State),
Delete(Delete),
Kill(Kill),
}
#[derive(Parser, Debug)]
#[clap(version, author, about = crate_description!())]
struct Cli {
#[clap(flatten)]
global: GlobalOpts,
#[clap(subcommand)]
subcmd: SubCommand,
}
async fn cmd_run(subcmd: SubCommand, root_path: &Path, logger: &Logger) -> Result<()> {
match subcmd {
SubCommand::Standard(cmd) => match cmd {
StandardCmd::Create(create) => commands::create::run(create, root_path, logger).await,
StandardCmd::Start(start) => commands::start::run(start, root_path, logger).await,
StandardCmd::Delete(delete) => commands::delete::run(delete, root_path, logger).await,
StandardCmd::State(state) => commands::state::run(state, root_path, logger),
StandardCmd::Kill(kill) => commands::kill::run(kill, root_path, logger),
},
SubCommand::Common(cmd) => match cmd {
CommonCmd::Run(run) => commands::run::run(run, root_path, logger).await,
CommonCmd::Spec(spec) => commands::spec::run(spec, logger),
CommonCmd::List(list) => commands::list::run(list, root_path, logger),
CommonCmd::Exec(exec) => commands::exec::run(exec, root_path, logger).await,
CommonCmd::Ps(ps) => commands::ps::run(ps, root_path, logger),
CommonCmd::Pause(pause) => commands::pause::run(pause, root_path, logger),
CommonCmd::Resume(resume) => commands::resume::run(resume, root_path, logger),
_ => Err(anyhow!("command is not implemented yet")),
},
_ => unreachable!(),
}
}
fn setup_logger(
log_file: Option<PathBuf>,
log_level: slog::Level,
) -> Result<(Logger, Option<AsyncGuard>)> {
if let Some(ref file) = log_file {
let log_writer = OpenOptions::new()
.write(true)
.read(true)
.create(true)
.truncate(true)
.open(file)?;
// TODO: Support 'text' log format.
let (logger_local, logger_async_guard_local) =
logging::create_logger(crate_name!(), crate_name!(), log_level, log_writer);
Ok((logger_local, Some(logger_async_guard_local)))
} else {
let logger = slog::Logger::root(slog::Discard, o!());
Ok((logger, None))
}
}
async fn real_main() -> Result<()> {
let cli = Cli::parse();
if let SubCommand::Init {} = cli.subcmd {
rustjail::container::init_child();
exit(0);
}
let root_path = if let Some(path) = cli.global.root {
path
} else {
PathBuf::from(DEFAULT_ROOT_DIR)
};
let log_level = if cli.global.debug {
slog::Level::Debug
} else {
DEFAULT_LOG_LEVEL
};
let (logger, _async_guard) = setup_logger(cli.global.log, log_level)?;
cmd_run(cli.subcmd, &root_path, &logger).await?;
Ok(())
}
#[tokio::main]
async fn main() {
if let Err(e) = real_main().await {
eprintln!("ERROR: {}", e);
exit(1);
}
exit(0);
}

View File

@@ -14,7 +14,6 @@ and with different container managers.
- [Docker](https://github.com/kata-containers/kata-containers/tree/main/tests/integration/docker)
- [`Nerdctl`](https://github.com/kata-containers/kata-containers/tree/main/tests/integration/nerdctl)
- [`Nydus`](https://github.com/kata-containers/kata-containers/tree/main/tests/integration/nydus)
- [`Runk`](https://github.com/kata-containers/kata-containers/tree/main/tests/integration/runk)
2. [Stability tests](https://github.com/kata-containers/kata-containers/tree/main/tests/stability)
3. [Metrics](https://github.com/kata-containers/kata-containers/tree/main/tests/metrics)
4. [Functional](https://github.com/kata-containers/kata-containers/tree/main/tests/functional)

View File

@@ -1,65 +0,0 @@
#!/bin/bash
#
# Copyright (c) 2023 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
#
set -o errexit
set -o nounset
set -o pipefail
kata_tarball_dir="${2:-kata-artifacts}"
runk_dir="$(dirname "$(readlink -f "$0")")"
source "${runk_dir}/../../common.bash"
source "${runk_dir}/../../gha-run-k8s-common.sh"
function install_dependencies() {
info "Installing the dependencies needed for running the runk tests"
# Dependency list of projects that we can rely on the system packages
# - jq
declare -a system_deps=(
jq
)
sudo apt-get update
sudo apt-get -y install "${system_deps[@]}"
ensure_yq
# Dependency list of projects that we can install them
# directly from their releases on GitHub:
# - containerd
# - cri-container-cni release tarball already includes CNI plugins
declare -a github_deps
github_deps[0]="cri_containerd:$(get_from_kata_deps ".externals.containerd.${CONTAINERD_VERSION}")"
github_deps[1]="runc:$(get_from_kata_deps ".externals.runc.latest")"
github_deps[2]="cni_plugins:$(get_from_kata_deps ".externals.cni-plugins.version")"
for github_dep in "${github_deps[@]}"; do
IFS=":" read -r -a dep <<< "${github_dep}"
install_${dep[0]} "${dep[1]}"
done
# Requires bats to run the tests
install_bats
}
function run() {
info "Running runk tests using"
bats "${runk_dir}/runk-tests.bats"
}
function main() {
action="${1:-}"
case "${action}" in
install-dependencies) install_dependencies ;;
install-kata) install_kata ;;
run) run ;;
*) >&2 die "Invalid argument" ;;
esac
}
main "$@"

View File

@@ -1,127 +0,0 @@
#!/usr/bin/env bats
#
# Copyright (c) 2023,2024 Kata Contributors
#
# SPDX-License-Identifier: Apache-2.0
#
# This test will validate runk with containerd
load "${BATS_TEST_DIRNAME}/../../common.bash"
load "${BATS_TEST_DIRNAME}/../../metrics/lib/common.bash"
setup_file() {
export RUNK_BIN_PATH="/usr/local/bin/runk"
export TEST_IMAGE="quay.io/prometheus/busybox:latest"
export CONTAINER_ID="id1"
export PID_FILE="${CONTAINER_ID}.pid"
export WORK_DIR="${BATS_FILE_TMPDIR}"
echo "pull container image"
check_images ${TEST_IMAGE}
}
setup() {
# Bind mount ${WORK_DIR}:/tmp. Tests below will store files in this dir and check them when container is frozon.
sudo ctr run --pid-file ${PID_FILE} -d \
--mount type=bind,src=${WORK_DIR},dst=/tmp,options=rbind:rw \
--runc-binary ${RUNK_BIN_PATH} \
${TEST_IMAGE} \
${CONTAINER_ID}
read CID PID STATUS <<< $(sudo ctr t ls | grep ${CONTAINER_ID})
# Check the pid is consistent
[ "${PID}" == "$(cat "${PID_FILE}")" ]
# Check the container status is RUNNING
[ "${STATUS}" == "RUNNING" ]
}
teardown() {
echo "delete the container"
if sudo ctr t list -q | grep -q "${CONTAINER_ID}"; then
stop_container
fi
sudo ctr c rm "${CONTAINER_ID}"
sudo rm -f "${PID_FILE}"
}
stop_container() {
local cmd
sudo ctr t kill --signal SIGKILL --all "${CONTAINER_ID}"
# poll for a while until the task receives signal and exit
cmd='[ "STOPPED" == "$(sudo ctr t ls | grep ${CONTAINER_ID} | awk "{print \$3}")" ]'
waitForProcess 10 1 "${cmd}"
echo "check the container is stopped"
# there is only title line of ps command
[ "1" == "$(sudo ctr t ps ${CONTAINER_ID} | wc -l)" ]
}
@test "start container with runk" {
}
@test "exec process in a container" {
sudo ctr t exec --exec-id id1 "${CONTAINER_ID}" sh -c "echo hello > /tmp/foo"
# Check exec succeeded
[ "hello" == "$(sudo ctr t exec --exec-id id1 "${CONTAINER_ID}" cat /tmp/foo)" ]
}
@test "run ps command" {
sudo ctr t exec --detach --exec-id id1 "${CONTAINER_ID}" sh
return_code=$?
echo "ctr t exec sh return: ${return_code}"
# Give some time for the sh process to start within the container.
sleep 5
ps_out="$(sudo ctr t ps ${CONTAINER_ID})" || die "ps command failed"
printf "ps output:\n%s\n" "${ps_out}"
lines_no="$(printf "%s\n" "${ps_out}" | wc -l)"
echo "ps output lines: ${lines_no}"
# one line is the titles, and the other 2 lines are process info
[ "3" == "${lines_no}" ]
}
@test "pause and resume the container" {
# The process outputs lines into /tmp/{CONTAINER_ID}, which can be read in host when it's frozon.
sudo ctr t exec --detach --exec-id id2 ${CONTAINER_ID} \
sh -c "while true; do echo hello >> /tmp/${CONTAINER_ID}; sleep 0.1; done"
# sleep for 1s to make sure the process outputs some lines
sleep 1
sudo ctr t pause "${CONTAINER_ID}"
# Check the status is PAUSED
[ "PAUSED" == "$(sudo ctr t ls | grep ${CONTAINER_ID} | grep -o PAUSED)" ]
echo "container is paused"
local TMP_FILE="${WORK_DIR}/${CONTAINER_ID}"
local lines1=$(cat ${TMP_FILE} | wc -l)
# sleep for a while and check the lines are not changed.
sleep 1
local lines2=$(cat ${TMP_FILE} | wc -l)
# Check the paused container is not running the process (paused indeed)
[ ${lines1} == ${lines2} ]
sudo ctr t resume ${CONTAINER_ID}
# Check the resumed container has status of RUNNING
[ "RUNNING" == "$(sudo ctr t ls | grep ${CONTAINER_ID} | grep -o RUNNING)" ]
echo "container is resumed"
# sleep for a while and check the lines are changed.
sleep 1
local lines3=$(cat ${TMP_FILE} | wc -l)
# Check the process is running again
[ ${lines2} -lt ${lines3} ]
}
@test "kill the container and poll until it is stopped" {
stop_container
}
@test "kill --all is allowed regardless of the container state" {
# High-level container runtimes such as containerd call the kill command with
# --all option in order to terminate all processes inside the container
# even if the container already is stopped. Hence, a low-level runtime
# should allow kill --all regardless of the container state like runc.
echo "test kill --all is allowed regardless of the container state"
# Check kill should fail because the container is paused
stop_container
run sudo ctr t kill --signal SIGKILL ${CONTAINER_ID}
[ $status -eq 1 ]
# Check kill --all should not fail
sudo ctr t kill --signal SIGKILL --all "${CONTAINER_ID}"
}

View File

@@ -199,8 +199,6 @@ rootfs-initrd-confidential-tarball: agent-tarball pause-image-tarball coco-guest
rootfs-initrd-tarball: agent-tarball
${MAKE} $@-build
runk-tarball: copy-scripts-for-the-tools-build
${MAKE} $@-build
rootfs-image-nvidia-gpu-tarball: agent-tarball busybox-tarball kernel-nvidia-gpu-tarball
${MAKE} $@-build

View File

@@ -135,7 +135,6 @@ options:
rootfs-image-mariner
rootfs-initrd
rootfs-initrd-confidential
runk
shim-v2
trace-forwarder
virtiofsd
@@ -1277,10 +1276,6 @@ install_kata_manager() {
install_script_helper "kata-manager.sh"
}
install_runk() {
install_tools_helper "runk"
}
install_trace_forwarder() {
install_tools_helper "trace-forwarder"
}
@@ -1329,7 +1324,6 @@ handle_build() {
install_qemu_snp_experimental
install_qemu_tdx_experimental
install_stratovirt
install_runk
install_shimv2
install_trace_forwarder
install_virtiofsd
@@ -1417,8 +1411,6 @@ handle_build() {
rootfs-cca-confidential-initrd) install_initrd_confidential ;;
runk) install_runk ;;
shim-v2) install_shimv2 ;;
trace-forwarder) install_trace_forwarder ;;
@@ -1582,7 +1574,6 @@ main() {
rootfs-initrd
rootfs-initrd-confidential
rootfs-initrd-mariner
runk
shim-v2
trace-forwarder
virtiofsd