2026-10-09 02:35:14 +07:00
|
|
|
#!/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 <berkas> memakai env milik SATU HOST
|
|
|
|
|
# (generated/host-<x>/.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() {
|
|
|
|
|
# <name> <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() { # <label> <container> <url> [timeout]
|
|
|
|
|
local label="$1" container="$2"; shift 2
|
|
|
|
|
if host_expects_container "${container}"; then
|
|
|
|
|
check_url "${label}" "$@"
|
|
|
|
|
else
|
|
|
|
|
test_skip "${label}" "Dijalankan di host lain (mode --env-file)"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Jalur compose yang sama dengan init_host_mode (berkas compose ada di sebelah .env).
|
|
|
|
|
compose_ref() {
|
|
|
|
|
local dir; dir="$(dirname "${ENV_FILE:-.env}")"
|
|
|
|
|
printf ' -f %s/docker-compose.yml --env-file %s' "${dir}" "${ENV_FILE:-.env}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Citra klien untuk cek "dari dalam jaringan Docker". Diambil dari daftar citra stack
|
|
|
|
|
# sendiri (itulah yang di-prefetch), BUKAN tag :latest hardcode: compose memakai tag
|
|
|
|
|
# pinned, dan cek pernah FAIL hanya karena meminta citra yang memang tidak pernah ada
|
|
|
|
|
# di server (tarikan quay.io di sana gagal).
|
|
|
|
|
resolve_stack_image() { # <pola-egrep> [kandidat-cadangan...]
|
|
|
|
|
local pat="$1"; shift
|
|
|
|
|
local cand images
|
|
|
|
|
images="$(docker compose $(compose_ref) config --images 2>/dev/null | grep -iE "${pat}" || true)"
|
|
|
|
|
for cand in ${images} "$@"; do
|
|
|
|
|
if docker image inspect "${cand}" >/dev/null 2>&1; then printf '%s' "${cand}"; return 0; fi
|
|
|
|
|
done
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Nama jaringan Docker milik sebuah container — jangan hardcode nama jaringan.
|
|
|
|
|
container_network() { # <container>
|
|
|
|
|
docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' "$1" 2>/dev/null \
|
|
|
|
|
| awk '{print $1}'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Cek fungsional komponen bermakna bila container-nya ADA DI HOST INI, atau endpoint-nya
|
|
|
|
|
# memang diarahkan ke luar (.env) oleh operator.
|
|
|
|
|
component_checkable() { # <container>
|
|
|
|
|
if docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$1"; then return 0; fi
|
|
|
|
|
if container_external "$1"; then return 0; fi
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Bila komponen dikelola host ini -> kembali 0 (cek boleh dijalankan). Selain itu catat
|
|
|
|
|
# SKIP untuk setiap label dan kembali 1. Dipakai sebagai `... || return 0`.
|
|
|
|
|
# Sebabnya: cek fungsional di skrip ini memakai URL localhost, jadi kalau komponennya ada
|
|
|
|
|
# di host lain hasilnya FAIL palsu, padahal yang benar SKIP.
|
|
|
|
|
require_component() { # <container> <label>...
|
|
|
|
|
local container="$1"; shift
|
|
|
|
|
if component_checkable "${container}"; then return 0; fi
|
|
|
|
|
local label
|
|
|
|
|
for label in "$@"; do
|
|
|
|
|
test_skip "${label}" "Dijalankan di host lain (mode --env-file)"
|
|
|
|
|
done
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
check_docker() {
|
|
|
|
|
local container="$1"
|
|
|
|
|
local name="$2"
|
|
|
|
|
if ! host_expects_container "${container}"; then
|
|
|
|
|
test_skip "${name}" "Container ${container} dijalankan di host lain (mode --env-file)"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
if docker ps --format "{{.Names}}" | grep -q "${container}"; then
|
|
|
|
|
test_pass "${name}" "Container ${container} running"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
if container_external "${container}"; then
|
|
|
|
|
test_skip "${name}" "Container ${container} dikelola eksternal (endpoint di .env)"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
test_fail "${name}" "Container ${container} tidak running"
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Helper: query Trino via SERVICE ACCOUNT (HTTP Basic) lewat nginx proxy.
|
|
|
|
|
# Returns: seluruh response JSON yang mengandung 'data'
|
|
|
|
|
trino_query() {
|
|
|
|
|
local sql="$1"
|
|
|
|
|
local user="${2:-svc_seed}"
|
|
|
|
|
local pass="${3:-${TRINO_SVC_SEED_PASSWORD}}"
|
|
|
|
|
local next resp
|
|
|
|
|
|
|
|
|
|
resp=$(curl -skf -X POST "${E2E_TRINO_URL}/v1/statement" \
|
|
|
|
|
-u "${user}:${pass}" \
|
|
|
|
|
-H "Content-Type: text/plain" \
|
|
|
|
|
-d "${sql}" 2>/dev/null || true)
|
|
|
|
|
if [[ -z "${resp}" ]]; then
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
next=$(echo "${resp}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('nextUri',''))" 2>/dev/null || true)
|
|
|
|
|
|
|
|
|
|
local i=0
|
|
|
|
|
while [[ -n "${next}" && ${i} -lt 15 ]]; do
|
|
|
|
|
sleep 2
|
|
|
|
|
resp=$(curl -skf -u "${user}:${pass}" "${next}" 2>/dev/null || true)
|
|
|
|
|
if [[ -z "${resp}" ]]; then
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
next=$(echo "${resp}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('nextUri',''))" 2>/dev/null || true)
|
|
|
|
|
echo "${resp}" | python3 -c "import sys,json; d=json.load(sys.stdin); [print(r) for r in d.get('data',[])]" 2>/dev/null || true
|
|
|
|
|
i=$((i + 1))
|
|
|
|
|
done
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# L5: Governance Layer — Keycloak (bab 4.1.1)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
test_governance_keycloak() {
|
|
|
|
|
print_section "L5.1 — Governance: Keycloak (IAM)"
|
|
|
|
|
|
|
|
|
|
# 4.1.1.9 Verifikasi: test login SSO
|
|
|
|
|
check_docker "sicabe-keycloak" "Keycloak container running"
|
|
|
|
|
|
|
|
|
|
local kc_url="http://localhost:${KEYCLOAK_PORT:-12080}"
|
|
|
|
|
check_url_scoped "Keycloak health endpoint" sicabe-keycloak "${kc_url}/health/ready" 60
|
|
|
|
|
|
|
|
|
|
# Cek di bawah memakai URL localhost -> hanya bermakna bila Keycloak di host ini.
|
|
|
|
|
# Cek di bawah memakai URL localhost + kredensial ADMIN → hanya bermakna bila
|
|
|
|
|
# Keycloak di host ini. Bila Keycloak EKSTERNAL (host lain, tanpa akses admin),
|
|
|
|
|
# verifikasi admin didelegasikan ke pengelola IdP → SKIP (bukan FAIL).
|
|
|
|
|
if ! host_expects_container sicabe-keycloak; then
|
|
|
|
|
test_skip "Keycloak admin login" "Keycloak eksternal/milik host lain — didelegasikan ke admin IdP"
|
|
|
|
|
test_skip "Keycloak realm '${SICABE_REALM:-sicabe}'" "eksternal"
|
|
|
|
|
test_skip "Keycloak OIDC configuration" "eksternal"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Test login dengan admin credentials
|
|
|
|
|
local token
|
|
|
|
|
token=$(curl -sf -X POST "${kc_url}/realms/master/protocol/openid-connect/token" \
|
|
|
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
|
|
|
--data-urlencode "client_id=admin-cli" \
|
|
|
|
|
--data-urlencode "username=${KEYCLOAK_ADMIN:-admin}" \
|
|
|
|
|
--data-urlencode "password=${KEYCLOAK_ADMIN_PASSWORD}" \
|
|
|
|
|
--data-urlencode "grant_type=password" \
|
|
|
|
|
2>/dev/null) || true
|
|
|
|
|
|
|
|
|
|
local access_token=""
|
|
|
|
|
if [[ -n "${token}" ]]; then
|
|
|
|
|
access_token=$(echo "${token}" | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null)
|
|
|
|
|
fi
|
|
|
|
|
if [[ -n "${access_token}" ]]; then
|
|
|
|
|
test_pass "Keycloak admin login" "Token diperoleh"
|
|
|
|
|
else
|
|
|
|
|
test_fail "Keycloak admin login" "Gagal login sebagai ${KEYCLOAK_ADMIN:-admin} (periksa KEYCLOAK_ADMIN_PASSWORD)"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Test realm sicabe ada (Keycloak Admin API butuh Bearer token, bukan Basic Auth)
|
|
|
|
|
local realm_check
|
|
|
|
|
realm_check=$(curl -sf -H "Authorization: Bearer ${access_token}" \
|
|
|
|
|
"${kc_url}/admin/realms/${SICABE_REALM:-sicabe}" 2>/dev/null) || true
|
|
|
|
|
if echo "${realm_check}" | grep -q "enabled"; then
|
|
|
|
|
test_pass "Keycloak realm '${SICABE_REALM:-sicabe}' ada" "Realm aktif"
|
|
|
|
|
else
|
|
|
|
|
test_fail "Keycloak realm '${SICABE_REALM:-sicabe}'" "Realm tidak ditemukan"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Test OpenID configuration
|
|
|
|
|
local oidc_config
|
|
|
|
|
oidc_config=$(curl -sf "${kc_url}/realms/${SICABE_REALM:-sicabe}/.well-known/openid-configuration" 2>/dev/null) || true
|
|
|
|
|
if echo "${oidc_config}" | grep -q "authorization_endpoint"; then
|
|
|
|
|
test_pass "Keycloak OIDC configuration" "OpenID discovery berfungsi"
|
|
|
|
|
else
|
|
|
|
|
test_fail "Keycloak OIDC configuration" "OpenID discovery gagal"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# L2: Storage Layer — MinIO (bab 4.2.1)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
test_storage_minio() {
|
|
|
|
|
print_section "L2.1 — Storage: MinIO (Object Storage)"
|
|
|
|
|
|
|
|
|
|
check_docker "sicabe-minio" "MinIO container running"
|
|
|
|
|
|
|
|
|
|
local minio_url="http://localhost:${MINIO_PORT:-12900}"
|
|
|
|
|
check_url_scoped "MinIO API" sicabe-minio "${minio_url}/minio/health/live" 45
|
|
|
|
|
check_url_scoped "MinIO Cluster health" sicabe-minio "${minio_url}/minio/health/cluster" 45
|
|
|
|
|
|
|
|
|
|
# Test S3 API — buat bucket test via AWS CLI atau mc
|
|
|
|
|
if command -v mc &>/dev/null; then
|
|
|
|
|
# MinIO milik host lain -> mc ke URL localhost akan FAIL palsu; catat SKIP.
|
|
|
|
|
if require_component sicabe-minio \
|
|
|
|
|
"MinIO bucket '${MINIO_BUCKET_ICEBERG_DATA:-iceberg-data}' ada" \
|
|
|
|
|
"MinIO upload/download"; then
|
|
|
|
|
mc alias set "sicabe-test" "${minio_url}" \
|
|
|
|
|
"${MINIO_ROOT_USER:-minioadmin}" "${MINIO_ROOT_PASSWORD}" >/dev/null 2>&1
|
|
|
|
|
|
|
|
|
|
# List buckets
|
|
|
|
|
local buckets
|
|
|
|
|
buckets=$(mc ls "sicabe-test" 2>/dev/null)
|
|
|
|
|
if echo "${buckets}" | grep -q "${MINIO_BUCKET_ICEBERG_DATA:-iceberg-data}"; then
|
|
|
|
|
test_pass "MinIO bucket '${MINIO_BUCKET_ICEBERG_DATA:-iceberg-data}' ada"
|
|
|
|
|
else
|
|
|
|
|
test_fail "MinIO bucket '${MINIO_BUCKET_ICEBERG_DATA:-iceberg-data}'" "Tidak ditemukan"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Test upload/download (4.2.1.9). Catatan: `mc cp` dites langsung sebagai kondisi
|
|
|
|
|
# (dulu lewat $? — di bawah `set -e` kegagalan justru menghentikan skrip).
|
|
|
|
|
echo "test-data-${RANDOM}" > /tmp/sicabe-verify.txt
|
|
|
|
|
if mc cp /tmp/sicabe-verify.txt "sicabe-test/${MINIO_BUCKET_ICEBERG_DATA:-iceberg-data}/.verify/test-${RANDOM}.txt" >/dev/null 2>&1; then
|
|
|
|
|
test_pass "MinIO upload/download" "S3 API berfungsi (upload)"
|
|
|
|
|
mc cp "sicabe-test/${MINIO_BUCKET_ICEBERG_DATA:-iceberg-data}/.verify/"* /tmp/sicabe-download.txt >/dev/null 2>&1 || true
|
|
|
|
|
else
|
|
|
|
|
test_fail "MinIO upload/download" "Gagal upload file via S3 API"
|
|
|
|
|
fi
|
|
|
|
|
rm -f /tmp/sicabe-verify.txt /tmp/sicabe-download.txt
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
test_skip "MinIO S3 API test" "mc client tidak tersedia"
|
|
|
|
|
# Fallback: jalankan klien mc dari dalam jaringan Docker. Hanya bermakna bila
|
|
|
|
|
# MinIO memang dikelola host ini — kalau tidak, cek ini PASS palsu.
|
|
|
|
|
# Penjaga utama: container benar-benar JALAN di host ini. Urutan ini penting —
|
|
|
|
|
# di bundle host, S3_ENDPOINT memang berbeda (IP lintas host) sehingga
|
|
|
|
|
# container_external bisa salah menganggap MinIO "eksternal" padahal lokal.
|
|
|
|
|
if ! docker ps --format '{{.Names}}' | grep -qx 'sicabe-minio'; then
|
|
|
|
|
if container_external sicabe-minio; then
|
|
|
|
|
test_skip "MinIO accessible via container" "Container sicabe-minio dikelola eksternal (endpoint di .env)"
|
|
|
|
|
else
|
|
|
|
|
test_skip "MinIO accessible via container" "Container sicabe-minio dijalankan di host lain (mode --env-file)"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
local mc_image="" mc_net="" mc_err=""
|
|
|
|
|
mc_image="$(resolve_stack_image '/mc[:@]' 'minio/mc:latest')" || mc_image=""
|
|
|
|
|
mc_net="$(container_network sicabe-minio)" || mc_net=""
|
|
|
|
|
if [[ -z "${mc_image}" ]]; then
|
|
|
|
|
test_skip "MinIO accessible via container" "citra mc tidak ada di host — jalankan scripts/prefetch-images.sh"
|
|
|
|
|
elif [[ -z "${mc_net}" ]]; then
|
|
|
|
|
test_skip "MinIO accessible via container" "jaringan container MinIO tidak terbaca"
|
|
|
|
|
elif mc_err="$(docker run --rm --pull=never --network="${mc_net}" "${mc_image}" \
|
|
|
|
|
alias set test http://minio:9000 "${MINIO_ROOT_USER:-minioadmin}" "${MINIO_ROOT_PASSWORD:-}" 2>&1)"; then
|
|
|
|
|
test_pass "MinIO accessible via container" "Docker mc dapat konek (${mc_image})"
|
|
|
|
|
else
|
|
|
|
|
test_fail "MinIO accessible via container" "Docker mc gagal konek (${mc_image}, network ${mc_net})"
|
|
|
|
|
printf '%s\n' "${mc_err}" | grep -v '^[[:space:]]*$' | head -3 | sed 's/^/ /' || true
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# L2: Storage Layer — Nessie (bab 4.2.3)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
test_storage_nessie() {
|
|
|
|
|
print_section "L2.2 — Storage: Project Nessie (Metadata Catalog)"
|
|
|
|
|
|
|
|
|
|
check_docker "sicabe-nessie" "Nessie container running"
|
|
|
|
|
|
|
|
|
|
local nessie_url="http://localhost:${NESSIE_PORT:-12120}"
|
|
|
|
|
|
|
|
|
|
# Health via API port (management 9000 tidak dipublikasikan)
|
|
|
|
|
check_url_scoped "Nessie API config" sicabe-nessie "${nessie_url}/api/v2/config" 60
|
2026-10-12 21:10:45 +07:00
|
|
|
check_url_scoped "Nessie Iceberg REST config" sicabe-nessie "${nessie_url}${NESSIE_ICEBERG_PREFIX:-/iceberg}/v1/config" 60
|
2026-10-09 02:35:14 +07:00
|
|
|
|
|
|
|
|
require_component sicabe-nessie \
|
|
|
|
|
"Nessie branch 'main'" \
|
|
|
|
|
"Nessie warehouse" || return 0
|
|
|
|
|
|
|
|
|
|
# Cek reference 'main' via Nessie REST API v2
|
|
|
|
|
local tree
|
|
|
|
|
tree=$(curl -sf "${nessie_url}/api/v2/trees" 2>/dev/null || true)
|
|
|
|
|
if echo "${tree}" | grep -q "main"; then
|
|
|
|
|
test_pass "Nessie branch 'main'" "Reference utama tersedia"
|
|
|
|
|
else
|
|
|
|
|
test_skip "Nessie branch 'main'" "Belum ada reference (katalog kosong)"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Warehouse terhubung MinIO (indikatif via config)
|
|
|
|
|
if curl -sf "${nessie_url}/api/v2/config" 2>/dev/null | grep -qi "warehouse"; then
|
|
|
|
|
test_pass "Nessie warehouse" "Konfigurasi warehouse tersedia"
|
|
|
|
|
else
|
|
|
|
|
test_skip "Nessie warehouse" "Info warehouse tidak terlihat di /api/v2/config"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# L3: Processing Layer — Trino (bab 4.3.1)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
test_processing_trino() {
|
|
|
|
|
print_section "L3.1 — Processing: Trino (Query Engine)"
|
|
|
|
|
|
|
|
|
|
check_docker "sicabe-trino-coordinator" "Trino coordinator running"
|
|
|
|
|
|
|
|
|
|
local trino_url="https://localhost:${TRINO_HTTPS_PORT:-12443}"
|
|
|
|
|
check_url_scoped "Trino health" sicabe-trino-coordinator "${trino_url}/v1/info" 60
|
|
|
|
|
|
|
|
|
|
require_component sicabe-trino-coordinator \
|
|
|
|
|
"Trino node status" \
|
|
|
|
|
"Trino Iceberg catalog" \
|
|
|
|
|
"Trino system catalog" \
|
|
|
|
|
"Trino Iceberg schemas" || return 0
|
|
|
|
|
|
|
|
|
|
# Get Trino node info
|
|
|
|
|
local node_info
|
|
|
|
|
node_info=$(curl -skf "${trino_url}/v1/info" 2>/dev/null)
|
|
|
|
|
if echo "${node_info}" | grep -qE '"state":"ACTIVE"|"state" : "ACTIVE"'; then
|
|
|
|
|
test_pass "Trino node status" "Trino dalam keadaan ACTIVE"
|
|
|
|
|
else
|
|
|
|
|
test_fail "Trino node status" "Trino tidak active: ${node_info}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Test query — SHOW CATALOGS (4.3.1.8)
|
|
|
|
|
# Trino 483 via service account (HTTP Basic) + polling nextUri
|
|
|
|
|
local query_result
|
|
|
|
|
query_result=$(trino_query "SHOW CATALOGS") || true
|
|
|
|
|
|
|
|
|
|
if echo "${query_result}" | grep -q "iceberg"; then
|
|
|
|
|
test_pass "Trino Iceberg catalog" "Catalog 'iceberg' terdeteksi"
|
|
|
|
|
else
|
|
|
|
|
test_fail "Trino Iceberg catalog" "Catalog 'iceberg' tidak muncul. Cek konektor Nessie."
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if echo "${query_result}" | grep -q "system"; then
|
|
|
|
|
test_pass "Trino system catalog" "Catalog 'system' terdeteksi"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Test query — SHOW SCHEMAS di iceberg catalog
|
|
|
|
|
local schemas
|
|
|
|
|
schemas=$(trino_query "SHOW SCHEMAS FROM iceberg") || true
|
|
|
|
|
|
|
|
|
|
if echo "${schemas}" | grep -q "bronze"; then
|
|
|
|
|
test_pass "Trino Iceberg schemas" "Schema 'bronze' terdeteksi"
|
|
|
|
|
elif echo "${schemas}" | grep -q "information_schema"; then
|
|
|
|
|
test_pass "Trino Iceberg schemas" "Minimal information_schema ada"
|
|
|
|
|
else
|
|
|
|
|
test_skip "Trino Iceberg schemas" "Schema belum dibuat. Jalankan bootstrap.sh step 4."
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# L4: Serving Layer — StarRocks (bab 4.4.1)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
test_serving_starrocks() {
|
|
|
|
|
print_section "L4.1 — Serving: StarRocks (shared-data: FE + CN)"
|
|
|
|
|
|
|
|
|
|
check_docker "sicabe-starrocks-fe" "StarRocks FE container running"
|
|
|
|
|
check_docker "sicabe-starrocks-cn" "StarRocks CN container running"
|
|
|
|
|
|
|
|
|
|
# Test koneksi MySQL ke StarRocks FE
|
|
|
|
|
if command -v mysql &>/dev/null; then
|
|
|
|
|
# StarRocks milik host lain -> mysql ke URL localhost akan FAIL palsu; catat SKIP.
|
|
|
|
|
if require_component sicabe-starrocks-fe \
|
|
|
|
|
"StarRocks FE status" \
|
|
|
|
|
"StarRocks CN status" \
|
|
|
|
|
"StarRocks storage volume" \
|
|
|
|
|
"StarRocks External Catalog" \
|
|
|
|
|
"StarRocks LDAP group provider"; then
|
|
|
|
|
sr_mysql() {
|
|
|
|
|
mysql -h 127.0.0.1 -P "${STARROCKS_FE_PORT:-12930}" -u root -p"${STARROCKS_ROOT_PASSWORD}" -e "$1" 2>/dev/null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
local sr_status
|
|
|
|
|
sr_status=$(sr_mysql "SHOW PROC '/frontends';") || true
|
|
|
|
|
if echo "${sr_status}" | grep -q "Alive"; then
|
|
|
|
|
test_pass "StarRocks FE status" "Frontend leader aktif"
|
|
|
|
|
else
|
|
|
|
|
test_fail "StarRocks FE status" "Tidak bisa konek. mysql -h localhost -P ${STARROCKS_FE_PORT:-12930} -u root -p"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Compute node (shared-data) — bukan BACKEND
|
|
|
|
|
local cn_status
|
|
|
|
|
cn_status=$(sr_mysql "SHOW COMPUTE NODES;") || true
|
|
|
|
|
if echo "${cn_status}" | grep -qi "Alive"; then
|
|
|
|
|
test_pass "StarRocks CN status" "Compute node aktif"
|
|
|
|
|
else
|
|
|
|
|
test_fail "StarRocks CN status" "Tidak ada compute node aktif"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Storage volume S3 (shared-data)
|
|
|
|
|
local sv
|
|
|
|
|
sv=$(sr_mysql "SHOW STORAGE VOLUMES;") || true
|
|
|
|
|
if echo "${sv}" | grep -q "s3_volume"; then
|
|
|
|
|
test_pass "StarRocks storage volume" "s3_volume (S3/MinIO) terdaftar"
|
|
|
|
|
else
|
|
|
|
|
test_skip "StarRocks storage volume" "s3_volume belum dibuat. Jalankan bootstrap.sh step 5."
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# External Catalog via Nessie
|
|
|
|
|
local catalogs
|
|
|
|
|
catalogs=$(sr_mysql "SHOW CATALOGS;") || true
|
|
|
|
|
if echo "${catalogs}" | grep -q "iceberg_catalog"; then
|
|
|
|
|
test_pass "StarRocks External Catalog" "Catalog 'iceberg_catalog' terdaftar"
|
|
|
|
|
else
|
|
|
|
|
test_skip "StarRocks External Catalog" "Catalog 'iceberg_catalog' belum dibuat. Jalankan bootstrap.sh step 5."
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# LDAP group provider
|
|
|
|
|
local gp
|
|
|
|
|
gp=$(sr_mysql "SHOW CREATE GROUP PROVIDER sr_ldap;") || true
|
|
|
|
|
if echo "${gp}" | grep -qi "ldap"; then
|
|
|
|
|
test_pass "StarRocks LDAP group provider" "sr_ldap terdaftar"
|
|
|
|
|
else
|
|
|
|
|
test_skip "StarRocks LDAP group provider" "sr_ldap belum dibuat."
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
test_skip "StarRocks FE connection" "MySQL client tidak tersedia"
|
|
|
|
|
# Fallback tanpa citra tambahan: klien mysql sudah ADA di dalam container
|
|
|
|
|
# starrocks-fe (dipakai healthcheck-nya sendiri) -> cukup docker exec.
|
|
|
|
|
# mysql:8 tidak dipakai stack, jadi tidak pernah ikut di-prefetch (dulu FAIL).
|
|
|
|
|
if ! docker ps --format '{{.Names}}' | grep -qx 'sicabe-starrocks-fe'; then
|
|
|
|
|
test_skip "StarRocks accessible via container" "Container sicabe-starrocks-fe tidak jalan di host ini"
|
|
|
|
|
elif docker exec sicabe-starrocks-fe \
|
|
|
|
|
mysql --connect-timeout 5 -h127.0.0.1 -P9030 -uroot -p"${STARROCKS_ROOT_PASSWORD:-}" \
|
|
|
|
|
-e "SELECT 1;" >/dev/null 2>&1; then
|
|
|
|
|
test_pass "StarRocks accessible via container" "Docker MySQL dapat konek (exec starrocks-fe)"
|
|
|
|
|
else
|
|
|
|
|
test_fail "StarRocks accessible via container" "Docker MySQL gagal konek (exec starrocks-fe)"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# L1: Ingestion Layer — Airflow (bab 4.5.1)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
test_ingestion_airflow() {
|
|
|
|
|
print_section "L1.1 — Ingestion: Airflow (Orchestrator)"
|
|
|
|
|
|
|
|
|
|
check_docker "sicabe-airflow-webserver" "Airflow webserver container running"
|
|
|
|
|
check_docker "sicabe-airflow-scheduler" "Airflow scheduler container running"
|
|
|
|
|
|
|
|
|
|
local airflow_url="http://localhost:${AIRFLOW_PORT:-12082}"
|
|
|
|
|
check_url_scoped "Airflow webserver" sicabe-airflow-webserver "${airflow_url}/api/v2/monitor/health" 90
|
|
|
|
|
|
|
|
|
|
require_component sicabe-airflow-webserver \
|
|
|
|
|
"Airflow health" \
|
|
|
|
|
"Airflow REST API" \
|
|
|
|
|
"Airflow DAG 'sicabe_batch_daily'" || return 0
|
|
|
|
|
|
|
|
|
|
# Cek health endpoint (Airflow 3.x: /api/v2/monitor/health)
|
|
|
|
|
local health
|
|
|
|
|
health=$(curl -sf "${airflow_url}/api/v2/monitor/health" 2>/dev/null)
|
|
|
|
|
if echo "${health}" | grep -q "healthy"; then
|
|
|
|
|
test_pass "Airflow health" "Semua komponen healthy"
|
|
|
|
|
elif echo "${health}" | grep -q "scheduler.*status"; then
|
|
|
|
|
test_pass "Airflow health endpoint" "Health endpoint berfungsi"
|
|
|
|
|
else
|
|
|
|
|
test_fail "Airflow health" "Health check gagal: ${health}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Cek airflow info via REST API (4.5.1.9) — Airflow 3.x pakai /api/v2
|
|
|
|
|
local af_info
|
|
|
|
|
af_info=$(curl -sf "${airflow_url}/api/v2/monitor/health" 2>/dev/null || true)
|
|
|
|
|
if [[ -n "${af_info}" ]]; then
|
|
|
|
|
test_pass "Airflow REST API" "API v2 berfungsi"
|
|
|
|
|
else
|
|
|
|
|
test_skip "Airflow REST API" "API tidak merespon. Mungkin auth diperlukan."
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Cek DAG example via Airflow CLI (Airflow 3.x: REST /api/v1/dags tidak ada)
|
|
|
|
|
local dags
|
|
|
|
|
if ! host_expects_container sicabe-airflow-webserver; then
|
|
|
|
|
test_skip "Airflow DAG 'sicabe_batch_daily'" "Airflow dijalankan di host lain (mode --env-file)"
|
|
|
|
|
dags=""
|
|
|
|
|
else
|
|
|
|
|
dags=$(docker exec sicabe-airflow-webserver airflow dags list 2>/dev/null || true)
|
|
|
|
|
fi
|
|
|
|
|
if echo "${dags}" | grep -q "sicabe_batch_daily"; then
|
|
|
|
|
test_pass "Airflow DAG 'sicabe_batch_daily'" "DAG terdaftar"
|
|
|
|
|
else
|
|
|
|
|
test_skip "Airflow DAG 'sicabe_batch_daily'" "DAG belum didaftarkan. Jalankan bootstrap.sh step 6."
|
|
|
|
|
fi
|
|
|
|
|
if echo "${dags}" | grep -q "sicabe_cdc_realtime"; then
|
|
|
|
|
test_pass "Airflow DAG 'sicabe_cdc_realtime'" "DAG terdaftar"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# L5: Governance Layer — OpenMetadata (bab 4.6.1)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
test_governance_openmetadata() {
|
|
|
|
|
print_section "L5.2 — Governance: OpenMetadata (Metadata Platform)"
|
|
|
|
|
|
|
|
|
|
check_docker "sicabe-openmetadata" "OpenMetadata container running"
|
|
|
|
|
|
|
|
|
|
local om_url="http://localhost:${OPENMETADATA_PORT:-12085}"
|
|
|
|
|
check_url_scoped "OpenMetadata health" sicabe-openmetadata "${om_url}/health" 90
|
|
|
|
|
|
|
|
|
|
require_component sicabe-openmetadata \
|
|
|
|
|
"OpenMetadata API" \
|
|
|
|
|
"OpenMetadata UI" \
|
|
|
|
|
"OpenMetadata login" || return 0
|
|
|
|
|
|
|
|
|
|
# Cek API version
|
|
|
|
|
local api_info
|
|
|
|
|
api_info=$(curl -sf "${om_url}/health" 2>/dev/null)
|
|
|
|
|
if [[ -n "${api_info}" ]]; then
|
|
|
|
|
test_pass "OpenMetadata API" "API v1 berfungsi"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Cek UI
|
|
|
|
|
local ui_check
|
|
|
|
|
ui_check=$(curl -sf "${om_url}" 2>/dev/null)
|
|
|
|
|
if echo "${ui_check}" | grep -qi "openmetadata"; then
|
|
|
|
|
test_pass "OpenMetadata UI" "UI dapat diakses"
|
|
|
|
|
elif [[ -n "${ui_check}" ]]; then
|
|
|
|
|
test_pass "OpenMetadata UI" "UI merespon"
|
|
|
|
|
else
|
|
|
|
|
test_fail "OpenMetadata UI" "UI tidak merespon"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Test login (OpenMetadata 2.0.x: email admin@open-metadata.org, password Base64.
|
|
|
|
|
# Catatan: OM 2.0.x tidak lagi membaca OPENMETADATA_ADMIN_PASSWORD saat bootstrap
|
|
|
|
|
# — default-nya 'admin'; password dari .env dipakai bila sudah diganti lewat UI.)
|
|
|
|
|
local login_test
|
|
|
|
|
local om_pass_b64
|
|
|
|
|
om_pass_b64=$(printf '%s' "${OPENMETADATA_ADMIN_PASSWORD:-admin}" | base64)
|
|
|
|
|
login_test=$(curl -sf -X POST "${om_url}/api/v1/users/login" \
|
|
|
|
|
-H "Content-Type: application/json" \
|
|
|
|
|
-d "{\"email\":\"admin@open-metadata.org\",\"password\":\"${om_pass_b64}\"}" \
|
|
|
|
|
2>/dev/null) || true
|
|
|
|
|
if echo "${login_test}" | grep -q "accessToken"; then
|
|
|
|
|
test_pass "OpenMetadata login" "Admin login berhasil"
|
|
|
|
|
else
|
|
|
|
|
test_skip "OpenMetadata login" "Login gagal — mungkin password berbeda"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# L5: Governance Layer — Grafana (bab 4.6.3)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
test_governance_grafana() {
|
|
|
|
|
print_section "L5.3 — Governance: Grafana (Observability)"
|
|
|
|
|
|
|
|
|
|
check_docker "sicabe-grafana" "Grafana container running"
|
|
|
|
|
|
|
|
|
|
local grafana_url="http://localhost:${GRAFANA_PORT:-12300}"
|
|
|
|
|
check_url_scoped "Grafana health" sicabe-grafana "${grafana_url}/api/health" 60
|
|
|
|
|
|
|
|
|
|
require_component sicabe-grafana \
|
|
|
|
|
"Grafana login" \
|
|
|
|
|
"Grafana datasource Prometheus" \
|
|
|
|
|
"Grafana datasource MinIO Metrics" || return 0
|
|
|
|
|
|
|
|
|
|
# Cek login (Grafana 13.x: /api/auth/login deprecated — pakai Basic Auth)
|
|
|
|
|
local login
|
|
|
|
|
login=$(curl -sf -u "admin:${GRAFANA_ADMIN_PASSWORD:-grafana123}" \
|
|
|
|
|
"${grafana_url}/api/user" 2>/dev/null) || true
|
|
|
|
|
if echo "${login}" | grep -q "\"login\""; then
|
|
|
|
|
test_pass "Grafana login" "Admin Basic Auth berhasil"
|
|
|
|
|
else
|
|
|
|
|
test_fail "Grafana login" "Gagal autentikasi"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Cek datasource provisioning (4.6.3.6)
|
|
|
|
|
# Catatan (review Fase 6): datasource 'OpenMetadata' DIHAPUS karena plugin
|
|
|
|
|
# 'grafana-opendatasource-http' tidak ada di katalog Grafana. 'MinIO Metrics'
|
|
|
|
|
# juga menunjuk Prometheus — metrik MinIO diambil lewat scrape, bukan dibaca
|
|
|
|
|
# langsung dari API S3 MinIO.
|
|
|
|
|
local ds
|
|
|
|
|
ds=$(curl -sf -u "admin:${GRAFANA_ADMIN_PASSWORD:-grafana123}" \
|
|
|
|
|
"${grafana_url}/api/datasources" 2>/dev/null || true)
|
|
|
|
|
if [[ -n "${ds}" ]]; then
|
|
|
|
|
if echo "${ds}" | grep -q "Prometheus"; then
|
|
|
|
|
test_pass "Grafana datasource Prometheus" "Terprovisioning"
|
|
|
|
|
fi
|
|
|
|
|
if echo "${ds}" | grep -q "MinIO Metrics"; then
|
|
|
|
|
test_pass "Grafana datasource MinIO Metrics" "Terprovisioning (metrik MinIO via Prometheus)"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# E2E: End-to-End Smoke Test (Bab 6.2)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
test_e2e_smoke() {
|
|
|
|
|
print_section "E2E — End-to-End Smoke Test (Lintas Lapisan)"
|
|
|
|
|
echo -e "${INFO} Skenario: Alur data dari Ingestion → Storage → Processing → Serving → Governance${NC}"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# Prasyarat: semua komponen running
|
|
|
|
|
echo -e "${INFO} 1/5 Memeriksa semua komponen running..."
|
|
|
|
|
local all_running=true
|
|
|
|
|
# dremio OPT-IN (profile khusus, tidak ikut `--profile all`) → tidak wajib.
|
|
|
|
|
local optional_svcs="dremio"
|
|
|
|
|
for svc in keycloak openldap minio nessie opa trino starrocks-fe starrocks-cn dremio airflow-webserver openmetadata grafana; do
|
|
|
|
|
# Mode host: komponen host lain tidak dihitung (keterjangkauannya
|
|
|
|
|
# sudah diverifikasi `install.sh --preflight`).
|
|
|
|
|
if ! host_expects_container "sicabe-${svc}"; then
|
|
|
|
|
echo -e " [ .. ] sicabe-${svc} — di host lain, dilewati"
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
if ! docker ps --format "{{.Names}}" | grep -q "sicabe-${svc}"; then
|
|
|
|
|
if [[ " ${optional_svcs} " == *" ${svc} "* ]]; then
|
|
|
|
|
echo -e " ${WARN} sicabe-${svc} — tidak running (opsional, dilewati)"
|
|
|
|
|
else
|
|
|
|
|
echo -e " ${FAIL} sicabe-${svc} — TIDAK RUNNING"
|
|
|
|
|
all_running=false
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
if ${all_running}; then
|
|
|
|
|
if [[ ${HOST_MODE} -eq 1 ]]; then
|
|
|
|
|
test_pass "E2E Prasyarat" "Semua container MILIK HOST INI running (host lain di-SKIP; keterjangkauan dicek --preflight)"
|
|
|
|
|
else
|
|
|
|
|
test_pass "E2E Prasyarat" "Semua container running"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
test_fail "E2E Prasyarat" "Beberapa container tidak running"
|
|
|
|
|
echo -e "${ERR} E2E test dihentikan — tidak semua komponen tersedia."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Step 1: [Ingestion] Test koneksi ke sumber data & MinIO
|
|
|
|
|
echo -e "${INFO} 2/5 [Ingestion] Verifikasi pipeline integration..."
|
|
|
|
|
local airflow_ok=false
|
|
|
|
|
if curl -sf "${E2E_AIRFLOW_URL}/api/v2/monitor/health" >/dev/null 2>&1; then
|
|
|
|
|
airflow_ok=true
|
|
|
|
|
test_pass "E2E Ingestion: Airflow siap"
|
|
|
|
|
else
|
|
|
|
|
test_fail "E2E Ingestion: Airflow" "Airflow tidak siap (${E2E_AIRFLOW_URL})"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Step 2: [Storage] Data tersimpan sebagai Iceberg, tercatat di Nessie
|
|
|
|
|
echo -e "${INFO} 3/5 [Storage] Verifikasi Iceberg + Nessie..."
|
|
|
|
|
local tq_result
|
|
|
|
|
tq_result=$(trino_query "SELECT 'Storage Layer OK' AS status") || true
|
|
|
|
|
if [[ -n "${tq_result}" ]]; then
|
|
|
|
|
test_pass "E2E Storage: Trino query execution"
|
|
|
|
|
# Coba query Iceberg catalog
|
|
|
|
|
local iceberg_query
|
|
|
|
|
iceberg_query=$(trino_query "SHOW TABLES FROM iceberg.bronze") || true
|
|
|
|
|
if [[ -n "${iceberg_query}" ]]; then
|
|
|
|
|
test_pass "E2E Storage: Iceberg catalog queryable via Trino"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
test_fail "E2E Storage: Trino" "Query execution gagal"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Step 3: [Processing] dbt via Trino transformasi
|
|
|
|
|
echo -e "${INFO} 4/5 [Processing] Verifikasi data processing..."
|
|
|
|
|
if [[ "${airflow_ok}" == true ]]; then
|
|
|
|
|
# `docker exec` hanya berlaku untuk container LOKAL. Di host lain → SKIP (bukan FAIL):
|
|
|
|
|
# keterjangkauan lintas host sudah diverifikasi `--preflight`, dan health API di atas
|
|
|
|
|
# membuktikan Airflow siap dari sini.
|
|
|
|
|
if [[ ${HOST_MODE} -eq 1 ]] && ! host_expects_container sicabe-airflow-webserver; then
|
|
|
|
|
test_skip "E2E Processing: DAG" "Airflow di host lain (mode --env-file) — cek DAG via UI/API di sana"
|
|
|
|
|
elif docker exec sicabe-airflow-webserver airflow dags list 2>/dev/null | grep -q "sicabe"; then
|
|
|
|
|
test_pass "E2E Processing: DAG siap dijalankan"
|
|
|
|
|
else
|
|
|
|
|
test_skip "E2E Processing: DAG" "DAG contoh belum di-deploy. Jalankan bootstrap.sh step 6."
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Step 4: [Serving] StarRocks siap melayani query
|
|
|
|
|
echo -e "${INFO} 5/5 [Serving] Verifikasi StarRocks..."
|
|
|
|
|
if command -v mysql &>/dev/null; then
|
|
|
|
|
if mysql --protocol=TCP -h "${E2E_SR_HOST}" -P "${STARROCKS_FE_PORT:-12930}" -u root -p"${STARROCKS_ROOT_PASSWORD}" -e "SELECT 1 AS test;" >/dev/null 2>&1; then
|
|
|
|
|
test_pass "E2E Serving: StarRocks query execution"
|
|
|
|
|
else
|
|
|
|
|
test_fail "E2E Serving: StarRocks" "Query execution gagal (${E2E_SR_HOST}:${STARROCKS_FE_PORT:-12930})"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
test_skip "E2E Serving: StarRocks" "MySQL client tidak tersedia"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Step 5: [Governance] Verifikasi governance
|
|
|
|
|
echo -e "${INFO} 6/5 [Governance] Verifikasi metadata & observability..."
|
|
|
|
|
if curl -sf "${E2E_OM_URL}/health" >/dev/null 2>&1; then
|
|
|
|
|
test_pass "E2E Governance: OpenMetadata siap"
|
|
|
|
|
fi
|
|
|
|
|
if curl -sf "${E2E_GRAFANA_URL}/api/health" >/dev/null 2>&1; then
|
|
|
|
|
test_pass "E2E Governance: Grafana siap"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# PERF: Performance Baseline Test (Bab 6.3)
|
|
|
|
|
# =============================================================================
|
|
|
|
|
test_performance_baseline() {
|
|
|
|
|
print_section "PERF — Performance Baseline Test"
|
|
|
|
|
|
|
|
|
|
# Test latency endpoint setiap komponen
|
|
|
|
|
echo -e "${INFO} Mengukur latency endpoint setiap lapisan...${NC}"
|
|
|
|
|
|
|
|
|
|
local endpoints=(
|
|
|
|
|
"Keycloak:http://localhost:${KEYCLOAK_PORT:-12080}/health/ready"
|
|
|
|
|
"MinIO:http://localhost:${MINIO_PORT:-12900}/minio/health/live"
|
|
|
|
|
"Nessie:http://localhost:${NESSIE_PORT:-12120}/api/v2/config"
|
|
|
|
|
"Trino:https://localhost:${TRINO_HTTPS_PORT:-12443}/v1/info"
|
|
|
|
|
"OPA:http://localhost:${OPA_PORT:-12181}/v1/data/trino/allow"
|
|
|
|
|
"Airflow:http://localhost:${AIRFLOW_PORT:-12082}/api/v2/monitor/health"
|
|
|
|
|
"OpenMetadata:http://localhost:${OPENMETADATA_PORT:-12085}/health"
|
|
|
|
|
"Grafana:http://localhost:${GRAFANA_PORT:-12300}/api/health"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for ep in "${endpoints[@]}"; do
|
|
|
|
|
local name="${ep%%:*}"
|
|
|
|
|
local url="${ep#*:}"
|
|
|
|
|
# Mode host: ukur hanya komponen milik host ini — latency ke
|
|
|
|
|
# localhost komponen host lain tidak bermakna.
|
|
|
|
|
local ep_container=""
|
|
|
|
|
case "${name}" in
|
|
|
|
|
Keycloak) ep_container="sicabe-keycloak" ;;
|
|
|
|
|
MinIO) ep_container="sicabe-minio" ;;
|
|
|
|
|
Nessie) ep_container="sicabe-nessie" ;;
|
|
|
|
|
Trino) ep_container="sicabe-trino-coordinator" ;;
|
|
|
|
|
OPA) ep_container="sicabe-opa" ;;
|
|
|
|
|
Airflow) ep_container="sicabe-airflow-webserver" ;;
|
|
|
|
|
OpenMetadata) ep_container="sicabe-openmetadata" ;;
|
|
|
|
|
Grafana) ep_container="sicabe-grafana" ;;
|
|
|
|
|
esac
|
|
|
|
|
if [[ -n "${ep_container}" ]] && ! host_expects_container "${ep_container}"; then
|
|
|
|
|
echo -e " [ .. ] ${name} — di host lain, dilewati"
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
local total_time=0
|
|
|
|
|
local success=true
|
|
|
|
|
|
|
|
|
|
for run in 1 2 3; do
|
|
|
|
|
local time_val
|
|
|
|
|
time_val=$(curl -kso /dev/null -w "%{time_total}" --max-time 5 "${url}" 2>/dev/null || echo "0")
|
|
|
|
|
total_time=$(echo "${total_time} + ${time_val}" | bc -l 2>/dev/null || echo "0")
|
|
|
|
|
if [[ "${time_val}" == "0" ]]; then
|
|
|
|
|
success=false
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [[ "${success}" == true ]]; then
|
|
|
|
|
local avg
|
|
|
|
|
avg=$(echo "scale=3; ${total_time} / 3" | bc -l 2>/dev/null || echo "N/A")
|
|
|
|
|
test_pass "PERF Latency: ${name}" "Rata-rata ${avg}s"
|
|
|
|
|
else
|
|
|
|
|
test_skip "PERF Latency: ${name}" "Endpoint tidak merespon"
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# Test query latency Trino
|
|
|
|
|
echo -e "${INFO} Mengukur query latency Trino...${NC}"
|
|
|
|
|
if curl -skf "https://localhost:${TRINO_HTTPS_PORT:-12443}/v1/info" >/dev/null 2>&1; then
|
|
|
|
|
local query_times=()
|
|
|
|
|
for run in 1 2; do
|
|
|
|
|
local qtime
|
|
|
|
|
qtime=$(curl -skf -X POST "https://localhost:${TRINO_HTTPS_PORT:-12443}/v1/statement" \
|
|
|
|
|
-u "svc_seed:${TRINO_SVC_SEED_PASSWORD}" \
|
|
|
|
|
-H "Content-Type: text/plain" \
|
|
|
|
|
-d "SELECT 1" -w "%{time_total}" -o /dev/null 2>/dev/null || echo "0")
|
|
|
|
|
query_times+=("${qtime}")
|
|
|
|
|
done
|
|
|
|
|
local qavg
|
|
|
|
|
qavg=$(echo "scale=3; (${query_times[0]} + ${query_times[1]}) / 2" | bc -l 2>/dev/null || echo "N/A")
|
|
|
|
|
test_pass "PERF Trino query" "Rata-rata ${qavg}s"
|
|
|
|
|
else
|
|
|
|
|
test_skip "PERF Trino query" "Trino tidak tersedia"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# REPORT
|
|
|
|
|
# =============================================================================
|
|
|
|
|
show_report() {
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${BOLD}${BLUE}═══════════════════════════════════════════════════════${NC}"
|
|
|
|
|
echo -e "${BOLD} LAPORAN VERIFIKASI${NC}"
|
|
|
|
|
echo -e "${BOLD}${BLUE}═══════════════════════════════════════════════════════${NC}"
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e " Total Pass: ${PASS_COUNT}"
|
|
|
|
|
echo -e " Total Fail: ${FAIL_COUNT}"
|
|
|
|
|
echo -e " Total Skip: ${SKIP_COUNT}"
|
|
|
|
|
echo -e " Total: $((PASS_COUNT + FAIL_COUNT + SKIP_COUNT))"
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e " Report file: ${REPORT_FILE}"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
if [[ ${FAIL_COUNT} -eq 0 ]]; then
|
|
|
|
|
echo -e "${GREEN}${BOLD} ✅ SEMUA TEST PASS${NC}"
|
|
|
|
|
else
|
|
|
|
|
echo -e "${RED}${BOLD} ❌ ${FAIL_COUNT} TEST FAILED${NC}"
|
|
|
|
|
echo -e " Periksa ${REPORT_FILE} untuk detail."
|
|
|
|
|
fi
|
|
|
|
|
echo ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# MAIN
|
|
|
|
|
# =============================================================================
|
|
|
|
|
main() {
|
|
|
|
|
# Mode per-host ditentukan setelah env dimuat (ENV_FILE) dan fungsi tersedia.
|
|
|
|
|
init_host_mode
|
|
|
|
|
|
|
|
|
|
local TEST_LAYER="ALL"
|
|
|
|
|
|
|
|
|
|
if [[ $# -gt 0 ]]; then
|
|
|
|
|
case "$1" in
|
|
|
|
|
--env-file)
|
|
|
|
|
# Sudah ditangani sebelum pemuatan env (lihat di atas banner).
|
|
|
|
|
shift 2 2>/dev/null || shift
|
|
|
|
|
;;
|
|
|
|
|
--help|-h)
|
|
|
|
|
echo "Usage: $0 [--layer L1|L2|L3|L4|L5|E2E|PERF] [--list] [--report]"
|
|
|
|
|
echo " Default: semua test"
|
|
|
|
|
echo " --layer L1 Test Ingestion Layer (Airflow)"
|
|
|
|
|
echo " --layer L2 Test Storage Layer (MinIO, Nessie)"
|
|
|
|
|
echo " --layer L3 Test Processing Layer (Trino)"
|
|
|
|
|
echo " --layer L4 Test Serving Layer (StarRocks)"
|
|
|
|
|
echo " --layer L5 Test Governance Layer (Keycloak, OpenMetadata, Grafana)"
|
|
|
|
|
echo " --layer E2E End-to-End Smoke Test"
|
|
|
|
|
echo " --layer PERF Performance Baseline Test"
|
|
|
|
|
echo " --list Daftar test yang tersedia"
|
|
|
|
|
echo " --report Tampilkan hasil test terakhir"
|
|
|
|
|
exit 0
|
|
|
|
|
;;
|
|
|
|
|
--list)
|
|
|
|
|
echo "Test yang tersedia:"
|
|
|
|
|
echo " L1 - Ingestion Layer: Airflow"
|
|
|
|
|
echo " L2 - Storage Layer: MinIO, Nessie"
|
|
|
|
|
echo " L3 - Processing Layer: Trino"
|
|
|
|
|
echo " L4 - Serving Layer: StarRocks"
|
|
|
|
|
echo " L5 - Governance Layer: Keycloak, OpenMetadata, Grafana"
|
|
|
|
|
echo " E2E - End-to-End Smoke Test"
|
|
|
|
|
echo " PERF - Performance Baseline Test"
|
|
|
|
|
exit 0
|
|
|
|
|
;;
|
|
|
|
|
--layer)
|
|
|
|
|
TEST_LAYER="${2:-ALL}"
|
|
|
|
|
;;
|
|
|
|
|
--report)
|
|
|
|
|
local latest_report
|
|
|
|
|
latest_report=$(ls -t "${SCRIPT_DIR}/output"/test-report-*.log 2>/dev/null | head -1)
|
|
|
|
|
if [[ -n "${latest_report}" ]]; then
|
|
|
|
|
cat "${latest_report}"
|
|
|
|
|
else
|
|
|
|
|
echo "Belum ada laporan test. Jalankan $0 terlebih dahulu."
|
|
|
|
|
fi
|
|
|
|
|
exit 0
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
echo -e "${ERR} Argumen tidak dikenal: $1"
|
|
|
|
|
echo "Gunakan --help untuk bantuan."
|
|
|
|
|
exit 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Setup test log
|
|
|
|
|
TEST_LOG="${REPORT_FILE}"
|
|
|
|
|
exec > >(tee -a "${TEST_LOG}") 2>&1
|
|
|
|
|
|
|
|
|
|
echo -e "${BOLD}${BLUE}"
|
|
|
|
|
echo "╔═══════════════════════════════════════════════════════╗"
|
|
|
|
|
echo "║ eBdesk SiCABE — Test & Verification ║"
|
|
|
|
|
echo "║ Bab 6: Verifikasi & Smoke Test ║"
|
|
|
|
|
echo "╚═══════════════════════════════════════════════════════╝"
|
|
|
|
|
echo -e "${NC}"
|
|
|
|
|
echo "Timestamp: $(date)"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
# Pastikan Docker berjalan
|
|
|
|
|
if ! docker ps >/dev/null 2>&1; then
|
|
|
|
|
echo -e "${ERR} Docker tidak berjalan. Test dihentikan."
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
case "${TEST_LAYER}" in
|
|
|
|
|
L1) test_ingestion_airflow ;;
|
|
|
|
|
L2) test_storage_minio; test_storage_nessie ;;
|
|
|
|
|
L3) test_processing_trino ;;
|
|
|
|
|
L4) test_serving_starrocks ;;
|
|
|
|
|
L5) test_governance_keycloak; test_governance_openmetadata; test_governance_grafana ;;
|
|
|
|
|
E2E) test_e2e_smoke ;;
|
|
|
|
|
PERF) test_performance_baseline ;;
|
|
|
|
|
ALL)
|
|
|
|
|
test_governance_keycloak
|
|
|
|
|
test_storage_minio
|
|
|
|
|
test_storage_nessie
|
|
|
|
|
test_processing_trino
|
|
|
|
|
test_serving_starrocks
|
|
|
|
|
test_ingestion_airflow
|
|
|
|
|
test_governance_openmetadata
|
|
|
|
|
test_governance_grafana
|
|
|
|
|
test_e2e_smoke
|
|
|
|
|
test_performance_baseline
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
|
|
|
|
|
show_report
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main "$@"
|