From 4c5b27cb70b4ce684077e2e18f8beff783cef0b6 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Sun, 14 Jun 2026 14:04:49 +0000 Subject: [PATCH] test(controller): merge host/port into a 'masters' list for failover in enter_ctx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/controller/testsuite/test.c | 55 +++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/src/controller/testsuite/test.c b/src/controller/testsuite/test.c index 915deb1e..ed817f3f 100644 --- a/src/controller/testsuite/test.c +++ b/src/controller/testsuite/test.c @@ -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);