586 lines
36 KiB
Markdown
586 lines
36 KiB
Markdown
# Brief: admin-config-tool
|
||
|
||
## Problem
|
||
|
||
XCE operators have no tool to build and deploy cluster configuration. Currently:
|
||
|
||
- **Config authoring**: YAML config files (`configs/hosts.yaml`, `queues.yaml`, `resources.yaml`, `users.yaml`) exist as documentation examples with JSON schemas for validation (`configs/schema/*.schema.json`), but **no tool compiles them** into the `ClusterConfig` Cap'n Proto binary that the controller expects.
|
||
- **Config deployment**: `OP_upgradeConfig` exists on the controller side (`config_repl.c:1536` — receives binary blob, verifies SHA-256, saves, pushes to standbys, awaits majority ACK, signals restart), but **no CLI sends it**. Today: manual scp + restart.
|
||
|
||
End users use `xcecli` (Go/Cobra → REST → apiserver → CGO → controller) for non-admin operations. There is no admin-facing tool.
|
||
|
||
**Who**: XCE cluster operators. **Pain**: manual config management, error-prone YAML→binary translation, no primary auto-discovery.
|
||
|
||
## Current State
|
||
|
||
| What | Where | Status |
|
||
|------|-------|--------|
|
||
| `ClusterConfig` capnp struct | `src/schema/controller.capnp:710-732` | 21 fields, wire-ready |
|
||
| YAML config examples | `configs/{hosts,queues,resources,users}.yaml` | Human-authored, NOT loaded by any code |
|
||
| JSON schemas | `configs/schema/*.schema.json` | JSON Schema 2020-12 |
|
||
| `OP_upgradeConfig` handler | `config_repl.c:1536` `do_upgrade_config_with_bytes()` | Receives binary, verifies, pushes, awaits majority |
|
||
| `UpgradeConfigRequest/Reply` | `controller.capnp:792-797` | Defined in schema |
|
||
| libxce upgrade API | `request.c:1046` has extractors only | No send function |
|
||
| Controller wire protocol | TCP+TLS, capnp-encoded `Request`/`Reply`, same CA as controller-to-controller | Exists, used by all C daemons |
|
||
|
||
**Key gap**: YAML → ClusterConfig binary tool does not exist. Upload CLI does not exist.
|
||
|
||
## Desired Outcome
|
||
|
||
Single `xceadmin` Python CLI that operators use for cluster configuration management:
|
||
|
||
```
|
||
# Build: auto-discover YAML files + schemas from standard directory layout
|
||
xceadmin config build configs/ # default output: cluster.bin
|
||
xceadmin config build configs/ -o cluster.bin # explicit output
|
||
xceadmin config build configs/ -o - # stdout (for piping)
|
||
|
||
# Push: zero-config dev (same as xce_test), single --controller for production
|
||
xceadmin config push cluster.bin # default: localhost:9991
|
||
xceadmin config push --controller ctrl-1:9991 cluster.bin # production: any one controller
|
||
```
|
||
|
||
**Standard config directory layout** (auto-discovered):
|
||
```
|
||
configs/
|
||
├── hosts.yaml # optional — missing = empty hosts section
|
||
├── queues.yaml # optional
|
||
├── resources.yaml # optional
|
||
├── users.yaml # optional
|
||
└── schema/ # required for validation
|
||
├── hosts.schema.json
|
||
├── queues.schema.json
|
||
├── resources.schema.json
|
||
└── users.schema.json
|
||
```
|
||
|
||
- **config build**: Scan directory → validate present YAML files against corresponding JSON schemas → map to `ClusterConfig` capnp struct → serialize to `cluster.bin` → output SHA-256. Missing files = empty section (not error). Schema directory must exist.
|
||
- **config push**: Read `cluster.bin` → connect to controller (default `localhost:9991`) → probe current `configGeneration` → if binary's generation ≤ current, increment to `current + 1` and re-encode (controller rejects non-increasing generations as no-op) → controller reply includes peer list + primary address → follow redirect if needed → send capnp `Request{OP_upgradeConfig, UpgradeConfigRequest{sha256, bytes}}` → receive `Reply` → display `{versionTag, ackedCount, error}`. Admin can force a specific generation with `--generation N`.
|
||
- **Shared package**: `xcecommon` Python package with capnp schema handling, wire protocol, and config models — reusable by future Python-based tools
|
||
|
||
## Approach
|
||
|
||
**Chosen: Python CLI with single dependency on `controller.capnp`.**
|
||
|
||
```
|
||
src/schema/controller.capnp ← single source of truth
|
||
│
|
||
├──▶ c-capnproto → libxce → controller (C daemon, existing)
|
||
│
|
||
└──▶ pycapnp → xceadmin (Python, new)
|
||
│
|
||
├── config build: YAML → ClusterConfig → bytes → cluster.bin
|
||
└── config push: Request{OP_upgradeConfig, ...} → TLS → Reply
|
||
```
|
||
|
||
- **Language**: Python. YAML parsing trivial (`yaml`), JSON Schema validation (`jsonschema`), capnp encoding (`pycapnp`), CLI (`click` or `argparse`).
|
||
- **Protocol**: TCP+TLS directly to controller (NOT through apiserver). Zero-config dev experience — default `localhost:9991`, same as xce_test. Single `--controller` flag for production override.
|
||
- **Dependencies**: `controller.capnp` schema file only. Zero CGO, zero libxce. Pure Python, open-source ready.
|
||
- **Auto primary detection**: Connect to given controller → controller reply carries peer list + primary address (standby redirect, already in reply schema) → follow redirect to primary → send request. One controller is enough to bootstrap full cluster discovery. Same mechanism xce_test uses
|
||
- **No CGO**: Wire protocol is TLS connect → send one capnp `Request` → receive one capnp `Reply` → exit. ~150 lines of Python. No iomgr event loop, no vtask thread pool, no persistent connections — those are for daemons, not one-shot CLI tools.
|
||
|
||
### Architecture Decisions (from 2026-07-06 discovery session)
|
||
|
||
| Decision | Rationale |
|
||
|----------|-----------|
|
||
| **Python** over Go | Future open-source release; shared capnp infra with planned Python xcecli rewrite; `pip install` no compilation |
|
||
| **Pure Python wire** over CGO+libxce | Open-source friendly; no C compilation; wire protocol is one-shot send/receive (~150 lines) |
|
||
| **Direct controller** over apiserver | Admin path needs no RBAC middleware; apiserver may be restarting during config upgrade; controller SSL is same CA |
|
||
| **Single `controller.capnp` dependency** | Schema IS the protocol contract; versioned; no extra API surface |
|
||
| **Separate `xceadmin` binary** over extending xcecli | `xcecli` = user tool (HTTP → apiserver); `xceadmin` = operator tool (TLS → controller); different audiences, different protocols |
|
||
| **Auto primary detection** | Operator shouldn't need to know which controller is primary; connect to any one, discover peers + primary from reply |
|
||
| **Config build offline** | YAML→binary is pure data transform; no controller connection needed; cluster.bin can be pre-built, reviewed, version-controlled |
|
||
|
||
### xcecli vs xceadmin
|
||
|
||
| | xcecli | xceadmin |
|
||
|---|--------|----------|
|
||
| **Audience** | End users | Operators |
|
||
| **Protocol** | HTTP/HTTPS (REST, JSON) | TCP+TLS (capnp binary) |
|
||
| **Target** | apiserver | controller (primary) |
|
||
| **Auth** | JWT token (login → apiserver) | None — same zero-config TLS as xce_test |
|
||
| **Auth middleware** | Full: WhitelistURL→WhitelistIP→RateLimit→JWT→CORS→RBAC | None — direct TCP+TLS, same trust model as controller peers |
|
||
| **Use cases** | Submit jobs, list queues, check status | Push config, cluster admin, force snapshot |
|
||
| **Language** | Go/Cobra (current), Python (future) | Python |
|
||
|
||
## Project Decomposition (from 2026-07-06 discovery)
|
||
|
||
This discovery session (Path E — mixed decomposition) identified 3 workstreams:
|
||
|
||
| # | Workstream | Type | Language | Dependencies |
|
||
|---|-----------|------|----------|--------------|
|
||
| 1 | **admin-config-tool** | NEW spec | Python | `controller.capnp` only |
|
||
| 2 | **config-upgrade-snapshot** | EXISTING spec extension | C | entity-bearing-snapshots (blocked upstream) |
|
||
| 3 | **perf-tuning-extraction** | DIRECT work | doc | None |
|
||
|
||
**Dependency flow:**
|
||
```
|
||
admin-config-tool (standalone, ships first)
|
||
│
|
||
│ config push triggers controller-side path
|
||
▼
|
||
config-upgrade-snapshot (controller: replicate → snapshot → restart → activate)
|
||
│
|
||
│ blocked on
|
||
▼
|
||
entity-bearing-snapshots (upstream blocker, tracked in roadmap)
|
||
```
|
||
|
||
## Scope
|
||
|
||
- **In**:
|
||
- `xceadmin config build`: YAML parsing, JSON Schema validation, capnp ClusterConfig serialization, cluster.bin output with SHA-256. `configGeneration` defaults to 0 (offline-safe).
|
||
- `xceadmin config push`: single controller address (default `localhost:9991`), probe current configGeneration from controller, auto-increment if needed, auto primary detection via peer-list-from-reply, TLS+capnp wire protocol, capnp UpgradeConfigRequest/Reply, result display. `--generation N` flag for explicit override.
|
||
- `xceadmin config diff`: (nice-to-have) decode two cluster.bin files, human-readable field-by-field diff. Pre-push safety: "what will this config change actually do?"
|
||
- `xceadmin config push --dry-run`: (nice-to-have) validate controller will accept the config without committing it
|
||
- `xcecommon` shared Python package: capnp schema loading, wire protocol (TLS+capnp framing), config model types
|
||
- Build system integration: pip-based install (editable for dev, pinned for release), nix dev shell packages, xce-build.sh component target
|
||
- Skills integration (see Skill Integration section below):
|
||
- `xce-build`: add `xceadmin` component target (Python package build + test)
|
||
- `xce-deploy-ha`: replace manual `ha/data/cluster.bin` prerequisite with `xceadmin config build` step; add `--build-config` flag for on-the-fly YAML→binary generation
|
||
- `xce-test-ha`: (future) config push validation round in soak loop — deferred until `config-upgrade-snapshot` lands on controller side
|
||
- Validation: reject configs using reserved name `"lost_and_found"` for queue/host (defense-in-depth — controller enforces at runtime via FATAL, tool prevents at authoring time)
|
||
- **Out**:
|
||
- Controller-side config replication/snapshot/restart logic (owned by `config-upgrade-snapshot`)
|
||
- Entity-bearing snapshot payload (owned by `entity-bearing-snapshots`, upstream blocker)
|
||
- Performance tuning parameter extraction (separate direct work item)
|
||
- xcecli Python rewrite (future, separate spec)
|
||
- xce-test-ha config push validation round (deferred until `config-upgrade-snapshot` lands — config push to a controller that can't replicate/snapshot is a no-op from the soak's perspective)
|
||
|
||
## Skill Integration
|
||
|
||
Three existing skills touch `cluster.bin` — the binary config blob that `xceadmin config build` produces. Currently `cluster.bin` is a hand-crafted opaque artifact with no visible provenance. `xceadmin` replaces that with a reproducible build step.
|
||
|
||
### xce-build — add `xceadmin` component
|
||
|
||
New component target for the Python package:
|
||
|
||
```
|
||
/xce-build xceadmin # pip install -e . (editable, for dev)
|
||
/xce-build xceadmin release # pip install . (pinned, for production)
|
||
```
|
||
|
||
Build path differs from C components: Python, not C. Runs inside nix dev shell (pycapnp needs capnproto C++ runtime from nix). Build output: `xceadmin` CLI entry point + `xcecommon` package importable.
|
||
|
||
This is the **blocking** integration — other skills can't use xceadmin until this lands.
|
||
|
||
### xce-deploy-ha — generate cluster.bin from YAML
|
||
|
||
**Current flow:**
|
||
```
|
||
ha/data/cluster.bin (pre-built, manual, opaque) → deploy-ha.sh → controller/ctrl-1/conf/
|
||
```
|
||
|
||
**New flow:**
|
||
```
|
||
configs/*.yaml → xceadmin config build → cluster.bin → deploy-ha.sh → ...
|
||
```
|
||
|
||
Changes to the skill:
|
||
|
||
| What | Before | After |
|
||
|------|--------|-------|
|
||
| Prerequisites | `cluster.bin` must exist under `ha/data/` | `cluster.bin` built from `configs/*.yaml` via `xceadmin config build` |
|
||
| Config build step | None (manual) | `xceadmin config build configs/ -o ha/data/cluster.bin` (auto, or skip if `--use-existing-config`) |
|
||
| `--build-config` flag | N/A | New flag: force rebuild cluster.bin from YAML before deploy |
|
||
|
||
`deploy-ha.sh` gains an optional precondition: if `cluster.bin` missing or `--build-config` passed, run `xceadmin config build`. Otherwise use existing `cluster.bin` as-is (backward compatible — operators can still hand-craft for custom configs).
|
||
|
||
### xce-test-ha — config push validation (future)
|
||
|
||
Deferred until `config-upgrade-snapshot` lands on controller side. When ready:
|
||
|
||
- **New soak round type**: kill primary, push new config via `xceadmin config push`, verify controller accepted + standbys received
|
||
- **Coverage**: exercises `OP_upgradeConfig` → replication → majority ACK path under failover
|
||
- **Verdict**: config push failure = FAIL (config consistency is a data-safety property)
|
||
|
||
Not scoped into admin-config-tool tasks — belongs to `config-upgrade-snapshot` spec. Listed here for cross-spec visibility.
|
||
|
||
### Integration dependency order
|
||
|
||
```
|
||
xce-build (+xceadmin) ← THIS SPEC, blocking
|
||
│
|
||
▼
|
||
xce-deploy-ha ← THIS SPEC, immediate after xce-build
|
||
│
|
||
▼
|
||
xce-test-ha ← config-upgrade-snapshot spec (future)
|
||
(config push round) blocked on entity-bearing-snapshots
|
||
```
|
||
|
||
## Harness Enhancement
|
||
|
||
The `xceadmin` tool is Python — first Python component in a C/Go project. This requires harness changes across the development toolchain.
|
||
|
||
### xce-build.sh — new `xceadmin` component
|
||
|
||
Add a new case to `src/tools/xce-build.sh`:
|
||
|
||
```bash
|
||
xceadmin)
|
||
echo "=== Building xceadmin Python package ==="
|
||
pip install -e . --quiet
|
||
echo "=== Running xceadmin tests ==="
|
||
python -m pytest tests/ -v
|
||
;;
|
||
```
|
||
|
||
Python path — no meson setup. `pip install -e .` for dev (editable), `pip install .` for release. Tests via `pytest`, not meson test. Runs inside nix dev shell (capnproto C++ runtime needed for pycapnp compile). The `release` profile uses `pip install .` without `-e`.
|
||
|
||
### xce-build SKILL.md — add component + usage note
|
||
|
||
- **Usage line**: `Components: all (default), lib, controller, scheduler, worker, apiserver, xcecli, xceadmin`
|
||
- **Profile notes**: `xceadmin` ignores meson profiles — it always uses pip. The profile arg affects C components only; xceadmin builds regardless of profile.
|
||
- **Examples**: `/xce-build xceadmin` (editable), `/xce-build xceadmin release` (pinned)
|
||
|
||
### xce-deploy-ha SKILL.md — config build step
|
||
|
||
Changes to the skill document:
|
||
|
||
- **Prerequisites**: replace "TLS certs and a `cluster.bin` must exist under `ha/data/`" with "TLS certs must exist under `ha/data/`. `cluster.bin` is built from `configs/*.yaml` via `xceadmin config build` (or use existing if `--use-existing-config`)."
|
||
- **New `--build-config` flag**: forces rebuild of `cluster.bin` from YAML before deploy. Without it, uses existing `cluster.bin` as-is (backward compatible).
|
||
- **New step** (between resolve paths and stop running): optional `xceadmin config build configs/ -o ha/data/cluster.bin` if `--build-config` passed or `cluster.bin` missing.
|
||
- **Troubleshooting**: add "`pycapnp` import error" → run `/xce-build xceadmin` first.
|
||
|
||
### settings.local.json — Python permissions
|
||
|
||
Add Bash permission allowlist entries for xceadmin development:
|
||
|
||
```json
|
||
"Bash(pip install *)",
|
||
"Bash(python -m pytest *)",
|
||
"Bash(xceadmin config build *)",
|
||
"Bash(xceadmin config push *)",
|
||
"Bash(ruff format *)",
|
||
"Bash(ruff check *)"
|
||
```
|
||
|
||
### PostToolUse hooks — Python formatting + lint
|
||
|
||
Add a ruff format hook for Python file edits (`settings.json`):
|
||
|
||
```json
|
||
{
|
||
"matcher": "Write|Edit",
|
||
"hooks": [
|
||
{
|
||
"type": "command",
|
||
"command": "[[ \"$FILE_PATH\" == *.py ]] && ruff format --quiet \"$FILE_PATH\" || true",
|
||
"timeout": 5
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Add a ruff check hook for lint feedback:
|
||
|
||
```json
|
||
{
|
||
"matcher": "Write|Edit",
|
||
"hooks": [
|
||
{
|
||
"type": "command",
|
||
"command": "[[ \"$FILE_PATH\" == *.py ]] && ruff check --quiet \"$FILE_PATH\" || true",
|
||
"timeout": 5
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### PreToolUse hook — YAML schema guard (nice-to-have)
|
||
|
||
Validate config YAML files against JSON schemas before accepting edits. Prevents commits of invalid config:
|
||
|
||
```json
|
||
{
|
||
"matcher": "Write|Edit",
|
||
"hooks": [
|
||
{
|
||
"type": "command",
|
||
"command": "[[ \"$FILE_PATH\" == configs/*.yaml ]] && xceadmin config validate \"$FILE_PATH\" || true",
|
||
"timeout": 5
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
This requires `xceadmin config validate` subcommand (nice-to-have: validates a single YAML file against its schema, no binary output). Deferred to a follow-up task after core `config build` lands.
|
||
|
||
### `/xce-admin` skill (deferred)
|
||
|
||
Thin skill wrapper around `xceadmin config build/push` for agent-driven workflows. Not needed for spec implementation — CLI is sufficient. Deferred until CLI is stable and operator workflows are validated.
|
||
|
||
### Harness change summary
|
||
|
||
| # | What | Where | Priority | Phase |
|
||
|---|------|-------|----------|-------|
|
||
| 1 | `xceadmin` case in build script | `src/tools/xce-build.sh` | **Blocking** | Implementation |
|
||
| 2 | Component list + pip note | `.claude/skills/xce-build/SKILL.md` | **Blocking** | Implementation |
|
||
| 3 | `--build-config` + config build step | `.claude/skills/xce-deploy-ha/SKILL.md` | **Blocking** | Implementation |
|
||
| 4 | pip/pytest/xceadmin permissions | `.claude/settings.local.json` | **Blocking** | Implementation |
|
||
| 5 | ruff format hook on `.py` edits | `.claude/settings.json` | Nice-to-have | Implementation |
|
||
| 6 | YAML schema guard on config edits | `.claude/settings.json` | Nice-to-have | Follow-up |
|
||
| 7 | `/xce-admin` skill wrapper | `.claude/skills/xce-admin/SKILL.md` | Deferred | Post-spec |
|
||
|
||
## Boundary Candidates
|
||
|
||
- **`xceadmin` CLI** — operator-facing commands, argument parsing, output formatting
|
||
- **`xcecommon` Python package** — shared capnp schema loading, wire protocol (TLS+capnp), config model types. Reusable by future Python xcecli.
|
||
- **`config-build` module** — YAML→ClusterConfig mapping, JSON Schema validation, serialization
|
||
- **`config-push` module** — primary auto-detection, TLS+capnp send/receive, result parsing
|
||
|
||
## Upstream / Downstream
|
||
|
||
- **Upstream**: `controller.capnp` schema (source of truth for ClusterConfig, Request/Reply, OP codes)
|
||
- **Downstream**: Controller `OP_upgradeConfig` handler (`config_repl.c` — already exists)
|
||
- **Consumed by**:
|
||
- `config-upgrade-snapshot` spec — xceadmin is the trigger; controller-side handles replication/snapshot/restart
|
||
- `xce-build` skill — adds `xceadmin` component target (blocking integration)
|
||
- `xce-deploy-ha` skill — `xceadmin config build` replaces manual `cluster.bin` prerequisite
|
||
- `xce-test-ha` skill — (future) config push validation round, deferred until config-upgrade-snapshot lands
|
||
- **Future consumer**: Python xcecli rewrite (imports `xcecommon` for capnp schema types)
|
||
|
||
## Constraints
|
||
|
||
- **Python ≥ 3.10** (match system Python, avoid bleeding-edge features)
|
||
- **pycapnp** requires capnproto C++ runtime (`libcapnp-dev`) — separate from project's c-capnproto (C bindings). Both capnp runtimes can coexist; binary format is standardized.
|
||
- **Zero-config TLS** — same dev experience as xce_test: default `localhost:9991`, no cert flags needed. Controller peer trust model (X.509 CN validation via `validate_host()` — tracked in roadmap hardening).
|
||
- **No dependency on a running apiserver** — admin tool connects directly to controller. Config deployment works even when apiserver is down or restarting.
|
||
- **Open-source readiness** — pure Python, `pip install` friendly, no compilation step, clear single-schema dependency.
|
||
|
||
## Nix Environment
|
||
|
||
`xceadmin` is the first Python component in the XCE project. The nix dev shell currently provides C/C++ tooling only — zero Python packages. All needed Python packages are available in nixpkgs.
|
||
|
||
### Packages to add
|
||
|
||
Current `src/nixSupport/xce.nix` `nativeBuildInputs` already includes `capnproto` (line 21) — the C++ runtime that pycapnp wraps. Need to add:
|
||
|
||
| Package | Nix attr | Purpose | Size |
|
||
|---------|----------|---------|------|
|
||
| Python 3 | `python3` | Interpreter (≥ 3.10) | ~50MB |
|
||
| pycapnp | `python3Packages.pycapnp` | capnp schema → Python types | small |
|
||
| PyYAML | `python3Packages.pyyaml` | YAML config parsing | small |
|
||
| jsonschema | `python3Packages.jsonschema` | JSON Schema validation | small |
|
||
| click | `python3Packages.click` | CLI argument parsing | small |
|
||
| pytest | `python3Packages.pytest` | Test framework | small |
|
||
| ruff | `python3Packages.ruff` | Python formatter + linter | ~10MB |
|
||
|
||
All confirmed available in nixpkgs (unstable): pycapnp 2.2.3, pyyaml 6.0.3, jsonschema 4.26.0, click 8.3.1, pytest 9.0.3, ruff 0.15.20.
|
||
|
||
### Where to add
|
||
|
||
Two options:
|
||
|
||
**Option A: Add to `xce.nix`** (recommended). Every dev gets Python toolchain. Simpler — one flake, one shell. Python deps are ~60MB total, negligible vs current C toolchain.
|
||
|
||
**Option B: Separate `xceadmin.nix`**. Lighter shell for C-only devs. More maintenance — two nix files, two shells, cross-component workflows need manual env switching.
|
||
|
||
Recommend **Option A** — xceadmin is part of the standard toolchain (like xcecli), not an optional extra. All devs should be able to `xceadmin config build` without switching shells.
|
||
|
||
### New `nativeBuildInputs` block (addition to xce.nix)
|
||
|
||
```nix
|
||
# Python — for xceadmin (admin config tool)
|
||
python3
|
||
python3Packages.pycapnp
|
||
python3Packages.pyyaml
|
||
python3Packages.jsonschema
|
||
python3Packages.click
|
||
python3Packages.pytest
|
||
python3Packages.ruff
|
||
```
|
||
|
||
### pycapnp build note
|
||
|
||
`pycapnp` wraps the capnproto C++ library at compile time (`pip install` compiles a C extension). Nix's `python3Packages.pycapnp` handles this correctly — it links against nix-provided `capnproto`. No additional `libcapnp-dev` or system package needed. This is one of the advantages of using nix pycapnp over `pip install pycapnp`.
|
||
|
||
### CI implications
|
||
|
||
- CI already uses nix dev shell — adding packages to `xce.nix` makes them available in CI automatically
|
||
- `config build` golden-file tests need pycapnp for capnp encode (can't mock without schema)
|
||
- `config push` mock tests need pycapnp + TLS (Python `ssl` stdlib, no extra dep)
|
||
|
||
## Testing Strategy
|
||
|
||
### config build (offline — pure data transform)
|
||
|
||
**Golden-file tests** (primary). Given a `configs/` directory, verify output matches expected:
|
||
|
||
```
|
||
tests/
|
||
├── fixtures/
|
||
│ ├── minimal/ # hosts.yaml + queues.yaml only
|
||
│ │ ├── configs/
|
||
│ │ │ ├── hosts.yaml
|
||
│ │ │ ├── queues.yaml
|
||
│ │ │ └── schema/
|
||
│ │ └── expected.bin # pre-built golden output
|
||
│ ├── full/ # all 4 YAML files
|
||
│ └── invalid/ # schema violations → expect error
|
||
├── test_config_build.py # pytest: build → compare binary, verify SHA-256
|
||
└── test_schema_validation.py # pytest: each schema violation case
|
||
```
|
||
|
||
- Framework: `pytest` (standard Python, no deps beyond dev)
|
||
- Zero external dependencies — no controller, no network
|
||
- Pre-build golden files once; commit to repo
|
||
- Schema validation: test each error case (missing required field, wrong type, invalid enum, reserved name `lost_and_found`)
|
||
|
||
### config push (online — needs controller)
|
||
|
||
**Option A: Mock controller** (unit/integration). Python TLS server that speaks enough capnp wire protocol to accept `OP_upgradeConfig` and return canned `UpgradeConfigReply`.
|
||
|
||
**Option B: Real controller** (E2E). Piggyback on existing `/xce-test-ha` cluster. After cluster is up, `xceadmin config push` a known config and verify controller accepts it.
|
||
|
||
- Option A for dev loop + CI (fast, no cluster setup)
|
||
- Option B for acceptance (real SSL, real replication, real controller)
|
||
|
||
Recommended: Option A for spec tasks. Option B deferred to xce-test-ha integration after config-upgrade-snapshot lands.
|
||
|
||
### CI Integration
|
||
|
||
- `config build` tests: run in any Python CI (no C deps needed — pycapnp only for encode, can mock)
|
||
- `config push` mock tests: need pycapnp + controller.capnp compiled for Python
|
||
- Target: 80%+ coverage (project standard)
|
||
|
||
## Open Design Questions
|
||
|
||
Issues identified during 2026-07-07 review — need resolution before or during requirements generation.
|
||
|
||
### Critical (resolve before requirements)
|
||
|
||
| # | Question | Why it matters | Candidates |
|
||
|---|----------|---------------|------------|
|
||
| 1 | **YAML→capnp mapping incomplete** | `ClusterConfig` has 21 fields; 4 YAML files cover ~10. Remaining 11 (clusterName, port, candidates, heartbeat/interval timeouts, jobmgr/workermgr/qmgr/resmgr hash configs, xstrings, configGeneration) have no YAML source | (a) xceadmin fills defaults from controller's `get_default_conf()` values, (b) add a `cluster.yaml` for system-level fields, (c) accept CLI flags `--cluster-name`, `--port`, `--peers` |
|
||
| 2 | **configGeneration** | Monotonic counter. **RESOLVED**: Controller uses it to detect new configs — same generation as current = no-op (nothing changed). xceadmin MUST read the current generation from the controller, increment it, and embed the incremented value in the binary before pushing. `xceadmin config build` sets it to 0 (offline-safe default). `xceadmin config push` auto-reads current generation, increments, rebuilds with new generation, then pushes. Admin can override with `--generation N`. Controller strictly rejects generations ≤ current (monotonic enforcement). | ✅ Decided: xceadmin-managed increment, controller-enforced monotonic |
|
||
| 3 | **candidates field** | Controller peer list (`List(MasterCandidate)`). Currently passed as CLI args to `deploy-ha.sh`, not in YAML. xceadmin needs peer info for config build | (a) `--peers localhost:9991,localhost:9992,localhost:9993` CLI flag, (b) read from `hosts.yaml` host list, (c) separate `peers.yaml` |
|
||
| 4 | **YAML key ≠ capnp field name** | YAML `groupname` → capnp `GroupConfig.name`, YAML `exclude_members` → capnp `exmembers`. Every field needs explicit mapping definition | Write a field-mapping table in design.md; auto-generate from capnp schema annotations if possible |
|
||
| 5 | **Nested struct defaults** | `QueueConf` has 14 fields — YAML sets ~6. Remaining (attrs, status, schedDelay, resreq, preExec, postExec) need defaults | Use controller's `get_default_conf()` values from `myconf.c`; document which fields are operator-facing vs system-defaulted |
|
||
|
||
### Important (resolve during design)
|
||
|
||
| # | Question | Why it matters | Candidates |
|
||
|---|----------|---------------|------------|
|
||
| 6 | **Wire framing format** | Controller expects 8-byte uint64 size prefix (host byte order) + capnp payload. ~5 lines of Python but wrong byte order = silent reject | Document in design.md; mock controller test catches framing errors |
|
||
| 7 | **TLS cert discovery** | xceadmin needs CA cert to verify controller TLS. Dev: `ha/data/xce.crt`. Production: configurable path | (a) `--ca-cert` flag, (b) `XCECERT` env var, (c) default `ha/data/xce.crt` for dev, require explicit for non-localhost |
|
||
| 8 | **capnp schema compilation** | `capnp compile -o python controller.capnp` → `controller_capnp.py`. When and where? | (a) build time in xce-build.sh (committed output), (b) runtime in xceadmin startup (needs capnp binary), (c) nix derivation that produces the .py file |
|
||
| 9 | **Python package layout** | Where does code live? Affects pip install, import paths, xce-build.sh | (a) `src/xceadmin/` alongside C code, (b) `tools/xceadmin/` with deploy-ha.sh, (c) top-level `xceadmin/` |
|
||
| 10 | **existing cluster.bin provenance** | `ha/data/cluster.bin` is 8704 bytes, SHA 4986624f..., built June 21 by unknown method. Can't validate xceadmin output against it | Regenerate from known YAML inputs; use as golden-file test once xceadmin produces identical output for same inputs |
|
||
|
||
## Configuration Lifecycle & Rollback (Future Roadmap)
|
||
|
||
### Source of truth: Git-managed YAML
|
||
|
||
YAML config files (`configs/*.yaml`) are the authoritative source of truth. Operators manage them in git — version control, code review, audit trail are all free. `cluster.bin` is a **derived artifact** (like a compiled binary), never committed to git.
|
||
|
||
```
|
||
git tag v1.2.0 → YAML at v1.2.0 → xceadmin config build → cluster.bin (deterministic)
|
||
│
|
||
xceadmin config push
|
||
│
|
||
▼
|
||
controller cluster
|
||
```
|
||
|
||
**Deterministic builds**: same YAML + same generation value + same xceadmin version → byte-identical `cluster.bin`. `configGeneration` defaults to 0 in offline builds (`xceadmin config build`) and is auto-incremented by `xceadmin config push` (read current from controller, +1). Any git revision can be rebuilt to the exact binary that was (or would have been) pushed. No need to store historical binaries.
|
||
|
||
### What git gives us (free)
|
||
|
||
| Capability | How |
|
||
|-----------|-----|
|
||
| Version history | `git log configs/` |
|
||
| Diff between versions | `git diff v1.2.0 v1.3.0 -- configs/` |
|
||
| Rollback source | `git checkout v1.2.0 -- configs/ && xceadmin config build` |
|
||
| Audit trail | `git blame configs/queues.yaml` |
|
||
| Code review | PR against `configs/` branch |
|
||
|
||
### What still needs building
|
||
|
||
Git manages the **source**. These parts need implementation:
|
||
|
||
| # | Capability | What it does | Owned by |
|
||
|---|-----------|-------------|----------|
|
||
| R1 | **Binary diff** | `xceadmin config diff cluster-v1.bin cluster-v2.bin` — human-readable diff of two built config binaries (decode both → compare field-by-field → pretty-print changes). Useful for pre-push review: "what will this config change actually do?" | admin-config-tool |
|
||
| R2 | **Dry-run push** | `xceadmin config push --dry-run cluster.bin` — validate controller will accept the config (SHA check, version check) without committing. Safety net before real push | admin-config-tool |
|
||
| R3 | **Config change snapshot** | After config upgrade, take an entity-bearing snapshot of the new configuration state. This anchors the config change in the snapshot chain — without it, rollback has no durable record of what the cluster looked like before/after the change. Controller-side: insert snapshot step into the `OP_upgradeConfig` pipeline (between step 5 "majority ACK" and step 6 "commit WAL_CONFIG_MARKER" in the current handler). | config-upgrade-snapshot |
|
||
| R4 | **Controller version history** | Controller tracks last N config versions (tag + SHA + timestamp). `xceadmin config history` queries it. Needed for: "what config is the cluster running?" and "what was running before the last push?" Depends on R3 (snapshot chain provides durable version records). | config-upgrade-snapshot |
|
||
| R5 | **Controller-side rollback** | `xceadmin config rollback` (or `xceadmin config push --rollback-to v1.2.0`) — push a previous config version. Controller-side concerns: (a) validate rollback target is safe (no running jobs reference deleted queues/hosts), (b) snapshot before rollback for forward-recovery, (c) bump configGeneration even on rollback (monotonic), (d) restart/activate the rolled-back config | config-upgrade-snapshot |
|
||
| R6 | **Rollback safety validation** | Before rolling back, verify: (a) no running jobs use queues being deleted, (b) no active workers on hosts being removed, (c) resource definitions haven't changed in ways that break in-flight allocations. Controller-side check, client-side initiated | config-upgrade-snapshot |
|
||
| R7 | **Config activation status** | `xceadmin config status` — show: current config tag, when applied, which nodes have it, any pending restart, snapshot status. Operator visibility into config rollout state | config-upgrade-snapshot |
|
||
| R8 | **Config promotion pipeline** | Dev → staging → production config promotion. Same YAML source, different `--peers`/`--cluster-name` flags at build time. Git branches map to environments | admin-config-tool (future) |
|
||
|
||
### Controller-side pipeline: current vs target
|
||
|
||
```
|
||
CURRENT (config_repl.c — exists today):
|
||
xceadmin push → SHA verify → decode → gen++ → save → replicate → WAL marker → restart signal
|
||
↑
|
||
step 8 MISSING
|
||
TARGET (after config-upgrade-snapshot):
|
||
xceadmin push → SHA verify → decode → gen++ → save → replicate → [SNAPSHOT] → WAL marker → restart signal
|
||
↑
|
||
R3: entity-bearing
|
||
snapshot of new config
|
||
```
|
||
|
||
The snapshot insertion point matters: it goes AFTER replication (all standbys have the config) but BEFORE the WAL marker commit (so the snapshot is part of the committed state). This ensures a rollback target always has a consistent snapshot to restore from.
|
||
|
||
### Rollback workflow (end-to-end vision)
|
||
|
||
```
|
||
# 1. Discover what went wrong
|
||
xceadmin config diff cluster-current.bin cluster-suspect.bin
|
||
# → "queues.normal.priority changed 10→1, queues.express removed"
|
||
|
||
# 2. Find the last good version
|
||
xceadmin config history
|
||
# → v1.2.0 (active), v1.1.0 (previous), v1.0.0 (initial)
|
||
|
||
# 3. Dry-run the rollback
|
||
xceadmin config build --git-ref v1.1.0 -o cluster-v1.1.0.bin
|
||
xceadmin config push --dry-run cluster-v1.1.0.bin
|
||
# → "OK: controller will accept this config (generation 3→4)"
|
||
|
||
# 4. Execute rollback
|
||
xceadmin config push cluster-v1.1.0.bin
|
||
# → "Pushed (tag=v1.1.0, SHA=..., acked=3/3). Controller restarting."
|
||
|
||
# 5. Verify
|
||
xceadmin config status
|
||
# → "v1.1.0 active on 3/3 nodes"
|
||
```
|
||
|
||
### Rollback safety: what can go wrong
|
||
|
||
| Scenario | Risk | Mitigation |
|
||
|----------|------|------------|
|
||
| Rollback deletes a queue | Running jobs on that queue become unmanageable | R5: controller refuses rollback while jobs reference deleted queues |
|
||
| Rollback removes a host | Workers on that host lose config | R5: controller refuses rollback while active workers on removed hosts |
|
||
| Rollback changes resource defs | In-flight allocations invalid | Controller fences: drain before rollback, or mark resources "changing" |
|
||
| Rollback during network partition | Standbys miss the config push | Majority ACK required (existing OP_upgradeConfig behavior) |
|
||
| Rapid rollback→forward→rollback | configGeneration churn, snapshot chain fragmentation | Rate-limit config changes (1 per N seconds); snapshot GC |
|
||
|
||
### What's in-scope for admin-config-tool vs deferred
|
||
|
||
| Capability | This spec | Why |
|
||
|-----------|-----------|-----|
|
||
| YAML source in git | ✅ (documentation) | Free — already how configs/ works |
|
||
| Deterministic build | ✅ (core) | Same YAML → same binary (generation not set — controller-owned) |
|
||
| Binary diff (R1) | ✅ (nice-to-have) | Pure Python, offline, high operator value |
|
||
| Dry-run push (R2) | ✅ (nice-to-have) | Needs `ProbeConfigTag` RPC (already exists in controller) |
|
||
| Config change snapshot (R3) | ❌ deferred | Controller-side — insert entity-bearing snapshot into OP_upgradeConfig pipeline; blocked on entity-bearing-snapshots |
|
||
| Version history (R4) | ❌ deferred | Controller-side — depends on R3 snapshot chain |
|
||
| Rollback (R5) | ❌ deferred | Controller-side — needs snapshot chain + restart |
|
||
| Safety validation (R6) | ❌ deferred | Controller-side — needs job/worker state queries |
|
||
| Config status (R7) | ❌ deferred | Controller-side — needs cluster-wide state tracking |
|
||
| Promotion pipeline (R8) | ❌ deferred | Wraps git branches + xceadmin build — future enhancement |
|
||
|
||
## Existing Spec Touchpoints
|
||
|
||
- **Extends**: `config-upgrade-snapshot` (provides the client-side trigger that config-upgrade-snapshot's controller-side path responds to; R3 config-change-snapshot is the missing controller-side step — insert entity-bearing snapshot between replication and WAL marker commit in the `OP_upgradeConfig` pipeline)
|
||
- **Adjacent**: `ha-term-safety` (the WAL_CONFIG_MARKER this tool's upload commits anchors on the Raft log via ha-term-safety's term-index infrastructure)
|
||
- **Upstream blocker**: `entity-bearing-snapshots` — must land before R3 (config change snapshot) and R4 (version history) can be implemented
|
||
- **Blocks**: nothing — xceadmin ships independently; the current controller pipeline (SHA verify → decode → gen++ → save → replicate → WAL marker → restart) already works without the snapshot step
|
||
- **Future**: configuration lifecycle (R1–R8 above) — split across admin-config-tool (R1, R2, R8) and config-upgrade-snapshot (R3–R7)
|