diff --git a/.kiro/specs/controller-status/tasks.md b/.kiro/specs/controller-status/tasks.md index 58c4a717..66e90b9e 100644 --- a/.kiro/specs/controller-status/tasks.md +++ b/.kiro/specs/controller-status/tasks.md @@ -49,7 +49,7 @@ - _Requirements: 1.3, 3.3, 5.1, 5.2, 8.1, 8.2, 8.3_ - _Boundary: cmd_ctrlstatus_ - _Depends: 3.1_ -- [ ] 4.2 Switch wait-primary to the authoritative status probe +- [x] 4.2 Switch wait-primary to the authoritative status probe - Replace the wait-primary probe (currently a job-list call) with the authoritative reach-the-primary status call. Success is an authoritative reply from the primary; on success, print the primary's identity. A standby/hint answer is not success — the probe follows the redirect or keeps polling until the primary answers or the deadline elapses. Preserve the existing timeout-argument handling, monotonic deadline, poll interval, log-quieting, and success/failure exit semantics. - Observable: against a healthy cluster wait-primary returns and names the primary; a malformed timeout still errors; the existing exit-code behavior is unchanged; and it does not succeed from a standby-only answer. - _Requirements: 9.1, 9.2, 9.3, 9.4_ diff --git a/src/controller/testsuite/test.c b/src/controller/testsuite/test.c index 99be18c0..e3a52023 100644 --- a/src/controller/testsuite/test.c +++ b/src/controller/testsuite/test.c @@ -1083,23 +1083,28 @@ int cmd_expect_job(int argc, char* argv[]) return 0; } -/* `wait-primary [timeout_ms]` (xce-test-e2e-primitives task 6.1, Req 4.1-4.4, - * design.md cmd_wait_primary). +/* `wait-primary [timeout_ms]` (controller-status task 4.2, Req 9.1-9.4; + * originally xce-test-e2e-primitives task 6.1; design.md "cmd_wait_primary probe + * swap — State contract"). * - * Blocks until a cheap, failover-aware client op succeeds, or the timeout - * elapses. The probe is the print-free joblist_query(NULL, NULL, 0): it drives - * active_ctx + libxce master-rotation/failover and returns a job count (>= 0) - * only when a primary actually served the request, or < 0 on error (Req 4.1). + * Blocks until the AUTHORITATIVE controller status probe reaches the serving + * primary, or the timeout elapses. The probe is xcecli_ctrlstatus_primary: it + * drives active_ctx + libxce master-rotation/failover and returns 0 ONLY when a + * reply with responderRole == PRIMARY was obtained (it internally follows the + * redirect to the primary), or a typed negative when no primary answered within + * its budget (Req 9.1). A standby/hint answer is NOT success — the probe keeps + * polling until the primary itself answers (this avoids a false success on a + * standby's stale primary view after a primary death). * - * On the first successful probe we return 0 (Req 4.2). Otherwise we sleep a - * short poll interval and retry, re-checking a MONOTONIC deadline between - * attempts so the command returns within a bounded time of the timeout and - * never hangs indefinitely when there is no primary (Req 4.3). The deadline is - * derived from CLOCK_MONOTONIC, never wall-clock, so a clock step cannot skew - * it. + * On the first authoritative probe we print the served primary's identity and + * return 0 (Req 9.2). Otherwise we sleep a short poll interval and retry, + * re-checking a MONOTONIC deadline between attempts so the command returns + * within a bounded time of the timeout and never hangs indefinitely when there + * is no primary (Req 9.3). The deadline is derived from CLOCK_MONOTONIC, never + * wall-clock, so a clock step cannot skew it. * * The timeout is argv[1] in milliseconds when supplied, else - * WAIT_PRIMARY_DEFAULT_MS (Req 4.4). A malformed timeout argument is a usage + * WAIT_PRIMARY_DEFAULT_MS (Req 9.4). A malformed timeout argument is a usage * error (return < 0) rather than a silent fallback to the default. */ #define WAIT_PRIMARY_DEFAULT_MS 30000L /* default max wait when arg omitted */ #define WAIT_PRIMARY_POLL_MS 200L /* sleep between probe attempts */ @@ -1149,26 +1154,42 @@ int cmd_wait_primary(int argc, char* argv[]) start_ms = monotonic_ms(); /* The poll probes repeatedly and EXPECTS failure until a primary serves, so - * it must not spam the console: the probe suppresses its own error print - * (quiet=1), and we raise the client log threshold for the duration of the - * loop to silence libxce's per-attempt "xcecli failover: attempt N" INFO - * logs — RESTORED to the user's level on every exit path. */ + * it must not spam the console: xcecli_ctrlstatus_primary suppresses its own + * per-attempt output, and we raise the client log threshold for the duration + * of the loop to silence libxce's per-attempt "xcecli failover: attempt N" + * INFO logs — RESTORED to the user's level on every exit path. */ mylog_setlevel((char*)"warn"); /* Probe, then re-check the monotonic deadline between attempts. Each probe - * may itself take time (libxce failover has its own internal retry budget); - * we re-read the clock after every probe returns so the overall loop honors - * the deadline and returns failure in bounded time. */ + * may itself take time (libxce failover + redirect-to-primary has its own + * internal retry budget); we re-read the clock after every probe returns so + * the overall loop honors the deadline and returns failure in bounded time. */ for (;;) { - int rc = joblist_query(NULL, NULL, 0, /*quiet=*/1); + xce_ctrlstatus_t* status = NULL; + int rc = xcecli_ctrlstatus_primary(active_ctx->ctx, + &status); + + /* Success ONLY on an authoritative reply from the serving primary + * (responderRole == PRIMARY, guaranteed by xcecli_ctrlstatus_primary). + * Print the served primary's identity (the responder IS the primary, so + * primaryName == responderName) and free the reply (Req 9.1, 9.2). */ + if (rc == 0) { + const char* primary = (status != NULL) + ? (NULLSTR(status->primaryName) + ? status->responderName + : status->primaryName) + : NULL; - if (rc >= 0) { elapsed_ms = monotonic_ms() - start_ms; mylog_setlevel(g_active_loglevel); - printf("wait-primary: primary serving after %ld ms\n", elapsed_ms); + printf("wait-primary: primary '%s' serving after %ld ms\n", + NULLSTR(primary) ? "unknown" : primary, elapsed_ms); + xcecli_free_ctrlstatus(status); return 0; } + /* Non-authoritative / failed probe: nothing to free (the typed-failure + * contract leaves *status untouched), keep polling until the deadline. */ elapsed_ms = monotonic_ms() - start_ms; if (elapsed_ms >= timeout_ms) {