#!/usr/bin/env bash # ============================================================================= # eBdesk SiCABE — Test & Verification Script # ============================================================================= # Sesuai Bab 6 dokumen Deployment Guide: # 6.1 Daftar Uji Verifikasi per Lapisan # 6.2 End-to-End Smoke Test (Lintas Lapisan) # 6.3 Performance Baseline Test # # Test Categories: # L1 = Ingestion Layer (Airflow, Sling) # L2 = Storage Layer (MinIO, Iceberg, Nessie) # L3 = Processing Layer (Trino, OPA, dbt) # L4 = Serving Layer (StarRocks shared-data, Dremio) # L5 = Governance Layer (Keycloak, OpenMetadata, Great Expectations, Grafana) # E2E = End-to-End Smoke Test (Lintas Lapisan) # PERF = Performance Baseline # # Usage: # ./test-verification.sh # Jalankan semua test # ./test-verification.sh --layer L1 # Test layer tertentu (L1-L5) # ./test-verification.sh --layer E2E # End-to-End smoke test # ./test-verification.sh --layer PERF # Performance baseline # ./test-verification.sh --list # Daftar semua test # ./test-verification.sh --report # Tampilkan laporan test terakhir # ============================================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "${SCRIPT_DIR}" # ─── Shared Library ────────────────────────────────────────────────────────── source "${SCRIPT_DIR}/lib/common.sh" source "${SCRIPT_DIR}/config/environment.defaults.sh" # Multi-host (Fase 2c): --env-file memakai env milik SATU HOST # (generated/host-/.env) sehingga pemeriksaan berjalan sebagaimana host itu # melihatnya — endpoint container menunjuk alamat host, bukan localhost/12xxx. # Dipindai lebih dulu karena pemuatan env terjadi sebelum parsing argumen. ENV_FILE_OVERRIDE="" _prev="" for _arg in "$@"; do [[ "${_prev}" == "--env-file" ]] && ENV_FILE_OVERRIDE="${_arg}" _prev="${_arg}" done ENV_FILE="${ENV_FILE_OVERRIDE:-${SCRIPT_DIR}/.env}" [[ "${ENV_FILE}" != /* ]] && ENV_FILE="${SCRIPT_DIR}/${ENV_FILE}" if [[ -n "${ENV_FILE_OVERRIDE}" && ! -f "${ENV_FILE}" ]]; then echo -e "${ERR} Berkas env tidak ditemukan: ${ENV_FILE}" exit 1 fi if [[ -f "${ENV_FILE}" ]]; then set -a; source "${ENV_FILE}"; set +a [[ -n "${ENV_FILE_OVERRIDE}" ]] && echo -e "${INFO} env-file: ${ENV_FILE} (mode host)" fi # ─── Endpoint E2E (sadar host) ────────────────────────────────────────────── # `sicabe_local_url`/`sicabe_local_host` (lib/common.sh): single-host → localhost; # multi-host (bundle memuat SICABE_HOST_NAME) → alamat host PEMILIK. Sebelumnya blok # E2E memakai `localhost` hardcode → GAGAL di multi-host karena Trino/StarRocks/Airflow # berada di host lain. E2E_TRINO_URL="$(sicabe_local_url https TRINO_HOST "${TRINO_HTTPS_PORT:-12443}" TRINO_URL)" E2E_AIRFLOW_URL="$(sicabe_local_url http AIRFLOW_WEBSERVER_HOST "${AIRFLOW_PORT:-12082}" AIRFLOW_URL)" E2E_OM_URL="$(sicabe_local_url http OPENMETADATA_HOST "${OPENMETADATA_PORT:-12085}" OPENMETADATA_URL)" E2E_GRAFANA_URL="$(sicabe_local_url http GRAFANA_HOST "${GRAFANA_PORT:-12300}" GRAFANA_URL)" # StarRocks via MySQL/TCP: `localhost` memicu unix-socket → pakai 127.0.0.1 di single-host. E2E_SR_HOST="$(sicabe_local_host STARROCKS_FE_HOST)" [[ "${E2E_SR_HOST}" == "localhost" ]] && E2E_SR_HOST="127.0.0.1" # ─── Report File ───────────────────────────────────────────────────────────── REPORT_FILE="${SCRIPT_DIR}/output/test-report-$(date +%Y%m%d-%H%M%S).log" mkdir -p "${SCRIPT_DIR}/output" PASS_COUNT=0 FAIL_COUNT=0 SKIP_COUNT=0 TEST_LOG="" # ─── Test Helpers ──────────────────────────────────────────────────────────── print_section() { local title="$1" echo "" | tee -a "${TEST_LOG}" echo -e "${BOLD}${BLUE}═══════════════════════════════════════════════════════${NC}" | tee -a "${TEST_LOG}" echo -e "${BOLD} ${title}${NC}" | tee -a "${TEST_LOG}" echo -e "${BOLD}${BLUE}═══════════════════════════════════════════════════════${NC}" | tee -a "${TEST_LOG}" echo "" | tee -a "${TEST_LOG}" } test_pass() { local test_name="$1" local detail="${2:-}" PASS_COUNT=$((PASS_COUNT + 1)) echo -e " ${PASS} ${test_name}" | tee -a "${TEST_LOG}" if [[ -n "${detail}" ]]; then echo " ${detail}" | tee -a "${TEST_LOG}" fi return 0 } test_fail() { local test_name="$1" local detail="${2:-}" FAIL_COUNT=$((FAIL_COUNT + 1)) echo -e " ${FAIL} ${test_name}" | tee -a "${TEST_LOG}" if [[ -n "${detail}" ]]; then echo " ${detail}" | tee -a "${TEST_LOG}" fi return 0 } test_skip() { local test_name="$1" local reason="${2:-}" SKIP_COUNT=$((SKIP_COUNT + 1)) echo -e " ${YELLOW}[SKIP]${NC} ${test_name}" | tee -a "${TEST_LOG}" if [[ -n "${reason}" ]]; then echo " ${reason}" | tee -a "${TEST_LOG}" fi return 0 } check_url() { # [budget-detik] — POLL sampai budget habis. Verifikasi dijalankan tepat # setelah fresh install, saat banyak service masih warmup (mis. container # sicabe-airflow-webserver menjalankan `airflow api-server` dgn healthcheck # start_period 30s + interval 15s). Sebelumnya HANYA 1 percobaan (arg ke-3 dipakai # sebagai --max-time curl) → service yang belum siap dilaporkan FAIL padahal sehat. local name="$1" local url="$2" local budget="${3:-10}" local deadline=$(( $(date +%s) + budget )) while :; do if curl -skf --max-time 5 "${url}" >/dev/null 2>&1; then test_pass "${name}" "${url}" return 0 fi if (( $(date +%s) >= deadline )); then test_fail "${name}" "Tidak dapat mengakses ${url} (setelah ${budget}s; masih warmup?)" return 1 fi sleep 2 done } # ─── Komponen existing (dikelola sistem lain) ──────────────────────────────── # Bila endpoint komponen diarahkan ke luar (.env), container-nya memang TIDAK ada # di host ini → uji container jadi SKIP, bukan FAIL. Logika sama dengan install.sh. external_services() { local out=" ${EXTERNAL_SERVICES:-} " if [[ -n "${KEYCLOAK_URL:-}" && "${KEYCLOAK_URL}" != "http://localhost:${KEYCLOAK_PORT:-12080}" ]]; then out+=" keycloak "; fi if [[ -n "${LDAP_URL:-}" && "${LDAP_URL}" != "ldap://openldap:389" ]]; then out+=" openldap "; fi if [[ -n "${S3_ENDPOINT:-}" && "${S3_ENDPOINT}" != "http://minio:9000" ]]; then out+=" minio minio-init "; fi if [[ -n "${NESSIE_URL:-}" && "${NESSIE_URL}" != "http://nessie:19120" ]]; then out+=" nessie "; fi if [[ -n "${TRINO_URL:-}" && "${TRINO_URL}" != "https://localhost:${TRINO_HTTPS_PORT:-12443}" ]]; then out+=" trino "; fi if [[ -n "${OPA_URL:-}" && "${OPA_URL}" != "http://localhost:${OPA_PORT:-12181}" ]]; then out+=" opa "; fi if [[ -n "${STARROCKS_HOST:-}" && "${STARROCKS_HOST}" != "localhost" && "${STARROCKS_HOST}" != "127.0.0.1" ]]; then out+=" starrocks-fe starrocks-cn "; fi if [[ -n "${AIRFLOW_HOST:-}" && "${AIRFLOW_HOST}" != "localhost" && "${AIRFLOW_HOST}" != "127.0.0.1" ]]; then out+=" airflow-init airflow-webserver airflow-dag-processor airflow-scheduler "; fi if [[ -n "${OPENMETADATA_URL:-}" && "${OPENMETADATA_URL}" != "http://localhost:${OPENMETADATA_PORT:-12085}" ]]; then out+=" openmetadata-opensearch openmetadata-migrate openmetadata-server "; fi if [[ -n "${GRAFANA_URL:-}" && "${GRAFANA_URL}" != "http://localhost:${GRAFANA_PORT:-12300}" ]]; then out+=" grafana "; fi # PENTING: `grep -v '^$'` exit 1 saat tak ada baris cocok (tanpa service eksternal) # → dengan `set -euo pipefail` bisa mematikan skrip. `awk 'NF'` setara, selalu exit 0. echo "${out}" | tr ' ' '\n' | awk 'NF' | sort -u | paste -sd' ' - } # sicabe-trino → trino ; sicabe-starrocks-cn → starrocks-cn container_external() { local svc="${1#sicabe-}" [[ " $(external_services) " == *" ${svc} "* ]] } HOST_MODE=0 HOST_CONTAINERS="" init_host_mode() { [[ -z "${ENV_FILE_OVERRIDE:-}" ]] && return 0 local dir; dir="$(dirname "${ENV_FILE}")" [[ -f "${dir}/docker-compose.yml" ]] || return 0 HOST_MODE=1 # `|| true` PENTING: di bawah `set -euo pipefail`, kegagalan pipeline ini MEMATIKAN # skrip DIAM-DIAM (gejala: berhenti tepat setelah baris "[INFO] env-file:"). Penyebab # umum: `docker compose` tak tersedia/daemon mati, atau PyYAML (python3-yaml) belum ada. HOST_CONTAINERS="$(docker compose -f "${dir}/docker-compose.yml" --env-file "${ENV_FILE}" \ --profile all config 2>/dev/null | python3 -c " import sys, yaml d = yaml.safe_load(sys.stdin) or {} for s in (d.get('services') or {}).values(): n = s.get('container_name') if n: print(n)" 2>/dev/null || true)" if [[ -z "${HOST_CONTAINERS}" ]]; then echo -e "${ERR} Gagal membaca daftar container dari bundle host." >&2 echo " docker compose -f ${dir}/docker-compose.yml --env-file ${ENV_FILE} --profile all config" >&2 echo " Cek: 'docker compose' tersedia & daemon jalan, dan PyYAML terpasang" >&2 echo " (mis. sudo ./scripts/prepare-host.sh). Lalu ulangi perintah ini." >&2 exit 1 fi [[ -n "${SICABE_HOST_NAME:-}" ]] && echo -e "${INFO} Mode host '${SICABE_HOST_NAME}' (tiers=${SICABE_HOST_TIERS:-?}) — komponen host lain di-SKIP" } host_expects_container() { [[ ${HOST_MODE} -eq 0 ]] && return 0 printf '%s\n' "${HOST_CONTAINERS}" | grep -qx "$1" } # Cek endpoint untuk komponen TERTENTU. Di mode host, komponen milik host lain # di-SKIP (bukan FAIL): `localhost:12xxx` memang tidak melayani komponen yang ada di # host lain. Keterjangkauan lintas host diverifikasi `install.sh --preflight`. check_url_scoped() { #