feat(scheduler-core): build xceadmin scheduler recording client functions (task 10.3)

Adds record_start()/record_stop()/record_status() to new
src/xceadmin/src/xceadmin/scheduler.py, following push_config()'s exact
pipeline shape (load_schema -> follow_redirects -> decode_reply). Deliberately
diverges from push_config()'s "return failure as data" convention because
this task's own Observable text requires raising a typed error on an error
reply, not just connection/decode failure -- a new SchedAdminError
(xcecommon/errors.py) carries the full reply state (error/recording/file/
event_count/started_at) when SchedAdminReply.ok is false.

test_scheduler.py mirrors test_config_push_mock.py's real mock-TLS-server
technique, covering all three actions, redirect-following (standby-to-
primary), and the error-reply-raises-SchedAdminError path across the
error codes reference table.

188 passed, 3 skipped via the real xceadmin test runner (meson compile
-C xce.build xceadmin).
This commit is contained in:
Rongsong Shen 2026-08-06 04:43:40 +00:00
parent 30ff995cac
commit b64875f4c3
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
5 changed files with 677 additions and 1 deletions

View file

@ -21,6 +21,7 @@ from xcecommon.errors import (
NoPrimaryError,
ReplyDecodeError,
ReservedNameError,
SchedAdminError,
SchemaNotFoundError,
TooManyRedirectsError,
ValidationError,
@ -36,6 +37,7 @@ ALL_SUBCLASSES = [
ReplyDecodeError,
TooManyRedirectsError,
NoPrimaryError,
SchedAdminError,
]
@ -171,6 +173,31 @@ def test_no_primary_error_carries_host_and_port() -> None:
assert caught.port == 9991
def test_sched_admin_error_carries_error_and_reply_state() -> None:
"""Requirement 5.10 (declared here, raised by a later task): a
``SchedAdminReply{ok=false, error=...}`` is surfaced as a typed error
carrying the reason plus the reply's recording/file/event/started-at
state (e.g. "already recording" still reports the current file)."""
with pytest.raises(XceAdminError) as exc_info:
raise SchedAdminError(
"scheduler admin request failed: already recording",
error="already recording",
recording=True,
file="/var/log/xce/current.replay",
event_count=42,
started_at=1234567890,
)
caught = exc_info.value
assert isinstance(caught, SchedAdminError)
assert caught.message == "scheduler admin request failed: already recording"
assert caught.error == "already recording"
assert caught.recording is True
assert caught.file == "/var/log/xce/current.replay"
assert caught.event_count == 42
assert caught.started_at == 1234567890
def test_catching_base_type_recovers_message_for_every_subclass() -> None:
"""The entire point of the shared exception contract (design.md,
xceadmin.cli Implementation Note): a single `except XceAdminError as e`
@ -186,6 +213,12 @@ def test_catching_base_type_recovers_message_for_every_subclass() -> None:
(ReplyDecodeError("msg-f", reason="r"), "msg-f"),
(TooManyRedirectsError("msg-g", hops=3), "msg-g"),
(NoPrimaryError("msg-h", host="h", port=1), "msg-h"),
(
SchedAdminError(
"msg-i", error="e", recording=False, file="", event_count=0, started_at=0
),
"msg-i",
),
]
for exc, expected_message in cases: