kata-deploy: Add tests for custom runtimes Helm templates

Add Bats tests to verify the custom runtimes Helm template rendering,
and that the we can start a pod with the custom runtime.

Tests were written with Cursor's help.

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
This commit is contained in:
Fabiano Fidêncio
2026-01-22 17:08:12 +01:00
parent 3be57bb501
commit d8a3272f85
4 changed files with 553 additions and 39 deletions

View File

@@ -0,0 +1,381 @@
#!/usr/bin/env bats
# Copyright (c) 2025 NVIDIA Corporation
#
# SPDX-License-Identifier: Apache-2.0
#
# End-to-end tests for kata-deploy custom runtimes feature
# These tests deploy kata-deploy with custom runtimes and verify pods can run
#
# 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.)
load "${BATS_TEST_DIRNAME}/../../common.bash"
repo_root_dir="${BATS_TEST_DIRNAME}/../../../"
load "${repo_root_dir}/tests/gha-run-k8s-common.sh"
# Load shared helm deployment helpers
source "${BATS_TEST_DIRNAME}/lib/helm-deploy.bash"
# Test configuration
CUSTOM_RUNTIME_NAME="special-workload"
CUSTOM_RUNTIME_HANDLER="kata-my-custom-handler"
TEST_POD_NAME="kata-deploy-custom-verify"
CHART_PATH="$(get_chart_path)"
# =============================================================================
# Template Rendering Tests (no cluster required)
# =============================================================================
@test "Helm template: ConfigMap is created with custom runtime" {
helm template kata-deploy "${CHART_PATH}" \
-f "${CUSTOM_VALUES_FILE}" \
--set image.reference=quay.io/kata-containers/kata-deploy \
--set image.tag=latest \
> /tmp/rendered.yaml
# Check that ConfigMap exists
grep -q "kind: ConfigMap" /tmp/rendered.yaml
grep -q "kata-deploy-custom-configs" /tmp/rendered.yaml
grep -q "${CUSTOM_RUNTIME_HANDLER}" /tmp/rendered.yaml
}
@test "Helm template: RuntimeClass is created with correct handler" {
helm template kata-deploy "${CHART_PATH}" \
-f "${CUSTOM_VALUES_FILE}" \
--set image.reference=quay.io/kata-containers/kata-deploy \
--set image.tag=latest \
> /tmp/rendered.yaml
grep -q "kind: RuntimeClass" /tmp/rendered.yaml
grep -q "handler: ${CUSTOM_RUNTIME_HANDLER}" /tmp/rendered.yaml
}
@test "Helm template: Drop-in file is included in ConfigMap" {
helm template kata-deploy "${CHART_PATH}" \
-f "${CUSTOM_VALUES_FILE}" \
--set image.reference=quay.io/kata-containers/kata-deploy \
--set image.tag=latest \
> /tmp/rendered.yaml
grep -q "dropin-${CUSTOM_RUNTIME_HANDLER}.toml" /tmp/rendered.yaml
grep -q "dial_timeout = 999" /tmp/rendered.yaml
}
@test "Helm template: CUSTOM_RUNTIMES_ENABLED env var is set" {
helm template kata-deploy "${CHART_PATH}" \
-f "${CUSTOM_VALUES_FILE}" \
--set image.reference=quay.io/kata-containers/kata-deploy \
--set image.tag=latest \
> /tmp/rendered.yaml
grep -q "CUSTOM_RUNTIMES_ENABLED" /tmp/rendered.yaml
grep -A1 "CUSTOM_RUNTIMES_ENABLED" /tmp/rendered.yaml | grep -q '"true"'
}
@test "Helm template: custom-configs volume is mounted" {
helm template kata-deploy "${CHART_PATH}" \
-f "${CUSTOM_VALUES_FILE}" \
--set image.reference=quay.io/kata-containers/kata-deploy \
--set image.tag=latest \
> /tmp/rendered.yaml
grep -q "mountPath: /custom-configs/" /tmp/rendered.yaml
grep -q "name: custom-configs" /tmp/rendered.yaml
}
@test "Helm template: No custom runtime resources when disabled" {
helm template kata-deploy "${CHART_PATH}" \
--set image.reference=quay.io/kata-containers/kata-deploy \
--set image.tag=latest \
--set customRuntimes.enabled=false \
> /tmp/rendered.yaml
! grep -q "kata-deploy-custom-configs" /tmp/rendered.yaml
! grep -q "CUSTOM_RUNTIMES_ENABLED" /tmp/rendered.yaml
}
@test "Helm template: Custom runtimes only mode (no standard shims)" {
# Test that Helm chart renders correctly when all standard shims are disabled
# and only custom runtimes are enabled
# Use generate_base_values to get all shims disabled except KATA_HYPERVISOR
local base_values_file
base_values_file=$(mktemp)
generate_base_values "${base_values_file}"
# Create overlay that disables the one enabled shim and adds custom runtimes
local custom_values_file
custom_values_file=$(mktemp)
cat > "${custom_values_file}" <<EOF
# Disable the shim that was enabled in base values
shims:
${KATA_HYPERVISOR}:
enabled: false
# Enable only custom runtimes
customRuntimes:
enabled: true
runtimes:
my-only-runtime:
baseConfig: "qemu"
dropIn: |
[hypervisor.qemu]
enable_debug = true
runtimeClass: |
kind: RuntimeClass
apiVersion: node.k8s.io/v1
metadata:
name: kata-my-only-runtime
handler: kata-my-only-runtime
scheduling:
nodeSelector:
katacontainers.io/kata-runtime: "true"
containerd:
snapshotter: ""
crio:
pullType: ""
EOF
helm template kata-deploy "${CHART_PATH}" \
-f "${base_values_file}" \
-f "${custom_values_file}" \
> /tmp/rendered.yaml
rm -f "${base_values_file}" "${custom_values_file}"
# Verify custom runtime resources are created
grep -q "kata-deploy-custom-configs" /tmp/rendered.yaml
grep -q "CUSTOM_RUNTIMES_ENABLED" /tmp/rendered.yaml
grep -q "kata-my-only-runtime" /tmp/rendered.yaml
# Verify SHIMS env var is empty (no standard shims)
local shims_value
shims_value=$(grep -A1 'name: SHIMS$' /tmp/rendered.yaml | grep 'value:' | head -1 || echo "")
echo "# SHIMS env value: ${shims_value}" >&3
}
# =============================================================================
# End-to-End Tests (require cluster with kata-deploy)
# =============================================================================
@test "E2E: Custom RuntimeClass exists with correct properties" {
# Check RuntimeClass exists
run kubectl get runtimeclass "${CUSTOM_RUNTIME_HANDLER}" -o name
if [[ "${status}" -ne 0 ]]; then
echo "# RuntimeClass not found. kata-deploy logs:" >&3
kubectl -n kube-system logs -l name=kata-deploy --tail=50 2>/dev/null || true
fail "Custom RuntimeClass ${CUSTOM_RUNTIME_HANDLER} not found"
fi
echo "# RuntimeClass ${CUSTOM_RUNTIME_HANDLER} exists" >&3
# Verify handler is correct
local handler
handler=$(kubectl get runtimeclass "${CUSTOM_RUNTIME_HANDLER}" -o jsonpath='{.handler}')
echo "# Handler: ${handler}" >&3
[[ "${handler}" == "${CUSTOM_RUNTIME_HANDLER}" ]]
# Verify overhead is set
local overhead_memory
overhead_memory=$(kubectl get runtimeclass "${CUSTOM_RUNTIME_HANDLER}" -o jsonpath='{.overhead.podFixed.memory}')
echo "# Overhead memory: ${overhead_memory}" >&3
[[ "${overhead_memory}" == "640Mi" ]]
local overhead_cpu
overhead_cpu=$(kubectl get runtimeclass "${CUSTOM_RUNTIME_HANDLER}" -o jsonpath='{.overhead.podFixed.cpu}')
echo "# Overhead CPU: ${overhead_cpu}" >&3
[[ "${overhead_cpu}" == "500m" ]]
# Verify nodeSelector is set
local node_selector
node_selector=$(kubectl get runtimeclass "${CUSTOM_RUNTIME_HANDLER}" -o jsonpath='{.scheduling.nodeSelector.katacontainers\.io/kata-runtime}')
echo "# Node selector: ${node_selector}" >&3
[[ "${node_selector}" == "true" ]]
# Verify label is set (Helm sets this to "Helm" when it manages the resource)
local label
label=$(kubectl get runtimeclass "${CUSTOM_RUNTIME_HANDLER}" -o jsonpath='{.metadata.labels.app\.kubernetes\.io/managed-by}')
echo "# Label app.kubernetes.io/managed-by: ${label}" >&3
[[ "${label}" == "Helm" ]]
BATS_TEST_COMPLETED=1
}
@test "E2E: Custom runtime can run a pod" {
# Check if the custom RuntimeClass exists
if ! kubectl get runtimeclass "${CUSTOM_RUNTIME_HANDLER}" &>/dev/null; then
skip "Custom RuntimeClass ${CUSTOM_RUNTIME_HANDLER} not found"
fi
# Create a test pod using the custom runtime
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: ${TEST_POD_NAME}
spec:
runtimeClassName: ${CUSTOM_RUNTIME_HANDLER}
restartPolicy: Never
nodeSelector:
katacontainers.io/kata-runtime: "true"
containers:
- name: test
image: quay.io/kata-containers/alpine-bash-curl:latest
command: ["echo", "OK"]
EOF
# Wait for pod to complete or become ready
echo "# Waiting for pod to be ready..." >&3
local timeout=120
local start_time
start_time=$(date +%s)
while true; do
local phase
phase=$(kubectl get pod "${TEST_POD_NAME}" -o jsonpath='{.status.phase}' 2>/dev/null || echo "Unknown")
case "${phase}" in
Succeeded|Running)
echo "# Pod reached phase: ${phase}" >&3
break
;;
Failed)
echo "# Pod failed" >&3
kubectl describe pod "${TEST_POD_NAME}" >&3
fail "Pod failed to run with custom runtime"
;;
*)
local current_time
current_time=$(date +%s)
if (( current_time - start_time > timeout )); then
echo "# Timeout waiting for pod" >&3
kubectl describe pod "${TEST_POD_NAME}" >&3
fail "Timeout waiting for pod to be ready"
fi
sleep 5
;;
esac
done
# Verify pod ran successfully
local exit_code
exit_code=$(kubectl get pod "${TEST_POD_NAME}" -o jsonpath='{.status.containerStatuses[0].state.terminated.exitCode}' 2>/dev/null || echo "")
if [[ "${exit_code}" == "0" ]] || [[ "$(kubectl get pod "${TEST_POD_NAME}" -o jsonpath='{.status.phase}')" == "Running" ]]; then
echo "# Pod ran successfully with custom runtime" >&3
BATS_TEST_COMPLETED=1
else
fail "Pod did not complete successfully (exit code: ${exit_code})"
fi
}
# =============================================================================
# Setup and Teardown
# =============================================================================
setup_file() {
ensure_helm
echo "# Using base config: ${KATA_HYPERVISOR}" >&3
echo "# Custom runtime handler: ${CUSTOM_RUNTIME_HANDLER}" >&3
echo "# Image: ${DOCKER_REGISTRY}/${DOCKER_REPO}:${DOCKER_TAG}" >&3
echo "# K8s distribution: ${KUBERNETES}" >&3
# Create values file for custom runtimes
export DEPLOY_VALUES_FILE=$(mktemp)
cat > "${DEPLOY_VALUES_FILE}" <<EOF
customRuntimes:
enabled: true
runtimes:
${CUSTOM_RUNTIME_NAME}:
baseConfig: "${KATA_HYPERVISOR}"
dropIn: |
[agent.kata]
dial_timeout = 999
runtimeClass: |
kind: RuntimeClass
apiVersion: node.k8s.io/v1
metadata:
name: ${CUSTOM_RUNTIME_HANDLER}
labels:
app.kubernetes.io/managed-by: kata-deploy
handler: ${CUSTOM_RUNTIME_HANDLER}
overhead:
podFixed:
memory: "640Mi"
cpu: "500m"
scheduling:
nodeSelector:
katacontainers.io/kata-runtime: "true"
containerd:
snapshotter: ""
crio:
pullType: ""
EOF
echo "# Deploying kata-deploy with custom runtimes..." >&3
deploy_kata "${DEPLOY_VALUES_FILE}"
echo "# kata-deploy deployed successfully" >&3
}
setup() {
# Create temporary values file for template tests
CUSTOM_VALUES_FILE=$(mktemp)
cat > "${CUSTOM_VALUES_FILE}" <<EOF
customRuntimes:
enabled: true
runtimes:
${CUSTOM_RUNTIME_NAME}:
baseConfig: "${KATA_HYPERVISOR:-qemu}"
dropIn: |
[agent.kata]
dial_timeout = 999
runtimeClass: |
kind: RuntimeClass
apiVersion: node.k8s.io/v1
metadata:
name: ${CUSTOM_RUNTIME_HANDLER}
labels:
app.kubernetes.io/managed-by: kata-deploy
handler: ${CUSTOM_RUNTIME_HANDLER}
overhead:
podFixed:
memory: "640Mi"
cpu: "500m"
scheduling:
nodeSelector:
katacontainers.io/kata-runtime: "true"
containerd:
snapshotter: ""
crio:
pullType: ""
EOF
}
teardown() {
# Show pod details for debugging if test failed
if [[ "${BATS_TEST_COMPLETED:-}" != "1" ]]; then
echo "# Test failed, gathering diagnostics..." >&3
kubectl describe pod "${TEST_POD_NAME}" 2>/dev/null || true
echo "# kata-deploy logs:" >&3
kubectl -n kube-system logs -l name=kata-deploy --tail=100 2>/dev/null || true
fi
# Clean up test pod
kubectl delete pod "${TEST_POD_NAME}" --ignore-not-found=true --wait=false 2>/dev/null || true
# Clean up temp file
[[ -f "${CUSTOM_VALUES_FILE:-}" ]] && rm -f "${CUSTOM_VALUES_FILE}"
}
teardown_file() {
echo "# Cleaning up..." >&3
kubectl delete pod "${TEST_POD_NAME}" --ignore-not-found=true --wait=true --timeout=60s 2>/dev/null || true
uninstall_kata
[[ -f "${DEPLOY_VALUES_FILE:-}" ]] && rm -f "${DEPLOY_VALUES_FILE}"
}

