myxce/src/controller/main.c

283 lines
9.8 KiB
C

#include <argp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <tlog.h>
#include "myconf.h"
#include "utils.h"
#include "xce/cluster.h"
extern int myapp_run(myconf_t*);
/* forward declarations — implemented in startup_validation.c */
int validate_vcpus(int vcpus);
int validate_peer_count(int n_peers);
int validate_cert_files(const char *cert_path, const char *key_path,
char *err_buf, size_t err_len);
static char doc[] = "xce_controller";
static char args_doc[] = "";
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
#define OPT_CONF_KEEP_N 257
#define OPT_CONF_STALE_TIMEOUT 258
#define OPT_VCPUS 259
#define OPT_CERTDIR 260
#define OPT_SYNC_TIMEOUT 261
#define OPT_WAL_SYNC 262
struct arguments {
char* myname;
char* logfile;
char* workdir;
int verbose;
char* peers_str;
int conf_keep_n;
long conf_stale_timeout;
int vcpus;
char* certdir;
uint32_t sync_timeout_ms;
uint32_t wal_sync_ms;
};
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" },
{ "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)." },
{ "conf-keep-n", OPT_CONF_KEEP_N, "N", 0,
"Versioned config files to retain (default: 2)" },
{ "conf-stale-timeout", OPT_CONF_STALE_TIMEOUT, "SECS", 0,
"Seconds before saved config is flagged stale (0=disabled, default: 86400)" },
{ "vcpus", OPT_VCPUS, "N", 0,
"Number of virtual CPUs for the task manager (must be >= 1)" },
{ "certdir", OPT_CERTDIR, "DIR", 0,
"Directory containing xce.crt and xce.key (default: <workdir>/certs)" },
{ "sync-timeout-ms", OPT_SYNC_TIMEOUT, "MS", 0,
"Synchronous majority-replication commit barrier timeout in ms (0 = default)" },
{ "wal-sync-ms", OPT_WAL_SYNC, "MS", 0,
"WAL flush (fsync) cadence in ms (0 = default)" },
{ 0 }
};
static error_t parse_opt(int key, char* arg, struct argp_state* state)
{
struct arguments* arguments = state->input;
switch (key) {
case 'l':
arguments->logfile = arg;
break;
case 'v':
arguments->verbose++;
break;
case 'w':
arguments->workdir = arg;
break;
case 'n':
arguments->myname = arg;
break;
case OPT_PEERS:
arguments->peers_str = arg;
break;
case OPT_CONF_KEEP_N:
arguments->conf_keep_n = atoi(arg);
if (arguments->conf_keep_n < 1)
argp_error(state, "--conf-keep-n must be >= 1 (got: %s)", arg);
break;
case OPT_CONF_STALE_TIMEOUT:
arguments->conf_stale_timeout = atol(arg);
if (arguments->conf_stale_timeout < 0)
argp_error(state, "--conf-stale-timeout must be >= 0 (got: %s)", arg);
break;
case OPT_VCPUS:
arguments->vcpus = atoi(arg);
if (validate_vcpus(arguments->vcpus) != 0)
argp_error(state, "--vcpus must be >= 1 (got: %s)", arg);
break;
case OPT_CERTDIR:
arguments->certdir = arg;
break;
case OPT_SYNC_TIMEOUT: {
int v = atoi(arg);
if (v < 0)
argp_error(state, "--sync-timeout-ms must be >= 0 (got: %s)", arg);
arguments->sync_timeout_ms = (uint32_t)v;
break;
}
case OPT_WAL_SYNC: {
int v = atoi(arg);
if (v < 0)
argp_error(state, "--wal-sync-ms must be >= 0 (got: %s)", arg);
arguments->wal_sync_ms = (uint32_t)v;
break;
}
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = { options, parse_opt, args_doc, doc };
/*
* Saved argv for self-restart (ha-convergence-recovery Req 1.3).
*
* main() captures the original, NULL-terminated argv pointer before argp_parse
* mutates the program state, so ha_self_restart can execv() the controller back
* into existence after a demotion/rejoin convergence truncates the divergent WAL
* suffix. argv supplied to main() is already NULL-terminated per C11 §5.1.2.2.1
* (argv[argc] == NULL) and lives for the whole program lifetime, so storing the
* pointer is sufficient — no copy needed.
*/
static char **g_saved_argv = NULL;
/*
* controller_saved_argv — accessor declared in ha.h, consumed by ha_self_restart.
* Returns the NULL-terminated argv captured by main(), or NULL if never saved
* (e.g. unit-test binaries that do not run main()).
*/
char **controller_saved_argv(void)
{
return g_saved_argv;
}
int main(int argc, char* argv[])
{
struct arguments arguments;
myconf_t myconf;
char* logfile = "/tmp/xce_controller.log";
char* loglevel = "info";
int logsize = 64 * 1024 * 1024;
int logcount = 10;
char key_file[XCE_MAXPATH_LEN + 1];
char cert_file[XCE_MAXPATH_LEN + 1];
char myname[XCE_MAXNAME_LEN + 1];
/* Capture the NULL-terminated argv for self-restart BEFORE argp_parse so a
* later demotion/rejoin convergence can execv() the same image (Req 1.3). */
g_saved_argv = argv;
memset(&arguments, 0x0, sizeof(arguments));
arguments.conf_keep_n = 2;
arguments.conf_stale_timeout = 86400;
arguments.vcpus = 0; /* 0 = not set; use cluster config default */
arguments.sync_timeout_ms = 0; /* 0 = not set; use persist config default */
arguments.wal_sync_ms = 0; /* 0 = not set; use persist config default */
argp_parse(&argp, argc, argv, 0, 0, &arguments);
if (arguments.logfile != NULL) {
logfile = arguments.logfile;
}
if (arguments.verbose != 0) {
loglevel = verbose_to_loglevel(arguments.verbose);
}
mylog_init(logfile, loglevel, logsize, logcount);
if (arguments.workdir == NULL) {
INFO("no cluster work directory specified. The default work directory "
"`" DEFAULT_WORKDIR "' will be used.");
arguments.workdir = DEFAULT_WORKDIR;
}
if (arguments.myname == NULL) {
if (get_myname(myname, sizeof(myname) - 1) < 0) {
ERROR("get_myname");
return -1;
}
arguments.myname = myname;
}
INFO("My name is %s", arguments.myname);
const char *certdir = arguments.certdir;
char certdir_buf[XCE_MAXPATH_LEN + 1];
if (!certdir) {
snprintf(certdir_buf, sizeof(certdir_buf),
"%s/certs", arguments.workdir);
certdir = certdir_buf;
}
snprintf(key_file, XCE_MAXPATH_LEN, "%s/xce.key", certdir);
snprintf(cert_file, XCE_MAXPATH_LEN, "%s/xce.crt", certdir);
myconf.cluster_config = NULL;
myconf.conffile = NULL; /* set by load_primary_config() or fetch_config_from_primary() */
myconf.certfile = cert_file;
myconf.keyfile = key_file;
myconf.workdir = arguments.workdir;
myconf.name = arguments.myname;
myconf.conf_keep_n = arguments.conf_keep_n;
myconf.conf_stale_timeout = (time_t)arguments.conf_stale_timeout;
myconf.vcpus = arguments.vcpus;
myconf.sync_timeout_ms = arguments.sync_timeout_ms;
myconf.wal_sync_ms = arguments.wal_sync_ms;
memset(&myconf.peers_cfg, 0, sizeof(myconf.peers_cfg));
if (arguments.peers_str) {
if (xce_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;
}
/* Req 4.1: reject even-N peer counts (cannot form safe majority) */
if (validate_peer_count(myconf.peers_cfg.candidates_count) != 0) {
fprintf(stderr,
"xce_controller: --peers: even peer count (%d) is not "
"allowed; Raft quorum requires an odd number of nodes "
"(1, 3, 5, ...)\n",
myconf.peers_cfg.candidates_count);
return 1;
}
/* CA cert from certdir (same certificate as certfile) */
snprintf(myconf.peers_cfg.ca_cert_path,
sizeof(myconf.peers_cfg.ca_cert_path),
"%s/xce.crt", certdir);
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);
for (int i = 0; i < myconf.peers_cfg.candidates_count; i++) {
if (myconf.peers_cfg.candidates[i].hostname[0] == '\0' ||
myconf.peers_cfg.candidates[i].port == 0) {
WARN("--peers: candidate %d has empty host or port 0 — "
"check peer string", i);
}
}
} else {
INFO("No --peers flag; running as single-node primary");
}
/* Req 4.2: cert pre-flight — fail early with a clear path in the message */
{
char cert_err[XCE_MAXPATH_LEN + 64];
if (validate_cert_files(cert_file, key_file,
cert_err, sizeof(cert_err)) != 0) {
fprintf(stderr, "xce_controller: %s\n", cert_err);
return 1;
}
}
myapp_run(&myconf);
mylog_fini();
return 0;
}