fix(dashboard): a served profile is not a gateway of its own
Gateway liveness reports a profile the multiplexer serves as running on
the multiplexer's PID (446f6f79a8). The dashboard read that answer as
"this profile runs its own gateway" in two places. multiplexed_profile_refusal
returned None for every served profile, so while a multiplexer was live
(the only time it matters) start/stop were never refused and a restart
was never rewritten to the multiplexer. The /api/status topology listed
one phantom gateway per served profile beside the host.
_has_own_gateway() answers the actual question: a live gateway whose PID
is not the multiplexer serving the profile. The existing lifecycle test
stubbed _check_gateway_running to False, which is what hid it; it now
runs on the real liveness.
This commit is contained in:
committed by
Teknium
parent
35c0ea3dff
commit
fdd49a372c
@@ -158,7 +158,9 @@ def _collect_profile_gateway_topology() -> Dict[str, Any]:
|
||||
multiplex = False
|
||||
for name, home in homes:
|
||||
try:
|
||||
if not _check_gateway_running(home):
|
||||
# A served profile's liveness is the multiplexer's: listing it here showed one phantom
|
||||
# gateway per served profile beside the host.
|
||||
if not (_check_gateway_running(home) if name == "default" else _has_own_gateway(home)):
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
@@ -528,6 +530,24 @@ def _profile_is_multiplexed(profile: str) -> bool:
|
||||
return named_profile_served_by_running_multiplexer(profile)
|
||||
|
||||
|
||||
def _has_own_gateway(profile_dir: Path) -> bool:
|
||||
"""A live gateway of the profile's OWN (a ``--force``-started separate one), not the multiplexer that
|
||||
serves it. Gateway liveness reports a served profile as running on the multiplexer's PID (#97120),
|
||||
so reading liveness alone made every served profile look self-hosted and the refusal below never
|
||||
fired while a multiplexer was live, which is the only time it is needed."""
|
||||
from gateway.status import get_running_pid, multiplexer_liveness_for_profile, resolve_gateway_liveness
|
||||
from hermes_cli.profiles import _check_gateway_running
|
||||
if not _check_gateway_running(profile_dir):
|
||||
return False
|
||||
served = multiplexer_liveness_for_profile(profile_dir)
|
||||
if served is None:
|
||||
return True
|
||||
liveness = resolve_gateway_liveness(
|
||||
profile_dir=profile_dir, use_cache=False,
|
||||
pid_probe=lambda path: get_running_pid(path, cleanup_stale=False))
|
||||
return liveness.running and liveness.pid != served[0]
|
||||
|
||||
|
||||
def multiplexed_profile_refusal(profile: Optional[str], verb: str) -> Optional[str]:
|
||||
"""Refusal text for ``gateway start``/``stop`` on a named profile with no gateway of its own (a
|
||||
``--force``-started separate one is managed normally), else None. ``stop`` is refused only when the
|
||||
@@ -552,8 +572,7 @@ def multiplexed_profile_refusal(profile: Optional[str], verb: str) -> Optional[s
|
||||
return standalone_rescan_message(requested)
|
||||
if not served and verb != "start":
|
||||
return None
|
||||
from hermes_cli.profiles import _check_gateway_running
|
||||
if _check_gateway_running(profile_dir):
|
||||
if _has_own_gateway(profile_dir):
|
||||
return None
|
||||
if served:
|
||||
return (f"The default gateway already serves profile '{requested}' as a multiplexer; "
|
||||
|
||||
@@ -227,9 +227,10 @@ def test_dashboard_liveness_ladder_reports_served_profile_running(served_root):
|
||||
def test_dashboard_lifecycle_verbs_target_the_multiplexer(served_root, monkeypatch):
|
||||
"""`gateway restart` for a served profile restarts the multiplexer (a `-p X` child only exits 78 into
|
||||
the action log); `start`/`stop` refuse; a profile with its own gateway is managed normally."""
|
||||
from hermes_cli import profiles as profiles_mod
|
||||
from hermes_cli import web_server_gateway
|
||||
from hermes_cli.web_server_gateway import _gateway_subcommand, _profile_action_environment, multiplexed_profile_refusal
|
||||
monkeypatch.setattr(profiles_mod, "_check_gateway_running", lambda home: False)
|
||||
# No stub: a served profile's liveness answers "running" on the MULTIPLEXER's pid, and that must
|
||||
# not read as a gateway of its own (stubbing it False hid exactly that).
|
||||
# This process's own HERMES_HOME is coder's; the restart child must still run under the DEFAULT
|
||||
# home (the multiplexer's) — a bare `gateway restart` here would inherit coder's home and exit 78.
|
||||
restart = _gateway_subcommand("coder", "restart")
|
||||
@@ -239,7 +240,7 @@ def test_dashboard_lifecycle_verbs_target_the_multiplexer(served_root, monkeypat
|
||||
assert _gateway_subcommand("other", "restart") == ["-p", "other", "gateway", "restart"]
|
||||
assert multiplexed_profile_refusal("other", "stop") is None
|
||||
# coder started its own gateway with --force: it is that gateway the verbs address.
|
||||
monkeypatch.setattr(profiles_mod, "_check_gateway_running", lambda home: True)
|
||||
monkeypatch.setattr(web_server_gateway, "_has_own_gateway", lambda profile_dir: True)
|
||||
assert _gateway_subcommand("coder", "restart") == ["-p", "coder", "gateway", "restart"]
|
||||
assert multiplexed_profile_refusal("coder", "stop") is None
|
||||
|
||||
@@ -260,10 +261,8 @@ def test_the_multiplexer_restart_names_the_root_even_under_a_sticky_active_profi
|
||||
action log says restarted and the multiplexer never was."""
|
||||
from pathlib import Path
|
||||
|
||||
from hermes_cli import profiles as profiles_mod
|
||||
from hermes_cli.main import _apply_profile_override
|
||||
from hermes_cli.web_server_gateway import _gateway_subcommand, _profile_action_environment
|
||||
monkeypatch.setattr(profiles_mod, "_check_gateway_running", lambda home: False)
|
||||
monkeypatch.setenv("HERMES_HOME", str(served_root)) # the dashboard runs as the default profile
|
||||
(served_root / "active_profile").write_text("coder")
|
||||
monkeypatch.setattr(Path, "home", lambda: served_root.parent)
|
||||
@@ -277,3 +276,13 @@ def test_the_multiplexer_restart_names_the_root_even_under_a_sticky_active_profi
|
||||
monkeypatch.setattr("sys.argv", ["hermes", *restart])
|
||||
_apply_profile_override() # what the spawned child does first
|
||||
assert os.environ["HERMES_HOME"] == str(served_root)
|
||||
|
||||
|
||||
def test_the_topology_lists_a_served_profile_under_the_multiplexer_not_as_its_own_gateway(served_root, monkeypatch):
|
||||
"""`/api/status` topology: a served profile's liveness is the multiplexer's, so it is one gateway
|
||||
serving both, not a second gateway entry for `coder` beside it."""
|
||||
from hermes_cli.web_server_gateway import _collect_profile_gateway_topology
|
||||
monkeypatch.setenv("HERMES_HOME", str(served_root))
|
||||
topology = _collect_profile_gateway_topology()
|
||||
assert [g["profile"] for g in topology["gateways"]] == ["default"]
|
||||
assert "coder" in topology["gateways"][0]["served_profiles"]
|
||||
|
||||
Reference in New Issue
Block a user