View File

@@ -31,6 +31,9 @@ load "${BATS_TEST_DIRNAME}/../../common.bash"
repo_root_dir="${BATS_TEST_DIRNAME}/../../../"
load "${repo_root_dir}/tests/gha-run-k8s-common.sh"
# Load shared helm deployment helpers
source "${BATS_TEST_DIRNAME}/lib/helm-deploy.bash"
setup() {
ensure_helm
@@ -79,7 +82,6 @@ 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)
@@ -97,43 +99,11 @@ EOF
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 \
# Deploy kata-deploy using shared helper with verification options
HELM_TIMEOUT="${helm_timeout}" deploy_kata "" \
--set-file verification.pod="${verification_yaml}" \
--set verification.timeout="${verification_timeout}" \
--set verification.daemonsetTimeout="${daemonset_timeout}" \
--namespace kube-system \
--wait --timeout "${helm_timeout}"
--set verification.daemonsetTimeout="${daemonset_timeout}"
rm -f "${verification_yaml}"
@@ -179,7 +149,5 @@ EOF
}
teardown() {
pushd "${repo_root_dir}"
helm uninstall kata-deploy --ignore-not-found --wait --cascade foreground --timeout 10m --namespace kube-system --debug
popd
uninstall_kata
}

View File

@@ -0,0 +1,164 @@
#!/bin/bash
# Copyright (c) 2025 NVIDIA Corporation
#
# SPDX-License-Identifier: Apache-2.0
#
# Shared helm deployment helpers for kata-deploy tests
#
# 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.)
HELM_RELEASE_NAME="${HELM_RELEASE_NAME:-kata-deploy}"
HELM_NAMESPACE="${HELM_NAMESPACE:-kube-system}"
# Get the path to the helm chart
get_chart_path() {
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "${script_dir}/../../../../tools/packaging/kata-deploy/helm-chart/kata-deploy"
}
# Generate base values YAML that disables all shims except the specified one
# Arguments:
# $1 - Output file path
# $2 - (Optional) Additional values file to merge
generate_base_values() {
local output_file="$1"
local extra_values_file="${2:-}"
cat > "${output_file}" <<EOF
image:
reference: ${DOCKER_REGISTRY}/${DOCKER_REPO}
tag: ${DOCKER_TAG}
k8sDistribution: "${KUBERNETES}"
debug: true
# Disable all shims, then enable only the one we need
shims:
clh:
enabled: false
cloud-hypervisor:
enabled: false
dragonball:
enabled: false
fc:
enabled: false
qemu:
enabled: false
qemu-runtime-rs:
enabled: false
qemu-cca:
enabled: false
qemu-se:
enabled: false
qemu-se-runtime-rs:
enabled: false
qemu-nvidia-gpu:
enabled: false
qemu-nvidia-gpu-snp:
enabled: false
qemu-nvidia-gpu-tdx:
enabled: false
qemu-sev:
enabled: false
qemu-snp:
enabled: false
qemu-snp-runtime-rs:
enabled: false
qemu-tdx:
enabled: false
qemu-tdx-runtime-rs:
enabled: false
qemu-coco-dev:
enabled: false
qemu-coco-dev-runtime-rs:
enabled: false
${KATA_HYPERVISOR}:
enabled: true
defaultShim:
amd64: ${KATA_HYPERVISOR}
arm64: ${KATA_HYPERVISOR}
runtimeClasses:
enabled: true
createDefault: true
EOF
}
# Deploy kata-deploy using helm
# Arguments:
# $1 - (Optional) Additional values file to merge with base values
# $@ - (Optional) Additional helm arguments (after the first positional arg)
deploy_kata() {
local extra_values_file="${1:-}"
shift || true
local extra_helm_args=("$@")
local chart_path
local values_yaml
chart_path="$(get_chart_path)"
values_yaml=$(mktemp)
# Generate base values
generate_base_values "${values_yaml}"
# Add required helm repos for dependencies
helm repo add node-feature-discovery https://kubernetes-sigs.github.io/node-feature-discovery/charts 2>/dev/null || true
helm repo update
# Build helm dependencies
helm dependency build "${chart_path}"
# Build helm command
local helm_cmd=(
helm upgrade --install "${HELM_RELEASE_NAME}" "${chart_path}"
-f "${values_yaml}"
)
# Add extra values file if provided
if [[ -n "${extra_values_file}" && -f "${extra_values_file}" ]]; then
helm_cmd+=(-f "${extra_values_file}")
fi
# Add any extra helm arguments
if [[ ${#extra_helm_args[@]} -gt 0 ]]; then
helm_cmd+=("${extra_helm_args[@]}")
fi
helm_cmd+=(
--namespace "${HELM_NAMESPACE}"
--wait --timeout "${HELM_TIMEOUT:-10m}"
)
# Run helm install
"${helm_cmd[@]}"
local ret=$?
rm -f "${values_yaml}"
if [[ ${ret} -ne 0 ]]; then
echo "Helm install failed with exit code ${ret}" >&2
return ${ret}
fi
# Wait for daemonset to be ready
kubectl -n "${HELM_NAMESPACE}" rollout status daemonset/kata-deploy --timeout=300s
# Give it a moment to configure runtimes
sleep 10
return 0
}
# Uninstall kata-deploy
uninstall_kata() {
helm uninstall "${HELM_RELEASE_NAME}" -n "${HELM_NAMESPACE}" \
--ignore-not-found --wait --cascade foreground --timeout 10m || true
}

View File

@@ -19,6 +19,7 @@ if [[ -n "${KATA_DEPLOY_TEST_UNION:-}" ]]; then
else
KATA_DEPLOY_TEST_UNION=( \
"kata-deploy.bats" \
"kata-deploy-custom-runtimes.bats" \
)
fi