feat: P0-1e expand handleConfigPush with host config and cluster dispatch

Wires the remaining cluster_config_t data into the notification pipeline:
- dispatchConfigPush() extracted as a pure-Go helper (CGO-free, testable)
  that drives OnQueueUpdated, OnResourceUpdated, OnHostConfigUpdated, and
  OnClusterUpdated from a single config push event
- handleConfigPush() delegates to dispatchConfigPush via the new helper,
  eliminating the previous inline loops
- OnHostConfigUpdated and OnClusterUpdated added to NotificationHandler
  interface and implemented with partial-merge semantics in DefaultHandler
- rawHostConfigData/rawHostConfigToModel and rawClusterData/rawClusterToModel
  added to convert_pure.go with their CGO extraction counterparts in convert.go

Safety fixes from code review:
- OnHostUpdated, OnHostConfigUpdated, OnClusterUpdated now copy *incoming to
  a local merged value before mutation, closing the TOCTOU window between
  store.Get and store.Put under concurrent pushes
- cClusterConfQueues and cClusterConfResources gain nil-pointer guards
  matching the existing guard in cClusterConfHostConfigs
- handleConfigPush gains an explicit gHandler nil check
- cClusterConfToClusterInfo returns nil on empty clustername (prevents phantom
  empty-key write to store.Clusters); handleConfigPush logs a warning when nil
- 36 unit tests passing (CGO_ENABLED=0)
This commit is contained in:
Rongsong Shen 2026-04-16 11:25:03 +00:00
parent 00c4472d6c
commit 893812e7aa
8 changed files with 850 additions and 11 deletions

View file

