forked from lei.chen/myxce
Captures all uncommitted work from ha-config-bootstrap, ha-heartbeat-liveness, and associated spec/tooling updates: - src: ha.c, ha.h, cluster.c, config_repl.c, myapp.c, raft.c — heartbeat liveness, RDHUP integration, committing phase fixes - tests: test_ha_role.c, test_config_repl.c, test_ha_rdhup_integration.c, test_p1_integration.c, test_p3_hardening.c, scheduler test - specs: election-iomgr-refactor, ha-heartbeat-liveness, ha-peer-connection-reuse, raft-library (new); ha-config-bootstrap and election-iomgr-refactor task/research updates - tooling: .wolf/ state, ha/data cluster fixtures, raft/raft.tla, src/build symlink, xce-build skill update
129 lines
4.3 KiB
C
129 lines
4.3 KiB
C
/* P1-7: HA Foundation integration tests — assembled system */
|
|
#include <stdatomic.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <unity/unity.h>
|
|
|
|
#include "ha.h"
|
|
|
|
/* Test 1: Single-node startup: after ha_init + ha_do_promote(priority=0),
|
|
* role must be PRIMARY */
|
|
static void test_single_node_starts_as_primary(void)
|
|
{
|
|
ha_state_t ha;
|
|
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,
|
|
};
|
|
ha_init(&ha, &cfg, 0 /* priority 0 = highest */);
|
|
/* Simulate am_i_primary() decision for priority-0 node */
|
|
ha_do_promote(&ha);
|
|
TEST_ASSERT_EQUAL_INT(CTRL_PRIMARY,
|
|
(int)atomic_load_explicit(&ha.role, memory_order_acquire));
|
|
ha_fini(&ha);
|
|
printf("test_single_node_starts_as_primary: PASS\n");
|
|
}
|
|
|
|
/* Test 2: Term fencing — promote increments term; second promote increments again */
|
|
static void test_term_fencing_monotonic(void)
|
|
{
|
|
ha_state_t ha;
|
|
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,
|
|
};
|
|
ha_init(&ha, &cfg, 0);
|
|
uint64_t t0 = atomic_load_explicit(&ha.term, memory_order_acquire);
|
|
ha_do_promote(&ha);
|
|
uint64_t t1 = atomic_load_explicit(&ha.term, memory_order_acquire);
|
|
ha_do_demote(&ha);
|
|
ha_do_promote(&ha);
|
|
uint64_t t2 = atomic_load_explicit(&ha.term, memory_order_acquire);
|
|
TEST_ASSERT_TRUE(t1 > t0);
|
|
TEST_ASSERT_TRUE(t2 > t1);
|
|
ha_fini(&ha);
|
|
printf("test_term_fencing_monotonic: PASS\n");
|
|
}
|
|
|
|
|
|
/* Test 4: Redirect guard — primary never redirects; standby with known
|
|
* primary redirects; standby with no known primary reports nomaster */
|
|
static void test_redirect_guard_integration(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,
|
|
};
|
|
|
|
/* Primary: no redirect */
|
|
ha_state_t ha_primary;
|
|
ha_init(&ha_primary, &cfg, 0);
|
|
atomic_store_explicit(&ha_primary.role, (int32_t)CTRL_PRIMARY,
|
|
memory_order_release);
|
|
TEST_ASSERT_EQUAL_INT(0, ha_should_redirect(&ha_primary));
|
|
|
|
/* Standby with known primary: redirect */
|
|
ha_state_t ha_standby;
|
|
ha_init(&ha_standby, &cfg, 1);
|
|
strncpy(ha_standby.primary_host, "10.0.0.1",
|
|
sizeof(ha_standby.primary_host) - 1);
|
|
ha_standby.primary_port = 7000;
|
|
TEST_ASSERT_EQUAL_INT(1, ha_should_redirect(&ha_standby));
|
|
TEST_ASSERT_EQUAL_INT(0, ha_no_primary_known(&ha_standby));
|
|
|
|
/* Standby with no known primary: no redirect, nomaster */
|
|
ha_state_t ha_cold;
|
|
ha_init(&ha_cold, &cfg, 2);
|
|
TEST_ASSERT_EQUAL_INT(0, ha_should_redirect(&ha_cold));
|
|
TEST_ASSERT_EQUAL_INT(1, ha_no_primary_known(&ha_cold));
|
|
|
|
ha_fini(&ha_primary);
|
|
ha_fini(&ha_standby);
|
|
ha_fini(&ha_cold);
|
|
printf("test_redirect_guard_integration: PASS\n");
|
|
}
|
|
|
|
/* Test 5: Hook idempotency — double-promote does not register hooks twice
|
|
* (full hook registration needs a cluster_t — this tests the hooks_registered
|
|
* guard) */
|
|
static void test_hooks_registered_guard_integration(void)
|
|
{
|
|
ha_state_t ha;
|
|
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,
|
|
};
|
|
ha_init(&ha, &cfg, 0);
|
|
TEST_ASSERT_EQUAL_INT(0, ha.hooks_registered);
|
|
/* Simulate idempotency: hooks_registered = 1 after first promote */
|
|
ha.hooks_registered = 1;
|
|
/* Second promote should see guard and skip re-registration */
|
|
TEST_ASSERT_TRUE(ha.hooks_registered == 1); /* guard still set */
|
|
/* Demote resets guard */
|
|
ha_do_demote(&ha);
|
|
TEST_ASSERT_EQUAL_INT(0, ha.hooks_registered);
|
|
ha_fini(&ha);
|
|
printf("test_hooks_registered_guard_integration: PASS\n");
|
|
}
|
|
|
|
void test_p1_integration(void)
|
|
{
|
|
RUN_TEST(test_single_node_starts_as_primary);
|
|
RUN_TEST(test_term_fencing_monotonic);
|
|
RUN_TEST(test_redirect_guard_integration);
|
|
RUN_TEST(test_hooks_registered_guard_integration);
|
|
}
|