feat(component-failover-adoption): add candidate-list-to-client-config helper
xce_cluster_config_to_client_config() converts a validated candidate list into a fully-populated xce_client_config_t, copying candidates in order with no special-casing for a single candidate. The truncate-with-warning safety net stays in place as a defensive guard now that N_MAX_MASTERS and MAX_HA_CANDIDATES match. Also removes a WARN check in xce_cluster_config_validate() that became unreachable dead code once task 1.1 reconciled the two candidate-count constants (both checks tested the same field against the same constant with no mutation between them). Task 1.2 of component-failover-adoption (Requirement 1 AC1-AC7).
This commit is contained in:
parent
fe8b4b86bf
commit
210faa2b7a
4 changed files with 190 additions and 5 deletions
|
|
@ -7,6 +7,7 @@
|
|||
#define XCE_CLUSTER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "client.h" /* xce_client_config_t, N_MAX_MASTERS */
|
||||
#include "config.h" /* MAX_HA_CANDIDATES, XCE_MAXHOSTNAME_LEN */
|
||||
|
||||
/**
|
||||
|
|
@ -28,7 +29,6 @@ typedef struct {
|
|||
* (encryption without client-side peer certificate verification).
|
||||
*
|
||||
* candidates_count must be in [1, MAX_HA_CANDIDATES].
|
||||
* A WARN is logged at startup if candidates_count > 3.
|
||||
*/
|
||||
typedef struct {
|
||||
xce_candidate_t candidates[MAX_HA_CANDIDATES];
|
||||
|
|
@ -79,6 +79,24 @@ typedef struct {
|
|||
*/
|
||||
int xce_cluster_config_validate(const xce_cluster_config_t* cfg);
|
||||
|
||||
/**
|
||||
* xce_cluster_config_to_client_config() — convert a validated candidate list
|
||||
* into an xce_client_config_t's master fields (nmasters/masters/master_ports).
|
||||
*
|
||||
* Copies up to N_MAX_MASTERS candidates from cfg->candidates[] in order.
|
||||
* If cfg->candidates_count > N_MAX_MASTERS, copies the first N_MAX_MASTERS and
|
||||
* logs a WARN identifying the truncated count (mirrors xce_test's existing
|
||||
* clamp behavior).
|
||||
*
|
||||
* Does NOT set out->from / out->key — caller sets those separately.
|
||||
* Does NOT validate cfg — caller must have already called
|
||||
* xce_cluster_config_validate() successfully.
|
||||
*
|
||||
* Returns 0 on success, -EINVAL if cfg or out is NULL or cfg->candidates_count < 1.
|
||||
*/
|
||||
int xce_cluster_config_to_client_config(const xce_cluster_config_t* cfg,
|
||||
xce_client_config_t* out);
|
||||
|
||||
/**
|
||||
* xce_connect_cluster() — connect to the cluster via candidate list.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -96,10 +96,40 @@ int xce_cluster_config_validate(const xce_cluster_config_t* cfg)
|
|||
}
|
||||
if (cfg->identity[0] == '\0')
|
||||
return -EINVAL;
|
||||
if (cfg->candidates_count > 3)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── Config conversion ─────────────────────────────────────────── */
|
||||
|
||||
/* Mirrors xce_test's inline masters-array population at
|
||||
* src/controller/testsuite/test.c:360-372, generalized into a reusable
|
||||
* function. out->masters[i] are pointers into cfg->candidates[i].hostname,
|
||||
* not copies -- same non-owning-storage contract xce_test already relies on
|
||||
* (see that file's comment on master_cfg's static lifetime), so the caller
|
||||
* must keep cfg alive at least as long as out is in use. */
|
||||
int xce_cluster_config_to_client_config(const xce_cluster_config_t* cfg,
|
||||
xce_client_config_t* out)
|
||||
{
|
||||
if (!cfg || !out)
|
||||
return -EINVAL;
|
||||
if (cfg->candidates_count < 1)
|
||||
return -EINVAL;
|
||||
|
||||
int count = cfg->candidates_count;
|
||||
if (count > N_MAX_MASTERS) {
|
||||
fprintf(stderr,
|
||||
"WARN: HA cluster has %d candidates; validated for <=3 only\n",
|
||||
cfg->candidates_count);
|
||||
"WARN: HA cluster has %d candidates; using first %d "
|
||||
"(N_MAX_MASTERS)\n",
|
||||
count, N_MAX_MASTERS);
|
||||
count = N_MAX_MASTERS;
|
||||
}
|
||||
|
||||
out->nmasters = count;
|
||||
for (int i = 0; i < count; i++) {
|
||||
out->masters[i] = (char*)cfg->candidates[i].hostname;
|
||||
out->master_ports[i] = cfg->candidates[i].port;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <string.h>
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "client.h"
|
||||
#include "xce/cluster.h"
|
||||
|
||||
/* ── Test seam: mock connect function ───────────────────────────── */
|
||||
|
|
@ -163,6 +164,132 @@ static void test_reconnect_step_backs_off_on_failure(void)
|
|||
printf("test_reconnect_step_backs_off_on_failure: PASS\n");
|
||||
}
|
||||
|
||||
/* ── xce_cluster_config_to_client_config() tests ──────────────────
|
||||
* Builds a cfg with `n` candidates at distinct host/port values, so the
|
||||
* copy-in-order behavior can be verified precisely. */
|
||||
static xce_cluster_config_t make_config_with_n_candidates(int n)
|
||||
{
|
||||
xce_cluster_config_t cfg = {0};
|
||||
for (int i = 0; i < n && i < MAX_HA_CANDIDATES; i++) {
|
||||
char host[XCE_MAXHOSTNAME_LEN];
|
||||
snprintf(host, sizeof(host), "10.0.2.%d", i + 1);
|
||||
strncpy(cfg.candidates[i].hostname, host, XCE_MAXHOSTNAME_LEN - 1);
|
||||
cfg.candidates[i].port = 9000 + i;
|
||||
}
|
||||
cfg.candidates_count = n;
|
||||
strncpy(cfg.identity, "conv-test", sizeof(cfg.identity) - 1);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static void assert_converted_masters_match(const xce_cluster_config_t* cfg,
|
||||
const xce_client_config_t* out,
|
||||
int expected_count)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_INT(expected_count, out->nmasters);
|
||||
for (int i = 0; i < expected_count; i++) {
|
||||
TEST_ASSERT_EQUAL_STRING(cfg->candidates[i].hostname,
|
||||
out->masters[i]);
|
||||
TEST_ASSERT_EQUAL_INT(cfg->candidates[i].port, out->master_ports[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Test 10: one candidate goes through the identical code path as any other
|
||||
* count -- no special-cased "single controller" branch (Req 1.5). */
|
||||
static void test_convert_one_candidate(void)
|
||||
{
|
||||
xce_cluster_config_t cfg = make_config_with_n_candidates(1);
|
||||
xce_client_config_t out = {0};
|
||||
int rc = xce_cluster_config_to_client_config(&cfg, &out);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
assert_converted_masters_match(&cfg, &out, 1);
|
||||
printf("test_convert_one_candidate: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 11: three candidates -- ordinary multi-candidate case. */
|
||||
static void test_convert_three_candidates(void)
|
||||
{
|
||||
xce_cluster_config_t cfg = make_config_with_n_candidates(3);
|
||||
xce_client_config_t out = {0};
|
||||
int rc = xce_cluster_config_to_client_config(&cfg, &out);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
assert_converted_masters_match(&cfg, &out, 3);
|
||||
printf("test_convert_three_candidates: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 12: four candidates -- the N_MAX_MASTERS boundary, still the
|
||||
* ordinary (non-clamped) path since N_MAX_MASTERS == MAX_HA_CANDIDATES. */
|
||||
static void test_convert_four_candidates(void)
|
||||
{
|
||||
xce_cluster_config_t cfg = make_config_with_n_candidates(4);
|
||||
xce_client_config_t out = {0};
|
||||
int rc = xce_cluster_config_to_client_config(&cfg, &out);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(N_MAX_MASTERS, 4);
|
||||
assert_converted_masters_match(&cfg, &out, 4);
|
||||
printf("test_convert_four_candidates: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 13: NULL cfg is rejected. */
|
||||
static void test_convert_rejects_null_cfg(void)
|
||||
{
|
||||
xce_client_config_t out = {0};
|
||||
int rc = xce_cluster_config_to_client_config(NULL, &out);
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||
printf("test_convert_rejects_null_cfg: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 14: NULL out is rejected. */
|
||||
static void test_convert_rejects_null_out(void)
|
||||
{
|
||||
xce_cluster_config_t cfg = make_config_with_n_candidates(1);
|
||||
int rc = xce_cluster_config_to_client_config(&cfg, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||
printf("test_convert_rejects_null_out: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 15: candidates_count < 1 is rejected. */
|
||||
static void test_convert_rejects_zero_candidates(void)
|
||||
{
|
||||
xce_cluster_config_t cfg = make_config_with_n_candidates(0);
|
||||
xce_client_config_t out = {0};
|
||||
int rc = xce_cluster_config_to_client_config(&cfg, &out);
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||
printf("test_convert_rejects_zero_candidates: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 16: an artificially invalid over-count input (candidates_count >
|
||||
* N_MAX_MASTERS, bypassing xce_cluster_config_validate()) exercises the
|
||||
* defensive truncation path -- clamps to N_MAX_MASTERS and still populates
|
||||
* the first N_MAX_MASTERS candidates correctly. A properly validated input
|
||||
* can never reach this path today since N_MAX_MASTERS == MAX_HA_CANDIDATES. */
|
||||
static void test_convert_truncates_over_max_candidates(void)
|
||||
{
|
||||
xce_cluster_config_t cfg = make_config_with_n_candidates(MAX_HA_CANDIDATES);
|
||||
cfg.candidates_count = MAX_HA_CANDIDATES + 1; /* artificially invalid */
|
||||
xce_client_config_t out = {0};
|
||||
int rc = xce_cluster_config_to_client_config(&cfg, &out);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
assert_converted_masters_match(&cfg, &out, N_MAX_MASTERS);
|
||||
printf("test_convert_truncates_over_max_candidates: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 17: the helper does not set identity/auth fields -- caller sets
|
||||
* out->from / out->key separately. */
|
||||
static void test_convert_does_not_set_identity_fields(void)
|
||||
{
|
||||
xce_cluster_config_t cfg = make_config_with_n_candidates(2);
|
||||
xce_client_config_t out = {0};
|
||||
char sentinel_from[] = "sentinel-from";
|
||||
char sentinel_key[] = "sentinel-key";
|
||||
out.from = sentinel_from;
|
||||
out.key = sentinel_key;
|
||||
int rc = xce_cluster_config_to_client_config(&cfg, &out);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_PTR(sentinel_from, out.from);
|
||||
TEST_ASSERT_EQUAL_PTR(sentinel_key, out.key);
|
||||
printf("test_convert_does_not_set_identity_fields: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Test group entry point ─────────────────────────────────────── */
|
||||
void test_cluster(void)
|
||||
{
|
||||
|
|
@ -175,4 +302,12 @@ void test_cluster(void)
|
|||
RUN_TEST(test_connect_returns_null_all_fail);
|
||||
RUN_TEST(test_reconnect_step_reaches_connected);
|
||||
RUN_TEST(test_reconnect_step_backs_off_on_failure);
|
||||
RUN_TEST(test_convert_one_candidate);
|
||||
RUN_TEST(test_convert_three_candidates);
|
||||
RUN_TEST(test_convert_four_candidates);
|
||||
RUN_TEST(test_convert_rejects_null_cfg);
|
||||
RUN_TEST(test_convert_rejects_null_out);
|
||||
RUN_TEST(test_convert_rejects_zero_candidates);
|
||||
RUN_TEST(test_convert_truncates_over_max_candidates);
|
||||
RUN_TEST(test_convert_does_not_set_identity_fields);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue