feat: add --peers to controller for HA peer list and priority election

- myconf_t gains peers_cfg (xce_cluster_config_t) populated from --peers
- controller/main.c: parse --peers host:port,... via argp; ca_cert_path
  auto-derived from workdir/certs/xce.crt (no --ca-cert flag needed)
- cluster_init: derive my_priority from position in peers list (index 0 =
  primary); fall back to 0 when --peers not given (single-node mode)
This commit is contained in:
Rongsong Shen 2026-05-08 04:40:27 +00:00
parent d61f91b99c
commit 59597e105e
10 changed files with 8046 additions and 325 deletions

View file

@ -6,6 +6,7 @@
#include "myconf.h"
#include "utils.h"
#include "xce/cluster.h"
extern int myapp_run(myconf_t*);
@ -15,21 +16,60 @@ const char* argp_program_version
= MK_VERSION("xce v0.0.1-", STR(VERSION_COMMIT_ID));
const char* argp_program_bug_address = BUG_EMAIL;
/* Long-option key (above ASCII to avoid collisions with short opts) */
#define OPT_PEERS 256
struct arguments {
char* myname;
char* logfile;
char* workdir;
int verbose;
char* peers_str;
};
static struct argp_option options[] = {
{ "workdir", 'w', "DIR", 0, "The work directory of xce controller" },
{ "logfile", 'l', "FILE", 0, "The full path of log file" },
{ "verbose", 'v', 0, 0, "Produce verbose logs" },
{ "myname", 'n', "STR", 0, "The name of controller" },
{ "workdir", 'w', "DIR", 0, "The work directory of xce controller" },
{ "logfile", 'l', "FILE", 0, "The full path of log file" },
{ "verbose", 'v', 0, 0, "Produce verbose logs" },
{ "myname", 'n', "STR", 0, "The name of controller" },
{ "peers", OPT_PEERS, "PEERS", 0,
"HA peer list: comma-separated host:port pairs ordered by priority "
"(e.g. ctrl01:9990,ctrl02:9990). Index 0 = highest priority (primary)." },
{ 0 }
};
/* parse_peers — split "host:port,host:port,..." into cfg->candidates[].
* Accepts optional "name=host:port" prefix (name is ignored for controller).
* Returns 0 on success, -1 on empty input or parse error. */
static int parse_peers(const char* peers_str, xce_cluster_config_t* cfg)
{
cfg->candidates_count = 0;
if (!peers_str || peers_str[0] == '\0')
return -1;
char buf[2048];
strncpy(buf, peers_str, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
char* saveptr;
char* tok = strtok_r(buf, ",", &saveptr);
while (tok && cfg->candidates_count < MAX_HA_CANDIDATES) {
char* eq = strchr(tok, '=');
char* addr = eq ? eq + 1 : tok;
char* colon = strrchr(addr, ':');
if (!colon)
return -1;
*colon = '\0';
strncpy(cfg->candidates[cfg->candidates_count].hostname, addr,
XCE_MAXHOSTNAME_LEN - 1);
cfg->candidates[cfg->candidates_count].hostname[XCE_MAXHOSTNAME_LEN - 1] = '\0';
cfg->candidates[cfg->candidates_count].port = atoi(colon + 1);
cfg->candidates_count++;
tok = strtok_r(NULL, ",", &saveptr);
}
return cfg->candidates_count > 0 ? 0 : -1;
}
static error_t parse_opt(int key, char* arg, struct argp_state* state)
{
struct arguments* arguments = state->input;
@ -47,6 +87,9 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state)
case 'n':
arguments->myname = arg;
break;
case OPT_PEERS:
arguments->peers_str = arg;
break;
default:
return ARGP_ERR_UNKNOWN;
}
@ -111,6 +154,27 @@ int main(int argc, char* argv[])
myconf.keyfile = key_file;
myconf.workdir = arguments.workdir;
myconf.name = arguments.myname;
memset(&myconf.peers_cfg, 0, sizeof(myconf.peers_cfg));
if (arguments.peers_str) {
if (parse_peers(arguments.peers_str, &myconf.peers_cfg) != 0) {
fprintf(stderr,
"xce_controller: --peers parse error: expected "
"host:port,... got: %s\n",
arguments.peers_str);
return 1;
}
/* CA cert derived from workdir — same infrastructure as certfile/keyfile */
snprintf(myconf.peers_cfg.ca_cert_path,
sizeof(myconf.peers_cfg.ca_cert_path),
"%s/certs/xce.crt", arguments.workdir);
strncpy(myconf.peers_cfg.identity, arguments.myname,
sizeof(myconf.peers_cfg.identity) - 1);
INFO("HA mode: %d controller candidates, identity=%s",
myconf.peers_cfg.candidates_count, myconf.peers_cfg.identity);
} else {
INFO("No --peers flag; running as single-node primary");
}
myapp_run(&myconf);