From b1783abdce2bc390c1422326e6b0ccc364985cb4 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 1 Sep 2026 17:52:02 +0100 Subject: [PATCH] test(multiplayer): isolate agones lifecycle smoke --- multiplayer-next.md | 2 +- scripts/verify_kind_agones.sh | 12 +++++++++++- server/security/test_fleet_manifests.py | 8 ++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/multiplayer-next.md b/multiplayer-next.md index 57e7657a..f5b21289 100644 --- a/multiplayer-next.md +++ b/multiplayer-next.md @@ -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; `compose.allocated-smoke.yml` independently runs the real testkit API against PostgreSQL and verifies authenticated result, idempotent retry, shutdown acknowledgment, durable receipt/audit rows, and clean API process stop | `.github/workflows/allocated-compose.yml` runs `make verify-allocated-compose`. Allocated game-process drain, full queue/proposal/allocation orchestration over HTTP, and legacy fixture non-regression remain open | -| 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; `.github/workflows/agones-integration.yml` runs it for infrastructure changes and on demand | The cloud-free runner is 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.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 in an explicitly separate Agones-only supervisor/UDP readiness mode, and verifies readiness plus allocation of a dynamic UDP endpoint; `.github/workflows/agones-integration.yml` runs it for infrastructure changes and on demand | The cloud-free runner is committed and fails clearly when Docker/kind/Helm are unavailable. CI/live evidence for production control-plane registration, roster/no-show, both readiness stages, 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 | diff --git a/scripts/verify_kind_agones.sh b/scripts/verify_kind_agones.sh index 8a4cde93..715b75d8 100755 --- a/scripts/verify_kind_agones.sh +++ b/scripts/verify_kind_agones.sh @@ -63,7 +63,17 @@ kubectl wait --for=condition=available deployment/agones-allocator \ # 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|" \ +# +# This runner is intentionally an Agones lifecycle smoke, not a substitute for +# the production control-plane gate: there is no PostgreSQL/API/roster backend +# in this disposable cluster. Disable only those production-only child paths so +# the real supervisor can validate the assigned endpoint, launch the exported +# server, and call the Agones SDK Ready endpoint. +zero_digest="$(printf '0%.0s' {1..64})" +sed -e "s|ghcr.io/cosmic-clash/game-server@sha256:${zero_digest}|$game_server_image|" \ + -e 's|--control-plane-url=http://control-plane.cosmic-clash.svc.cluster.local:8080|--control-plane-url=|' \ + -e '/- --roster-path=\/run\/cosmic-clash\/join-roster.json/d' \ + -e '/- --allocated-mode$/d' \ deploy/k8s/base/fleet.yaml > "$work_dir/fleet.yaml" kubectl apply -f deploy/k8s/base/namespace.yaml diff --git a/server/security/test_fleet_manifests.py b/server/security/test_fleet_manifests.py index a3040804..4963df0c 100644 --- a/server/security/test_fleet_manifests.py +++ b/server/security/test_fleet_manifests.py @@ -3,6 +3,7 @@ import unittest BASE = Path(__file__).parents[2] / "deploy" / "k8s" +ROOT = Path(__file__).parents[2] class FleetManifestTest(unittest.TestCase): @@ -89,6 +90,13 @@ class FleetManifestTest(unittest.TestCase): self.assertIn(field, network) self.assertIn("control-plane-service.yaml", base) + def test_kind_runner_is_explicitly_separate_from_production_roster_flow(self): + runner = (ROOT / "scripts/verify_kind_agones.sh").read_text() + self.assertIn("Agones lifecycle smoke", runner) + self.assertIn("--control-plane-url=", runner) + self.assertIn("--allocated-mode", runner) + self.assertIn("dynamic UDP port", runner) + if __name__ == "__main__": unittest.main()