feat(controller-status): source per-controller connection status in ctrlstatus_fill (task 8.2)
Populates CtrlPeer.status (added in task 8.1) inside ctrlstatus_fill's peer-building loop: self always reports ok; a primary responder sources every other peer from the live replication connection registry (replicator_t.standbys[], host+port matched, reusing repl_active_standby_count's ep!=NULL && !in_catch_up predicate) -- dead/catching-up/ok; a non-primary responder reports unknown for every non-self peer. A self-identity mismatch (pre-existing misconfiguration) logs one WARN and falls through to the normal non-self sourcing rather than forcing any peer to ok. Read-only: a bounded lock-protected scan, no state mutation. TDD: 3 new tests (primary-sourced-from-registry, standby-unknown-except- self, self-mismatch-falls-through) plus an extended no-mutation assertion; RED->GREEN independently reproduced by the reviewer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
1b278c0e9a
commit
ab5dd91537
3 changed files with 288 additions and 1 deletions
|
|
@ -158,6 +158,8 @@
|
|||
- 6.1: `responderTimeMs @13: UInt64` appended to `CtrlStatus` (after `peerCount @12`, append-only) + matching `uint64_t responder_time_ms` in the hand-authored `xce_ctrlstatus_t` (`src/include/typemap.h`); codec wire offset 48, round-trips. Commit 65ddafe.
|
||||
- 6.2: `ctrlstatus_fill` role-branches committed sourcing — PRIMARY → own `persist.committed_lsn`/`committed_config_tag` (atomic acquire); else → `ha->last_known_committed_lsn` (atomic acquire) + `ha->last_known_committed_config_tag` (plain aligned read, NO `ha->lock` per design — `ha.h:172` is non-atomic, written under lock at `ha_convergence.c:377`). `appliedLsn` unchanged. Timestamp stamped from `CLOCK_REALTIME` (NOT monotonic). Read-only. STALENESS CAVEAT (in-boundary): `last_known_committed_lsn` refreshes at CtrlHello join/RDHUP, not per heartbeat — off-primary committed can lag; live-fresh = OUT of boundary → ha-heartbeat-liveness/ha-repl-hardening. Commit 3799bd6.
|
||||
- 6.3 LEARNING: the controller log timestamp formatter is NOT in `xcelog.h` (an 85-line thin wrapper that just `#include "tlog.h"`); the `YYYY-MM-DD HH:MM:SS,mmm` prefix is assembled inline in vendored tinylog (`tlog.c` `_tlog_root_default_format` ~393/398, via `my_tlog_format` in `src/lib/utils.c:221/228`) — no caller-facing helper, so it was REPLICATED byte-equivalently (`localtime_r` + `strftime "%Y-%m-%d %H:%M:%S"` + `,%03u` of `ms%1000`) in a new header-only `src/controller/testsuite/ctrlstatus_fmt.h` (`ctrlstatus_format_time_ms`), shared by `test.c` (`cmd_ctrlstatus` prints `responderTime:` after `fenced`) and `test_ctrlstatus.c` (TZ=UTC-pinned exact-string unit test). LOCAL time on both sides — comparable to `ctrl-N.log` under the same TZ.
|
||||
- 8.2 LEARNING: the new `replicator_t.lock` acquisition in `ctrlstatus_fill`'s peer-building loop is held around the whole per-peer loop (calloc + self/primary/non-primary branching for all configured peers), slightly wider than `repl_active_standby_count`'s own lock-and-scan shape that design.md's Constraints-checked note cites as the precedent. Still bounded/safe (n ≤ `MAX_HA_CANDIDATES`, inner scan ≤ `MAX_STANDBYS`, no I/O) and reviewer-approved as non-blocking, but a future touch to this loop should narrow the lock to wrap only the inner `MAX_STANDBYS` registry scan per non-self peer, matching the cited precedent exactly.
|
||||
|
||||
- 6.4 VERIFIED 2026-07-02 (previously HAND-OFF, blocked on overlayfs — unblocked by ha-commit-durability 6.1 confirming `~/tmp` is a genuine ext4 fs in this sandbox): deployed a 3-node cluster to `~/tmp/xce-ctrlstatus-deploy` via `src/tools/deploy-ha.sh`, submitted jobs against the full masters list, then ran `ctrlstatus` pointed directly at standby ctrl-2 (`set masters localhost:9992`). Result: `answered by ctrl-2 (standby)`, `committedLsn: 4` / `appliedLsn: 4` — non-zero, matching, no inversion (the exact 2026-06-20 bug symptom does not reproduce). `responderTime: 2026-07-02 14:18:19,689` is a live, current wall-clock value consistent with the session timeline. NOTE on the log-correlation half of the Observable: `ctrlstatus_fill` (ctrlstatus.c) has no logging call for the query itself (pure snapshot-fill, no INFO/TRACE line), so there is no literal `ctrl-N.log` line to string-match against — verified the weaker-but-sufficient form instead (responderTime is live/plausible, not frozen/stale/wrong-TZ). PASS. Found and logged (unrelated, low-priority): bug-1400 (xce_test cmd_jobsub SEGVs on malformed positional args instead of required getopt flags — test-client input validation gap, not a production bug) and bug-1401 (ctrlstatus reply decode leaks ~120 bytes per call in `decode_CtrlStatus_ptr`, ASAN-only visibility, ~103 allocs/12KB observed over a dozen calls in this session).
|
||||
|
||||
## 8. Delta: per-controller connection status (Req 13)
|
||||
|
|
@ -169,7 +171,7 @@
|
|||
- _Requirements: 13.1_
|
||||
- _Boundary: schema + typemap_
|
||||
|
||||
- [ ] 8.2 Source each configured controller's connection status in the status handler
|
||||
- [x] 8.2 Source each configured controller's connection status in the status handler
|
||||
- Confirm the exact field path from the controller's top-level cluster state to the replication connection registry (the research item carried from design) before wiring the read below.
|
||||
- TDD (red first, in `test_ctrlstatus.c`): a PRIMARY responder with three peer connection states (one live and caught up, one live and catching up, one with no live connection) reports ok/catching-up/dead respectively for those three peers, and ok for its own entry; a STANDBY responder reports ok for its own entry and unknown for every other configured peer, including the primary; a fixture whose own identity matches no configured peer (misconfiguration case) still produces a defined, non-crashing result via the normal non-self sourcing (no entry force-corrected to ok) and logs a one-time warning; the existing no-mutation assertion is extended to cover whatever connection-registry state this task reads.
|
||||
- Implement the sourcing: a peer matching the responder's own identity always reports ok; when the responder is currently primary, every other peer's status is sourced by matching it against the live replication connection registry (no live connection → dead; live but not yet caught up → catching-up; live and caught up → ok); when the responder is not primary, every other peer's status is reported as unknown, since only the primary has direct connection visibility into every other node. The read stays bounded (a lock-protected scan of a small, fixed-size in-memory registry, no network I/O or unbounded wait) and mutates no state.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include "cluster.h"
|
||||
#include "ha.h"
|
||||
#include "persist.h"
|
||||
#include "utils.h" /* WARN */
|
||||
#include "xce/cluster_def.h"
|
||||
|
||||
/*
|
||||
|
|
@ -197,13 +198,89 @@ void ctrlstatus_fill(cluster_t* c, xce_ctrlstatus_t* st)
|
|||
if (n > 0) {
|
||||
st->peers.list = (xce_ctrlpeer_t**)calloc((size_t)n,
|
||||
sizeof(xce_ctrlpeer_t*));
|
||||
|
||||
/* per-controller connection status (Req 13.1-13.5; design.md Req 13
|
||||
* delta, Locked Decisions 3-6): the responder's own entry always
|
||||
* reports ok; when the responder is currently primary, every OTHER
|
||||
* peer is sourced by host+port match against the live replication
|
||||
* connection registry (replicator_t.standbys[]) -- no live
|
||||
* connection -> dead, live but not yet caught up -> catching-up,
|
||||
* live and caught up -> ok; when the responder is not primary,
|
||||
* every other peer is unknown, since only the primary has direct
|
||||
* connection visibility into every other node. */
|
||||
int is_primary = (st->responderRole == (uint8_t)CTRL_PRIMARY);
|
||||
/* ha->repl is a back-pointer wired once commit-engine setup runs
|
||||
* (cluster.c) and may be NULL before that (or in a minimal test
|
||||
* fixture); &c->persist.repl is always a valid fallback (mirrors
|
||||
* the ha->repl ? ha->repl : &p->repl idiom at ha.c:2673). */
|
||||
replicator_t* repl = ha->repl ? ha->repl : &c->persist.repl;
|
||||
|
||||
/* Self-match fallback (design-review finding): if this responder's
|
||||
* own identity matches none of its configured peers -- a pre-
|
||||
* existing misconfiguration, not new to this delta -- WARN once per
|
||||
* call and let every peer fall through to the normal non-self
|
||||
* branches below. No entry is force-corrected to ok in this case;
|
||||
* this is an explicit, logged degradation rather than a silent or
|
||||
* fabricated result. */
|
||||
int self_found = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (strcmp(mc->peers_cfg.candidates[i].name,
|
||||
st->responderName) == 0) {
|
||||
self_found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!self_found) {
|
||||
WARN("ctrlstatus: responder identity '%s' matches none of the "
|
||||
"%d configured peers -- per-peer connection status falls "
|
||||
"through to the non-self sourcing for every configured "
|
||||
"peer (check this node's --peers/name configuration)",
|
||||
st->responderName, n);
|
||||
}
|
||||
|
||||
if (is_primary)
|
||||
mylock_lock(&repl->lock);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
xce_ctrlpeer_t* p = (xce_ctrlpeer_t*)calloc(1, sizeof(*p));
|
||||
p->name = mc->peers_cfg.candidates[i].name;
|
||||
p->host = mc->peers_cfg.candidates[i].hostname;
|
||||
p->port = (uint32_t)mc->peers_cfg.candidates[i].port;
|
||||
|
||||
if (strcmp(mc->peers_cfg.candidates[i].name,
|
||||
st->responderName) == 0) {
|
||||
p->status = 1u; /* self: always ok (Req 13.2) */
|
||||
} else if (is_primary) {
|
||||
/* Reuses repl_active_standby_count's exact ep != NULL &&
|
||||
* !in_catch_up predicate (repl.c) rather than inventing a
|
||||
* new one (Locked Decision 3). */
|
||||
uint8_t status = 3u; /* default: no matching live slot */
|
||||
for (int j = 0; j < MAX_STANDBYS; j++) {
|
||||
if (strcmp(repl->standbys[j].host,
|
||||
mc->peers_cfg.candidates[i].hostname) == 0 &&
|
||||
repl->standbys[j].port == p->port) {
|
||||
if (repl->standbys[j].ep == NULL) {
|
||||
status = 3u; /* matched slot, no live conn */
|
||||
} else if (atomic_load_explicit(
|
||||
&repl->standbys[j].in_catch_up,
|
||||
memory_order_acquire)) {
|
||||
status = 2u; /* catching-up */
|
||||
} else {
|
||||
status = 1u; /* ok */
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
p->status = status;
|
||||
} else {
|
||||
p->status = 0u; /* unknown: only the primary can see peers */
|
||||
}
|
||||
|
||||
st->peers.list[i] = p;
|
||||
}
|
||||
|
||||
if (is_primary)
|
||||
mylock_unlock(&repl->lock);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -302,6 +302,20 @@ static void test_ctrlstatus_no_mutation(void)
|
|||
atomic_store_explicit(&c.ha.last_known_elected_at_ms,
|
||||
UINT64_C(1781939830911), memory_order_release);
|
||||
|
||||
/* Req 13.5: reading the peer connection-status registry must not mutate
|
||||
* it either. Populate one live standby slot matching a configured peer
|
||||
* so the primary-branch registry scan actually executes during this
|
||||
* call (role is already PRIMARY above). */
|
||||
TEST_ASSERT_EQUAL_INT(0, repl_init(&c.persist.repl));
|
||||
c.ha.repl = &c.persist.repl;
|
||||
int dummy_ep;
|
||||
c.persist.repl.standbys[0].ep = (repl_ep_t)&dummy_ep;
|
||||
strncpy(c.persist.repl.standbys[0].host, "10.0.0.2",
|
||||
sizeof(c.persist.repl.standbys[0].host) - 1);
|
||||
c.persist.repl.standbys[0].port = 7002;
|
||||
atomic_store_explicit(&c.persist.repl.standbys[0].in_catch_up, 0,
|
||||
memory_order_release);
|
||||
|
||||
int32_t role0 = atomic_load_explicit(&c.ha.role, memory_order_acquire);
|
||||
uint64_t term0 = atomic_load_explicit(&c.ha.term, memory_order_acquire);
|
||||
uint64_t applied0 = atomic_load_explicit(&c.ha.applied_lsn,
|
||||
|
|
@ -317,6 +331,13 @@ static void test_ctrlstatus_no_mutation(void)
|
|||
uint64_t vertag0 = mc.config_version_tag;
|
||||
uint64_t elected0 = atomic_load_explicit(&c.ha.last_known_elected_at_ms,
|
||||
memory_order_acquire);
|
||||
repl_ep_t ep0 = c.persist.repl.standbys[0].ep;
|
||||
char host0[XCE_MAXNAME_LEN];
|
||||
strncpy(host0, c.persist.repl.standbys[0].host, sizeof(host0));
|
||||
uint32_t port0 = c.persist.repl.standbys[0].port;
|
||||
int incatchup0 = atomic_load_explicit(
|
||||
&c.persist.repl.standbys[0].in_catch_up,
|
||||
memory_order_acquire);
|
||||
|
||||
xce_ctrlstatus_t st;
|
||||
ctrlstatus_fill(&c, &st);
|
||||
|
|
@ -343,7 +364,17 @@ static void test_ctrlstatus_no_mutation(void)
|
|||
TEST_ASSERT_EQUAL_UINT64(elected0,
|
||||
atomic_load_explicit(&c.ha.last_known_elected_at_ms,
|
||||
memory_order_acquire));
|
||||
/* Req 13.5: the connection-registry slot this call's primary-branch
|
||||
* peer-status scan read must be unchanged too. */
|
||||
TEST_ASSERT_EQUAL_PTR(ep0, c.persist.repl.standbys[0].ep);
|
||||
TEST_ASSERT_EQUAL_STRING(host0, c.persist.repl.standbys[0].host);
|
||||
TEST_ASSERT_EQUAL_UINT32(port0, c.persist.repl.standbys[0].port);
|
||||
TEST_ASSERT_EQUAL_INT(incatchup0,
|
||||
atomic_load_explicit(
|
||||
&c.persist.repl.standbys[0].in_catch_up,
|
||||
memory_order_acquire));
|
||||
|
||||
repl_fini(&c.persist.repl);
|
||||
cs_teardown_cluster(&c);
|
||||
printf("test_ctrlstatus_no_mutation: PASS\n");
|
||||
}
|
||||
|
|
@ -781,6 +812,180 @@ static void test_ctrlstatus_standby_reports_learned_elected_at_ms(void)
|
|||
printf("test_ctrlstatus_standby_reports_learned_elected_at_ms: PASS\n");
|
||||
}
|
||||
|
||||
/* ── per-controller connection status (task 8.2, Req 13) ──────────────────
|
||||
* Design delta "Design Delta — Requirement 13", "ctrlstatus_fill / peer-
|
||||
* status delta — Service contract": self always ok; PRIMARY sources every
|
||||
* other peer by host+port match against the live replication registry
|
||||
* (replicator_t.standbys[]); non-primary sources every other peer as
|
||||
* unknown; a self-match miss (misconfiguration) WARNs once and falls
|
||||
* through to the normal non-self branches for every peer (no forced ok).
|
||||
*/
|
||||
|
||||
/* (o) PRIMARY: three OTHER configured peers in three distinct connection
|
||||
* states -- live+caught-up -> ok, live+catching-up -> catching-up, no live
|
||||
* connection at all -> dead -- plus the responder's own entry, always ok
|
||||
* regardless of the registry (Req 13.1-13.3, Locked Decisions 3-4). */
|
||||
static void test_ctrlstatus_peer_status_primary_sourced_from_registry(void)
|
||||
{
|
||||
cluster_t c;
|
||||
myconf_t mc;
|
||||
cs_setup_cluster(&c, &mc);
|
||||
|
||||
atomic_store_explicit(&c.ha.role, (int32_t)CTRL_PRIMARY,
|
||||
memory_order_release);
|
||||
|
||||
/* cs_setup_cluster's default 3 candidates are self=ctrlA + ctrlB/ctrlC;
|
||||
* add a 4th (MAX_HA_CANDIDATES == 4 covers self + 3 others exactly) with
|
||||
* no matching registry slot at all. */
|
||||
mc.peers_cfg.candidates_count = 4;
|
||||
strncpy(mc.peers_cfg.candidates[3].name, "ctrlD",
|
||||
sizeof(mc.peers_cfg.candidates[3].name) - 1);
|
||||
strncpy(mc.peers_cfg.candidates[3].hostname, "10.0.0.4",
|
||||
sizeof(mc.peers_cfg.candidates[3].hostname) - 1);
|
||||
mc.peers_cfg.candidates[3].port = 7004;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, repl_init(&c.persist.repl));
|
||||
c.ha.repl = &c.persist.repl;
|
||||
|
||||
/* ctrlB (10.0.0.2:7002): live, caught up -> ok */
|
||||
int dummy_ep_b, dummy_ep_c;
|
||||
c.persist.repl.standbys[0].ep = (repl_ep_t)&dummy_ep_b;
|
||||
strncpy(c.persist.repl.standbys[0].host, "10.0.0.2",
|
||||
sizeof(c.persist.repl.standbys[0].host) - 1);
|
||||
c.persist.repl.standbys[0].port = 7002;
|
||||
atomic_store_explicit(&c.persist.repl.standbys[0].in_catch_up, 0,
|
||||
memory_order_release);
|
||||
|
||||
/* ctrlC (10.0.0.3:7003): live, still catching up -> catching-up */
|
||||
c.persist.repl.standbys[1].ep = (repl_ep_t)&dummy_ep_c;
|
||||
strncpy(c.persist.repl.standbys[1].host, "10.0.0.3",
|
||||
sizeof(c.persist.repl.standbys[1].host) - 1);
|
||||
c.persist.repl.standbys[1].port = 7003;
|
||||
atomic_store_explicit(&c.persist.repl.standbys[1].in_catch_up, 1,
|
||||
memory_order_release);
|
||||
|
||||
/* standbys[2] left zeroed (ep == NULL, host == "") -> ctrlD (10.0.0.4)
|
||||
* has no matching slot at all -> dead. */
|
||||
|
||||
xce_ctrlstatus_t st;
|
||||
ctrlstatus_fill(&c, &st);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT32(4, st.peerCount);
|
||||
TEST_ASSERT_EQUAL_INT(4, st.peers.count);
|
||||
TEST_ASSERT_NOT_NULL(st.peers.list);
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlA", st.peers.list[0]->name);
|
||||
TEST_ASSERT_EQUAL_UINT8(1, st.peers.list[0]->status); /* self: ok */
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlB", st.peers.list[1]->name);
|
||||
TEST_ASSERT_EQUAL_UINT8(1, st.peers.list[1]->status); /* live+caught up */
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlC", st.peers.list[2]->name);
|
||||
TEST_ASSERT_EQUAL_UINT8(2, st.peers.list[2]->status); /* live+catching up */
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlD", st.peers.list[3]->name);
|
||||
TEST_ASSERT_EQUAL_UINT8(3, st.peers.list[3]->status); /* no live conn */
|
||||
|
||||
ctrlstatus_free_fields(&st);
|
||||
repl_fini(&c.persist.repl);
|
||||
cs_teardown_cluster(&c);
|
||||
printf("test_ctrlstatus_peer_status_primary_sourced_from_registry: PASS\n");
|
||||
}
|
||||
|
||||
/* (p) STANDBY: reports ok for its own entry and unknown (0) for every other
|
||||
* configured peer, including the primary -- even when a live-looking
|
||||
* registry entry exists for that peer, since only the primary has direct
|
||||
* connection visibility into every other node (Req 13.2, 13.4, Locked
|
||||
* Decision 5). */
|
||||
static void test_ctrlstatus_peer_status_standby_unknown_except_self(void)
|
||||
{
|
||||
cluster_t c;
|
||||
myconf_t mc;
|
||||
cs_setup_cluster(&c, &mc);
|
||||
|
||||
atomic_store_explicit(&c.ha.role, (int32_t)CTRL_STANDBY,
|
||||
memory_order_release);
|
||||
|
||||
/* Even with a live-looking registry entry for ctrlB, a non-primary
|
||||
* responder must NOT read it -- it has no direct connection visibility
|
||||
* to any peer but itself. */
|
||||
TEST_ASSERT_EQUAL_INT(0, repl_init(&c.persist.repl));
|
||||
c.ha.repl = &c.persist.repl;
|
||||
int dummy_ep;
|
||||
c.persist.repl.standbys[0].ep = (repl_ep_t)&dummy_ep;
|
||||
strncpy(c.persist.repl.standbys[0].host, "10.0.0.2",
|
||||
sizeof(c.persist.repl.standbys[0].host) - 1);
|
||||
c.persist.repl.standbys[0].port = 7002;
|
||||
atomic_store_explicit(&c.persist.repl.standbys[0].in_catch_up, 0,
|
||||
memory_order_release);
|
||||
|
||||
xce_ctrlstatus_t st;
|
||||
ctrlstatus_fill(&c, &st);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(3, st.peers.count);
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlA", st.peers.list[0]->name);
|
||||
TEST_ASSERT_EQUAL_UINT8(1, st.peers.list[0]->status); /* self: ok */
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlB", st.peers.list[1]->name);
|
||||
TEST_ASSERT_EQUAL_UINT8(0, st.peers.list[1]->status); /* unknown */
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlC", st.peers.list[2]->name);
|
||||
TEST_ASSERT_EQUAL_UINT8(0, st.peers.list[2]->status); /* unknown, incl. primary */
|
||||
|
||||
ctrlstatus_free_fields(&st);
|
||||
repl_fini(&c.persist.repl);
|
||||
cs_teardown_cluster(&c);
|
||||
printf("test_ctrlstatus_peer_status_standby_unknown_except_self: PASS\n");
|
||||
}
|
||||
|
||||
/* (q) Misconfiguration fallback: a responder whose own identity matches NO
|
||||
* configured peer still produces a defined, non-crashing result via the
|
||||
* normal non-self sourcing -- no entry is force-corrected to ok (design.md
|
||||
* Req 13 delta, self-match fallback). One genuine live+caught-up registry
|
||||
* slot (matching ctrlB) proves the normal primary-branch sourcing still
|
||||
* runs for every peer despite the self-match miss; ctrlA -- which would
|
||||
* have been "self" under correct config -- gets the ordinary unmatched ->
|
||||
* dead outcome like ctrlC, proving nothing was force-corrected to ok. The
|
||||
* implementation also WARNs once per call in this case; this codebase has
|
||||
* no log-content capture harness to assert that in a unit test, so it is
|
||||
* verified by code inspection instead of a test assertion here. */
|
||||
static void test_ctrlstatus_peer_status_self_mismatch_falls_through(void)
|
||||
{
|
||||
cluster_t c;
|
||||
myconf_t mc;
|
||||
cs_setup_cluster(&c, &mc);
|
||||
|
||||
/* Own identity matches none of the configured peer names (misconfigured
|
||||
* --name vs --peers). */
|
||||
c.myhost_info.name = "ctrlZ";
|
||||
|
||||
atomic_store_explicit(&c.ha.role, (int32_t)CTRL_PRIMARY,
|
||||
memory_order_release);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, repl_init(&c.persist.repl));
|
||||
c.ha.repl = &c.persist.repl;
|
||||
|
||||
/* ctrlB (10.0.0.2:7002): live, caught up -> ok (genuine registry match). */
|
||||
int dummy_ep;
|
||||
c.persist.repl.standbys[0].ep = (repl_ep_t)&dummy_ep;
|
||||
strncpy(c.persist.repl.standbys[0].host, "10.0.0.2",
|
||||
sizeof(c.persist.repl.standbys[0].host) - 1);
|
||||
c.persist.repl.standbys[0].port = 7002;
|
||||
atomic_store_explicit(&c.persist.repl.standbys[0].in_catch_up, 0,
|
||||
memory_order_release);
|
||||
/* No matching slot for ctrlA (10.0.0.1:7001) or ctrlC (10.0.0.3:7003). */
|
||||
|
||||
xce_ctrlstatus_t st;
|
||||
ctrlstatus_fill(&c, &st);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(3, st.peers.count);
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlA", st.peers.list[0]->name);
|
||||
TEST_ASSERT_EQUAL_UINT8(3, st.peers.list[0]->status); /* NOT forced to ok */
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlB", st.peers.list[1]->name);
|
||||
TEST_ASSERT_EQUAL_UINT8(1, st.peers.list[1]->status); /* genuine match */
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlC", st.peers.list[2]->name);
|
||||
TEST_ASSERT_EQUAL_UINT8(3, st.peers.list[2]->status);
|
||||
|
||||
ctrlstatus_free_fields(&st);
|
||||
repl_fini(&c.persist.repl);
|
||||
cs_teardown_cluster(&c);
|
||||
printf("test_ctrlstatus_peer_status_self_mismatch_falls_through: PASS\n");
|
||||
}
|
||||
|
||||
/* ── suite entry point ─────────────────────────────────────── */
|
||||
|
||||
void test_ctrlstatus(void)
|
||||
|
|
@ -803,4 +1008,7 @@ void test_ctrlstatus(void)
|
|||
RUN_TEST(test_heartbeatmsg_electedatms_roundtrip);
|
||||
RUN_TEST(test_ctrlstatus_primary_reports_own_elected_at_ms);
|
||||
RUN_TEST(test_ctrlstatus_standby_reports_learned_elected_at_ms);
|
||||
RUN_TEST(test_ctrlstatus_peer_status_primary_sourced_from_registry);
|
||||
RUN_TEST(test_ctrlstatus_peer_status_standby_unknown_except_self);
|
||||
RUN_TEST(test_ctrlstatus_peer_status_self_mismatch_falls_through);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue