feat(controller-status): handle_ctrlstatus read-only state snapshot + dispatch + configChecksum as Text hex (task 2.1)
This commit is contained in:
parent
97a501e96e
commit
a6e03d87ab
9 changed files with 502 additions and 3 deletions
|
|
@ -60,6 +60,7 @@ extern void test_demotion_convergence(void);
|
|||
extern void test_convergence_verification(void);
|
||||
extern void test_coldstart_lsn0(void);
|
||||
extern void test_commit_durability(void);
|
||||
extern void test_ctrlstatus(void);
|
||||
|
||||
void setUp(void) { return; }
|
||||
|
||||
|
|
@ -189,6 +190,7 @@ int main(int argc, char* argv[])
|
|||
TEST(test_convergence_verification);
|
||||
TEST(test_coldstart_lsn0);
|
||||
TEST(test_commit_durability);
|
||||
TEST(test_ctrlstatus);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
|
|||
292
src/controller/testsuite/test_ctrlstatus.c
Normal file
292
src/controller/testsuite/test_ctrlstatus.c
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
/* test_ctrlstatus.c — unit tests for the controller status handler
|
||||
* (controller-status spec, task 2.1).
|
||||
*
|
||||
* Exercises ctrlstatus_fill(), the pure local-state snapshot used by
|
||||
* handle_ctrlstatus(). Splitting the snapshot from the socket send (mirroring
|
||||
* ha_nop_reply_state_report) makes the field sourcing unit-testable without an
|
||||
* endpoint. Covers Requirements 1.2, 1.3, 2.1, 2.2, 2.3, 3.1, 3.2, 4.1, 4.2,
|
||||
* 5.1, 5.2, 5.3, 6.1, 6.2, 6.3, 6.4.
|
||||
*/
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "ha.h"
|
||||
#include "include/cluster.h"
|
||||
#include "myconf.h"
|
||||
#include "persist.h"
|
||||
#include "xce/cluster_def.h"
|
||||
|
||||
/* ── helpers ────────────────────────────────────────────────── */
|
||||
|
||||
static ha_config_t cs_valid_cfg(void)
|
||||
{
|
||||
ha_config_t cfg = {
|
||||
.heartbeat_interval_ms = 3000,
|
||||
.heartbeat_timeout_ms = 10000,
|
||||
.election_timeout_ms = 2000,
|
||||
.probe_connect_timeout_ms = 500,
|
||||
.max_promotion_retries = 5,
|
||||
};
|
||||
return cfg;
|
||||
}
|
||||
|
||||
/* Build a minimal cluster_t whose ha/myconf fields the handler reads.
|
||||
* Only the fields ctrlstatus_fill() touches are initialised. */
|
||||
static void cs_setup_cluster(cluster_t* c, myconf_t* mc)
|
||||
{
|
||||
memset(c, 0, sizeof(*c));
|
||||
memset(mc, 0, sizeof(*mc));
|
||||
|
||||
ha_config_t cfg = cs_valid_cfg();
|
||||
TEST_ASSERT_EQUAL_INT(0, ha_init(&c->ha, &cfg, 0));
|
||||
|
||||
c->myconf = mc;
|
||||
mc->name = "ctrlA";
|
||||
mc->cluster_config = NULL; /* cluster name read from peers/own identity */
|
||||
|
||||
/* own identity (xce_peerinfo_t.name is a char*, not a buffer) */
|
||||
c->myhost_info.name = "ctrlA";
|
||||
c->myhost = &c->myhost_info;
|
||||
|
||||
/* loaded config version + checksum */
|
||||
mc->config_version_tag = 0xAABBCCDDEE001122ULL;
|
||||
for (int i = 0; i < 32; i++)
|
||||
mc->config_sha256[i] = (uint8_t)(i + 1);
|
||||
|
||||
/* configured controllers (peers) */
|
||||
mc->peers_cfg.candidates_count = 3;
|
||||
strncpy(mc->peers_cfg.candidates[0].name, "ctrlA",
|
||||
sizeof(mc->peers_cfg.candidates[0].name) - 1);
|
||||
strncpy(mc->peers_cfg.candidates[0].hostname, "10.0.0.1",
|
||||
sizeof(mc->peers_cfg.candidates[0].hostname) - 1);
|
||||
mc->peers_cfg.candidates[0].port = 7001;
|
||||
strncpy(mc->peers_cfg.candidates[1].name, "ctrlB",
|
||||
sizeof(mc->peers_cfg.candidates[1].name) - 1);
|
||||
strncpy(mc->peers_cfg.candidates[1].hostname, "10.0.0.2",
|
||||
sizeof(mc->peers_cfg.candidates[1].hostname) - 1);
|
||||
mc->peers_cfg.candidates[1].port = 7002;
|
||||
strncpy(mc->peers_cfg.candidates[2].name, "ctrlC",
|
||||
sizeof(mc->peers_cfg.candidates[2].name) - 1);
|
||||
strncpy(mc->peers_cfg.candidates[2].hostname, "10.0.0.3",
|
||||
sizeof(mc->peers_cfg.candidates[2].hostname) - 1);
|
||||
mc->peers_cfg.candidates[2].port = 7003;
|
||||
}
|
||||
|
||||
static void cs_teardown_cluster(cluster_t* c)
|
||||
{
|
||||
ha_fini(&c->ha);
|
||||
}
|
||||
|
||||
/* ── tests ──────────────────────────────────────────────────── */
|
||||
|
||||
/* (a) PRIMARY: responderRole==PRIMARY, primaryName==self, peers/version
|
||||
* populated; committed/applied/term reflect the atomics. (Req 2.1, 3.1, 3.2,
|
||||
* 4.1, 4.2, 5.1, 6.1, 6.2, 6.4) */
|
||||
static void test_ctrlstatus_primary(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);
|
||||
atomic_store_explicit(&c.ha.term, 7, memory_order_release);
|
||||
atomic_store_explicit(&c.ha.applied_lsn, 42, memory_order_release);
|
||||
atomic_store_explicit(&c.ha.committed_config_tag, 0xAABBCCDDEE001122ULL,
|
||||
memory_order_release);
|
||||
atomic_store_explicit(&c.persist.committed_lsn, 41, memory_order_release);
|
||||
/* A primary knows itself as the primary */
|
||||
strncpy(c.ha.primary_host, "ctrlA", sizeof(c.ha.primary_host) - 1);
|
||||
c.ha.primary_port = 7001;
|
||||
|
||||
xce_ctrlstatus_t st;
|
||||
ctrlstatus_fill(&c, &st);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8((uint8_t)CTRL_PRIMARY, st.responderRole);
|
||||
TEST_ASSERT_NOT_NULL(st.responderName);
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlA", st.responderName);
|
||||
TEST_ASSERT_NOT_NULL(st.primaryName);
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlA", st.primaryName);
|
||||
TEST_ASSERT_EQUAL_UINT64(7, st.term);
|
||||
TEST_ASSERT_EQUAL_UINT64(42, st.appliedLsn);
|
||||
TEST_ASSERT_EQUAL_UINT64(41, st.committedLsn);
|
||||
TEST_ASSERT_EQUAL_UINT64(0xAABBCCDDEE001122ULL, st.configVersion);
|
||||
TEST_ASSERT_EQUAL_UINT64(0xAABBCCDDEE001122ULL, st.committedConfigVersion);
|
||||
/* configChecksum: 64-char lowercase-hex of config_sha256[i]=(i+1), i.e.
|
||||
* bytes 0x01..0x20. (Req 5.2 content checksum, 5.3 divergence detection) */
|
||||
TEST_ASSERT_NOT_NULL(st.configChecksum);
|
||||
TEST_ASSERT_EQUAL_UINT(64u, (unsigned)strlen(st.configChecksum));
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
|
||||
st.configChecksum);
|
||||
TEST_ASSERT_EQUAL_UINT32(3, st.peerCount);
|
||||
TEST_ASSERT_EQUAL_INT(3, st.peers.count);
|
||||
TEST_ASSERT_NOT_NULL(st.peers.list);
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlB", st.peers.list[1]->name);
|
||||
TEST_ASSERT_EQUAL_STRING("10.0.0.2", st.peers.list[1]->host);
|
||||
TEST_ASSERT_EQUAL_UINT32(7002, st.peers.list[1]->port);
|
||||
TEST_ASSERT_EQUAL_UINT(0, st.fenced);
|
||||
|
||||
ctrlstatus_free_fields(&st);
|
||||
cs_teardown_cluster(&c);
|
||||
printf("test_ctrlstatus_primary: PASS\n");
|
||||
}
|
||||
|
||||
/* (b) STANDBY with known primary: reports that primary, role==STANDBY.
|
||||
* (Req 2.2, 3.1, 3.2) */
|
||||
static void test_ctrlstatus_standby_known_primary(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);
|
||||
strncpy(c.ha.primary_host, "ctrlB", sizeof(c.ha.primary_host) - 1);
|
||||
c.ha.primary_port = 7002;
|
||||
|
||||
/* All-0xFF digest → 64 'f' chars: a distinct content checksum from the
|
||||
* primary case proves Req 5.3 — same version tag but differing config bytes
|
||||
* produce a differing hex; identical bytes would yield identical hex. */
|
||||
for (int i = 0; i < 32; i++)
|
||||
mc.config_sha256[i] = 0xFFu;
|
||||
|
||||
xce_ctrlstatus_t st;
|
||||
ctrlstatus_fill(&c, &st);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8((uint8_t)CTRL_STANDBY, st.responderRole);
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlA", st.responderName);
|
||||
TEST_ASSERT_NOT_NULL(st.primaryName);
|
||||
TEST_ASSERT_EQUAL_STRING("ctrlB", st.primaryName);
|
||||
TEST_ASSERT_NOT_NULL(st.configChecksum);
|
||||
TEST_ASSERT_EQUAL_UINT(64u, (unsigned)strlen(st.configChecksum));
|
||||
TEST_ASSERT_EQUAL_STRING(
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
st.configChecksum);
|
||||
|
||||
ctrlstatus_free_fields(&st);
|
||||
cs_teardown_cluster(&c);
|
||||
printf("test_ctrlstatus_standby_known_primary: PASS\n");
|
||||
}
|
||||
|
||||
/* (c) No known primary → empty primaryName (client renders "unknown").
|
||||
* (Req 1.3, 2.3) */
|
||||
static void test_ctrlstatus_unknown_primary(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);
|
||||
/* primary_host left empty by ha_init → unknown */
|
||||
|
||||
xce_ctrlstatus_t st;
|
||||
ctrlstatus_fill(&c, &st);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8((uint8_t)CTRL_STANDBY, st.responderRole);
|
||||
TEST_ASSERT_NOT_NULL(st.primaryName);
|
||||
TEST_ASSERT_EQUAL_STRING("", st.primaryName); /* empty ⇒ "unknown" */
|
||||
|
||||
ctrlstatus_free_fields(&st);
|
||||
cs_teardown_cluster(&c);
|
||||
printf("test_ctrlstatus_unknown_primary: PASS\n");
|
||||
}
|
||||
|
||||
/* (d) fenced reflects in_catch_up. (Req 6.3) */
|
||||
static void test_ctrlstatus_fenced_when_in_catch_up(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);
|
||||
atomic_store_explicit(&c.ha.in_catch_up, 1, memory_order_release);
|
||||
|
||||
xce_ctrlstatus_t st;
|
||||
ctrlstatus_fill(&c, &st);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT(1u, st.fenced ? 1u : 0u);
|
||||
|
||||
ctrlstatus_free_fields(&st);
|
||||
cs_teardown_cluster(&c);
|
||||
printf("test_ctrlstatus_fenced_when_in_catch_up: PASS\n");
|
||||
}
|
||||
|
||||
/* (e) No-mutation: ctrlstatus_fill() must not change any HA/config state.
|
||||
* Snapshot the relevant fields, call fill, assert they are unchanged. (Req 1.2)
|
||||
*/
|
||||
static void test_ctrlstatus_no_mutation(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);
|
||||
atomic_store_explicit(&c.ha.term, 9, memory_order_release);
|
||||
atomic_store_explicit(&c.ha.applied_lsn, 100, memory_order_release);
|
||||
atomic_store_explicit(&c.persist.committed_lsn, 99, memory_order_release);
|
||||
atomic_store_explicit(&c.ha.committed_config_tag, 0x1234ULL,
|
||||
memory_order_release);
|
||||
atomic_store_explicit(&c.ha.in_catch_up, 0, memory_order_release);
|
||||
strncpy(c.ha.primary_host, "ctrlA", sizeof(c.ha.primary_host) - 1);
|
||||
c.ha.primary_port = 7001;
|
||||
|
||||
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,
|
||||
memory_order_acquire);
|
||||
uint64_t committed0 = atomic_load_explicit(&c.persist.committed_lsn,
|
||||
memory_order_acquire);
|
||||
uint64_t cfgtag0 = atomic_load_explicit(&c.ha.committed_config_tag,
|
||||
memory_order_acquire);
|
||||
int catchup0 = atomic_load_explicit(&c.ha.in_catch_up,
|
||||
memory_order_acquire);
|
||||
char primary0[XCE_MAXNAME_LEN];
|
||||
strncpy(primary0, c.ha.primary_host, sizeof(primary0));
|
||||
uint32_t port0 = c.ha.primary_port;
|
||||
uint64_t vertag0 = mc.config_version_tag;
|
||||
|
||||
xce_ctrlstatus_t st;
|
||||
ctrlstatus_fill(&c, &st);
|
||||
ctrlstatus_free_fields(&st);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(role0, atomic_load_explicit(&c.ha.role,
|
||||
memory_order_acquire));
|
||||
TEST_ASSERT_EQUAL_UINT64(term0, atomic_load_explicit(&c.ha.term,
|
||||
memory_order_acquire));
|
||||
TEST_ASSERT_EQUAL_UINT64(applied0,
|
||||
atomic_load_explicit(&c.ha.applied_lsn,
|
||||
memory_order_acquire));
|
||||
TEST_ASSERT_EQUAL_UINT64(committed0,
|
||||
atomic_load_explicit(&c.persist.committed_lsn,
|
||||
memory_order_acquire));
|
||||
TEST_ASSERT_EQUAL_UINT64(cfgtag0,
|
||||
atomic_load_explicit(&c.ha.committed_config_tag,
|
||||
memory_order_acquire));
|
||||
TEST_ASSERT_EQUAL_INT(catchup0,
|
||||
atomic_load_explicit(&c.ha.in_catch_up,
|
||||
memory_order_acquire));
|
||||
TEST_ASSERT_EQUAL_STRING(primary0, c.ha.primary_host);
|
||||
TEST_ASSERT_EQUAL_UINT32(port0, c.ha.primary_port);
|
||||
TEST_ASSERT_EQUAL_UINT64(vertag0, mc.config_version_tag);
|
||||
|
||||
cs_teardown_cluster(&c);
|
||||
printf("test_ctrlstatus_no_mutation: PASS\n");
|
||||
}
|
||||
|
||||
/* ── suite entry point ─────────────────────────────────────── */
|
||||
|
||||
void test_ctrlstatus(void)
|
||||
{
|
||||
RUN_TEST(test_ctrlstatus_primary);
|
||||
RUN_TEST(test_ctrlstatus_standby_known_primary);
|
||||
RUN_TEST(test_ctrlstatus_unknown_primary);
|
||||
RUN_TEST(test_ctrlstatus_fenced_when_in_catch_up);
|
||||
RUN_TEST(test_ctrlstatus_no_mutation);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue