#!/usr/bin/env bash set -euo pipefail script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) # shellcheck source=scripts/cluster/common.sh source "$script_dir/common.sh" drill=${1:-} confirmation=${2:-} [[ $confirmation == --execute ]] || { echo 'usage: failure-drill.sh {api|database|witness} --execute' >&2 exit 64 } load_cluster_env case $drill in api) started=$(date +%s) pod=$(cluster_ssh "$CLUSTER_NINGBO_HOST" \ "k3s kubectl get pods -n easyai -l app.kubernetes.io/name=easyai-api,easyai.io/site=ningbo -o jsonpath='{.items[0].metadata.name}'") cluster_ssh "$CLUSTER_NINGBO_HOST" \ "k3s kubectl delete pod '$pod' -n easyai --wait=false >/dev/null" for _ in $(seq 1 20); do if curl -fsS --max-time 3 --resolve "ai.51easyai.com:443:${CLUSTER_NINGBO_HOST#*@}" \ https://ai.51easyai.com/api/v1/readyz | grep -q '"ok":true'; then elapsed=$(( $(date +%s) - started )) break fi sleep 1 done [[ ${elapsed:-99} -le 10 ]] echo "api_failure_drill=PASS failover_seconds=$elapsed" ;; database) old_primary=$(cluster_ssh "$CLUSTER_NINGBO_HOST" \ "k3s kubectl get cluster easyai-postgres -n easyai -o jsonpath='{.status.currentPrimary}'") started=$(date +%s) cluster_ssh "$CLUSTER_NINGBO_HOST" \ "k3s kubectl delete pod '$old_primary' -n easyai --grace-period=0 --force >/dev/null" for _ in $(seq 1 150); do new_primary=$(cluster_ssh "$CLUSTER_NINGBO_HOST" \ "k3s kubectl get cluster easyai-postgres -n easyai -o jsonpath='{.status.currentPrimary}'") if [[ -n $new_primary && $new_primary != "$old_primary" ]]; then elapsed=$(( $(date +%s) - started )) break fi sleep 2 done [[ ${elapsed:-999} -le 300 ]] curl -fsS --max-time 10 https://ai.51easyai.com/api/v1/readyz | grep -q '"ok":true' cluster_ssh "$CLUSTER_NINGBO_HOST" bash -s -- "$new_primary" <<'REMOTE' set -euo pipefail primary=$1 cleanup_probe() { k3s kubectl exec -n easyai "$primary" -c postgres -- \ psql -X -U postgres -d easyai_ai_gateway -c \ "DELETE FROM system_settings WHERE setting_key='ha_acceptance_probe';" >/dev/null 2>&1 || true } trap cleanup_probe EXIT HUP INT TERM k3s kubectl exec -n easyai "$primary" -c postgres -- \ psql -X -v ON_ERROR_STOP=1 -U postgres -d easyai_ai_gateway -c \ "INSERT INTO system_settings(setting_key,value) VALUES('ha_acceptance_probe',jsonb_build_object('at',now())) ON CONFLICT(setting_key) DO UPDATE SET value=excluded.value,updated_at=now();" >/dev/null for _ in $(seq 1 900); do if [[ $(k3s kubectl get cluster easyai-postgres -n easyai -o jsonpath='{.status.readyInstances}') == 2 ]]; then replication=$(k3s kubectl exec -n easyai "$primary" -c postgres -- \ psql -X -U postgres -d easyai_ai_gateway -At -c \ "SELECT count(*) FROM pg_stat_replication WHERE sync_state IN ('sync','quorum');") [[ $replication == 1 ]] && break fi sleep 2 done [[ ${replication:-0} == 1 ]] k3s kubectl exec -n easyai "$primary" -c postgres -- \ psql -X -v ON_ERROR_STOP=1 -U postgres -d easyai_ai_gateway -c \ "DELETE FROM system_settings WHERE setting_key='ha_acceptance_probe';" >/dev/null trap - EXIT HUP INT TERM REMOTE echo "database_failure_drill=PASS promotion_seconds=$elapsed old_primary=$old_primary new_primary=$new_primary" ;; witness) cluster_ssh "$CLUSTER_LOS_ANGELES_HOST" 'systemctl stop k3s' trap 'cluster_ssh "$CLUSTER_LOS_ANGELES_HOST" "systemctl start k3s" || true' EXIT HUP INT TERM cluster_ssh "$CLUSTER_NINGBO_HOST" \ "k3s kubectl get --raw='/readyz?verbose' | grep -q '\\[+\\]etcd ok'" curl -fsS --max-time 10 https://ai.51easyai.com/api/v1/readyz | grep -q '"ok":true' cluster_ssh "$CLUSTER_LOS_ANGELES_HOST" 'systemctl start k3s' cluster_ssh "$CLUSTER_NINGBO_HOST" \ 'k3s kubectl wait --for=condition=Ready node/easyai-los-angeles --timeout=180s' trap - EXIT HUP INT TERM echo 'witness_failure_drill=PASS quorum_retained=true' ;; *) echo 'drill must be api, database, or witness' >&2 exit 64 ;; esac