Merge pull request #108 from pyopower/fix/reload-added-system-config

fix(reload): a system added by SIGHUP gets its own config, and answers
develop
ce5rpy 3 days ago committed by GitHub
commit 3b1355decb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -716,10 +716,11 @@ def run_peer_server(
n = reopen_file_handlers()
logger.info("(LOGGER) Reopened %s file log handler(s) after SIGUSR2", n)
def _create_hbp_protocol(system_name: str) -> Any:
def _create_hbp_protocol(system_name: str, system_config: dict[str, Any] | None = None) -> Any:
# Reload passes the config it is building: a system added there is not in the live one yet.
return HBPProtocolFactory(
system_name,
config,
config if system_config is None else system_config,
report_sender,
router=acl_router,
dmrd_received=routing_use_cases.dmrd_received,

@ -198,7 +198,7 @@ def reload_server_config(
protocols: dict[str, Any],
transports: dict[str, Any],
*,
create_protocol: Callable[[str], Any],
create_protocol: Callable[[str, dict[str, Any]], Any],
listen_udp: Callable[[str, BindSpec, Any], Any],
stop_listener: Callable[[Any], None],
on_systems_changed: Callable[[], None] | None = None,
@ -212,6 +212,11 @@ def reload_server_config(
Preserves PEERS / STATS and protocol STATUS for systems that stay up with the
same bind address. Returns a Deferred that fires on the reactor thread when
listeners are rebound (waits for ``stopListening`` before re-bind).
``create_protocol(name, config)`` gets the config being reloaded: a new
system exists only there until the caller swaps it in, so a protocol built
from the live config would see no system block at all (no MODE: a MASTER
that answers nothing).
"""
log = log or logger
try:
@ -273,7 +278,7 @@ def reload_server_config(
for name in sorted(new_enabled - mapped_old_enabled):
sys_cfg = copy.deepcopy(new_systems[name])
config.setdefault("SYSTEMS", {})[name] = sys_cfg
proto = create_protocol(name)
proto = create_protocol(name, config)
protocols[name] = proto
if should_bind_udp is None or should_bind_udp(name, sys_cfg):
_schedule_start(name, sys_cfg, proto, bind_spec(sys_cfg))
@ -297,7 +302,7 @@ def reload_server_config(
inject_only = should_bind_udp is not None and not should_bind_udp(name, merged)
was_inject_only = should_bind_udp is not None and not should_bind_udp(name, old_cfg)
if proto is None:
proto = create_protocol(name)
proto = create_protocol(name, config)
protocols[name] = proto
if inject_only:
transports.pop(name, None)

@ -152,7 +152,7 @@ def test_reload_collapsed_generator_migrates_listener_without_rebind() -> None:
_Loader(),
protocols,
transports,
create_protocol=lambda name: _FakeProto(name),
create_protocol=lambda name, _cfg: _FakeProto(name),
listen_udp=_listen,
stop_listener=lambda port: port.stopListening() if port else None,
).addCallback(_done)
@ -223,7 +223,7 @@ def test_reload_defers_rebind_until_stop_completes() -> None:
_Loader(),
protocols,
transports,
create_protocol=lambda name: _FakeProto(name),
create_protocol=lambda name, _cfg: _FakeProto(name),
listen_udp=_listen,
stop_listener=lambda port: port.stopListening() if port else None,
)
@ -236,3 +236,61 @@ def test_reload_defers_rebind_until_stop_completes() -> None:
stop_fired.callback(None)
reactor.runUntilCurrent()
assert listen_calls == [52556]
def test_a_master_added_on_reload_answers_its_peers() -> None:
"""Regression (ADN 2131, 25-sep-2026): a MASTER added by SIGHUP logged "added system ...
listening" but answered nothing. Its protocol was built from the live config, where the
new system did not exist yet (it is swapped in after the reload): no MODE, no login."""
from adn_server.application.runtime_context import (
ConfigProxy,
RuntimeContext,
RuntimeContextHolder,
prepare_reload_config,
swap_runtime_config,
)
from adn_server.infrastructure.acl_router import InMemoryAclRouter
from adn_server.infrastructure.config_loader import process_acls
from adn_server.infrastructure.twisted_adapters.udp_hbp import HBPProtocolFactory
from tests.support.hbp_repeat_stack import RecordingTransport
holder = RuntimeContextHolder(RuntimeContext(config=_base_config()))
live = ConfigProxy(holder) # what the server hands its protocols
incoming = _base_config()
incoming["SYSTEMS"]["SMS-NEW"] = {
"MODE": "MASTER", "ENABLED": True, "IP": "", "PORT": 62999, "PASSPHRASE": "secret",
"MAX_PEERS": 1, "REPEAT": True, "USE_ACL": True, "REG_ACL": "PERMIT:ALL", "SUB_ACL": "PERMIT:ALL",
"TGID_TS1_ACL": "PERMIT:ALL", "TGID_TS2_ACL": "PERMIT:ALL", "ALLOW_UNREG_ID": True,
}
class _Loader(YamlConfigLoader):
def load(self, _path: str) -> dict[str, Any]:
process_acls(incoming) # as the real loader does
return incoming
def _create(name: str, system_config: dict[str, Any] | None = None) -> Any:
# Mirrors peer_server._create_hbp_protocol: live config unless reload passes its own.
return HBPProtocolFactory(name, live if system_config is None else system_config, router=InMemoryAclRouter())
new_config = prepare_reload_config(holder)
protocols = {name: _FakeProto(name) for name in _base_config()["SYSTEMS"]}
reload_server_config(
new_config,
"adn-server.yaml",
_Loader(),
protocols,
{name: _FakePort() for name in protocols},
create_protocol=_create,
listen_udp=lambda _n, _b, _p: _FakePort(),
stop_listener=lambda port: port.stopListening() if port else None,
)
from twisted.internet import reactor
reactor.runUntilCurrent()
swap_runtime_config(holder, new_config)
proto = protocols["SMS-NEW"]
assert proto._config.get("MODE") == "MASTER"
proto.transport = RecordingTransport()
proto.datagramReceived(b"RPTL" + (213003590).to_bytes(4, "big"), ("127.0.0.1", 50000))
assert proto.transport.sent and proto.transport.sent[0][0].startswith(b"RPTACK")

Loading…
Cancel
Save

Powered by TurnKey Linux.