feat(controller-status): xcecli_ctrlstatus_primary authoritative reach-the-primary read (task 3.2)
This commit is contained in:
parent
691cd9199e
commit
03813bf560
4 changed files with 291 additions and 2 deletions
128
src/lib/client.c
128
src/lib/client.c
|
|
@ -1,6 +1,7 @@
|
|||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "client.h"
|
||||
|
|
@ -9,6 +10,7 @@
|
|||
#include "mylock.h"
|
||||
|
||||
#include "request.h"
|
||||
#include "xce/errnox.h" /* E_XCE_NOT_READY */
|
||||
#include "xce/types.h"
|
||||
#include "xceapi.h"
|
||||
#include "xcectx.h"
|
||||
|
|
@ -336,6 +338,132 @@ int xcecli_ctrlstatus(xcecli_ctx_t c, xce_ctrlstatus_t** status)
|
|||
xce_ctrlstatus(&(ctx->ctx), status));
|
||||
}
|
||||
|
||||
/*
|
||||
* The primary's responder role on the wire is ctrl_role_t PRIMARY. That enum
|
||||
* lives in the controller-private ha.h (CTRL_PRIMARY == 0) and is intentionally
|
||||
* not exposed to libxce; the wire ordinal is part of the ctrlstatus reply
|
||||
* contract (controller/ctrlstatus.c stamps responderRole = ha->role). We pin it
|
||||
* here as a named constant so the comparison reads intentionally rather than as
|
||||
* a bare 0. Any reorder of ctrl_role_t is a Revalidation Trigger for this spec.
|
||||
*/
|
||||
#define XCECLI_CTRLSTATUS_ROLE_PRIMARY ((uint8_t)0)
|
||||
|
||||
bool xcecli_ctrlstatus_is_authoritative(const xce_ctrlstatus_t* status)
|
||||
{
|
||||
if (status == NULL) {
|
||||
return false;
|
||||
}
|
||||
return status->responderRole == XCECLI_CTRLSTATUS_ROLE_PRIMARY;
|
||||
}
|
||||
|
||||
int xcecli_ctrlstatus_primary_addr(const xce_ctrlstatus_t* status,
|
||||
char* host_out, size_t host_sz,
|
||||
uint16_t* port_out)
|
||||
{
|
||||
int i;
|
||||
|
||||
if ((status == NULL) || (host_out == NULL) || (host_sz == 0)
|
||||
|| (port_out == NULL)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* No known primary (empty/NULL primaryName) ⇒ nothing to target; the caller
|
||||
* falls back to plain master rotation (Req 2.3 unknown-primary). */
|
||||
if ((status->primaryName == NULL) || (status->primaryName[0] == '\0')) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/* Map the reported primaryName to its configured address via the reply's
|
||||
* peers list. The list is a small bounded set (configured controllers). */
|
||||
for (i = 0; i < status->peers.count; i++) {
|
||||
xce_ctrlpeer_t* p = status->peers.list[i];
|
||||
|
||||
if ((p == NULL) || (p->name == NULL) || (p->host == NULL)) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(p->name, status->primaryName) != 0) {
|
||||
continue;
|
||||
}
|
||||
if ((p->host[0] == '\0') || (p->port == 0)) {
|
||||
/* named primary has no usable address — treat as absent */
|
||||
break;
|
||||
}
|
||||
|
||||
strncpy(host_out, p->host, host_sz - 1);
|
||||
host_out[host_sz - 1] = '\0';
|
||||
*port_out = (uint16_t)p->port;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* primaryName not present in peers (or no usable address): fall back to
|
||||
* rotation rather than targeting a bogus address. */
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
int xcecli_ctrlstatus_primary(xcecli_ctx_t c, xce_ctrlstatus_t** status)
|
||||
{
|
||||
api_ctx_t* ctx = (api_ctx_t*)c;
|
||||
xce_ctrlstatus_t* hint;
|
||||
int rc;
|
||||
int attempt;
|
||||
int max_attempts;
|
||||
|
||||
if ((c == NULL) || (status == NULL)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Bound the redirect loop by the configured failover retry budget so we
|
||||
* never spin unbounded chasing a never-appearing primary (each inner
|
||||
* xcecli_ctrlstatus already runs its own full reach-some-node failover). */
|
||||
max_attempts = ctx->failover.retry.max_retries;
|
||||
if (max_attempts < 1) {
|
||||
max_attempts = 1;
|
||||
}
|
||||
|
||||
for (attempt = 0; attempt < max_attempts; attempt++) {
|
||||
hint = NULL;
|
||||
|
||||
rc = xcecli_ctrlstatus(c, &hint);
|
||||
if (rc < 0) {
|
||||
/* no reachable controller within the inner budget ⇒ typed failure;
|
||||
* *status untouched (never a hint reply as the result, Req 9.2). */
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (xcecli_ctrlstatus_is_authoritative(hint)) {
|
||||
/* the primary itself answered — authoritative result */
|
||||
*status = hint;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Non-authoritative HINT (standby/electing/init). Direct the next query
|
||||
* at the reported primary if we can map it to an address; otherwise let
|
||||
* the next xcecli_ctrlstatus rotate the configured master list. The
|
||||
* redirect hint is consumed one-shot by xce_failover_next_target. */
|
||||
{
|
||||
char phost[XCE_MAXHOSTNAME_LEN];
|
||||
uint16_t pport = 0;
|
||||
|
||||
if (xcecli_ctrlstatus_primary_addr(hint, phost, sizeof(phost),
|
||||
&pport)
|
||||
== 0) {
|
||||
mylock_lock(&ctx->failover.lock);
|
||||
xce_ctx_capture_redirect_hint(&ctx->ctx, phost, pport);
|
||||
mylock_unlock(&ctx->failover.lock);
|
||||
}
|
||||
}
|
||||
|
||||
/* Free the non-authoritative interim reply before the next attempt — no
|
||||
* leak, and we never return a hint as the authoritative result. */
|
||||
xcecli_free_ctrlstatus(hint);
|
||||
}
|
||||
|
||||
/* Budget exhausted without the primary answering: typed failure, *status
|
||||
* untouched (Req 9.2 — never return a non-primary reply as authoritative).
|
||||
*/
|
||||
return -E_XCE_NOT_READY;
|
||||
}
|
||||
|
||||
int xcecli_notify_jobfinish(xcecli_ctx_t c, xce_jobfinish_t* jfinish)
|
||||
{
|
||||
api_ctx_t* ctx = (api_ctx_t*)c;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue