Files
noonghunnaandClaude Opus 4.8 65eb109812 refactor(pods): rename cluster → pod (#610) + heterogeneous-rig guidance
Per maintainer call: "cluster" conventionally means multiple networked
machines (a non-goal here — LiteLLM fronts multi-host) and is best reserved
for a future enterprise/multi-node meaning. "Pod" is the accurate analogy
for what this is — one model on a GPU subset on ONE host (k8s/RunPod sense).
The capability is unchanged; only the name.

Scoped rename (cluster→pod, case-aware) across the pod feature ONLY:
- scripts/cluster.sh → scripts/pod.sh; test-cluster-cli.sh → test-pod-cli.sh;
  docs/CLUSTERS.md → docs/PODS.md
- estate_cli.py verbs + wording; app.py (ClusterCreateScreen→PodCreateScreen,
  action_new_cluster→new_pod, _populate_clusters→_populate_pods, #cluster-view
  →#pod-view, the [N] help/empty-state text); services.py cluster_create_plan
  →pod_create_plan; data.py kind cluster_create→pod_create; tests + doc
  pointers (HARDWARE/MULTI_CARD/README/c3-README)
- UNTOUCHED (unrelated "cluster"): compat.py + test-profiles-compat.sh (the
  VRAM-topology classifier), services.py:1863 / test_services.py (the scene-
  table "cluster by group" verb), older docs, .venv

Also folds in the [N] discoverability fix (n was already bound to
serving_switch — moved to N; empty-estate now shows a "no pods — [N] new
pod" affordance + a help entry) and a heterogeneous-rig section in PODS.md:
one homogeneous pod per card family (2×3090 · GB10 · 6000 Pro) is the clean
pattern — mixing families in one TP pod makes NCCL wait on the slowest +
wastes VRAM; a worked 2-pod lifecycle walkthrough.

Verified: test-pod-cli + estate/gpu/profiles guards green; 25 c3 pod/binding
+ 239 fast tests green; pod.sh live create/list/D1-reject on 2×3090; zero
stray "cluster" in pod files (scene-verb preserved); no CLUSTERS.md links
left; PODS.md leak-clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01EfF565T9eSLaqGzidyJ1Pm
2026-07-07 02:10:17 +00:00

73 lines
3.1 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# test-pod-cli.sh — scripts/pod.sh + the estate_cli pod verbs
# (#610 Phase A). Runs hardware-free via CLUB3090_FAKE_GPUS (kv-calc prices
# by card NAME, no real GPU needed) so CI exercises the full lifecycle:
# create (with D1 fit) · count!=TP hard-reject · GPU-collision reject ·
# list --json · status · rm · the index-based estate file.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$ROOT"
# Two fake RTX 3090s so a TP=2 slug fits and a TP=1 slug leaves GPU 0 free.
export CLUB3090_FAKE_GPUS='0:NVIDIA GeForce RTX 3090:24576:8.6,1:NVIDIA GeForce RTX 3090:24576:8.6'
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
EST="$TMP/estate.yml"
CL=(bash scripts/pod.sh)
fail() { echo "FAIL: $*" >&2; exit 1; }
# 1. create a TP=1 pod on GPU 1 — must fit + write the file.
"${CL[@]}" create chat --gpus 1 --slug vllm/minimal --file "$EST" >/dev/null 2>&1 \
|| fail "create chat (TP=1 on GPU 1) should succeed"
[[ -f "$EST" ]] || fail "estate file not written"
grep -q 'name: chat' "$EST" || fail "chat not in estate file"
# Indices stay index-based in the file (UUIDs resolved at boot, #610 Phase A).
grep -qE '^\s*-\s*1\s*$' "$EST" || fail "estate file should store index-based gpus"
# 2. D1: count != compose TP -> hard reject (vllm/dual is TP=2).
if "${CL[@]}" create bad --gpus 1 --slug vllm/dual --file "$EST" >/dev/null 2>&1; then
fail "create with 1 GPU for a TP=2 slug must be rejected (D1)"
fi
err="$("${CL[@]}" create bad --gpus 1 --slug vllm/dual --file "$EST" 2>&1 || true)"
grep -q 'TP=2' <<<"$err" || fail "D1 rejection should name the TP mismatch (got: $err)"
# 3. GPU collision -> validate_estate reject (GPU 1 already claimed by chat).
if "${CL[@]}" create chat2 --gpus 1 --slug vllm/minimal --file "$EST" >/dev/null 2>&1; then
fail "second pod on the same GPU must be rejected"
fi
# 4. list --json: one pod, GPU 0 free.
json="$("${CL[@]}" list --json --file "$EST" 2>/dev/null)"
python3 - "$json" <<'PY' || exit 1
import json, sys
d = json.loads(sys.argv[1])
assert len(d["pods"]) == 1, d
assert d["pods"][0]["name"] == "chat", d
assert d["free_gpus"] == [0], d
assert d["claimed_gpus"] == [1], d
print(" list --json ok")
PY
# 5. status (nothing running) -> down, placement unknown, valid --json shape.
sjson="$("${CL[@]}" status --json --file "$EST" 2>/dev/null)"
python3 - "$sjson" <<'PY' || exit 1
import json, sys
d = json.loads(sys.argv[1])
c = d["pods"][0]
assert c["running"] is False, c
assert c["placement"]["placement"] == "unknown", c
assert set(c["placement"]) == {"requested", "actual", "placement"}, c
print(" status --json ok")
PY
# 6. a second, non-colliding pod on GPU 0 succeeds.
"${CL[@]}" create chat0 --gpus 0 --slug vllm/minimal --file "$EST" >/dev/null 2>&1 \
|| fail "create chat0 on the free GPU 0 should succeed"
# 7. rm removes it from the plan.
"${CL[@]}" rm chat0 --file "$EST" >/dev/null 2>&1 || fail "rm chat0 should succeed"
grep -q 'name: chat0' "$EST" && fail "chat0 still in estate file after rm"
echo "PASS: pod.sh create/list/status/rm + D1 count!=TP + GPU-collision rejects (fake-GPU)"