test: verify go native release layout
This commit is contained in:
@@ -167,7 +167,7 @@ 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
|
||||
```
|
||||
|
||||
`go_systemd_prod_smoke.py` 通过 SSH 检查四个 Go systemd 服务是否 `active` 且 `enabled`,并确认 `808`、`32960` 由 `gateway` 监听、`20210` 由 `realtime-api` 监听,同时要求 `/opt/lingniu-go-native/spool/gateway` 没有待回放文件,避免旧 Java/Docker 进程占用生产端口或 Kafka spool 静默堆积。运行环境需要已配置 SSH 免密或已建立可用的 SSH 认证方式。
|
||||
`go_systemd_prod_smoke.py` 通过 SSH 检查四个 Go systemd 服务是否 `active` 且 `enabled`,并确认 `808`、`32960` 由 `gateway` 监听、`20210` 由 `realtime-api` 监听,同时要求 `/opt/lingniu-go-native/current` 指向有效 release、四个生产二进制均可执行、`/opt/lingniu-go-native/spool/gateway` 没有待回放文件,避免旧 Java/Docker 进程占用生产端口、部署目录错乱或 Kafka spool 静默堆积。运行环境需要已配置 SSH 免密或已建立可用的 SSH 认证方式。
|
||||
|
||||
`go_native_prod_smoke.py` 通过 `20210` HTTP API 验证 GB32960、JT808、宇通 MQTT 的 RAW 查询及结构化 `parsed_json`,三类协议的位置和里程点查询及 `frame_id` RAW 回链,GB32960/JT808 的 `daily_mileage_km`、`daily_total_mileage_km` 统计公式,以及三类协议的 Redis realtime snapshot、online、protocol 查询。实时 snapshot/protocol 查询会校验 `updated_at_ms` 新鲜度;默认检查今天东八区数据时,还会要求三类 RAW 最新样本不超过 15 分钟,避免旧数据误判为接收正常;任一检查不达标时退出码为非 0。
|
||||
|
||||
@@ -213,6 +213,7 @@ journalctl -u lingniu-go-realtime-api --since '5 minutes ago' --no-pager
|
||||
2026-07-02 01:30 CST 已验证:
|
||||
|
||||
- 四个 Go systemd 服务均为 `active` 且 `enabled`。
|
||||
- `/opt/lingniu-go-native/current` 指向有效 release,四个生产二进制均存在且可执行。
|
||||
- `gateway` 正在监听 `32960` 和 `808`。
|
||||
- `realtime-api` 正在监听 `20210`。
|
||||
- gateway Kafka spool 当前无待回放文件。
|
||||
|
||||
@@ -23,7 +23,9 @@ DEFAULT_PORTS = {
|
||||
32960: "gateway",
|
||||
20210: "realtime-api",
|
||||
}
|
||||
DEFAULT_RELEASE_ROOT = "/opt/lingniu-go-native"
|
||||
DEFAULT_SPOOL_DIR = "/opt/lingniu-go-native/spool/gateway"
|
||||
DEFAULT_BINARIES = ["gateway", "history-writer", "stat-writer", "realtime-api"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -92,6 +94,14 @@ def spool_check(output: str) -> Check:
|
||||
return Check("spool.gateway", status, output.strip() or "missing")
|
||||
|
||||
|
||||
def release_check(output: str) -> Check:
|
||||
values = parse_key_values(output)
|
||||
current = values.get("current", "")
|
||||
binaries_present = [values.get(binary, "0") == "1" for binary in DEFAULT_BINARIES]
|
||||
status = "pass" if current and all(binaries_present) else "fail"
|
||||
return Check("release.current", status, output.strip() or "missing")
|
||||
|
||||
|
||||
def parse_key_values(output: str) -> dict[str, str]:
|
||||
values: dict[str, str] = {}
|
||||
for part in output.split():
|
||||
@@ -132,29 +142,42 @@ def remote_command(services: list[str]) -> str:
|
||||
"bytes=$(du -sb \"$spool\" 2>/dev/null | awk '{print $1}'); "
|
||||
"recent=$(find \"$spool\" -type f -mmin -5 | wc -l); "
|
||||
"printf 'files=%s bytes=%s recent=%s\\n' \"$files\" \"${bytes:-0}\" \"$recent\"; "
|
||||
"else printf 'files=-1 bytes=0 recent=-1 missing=%s\\n' \"$spool\"; fi"
|
||||
"else printf 'files=-1 bytes=0 recent=-1 missing=%s\\n' \"$spool\"; fi; "
|
||||
"printf '\\n--RELEASE--\\n'; "
|
||||
"root='" + DEFAULT_RELEASE_ROOT + "'; "
|
||||
"current=$(readlink -f \"$root/current\" 2>/dev/null || true); "
|
||||
"printf 'current=%s' \"$current\"; "
|
||||
"for bin in " + " ".join(DEFAULT_BINARIES) + "; do "
|
||||
"if test -x \"$root/current/$bin\"; then present=1; else present=0; fi; "
|
||||
"printf ' %s=%s' \"$bin\" \"$present\"; "
|
||||
"done; printf '\\n'"
|
||||
)
|
||||
|
||||
|
||||
def split_remote_output(output: str) -> tuple[str, str, str]:
|
||||
def split_remote_output(output: str) -> tuple[str, str, str, str]:
|
||||
ports_marker = "\n--PORTS--\n"
|
||||
spool_marker = "\n--SPOOL--\n"
|
||||
release_marker = "\n--RELEASE--\n"
|
||||
if ports_marker not in output:
|
||||
return output, "", ""
|
||||
return output, "", "", ""
|
||||
service_output, rest = output.split(ports_marker, 1)
|
||||
if spool_marker not in rest:
|
||||
return service_output, rest, ""
|
||||
return service_output, rest, "", ""
|
||||
port_output, spool_output = rest.split(spool_marker, 1)
|
||||
return service_output, port_output, spool_output
|
||||
if release_marker not in spool_output:
|
||||
return service_output, port_output, spool_output, ""
|
||||
spool_output, release_output = spool_output.split(release_marker, 1)
|
||||
return service_output, port_output, spool_output, release_output
|
||||
|
||||
|
||||
def run(args: argparse.Namespace) -> tuple[str, list[Check]]:
|
||||
output = ssh(args.host, args.user, remote_command(DEFAULT_SERVICES), args.timeout)
|
||||
service_output, port_output, spool_output = split_remote_output(output)
|
||||
service_output, port_output, spool_output, release_output = split_remote_output(output)
|
||||
checks = (
|
||||
service_checks(service_output, DEFAULT_SERVICES)
|
||||
+ port_checks(port_output, DEFAULT_PORTS)
|
||||
+ [spool_check(spool_output)]
|
||||
+ [release_check(release_output)]
|
||||
)
|
||||
status = "fail" if any(check.status == "fail" for check in checks) else "pass"
|
||||
return status, checks
|
||||
|
||||
@@ -78,6 +78,24 @@ LISTEN 0 4096 *:20210 *:* users:(("realtime-api",pid=11,fd=3))
|
||||
|
||||
self.assertEqual(check.status, "fail")
|
||||
|
||||
def test_release_check_passes_when_current_release_has_all_binaries(self):
|
||||
check = smoke.release_check(
|
||||
"current=/opt/lingniu-go-native/releases/8def635 "
|
||||
"gateway=1 history-writer=1 stat-writer=1 realtime-api=1"
|
||||
)
|
||||
|
||||
self.assertEqual(check.status, "pass")
|
||||
self.assertIn("8def635", check.message)
|
||||
|
||||
def test_release_check_fails_when_binary_is_missing(self):
|
||||
check = smoke.release_check(
|
||||
"current=/opt/lingniu-go-native/releases/8def635 "
|
||||
"gateway=1 history-writer=0 stat-writer=1 realtime-api=1"
|
||||
)
|
||||
|
||||
self.assertEqual(check.status, "fail")
|
||||
self.assertIn("history-writer=0", check.message)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user