first commit
This commit is contained in:
Executable
+212
@@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# test-inventory.sh — uji validator inventory multi-host (Fase 1)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Menguji `lib/cluster/inventory.py` secara negatif (harus DITOLAK) dan positif
|
||||
# (harus DITERIMA). Sengaja banyak kasus negatif: validator yang selalu setuju
|
||||
# tidak berguna — setiap aturan penempatan R10 §4.3 / R11 §3.2 diuji terpisah.
|
||||
#
|
||||
# Jalankan: ./scripts/test-inventory.sh
|
||||
# =============================================================================
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DEPLOY_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
INV="${DEPLOY_DIR}/lib/cluster/inventory.py"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "${TMP}"' EXIT
|
||||
|
||||
GRN=$'\e[32m'; RED=$'\e[31m'; BLD=$'\e[1m'; NC=$'\e[0m'
|
||||
PASS_N=0; FAIL_N=0
|
||||
|
||||
check() { # <harapan: reject|accept> <nama> <yaml>
|
||||
local expect="$1" name="$2" yaml="$3" rc out
|
||||
printf '%b' "$yaml" > "${TMP}/t.yml"
|
||||
out="$(python3 "${INV}" validate "${TMP}/t.yml" 2>&1)"; rc=$?
|
||||
if [[ "${expect}" == "reject" ]]; then
|
||||
if [[ ${rc} -ne 0 && "${out}" != *"Traceback"* ]]; then
|
||||
printf " ${GRN}✅ ditolak${NC} %-42s → %s\n" "${name}" "$(echo "${out}" | sed -n '2p' | cut -c1-66)"
|
||||
PASS_N=$((PASS_N + 1))
|
||||
else
|
||||
printf " ${RED}❌ MASALAH${NC} %-42s rc=%s (harus ditolak tanpa traceback)\n" "${name}" "${rc}"
|
||||
echo "${out}" | head -4 | sed 's/^/ /'
|
||||
FAIL_N=$((FAIL_N + 1))
|
||||
fi
|
||||
else
|
||||
if [[ ${rc} -eq 0 ]]; then
|
||||
printf " ${GRN}✅ diterima${NC} %-41s\n" "${name}"
|
||||
PASS_N=$((PASS_N + 1))
|
||||
else
|
||||
printf " ${RED}❌ DITOLAK (harusnya valid)${NC} %s\n" "${name}"
|
||||
echo "${out}" | head -4 | sed 's/^/ /'
|
||||
FAIL_N=$((FAIL_N + 1))
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
echo -e "${BLD}Uji validator inventory (lib/cluster/inventory.py)${NC}"
|
||||
echo
|
||||
echo "── harus DITOLAK ──"
|
||||
check reject "state dipecah ke 2 host" 'hosts:\n a:\n address: 1.1.1.1\n tiers: [state]\n b:\n address: 2.2.2.2\n tiers: [state, engine]\n'
|
||||
check reject "coord di 2 host" 'hosts:\n a:\n address: 1.1.1.1\n tiers: [coord, engine]\n b:\n address: 2.2.2.2\n tiers: [coord, state]\n'
|
||||
check reject "state + engine satu host (anti-affinity)" 'hosts:\n a:\n address: 1.1.1.1\n tiers: [state, engine, coord]\n'
|
||||
check reject "preset tak dikenal" 'preset: a9\nhosts:\n a:\n address: 1.1.1.1\n tiers: [state]\n b:\n address: 2.2.2.2\n tiers: [engine, coord]\n'
|
||||
check reject "tier tak dikenal" 'hosts:\n a:\n address: 1.1.1.1\n tiers: [state, engine, coord, banjar]\n'
|
||||
check reject "address kosong" 'hosts:\n a:\n tiers: [state, engine, coord]\n'
|
||||
check reject "components.host tidak ada di hosts" 'hosts:\n a:\n address: 1.1.1.1\n tiers: [state]\n b:\n address: 2.2.2.2\n tiers: [engine, coord]\ncomponents:\n grafana: { host: tidak-ada }\n'
|
||||
check reject "tanpa host bertier engine" 'hosts:\n a:\n address: 1.1.1.1\n tiers: [state, coord]\n'
|
||||
check reject "hosts kosong" 'preset: a3\nhosts: {}\n'
|
||||
check reject "YAML rusak" 'hosts:\n a: [ini: bukan: yaml\n'
|
||||
|
||||
echo
|
||||
echo "── harus DITOLAK (TLS & domain) ──"
|
||||
check reject "tls.mode tak dikenal" 'tls:
|
||||
mode: tls
|
||||
cluster:
|
||||
domain: sicabe.com
|
||||
preset: a3
|
||||
hosts:
|
||||
control: { address: 1.1.1.1, tiers: [identity, policy, pipeline, governance] }
|
||||
compute: { address: 1.1.1.2, tiers: [coord, engine] }
|
||||
state: { address: 1.1.1.3, tiers: [state] }
|
||||
'
|
||||
check reject "edge tanpa domain" 'tls:
|
||||
mode: edge
|
||||
preset: a3
|
||||
hosts:
|
||||
control: { address: 1.1.1.1, tiers: [identity, policy, pipeline, governance] }
|
||||
compute: { address: 1.1.1.2, tiers: [coord, engine] }
|
||||
state: { address: 1.1.1.3, tiers: [state] }
|
||||
'
|
||||
check reject "domain memuat skema" 'tls:
|
||||
mode: edge
|
||||
cluster:
|
||||
domain: https://sicabe.com
|
||||
preset: a3
|
||||
hosts:
|
||||
control: { address: 1.1.1.1, tiers: [identity, policy, pipeline, governance] }
|
||||
compute: { address: 1.1.1.2, tiers: [coord, engine] }
|
||||
state: { address: 1.1.1.3, tiers: [state] }
|
||||
'
|
||||
check reject "domain tanpa titik" 'tls:
|
||||
mode: full
|
||||
cluster:
|
||||
domain: sicabe
|
||||
preset: a3
|
||||
hosts:
|
||||
control: { address: 1.1.1.1, tiers: [identity, policy, pipeline, governance] }
|
||||
compute: { address: 1.1.1.2, tiers: [coord, engine] }
|
||||
state: { address: 1.1.1.3, tiers: [state] }
|
||||
'
|
||||
|
||||
echo
|
||||
echo "── harus DITERIMA ──"
|
||||
check accept "cluster.example.yml (preset a3)" "$(cat "${DEPLOY_DIR}/cluster.example.yml")"
|
||||
check accept "edge + domain sah" 'tls:
|
||||
mode: edge
|
||||
cluster:
|
||||
domain: sicabe.com
|
||||
preset: a3
|
||||
hosts:
|
||||
control: { address: 1.1.1.1, tiers: [identity, policy, pipeline, governance] }
|
||||
compute: { address: 1.1.1.2, tiers: [coord, engine] }
|
||||
state: { address: 1.1.1.3, tiers: [state] }
|
||||
'
|
||||
check accept "preset b5 (5 host, tanpa tiers eksplisit)" 'preset: b5\nhosts:\n control: { address: 10.0.0.10 }\n coord: { address: 10.0.0.11 }\n engine-1: { address: 10.0.0.12 }\n engine-2: { address: 10.0.0.13 }\n state: { address: 10.0.0.14 }\n'
|
||||
|
||||
echo
|
||||
echo "── distribusi penempatan (plan) ──"
|
||||
plan_out="$(python3 "${INV}" plan "${DEPLOY_DIR}/cluster.example.yml" 2>&1)"
|
||||
plan_check() { # <nama> <host> <service-yang-harus-ada>
|
||||
local name="$1" host="$2" svc="$3"
|
||||
if echo "${plan_out}" | python3 -c "
|
||||
import json,sys
|
||||
p=json.load(sys.stdin)
|
||||
sys.exit(0 if '$svc' in p.get('$host',{}).get('services',[]) else 1)"; then
|
||||
printf " ${GRN}✅${NC} %-46s %s ∈ %s\n" "$name" "$svc" "$host"; PASS_N=$((PASS_N + 1))
|
||||
else
|
||||
printf " ${RED}❌${NC} %-46s %s TIDAK di %s\n" "$name" "$svc" "$host"; FAIL_N=$((FAIL_N + 1))
|
||||
fi
|
||||
}
|
||||
plan_check "a3: coordinator di compute" compute trino-coordinator
|
||||
plan_check "a3: worker di compute" compute trino-worker
|
||||
plan_check "a3: postgres di state" state postgres
|
||||
plan_check "a3: minio di state" state minio
|
||||
plan_check "a3: keycloak di control" control keycloak
|
||||
plan_check "a3: grafana di control" control grafana
|
||||
plan_check "a3: airflow-webserver di control" control airflow-webserver
|
||||
plan_not_in() { # <nama> <host> <service-yang-harus-TIDAK-ada>
|
||||
local name="$1" host="$2" svc="$3"
|
||||
if echo "${plan_out}" | python3 -c "
|
||||
import json,sys
|
||||
p=json.load(sys.stdin)
|
||||
sys.exit(0 if '$svc' not in p.get('$host',{}).get('services',[]) else 1)"; then
|
||||
printf " ${GRN}✅${NC} %-46s %s ∉ %s\n" "${name}" "${svc}" "${host}"; PASS_N=$((PASS_N + 1))
|
||||
else
|
||||
printf " ${RED}❌${NC} %-46s %s ADA di %s\n" "${name}" "${svc}" "${host}"; FAIL_N=$((FAIL_N + 1))
|
||||
fi
|
||||
}
|
||||
plan_not_in "a3: postgres tidak di control" control postgres
|
||||
plan_not_in "a3: trino-worker tidak di control" control trino-worker
|
||||
plan_not_in "a3: dremio off (default) tidak ditempatkan" compute dremio
|
||||
|
||||
# b5: tier engine >1 host → worker & CN harus muncul di KEDUA host engine
|
||||
printf 'preset: b5\nhosts:\n control: { address: 10.0.0.10 }\n coord: { address: 10.0.0.11 }\n engine-1: { address: 10.0.0.12 }\n engine-2: { address: 10.0.0.13 }\n state: { address: 10.0.0.14 }\n' > "${TMP}/b5.yml"
|
||||
b5_out="$(python3 "${INV}" plan "${TMP}/b5.yml" 2>&1)"
|
||||
b5_check() { # <host> <service>
|
||||
if echo "${b5_out}" | python3 -c "
|
||||
import json,sys
|
||||
p=json.load(sys.stdin)
|
||||
sys.exit(0 if '$2' in p.get('$1',{}).get('services',[]) else 1)"; then
|
||||
printf " ${GRN}✅${NC} b5: %-14s %s\n" "$2 ∈ $1" ""; PASS_N=$((PASS_N + 1))
|
||||
else
|
||||
printf " ${RED}❌${NC} b5: %s TIDAK di %s\n" "$2" "$1"; FAIL_N=$((FAIL_N + 1))
|
||||
fi
|
||||
}
|
||||
b5_check engine-1 trino-worker
|
||||
b5_check engine-2 trino-worker
|
||||
b5_check engine-1 starrocks-cn
|
||||
b5_check engine-2 starrocks-cn
|
||||
b5_check coord trino-coordinator
|
||||
if echo "${b5_out}" | python3 -c "
|
||||
import json,sys
|
||||
p=json.load(sys.stdin)
|
||||
sys.exit(0 if 'trino-worker' not in p.get('coord',{}).get('services',[]) else 1)"; then
|
||||
printf " ${GRN}✅${NC} b5: trino-worker tidak di coord (coord ⊥ engine)\n"; PASS_N=$((PASS_N + 1))
|
||||
else
|
||||
printf " ${RED}❌${NC} b5: trino-worker ada di coord\n"; FAIL_N=$((FAIL_N + 1))
|
||||
fi
|
||||
|
||||
echo "── URL browser-facing turunan (tls.mode) ──"
|
||||
url_case() { # <nama> <yaml> <harapan: kosong|ada> <var>
|
||||
local name="$1" yaml="$2" expect="$3" var="$4" out
|
||||
printf '%b' "$yaml" > "${TMP}/tls.yml"
|
||||
out="$(python3 -c "
|
||||
import sys; sys.path.insert(0,'${DEPLOY_DIR}/lib/cluster')
|
||||
import inventory
|
||||
doc = inventory.apply_preset(inventory.load('${TMP}/tls.yml'))
|
||||
print('|'.join(f'{k}={v}' for k,v in sorted(inventory.public_urls(doc).items())))" 2>/dev/null)"
|
||||
if [[ "${expect}" == "kosong" ]]; then
|
||||
if [[ -z "${out}" ]]; then printf " ${GRN}✅${NC} %s\n" "${name}"; PASS_N=$((PASS_N+1))
|
||||
else printf " ${RED}❌${NC} %s — seharusnya kosong, dapat: %s\n" "${name}" "${out}"; FAIL_N=$((FAIL_N+1)); fi
|
||||
else
|
||||
if [[ "${out}" == *"${var}=${expect}"* ]]; then printf " ${GRN}✅${NC} %s\n" "${name}"; PASS_N=$((PASS_N+1))
|
||||
else printf " ${RED}❌${NC} %s — tidak menemukan %s=%s di: %s\n" "${name}" "${var}" "${expect}" "${out}"; FAIL_N=$((FAIL_N+1)); fi
|
||||
fi
|
||||
}
|
||||
A3='tls:\n mode: none\ncluster:\n domain: sicabe.com\npreset: a3\nhosts:\n control: { address: 1.1.1.1, tiers: [identity, policy, pipeline, governance] }\n compute: { address: 1.1.1.2, tiers: [coord, engine] }\n state: { address: 1.1.1.3, tiers: [state] }\n'
|
||||
EDGE='tls:\n mode: edge\ncluster:\n domain: sicabe.com\npreset: a3\nhosts:\n control: { address: 1.1.1.1, tiers: [identity, policy, pipeline, governance] }\n compute: { address: 1.1.1.2, tiers: [coord, engine] }\n state: { address: 1.1.1.3, tiers: [state] }\n'
|
||||
FULL='tls:\n mode: full\ncluster:\n domain: contoh.org\npreset: a3\nhosts:\n control: { address: 1.1.1.1, tiers: [identity, policy, pipeline, governance] }\n compute: { address: 1.1.1.2, tiers: [coord, engine] }\n state: { address: 1.1.1.3, tiers: [state] }\n'
|
||||
url_case "mode none -> TIDAK ada URL publik" "${A3}" kosong ""
|
||||
url_case "mode edge -> Keycloak https" "${EDGE}" "https://kc.sicabe.com" "KEYCLOAK_PUBLIC_URL"
|
||||
url_case "mode edge -> Grafana https" "${EDGE}" "https://grafana.sicabe.com" "GRAFANA_PUBLIC_URL"
|
||||
url_case "mode edge -> MinIO console https" "${EDGE}" "https://minio.sicabe.com" "MINIO_CONSOLE_PUBLIC_URL"
|
||||
url_case "mode full -> memakai domain sendiri" "${FULL}" "https://om.contoh.org" "OPENMETADATA_PUBLIC_URL"
|
||||
|
||||
echo
|
||||
if [[ ${FAIL_N} -eq 0 ]]; then
|
||||
echo -e " ${GRN}${BLD}SEMUA LULUS${NC} (${PASS_N} cek)"
|
||||
exit 0
|
||||
fi
|
||||
echo -e " ${RED}${BLD}GAGAL${NC}: ${FAIL_N} bermasalah · ${PASS_N} lulus"
|
||||
exit 1
|
||||
Reference in New Issue
Block a user