Files
awoooi/scripts/test-k8s-executor.js
OG T 7478dc0254 feat(phase6-9): Complete modular architecture and Agent Teams
Phase 6.4 - Modular Architecture:
- Add lewooogo-brain adapters for LLM providers
- Add lewooogo-data dual memory (Redis + PostgreSQL)
- Implement consensus engine for multi-agent decisions
- Add incident memory service for historical context

Phase 9 - Agent Teams (Claude Agent SDK):
- Add base agent class with Claude Sonnet 4 integration
- Implement action planner, blast radius, and security agents
- Add agent API endpoints and proposal workflow
- Integrate ADR-009 OpenClaw Agent Teams architecture

DevOps & CI/CD:
- Add GitHub Actions CI/CD workflows (ci.yaml, cd.yaml)
- Add pre-commit hooks and secrets baseline
- Add docker-compose for local development
- Update Kubernetes network policies

Frontend Improvements:
- Add auto-healing error boundary component
- Update i18n messages for agent features
- Enhance dual-state incident card with execution feedback

Documentation:
- Add 7 ADRs covering MCP, design system, architecture decisions
- Update ARCHITECTURE_MEMORY.md with modular design
- Add GLOBAL_RULES.md and SOUL.md for project identity

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-23 18:40:36 +08:00

