feat(libxce-failover): endpoint rotator — hint-first target selection + master rotation; keep api_ctx_t decls private (task 2.2)
This commit is contained in:
parent
fa2ac05036
commit
c7e11a4e9e
5 changed files with 300 additions and 5 deletions
|
|
@ -26,8 +26,11 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "client_internal.h" /* api_ctx_t, xce_failover_state_t */
|
||||
#include "failover_harness.h"
|
||||
#include "request.h"
|
||||
#include "xce/client_failover.h"
|
||||
|
|
@ -450,6 +453,170 @@ static void test_classify_unknown_is_terminal(void)
|
|||
printf("test_classify_unknown_is_terminal: PASS\n");
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* Task 2.2 — EndpointRotator target selection (xce_failover_next_target)
|
||||
* RED→GREEN tests. Pure, socket-free target-selection logic; the
|
||||
* live connect path is covered by E2E task 6.2.
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* Build a 3-master client config (distinct host:port) the rotator selects over.
|
||||
* Strings are static-duration so the api_ctx may borrow them by pointer. */
|
||||
static void fo_make_client_masters(xce_client_config_t *conf)
|
||||
{
|
||||
static char *hosts[N_MAX_MASTERS] = { "10.30.0.1", "10.30.0.2",
|
||||
"10.30.0.3" };
|
||||
static int ports[N_MAX_MASTERS] = { 8001, 8002, 8003 };
|
||||
|
||||
memset(conf, 0, sizeof(*conf));
|
||||
conf->nmasters = N_MAX_MASTERS;
|
||||
for (int i = 0; i < N_MAX_MASTERS; i++) {
|
||||
conf->masters[i] = hosts[i];
|
||||
conf->master_ports[i] = ports[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Minimal api_ctx for target-selection tests: only conf, the failover cursor,
|
||||
* and the ctx redirect-hint fields are exercised. No endpoint/socket touched. */
|
||||
static void fo_make_api_ctx(api_ctx_t *ac, xce_client_config_t *conf)
|
||||
{
|
||||
memset(ac, 0, sizeof(*ac));
|
||||
ac->conf = conf;
|
||||
ac->failover.cur_master = 0;
|
||||
}
|
||||
|
||||
static void fo_set_hint(api_ctx_t *ac, const char *host, uint16_t port)
|
||||
{
|
||||
strncpy(ac->ctx.redirect_hint_host, host, XCE_MAXHOSTNAME_LEN - 1);
|
||||
ac->ctx.redirect_hint_host[XCE_MAXHOSTNAME_LEN - 1] = '\0';
|
||||
ac->ctx.redirect_hint_port = port;
|
||||
}
|
||||
|
||||
/* Req 2.2, 8.4: with a redirect hint set, next_target returns the hint
|
||||
* host/port and CONSUMES (clears) it; the following call ignores the stale
|
||||
* hint and advances the master cursor instead. */
|
||||
static void test_next_target_hint_first_then_consumed(void)
|
||||
{
|
||||
xce_client_config_t conf;
|
||||
api_ctx_t ac;
|
||||
fo_make_client_masters(&conf);
|
||||
fo_make_api_ctx(&ac, &conf);
|
||||
fo_set_hint(&ac, "10.99.0.7", 9100);
|
||||
|
||||
char host[XCE_MAXHOSTNAME_LEN] = { 0 };
|
||||
uint16_t port = 0;
|
||||
|
||||
int rc = xce_failover_next_target(&ac, host, sizeof(host), &port);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_STRING("10.99.0.7", host);
|
||||
TEST_ASSERT_EQUAL_INT(9100, port);
|
||||
|
||||
/* hint consumed: cleared on the ctx, cursor untouched */
|
||||
TEST_ASSERT_EQUAL_STRING("", ac.ctx.redirect_hint_host);
|
||||
TEST_ASSERT_EQUAL_INT(0, ac.failover.cur_master);
|
||||
|
||||
/* next call has no hint → advances cursor to master 1 */
|
||||
rc = xce_failover_next_target(&ac, host, sizeof(host), &port);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_STRING("10.30.0.2", host);
|
||||
TEST_ASSERT_EQUAL_INT(8002, port);
|
||||
TEST_ASSERT_EQUAL_INT(1, ac.failover.cur_master);
|
||||
|
||||
printf("test_next_target_hint_first_then_consumed: PASS\n");
|
||||
}
|
||||
|
||||
/* Req 2.1, 8.4: with no hint, successive next_target calls advance through the
|
||||
* configured masters and WRAP around; the cursor lives on failover.cur_master. */
|
||||
static void test_next_target_rotates_and_wraps(void)
|
||||
{
|
||||
xce_client_config_t conf;
|
||||
api_ctx_t ac;
|
||||
fo_make_client_masters(&conf);
|
||||
fo_make_api_ctx(&ac, &conf);
|
||||
|
||||
char host[XCE_MAXHOSTNAME_LEN] = { 0 };
|
||||
uint16_t port = 0;
|
||||
|
||||
/* from cur_master=0, first rotation advances to master 1 */
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_failover_next_target(&ac, host, sizeof(host),
|
||||
&port));
|
||||
TEST_ASSERT_EQUAL_STRING("10.30.0.2", host);
|
||||
TEST_ASSERT_EQUAL_INT(8002, port);
|
||||
TEST_ASSERT_EQUAL_INT(1, ac.failover.cur_master);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_failover_next_target(&ac, host, sizeof(host),
|
||||
&port));
|
||||
TEST_ASSERT_EQUAL_STRING("10.30.0.3", host);
|
||||
TEST_ASSERT_EQUAL_INT(8003, port);
|
||||
TEST_ASSERT_EQUAL_INT(2, ac.failover.cur_master);
|
||||
|
||||
/* wrap back to master 0 */
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_failover_next_target(&ac, host, sizeof(host),
|
||||
&port));
|
||||
TEST_ASSERT_EQUAL_STRING("10.30.0.1", host);
|
||||
TEST_ASSERT_EQUAL_INT(8001, port);
|
||||
TEST_ASSERT_EQUAL_INT(0, ac.failover.cur_master);
|
||||
|
||||
printf("test_next_target_rotates_and_wraps: PASS\n");
|
||||
}
|
||||
|
||||
/* Req 4.4, 8.4: after a hint is consumed, the very next call advances to the
|
||||
* next master — correctness is independent of the (one-shot) hint. */
|
||||
static void test_next_target_hint_then_fallback_rotation(void)
|
||||
{
|
||||
xce_client_config_t conf;
|
||||
api_ctx_t ac;
|
||||
fo_make_client_masters(&conf);
|
||||
fo_make_api_ctx(&ac, &conf);
|
||||
/* start the cursor at master 1 to prove fallback advances from current */
|
||||
ac.failover.cur_master = 1;
|
||||
fo_set_hint(&ac, "10.99.0.9", 9200);
|
||||
|
||||
char host[XCE_MAXHOSTNAME_LEN] = { 0 };
|
||||
uint16_t port = 0;
|
||||
|
||||
/* hint first */
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_failover_next_target(&ac, host, sizeof(host),
|
||||
&port));
|
||||
TEST_ASSERT_EQUAL_STRING("10.99.0.9", host);
|
||||
TEST_ASSERT_EQUAL_INT(9200, port);
|
||||
TEST_ASSERT_EQUAL_INT(1, ac.failover.cur_master); /* cursor untouched */
|
||||
|
||||
/* fallback: advances from master 1 → master 2 */
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_failover_next_target(&ac, host, sizeof(host),
|
||||
&port));
|
||||
TEST_ASSERT_EQUAL_STRING("10.30.0.3", host);
|
||||
TEST_ASSERT_EQUAL_INT(8003, port);
|
||||
TEST_ASSERT_EQUAL_INT(2, ac.failover.cur_master);
|
||||
|
||||
printf("test_next_target_hint_then_fallback_rotation: PASS\n");
|
||||
}
|
||||
|
||||
/* Guard: NULL/invalid arguments are rejected without touching state. */
|
||||
static void test_next_target_rejects_bad_args(void)
|
||||
{
|
||||
xce_client_config_t conf;
|
||||
api_ctx_t ac;
|
||||
fo_make_client_masters(&conf);
|
||||
fo_make_api_ctx(&ac, &conf);
|
||||
|
||||
char host[XCE_MAXHOSTNAME_LEN] = { 0 };
|
||||
uint16_t port = 0;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL,
|
||||
xce_failover_next_target(NULL, host, sizeof(host),
|
||||
&port));
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL,
|
||||
xce_failover_next_target(&ac, NULL, sizeof(host),
|
||||
&port));
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL,
|
||||
xce_failover_next_target(&ac, host, 0, &port));
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL,
|
||||
xce_failover_next_target(&ac, host, sizeof(host),
|
||||
NULL));
|
||||
|
||||
printf("test_next_target_rejects_bad_args: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Suite entry point ──────────────────────────────────────────────── */
|
||||
|
||||
void test_libxce_retry(void)
|
||||
|
|
@ -467,5 +634,9 @@ void test_libxce_retry(void)
|
|||
RUN_TEST(test_classify_dup_replay_rule);
|
||||
RUN_TEST(test_classify_zero_is_success);
|
||||
RUN_TEST(test_classify_unknown_is_terminal);
|
||||
RUN_TEST(test_next_target_hint_first_then_consumed);
|
||||
RUN_TEST(test_next_target_rotates_and_wraps);
|
||||
RUN_TEST(test_next_target_hint_then_fallback_rotation);
|
||||
RUN_TEST(test_next_target_rejects_bad_args);
|
||||
UNITY_END();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue