Files
OpenShell/e2e/parity/kubernetes-options.sh
T
Jesse JaggarsandDrew Newberry 02b664bb0d refactor(config): normalize and enforce gateway schema v2 (#2814)
* refactor(config): normalize compute driver field names

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* refactor(config): introduce canonical gateway fields

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* refactor(config): enforce gateway schema version 2

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(config): preserve compute driver runtime guarantees

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(config): address schema v2 review regressions

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(config): complete schema v2 migration safeguards

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(config): expand schema v2 regression coverage

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(config): add schema v2 parity manifest

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(config): correct parity manifest inventory

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* docs(config): record schema v2 intentional changes

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* docs(config): disposition schema v2 parity gaps

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): add dual schema parity harness

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): establish compute lifecycle parity baseline

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(config): preserve gateway option compatibility

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): record gateway option parity

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* docs(config): close gateway-wide parity gaps

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(podman): apply configured pids limit

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): validate Podman option parity

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): add Kubernetes option parity harness

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): record Kubernetes option parity

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): disposition VM parity lanes

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): add external driver parity lane

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(e2e): preserve external driver pull policy

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): attest parity artifacts and launches

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): require clean parity build sources

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): bind parity runtime artifacts

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(e2e): use isolated supervisor tags

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(e2e): qualify parity image tags

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(e2e): serve parity supervisor locally

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): isolate parity podman services

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): harden parity evidence provenance

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): pin parity sandbox artifacts

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): attest parity runtime inputs

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): bind parity runtime evidence

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): record compute boundary parity

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(e2e): disposition cross-cutting parity lanes

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(packaging): preflight gateway config upgrades

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(config): preserve rebase integration guarantees

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(ci): isolate temporary git signing config

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(config): update remaining schema v2 consumers

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(ci): provide e2fs tools to VM tests

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(config): align preflight with gateway startup

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(vm): preserve rootfs tar configuration

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* chore(config): adopt duration unit constructors

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(packaging): preflight RPM gateway config

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(config): address driver review findings

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(e2e): require fresh semantic parity evidence

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* fix(docker): update tests for renamed sandbox label

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>

* test(gateway): preserve selective driver coverage after rebase

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

---------

Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Co-authored-by: Drew Newberry <anewberry@nvidia.com>
2026-09-11 05:00:24 +00:00

433 lines
20 KiB
Bash

