forked from lei.chen/myxce
feat(admin-config-tool): task 5.6 - mock-controller push/dry-run coverage
Adds end-to-end propagation tests through push_config's real composition with xceadmin.discovery (not just discovery.py's own unit tests): a 4-mock-server redirect chain proving TooManyRedirectsError surfaces correctly, and a monkeypatched-timeout test proving NoPrimaryError does too, without waiting out a real 30s socket timeout. Also locks in and documents a real wire-contract limitation: ProbeConfigTagReply carries no rejection signal, so dry-run's accepted=False path is unreachable today by design, not an oversight. This completes all 18 sub-tasks in tasks.md.
This commit is contained in:
parent
48a5a0b4e6
commit
67cc361191
2 changed files with 98 additions and 8 deletions
|
|
@ -22,7 +22,7 @@ from pathlib import Path
|
|||
import pytest
|
||||
|
||||
from xcecommon import wire
|
||||
from xcecommon.errors import ReplyDecodeError
|
||||
from xcecommon.errors import ConnectionTimeoutError, NoPrimaryError, ReplyDecodeError, TooManyRedirectsError
|
||||
from xcecommon.schema import load_schema
|
||||
from xceadmin import push
|
||||
|
||||
|
|
@ -268,3 +268,91 @@ def test_probe_config_zero_generation_still_accepted(mock_controller, cluster_bi
|
|||
assert result.current_generation == 0
|
||||
assert result.would_be_generation == 1
|
||||
assert result.accepted is True
|
||||
|
||||
|
||||
@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
|
||||
):
|
||||
"""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).
|
||||
"""
|
||||
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())
|
||||
|
||||
srv = mock_controller(_handler)
|
||||
|
||||
result = push.probe_config(cluster_bin, f"127.0.0.1:{srv.port}", CERT_DIR)
|
||||
|
||||
assert result.accepted is True
|
||||
assert result.reason == ""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# End-to-end redirect/discovery failure propagation through push_config
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_push_config_propagates_too_many_redirects_end_to_end(mock_controller, cluster_bin):
|
||||
"""A real chain of 4 redirecting mock servers -- push_config's own
|
||||
composition with xceadmin.discovery must surface TooManyRedirectsError,
|
||||
not silently follow forever or return a bogus result.
|
||||
"""
|
||||
mod = load_schema()
|
||||
servers = {}
|
||||
|
||||
def _make_redirect_handler(next_port_getter):
|
||||
def _handler(conn):
|
||||
wire.recv_frame(conn)
|
||||
reply = mod.RequestReply.new_message()
|
||||
reply.status = 0
|
||||
reply.redirectHost = "127.0.0.1"
|
||||
reply.redirectPort = next_port_getter()
|
||||
reply.data.nodata = None
|
||||
wire.send_frame(conn, reply.to_bytes())
|
||||
|
||||
return _handler
|
||||
|
||||
# 5 servers: a -> b -> c -> d -> e (4 redirects, one too many).
|
||||
names = ["a", "b", "c", "d", "e"]
|
||||
for i, name in enumerate(names[:-1]):
|
||||
next_name = names[i + 1]
|
||||
servers[name] = mock_controller(_make_redirect_handler(lambda n=next_name: servers[n].port))
|
||||
# The last server ('e') also redirects -- this is hop #4, exceeding the
|
||||
# 3-hop limit before it would ever be contacted.
|
||||
servers["e"] = mock_controller(_make_redirect_handler(lambda: 1))
|
||||
|
||||
with pytest.raises(TooManyRedirectsError):
|
||||
push.push_config(cluster_bin, f"127.0.0.1:{servers['a'].port}", CERT_DIR)
|
||||
|
||||
|
||||
def test_push_config_propagates_no_primary_error_on_connection_timeout(cluster_bin, monkeypatch):
|
||||
"""xceadmin.discovery already unit-tests this conversion directly
|
||||
(test_discovery.py); this proves push_config's actual composition
|
||||
with it surfaces the same NoPrimaryError, without waiting out a real
|
||||
30s socket timeout -- xcecommon.wire.connect is monkeypatched to
|
||||
raise ConnectionTimeoutError immediately, exactly what it would
|
||||
eventually raise on a genuine timeout.
|
||||
"""
|
||||
|
||||
def _fake_connect(host, port, cert_dir, timeout_s=30.0):
|
||||
raise ConnectionTimeoutError(f"timeout connecting to {host}:{port}", host, port)
|
||||
|
||||
monkeypatch.setattr(wire, "connect", _fake_connect)
|
||||
|
||||
with pytest.raises(NoPrimaryError):
|
||||
push.push_config(cluster_bin, "127.0.0.1:9991", CERT_DIR)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue