feat(ha-single-leader-role): delete force-promotion watchdog + convergence/ack machinery (task 4.1)

This commit is contained in:
Rongsong Shen 2026-06-13 12:05:08 +00:00
parent b7b951b9e7
commit a5bf86112a
10 changed files with 95 additions and 1869 deletions

View file

@ -50,6 +50,14 @@ static int stub_phase2(void *c) { (void)c; g_phase2_calls++; return 0;
static int stub_push_config(void *c) { (void)c; g_push_config_calls++; return 0; }
static int stub_ctrlhello(void *c) { (void)c; g_ctrlhello_calls++; return 0; }
/* ha-single-leader-role task 4.1: the replication-ack handler was deleted (the
* ack-counting force-promotion machinery is gone — promotion is driven solely
* by the heartbeat decision loop + per-entry majority ack). No shim is added
* (design §"Transitional-State Deletions": no replacements, no shims). The
* ack-driven-promotion cases were deleted; the surviving state-entry cases no
* longer call it (production ha_elect_evaluate_and_signal now lands the winner
* directly per task 5.1 territory). */
/* ── Shared HA config ───────────────────────────────────────────────────── */
static ha_config_t g_cfg = {
@ -196,50 +204,11 @@ static void test_rdhup_full_path_reaches_committing(void)
}
/* ═══════════════════════════════════════════════════════════════════════════
* Test 2: RDHUP → COMMITTING → PRIMARY via ha_repl_ack_received
*
* Req 8.2: majority ack in COMMITTING → CTRL_PRIMARY
* Req 6.5: promoted standby serves traffic (dispatch gate opens)
* Test 2 (RDHUP → PRIMARY via replication-ack handler) DELETED — ha-single-leader-role
* task 4.1. Subject was the deleted ack-driven force-promotion machinery
* (the replication-ack handler); nothing left to verify. No shim, no retarget
* (design §"Transitional-State Deletions").
* ═════════════════════════════════════════════════════════════════════════ */
static void test_rdhup_committing_to_primary_on_ack(void)
{
install_stubs();
cluster_t c;
myconf_t myconf;
make_cluster(&c, &myconf, "ctrl-a");
TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &g_cfg, 0));
ha_do_demote(&c.ha);
c.ha.phase2_done = 1;
c.ha.n_peers = 1; /* 2-node cluster: quorum=1, threshold=0 */
g_rdhup_state.c = &c;
ha_rdhup_election_hook = rdhup_election_drives_evaluate;
int sv[2];
TEST_ASSERT_EQUAL_INT(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sv));
c.ha.primary_ctrl_fd = sv[0];
/* Fire RDHUP → should reach CTRL_PRIMARY_COMMITTING */
ha_on_primary_ctrl_fd_rdhup(&c.ha, &c);
close(sv[1]);
int role = (int)atomic_load_explicit(&c.ha.role, memory_order_acquire);
TEST_ASSERT_EQUAL_INT((int)CTRL_PRIMARY_COMMITTING, role);
/* n_peers=1 → quorum=(1+2)/2=1 → threshold=0 → first ack promotes.
* ha_repl_ack_received increments count; acks(1) >= quorum-1(0) → promote. */
ha_repl_ack_received(&c.ha, &c);
role = (int)atomic_load_explicit(&c.ha.role, memory_order_acquire);
TEST_ASSERT_EQUAL_INT_MESSAGE((int)CTRL_PRIMARY, role,
"ha_repl_ack_received must drive COMMITTING → CTRL_PRIMARY on quorum ack (Req 8.2)");
clear_stubs();
ha_fini(&c.ha);
printf("test_rdhup_committing_to_primary_on_ack: PASS (role=%d==CTRL_PRIMARY)\n", role);
}
/* ═══════════════════════════════════════════════════════════════════════════
* Test 3: RDHUP in single-node cluster → immediate PRIMARY (no COMMITTING)
@ -273,15 +242,11 @@ static void test_rdhup_single_node_reaches_primary(void)
ha_on_primary_ctrl_fd_rdhup(&c.ha, &c);
close(sv[1]);
/* n_peers=0 → quorum=(0+2)/2=1 → threshold=0 → acks(0) >= 0 → promoted inline */
/* n_peers=0 → ha_elect_evaluate_and_signal single-node shortcircuit lands
* CTRL_PRIMARY directly (no transitional COMMITTING, no ack machinery —
* ha-single-leader-role). The old replication-ack fallback was excised
* (function deleted task 4.1); the PRIMARY assertion stands on its own. */
int role = (int)atomic_load_explicit(&c.ha.role, memory_order_acquire);
/* Single-node: no standbys to ack, committing_timeout degrades to PRIMARY.
* The committing timeout is 10s; for single-node we call ha_repl_ack_received
* once to simulate the degraded-mode zero-threshold promotion (threshold=0). */
if (role == (int)CTRL_PRIMARY_COMMITTING) {
ha_repl_ack_received(&c.ha, &c);
role = (int)atomic_load_explicit(&c.ha.role, memory_order_acquire);
}
TEST_ASSERT_EQUAL_INT_MESSAGE((int)CTRL_PRIMARY, role,
"single-node RDHUP path must reach CTRL_PRIMARY (Req 9.1)");
@ -291,52 +256,12 @@ static void test_rdhup_single_node_reaches_primary(void)
}
/* ═══════════════════════════════════════════════════════════════════════════
* Test 4: 3-node cluster RDHUP — two acks needed, premature ack stays COMMITTING
*
* Req 8.2: acks < quorum-1 → stays COMMITTING; acks >= quorum-1 → PRIMARY
* 3-node (n_peers=2): quorum=2, threshold=1 → need 1 ack to promote
* Test 4 (3-node RDHUP — ack-driven promotion) DELETED — ha-single-leader-role
* task 4.1. Subject was the deleted ack-counting promotion machinery
* (the replication-ack handler drives COMMITTING → PRIMARY on threshold acks);
* the mechanism is gone, nothing left to verify. No shim (design §"Transitional-
* State Deletions").
* ═════════════════════════════════════════════════════════════════════════ */
static void test_rdhup_3node_one_ack_promotes(void)
{
install_stubs();
cluster_t c;
myconf_t myconf;
make_cluster(&c, &myconf, "ctrl-a");
myconf.peers_cfg.candidates_count = 3;
snprintf(myconf.peers_cfg.candidates[2].name,
sizeof(myconf.peers_cfg.candidates[2].name), "ctrl-c");
myconf.peers_cfg.candidates[2].port = 7003;
TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &g_cfg, 0));
ha_do_demote(&c.ha);
c.ha.phase2_done = 1;
c.ha.n_peers = 2; /* 3-node: quorum=2, threshold=1 */
g_rdhup_state.c = &c;
ha_rdhup_election_hook = rdhup_election_drives_evaluate;
int sv[2];
TEST_ASSERT_EQUAL_INT(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sv));
c.ha.primary_ctrl_fd = sv[0];
ha_on_primary_ctrl_fd_rdhup(&c.ha, &c);
close(sv[1]);
int role = (int)atomic_load_explicit(&c.ha.role, memory_order_acquire);
TEST_ASSERT_EQUAL_INT_MESSAGE((int)CTRL_PRIMARY_COMMITTING, role,
"3-node RDHUP must enter CTRL_PRIMARY_COMMITTING before acks");
/* First ack: count=1 >= threshold=1 → promotes */
ha_repl_ack_received(&c.ha, &c);
role = (int)atomic_load_explicit(&c.ha.role, memory_order_acquire);
TEST_ASSERT_EQUAL_INT_MESSAGE((int)CTRL_PRIMARY, role,
"3-node: first ack (count=1 >= threshold=1) must promote to CTRL_PRIMARY (Req 8.2)");
clear_stubs();
ha_fini(&c.ha);
printf("test_rdhup_3node_one_ack_promotes: PASS (role=%d==CTRL_PRIMARY)\n", role);
}
/* ═══════════════════════════════════════════════════════════════════════════
* Test 5: config push and ctrlhello hooks fire during COMMITTING entry
@ -422,9 +347,10 @@ static void test_rdhup_primary_dispatch_gate_opens(void)
ha_on_primary_ctrl_fd_rdhup(&c.ha, &c);
close(sv[1]);
/* Drive to CTRL_PRIMARY via ack */
ha_repl_ack_received(&c.ha, &c);
/* ha-single-leader-role: ha_elect_evaluate_and_signal lands the winner in
* CTRL_PRIMARY directly (no transitional COMMITTING, no ack machinery). The
* old replication-ack drive-step was excised (function deleted task 4.1);
* the dispatch-gate assertion below is the subject of this case. */
int role = (int)atomic_load_explicit(&c.ha.role, memory_order_acquire);
TEST_ASSERT_EQUAL_INT((int)CTRL_PRIMARY, role);
@ -450,9 +376,11 @@ static void test_rdhup_primary_dispatch_gate_opens(void)
void test_ha_rdhup_integration(void)
{
RUN_TEST(test_rdhup_full_path_reaches_committing);
RUN_TEST(test_rdhup_committing_to_primary_on_ack);
/* test_rdhup_committing_to_primary_on_ack deleted (ha-single-leader-role 4.1:
* ack-driven promotion machinery removed, no shim) */
RUN_TEST(test_rdhup_single_node_reaches_primary);
RUN_TEST(test_rdhup_3node_one_ack_promotes);
/* test_rdhup_3node_one_ack_promotes deleted (ha-single-leader-role 4.1:
* ack-counting promotion machinery removed, no shim) */
RUN_TEST(test_rdhup_committing_fires_replication_hooks);
RUN_TEST(test_rdhup_primary_dispatch_gate_opens);
}