fix(xceadmin): probe_config uses Request envelope + follow_redirects (bug-1506, bug-1507)

- probe_config() now wraps probe in Request envelope matching push_config()
- Controller OPCode_probeConfigTag handler replies through xce_reply()
  with redirectHost/redirectPort populated
- follow_redirects works identically for both push and probe flows
This commit is contained in:
Rongsong Shen 2026-08-10 09:53:48 +00:00
parent d70bf6a402
commit b00e97ffe6
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
3 changed files with 86 additions and 54 deletions

View file

@ -271,16 +271,18 @@ def test_config_push_connection_refused_exits_nonzero_not_raw_traceback(cluster_
assert result.output.strip() != ""
def test_config_push_dry_run_reports_generations(mock_controller, cluster_bin):
def test_config_push_dry_run_reports_generations(mock_controller, cluster_bin, wrap_reply):
from xcecommon import wire
mod = load_schema()
def _handler(conn):
wire.recv_frame(conn)
reply = mod.ProbeConfigTagReply.new_message()
reply.configTag = 4
wire.send_frame(conn, reply.to_bytes())
inner = mod.RequestReply.new_message()
inner.status = 0
probe_reply = inner.data.init("probeconfigtagreplymsg")
probe_reply.configTag = 4
wire.send_frame(conn, wrap_reply(inner))
srv = mock_controller(_handler)

View file

@ -244,19 +244,29 @@ def test_push_config_explicit_port_parsed():
# ---------------------------------------------------------------------------
def test_probe_config_reports_current_and_would_be_generation(mock_controller, cluster_bin):
def test_probe_config_reports_current_and_would_be_generation(mock_controller, cluster_bin, wrap_reply):
"""bug-1507: probe_config now sends a Request envelope and decodes
through follow_redirects → decode_reply, matching push_config's path.
The mock server must unwrap the Request envelope on receive and wrap
its reply the same way a real controller does. """
mod = load_schema()
received = {}
def _handler(conn):
request_bytes = wire.recv_frame(conn)
received["request_bytes"] = request_bytes
with mod.ProbeConfigTagRequest.from_bytes(request_bytes) as req:
received["name"] = req.name
# Request{opcode: probeConfigTag, body.probeconfigtagrequestmsg: ...}
with mod.Request.from_bytes(request_bytes) as req:
assert req.opcode == "probeConfigTag"
assert req.body.which() == "probeconfigtagrequestmsg"
received["name"] = req.body.probeconfigtagrequestmsg.name
reply = mod.ProbeConfigTagReply.new_message()
reply.configTag = 41
wire.send_frame(conn, reply.to_bytes())
# Build reply: RequestReply{status:0, data.probeconfigtagreplymsg: {configTag:41}}
inner = mod.RequestReply.new_message()
inner.status = 0
probe_reply = inner.data.init("probeconfigtagreplymsg")
probe_reply.configTag = 41
wire.send_frame(conn, wrap_reply(inner))
srv = mock_controller(_handler)
@ -266,21 +276,22 @@ def test_probe_config_reports_current_and_would_be_generation(mock_controller, c
assert result.would_be_generation == 42
assert result.accepted is True
assert result.reason == ""
assert received["name"] == "" # matches config_repl.c's xce_probe_config_tag exactly
assert received["name"] == ""
def test_probe_config_zero_generation_still_accepted(mock_controller, cluster_bin):
def test_probe_config_zero_generation_still_accepted(mock_controller, cluster_bin, wrap_reply):
"""A fresh cluster with no config committed yet (configTag == 0) is
still a successful probe -- there is no rejection signal on this
wire shape (module docstring); the caller decides significance.
"""
still a successful probe -- configTag == 0 is a valid, real response
from a real controller handler now (bug-1506). """
mod = load_schema()
def _handler(conn):
wire.recv_frame(conn)
reply = mod.ProbeConfigTagReply.new_message()
reply.configTag = 0
wire.send_frame(conn, reply.to_bytes())
inner = mod.RequestReply.new_message()
inner.status = 0
probe_reply = inner.data.init("probeconfigtagreplymsg")
probe_reply.configTag = 0
wire.send_frame(conn, wrap_reply(inner))
srv = mock_controller(_handler)
@ -293,27 +304,23 @@ def test_probe_config_zero_generation_still_accepted(mock_controller, cluster_bi
@pytest.mark.parametrize("tag_value", [0, 1, 999999])
def test_probe_config_has_no_wire_level_rejection_signal_by_design(
mock_controller, cluster_bin, tag_value
mock_controller, cluster_bin, tag_value, wrap_reply
):
"""5.6 asks for both an accepted and a rejected dry-run outcome, but
``ProbeConfigTagReply`` (module docstring, ``push.py`` module
docstring) has only a bare ``configTag`` -- no field exists on the
real wire contract to signal rejection, matching the real C
reference client (``config_repl.c``'s ``xce_probe_config_tag``, which
also returns only a bare tag). This test locks in that limitation
explicitly rather than silently having no coverage for it: every
successful round-trip, regardless of the returned ``configTag``
value, yields ``accepted=True`` -- there is no reachable code path
that produces ``accepted=False`` today. A real "would this SHA be
accepted" check is future controller-side work (Requirement 6.1).
"""
"""ProbeConfigTagReply (controller.capnp:909) has only a bare
``configTag`` field -- no separate accept/reject flag exists. A
real controller handler (bug-1506) always returns status==0 here;
every successful round-trip, regardless of the returned configTag
value, yields ``accepted=True``. A real "would this SHA be accepted"
check is future work (Requirement 6.1). """
mod = load_schema()
def _handler(conn):
wire.recv_frame(conn)
reply = mod.ProbeConfigTagReply.new_message()
reply.configTag = tag_value
wire.send_frame(conn, reply.to_bytes())
inner = mod.RequestReply.new_message()
inner.status = 0
probe_reply = inner.data.init("probeconfigtagreplymsg")
probe_reply.configTag = tag_value
wire.send_frame(conn, wrap_reply(inner))
srv = mock_controller(_handler)