@ -0,0 +1,117 @@
# API Server Backend Enhancement — Task Tracker
## Status Legend
- [x] Completed
- [ ] Pending
- [>] In Progress (blocked)
---
## P0-1: Handle Cluster Config Push
- [x] **#1 P0-1a: Add HostConfig + Cluster fields to models**
- `models/clusters.go` — Added `HeartbeatInterval`, `Vcpus`, `InactiveHeartbeatCount` to `ClusterInfo`
- `models/hosts.go` — Changed `HostStateStr(0)` from `"OK"` to `"UNKNOWN"`
- `models/clusters_test.go` — New: verifies new fields serialize + existing fields unchanged
- `models/hosts_test.go` — New: verifies `HostStateStr` for zero, flags, non-flag, combined
- All 15 tests passing
- [x] **#2 P0-1b: Add pure Go conversion functions + TDD tests**
- `backend/convert_pure.go` — Added `rawHostConfigData` + `rawHostConfigToModel()`, `rawClusterData` + `rawClusterToModel()`
- `backend/convert_test.go` — 8 new table-driven tests: zero-value ints, empty strings, normal values, worker/config-sourced field isolation
- `TODO(dispatchwindow)` comment added per plan spec
- `DisptWindow` guard test added to catch accidental future CGO layer changes
- All 26 backend tests passing
- [x] **#3 P0-1c: Add CGO conversion layer in convert.go**
- `backend/convert.go` — Added `cHostConfigToModel()`, `cClusterConfHostConfigs()`, `cClusterConfToClusterInfo()`
- `cResdefToModel` intentionally skipped — `xce_resdef_t` doesn't exist in C codebase and `ResourceInfo` has no store/consumer (YAGNI); existing `cResourceToModel` → `ResourceDefinitionInfo` already handles config push
- `cClusterConfResources` already existed — no change needed
- CGO compilation verified passing
- All unit tests still passing
- [x] **#4 P0-1d: Implement handlers with partial merge + TDD**
- `backend/notification.go` — Added `OnClusterUpdated(cluster *models.ClusterInfo)` and `OnHostConfigUpdated(host *models.HostInfo)` to `NotificationHandler` interface
- `backend/handler.go` — Implemented partial-merge `OnClusterUpdated` (preserves Version, Status, ControllerName, NumServers, Resources, HostTypes, HostModels, ClusterAdmins), `OnHostConfigUpdated` (preserves HostStatus, CPUFactor, NumCPUs, NumCores, NumThreads, MaxMem, MaxSwap, MaxTmp, NumDisk, NumRun, NumSSusp, NumUSusp, NumDone, Topology, SchedStop), fixed `OnHostUpdated` (preserves HostModel, HostType, MaxSlot, DisptWindow via two-value `Get()`)
- `backend/handler_test.go` — 6 new tests: new host, partial merge for each of OnHostConfigUpdated, OnClusterUpdated, OnHostUpdated
- All backend tests passing (32 total)
- [x] **#5 P0-1e: Expand handleConfigPush** (unblocked)
- `backend/clib.go` — Expand `handleConfigPush()` to call `cClusterConfHostConfigs()` and `cClusterConfToClusterInfo()`
- Call `gHandler.OnHostConfigUpdated()` per host config, `gHandler.OnClusterUpdated()` for cluster settings
---
## P0-2: Host Control via Backend
- [ ] **#6 P0-2a: C-side plumbing for host control**
- `schema/controller.capnp` — Add `HostControl` struct + `hostctrlmsg @21`
- `include/typemap.h` — Add `xce_hostctrl_t* hostctrlmsg;` to body union
- `include/client.h` — Add `xcecli_hostctrl()` declaration
- `include/xceapi.h` — Add `xce_hostctrl()` declaration
- `include/request.h` — Add `xce_hostctrl_req()` declaration
- `lib/client.c` — Implement `xcecli_hostctrl()`
- `lib/xceapi.c` — Implement `xce_hostctrl()` with `cbuf_get()` wait
- `lib/request.c` — Implement `xce_hostctrl_req()` (validate HOST_OPEN/HOST_CLOSE only)
- `lib/helper.c` — Add `Request_body_hostctrlmsg` case to `xce_request_body()` + `xce_request_msg_name()`
- `controller/cluster.c` — Add `OPCode_hostcontrol` case → `do_hostcontrol_op()`
- `controller/worker.c` — Implement `do_hostcontrol_op()` calling `worker_ctrl()`
- `controller/include/cluster.h` — Declare `do_hostcontrol_op()`
- Regenerate capnp: `ninja -C build`
- [>] **#7 P0-2b: Implement Go-side ControlHost + fix allFlag race** (blocked by #6)
- `backend/clib.go` — Replace `ControlHost()` stub with real implementation via `xcecli_hostctrl()`
- `controllers/hosts.go` — Fix data race: remove `var allFlag bool`, return from `preCheckHostControlRequest` as local
- `controllers/queues.go` — Same allFlag data race fix (lines 534-536 / 465)
- [ ] **#8 P0-2c: Register routes + RBAC for host control/select**
- `router/routers.go` — `POST /xce/v1/hosts/control` → RBAC `host:control` (cluster_admin)
- `router/routers.go` — `POST /xce/v1/hosts/select` → RBAC `host:view` (all authenticated)
- `config/rbac.yaml` — Add `host:control` and `host:view` action entries
- [ ] **#9 P0-2d: Update stub.go with new function stubs**
- `backend/stub.go` — Keep `ControlHost` stub (return `errNoCGO`), add `StartHeartbeat(ctx context.Context){}`, `SetHeartbeatInterval(secs int){}`, `EvalSelectForHost(...)` returning `errNoCGO`, `Cleanup(){}`
- Add `"context"` to import block
---
## P1-1: Handle Resource Value Push
- [ ] **#10 P1-1: Handle resource value push (OPCode_notify)**
- `backend/clib.go` — Add CGO val helpers to preamble (`xce_val_which`, `xce_val_i32`, etc.)
- `backend/clib.go` — Add `OPCode_notify` case at top-level switch in `go_push_handler()` (NOT inside `handlePush()`)
- `backend/clib.go` — Add `handleNotifyData()` for `NotifyData_data_schedUpdateResvals`
- `backend/convert.go` — Add `cResvalToDistribution()` — switch on `Value_u_*` constants, all 7 cases separate
---
## P1-2: Host Resource Requirement Select
- [ ] **#11 P1-2a: C select expression parser (PEG + eval)**
- `include/resreq.h` — Replace empty `xce_resreq_select_` with expression tree types, add enums, add extern declarations
- `lib/xce_select.peg` — New PEG grammar for select boolean expressions (right-recursive)
- `lib/resreq.c` — Implement `parse_select()`, `eval_select()`, `free_select()`; update `kill_primary_resreq()`
- `lib/meson.build` — Add `xce_select.peg` to explicit source list
- [>] **#12 P1-2b: Go EvalSelectForHost + FilterHostByResReqSelect** (blocked by #11)
- `backend/clib.go` — Add `EvalSelectForHost()` using `cgo.Handle` for callback context, add `#include "resreq.h"`, extern declarations, `xce_select_destroy` helper
- `controllers/hosts.go` — Implement `FilterHostByResReqSelect`, update `preCheckHostRequest()` limit from 4094→1024 bytes
---
## P1-3: Heartbeat Keep-Alive
- [ ] **#13 P1-3: Heartbeat keep-alive implementation**
- `backend/clib.go` — Add `heartbeatIntervalSecs int32` (default 30), `SetHeartbeatInterval()`, `heartbeatFn` var, `StartHeartbeat(ctx context.Context)`
- `backend/handler.go` — `OnClusterUpdated` calls `SetHeartbeatInterval(cluster.HeartbeatInterval)` after store update
- `cmd/main.go` — Call `backend.StartHeartbeat(ctx)` after `backend.RegisterToBackend(...)`
---
## Final
- [>] **#14 Build and test verification** (blocked by all others)
- `make test` in `src/frontend/` passes
- `ninja -C build` for C components passes
- RBAC verification
- Input safety verification (1024-byte limit, 256-host limit)