253 lines
7.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* AWOOOI 自動化 QA - K8s Executor 端對端測試
* ==========================================
* Phase 3: 驗證 K8s 執行器實際運作
*
* 測試流程:
* 1. 檢查 K8s 叢集連線
* 2. 創建/驗證 sandbox namespace
* 3. 部署測試 Pod
* 4. 發射告警 → 創建 Approval
* 5. 模擬簽核 (Multi-Sig)
* 6. 驗證 Pod 被實際重啟 (AGE 歸零)
*
* 用法: node scripts/test-k8s-executor.js
*/
const http = require('http')
const { execSync } = require('child_process')
const API_URL = 'http://localhost:8000'
const NAMESPACE = 'awoooi-sandbox'
const TEST_POD_NAME = 'qa-test-pod'
const KUBECONFIG = '/Users/ogt/awoooi/apps/api/k3s-prod.yaml'
// ANSI Colors
const GREEN = '\x1b[32m'
const RED = '\x1b[31m'
const YELLOW = '\x1b[33m'
const CYAN = '\x1b[36m'
const RESET = '\x1b[0m'
function log(status, message) {
const icon = status === 'pass' ? `${GREEN}${RESET}` :
status === 'fail' ? `${RED}${RESET}` :
status === 'info' ? `${CYAN}${RESET}` : `${YELLOW}?${RESET}`
console.log(`${icon} ${message}`)
}
function kubectl(cmd) {
try {
return execSync(`KUBECONFIG=${KUBECONFIG} kubectl ${cmd}`, { encoding: 'utf-8', timeout: 30000 }).trim()
} catch (err) {
return null
}
}
function httpRequest(method, path, body = null) {
return new Promise((resolve, reject) => {
const url = new URL(path, API_URL)
const options = {
hostname: url.hostname,
port: url.port,
path: url.pathname,
method,
headers: { 'Content-Type': 'application/json' },
}
const req = http.request(options, (res) => {
let data = ''
res.on('data', (chunk) => (data += chunk))
res.on('end', () => {
try {
resolve({ status: res.statusCode, data: JSON.parse(data) })
} catch (e) {
resolve({ status: res.statusCode, data: data })
}
})
})
req.on('error', reject)
if (body) req.write(JSON.stringify(body))
req.end()
})
}
async function main() {
console.log('\n' + '═'.repeat(55))
console.log(' AWOOOI Phase 3 QA - K8s Executor 端對端測試')
console.log('═'.repeat(55) + '\n')
// Step 1: Check K8s cluster connection
log('info', '步驟 1: 檢查 K8s 叢集連線...')
const version = kubectl('version --client -o json')
if (version) {
log('pass', 'kubectl 可用')
} else {
log('fail', 'kubectl 不可用,請確認 PATH')
process.exit(1)
}
const clusterInfo = kubectl('cluster-info 2>/dev/null')
if (clusterInfo && clusterInfo.includes('running')) {
log('pass', 'K8s 叢集連線正常')
} else {
log('fail', 'K8s 叢集連線失敗')
process.exit(1)
}
// Step 2: Create sandbox namespace
log('info', `步驟 2: 創建 ${NAMESPACE} namespace...`)
const nsExists = kubectl(`get namespace ${NAMESPACE} 2>/dev/null`)
if (!nsExists) {
kubectl(`create namespace ${NAMESPACE}`)
log('pass', `Namespace ${NAMESPACE} 已創建`)
} else {
log('info', `Namespace ${NAMESPACE} 已存在`)
}
// Step 3: Deploy test pod
log('info', '步驟 3: 部署測試 Pod...')
const podManifest = `
apiVersion: v1
kind: Pod
metadata:
name: ${TEST_POD_NAME}
namespace: ${NAMESPACE}
labels:
app: qa-test
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
`
// Delete existing pod first
kubectl(`delete pod ${TEST_POD_NAME} -n ${NAMESPACE} --ignore-not-found=true`)
// Create new pod
try {
execSync(`echo '${podManifest}' | KUBECONFIG=${KUBECONFIG} kubectl apply -f -`, { encoding: 'utf-8' })
log('pass', `Pod ${TEST_POD_NAME} 已部署`)
} catch (err) {
log('fail', `Pod 部署失敗: ${err.message}`)
process.exit(1)
}
// Wait for pod to be ready
log('info', '等待 Pod 就緒...')
for (let i = 0; i < 30; i++) {
const status = kubectl(`get pod ${TEST_POD_NAME} -n ${NAMESPACE} -o jsonpath='{.status.phase}'`)
if (status === 'Running') {
log('pass', 'Pod 狀態: Running')
break
}
await new Promise(r => setTimeout(r, 1000))
}
// Record initial pod creation time
const initialCreationTime = kubectl(`get pod ${TEST_POD_NAME} -n ${NAMESPACE} -o jsonpath='{.metadata.creationTimestamp}'`)
log('info', `初始創建時間: ${initialCreationTime}`)
// Step 4: Fire custom alert targeting our test pod
log('info', '步驟 4: 發射測試告警...')
try {
const alertPayload = {
alert_type: 'k8s_pod_crash',
severity: 'critical',
source: 'awoooi-qa-test',
target_resource: TEST_POD_NAME,
namespace: NAMESPACE,
message: `[QA TEST] Pod ${TEST_POD_NAME} CrashLoopBackOff - needs restart`,
metrics: {
restart_count: 5,
cpu_percent: 95,
test: true
}
}
const { status, data } = await httpRequest('POST', '/api/v1/webhooks/alerts', alertPayload)
if (status === 200 || status === 201) {
log('pass', `告警已發射Approval ID: ${data.approval_id?.slice(0, 8) || data.id?.slice(0, 8)}...`)
} else {
log('fail', `告警發射失敗: ${JSON.stringify(data)}`)
}
} catch (err) {
log('fail', `告警發射失敗: ${err.message}`)
}
// Step 5: Get approval and sign it
log('info', '步驟 5: 取得並簽核 Approval...')
await new Promise(r => setTimeout(r, 1000)) // Wait for approval to be created
let approvalId = null
try {
const { data } = await httpRequest('GET', '/api/v1/approvals/pending')
if (data.approvals && data.approvals.length > 0) {
approvalId = data.approvals[0].id
log('info', `找到 Approval: ${approvalId.slice(0, 8)}...`)
// Get required signatures
const requiredSigs = data.approvals[0].required_signatures || 2
// Sign with multiple users
for (let i = 1; i <= requiredSigs; i++) {
const signResult = await httpRequest('POST', `/api/v1/approvals/${approvalId}/sign`, {
signer_id: `qa-signer-${i}`,
signer_name: `QA Signer ${i}`,
comment: `K8s executor test signature ${i}`,
})
if (signResult.status === 200) {
log('pass', `簽核 ${i}/${requiredSigs} 成功`)
} else if (signResult.status === 409) {
log('info', `簽核 ${i} 跳過 (重複)`)
}
}
} else {
log('fail', '無待簽核項目')
}
} catch (err) {
log('fail', `簽核失敗: ${err.message}`)
}
// Step 6: Wait and verify pod was restarted
log('info', '步驟 6: 等待 K8s 執行器執行...')
await new Promise(r => setTimeout(r, 5000)) // Give executor time to run
const finalCreationTime = kubectl(`get pod ${TEST_POD_NAME} -n ${NAMESPACE} -o jsonpath='{.metadata.creationTimestamp}' 2>/dev/null`)
console.log('\n' + '─'.repeat(55))
console.log(' 驗證結果')
console.log('─'.repeat(55))
if (!finalCreationTime) {
log('info', 'Pod 已被刪除 (executor 執行 delete pod)')
log('pass', '✨ K8s Executor 執行成功!')
} else if (finalCreationTime !== initialCreationTime) {
log('pass', `Pod 創建時間已更新: ${finalCreationTime}`)
log('pass', '✨ K8s Executor 執行成功!(Pod 重啟)')
} else {
log('info', `Pod 創建時間未變: ${finalCreationTime}`)
log('info', '可能原因: 執行器正在處理或 kubeconfig 權限問題')
// Check API logs for execution status
log('info', '檢查後端日誌...')
}
// Cleanup
log('info', '清理測試資源...')
kubectl(`delete pod ${TEST_POD_NAME} -n ${NAMESPACE} --ignore-not-found=true`)
console.log('\n' + '═'.repeat(55))
console.log(' Phase 3 K8s Executor 測試完成')
console.log('═'.repeat(55) + '\n')
}
main().catch((err) => {
log('fail', `測試失敗: ${err.message}`)
process.exit(1)
})