Compare commits
13 Commits
8e01264e3f
...
dev_1.0.0_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c7cd1d09f | ||
|
|
63fe5c61f5 | ||
|
|
8764a02604 | ||
|
|
b4556d86c2 | ||
|
|
f51dbb400a | ||
|
|
5bd0209ca6 | ||
|
|
231b27bb42 | ||
|
|
8a50657b43 | ||
|
|
afbe700520 | ||
|
|
35ed0f72f2 | ||
|
|
752d2991ec | ||
|
|
471f3e13f9 | ||
| 59a38513a4 |
@@ -12,7 +12,7 @@ Options:
|
|||||||
--master-offline Build master offline image (requires src/master/offline_wheels.tar.gz)
|
--master-offline Build master offline image (requires src/master/offline_wheels.tar.gz)
|
||||||
--metric Build metric module images (ftp, prometheus, grafana, test nodes)
|
--metric Build metric module images (ftp, prometheus, grafana, test nodes)
|
||||||
--no-cache Build all images without using Docker layer cache
|
--no-cache Build all images without using Docker layer cache
|
||||||
--only LIST Comma-separated targets to build: core,master,metric,web,alert,all
|
--only LIST Comma-separated targets to build: core,master,metric,web,alert,sys,all
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
@@ -31,6 +31,7 @@ build_master_offline=false
|
|||||||
build_metric=true
|
build_metric=true
|
||||||
build_web=true
|
build_web=true
|
||||||
build_alert=true
|
build_alert=true
|
||||||
|
build_sys=true
|
||||||
no_cache=false
|
no_cache=false
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
@@ -62,7 +63,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
fi
|
fi
|
||||||
sel="$2"; shift 2
|
sel="$2"; shift 2
|
||||||
# reset all, then enable selected
|
# reset all, then enable selected
|
||||||
build_core=false; build_master=false; build_metric=false; build_web=false; build_alert=false
|
build_core=false; build_master=false; build_metric=false; build_web=false; build_alert=false; build_sys=false
|
||||||
IFS=',' read -ra parts <<< "$sel"
|
IFS=',' read -ra parts <<< "$sel"
|
||||||
for p in "${parts[@]}"; do
|
for p in "${parts[@]}"; do
|
||||||
case "$p" in
|
case "$p" in
|
||||||
@@ -71,7 +72,8 @@ while [[ $# -gt 0 ]]; do
|
|||||||
metric) build_metric=true ;;
|
metric) build_metric=true ;;
|
||||||
web) build_web=true ;;
|
web) build_web=true ;;
|
||||||
alert) build_alert=true ;;
|
alert) build_alert=true ;;
|
||||||
all) build_core=true; build_master=true; build_metric=true; build_web=true; build_alert=true ;;
|
sys) build_sys=true ;;
|
||||||
|
all) build_core=true; build_master=true; build_metric=true; build_web=true; build_alert=true; build_sys=true ;;
|
||||||
*) echo "Unknown --only target: $p" >&2; exit 1 ;;
|
*) echo "Unknown --only target: $p" >&2; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
@@ -286,6 +288,42 @@ if [[ "$build_metric" == true ]]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# =======================================
|
||||||
|
# Sys (system tests) node images
|
||||||
|
# =======================================
|
||||||
|
|
||||||
|
if [[ "$build_sys" == true ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "Building Sys node images..."
|
||||||
|
|
||||||
|
sys_base_images=(
|
||||||
|
"ubuntu:22.04"
|
||||||
|
"nvidia/cuda:12.2.2-runtime-ubuntu22.04"
|
||||||
|
)
|
||||||
|
|
||||||
|
for base_image in "${sys_base_images[@]}"; do
|
||||||
|
if ! pull_base_image "$base_image"; then
|
||||||
|
build_failed=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
sys_builds=(
|
||||||
|
"Sys Node|src/sys/build/node/Dockerfile|argus-sys-node:latest|."
|
||||||
|
"Sys Metric Test Node|src/sys/build/test-node/Dockerfile|argus-sys-metric-test-node:latest|."
|
||||||
|
"Sys Metric Test GPU Node|src/sys/build/test-gpu-node/Dockerfile|argus-sys-metric-test-gpu-node:latest|."
|
||||||
|
)
|
||||||
|
|
||||||
|
for build_spec in "${sys_builds[@]}"; do
|
||||||
|
IFS='|' read -r image_label dockerfile_path image_tag build_context <<< "$build_spec"
|
||||||
|
if build_image "$image_label" "$dockerfile_path" "$image_tag" "$build_context"; then
|
||||||
|
images_built+=("$image_tag")
|
||||||
|
else
|
||||||
|
build_failed=true
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# =======================================
|
# =======================================
|
||||||
# Web & Alert module images
|
# Web & Alert module images
|
||||||
# =======================================
|
# =======================================
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
DATA_ROOT=/home/argus/tmp/private/argus
|
DATA_ROOT=/home/argus/tmp/private/argus
|
||||||
ARGUS_UID=1048
|
ARGUS_BUILD_UID=1048
|
||||||
ARGUS_GID=1048
|
ARGUS_BUILD_GID=1048
|
||||||
|
|
||||||
USE_INTRANET=false
|
USE_INTRANET=false
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
global:
|
|
||||||
resolve_timeout: 5m
|
|
||||||
|
|
||||||
route:
|
|
||||||
group_by: ['alertname', 'instance'] # 分组:相同 alertname + instance 的告警合并
|
|
||||||
group_wait: 30s # 第一个告警后,等 30s 看是否有同组告警一起发
|
|
||||||
group_interval: 5m # 同组告警变化后,至少 5 分钟再发一次
|
|
||||||
repeat_interval: 3h # 相同告警,3 小时重复提醒一次
|
|
||||||
receiver: 'null'
|
|
||||||
|
|
||||||
receivers:
|
|
||||||
- name: 'null'
|
|
||||||
|
|
||||||
inhibit_rules:
|
|
||||||
- source_match:
|
|
||||||
severity: 'critical' # critical 告警存在时
|
|
||||||
target_match:
|
|
||||||
severity: 'warning' # 抑制相同 instance 的 warning 告警
|
|
||||||
equal: ['instance']
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
172.18.0.2
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../" && pwd)"
|
|
||||||
project_root="$(cd "$root/../../.." && pwd)"
|
|
||||||
|
|
||||||
source "$project_root/scripts/common/build_user.sh"
|
|
||||||
load_build_user
|
|
||||||
|
|
||||||
# 创建新的private目录结构 (基于argus目录结构)
|
|
||||||
echo "[INFO] Creating private directory structure for supervisor-based containers..."
|
|
||||||
mkdir -p "$root/private/argus/alert/alertmanager"
|
|
||||||
mkdir -p "$root/private/argus/etc/"
|
|
||||||
|
|
||||||
# 设置数据目录权限
|
|
||||||
echo "[INFO] Setting permissions for data directories..."
|
|
||||||
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" "$root/private/argus/alert/alertmanager" 2>/dev/null || true
|
|
||||||
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" "$root/private/argus/etc" 2>/dev/null || true
|
|
||||||
|
|
||||||
echo "[INFO] Supervisor-based containers will manage their own scripts and configurations"
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
compose_cmd="docker compose"
|
|
||||||
if ! $compose_cmd version >/dev/null 2>&1; then
|
|
||||||
if command -v docker-compose >/dev/null 2>&1; then compose_cmd="docker-compose"; else
|
|
||||||
echo "需要 Docker Compose,请安装后重试" >&2; exit 1; fi
|
|
||||||
fi
|
|
||||||
$compose_cmd -p alert-mvp up -d --remove-orphans
|
|
||||||
echo "[OK] 服务已启动:Alertmanager http://localhost:9093"
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# Alertmanager 测试脚本
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
ALERTMANAGER_URL="http://localhost:9093"
|
|
||||||
TEST_ALERT_NAME_CRITICAL="NodeDown"
|
|
||||||
TEST_ALERT_NAME_WARNING="HighCPU"
|
|
||||||
TMP_LOG="/tmp/test-alertmanager.log"
|
|
||||||
|
|
||||||
# 等待参数
|
|
||||||
am_wait_attempts=30
|
|
||||||
am_wait_interval=2
|
|
||||||
|
|
||||||
GREEN="\033[1;32m"
|
|
||||||
RED="\033[1;31m"
|
|
||||||
YELLOW="\033[1;33m"
|
|
||||||
RESET="\033[0m"
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 函数定义
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
wait_for_alertmanager() {
|
|
||||||
local attempt=1
|
|
||||||
echo "[INFO] 等待 Alertmanager 启动中..."
|
|
||||||
while (( attempt <= am_wait_attempts )); do
|
|
||||||
if curl -fsS "${ALERTMANAGER_URL}/api/v2/status" >/dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN}[OK] Alertmanager 已就绪 (attempt=${attempt}/${am_wait_attempts})${RESET}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo "[..] Alertmanager 尚未就绪 (${attempt}/${am_wait_attempts})"
|
|
||||||
sleep "${am_wait_interval}"
|
|
||||||
(( attempt++ ))
|
|
||||||
done
|
|
||||||
echo -e "${RED}[ERROR] Alertmanager 在 ${am_wait_attempts} 次尝试后仍未就绪${RESET}"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
log_step() {
|
|
||||||
echo -e "${YELLOW}==== $1 ====${RESET}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 主流程
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
log_step "测试 Alertmanager 开始"
|
|
||||||
echo "[INFO] Alertmanager 地址: $ALERTMANAGER_URL"
|
|
||||||
|
|
||||||
# Step 1: 等待 Alertmanager 启动
|
|
||||||
wait_for_alertmanager
|
|
||||||
|
|
||||||
# Step 2: 触发一个critical测试告警
|
|
||||||
echo "[INFO] 发送critical测试告警..."
|
|
||||||
curl -fsS -X POST "${ALERTMANAGER_URL}/api/v2/alerts" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '[
|
|
||||||
{
|
|
||||||
"labels": {
|
|
||||||
"alertname": "'"${TEST_ALERT_NAME_CRITICAL}"'",
|
|
||||||
"instance": "node-1",
|
|
||||||
"severity": "critical"
|
|
||||||
},
|
|
||||||
"annotations": {
|
|
||||||
"summary": "节点 node-1 宕机"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]' \
|
|
||||||
-o "$TMP_LOG"
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}[OK] 已成功发送critical测试告警${RESET}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}[ERROR] critical告警发送失败!${RESET}"
|
|
||||||
cat "$TMP_LOG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Step 3: 触发一个warning测试告警
|
|
||||||
echo "[INFO] 发送warning测试告警..."
|
|
||||||
curl -fsS -X POST "${ALERTMANAGER_URL}/api/v2/alerts" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '[
|
|
||||||
{
|
|
||||||
"labels": {
|
|
||||||
"alertname": "'"${TEST_ALERT_NAME_WARNING}"'",
|
|
||||||
"instance": "node-1",
|
|
||||||
"severity": "warning"
|
|
||||||
},
|
|
||||||
"annotations": {
|
|
||||||
"summary": "节点 node-1 CPU 使用率过高"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]' \
|
|
||||||
-o "$TMP_LOG"
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}[OK] 已成功发送warning测试告警${RESET}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}[ERROR] warning告警发送失败!${RESET}"
|
|
||||||
cat "$TMP_LOG"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# Alertmanager 测试脚本(含启动等待)
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
ALERTMANAGER_URL="http://localhost:9093"
|
|
||||||
TEST_ALERT_NAME_CRITICAL="NodeDown"
|
|
||||||
TEST_ALERT_NAME_WARNING="HighCPU"
|
|
||||||
TMP_LOG="/tmp/test-alertmanager.log"
|
|
||||||
|
|
||||||
# 等待参数
|
|
||||||
am_wait_attempts=30
|
|
||||||
am_wait_interval=2
|
|
||||||
|
|
||||||
GREEN="\033[1;32m"
|
|
||||||
RED="\033[1;31m"
|
|
||||||
YELLOW="\033[1;33m"
|
|
||||||
RESET="\033[0m"
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 函数定义
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
wait_for_alertmanager() {
|
|
||||||
local attempt=1
|
|
||||||
echo "[INFO] 等待 Alertmanager 启动中..."
|
|
||||||
while (( attempt <= am_wait_attempts )); do
|
|
||||||
if curl -fsS "${ALERTMANAGER_URL}/api/v2/status" >/dev/null 2>&1; then
|
|
||||||
echo -e "${GREEN}[OK] Alertmanager 已就绪 (attempt=${attempt}/${am_wait_attempts})${RESET}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo "[..] Alertmanager 尚未就绪 (${attempt}/${am_wait_attempts})"
|
|
||||||
sleep "${am_wait_interval}"
|
|
||||||
(( attempt++ ))
|
|
||||||
done
|
|
||||||
echo -e "${RED}[ERROR] Alertmanager 在 ${am_wait_attempts} 次尝试后仍未就绪${RESET}"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
log_step() {
|
|
||||||
echo -e "${YELLOW}==== $1 ====${RESET}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ==========================================================
|
|
||||||
# 主流程
|
|
||||||
# ==========================================================
|
|
||||||
|
|
||||||
log_step "查询 Alertmanager 当前告警列表开始"
|
|
||||||
echo "[INFO] Alertmanager 地址: $ALERTMANAGER_URL"
|
|
||||||
|
|
||||||
# Step 1: 等待 Alertmanager 启动
|
|
||||||
wait_for_alertmanager
|
|
||||||
|
|
||||||
# Step 2: 查询当前告警列表
|
|
||||||
echo "[INFO] 查询当前告警..."
|
|
||||||
sleep 1
|
|
||||||
curl -fsS "${ALERTMANAGER_URL}/api/v2/alerts" | jq '.' || {
|
|
||||||
echo -e "${RED}[WARN] 无法解析返回 JSON,请检查 jq 是否安装${RESET}"
|
|
||||||
curl -s "${ALERTMANAGER_URL}/api/v2/alerts"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Step 3: 检查告警是否包含 NodeDown
|
|
||||||
if curl -fsS "${ALERTMANAGER_URL}/api/v2/alerts" | grep -q "${TEST_ALERT_NAME_CRITICAL}"; then
|
|
||||||
echo -e "${GREEN}✅ 测试通过:Alertmanager 已成功接收告警 ${TEST_ALERT_NAME_CRITICAL}${RESET}"
|
|
||||||
else
|
|
||||||
echo -e "${RED}❌ 测试失败:未检测到告警 ${TEST_ALERT_NAME_CRITICAL}${RESET}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_step "测试结束"
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
compose_cmd="docker compose"
|
|
||||||
if ! $compose_cmd version >/dev/null 2>&1; then
|
|
||||||
if command -v docker-compose >/dev/null 2>&1; then compose_cmd="docker-compose"; else
|
|
||||||
echo "需要 Docker Compose,请安装后重试" >&2; exit 1; fi
|
|
||||||
fi
|
|
||||||
$compose_cmd -p alert-mvp down
|
|
||||||
echo "[OK] 已停止所有容器"
|
|
||||||
|
|
||||||
# 清理private目录内容
|
|
||||||
echo "[INFO] 清理private目录内容..."
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
if [ -d "private" ]; then
|
|
||||||
# 删除private目录及其所有内容
|
|
||||||
rm -rf private
|
|
||||||
echo "[OK] 已清理private目录"
|
|
||||||
else
|
|
||||||
echo "[INFO] private目录不存在,无需清理"
|
|
||||||
fi
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
echo "======================================="
|
|
||||||
echo "ARGUS Alert System End-to-End Test"
|
|
||||||
echo "======================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 记录测试开始时间
|
|
||||||
test_start_time=$(date +%s)
|
|
||||||
|
|
||||||
# 函数:等待服务就绪
|
|
||||||
wait_for_services() {
|
|
||||||
echo "[INFO] Waiting for all services to be ready..."
|
|
||||||
local max_attempts=${SERVICE_WAIT_ATTEMPTS:-120}
|
|
||||||
local attempt=1
|
|
||||||
|
|
||||||
while [ $attempt -le $max_attempts ]; do
|
|
||||||
if curl -fs http://localhost:9093/api/v2/status >/dev/null 2>&1; then
|
|
||||||
echo "[OK] All services are ready!"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo " Waiting for services... ($attempt/$max_attempts)"
|
|
||||||
sleep 5
|
|
||||||
((attempt++))
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "[ERROR] Services not ready after $max_attempts attempts"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# 函数:显示测试步骤
|
|
||||||
show_step() {
|
|
||||||
echo ""
|
|
||||||
echo "🔄 Step $1: $2"
|
|
||||||
echo "----------------------------------------"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 函数:验证步骤结果
|
|
||||||
verify_step() {
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "✅ $1 - SUCCESS"
|
|
||||||
else
|
|
||||||
echo "❌ $1 - FAILED"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 开始端到端测试
|
|
||||||
show_step "1" "Bootstrap - Initialize environment"
|
|
||||||
./scripts/01_bootstrap.sh
|
|
||||||
verify_step "Bootstrap"
|
|
||||||
|
|
||||||
show_step "2" "Startup - Start all services"
|
|
||||||
./scripts/02_up.sh
|
|
||||||
verify_step "Service startup"
|
|
||||||
|
|
||||||
# 等待服务完全就绪
|
|
||||||
wait_for_services || exit 1
|
|
||||||
|
|
||||||
# 发送告警数据
|
|
||||||
show_step "3" "Add alerts - Send test alerts to Alertmanager"
|
|
||||||
./scripts/03_alertmanager_add_alert.sh
|
|
||||||
verify_step "Send test alerts"
|
|
||||||
|
|
||||||
# 查询告警数据
|
|
||||||
show_step "4" "Verify data - Query Alertmanager"
|
|
||||||
./scripts/04_query_alerts.sh
|
|
||||||
verify_step "Data verification"
|
|
||||||
|
|
||||||
|
|
||||||
# 检查服务健康状态
|
|
||||||
show_step "Health" "Check service health"
|
|
||||||
echo "[INFO] Checking service health..."
|
|
||||||
|
|
||||||
# 检查 Alertmanager 状态
|
|
||||||
if curl -fs "http://localhost:9093/api/v2/status" >/dev/null 2>&1; then
|
|
||||||
am_status="available"
|
|
||||||
echo "✅ Alertmanager status: $am_status"
|
|
||||||
else
|
|
||||||
am_status="unavailable"
|
|
||||||
echo "⚠️ Alertmanager status: $am_status"
|
|
||||||
fi
|
|
||||||
verify_step "Service health check"
|
|
||||||
|
|
||||||
# 清理环境
|
|
||||||
show_step "5" "Cleanup - Stop all services"
|
|
||||||
./scripts/05_down.sh
|
|
||||||
verify_step "Service cleanup"
|
|
||||||
|
|
||||||
# 计算总测试时间
|
|
||||||
test_end_time=$(date +%s)
|
|
||||||
total_time=$((test_end_time - test_start_time))
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "======================================="
|
|
||||||
echo "🎉 END-TO-END TEST COMPLETED SUCCESSFULLY!"
|
|
||||||
echo "======================================="
|
|
||||||
echo "📊 Test Summary:"
|
|
||||||
echo " • Total time: ${total_time}s"
|
|
||||||
echo " • Alertmanager status: $am_status"
|
|
||||||
echo " • All services started and stopped successfully"
|
|
||||||
echo ""
|
|
||||||
echo "✅ The ARGUS Alert system is working correctly!"
|
|
||||||
echo ""
|
|
||||||
113
src/alert/tests/scripts/verify_alertmanager.sh
Normal file
113
src/alert/tests/scripts/verify_alertmanager.sh
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# verify_alertmanager.sh
|
||||||
|
# 用于部署后验证 Prometheus 与 Alertmanager 通信链路是否正常
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# 基础配置
|
||||||
|
#=============================
|
||||||
|
PROM_URL="${PROM_URL:-http://prom.metric.argus.com:9090}"
|
||||||
|
ALERT_URL="${ALERT_URL:-http://alertmanager.alert.argus.com:9093}"
|
||||||
|
# TODO: 根据实际部署环境调整规则目录
|
||||||
|
DATA_ROOT="${DATA_ROOT:-/private/argus}"
|
||||||
|
RULE_DIR = "$DATA_ROOT/metric/prometheus/rules"
|
||||||
|
TMP_RULE="/tmp/test_rule.yml"
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# 辅助函数
|
||||||
|
#=============================
|
||||||
|
GREEN="\033[32m"; RED="\033[31m"; YELLOW="\033[33m"; RESET="\033[0m"
|
||||||
|
|
||||||
|
log_info() { echo -e "${YELLOW}[INFO]${RESET} $1"; }
|
||||||
|
log_success() { echo -e "${GREEN}[OK]${RESET} $1"; }
|
||||||
|
log_error() { echo -e "${RED}[ERROR]${RESET} $1"; }
|
||||||
|
|
||||||
|
fail_exit() { log_error "$1"; exit 1; }
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 1: 检查 Alertmanager 是否可访问
|
||||||
|
#=============================
|
||||||
|
log_info "检查 Alertmanager 状态..."
|
||||||
|
if curl -sSf "${ALERT_URL}/api/v2/status" >/dev/null 2>&1; then
|
||||||
|
log_success "Alertmanager 服务正常 (${ALERT_URL})"
|
||||||
|
else
|
||||||
|
fail_exit "无法访问 Alertmanager,请检查端口映射与容器状态。"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 2: 手动发送测试告警
|
||||||
|
#=============================
|
||||||
|
log_info "发送手动测试告警..."
|
||||||
|
curl -s -XPOST "${ALERT_URL}/api/v2/alerts" -H "Content-Type: application/json" -d '[
|
||||||
|
{
|
||||||
|
"labels": {
|
||||||
|
"alertname": "ManualTestAlert",
|
||||||
|
"severity": "info"
|
||||||
|
},
|
||||||
|
"annotations": {
|
||||||
|
"summary": "This is a test alert from deploy verification"
|
||||||
|
},
|
||||||
|
"startsAt": "'$(date -Iseconds)'"
|
||||||
|
}
|
||||||
|
]' >/dev/null && log_success "测试告警已成功发送到 Alertmanager"
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 3: 检查 Prometheus 配置中是否包含 Alertmanager
|
||||||
|
#=============================
|
||||||
|
log_info "检查 Prometheus 是否配置了 Alertmanager..."
|
||||||
|
if curl -s "${PROM_URL}/api/v1/status/config" | grep -q "alertmanagers"; then
|
||||||
|
log_success "Prometheus 已配置 Alertmanager 目标"
|
||||||
|
else
|
||||||
|
fail_exit "Prometheus 未配置 Alertmanager,请检查 prometheus.yml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 4: 创建并加载测试告警规则
|
||||||
|
#=============================
|
||||||
|
log_info "创建临时测试规则 ${TMP_RULE} ..."
|
||||||
|
cat <<EOF > "${TMP_RULE}"
|
||||||
|
groups:
|
||||||
|
- name: deploy-verify-group
|
||||||
|
rules:
|
||||||
|
- alert: DeployVerifyAlert
|
||||||
|
expr: vector(1)
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Deployment verification alert"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p "${RULE_DIR}"
|
||||||
|
cp "${TMP_RULE}" "${RULE_DIR}/test_rule.yml"
|
||||||
|
|
||||||
|
log_info "重载 Prometheus 以加载新规则..."
|
||||||
|
if curl -s -X POST "${PROM_URL}/-/reload" >/dev/null; then
|
||||||
|
log_success "Prometheus 已重载规则"
|
||||||
|
else
|
||||||
|
fail_exit "Prometheus reload 失败,请检查 API 可访问性。"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 5: 等待并验证 Alertmanager 是否收到告警
|
||||||
|
#=============================
|
||||||
|
log_info "等待告警触发 (约5秒)..."
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
if curl -s "${ALERT_URL}/api/v2/alerts" | grep -q "DeployVerifyAlert"; then
|
||||||
|
log_success "Prometheus → Alertmanager 告警链路验证成功"
|
||||||
|
else
|
||||||
|
fail_exit "未在 Alertmanager 中检测到 DeployVerifyAlert,请检查网络或配置。"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 6: 清理测试规则
|
||||||
|
#=============================
|
||||||
|
log_info "清理临时测试规则..."
|
||||||
|
rm -f "${RULE_DIR}/test_rule.yml" "${TMP_RULE}"
|
||||||
|
|
||||||
|
curl -s -X POST "${PROM_URL}/-/reload" >/dev/null \
|
||||||
|
&& log_success "Prometheus 已清理验证规则" \
|
||||||
|
|| log_error "Prometheus reload 清理失败,请手动确认。"
|
||||||
|
|
||||||
|
log_success "部署验证全部通过!Prometheus ↔ Alertmanager 通信正常。"
|
||||||
@@ -1 +1 @@
|
|||||||
1.33.0
|
1.35.0
|
||||||
|
|||||||
@@ -581,6 +581,372 @@
|
|||||||
],
|
],
|
||||||
"title": "Node Process Count",
|
"title": "Node Process Count",
|
||||||
"type": "timeseries"
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "GPU Utilization (%)",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 2,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 95
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 24
|
||||||
|
},
|
||||||
|
"id": 301,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "DCGM_FI_DEV_GPU_UTIL{hostname=~\"$hostname\"}",
|
||||||
|
"legendFormat": "{{hostname}} GPU{{gpu}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "GPU 利用率 (单卡)",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": true,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "Memory Used (%)",
|
||||||
|
"axisPlacement": "left",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 2,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 95
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "percent"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 24
|
||||||
|
},
|
||||||
|
"id": 403,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "round(DCGM_FI_DEV_FB_USED{hostname=~\"$hostname\"} / (DCGM_FI_DEV_FB_USED{hostname=~\"$hostname\"} + DCGM_FI_DEV_FB_FREE{hostname=~\"$hostname\"}) * 100)",
|
||||||
|
"legendFormat": "{{hostname}} GPU{{gpu}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "GPU 显存使用率 (单卡)",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": true,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "Temperature (℃)",
|
||||||
|
"axisPlacement": "left",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 2,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 100,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 75
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 85
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "celsius"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 0,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"id": 501,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "DCGM_FI_DEV_GPU_TEMP{hostname=~\"$hostname\"}",
|
||||||
|
"legendFormat": "{{hostname}} GPU{{gpu}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "GPU 温度(单卡)",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": true,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "Power (W)",
|
||||||
|
"axisPlacement": "left",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 10,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 2,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"max": 300,
|
||||||
|
"min": 0,
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"value": 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 300
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unit": "watt"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 32
|
||||||
|
},
|
||||||
|
"id": 502,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "multi",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "DCGM_FI_DEV_POWER_USAGE{hostname=~\"$hostname\"}",
|
||||||
|
"legendFormat": "{{hostname}} GPU{{gpu}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "GPU 功率 (单卡)",
|
||||||
|
"type": "timeseries"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"refresh": "15s",
|
"refresh": "15s",
|
||||||
@@ -589,11 +955,6 @@
|
|||||||
"templating": {
|
"templating": {
|
||||||
"list": [
|
"list": [
|
||||||
{
|
{
|
||||||
"current": {
|
|
||||||
"selected": true,
|
|
||||||
"text": "node-exporter-A1",
|
|
||||||
"value": "node-exporter-A1"
|
|
||||||
},
|
|
||||||
"datasource": {
|
"datasource": {
|
||||||
"type": "prometheus"
|
"type": "prometheus"
|
||||||
},
|
},
|
||||||
@@ -623,7 +984,7 @@
|
|||||||
},
|
},
|
||||||
"timepicker": {},
|
"timepicker": {},
|
||||||
"timezone": "",
|
"timezone": "",
|
||||||
"title": "Node and GPU Metrics",
|
"title": "Node and GPU Metrics (by hostname)",
|
||||||
"uid": "node_gpu_metrics",
|
"uid": "node_gpu_metrics_by_hostname",
|
||||||
"weekStart": ""
|
"weekStart": ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -622,7 +622,7 @@
|
|||||||
},
|
},
|
||||||
"timepicker": {},
|
"timepicker": {},
|
||||||
"timezone": "",
|
"timezone": "",
|
||||||
"title": "Node and GPU Metrics",
|
"title": "Node and GPU Metrics (by instance)",
|
||||||
"uid": "node_gpu_metrics",
|
"uid": "node_gpu_metrics_by_instance",
|
||||||
"weekStart": ""
|
"weekStart": ""
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/sys/build/node/Dockerfile
Normal file
36
src/sys/build/node/Dockerfile
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
|
TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
ARG USE_INTRANET=false
|
||||||
|
ARG ARGUS_BUILD_UID=2133
|
||||||
|
ARG ARGUS_BUILD_GID=2015
|
||||||
|
|
||||||
|
ENV ARGUS_BUILD_UID=${ARGUS_BUILD_UID} \
|
||||||
|
ARGUS_BUILD_GID=${ARGUS_BUILD_GID}
|
||||||
|
|
||||||
|
# Optional: switch to intranet apt mirrors during build
|
||||||
|
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
||||||
|
echo "Configuring intranet apt sources..." && \
|
||||||
|
cp /etc/apt/sources.list /etc/apt/sources.list.bak && \
|
||||||
|
echo "deb [trusted=yes] http://10.68.64.1/ubuntu2204/ jammy main" > /etc/apt/sources.list && \
|
||||||
|
echo 'Acquire::https::Verify-Peer "false";' > /etc/apt/apt.conf.d/99disable-ssl-check && \
|
||||||
|
echo 'Acquire::https::Verify-Host "false";' >> /etc/apt/apt.conf.d/99disable-ssl-check; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install base tools and all libs that Fluent Bit may require at runtime
|
||||||
|
# so that start-fluent-bit.sh will NOT fallback to apt during container start.
|
||||||
|
RUN set -eux; \
|
||||||
|
apt-get update; \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates tzdata \
|
||||||
|
procps iproute2 net-tools lsof \
|
||||||
|
libpq5 libyaml-0-2 libsasl2-2 libldap-2.5-0; \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Keep root; compose provides entrypoint via bind mount
|
||||||
|
USER root
|
||||||
|
|
||||||
|
CMD ["bash", "-lc", "sleep infinity"]
|
||||||
|
|
||||||
34
src/sys/build/test-gpu-node/Dockerfile
Normal file
34
src/sys/build/test-gpu-node/Dockerfile
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
FROM nvidia/cuda:12.2.2-runtime-ubuntu22.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
|
TZ=Asia/Shanghai \
|
||||||
|
NVIDIA_VISIBLE_DEVICES=all \
|
||||||
|
NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
||||||
|
|
||||||
|
ARG USE_INTRANET=false
|
||||||
|
ARG ARGUS_BUILD_UID=2133
|
||||||
|
ARG ARGUS_BUILD_GID=2015
|
||||||
|
|
||||||
|
ENV ARGUS_BUILD_UID=${ARGUS_BUILD_UID} \
|
||||||
|
ARGUS_BUILD_GID=${ARGUS_BUILD_GID}
|
||||||
|
|
||||||
|
# Optional intranet mirror for build-time apt
|
||||||
|
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
||||||
|
echo "Configuring intranet apt sources..." && \
|
||||||
|
cp /etc/apt/sources.list /etc/apt/sources.list.bak && \
|
||||||
|
echo "deb [trusted=yes] http://10.68.64.1/ubuntu2204/ jammy main" > /etc/apt/sources.list && \
|
||||||
|
echo 'Acquire::https::Verify-Peer "false";' > /etc/apt/apt.conf.d/99disable-ssl-check && \
|
||||||
|
echo 'Acquire::https::Verify-Host "false";' >> /etc/apt/apt.conf.d/99disable-ssl-check; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pre-install curl and diagnostics to avoid runtime apt installs in GPU test node
|
||||||
|
RUN set -eux; \
|
||||||
|
apt-get update; \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
curl ca-certificates tzdata \
|
||||||
|
procps iproute2 net-tools lsof; \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
USER root
|
||||||
|
CMD ["bash", "-lc", "sleep infinity"]
|
||||||
|
|
||||||
32
src/sys/build/test-node/Dockerfile
Normal file
32
src/sys/build/test-node/Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
|
TZ=Asia/Shanghai
|
||||||
|
|
||||||
|
ARG USE_INTRANET=false
|
||||||
|
ARG ARGUS_BUILD_UID=2133
|
||||||
|
ARG ARGUS_BUILD_GID=2015
|
||||||
|
|
||||||
|
ENV ARGUS_BUILD_UID=${ARGUS_BUILD_UID} \
|
||||||
|
ARGUS_BUILD_GID=${ARGUS_BUILD_GID}
|
||||||
|
|
||||||
|
# Optional intranet mirror for build-time apt
|
||||||
|
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
||||||
|
echo "Configuring intranet apt sources..." && \
|
||||||
|
cp /etc/apt/sources.list /etc/apt/sources.list.bak && \
|
||||||
|
echo "deb [trusted=yes] http://10.68.64.1/ubuntu2204/ jammy main" > /etc/apt/sources.list && \
|
||||||
|
echo 'Acquire::https::Verify-Peer "false";' > /etc/apt/apt.conf.d/99disable-ssl-check && \
|
||||||
|
echo 'Acquire::https::Verify-Host "false";' >> /etc/apt/apt.conf.d/99disable-ssl-check; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pre-install curl and common diagnostics to avoid runtime apt installs
|
||||||
|
RUN set -eux; \
|
||||||
|
apt-get update; \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
curl ca-certificates tzdata \
|
||||||
|
procps iproute2 net-tools lsof; \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
USER root
|
||||||
|
CMD ["bash", "-lc", "sleep infinity"]
|
||||||
|
|
||||||
@@ -47,6 +47,8 @@
|
|||||||
- `./scripts/11_metric_node_install.sh` 在 CPU 节点安装并验证端点
|
- `./scripts/11_metric_node_install.sh` 在 CPU 节点安装并验证端点
|
||||||
- `./scripts/12_metric_gpu_install.sh` 在 GPU 节点安装并等待 9100/9400 就绪(仅启用 GPU 时)
|
- `./scripts/12_metric_gpu_install.sh` 在 GPU 节点安装并等待 9100/9400 就绪(仅启用 GPU 时)
|
||||||
- `./scripts/13_metric_verify.sh` 对 master/Prometheus/数据面/Grafana 做综合校验(含 GPU 时校验 dcgm 指标)
|
- `./scripts/13_metric_verify.sh` 对 master/Prometheus/数据面/Grafana 做综合校验(含 GPU 时校验 dcgm 指标)
|
||||||
|
- `./scripts/15_alert_verify.sh` 对alertmanager进行校验
|
||||||
|
- `./scripts/16_web_verify.sh` 对web页面进行校验综合校验。
|
||||||
- `./scripts/14_metric_cleanup.sh` 清理 FTP 产物
|
- `./scripts/14_metric_cleanup.sh` 清理 FTP 产物
|
||||||
- `./scripts/09_down.sh` 回收容器、网络并清理 `private*/`、`tmp/`
|
- `./scripts/09_down.sh` 回收容器、网络并清理 `private*/`、`tmp/`
|
||||||
|
|
||||||
@@ -133,6 +135,13 @@
|
|||||||
- `09_down.sh`
|
- `09_down.sh`
|
||||||
- 目的:栈销毁与环境清理;必要时使用临时容器修正属主再删除 `private*` 目录
|
- 目的:栈销毁与环境清理;必要时使用临时容器修正属主再删除 `private*` 目录
|
||||||
|
|
||||||
|
- `15_alert_verify.sh`
|
||||||
|
- 目的:验证alertmanager的可用性、Prometheus到alertmanager的连通性。
|
||||||
|
- 操作:在Prometheus中增加一个恒为真的告警规则,查看alertmanager是否收到该告警
|
||||||
|
- `16_web_verify.sh`
|
||||||
|
- 目的:验证web页面是否可用。
|
||||||
|
- 使用playwright分别验证各个模块的页面是否可用,以及符合预期。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 常见问题与排查
|
### 常见问题与排查
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ services:
|
|||||||
ipv4_address: 172.31.0.4
|
ipv4_address: 172.31.0.4
|
||||||
|
|
||||||
node-a:
|
node-a:
|
||||||
image: ubuntu:22.04
|
image: argus-sys-node:latest
|
||||||
container_name: argus-node-a
|
container_name: argus-node-a
|
||||||
hostname: dev-yyrshare-nbnyx10-cp2f-pod-0
|
hostname: dev-yyrshare-nbnyx10-cp2f-pod-0
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -113,7 +113,7 @@ services:
|
|||||||
- sysnet
|
- sysnet
|
||||||
|
|
||||||
node-b:
|
node-b:
|
||||||
image: ubuntu:22.04
|
image: argus-sys-node:latest
|
||||||
container_name: argus-node-b
|
container_name: argus-node-b
|
||||||
hostname: dev-yyrshare-uuuu10-ep2f-pod-0
|
hostname: dev-yyrshare-uuuu10-ep2f-pod-0
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -260,7 +260,7 @@ services:
|
|||||||
max-file: "3"
|
max-file: "3"
|
||||||
|
|
||||||
test-node:
|
test-node:
|
||||||
image: ubuntu:22.04
|
image: argus-sys-metric-test-node:latest
|
||||||
container_name: argus-metric-test-node
|
container_name: argus-metric-test-node
|
||||||
hostname: test-metric-node-001
|
hostname: test-metric-node-001
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -303,7 +303,7 @@ services:
|
|||||||
|
|
||||||
test-gpu-node:
|
test-gpu-node:
|
||||||
profiles: ["gpu"]
|
profiles: ["gpu"]
|
||||||
image: nvidia/cuda:12.2.2-runtime-ubuntu22.04
|
image: argus-sys-metric-test-gpu-node:latest
|
||||||
container_name: argus-metric-test-gpu-node
|
container_name: argus-metric-test-gpu-node
|
||||||
hostname: test-metric-gpu-node-001
|
hostname: test-metric-gpu-node-001
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
ENABLE_GPU=false
|
ENABLE_GPU=false
|
||||||
|
CLEANUP=true
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@@ -11,6 +12,7 @@ Usage: 00_e2e_test.sh [options]
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
--enable-gpu 启用 GPU 相关拓扑与测试流程
|
--enable-gpu 启用 GPU 相关拓扑与测试流程
|
||||||
|
--no-clean 跳过清理流程(不执行 14 和 09)
|
||||||
-h, --help 显示帮助信息
|
-h, --help 显示帮助信息
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
@@ -21,6 +23,10 @@ while [[ $# -gt 0 ]]; do
|
|||||||
ENABLE_GPU=true
|
ENABLE_GPU=true
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--no-clean)
|
||||||
|
CLEANUP=false
|
||||||
|
shift
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
usage
|
usage
|
||||||
exit 0
|
exit 0
|
||||||
@@ -35,6 +41,7 @@ done
|
|||||||
|
|
||||||
export ARGUS_SYS_ENABLE_GPU=$ENABLE_GPU
|
export ARGUS_SYS_ENABLE_GPU=$ENABLE_GPU
|
||||||
|
|
||||||
|
# 基础步骤(不包含清理与下线)
|
||||||
SCRIPTS=(
|
SCRIPTS=(
|
||||||
"01_bootstrap.sh"
|
"01_bootstrap.sh"
|
||||||
"02_up.sh"
|
"02_up.sh"
|
||||||
@@ -48,10 +55,18 @@ SCRIPTS=(
|
|||||||
"11_metric_node_install.sh"
|
"11_metric_node_install.sh"
|
||||||
"12_metric_gpu_install.sh"
|
"12_metric_gpu_install.sh"
|
||||||
"13_metric_verify.sh"
|
"13_metric_verify.sh"
|
||||||
"14_metric_cleanup.sh"
|
"15_alert_verify.sh"
|
||||||
"09_down.sh"
|
"16_web_verify.sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 如未禁用清理,则追加清理与下线步骤(保持原有顺序)
|
||||||
|
if [[ "$CLEANUP" == "true" ]]; then
|
||||||
|
SCRIPTS+=(
|
||||||
|
"14_metric_cleanup.sh"
|
||||||
|
"09_down.sh"
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
for script in "${SCRIPTS[@]}"; do
|
for script in "${SCRIPTS[@]}"; do
|
||||||
echo "[SYS-E2E] Running $script"
|
echo "[SYS-E2E] Running $script"
|
||||||
"$SCRIPT_DIR/$script"
|
"$SCRIPT_DIR/$script"
|
||||||
@@ -59,4 +74,8 @@ for script in "${SCRIPTS[@]}"; do
|
|||||||
echo
|
echo
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "[SYS-E2E] All tests completed"
|
if [[ "$CLEANUP" == "true" ]]; then
|
||||||
|
echo "[SYS-E2E] All tests completed"
|
||||||
|
else
|
||||||
|
echo "[SYS-E2E] All tests completed (cleanup skipped)"
|
||||||
|
fi
|
||||||
|
|||||||
@@ -43,6 +43,29 @@ fi
|
|||||||
|
|
||||||
pushd "$PLUGIN_DIR" >/dev/null
|
pushd "$PLUGIN_DIR" >/dev/null
|
||||||
|
|
||||||
|
# --- Inject agent binary built in 01_bootstrap (if present) ---
|
||||||
|
AGENT_PATH_FILE="$TEST_ROOT/tmp/agent_binary_path"
|
||||||
|
AGENT_BIN_CANDIDATE="$REPO_ROOT/src/agent/dist/argus-agent"
|
||||||
|
if [[ -f "$AGENT_PATH_FILE" ]]; then
|
||||||
|
AGENT_BIN="$(tr -d '\n' < "$AGENT_PATH_FILE")"
|
||||||
|
else
|
||||||
|
AGENT_BIN="$AGENT_BIN_CANDIDATE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -x "$AGENT_BIN" ]]; then
|
||||||
|
echo "[SYS-METRIC] 使用 01 阶段构建的 agent: $AGENT_BIN"
|
||||||
|
TARGET_BIN="plugins/argus-agent/bin/argus-agent"
|
||||||
|
if [[ -f "$TARGET_BIN" ]]; then
|
||||||
|
cp -f "$AGENT_BIN" "$TARGET_BIN"
|
||||||
|
else
|
||||||
|
mkdir -p "$(dirname "$TARGET_BIN")"
|
||||||
|
cp "$AGENT_BIN" "$TARGET_BIN"
|
||||||
|
fi
|
||||||
|
chmod +x "$TARGET_BIN"
|
||||||
|
else
|
||||||
|
echo "[SYS-METRIC] 未找到可执行的 agent 二进制(预期: $AGENT_BIN),继续使用插件目录内置版本"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "[SYS-METRIC] Bumping metric artifact version..."
|
echo "[SYS-METRIC] Bumping metric artifact version..."
|
||||||
bash scripts/version-manager.sh bump minor
|
bash scripts/version-manager.sh bump minor
|
||||||
|
|
||||||
|
|||||||
103
src/sys/tests/scripts/15_alert_verify.sh
Normal file
103
src/sys/tests/scripts/15_alert_verify.sh
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# verify_alertmanager.sh
|
||||||
|
# Verify the communication between Prometheus and Alertmanager after deployment
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "[INFO] Verifying Prometheus ↔ Alertmanager communication..."
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
TMP_DIR="$TEST_ROOT/tmp"
|
||||||
|
mkdir -p "$TMP_DIR"
|
||||||
|
|
||||||
|
PRIVATE_CORE="$TEST_ROOT/private"
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Load environment variables
|
||||||
|
#=============================
|
||||||
|
if [[ -f "$TEST_ROOT/.env" ]]; then
|
||||||
|
set -a; source "$TEST_ROOT/.env"; set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Basic configuration
|
||||||
|
#=============================
|
||||||
|
PROM_URL="http://localhost:${PROMETHEUS_PORT:-9090}"
|
||||||
|
ALERT_URL="http://localhost:${ALERTMANAGER_PORT:-9093}"
|
||||||
|
RULE_DIR="$PRIVATE_CORE/argus/metric/prometheus/rules"
|
||||||
|
TMP_RULE="$TMP_DIR/test_rule.yml"
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Helper functions
|
||||||
|
#=============================
|
||||||
|
GREEN="\033[32m"; RED="\033[31m"; YELLOW="\033[33m"; RESET="\033[0m"
|
||||||
|
|
||||||
|
log_info() { echo -e "${YELLOW}[INFO]${RESET} $1"; }
|
||||||
|
log_success() { echo -e "${GREEN}[OK]${RESET} $1"; }
|
||||||
|
log_warn() { echo -e "${YELLOW}[WARN]${RESET} $1"; }
|
||||||
|
log_error() { echo -e "${RED}[ERROR]${RESET} $1"; }
|
||||||
|
|
||||||
|
fail_exit() { log_error "$1"; exit 1; }
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 1: Check Alertmanager accessibility
|
||||||
|
#=============================
|
||||||
|
log_info "Checking Alertmanager status..."
|
||||||
|
if curl -sSf "${ALERT_URL}/api/v2/status" >/dev/null 2>&1; then
|
||||||
|
log_success "Alertmanager is reachable at ${ALERT_URL}"
|
||||||
|
else
|
||||||
|
fail_exit "Alertmanager is not reachable. Please check container or port mapping."
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 2: Create and load a temporary test alert rule
|
||||||
|
#=============================
|
||||||
|
log_info "Creating temporary alert rule at ${TMP_RULE}..."
|
||||||
|
cat <<EOF > "${TMP_RULE}"
|
||||||
|
groups:
|
||||||
|
- name: deploy-verify-group
|
||||||
|
rules:
|
||||||
|
- alert: DeployVerifyAlert
|
||||||
|
expr: vector(1)
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "Deployment verification alert"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p "${RULE_DIR}"
|
||||||
|
cp "${TMP_RULE}" "${RULE_DIR}/test_rule.yml"
|
||||||
|
|
||||||
|
log_info "Reloading Prometheus to apply the test rule..."
|
||||||
|
if curl -s -X POST "${PROM_URL}/-/reload" >/dev/null; then
|
||||||
|
log_success "Prometheus successfully reloaded rules"
|
||||||
|
else
|
||||||
|
fail_exit "Failed to reload Prometheus. Check API accessibility."
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 3: Verify alert received by Alertmanager
|
||||||
|
#=============================
|
||||||
|
log_info "Waiting for alert propagation (~30 seconds)..."
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
if curl -s "${ALERT_URL}/api/v2/alerts" | grep -q "DeployVerifyAlert"; then
|
||||||
|
log_success "Prometheus → Alertmanager alert path verified successfully"
|
||||||
|
else
|
||||||
|
fail_exit "DeployVerifyAlert not found in Alertmanager. Check configuration or network."
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 4: Cleanup test rule
|
||||||
|
#=============================
|
||||||
|
log_info "Cleaning up temporary alert rule..."
|
||||||
|
rm -f "${RULE_DIR}/test_rule.yml" "${TMP_RULE}"
|
||||||
|
|
||||||
|
if curl -s -X POST "${PROM_URL}/-/reload" >/dev/null; then
|
||||||
|
log_success "Prometheus successfully reloaded after cleanup"
|
||||||
|
else
|
||||||
|
log_warn "Prometheus reload after cleanup failed. Please check manually."
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Alertmanager verification completed successfully. Communication with Prometheus is healthy."
|
||||||
115
src/sys/tests/scripts/16_web_verify.sh
Normal file
115
src/sys/tests/scripts/16_web_verify.sh
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# verify-web-test.sh
|
||||||
|
# Verify frontend service availability and run Playwright end-to-end tests
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo '[INFO] Verifying Web frontend...'
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
TEST_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$TEST_ROOT/../../.." && pwd)"
|
||||||
|
WEB_DIR="$REPO_ROOT/src/web"
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Load environment variables
|
||||||
|
#=============================
|
||||||
|
if [[ -f "$TEST_ROOT/.env" ]]; then
|
||||||
|
set -a; source "$TEST_ROOT/.env"; set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPORT_DIR="$WEB_DIR/playwright-report"
|
||||||
|
FRONTEND_URL="http://localhost:${WEB_PROXY_PORT_8080:-8080}"
|
||||||
|
TIMEOUT=120 # max wait time (seconds) for frontend to be ready
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Helper functions
|
||||||
|
#=============================
|
||||||
|
GREEN="\033[32m"; RED="\033[31m"; YELLOW="\033[33m"; RESET="\033[0m"
|
||||||
|
|
||||||
|
log_info() { echo -e "${YELLOW}[INFO]${RESET} $1"; }
|
||||||
|
log_success() { echo -e "${GREEN}[OK]${RESET} $1"; }
|
||||||
|
log_warn() { echo -e "${YELLOW}[WARN]${RESET} $1"; }
|
||||||
|
log_error() { echo -e "${RED}[ERROR]${RESET} $1"; }
|
||||||
|
|
||||||
|
fail_exit() { log_error "$1"; exit 1; }
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 1: Wait for frontend service
|
||||||
|
#=============================
|
||||||
|
log_info "[1/4] Checking if frontend service is up (${FRONTEND_URL})..."
|
||||||
|
|
||||||
|
for ((i=1; i<=TIMEOUT; i++)); do
|
||||||
|
STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$FRONTEND_URL" || true)
|
||||||
|
if [[ "$STATUS_CODE" == "200" ]]; then
|
||||||
|
log_success "Frontend service is accessible at ${FRONTEND_URL}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
if [[ $i -eq $TIMEOUT ]]; then
|
||||||
|
fail_exit "Timeout waiting for frontend service to become ready (${TIMEOUT}s)."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 2: Run Playwright tests
|
||||||
|
#=============================
|
||||||
|
log_info "[2/4] Running Playwright automated tests in headless mode..."
|
||||||
|
|
||||||
|
cd "$WEB_DIR"
|
||||||
|
|
||||||
|
# Ensure dependencies installed
|
||||||
|
if [ ! -d "node_modules" ]; then
|
||||||
|
log_warn "Dependencies not found. Installing via npm ci..."
|
||||||
|
npm ci
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Checking Playwright browsers..."
|
||||||
|
if [ -d "node_modules/playwright" ]; then
|
||||||
|
log_info "Found node_modules/playwright, checking if browsers are complete..."
|
||||||
|
# 使用 dry-run 确认浏览器是否完整
|
||||||
|
if npx playwright install --dry-run | grep -q "All required browsers are installed"; then
|
||||||
|
log_info "All Playwright browsers are already installed, skipping installation."
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
log_info "Playwright browsers incomplete, installing..."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_info "Playwright browsers not found, installing..."
|
||||||
|
npx playwright install --with-deps > /dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean previous reports
|
||||||
|
rm -rf "$REPORT_DIR"
|
||||||
|
|
||||||
|
# Run Playwright tests wrapped with xvfb-run to avoid GUI
|
||||||
|
set +e # temporarily disable exit-on-error
|
||||||
|
env BASE_URL="$FRONTEND_URL" xvfb-run --auto-servernum npx playwright test tests/playwright --reporter=list
|
||||||
|
TEST_RESULT=$?
|
||||||
|
set -e # re-enable strict mode
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 3: Check test results
|
||||||
|
#=============================
|
||||||
|
log_info "[3/4] Checking test results..."
|
||||||
|
|
||||||
|
if [[ $TEST_RESULT -eq 0 ]]; then
|
||||||
|
log_success "All Playwright tests passed successfully."
|
||||||
|
else
|
||||||
|
log_error "Some Playwright tests failed. Please review the test report."
|
||||||
|
fi
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Step 4: Report generation
|
||||||
|
#=============================
|
||||||
|
log_info "[4/4] Checking Playwright report..."
|
||||||
|
|
||||||
|
if [[ -d "$REPORT_DIR" ]]; then
|
||||||
|
log_success "Test report generated at: $REPORT_DIR"
|
||||||
|
echo "You can view it using:"
|
||||||
|
echo " npx playwright show-report"
|
||||||
|
else
|
||||||
|
log_warn "Report directory not found. Check Playwright execution logs."
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Web frontend verify finished."
|
||||||
3
src/web/.gitignore
vendored
3
src/web/.gitignore
vendored
@@ -1,6 +1,9 @@
|
|||||||
# Node modules
|
# Node modules
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
# playwright report
|
||||||
|
playwright-report/
|
||||||
|
|
||||||
# Build output
|
# Build output
|
||||||
/dist
|
/dist
|
||||||
/build
|
/build
|
||||||
|
|||||||
64
src/web/package-lock.json
generated
64
src/web/package-lock.json
generated
@@ -20,6 +20,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
|
"@playwright/test": "^1.56.1",
|
||||||
"@types/react": "^19.1.10",
|
"@types/react": "^19.1.10",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
@@ -1231,6 +1232,22 @@
|
|||||||
"react": "^18.x || ^19.x"
|
"react": "^18.x || ^19.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@playwright/test": {
|
||||||
|
"version": "1.56.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz",
|
||||||
|
"integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright": "1.56.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rolldown/pluginutils": {
|
"node_modules/@rolldown/pluginutils": {
|
||||||
"version": "1.0.0-beta.34",
|
"version": "1.0.0-beta.34",
|
||||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.34.tgz",
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.34.tgz",
|
||||||
@@ -2860,6 +2877,53 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.56.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
|
||||||
|
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.56.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.56.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
|
||||||
|
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright/node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"test:web": "playwright test",
|
||||||
|
"test:web:report": "playwright show-report"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
@@ -22,6 +24,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
|
"@playwright/test": "^1.56.1",
|
||||||
"@types/react": "^19.1.10",
|
"@types/react": "^19.1.10",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
|||||||
28
src/web/playwright.config.ts
Normal file
28
src/web/playwright.config.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { defineConfig } from '@playwright/test';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './tests',
|
||||||
|
testIgnore: ['**/src/assets/**', '**/*.png', '**/*.jpg', '**/*.svg'],
|
||||||
|
timeout: 60 * 1000,
|
||||||
|
retries: 1,
|
||||||
|
use: {
|
||||||
|
headless: true,
|
||||||
|
viewport: { width: 1280, height: 720 },
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
|
screenshot: 'only-on-failure',
|
||||||
|
video: 'retain-on-failure',
|
||||||
|
launchOptions: {
|
||||||
|
args: [
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-gpu',
|
||||||
|
'--disable-dev-shm-usage',
|
||||||
|
'--disable-software-rasterizer',
|
||||||
|
'--headless=new'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reporter: [
|
||||||
|
['list'],
|
||||||
|
['html', { open: 'never', outputFolder: 'playwright-report' }]
|
||||||
|
]
|
||||||
|
});
|
||||||
@@ -71,7 +71,7 @@ export function NodeTable({
|
|||||||
size="xs"
|
size="xs"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
component="a"
|
component="a"
|
||||||
href={`${EXTERNAL_HOST.GRAFANA}/d/node_gpu_metrics/node-and-gpu-metrics?var-hostname=${encodeURIComponent(node.name)}`}
|
href={`${EXTERNAL_HOST.GRAFANA}/d/node_gpu_metrics_by_hostname/node-and-gpu-metrics-by-hostname?var-hostname=${encodeURIComponent(node.name)}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import grafanaLogo from "../assets/grafana.png";
|
import grafanaLogo from "../assets/grafana.png";
|
||||||
import prometheusLogo from "../assets/prometheus.png";
|
import prometheusLogo from "../assets/prometheus.png";
|
||||||
import esLogo from "../assets/es.png";
|
|
||||||
import kibanaLogo from "../assets/kibana.png";
|
import kibanaLogo from "../assets/kibana.png";
|
||||||
import { EXTERNAL_HOST } from "./api";
|
import { EXTERNAL_HOST } from "./api";
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
172.18.0.3
|
|
||||||
87
src/web/tests/playwright/alerts.spec.ts
Normal file
87
src/web/tests/playwright/alerts.spec.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import {test, expect} from "@playwright/test";
|
||||||
|
import {BASE_URL} from './helpers/utils'
|
||||||
|
|
||||||
|
test.describe("Alerts 页面功能测试", () => {
|
||||||
|
test.beforeEach(async ({page}) => {
|
||||||
|
await page.goto(`${BASE_URL}/alerts`); // 根据你实际路由调整
|
||||||
|
});
|
||||||
|
|
||||||
|
test("页面加载并显示告警统计", async ({page}) => {
|
||||||
|
await expect(page.locator("text=告警详情").first()).toBeVisible();
|
||||||
|
await expect(page.locator("text=总数").first()).toBeVisible();
|
||||||
|
await expect(page.locator("text=严重").first()).toBeVisible();
|
||||||
|
await expect(page.locator("text=警告").first()).toBeVisible();
|
||||||
|
await expect(page.locator("text=信息").first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("筛选功能验证", async ({ page }) => {
|
||||||
|
// 等待页面加载完成
|
||||||
|
await page.waitForSelector("table");
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// 1️⃣ 选择“严重性”= critical
|
||||||
|
// ==========================
|
||||||
|
const severitySelect = page.locator('label:has-text("严重性")').locator('..').locator('input');
|
||||||
|
await severitySelect.click(); // 打开下拉菜单
|
||||||
|
|
||||||
|
const criticalOption = page.locator('[role="option"]:has-text("critical")');
|
||||||
|
await criticalOption.waitFor({ state: 'visible', timeout: 5000 });
|
||||||
|
await criticalOption.click();
|
||||||
|
|
||||||
|
// 验证选择已生效
|
||||||
|
await expect(severitySelect).toHaveValue("critical");
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// 2️⃣ 选择“状态”= active
|
||||||
|
// ==========================
|
||||||
|
const stateSelect = page.locator('label:has-text("状态")').locator('..').locator('input');
|
||||||
|
await stateSelect.click();
|
||||||
|
|
||||||
|
const activeOption = page.locator('[role="option"]:has-text("Active")');
|
||||||
|
await activeOption.waitFor({ state: 'visible', timeout: 5000 });
|
||||||
|
await activeOption.click();
|
||||||
|
|
||||||
|
await expect(stateSelect).toHaveValue("Active");
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// 4️⃣ 验证筛选结果(可选)
|
||||||
|
// ==========================
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
const rows = page.locator('table tbody tr');
|
||||||
|
const count = await rows.count();
|
||||||
|
expect(count).toBeGreaterThanOrEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test("排序功能", async ({page}) => {
|
||||||
|
const severityHeader = page.locator("th:has-text('严重性') button").first();
|
||||||
|
await severityHeader.click(); // 切换升序
|
||||||
|
await severityHeader.click(); // 切换降序
|
||||||
|
|
||||||
|
const instanceHeader = page.locator("th:has-text('节点') button").first();
|
||||||
|
await instanceHeader.click();
|
||||||
|
await instanceHeader.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("分页功能", async ({page}) => {
|
||||||
|
const nextButton = page.locator("button:has-text('下一页')").first();
|
||||||
|
const prevButton = page.locator("button:has-text('上一页')").first();
|
||||||
|
|
||||||
|
if (await nextButton.isEnabled()) {
|
||||||
|
await nextButton.click();
|
||||||
|
await expect(prevButton).toBeEnabled();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("展开更多信息行", async ({page}) => {
|
||||||
|
const infoIcons = page.locator("table tbody tr td [title='显示/隐藏更多信息']");
|
||||||
|
if (await infoIcons.count() > 0) {
|
||||||
|
await infoIcons.first().click();
|
||||||
|
// 展开的详情行应出现
|
||||||
|
const details = page.locator("table tbody tr >> text=alertname");
|
||||||
|
const detailCount = await details.count();
|
||||||
|
expect(detailCount).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
52
src/web/tests/playwright/dashboard.spec.ts
Normal file
52
src/web/tests/playwright/dashboard.spec.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import {test, expect} from '@playwright/test';
|
||||||
|
import {BASE_URL} from './helpers/utils'
|
||||||
|
|
||||||
|
test.describe('Dashboard 页面测试', () => {
|
||||||
|
|
||||||
|
test.beforeEach(async ({page}) => {
|
||||||
|
// 打开仪表盘页面
|
||||||
|
await page.goto(`${BASE_URL}/dashboard`, {waitUntil: 'networkidle'});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('应能成功加载页面并显示标题', async ({page}) => {
|
||||||
|
await expect(page.locator('text=仪表盘').first()).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('应显示节点健康状态卡片', async ({page}) => {
|
||||||
|
const healthCard = page.locator('text=节点健康状态');
|
||||||
|
await expect(healthCard).toBeVisible();
|
||||||
|
|
||||||
|
// 检查环形图是否渲染
|
||||||
|
const ring = page.locator('svg'); // RingProgress 是 SVG 渲染的
|
||||||
|
const ringCount = await ring.count();
|
||||||
|
expect(ringCount).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('应显示告警统计信息', async ({page}) => {
|
||||||
|
const alertCard = page.locator('text=告警统计');
|
||||||
|
await expect(alertCard).toBeVisible();
|
||||||
|
|
||||||
|
// 检查告警类别
|
||||||
|
const labels = ['总数', '严重', '警告', '信息'];
|
||||||
|
for (const label of labels) {
|
||||||
|
await expect(page.locator(`text=${label}`).first()).toBeVisible();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('应正确渲染集群节点表格', async ({page}) => {
|
||||||
|
const tableHeaders = ['ID', '名称', '状态', '类型', '版本'];
|
||||||
|
for (const header of tableHeaders) {
|
||||||
|
await expect(page.locator(`th:has-text("${header}")`).first()).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 至少有一行节点数据
|
||||||
|
const rows = await page.locator('tbody tr').count();
|
||||||
|
expect(rows).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('页面应无加载错误提示', async ({page}) => {
|
||||||
|
await expect(page.locator('text=加载中...')).toHaveCount(0);
|
||||||
|
await expect(page.locator('text=数据加载失败')).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
28
src/web/tests/playwright/helpers/entrycards-helpers.ts
Normal file
28
src/web/tests/playwright/helpers/entrycards-helpers.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Page, expect } from '@playwright/test';
|
||||||
|
import type { metricsEntries } from '../../../src/config/entries';
|
||||||
|
|
||||||
|
export async function testEntryCards(
|
||||||
|
page: Page,
|
||||||
|
entries: typeof metricsEntries,
|
||||||
|
checkLinkNavigation = false
|
||||||
|
) {
|
||||||
|
for (const entry of entries) {
|
||||||
|
// 先根据 label 找到包含该文本的卡片
|
||||||
|
const card = page.locator(`.mantine-Card-root:has-text("${entry.label}")`);
|
||||||
|
await expect(card).toBeVisible({ timeout: 10000 });
|
||||||
|
|
||||||
|
// 检查卡片内部的链接,忽略端口号
|
||||||
|
const link = card.locator('a');
|
||||||
|
const href = await link.getAttribute('href');
|
||||||
|
|
||||||
|
// 正则:保留协议和 host,忽略端口号
|
||||||
|
const expectedHrefPattern = entry.href.replace(/:(\d+)/, '(:\\d+)?');
|
||||||
|
expect(href).toMatch(new RegExp(`^${expectedHrefPattern}$`));
|
||||||
|
|
||||||
|
// 检查图标
|
||||||
|
const img = card.locator('img');
|
||||||
|
await expect(img).toBeVisible();
|
||||||
|
await expect(img).toHaveAttribute('src', /(\/assets\/.+|data:image\/png;base64,)/);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/web/tests/playwright/helpers/testUtils.ts
Normal file
25
src/web/tests/playwright/helpers/testUtils.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Page, expect } from '@playwright/test';
|
||||||
|
import { BASE_URL } from './utils'
|
||||||
|
/**
|
||||||
|
* 通用函数:验证页面导航是否正确
|
||||||
|
*/
|
||||||
|
export async function checkPage(page: Page, path: string, title: string) {
|
||||||
|
await page.goto(`${BASE_URL}`);
|
||||||
|
const menu = page.getByRole('link', { name: title });
|
||||||
|
await expect(menu).toBeVisible();
|
||||||
|
await menu.click();
|
||||||
|
await expect(page).toHaveURL(new RegExp(`${path}`));
|
||||||
|
await expect(page.locator('body')).toContainText(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查页面是否存在 JS 错误
|
||||||
|
*/
|
||||||
|
export async function noConsoleError(page: Page) {
|
||||||
|
const errors: string[] = [];
|
||||||
|
page.on('console', msg => {
|
||||||
|
if (msg.type() === 'error') errors.push(msg.text());
|
||||||
|
});
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
expect(errors, `发现 JS 错误: ${errors.join(', ')}`).toHaveLength(0);
|
||||||
|
}
|
||||||
1
src/web/tests/playwright/helpers/utils.ts
Normal file
1
src/web/tests/playwright/helpers/utils.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const BASE_URL = process.env.BASE_URL || "http://localhost:8080";
|
||||||
17
src/web/tests/playwright/logs.spec.ts
Normal file
17
src/web/tests/playwright/logs.spec.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { logsEntries } from './test-entries';
|
||||||
|
import { testEntryCards } from './helpers/entrycards-helpers';
|
||||||
|
import { BASE_URL } from './helpers/utils';
|
||||||
|
|
||||||
|
test.describe('Logs Page', () => {
|
||||||
|
test('should render all log cards', async ({ page }) => {
|
||||||
|
await page.goto(`${BASE_URL}/logs`);
|
||||||
|
|
||||||
|
// 等待标题可见
|
||||||
|
const title = page.locator('h2', { hasText: '日志详情' });
|
||||||
|
await expect(title).toBeVisible({ timeout: 10000 });
|
||||||
|
|
||||||
|
// 测试所有 log card
|
||||||
|
await testEntryCards(page, logsEntries);
|
||||||
|
});
|
||||||
|
});
|
||||||
15
src/web/tests/playwright/metric.spec.ts
Normal file
15
src/web/tests/playwright/metric.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { metricsEntries } from './test-entries';
|
||||||
|
import { testEntryCards } from './helpers/entrycards-helpers';
|
||||||
|
import { BASE_URL } from './helpers/utils';
|
||||||
|
|
||||||
|
test.describe('Metrics Page', () => {
|
||||||
|
test('should render all metric cards', async ({ page }) => {
|
||||||
|
await page.goto(`${BASE_URL}/metrics`);
|
||||||
|
|
||||||
|
const title = page.locator('h2', { hasText: '指标详情' });
|
||||||
|
await expect(title).toBeVisible({ timeout: 10000 });
|
||||||
|
|
||||||
|
await testEntryCards(page, metricsEntries);
|
||||||
|
});
|
||||||
|
});
|
||||||
64
src/web/tests/playwright/node-info.spec.ts
Normal file
64
src/web/tests/playwright/node-info.spec.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import {test, expect} from "@playwright/test";
|
||||||
|
import {BASE_URL} from './helpers/utils'
|
||||||
|
|
||||||
|
test.describe("节点信息页面 NodeInfo", () => {
|
||||||
|
test.beforeEach(async ({page}) => {
|
||||||
|
await page.goto(`${BASE_URL}/nodeInfo`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("页面标题应该正确显示", async ({page}) => {
|
||||||
|
const title = page.locator('h1,h2,h3:has-text("节点信息")').first();
|
||||||
|
await title.waitFor({timeout: 10000});
|
||||||
|
await expect(title).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("节点表格应该加载数据", async ({page}) => {
|
||||||
|
const rows = page.locator("table tbody tr");
|
||||||
|
await rows.first().waitFor({timeout: 10000});
|
||||||
|
const count = await rows.count();
|
||||||
|
expect(count).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('节点详情测试', async ({page}) => {
|
||||||
|
const firstDetailBtn = page.locator('text=查看详情').first();
|
||||||
|
await firstDetailBtn.waitFor({timeout: 10000});
|
||||||
|
await firstDetailBtn.scrollIntoViewIfNeeded();
|
||||||
|
await firstDetailBtn.click({force: true});
|
||||||
|
|
||||||
|
const drawer = page.locator('role=dialog[name="节点详情"]');
|
||||||
|
await drawer.waitFor({timeout: 10000});
|
||||||
|
await expect(drawer).toBeVisible();
|
||||||
|
|
||||||
|
for (const label of ['注册时间', '最近上报时间', '最后更新时间', '元数据信息', '健康信息', '配置信息', '标签信息']) {
|
||||||
|
const el = drawer.locator(`text=${label}`).first();
|
||||||
|
await el.waitFor({timeout: 5000});
|
||||||
|
await expect(el).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
test("每个节点的 Grafana 按钮链接正确", async ({ page }) => {
|
||||||
|
await page.waitForSelector("table tbody tr", { timeout: 10000 });
|
||||||
|
|
||||||
|
// 查找 Grafana 链接(根据快照,它是 link 而非 button)
|
||||||
|
const grafanaLinks = page.getByRole("link", { name: "Grafana" });
|
||||||
|
const count = await grafanaLinks.count();
|
||||||
|
|
||||||
|
// 如果没找到,保存上下文方便排查
|
||||||
|
if (count === 0) {
|
||||||
|
const html = await page.content();
|
||||||
|
console.error("❌ 未找到 Grafana 链接,页面 HTML 片段如下:\n", html.slice(0, 2000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 至少应该有一行节点
|
||||||
|
expect(count).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// 校验链接 href
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const link = grafanaLinks.nth(i);
|
||||||
|
await expect(link).toHaveAttribute(
|
||||||
|
"href",
|
||||||
|
/\/d\/node_gpu_metrics_by_hostname\/node-and-gpu-metrics-by-hostname\?var-hostname=/
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
14
src/web/tests/playwright/test-entries.ts
Normal file
14
src/web/tests/playwright/test-entries.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { EXTERNAL_HOST } from "../../src/config/api";
|
||||||
|
|
||||||
|
export const metricsEntries = [
|
||||||
|
{ label: "Grafana", href: EXTERNAL_HOST.GRAFANA_DASHBOARD, icon: '' },
|
||||||
|
{ label: "Prometheus", href: EXTERNAL_HOST.PROMETHEUS, icon: '' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const logsEntries = [
|
||||||
|
{ label: "Kibana", href: EXTERNAL_HOST.KIBANA, icon: '' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const alertsEntries = [
|
||||||
|
{ label: "Alertmanager", href: EXTERNAL_HOST.ALERTS, icon: '' },
|
||||||
|
];
|
||||||
21
src/web/tests/playwright/web-pages.spec.ts
Normal file
21
src/web/tests/playwright/web-pages.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { test } from '@playwright/test';
|
||||||
|
import { checkPage, noConsoleError } from './helpers/testUtils';
|
||||||
|
import { BASE_URL } from './helpers/utils'
|
||||||
|
|
||||||
|
const pages = [
|
||||||
|
{ path: '/dashboard', title: '仪表盘' },
|
||||||
|
{ path: '/nodeInfo', title: '节点信息' },
|
||||||
|
{ path: '/metrics', title: '指标详情' },
|
||||||
|
{ path: '/logs', title: '日志详情' },
|
||||||
|
{ path: '/alerts', title: '告警详情' }
|
||||||
|
];
|
||||||
|
|
||||||
|
test.describe('Argus Web 页面可用性巡检', () => {
|
||||||
|
for (const { path, title } of pages) {
|
||||||
|
test(`${title} 页面加载验证`, async ({ page }) => {
|
||||||
|
await page.goto(`${BASE_URL}${path}`);
|
||||||
|
await checkPage(page, path, title);
|
||||||
|
await noConsoleError(page);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../" && pwd)"
|
|
||||||
project_root="$(cd "$root/../../.." && pwd)"
|
|
||||||
|
|
||||||
source "$project_root/scripts/common/build_user.sh"
|
|
||||||
load_build_user
|
|
||||||
|
|
||||||
# 创建新的private目录结构 (基于argus目录结构)
|
|
||||||
echo "[INFO] Creating private directory structure for supervisor-based containers..."
|
|
||||||
mkdir -p "$root/private/argus/web"
|
|
||||||
mkdir -p "$root/private/argus/etc/"
|
|
||||||
|
|
||||||
# 设置数据目录权限
|
|
||||||
echo "[INFO] Setting permissions for data directories..."
|
|
||||||
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" "$root/private/argus/web" 2>/dev/null || true
|
|
||||||
chown -R "${ARGUS_BUILD_UID}:${ARGUS_BUILD_GID}" "$root/private/argus/etc" 2>/dev/null || true
|
|
||||||
|
|
||||||
echo "[INFO] Supervisor-based containers will manage their own scripts and configurations"
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
compose_cmd="docker compose"
|
|
||||||
if ! $compose_cmd version >/dev/null 2>&1; then
|
|
||||||
if command -v docker-compose >/dev/null 2>&1; then compose_cmd="docker-compose"; else
|
|
||||||
echo "需要 Docker Compose,请安装后重试" >&2; exit 1; fi
|
|
||||||
fi
|
|
||||||
$compose_cmd -p alert-mvp up -d --remove-orphans
|
|
||||||
echo "[OK] 服务已启动:Web Frontend http://localhost:8080"
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
WEB_URL=${WEB_URL:-"http://localhost:8080"}
|
|
||||||
API_URL=${API_URL:-"http://master.argus.com/api/v1/master/nodes"}
|
|
||||||
TIMEOUT=10
|
|
||||||
|
|
||||||
GREEN="\033[1;32m"
|
|
||||||
RED="\033[1;31m"
|
|
||||||
YELLOW="\033[1;33m"
|
|
||||||
RESET="\033[0m"
|
|
||||||
|
|
||||||
echo "[info] 测试 Argus Web 前端启动状态..."
|
|
||||||
echo "--------------------------------------------------"
|
|
||||||
|
|
||||||
# 等待 web 前端可用
|
|
||||||
attempt=1
|
|
||||||
while (( attempt <= 10 )); do
|
|
||||||
if curl -fsS -m "$TIMEOUT" -o /dev/null "$WEB_URL"; then
|
|
||||||
echo "[ok] Web 前端已启动 (${attempt}/10)"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
echo "[..] 等待 Web 前端启动中 (${attempt}/10)"
|
|
||||||
sleep 3
|
|
||||||
(( attempt++ ))
|
|
||||||
done
|
|
||||||
|
|
||||||
if (( attempt > 10 )); then
|
|
||||||
echo "[err] Web 前端在 30 秒内未启动"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 1. 检查首页可访问性
|
|
||||||
echo "[test] 检查首页访问..."
|
|
||||||
if curl -fsS "$WEB_URL" -m "$TIMEOUT" | grep -q "<title>"; then
|
|
||||||
echo -e "[${GREEN}ok${RESET}] 首页可访问"
|
|
||||||
else
|
|
||||||
echo -e "[${RED}err${RESET}] 首页访问失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 2. 检查静态资源加载
|
|
||||||
echo "[test] 检查静态资源..."
|
|
||||||
if curl -fsS "$WEB_URL/static/js" -m "$TIMEOUT" | grep -q "Cannot GET"; then
|
|
||||||
echo -e "[${YELLOW}warn${RESET}] 静态资源路径可能未正确配置"
|
|
||||||
else
|
|
||||||
echo -e "[${GREEN}ok${RESET}] 静态资源服务正常"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 3. 检查前端路由兼容
|
|
||||||
echo "[test] 检查 React Router 路由兼容..."
|
|
||||||
if curl -fsS "$WEB_URL/dashboard" -m "$TIMEOUT" | grep -q "<title>"; then
|
|
||||||
echo -e "[${GREEN}ok${RESET}] React Router 路由兼容正常"
|
|
||||||
else
|
|
||||||
echo -e "[${YELLOW}warn${RESET}] /dashboard 路由未正确返回 index.html"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 4. 测试 API 代理访问
|
|
||||||
echo "[test] 检查 API 代理..."
|
|
||||||
if curl -fsS "$API_URL" -m "$TIMEOUT" | grep -q "nodes"; then
|
|
||||||
echo -e "[${GREEN}ok${RESET}] API 代理成功"
|
|
||||||
else
|
|
||||||
echo -e "[${YELLOW}warn${RESET}] API 代理请求失败,请检查 Nginx proxy_pass"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 5. 页面关键字验证
|
|
||||||
echo "[test] 检查关键内容..."
|
|
||||||
if curl -fsS "$WEB_URL" | grep -q "Argus"; then
|
|
||||||
echo -e "[${GREEN}ok${RESET}] 页面包含关键字 'Argus'"
|
|
||||||
else
|
|
||||||
echo -e "[${YELLOW}warn${RESET}] 页面内容中未找到 'Argus'"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 6. DNS 检查
|
|
||||||
echo "[test] 检查 DNS 解析..."
|
|
||||||
if dig +short web.argus.com >/dev/null; then
|
|
||||||
echo -e "[${GREEN}ok${RESET}] 域名 web.argus.com 解析正常"
|
|
||||||
else
|
|
||||||
echo -e "[${YELLOW}warn${RESET}] 域名 web.argus.com 解析失败"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 7. 响应时间测试
|
|
||||||
echo "[test] 检查响应时间..."
|
|
||||||
response_time=$(curl -o /dev/null -s -w "%{time_total}\n" "$WEB_URL")
|
|
||||||
echo "[info] 响应时间: ${response_time}s"
|
|
||||||
if (( $(echo "$response_time > 2.0" | bc -l) )); then
|
|
||||||
echo -e "[${YELLOW}warn${RESET}] 响应时间较慢(>2s)"
|
|
||||||
else
|
|
||||||
echo -e "[${GREEN}ok${RESET}] 响应时间正常"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "--------------------------------------------------"
|
|
||||||
echo "[done] Web 前端测试完成 ✅"
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
compose_cmd="docker compose"
|
|
||||||
if ! $compose_cmd version >/dev/null 2>&1; then
|
|
||||||
if command -v docker-compose >/dev/null 2>&1; then compose_cmd="docker-compose"; else
|
|
||||||
echo "需要 Docker Compose,请安装后重试" >&2; exit 1; fi
|
|
||||||
fi
|
|
||||||
$compose_cmd -p alert-mvp down
|
|
||||||
echo "[OK] 已停止所有容器"
|
|
||||||
|
|
||||||
# 清理private目录内容
|
|
||||||
echo "[INFO] 清理private目录内容..."
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
if [ -d "private" ]; then
|
|
||||||
# 删除private目录及其所有内容
|
|
||||||
rm -rf private
|
|
||||||
echo "[OK] 已清理private目录"
|
|
||||||
else
|
|
||||||
echo "[INFO] private目录不存在,无需清理"
|
|
||||||
fi
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
echo "======================================="
|
|
||||||
echo "ARGUS Web System End-to-End Test"
|
|
||||||
echo "======================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 记录测试开始时间
|
|
||||||
test_start_time=$(date +%s)
|
|
||||||
|
|
||||||
# 函数:等待服务就绪
|
|
||||||
wait_for_services() {
|
|
||||||
echo "[INFO] Waiting for all services to be ready..."
|
|
||||||
local max_attempts=${SERVICE_WAIT_ATTEMPTS:-120}
|
|
||||||
local attempt=1
|
|
||||||
|
|
||||||
while [ $attempt -le $max_attempts ]; do
|
|
||||||
if curl -fs http://localhost:8080 >/dev/null 2>&1; then
|
|
||||||
echo "[OK] All services are ready!"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo " Waiting for services... ($attempt/$max_attempts)"
|
|
||||||
sleep 5
|
|
||||||
((attempt++))
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "[ERROR] Services not ready after $max_attempts attempts"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# 函数:显示测试步骤
|
|
||||||
show_step() {
|
|
||||||
echo ""
|
|
||||||
echo "🔄 Step $1: $2"
|
|
||||||
echo "----------------------------------------"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 函数:验证步骤结果
|
|
||||||
verify_step() {
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "✅ $1 - SUCCESS"
|
|
||||||
else
|
|
||||||
echo "❌ $1 - FAILED"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 开始端到端测试
|
|
||||||
show_step "1" "Bootstrap - Initialize environment"
|
|
||||||
./scripts/01_bootstrap.sh
|
|
||||||
verify_step "Bootstrap"
|
|
||||||
|
|
||||||
show_step "2" "Startup - Start all services"
|
|
||||||
./scripts/02_up.sh
|
|
||||||
verify_step "Service startup"
|
|
||||||
|
|
||||||
# 等待服务完全就绪
|
|
||||||
wait_for_services || exit 1
|
|
||||||
|
|
||||||
# 测试前端页面
|
|
||||||
show_step "3" "Web - Check frontend availability"
|
|
||||||
./scripts/03_web_health_check.sh
|
|
||||||
verify_step "Web frontend availability"
|
|
||||||
|
|
||||||
# 清理环境
|
|
||||||
show_step "4" "Cleanup - Stop all services"
|
|
||||||
./scripts/04_down.sh
|
|
||||||
verify_step "Service cleanup"
|
|
||||||
|
|
||||||
# 计算总测试时间
|
|
||||||
test_end_time=$(date +%s)
|
|
||||||
total_time=$((test_end_time - test_start_time))
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "======================================="
|
|
||||||
echo "🎉 END-TO-END TEST COMPLETED SUCCESSFULLY!"
|
|
||||||
echo "======================================="
|
|
||||||
echo "📊 Test Summary:"
|
|
||||||
echo " • Total time: ${total_time}s"
|
|
||||||
echo " • Alertmanager status: $am_status"
|
|
||||||
echo " • All services started and stopped successfully"
|
|
||||||
echo ""
|
|
||||||
echo "✅ The ARGUS Web system is working correctly!"
|
|
||||||
echo ""
|
|
||||||
77
src/web/tests/scripts/verify-web-frontend.sh
Normal file
77
src/web/tests/scripts/verify-web-frontend.sh
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# -----------------------------------------
|
||||||
|
# Web 前端自动化验证脚本(部署后执行)
|
||||||
|
# -----------------------------------------
|
||||||
|
|
||||||
|
PROJECT_ROOT="$(dirname "$0")"
|
||||||
|
WEB_DIR="$PROJECT_ROOT"
|
||||||
|
REPORT_DIR="$WEB_DIR/playwright-report"
|
||||||
|
FRONTEND_URL="http://web.argus.com:8080"
|
||||||
|
TIMEOUT=120 # 最长等待前端启动时间(秒)
|
||||||
|
|
||||||
|
echo "🔍 [1/4] 检查前端服务是否已启动 (${FRONTEND_URL}) ..."
|
||||||
|
|
||||||
|
# 等待前端服务可访问
|
||||||
|
for ((i=1; i<=$TIMEOUT; i++)); do
|
||||||
|
STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$FRONTEND_URL" || true)
|
||||||
|
if [[ "$STATUS_CODE" == "200" ]]; then
|
||||||
|
echo "✅ 前端服务已启动并可访问"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
if [ $i -eq $TIMEOUT ]; then
|
||||||
|
echo "❌ 等待前端启动超时 (${TIMEOUT}s)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# -----------------------------------------
|
||||||
|
# 2. 执行 Playwright 测试
|
||||||
|
# -----------------------------------------
|
||||||
|
echo "[2/4] 执行 Playwright 自动化测试..."
|
||||||
|
|
||||||
|
cd "$WEB_DIR"
|
||||||
|
|
||||||
|
# 确保依赖已安装
|
||||||
|
if [ ! -d "node_modules" ]; then
|
||||||
|
echo "未检测到依赖,开始安装..."
|
||||||
|
npm ci
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理旧报告
|
||||||
|
rm -rf "$REPORT_DIR"
|
||||||
|
|
||||||
|
# 运行测试(带失败检测)
|
||||||
|
set +e # 暂时关闭自动退出,便于捕获测试结果
|
||||||
|
npx playwright test tests/playwright --reporter=list,html
|
||||||
|
TEST_RESULT=$?
|
||||||
|
set -e # 恢复严格模式
|
||||||
|
|
||||||
|
# -----------------------------------------
|
||||||
|
# 3. 检查测试结果
|
||||||
|
# -----------------------------------------
|
||||||
|
echo "[3/4] 检查测试结果..."
|
||||||
|
|
||||||
|
if [ $TEST_RESULT -eq 0 ]; then
|
||||||
|
echo "[✓] 所有测试通过!"
|
||||||
|
else
|
||||||
|
echo "[X] 存在测试未通过,请查看报告。"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------
|
||||||
|
# 4. 输出报告信息
|
||||||
|
# -----------------------------------------
|
||||||
|
echo "[4/4] 生成测试报告..."
|
||||||
|
|
||||||
|
if [ -d "$REPORT_DIR" ]; then
|
||||||
|
echo "测试报告已生成:$REPORT_DIR"
|
||||||
|
echo "可执行以下命令查看详细报告:"
|
||||||
|
echo " npx playwright show-report"
|
||||||
|
else
|
||||||
|
echo "未生成报告目录,请检查执行日志。"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 将测试结果作为退出码返回
|
||||||
|
exit $TEST_RESULT
|
||||||
Reference in New Issue
Block a user