test(controller): merge host/port into a 'masters' list for failover in enter_ctx

The xce_test client config previously used separate 'host' and 'port' settings
and hard-coded nmasters=1, so it could only target a single controller and
could not exercise controller HA failover.

Merge them into a single 'masters' setting (comma-separated "name=host:port"
or "host:port" entries — the same syntax as the controller --peers flag) and
parse it in enter_ctx() with the shared libxce parser xce_parse_peers(), filling
config.nmasters / config.masters[] / config.master_ports[] (capped at
N_MAX_MASTERS). The test client now fails over across controller candidates
exactly like the worker/scheduler/apiserver.

The parsed list lives in a static xce_cluster_config_t so its hostname buffers
outlive enter_ctx() (xcecli_ctx_init stores the config pointer, it does not copy
masters[]). enter_ctx() now returns -EINVAL on an invalid/empty masters setting
(callers already check rc<0). Default masters='localhost:9990' preserves the
prior single-controller default. Builds clean; 'set' shows the masters setting.
This commit is contained in:
Rongsong Shen 2026-06-14 14:04:49 +00:00
parent 708f2279e3
commit 4c5b27cb70

View file

@ -20,6 +20,7 @@
#include "delay.h"
#include "idgen.h"
#include "preason.h"
#include "xce/cluster.h" /* xce_parse_peers, xce_cluster_config_t */
#include "xce/types.h"
typedef int (*cmd_func_t)(int, char*[]);
@ -120,11 +121,13 @@ static idgen_ctx_t idctx;
delaymgr_t delaymgr;
static setting_t settings[] = {
{ (char*)"host", (char*)NULL, (char*)"localhost" },
{ (char*)"port", (char*)NULL, (char*)"9990" },
{ (char*)"secret", (char*)NULL, (char*)"keepSecret" },
{ (char*)"myname", (char*)NULL, (char*)"localhost" },
{ (char*)NULL, (char*)NULL, (char*)NULL }
/* HA controller list: comma-separated "name=host:port" or "host:port"
* entries (same syntax as the controller --peers flag), enabling the
* test client to fail over across controller candidates. */
{ (char*)"masters", (char*)NULL, (char*)"localhost:9990" },
{ (char*)"secret", (char*)NULL, (char*)"keepSecret" },
{ (char*)"myname", (char*)NULL, (char*)"localhost" },
{ (char*)NULL, (char*)NULL, (char*)NULL }
};
static command_t top_commands[] = {
@ -259,13 +262,45 @@ static char* get_setting(char* key)
static xce_client_config_t config;
/* Holds the parsed HA controller list. Static so the hostname[] buffers it
* owns outlive enter_ctx(): xcecli_ctx_init() stores the config pointer (it
* does not copy masters[]), so config.masters[i] must reference storage that
* persists for the lifetime of the client context. */
static xce_cluster_config_t master_cfg;
int enter_ctx(command_ctx_t* ctx)
{
config.nmasters = 1;
config.masters[0] = get_setting("host");
config.master_ports[0] = atoi(get_setting("port"));
config.from = get_setting("myname");
config.key = get_setting("secret");
char* masters = get_setting("masters");
int i;
/* Parse the controller HA peer list with the shared libxce parser — the
* same "name=host:port,..."/"host:port,..." syntax used by the controller
* --peers flag — so the test client fails over across candidates exactly
* like the worker/scheduler/apiserver do. */
memset(&master_cfg, 0, sizeof(master_cfg));
if ((masters == NULL) || (xce_parse_peers(masters, &master_cfg) != 0) ||
(master_cfg.candidates_count < 1)) {
fprintf(stderr, "enter_ctx: invalid 'masters' setting `%s'\n",
masters ? masters : "(null)");
return -EINVAL;
}
config.nmasters = master_cfg.candidates_count;
if (config.nmasters > N_MAX_MASTERS) {
fprintf(stderr,
"enter_ctx: %d masters configured, using first %d "
"(N_MAX_MASTERS)\n",
config.nmasters, N_MAX_MASTERS);
config.nmasters = N_MAX_MASTERS;
}
for (i = 0; i < config.nmasters; i++) {
config.masters[i] = master_cfg.candidates[i].hostname;
config.master_ports[i] = master_cfg.candidates[i].port;
}
config.from = get_setting("myname");
config.key = get_setting("secret");
xcecli_ctx_init(&(ctx->ctx), &config);
xcecli_set_handler(ctx->ctx, &myreq_handler, NULL);