//go:build cgo package backend /* #include #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 }