mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-05-01 05:04:26 +00:00
tools: Add runk
Add a Rust-based standard OCI container runtime based on Kata agent. You can build and install runk as follows: ```sh $ cd src/tools/runk $ make $ sudo make install $ runk --help ``` Fixes: #2784 Signed-off-by: Manabu Sugimoto <Manabu.Sugimoto@sony.com>
This commit is contained in:
parent
2c218a07b9
commit
b221a2590f
1
Makefile
1
Makefile
@ -14,6 +14,7 @@ TOOLS =
|
|||||||
|
|
||||||
TOOLS += agent-ctl
|
TOOLS += agent-ctl
|
||||||
TOOLS += trace-forwarder
|
TOOLS += trace-forwarder
|
||||||
|
TOOLS += runk
|
||||||
|
|
||||||
STANDARD_TARGETS = build check clean install test vendor
|
STANDARD_TARGETS = build check clean install test vendor
|
||||||
|
|
||||||
|
@ -132,6 +132,7 @@ The table below lists the remaining parts of the project:
|
|||||||
| [osbuilder](tools/osbuilder) | infrastructure | Tool to create "mini O/S" rootfs and initrd images and kernel for the hypervisor. |
|
| [osbuilder](tools/osbuilder) | infrastructure | Tool to create "mini O/S" rootfs and initrd images and kernel for the hypervisor. |
|
||||||
| [`agent-ctl`](src/tools/agent-ctl) | utility | Tool that provides low-level access for testing the agent. |
|
| [`agent-ctl`](src/tools/agent-ctl) | utility | Tool that provides low-level access for testing the agent. |
|
||||||
| [`trace-forwarder`](src/tools/trace-forwarder) | utility | Agent tracing helper. |
|
| [`trace-forwarder`](src/tools/trace-forwarder) | utility | Agent tracing helper. |
|
||||||
|
| [`runk`](src/tools/runk) | utility | Standard OCI container runtime based on the agent. |
|
||||||
| [`ci`](https://github.com/kata-containers/ci) | CI | Continuous Integration configuration files and scripts. |
|
| [`ci`](https://github.com/kata-containers/ci) | CI | Continuous Integration configuration files and scripts. |
|
||||||
| [`katacontainers.io`](https://github.com/kata-containers/www.katacontainers.io) | Source for the [`katacontainers.io`](https://www.katacontainers.io) site. |
|
| [`katacontainers.io`](https://github.com/kata-containers/www.katacontainers.io) | Source for the [`katacontainers.io`](https://www.katacontainers.io) site. |
|
||||||
|
|
||||||
|
1
src/tools/runk/.gitignore
vendored
Normal file
1
src/tools/runk/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/vendor/
|
1474
src/tools/runk/Cargo.lock
generated
Normal file
1474
src/tools/runk/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
src/tools/runk/Cargo.toml
Normal file
29
src/tools/runk/Cargo.toml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
[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"] }
|
||||||
|
oci = { path = "../../libs/oci" }
|
||||||
|
logging = { path = "../../libs/logging" }
|
||||||
|
liboci-cli = "0.0.3"
|
||||||
|
clap = { version = "3.0.6", 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.15.0", features = ["full"] }
|
||||||
|
serde = { version = "1.0.133", features = ["derive"] }
|
||||||
|
serde_json = "1.0.74"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"libcontainer"
|
||||||
|
]
|
60
src/tools/runk/Makefile
Normal file
60
src/tools/runk/Makefile
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Copyright 2021-2022 Sony Group Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
include ../../../utils.mk
|
||||||
|
|
||||||
|
TARGET = runk
|
||||||
|
TARGET_PATH = target/$(TRIPLE)/$(BUILD_TYPE)/$(TARGET)
|
||||||
|
|
||||||
|
AGENT_TARGET = oci-kata-agent
|
||||||
|
AGENT_TARGET_PATH = target/$(TRIPLE)/$(BUILD_TYPE)/$(AGENT_TARGET)
|
||||||
|
AGENT_SOURCE_PATH = ../../agent
|
||||||
|
|
||||||
|
# BINDIR is a directory for installing executable programs
|
||||||
|
BINDIR := /usr/local/bin
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := default
|
||||||
|
default: build
|
||||||
|
|
||||||
|
build: build-agent build-runk
|
||||||
|
|
||||||
|
build-agent:
|
||||||
|
make -C $(AGENT_SOURCE_PATH) STANDARD_OCI_RUNTIME=yes
|
||||||
|
|
||||||
|
build-runk:
|
||||||
|
@RUSTFLAGS="$(EXTRA_RUSTFLAGS) --deny warnings" cargo build --target $(TRIPLE) --$(BUILD_TYPE)
|
||||||
|
|
||||||
|
install: install-agent install-runk
|
||||||
|
|
||||||
|
install-agent:
|
||||||
|
install -D $(AGENT_SOURCE_PATH)/$(AGENT_TARGET_PATH) $(BINDIR)/$(AGENT_TARGET)
|
||||||
|
|
||||||
|
install-runk:
|
||||||
|
install -D $(TARGET_PATH) $(BINDIR)/$(TARGET)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
cargo clean
|
||||||
|
|
||||||
|
vendor:
|
||||||
|
cargo vendor
|
||||||
|
|
||||||
|
test:
|
||||||
|
cargo test --all --target $(TRIPLE) -- --nocapture
|
||||||
|
|
||||||
|
check: standard_rust_check
|
||||||
|
|
||||||
|
.PHONY: \
|
||||||
|
build \
|
||||||
|
build-agent \
|
||||||
|
build-runk \
|
||||||
|
install \
|
||||||
|
install-agent \
|
||||||
|
install-runk \
|
||||||
|
clean \
|
||||||
|
clippy \
|
||||||
|
format \
|
||||||
|
vendor \
|
||||||
|
test \
|
||||||
|
check \
|
282
src/tools/runk/README.md
Normal file
282
src/tools/runk/README.md
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
# 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/master/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/master/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
|
||||||
|
|
||||||
|
`runk` uses the modified the `kata-agent` binary, `oci-kata-agent`, which is an agent to be called from `runk`.
|
||||||
|
Therefore, you also need to build the `oci-kata-agent` to run `runk`.
|
||||||
|
|
||||||
|
You can build both `runk` and `oci-kata-agent` as follows.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cd runk
|
||||||
|
$ make
|
||||||
|
```
|
||||||
|
|
||||||
|
To install `runk` and `oci-kata-agent` into default directory for install executable program (`/usr/local/bin`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo 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" -xvf -
|
||||||
|
```
|
||||||
|
|
||||||
|
> **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/master/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 `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-tool`
|
||||||
|
|
||||||
|
> **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-incubator/cri-tools
|
||||||
|
$ pushd $GOPATH/src/github.com/kubernetes-incubator/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"
|
||||||
|
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
|
||||||
|
/ #
|
||||||
|
```
|
||||||
|
|
23
src/tools/runk/libcontainer/Cargo.toml
Normal file
23
src/tools/runk/libcontainer/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[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"] }
|
||||||
|
oci = { path = "../../../libs/oci" }
|
||||||
|
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"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tempfile = "3.3.0"
|
121
src/tools/runk/libcontainer/src/builder.rs
Normal file
121
src/tools/runk/libcontainer/src/builder.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use crate::container::{get_config_path, ContainerContext};
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use derive_builder::Builder;
|
||||||
|
use oci::Spec;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[derive(Default, Builder, Debug)]
|
||||||
|
pub struct Container {
|
||||||
|
id: String,
|
||||||
|
bundle: PathBuf,
|
||||||
|
root: PathBuf,
|
||||||
|
console_socket: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Container {
|
||||||
|
pub fn create_ctx(self) -> Result<ContainerContext> {
|
||||||
|
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"))?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if spec.root.is_some() {
|
||||||
|
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 the rootfs path in the spec file is a relative path,
|
||||||
|
// convert it into a canonical path to pass validation of rootfs in the agent.
|
||||||
|
if !&rootfs_path.is_absolute() {
|
||||||
|
let rootfs_name = rootfs_path
|
||||||
|
.file_name()
|
||||||
|
.ok_or_else(|| anyhow!("invalid rootfs name"))?;
|
||||||
|
spec_root.path = bundle_canon
|
||||||
|
.join(rootfs_name)
|
||||||
|
.to_str()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.ok_or_else(|| anyhow!("failed to convert bundle path"))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ContainerContext {
|
||||||
|
id: self.id,
|
||||||
|
bundle: self.bundle,
|
||||||
|
state_root: self.root,
|
||||||
|
spec,
|
||||||
|
// 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,
|
||||||
|
console_socket: self.console_socket,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::container::CONFIG_FILE_NAME;
|
||||||
|
use oci::Spec;
|
||||||
|
use std::{fs::File, path::PathBuf};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TestData {
|
||||||
|
id: String,
|
||||||
|
bundle: PathBuf,
|
||||||
|
root: PathBuf,
|
||||||
|
console_socket: Option<PathBuf>,
|
||||||
|
spec: Spec,
|
||||||
|
no_pivot_root: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_ctx() {
|
||||||
|
let bundle_dir = tempdir().unwrap();
|
||||||
|
let config_file = bundle_dir.path().join(CONFIG_FILE_NAME);
|
||||||
|
let spec = Spec::default();
|
||||||
|
let file = File::create(config_file).unwrap();
|
||||||
|
serde_json::to_writer(&file, &spec).unwrap();
|
||||||
|
|
||||||
|
let test_data = TestData {
|
||||||
|
id: String::from("test"),
|
||||||
|
bundle: PathBuf::from(bundle_dir.into_path()),
|
||||||
|
root: PathBuf::from("test"),
|
||||||
|
console_socket: Some(PathBuf::from("test")),
|
||||||
|
spec: Spec::default(),
|
||||||
|
no_pivot_root: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let test_ctx = ContainerContext {
|
||||||
|
id: test_data.id.clone(),
|
||||||
|
bundle: test_data.bundle.clone(),
|
||||||
|
state_root: test_data.root.clone(),
|
||||||
|
spec: test_data.spec.clone(),
|
||||||
|
no_pivot_root: test_data.no_pivot_root,
|
||||||
|
console_socket: test_data.console_socket.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let ctx = ContainerBuilder::default()
|
||||||
|
.id(test_data.id.clone())
|
||||||
|
.bundle(test_data.bundle.clone())
|
||||||
|
.root(test_data.root.clone())
|
||||||
|
.console_socket(test_data.console_socket.clone())
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.create_ctx()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(test_ctx, ctx);
|
||||||
|
}
|
||||||
|
}
|
40
src/tools/runk/libcontainer/src/cgroup.rs
Normal file
40
src/tools/runk/libcontainer/src/cgroup.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use rustjail::cgroups::fs::Manager as CgroupManager;
|
||||||
|
use std::{
|
||||||
|
path::Path,
|
||||||
|
{fs, thread, time},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn destroy_cgroup(cgroup_mg: &CgroupManager) -> Result<()> {
|
||||||
|
for path in cgroup_mg.paths.values() {
|
||||||
|
remove_cgroup_dir(Path::new(path))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
fn remove_cgroup_dir(path: &Path) -> 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 !path.exists() || fs::remove_dir(path).is_ok() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
retries -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(anyhow!("failed to remove cgroups paths: {:?}", path));
|
||||||
|
}
|
151
src/tools/runk/libcontainer/src/container.rs
Normal file
151
src/tools/runk/libcontainer/src/container.rs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use crate::status::Status;
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use nix::unistd::{chdir, unlink, Pid};
|
||||||
|
use oci::Spec;
|
||||||
|
use rustjail::{
|
||||||
|
container::{BaseContainer, LinuxContainer, EXEC_FIFO_FILENAME},
|
||||||
|
process::Process,
|
||||||
|
specconv::CreateOpts,
|
||||||
|
};
|
||||||
|
use slog::Logger;
|
||||||
|
use std::{
|
||||||
|
env::current_dir,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CONFIG_FILE_NAME: &str = "config.json";
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub enum ContainerAction {
|
||||||
|
Create,
|
||||||
|
Run,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct ContainerContext {
|
||||||
|
pub id: String,
|
||||||
|
pub bundle: PathBuf,
|
||||||
|
pub state_root: PathBuf,
|
||||||
|
pub spec: Spec,
|
||||||
|
pub no_pivot_root: bool,
|
||||||
|
pub console_socket: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContainerContext {
|
||||||
|
pub async fn launch(&self, action: ContainerAction, logger: &Logger) -> Result<Pid> {
|
||||||
|
Status::create_dir(&self.state_root, &self.id)?;
|
||||||
|
|
||||||
|
let current_dir = current_dir()?;
|
||||||
|
chdir(&self.bundle)?;
|
||||||
|
|
||||||
|
let create_opts = CreateOpts {
|
||||||
|
cgroup_name: "".to_string(),
|
||||||
|
use_systemd_cgroup: false,
|
||||||
|
no_pivot_root: self.no_pivot_root,
|
||||||
|
no_new_keyring: false,
|
||||||
|
spec: Some(self.spec.clone()),
|
||||||
|
rootless_euid: false,
|
||||||
|
rootless_cgroup: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ctr = LinuxContainer::new(
|
||||||
|
&self.id,
|
||||||
|
&self
|
||||||
|
.state_root
|
||||||
|
.to_str()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.ok_or_else(|| anyhow!("failed to convert bundle path"))?,
|
||||||
|
create_opts.clone(),
|
||||||
|
logger,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let process = if self.spec.process.is_some() {
|
||||||
|
Process::new(
|
||||||
|
logger,
|
||||||
|
self.spec
|
||||||
|
.process
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow!("process config was not present in the spec file"))?,
|
||||||
|
&self.id,
|
||||||
|
true,
|
||||||
|
0,
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("no process configuration"));
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ref csocket_path) = self.console_socket {
|
||||||
|
ctr.set_console_socket(csocket_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match action {
|
||||||
|
ContainerAction::Create => {
|
||||||
|
ctr.start(process).await?;
|
||||||
|
}
|
||||||
|
ContainerAction::Run => {
|
||||||
|
ctr.run(process).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let oci_state = ctr.oci_state()?;
|
||||||
|
let status = Status::new(
|
||||||
|
&self.state_root,
|
||||||
|
oci_state,
|
||||||
|
ctr.init_process_start_time,
|
||||||
|
ctr.created,
|
||||||
|
ctr.cgroup_manager
|
||||||
|
.ok_or_else(|| anyhow!("cgroup manager was not present"))?,
|
||||||
|
create_opts,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
status.save()?;
|
||||||
|
|
||||||
|
if action == ContainerAction::Run {
|
||||||
|
let fifo_path = get_fifo_path(&status);
|
||||||
|
if fifo_path.exists() {
|
||||||
|
unlink(&fifo_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chdir(¤t_dir)?;
|
||||||
|
|
||||||
|
Ok(Pid::from_raw(ctr.init_process_pid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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_BUNDLE_PATH)
|
||||||
|
.join(TEST_CONTAINER_ID)
|
||||||
|
.join(EXEC_FIFO_FILENAME);
|
||||||
|
let status = create_dummy_status();
|
||||||
|
|
||||||
|
assert_eq!(get_fifo_path(&status), test_data);
|
||||||
|
}
|
||||||
|
}
|
10
src/tools/runk/libcontainer/src/lib.rs
Normal file
10
src/tools/runk/libcontainer/src/lib.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
pub mod builder;
|
||||||
|
pub mod cgroup;
|
||||||
|
pub mod container;
|
||||||
|
pub mod status;
|
||||||
|
pub mod utils;
|
246
src/tools/runk/libcontainer/src/status.rs
Normal file
246
src/tools/runk/libcontainer/src/status.rs
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
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 oci::{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>,
|
||||||
|
pub cgroup_manager: CgroupManager,
|
||||||
|
pub config: CreateOpts,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Status {
|
||||||
|
pub fn new(
|
||||||
|
root: &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: PathBuf::from(&oci_state.bundle),
|
||||||
|
rootfs,
|
||||||
|
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!("no such process"));
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
Ok(()) => Ok(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_current_container_state(status: &Status) -> Result<ContainerState> {
|
||||||
|
let running = is_process_running(Pid::from_raw(status.pid))?;
|
||||||
|
let mut has_fifo = false;
|
||||||
|
|
||||||
|
if running {
|
||||||
|
let fifo = get_fifo_path(status);
|
||||||
|
if fifo.exists() {
|
||||||
|
has_fifo = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if running && !has_fifo {
|
||||||
|
// TODO: Check paused status.
|
||||||
|
// runk does not support pause command currently.
|
||||||
|
}
|
||||||
|
|
||||||
|
if !running {
|
||||||
|
Ok(ContainerState::Stopped)
|
||||||
|
} else if has_fifo {
|
||||||
|
Ok(ContainerState::Created)
|
||||||
|
} else {
|
||||||
|
Ok(ContainerState::Running)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_all_pid(cgm: &CgroupManager) -> Result<Vec<Pid>> {
|
||||||
|
let cgroup_path = cgm.paths.get("devices");
|
||||||
|
match cgroup_path {
|
||||||
|
Some(v) => {
|
||||||
|
let path = Path::new(v);
|
||||||
|
if !path.exists() {
|
||||||
|
return Err(anyhow!("cgroup devices file does not exist"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let procs_path = path.join("cgroup.procs");
|
||||||
|
let pids: Vec<Pid> = lines_from_file(&procs_path)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| {
|
||||||
|
Pid::from_raw(
|
||||||
|
v.parse::<pid_t>()
|
||||||
|
.expect("failed to parse string into pid_t"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(pids)
|
||||||
|
}
|
||||||
|
None => Err(anyhow!("cgroup devices file dose not exist")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::utils::test_utils::*;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use nix::unistd::getpid;
|
||||||
|
use oci::ContainerState;
|
||||||
|
use rustjail::cgroups::fs::Manager as CgroupManager;
|
||||||
|
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_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() {
|
||||||
|
let status = create_dummy_status();
|
||||||
|
let state = get_current_container_state(&status).unwrap();
|
||||||
|
assert_eq!(state, ContainerState::Running);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_all_pid() {
|
||||||
|
let cgm: CgroupManager = serde_json::from_str(TEST_CGM_DATA).unwrap();
|
||||||
|
assert!(get_all_pid(&cgm).is_ok());
|
||||||
|
}
|
||||||
|
}
|
106
src/tools/runk/libcontainer/src/utils.rs
Normal file
106
src/tools/runk/libcontainer/src/utils.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use nix::sys::stat::Mode;
|
||||||
|
use std::{
|
||||||
|
fs::{DirBuilder, File},
|
||||||
|
io::{prelude::*, BufReader},
|
||||||
|
os::unix::fs::DirBuilderExt,
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
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)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod test_utils {
|
||||||
|
use crate::status::Status;
|
||||||
|
use nix::unistd::getpid;
|
||||||
|
use oci::State as OCIState;
|
||||||
|
use oci::{ContainerState, Root, Spec};
|
||||||
|
use rustjail::cgroups::fs::Manager as CgroupManager;
|
||||||
|
use rustjail::specconv::CreateOpts;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
pub const TEST_CONTAINER_ID: &str = "test";
|
||||||
|
pub const TEST_BUNDLE_PATH: &str = "/test";
|
||||||
|
pub const TEST_ANNOTATION: &str = "test";
|
||||||
|
pub const TEST_CGM_DATA: &str = r#"{
|
||||||
|
"paths": {
|
||||||
|
"devices": "/sys/fs/cgroup/devices"
|
||||||
|
},
|
||||||
|
"mounts": {
|
||||||
|
"devices": "/sys/fs/cgroup/devices"
|
||||||
|
},
|
||||||
|
"cpath": "test"
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
pub fn create_dummy_opts() -> CreateOpts {
|
||||||
|
let spec = Spec {
|
||||||
|
root: Some(Root::default()),
|
||||||
|
..Default::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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_dummy_oci_state() -> OCIState {
|
||||||
|
OCIState {
|
||||||
|
version: "1.0.0".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 status = Status::new(
|
||||||
|
Path::new(TEST_BUNDLE_PATH),
|
||||||
|
oci_state.clone(),
|
||||||
|
1,
|
||||||
|
created,
|
||||||
|
cgm,
|
||||||
|
create_dummy_opts(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
37
src/tools/runk/src/commands/create.rs
Normal file
37
src/tools/runk/src/commands/create.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use libcontainer::{builder::ContainerBuilder, container::ContainerAction};
|
||||||
|
use liboci_cli::Create;
|
||||||
|
use nix::unistd::Pid;
|
||||||
|
use slog::{info, Logger};
|
||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
|
pub async fn run(opts: Create, root: &Path, logger: &Logger) -> Result<()> {
|
||||||
|
let ctx = ContainerBuilder::default()
|
||||||
|
.id(opts.container_id)
|
||||||
|
.bundle(opts.bundle)
|
||||||
|
.root(root.to_path_buf())
|
||||||
|
.console_socket(opts.console_socket)
|
||||||
|
.build()?
|
||||||
|
.create_ctx()?;
|
||||||
|
|
||||||
|
let pid = ctx.launch(ContainerAction::Create, logger).await?;
|
||||||
|
|
||||||
|
if let Some(ref pid_file) = opts.pid_file {
|
||||||
|
create_pid_file(pid_file, pid)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(&logger, "create command finished successfully");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_pid_file<P: AsRef<Path>>(pid_file: P, pid: Pid) -> Result<()> {
|
||||||
|
fs::write(pid_file.as_ref(), format!("{}", pid))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
103
src/tools/runk/src/commands/delete.rs
Normal file
103
src/tools/runk/src/commands/delete.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use libcontainer::{
|
||||||
|
cgroup,
|
||||||
|
status::{get_current_container_state, Status},
|
||||||
|
};
|
||||||
|
use liboci_cli::Delete;
|
||||||
|
use nix::{
|
||||||
|
errno::Errno,
|
||||||
|
sys::signal::{kill, Signal},
|
||||||
|
unistd::Pid,
|
||||||
|
};
|
||||||
|
use oci::{ContainerState, State as OCIState};
|
||||||
|
use rustjail::container;
|
||||||
|
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 status = if let Ok(value) = Status::load(root, container_id) {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
fs::remove_dir_all(status_dir)?;
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
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: get_current_container_state(&status)?,
|
||||||
|
pid: status.pid,
|
||||||
|
bundle: status
|
||||||
|
.bundle
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| anyhow!("invalid bundle path"))?
|
||||||
|
.to_string(),
|
||||||
|
annotations: spec.annotations.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if spec.hooks.is_some() {
|
||||||
|
let hooks = spec
|
||||||
|
.hooks
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| anyhow!("hooks config was not present"))?;
|
||||||
|
for h in hooks.poststop.iter() {
|
||||||
|
container::execute_hook(logger, h, &oci_state).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match oci_state.status {
|
||||||
|
ContainerState::Stopped => {
|
||||||
|
destroy_container(&status)?;
|
||||||
|
}
|
||||||
|
ContainerState::Created => {
|
||||||
|
kill(Pid::from_raw(status.pid), Some(Signal::SIGKILL))?;
|
||||||
|
destroy_container(&status)?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if opts.force {
|
||||||
|
match kill(Pid::from_raw(status.pid), Some(Signal::SIGKILL)) {
|
||||||
|
Err(errno) => {
|
||||||
|
if errno != Errno::ESRCH {
|
||||||
|
return Err(anyhow!("{}", errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(()) => {}
|
||||||
|
}
|
||||||
|
destroy_container(&status)?;
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"cannot delete container {} that is not stopped",
|
||||||
|
container_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(&logger, "delete command finished successfully");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy_container(status: &Status) -> Result<()> {
|
||||||
|
cgroup::destroy_cgroup(&status.cgroup_manager)?;
|
||||||
|
status.remove_dir()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
82
src/tools/runk/src/commands/kill.rs
Normal file
82
src/tools/runk/src/commands/kill.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use libcontainer::status::{self, get_current_container_state, Status};
|
||||||
|
use liboci_cli::Kill;
|
||||||
|
use nix::{
|
||||||
|
sys::signal::{kill, Signal},
|
||||||
|
unistd::Pid,
|
||||||
|
};
|
||||||
|
use oci::ContainerState;
|
||||||
|
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 status = Status::load(state_root, container_id)?;
|
||||||
|
let current_state = get_current_container_state(&status)?;
|
||||||
|
let sig = parse_signal(&opts.signal)?;
|
||||||
|
|
||||||
|
// TODO: liboci-cli does not support --all option for kill command.
|
||||||
|
// After liboci-cli supports the option, we will change the following code.
|
||||||
|
let all = false;
|
||||||
|
if all {
|
||||||
|
let pids = status::get_all_pid(&status.cgroup_manager)?;
|
||||||
|
for pid in pids {
|
||||||
|
if !status::is_process_running(pid)? {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
kill(pid, sig)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if current_state == ContainerState::Stopped {
|
||||||
|
return Err(anyhow!("container {} not running", container_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = Pid::from_raw(status.pid);
|
||||||
|
if status::is_process_running(p)? {
|
||||||
|
kill(p, sig)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
12
src/tools/runk/src/commands/mod.rs
Normal file
12
src/tools/runk/src/commands/mod.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
pub mod create;
|
||||||
|
pub mod delete;
|
||||||
|
pub mod kill;
|
||||||
|
pub mod run;
|
||||||
|
pub mod spec;
|
||||||
|
pub mod start;
|
||||||
|
pub mod state;
|
26
src/tools/runk/src/commands/run.rs
Normal file
26
src/tools/runk/src/commands/run.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use libcontainer::{builder::ContainerBuilder, container::ContainerAction};
|
||||||
|
use liboci_cli::Run;
|
||||||
|
use slog::{info, Logger};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub async fn run(opts: Run, root: &Path, logger: &Logger) -> Result<()> {
|
||||||
|
let ctx = ContainerBuilder::default()
|
||||||
|
.id(opts.container_id)
|
||||||
|
.bundle(opts.bundle)
|
||||||
|
.root(root.to_path_buf())
|
||||||
|
.console_socket(opts.console_socket)
|
||||||
|
.build()?
|
||||||
|
.create_ctx()?;
|
||||||
|
|
||||||
|
ctx.launch(ContainerAction::Run, logger).await?;
|
||||||
|
|
||||||
|
info!(&logger, "run command finished successfully");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
207
src/tools/runk/src/commands/spec.rs
Normal file
207
src/tools/runk/src/commands/spec.rs
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
// 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(())
|
||||||
|
}
|
48
src/tools/runk/src/commands/start.rs
Normal file
48
src/tools/runk/src/commands/start.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use crate::commands::state::get_container_state_name;
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use libcontainer::{
|
||||||
|
container::get_fifo_path,
|
||||||
|
status::{get_current_container_state, Status},
|
||||||
|
};
|
||||||
|
use liboci_cli::Start;
|
||||||
|
use nix::unistd::unlink;
|
||||||
|
use oci::ContainerState;
|
||||||
|
use slog::{info, Logger};
|
||||||
|
use std::{fs::OpenOptions, io::prelude::*, path::Path, time::SystemTime};
|
||||||
|
|
||||||
|
pub fn run(opts: Start, state_root: &Path, logger: &Logger) -> Result<()> {
|
||||||
|
let mut status = Status::load(state_root, &opts.container_id)?;
|
||||||
|
let state = get_current_container_state(&status)?;
|
||||||
|
if state != ContainerState::Created {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"cannot start a container in the {} state",
|
||||||
|
get_container_state_name(state)
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let fifo_path = get_fifo_path(&status);
|
||||||
|
let mut file = OpenOptions::new().write(true).open(&fifo_path)?;
|
||||||
|
|
||||||
|
file.write_all("0".as_bytes())?;
|
||||||
|
|
||||||
|
info!(&logger, "container started");
|
||||||
|
|
||||||
|
status.process_start_time = SystemTime::now()
|
||||||
|
.duration_since(SystemTime::UNIX_EPOCH)?
|
||||||
|
.as_secs();
|
||||||
|
|
||||||
|
status.save()?;
|
||||||
|
|
||||||
|
if fifo_path.exists() {
|
||||||
|
unlink(&fifo_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(&logger, "start command finished successfully");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
79
src/tools/runk/src/commands/state.rs
Normal file
79
src/tools/runk/src/commands/state.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// Copyright 2021-2022 Sony Group Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use libcontainer::status::{get_current_container_state, Status};
|
||||||
|
use liboci_cli::State;
|
||||||
|
use oci::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 status = Status::load(state_root, &opts.container_id)?;
|
||||||
|
let state = get_current_container_state(&status)?;
|
||||||
|
let oci_state = RuntimeState::new(status, 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 oci::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));
|
||||||
|
}
|
||||||
|
}
|
111
src/tools/runk/src/main.rs
Normal file
111
src/tools/runk/src/main.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// 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, StandardCmd};
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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),
|
||||||
|
StandardCmd::Kill(kill) => commands::kill::run(kill, root_path, logger),
|
||||||
|
StandardCmd::Delete(delete) => commands::delete::run(delete, root_path, logger).await,
|
||||||
|
StandardCmd::State(state) => commands::state::run(state, 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),
|
||||||
|
_ => {
|
||||||
|
return Err(anyhow!("command is not implemented yet"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user