mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
test(multiplayer): add disposable kind agones gate
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
.PHONY: verify-phase6 verify-enet-integration verify-steam-templates verify-supply-chain verify-multiplayer-local
|
||||
.PHONY: verify-phase6 verify-enet-integration verify-steam-templates verify-supply-chain verify-kind-agones verify-multiplayer-local
|
||||
|
||||
verify-multiplayer-local:
|
||||
bash scripts/verify_multiplayer_local.sh
|
||||
@@ -14,3 +14,6 @@ verify-steam-templates:
|
||||
|
||||
verify-supply-chain:
|
||||
python3 scripts/verify_supply_chain.py
|
||||
|
||||
verify-kind-agones:
|
||||
bash scripts/verify_kind_agones.sh
|
||||
|
||||
+2
-2
@@ -1248,7 +1248,7 @@ the local/CI/community transport, not a silent production fallback.
|
||||
| 8.46 `[D:8.5,8.7,8.9,8.10,8.14,8.18,8.21,8.23,8.25]` | **IN PROGRESS.** Go unit/race coverage spans the current domain/store/supervisor policies, and fuzz targets now exercise queue input, result payload hashing and revision events | `server/domain/*_test.go`, `server/store/*_test.go`, `server/supervisor/*_test.go`, `server/migrations/*_test.go` and `server/domain/fuzz_test.go` pass normal/race suites; `go test -race ./...` passes across API, domain, migrations, observability, store, supervisor and testkit; `go vet ./...` passes; each of the three declared domain fuzz targets passes a bounded 4-second run; PostgreSQL live migration execution now runs clean (§8.5), and four real-concurrency cases are covered against a live database with `-race`: §8.14's queue-heartbeat revision race, §8.18's two-matcher contested-ticket race, §8.30's cross-allocator-replica capacity race, and §8.21/§8.25's concurrent identical-result-submission race; the "lost Redis" fixture is covered live against a real server (§8.14: real TTL expiry, repair-after-`FLUSHALL`; fake Steam/allocator fixtures are §8.47's testkit, already done). Further transaction fixtures (e.g. concurrent proposal-recovery expiry races, live Redis failover mid-write under load) remain |
|
||||
| 8.47 `[D:8.7,8.30]` | **IN PROGRESS.** Offline testkit provides deterministic fake Steam verification and fake allocation with forced failure injection | `server/testkit/` covers verified identity/replay, unknown identity, wrong App ID, expiry, no capacity, compatibility-key conflict, idempotent allocation replay and cloud-free forced allocation failure in `TestOfflineFakesCoverVerificationAndAllocationFailureMatrix`; API/Compose integration and live exhaustive matrix remain |
|
||||
| 8.48 `[D:8.10,8.14,8.17,8.18,8.27,8.31,8.35,8.47]` | **IN PROGRESS.** Offline testkit exercises verified queue projection → ranked six-player proposal → ENet allocation → assignment-ready manifest → certified durable result receipt | `server/testkit/pipeline_test.go` covers the cross-domain success path without Steam/cloud secrets; independent Compose fixture, process shutdown, result ack over HTTP and legacy fixture non-regression remain |
|
||||
| 8.49 `[D:8.25,8.26,8.28,8.29,8.30,8.31,8.35,8.36]` | Disposable `kind` + Agones integration gate | CI covers dynamic ports, both readiness stages, roster/no-show, races, multi-match node, result-pending reconciliation, drain and rollback |
|
||||
| 8.49 `[D:8.25,8.26,8.28,8.29,8.30,8.31,8.35,8.36]` | **IN PROGRESS.** `scripts/verify_kind_agones.sh` creates a disposable kind cluster, installs pinned Agones, loads the real `game-server` image, applies the Fleet, and verifies readiness plus allocation of a dynamic UDP endpoint | The cloud-free runner is now committed and fails clearly when Docker/kind/Helm are unavailable. CI/live evidence for both readiness stages, roster/no-show, races, multi-match node, result-pending reconciliation, drain, and rollback remains open |
|
||||
| 8.50 `[D:8.25,8.37,8.43,8.49]` | Network/chaos suite: 100 ms RTT, jitter/loss, client/API/matcher restart, game-pod death, node drain, Redis failover and control-plane loss | System recovers to a defined state; infrastructure-caused cases cannot penalise affected players |
|
||||
| 8.51 `[D:8.17,8.18,8.30,8.31,8.45]` | Load test >=10,000 queued clients, >=100 proposals/s and forecast launch concurrency x2 | API p95 <=250 ms, durable matcher fence holds, both readiness/allocation SLOs are met and replicas scale without duplicate claims |
|
||||
| 8.52 `[D:8.32,8.34,8.45,8.51]` | Per-region cost model from measured density, warm capacity, bandwidth, DB/Redis and telemetry; add budgets and allocation quotas | Cost per completed match and forecast monthly bands are recorded; a denial-of-wallet test triggers limits/alerts before budget breach |
|
||||
@@ -1455,7 +1455,7 @@ Observability redaction now adds content-aware protection on top of denylisted f
|
||||
|
||||
The following Phase 8 slices have local implementation and verification evidence in this document: 8.29 dynamic allocated launch flags and endpoint handling; 8.30 allocator claim/reconciliation; 8.31 signed assignment/roster validation; 8.35 initial-connect no-show and casual bot policy; 8.36 controlled drain and shutdown acknowledgment; 8.39–8.43 client state, assignment, profile, recovery, and idempotent action retry; 8.44 structured observability and content-aware redaction; 8.45 bounded API metrics export plus optional Prometheus scrape/alert rules; 8.46 normal/race/vet/fuzz coverage; and 8.47–8.48 offline testkit coverage. Their remaining acceptance text is infrastructure or production dependent where explicitly noted below the corresponding row.
|
||||
|
||||
The following are not locally certifiable from this workspace and remain open prerequisites rather than silently “done”: Valve/GodotSteam credentials and hosted SDR (7.1–7.8), live PostgreSQL/Redis execution where Docker is unavailable, live Agones/kind lifecycle (8.30–8.38, 8.49), public-network chaos/load/cost/release gates (8.50–8.53), and real-hardware graphics profiling (0.15b onward). `TODO.md`’s AI-training and presentation tasks remain separate from multiplayer and are not marked by this index.
|
||||
The following are not locally certifiable from this workspace and remain open prerequisites rather than silently “done”: Valve/GodotSteam credentials and hosted SDR (7.1–7.8), live PostgreSQL/Redis execution where Docker is unavailable, live Agones/kind lifecycle (8.30–8.38, 8.49), public-network chaos/load/cost/release gates (8.50–8.53), and real-hardware graphics profiling (0.15b onward). `make verify-kind-agones` is the committed runner for 8.49; it requires a running Docker daemon plus kind, kubectl, and Helm. `TODO.md`’s AI-training and presentation tasks remain separate from multiplayer and are not marked by this index.
|
||||
|
||||
The deferred teamplay TODO prerequisite is now implemented locally but not
|
||||
enabled: team-touch credit is opt-in and the evaluator can run paired 2v2
|
||||
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Disposable integration gate for multiplayer-next.md §8.49. This deliberately
|
||||
# does not touch an existing cluster: kind creates an isolated cluster and the
|
||||
# EXIT trap removes only that named cluster.
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$root_dir"
|
||||
|
||||
cluster_name="${KIND_CLUSTER_NAME:-cosmic-clash-agones-smoke}"
|
||||
agones_version="${AGONES_VERSION:-1.49.0}"
|
||||
game_server_image="${GAME_SERVER_IMAGE:-cosmic-clash-game-server:kind}"
|
||||
kind_node_image="${KIND_NODE_IMAGE:-kindest/node:v1.33.1}"
|
||||
work_dir="$(mktemp -d "${TMPDIR:-/tmp}/cosmic-clash-agones.XXXXXX")"
|
||||
|
||||
cleanup() {
|
||||
local status=$?
|
||||
kind delete cluster --name "$cluster_name" >/dev/null 2>&1 || true
|
||||
rm -rf "$work_dir"
|
||||
exit "$status"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
for tool in docker kind kubectl helm; do
|
||||
command -v "$tool" >/dev/null 2>&1 || {
|
||||
echo "8.49 requires '$tool'; install Docker, kind, kubectl, and Helm to run the disposable gate" >&2
|
||||
exit 2
|
||||
}
|
||||
done
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "8.49 requires a running Docker daemon" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
kind delete cluster --name "$cluster_name" >/dev/null 2>&1 || true
|
||||
|
||||
if ! docker image inspect "$game_server_image" >/dev/null 2>&1; then
|
||||
echo "Building $game_server_image from the pinned game-server target"
|
||||
docker build --target game-server -t "$game_server_image" .
|
||||
fi
|
||||
|
||||
kind create cluster --name "$cluster_name" --image "$kind_node_image" --wait 120s
|
||||
kind load docker-image "$game_server_image" --name "$cluster_name"
|
||||
|
||||
helm repo add agones https://agones.dev/chart/stable >/dev/null
|
||||
helm repo update >/dev/null
|
||||
helm upgrade --install agones agones/agones \
|
||||
--namespace agones-system --create-namespace \
|
||||
--version "$agones_version" \
|
||||
--set agones.crds.cleanup.enabled=true \
|
||||
--set agones.controller.replicas=1 \
|
||||
--set agones.extensions.replicas=1 \
|
||||
--set agones.allocator.replicas=1 \
|
||||
--wait --timeout 5m
|
||||
|
||||
kubectl wait --for=condition=available deployment/agones-controller \
|
||||
-n agones-system --timeout=180s
|
||||
kubectl wait --for=condition=available deployment/agones-allocator \
|
||||
-n agones-system --timeout=180s
|
||||
|
||||
# The base Fleet intentionally carries a release-time digest placeholder. For
|
||||
# this isolated run only, replace that exact placeholder with the image loaded
|
||||
# into kind. No repository manifest is modified and no mutable image is used
|
||||
# outside the disposable cluster.
|
||||
sed "s|ghcr.io/cosmic-clash/game-server@sha256:$(printf '0%.0s' {1..64})|$game_server_image|" \
|
||||
deploy/k8s/base/fleet.yaml > "$work_dir/fleet.yaml"
|
||||
|
||||
kubectl apply -f deploy/k8s/base/namespace.yaml
|
||||
kubectl -n cosmic-clash create secret generic cosmic-clash-game-server \
|
||||
--from-literal=drain-token=kind-smoke-drain-token \
|
||||
--from-literal=join-signing-key=kind-smoke-signing-key \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
kubectl apply -f deploy/k8s/base/service-accounts.yaml
|
||||
kubectl apply -f "$work_dir/fleet.yaml"
|
||||
|
||||
kubectl wait --for=jsonpath='{.status.ready}'=2 \
|
||||
fleet/cosmic-clash-game -n cosmic-clash --timeout=5m
|
||||
|
||||
cat > "$work_dir/allocation.yaml" <<'EOF'
|
||||
apiVersion: allocation.agones.dev/v1
|
||||
kind: GameServerAllocation
|
||||
metadata:
|
||||
generateName: cosmic-clash-smoke-
|
||||
namespace: cosmic-clash
|
||||
spec:
|
||||
fleet:
|
||||
name: cosmic-clash-game
|
||||
EOF
|
||||
kubectl create -f "$work_dir/allocation.yaml" -o json > "$work_dir/allocation.json"
|
||||
|
||||
python3 - "$work_dir/allocation.json" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
doc = json.load(open(sys.argv[1], encoding="utf-8"))
|
||||
status = doc.get("status", {})
|
||||
if status.get("state") != "Allocated":
|
||||
raise SystemExit(f"allocation state is {status.get('state')!r}, expected 'Allocated'")
|
||||
ports = status.get("gameServer", {}).get("status", {}).get("ports", [])
|
||||
if not ports or not any(p.get("port", 0) > 0 and p.get("port") != 7777 for p in ports):
|
||||
raise SystemExit(f"allocation did not return a dynamic UDP port: {ports!r}")
|
||||
print("8.49 PASS: Fleet became ready and allocation returned a dynamic UDP port")
|
||||
PY
|
||||
Reference in New Issue
Block a user