Every chainsaw `check` used the regex operator `=~` (`($stdout =~ 'X'): true`),
which chainsaw's expression engine rejects ('SyntaxError: Unknown char: ~') — so
EVERY such test failed on the assertion even when the behavior passed. Proven with
data-survival (R-STATE-01): the Longhorn canary genuinely SURVIVED pod destruction
(DATA_SURVIVED_OK) but the test FAILED on the `=~` syntax. Converted all 10 tests to
JMESPath `contains($stdout, 'X')` (data-survival's alternation → contains||contains).
data-survival now PASSES; the other 9 (later-phase components) are syntactically fixed
for when their phases deploy.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
113 lines
4.8 KiB
YAML
113 lines
4.8 KiB
YAML
# =============================================================================
|
|
# L3 FAULT-INJECTION — Data survival on rebuild (R-STATE-01) [RUNNABLE / self-skips]
|
|
# *SAFETY-CRITICAL*
|
|
# Catalog: docs/14 "Data survival (R-STATE-01, L3)"
|
|
# Spec: docs/04-storage-and-state.md
|
|
#
|
|
# GIVEN a stateful workload with written data on a longhorn-backed PVC,
|
|
# WHEN the workload is DESTROYED entirely and a fresh one re-bound to the PVC,
|
|
# THEN the data is intact.
|
|
#
|
|
# R-TEST-05: proves no-data-loss by actually destroying the workload (not a
|
|
# happy-path read). The PVC is kept; the pod is deleted and rebuilt.
|
|
#
|
|
# RUNNABLE / SELF-SKIPPING: Longhorn (T-2.1) is not on the base cluster yet.
|
|
# This test SKIPS CLEANLY (green) when no `longhorn` StorageClass exists, and
|
|
# runs the full destroy/rebuild survival check the moment one does — no edit
|
|
# needed. Hermetic: PVC + pods live in the chainsaw per-test namespace and are
|
|
# torn down with it.
|
|
# =============================================================================
|
|
apiVersion: chainsaw.kyverno.io/v1alpha1
|
|
kind: Test
|
|
metadata:
|
|
name: data-survival
|
|
labels:
|
|
conformance.home.k3s/R-STATE-01: "true"
|
|
annotations:
|
|
conformance.home.k3s/requirements: "R-STATE-01"
|
|
conformance.home.k3s/layer: "L3"
|
|
conformance.home.k3s/safety-critical: "true"
|
|
# executable => runs against a cluster; self-skips (green) until longhorn exists.
|
|
conformance.home.k3s/executable: "true"
|
|
conformance.home.k3s/intent: >-
|
|
Destroy the workload entirely and re-bind a fresh one to the same longhorn
|
|
PVC; written data survives. Skips cleanly until a longhorn StorageClass exists.
|
|
spec:
|
|
steps:
|
|
- name: data-survives-destroy-and-rebuild
|
|
try:
|
|
- script:
|
|
content: |
|
|
set -euo pipefail
|
|
NS="$NAMESPACE"
|
|
|
|
# -- Gate: skip cleanly (green) if the longhorn tier is not present yet. --
|
|
if ! kubectl get storageclass longhorn >/dev/null 2>&1; then
|
|
echo "SKIP: 'longhorn' StorageClass absent — data-survival not applicable on this cluster yet"
|
|
echo DATA_SURVIVAL_SKIPPED_OK
|
|
exit 0
|
|
fi
|
|
|
|
CANARY="conformance-canary-$(date +%s%N)"
|
|
|
|
# -- GIVEN: a PVC on longhorn + a writer pod that writes the canary. --
|
|
kubectl -n "$NS" apply -f - <<EOF
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: survivor
|
|
spec:
|
|
accessModes: [ReadWriteOnce]
|
|
storageClassName: longhorn
|
|
resources:
|
|
requests:
|
|
storage: 1Gi
|
|
EOF
|
|
|
|
apply_pod() {
|
|
kubectl -n "$NS" apply -f - <<EOF
|
|
apiVersion: v1
|
|
kind: Pod
|
|
metadata:
|
|
name: $1
|
|
spec:
|
|
restartPolicy: Never
|
|
containers:
|
|
- name: app
|
|
image: busybox:1.36.1
|
|
command: ["sh", "-c", "sleep 3600"]
|
|
volumeMounts:
|
|
- name: data
|
|
mountPath: /data
|
|
volumes:
|
|
- name: data
|
|
persistentVolumeClaim:
|
|
claimName: survivor
|
|
EOF
|
|
}
|
|
|
|
apply_pod writer
|
|
kubectl -n "$NS" wait --for=condition=Ready pod/writer --timeout=180s
|
|
printf '%s' "$CANARY" | kubectl -n "$NS" exec -i writer -- sh -c 'cat > /data/canary && sync'
|
|
echo "wrote canary: $(kubectl -n "$NS" exec writer -- cat /data/canary)"
|
|
|
|
# -- WHEN: FAULT INJECTION — destroy the workload ENTIRELY (keep the PVC). --
|
|
kubectl -n "$NS" delete pod writer --wait=true --timeout=180s
|
|
kubectl -n "$NS" get pvc survivor # PVC must still exist
|
|
|
|
# -- THEN: a fresh workload re-bound to the same PVC still sees the data. --
|
|
apply_pod reader
|
|
kubectl -n "$NS" wait --for=condition=Ready pod/reader --timeout=240s
|
|
GOT="$(kubectl -n "$NS" exec reader -- cat /data/canary)"
|
|
echo "recovered=$GOT expected=$CANARY"
|
|
[ "$GOT" = "$CANARY" ] && echo DATA_SURVIVED_OK
|
|
check:
|
|
# green on either a clean skip (no longhorn) or a proven survival.
|
|
(contains($stdout, 'DATA_SURVIVED_OK') || contains($stdout, 'DATA_SURVIVAL_SKIPPED_OK')): true
|
|
finally:
|
|
- script:
|
|
content: |
|
|
kubectl -n "$NAMESPACE" delete pod writer reader --ignore-not-found --wait=false || true
|
|
kubectl -n "$NAMESPACE" delete pvc survivor --ignore-not-found --wait=false || true
|
|
echo "cleanup best-effort; chainsaw namespace teardown finalizes"
|