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
|
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。
|
`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 已验证:
|
2026-07-02 01:30 CST 已验证:
|
||||||
|
|
||||||
- 四个 Go systemd 服务均为 `active` 且 `enabled`。
|
- 四个 Go systemd 服务均为 `active` 且 `enabled`。
|
||||||
|
- `/opt/lingniu-go-native/current` 指向有效 release,四个生产二进制均存在且可执行。
|
||||||
- `gateway` 正在监听 `32960` 和 `808`。
|
- `gateway` 正在监听 `32960` 和 `808`。
|
||||||
- `realtime-api` 正在监听 `20210`。
|
- `realtime-api` 正在监听 `20210`。
|
||||||
- gateway Kafka spool 当前无待回放文件。
|
- gateway Kafka spool 当前无待回放文件。
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ DEFAULT_PORTS = {
|
|||||||
32960: "gateway",
|
32960: "gateway",
|
||||||
20210: "realtime-api",
|
20210: "realtime-api",
|
||||||
}
|
}
|
||||||
|
DEFAULT_RELEASE_ROOT = "/opt/lingniu-go-native"
|
||||||
DEFAULT_SPOOL_DIR = "/opt/lingniu-go-native/spool/gateway"
|
DEFAULT_SPOOL_DIR = "/opt/lingniu-go-native/spool/gateway"
|
||||||
|
DEFAULT_BINARIES = ["gateway", "history-writer", "stat-writer", "realtime-api"]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -92,6 +94,14 @@ def spool_check(output: str) -> Check:
|
|||||||
return Check("spool.gateway", status, output.strip() or "missing")
|
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]:
|
def parse_key_values(output: str) -> dict[str, str]:
|
||||||
values: dict[str, str] = {}
|
values: dict[str, str] = {}
|
||||||
for part in output.split():
|
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}'); "
|
"bytes=$(du -sb \"$spool\" 2>/dev/null | awk '{print $1}'); "
|
||||||
"recent=$(find \"$spool\" -type f -mmin -5 | wc -l); "
|
"recent=$(find \"$spool\" -type f -mmin -5 | wc -l); "
|
||||||
"printf 'files=%s bytes=%s recent=%s\\n' \"$files\" \"${bytes:-0}\" \"$recent\"; "
|
"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"
|
ports_marker = "\n--PORTS--\n"
|
||||||
spool_marker = "\n--SPOOL--\n"
|
spool_marker = "\n--SPOOL--\n"
|
||||||
|
release_marker = "\n--RELEASE--\n"
|
||||||
if ports_marker not in output:
|
if ports_marker not in output:
|
||||||
return output, "", ""
|
return output, "", "", ""
|
||||||
service_output, rest = output.split(ports_marker, 1)
|
service_output, rest = output.split(ports_marker, 1)
|
||||||
if spool_marker not in rest:
|
if spool_marker not in rest:
|
||||||
return service_output, rest, ""
|
return service_output, rest, "", ""
|
||||||
port_output, spool_output = rest.split(spool_marker, 1)
|
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]]:
|
def run(args: argparse.Namespace) -> tuple[str, list[Check]]:
|
||||||
output = ssh(args.host, args.user, remote_command(DEFAULT_SERVICES), args.timeout)
|
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 = (
|
checks = (
|
||||||
service_checks(service_output, DEFAULT_SERVICES)
|
service_checks(service_output, DEFAULT_SERVICES)
|
||||||
+ port_checks(port_output, DEFAULT_PORTS)
|
+ port_checks(port_output, DEFAULT_PORTS)
|
||||||
+ [spool_check(spool_output)]
|
+ [spool_check(spool_output)]
|
||||||
|
+ [release_check(release_output)]
|
||||||
)
|
)
|
||||||
status = "fail" if any(check.status == "fail" for check in checks) else "pass"
|
status = "fail" if any(check.status == "fail" for check in checks) else "pass"
|
||||||
return status, checks
|
return status, checks
|
||||||
|
|||||||
@@ -78,6 +78,24 @@ LISTEN 0 4096 *:20210 *:* users:(("realtime-api",pid=11,fd=3))
|
|||||||
|
|
||||||
self.assertEqual(check.status, "fail")
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user