Compare commits

..

35 Commits

Author SHA1 Message Date
stevenhorsman
aa11441c1a workflows: Create workflow to stale issues based on date
The standard stale/action is intended to be run regularly with
a date offset, but we want to have one we can run against a specific
date in order to run the stale bot against issues created since a particular
release milestone, so calculate the offset in one step and use it in the next.

At the moment we want to run this to stale issues before 9th October 2022 when Kata 3.0 was release, so default to this.

Note the stale action only processes a few issues at a time to avoid rate limiting, so why we want a cron job to it can get through
the backlog, but also to stale/unstale issues that are commented on.
2026-01-22 11:32:01 +00:00
Steve Horsman
2cd76796bd Merge pull request #12305 from stevenhorsman/fix-stalebot-permissions
ci: Fix stalebot permissions
2026-01-22 10:02:43 +00:00
Hyounggyu Choi
bc131a84b9 GHA: Set timeout for kata-deploy and kbs cleanup
It was observed that some kata-deploy cleanup steps could hang,
causing the workflow to never finish properly. In these cases,
a QEMU process was not cleaned up and kept printing debug logs
to the journal. Over time, this maxed out the runner’s disk
usage and caused the runner service to stop.

Set timeouts for the relevant cleanup steps to avoid this.

Signed-off-by: Hyounggyu Choi <Hyounggyu.Choi@ibm.com>
2026-01-22 10:32:24 +01:00
Fabiano Fidêncio
dacb14619d kata-deploy: Make verification ConfigMap a regular resource
The verification job mounts a ConfigMap containing the pod spec for
the Kata runtime test. Previously, both the ConfigMap and the Job were
Helm hooks with different weights (-5 and 0 respectively).

On k3s, a race condition was observed where the Job pod would be
scheduled before the kubelet's informer cache had registered the
ConfigMap, causing a FailedMount error:

  MountVolume.SetUp failed for volume "pod-spec": object
  "kube-system"/"kata-deploy-verification-spec" not registered

This happened because k3s's lightweight architecture schedules pods
very quickly, and the hook weight difference only controls Helm's
ordering, not actual timing between resource creation and cache sync.

By making the ConfigMap a regular chart resource (removing hook
annotations), it is created during the main chart installation phase,
well before any post-install hooks run. This guarantees the ConfigMap
is fully propagated to all kubelets before the verification Job starts.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-21 20:14:33 +01:00
Fabiano Fidêncio
89e287c3b2 kata-deploy: Add more permissions to verification job's RBAC
The verification job needs to list nodes to check for the
katacontainers.io/kata-runtime label and list events to detect
FailedCreatePodSandBox errors during pod creation.

This was discovered when testing with k0s, where the service account
lacked the required cluster-scope permissions to list nodes.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-21 20:14:33 +01:00
Fabiano Fidêncio
869dd5ac65 kata-deploy: Enable dynamic drop-in support for k0s
Remove k0s-worker and k0s-controller from
RUNTIMES_WITHOUT_CONTAINERD_DROP_IN_SUPPORT and always return true for
k0s in is_containerd_capable_of_using_drop_in_files since k0s auto-loads
from containerd.d/ directory regardless of containerd version.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-21 20:14:33 +01:00
Fabiano Fidêncio
d4ea02e339 kata-deploy: Add microk8s support with dynamic version detection
Add microk8s case to get_containerd_paths() method and remove microk8s
from RUNTIMES_WITHOUT_CONTAINERD_DROP_IN_SUPPORT to enable dynamic
containerd version checking.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-21 20:14:33 +01:00
Fabiano Fidêncio
69dd9679c2 kata-deploy: Centralize containerd path management
Introduce ContainerdPaths struct and get_containerd_paths() method to
centralize the complex logic for determining containerd configuration
file paths across different Kubernetes distributions.

The new ContainerdPaths struct includes:
- config_file: File to read containerd version from and write to
- backup_file: Backup file path before modification
- imports_file: File to add/remove drop-in imports from (Option<String>)
- drop_in_file: Path to the drop-in configuration file
- use_drop_in: Whether drop-in files can be used

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-21 20:14:33 +01:00
Fabiano Fidêncio
606c12df6d kata-deploy: fix JSONPath parsing for labels with dots
The JSONPath parser was incorrectly splitting on escaped dots (\.)
causing microk8s detection to fail. Labels like "microk8s.io/cluster"
were being split into ["microk8s\", "io/cluster"] instead of being
treated as a single key.

This adds a split_jsonpath() helper that properly handles escaped dots,
allowing the automatic microk8s detection via the node label to work
correctly.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-21 20:14:33 +01:00
Fabiano Fidêncio
ec18dd79ba tests: Simplify kata-deploy test to use helm directly
The kata-deploy test was using helm_helper which made it hard to debug
failures (die() calls would cause "Executed 0 tests" errors) and added
unnecessary complexity.

The test now calls helm directly like a user would, making it simpler
and more representative of real-world usage. The verification job status
is explicitly checked with proper failure detection instead of relying
on helm --wait.

Timeouts are configurable via environment variables to account for
different network speeds and image sizes:
- KATA_DEPLOY_TIMEOUT (default: 600s)
- KATA_DEPLOY_DAEMONSET_TIMEOUT (default: 300s)
- KATA_DEPLOY_VERIFICATION_TIMEOUT (default: 120s)

Documentation has been added to explain what each timeout controls and
how to customize them.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-21 20:14:33 +01:00
Fabiano Fidêncio
86e0b08b13 kata-deploy: Improve verification job timing and failure detection
The verification job now supports configurable timeouts to accommodate
different environments and network conditions. The daemonset timeout
defaults to 1200 seconds (20 minutes) to allow for large image downloads,
while the verification pod timeout defaults to 180 seconds.

The job now waits for the DaemonSet to exist, pods to be scheduled,
rollout to complete, and nodes to be labeled before creating the
verification pod. A 15-second delay is added after node labeling to
allow kubelet time to refresh runtime information.

Retry logic with 3 attempts and a 10-second delay handles transient
FailedCreatePodSandBox errors that can occur during runtime
initialization. The job only fails on pod errors after a 30-second
grace period to avoid false positives from timing issues.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-21 20:14:33 +01:00
Fabiano Fidêncio
2369cf585d tests: Fix retry loop bugs in helm_helper
The retry loop in helm_helper had two bugs:
1. Counter initialized to 10 instead of 0, causing immediate failure
2. Exit condition used -eq instead of -ge, incorrect for loop logic

These bugs would cause helm_helper to fail immediately on the first
retry attempt instead of properly retrying up to max_tries times.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-21 20:14:33 +01:00
stevenhorsman
19efeae12e workflow: Fix stalebot permissions
When looking into stale bot more for issues, I realised that our existing
stale job would need permissions to work. Unfortunately the behaviour
of the actions without these permissions is to log, but still finish as successful.
This means it was hard to spot we had an issue.

Add the required permissions to get this working again and improve the message
Also add concurrency rule to make zizmor happy

Signed-off-by: stevenhorsman <steven@uk.ibm.com>
2026-01-21 17:28:59 +00:00
Steve Horsman
70f6543333 Merge pull request #12371 from stevenhorsman/cargo-check
build: Add cargo check
2026-01-21 14:50:07 +00:00
Steve Horsman
4eb50d7b59 Merge pull request #12334 from stevenhorsman/rust-linting-improvements
Rust linting improvements
2026-01-21 14:01:37 +00:00
Steve Horsman
ba47bb6583 Merge pull request #11421 from kata-containers/dependabot/go_modules/src/runtime/github.com/urfave/cli-1.22.17
build(deps): bump github.com/urfave/cli from 1.22.14 to 1.22.17 in /src/runtime
2026-01-21 11:46:02 +00:00
stevenhorsman
62847e1efb kata-ctl: Remove unnecessary unwrap
Switch `is_err()` and then `unwrap_err()` for `if let` which is
"more idiomatic"

Signed-off-by: stevenhorsman <steven@uk.ibm.com>
2026-01-21 08:53:40 +00:00
stevenhorsman
78824e0181 agent: Remove unnecessary unwrap
Switch `is_some()` and then `unwrap()` for `if let` which is
"more idiomatic"

Signed-off-by: stevenhorsman <steven@uk.ibm.com>
2026-01-21 08:53:40 +00:00
stevenhorsman
d135a186e1 libs: Remove unnecessary unwrap
Switch `is_err()` and then `unwrap_err()` for `if let` which is
"more idiomatic"

Signed-off-by: stevenhorsman <steven@uk.ibm.com>
2026-01-21 08:52:48 +00:00
stevenhorsman
949e0c2ca0 libs: Remove unused imports
Tidy up the imports

Signed-off-by: stevenhorsman <steven@uk.ibm.com>
2026-01-21 08:52:48 +00:00
stevenhorsman
83b0c44986 dragonball: Remove unused imports
Clean up the imports

Signed-off-by: stevenhorsman <steven@uk.ibm.com>
2026-01-21 08:52:48 +00:00
stevenhorsman
7a02c54b6c kata-ctl: Allow unused assigned in clap parsing
command isn't ever read, but leave it in for now, so we don't disrupt
the parsing option

Signed-off-by: stevenhorsman <steven@uk.ibm.com>
2026-01-21 08:52:48 +00:00
stevenhorsman
bf1539b802 libs: Replace manual default
HugePageType has a manual default that can be derived
more concisely

Signed-off-by: stevenhorsman <steven@uk.ibm.com>
2026-01-21 08:52:47 +00:00
stevenhorsman
0fd9eebf0f kata-ctl: Update Cargo.lock
The cargo check identified that the lock file is out of date,
so bump this to fix the issue

Signed-off-by: stevenhorsman <steven@uk.ibm.com>
2026-01-20 16:07:34 +00:00
stevenhorsman
3f1533ae8a build: Add cargo check
We've had a couple of occasions that Cargo.lock has been out of sync
with Cargo.toml, so try and extend our rust check to pick this up in the CI.

There is probably a more elegant way than doing `cargo check` and
checking for changes, but I'll start with this approach

Signed-off-by: stevenhorsman <steven@uk.ibm.com>
2026-01-20 16:07:34 +00:00
Greg Kurz
cf3441bd2c agent: Refresh Cargo.lock
Downstream builders at Red Hat complain that `Cargo.lock` doesn't match
`Cargo.toml`.

Run `cargo check` to refresh `Cargo.lock`.

`git bisect` shows that 7cfb97d41b is the first commit where
`cargo check` has an effect in `src/agent`.

Signed-off-by: Greg Kurz <groug@kaod.org>
2026-01-20 14:44:47 +01:00
Fabiano Fidêncio
e0158869b1 tests: Add common bats test runner function
Add run_bats_tests() function to common.bash that provides consistent
test execution and reporting across all test suites (k8s, nvidia,
kata-deploy).

This removes duplicated test runner code from run_kubernetes_tests.sh,
run_kubernetes_nv_tests.sh, and run-kata-deploy-tests.sh.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-20 12:31:55 +01:00
Fabiano Fidêncio
5aff81198f helm-chart: Fix warnings on README
nydus -> `nydus`
erofs -> `erofs`

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-19 22:41:50 +01:00
Fabiano Fidêncio
b5a986eacf kata-deploy: Add runtime-rs TDX / SNP runtimeclasses
https://github.com/kata-containers/kata-containers/pull/11534 has been
merged and it added all the needed bits to deploy the QEMU SNP / TDX
runtime-rs variants, apart from the kata-deploy additions, which is done
by this PR.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-19 22:41:50 +01:00
Fabiano Fidêncio
c7570427d2 tests: Add report generation to NVIDIA tests
The NVIDIA GPU test runner script was not generating test reports,
causing the report_tests() function in gha-run.sh to have nothing
to display. This aligns the script with run_kubernetes_tests.sh by:

- Adding set -o pipefail for proper pipeline error handling
- Creating a reports directory with timestamped subdirectory
- Capturing test output to files with ok-/not_ok- prefixes
- Adding --timing flag to bats for timing information

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-19 18:21:43 +01:00
Fabiano Fidêncio
c1216598e8 static-checks: Fix kata-deploy reference
Let's just point to the official documentation rather than explaining
exactly how to deploy (and the current text was very outdated).

Removing fluentd / minikube examples is out of context of this commit.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-19 15:09:20 +01:00
Fabiano Fidêncio
96e1fb4ca6 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>
2026-01-19 14:43:53 +01:00
Fabiano Fidêncio
f68c25de6a kata-deploy: Switch to the rust version
Let's remove the script and rely only on the rust version from now on.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-19 14:07:49 +01:00
Fabiano Fidêncio
d7aa793dde Revert "ci: Run a nightly job using the kata-deploy rust"
This reverts commit 6130d7330f, as we're
officially swithcing to the rust version of kata-deploy.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
2026-01-19 14:07:49 +01:00
dependabot[bot]
2edb161c53 build(deps): bump github.com/urfave/cli in /src/runtime
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.14 to 1.22.17.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.22.14...v1.22.17)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli
  dependency-version: 1.22.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 09:04:41 +00:00
92 changed files with 1071 additions and 9504 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,36 +0,0 @@
name: Kata Containers Nightly CI (Rust)
on:
schedule:
- cron: '0 1 * * *' # Run at 1 AM UTC (1 hour after script-based nightly)
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions: {}
jobs:
kata-containers-ci-on-push-rust:
permissions:
contents: read
packages: write
id-token: write
attestations: write
uses: ./.github/workflows/ci.yaml
with:
commit-hash: ${{ github.sha }}
pr-number: "nightly-rust"
tag: ${{ github.sha }}-nightly-rust
target-branch: ${{ github.ref_name }}
build-type: "rust" # Use Rust-based build
secrets:
AUTHENTICATED_IMAGE_PASSWORD: ${{ secrets.AUTHENTICATED_IMAGE_PASSWORD }}
AZ_APPID: ${{ secrets.AZ_APPID }}
AZ_TENANT_ID: ${{ secrets.AZ_TENANT_ID }}
AZ_SUBSCRIPTION_ID: ${{ secrets.AZ_SUBSCRIPTION_ID }}
CI_HKD_PATH: ${{ secrets.CI_HKD_PATH }}
ITA_KEY: ${{ secrets.ITA_KEY }}
QUAY_DEPLOYER_PASSWORD: ${{ secrets.QUAY_DEPLOYER_PASSWORD }}
NGC_API_KEY: ${{ secrets.NGC_API_KEY }}
KBUILD_SIGN_PIN: ${{ secrets.KBUILD_SIGN_PIN }}

View File

