Created
April 16, 2026 17:15
-
-
Save mvasilenko/96c016b73083fd1f2dc678a6272cb5b5 to your computer and use it in GitHub Desktop.
shell script for testing Dragonfly operator CRD upgrade with custom probes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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