From f4d9c9ef5a0dd5b8b24c6d1f98ab84d04505478a Mon Sep 17 00:00:00 2001 From: lingniu Date: Thu, 2 Jul 2026 01:53:43 +0800 Subject: [PATCH] test: add go production acceptance smoke --- docs/operations/current-ecs-deployment.md | 8 ++ .../go-vehicle-gateway-verification.md | 12 ++ tools/go_prod_acceptance.py | 136 ++++++++++++++++++ tools/test_go_prod_acceptance.py | 53 +++++++ 4 files changed, 209 insertions(+) create mode 100755 tools/go_prod_acceptance.py create mode 100644 tools/test_go_prod_acceptance.py diff --git a/docs/operations/current-ecs-deployment.md b/docs/operations/current-ecs-deployment.md index 54168d87..da85cca4 100644 --- a/docs/operations/current-ecs-deployment.md +++ b/docs/operations/current-ecs-deployment.md @@ -154,6 +154,14 @@ curl -sS 'http://115.29.187.205:20210/api/stats/daily-metrics?dateFrom=2026-07-0 部署后推荐直接运行 Go 原生生产 smoke: +```bash +python3 tools/go_prod_acceptance.py --date 2026-07-02 +``` + +该聚合脚本会串行执行 systemd/端口检查、Kafka topic/consumer lag 检查、HTTP 数据链路检查,任一子检查失败时整体退出码为非 0。需要 SSH 可登录应用 ECS 和 Kafka ECS。 + +也可以单独运行子检查: + ```bash python3 tools/go_systemd_prod_smoke.py --host 115.29.187.205 --user root python3 tools/go_native_prod_smoke.py --date 2026-07-02 --timeout 8 diff --git a/docs/operations/go-vehicle-gateway-verification.md b/docs/operations/go-vehicle-gateway-verification.md index d4b94c94..56c5dc00 100644 --- a/docs/operations/go-vehicle-gateway-verification.md +++ b/docs/operations/go-vehicle-gateway-verification.md @@ -20,6 +20,18 @@ go build ./cmd/realtime-api ## 生产原生部署验证 +```bash +python3 tools/go_prod_acceptance.py --date 2026-07-02 +``` + +该命令会聚合执行: + +- `go_systemd_prod_smoke.py`:应用 ECS systemd 服务和端口归属 +- `go_kafka_prod_smoke.py`:Kafka topic 和消费组 lag +- `go_native_prod_smoke.py`:HTTP API、TDengine/MySQL/Redis 数据链路 + +如需单独检查 systemd: + ```bash python3 tools/go_systemd_prod_smoke.py --host 115.29.187.205 --user root ``` diff --git a/tools/go_prod_acceptance.py b/tools/go_prod_acceptance.py new file mode 100755 index 00000000..d9a3c9de --- /dev/null +++ b/tools/go_prod_acceptance.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""Run the Go native production acceptance smoke suite.""" + +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +from dataclasses import asdict, dataclass +from typing import Any + + +DEFAULT_APP_HOST = "115.29.187.205" +DEFAULT_KAFKA_HOST = "114.55.58.251" +DEFAULT_USER = "root" +DEFAULT_BASE_URL = "http://115.29.187.205:20210" + + +@dataclass(frozen=True) +class ChildCommand: + name: str + argv: list[str] + + +@dataclass(frozen=True) +class ChildResult: + name: str + status: str + exit_code: int + payload: dict[str, Any] + + +def build_commands(args: argparse.Namespace) -> list[ChildCommand]: + timeout = str(args.timeout) + return [ + ChildCommand("systemd", [ + sys.executable, + "tools/go_systemd_prod_smoke.py", + "--host", + args.app_host, + "--user", + args.ssh_user, + "--timeout", + timeout, + ]), + ChildCommand("kafka", [ + sys.executable, + "tools/go_kafka_prod_smoke.py", + "--host", + args.kafka_host, + "--user", + args.ssh_user, + "--max-lag", + str(args.max_lag), + "--timeout", + timeout, + ]), + ChildCommand("http", [ + sys.executable, + "tools/go_native_prod_smoke.py", + "--base-url", + args.base_url, + "--timeout", + timeout, + ] + (["--date", args.date] if args.date else [])), + ] + + +def run_child(command: ChildCommand, timeout: float) -> ChildResult: + completed = subprocess.run( + command.argv, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=timeout, + ) + payload = parse_json_output(completed.stdout) + if completed.stderr.strip(): + payload.setdefault("stderr", completed.stderr.strip()) + status = payload.get("status") + if status not in {"pass", "fail"}: + status = "pass" if completed.returncode == 0 else "fail" + return ChildResult(command.name, status, completed.returncode, payload) + + +def parse_json_output(output: str) -> dict[str, Any]: + output = output.strip() + if not output: + return {} + start = output.find("{") + end = output.rfind("}") + if start < 0 or end < start: + return {"rawOutput": output} + try: + return json.loads(output[start:end + 1]) + except json.JSONDecodeError: + return {"rawOutput": output} + + +def overall_status(results: list[ChildResult]) -> str: + if not results: + return "fail" + return "fail" if any(result.status != "pass" or result.exit_code != 0 for result in results) else "pass" + + +def parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--app-host", default=DEFAULT_APP_HOST) + parser.add_argument("--kafka-host", default=DEFAULT_KAFKA_HOST) + parser.add_argument("--ssh-user", default=DEFAULT_USER) + parser.add_argument("--base-url", default=DEFAULT_BASE_URL) + parser.add_argument("--date", help="Shanghai local date, YYYY-MM-DD. Defaults to today in go_native_prod_smoke.py.") + parser.add_argument("--timeout", type=float, default=20.0) + parser.add_argument("--max-lag", type=int, default=100) + return parser.parse_args(argv) + + +def main(argv: list[str]) -> int: + args = parse_args(argv) + results: list[ChildResult] = [] + for command in build_commands(args): + try: + results.append(run_child(command, args.timeout + 10)) + except Exception as exc: + results.append(ChildResult(command.name, "fail", 1, {"status": "fail", "error": str(exc)})) + status = overall_status(results) + print(json.dumps({ + "status": status, + "checks": [asdict(result) for result in results], + }, ensure_ascii=False, indent=2)) + return 0 if status == "pass" else 1 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/tools/test_go_prod_acceptance.py b/tools/test_go_prod_acceptance.py new file mode 100644 index 00000000..ac902d91 --- /dev/null +++ b/tools/test_go_prod_acceptance.py @@ -0,0 +1,53 @@ +import argparse +import sys +import unittest + +from tools import go_prod_acceptance as acceptance + + +class GoProdAcceptanceTest(unittest.TestCase): + def test_build_commands_runs_systemd_kafka_and_http_smokes(self): + args = argparse.Namespace( + app_host="115.29.187.205", + kafka_host="114.55.58.251", + ssh_user="root", + base_url="http://115.29.187.205:20210", + date="2026-07-02", + timeout=8.0, + max_lag=100, + ) + + commands = acceptance.build_commands(args) + + self.assertEqual(len(commands), 3) + self.assertEqual(commands[0].name, "systemd") + self.assertEqual(commands[0].argv[:2], [sys.executable, "tools/go_systemd_prod_smoke.py"]) + self.assertIn("--host", commands[0].argv) + self.assertIn("115.29.187.205", commands[0].argv) + self.assertEqual(commands[1].name, "kafka") + self.assertIn("tools/go_kafka_prod_smoke.py", commands[1].argv) + self.assertIn("--max-lag", commands[1].argv) + self.assertEqual(commands[2].name, "http") + self.assertIn("tools/go_native_prod_smoke.py", commands[2].argv) + self.assertIn("--base-url", commands[2].argv) + + def test_overall_status_fails_when_any_child_fails(self): + results = [ + acceptance.ChildResult("systemd", "pass", 0, {"status": "pass"}), + acceptance.ChildResult("kafka", "fail", 1, {"status": "fail"}), + ] + + self.assertEqual(acceptance.overall_status(results), "fail") + + def test_overall_status_passes_when_all_children_pass(self): + results = [ + acceptance.ChildResult("systemd", "pass", 0, {"status": "pass"}), + acceptance.ChildResult("kafka", "pass", 0, {"status": "pass"}), + acceptance.ChildResult("http", "pass", 0, {"status": "pass"}), + ] + + self.assertEqual(acceptance.overall_status(results), "pass") + + +if __name__ == "__main__": + unittest.main()