@@ -19,11 +19,6 @@ on:
required: false
type: string
default: no
build-type:
description: The build type for kata-deploy. Use 'rust' for Rust-based build, empty or omit for script-based (default).
required: false
type: string
default: ""
secrets:
AUTHENTICATED_IMAGE_PASSWORD:
required: true
@@ -77,7 +72,6 @@ jobs:
target-branch: ${{ inputs.target-branch }}
runner: ubuntu-22.04
arch: amd64
build-type: ${{ inputs.build-type }}
secrets:
QUAY_DEPLOYER_PASSWORD: ${{ secrets.QUAY_DEPLOYER_PASSWORD }}
@@ -110,7 +104,6 @@ jobs:
target-branch: ${{ inputs.target-branch }}
runner: ubuntu-24.04-arm
arch: arm64
build-type: ${{ inputs.build-type }}
secrets:
QUAY_DEPLOYER_PASSWORD: ${{ secrets.QUAY_DEPLOYER_PASSWORD }}
@@ -156,7 +149,6 @@ jobs:
target-branch: ${{ inputs.target-branch }}
runner: ubuntu-24.04-s390x
arch: s390x
build-type: ${{ inputs.build-type }}
secrets:
QUAY_DEPLOYER_PASSWORD: ${{ secrets.QUAY_DEPLOYER_PASSWORD }}
@@ -175,7 +167,6 @@ jobs:
target-branch: ${{ inputs.target-branch }}
runner: ubuntu-24.04-ppc64le
arch: ppc64le
build-type: ${{ inputs.build-type }}
secrets:
QUAY_DEPLOYER_PASSWORD: ${{ secrets.QUAY_DEPLOYER_PASSWORD }}
@@ -297,7 +288,7 @@ jobs:
tarball-suffix: -${{ inputs.tag }}
registry: ghcr.io
repo: ${{ github.repository_owner }}/kata-deploy-ci
tag: ${{ inputs.tag }}-amd64${{ inputs.build-type == 'rust' && '-rust' || '' }}
tag: ${{ inputs.tag }}-amd64
commit-hash: ${{ inputs.commit-hash }}
pr-number: ${{ inputs.pr-number }}
target-branch: ${{ inputs.target-branch }}
@@ -313,7 +304,7 @@ jobs:
with:
registry: ghcr.io
repo: ${{ github.repository_owner }}/kata-deploy-ci
tag: ${{ inputs.tag }}-arm64${{ inputs.build-type == 'rust' && '-rust' || '' }}
tag: ${{ inputs.tag }}-arm64
commit-hash: ${{ inputs.commit-hash }}
pr-number: ${{ inputs.pr-number }}
target-branch: ${{ inputs.target-branch }}
@@ -326,7 +317,7 @@ jobs:
tarball-suffix: -${{ inputs.tag }}
registry: ghcr.io
repo: ${{ github.repository_owner }}/kata-deploy-ci
tag: ${{ inputs.tag }}-amd64${{ inputs.build-type == 'rust' && '-rust' || '' }}
tag: ${{ inputs.tag }}-amd64
commit-hash: ${{ inputs.commit-hash }}
pr-number: ${{ inputs.pr-number }}
target-branch: ${{ inputs.target-branch }}
@@ -348,7 +339,7 @@ jobs:
tarball-suffix: -${{ inputs.tag }}
registry: ghcr.io
repo: ${{ github.repository_owner }}/kata-deploy-ci
tag: ${{ inputs.tag }}-amd64${{ inputs.build-type == 'rust' && '-rust' || '' }}
tag: ${{ inputs.tag }}-amd64
commit-hash: ${{ inputs.commit-hash }}
pr-number: ${{ inputs.pr-number }}
target-branch: ${{ inputs.target-branch }}
@@ -366,7 +357,7 @@ jobs:
with:
registry: ghcr.io
repo: ${{ github.repository_owner }}/kata-deploy-ci
tag: ${{ inputs.tag }}-s390x${{ inputs.build-type == 'rust' && '-rust' || '' }}
tag: ${{ inputs.tag }}-s390x
commit-hash: ${{ inputs.commit-hash }}
pr-number: ${{ inputs.pr-number }}
target-branch: ${{ inputs.target-branch }}
@@ -380,7 +371,7 @@ jobs:
with:
registry: ghcr.io
repo: ${{ github.repository_owner }}/kata-deploy-ci
tag: ${{ inputs.tag }}-ppc64le${{ inputs.build-type == 'rust' && '-rust' || '' }}
tag: ${{ inputs.tag }}-ppc64le
commit-hash: ${{ inputs.commit-hash }}
pr-number: ${{ inputs.pr-number }}
target-branch: ${{ inputs.target-branch }}
@@ -392,7 +383,7 @@ jobs:
with:
registry: ghcr.io
repo: ${{ github.repository_owner }}/kata-deploy-ci
tag: ${{ inputs.tag }}-amd64${{ inputs.build-type == 'rust' && '-rust' || '' }}
tag: ${{ inputs.tag }}-amd64
commit-hash: ${{ inputs.commit-hash }}
pr-number: ${{ inputs.pr-number }}
target-branch: ${{ inputs.target-branch }}

View File

@@ -82,7 +82,6 @@ jobs:
target-branch: ${{ github.ref_name }}
runner: ubuntu-22.04
arch: amd64
build-type: "" # Use script-based build (default)
secrets:
QUAY_DEPLOYER_PASSWORD: ${{ secrets.QUAY_DEPLOYER_PASSWORD }}
@@ -100,7 +99,6 @@ jobs:
target-branch: ${{ github.ref_name }}
runner: ubuntu-24.04-arm
arch: arm64
build-type: "" # Use script-based build (default)
secrets:
QUAY_DEPLOYER_PASSWORD: ${{ secrets.QUAY_DEPLOYER_PASSWORD }}
@@ -118,7 +116,6 @@ jobs:
target-branch: ${{ github.ref_name }}
runner: s390x
arch: s390x
build-type: "" # Use script-based build (default)
secrets:
QUAY_DEPLOYER_PASSWORD: ${{ secrets.QUAY_DEPLOYER_PASSWORD }}
@@ -136,7 +133,6 @@ jobs:
target-branch: ${{ github.ref_name }}
runner: ubuntu-24.04-ppc64le
arch: ppc64le
build-type: "" # Use script-based build (default)
secrets:
QUAY_DEPLOYER_PASSWORD: ${{ secrets.QUAY_DEPLOYER_PASSWORD }}

View File

@@ -30,11 +30,6 @@ on:
description: The arch of the tarball.
required: true
type: string
build-type:
description: The build type for kata-deploy. Use 'rust' for Rust-based build, empty or omit for script-based (default).
required: false
type: string
default: ""
secrets:
QUAY_DEPLOYER_PASSWORD:
required: true
@@ -106,10 +101,8 @@ jobs:
REGISTRY: ${{ inputs.registry }}
REPO: ${{ inputs.repo }}
TAG: ${{ inputs.tag }}
BUILD_TYPE: ${{ inputs.build-type }}
run: |
./tools/packaging/kata-deploy/local-build/kata-deploy-build-and-upload-payload.sh \
"$(pwd)/kata-static.tar.zst" \
"${REGISTRY}/${REPO}" \
"${TAG}" \
"${BUILD_TYPE}"
"${TAG}"

View File

@@ -126,5 +126,6 @@ jobs:
- name: Delete CoCo KBS
if: always() && matrix.environment.name != 'nvidia-gpu'
timeout-minutes: 10
run: |
bash tests/integration/kubernetes/gha-run.sh delete-coco-kbs

View File

@@ -137,10 +137,12 @@ jobs:
- name: Delete kata-deploy
if: always()
timeout-minutes: 10
run: bash tests/integration/kubernetes/gha-run.sh cleanup-zvsi
- name: Delete CoCo KBS
if: always()
timeout-minutes: 10
run: |
if [ "${KBS}" == "true" ]; then
bash tests/integration/kubernetes/gha-run.sh delete-coco-kbs

View File

@@ -120,10 +120,12 @@ jobs:
- name: Delete kata-deploy
if: always()
timeout-minutes: 15
run: bash tests/integration/kubernetes/gha-run.sh cleanup
- name: Delete CoCo KBS
if: always()
timeout-minutes: 10
run: |
[[ "${KATA_HYPERVISOR}" == "qemu-tdx" ]] && echo "ITA_KEY=${GH_ITA_KEY}" >> "${GITHUB_ENV}"
bash tests/integration/kubernetes/gha-run.sh delete-coco-kbs

View File

@@ -87,4 +87,4 @@ jobs:
- name: Report tests
if: always()
run: bash tests/integration/kubernetes/gha-run.sh report-tests
run: bash tests/functional/kata-deploy/gha-run.sh report-tests

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

@@ -6,14 +6,21 @@ on:
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
stale:
name: stale
runs-on: ubuntu-22.04
permissions:
actions: write # Needed to manage caches for state persistence across runs
pull-requests: write # Needed to add/remove labels, post comments, or close PRs
steps:
- uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
with:
stale-pr-message: 'This PR has been opened without with no activity for 180 days. Comment on the issue otherwise it will be closed in 7 days'
stale-pr-message: 'This PR has been opened without activity for 180 days. Please comment on the issue or it will be closed in 7 days.'
days-before-pr-stale: 180
days-before-pr-close: 7
days-before-issue-stale: -1

41
.github/workflows/stale_issues.yaml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: 'Stale issues with activity before a fixed date'
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
inputs:
date:
description: "Date of stale cut-off. All issues not updated since this date will be marked as stale. Format: YYYY-MM-DD e.g. 2022-10-09"
default: "2022-10-09"
required: false
type: string
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
stale:
name: stale
runs-on: ubuntu-24.04
permissions:
actions: write # Needed to manage caches for state persistence across runs
issues: write # Needed to add/remove labels, post comments, or close issues
steps:
- name: Calculate the age to stale
run: |
echo AGE=$(( ( $(date +%s) - $(date -d "${DATE:-2022-10-09}" +%s) ) / 86400 )) >> "$GITHUB_ENV"
env:
DATE: ${{ inputs.date }}
- name: Run the stale action
uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
with:
stale-pr-message: 'This issue has had no activity for at least ${AGE} days. Please comment on the issue, or it will be closed in 30 days'
days-before-pr-stale: -1
days-before-pr-close: -1
days-before-issue-stale: ${AGE}
days-before-issue-close: 30

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

@@ -103,48 +103,8 @@ $ minikube ssh "grep -c -E 'vmx|svm' /proc/cpuinfo"
## Installing Kata Containers
You can now install the Kata Containers runtime components. You will need a local copy of some Kata
Containers components to help with this, and then use `kubectl` on the host (that Minikube has already
configured for you) to deploy them:
```sh
$ git clone https://github.com/kata-containers/kata-containers.git
$ cd kata-containers/tools/packaging/kata-deploy
$ kubectl apply -f kata-rbac/base/kata-rbac.yaml
$ kubectl apply -f kata-deploy/base/kata-deploy.yaml
```
This installs the Kata Containers components into `/opt/kata` inside the Minikube node. It can take
a few minutes for the operation to complete. You can check the installation has worked by checking
the status of the `kata-deploy` pod, which will be executing
[this script](../../tools/packaging/kata-deploy/scripts/kata-deploy.sh),
and will be executing a `sleep infinity` once it has successfully completed its work.
You can accomplish this by running the following:
```sh
$ podname=$(kubectl -n kube-system get pods -o=name | grep -F kata-deploy | sed 's?pod/??')
$ kubectl -n kube-system exec ${podname} -- ps -ef | grep -F infinity
```
> *NOTE:* This check only works for single node clusters, which is the default for Minikube.
> For multi-node clusters, the check would need to be adapted to check `kata-deploy` had
> completed on all nodes.
## Enabling Kata Containers
Now you have installed the Kata Containers components in the Minikube node. Next, you need to configure
Kubernetes `RuntimeClass` to know when to use Kata Containers to run a pod.
### Register the runtime
Now register the `kata qemu` runtime with that class. This should result in no errors:
```sh
$ cd kata-containers/tools/packaging/kata-deploy/runtimeclasses
$ kubectl apply -f kata-runtimeClasses.yaml
```
The Kata Containers installation process should be complete and enabled in the Minikube cluster.
You can now install the Kata Containers runtime components
[following the official instructions](../../tools/packaging/kata-deploy/helm-chart).
## Testing Kata Containers

1
src/agent/Cargo.lock generated
View File

@@ -4305,6 +4305,7 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
name = "test-utils"
version = "0.1.0"
dependencies = [
"libc",
"nix 0.26.4",
]

View File

