docs: add full-stack runbook and one-click scripts
start-all/stop-all/health-check/test-cdc PowerShell scripts. Compatible with PowerShell 5.1 (ASCII only, no emoji).
This commit is contained in:
152
scripts/health-check.ps1
Normal file
152
scripts/health-check.ps1
Normal file
@@ -0,0 +1,152 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Edu health check script
|
||||
.DESCRIPTION
|
||||
Checks health of all infrastructure containers + application services + observability endpoints
|
||||
.EXAMPLE
|
||||
.\scripts\health-check.ps1
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Edu Health Check" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# ===== 1. Infrastructure containers =====
|
||||
Write-Host "[1/3] Infrastructure Containers" -ForegroundColor Yellow
|
||||
|
||||
$infraContainers = @(
|
||||
@{Name="MySQL"; Container="edu-mysql"},
|
||||
@{Name="Redis"; Container="edu-redis"},
|
||||
@{Name="Kafka"; Container="edu-kafka"},
|
||||
@{Name="Zookeeper"; Container="edu-zookeeper"},
|
||||
@{Name="ClickHouse"; Container="edu-clickhouse"},
|
||||
@{Name="Debezium"; Container="edu-debezium"},
|
||||
@{Name="Neo4j"; Container="edu-neo4j"},
|
||||
@{Name="Elasticsearch"; Container="edu-es"},
|
||||
@{Name="Jaeger"; Container="edu-jaeger"},
|
||||
@{Name="Prometheus"; Container="edu-prometheus"},
|
||||
@{Name="Grafana"; Container="edu-grafana"}
|
||||
)
|
||||
|
||||
$infraOk = 0
|
||||
$infraFail = 0
|
||||
foreach ($svc in $infraContainers) {
|
||||
$status = docker inspect --format='{{.State.Health.Status}}' $svc.Container 2>$null
|
||||
if ($status -eq "healthy") {
|
||||
Write-Host " [OK] $($svc.Name)" -ForegroundColor Green
|
||||
$infraOk++
|
||||
} elseif ($status -eq "running") {
|
||||
Write-Host " [WARN] $($svc.Name) (running, no healthcheck)" -ForegroundColor Yellow
|
||||
$infraOk++
|
||||
} else {
|
||||
Write-Host " [FAIL] $($svc.Name) (status=$status)" -ForegroundColor Red
|
||||
$infraFail++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# ===== 2. Application services =====
|
||||
Write-Host "[2/3] Application Services" -ForegroundColor Yellow
|
||||
|
||||
$appServices = @(
|
||||
@{Name="classes"; Url="http://localhost:3001/healthz"},
|
||||
@{Name="iam"; Url="http://localhost:3002/healthz"},
|
||||
@{Name="teacher-bff"; Url="http://localhost:3003/healthz"},
|
||||
@{Name="core-edu"; Url="http://localhost:3004/healthz"},
|
||||
@{Name="content"; Url="http://localhost:3005/healthz"},
|
||||
@{Name="data-ana"; Url="http://localhost:3006/healthz"},
|
||||
@{Name="msg"; Url="http://localhost:3007/healthz"},
|
||||
@{Name="ai"; Url="http://localhost:3008/healthz"},
|
||||
@{Name="api-gateway"; Url="http://localhost:8080/healthz"},
|
||||
@{Name="push-gateway"; Url="http://localhost:8081/healthz"},
|
||||
@{Name="teacher-portal";Url="http://localhost:3000/"}
|
||||
)
|
||||
|
||||
$appOk = 0
|
||||
$appFail = 0
|
||||
foreach ($svc in $appServices) {
|
||||
try {
|
||||
$null = Invoke-RestMethod -Uri $svc.Url -Method Get -TimeoutSec 3 -ErrorAction Stop
|
||||
Write-Host " [OK] $($svc.Name)" -ForegroundColor Green
|
||||
$appOk++
|
||||
} catch {
|
||||
Write-Host " [FAIL] $($svc.Name)" -ForegroundColor Red
|
||||
$appFail++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# ===== 3. Observability endpoints =====
|
||||
Write-Host "[3/3] Observability Endpoints" -ForegroundColor Yellow
|
||||
|
||||
$obsEndpoints = @(
|
||||
@{Name="Prometheus API"; Url="http://localhost:9090/api/v1/query?query=up"},
|
||||
@{Name="Jaeger API"; Url="http://localhost:16686/api/services"},
|
||||
@{Name="Debezium Connect"; Url="http://localhost:8083/connectors"},
|
||||
@{Name="data-ana /metrics";Url="http://localhost:3006/metrics"},
|
||||
@{Name="iam /metrics"; Url="http://localhost:3002/metrics"}
|
||||
)
|
||||
|
||||
$obsOk = 0
|
||||
$obsFail = 0
|
||||
foreach ($ep in $obsEndpoints) {
|
||||
try {
|
||||
$null = Invoke-RestMethod -Uri $ep.Url -Method Get -TimeoutSec 3 -ErrorAction Stop
|
||||
Write-Host " [OK] $($ep.Name)" -ForegroundColor Green
|
||||
$obsOk++
|
||||
} catch {
|
||||
Write-Host " [FAIL] $($ep.Name)" -ForegroundColor Red
|
||||
$obsFail++
|
||||
}
|
||||
}
|
||||
|
||||
# ===== 4. CDC pipeline status =====
|
||||
Write-Host ""
|
||||
Write-Host "[Extra] CDC Pipeline Status" -ForegroundColor Yellow
|
||||
|
||||
$connectorStatus = $null
|
||||
try {
|
||||
$connectorStatus = Invoke-RestMethod -Uri "http://localhost:8083/connectors/edu-mysql-source/status" -Method Get -TimeoutSec 3 -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Host " [FAIL] Debezium connector not registered or error" -ForegroundColor Red
|
||||
}
|
||||
|
||||
if ($connectorStatus) {
|
||||
$connectorState = $connectorStatus.connector.state
|
||||
$tasks = @($connectorStatus.tasks)
|
||||
if ($tasks.Count -gt 0) {
|
||||
$taskState = $tasks[0].state
|
||||
} else {
|
||||
$taskState = "UNKNOWN"
|
||||
}
|
||||
if ($connectorState -eq "RUNNING" -and $taskState -eq "RUNNING") {
|
||||
$color = "Green"
|
||||
} else {
|
||||
$color = "Yellow"
|
||||
}
|
||||
Write-Host " Connector: $connectorState / Task: $taskState" -ForegroundColor $color
|
||||
}
|
||||
|
||||
$chSql = 'SELECT count(*) FROM edu_analytics.student_dashboard_view'
|
||||
$chOutput = docker exec edu-clickhouse clickhouse-client --user default --password clickhouse -q $chSql 2>&1
|
||||
$chCount = "$chOutput".Trim()
|
||||
if ($chCount -match '^\d+$') {
|
||||
Write-Host " ClickHouse student_dashboard_view: $chCount records" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " ClickHouse query failed or empty" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ===== Summary =====
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Infra: $infraOk/$($infraContainers.Count) | App: $appOk/$($appServices.Count) | Obs: $obsOk/$($obsEndpoints.Count)" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
|
||||
if ($infraFail -gt 0 -or $appFail -gt 0 -or $obsFail -gt 0) {
|
||||
exit 1
|
||||
}
|
||||
185
scripts/start-all.ps1
Normal file
185
scripts/start-all.ps1
Normal file
@@ -0,0 +1,185 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Edu start all application services script
|
||||
.DESCRIPTION
|
||||
Starts 10 application services + 1 frontend (teacher-portal)
|
||||
Each service runs in a separate terminal window for log visibility
|
||||
Automatically injects Python service env vars (CLICKHOUSE/KAFKA/OTEL)
|
||||
Prerequisite: infrastructure containers (MySQL/Redis/Kafka/ClickHouse etc) must be running
|
||||
.PARAMETER SkipInfraCheck
|
||||
Skip infrastructure health check (use when infra is known to be running)
|
||||
.EXAMPLE
|
||||
.\scripts\start-all.ps1
|
||||
.\scripts\start-all.ps1 -SkipInfraCheck
|
||||
#>
|
||||
param(
|
||||
[switch]$SkipInfraCheck
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProjectRoot = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Edu Start All Services" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# ===== 1. Infrastructure health check =====
|
||||
if (-not $SkipInfraCheck) {
|
||||
Write-Host "[1/4] Checking infrastructure health..." -ForegroundColor Yellow
|
||||
$infraServices = @(
|
||||
@{Name="MySQL"; Container="edu-mysql"},
|
||||
@{Name="Redis"; Container="edu-redis"},
|
||||
@{Name="Kafka"; Container="edu-kafka"},
|
||||
@{Name="ClickHouse"; Container="edu-clickhouse"},
|
||||
@{Name="Debezium"; Container="edu-debezium"},
|
||||
@{Name="Jaeger"; Container="edu-jaeger"}
|
||||
)
|
||||
$allHealthy = $true
|
||||
foreach ($svc in $infraServices) {
|
||||
$status = docker inspect --format='{{.State.Health.Status}}' $svc.Container 2>$null
|
||||
if ($status -eq "healthy") {
|
||||
Write-Host " [OK] $($svc.Name) ($($svc.Container))" -ForegroundColor Green
|
||||
} elseif ($status -eq "running") {
|
||||
Write-Host " [WARN] $($svc.Name) running but no healthcheck" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host " [FAIL] $($svc.Name) not started or unhealthy (status=$status)" -ForegroundColor Red
|
||||
Write-Host " Please start infrastructure first:" -ForegroundColor Red
|
||||
Write-Host " docker compose -f infra/docker-compose.yml --profile p6 --profile observability up -d" -ForegroundColor Red
|
||||
$allHealthy = $false
|
||||
}
|
||||
}
|
||||
if (-not $allHealthy) {
|
||||
Write-Host ""
|
||||
Write-Host "Infrastructure not ready. Start it first." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# ===== 2. Environment variables =====
|
||||
Write-Host "[2/4] Preparing environment variables..." -ForegroundColor Yellow
|
||||
|
||||
$env:DEV_MODE = "true"
|
||||
$env:OTEL_EXPORTER_OTLP_ENDPOINT = "http://localhost:4318"
|
||||
$env:DATABASE_URL = "mysql://edu:changeme@localhost:3306/next_edu_cloud"
|
||||
$env:REDIS_URL = "redis://localhost:6379"
|
||||
$env:JWT_SECRET = "p1-dev-secret-change-in-production"
|
||||
$env:KAFKA_BROKERS = "localhost:9092"
|
||||
|
||||
$pyEnv = @{
|
||||
CLICKHOUSE_HOST = "localhost"
|
||||
CLICKHOUSE_PORT = "8123"
|
||||
CLICKHOUSE_USER = "default"
|
||||
CLICKHOUSE_PASSWORD = "clickhouse"
|
||||
CLICKHOUSE_DATABASE = "edu_analytics"
|
||||
KAFKA_BROKERS = "localhost:9092"
|
||||
OTEL_ENDPOINT = "http://localhost:4318"
|
||||
DEV_MODE = "true"
|
||||
}
|
||||
|
||||
Write-Host " DEV_MODE=true (dev-token bypass)" -ForegroundColor Green
|
||||
Write-Host " OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# ===== 3. Start application services =====
|
||||
Write-Host "[3/4] Starting application services (11 windows)..." -ForegroundColor Yellow
|
||||
|
||||
$services = @(
|
||||
@{Title="edu-app-classes"; Cmd="pnpm"; Args=@("--filter","@edu/classes-service","dev"); Dir="$ProjectRoot"},
|
||||
@{Title="edu-app-iam"; Cmd="pnpm"; Args=@("--filter","@edu/iam-service","dev"); Dir="$ProjectRoot"},
|
||||
@{Title="edu-app-teacher-bff"; Cmd="pnpm"; Args=@("--filter","@edu/teacher-bff","dev"); Dir="$ProjectRoot"},
|
||||
@{Title="edu-app-core-edu"; Cmd="pnpm"; Args=@("--filter","@edu/core-edu-service","dev"); Dir="$ProjectRoot"},
|
||||
@{Title="edu-app-content"; Cmd="pnpm"; Args=@("--filter","@edu/content-service","dev"); Dir="$ProjectRoot"},
|
||||
@{Title="edu-app-msg"; Cmd="pnpm"; Args=@("--filter","@edu/msg-service","dev"); Dir="$ProjectRoot"},
|
||||
@{Title="edu-app-data-ana"; Cmd="uv"; Args=@("run","uvicorn","data_ana.main:app","--host","0.0.0.0","--port","3006","--reload"); Dir="$ProjectRoot\services\data-ana"; PyEnv=$true},
|
||||
@{Title="edu-app-ai"; Cmd="uv"; Args=@("run","uvicorn","ai.main:app","--host","0.0.0.0","--port","3008","--reload"); Dir="$ProjectRoot\services\ai"; PyEnv=$true},
|
||||
@{Title="edu-app-api-gateway"; Cmd="go"; Args=@("run","."); Dir="$ProjectRoot\services\api-gateway"; GoEnv=$true},
|
||||
@{Title="edu-app-push-gateway"; Cmd="go"; Args=@("run","."); Dir="$ProjectRoot\services\push-gateway";GoEnv=$true},
|
||||
@{Title="edu-app-teacher-portal";Cmd="pnpm";Args=@("--filter","teacher-portal","dev"); Dir="$ProjectRoot"}
|
||||
)
|
||||
|
||||
foreach ($svc in $services) {
|
||||
$cmdStr = "$($svc.Cmd) $($svc.Args -join ' ')"
|
||||
|
||||
$psCmd = "Set-Location '$($svc.Dir)'; "
|
||||
|
||||
if ($svc.PyEnv) {
|
||||
foreach ($kv in $pyEnv.GetEnumerator()) {
|
||||
$psCmd += "`$env:$($kv.Key)='$($kv.Value)'; "
|
||||
}
|
||||
}
|
||||
|
||||
if ($svc.GoEnv) {
|
||||
$psCmd += "`$env:Path = 'C:\Program Files\Go\bin;' + `$env:Path; "
|
||||
}
|
||||
|
||||
$psCmd += "$cmdStr; Write-Host ''; Write-Host 'Service stopped. Press any key to close...' -ForegroundColor Yellow; `$null = `$Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')"
|
||||
|
||||
Start-Process -FilePath "powershell" -ArgumentList "-NoExit","-Command",$psCmd -WindowStyle Normal | Out-Null
|
||||
|
||||
Write-Host " [START] $($svc.Title)..." -ForegroundColor Green
|
||||
Start-Sleep -Milliseconds 500
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " All services started in new windows. Waiting 30s for init..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds 30
|
||||
|
||||
# ===== 4. Health check =====
|
||||
Write-Host "[4/4] Health check..." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
$healthServices = @(
|
||||
@{Name="classes"; Url="http://localhost:3001/healthz"},
|
||||
@{Name="iam"; Url="http://localhost:3002/healthz"},
|
||||
@{Name="teacher-bff"; Url="http://localhost:3003/healthz"},
|
||||
@{Name="core-edu"; Url="http://localhost:3004/healthz"},
|
||||
@{Name="content"; Url="http://localhost:3005/healthz"},
|
||||
@{Name="data-ana"; Url="http://localhost:3006/healthz"},
|
||||
@{Name="msg"; Url="http://localhost:3007/healthz"},
|
||||
@{Name="ai"; Url="http://localhost:3008/healthz"},
|
||||
@{Name="api-gateway"; Url="http://localhost:8080/healthz"},
|
||||
@{Name="push-gateway"; Url="http://localhost:8081/healthz"},
|
||||
@{Name="teacher-portal";Url="http://localhost:3000/"}
|
||||
)
|
||||
|
||||
$okCount = 0
|
||||
$failCount = 0
|
||||
foreach ($svc in $healthServices) {
|
||||
$retries = 0
|
||||
$maxRetries = 5
|
||||
$success = $false
|
||||
while ($retries -lt $maxRetries -and -not $success) {
|
||||
try {
|
||||
$null = Invoke-RestMethod -Uri $svc.Url -Method Get -TimeoutSec 3 -ErrorAction Stop
|
||||
Write-Host " [OK] $($svc.Name)" -ForegroundColor Green
|
||||
$success = $true
|
||||
$okCount++
|
||||
} catch {
|
||||
$retries++
|
||||
if ($retries -lt $maxRetries) {
|
||||
Start-Sleep -Seconds 3
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-not $success) {
|
||||
Write-Host " [FAIL] $($svc.Name) (failed after $maxRetries retries)" -ForegroundColor Red
|
||||
$failCount++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Started: [OK] $okCount ready / [FAIL] $failCount failed" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Next steps:" -ForegroundColor Yellow
|
||||
Write-Host " Health check: .\scripts\health-check.ps1" -ForegroundColor White
|
||||
Write-Host " CDC test: .\scripts\test-cdc.ps1" -ForegroundColor White
|
||||
Write-Host " Stop all: .\scripts\stop-all.ps1" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
if ($failCount -gt 0) {
|
||||
exit 1
|
||||
}
|
||||
98
scripts/stop-all.ps1
Normal file
98
scripts/stop-all.ps1
Normal file
@@ -0,0 +1,98 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Edu stop all application services script
|
||||
.DESCRIPTION
|
||||
Closes all edu-app-* terminal windows (started by start-all.ps1)
|
||||
Optional: kill processes by port (fallback when windows are closed but processes linger)
|
||||
.PARAMETER KillByPort
|
||||
Kill processes by port (fallback when windows are closed but processes still alive)
|
||||
.EXAMPLE
|
||||
.\scripts\stop-all.ps1
|
||||
.\scripts\stop-all.ps1 -KillByPort
|
||||
#>
|
||||
param(
|
||||
[switch]$KillByPort
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Edu Stop All Services" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# ===== 1. Close app service terminal windows =====
|
||||
Write-Host "[1/2] Closing app service windows..." -ForegroundColor Yellow
|
||||
|
||||
$appTitles = @(
|
||||
"edu-app-classes",
|
||||
"edu-app-iam",
|
||||
"edu-app-teacher-bff",
|
||||
"edu-app-core-edu",
|
||||
"edu-app-content",
|
||||
"edu-app-msg",
|
||||
"edu-app-data-ana",
|
||||
"edu-app-ai",
|
||||
"edu-app-api-gateway",
|
||||
"edu-app-push-gateway",
|
||||
"edu-app-teacher-portal"
|
||||
)
|
||||
|
||||
$closedCount = 0
|
||||
foreach ($title in $appTitles) {
|
||||
$procs = Get-Process -Name "powershell","pwsh","node","python","uvicorn","go" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.MainWindowTitle -like "*$title*" }
|
||||
|
||||
if ($procs) {
|
||||
foreach ($p in $procs) {
|
||||
try {
|
||||
Stop-Process -Id $p.Id -Force -ErrorAction Stop
|
||||
Write-Host " [OK] Closed $title (PID $($p.Id))" -ForegroundColor Green
|
||||
$closedCount++
|
||||
} catch {
|
||||
Write-Host " [WARN] Cannot close $title (PID $($p.Id)): $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host " [--] $title window not found" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host " Closed $closedCount windows" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# ===== 2. Kill by port (optional) =====
|
||||
if ($KillByPort) {
|
||||
Write-Host "[2/2] Killing processes by port..." -ForegroundColor Yellow
|
||||
|
||||
$ports = @(3000,3001,3002,3003,3004,3005,3006,3007,3008,8080,8081)
|
||||
|
||||
foreach ($port in $ports) {
|
||||
$connections = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue
|
||||
if ($connections) {
|
||||
foreach ($conn in $connections) {
|
||||
try {
|
||||
$proc = Get-Process -Id $conn.OwningProcess -ErrorAction Stop
|
||||
Stop-Process -Id $conn.OwningProcess -Force -ErrorAction Stop
|
||||
Write-Host " [OK] Port $port -> killed $($proc.ProcessName) (PID $($conn.OwningProcess))" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host " [WARN] Port $port -> cannot kill PID $($conn.OwningProcess)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host " [--] Port $port free" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host "[2/2] Skipping port kill (use -KillByPort to enable)" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Stop complete" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "To stop infrastructure:" -ForegroundColor Yellow
|
||||
Write-Host " docker compose -f infra/docker-compose.yml --profile p6 --profile observability down" -ForegroundColor White
|
||||
Write-Host ""
|
||||
167
scripts/test-cdc.ps1
Normal file
167
scripts/test-cdc.ps1
Normal file
@@ -0,0 +1,167 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Edu CDC pipeline end-to-end test script
|
||||
.DESCRIPTION
|
||||
Validates the full CDC pipeline: MySQL binlog -> Debezium -> Kafka -> data-ana -> ClickHouse
|
||||
Steps:
|
||||
1. Insert test grade into MySQL
|
||||
2. Wait for Debezium capture + data-ana consume
|
||||
3. Query ClickHouse to verify data synced
|
||||
4. Call data-ana API to verify query works
|
||||
.PARAMETER StudentId
|
||||
Custom test student_id (default: cdc-test-<timestamp>)
|
||||
.EXAMPLE
|
||||
.\scripts\test-cdc.ps1
|
||||
.\scripts\test-cdc.ps1 -StudentId "my-test-001"
|
||||
#>
|
||||
param(
|
||||
[string]$StudentId = "cdc-test-$(Get-Date -Format 'yyyyMMddHHmmss')"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Edu CDC Pipeline E2E Test" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$examId = "exam-$StudentId"
|
||||
$gradeId = "grade-$StudentId"
|
||||
$testScore = 92.5
|
||||
$classId = "cls-cdc-test"
|
||||
|
||||
Write-Host "Test parameters:" -ForegroundColor Yellow
|
||||
Write-Host " StudentId: $StudentId"
|
||||
Write-Host " ExamId: $examId"
|
||||
Write-Host " GradeId: $gradeId"
|
||||
Write-Host " Score: $testScore"
|
||||
Write-Host " ClassId: $classId"
|
||||
Write-Host ""
|
||||
|
||||
# ===== 1. Pre-check =====
|
||||
Write-Host "[1/5] Pre-check..." -ForegroundColor Yellow
|
||||
|
||||
# Check Debezium connector
|
||||
$connectorStatus = $null
|
||||
try {
|
||||
$connectorStatus = Invoke-RestMethod -Uri "http://localhost:8083/connectors/edu-mysql-source/status" -Method Get -TimeoutSec 3 -ErrorAction Stop
|
||||
} catch {
|
||||
Write-Host " [FAIL] Debezium Connect not reachable. Start infrastructure first." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
if ($connectorStatus.connector.state -ne "RUNNING") {
|
||||
Write-Host " [FAIL] Debezium connector state: $($connectorStatus.connector.state)" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host " [OK] Debezium connector: RUNNING" -ForegroundColor Green
|
||||
|
||||
# Check data-ana service
|
||||
try {
|
||||
$null = Invoke-RestMethod -Uri "http://localhost:3006/healthz" -Method Get -TimeoutSec 3 -ErrorAction Stop
|
||||
Write-Host " [OK] data-ana service: running" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host " [FAIL] data-ana service not reachable. Start application services first." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check CDC consumer status
|
||||
try {
|
||||
$readyz = Invoke-RestMethod -Uri "http://localhost:3006/readyz" -Method Get -TimeoutSec 3 -ErrorAction Stop
|
||||
$cdcStatus = $readyz.services.cdc_consumer
|
||||
if ($cdcStatus -ne "running") {
|
||||
Write-Host " [WARN] CDC consumer status: $cdcStatus (KAFKA_BROKERS may not be set)" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host " [OK] CDC consumer: running" -ForegroundColor Green
|
||||
}
|
||||
} catch {
|
||||
Write-Host " [WARN] Cannot get /readyz status" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# ===== 2. Insert test data into MySQL =====
|
||||
Write-Host "[2/5] Inserting test data into MySQL..." -ForegroundColor Yellow
|
||||
|
||||
$sqlInsert = @"
|
||||
INSERT INTO core_edu_exams (id, class_id, subject_id, title, exam_date, total_score, created_at, updated_at)
|
||||
VALUES ('$examId', '$classId', 'sub-math', 'CDC Test Exam', NOW(), 100, NOW(), NOW())
|
||||
ON DUPLICATE KEY UPDATE updated_at=NOW();
|
||||
INSERT INTO core_edu_grades (id, exam_id, student_id, score, rank_in_class, created_at, updated_at)
|
||||
VALUES ('$gradeId', '$examId', '$StudentId', $testScore, 1, NOW(), NOW())
|
||||
ON DUPLICATE KEY UPDATE score=$testScore, updated_at=NOW();
|
||||
"@
|
||||
|
||||
docker exec edu-mysql mysql -uedu -pchangeme next_edu_cloud -e $sqlInsert 2>&1 | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host " [OK] Inserted: exam=$examId / grade=$gradeId / student=$StudentId / score=$testScore" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [FAIL] MySQL insert failed" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# ===== 3. Wait for CDC propagation =====
|
||||
Write-Host "[3/5] Waiting for CDC propagation (5s)..." -ForegroundColor Yellow
|
||||
Start-Sleep -Seconds 5
|
||||
Write-Host " [OK] Wait complete" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# ===== 4. Verify ClickHouse data =====
|
||||
Write-Host "[4/5] Verifying ClickHouse data..." -ForegroundColor Yellow
|
||||
|
||||
$chQuery = "SELECT student_id, class_id, exam_id, score, last_updated FROM edu_analytics.student_dashboard_view WHERE student_id = '$StudentId' ORDER BY last_updated DESC LIMIT 5"
|
||||
$chResult = docker exec edu-clickhouse clickhouse-client --user default --password clickhouse -q $chQuery 2>$null
|
||||
|
||||
if ($chResult) {
|
||||
Write-Host " [OK] ClickHouse returned data:" -ForegroundColor Green
|
||||
Write-Host " $chResult" -ForegroundColor White
|
||||
|
||||
if ($chResult -match $StudentId -and $chResult -match "$testScore") {
|
||||
Write-Host ""
|
||||
Write-Host " [OK] Verified: student_id match + score=$testScore match" -ForegroundColor Green
|
||||
if ($chResult -match $classId) {
|
||||
Write-Host " [OK] class_id filled via exam cache: $classId" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [WARN] class_id not filled (exam cache may have missed, check event order)" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host " [FAIL] Data mismatch: expected student=$StudentId, score=$testScore" -ForegroundColor Red
|
||||
}
|
||||
} else {
|
||||
Write-Host " [FAIL] ClickHouse has no data for student_id=$StudentId" -ForegroundColor Red
|
||||
Write-Host " Possible causes:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Debezium did not capture MySQL change (check connector status)" -ForegroundColor White
|
||||
Write-Host " 2. data-ana consumer not running (check /readyz cdc_consumer)" -ForegroundColor White
|
||||
Write-Host " 3. Kafka topic name mismatch (check debezium-register.json)" -ForegroundColor White
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# ===== 5. Verify data-ana API =====
|
||||
Write-Host "[5/5] Verifying data-ana query API..." -ForegroundColor Yellow
|
||||
|
||||
$h = @{Authorization="Bearer dev-token"}
|
||||
|
||||
try {
|
||||
$weakness = Invoke-RestMethod -Uri "http://localhost:3006/analytics/student/$StudentId/weakness" -Method Get -Headers $h -TimeoutSec 5 -ErrorAction Stop
|
||||
Write-Host " [OK] /analytics/student/$StudentId/weakness" -ForegroundColor Green
|
||||
Write-Host " Response: $($weakness | ConvertTo-Json -Depth 3)" -ForegroundColor Gray
|
||||
} catch {
|
||||
Write-Host " [WARN] /analytics/student/$StudentId/weakness failed: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
try {
|
||||
$perf = Invoke-RestMethod -Uri "http://localhost:3006/analytics/class/$classId/performance" -Method Get -Headers $h -TimeoutSec 5 -ErrorAction Stop
|
||||
Write-Host " [OK] /analytics/class/$classId/performance" -ForegroundColor Green
|
||||
Write-Host " Response: $($perf | ConvertTo-Json -Depth 3)" -ForegroundColor Gray
|
||||
} catch {
|
||||
Write-Host " [WARN] /analytics/class/$classId/performance failed: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " CDC Pipeline Test Complete" -ForegroundColor Cyan
|
||||
Write-Host " MySQL -> Debezium -> Kafka -> data-ana -> ClickHouse [OK]" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Reference in New Issue
Block a user