2026-10-09 02:35:14 +07:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# eBdesk SiCABE — Verifikasi Phase 2 (Storage & Catalog): MinIO/S3 + Nessie
|
|
|
|
|
#
|
|
|
|
|
# Gate "kontrak storage": memastikan object storage & Nessie benar-benar SIAP
|
|
|
|
|
# dipakai fase berikutnya (Trino, StarRocks, Airflow/Sling, Dremio) — termasuk
|
|
|
|
|
# saat memakai object storage / Nessie EXISTING (S3_ENDPOINT, NESSIE_URL).
|
|
|
|
|
#
|
|
|
|
|
# Yang diperiksa:
|
|
|
|
|
# A. Object storage : endpoint terjangkau, bucket ada, tulis+baca berhasil
|
2026-10-12 21:10:45 +07:00
|
|
|
# B. Nessie : /api/v2/config, default branch, endpoint Iceberg REST <prefix>/v1/config
|
|
|
|
|
# (prefix dari NESSIE_ICEBERG_PREFIX, default /iceberg)
|
2026-10-09 02:35:14 +07:00
|
|
|
# C. Version store : database `nessie` + tabel Nessie di PostgreSQL (JDBC2)
|
|
|
|
|
# D. Kontrak & antarmuka:
|
|
|
|
|
# D1 (milik Phase 2) : warehouse & endpoint S3 pada Nessie (compose) → FAIL bila salah
|
|
|
|
|
# D2 (milik fase 3/4/5) : config Trino / Sling / StarRocks selaras → WARN/SKIP saja
|
|
|
|
|
#
|
|
|
|
|
# Mode --deep (opsional — menguji KONSUMEN, jadi komponennya harus sudah ada):
|
|
|
|
|
# E. Trino : baca nyata dari warehouse (SELECT count(*) FROM iceberg.bronze.orders)
|
|
|
|
|
#
|
|
|
|
|
# Prinsip gate: hanya memverifikasi kontrak yang DIMILIKI fase ini. Pemeriksaan yang
|
|
|
|
|
# bergantung pada runtime fase lain (mis. Trino di fase 3) tidak dijalankan kecuali --deep,
|
|
|
|
|
# supaya gate tidak pernah "hijau karena di-SKIP" atau salah atribusi kegagalan.
|
|
|
|
|
#
|
|
|
|
|
# Exit code: 0 = semua gate lolos (WARN/SKIP boleh), 1 = ada FAIL.
|
|
|
|
|
#
|
|
|
|
|
# Usage:
|
|
|
|
|
# ./scripts/verify-phase2-storage.sh
|
|
|
|
|
# ./scripts/verify-phase2-storage.sh --deep # + uji baca via Trino (fase 3)
|
|
|
|
|
# S3_ENDPOINT=https://s3.corp.internal NESSIE_URL=https://nessie.corp.internal \
|
|
|
|
|
# ./scripts/verify-phase2-storage.sh
|
|
|
|
|
# =============================================================================
|
|
|
|
|
set -uo pipefail
|
|
|
|
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
|
|
# shellcheck source=../lib/common.sh
|
|
|
|
|
source "${SCRIPT_DIR}/lib/common.sh"
|
|
|
|
|
# shellcheck source=../config/environment.defaults.sh
|
|
|
|
|
source "${SCRIPT_DIR}/config/environment.defaults.sh"
|
|
|
|
|
sicabe_load_env "${SCRIPT_DIR}" "$@"
|
|
|
|
|
|
|
|
|
|
# Config RELEVAN: mode host (--env-file) = BUNDLE host (ter-render), bukan deploy/config/ sumber.
|
|
|
|
|
if [[ -n "${SICABE_BUNDLE_DIR:-}" ]]; then CONFIG_DIR="${SICABE_BUNDLE_DIR}/config"; else CONFIG_DIR="${SCRIPT_DIR}/config"; fi
|
|
|
|
|
|
|
|
|
|
S3_ENDPOINT="${S3_ENDPOINT:-http://minio:9000}"
|
2026-10-12 21:10:45 +07:00
|
|
|
S3_SCHEME="${S3_ENDPOINT%%://*}" # http | https — dipakai uji kesehatan host
|
2026-10-09 02:35:14 +07:00
|
|
|
S3_REGION="${S3_REGION:-us-east-1}"
|
|
|
|
|
S3_ACCESS_KEY="${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}}"
|
|
|
|
|
S3_SECRET_KEY="${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-}}"
|
|
|
|
|
BUCKET="${MINIO_BUCKET_ICEBERG_DATA:-iceberg-data}"
|
2026-10-11 15:09:43 +07:00
|
|
|
WAREHOUSE="${ICEBERG_WAREHOUSE:-warehouse}"
|
2026-10-09 02:35:14 +07:00
|
|
|
NESSIE_URL="${NESSIE_URL:-http://nessie:19120}"
|
|
|
|
|
NESSIE_HOST="${NESSIE_HOST:-localhost}"
|
|
|
|
|
NESSIE_PORT="${NESSIE_PORT:-12120}"
|
2026-10-12 14:26:44 +07:00
|
|
|
# API host-facing. Bila NESSIE_URL menunjuk ALAMAT (IP/FQDN — Nessie EKSTERNAL/lintas-host),
|
|
|
|
|
# pakai apa adanya (sudah memuat port benar: generator memakai NESSIE_INTERNAL_PORT, mis.
|
|
|
|
|
# 19120). Selebihnya (nama service `nessie:19120` / single-host) → host:port terbit (NESSIE_PORT).
|
|
|
|
|
NESSIE_HOST_API="$(sicabe_host_url NESSIE_URL NESSIE_HOST "${NESSIE_PORT:-12120}")"
|
2026-10-09 02:35:14 +07:00
|
|
|
NESSIE_DB="${NESSIE_DB:-nessie}"
|
2026-10-12 21:10:45 +07:00
|
|
|
# Prefix REST Iceberg Nessie (mis. `/iceberg`); ubah bila layanan existing memakai prefix lain.
|
|
|
|
|
ICEBERG_PREFIX="${NESSIE_ICEBERG_PREFIX:-/iceberg}"
|
2026-10-09 02:35:14 +07:00
|
|
|
|
|
|
|
|
# Mode --deep: ikut menguji KONSUMEN (Trino) — komponennya harus sudah ada (fase 3+).
|
|
|
|
|
DEEP=0
|
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
--env-file) shift 2 ;; # sudah dipakai sicabe_load_env() di atas
|
|
|
|
|
--deep) DEEP=1; shift ;;
|
|
|
|
|
"") break ;;
|
|
|
|
|
*) echo "Argumen tidak dikenal: $1 (gunakan --deep)"; exit 1 ;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
MC_IMAGE="quay.io/minio/mc:${MINIO_MC_VERSION:-RELEASE.2025-08-13T08-35-41Z}"
|
|
|
|
|
TRINO_URL="$(sicabe_local_url https TRINO_HOST "${TRINO_HTTPS_PORT:-12443}" TRINO_URL)"
|
|
|
|
|
|
|
|
|
|
PASS_N=0
|
|
|
|
|
FAIL_N=0
|
|
|
|
|
WARN_N=0
|
|
|
|
|
SKIP_N=0
|
|
|
|
|
pass() { echo -e " ${PASS} $1"; PASS_N=$((PASS_N + 1)); }
|
|
|
|
|
FAIL_MSGS=()
|
|
|
|
|
fail() { echo -e " ${FAIL} $1"; FAIL_N=$((FAIL_N + 1)); FAIL_MSGS+=("$1"); }
|
|
|
|
|
warn() { echo -e " ${WARN} $1"; WARN_N=$((WARN_N + 1)); }
|
|
|
|
|
skip() { echo -e " ${YELLOW}[SKIP]${NC} $1"; SKIP_N=$((SKIP_N + 1)); }
|
|
|
|
|
|
2026-10-12 21:10:45 +07:00
|
|
|
# Kode HTTP untuk <url> (000 = koneksi gagal/timeout). Dipakai agar pesan FAIL
|
|
|
|
|
# menampilkan PENYEBAB (404/401/500/000), bukan hanya "tidak merespon".
|
2026-10-12 21:53:26 +07:00
|
|
|
http_code() { local c; c="$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$1" 2>/dev/null)"; printf '%s' "${c:-000}"; }
|
2026-10-12 21:10:45 +07:00
|
|
|
code_hint() {
|
|
|
|
|
case "$1" in
|
|
|
|
|
000) echo "tidak terjangkau (koneksi/DNS/timeout) — cek port, alamat, jaringan";;
|
|
|
|
|
401|403) echo "butuh autentikasi — Nessie existing mungkin memakai bearer token";;
|
|
|
|
|
404) echo "path tidak ada — versi Nessie lama / Iceberg REST belum aktif (butuh warehouse+object store) / proxy tak merutekan path ini";;
|
|
|
|
|
429) echo "rate-limited oleh proxy/edge";;
|
|
|
|
|
500|502|503|504) echo "error di server/proxy — cek konfigurasi Nessie (warehouse/object store) atau proxy";;
|
|
|
|
|
*) echo "HTTP $1";;
|
|
|
|
|
esac
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-09 02:35:14 +07:00
|
|
|
container_running() {
|
|
|
|
|
docker ps --format "{{.Names}}" 2>/dev/null | grep -q "^$1$"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# A. Object storage
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
s3_mc() {
|
|
|
|
|
# Menjalankan perintah mc di container (image ter-pin) dengan kredensial S3
|
|
|
|
|
# lewat environment, agar tidak muncul di daftar proses.
|
|
|
|
|
docker run --rm -i \
|
|
|
|
|
--network "${SICABE_NETWORK:-sicabe-network}" \
|
|
|
|
|
-e S3E="${S3_ENDPOINT}" -e S3U="${S3_ACCESS_KEY}" -e S3P="${S3_SECRET_KEY}" \
|
|
|
|
|
-e BUCKET="${BUCKET}" \
|
|
|
|
|
--entrypoint sh "${MC_IMAGE}" -c "$1" 2>&1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
check_object_storage() {
|
|
|
|
|
echo -e "${BOLD}A. Object storage — ${S3_ENDPOINT} (bucket '${BUCKET}')${NC}"
|
|
|
|
|
|
|
|
|
|
if [[ -z "${S3_SECRET_KEY}" ]]; then
|
|
|
|
|
fail "S3_SECRET_KEY / MINIO_ROOT_PASSWORD kosong — kredensial storage tidak lengkap"
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# S3_ENDPOINT umumnya hanya terjangkau dari jaringan Docker (mis.
|
|
|
|
|
# http://minio:9000) → uji keterjangkauan dilakukan dari container (uji mc).
|
|
|
|
|
# Di host kita cek port publik MinIO saja (informatif, bukan gate).
|
2026-10-13 21:26:50 +07:00
|
|
|
# Bila S3_ENDPOINT berupa ALAMAT (IP/FQDN, mis. https://minio-api.sicabe.com:8443),
|
|
|
|
|
# pakai APA ADANYA — termasuk port-nya — lewat sicabe_host_url. Tanpa ini, URL
|
|
|
|
|
# penuh diprobe di port terbit lokal (12900) → WARN palsu.
|
|
|
|
|
local host_ep="$(sicabe_host_url S3_ENDPOINT MINIO_HOST "${MINIO_PORT:-12900}" "${S3_SCHEME:-http}")"
|
2026-10-09 02:35:14 +07:00
|
|
|
local host_code
|
|
|
|
|
host_code=$(curl -sk -o /dev/null -w "%{http_code}" --max-time 8 \
|
|
|
|
|
"${host_ep}/minio/health/live" 2>/dev/null || true)
|
|
|
|
|
if [[ "${host_code}" == "200" ]]; then
|
|
|
|
|
pass "Storage terjangkau dari host: ${host_ep}"
|
|
|
|
|
else
|
2026-10-12 21:10:45 +07:00
|
|
|
warn "Storage tidak terjangkau dari host (${host_ep}) — normal bila memakai S3 eksternal [HTTP ${host_code:-000} — $(code_hint "${host_code:-000}")]"
|
2026-10-09 02:35:14 +07:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if ! docker image inspect "${MC_IMAGE}" >/dev/null 2>&1; then
|
|
|
|
|
skip "Image mc '${MC_IMAGE}' belum ada lokal — lewati uji bucket tulis/baca"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local out
|
|
|
|
|
out=$(s3_mc 'if ! mc alias set probe "$S3E" "$S3U" "$S3P" >/dev/null 2>&1; then
|
|
|
|
|
echo "mc: $(mc alias set probe "$S3E" "$S3U" "$S3P" 2>&1 | tail -2)"
|
|
|
|
|
echo ALIAS_FAIL
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
mc ls "probe/$BUCKET" >/dev/null 2>&1 && echo BUCKET_OK || echo BUCKET_MISSING')
|
|
|
|
|
|
|
|
|
|
if [[ "${out}" == *ALIAS_FAIL* ]]; then
|
|
|
|
|
fail "Gagal autentikasi/terjangkau dari container (cek S3_ENDPOINT & kredensial)"
|
|
|
|
|
warn " keluaran mc: $(echo "${out}" | tr '\n' ' ' | cut -c1-160)"
|
|
|
|
|
return 1
|
|
|
|
|
elif [[ "${out}" == *BUCKET_OK* ]]; then
|
|
|
|
|
pass "Bucket '${BUCKET}' ada dan bisa diakses dari container"
|
|
|
|
|
else
|
|
|
|
|
fail "Bucket '${BUCKET}' tidak ada / tidak bisa diakses"
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
out=$(s3_mc 'mc alias set probe "$S3E" "$S3U" "$S3P" >/dev/null 2>&1
|
|
|
|
|
echo "verify-$RANDOM" > /tmp/probe.txt
|
|
|
|
|
if mc cp /tmp/probe.txt "probe/$BUCKET/.verify/probe.txt" >/dev/null 2>&1; then
|
|
|
|
|
mc cat "probe/$BUCKET/.verify/probe.txt" >/dev/null 2>&1 && echo RW_OK || echo READ_FAIL
|
|
|
|
|
mc rm "probe/$BUCKET/.verify/probe.txt" >/dev/null 2>&1
|
|
|
|
|
else
|
|
|
|
|
echo WRITE_FAIL
|
|
|
|
|
fi')
|
|
|
|
|
case "${out}" in
|
|
|
|
|
*RW_OK*) pass "Uji tulis+baca objek di bucket '${BUCKET}' berhasil" ;;
|
|
|
|
|
*WRITE_FAIL*) fail "Gagal menulis objek ke bucket '${BUCKET}'" ;;
|
|
|
|
|
*READ_FAIL*) fail "Gagal membaca kembali objek dari bucket '${BUCKET}'" ;;
|
|
|
|
|
*) warn "Hasil uji tulis/baca tidak terbaca: ${out}" ;;
|
|
|
|
|
esac
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# B. Nessie
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
check_nessie() {
|
|
|
|
|
echo -e "${BOLD}B. Nessie — ${NESSIE_URL} (API host: ${NESSIE_HOST_API})${NC}"
|
|
|
|
|
|
2026-10-12 21:10:45 +07:00
|
|
|
local cfg code
|
2026-10-09 02:35:14 +07:00
|
|
|
cfg=$(curl -sf --max-time 10 "${NESSIE_HOST_API}/api/v2/config" 2>/dev/null || echo "")
|
|
|
|
|
if [[ -z "${cfg}" ]]; then
|
2026-10-12 21:10:45 +07:00
|
|
|
code="$(http_code "${NESSIE_HOST_API}/api/v2/config")"
|
|
|
|
|
fail "Nessie REST /api/v2/config tidak merespon di ${NESSIE_HOST_API} [HTTP ${code} — $(code_hint "${code}")]"
|
2026-10-09 02:35:14 +07:00
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
pass "Nessie REST v2 aktif"
|
|
|
|
|
echo "${cfg}" | python3 -c "
|
|
|
|
|
import sys,json
|
|
|
|
|
d=json.load(sys.stdin)
|
|
|
|
|
print(' defaultBranch =', d.get('defaultBranch'))
|
|
|
|
|
print(' specVersion =', d.get('specVersion'))" 2>/dev/null || true
|
|
|
|
|
|
|
|
|
|
local iceberg_cfg
|
2026-10-12 21:10:45 +07:00
|
|
|
iceberg_cfg=$(curl -sf --max-time 10 "${NESSIE_HOST_API}${ICEBERG_PREFIX}/v1/config" 2>/dev/null || echo "")
|
2026-10-09 02:35:14 +07:00
|
|
|
if [[ -n "${iceberg_cfg}" ]]; then
|
2026-10-12 21:10:45 +07:00
|
|
|
pass "Endpoint Iceberg REST (${ICEBERG_PREFIX}) aktif (dipakai Sling & StarRocks)"
|
2026-10-09 02:35:14 +07:00
|
|
|
else
|
2026-10-12 21:10:45 +07:00
|
|
|
code="$(http_code "${NESSIE_HOST_API}${ICEBERG_PREFIX}/v1/config")"
|
|
|
|
|
fail "Endpoint Iceberg REST ${NESSIE_HOST_API}${ICEBERG_PREFIX} tidak merespon [HTTP ${code} — $(code_hint "${code}")]"
|
2026-10-09 02:35:14 +07:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local branch
|
|
|
|
|
branch=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "${NESSIE_HOST_API}/api/v2/trees/main" 2>/dev/null || echo "000")
|
|
|
|
|
if [[ "${branch}" == "200" ]]; then
|
|
|
|
|
pass "Branch 'main' tersedia"
|
|
|
|
|
else
|
2026-10-12 21:10:45 +07:00
|
|
|
warn "Branch 'main' tidak merespon 200 [HTTP ${branch} — $(code_hint "${branch}")] — katalog mungkin masih kosong"
|
2026-10-09 02:35:14 +07:00
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# C. Version store (PostgreSQL)
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
check_version_store() {
|
|
|
|
|
echo -e "${BOLD}C. Version store Nessie di PostgreSQL (JDBC2)${NC}"
|
|
|
|
|
if ! docker ps --format "{{.Names}}" 2>/dev/null | grep -q "^sicabe-postgres$"; then
|
|
|
|
|
skip "Container sicabe-postgres tidak berjalan — lewati cek version store"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local dbs
|
|
|
|
|
dbs=$(docker exec sicabe-postgres psql -U "${POSTGRES_USER:-sicabe_admin}" -d postgres -tAc \
|
|
|
|
|
"SELECT datname FROM pg_database;" 2>/dev/null || echo "")
|
|
|
|
|
# NB: keluaran psql dipisah baris baru → gunakan grep -x (bukan "* x *").
|
|
|
|
|
if grep -qx "${NESSIE_DB}" <<< "${dbs}"; then
|
|
|
|
|
pass "Database '${NESSIE_DB}' ada"
|
|
|
|
|
else
|
|
|
|
|
fail "Database '${NESSIE_DB}' tidak ada (dibuat oleh config/postgres/init/01-create-dbs.sh)"
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local tables
|
|
|
|
|
tables=$(docker exec sicabe-postgres psql -U "${NESSIE_DB_USER:-nessie}" -d "${NESSIE_DB}" -tAc \
|
|
|
|
|
"SELECT count(*) FROM information_schema.tables WHERE table_schema='nessie';" 2>/dev/null || echo "")
|
|
|
|
|
if [[ "${tables}" =~ ^[0-9]+$ ]] && [[ "${tables}" -gt 0 ]]; then
|
|
|
|
|
pass "Schema 'nessie' berisi ${tables} tabel (version store aktif)"
|
|
|
|
|
else
|
|
|
|
|
warn "Schema 'nessie' belum berisi tabel — Nessie mungkin belum pernah start"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# D1. Kontrak Phase 2 — warehouse & endpoint S3 pada Nessie (compose)
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
check_storage_contract() {
|
|
|
|
|
echo -e "${BOLD}D1. Kontrak Phase 2 — warehouse & endpoint S3 Nessie${NC}"
|
|
|
|
|
|
|
|
|
|
local cfg wh ep
|
|
|
|
|
cfg=$(docker compose -f "${SCRIPT_DIR}/docker-compose.yml" --profile all config 2>/dev/null || echo "")
|
|
|
|
|
if [[ -z "${cfg}" ]]; then
|
|
|
|
|
fail "Tidak bisa membaca konfigurasi compose"
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
wh=$(echo "${cfg}" | sed -n 's/.*WAREHOUSE_LOCATION: *//p' | head -1 | tr -d '"')
|
|
|
|
|
ep=$(echo "${cfg}" | sed -n 's/.*DEFAULT_OPTIONS_ENDPOINT: *//p' | head -1 | tr -d '"')
|
|
|
|
|
|
2026-10-11 15:09:43 +07:00
|
|
|
if [[ "${wh}" == "s3://${BUCKET}/${WAREHOUSE}" ]]; then
|
|
|
|
|
pass "Warehouse Nessie → ${wh} (bucket '${BUCKET}' + warehouse '${WAREHOUSE}')"
|
2026-10-09 02:35:14 +07:00
|
|
|
elif [[ -n "${wh}" ]]; then
|
2026-10-11 15:09:43 +07:00
|
|
|
fail "Warehouse Nessie '${wh}' ≠ 's3://${BUCKET}/${WAREHOUSE}' — bucket/warehouse tidak sinkron"
|
2026-10-09 02:35:14 +07:00
|
|
|
else
|
|
|
|
|
fail "WAREHOUSE_LOCATION Nessie tidak ditemukan di compose"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ "${ep}" == "${S3_ENDPOINT}" ]]; then
|
|
|
|
|
pass "Endpoint S3 Nessie → ${ep}"
|
|
|
|
|
elif [[ -n "${ep}" ]]; then
|
|
|
|
|
fail "Endpoint S3 Nessie '${ep}' ≠ S3_ENDPOINT '${S3_ENDPOINT}'"
|
|
|
|
|
else
|
|
|
|
|
warn "DEFAULT_OPTIONS_ENDPOINT Nessie tidak ditemukan di compose"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
pass "Region S3: ${S3_REGION}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# D2. Antarmuka konsumen (Trino/Sling/StarRocks) — milik fase 3/4/5 → WARN/SKIP
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
check_consumer_interfaces() {
|
|
|
|
|
echo -e "${BOLD}D2. Antarmuka konsumen — Trino / Sling / StarRocks (informatif, bukan gate)${NC}"
|
|
|
|
|
|
|
|
|
|
if ! sicabe_host_expects_container sicabe-trino-coordinator; then
|
|
|
|
|
skip "Container sicabe-trino-coordinator di host lain (mode host) - dilewati"
|
|
|
|
|
elif container_running sicabe-trino-coordinator; then
|
|
|
|
|
local props="${CONFIG_DIR}/trino/catalog/iceberg.properties"
|
|
|
|
|
local prop_bucket prop_endpoint
|
|
|
|
|
prop_bucket=$(sed -n "s|.*default-warehouse-dir=s3://\([^/]*\)/.*|\1|p" "${props}" 2>/dev/null)
|
|
|
|
|
prop_endpoint=$(sed -n "s|^s3.endpoint=||p" "${props}" 2>/dev/null)
|
|
|
|
|
if [[ "${prop_bucket}" == '${ENV:MINIO_BUCKET_ICEBERG_DATA}' || "${prop_bucket}" == "${BUCKET}" ]]; then
|
|
|
|
|
pass "Trino iceberg.properties → bucket dari env (uji penuh di gate Phase 3)"
|
|
|
|
|
else
|
|
|
|
|
warn "Trino iceberg.properties bucket '${prop_bucket:-?}' ≠ '${BUCKET}'"
|
|
|
|
|
fi
|
|
|
|
|
if [[ "${prop_endpoint}" == '${ENV:S3_ENDPOINT}' || "${prop_endpoint}" == "${S3_ENDPOINT}" ]]; then
|
|
|
|
|
pass "Trino iceberg.properties → endpoint dari env"
|
|
|
|
|
else
|
|
|
|
|
warn "Trino iceberg.properties masih hardcode endpoint: ${prop_endpoint:-?}"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
skip "Trino belum berjalan (fase 3) — lewati pemeriksaan antarmukanya"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if ! sicabe_host_expects_container sicabe-airflow-webserver; then
|
|
|
|
|
skip "Container sicabe-airflow-webserver di host lain (mode host) - dilewati"
|
|
|
|
|
elif container_running sicabe-airflow-webserver; then
|
|
|
|
|
local sling_file="${CONFIG_DIR}/airflow/sling/env.yaml"
|
|
|
|
|
if [[ -f "${sling_file}" ]]; then
|
|
|
|
|
local sling_uri sling_ep
|
|
|
|
|
sling_uri=$(sed -n 's/^ *rest_uri: *//p' "${sling_file}" | head -1)
|
|
|
|
|
sling_ep=$(sed -n 's/^ *s3_endpoint: *//p' "${sling_file}" | head -1)
|
|
|
|
|
if [[ "${sling_uri}" == *nessie* ]]; then
|
|
|
|
|
pass "Sling env.yaml → rest_uri ${sling_uri}"
|
|
|
|
|
else
|
|
|
|
|
warn "Sling env.yaml rest_uri tidak menunjuk Nessie: ${sling_uri:-?}"
|
|
|
|
|
fi
|
|
|
|
|
[[ -n "${sling_ep}" ]] && pass "Sling env.yaml → s3_endpoint ${sling_ep}"
|
|
|
|
|
else
|
|
|
|
|
skip "config/airflow/sling/env.yaml belum ada (dibuat bootstrap step 6 — fase 5)"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
skip "Airflow belum berjalan (fase 5) — lewati pemeriksaan antarmuka Sling"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if ! sicabe_host_expects_container sicabe-starrocks-fe; then
|
|
|
|
|
skip "Container sicabe-starrocks-fe di host lain (mode host) - dilewati"
|
|
|
|
|
elif container_running sicabe-starrocks-fe; then
|
|
|
|
|
local sr_ep
|
|
|
|
|
sr_ep=$(grep -m1 -oE "'aws\.s3\.endpoint' = '[^']*'" "${SCRIPT_DIR}/bootstrap.sh" 2>/dev/null |
|
|
|
|
|
sed "s/.*= '//; s/'//")
|
|
|
|
|
if [[ -n "${sr_ep}" ]]; then
|
|
|
|
|
pass "StarRocks s3_volume endpoint → ${sr_ep} (uji penuh di gate Phase 4)"
|
|
|
|
|
else
|
|
|
|
|
warn "Tidak bisa membaca endpoint s3_volume StarRocks dari bootstrap.sh"
|
|
|
|
|
fi
|
|
|
|
|
else
|
|
|
|
|
skip "StarRocks belum berjalan (fase 4) — lewati pemeriksaan antarmukanya"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# E. Trino — bukti warehouse bisa dibaca
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
check_trino_read() {
|
|
|
|
|
echo -e "${BOLD}E. Trino — baca nyata dari warehouse${NC}"
|
|
|
|
|
if ! curl -skf --max-time 10 "${TRINO_URL}/v1/info" >/dev/null 2>&1; then
|
|
|
|
|
skip "Trino belum siap — lewati uji baca"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local result
|
|
|
|
|
result=$(curl -sk --max-time 20 -X POST "${TRINO_URL}/v1/statement" \
|
|
|
|
|
-u "svc_seed:${TRINO_SVC_SEED_PASSWORD}" \
|
|
|
|
|
-H "Content-Type: text/plain" \
|
|
|
|
|
-d "SELECT count(*) FROM iceberg.bronze.orders" 2>/dev/null || echo "")
|
|
|
|
|
|
|
|
|
|
if [[ -z "${result}" ]]; then
|
|
|
|
|
skip "Query Trino tidak menghasilkan respon (service account/permission)"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Trino mengembalikan status QUEUED + nextUri → perlu polling sampai FINISHED.
|
|
|
|
|
local state next tries=0
|
|
|
|
|
while [[ ${tries} -lt 15 ]]; do
|
|
|
|
|
state=$(echo "${result}" | python3 -c "
|
|
|
|
|
import sys,json
|
|
|
|
|
try: print(json.load(sys.stdin).get('stats',{}).get('state',''))
|
|
|
|
|
except Exception: print('')" 2>/dev/null)
|
|
|
|
|
[[ "${state}" == "FINISHED" || "${state}" == "FAILED" ]] && break
|
|
|
|
|
next=$(echo "${result}" | python3 -c "
|
|
|
|
|
import sys,json
|
|
|
|
|
try: print(json.load(sys.stdin).get('nextUri',''))
|
|
|
|
|
except Exception: print('')" 2>/dev/null)
|
|
|
|
|
[[ -z "${next}" ]] && break
|
|
|
|
|
sleep 1
|
|
|
|
|
result=$(curl -sk --max-time 20 "${next}" 2>/dev/null || echo "")
|
|
|
|
|
tries=$((tries + 1))
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [[ "${state}" == "FINISHED" ]]; then
|
|
|
|
|
pass "Query 'SELECT count(*) FROM iceberg.bronze.orders' FINISHED (warehouse terbaca)"
|
|
|
|
|
elif [[ "${state}" == "FAILED" ]]; then
|
|
|
|
|
warn "Query gagal — kemungkinan tabel bronze.orders belum di-seed (scripts/seed-sample-data.sh)"
|
|
|
|
|
else
|
|
|
|
|
skip "Status query belum selesai (${state:-tidak diketahui})"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# Main
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
main() {
|
|
|
|
|
print_section "Verifikasi Phase 2 — Kontrak Storage & Catalog (MinIO/S3 + Nessie)"
|
2026-10-11 15:09:43 +07:00
|
|
|
echo -e " Object storage : ${CYAN}${S3_ENDPOINT}${NC} (bucket ${BUCKET}, warehouse ${WAREHOUSE}, region ${S3_REGION})"
|
2026-10-09 02:35:14 +07:00
|
|
|
echo -e " Nessie : ${CYAN}${NESSIE_URL}${NC} (API host ${NESSIE_HOST_API})"
|
|
|
|
|
echo ""
|
|
|
|
|
|
|
|
|
|
check_object_storage
|
|
|
|
|
check_nessie
|
|
|
|
|
check_version_store
|
|
|
|
|
check_storage_contract
|
|
|
|
|
check_consumer_interfaces
|
|
|
|
|
|
|
|
|
|
if [[ ${DEEP} -eq 1 ]]; then
|
|
|
|
|
check_trino_read
|
|
|
|
|
else
|
|
|
|
|
echo -e "${INFO} Mode gate fase 2: uji baca via Trino DILEWATI (runtime fase 3) — pakai --deep untuk menjalankannya."
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${BOLD}Ringkasan:${NC} ${GREEN}${PASS_N} PASS${NC} · ${RED}${FAIL_N} FAIL${NC} · ${YELLOW}${WARN_N} WARN${NC} · ${YELLOW}${SKIP_N} SKIP${NC}"
|
|
|
|
|
if [[ ${FAIL_N} -gt 0 ]]; then
|
|
|
|
|
echo ""
|
|
|
|
|
echo -e "${BOLD}Yang gagal (${FAIL_N}):${NC}"
|
|
|
|
|
local _i=1 _m
|
|
|
|
|
for _m in "${FAIL_MSGS[@]}"; do
|
|
|
|
|
echo -e " ${RED}${_i}.${NC} ${_m}"
|
|
|
|
|
_i=$((_i + 1))
|
|
|
|
|
done
|
|
|
|
|
echo -e "${INFO} Perbaiki item di atas, lalu jalankan ulang: ./scripts/${0##*/}"
|
|
|
|
|
fi
|
|
|
|
|
if [[ ${FAIL_N} -eq 0 ]]; then
|
|
|
|
|
echo -e "${OK} ${GREEN}Kontrak storage Phase 2 terpenuhi — aman lanjut ke fase berikutnya.${NC}"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
echo -e "${ERR} Ada ${FAIL_N} gate yang gagal — perbaiki sebelum melanjutkan."
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main "$@"
|