feat(ha-term-safety): drive raft_log_append on primary append path (task 3.1)
This commit is contained in:
parent
3dd8493fff
commit
6a519a928b
5 changed files with 299 additions and 11 deletions
|
|
@ -961,6 +961,147 @@ static void test_init_empty_wal_zero_floor(void)
|
|||
printf("test_init_empty_wal_zero_floor: PASS\n");
|
||||
}
|
||||
|
||||
/* ================================================================== */
|
||||
/* Task 3.1 — drive raft_log_append on the primary append path */
|
||||
/* ================================================================== */
|
||||
|
||||
/* Bring a make_cluster()'d node up as the raft LEADER so raft_log_append is
|
||||
* enabled (it returns -EPERM off the leader). We init the library from the
|
||||
* recovered floor (task 2.5) and set the live election term + raft role
|
||||
* directly — modelling a node that has already won an election at `term`
|
||||
* (the cerebrum raft-sim convention: set state.term after a clean election;
|
||||
* raft_log_append stamps entries from s->term). */
|
||||
static void make_leader(cluster_t* c, uint64_t term, int n_peers)
|
||||
{
|
||||
atomic_store(&c->ha.role, CTRL_PRIMARY);
|
||||
atomic_store(&c->ha.term, term);
|
||||
ha_init_raft(&c->ha, c, n_peers);
|
||||
c->ha.raft.term = term;
|
||||
c->ha.raft.role = RAFT_LEADER;
|
||||
}
|
||||
|
||||
/* Test: a primary job-state append driven through ha_primary_log_append goes
|
||||
* through the library — the library assigns the LSN (== s->log_len post-append),
|
||||
* stamps the live election term into the WAL header, and keeps the persist
|
||||
* write cursor co-tracked with the library index (current_lsn == log_len + 1).
|
||||
* Against the pre-3.1 hand-rolled path this entry point does not exist
|
||||
* (compile RED) — and once it exists the library MUST be the LSN/term source
|
||||
* (R1.1, R1.2, R2.1). */
|
||||
static void test_primary_log_append_stamps_term_and_assigns_lsn(void)
|
||||
{
|
||||
char tmp[256];
|
||||
strcpy(tmp, "/tmp/xce_ts_plap_XXXXXX");
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(tmp));
|
||||
|
||||
const uint64_t T = 7;
|
||||
|
||||
cluster_t* c = make_cluster(tmp);
|
||||
TEST_ASSERT_NOT_NULL(c);
|
||||
make_leader(c, T, /*n_peers*/1);
|
||||
|
||||
/* Fresh leader, empty log: first append takes library index 1. */
|
||||
TEST_ASSERT_EQUAL_UINT64(0, c->ha.raft.log_len);
|
||||
|
||||
wal_jobstate_t js = { .jobid = 42, .state = 3 };
|
||||
uint64_t lsn = 0;
|
||||
TEST_ASSERT_EQUAL_INT(0,
|
||||
ha_primary_log_append(c, WAL_JOB_UPDATE, &js, sizeof(js), &lsn));
|
||||
|
||||
/* (a) The LIBRARY assigned the LSN and grew its log to it. */
|
||||
TEST_ASSERT_EQUAL_UINT64(1, lsn);
|
||||
TEST_ASSERT_EQUAL_UINT64(1, c->ha.raft.log_len);
|
||||
|
||||
/* (b) The WAL header recorded the live election term for that LSN. */
|
||||
TEST_ASSERT_EQUAL_UINT64(T, persist_entry_term(&c->persist, 1));
|
||||
|
||||
/* (d) Co-tracking invariant: the persist write cursor advanced exactly in
|
||||
* lockstep with the library index — current_lsn == log_len + 1 (next LSN). */
|
||||
TEST_ASSERT_EQUAL_UINT64(c->ha.raft.log_len + 1, c->persist.current_lsn);
|
||||
|
||||
/* A second append keeps the two counters co-tracked. */
|
||||
js.jobid = 43;
|
||||
TEST_ASSERT_EQUAL_INT(0,
|
||||
ha_primary_log_append(c, WAL_JOB_UPDATE, &js, sizeof(js), &lsn));
|
||||
TEST_ASSERT_EQUAL_UINT64(2, lsn);
|
||||
TEST_ASSERT_EQUAL_UINT64(2, c->ha.raft.log_len);
|
||||
TEST_ASSERT_EQUAL_UINT64(T, persist_entry_term(&c->persist, 2));
|
||||
TEST_ASSERT_EQUAL_UINT64(c->ha.raft.log_len + 1, c->persist.current_lsn);
|
||||
|
||||
teardown_cluster(c);
|
||||
printf("test_primary_log_append_stamps_term_and_assigns_lsn: PASS\n");
|
||||
}
|
||||
|
||||
/* Test: the own-term promotion no-op (ha_arm_own_term_gate) is routed through
|
||||
* the library, so the WAL_NOOP entry records the newly established term (not 0)
|
||||
* and the library index tracks it (R1.2). Pre-3.1 the noop went through a
|
||||
* hand-rolled persist_wal_async with a best-effort p->term and did NOT advance
|
||||
* s->log_len. */
|
||||
static void test_own_term_noop_records_live_term_via_library(void)
|
||||
{
|
||||
char tmp[256];
|
||||
strcpy(tmp, "/tmp/xce_ts_noop_XXXXXX");
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(tmp));
|
||||
|
||||
const uint64_t T = 5;
|
||||
|
||||
cluster_t* c = make_cluster(tmp);
|
||||
TEST_ASSERT_NOT_NULL(c);
|
||||
make_leader(c, T, /*n_peers*/1);
|
||||
|
||||
/* Register the persist hooks then arm the own-term gate exactly as the
|
||||
* promotion tail does (ha_promote_to_primary / raft_cb_become_leader). */
|
||||
ha_arm_own_term_gate_for_test(c);
|
||||
TEST_ASSERT_EQUAL_INT(0, persist_flush(&c->persist));
|
||||
|
||||
/* The no-op landed at library index 1 with the live term recorded. */
|
||||
TEST_ASSERT_EQUAL_UINT64(1, c->ha.raft.log_len);
|
||||
TEST_ASSERT_EQUAL_UINT64(T, persist_entry_term(&c->persist, 1));
|
||||
TEST_ASSERT_EQUAL_UINT64(c->ha.raft.log_len + 1, c->persist.current_lsn);
|
||||
|
||||
teardown_cluster(c);
|
||||
printf("test_own_term_noop_records_live_term_via_library: PASS\n");
|
||||
}
|
||||
|
||||
/* Test: when raft leadership is NOT active (the library is not RAFT_LEADER —
|
||||
* the init-time non-raft promotion road and engine-less test contexts),
|
||||
* ha_primary_log_append still appends locally through the legacy path so the
|
||||
* non-raft promotion tail keeps working, WITHOUT growing the library log (the
|
||||
* library is not tracking the log on that road, so no co-tracking hazard).
|
||||
* The entry still records the live election term via persist_set_term. */
|
||||
static void test_primary_log_append_non_leader_legacy_fallback(void)
|
||||
{
|
||||
char tmp[256];
|
||||
strcpy(tmp, "/tmp/xce_ts_plapfb_XXXXXX");
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(tmp));
|
||||
|
||||
const uint64_t T = 6;
|
||||
|
||||
cluster_t* c = make_cluster(tmp);
|
||||
TEST_ASSERT_NOT_NULL(c);
|
||||
/* Non-raft promotion road: controller role PRIMARY + live term, but the
|
||||
* raft library was never driven to LEADER (raft.role stays FOLLOWER). */
|
||||
atomic_store(&c->ha.role, CTRL_PRIMARY);
|
||||
atomic_store(&c->ha.term, T);
|
||||
ha_init_raft(&c->ha, c, /*n_peers*/1);
|
||||
TEST_ASSERT_NOT_EQUAL_INT(RAFT_LEADER, c->ha.raft.role);
|
||||
|
||||
wal_jobstate_t js = { .jobid = 7, .state = 1 };
|
||||
uint64_t lsn = 0;
|
||||
TEST_ASSERT_EQUAL_INT(0,
|
||||
ha_primary_log_append(c, WAL_JOB_UPDATE, &js, sizeof(js), &lsn));
|
||||
|
||||
/* Appended at the contiguous next LSN (current_lsn started at 1). */
|
||||
TEST_ASSERT_EQUAL_UINT64(1, lsn);
|
||||
TEST_ASSERT_EQUAL_UINT64(2, c->persist.current_lsn);
|
||||
/* Live term still recorded for the entry. */
|
||||
TEST_ASSERT_EQUAL_UINT64(T, persist_entry_term(&c->persist, 1));
|
||||
/* The library log was NOT grown by the legacy fallback (no raft leadership). */
|
||||
TEST_ASSERT_EQUAL_UINT64(0, c->ha.raft.log_len);
|
||||
|
||||
teardown_cluster(c);
|
||||
printf("test_primary_log_append_non_leader_legacy_fallback: PASS\n");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Suite entry point */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
@ -987,4 +1128,7 @@ void test_term_safety_adapter(void)
|
|||
RUN_TEST(test_on_commit_advanced_wired_feeds_barrier);
|
||||
RUN_TEST(test_init_restores_recovered_floor);
|
||||
RUN_TEST(test_init_empty_wal_zero_floor);
|
||||
RUN_TEST(test_primary_log_append_stamps_term_and_assigns_lsn);
|
||||
RUN_TEST(test_own_term_noop_records_live_term_via_library);
|
||||
RUN_TEST(test_primary_log_append_non_leader_legacy_fallback);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue