Skip to content

Instantly share code, notes, and snippets.

@mvasilenko
Created April 16, 2026 17:15
Show Gist options
  • Select an option

  • Save mvasilenko/96c016b73083fd1f2dc678a6272cb5b5 to your computer and use it in GitHub Desktop.

Select an option

Save mvasilenko/96c016b73083fd1f2dc678a6272cb5b5 to your computer and use it in GitHub Desktop.
shell script for testing Dragonfly operator CRD upgrade with custom probes
#!/usr/bin/env bash
# Verifies operator upgrade from upstream to fork is a no-op for existing CRs,
# then tests opt-in custom probe ConfigMaps.
#
# Usage:
# [UPSTREAM_VERSION=v1.5.0] [FORK_VERSION=v1.5.1] bash tests/dragonfly/test-upgrade-probes.sh [--cleanup]
set -euo pipefail
CONTEXT="${CONTEXT:-k3d-k3s-default}"
DRAGONFLY_NS="${DRAGONFLY_NS:-default}"
K="kubectl --context $CONTEXT"
CR_NAME="dragonfly-upgrade-test"
OPERATOR_NS="dragonfly-operator-system"
OPERATOR_DEPLOY="dragonfly-operator-controller-manager"
UPSTREAM_VERSION="${UPSTREAM_VERSION:-v1.5.0}"
FORK_VERSION="${FORK_VERSION:-v1.5.1}"
UPSTREAM_REPO="https://raw.githubusercontent.com/dragonflydb/dragonfly-operator"
FORK_REPO="https://raw.githubusercontent.com/mvasilenko/dragonfly-operator"
TMPDIR=$(mktemp -d)
UPSTREAM_MANIFEST="${TMPDIR}/upstream-operator.yaml"
FORK_MANIFEST="${TMPDIR}/fork-operator.yaml"
DO_CLEANUP=false
for arg in "$@"; do [[ "$arg" == "--cleanup" ]] && DO_CLEANUP=true; done
cleanup() {
if $DO_CLEANUP; then
echo "=== Cleanup ==="
$K delete dragonfly "$CR_NAME" -n "$DRAGONFLY_NS" --ignore-not-found --timeout=60s || true
for suffix in custom-liveness custom-readiness custom-startup; do
$K -n "$DRAGONFLY_NS" delete configmap "${CR_NAME}-${suffix}" --ignore-not-found || true
done
$K delete -f "$FORK_MANIFEST" --ignore-not-found || true
$K delete -f "$UPSTREAM_MANIFEST" --ignore-not-found || true
fi
rm -rf "$TMPDIR"
}
trap cleanup EXIT
trap 'echo "FAIL at line $LINENO"; exit 1' ERR
wait_pod() {
local pod="$1" timeout="${2:-180}"
local j=0
until $K -n "$DRAGONFLY_NS" get pod/"$pod" &>/dev/null; do
j=$(( j + 1 ))
[[ $j -gt $timeout ]] && { echo " FAIL: pod $pod did not appear within ${timeout}s"; exit 1; }
sleep 1
done
$K -n "$DRAGONFLY_NS" wait pod/"$pod" --for=condition=Ready --timeout="${timeout}s"
}
assert_ping() {
local pod="$1"
local result=$($K -n "$DRAGONFLY_NS" exec "$pod" -- redis-cli PING 2>/dev/null)
[[ "$result" == "PONG" ]] || { echo " FAIL: PING on $pod returned '$result'"; exit 1; }
}
echo "=== Downloading manifests ==="
curl -fsSL "${UPSTREAM_REPO}/${UPSTREAM_VERSION}/manifests/dragonfly-operator.yaml" -o "$UPSTREAM_MANIFEST"
curl -fsSL "${FORK_REPO}/${FORK_VERSION}/manifests/dragonfly-operator.yaml" -o "$FORK_MANIFEST"
echo " OK: upstream ${UPSTREAM_VERSION}, fork ${FORK_VERSION}"
$K delete dragonfly "$CR_NAME" -n "$DRAGONFLY_NS" --ignore-not-found --timeout=60s 2>/dev/null || true
$K delete -f "$FORK_MANIFEST" --ignore-not-found 2>/dev/null || true
echo "=== Phase 1: Install upstream ${UPSTREAM_VERSION} ==="
$K apply --server-side --force-conflicts -f "$UPSTREAM_MANIFEST"
$K wait crd/dragonflies.dragonflydb.io --for=condition=Established --timeout=60s
$K -n "$OPERATOR_NS" rollout status deployment/"$OPERATOR_DEPLOY" --timeout=120s
echo "=== Phase 2: Create Dragonfly CR ==="
$K apply -n "$DRAGONFLY_NS" -f - <<EOF
apiVersion: dragonflydb.io/v1alpha1
kind: Dragonfly
metadata:
name: ${CR_NAME}
spec:
replicas: 2
args:
- "--maxmemory=256mb"
resources:
requests: { cpu: 100m, memory: 512Mi }
limits: { cpu: "1", memory: 512Mi }
networkPolicyEnabled: false
EOF
for i in 0 1; do wait_pod "${CR_NAME}-${i}"; done
echo " OK: pods Ready"
BASELINE_UID_0=$($K -n "$DRAGONFLY_NS" get pod "${CR_NAME}-0" -o jsonpath='{.metadata.uid}')
BASELINE_UID_1=$($K -n "$DRAGONFLY_NS" get pod "${CR_NAME}-1" -o jsonpath='{.metadata.uid}')
BASELINE_RESTARTS_0=$($K -n "$DRAGONFLY_NS" get pod "${CR_NAME}-0" -o jsonpath='{.status.containerStatuses[0].restartCount}')
BASELINE_RESTARTS_1=$($K -n "$DRAGONFLY_NS" get pod "${CR_NAME}-1" -o jsonpath='{.status.containerStatuses[0].restartCount}')
assert_ping "${CR_NAME}-0"
echo "=== Phase 3: Upgrade to fork ${FORK_VERSION} ==="
$K apply --server-side --force-conflicts -f "$FORK_MANIFEST"
$K -n "$OPERATOR_NS" rollout status deployment/"$OPERATOR_DEPLOY" --timeout=120s
echo "=== Phase 4: Verify backward compatibility ==="
for i in 0 1; do
pod="${CR_NAME}-${i}"
CURRENT_UID=$($K -n "$DRAGONFLY_NS" get pod "$pod" -o jsonpath='{.metadata.uid}')
CURRENT_RESTARTS=$($K -n "$DRAGONFLY_NS" get pod "$pod" -o jsonpath='{.status.containerStatuses[0].restartCount}')
eval "EXPECTED_UID=\$BASELINE_UID_${i}"
eval "EXPECTED_RESTARTS=\$BASELINE_RESTARTS_${i}"
[[ "$CURRENT_UID" == "$EXPECTED_UID" ]] || { echo " FAIL: $pod recreated"; exit 1; }
[[ "$CURRENT_RESTARTS" == "$EXPECTED_RESTARTS" ]] || { echo " FAIL: $pod restarted"; exit 1; }
done
echo " OK: pods unchanged"
READINESS_CMD=$($K -n "$DRAGONFLY_NS" get sts "$CR_NAME" \
-o jsonpath='{.spec.template.spec.containers[0].readinessProbe.exec.command}')
[[ "$READINESS_CMD" == *"healthcheck.sh"* ]] || { echo " FAIL: probes changed unexpectedly"; exit 1; }
echo " OK: probes unchanged"
assert_ping "${CR_NAME}-0"
echo "=== Phase 5: Patch CR with custom probe ConfigMaps ==="
LIVENESS_CM="${CR_NAME}-custom-liveness"
READINESS_CM="${CR_NAME}-custom-readiness"
STARTUP_CM="${CR_NAME}-custom-startup"
PROBE_YAML=$(mktemp)
cat > "$PROBE_YAML" <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: ${LIVENESS_CM}
namespace: ${DRAGONFLY_NS}
data:
liveness-check.sh: |
#!/bin/sh
HOST="localhost"
PORT=\${HEALTHCHECK_PORT:-9999}
RESPONSE=\$(timeout 4 redis-cli -h "\$HOST" -p "\$PORT" PING 2>/dev/null)
case "\$RESPONSE" in
PONG|*LOADING*) exit 0 ;;
*) exit 1 ;;
esac
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ${READINESS_CM}
namespace: ${DRAGONFLY_NS}
data:
readiness-check.sh: |
#!/bin/sh
HOST="localhost"
PORT=\${HEALTHCHECK_PORT:-9999}
RESPONSE=\$(timeout 4 redis-cli -h "\$HOST" -p "\$PORT" INFO persistence 2>/dev/null)
if echo "\$RESPONSE" | grep -q "^loading:0"; then
exit 0
fi
exit 1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ${STARTUP_CM}
namespace: ${DRAGONFLY_NS}
data:
startup-check.sh: |
#!/bin/sh
HOST="localhost"
PORT=\${HEALTHCHECK_PORT:-9999}
RESPONSE=\$(timeout 4 redis-cli -h "\$HOST" -p "\$PORT" PING 2>/dev/null)
case "\$RESPONSE" in
PONG|*LOADING*) exit 0 ;;
*) exit 1 ;;
esac
EOF
$K apply -f "$PROBE_YAML"
rm -f "$PROBE_YAML"
$K -n "$DRAGONFLY_NS" patch dragonfly "$CR_NAME" --type merge -p "{
\"spec\": {
\"customLivenessProbeConfigMap\": {\"name\": \"${LIVENESS_CM}\"},
\"customReadinessProbeConfigMap\": {\"name\": \"${READINESS_CM}\"},
\"customStartupProbeConfigMap\": {\"name\": \"${STARTUP_CM}\"}
}
}"
# Operator detects spec change — wait for StatefulSet template to be patched with custom probes
echo " Waiting for operator to update StatefulSet template..."
j=0
while true; do
READINESS_CMD=$($K -n "$DRAGONFLY_NS" get sts "$CR_NAME" \
-o jsonpath='{.spec.template.spec.containers[0].readinessProbe.exec.command}' 2>/dev/null)
[[ "$READINESS_CMD" == *"/etc/dragonfly/probes/"* ]] && break
j=$(( j + 1 ))
[[ $j -gt 120 ]] && { echo " FAIL: probes not updated within 120s"; exit 1; }
sleep 1
done
echo " OK: StatefulSet probes updated"
# Wait for all pods to be on the latest revision and ready
echo " Waiting for pods to finish rolling..."
j=0
while true; do
UPDATE_REV=$($K -n "$DRAGONFLY_NS" get sts "$CR_NAME" -o jsonpath='{.status.updateRevision}')
ALL_MATCH=true
for i in 0 1; do
POD_REV=$($K -n "$DRAGONFLY_NS" get pod "${CR_NAME}-${i}" -o jsonpath='{.metadata.labels.controller-revision-hash}' 2>/dev/null)
POD_READY=$($K -n "$DRAGONFLY_NS" get pod "${CR_NAME}-${i}" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null)
[[ "$POD_REV" == "$UPDATE_REV" && "$POD_READY" == "True" ]] || ALL_MATCH=false
done
$ALL_MATCH && break
j=$(( j + 1 ))
[[ $j -gt 180 ]] && { echo " FAIL: rollout did not complete within 180s"; exit 1; }
sleep 1
done
echo " OK: pods rolled by operator"
POD_VOLUMES=$($K -n "$DRAGONFLY_NS" get pod "${CR_NAME}-0" -o jsonpath='{.spec.volumes[*].name}')
for vol_name in liveness-probe readiness-probe startup-probe; do
[[ "$POD_VOLUMES" == *"$vol_name"* ]] || { echo " FAIL: missing volume $vol_name"; exit 1; }
done
LIVENESS_CONTENT=$($K -n "$DRAGONFLY_NS" exec "${CR_NAME}-0" -- cat /etc/dragonfly/probes/liveness-check.sh 2>/dev/null)
[[ "$LIVENESS_CONTENT" == *"timeout 4"* ]] || { echo " FAIL: liveness script missing 'timeout 4'"; exit 1; }
echo " OK: custom probes mounted (timeout 4s)"
assert_ping "${CR_NAME}-0"
echo ""
echo "=== ALL PHASES PASSED ==="
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment