将 handlers/kubernetes.go (4204行/134函数) 按业务域拆分为: - kubernetes.go (106L): struct + constructor + 共享helpers - k8s_cluster.go (642L): 集群CRUD + 同步 + RBAC - k8s_workload.go (696L): Deployment/STS/DS/ReplicaSet操作 - k8s_pod.go (653L): Pod日志/终端/exec/指标 - k8s_pod_files.go (206L): Pod文件浏览/上传/下载 - k8s_resource.go (351L): Service/Ingress/ConfigMap/Secret/PVC/Topology - k8s_hpa_cronjob.go (384L): HPA + CronJob - k8s_yaml.go (213L): YAML get/update/delete/apply - k8s_namespace.go (305L): Namespace CRUD - k8s_events.go (207L): 事件列表+聚合 - k8s_node.go (428L): Node详情+资源分配 - k8s_compare.go (146L): 多集群对比 验收: go build零错误, 425条路由不变, 26个K8s API端点回归测试全通过
374 lines
12 KiB
Markdown
374 lines
12 KiB
Markdown
# AIOps 验证计划 — 全模块回归测试
|
|
|
|
> 基于 ROADMAP P0-P3 模块定义,每个模块开发前先写验证用例,实现后必须跑通。
|
|
> 最后更新: 2026-06-25
|
|
|
|
## 通用验证流程
|
|
|
|
```bash
|
|
# 0. 环境准备
|
|
cd /srv/aiops
|
|
|
|
# 1. 重建后端
|
|
docker-compose up -d --build backend
|
|
sleep 10 && docker ps --format '{{.Names}} {{.Status}}' | grep aiops-backend
|
|
|
|
# 2. 获取 JWT Token(用 Python 避免 shell 引号问题)
|
|
python3 -c "
|
|
import urllib.request, json
|
|
login_data = json.dumps({'username': 'admin', 'password': '123456'}).encode()
|
|
req = urllib.request.Request('http://127.0.0.1:8080/api/v1/auth/login', data=login_data, method='POST')
|
|
req.add_header('Content-Type', 'application/json')
|
|
resp = urllib.request.urlopen(req)
|
|
print(json.loads(resp.read())['data']['token'])
|
|
" > /tmp/aiops_token
|
|
TOKEN=$(cat /tmp/aiops_token)
|
|
|
|
# 3. 验证后端健康
|
|
curl -s http://127.0.0.1:8080/health | python3 -m json.tool
|
|
|
|
# 4. 前端构建
|
|
cd frontend && npm run build 2>&1 | tail -3
|
|
```
|
|
|
|
---
|
|
|
|
## P0-Q1: 前端类型安全治理
|
|
|
|
### 验证用例
|
|
|
|
```bash
|
|
# U1: TypeScript 编译零错误
|
|
cd /srv/aiops/frontend && npx tsc --noEmit 2>&1 | tail -5
|
|
# 期望: 退出码 0, 无输出
|
|
|
|
# U2: ESLint any 类型数量
|
|
npx eslint src/ --quiet --format json 2>/dev/null | python3 -c "
|
|
import json, sys
|
|
data = json.load(sys.stdin)
|
|
any_count = sum(1 for r in data for m in r.get('messages', []) if m.get('ruleId') == '@typescript-eslint/no-explicit-any')
|
|
print(f'any 类型: {any_count} 处')
|
|
# 期望: < 50
|
|
"
|
|
|
|
# U3: ESLint unused-vars 数量
|
|
npx eslint src/ --quiet --format json 2>/dev/null | python3 -c "
|
|
import json, sys
|
|
data = json.load(sys.stdin)
|
|
unused = sum(1 for r in data for m in r.get('messages', []) if m.get('ruleId') == '@typescript-eslint/no-unused-vars')
|
|
print(f'未使用变量: {unused} 处')
|
|
# 期望: < 20
|
|
"
|
|
|
|
# U4: console.* 数量
|
|
grep -rn 'console\.\(log\|debug\|info\)' src/ --include='*.ts' --include='*.tsx' | grep -v node_modules | wc -l
|
|
# 期望: < 10
|
|
|
|
# U5: api.ts 中无泄露 token 的 console.log
|
|
grep -n 'console.*token\|console.*Token' src/services/api.ts
|
|
# 期望: 无输出
|
|
|
|
# U6: types/ 目录覆盖率
|
|
wc -l src/types/*.ts
|
|
# 期望: > 500 行(当前 375 行)
|
|
|
|
# U7: Vite 构建成功
|
|
npm run build 2>&1 | tail -3
|
|
# 期望: built in Ns
|
|
```
|
|
|
|
### 通过标准
|
|
- [ ] U1: `tsc --noEmit` 零错误
|
|
- [ ] U2: `any` 类型 < 50 处
|
|
- [ ] U3: 未使用变量 < 20 处
|
|
- [ ] U4: `console.*` < 10 处
|
|
- [ ] U5: api.ts 无 token 泄露
|
|
- [ ] U6: types/ 覆盖率提升
|
|
- [ ] U7: Vite 构建成功
|
|
|
|
---
|
|
|
|
## P0-Q2: 后端 kubernetes.go 模块化拆分
|
|
|
|
### 验证用例
|
|
|
|
```bash
|
|
# U1: Go 编译
|
|
cd /srv/aiops/backend && go build ./... 2>&1
|
|
# 期望: 零错误
|
|
|
|
# U2: 拆分后文件行数
|
|
for f in internal/api/handlers/k8s_*.go; do
|
|
echo "$(wc -l < "$f") $f"
|
|
done | sort -rn
|
|
# 期望: 每个文件 < 800 行
|
|
|
|
# U3: 路由总数不变(拆分前后对比)
|
|
grep -cE '^\s+(api\.|r\.|router\.|group\.|authorized\.)' internal/api/routes/routes.go
|
|
# 期望: 路由数量与拆分前一致
|
|
|
|
# U4: API 回归测试 — 抽样核心端点
|
|
TOKEN=$(cat /tmp/aiops_token)
|
|
|
|
# 集群列表
|
|
curl -s http://127.0.0.1:8080/api/v1/kubernetes-clusters \
|
|
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'clusters: {len(d[\"data\"])}')"
|
|
# 期望: 返回集群数组
|
|
|
|
# Deployment 列表
|
|
curl -s "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespace/devops/deployments" \
|
|
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'deployments: {len(d[\"data\"])}')"
|
|
# 期望: 返回 Deployment 数组
|
|
|
|
# Pod 列表
|
|
curl -s "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespace/devops/pods" \
|
|
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'pods: {len(d[\"data\"])}')"
|
|
# 期望: 返回 Pod 数组
|
|
|
|
# YAML 获取
|
|
curl -s "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespace/devops/deployments/redis/yaml" \
|
|
-H "Authorization: Bearer $TOKEN" | head -3
|
|
# 期望: apiVersion: apps/v1
|
|
|
|
# 事件聚合
|
|
curl -s "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/events/summary" \
|
|
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin)['data']; print(f'events total: {d[\"total\"]}')"
|
|
# 期望: total 为数字
|
|
|
|
# 多集群对比
|
|
curl -s "http://127.0.0.1:8080/api/v1/kubernetes/compare" \
|
|
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin)['data']; print(f'clusters compared: {len(d[\"clusters\"])}')"
|
|
# 期望: clusters 数组
|
|
|
|
# Namespace 详情
|
|
curl -s "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespaces/detail" \
|
|
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'namespaces: {len(d[\"data\"])}')"
|
|
# 期望: 返回命名空间数组
|
|
|
|
# U5: 容器重新构建 + 启动
|
|
docker-compose up -d --build backend
|
|
sleep 10
|
|
docker ps --format '{{.Names}} {{.Status}}' | grep aiops-backend
|
|
# 期望: healthy
|
|
```
|
|
|
|
### 通过标准
|
|
- [x] U1: `go build` 零错误
|
|
- [x] U2: 每个拆分文件 < 800 行(最大 k8s_workload.go 696 行,kubernetes.go 降至 106 行核心文件)
|
|
- [x] U3: 路由数量不变(425 条路由,routes.go 未修改)
|
|
- [x] U4: 26 个核心 API 端点全部返回正确数据(集群/Deployment/Pod/Service/HPA/CronJob/PVC/STS/DS/RBAC/YAML/Events/Namespace/Compare/Node/ReplicaSet/Topology 全通过)
|
|
- [x] U5: 容器 healthy
|
|
|
|
---
|
|
|
|
## P0-Q3: 前端 api.ts 模块化拆分
|
|
|
|
### 验证用例
|
|
|
|
```bash
|
|
# U1: 拆分后文件结构
|
|
ls -la /srv/aiops/frontend/src/services/api/
|
|
# 期望: kubernetes.ts, deploy.ts, agent.ts, alert.ts, server.ts, cloud.ts, index.ts
|
|
|
|
# U2: 单文件行数
|
|
wc -l /srv/aiops/frontend/src/services/api/*.ts | sort -rn
|
|
# 期望: 每个文件 < 300 行
|
|
|
|
# U3: API 方法总数不变
|
|
grep -c 'export const\|async \|: Promise' /srv/aiops/frontend/src/services/api/index.ts
|
|
# 期望: re-export 352 个方法
|
|
|
|
# U4: TypeScript 编译
|
|
cd /srv/aiops/frontend && npx tsc --noEmit
|
|
# 期望: 零错误
|
|
|
|
# U5: Vite 构建
|
|
npm run build 2>&1 | tail -3
|
|
# 期望: built in Ns
|
|
|
|
# U6: 所有 import 路径正确(无断链)
|
|
grep -rn "from.*services/api" src/ | head -20
|
|
# 期望: 所有引用正常解析
|
|
```
|
|
|
|
### 通过标准
|
|
- [ ] U1: api/ 目录结构正确
|
|
- [ ] U2: 每个文件 < 300 行
|
|
- [ ] U3: 352 个方法全部保留
|
|
- [ ] U4: `tsc --noEmit` 零错误
|
|
- [ ] U5: Vite 构建成功
|
|
- [ ] U6: 所有 import 正常
|
|
|
|
---
|
|
|
|
## P1-F1: 告警静默规则
|
|
|
|
### 验证用例
|
|
|
|
```bash
|
|
TOKEN=$(cat /tmp/aiops_token)
|
|
|
|
# U1: 创建静默规则
|
|
curl -s -X POST "http://127.0.0.1:8080/api/v1/alerts/silences" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"name": "维护窗口静默",
|
|
"matchers": [{"key": "namespace", "value": "devops"}],
|
|
"starts_at": "2026-06-25T10:00:00Z",
|
|
"ends_at": "2026-06-25T12:00:00Z"
|
|
}' | python3 -m json.tool
|
|
# 期望: code=200
|
|
|
|
# U2: 查询静默规则列表
|
|
curl -s "http://127.0.0.1:8080/api/v1/alerts/silences" \
|
|
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'silences: {len(d[\"data\"])}')"
|
|
# 期望: >= 1
|
|
|
|
# U3: 静默期间告警不触发通知(需要模拟告警条件)
|
|
# 手动验证: 创建一个会匹配静默规则的告警规则,触发后检查飞书是否收到通知
|
|
|
|
# U4: 删除静默规则
|
|
SILENCE_ID=$(curl -s "http://127.0.0.1:8080/api/v1/alerts/silences" -H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; print(json.load(sys.stdin)['data'][0]['id'])")
|
|
curl -s -X DELETE "http://127.0.0.1:8080/api/v1/alerts/silences/$SILENCE_ID" \
|
|
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
|
|
# 期望: code=200
|
|
```
|
|
|
|
### 通过标准
|
|
- [ ] U1: 创建静默规则返回 200
|
|
- [ ] U2: 列表包含创建的规则
|
|
- [ ] U3: 静默期间不触发通知
|
|
- [ ] U4: 删除返回 200
|
|
- [ ] 前端 AlertCenter 有「静默规则」Tab
|
|
|
|
---
|
|
|
|
## P1-F2: 审计日志导出
|
|
|
|
### 验证用例
|
|
|
|
```bash
|
|
TOKEN=$(cat /tmp/aiops_token)
|
|
|
|
# U1: 导出 CSV
|
|
curl -s "http://127.0.0.1:8080/api/v1/audit/export?format=csv" \
|
|
-H "Authorization: Bearer $TOKEN" -o /tmp/audit_export.csv
|
|
file /tmp/audit_export.csv
|
|
# 期望: CSV 文件
|
|
|
|
# U2: 导出 Excel
|
|
curl -s "http://127.0.0.1:8080/api/v1/audit/export?format=xlsx" \
|
|
-H "Authorization: Bearer $TOKEN" -o /tmp/audit_export.xlsx
|
|
file /tmp/audit_export.xlsx
|
|
# 期望: Microsoft Excel 2007+
|
|
|
|
# U3: 带筛选条件导出
|
|
curl -s "http://127.0.0.1:8080/api/v1/audit/export?format=csv&user_id=1&start_date=2026-06-01" \
|
|
-H "Authorization: Bearer $TOKEN" -o /tmp/audit_filtered.csv
|
|
wc -l /tmp/audit_filtered.csv
|
|
# 期望: 行数 > 1(有数据)
|
|
```
|
|
|
|
### 通过标准
|
|
- [ ] U1: CSV 导出文件有效
|
|
- [ ] U2: Excel 导出文件有效
|
|
- [ ] U3: 筛选条件正确工作
|
|
- [ ] 前端有「导出」按钮
|
|
|
|
---
|
|
|
|
## P1-F3: 前端 RBAC 按钮级控制
|
|
|
|
### 验证用例
|
|
|
|
```bash
|
|
# U1: 创建只读角色(无写入权限)
|
|
TOKEN=$(cat /tmp/aiops_token)
|
|
curl -s -X POST "http://127.0.0.1:8080/api/v1/admin/roles" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name": "viewer", "permissions": ["read:servers", "read:k8s"]}' | python3 -m json.tool
|
|
# 期望: code=200
|
|
|
|
# U2: 创建 viewer 用户
|
|
curl -s -X POST "http://127.0.0.1:8080/api/v1/admin/users" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"username": "viewer_test", "password": "123456", "role_id": 2}' | python3 -m json.tool
|
|
# 期望: code=200
|
|
|
|
# U3: 以 viewer 身份登录
|
|
VIEWER_TOKEN=$(python3 -c "
|
|
import urllib.request, json
|
|
login_data = json.dumps({'username': 'viewer_test', 'password': '123456'}).encode()
|
|
req = urllib.request.Request('http://127.0.0.1:8080/api/v1/auth/login', data=login_data, method='POST')
|
|
req.add_header('Content-Type', 'application/json')
|
|
resp = urllib.request.urlopen(req)
|
|
print(json.loads(resp.read())['data']['token'])
|
|
")
|
|
|
|
# U4: viewer 调用写接口应被拒绝
|
|
curl -s -X POST "http://127.0.0.1:8080/api/v1/servers" \
|
|
-H "Authorization: Bearer $VIEWER_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"hostname": "test", "ip": "10.0.0.1"}' | python3 -m json.tool
|
|
# 期望: code=403
|
|
|
|
# U5: 前端验证(手动)
|
|
# 1. 用 viewer_test 登录前端
|
|
# 2. 确认侧边栏无「删除」按钮
|
|
# 3. 确认无权限的菜单不显示
|
|
```
|
|
|
|
### 通过标准
|
|
- [ ] U1-U2: 创建角色和用户成功
|
|
- [ ] U4: 写接口返回 403
|
|
- [ ] U5: 前端按钮/菜单按权限显示
|
|
|
|
---
|
|
|
|
## 回归测试基线(每次改动后必跑)
|
|
|
|
```bash
|
|
# === 后端回归 ===
|
|
TOKEN=$(cat /tmp/aiops_token)
|
|
|
|
# 集群
|
|
curl -sf http://127.0.0.1:8080/api/v1/kubernetes-clusters -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ clusters" || echo "❌ clusters"
|
|
|
|
# Deployments
|
|
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespace/devops/deployments" -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ deployments" || echo "❌ deployments"
|
|
|
|
# Pods
|
|
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespace/devops/pods" -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ pods" || echo "❌ pods"
|
|
|
|
# Events
|
|
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/events/summary" -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ events" || echo "❌ events"
|
|
|
|
# Namespace
|
|
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespaces/detail" -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ namespaces" || echo "❌ namespaces"
|
|
|
|
# Servers
|
|
curl -sf http://127.0.0.1:8080/api/v1/servers -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ servers" || echo "❌ servers"
|
|
|
|
# Pipelines
|
|
curl -sf http://127.0.0.1:8080/api/v1/pipelines -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ pipelines" || echo "❌ pipelines"
|
|
|
|
# Alerts
|
|
curl -sf http://127.0.0.1:8080/api/v1/alerts/rules -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ alerts" || echo "❌ alerts"
|
|
|
|
# Agent
|
|
curl -sf http://127.0.0.1:8080/api/v1/agent/latest -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ agent" || echo "❌ agent"
|
|
|
|
# Dashboard
|
|
curl -sf http://127.0.0.1:8080/api/v1/dashboard/stats -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ dashboard" || echo "❌ dashboard"
|
|
|
|
# === 前端回归 ===
|
|
cd /srv/aiops/frontend && npm run build 2>&1 | tail -1
|
|
|
|
# === 健康检查 ===
|
|
curl -sf http://127.0.0.1:8080/health > /dev/null && echo "✅ health" || echo "❌ health"
|
|
curl -sf http://127.0.0.1/ > /dev/null && echo "✅ frontend" || echo "❌ frontend"
|
|
```
|