Replaces apiserver's hand-written Go parsePeer() with a CGO wrapper around the shared C parser+validator pair (xce_parse_peers then xce_cluster_config_validate, both src/lib/cluster.c) that worker and scheduler already use from their own main.c's. Gives apiserver the same [name=]host:port[,...] syntax (including the name= prefix the old Go parser could not express) and identical malformed-candidate rejection (empty host, port <= 0, non-numeric port) -- round-1 review caught the first pass skipping the validate() half, silently accepting inputs worker/scheduler both reject. A malformed entry now rejects the whole candidate list rather than being skipped with a warning (matches xce_parse_peers()'s own all-or-nothing construction and worker/scheduler's precedent) -- surfaced loudly via RegisterToBackend's error, not silently. Parsing only -- RegisterToBackend()'s connect loop and tryConnect() are unchanged; collapsing them into a single connect call is task 4.2's job. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NNB4zGPCsHyDUCDJyMbiQN
97 lines
3.6 KiB
Go
97 lines
3.6 KiB
Go
//go:build cgo
|
|
|
|
package backend
|
|
|
|
/*
|
|
#include <stdlib.h>
|
|
#include "xce/cluster.h"
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"fmt"
|
|
"unsafe"
|
|
)
|
|
|
|
// Candidate is one parsed controller candidate: an optional logical name
|
|
// plus the host:port it resolves to. Mirrors xce_candidate_t (xce/cluster.h).
|
|
type Candidate struct {
|
|
Name string
|
|
Host string
|
|
Port int
|
|
}
|
|
|
|
// parseCandidates parses a comma-joined "[name=]host:port,..." candidate
|
|
// list through the same shared C parsing+validation pair (xce_parse_peers
|
|
// then xce_cluster_config_validate, both src/lib/cluster.c) that worker and
|
|
// scheduler already call directly, in that same order, from their C
|
|
// main.c's -- retiring apiserver's own hand-written parsePeer() in favor of
|
|
// it (Req 6.1: apiserver accepts the identical candidate-list syntax
|
|
// scheduler and worker already accept).
|
|
//
|
|
// xce_parse_peers alone is SYNTAX-only: it splits on ','/'='/':' but never
|
|
// rejects a zero/non-numeric port (atoi() silently defaults malformed input
|
|
// to 0) or an empty hostname. xce_cluster_config_validate is what actually
|
|
// rejects those (src/lib/cluster.c:85-100) -- worker/scheduler always call
|
|
// it immediately after a successful xce_parse_peers
|
|
// (src/scheduler/src/main.c:300-309, src/worker/src/main.c:414-422), so
|
|
// this wrapper must too, or apiserver would silently accept candidate
|
|
// strings worker/scheduler reject (Req 6.2).
|
|
// xce_cluster_config_validate also requires cfg->identity to be non-empty;
|
|
// parseCandidates has no real identity to give it at this layer (clib.go's
|
|
// RegisterToBackend assembles that separately, after parsing, from
|
|
// operator-supplied From/Key config) -- a placeholder is stamped here
|
|
// purely to satisfy that one precondition so the per-candidate
|
|
// hostname/port checks actually run. It is never read or reused beyond
|
|
// this call.
|
|
//
|
|
// On any malformed entry -- whether xce_parse_peers' own syntax check or
|
|
// xce_cluster_config_validate's semantic check fails -- the WHOLE string is
|
|
// rejected; neither function identifies which token was the bad one, and
|
|
// neither does this wrapper (Req 6.2): that matches worker's and
|
|
// scheduler's own established behavior, which likewise echoes the full
|
|
// attempted --peers string back on any parse/validate failure (main.c in
|
|
// both) rather than pinpointing one entry.
|
|
//
|
|
// If candidatesCount exceeds MAX_HA_CANDIDATES, xce_parse_peers silently
|
|
// stops reading further tokens (no error, no warning at this layer) --
|
|
// this wrapper does not add stricter behavior than the C functions have.
|
|
func parseCandidates(peersStr string) ([]Candidate, error) {
|
|
cPeers := C.CString(peersStr)
|
|
defer C.free(unsafe.Pointer(cPeers))
|
|
|
|
malformedErr := func() error {
|
|
return fmt.Errorf(
|
|
"apiserver: --peers parse error: expected \"[name=]host:port,...\" got %q",
|
|
peersStr)
|
|
}
|
|
|
|
var cCfg C.xce_cluster_config_t
|
|
if rc := C.xce_parse_peers(cPeers, &cCfg); rc != 0 {
|
|
return nil, malformedErr()
|
|
}
|
|
|
|
// Placeholder identity: satisfies xce_cluster_config_validate's
|
|
// non-empty-identity precondition only -- see doc comment above. cCfg is
|
|
// a fresh zero-valued struct, so the array is already NUL-padded past
|
|
// what we write here.
|
|
const placeholderIdentity = "apiserver-candidates-validate"
|
|
for i := 0; i < len(placeholderIdentity); i++ {
|
|
cCfg.identity[i] = C.char(placeholderIdentity[i])
|
|
}
|
|
if rc := C.xce_cluster_config_validate(&cCfg); rc != 0 {
|
|
return nil, malformedErr()
|
|
}
|
|
|
|
n := int(cCfg.candidates_count)
|
|
out := make([]Candidate, n)
|
|
for i := 0; i < n; i++ {
|
|
cand := &cCfg.candidates[i]
|
|
out[i] = Candidate{
|
|
Name: C.GoString(&cand.name[0]),
|
|
Host: C.GoString(&cand.hostname[0]),
|
|
Port: int(cand.port),
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|