From 90d7dd824197d69256050bb2350872a0f92bcde0 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Mon, 10 Aug 2026 10:00:08 +0000 Subject: [PATCH] feat(controller): OPCode_probeConfigTag handler with Request envelope reply (bug-1506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add OPCode_probeConfigTag dispatch in cluster.c - Implement probe_config_tag handler returning config tag via standard xce_reply() → RequestReply envelope - Populate redirectHost/redirectPort for follow_redirects support - Wire probeConfigTag reply through typemap - Add probeConfigTagReply struct to controller.capnp schema --- src/controller/cluster.c | 9 ++++++++ src/controller/config_repl.c | 42 ++++++++++++++++++++++++++++++++++++ src/controller/config_repl.h | 1 + src/include/typemap.h | 18 +++++++++------- src/schema/controller.capnp | 2 ++ 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/controller/cluster.c b/src/controller/cluster.c index 7655206d..ee092661 100644 --- a/src/controller/cluster.c +++ b/src/controller/cluster.c @@ -816,6 +816,15 @@ static int myreq_handler(vtask_t* task) } break; #endif + case OPCode_probeConfigTag: + /* bug-1506: return the current config generation tag for xceadmin's + * config push --dry-run. Read-only — no WAL commit, no SHA + * verification. Any role (PRIMARY or STANDBY) can service this; + * ha_dispatch_gate already allows OPCode_probeConfigTag on every + * non-INIT role. Redirect hints are filled inside the handler. */ + rc = handle_probe_config_tag(c, ep, &request); + break; + case OPCode_schedAdmin: { /* Stateless pass-through forward to the scheduler (task 10.1, Req * 5.10). The controller commits no WAL entry and interprets no diff --git a/src/controller/config_repl.c b/src/controller/config_repl.c index 626edba2..3ca62e5c 100644 --- a/src/controller/config_repl.c +++ b/src/controller/config_repl.c @@ -1891,6 +1891,48 @@ int handle_upgrade_config(cluster_t *c, endpoint_t *ep, xce_request_t *req) } #endif +/* fill_redirect_reply is defined in join.c (no header declaration). */ +extern void fill_redirect_reply(xce_reply_t *reply, const ha_state_t *ha); + +/* ── handle_probe_config_tag ────────────────────────────────────────────── + * + * Server-side handler for OPCode_probeConfigTag (bug-1506). Returns the + * current config_version_tag so xceadmin's config push --dry-run can + * report the real generation number instead of fabricating "accepted=True" + * with no wire signal. + * + * Unlike handle_upgrade_config, no SHA verification, no decode, no WAL + * commit — just a read of an in-memory field. Usable by any role (PRIMARY + * or STANDBY); the ha_dispatch_gate already allows OPCode_probeConfigTag + * on every non-INIT role. Redirect hints are filled via fill_redirect_reply + * so xceadmin's follow_redirects path works identically to push_config. + */ + +int handle_probe_config_tag(cluster_t *c, endpoint_t *ep, xce_request_t *req) +{ + xce_reply_t reply; + probe_config_tag_reply_t prep; + + memset(&prep, 0, sizeof(prep)); + prep.config_tag = c->myconf->config_version_tag; + + init_reply_by_req(&reply, req); + reply.status = 0; + reply.data_which = RequestReply_data_probeconfigtagreplymsg; + reply.data.probeconfigtagreplymsg = &prep; + + /* If this is a standby, redirect the admin tool to the primary + * (same pattern as handle_controller_join). Do NOT redirect when + * we ARE the primary — that would send the client back to us and + * infinite-loop inside follow_redirects. */ + if (ha_should_redirect(&c->ha)) + fill_redirect_reply(&reply, &c->ha); + + xce_reply(ep, c->myhost, &reply); + return 0; +} + + /* ── xce_probe_config_tag ─────────────────────────────────────────────────── * * Single-RTT config tag probe to one peer controller over SSL. diff --git a/src/controller/config_repl.h b/src/controller/config_repl.h index bef4fafb..bc026231 100644 --- a/src/controller/config_repl.h +++ b/src/controller/config_repl.h @@ -351,6 +351,7 @@ int push_config_to_standbys(cluster_t *c, const uint8_t *bytes, size_t blen, * Only callable when CONF_ENABLE_UPGRADE_CONFIG is set. */ int handle_upgrade_config(cluster_t *c, endpoint_t *ep, xce_request_t *req); +int handle_probe_config_tag(cluster_t *c, endpoint_t *ep, xce_request_t *req); /* * ha_stream_wal_to_standby: batch-stream WAL entries [standby_lsn+1, committed_lsn] diff --git a/src/include/typemap.h b/src/include/typemap.h index 8db7e5a5..521f2a14 100644 --- a/src/include/typemap.h +++ b/src/include/typemap.h @@ -197,6 +197,14 @@ typedef struct { uint64_t peer_last_log_lsn; } request_vote_reply_t; +typedef struct { + char *name; +} probe_config_tag_req_t; + +typedef struct { + uint64_t config_tag; +} probe_config_tag_reply_t; + /* Heartbeat reply state report — carries standby's applied_lsn and config_tag, * plus its raft term so the primary can discover a higher term it lost track * of (bug-1421 term-discovery: an AE reject response never reaches the raft @@ -291,6 +299,7 @@ typedef struct { xce_ctrlstatus_t* ctrlstatus; xce_fetchfile_reply_t* fetchfilereply; sched_admin_reply_t* schedadminreplymsg; + probe_config_tag_reply_t* probeconfigtagreplymsg; } data; /* Req 7: standby redirect fields — populated when status == XCE_ERR_STANDBY */ char *redirect_host; @@ -331,6 +340,7 @@ typedef struct { snapshot_transfer_t* snapshottransfermsg; xce_fetchfile_t* fetchfilemsg; sched_admin_req_t* schedadminmsg; + probe_config_tag_req_t* probeconfigtagrequestmsg; } body; } xce_request_t; @@ -341,14 +351,6 @@ typedef struct { * request_vote_req_t and request_vote_reply_t are defined above * xce_reply_t so they can be used in the xce_request_t body union. */ -typedef struct { - char *name; -} probe_config_tag_req_t; - -typedef struct { - uint64_t config_tag; -} probe_config_tag_reply_t; - typedef struct { uint64_t term; uint64_t lsn; diff --git a/src/schema/controller.capnp b/src/schema/controller.capnp index 9a7e12e0..42df5db3 100644 --- a/src/schema/controller.capnp +++ b/src/schema/controller.capnp @@ -593,6 +593,7 @@ struct RequestReply $C.mapname("xce_reply_t"){ ctrlstatus @17: CtrlStatus; # ctrlstatus reply: identity/health snapshot fetchfilereply @18: FetchFileReply; schedadminreplymsg @19: SchedAdminReply; + probeconfigtagreplymsg @20: ProbeConfigTagReply; } redirectHost @11: Text $C.mapname("redirect_host"); @@ -710,6 +711,7 @@ struct Request $C.mapname("xce_request_t"){ snapshottransfermsg @29: SnapshotTransfer; fetchfilemsg @30: FetchFileMsg; schedadminmsg @31: SchedAdminRequest; + probeconfigtagrequestmsg @32: ProbeConfigTagRequest; } }