feat(controller-peers-env-fallback): route the controller's two identity loops through the shared lookup (task 2.1)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
241ed3a142
commit
7a90af089c
3 changed files with 145 additions and 22 deletions
|
|
@ -22,7 +22,7 @@
|
|||
- Done when, after `/xce-build` (controller, scheduler, worker, harness) on a machine with no local cluster, the script runs with no cases and exits 0, and exits non-zero with the pre-flight message while a listener is bound to port 9990 or when pointed at a build directory missing a binary
|
||||
|
||||
- [ ] 2. Controller
|
||||
- [ ] 2.1 Route the controller's two existing identity loops through the shared lookup
|
||||
- [x] 2.1 Route the controller's two existing identity loops through the shared lookup
|
||||
- Replace the loop that picks the controller's election priority, keeping the existing "at least one candidate" guard so a standalone controller still gets priority 0, and keeping "no match gives the last index" at that call site
|
||||
- Replace the loop that picks the listen port, keeping the existing "no match with a peers list refuses to start" and "port 0 falls back to the default port" behavior
|
||||
- Pure refactor: no behavior change for any input
|
||||
|
|
|
|||
|
|
@ -1780,16 +1780,9 @@ int cluster_init_phase1(cluster_t* c)
|
|||
int my_priority = 0;
|
||||
const xce_cluster_config_t* pcfg = &c->myconf->peers_cfg;
|
||||
if (pcfg->candidates_count > 0) {
|
||||
my_priority = pcfg->candidates_count - 1; /* default: last = lowest */
|
||||
for (int i = 0; i < pcfg->candidates_count; i++) {
|
||||
const char* key = pcfg->candidates[i].name[0]
|
||||
? pcfg->candidates[i].name
|
||||
: pcfg->candidates[i].hostname;
|
||||
if (strncmp(key, c->myconf->name, XCE_MAXNAME_LEN - 1) == 0) {
|
||||
my_priority = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* No match: last index = lowest priority. */
|
||||
int idx = xce_peers_find_identity(pcfg, c->myconf->name);
|
||||
my_priority = idx >= 0 ? idx : pcfg->candidates_count - 1;
|
||||
INFO("HA peers: %d candidates, my_priority=%d (name=%s)",
|
||||
pcfg->candidates_count, my_priority, c->myconf->name);
|
||||
}
|
||||
|
|
@ -1873,7 +1866,8 @@ int cluster_open_listener(cluster_t* c)
|
|||
return 0;
|
||||
|
||||
/* Determine listen port:
|
||||
* 1. Search --peers candidates for the local name.
|
||||
* 1. Look up the local name among the --peers candidates
|
||||
* (xce_peers_find_identity()).
|
||||
* - Matched with a nonzero port: bind to it (unchanged happy
|
||||
* path; also how a single-node operator gets a custom port,
|
||||
* via a single self-referencing --peers entry).
|
||||
|
|
@ -1889,16 +1883,9 @@ int cluster_open_listener(cluster_t* c)
|
|||
bool matched = false;
|
||||
{
|
||||
const xce_cluster_config_t* pcfg = &c->myconf->peers_cfg;
|
||||
for (int j = 0; j < pcfg->candidates_count; j++) {
|
||||
const char* key = pcfg->candidates[j].name[0]
|
||||
? pcfg->candidates[j].name
|
||||
: pcfg->candidates[j].hostname;
|
||||
if (strncmp(key, c->myconf->name, XCE_MAXNAME_LEN - 1) == 0) {
|
||||
my_port = pcfg->candidates[j].port;
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int idx = xce_peers_find_identity(pcfg, c->myconf->name);
|
||||
matched = idx >= 0;
|
||||
my_port = matched ? (uint32_t)pcfg->candidates[idx].port : 0;
|
||||
|
||||
if (!matched && pcfg->candidates_count > 0) {
|
||||
ERROR("cluster_open_listener: local name '%s' not found in "
|
||||
|
|
|
|||
|
|
@ -9,11 +9,19 @@
|
|||
* Instead they manually zero-init cluster_t and call ha_init() on c.ha directly.
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "cluster.h"
|
||||
#include "ha.h"
|
||||
#include "xce/cluster_def.h"
|
||||
|
||||
|
|
@ -138,6 +146,130 @@ static void test_hooks_registered_guard_prevents_double_register(void)
|
|||
printf("test_hooks_registered_guard_prevents_double_register: PASS\n");
|
||||
}
|
||||
|
||||
/* ── cluster_open_listener(): which --peers candidate is this controller ──
|
||||
*
|
||||
* Characterization tests (controller-peers-env-fallback task 2.1): pin the
|
||||
* listener's candidate match before it is routed through
|
||||
* xce_peers_find_identity(). The key is the candidate's name, or its
|
||||
* hostname when unnamed; a match binds that candidate's port, and no match
|
||||
* with a non-empty --peers list refuses to start without opening a socket.
|
||||
* The port-0/standalone default-port fallback is left out: it binds a fixed
|
||||
* port (9990) that another process on this host may hold. */
|
||||
|
||||
static void listener_set_candidate(xce_cluster_config_t* cfg, int i,
|
||||
const char* name, const char* hostname,
|
||||
int port)
|
||||
{
|
||||
snprintf(cfg->candidates[i].name, XCE_MAXNAME_LEN, "%s", name);
|
||||
snprintf(cfg->candidates[i].hostname, XCE_MAXHOSTNAME_LEN, "%s",
|
||||
hostname);
|
||||
cfg->candidates[i].port = port;
|
||||
}
|
||||
|
||||
/* A zeroed cluster_t with only what cluster_open_listener() reads. */
|
||||
static cluster_t* listener_make_cluster(myconf_t* conf, const char* name)
|
||||
{
|
||||
cluster_t* c = calloc(1, sizeof(*c));
|
||||
TEST_ASSERT_NOT_NULL(c);
|
||||
memset(conf, 0, sizeof(*conf));
|
||||
conf->name = (char*)name;
|
||||
c->myconf = conf;
|
||||
c->myhost = &c->myhost_info;
|
||||
c->ep.fd = -1;
|
||||
return c;
|
||||
}
|
||||
|
||||
/* An unused TCP port: bind port 0, read it back, release it. */
|
||||
static int listener_free_port(void)
|
||||
{
|
||||
struct sockaddr_in sa;
|
||||
socklen_t len = sizeof(sa);
|
||||
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
TEST_ASSERT_TRUE(fd >= 0);
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
sa.sin_port = 0;
|
||||
TEST_ASSERT_EQUAL_INT(0, bind(fd, (struct sockaddr*)&sa, sizeof(sa)));
|
||||
TEST_ASSERT_EQUAL_INT(0, getsockname(fd, (struct sockaddr*)&sa, &len));
|
||||
close(fd);
|
||||
return ntohs(sa.sin_port);
|
||||
}
|
||||
|
||||
static int listener_bound_port(int fd)
|
||||
{
|
||||
struct sockaddr_in sa;
|
||||
socklen_t len = sizeof(sa);
|
||||
TEST_ASSERT_EQUAL_INT(0, getsockname(fd, (struct sockaddr*)&sa, &len));
|
||||
return ntohs(sa.sin_port);
|
||||
}
|
||||
|
||||
static void test_open_listener_binds_named_candidate_port(void)
|
||||
{
|
||||
myconf_t conf;
|
||||
cluster_t* c = listener_make_cluster(&conf, "ctrl-b");
|
||||
int port = listener_free_port();
|
||||
|
||||
conf.peers_cfg.candidates_count = 2;
|
||||
listener_set_candidate(&conf.peers_cfg, 0, "ctrl-a", "localhost", 1);
|
||||
listener_set_candidate(&conf.peers_cfg, 1, "ctrl-b", "localhost", port);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, cluster_open_listener(c));
|
||||
TEST_ASSERT_TRUE(c->ep.fd >= 0);
|
||||
TEST_ASSERT_EQUAL_INT(port, listener_bound_port(c->ep.fd));
|
||||
|
||||
close(c->ep.fd);
|
||||
free(c);
|
||||
}
|
||||
|
||||
static void test_open_listener_matches_unnamed_candidate_by_hostname(void)
|
||||
{
|
||||
myconf_t conf;
|
||||
cluster_t* c = listener_make_cluster(&conf, "node7");
|
||||
int port = listener_free_port();
|
||||
|
||||
conf.peers_cfg.candidates_count = 2;
|
||||
listener_set_candidate(&conf.peers_cfg, 0, "ctrl-a", "localhost", 1);
|
||||
listener_set_candidate(&conf.peers_cfg, 1, "", "node7", port);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, cluster_open_listener(c));
|
||||
TEST_ASSERT_TRUE(c->ep.fd >= 0);
|
||||
TEST_ASSERT_EQUAL_INT(port, listener_bound_port(c->ep.fd));
|
||||
|
||||
close(c->ep.fd);
|
||||
free(c);
|
||||
}
|
||||
|
||||
static void test_open_listener_refuses_when_name_not_in_peers(void)
|
||||
{
|
||||
myconf_t conf;
|
||||
cluster_t* c = listener_make_cluster(&conf, "ctrl-z");
|
||||
|
||||
conf.peers_cfg.candidates_count = 2;
|
||||
listener_set_candidate(&conf.peers_cfg, 0, "ctrl-a", "localhost", 1);
|
||||
listener_set_candidate(&conf.peers_cfg, 1, "ctrl-b", "localhost", 2);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, cluster_open_listener(c));
|
||||
TEST_ASSERT_EQUAL_INT(-1, c->ep.fd);
|
||||
|
||||
free(c);
|
||||
}
|
||||
|
||||
/* A named candidate is never matched by its hostname. */
|
||||
static void test_open_listener_ignores_hostname_of_named_candidate(void)
|
||||
{
|
||||
myconf_t conf;
|
||||
cluster_t* c = listener_make_cluster(&conf, "node7");
|
||||
|
||||
conf.peers_cfg.candidates_count = 1;
|
||||
listener_set_candidate(&conf.peers_cfg, 0, "ctrl-a", "node7", 1);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, cluster_open_listener(c));
|
||||
TEST_ASSERT_EQUAL_INT(-1, c->ep.fd);
|
||||
|
||||
free(c);
|
||||
}
|
||||
|
||||
void test_ha_cluster(void)
|
||||
{
|
||||
UnitySetTestFile(__FILE__);
|
||||
|
|
@ -146,4 +278,8 @@ void test_ha_cluster(void)
|
|||
RUN_TEST(test_ha_demote_to_standby_uses_c_ha);
|
||||
RUN_TEST(test_ha_promote_increments_term_via_cluster);
|
||||
RUN_TEST(test_hooks_registered_guard_prevents_double_register);
|
||||
RUN_TEST(test_open_listener_binds_named_candidate_port);
|
||||
RUN_TEST(test_open_listener_matches_unnamed_candidate_by_hostname);
|
||||
RUN_TEST(test_open_listener_refuses_when_name_not_in_peers);
|
||||
RUN_TEST(test_open_listener_ignores_hostname_of_named_candidate);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue