refactor!: rename the project from Monitor to MoShiro [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful
ci/woodpecker/push/main-amd64-build Pipeline was successful
Namespaces, assemblies, solution, binaries, images, systemd units, config paths, environment variables, protocol methods and the Linux user are all renamed. Breaking: existing databases, node identities and installed units must be recreated.
This commit is contained in:
1 parent
27ac7fb787
commit
2e146d4da7
309 files changed
+1565
-1572
No files matched your search
+31
-31
@@ -5,7 +5,7 @@
|
||||
Probe↔Agent 走真实的虚拟网络(eth0),Agent↔Backend 走 WSL 里的回环(故障用 tc 注入在 lo 上)。
|
||||
|
||||
前提:WSL 里有 Debian(root)和 tc;已发布 linux-x64 的 agent、backend、fleet-enroll(scripts/publish.sh),
|
||||
以及 Windows 的 probe(dotnet publish backend/probe/src/Monitor.Probe -c Release -r win-x64 -p:PublishAot=true -o backend/artifacts/aot-probe)。
|
||||
以及 Windows 的 probe(dotnet publish backend/probe/src/MoShiro.Probe -c Release -r win-x64 -p:PublishAot=true -o backend/artifacts/aot-probe)。
|
||||
|
||||
用法:python scripts/stability_wsl.py [场景名 ...] (不带参数运行全部)
|
||||
场景:init graceful crash probe_link backend_link silent soak
|
||||
@@ -23,11 +23,11 @@ import urllib.request
|
||||
OPENER = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
PROBE_EXE = os.path.join(ROOT, "backend", "artifacts", "aot-probe", "monitor-probe.exe")
|
||||
PROBE_EXE = os.path.join(ROOT, "backend", "artifacts", "aot-probe", "moshiro-probe.exe")
|
||||
PUB = "/mnt/" + ROOT[0].lower() + ROOT[2:].replace("\\", "/") + "/backend/artifacts/publish"
|
||||
WORK = os.path.join(os.environ.get("TEMP", "."), "stability")
|
||||
PORT = 29181
|
||||
ADMIN_PASSWORD = os.environ.get("MONITOR_ADMIN_PASSWORD", "correct horse battery") # 测试用后端的初始管理员密码
|
||||
ADMIN_PASSWORD = os.environ.get("MOSHIRO_ADMIN_PASSWORD", "correct horse battery") # 测试用后端的初始管理员密码
|
||||
RESULTS = []
|
||||
HIDDEN = subprocess.CREATE_NO_WINDOW # 所有子进程都不弹窗
|
||||
|
||||
@@ -109,14 +109,14 @@ def rss_mb(name):
|
||||
|
||||
|
||||
def start_backend():
|
||||
wsl(f"cd /root/mon && /root/mon/start.sh backend env ASPNETCORE_URLS=http://0.0.0.0:{PORT} Monitor__DataDirectory=/root/mon/data "
|
||||
f"Monitor__InitialAdminUser=admin 'Monitor__InitialAdminPassword={ADMIN_PASSWORD}' Monitor__SecureCookies=false ./monitor-backend")
|
||||
wsl(f"cd /root/mon && /root/mon/start.sh backend env ASPNETCORE_URLS=http://0.0.0.0:{PORT} MoShiro__DataDirectory=/root/mon/data "
|
||||
f"MoShiro__InitialAdminUser=admin 'MoShiro__InitialAdminPassword={ADMIN_PASSWORD}' MoShiro__SecureCookies=false ./moshiro-backend")
|
||||
return wait_until(backend_up, 30)
|
||||
|
||||
|
||||
def start_agent():
|
||||
wsl("/root/mon/start.sh agent ./monitor-agent --config /root/mon/agent.json")
|
||||
return wait_until(lambda: bool(pids("monitor-agent")), 10)
|
||||
wsl("/root/mon/start.sh agent ./moshiro-agent --config /root/mon/agent.json")
|
||||
return wait_until(lambda: bool(pids("moshiro-agent")), 10)
|
||||
|
||||
|
||||
def stop_proc(name, sig="TERM", timeout=30):
|
||||
@@ -124,7 +124,7 @@ def stop_proc(name, sig="TERM", timeout=30):
|
||||
start = now()
|
||||
wsl(f"pkill -{sig} -x {name} || true")
|
||||
gone = wait_until(lambda: not pids(name), timeout, 0.2)
|
||||
code = wsl(f"cat /root/mon/{name.replace('monitor-', '')}.exit 2>/dev/null || true")
|
||||
code = wsl(f"cat /root/mon/{name.replace('moshiro-', '')}.exit 2>/dev/null || true")
|
||||
return (now() - start if gone is not None else None), (int(code) if code.strip().lstrip('-').isdigit() else None)
|
||||
|
||||
|
||||
@@ -209,9 +209,9 @@ def setup(fresh=True):
|
||||
"""Clean state, copy the artifacts, start backend/agent, create the probe identity. The probe is not started."""
|
||||
global TOKEN, AGENT_ID
|
||||
PROBE.stop("kill")
|
||||
wsl("pkill -9 -x monitor-agent; pkill -9 -x monitor-backend; tc qdisc del dev eth0 ingress 2>/dev/null; tc qdisc del dev eth0 root 2>/dev/null; tc qdisc del dev lo root 2>/dev/null; tc qdisc del dev lo ingress 2>/dev/null; true")
|
||||
wsl("pkill -9 -x moshiro-agent; pkill -9 -x moshiro-backend; tc qdisc del dev eth0 ingress 2>/dev/null; tc qdisc del dev eth0 root 2>/dev/null; tc qdisc del dev lo root 2>/dev/null; tc qdisc del dev lo ingress 2>/dev/null; true")
|
||||
if fresh:
|
||||
wsl(f"rm -rf /root/mon && mkdir -p /root/mon && cp {PUB}/backend/linux-x64/* {PUB}/agent/linux-x64/* {PUB}/fleet-enroll/linux-x64/* /root/mon/ && chmod +x /root/mon/monitor-* /root/mon/fleet-enroll")
|
||||
wsl(f"rm -rf /root/mon && mkdir -p /root/mon && cp {PUB}/backend/linux-x64/* {PUB}/agent/linux-x64/* {PUB}/fleet-enroll/linux-x64/* /root/mon/ && chmod +x /root/mon/moshiro-* /root/mon/fleet-enroll")
|
||||
wsl("cat > /root/mon/start.sh <<'EOF'\n" + LAUNCHER + "EOF\nchmod +x /root/mon/start.sh")
|
||||
if os.path.isdir(WORK):
|
||||
subprocess.run(["cmd", "/c", "rmdir", "/s", "/q", WORK], capture_output=True, creationflags=HIDDEN)
|
||||
@@ -279,7 +279,7 @@ def healthy():
|
||||
def ensure_healthy(timeout=60):
|
||||
if not backend_up():
|
||||
start_backend()
|
||||
if not pids("monitor-agent"):
|
||||
if not pids("moshiro-agent"):
|
||||
start_agent()
|
||||
if not PROBE.alive():
|
||||
PROBE.start()
|
||||
@@ -292,7 +292,7 @@ def scenario_init():
|
||||
print("== init: cold starts in every order, bad configuration")
|
||||
# 1. everything down, then start in the opposite order: probe -> agent -> backend (each must retry until its peer is up)
|
||||
PROBE.stop("kill")
|
||||
wsl("pkill -9 -x monitor-agent; pkill -9 -x monitor-backend; true")
|
||||
wsl("pkill -9 -x moshiro-agent; pkill -9 -x moshiro-backend; true")
|
||||
time.sleep(1)
|
||||
PROBE.start()
|
||||
time.sleep(3)
|
||||
@@ -307,7 +307,7 @@ def scenario_init():
|
||||
|
||||
# 2. backend first, then agent, then probe (the normal order) after a full stop
|
||||
PROBE.stop("kill")
|
||||
wsl("pkill -9 -x monitor-agent; pkill -9 -x monitor-backend; true")
|
||||
wsl("pkill -9 -x moshiro-agent; pkill -9 -x moshiro-backend; true")
|
||||
start_backend()
|
||||
start_agent()
|
||||
PROBE.start()
|
||||
@@ -322,9 +322,9 @@ def scenario_init():
|
||||
check("probe: malformed config exits 1 with a readable error", r.returncode == 1 and "startup failed" in r.stderr + r.stdout, (r.stderr + r.stdout).strip()[:120])
|
||||
r = subprocess.run([PROBE_EXE, "--config", os.path.join(WORK, "missing.json")], capture_output=True, text=True, timeout=20, creationflags=HIDDEN)
|
||||
check("probe: missing config exits 1 with the file name", r.returncode == 1 and "missing.json" in r.stderr + r.stdout, (r.stderr + r.stdout).strip()[:120])
|
||||
out = wsl("cd /root/mon && echo '{ not json' > bad.json && ./monitor-agent --config bad.json 2>&1; echo exit=$?", check=False)
|
||||
out = wsl("cd /root/mon && echo '{ not json' > bad.json && ./moshiro-agent --config bad.json 2>&1; echo exit=$?", check=False)
|
||||
check("agent: malformed config exits 1", "exit=1" in out and "startup failed" in out, out.strip()[-120:])
|
||||
out = wsl("cd /root/mon && printf '{\"fleet\":{\"server\":\"http://127.0.0.1:1\",\"identity_dir\":\"/nonexistent\"}}' > noid.json && ./monitor-agent --config noid.json 2>&1; echo exit=$?", check=False)
|
||||
out = wsl("cd /root/mon && printf '{\"fleet\":{\"server\":\"http://127.0.0.1:1\",\"identity_dir\":\"/nonexistent\"}}' > noid.json && ./moshiro-agent --config noid.json 2>&1; echo exit=$?", check=False)
|
||||
check("agent: no identity says to run fleet-enroll first", "exit=1" in out and "fleet-enroll" in out, out.strip()[-140:])
|
||||
out = wsl(f"cd /root/mon && ./fleet-enroll --server http://127.0.0.1:{PORT} --join bogus --name x --identity-dir /tmp/enroll-x 2>&1; echo exit=$?", check=False)
|
||||
check("fleet-enroll: a bad join token fails clearly (non-zero exit)", "exit=0" not in out, out.strip()[-140:])
|
||||
@@ -345,7 +345,7 @@ def scenario_graceful():
|
||||
check("probe restarts and reconnects", wait_until(healthy, 30) is not None)
|
||||
|
||||
# agent
|
||||
secs, code = stop_proc("monitor-agent", "TERM")
|
||||
secs, code = stop_proc("moshiro-agent", "TERM")
|
||||
log = wsl("tail -5 /root/mon/agent.log")
|
||||
check("agent exits on SIGTERM within 5 s with code 0", secs is not None and secs < 5 and code == 0, f"{secs and round(secs, 2)}s code={code}")
|
||||
check("agent logs a clean stop (no crash trace)", "stopping" in log and "Unhandled" not in log, log.splitlines()[-1][:100] if log else "")
|
||||
@@ -355,7 +355,7 @@ def scenario_graceful():
|
||||
check("agent restarts, reconnects to backend and probe", wait_until(healthy, 40) is not None)
|
||||
|
||||
# backend
|
||||
secs, code = stop_proc("monitor-backend", "TERM", 30)
|
||||
secs, code = stop_proc("moshiro-backend", "TERM", 30)
|
||||
log = wsl("tail -5 /root/mon/backend.log")
|
||||
check("backend exits on SIGTERM within 15 s with code 0", secs is not None and secs < 15 and code == 0, f"{secs and round(secs, 2)}s code={code}")
|
||||
check("backend log has no unhandled exception", "Unhandled" not in log and "Exception" not in log, log.replace(chr(10), ' | ')[-160:])
|
||||
@@ -373,7 +373,7 @@ def scenario_crash():
|
||||
|
||||
# backend crash: the agent keeps its outbox; after restart nothing acknowledged is lost
|
||||
t = now()
|
||||
wsl("pkill -9 -x monitor-backend")
|
||||
wsl("pkill -9 -x moshiro-backend")
|
||||
time.sleep(25) # agent outbox accumulates (2-3 chunk rounds)
|
||||
start_backend()
|
||||
back = wait_until(healthy, 60)
|
||||
@@ -384,7 +384,7 @@ def scenario_crash():
|
||||
check("backend kill -9: chunks buffered by the agent are delivered after the restart (no gap)", not g, f"gaps in {n} buckets: {g}; chunks {before}->{after}")
|
||||
|
||||
# agent crash
|
||||
wsl("pkill -9 -x monitor-agent")
|
||||
wsl("pkill -9 -x moshiro-agent")
|
||||
time.sleep(5)
|
||||
start_agent()
|
||||
check("agent kill -9: recovers", wait_until(healthy, 40) is not None)
|
||||
@@ -435,7 +435,7 @@ def scenario_probe_link():
|
||||
print("== probe<->agent link: flapping, long blackhole (half-open), reconnect storms")
|
||||
ensure_healthy()
|
||||
time.sleep(20)
|
||||
mem0 = (PROBE.working_set_mb(), rss_mb("monitor-agent"))
|
||||
mem0 = (PROBE.working_set_mb(), rss_mb("moshiro-agent"))
|
||||
|
||||
# 1. flapping: 3 s blackholes every 10 s, 8 times
|
||||
for _ in range(8):
|
||||
@@ -458,7 +458,7 @@ def scenario_probe_link():
|
||||
|
||||
# 3. reconnect storm: kill the agent 12 times in a row
|
||||
for _ in range(12):
|
||||
wsl("pkill -9 -x monitor-agent; true")
|
||||
wsl("pkill -9 -x moshiro-agent; true")
|
||||
time.sleep(0.5)
|
||||
start_agent()
|
||||
time.sleep(2)
|
||||
@@ -475,7 +475,7 @@ def scenario_probe_link():
|
||||
time.sleep(2)
|
||||
check("8 probe restarts in a row: converges", wait_until(healthy, 60) is not None)
|
||||
time.sleep(40)
|
||||
mem1 = (PROBE.working_set_mb(), rss_mb("monitor-agent"))
|
||||
mem1 = (PROBE.working_set_mb(), rss_mb("moshiro-agent"))
|
||||
check("memory stays bounded after the storms", mem1[0] < mem0[0] + 15 and mem1[1] < mem0[1] + 15, f"probe {mem0[0]:.1f}->{mem1[0]:.1f} MB, agent {mem0[1]:.1f}->{mem1[1]:.1f} MB")
|
||||
ensure_healthy()
|
||||
|
||||
@@ -501,7 +501,7 @@ def scenario_backend_link():
|
||||
check("steady state: ~2 chunks / 10 s (udp + tcp), no duplicates", 4 <= c1 - c0 <= 8, f"{c1 - c0} chunks in 30 s")
|
||||
|
||||
# the backend is stopped for a while (graceful): the agent keeps collecting, delivers after
|
||||
stop_proc("monitor-backend", "TERM", 30)
|
||||
stop_proc("moshiro-backend", "TERM", 30)
|
||||
time.sleep(40)
|
||||
start_backend()
|
||||
check("backend down 40 s then up: everything converges", wait_until(healthy, 90) is not None)
|
||||
@@ -525,16 +525,16 @@ def scenario_silent():
|
||||
check("... and when the probe thaws it comes back", wait_until(healthy, 90) is not None)
|
||||
|
||||
# a frozen agent: the probe must drop the session and keep retrying; the backend must mark the agent offline
|
||||
wsl("pkill -STOP -x monitor-agent")
|
||||
wsl("pkill -STOP -x moshiro-agent")
|
||||
seen_off = wait_until(lambda: not agent_online(), 150, 1)
|
||||
check("frozen agent: the backend marks it offline", seen_off is not None, f"{seen_off and round(seen_off)}s" if seen_off else "still online after 150 s")
|
||||
wsl("pkill -CONT -x monitor-agent")
|
||||
wsl("pkill -CONT -x moshiro-agent")
|
||||
check("... and when the agent thaws everything converges", wait_until(healthy, 90) is not None)
|
||||
|
||||
# a frozen backend: the agent must notice (WebSocket ping timeout) and reconnect once the backend thaws
|
||||
wsl("pkill -STOP -x monitor-backend")
|
||||
wsl("pkill -STOP -x moshiro-backend")
|
||||
time.sleep(70)
|
||||
wsl("pkill -CONT -x monitor-backend")
|
||||
wsl("pkill -CONT -x moshiro-backend")
|
||||
check("frozen backend (70 s): converges after it thaws", wait_until(healthy, 90) is not None)
|
||||
time.sleep(30)
|
||||
g, n = gaps(3)
|
||||
@@ -546,7 +546,7 @@ def scenario_soak():
|
||||
print("== soak: 8 minutes with random faults; memory, convergence and data continuity")
|
||||
ensure_healthy()
|
||||
time.sleep(20)
|
||||
mem0 = (PROBE.working_set_mb(), rss_mb("monitor-agent"), rss_mb("monitor-backend"))
|
||||
mem0 = (PROBE.working_set_mb(), rss_mb("moshiro-agent"), rss_mb("moshiro-backend"))
|
||||
import random
|
||||
rnd = random.Random(7)
|
||||
end = now() + 8 * 60
|
||||
@@ -563,7 +563,7 @@ def scenario_soak():
|
||||
time.sleep(rnd.uniform(5, 12))
|
||||
clear_netem("eth0")
|
||||
elif fault == "agent_kill":
|
||||
wsl("pkill -9 -x monitor-agent; true")
|
||||
wsl("pkill -9 -x moshiro-agent; true")
|
||||
time.sleep(1)
|
||||
start_agent()
|
||||
elif fault == "probe_kill":
|
||||
@@ -574,7 +574,7 @@ def scenario_soak():
|
||||
clear_netem("eth0")
|
||||
check(f"after {faults} random faults everything converges", ensure_healthy(90) is not None)
|
||||
time.sleep(45)
|
||||
mem1 = (PROBE.working_set_mb(), rss_mb("monitor-agent"), rss_mb("monitor-backend"))
|
||||
mem1 = (PROBE.working_set_mb(), rss_mb("moshiro-agent"), rss_mb("moshiro-backend"))
|
||||
check("memory bounded", mem1[0] < mem0[0] + 15 and mem1[1] < mem0[1] + 20 and mem1[2] < mem0[2] + 40, f"probe {mem0[0]:.0f}->{mem1[0]:.0f}, agent {mem0[1]:.0f}->{mem1[1]:.0f}, backend {mem0[2]:.0f}->{mem1[2]:.0f} MB")
|
||||
m, _ = metrics("30s")
|
||||
check("the final minute is measured correctly", (m.get("loss.up") or 0) < 1 and (m.get("loss.down") or 0) < 1, f"{m.get('loss.up')} {m.get('loss.down')}")
|
||||
@@ -601,7 +601,7 @@ def main():
|
||||
except Exception as ex:
|
||||
check(f"{name} ran to completion", False, repr(ex)[:300])
|
||||
PROBE.stop("kill")
|
||||
wsl("pkill -9 -x monitor-agent; pkill -9 -x monitor-backend; tc qdisc del dev eth0 root 2>/dev/null; tc qdisc del dev eth0 ingress 2>/dev/null; tc qdisc del dev lo ingress 2>/dev/null; true")
|
||||
wsl("pkill -9 -x moshiro-agent; pkill -9 -x moshiro-backend; tc qdisc del dev eth0 root 2>/dev/null; tc qdisc del dev eth0 ingress 2>/dev/null; tc qdisc del dev lo ingress 2>/dev/null; true")
|
||||
failed = [r for r in RESULTS if not r[1]]
|
||||
print(f"\n{len(RESULTS) - len(failed)}/{len(RESULTS)} checks passed")
|
||||
for name, _, detail in failed:
|
||||
|
||||
Reference in new issue
Block a user