@@ -1588,9 +1588,11 @@ async fn join_namespaces(
cm.apply(p.pid)?;
}
if p.init && res.is_some() {
info!(logger, "set properties to cgroups!");
cm.set(res.unwrap(), false)?;
if p.init {
if let Some(resource) = res {
info!(logger, "set properties to cgroups!");
cm.set(resource, false)?;
}
}
info!(logger, "notify child to continue");

View File

@@ -10,7 +10,7 @@ use std::fs::File;
use std::sync::{Arc, Mutex};
use crossbeam_channel::{Receiver, Sender, TryRecvError};
use log::{debug, error, info, warn};
use log::{debug, info, warn};
use std::sync::mpsc;
use tracing::instrument;

View File

@@ -24,7 +24,6 @@ use dbs_legacy_devices::ConsoleHandler;
use dbs_pci::CAPABILITY_BAR_SIZE;
use dbs_utils::epoll_manager::EpollManager;
use kvm_ioctls::VmFd;
use log::error;
use virtio_queue::QueueSync;
#[cfg(feature = "dbs-virtio-devices")]

View File

@@ -770,10 +770,11 @@ impl MachineInfo {
}
/// Huge page type for VM RAM backend
#[derive(Clone, Debug, Deserialize_enum_str, Serialize_enum_str, PartialEq, Eq)]
#[derive(Clone, Debug, Deserialize_enum_str, Serialize_enum_str, PartialEq, Eq, Default)]
pub enum HugePageType {
/// Memory allocated using hugetlbfs backend
#[serde(rename = "hugetlbfs")]
#[default]
Hugetlbfs,
/// Memory allocated using transparent huge pages
@@ -781,12 +782,6 @@ pub enum HugePageType {
THP,
}
impl Default for HugePageType {
fn default() -> Self {
Self::Hugetlbfs
}
}
/// Virtual machine memory configuration information.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct MemoryInfo {

View File

@@ -366,8 +366,8 @@ key = "value"
let result = add_hypervisor_initdata_overrides(&encoded);
// This might fail depending on whether algorithm is required
if result.is_err() {
assert!(result.unwrap_err().to_string().contains("parse initdata"));
if let Err(error) = result {
assert!(error.to_string().contains("parse initdata"));
}
}
@@ -386,8 +386,8 @@ key = "value"
let result = add_hypervisor_initdata_overrides(&encoded);
// This might fail depending on whether version is required
if result.is_err() {
assert!(result.unwrap_err().to_string().contains("parse initdata"));
if let Err(error) = result {
assert!(error.to_string().contains("parse initdata"));
}
}
@@ -488,7 +488,7 @@ key = "value"
let valid_toml = r#"
version = "0.1.0"
algorithm = "sha384"
[data]
valid_key = "valid_value"
"#;
@@ -497,7 +497,7 @@ key = "value"
// Invalid TOML (missing version)
let invalid_toml = r#"
algorithm = "sha256"
[data]
key = "value"
"#;

View File

@@ -136,8 +136,6 @@ macro_rules! skip_loop_by_user {
#[cfg(test)]
mod tests {
use super::{skip_if_kvm_unaccessable, skip_if_not_root, skip_if_root};
#[test]
fn test_skip_if_not_root() {
skip_if_not_root!();

View File

@@ -49,7 +49,7 @@ require (
github.com/safchain/ethtool v0.6.2
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.11.1
github.com/urfave/cli v1.22.15
github.com/urfave/cli v1.22.17
github.com/vishvananda/netlink v1.3.1
github.com/vishvananda/netns v0.0.5
gitlab.com/nvidia/cloud-native/go-nvlib v0.0.0-20220601114329-47893b162965
@@ -85,7 +85,7 @@ require (
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containernetworking/cni v1.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect

View File

@@ -8,7 +8,6 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
@@ -70,9 +69,8 @@ github.com/containernetworking/plugins v1.9.0 h1:Mg3SXBdRGkdXyFC4lcwr6u2ZB2SDeL6
github.com/containernetworking/plugins v1.9.0/go.mod h1:JG3BxoJifxxHBhG3hFyxyhid7JgRVBu/wtooGEvWf1c=
github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=
github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cri-o/cri-o v1.34.0 h1:ux2URwAyENy5e5hD9Z95tshdfy98eqatZk0fxx3rhuk=
github.com/cri-o/cri-o v1.34.0/go.mod h1:kP40HG+1EW5CDNHjqQBFhb6dehT5dCBKcmtO5RZAm6k=
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
@@ -289,13 +287,13 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM=
github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0=
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=

View File

@@ -1,3 +1,4 @@
// Package md2man aims in converting markdown into roff (man pages).
package md2man
import (

View File

@@ -47,13 +47,13 @@ const (
tableStart = "\n.TS\nallbox;\n"
tableEnd = ".TE\n"
tableCellStart = "T{\n"
tableCellEnd = "\nT}\n"
tableCellEnd = "\nT}"
tablePreprocessor = `'\" t`
)
// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
// from markdown
func NewRoffRenderer() *roffRenderer { // nolint: golint
func NewRoffRenderer() *roffRenderer {
return &roffRenderer{}
}
@@ -316,9 +316,8 @@ func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, ente
} else if nodeLiteralSize(node) > 30 {
end = tableCellEnd
}
if node.Next == nil && end != tableCellEnd {
// Last cell: need to carriage return if we are at the end of the
// header row and content isn't wrapped in a "tablecell"
if node.Next == nil {
// Last cell: need to carriage return if we are at the end of the header row.
end += crTag
}
out(w, end)
@@ -356,7 +355,7 @@ func countColumns(node *blackfriday.Node) int {
}
func out(w io.Writer, output string) {
io.WriteString(w, output) // nolint: errcheck
io.WriteString(w, output) //nolint:errcheck
}
func escapeSpecialChars(w io.Writer, text []byte) {
@@ -395,7 +394,7 @@ func escapeSpecialCharsLine(w io.Writer, text []byte) {
i++
}
if i > org {
w.Write(text[org:i]) // nolint: errcheck
w.Write(text[org:i]) //nolint:errcheck
}
// escape a character
@@ -403,7 +402,7 @@ func escapeSpecialCharsLine(w io.Writer, text []byte) {
break
}
w.Write([]byte{'\\', text[i]}) // nolint: errcheck
w.Write([]byte{'\\', text[i]}) //nolint:errcheck
}
}

View File

@@ -257,7 +257,7 @@ github.com/containernetworking/plugins/pkg/testutils
# github.com/coreos/go-systemd/v22 v22.6.0
## explicit; go 1.23
github.com/coreos/go-systemd/v22/dbus
# github.com/cpuguy83/go-md2man/v2 v2.0.6
# github.com/cpuguy83/go-md2man/v2 v2.0.7
## explicit; go 1.12
github.com/cpuguy83/go-md2man/v2/md2man
# github.com/cri-o/cri-o v1.34.0
@@ -526,7 +526,7 @@ github.com/stretchr/testify/assert/yaml
# github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
## explicit
github.com/syndtr/gocapability/capability
# github.com/urfave/cli v1.22.15
# github.com/urfave/cli v1.22.17
## explicit; go 1.11
github.com/urfave/cli
# github.com/vishvananda/netlink v1.3.1

View File

@@ -3024,9 +3024,9 @@ dependencies = [
[[package]]
name = "qapi"
version = "0.14.0"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6412bdd014ebee03ddbbe79ac03a0b622cce4d80ba45254f6357c847f06fa38"
checksum = "7b047adab56acc4948d4b9b58693c1f33fd13efef2d6bb5f0f66a47436ceada8"
dependencies = [
"bytes",
"futures",
@@ -3061,9 +3061,9 @@ dependencies = [
[[package]]
name = "qapi-qmp"
version = "0.14.0"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8b944db7e544d2fa97595e9a000a6ba5c62c426fa185e7e00aabe4b5640b538"
checksum = "45303cac879d89361cad0287ae15f9ae1e7799b904b474152414aeece39b9875"
dependencies = [
"qapi-codegen",
"qapi-spec",

View File

@@ -81,6 +81,7 @@ pub enum Commands {
#[error("Argument is not valid")]
pub struct CheckArgument {
#[clap(subcommand)]
#[allow(unused_assignments)]
pub command: CheckSubCommand,
}

View File

@@ -486,11 +486,11 @@ mod tests {
let releases = get_kata_all_releases_by_url(KATA_GITHUB_RELEASE_URL);
// sometime in GitHub action accessing to github.com API may fail
// we can skip this test to prevent the whole test fail.
if releases.is_err() {
if let Err(error) = releases {
warn!(
sl!(),
"get kata version failed({:?}), this maybe a temporary error, just skip the test.",
releases.unwrap_err()
error
);
return;
}

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

@@ -1022,3 +1022,113 @@ function version_greater_than_equal() {
return 1
fi
}
# Run bats tests with proper reporting
#
# This function provides consistent test execution and reporting across
# all test suites (k8s, nvidia, kata-deploy, etc.)
#
# Parameters:
# $1 - Test directory (where tests are located and reports will be saved)
# $2 - Array name containing test files (passed by reference)
#
# Environment variables:
# BATS_TEST_FAIL_FAST - Set to "yes" to stop at first failure (default: "no")
#
# Example usage:
# tests=("test1.bats" "test2.bats")
# run_bats_tests "/path/to/tests" tests
#
function run_bats_tests() {
local test_dir="$1"
local -n test_array=$2
local fail_fast="${BATS_TEST_FAIL_FAST:-no}"
local report_dir="${test_dir}/reports/$(date +'%F-%T')"
mkdir -p "${report_dir}"
info "Running tests with bats version: $(bats --version). Save outputs to ${report_dir}"
local tests_fail=()
for test_entry in "${test_array[@]}"; do
test_entry=$(echo "${test_entry}" | tr -d '[:space:][:cntrl:]')
[ -z "${test_entry}" ] && continue
info "Executing ${test_entry}"
# Output file will be prefixed with "ok" or "not_ok" based on the result
local out_file="${report_dir}/${test_entry}.out"
pushd "${test_dir}" > /dev/null
if ! bats --timing --show-output-of-passing-tests "${test_entry}" | tee "${out_file}"; then
tests_fail+=("${test_entry}")
mv "${out_file}" "$(dirname "${out_file}")/not_ok-$(basename "${out_file}")"
[[ "${fail_fast}" == "yes" ]] && break
else
mv "${out_file}" "$(dirname "${out_file}")/ok-$(basename "${out_file}")"
fi
popd > /dev/null
done
if [[ ${#tests_fail[@]} -ne 0 ]]; then
die "Tests FAILED from suites: ${tests_fail[*]}"
fi
info "All tests SUCCEEDED"
}
# Report bats test results from the reports directory
#
# This function displays a summary of test results and outputs from
# the reports directory created by run_bats_tests().
#
# Parameters:
# $1 - Test directory (where reports subdirectory is located)
#
# Example usage:
# report_bats_tests "/path/to/tests"
#
function report_bats_tests() {
local test_dir="$1"
local reports_dir="${test_dir}/reports"
if [[ ! -d "${reports_dir}" ]]; then
warn "No reports directory found: ${reports_dir}"
return 1
fi
for report_dir in "${reports_dir}"/*; do
[[ ! -d "${report_dir}" ]] && continue
local ok=()
local not_ok=()
mapfile -t ok < <(find "${report_dir}" -name "ok-*.out" 2>/dev/null)
mapfile -t not_ok < <(find "${report_dir}" -name "not_ok-*.out" 2>/dev/null)
cat <<-EOF
SUMMARY ($(basename "${report_dir}")):
Pass: ${#ok[*]}
Fail: ${#not_ok[*]}
EOF
echo -e "\nSTATUSES:"
for out in "${not_ok[@]}" "${ok[@]}"; do
[[ -z "${out}" ]] && continue
local status
local bats
status=$(basename "${out}" | cut -d '-' -f1)
bats=$(basename "${out}" | cut -d '-' -f2- | sed 's/.out$//')
echo " ${status} ${bats}"
done
echo -e "\nOUTPUTS:"
for out in "${not_ok[@]}" "${ok[@]}"; do
[[ -z "${out}" ]] && continue
local bats
bats=$(basename "${out}" | cut -d '-' -f2- | sed 's/.out$//')
echo "::group::${bats}"
cat "${out}"
echo "::endgroup::"
done
done
}

View File

@@ -20,6 +20,10 @@ function run_tests() {
popd
}
function report_tests() {
report_bats_tests "${kata_deploy_dir}"
}
function cleanup_runtimeclasses() {
# Cleanup any runtime class that was left behind in the cluster, in
# case of a test failure, apart from the default one that comes from
@@ -59,6 +63,7 @@ function main() {
install-kubectl) install_kubectl ;;
get-cluster-credentials) get_cluster_credentials "kata-deploy" ;;
run-tests) run_tests ;;
report-tests) report_tests ;;
delete-cluster) cleanup "aks" "kata-deploy" ;;
*) >&2 echo "Invalid argument"; exit 2 ;;
esac

View File

@@ -4,15 +4,35 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Kata Deploy Functional Tests
#
# This test validates that kata-deploy successfully installs and configures
# Kata Containers on a Kubernetes cluster using Helm.
#
# Required environment variables:
# DOCKER_REGISTRY - Container registry for kata-deploy image
# DOCKER_REPO - Repository name for kata-deploy image
# DOCKER_TAG - Image tag to test
# KATA_HYPERVISOR - Hypervisor to test (qemu, clh, etc.)
# KUBERNETES - K8s distribution (microk8s, k3s, rke2, etc.)
#
# Optional timeout configuration (increase for slow networks or large images):
# KATA_DEPLOY_TIMEOUT - Overall helm timeout (default: 30m)
# KATA_DEPLOY_DAEMONSET_TIMEOUT - DaemonSet rollout timeout in seconds (default: 1200 = 20m)
# Includes time to pull kata-deploy image
# KATA_DEPLOY_VERIFICATION_TIMEOUT - Verification pod timeout in seconds (default: 180 = 3m)
# Time for verification pod to run
#
# Example with custom timeouts for slow network:
# KATA_DEPLOY_DAEMONSET_TIMEOUT=3600 bats kata-deploy.bats
#
load "${BATS_TEST_DIRNAME}/../../common.bash"
repo_root_dir="${BATS_TEST_DIRNAME}/../../../"
load "${repo_root_dir}/tests/gha-run-k8s-common.sh"
setup() {
ensure_yq
pushd "${repo_root_dir}"
ensure_helm
# We expect 2 runtime classes because:
# * `kata` is the default runtimeclass created by Helm, basically an alias for `kata-${KATA_HYPERVISOR}`.
@@ -26,50 +46,113 @@ setup() {
"kata\s+kata-${KATA_HYPERVISOR}" \
"kata-${KATA_HYPERVISOR}\s+kata-${KATA_HYPERVISOR}" \
)
# Set the latest image, the one generated as part of the PR, to be used as part of the tests
export HELM_IMAGE_REFERENCE="${DOCKER_REGISTRY}/${DOCKER_REPO}"
export HELM_IMAGE_TAG="${DOCKER_TAG}"
# Enable debug for Kata Containers
export HELM_DEBUG="true"
# Create the runtime class only for the shim that's being tested
export HELM_SHIMS="${KATA_HYPERVISOR}"
# Set the tested hypervisor as the default `kata` shim
export HELM_DEFAULT_SHIM="${KATA_HYPERVISOR}"
# Let the Helm chart create the default `kata` runtime class
export HELM_CREATE_DEFAULT_RUNTIME_CLASS="true"
HOST_OS=""
if [[ "${KATA_HOST_OS}" = "cbl-mariner" ]]; then
HOST_OS="${KATA_HOST_OS}"
fi
export HELM_HOST_OS="${HOST_OS}"
export HELM_K8S_DISTRIBUTION="${KUBERNETES}"
# Enable deployment verification (verifies Kata Containers
# VM kernel isolation by comparing node vs pod kernel)
export HELM_VERIFY_DEPLOYMENT="true"
helm_helper
echo "::group::kata-deploy logs"
kubectl -n kube-system logs --tail=100 -l name=kata-deploy
echo "::endgroup::"
echo "::group::Runtime classes"
kubectl get runtimeclass
echo "::endgroup::"
popd
}
@test "Test runtimeclasses are being properly created and container runtime is not broken" {
pushd "${repo_root_dir}"
# Create verification pod spec
local verification_yaml
verification_yaml=$(mktemp)
cat > "${verification_yaml}" << EOF
apiVersion: v1
kind: Pod
metadata:
name: kata-deploy-verify
spec:
runtimeClassName: kata-${KATA_HYPERVISOR}
restartPolicy: Never
nodeSelector:
katacontainers.io/kata-runtime: "true"
containers:
- name: verify
image: quay.io/kata-containers/alpine-bash-curl:latest
imagePullPolicy: Always
command:
- sh
- -c
- |
echo "=== Kata Verification ==="
echo "Kernel: \$(uname -r)"
echo "SUCCESS: Pod running with Kata runtime"
EOF
# Install kata-deploy via Helm
echo "Installing kata-deploy with Helm..."
local helm_chart_dir="tools/packaging/kata-deploy/helm-chart/kata-deploy"
# Timeouts can be customized via environment variables:
# - KATA_DEPLOY_TIMEOUT: Overall helm timeout (includes all hooks)
# Default: 600s (10 minutes)
# - KATA_DEPLOY_DAEMONSET_TIMEOUT: Time to wait for kata-deploy DaemonSet rollout (image pull + pod start)
# Default: 300s (5 minutes) - accounts for large image downloads
# - KATA_DEPLOY_VERIFICATION_TIMEOUT: Time to wait for verification pod to complete
# Default: 120s (2 minutes) - verification pod execution time
local helm_timeout="${KATA_DEPLOY_TIMEOUT:-600s}"
local daemonset_timeout="${KATA_DEPLOY_DAEMONSET_TIMEOUT:-300}"
local verification_timeout="${KATA_DEPLOY_VERIFICATION_TIMEOUT:-120}"
echo "Timeout configuration:"
echo " Helm overall: ${helm_timeout}"
echo " DaemonSet rollout: ${daemonset_timeout}s (includes image pull)"
echo " Verification pod: ${verification_timeout}s (pod execution)"
helm dependency build "${helm_chart_dir}"
# Disable all shims except the one being tested
helm upgrade --install kata-deploy "${helm_chart_dir}" \
--set image.reference="${DOCKER_REGISTRY}/${DOCKER_REPO}" \
--set image.tag="${DOCKER_TAG}" \
--set debug=true \
--set k8sDistribution="${KUBERNETES}" \
--set shims.clh.enabled=false \
--set shims.cloud-hypervisor.enabled=false \
--set shims.dragonball.enabled=false \
--set shims.fc.enabled=false \
--set shims.qemu.enabled=false \
--set shims.qemu-runtime-rs.enabled=false \
--set shims.qemu-cca.enabled=false \
--set shims.qemu-se.enabled=false \
--set shims.qemu-se-runtime-rs.enabled=false \
--set shims.qemu-nvidia-gpu.enabled=false \
--set shims.qemu-nvidia-gpu-snp.enabled=false \
--set shims.qemu-nvidia-gpu-tdx.enabled=false \
--set shims.qemu-sev.enabled=false \
--set shims.qemu-snp.enabled=false \
--set shims.qemu-snp-runtime-rs.enabled=false \
--set shims.qemu-tdx.enabled=false \
--set shims.qemu-tdx-runtime-rs.enabled=false \
--set shims.qemu-coco-dev.enabled=false \
--set shims.qemu-coco-dev-runtime-rs.enabled=false \
--set "shims.${KATA_HYPERVISOR}.enabled=true" \
--set "defaultShim.amd64=${KATA_HYPERVISOR}" \
--set "defaultShim.arm64=${KATA_HYPERVISOR}" \
--set runtimeClasses.enabled=true \
--set runtimeClasses.createDefault=true \
--set-file verification.pod="${verification_yaml}" \
--set verification.timeout="${verification_timeout}" \
--set verification.daemonsetTimeout="${daemonset_timeout}" \
--namespace kube-system \
--wait --timeout "${helm_timeout}"
rm -f "${verification_yaml}"
echo ""
echo "::group::kata-deploy logs"
kubectl -n kube-system logs --tail=200 -l name=kata-deploy
echo "::endgroup::"
echo ""
echo "::group::Runtime classes"
kubectl get runtimeclass
echo "::endgroup::"
# helm --wait already waits for post-install hooks to complete
# If helm returns successfully, the verification job passed
# The job is deleted after success (hook-delete-policy: hook-succeeded)
echo ""
echo "Helm install completed successfully - verification passed"
# We filter `kata-mshv-vm-isolation` out as that's present on AKS clusters, but that's not coming from kata-deploy
current_runtime_classes=$(kubectl get runtimeclasses | grep -v "kata-mshv-vm-isolation" | grep "kata" | wc -l)
[[ ${current_runtime_classes} -eq ${expected_runtime_classes} ]]
@@ -91,6 +174,8 @@ setup() {
# Check that the container runtime verison doesn't have unknown, which happens when containerd can't start properly
container_runtime_version=$(kubectl get nodes --no-headers -o custom-columns=CONTAINER_RUNTIME:.status.nodeInfo.containerRuntimeVersion)
[[ ${container_runtime_version} != *"containerd://Unknown"* ]]
popd
}
teardown() {

View File

@@ -6,10 +6,14 @@
#
set -e
set -o pipefail
kata_deploy_dir=$(dirname "$(readlink -f "$0")")
source "${kata_deploy_dir}/../../common.bash"
# Setting to "yes" enables fail fast, stopping execution at the first failed test.
export BATS_TEST_FAIL_FAST="${BATS_TEST_FAIL_FAST:-no}"
if [[ -n "${KATA_DEPLOY_TEST_UNION:-}" ]]; then
KATA_DEPLOY_TEST_UNION=("${KATA_DEPLOY_TEST_UNION}")
else
@@ -18,8 +22,4 @@ else
)
fi
info "Run tests"
for KATA_DEPLOY_TEST_ENTRY in "${KATA_DEPLOY_TEST_UNION[@]}"
do
bats --show-output-of-passing-tests "${KATA_DEPLOY_TEST_ENTRY}"
done
run_bats_tests "${kata_deploy_dir}" KATA_DEPLOY_TEST_UNION

View File

@@ -600,7 +600,7 @@ function helm_helper() {
yq -i ".shims.${shim}.enabled = true" "${values_yaml}"
yq -i ".shims.${shim}.supportedArches = [\"arm64\"]" "${values_yaml}"
;;
qemu-snp|qemu-tdx|qemu-nvidia-gpu-snp|qemu-nvidia-gpu-tdx)
qemu-snp|qemu-snp-runtime-rs|qemu-tdx|qemu-tdx-runtime-rs|qemu-nvidia-gpu-snp|qemu-nvidia-gpu-tdx)
yq -i ".shims.${shim}.enabled = true" "${values_yaml}"
yq -i ".shims.${shim}.supportedArches = [\"amd64\"]" "${values_yaml}"
;;
@@ -876,7 +876,7 @@ VERIFICATION_POD_EOF
max_tries=3
interval=10
i=10
i=0
# Retry loop for helm install to prevent transient failures due to instantly unreachable cluster
set +e # Disable immediate exit on failure
@@ -890,15 +890,16 @@ VERIFICATION_POD_EOF
fi
i=$((i+1))
if [[ ${i} -lt ${max_tries} ]]; then
echo "Retrying after ${interval} seconds (Attempt ${i} of $((max_tries - 1)))"
echo "Retrying after ${interval} seconds (Attempt ${i} of ${max_tries})"
else
break
fi
sleep "${interval}"
done
set -e # Re-enable immediate exit on failure
if [[ ${i} -eq ${max_tries} ]]; then
die "Failed to deploy kata-deploy after ${max_tries} tries"
if [[ ${i} -ge ${max_tries} ]]; then
echo "ERROR: Failed to deploy kata-deploy after ${max_tries} tries"
return 1
fi
# `helm install --wait` does not take effect on single replicas and maxUnavailable=1 DaemonSets

View File

@@ -326,41 +326,7 @@ function run_tests() {
# directory.
#
function report_tests() {
local reports_dir="${kubernetes_dir}/reports"
local ok
local not_ok
local status
if [[ ! -d "${reports_dir}" ]]; then
info "no reports directory found: ${reports_dir}"
return
fi
for report_dir in "${reports_dir}"/*; do
mapfile -t ok < <(find "${report_dir}" -name "ok-*.out")
mapfile -t not_ok < <(find "${report_dir}" -name "not_ok-*.out")
cat <<-EOF
SUMMARY ($(basename "${report_dir}")):
Pass: ${#ok[*]}
Fail: ${#not_ok[*]}
EOF
echo -e "\nSTATUSES:"
for out in "${not_ok[@]}" "${ok[@]}"; do
status=$(basename "${out}" | cut -d '-' -f1)
bats=$(basename "${out}" | cut -d '-' -f2- | sed 's/.out$//')
echo " ${status} ${bats}"
done
echo -e "\nOUTPUTS:"
for out in "${not_ok[@]}" "${ok[@]}"; do
bats=$(basename "${out}" | cut -d '-' -f2- | sed 's/.out$//')
echo "::group::${bats}"
cat "${out}"
echo "::endgroup::"
done
done
report_bats_tests "${kubernetes_dir}"
}
function collect_artifacts() {

View File

@@ -6,6 +6,7 @@
#
set -e
set -o pipefail
kubernetes_dir=$(dirname "$(readlink -f "$0")")
# shellcheck disable=SC1091 # import based on variable
@@ -53,20 +54,6 @@ if [[ "${ENABLE_NVRC_TRACE:-true}" == "true" ]]; then
enable_nvrc_trace
fi
info "Running tests with bats version: $(bats --version)"
tests_fail=()
for K8S_TEST_ENTRY in "${K8S_TEST_NV[@]}"
do
K8S_TEST_ENTRY=$(echo "${K8S_TEST_ENTRY}" | tr -d '[:space:][:cntrl:]')
info "$(kubectl get pods --all-namespaces 2>&1)"
info "Executing ${K8S_TEST_ENTRY}"
if ! bats --show-output-of-passing-tests "${K8S_TEST_ENTRY}"; then
tests_fail+=("${K8S_TEST_ENTRY}")
[[ "${K8S_TEST_FAIL_FAST}" = "yes" ]] && break
fi
done
[[ ${#tests_fail[@]} -ne 0 ]] && die "Tests FAILED from suites: ${tests_fail[*]}"
info "All tests SUCCEEDED"
# Use common bats test runner with proper reporting
export BATS_TEST_FAIL_FAST="${K8S_TEST_FAIL_FAST}"
run_bats_tests "${kubernetes_dir}" K8S_TEST_NV

View File

@@ -135,28 +135,6 @@ fi
ensure_yq
report_dir="${kubernetes_dir}/reports/$(date +'%F-%T')"
mkdir -p "${report_dir}"
info "Running tests with bats version: $(bats --version). Save outputs to ${report_dir}"
tests_fail=()
for K8S_TEST_ENTRY in "${K8S_TEST_UNION[@]}"
do
K8S_TEST_ENTRY=$(echo "$K8S_TEST_ENTRY" | tr -d '[:space:][:cntrl:]')
time info "$(kubectl get pods --all-namespaces 2>&1)"
info "Executing ${K8S_TEST_ENTRY}"
# Output file will be prefixed with "ok" or "not_ok" based on the result
out_file="${report_dir}/${K8S_TEST_ENTRY}.out"
if ! bats --timing --show-output-of-passing-tests "${K8S_TEST_ENTRY}" | tee "${out_file}"; then
tests_fail+=("${K8S_TEST_ENTRY}")
mv "${out_file}" "$(dirname "${out_file}")/not_ok-$(basename "${out_file}")"
[ "${K8S_TEST_FAIL_FAST}" = "yes" ] && break
else
mv "${out_file}" "$(dirname "${out_file}")/ok-$(basename "${out_file}")"
fi
done
[ ${#tests_fail[@]} -ne 0 ] && die "Tests FAILED from suites: ${tests_fail[*]}"
info "All tests SUCCEEDED"
# Use common bats test runner with proper reporting
export BATS_TEST_FAIL_FAST="${K8S_TEST_FAIL_FAST}"
run_bats_tests "${kubernetes_dir}" K8S_TEST_UNION

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

@@ -1,11 +1,8 @@
# Copyright Intel Corporation, 2022 IBM Corp.
# Copyright (c) 2025 NVIDIA Corporation
#
# SPDX-License-Identifier: Apache-2.0
ARG BASE_IMAGE_NAME=alpine
ARG BASE_IMAGE_TAG=3.22
FROM ${BASE_IMAGE_NAME}:${BASE_IMAGE_TAG} AS base
#### Nydus snapshotter & nydus image
FROM golang:1.24-alpine AS nydus-binary-downloader
@@ -17,53 +14,219 @@ ARG NYDUS_SNAPSHOTTER_REPO=https://github.com/containerd/nydus-snapshotter
RUN \
mkdir -p /opt/nydus-snapshotter && \
ARCH=$(uname -m) && \
if [[ "${ARCH}" == "x86_64" ]]; then ARCH=amd64 ; fi && \
if [[ "${ARCH}" == "aarch64" ]]; then ARCH=arm64; fi && \
ARCH="$(uname -m)" && \
if [ "${ARCH}" = "x86_64" ]; then ARCH=amd64 ; fi && \
if [ "${ARCH}" = "aarch64" ]; then ARCH=arm64; fi && \
apk add --no-cache curl && \
curl -fOL --progress-bar ${NYDUS_SNAPSHOTTER_REPO}/releases/download/${NYDUS_SNAPSHOTTER_VERSION}/nydus-snapshotter-${NYDUS_SNAPSHOTTER_VERSION}-linux-${ARCH}.tar.gz && \
tar xvzpf nydus-snapshotter-${NYDUS_SNAPSHOTTER_VERSION}-linux-${ARCH}.tar.gz -C /opt/nydus-snapshotter && \
rm nydus-snapshotter-${NYDUS_SNAPSHOTTER_VERSION}-linux-${ARCH}.tar.gz
curl -fOL --progress-bar "${NYDUS_SNAPSHOTTER_REPO}/releases/download/${NYDUS_SNAPSHOTTER_VERSION}/nydus-snapshotter-${NYDUS_SNAPSHOTTER_VERSION}-linux-${ARCH}.tar.gz" && \
tar xvzpf "nydus-snapshotter-${NYDUS_SNAPSHOTTER_VERSION}-linux-${ARCH}.tar.gz" -C /opt/nydus-snapshotter && \
rm "nydus-snapshotter-${NYDUS_SNAPSHOTTER_VERSION}-linux-${ARCH}.tar.gz"
#### Build binary package
FROM ubuntu:22.04 AS rust-builder
#### kata-deploy main image
# Default to Rust 1.90.0
ARG RUST_TOOLCHAIN=1.90.0
ENV DEBIAN_FRONTEND=noninteractive
ENV RUSTUP_HOME="/opt/rustup"
ENV CARGO_HOME="/opt/cargo"
ENV PATH="/opt/cargo/bin/:${PATH}"
# kata-deploy args
FROM base
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
ARG KATA_ARTIFACTS=./kata-static.tar.zst
RUN \
mkdir ${RUSTUP_HOME} ${CARGO_HOME} && \
chmod -R a+rwX ${RUSTUP_HOME} ${CARGO_HOME}
RUN \
apt-get update && \
apt-get --no-install-recommends -y install \
ca-certificates \
curl \
gcc \
libc6-dev \
musl-tools && \
apt-get clean && rm -rf /var/lib/apt/lists/ && \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain ${RUST_TOOLCHAIN}
WORKDIR /kata-deploy
# Copy standalone binary project
COPY binary /kata-deploy
# Install target and run tests based on architecture
# - AMD64/arm64: use musl for fully static binaries
# - PPC64le/s390x: use glibc (musl has issues on these platforms)
RUN \
HOST_ARCH="$(uname -m)"; \
rust_arch=""; \
rust_target=""; \
case "${HOST_ARCH}" in \
"x86_64") \
rust_arch="x86_64"; \
rust_target="${rust_arch}-unknown-linux-musl"; \
echo "Installing musl target for ${rust_target}"; \
rustup target add "${rust_target}"; \
;; \
"aarch64") \
rust_arch="aarch64"; \
rust_target="${rust_arch}-unknown-linux-musl"; \
echo "Installing musl target for ${rust_target}"; \
rustup target add "${rust_target}"; \
;; \
"ppc64le") \
rust_arch="powerpc64le"; \
rust_target="${rust_arch}-unknown-linux-gnu"; \
echo "Using glibc target for ${rust_target} (musl is not well supported on ppc64le)"; \
;; \
"s390x") \
rust_arch="s390x"; \
rust_target="${rust_arch}-unknown-linux-gnu"; \
echo "Using glibc target for ${rust_target} (musl is not well supported on s390x)"; \
;; \
*) echo "Unsupported architecture: ${HOST_ARCH}" && exit 1 ;; \
esac; \
echo "${rust_target}" > /tmp/rust_target
# Run tests using --test-threads=1 to prevent environment variable pollution between tests,
# and this is fine as we'll never ever have multiple binaries running at the same time.
RUN \
rust_target="$(cat /tmp/rust_target)"; \
echo "Running binary tests with target ${rust_target}..." && \
RUSTFLAGS="-D warnings" cargo test --target "${rust_target}" -- --test-threads=1 && \
echo "All tests passed!"
RUN \
rust_target="$(cat /tmp/rust_target)"; \
echo "Building kata-deploy binary for ${rust_target}..." && \
RUSTFLAGS="-D warnings" cargo build --release --target "${rust_target}" && \
mkdir -p /kata-deploy/bin && \
cp "/kata-deploy/target/${rust_target}/release/kata-deploy" /kata-deploy/bin/kata-deploy && \
echo "Cleaning up build artifacts to save disk space..." && \
rm -rf /kata-deploy/target && \
cargo clean
#### Extract kata artifacts
FROM alpine:3.22 AS artifact-extractor
ARG KATA_ARTIFACTS=kata-static.tar.zst
ARG DESTINATION=/opt/kata-artifacts
COPY ${KATA_ARTIFACTS} /
# I understand that in order to be on the safer side, it'd
# be good to have the alpine packages pointing to a very
# specific version, but this may break anyone else trying
# to use a different version of alpine for one reason or
# another. With this in mind, let's ignore DL3018.
# SC2086 is about using double quotes to prevent globbing and
# word splitting, which can also be ignored for now.
# hadolint ignore=DL3018,SC2086
COPY ${KATA_ARTIFACTS} /tmp/
RUN \
apk --no-cache add bash curl tar zstd && \
ARCH=$(uname -m) && \
if [ "${ARCH}" = "x86_64" ]; then ARCH=amd64; fi && \
if [ "${ARCH}" = "aarch64" ]; then ARCH=arm64; fi && \
DEBIAN_ARCH=${ARCH} && \
if [ "${DEBIAN_ARCH}" = "ppc64le" ]; then DEBIAN_ARCH=ppc64el; fi && \
curl -fL --progress-bar -o /usr/bin/kubectl https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/${ARCH}/kubectl && \
chmod +x /usr/bin/kubectl && \
curl -fL --progress-bar -o /usr/bin/jq https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-${DEBIAN_ARCH} && \
chmod +x /usr/bin/jq && \
mkdir -p ${DESTINATION} && \
tar --zstd -xvf ${WORKDIR}/${KATA_ARTIFACTS} -C ${DESTINATION} && \
rm -f ${WORKDIR}/${KATA_ARTIFACTS} && \
apk del curl tar zstd && \
apk --no-cache add py3-pip && \
pip install --no-cache-dir yq==3.2.3 --break-system-packages
apk add --no-cache tar zstd util-linux-misc && \
mkdir -p "${DESTINATION}" && \
tar --zstd -xf "/tmp/$(basename "${KATA_ARTIFACTS}")" -C "${DESTINATION}" && \
rm -f "/tmp/$(basename "${KATA_ARTIFACTS}")"
COPY scripts ${DESTINATION}/scripts
#### Prepare runtime dependencies (nsenter and required libraries)
# This stage assembles all runtime dependencies based on architecture
# using ldd to find exact library dependencies
FROM debian:bookworm-slim AS runtime-assembler
ARG DESTINATION=/opt/kata-artifacts
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN \
apt-get update && \
apt-get --no-install-recommends -y install \
util-linux && \
apt-get clean && rm -rf /var/lib/apt/lists/
# Copy the built binary to analyze its dependencies
COPY --from=rust-builder /kata-deploy/bin/kata-deploy /tmp/kata-deploy
# Create output directories
RUN mkdir -p /output/lib /output/lib64 /output/usr/bin
# Use ldd to find and copy all required libraries for the kata-deploy binary and nsenter
RUN \
HOST_ARCH="$(uname -m)"; \
echo "Preparing runtime dependencies for ${HOST_ARCH}"; \
case "${HOST_ARCH}" in \
"ppc64le"|"s390x") \
echo "Using glibc - copying libraries based on ldd output"; \
\
# Copy nsenter \
cp /usr/bin/nsenter /output/usr/bin/nsenter; \
\
# Show what the binaries need \
echo "Libraries needed by kata-deploy:"; \
ldd /tmp/kata-deploy || echo "ldd failed"; \
echo "Libraries needed by nsenter:"; \
ldd /usr/bin/nsenter || echo "ldd failed"; \
\
# Extract and copy all library paths from both binaries \
for binary in /tmp/kata-deploy /usr/bin/nsenter; do \
echo "Processing ${binary}..."; \
# Get libraries with "=>" (shared libs) \
ldd "${binary}" 2>/dev/null | grep "=>" | awk '{print $3}' | sort -u | while read -r lib; do \
if [ -n "${lib}" ] && [ -f "${lib}" ]; then \
dest_dir="/output$(dirname "${lib}")"; \
mkdir -p "${dest_dir}"; \
cp -Ln "${lib}" "${dest_dir}/" 2>/dev/null || true; \
echo " Copied lib: ${lib}"; \
fi; \
done; \
done; \
\
# Copy the dynamic linker - it's at /lib/ld64.so.1 (not /lib64/) \
echo "Copying dynamic linker:"; \
mkdir -p /output/lib; \
cp -Ln /lib/ld64.so* /output/lib/ 2>/dev/null || true; \
cp -Ln /lib64/ld64.so* /output/lib64/ 2>/dev/null || true; \
\
echo "glibc" > /output/.libc-type; \
;; \
*) \
echo "amd64/arm64: will use musl-based static binaries"; \
echo "musl" > /output/.libc-type; \
# Create placeholder so COPY doesn't fail \
touch /output/lib/.placeholder; \
touch /output/lib64/.placeholder; \
touch /output/usr/bin/.placeholder; \
;; \
esac
# Copy musl nsenter from alpine for amd64/arm64
COPY --from=artifact-extractor /usr/bin/nsenter /output/usr/bin/nsenter-musl
COPY --from=artifact-extractor /lib/ld-musl-*.so.1 /output/lib/
# For amd64/arm64, use the musl nsenter; for ppc64le/s390x, keep the glibc one
RUN \
HOST_ARCH="$(uname -m)"; \
case "${HOST_ARCH}" in \
"x86_64"|"aarch64") \
mv /output/usr/bin/nsenter-musl /output/usr/bin/nsenter; \
;; \
*) \
rm -f /output/usr/bin/nsenter-musl; \
;; \
esac
#### kata-deploy main image
FROM gcr.io/distroless/static-debian12@sha256:87bce11be0af225e4ca761c40babb06d6d559f5767fbf7dc3c47f0f1a466b92c
ARG DESTINATION=/opt/kata-artifacts
# Copy extracted kata artifacts
COPY --from=artifact-extractor ${DESTINATION} ${DESTINATION}
# Copy Rust binary
COPY --from=rust-builder /kata-deploy/bin/kata-deploy /usr/bin/kata-deploy
# Copy nsenter and required libraries (assembled based on architecture)
COPY --from=runtime-assembler /output/usr/bin/nsenter /usr/bin/nsenter
COPY --from=runtime-assembler /output/lib/ /lib/
COPY --from=runtime-assembler /output/lib64/ /lib64/
# Copy nydus snapshotter
COPY nydus-snapshotter ${DESTINATION}/nydus-snapshotter
COPY --from=nydus-binary-downloader /opt/nydus-snapshotter/bin/containerd-nydus-grpc ${DESTINATION}/nydus-snapshotter/
COPY --from=nydus-binary-downloader /opt/nydus-snapshotter/bin/nydus-overlayfs ${DESTINATION}/nydus-snapshotter/
# Copy runtimeclasses and node-feature-rules
COPY node-feature-rules ${DESTINATION}/node-feature-rules
ENTRYPOINT ["/usr/bin/kata-deploy"]

View File

@@ -1,232 +0,0 @@
# Copyright Intel Corporation, 2022 IBM Corp.
# Copyright (c) 2025 NVIDIA Corporation
#
# SPDX-License-Identifier: Apache-2.0
#### Nydus snapshotter & nydus image
FROM golang:1.24-alpine AS nydus-binary-downloader
# Keep the version here aligned with "ndyus-snapshotter.version"
# in versions.yaml
ARG NYDUS_SNAPSHOTTER_VERSION=v0.15.10
ARG NYDUS_SNAPSHOTTER_REPO=https://github.com/containerd/nydus-snapshotter
RUN \
mkdir -p /opt/nydus-snapshotter && \
ARCH="$(uname -m)" && \
if [ "${ARCH}" = "x86_64" ]; then ARCH=amd64 ; fi && \
if [ "${ARCH}" = "aarch64" ]; then ARCH=arm64; fi && \
apk add --no-cache curl && \
curl -fOL --progress-bar "${NYDUS_SNAPSHOTTER_REPO}/releases/download/${NYDUS_SNAPSHOTTER_VERSION}/nydus-snapshotter-${NYDUS_SNAPSHOTTER_VERSION}-linux-${ARCH}.tar.gz" && \
tar xvzpf "nydus-snapshotter-${NYDUS_SNAPSHOTTER_VERSION}-linux-${ARCH}.tar.gz" -C /opt/nydus-snapshotter && \
rm "nydus-snapshotter-${NYDUS_SNAPSHOTTER_VERSION}-linux-${ARCH}.tar.gz"
#### Build binary package
FROM ubuntu:22.04 AS rust-builder
# Default to Rust 1.90.0
ARG RUST_TOOLCHAIN=1.90.0
ENV DEBIAN_FRONTEND=noninteractive
ENV RUSTUP_HOME="/opt/rustup"
ENV CARGO_HOME="/opt/cargo"
ENV PATH="/opt/cargo/bin/:${PATH}"
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN \
mkdir ${RUSTUP_HOME} ${CARGO_HOME} && \
chmod -R a+rwX ${RUSTUP_HOME} ${CARGO_HOME}
RUN \
apt-get update && \
apt-get --no-install-recommends -y install \
ca-certificates \
curl \
gcc \
libc6-dev \
musl-tools && \
apt-get clean && rm -rf /var/lib/apt/lists/ && \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain ${RUST_TOOLCHAIN}
WORKDIR /kata-deploy
# Copy standalone binary project
COPY binary /kata-deploy
# Install target and run tests based on architecture
# - AMD64/arm64: use musl for fully static binaries
# - PPC64le/s390x: use glibc (musl has issues on these platforms)
RUN \
HOST_ARCH="$(uname -m)"; \
rust_arch=""; \
rust_target=""; \
case "${HOST_ARCH}" in \
"x86_64") \
rust_arch="x86_64"; \
rust_target="${rust_arch}-unknown-linux-musl"; \
echo "Installing musl target for ${rust_target}"; \
rustup target add "${rust_target}"; \
;; \
"aarch64") \
rust_arch="aarch64"; \
rust_target="${rust_arch}-unknown-linux-musl"; \
echo "Installing musl target for ${rust_target}"; \
rustup target add "${rust_target}"; \
;; \
"ppc64le") \
rust_arch="powerpc64le"; \
rust_target="${rust_arch}-unknown-linux-gnu"; \
echo "Using glibc target for ${rust_target} (musl is not well supported on ppc64le)"; \
;; \
"s390x") \
rust_arch="s390x"; \
rust_target="${rust_arch}-unknown-linux-gnu"; \
echo "Using glibc target for ${rust_target} (musl is not well supported on s390x)"; \
;; \
*) echo "Unsupported architecture: ${HOST_ARCH}" && exit 1 ;; \
esac; \
echo "${rust_target}" > /tmp/rust_target
# Run tests using --test-threads=1 to prevent environment variable pollution between tests,
# and this is fine as we'll never ever have multiple binaries running at the same time.
RUN \
rust_target="$(cat /tmp/rust_target)"; \
echo "Running binary tests with target ${rust_target}..." && \
RUSTFLAGS="-D warnings" cargo test --target "${rust_target}" -- --test-threads=1 && \
echo "All tests passed!"
RUN \
rust_target="$(cat /tmp/rust_target)"; \
echo "Building kata-deploy binary for ${rust_target}..." && \
RUSTFLAGS="-D warnings" cargo build --release --target "${rust_target}" && \
mkdir -p /kata-deploy/bin && \
cp "/kata-deploy/target/${rust_target}/release/kata-deploy" /kata-deploy/bin/kata-deploy && \
echo "Cleaning up build artifacts to save disk space..." && \
rm -rf /kata-deploy/target && \
cargo clean
#### Extract kata artifacts
FROM alpine:3.22 AS artifact-extractor
ARG KATA_ARTIFACTS=kata-static.tar.zst
ARG DESTINATION=/opt/kata-artifacts
COPY ${KATA_ARTIFACTS} /tmp/
RUN \
apk add --no-cache tar zstd util-linux-misc && \
mkdir -p "${DESTINATION}" && \
tar --zstd -xf "/tmp/$(basename "${KATA_ARTIFACTS}")" -C "${DESTINATION}" && \
rm -f "/tmp/$(basename "${KATA_ARTIFACTS}")"
#### Prepare runtime dependencies (nsenter and required libraries)
# This stage assembles all runtime dependencies based on architecture
# using ldd to find exact library dependencies
FROM debian:bookworm-slim AS runtime-assembler
ARG DESTINATION=/opt/kata-artifacts
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN \
apt-get update && \
apt-get --no-install-recommends -y install \
util-linux && \
apt-get clean && rm -rf /var/lib/apt/lists/
# Copy the built binary to analyze its dependencies
COPY --from=rust-builder /kata-deploy/bin/kata-deploy /tmp/kata-deploy
# Create output directories
RUN mkdir -p /output/lib /output/lib64 /output/usr/bin
# Use ldd to find and copy all required libraries for the kata-deploy binary and nsenter
RUN \
HOST_ARCH="$(uname -m)"; \
echo "Preparing runtime dependencies for ${HOST_ARCH}"; \
case "${HOST_ARCH}" in \
"ppc64le"|"s390x") \
echo "Using glibc - copying libraries based on ldd output"; \
\
# Copy nsenter \
cp /usr/bin/nsenter /output/usr/bin/nsenter; \
\
# Show what the binaries need \
echo "Libraries needed by kata-deploy:"; \
ldd /tmp/kata-deploy || echo "ldd failed"; \
echo "Libraries needed by nsenter:"; \
ldd /usr/bin/nsenter || echo "ldd failed"; \
\
# Extract and copy all library paths from both binaries \
for binary in /tmp/kata-deploy /usr/bin/nsenter; do \
echo "Processing ${binary}..."; \
# Get libraries with "=>" (shared libs) \
ldd "${binary}" 2>/dev/null | grep "=>" | awk '{print $3}' | sort -u | while read -r lib; do \
if [ -n "${lib}" ] && [ -f "${lib}" ]; then \
dest_dir="/output$(dirname "${lib}")"; \
mkdir -p "${dest_dir}"; \
cp -Ln "${lib}" "${dest_dir}/" 2>/dev/null || true; \
echo " Copied lib: ${lib}"; \
fi; \
done; \
done; \
\
# Copy the dynamic linker - it's at /lib/ld64.so.1 (not /lib64/) \
echo "Copying dynamic linker:"; \
mkdir -p /output/lib; \
cp -Ln /lib/ld64.so* /output/lib/ 2>/dev/null || true; \
cp -Ln /lib64/ld64.so* /output/lib64/ 2>/dev/null || true; \
\
echo "glibc" > /output/.libc-type; \
;; \
*) \
echo "amd64/arm64: will use musl-based static binaries"; \
echo "musl" > /output/.libc-type; \
# Create placeholder so COPY doesn't fail \
touch /output/lib/.placeholder; \
touch /output/lib64/.placeholder; \
touch /output/usr/bin/.placeholder; \
;; \
esac
# Copy musl nsenter from alpine for amd64/arm64
COPY --from=artifact-extractor /usr/bin/nsenter /output/usr/bin/nsenter-musl
COPY --from=artifact-extractor /lib/ld-musl-*.so.1 /output/lib/
# For amd64/arm64, use the musl nsenter; for ppc64le/s390x, keep the glibc one
RUN \
HOST_ARCH="$(uname -m)"; \
case "${HOST_ARCH}" in \
"x86_64"|"aarch64") \
mv /output/usr/bin/nsenter-musl /output/usr/bin/nsenter; \
;; \
*) \
rm -f /output/usr/bin/nsenter-musl; \
;; \
esac
#### kata-deploy main image
FROM gcr.io/distroless/static-debian12@sha256:87bce11be0af225e4ca761c40babb06d6d559f5767fbf7dc3c47f0f1a466b92c
ARG DESTINATION=/opt/kata-artifacts
# Copy extracted kata artifacts
COPY --from=artifact-extractor ${DESTINATION} ${DESTINATION}
# Copy Rust binary
COPY --from=rust-builder /kata-deploy/bin/kata-deploy /usr/bin/kata-deploy
# Copy nsenter and required libraries (assembled based on architecture)
COPY --from=runtime-assembler /output/usr/bin/nsenter /usr/bin/nsenter
COPY --from=runtime-assembler /output/lib/ /lib/
COPY --from=runtime-assembler /output/lib64/ /lib64/
# Copy nydus snapshotter
COPY nydus-snapshotter ${DESTINATION}/nydus-snapshotter
COPY --from=nydus-binary-downloader /opt/nydus-snapshotter/bin/containerd-nydus-grpc ${DESTINATION}/nydus-snapshotter/
COPY --from=nydus-binary-downloader /opt/nydus-snapshotter/bin/nydus-overlayfs ${DESTINATION}/nydus-snapshotter/
# Copy runtimeclasses and node-feature-rules
COPY node-feature-rules ${DESTINATION}/node-feature-rules
ENTRYPOINT ["/usr/bin/kata-deploy"]

View File

@@ -36,7 +36,9 @@ const ALL_SHIMS: &[&str] = &[
"qemu-se",
"qemu-se-runtime-rs",
"qemu-snp",
"qemu-snp-runtime-rs",
"qemu-tdx",
"qemu-tdx-runtime-rs",
];
/// Check if a shim is a QEMU-based shim (all QEMU shims start with "qemu")
@@ -824,6 +826,8 @@ VERSION_ID="24.04"
"qemu"
);
assert_eq!(get_hypervisor_name("qemu-se-runtime-rs").unwrap(), "qemu");
assert_eq!(get_hypervisor_name("qemu-snp-runtime-rs").unwrap(), "qemu");
assert_eq!(get_hypervisor_name("qemu-tdx-runtime-rs").unwrap(), "qemu");
}
#[test]

View File

@@ -85,7 +85,11 @@ pub async fn configure_snapshotter(
runtime: &str,
config: &Config,
) -> Result<()> {
let pluginid = if fs::read_to_string(&config.containerd_conf_file)
// Get all paths and drop-in capability in one call
let paths = config.get_containerd_paths(runtime).await?;
// Read containerd version from config_file to determine pluginid
let pluginid = if fs::read_to_string(&paths.config_file)
.unwrap_or_default()
.contains("version = 3")
{
@@ -94,28 +98,21 @@ pub async fn configure_snapshotter(
"\"io.containerd.grpc.v1.cri\".containerd"
};
let use_drop_in =
crate::runtime::is_containerd_capable_of_using_drop_in_files(config, runtime).await?;
let configuration_file: std::path::PathBuf = if use_drop_in {
// Ensure we have the absolute path with /host prefix
let base_path = if config.containerd_drop_in_conf_file.starts_with("/host") {
// Already has /host prefix
Path::new(&config.containerd_drop_in_conf_file).to_path_buf()
let configuration_file: std::path::PathBuf = if paths.use_drop_in {
// Only add /host prefix if path is not in /etc/containerd (which is mounted from host)
let base_path = if paths.drop_in_file.starts_with("/etc/containerd/") {
Path::new(&paths.drop_in_file).to_path_buf()
} else {
// Need to add /host prefix
let drop_in_path = config.containerd_drop_in_conf_file.trim_start_matches('/');
// Need to add /host prefix for paths outside /etc/containerd
let drop_in_path = paths.drop_in_file.trim_start_matches('/');
Path::new("/host").join(drop_in_path)
};
log::debug!("Snapshotter using drop-in config file: {:?}", base_path);
base_path
} else {
log::debug!(
"Snapshotter using main config file: {}",
config.containerd_conf_file
);
Path::new(&config.containerd_conf_file).to_path_buf()
log::debug!("Snapshotter using main config file: {}", paths.config_file);
Path::new(&paths.config_file).to_path_buf()
};
match snapshotter {

View File

@@ -7,6 +7,22 @@ use anyhow::{Context, Result};
use log::info;
use std::env;
/// Containerd configuration paths and capabilities for a specific runtime
#[derive(Debug, Clone)]
pub struct ContainerdPaths {
/// File to read containerd version from and write to (non-drop-in mode)
pub config_file: String,
/// Backup file path before modification
pub backup_file: String,
/// File to add/remove drop-in imports from (drop-in mode)
/// None if imports are not needed (e.g., k0s auto-loads from containerd.d/)
pub imports_file: Option<String>,
/// Path to the drop-in configuration file
pub drop_in_file: String,
/// Whether drop-in files can be used (based on containerd version)
pub use_drop_in: bool,
}
#[derive(Debug, Clone)]
pub struct Config {
pub node_name: String,
@@ -359,6 +375,50 @@ impl Config {
self.experimental_force_guest_pull_for_arch.join(",")
);
}
/// Get containerd configuration file paths based on runtime type and containerd version
pub async fn get_containerd_paths(&self, runtime: &str) -> Result<ContainerdPaths> {
use crate::runtime::manager;
// Check if drop-in files can be used based on containerd version
let use_drop_in = manager::is_containerd_capable_of_using_drop_in_files(self, runtime).await?;
let paths = match runtime {
"k0s-worker" | "k0s-controller" => ContainerdPaths {
config_file: "/etc/containerd/containerd.toml".to_string(),
backup_file: "/etc/containerd/containerd.toml.bak".to_string(), // Never used, but needed for consistency
imports_file: None, // k0s auto-loads from containerd.d/, imports not needed
drop_in_file: "/etc/containerd/containerd.d/kata-deploy.toml".to_string(),
use_drop_in,
},
"microk8s" => ContainerdPaths {
// microk8s uses containerd-template.toml instead of config.toml
config_file: "/etc/containerd/containerd-template.toml".to_string(),
backup_file: "/etc/containerd/containerd-template.toml.bak".to_string(),
imports_file: Some("/etc/containerd/containerd-template.toml".to_string()),
drop_in_file: self.containerd_drop_in_conf_file.clone(),
use_drop_in,
},
"k3s" | "k3s-agent" | "rke2-agent" | "rke2-server" => ContainerdPaths {
// k3s/rke2 generates config.toml from config.toml.tmpl on each restart
// We must modify the template file so our changes persist
config_file: "/etc/containerd/config.toml.tmpl".to_string(),
backup_file: "/etc/containerd/config.toml.tmpl.bak".to_string(),
imports_file: Some("/etc/containerd/config.toml.tmpl".to_string()),
drop_in_file: self.containerd_drop_in_conf_file.clone(),
use_drop_in,
},
_ => ContainerdPaths {
config_file: self.containerd_conf_file.clone(),
backup_file: self.containerd_conf_file_backup.clone(),
imports_file: Some(self.containerd_conf_file.clone()),
drop_in_file: self.containerd_drop_in_conf_file.clone(),
use_drop_in,
},
};
Ok(paths)
}
}
fn get_arch() -> Result<String> {
@@ -379,7 +439,7 @@ fn get_arch() -> Result<String> {
/// Returns only shims that are supported for that architecture
fn get_default_shims_for_arch(arch: &str) -> &'static str {
match arch {
"x86_64" => "clh cloud-hypervisor dragonball fc qemu qemu-coco-dev qemu-coco-dev-runtime-rs qemu-runtime-rs qemu-nvidia-gpu qemu-nvidia-gpu-snp qemu-nvidia-gpu-tdx qemu-snp qemu-tdx",
"x86_64" => "clh cloud-hypervisor dragonball fc qemu qemu-coco-dev qemu-coco-dev-runtime-rs qemu-runtime-rs qemu-nvidia-gpu qemu-nvidia-gpu-snp qemu-nvidia-gpu-tdx qemu-snp qemu-snp-runtime-rs qemu-tdx qemu-tdx-runtime-rs",
"aarch64" => "clh cloud-hypervisor dragonball fc qemu qemu-nvidia-gpu qemu-cca",
"s390x" => "qemu qemu-runtime-rs qemu-se qemu-se-runtime-rs qemu-coco-dev qemu-coco-dev-runtime-rs",
"ppc64le" => "qemu",

View File

@@ -459,13 +459,48 @@ impl K8sClient {
}
}
/// Split a JSONPath string by dots, but respect escaped dots (\.)
/// Example: "metadata.labels.microk8s\.io/cluster" -> ["metadata", "labels", "microk8s.io/cluster"]
fn split_jsonpath(path: &str) -> Vec<String> {
let mut parts = Vec::new();
let mut current = String::new();
let mut chars = path.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
// Check if next char is a dot (escaped dot)
if chars.peek() == Some(&'.') {
current.push(chars.next().unwrap()); // Add the dot literally
} else {
current.push(c); // Keep the backslash
}
} else if c == '.' {
if !current.is_empty() {
parts.push(current);
current = String::new();
}
} else {
current.push(c);
}
}
if !current.is_empty() {
parts.push(current);
}
parts
}
/// Get value from JSON using JSONPath-like syntax (simplified)
fn get_jsonpath_value(obj: &serde_json::Value, jsonpath: &str) -> Result<String> {
// Simple JSONPath implementation for common cases
// Supports: .field, .field.subfield, [index]
// Supports: .field, .field.subfield, [index], escaped dots (\.)
let mut current = serde_json::to_value(obj)?;
for part in jsonpath.trim_start_matches('.').split('.') {
// Split by unescaped dots only
let parts = split_jsonpath(jsonpath.trim_start_matches('.'));
for part in parts {
if part.is_empty() {
continue;
}
@@ -489,7 +524,7 @@ fn get_jsonpath_value(obj: &serde_json::Value, jsonpath: &str) -> Result<String>
.clone();
} else {
current = current
.get(part)
.get(&part)
.ok_or_else(|| anyhow::anyhow!("Field '{part}' not found"))?
.clone();
}
@@ -580,6 +615,25 @@ pub async fn update_runtimeclass(
mod tests {
use super::*;
#[test]
fn test_split_jsonpath_simple() {
let parts = split_jsonpath("metadata.labels.foo");
assert_eq!(parts, vec!["metadata", "labels", "foo"]);
}
#[test]
fn test_split_jsonpath_escaped_dot() {
// microk8s\.io/cluster should become a single key: microk8s.io/cluster
let parts = split_jsonpath(r"metadata.labels.microk8s\.io/cluster");
assert_eq!(parts, vec!["metadata", "labels", "microk8s.io/cluster"]);
}
#[test]
fn test_split_jsonpath_multiple_escaped_dots() {
let parts = split_jsonpath(r"a\.b\.c.d");
assert_eq!(parts, vec!["a.b.c", "d"]);
}
#[test]
fn test_get_jsonpath_value() {
let json = json!({
@@ -593,4 +647,18 @@ mod tests {
let result = get_jsonpath_value(&json, ".status.nodeInfo.containerRuntimeVersion").unwrap();
assert_eq!(result, "containerd://1.7.0");
}
#[test]
fn test_get_jsonpath_value_with_escaped_dot() {
let json = json!({
"metadata": {
"labels": {
"microk8s.io/cluster": "true"
}
}
});
let result = get_jsonpath_value(&json, r".metadata.labels.microk8s\.io/cluster").unwrap();
assert_eq!(result, "true");
}
}

View File

@@ -25,34 +25,29 @@ pub async fn configure_containerd_runtime(
let runtime_name = format!("kata-{adjusted_shim}");
let configuration = format!("configuration-{shim}");
log::info!("configure_containerd_runtime: Checking drop-in support");
let use_drop_in =
super::manager::is_containerd_capable_of_using_drop_in_files(config, runtime).await?;
log::info!("configure_containerd_runtime: use_drop_in={}", use_drop_in);
log::info!("configure_containerd_runtime: Getting containerd paths");
let paths = config.get_containerd_paths(runtime).await?;
log::info!("configure_containerd_runtime: use_drop_in={}", paths.use_drop_in);
let configuration_file: std::path::PathBuf = if use_drop_in {
// Ensure we have the absolute path with /host prefix
let base_path = if config.containerd_drop_in_conf_file.starts_with("/host") {
// Already has /host prefix
Path::new(&config.containerd_drop_in_conf_file).to_path_buf()
let configuration_file: std::path::PathBuf = if paths.use_drop_in {
// Only add /host prefix if path is not in /etc/containerd (which is mounted from host)
let base_path = if paths.drop_in_file.starts_with("/etc/containerd/") {
Path::new(&paths.drop_in_file).to_path_buf()
} else {
// Need to add /host prefix
let drop_in_path = config.containerd_drop_in_conf_file.trim_start_matches('/');
// Need to add /host prefix for paths outside /etc/containerd
let drop_in_path = paths.drop_in_file.trim_start_matches('/');
Path::new("/host").join(drop_in_path)
};
log::debug!("Using drop-in config file: {:?}", base_path);
base_path
} else {
log::debug!("Using main config file: {}", config.containerd_conf_file);
Path::new(&config.containerd_conf_file).to_path_buf()
log::debug!("Using main config file: {}", paths.config_file);
Path::new(&paths.config_file).to_path_buf()
};
let containerd_root_conf_file = if matches!(runtime, "k0s-worker" | "k0s-controller") {
"/etc/containerd/containerd.toml"
} else {
&config.containerd_conf_file
};
// Use config_file to read containerd version from
let containerd_root_conf_file = &paths.config_file;
let pluginid = if fs::read_to_string(containerd_root_conf_file)
.unwrap_or_default()
@@ -165,21 +160,22 @@ pub async fn configure_containerd(config: &Config, runtime: &str) -> Result<()>
fs::create_dir_all("/etc/containerd/")?;
let use_drop_in =
super::manager::is_containerd_capable_of_using_drop_in_files(config, runtime).await?;
// Get all paths and drop-in capability in one call
let paths = config.get_containerd_paths(runtime).await?;
if !use_drop_in {
if Path::new(&config.containerd_conf_file).exists()
&& !Path::new(&config.containerd_conf_file_backup).exists()
{
fs::copy(
&config.containerd_conf_file,
&config.containerd_conf_file_backup,
)?;
if !paths.use_drop_in {
// For non-drop-in, backup the correct config file for each runtime
if Path::new(&paths.config_file).exists() && !Path::new(&paths.backup_file).exists() {
fs::copy(&paths.config_file, &paths.backup_file)?;
}
} else {
// Create the drop-in file directory and file
let drop_in_file = format!("/host{}", config.containerd_drop_in_conf_file);
// Only add /host prefix if path is not in /etc/containerd (which is mounted from host)
let drop_in_file = if paths.drop_in_file.starts_with("/etc/containerd/") {
paths.drop_in_file.clone()
} else {
format!("/host{}", paths.drop_in_file)
};
log::info!("Creating drop-in file at: {}", drop_in_file);
if let Some(parent) = Path::new(&drop_in_file).parent() {
@@ -200,20 +196,20 @@ pub async fn configure_containerd(config: &Config, runtime: &str) -> Result<()>
}
// Add the drop-in file to the imports array in the main config
// The append_to_toml_array function is idempotent and will not add duplicates
log::info!(
"Adding drop-in to imports in: {}",
config.containerd_conf_file
);
let imports_path = ".imports";
let drop_in_path = format!("\"{}\"", config.containerd_drop_in_conf_file);
if let Some(imports_file) = &paths.imports_file {
log::info!("Adding drop-in to imports in: {}", imports_file);
let imports_path = ".imports";
let drop_in_path = format!("\"{}\"", paths.drop_in_file);
toml_utils::append_to_toml_array(
Path::new(&config.containerd_conf_file),
imports_path,
&drop_in_path,
)?;
log::info!("Successfully added drop-in to imports array");
toml_utils::append_to_toml_array(
Path::new(imports_file),
imports_path,
&drop_in_path,
)?;
log::info!("Successfully added drop-in to imports array");
} else {
log::info!("Runtime auto-loads drop-in files, skipping imports");
}
}
log::info!("Configuring {} shim(s)", config.shims_for_arch.len());
@@ -228,27 +224,27 @@ pub async fn configure_containerd(config: &Config, runtime: &str) -> Result<()>
}
pub async fn cleanup_containerd(config: &Config, runtime: &str) -> Result<()> {
let use_drop_in =
super::manager::is_containerd_capable_of_using_drop_in_files(config, runtime).await?;
// Get all paths and drop-in capability in one call
let paths = config.get_containerd_paths(runtime).await?;
if use_drop_in {
let drop_in_path = config.containerd_drop_in_conf_file.clone();
toml_utils::remove_from_toml_array(
Path::new(&config.containerd_conf_file),
".imports",
&format!("\"{drop_in_path}\""),
)?;
if paths.use_drop_in {
// Remove drop-in from imports array (if imports are used)
if let Some(imports_file) = &paths.imports_file {
toml_utils::remove_from_toml_array(
Path::new(imports_file),
".imports",
&format!("\"{}\"", paths.drop_in_file),
)?;
}
return Ok(());
}
if Path::new(&config.containerd_conf_file_backup).exists() {
fs::remove_file(&config.containerd_conf_file)?;
fs::rename(
&config.containerd_conf_file_backup,
&config.containerd_conf_file,
)?;
// For non-drop-in, restore from backup
if Path::new(&paths.backup_file).exists() {
fs::remove_file(&paths.config_file)?;
fs::rename(&paths.backup_file, &paths.config_file)?;
} else {
fs::remove_file(&config.containerd_conf_file).ok();
fs::remove_file(&paths.config_file).ok();
}
Ok(())
@@ -264,11 +260,13 @@ pub fn setup_containerd_config_files(runtime: &str, config: &Config) -> Result<(
}
}
"k0s-worker" | "k0s-controller" => {
let drop_in_file = format!("/host{}", config.containerd_drop_in_conf_file);
if let Some(parent) = Path::new(&drop_in_file).parent() {
// k0s uses /etc/containerd/containerd.d/ for drop-ins (no /host prefix needed)
// Path is fixed for k0s, so we can hardcode it here
let drop_in_file_path = "/etc/containerd/containerd.d/kata-deploy.toml";
if let Some(parent) = Path::new(drop_in_file_path).parent() {
fs::create_dir_all(parent)?;
}
fs::File::create(&drop_in_file)?;
fs::File::create(drop_in_file_path)?;
}
"containerd" => {
if !Path::new(&config.containerd_conf_file).exists() {

View File

@@ -26,12 +26,7 @@ const CONTAINERD_BASED_RUNTIMES: &[&str] = &[
];
/// Runtimes that don't support containerd drop-in configuration files
const RUNTIMES_WITHOUT_CONTAINERD_DROP_IN_SUPPORT: &[&str] = &[
"crio",
"k0s-worker",
"k0s-controller",
"microk8s",
];
const RUNTIMES_WITHOUT_CONTAINERD_DROP_IN_SUPPORT: &[&str] = &["crio"];
fn is_containerd_based(runtime: &str) -> bool {
CONTAINERD_BASED_RUNTIMES.contains(&runtime)
@@ -114,6 +109,11 @@ pub async fn is_containerd_capable_of_using_drop_in_files(
return Ok(false);
}
// k0s always supports drop-in files (auto-loads from containerd.d/)
if runtime == "k0s-worker" || runtime == "k0s-controller" {
return Ok(true);
}
// Check containerd version - only 2.0+ supports drop-in files properly
let runtime_version =
k8s::get_node_field(config, ".status.nodeInfo.containerRuntimeVersion").await?;

View File

@@ -12,6 +12,8 @@ pub const RUST_SHIMS: &[&str] = &[
"qemu-runtime-rs",
"qemu-coco-dev-runtime-rs",
"qemu-se-runtime-rs",
"qemu-snp-runtime-rs",
"qemu-tdx-runtime-rs",
];
pub fn is_rust_shim(shim: &str) -> bool {

View File

@@ -158,7 +158,7 @@ All values can be overridden with --set key=value or a custom `-f myvalues.yaml`
| `env.installationPrefix` | Prefix where to install the Kata artifacts | `/opt/kata` |
| `env.hostOS` | Provide host-OS setting, e.g. `cbl-mariner` to do additional configurations | `""` |
| `env.multiInstallSuffix` | Enable multiple Kata installation on the same node with suffix e.g. `/opt/kata-PR12232` | `""` |
| `env._experimentalSetupSnapshotter` | Deploys (nydus) and/or sets up (erofs, nydus) the snapshotter(s) specified as the value (supports multiple snapshotters, separated by commas; e.g., `nydus,erofs`) | `""` |
| `env._experimentalSetupSnapshotter` | Deploys (`nydus`) and/or sets up (`erofs`, `nydus`) the snapshotter(s) specified as the value (supports multiple snapshotters, separated by commas; e.g., `nydus,erofs`) | `""` |
| `env._experimentalForceGuestPull` | Enables `experimental_force_guest_pull` for the shim(s) specified as the value (supports multiple shims, separated by commas; e.g., `qemu-tdx,qemu-snp`) | `""` |
| `env._experimentalForceGuestPull_x86_64` | Enables `experimental_force_guest_pull` for the shim(s) specified as the value for x86_64 (if set, overrides `_experimentalForceGuestPull`) | `""` |
| `env._experimentalForceGuestPull_aarch64` | Enables `experimental_force_guest_pull` for the shim(s) specified as the value for aarch64 (if set, overrides `_experimentalForceGuestPull`) | `""` |
@@ -267,7 +267,7 @@ helm install kata-deploy oci://ghcr.io/kata-containers/kata-deploy-charts/kata-d
This includes all available Kata Containers shims:
- Standard shims: `qemu`, `qemu-runtime-rs`, `clh`, `cloud-hypervisor`, `dragonball`, `fc`
- TEE shims: `qemu-snp`, `qemu-tdx`, `qemu-se`, `qemu-se-runtime-rs`, `qemu-cca`, `qemu-coco-dev`, `qemu-coco-dev-runtime-rs`
- TEE shims: `qemu-snp`, `qemu-snp-runtime-rs`, `qemu-tdx`, `qemu-tdx-runtime-rs`, `qemu-se`, `qemu-se-runtime-rs`, `qemu-cca`, `qemu-coco-dev`, `qemu-coco-dev-runtime-rs`
- NVIDIA GPU shims: `qemu-nvidia-gpu`, `qemu-nvidia-gpu-snp`, `qemu-nvidia-gpu-tdx`
- Remote shims: `remote` (for `peer-pods`/`cloud-api-adaptor`, disabled by default)
@@ -401,8 +401,12 @@ shims:
enabled: false
qemu-snp:
enabled: false
qemu-snp-runtime-rs:
enabled: false
qemu-tdx:
enabled: false
qemu-tdx-runtime-rs:
enabled: false
qemu-se:
enabled: false
qemu-se-runtime-rs:

View File

@@ -290,18 +290,3 @@ Note: EXPERIMENTAL_FORCE_GUEST_PULL only checks containerd.forceGuestPull, not c
{{- end -}}
{{- join "," $shimNames -}}
{{- end -}}
{{/*
Detect if this is a Rust-based build by checking the image tag
Returns "true" if the tag contains "-rust", otherwise returns "false"
This is a temporary helper for dual script/rust support
*/}}
{{- define "kata-deploy.isRustBuild" -}}
{{- $tag := default .Chart.AppVersion .Values.image.tag -}}
{{- if or (contains "-rust" $tag) (contains "nightly-rust" $tag) -}}
true
{{- else -}}
false
{{- end -}}
{{- end -}}

View File

@@ -133,11 +133,7 @@ spec:
- name: kube-kata
image: {{ .Values.image.reference }}:{{ default .Chart.AppVersion .Values.image.tag }}
imagePullPolicy: {{ .Values.imagePullPolicy }}
{{- if eq (include "kata-deploy.isRustBuild" .) "true" }}
command: ["/usr/bin/kata-deploy", "install"]
{{- else }}
command: ["/opt/kata-artifacts/scripts/kata-deploy.sh", "install"]
{{- end }}
env:
- name: NODE_NAME
valueFrom:

View File

@@ -104,11 +104,7 @@ spec:
- name: kube-kata-cleanup
image: {{ .Values.image.reference }}:{{ default .Chart.AppVersion .Values.image.tag }}
imagePullPolicy: {{ .Values.imagePullPolicy }}
{{- if eq (include "kata-deploy.isRustBuild" .) "true" }}
command: ["/usr/bin/kata-deploy", "cleanup"]
{{- else }}
command: ["/opt/kata-artifacts/scripts/kata-deploy.sh", "cleanup"]
{{- end }}
env:
- name: NODE_NAME
valueFrom:

View File

@@ -24,7 +24,9 @@
"qemu-se-runtime-rs" (dict "memory" "1024Mi" "cpu" "1.0")
"qemu-se" (dict "memory" "1024Mi" "cpu" "1.0")
"qemu-snp" (dict "memory" "2048Mi" "cpu" "1.0")
"qemu-snp-runtime-rs" (dict "memory" "2048Mi" "cpu" "1.0")
"qemu-tdx" (dict "memory" "2048Mi" "cpu" "1.0")
"qemu-tdx-runtime-rs" (dict "memory" "2048Mi" "cpu" "1.0")
"qemu-nvidia-gpu" (dict "memory" "4096Mi" "cpu" "1.0")
"qemu-nvidia-gpu-snp" (dict "memory" "20480Mi" "cpu" "1.0")
"qemu-nvidia-gpu-tdx" (dict "memory" "20480Mi" "cpu" "1.0")

View File

@@ -13,10 +13,6 @@ metadata:
namespace: {{ .Release.Namespace }}
labels:
{{- include "kata-deploy.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
data:
pod-spec.yaml: |
{{- .Values.verification.pod | nindent 4 }}
@@ -32,9 +28,9 @@ metadata:
annotations:
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "0"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: 3
backoffLimit: 0
ttlSecondsAfterFinished: 3600
template:
metadata:
@@ -61,19 +57,132 @@ spec:
echo "Timeout: ${TIMEOUT}s"
echo ""
# Wait for kata-deploy DaemonSet to be ready
echo "Waiting for kata-deploy DaemonSet to be ready..."
# First, wait for kata-deploy DaemonSet to exist (it's created by Helm, not a hook)
echo "Waiting for kata-deploy DaemonSet to be created..."
{{- if .Values.env.multiInstallSuffix }}
kubectl rollout status daemonset/{{ .Chart.Name }}-{{ .Values.env.multiInstallSuffix }} -n {{ .Release.Namespace }} --timeout=600s
DAEMONSET_NAME="{{ .Chart.Name }}-{{ .Values.env.multiInstallSuffix }}"
{{- else }}
kubectl rollout status daemonset/{{ .Chart.Name }} -n {{ .Release.Namespace }} --timeout=600s
DAEMONSET_NAME="{{ .Chart.Name }}"
{{- end }}
max_wait=120
elapsed=0
while true; do
if kubectl get daemonset "${DAEMONSET_NAME}" -n {{ .Release.Namespace }} &>/dev/null; then
echo "DaemonSet ${DAEMONSET_NAME} exists"
break
fi
if [[ ${elapsed} -ge ${max_wait} ]]; then
echo "ERROR: Timeout waiting for DaemonSet to be created after ${max_wait}s"
kubectl get daemonset -n {{ .Release.Namespace }} || true
exit 1
fi
echo "Waiting for DaemonSet to be created... (${elapsed}s/${max_wait}s)"
sleep 2
elapsed=$((elapsed + 2))
done
# Now wait for kata-deploy DaemonSet pods to exist
echo ""
echo "Creating verification pod..."
POD_RESOURCE=$(kubectl apply -n "${VERIFY_NS}" -f /config/pod-spec.yaml -o name)
POD_NAME="${POD_RESOURCE#pod/}"
echo "Created: ${POD_NAME}"
echo "Waiting for kata-deploy DaemonSet pods to be scheduled..."
max_wait=120
elapsed=0
while true; do
pod_count=$(kubectl get pods -n {{ .Release.Namespace }} -l name=kata-deploy --no-headers 2>/dev/null | wc -l)
if [[ ${pod_count} -gt 0 ]]; then
echo "Found ${pod_count} kata-deploy pod(s)"
break
fi
if [[ ${elapsed} -ge ${max_wait} ]]; then
echo "ERROR: Timeout waiting for kata-deploy pods after ${max_wait}s"
kubectl get pods -n {{ .Release.Namespace }} -l name=kata-deploy || true
exit 1
fi
echo "Waiting for pods to be scheduled... (${elapsed}s/${max_wait}s)"
sleep 2
elapsed=$((elapsed + 2))
done
# Wait for kata-deploy DaemonSet to be ready (all pods running)
# This includes waiting for the kata-deploy image to be pulled, which can take 20+ minutes
echo ""
echo "Waiting for kata-deploy DaemonSet to be ready..."
DAEMONSET_TIMEOUT="{{ .Values.verification.daemonsetTimeout }}"
echo "DaemonSet timeout: ${DAEMONSET_TIMEOUT}s"
kubectl rollout status daemonset/"${DAEMONSET_NAME}" -n {{ .Release.Namespace }} --timeout="${DAEMONSET_TIMEOUT}s"
# Wait for nodes to be labeled with katacontainers.io/kata-runtime=true
# This label is set by kata-deploy when installation is complete
# This is a quick internal operation (copying artifacts), so use a fixed timeout
echo ""
echo "Waiting for nodes to be labeled with kata-runtime..."
max_wait=60
echo "Node label timeout: ${max_wait}s"
elapsed=0
while true; do
labeled_nodes=$(kubectl get nodes -l katacontainers.io/kata-runtime=true --no-headers 2>/dev/null | wc -l)
total_nodes=$(kubectl get nodes --no-headers 2>/dev/null | wc -l)
if [[ ${labeled_nodes} -gt 0 ]] && [[ ${labeled_nodes} -eq ${total_nodes} ]]; then
echo "All ${total_nodes} node(s) labeled with kata-runtime=true"
kubectl get nodes -L katacontainers.io/kata-runtime || true
break
fi
if [[ ${elapsed} -ge ${max_wait} ]]; then
echo "ERROR: Timeout waiting for nodes to be labeled after ${max_wait}s"
echo "Labeled nodes: ${labeled_nodes}/${total_nodes}"
echo "Node labels:"
kubectl get nodes -L katacontainers.io/kata-runtime || true
exit 1
fi
echo "Labeled nodes: ${labeled_nodes}/${total_nodes} (${elapsed}s/${max_wait}s)"
sleep 5
elapsed=$((elapsed + 5))
done
# Give kubelet time to pick up the new runtime configuration after containerd restart
echo ""
echo "Waiting 15s for kubelet to refresh runtime information..."
sleep 15
# Retry pod creation if it fails due to runtime not being ready yet
POD_NAME="kata-deploy-verify"
MAX_POD_RETRIES=3
POD_RETRY_DELAY=10
for pod_attempt in $(seq 1 ${MAX_POD_RETRIES}); do
echo ""
echo "Creating verification pod (attempt ${pod_attempt}/${MAX_POD_RETRIES})..."
# Clean up any existing pod first
kubectl delete pod "${POD_NAME}" -n "${VERIFY_NS}" --ignore-not-found --wait=true 2>/dev/null || true
kubectl apply -n "${VERIFY_NS}" -f /config/pod-spec.yaml
echo "Created: ${POD_NAME}"
# Wait a moment for pod to be scheduled and initial status
sleep 5
# Check for immediate sandbox creation failure
sandbox_error=$(kubectl get events -n "${VERIFY_NS}" --field-selector involvedObject.name="${POD_NAME}",type=Warning 2>/dev/null | grep -c "FailedCreatePodSandBox" || echo 0)
if [[ ${sandbox_error} -gt 0 ]] && [[ ${pod_attempt} -lt ${MAX_POD_RETRIES} ]]; then
echo "Pod sandbox creation failed, runtime may not be ready yet"
echo "Waiting ${POD_RETRY_DELAY}s before retry..."
kubectl delete pod "${POD_NAME}" -n "${VERIFY_NS}" --ignore-not-found --wait=true 2>/dev/null || true
sleep ${POD_RETRY_DELAY}
continue
fi
# Pod created successfully or we're on last attempt, proceed to wait for completion
break
done
# Ensure cleanup runs on any exit (success, failure, or signal)
cleanup() {
@@ -85,23 +194,70 @@ spec:
echo ""
echo "Waiting for verification pod to complete..."
if kubectl wait pod "${POD_NAME}" -n "${VERIFY_NS}" --for=jsonpath='{.status.phase}'=Succeeded --timeout="${TIMEOUT}s"; then
echo ""
echo "=== Verification Pod Logs ==="
kubectl logs "${POD_NAME}" -n "${VERIFY_NS}" || true
echo ""
echo "SUCCESS: Verification passed"
exit 0
else
echo ""
echo "=== Verification Failed ==="
echo "Pod status:"
kubectl describe pod "${POD_NAME}" -n "${VERIFY_NS}" || true
echo ""
echo "Pod logs:"
kubectl logs "${POD_NAME}" -n "${VERIFY_NS}" || true
exit 1
fi
# Wait for pod to either succeed or fail, checking every few seconds
start_time=$(date +%s)
while true; do
phase=$(kubectl get pod "${POD_NAME}" -n "${VERIFY_NS}" -o jsonpath='{.status.phase}' 2>/dev/null || echo "Unknown")
if [[ "${phase}" == "Succeeded" ]]; then
echo ""
echo "=== Verification Pod Logs ==="
kubectl logs "${POD_NAME}" -n "${VERIFY_NS}" || true
echo ""
echo "SUCCESS: Verification passed"
exit 0
fi
if [[ "${phase}" == "Failed" ]]; then
echo ""
echo "=== Verification Failed - Pod phase is Failed ==="
echo "Pod status:"
kubectl describe pod "${POD_NAME}" -n "${VERIFY_NS}" || true
echo ""
echo "Pod logs:"
kubectl logs "${POD_NAME}" -n "${VERIFY_NS}" || true
exit 1
fi
# Check if pod is stuck - look for events indicating it can't start
if [[ "${phase}" == "Pending" ]]; then
# Look for FailedCreatePodSandBox or other error events
error_count=$(kubectl get events -n "${VERIFY_NS}" --field-selector involvedObject.name="${POD_NAME}",type=Warning 2>/dev/null | grep -c "FailedCreatePodSandBox\|Failed\|Error" || echo 0)
# Only fail on errors if we've waited at least 30s (give time for transient issues)
current_time=$(date +%s)
elapsed=$((current_time - start_time))
if [[ ${error_count} -gt 0 ]] && [[ ${elapsed} -gt 30 ]]; then
echo ""
echo "=== Verification Failed - Pod stuck with ${error_count} error events ==="
echo "Events:"
kubectl get events -n "${VERIFY_NS}" --field-selector involvedObject.name="${POD_NAME}" || true
echo ""
echo "Pod status:"
kubectl describe pod "${POD_NAME}" -n "${VERIFY_NS}" || true
exit 1
fi
fi
# Check timeout
current_time=$(date +%s)
elapsed=$((current_time - start_time))
if [[ ${elapsed} -ge ${TIMEOUT} ]]; then
echo ""
echo "=== Verification Failed - Timeout after ${TIMEOUT}s ==="
echo "Pod phase: ${phase}"
echo "Pod status:"
kubectl describe pod "${POD_NAME}" -n "${VERIFY_NS}" || true
echo ""
echo "Pod logs:"
kubectl logs "${POD_NAME}" -n "${VERIFY_NS}" 2>/dev/null || echo "No logs available"
exit 1
fi
echo "Pod phase: ${phase}, elapsed: ${elapsed}s/${TIMEOUT}s"
sleep 5
done
volumeMounts:
- name: pod-spec
mountPath: /config
@@ -136,6 +292,12 @@ rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "list"]
- apiGroups: ["apps"]
resources: ["daemonsets"]
verbs: ["get", "list", "watch"]

View File

@@ -33,7 +33,7 @@ shims:
qemu-nvidia-gpu-tdx:
enabled: false
# Now enable TEE shims (qemu-snp, qemu-tdx, qemu-se, qemu-se-runtime-rs, qemu-cca, qemu-coco-dev, qemu-coco-dev-runtime-rs)
# Now enable TEE shims (qemu-snp, qemu-snp-runtime-rs, qemu-tdx, qemu-tdx-runtime-rs, qemu-se, qemu-se-runtime-rs, qemu-cca, qemu-coco-dev, qemu-coco-dev-runtime-rs)
qemu-snp:
enabled: true
supportedArches:
@@ -48,6 +48,20 @@ shims:
httpsProxy: ""
noProxy: ""
qemu-snp-runtime-rs:
enabled: true
supportedArches:
- amd64
allowedHypervisorAnnotations: []
containerd:
snapshotter: nydus
forceGuestPull: false
crio:
guestPull: true
agent:
httpsProxy: ""
noProxy: ""
qemu-tdx:
enabled: true
supportedArches:
@@ -62,6 +76,20 @@ shims:
httpsProxy: ""
noProxy: ""
qemu-tdx-runtime-rs:
enabled: true
supportedArches:
- amd64
allowedHypervisorAnnotations: []
containerd:
snapshotter: nydus
forceGuestPull: false
crio:
guestPull: true
agent:
httpsProxy: ""
noProxy: ""
qemu-se:
enabled: true
supportedArches:

View File

@@ -134,6 +134,20 @@ shims:
httpsProxy: ""
noProxy: ""
qemu-snp-runtime-rs:
enabled: true
supportedArches:
- amd64
allowedHypervisorAnnotations: []
containerd:
snapshotter: nydus
forceGuestPull: false
crio:
guestPull: true
agent:
httpsProxy: ""
noProxy: ""
qemu-tdx:
enabled: true
supportedArches:
@@ -148,6 +162,20 @@ shims:
httpsProxy: ""
noProxy: ""
qemu-tdx-runtime-rs:
enabled: true
supportedArches:
- amd64
allowedHypervisorAnnotations: []
containerd:
snapshotter: nydus
forceGuestPull: false
crio:
guestPull: true
agent:
httpsProxy: ""
noProxy: ""
qemu-se:
enabled: true
supportedArches:
@@ -284,9 +312,17 @@ verification:
# Namespace where verification pod will be created
namespace: default
# Timeout for verification pod to complete (seconds)
# Timeout for the verification pod itself to complete (seconds)
# This is how long to wait for the verification pod to run and finish successfully.
# Default: 180s (3 minutes)
timeout: 180
# Timeout for kata-deploy DaemonSet rollout (seconds)
# This includes waiting for the kata-deploy image to be pulled from the registry
# and pods to start. Large images over slow networks may need more time.
# Default: 1200s (20 minutes)
daemonsetTimeout: 1200
# Pod spec for verification (optional)
# If provided, a verification job will run after install/upgrade.
# If empty, no verification is performed.

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

View File

@@ -11,48 +11,35 @@ set -o nounset
set -o pipefail
set -o errtrace
KATA_DEPLOY_DIR="$(dirname "${0}")/../../kata-deploy"
KATA_DEPLOY_DIR="`dirname ${0}`/../../kata-deploy"
KATA_DEPLOY_ARTIFACT="${1:-"kata-static.tar.zst"}"
REGISTRY="${2:-"quay.io/kata-containers/kata-deploy"}"
TAG="${3:-}"
BUILD_TYPE="${4:-}"
# Determine which Dockerfile to use and build directory
DOCKERFILE="Dockerfile"
BUILD_SUFFIX=""
if [ "${BUILD_TYPE}" = "rust" ]; then
DOCKERFILE="Dockerfile.rust"
BUILD_SUFFIX="-rust"
echo "Building Rust-based kata-deploy image"
else
echo "Building script-based kata-deploy image (default)"
fi
echo "Copying ${KATA_DEPLOY_ARTIFACT} to ${KATA_DEPLOY_DIR}"
cp "${KATA_DEPLOY_ARTIFACT}" "${KATA_DEPLOY_DIR}"
cp ${KATA_DEPLOY_ARTIFACT} ${KATA_DEPLOY_DIR}
pushd "${KATA_DEPLOY_DIR}"
pushd ${KATA_DEPLOY_DIR}
arch=$(uname -m)
[ "$arch" = "x86_64" ] && arch="amd64"
IMAGE_TAG="${REGISTRY}:kata-containers-$(git rev-parse HEAD)-${arch}${BUILD_SUFFIX}"
IMAGE_TAG="${REGISTRY}:kata-containers-$(git rev-parse HEAD)-${arch}"
echo "Building the image using ${DOCKERFILE} from $(pwd)"
docker build --file "${DOCKERFILE}" --tag "${IMAGE_TAG}" .
echo "Building the image"
docker build --tag ${IMAGE_TAG} .
echo "Pushing the image to the registry"
docker push "${IMAGE_TAG}"
docker push ${IMAGE_TAG}
if [ -n "${TAG}" ]; then
ADDITIONAL_TAG="${REGISTRY}:${TAG}${BUILD_SUFFIX}"
ADDITIONAL_TAG="${REGISTRY}:${TAG}"
echo "Building the ${ADDITIONAL_TAG} image"
docker build --file "${DOCKERFILE}" --tag "${ADDITIONAL_TAG}" .
docker build --tag ${ADDITIONAL_TAG} .
echo "Pushing the image ${ADDITIONAL_TAG} to the registry"
docker push "${ADDITIONAL_TAG}"
docker push ${ADDITIONAL_TAG}
fi
popd

File diff suppressed because it is too large Load Diff

View File

@@ -184,6 +184,13 @@ standard_rust_check:
cargo clippy --all-targets --all-features --release \
-- \
-D warnings
cargo check
@DIFF=$$(git diff HEAD); \
if [ -n "$$DIFF" ]; then \
echo "ERROR: cargo check resulted in uncommited changes"; \
echo "$$DIFF"; \
exit 1; \
fi
# Install a file (full version).
#