feat: P2.5-5/6 add --peers/--ca-cert parsing to worker+scheduler with validation

Worker (P2.5-5):
- Added parse_peers() static helper to src/worker/src/main.c
- Switched getopt to getopt_long to support --peers and --ca-cert long options
- When --peers is given: parses into xce_cluster_config_t, calls
  xce_cluster_config_validate(), logs INFO with candidate count/identity/ca_cert
- If validation fails: prints clear error and exits(1)
- WARN logged when candidates_count > 3
- Backward compatible: no --peers falls back to existing single-controller path

Scheduler (P2.5-6):
- Added parse_peers() static helper to src/scheduler/src/main.c
- Extended argp struct with --peers (-P) and --ca-cert (-C) options
- Same validation/logging pattern as worker
- Backward compatible: no --peers falls back to existing single-controller path

Tests (new):
- src/lib/testsuite/test_cluster_parse.c: 8 unit tests for parse_peers logic
  (single entry, multiple entries, no-name format, empty/null/missing-port errors,
  name-stripped correctly, MAX_HA_CANDIDATES cap respected)
- Registered in test.c and meson.build
- All 8 tests GREEN; 3 pre-existing failures in unrelated GC/CSE tests unchanged
This commit is contained in:
Rongsong Shen 2026-05-08 02:32:56 +00:00
parent 0c11989da7
commit fd9cfce1c1
5 changed files with 356 additions and 7 deletions

View file

@ -1,6 +1,8 @@
#include "sqlite3.h"
#include "xcecd.h"
#include "xce/cluster.h"
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -248,6 +250,42 @@ static void master_rusages_callback(xce_job_rusages_t* rusages)
free_xce_job_rusages(rusages);
}
/* parse_peers — parse "--peers ctrl-1=10.0.1.1:7000,ctrl-2=10.0.1.2:7001"
* into cfg->candidates[]. Returns 0 on success, -1 on parse error.
* If peers_str is NULL or empty, returns -1 (caller falls back to single-host
* path). */
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) {
/* Format: name=host:port OR host:port */
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 int check_xce_server_dir(char* myfile)
{
char* xce_server_dir = getenv("XCE_SERVER_DIR");
@ -285,6 +323,10 @@ static int check_user(void)
return 1;
}
/* Long-option identifiers (beyond ASCII range to avoid collisions) */
#define OPT_PEERS 256
#define OPT_CA_CERT 257
int main(int argc, char* argv[])
{
int opt;
@ -293,6 +335,10 @@ int main(int argc, char* argv[])
int config_flag = 0;
char* custom_config_path = NULL;
__attribute__((unused)) char worker_log[1024] = { 0 };
const char* peers_str = NULL;
const char* ca_cert_str = NULL;
xce_cluster_config_t cluster_cfg = {0};
int use_ha = 0; /* 1 when --peers given */
check_user();
@ -382,10 +428,18 @@ int main(int argc, char* argv[])
return -1;
}
while ((opt = getopt(argc, argv, "c:p:m:n:l:h")) != -1) {
static struct option long_opts[] = {
{ "peers", required_argument, NULL, OPT_PEERS },
{ "ca-cert", required_argument, NULL, OPT_CA_CERT },
{ "help", no_argument, NULL, 'h' },
{ NULL, 0, NULL, 0 }
};
while ((opt = getopt_long(argc, argv, "c:p:m:n:l:h", long_opts, NULL))
!= -1) {
switch (opt) {
case 'c':
break; // Custom config file already handled above
break; /* Custom config file already handled above */
case 'p':
if (config_flag) {
fprintf(stderr, "Custom config file already specified, "
@ -415,18 +469,25 @@ int main(int argc, char* argv[])
if (logdir) {
FREEUP(logdir);
}
logdir = strdup(optarg);
}
break;
case OPT_PEERS:
peers_str = optarg;
break;
case OPT_CA_CERT:
ca_cert_str = optarg;
break;
case 'h':
default:
#ifdef VIRTUAL_CLUSTER
printf("Usage: %s -n virtual_name [-p remote_port] [-m "
"remote_host] [-l log_dir] [-h help]\n",
"remote_host] [-l log_dir] [--peers list] "
"[--ca-cert path] [-h help]\n",
argv[0]);
#else
printf("Usage: %s [-p remote_port] [-m remote_host] [-l log_dir ] "
printf("Usage: %s [-p remote_port] [-m remote_host] [-l log_dir] "
"[--peers ctrl-1=host:port,...] [--ca-cert path] "
"[-h help]\n",
argv[0]);
#endif
@ -434,6 +495,47 @@ int main(int argc, char* argv[])
}
}
/* P2.5-5: populate and validate xce_cluster_config_t when --peers given */
if (peers_str) {
if (parse_peers(peers_str, &cluster_cfg) != 0) {
fprintf(stderr,
"xcecd: --peers parse error: expected "
"\"[name=]host:port,...\" got \"%s\"\n",
peers_str);
return 1;
}
if (ca_cert_str) {
strncpy(cluster_cfg.ca_cert_path, ca_cert_str,
sizeof(cluster_cfg.ca_cert_path) - 1);
}
strncpy(cluster_cfg.identity, config.virutal_name,
sizeof(cluster_cfg.identity) - 1);
if (xce_cluster_config_validate(&cluster_cfg) != 0) {
fprintf(stderr,
"xcecd: invalid cluster config (missing ca-cert or "
"identity?). Use --ca-cert and -n.\n");
return 1;
}
if (cluster_cfg.candidates_count > 3) {
fprintf(stderr,
"xcecd: WARN: --peers has %d candidates; "
"only <=3 validated for HA\n",
cluster_cfg.candidates_count);
}
XCECD_LOG(XCE_LOG_INFO,
"HA cluster config: %d candidate(s), identity=%s, "
"ca_cert=%s",
cluster_cfg.candidates_count, cluster_cfg.identity,
cluster_cfg.ca_cert_path);
use_ha = 1;
} else {
XCECD_LOG(XCE_LOG_INFO,
"No --peers flag; using single-controller path "
"(host=%s port=%d)",
config.remote_host, config.remote_port);
}
(void)use_ha; /* P3 will consume this to call xce_connect_cluster() */
struct stat st = { 0 };
if (stat(logdir, &st) == -1) {