feat(component-failover-adoption): unify scheduler's dual connect paths into single pipeline

Replace scheduler's inline per-candidate loop and separate single-host
branch with one unified pipeline (mirrors worker's task 2.1 fix): build
exactly one validated candidate list, whether from --peers or synthesized
as a single candidate from the legacy default, then connect through the
shared xce_cluster_config_to_client_config() helper exactly once.

Adds sched_on_reconnect(), a rejoin callback that only sets a flag and
returns immediately (no I/O, no xcecli_* calls) to avoid the self-deadlock
worker hit in task 2.3 — registered via xcecli_set_reconnect_handler()
after xcecli_start(), since xcecli_start() resets any handler registered
earlier back to NULL.

Adds test_scheduler_connect.c: behavioral tests confirm no-peers and
single-candidate configs produce identical connect log sequences and that
unreachable candidates fail fast without retry loops; structural tests
source-scan scheduler.c to confirm a single xcecli_ctx_init() call site, a
single xcecli_join() call site, and a rejoin callback body free of I/O and
xcecli_* calls.

Live-validated against a 3-node HA controller cluster: a scheduler
configured with the standby listed first in --peers fails over via
xcecli's built-in retry-next mechanism to the primary and joins
successfully.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYSEeLmzjtD4RLDuUDhLoK
This commit is contained in:
Rongsong Shen 2026-08-11 07:58:17 +00:00
parent 619b119ef5
commit a9f283dc64
Signed by: rshen
SSH key fingerprint: SHA256:8Vf2pZ3h6rJa7EhGK+WaAQ9Mta/apZvteW6g9U/WKKk
4 changed files with 609 additions and 60 deletions

View file

@ -139,7 +139,15 @@ typedef struct {
xce_sched_core_t core;
xcecli_ctx_t apictx;
xce_cluster_config_t
cluster_cfg; /* HA candidate list — empty means single-host mode */
cluster_cfg; /* HA candidate list. Empty (candidates_count==0) only
* transiently before sched_init() runs, when no
* --peers was configured; sched_init() always
* synthesizes a single-candidate default in that case
* (component-failover-adoption, task 3.1, Req 1 AC5)
* before connecting, so it is populated with >=1
* candidate for the rest of the process's life --
* single-host is the candidates_count=1 case of the
* one HA mechanism, not a separate mode. */
/* Req 5 AC 10 (scheduler-core spec, task 9.1): recording directory,
* provisioned EXACTLY ONE WAY -- the --record-dir startup CLI flag
* (main.c), default "/var/log/xce/". Never settable via any