#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
# Compare the frozen schema-v1 and current schema-v2 Kubernetes driver against
# one explicitly supplied, disposable kind cluster. Gateway processes run on
# the host so the same cluster and candidate-owned oracle exercise both schemas.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
BASELINE_SHA="${OPENSHELL_PARITY_BASELINE_SHA:-74960ebfaeec4673885089ed995fad902459749f}"
CANDIDATE_SHA="${OPENSHELL_PARITY_CANDIDATE_SHA:-$(git -C "${ROOT}" rev-parse HEAD)}"
BASELINE_ROOT="${OPENSHELL_PARITY_BASELINE_ROOT:-}"
BASELINE_GATEWAY="${OPENSHELL_PARITY_BASELINE_GATEWAY:-}"
CANDIDATE_GATEWAY="${OPENSHELL_PARITY_CANDIDATE_GATEWAY:-}"
CLI="${OPENSHELL_PARITY_CLI:-}"
ARTIFACT_MANIFEST="${OPENSHELL_PARITY_ARTIFACT_MANIFEST:-}"
KUBECONFIG_PATH="${OPENSHELL_PARITY_KUBECONFIG:-}"
KUBE_CONTEXT="${OPENSHELL_PARITY_KUBE_CONTEXT:-}"
HOST_GATEWAY_IP="${OPENSHELL_PARITY_HOST_GATEWAY_IP:-}"
RUN_ID="${OPENSHELL_PARITY_RUN_ID:-$(date +%s)-$$}"
OUT="${OPENSHELL_PARITY_OUTPUT_DIR:-${ROOT}/target/parity/step8-kubernetes-${CANDIDATE_SHA:0:8}}"
SANDBOX_IMAGE="${OPENSHELL_PARITY_KUBERNETES_SANDBOX_IMAGE:-ghcr.io/nvidia/openshell-community/sandboxes/base:latest}"
SUPERVISOR_IMAGE="${OPENSHELL_PARITY_KUBERNETES_SUPERVISOR_IMAGE:-ghcr.io/nvidia/openshell/supervisor:latest}"
RUNTIME_CLASS="openshell-parity-runc-${RUN_ID}"
fail() {
echo "ERROR: Kubernetes option parity: $*" >&2
exit 1
}
kctl() {
kubectl --kubeconfig "${KUBECONFIG_PATH}" --context "${KUBE_CONTEXT}" "$@"
}
pick_port() {
python3 -I - <<'PY'
import socket
with socket.socket() as sock:
sock.bind(("0.0.0.0", 0))
print(sock.getsockname()[1])
PY
}
write_config() {
local variant=$1
local path=$2
local namespace=$3
local port=$4
local run_dir=$5
local gateway_id="step8-${variant}-${RUN_ID}"
local pull_policy
if [ "${variant}" = baseline ]; then
pull_policy=IfNotPresent
cat >"${path}" <<EOF
[openshell]
version = 1
[openshell.gateway]
name = "${gateway_id}"
bind_address = "0.0.0.0:${port}"
disable_tls = true
compute_drivers = ["kubernetes"]
default_image = "${SANDBOX_IMAGE}"
supervisor_image = "${SUPERVISOR_IMAGE}"
client_tls_secret_name = "parity-client-tls"
service_account_name = "parity-sandbox"
host_gateway_ip = "${HOST_GATEWAY_IP}"
enable_user_namespaces = false
sa_token_ttl_secs = 600
[openshell.gateway.auth]
allow_unauthenticated_users = true
[openshell.gateway.gateway_jwt]
signing_key_path = "${run_dir}/jwt/signing.pem"
public_key_path = "${run_dir}/jwt/public.pem"
kid_path = "${run_dir}/jwt/kid"
gateway_id = "${gateway_id}"
ttl_secs = 3600
[openshell.drivers.kubernetes]
namespace = "${namespace}"
workspace_mode = "shared"
gateway_id = "${gateway_id}"
image_pull_policy = "${pull_policy}"
image_pull_secrets = ["parity-pull-secret"]
supervisor_image_pull_policy = "${pull_policy}"
supervisor_sideload_method = "init-container"
topology = "combined"
grpc_endpoint = "http://host.openshell.internal:${port}"
ssh_socket_path = "/run/openshell/parity-kubernetes-ssh.sock"
workspace_default_storage_size = "64Mi"
workspace_storage_class = "standard"
default_runtime_class_name = "${RUNTIME_CLASS}"
app_armor_profile = "Unconfined"
sandbox_uid = 1000
sandbox_gid = 1000
EOF
else
pull_policy=if_not_present
cat >"${path}" <<EOF
[openshell]
version = 2
[openshell.gateway]
name = "${gateway_id}"
bind_address = "0.0.0.0:${port}"
disable_tls = true
compute_driver = "kubernetes"
[openshell.gateway.auth]
allow_unauthenticated_users = true
[openshell.gateway.gateway_jwt]
signing_key_path = "${run_dir}/jwt/signing.pem"
public_key_path = "${run_dir}/jwt/public.pem"
kid_path = "${run_dir}/jwt/kid"
gateway_id = "${gateway_id}"
ttl_secs = 3600
[openshell.drivers.kubernetes]
namespace = "${namespace}"
workspace_mode = "shared"
gateway_id = "${gateway_id}"
default_image = "${SANDBOX_IMAGE}"
image_pull_policy = "${pull_policy}"
image_pull_secrets = ["parity-pull-secret"]
service_account_name = "parity-sandbox"
supervisor_image = "${SUPERVISOR_IMAGE}"
supervisor_image_pull_policy = "${pull_policy}"
supervisor_sideload_method = "init-container"
topology = "combined"
grpc_endpoint = "http://host.openshell.internal:${port}"
ssh_socket_path = "/run/openshell/parity-kubernetes-ssh.sock"
client_tls_secret_name = "parity-client-tls"
host_gateway_ip = "${HOST_GATEWAY_IP}"
enable_user_namespaces = false
sa_token_ttl_secs = 600
workspace_default_storage_size = "64Mi"
workspace_storage_class = "standard"
default_runtime_class_name = "${RUNTIME_CLASS}"
app_armor_profile = "Unconfined"
sandbox_uid = 1000
sandbox_gid = 1000
EOF
fi
}
if [ "${1:-}" = --print-config ]; then
variant="${2:-}"
case "${variant}" in baseline|candidate) ;; *) fail "--print-config requires baseline or candidate" ;; esac
HOST_GATEWAY_IP="${HOST_GATEWAY_IP:-169.254.1.2}"
RUNTIME_CLASS=openshell-parity-runc-print
write_config "${variant}" /dev/stdout openshell-parity-print 18080 /tmp/openshell-parity-print
exit 0
fi
[ -n "${BASELINE_ROOT}" ] || fail "OPENSHELL_PARITY_BASELINE_ROOT is required"
[ -x "${BASELINE_GATEWAY}" ] || fail "baseline gateway is not executable: ${BASELINE_GATEWAY}"
[ -x "${CANDIDATE_GATEWAY}" ] || fail "candidate gateway is not executable: ${CANDIDATE_GATEWAY}"
[ -x "${CLI}" ] || fail "candidate CLI is not executable: ${CLI}"
[ -f "${KUBECONFIG_PATH}" ] || fail "private kubeconfig does not exist: ${KUBECONFIG_PATH}"
[ -n "${HOST_GATEWAY_IP}" ] || fail "OPENSHELL_PARITY_HOST_GATEWAY_IP is required; host routing is never guessed"
case "${KUBE_CONTEXT}" in kind-openshell-parity-*) ;; *) fail "refusing non-parity context: ${KUBE_CONTEXT:-<unset>}" ;; esac
[[ "${BASELINE_SHA}" =~ ^[0-9a-f]{40}$ ]] || fail "baseline SHA must be a full lowercase SHA-1"
[[ "${CANDIDATE_SHA}" =~ ^[0-9a-f]{40}$ ]] || fail "candidate SHA must be a full lowercase SHA-1"
[[ "${RUN_ID}" =~ ^[a-z0-9]([a-z0-9-]{0,30}[a-z0-9])?$ ]] || fail "run ID must be a lowercase DNS label of at most 32 characters"
[[ "${SANDBOX_IMAGE}" =~ ^[A-Za-z0-9][A-Za-z0-9._/:@+-]{0,254}$ ]] || fail "sandbox image contains unsafe characters"
[[ "${SUPERVISOR_IMAGE}" =~ ^[A-Za-z0-9][A-Za-z0-9._/:@+-]{0,254}$ ]] || fail "supervisor image contains unsafe characters"
python3 -I - "${HOST_GATEWAY_IP}" <<'PY'
import ipaddress, sys
value=ipaddress.ip_address(sys.argv[1])
if value.version != 4:
raise SystemExit("host gateway IP must be IPv4")
PY
[ "$(git -C "${BASELINE_ROOT}" rev-parse HEAD)" = "${BASELINE_SHA}" ] || fail "baseline worktree is not ${BASELINE_SHA}"
[ "$(git -C "${ROOT}" rev-parse HEAD)" = "${CANDIDATE_SHA}" ] || fail "candidate worktree is not ${CANDIDATE_SHA}"
[ "$(kubectl --kubeconfig "${KUBECONFIG_PATH}" config current-context)" = "${KUBE_CONTEXT}" ] || fail "private kubeconfig current context differs from requested parity context"
[ "$(kctl -n kube-system get configmap openshell-parity-guard -o jsonpath='{.data.context}')" = "${KUBE_CONTEXT}" ] || fail "cluster lacks the matching provisioning-time parity guard"
[ "$(kctl -n kube-system get configmap openshell-parity-guard -o jsonpath='{.data.purpose}')" = schema-v2-capability-parity ] || fail "cluster parity guard has the wrong purpose"
kctl get nodes -o name | grep -q '^node/openshell-parity-' || fail "requested context is not the dedicated OpenShell parity cluster"
[ "$(kctl get crd sandboxes.agents.x-k8s.io -o jsonpath='{.status.conditions[?(@.type=="Established")].status}')" = True ] || fail "Agent Sandbox CRD is not established"
[ -f "${ARTIFACT_MANIFEST}" ] || fail "OPENSHELL_PARITY_ARTIFACT_MANIFEST is required"
python3 -I - "${ARTIFACT_MANIFEST}" "${BASELINE_SHA}" "${CANDIDATE_SHA}" "${BASELINE_GATEWAY}" "${CANDIDATE_GATEWAY}" "${CLI}" <<'PY'
import hashlib, pathlib, sys, tomllib
manifest=tomllib.load(open(sys.argv[1],'rb'))
expected={'baseline_commit':sys.argv[2],'candidate_commit':sys.argv[3]}
for key, value in expected.items():
if manifest.get(key) != value:
raise SystemExit(f'artifact manifest {key} does not match')
for key, path in zip(('baseline_gateway_sha256','candidate_gateway_sha256','candidate_cli_sha256'),sys.argv[4:]):
digest=hashlib.sha256(pathlib.Path(path).read_bytes()).hexdigest()
if manifest.get(key) != digest:
raise SystemExit(f'artifact manifest {key} does not match supplied binary')
PY
PARITY_ROOT="$(realpath -m "${ROOT}/target/parity")"
OUT="$(realpath -m "${OUT}")"
case "${OUT}" in "${PARITY_ROOT}"/step8-kubernetes-*) ;; *) fail "output must be a step8-kubernetes-* directory below ${PARITY_ROOT}" ;; esac
[ ! -L "${OUT}" ] || fail "output directory must not be a symlink"
rm -rf --one-file-system "${OUT}"
umask 077
mkdir -p "${OUT}/raw"
cp "${ARTIFACT_MANIFEST}" "${OUT}/artifact-manifest.toml"
printf '%s\n' "${BASELINE_SHA}" >"${OUT}/baseline.sha"
printf '%s\n' "${CANDIDATE_SHA}" >"${OUT}/candidate.sha"
printf '%s\n' "${KUBE_CONTEXT}" >"${OUT}/context"
runtime_class_created=false
cleanup_cluster_fixture() {
local status=$?
local cleanup_status=0
set +e
if ${runtime_class_created}; then
kctl delete runtimeclass "${RUNTIME_CLASS}" --ignore-not-found --wait=true --timeout=120s >/dev/null 2>&1
cleanup_status=$?
fi
set -e
if [ "${status}" -eq 0 ] && [ "${cleanup_status}" -ne 0 ]; then
echo "ERROR: failed to confirm RuntimeClass cleanup" >&2
exit 1
fi
exit "${status}"
}
trap cleanup_cluster_fixture EXIT
cat <<EOF | kctl create -f - >/dev/null
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: ${RUNTIME_CLASS}
handler: runc
EOF
runtime_class_created=true
run_variant() (
set -euo pipefail
local variant=$1
local gateway=$2
local namespace="openshell-parity-${variant}-${RUN_ID}"
local sandbox="k8s-${variant:0:1}-${RUN_ID: -6}"
local resource="default--${sandbox}"
local run_dir="${OUT}/raw/${variant}"
local config="${run_dir}/gateway.toml"
local port
local gateway_pid=
local registered_endpoint
local namespace_created=false
mkdir -p "${run_dir}/jwt" "${run_dir}/xdg-config/openshell/gateways/parity" "${run_dir}/xdg-state" "${run_dir}/xdg-data"
cleanup_variant() {
local status=$?
local cleanup_status=0
set +e
if [ -n "${gateway_pid}" ]; then
kill "${gateway_pid}" >/dev/null 2>&1 || true
wait "${gateway_pid}" >/dev/null 2>&1 || true
fi
rm -f "${run_dir}/jwt/signing.pem" "${run_dir}/client.key"
if ${namespace_created}; then
kctl delete namespace "${namespace}" --ignore-not-found --wait=true --timeout=120s >"${run_dir}/namespace-delete.log" 2>&1
cleanup_status=$?
fi
set -e
if [ "${status}" -eq 0 ] && [ "${cleanup_status}" -ne 0 ]; then
echo "ERROR: ${variant} namespace cleanup was not confirmed" >&2
exit 1
fi
exit "${status}"
}
trap cleanup_variant EXIT
openssl genpkey -algorithm ED25519 -out "${run_dir}/jwt/signing.pem" >/dev/null 2>&1
openssl pkey -in "${run_dir}/jwt/signing.pem" -pubout -out "${run_dir}/jwt/public.pem" >/dev/null 2>&1
printf 'step8-%s\n' "${variant}" >"${run_dir}/jwt/kid"
openssl req -x509 -newkey rsa:2048 -nodes -subj "/CN=step8-parity-client" \
-keyout "${run_dir}/client.key" -out "${run_dir}/client.crt" -days 1 >/dev/null 2>&1
kctl create namespace "${namespace}" >"${run_dir}/namespace-create.log"
namespace_created=true
kctl -n "${namespace}" create serviceaccount parity-sandbox >"${run_dir}/service-account.log"
kctl -n "${namespace}" create secret generic parity-pull-secret \
--type=kubernetes.io/dockerconfigjson --from-literal=.dockerconfigjson='{"auths":{}}' >"${run_dir}/pull-secret.log"
kctl -n "${namespace}" create secret generic parity-client-tls \
--from-file=ca.crt="${run_dir}/client.crt" \
--from-file=tls.crt="${run_dir}/client.crt" \
--from-file=tls.key="${run_dir}/client.key" >"${run_dir}/client-tls-secret.log"
port="$(pick_port)"
write_config "${variant}" "${config}" "${namespace}" "${port}" "${run_dir}"
KUBECONFIG="${KUBECONFIG_PATH}" \
XDG_CONFIG_HOME="${run_dir}/xdg-config" XDG_STATE_HOME="${run_dir}/xdg-state" XDG_DATA_HOME="${run_dir}/xdg-data" \
OPENSHELL_DB_URL="sqlite:${run_dir}/gateway.db" \
"${gateway}" --config "${config}" >"${run_dir}/gateway.log" 2>&1 &
gateway_pid=$!
listener_ready=false
for _ in $(seq 1 60); do
if ! kill -0 "${gateway_pid}" >/dev/null 2>&1; then
fail "${variant} gateway exited before binding; see ${run_dir}/gateway.log"
fi
if python3 -I - "${port}" <<'PY'
import socket, sys
try:
with socket.create_connection(("127.0.0.1", int(sys.argv[1])), timeout=.2):
pass
except OSError:
raise SystemExit(1)
PY
then
listener_ready=true
break
fi
sleep 0.5
done
${listener_ready} || fail "${variant} gateway did not bind within 30 seconds"
registered_endpoint="http://127.0.0.1:${port}"
cat >"${run_dir}/xdg-config/openshell/gateways/parity/metadata.json" <<EOF
{"name":"parity","gateway_endpoint":"${registered_endpoint}","is_remote":false,"gateway_port":${port},"auth_mode":"plaintext"}
EOF
printf parity >"${run_dir}/xdg-config/openshell/active_gateway"
XDG_CONFIG_HOME="${run_dir}/xdg-config" XDG_STATE_HOME="${run_dir}/xdg-state" XDG_DATA_HOME="${run_dir}/xdg-data" \
timeout 360 "${CLI}" sandbox create --name "${sandbox}" --cpu 250m --memory 128Mi --detach \
>"${run_dir}/create.log" 2>&1
XDG_CONFIG_HOME="${run_dir}/xdg-config" XDG_STATE_HOME="${run_dir}/xdg-state" XDG_DATA_HOME="${run_dir}/xdg-data" \
timeout 60 "${CLI}" sandbox exec --name "${sandbox}" --no-tty -- \
sh -c 'printf step8-kubernetes-exec' >"${run_dir}/exec.log" 2>&1
grep -q 'step8-kubernetes-exec' "${run_dir}/exec.log" || fail "${variant} callback exec marker missing"
kctl -n "${namespace}" get sandbox "${resource}" -o json >"${run_dir}/sandbox.json"
kctl -n "${namespace}" get pod "${resource}" -o json >"${run_dir}/pod.json"
kctl -n "${namespace}" get pvc "workspace-${resource}" -o json >"${run_dir}/pvc.json"
python3 -I - "${run_dir}" "${SANDBOX_IMAGE}" "${SUPERVISOR_IMAGE}" "${HOST_GATEWAY_IP}" "${RUNTIME_CLASS}" <<'PY'
import json, pathlib, sys
def check(condition, message):
if not condition:
raise RuntimeError(message)
run=pathlib.Path(sys.argv[1]); sandbox_image, supervisor_image, host_ip, runtime_class=sys.argv[2:]
pod=json.loads((run/'pod.json').read_text()); pvc=json.loads((run/'pvc.json').read_text()); sb=json.loads((run/'sandbox.json').read_text())
spec=pod['spec']; agent=next(c for c in spec['containers'] if c['name']=='agent'); env={x['name']:x.get('value','') for x in agent.get('env',[])}
inits={c['name']:c for c in spec.get('initContainers',[])}; install=inits['openshell-supervisor-install']
vols={v['name']:v for v in spec.get('volumes',[])}; mounts={m['name']:m for m in agent.get('volumeMounts',[])}
hosts={(h, a['ip']) for a in spec.get('hostAliases',[]) for h in a.get('hostnames',[])}
check(pod['status']['phase']=='Running','Pod is not Running')
conditions={c['type']:c['status'] for c in sb.get('status',{}).get('conditions',[])}
check(conditions.get('Ready')=='True','Sandbox Ready condition is not true')
check(agent['image']==sandbox_image and agent['imagePullPolicy']=='IfNotPresent','sandbox image or pull policy differs')
check(install['image']==supervisor_image and install['imagePullPolicy']=='IfNotPresent','supervisor image or pull policy differs')
check([x['name'] for x in spec.get('imagePullSecrets',[])]==['parity-pull-secret'],'image pull Secret differs')
check(spec['serviceAccountName']=='parity-sandbox','ServiceAccount differs')
check(env['OPENSHELL_ENDPOINT'].startswith('http://host.openshell.internal:'),'callback endpoint differs')
check(env['OPENSHELL_SSH_SOCKET_PATH']=='/run/openshell/parity-kubernetes-ssh.sock','SSH socket differs')
check(env['OPENSHELL_SANDBOX_UID']=='1000' and env['OPENSHELL_SANDBOX_GID']=='1000','sandbox identity differs')
check(('host.openshell.internal',host_ip) in hosts and ('host.docker.internal',host_ip) in hosts,'host aliases differ')
check(spec['runtimeClassName']==runtime_class and spec.get('hostUsers',True) is not False,'RuntimeClass or user namespace posture differs')
check(agent['securityContext']['appArmorProfile']['type']=='Unconfined','AppArmor profile differs')
check(agent['resources']['requests']=={'cpu':'250m','memory':'128Mi'},'resource requests differ')
check(agent['resources']['limits']=={'cpu':'250m','memory':'128Mi'},'resource limits differ')
check(vols['openshell-sa-token']['projected']['sources'][0]['serviceAccountToken']['expirationSeconds']==600,'ServiceAccount token TTL differs')
check(vols['openshell-client-tls']['secret']['secretName']=='parity-client-tls','client TLS Secret differs')
check(mounts['openshell-client-tls']['readOnly'] is True,'client TLS mount is not read-only')
check(pvc['status']['phase']=='Bound' and pvc['spec']['storageClassName']=='standard','PVC phase or StorageClass differs')
check(pvc['spec']['resources']['requests']['storage']=='64Mi','PVC storage request differs')
labels=sb['metadata']['labels']
for key in ('openshell.ai/sandbox-id','openshell.ai/sandbox-name','openshell.ai/sandbox-workspace','openshell.ai/gateway-id','openshell.ai/managed-by'):
check(labels.get(key),f'managed label {key} missing')
observed_sideload='init-container' if 'openshell-supervisor-install' in inits else 'unknown'
observed_topology='combined' if [c['name'] for c in spec['containers']]==['agent'] else 'other'
observed_workspace_mode='shared' if pvc['metadata']['namespace']==pod['metadata']['namespace'] and pvc['metadata']['name'].startswith('workspace-default--') else 'other'
check(observed_sideload=='init-container','supervisor sideload method differs')
check(observed_topology=='combined','supervisor topology differs')
check(observed_workspace_mode=='shared','workspace placement differs')
normalized={
'scenario':'kubernetes-core-options','pod_phase':'Running','sandbox_ready':True,
'sandbox_image':agent['image'],'sandbox_image_pull_policy':agent['imagePullPolicy'],
'image_pull_secrets':['parity-pull-secret'],'service_account':'parity-sandbox',
'supervisor_image':install['image'],'supervisor_image_pull_policy':install['imagePullPolicy'],
'supervisor_sideload_method':observed_sideload,'topology':observed_topology,
'callback_endpoint_host':'host.openshell.internal','callback_exec':True,
'ssh_socket_path':env['OPENSHELL_SSH_SOCKET_PATH'],'client_tls_secret':'parity-client-tls',
'host_gateway_ip':host_ip,'sa_token_ttl_secs':600,'runtime_class_handler':'runc',
'enable_user_namespaces':False,'app_armor_profile':'Unconfined','sandbox_uid':1000,'sandbox_gid':1000,
'workspace_mode':observed_workspace_mode,'workspace_storage':'64Mi','workspace_storage_class':'standard','pvc_phase':'Bound',
'cpu':'250m','memory':'128Mi','managed_labels':True,
}
(run.parent.parent/f'{run.name}.normalized.json').write_text(json.dumps(normalized,sort_keys=True,separators=(',',':'))+'\n')
PY
XDG_CONFIG_HOME="${run_dir}/xdg-config" XDG_STATE_HOME="${run_dir}/xdg-state" XDG_DATA_HOME="${run_dir}/xdg-data" \
timeout 60 "${CLI}" sandbox delete "${sandbox}" >"${run_dir}/delete.log" 2>&1
for _ in $(seq 1 60); do
if ! kctl -n "${namespace}" get sandbox "${resource}" >/dev/null 2>&1 \
&& ! kctl -n "${namespace}" get pod "${resource}" >/dev/null 2>&1 \
&& ! kctl -n "${namespace}" get pvc "workspace-${resource}" >/dev/null 2>&1; then
break
fi
sleep 1
done
! kctl -n "${namespace}" get sandbox "${resource}" >/dev/null 2>&1 || fail "${variant} Sandbox remained after delete"
! kctl -n "${namespace}" get pod "${resource}" >/dev/null 2>&1 || fail "${variant} Pod remained after delete"
! kctl -n "${namespace}" get pvc "workspace-${resource}" >/dev/null 2>&1 || fail "${variant} PVC remained after delete"
)
set +e
run_variant baseline "${BASELINE_GATEWAY}"
baseline_status=$?
run_variant candidate "${CANDIDATE_GATEWAY}"
candidate_status=$?
set -e
baseline_success=false; candidate_success=false
[ "${baseline_status}" -eq 0 ] && baseline_success=true
[ "${candidate_status}" -eq 0 ] && candidate_success=true
parity=false; classification=regression; accepted=false
if ${baseline_success} && ${candidate_success} && [ -f "${OUT}/baseline.normalized.json" ] && [ -f "${OUT}/candidate.normalized.json" ]; then
if cmp -s "${OUT}/baseline.normalized.json" "${OUT}/candidate.normalized.json"; then
parity=true; classification=pass; accepted=true
fi
fi
cat >"${OUT}/comparison.json" <<EOF
{"baseline_commit":"${BASELINE_SHA}","candidate_commit":"${CANDIDATE_SHA}","baseline_success":${baseline_success},"candidate_success":${candidate_success},"parity":${parity},"classification":"${classification}","accepted":${accepted}}
EOF
if ! ${accepted}; then
fail "paired Kubernetes option oracle failed; see ${OUT}/comparison.json and ${OUT}/raw"
fi
cat "${OUT}/comparison.json"