mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
fix(multiplayer): repair allocated compose verification
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal deterministic Agones HTTP surface for the allocated Compose smoke."""
|
||||
import json
|
||||
import os
|
||||
import ssl
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
|
||||
@@ -41,4 +43,8 @@ class Handler(BaseHTTPRequestHandler):
|
||||
return
|
||||
|
||||
|
||||
HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
|
||||
server = HTTPServer(("0.0.0.0", 8443), Handler)
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.load_cert_chain(os.environ["FAKE_AGONES_TLS_CERT"], os.environ["FAKE_AGONES_TLS_KEY"])
|
||||
server.socket = context.wrap_socket(server.socket, server_side=True)
|
||||
server.serve_forever()
|
||||
|
||||
@@ -13,6 +13,10 @@ compose=(docker compose -p "$project" -f "$compose_file")
|
||||
|
||||
cleanup() {
|
||||
local rc=$?
|
||||
if [[ "$rc" != 0 && "${COMPOSE_KEEP_ON_FAILURE:-}" == 1 ]]; then
|
||||
echo "allocated Compose fixture retained for inspection: ${project}" >&2
|
||||
exit "$rc"
|
||||
fi
|
||||
"${compose[@]}" down --volumes --remove-orphans >/dev/null 2>&1 || true
|
||||
exit "$rc"
|
||||
}
|
||||
@@ -28,7 +32,7 @@ import base64, hashlib, hmac, json, pathlib, sys, time
|
||||
directory = pathlib.Path(sys.argv[1])
|
||||
key = b"compose-join-signing-key"
|
||||
expires = "2099-12-31T00:00:00Z"
|
||||
fields = ["compose-match", "compose-server", "compose-player", "compose-steam", "0", "0", "v1", "1", expires]
|
||||
fields = ["compose-match-0001", "compose-server-0001", "compose-player", "compose-steam", "0", "0", "v1", "1", expires]
|
||||
canonical = b"\0".join(field.encode() for field in fields)
|
||||
signature = base64.urlsafe_b64encode(hmac.new(key, canonical, hashlib.sha256).digest()).rstrip(b"=").decode()
|
||||
envelope = {"Authorisation": {"MatchID": fields[0], "ServerID": fields[1], "PlayerID": fields[2], "SteamID": fields[3], "Slot": 0, "Team": 0, "Protocol": fields[6], "Generation": 1, "ExpiresAt": expires}, "Signature": signature}
|
||||
@@ -36,14 +40,37 @@ envelope = {"Authorisation": {"MatchID": fields[0], "ServerID": fields[1], "Play
|
||||
(directory / "join-roster.json").write_text(json.dumps([base64.urlsafe_b64encode(json.dumps(envelope, separators=(",", ":")).encode()).rstrip(b"=").decode()]) + "\n")
|
||||
PY
|
||||
|
||||
command -v openssl >/dev/null 2>&1 || { echo "OpenSSL is required for the HTTPS fake Kubernetes API" >&2; exit 2; }
|
||||
printf 'compose-kubernetes-token' > "$smoke_dir/kubernetes-token"
|
||||
openssl req -x509 -newkey rsa:2048 -nodes -days 1 \
|
||||
-subj '/CN=agones-provider' -addext 'subjectAltName=DNS:agones-provider' \
|
||||
-keyout "$smoke_dir/fake-agones.key" -out "$smoke_dir/fake-agones.crt" >/dev/null 2>&1
|
||||
|
||||
token="$(python3 - "$secret" <<'PY'
|
||||
import base64, datetime, hashlib, hmac, json, sys
|
||||
secret = sys.argv[1].encode()
|
||||
payload = {"a": "compose-allocation-0001", "e": (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=1)).isoformat().replace("+00:00", "Z")}
|
||||
encoded = base64.urlsafe_b64encode(json.dumps(payload, separators=(",", ":")).encode()).rstrip(b"=")
|
||||
signature = hmac.new(secret, encoded, hashlib.sha256).digest()
|
||||
sig = base64.urlsafe_b64encode(signature).rstrip(b"=")
|
||||
print(encoded.decode() + "." + sig.decode())
|
||||
PY
|
||||
)"
|
||||
export COSMIC_CLASH_COMPOSE_WORKLOAD_TOKEN="$token"
|
||||
|
||||
"${compose[@]}" down --volumes --remove-orphans >/dev/null 2>&1 || true
|
||||
"${compose[@]}" up -d --build
|
||||
|
||||
for attempt in $(seq 1 60); do
|
||||
if "${compose[@]}" logs game-server 2>/dev/null | grep -q '"event":"server_started"'; then
|
||||
for attempt in $(seq 1 180); do
|
||||
if "${compose[@]}" logs game-server 2>/dev/null | grep -q ' server_started '; then
|
||||
break
|
||||
fi
|
||||
if [[ "$attempt" == 60 ]]; then
|
||||
if ! "${compose[@]}" ps --status running --services | grep -qx game-server; then
|
||||
"${compose[@]}" logs game-server >&2
|
||||
echo "allocated Compose game server exited before becoming ready" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$attempt" == 180 ]]; then
|
||||
"${compose[@]}" logs >&2
|
||||
echo "allocated Compose game server did not become ready" >&2
|
||||
exit 1
|
||||
@@ -70,8 +97,8 @@ done
|
||||
INSERT INTO identities (player_id, steam_id) VALUES ('compose-abandon-player', 'compose-abandon-steam');
|
||||
INSERT INTO queue_tickets (ticket_id, player_id, playlist, state, client_build, protocol_version, enqueued_at, expires_at)
|
||||
VALUES ('compose-abandon-ticket', 'compose-abandon-player', 'ranked', 'LIVE', 'build-1', 1, now() - interval '2 minutes', now() + interval '1 hour');
|
||||
INSERT INTO matches (match_id, playlist, state, region, protocol_version, server_id)
|
||||
VALUES ('compose-abandon-match', 'ranked', 'LIVE', 'EU', 1, 'compose-abandon-server');
|
||||
INSERT INTO matches (match_id, playlist, state, region, protocol_version, server_id, arena_path)
|
||||
VALUES ('compose-abandon-match', 'ranked', 'LIVE', 'EU', 1, 'compose-abandon-server', 'res://scenes/arena_01.tscn');
|
||||
INSERT INTO match_participants (match_id, player_id, ticket_id, slot, team, connection_generation, connected_at, disconnected_at)
|
||||
VALUES ('compose-abandon-match', 'compose-abandon-player', 'compose-abandon-ticket', 0, 0, 1, now() - interval '2 minutes', now() - interval '61 seconds');
|
||||
SQL
|
||||
@@ -168,57 +195,42 @@ done
|
||||
# workload authentication and mutation boundaries for every action below.
|
||||
"${compose[@]}" exec -T database psql -v ON_ERROR_STOP=1 -U cosmic_clash_test -d cosmic_clash_test <<'SQL'
|
||||
INSERT INTO game_servers (server_id, region, build, protocol_version, transport, state)
|
||||
VALUES ('compose-server', 'EU', 'build-1', 1, 'enet', 'ALLOCATED');
|
||||
VALUES ('compose-server-0001', 'EU', 'build-1', 1, 'enet', 'ALLOCATED');
|
||||
INSERT INTO matches (match_id, playlist, state, region, protocol_version, server_id, revision)
|
||||
VALUES ('compose-match', 'casual', 'RESULT_PENDING', 'EU', 1, 'compose-server', 2);
|
||||
VALUES ('compose-match-0001', 'casual', 'RESULT_PENDING', 'EU', 1, 'compose-server-0001', 2);
|
||||
INSERT INTO allocations (allocation_id, match_id, server_id, region, build, protocol_version, transport, request_digest, state, allocated_at)
|
||||
VALUES ('compose-allocation', 'compose-match', 'compose-server', 'EU', 'build-1', 1, 'enet', decode(repeat('00', 32), 'hex'), 'ALLOCATED', now());
|
||||
VALUES ('compose-allocation-0001', 'compose-match-0001', 'compose-server-0001', 'EU', 'build-1', 1, 'enet', decode(repeat('00', 32), 'hex'), 'ALLOCATED', now());
|
||||
SQL
|
||||
|
||||
token="$(python3 - "$secret" <<'PY'
|
||||
import base64, hashlib, hmac, json, sys, time
|
||||
secret = sys.argv[1].encode()
|
||||
payload = {"a": "compose-allocation", "e": time.time() + 300}
|
||||
encoded = base64.urlsafe_b64encode(json.dumps(payload, separators=(",", ":")).encode()).rstrip(b"=")
|
||||
signature = hmac.new(secret, encoded, hashlib.sha256).digest()
|
||||
sig = base64.urlsafe_b64encode(signature).rstrip(b"=")
|
||||
print(encoded.decode() + "." + sig.decode())
|
||||
PY
|
||||
)"
|
||||
|
||||
result_body='{"match_id":"compose-match","result_nonce":"compose-result-nonce-1234","score":{"team_0":3,"team_1":2},"integrity_state":"CERTIFIED"}'
|
||||
result_body='{"match_id":"compose-match-0001","result_nonce":"compose-result-nonce-1234","score":{"team_0":3,"team_1":2},"integrity_state":"CERTIFIED"}'
|
||||
curl -fsS -o /dev/null -w '%{http_code}' \
|
||||
-X POST "$api_url/v1/servers/compose-server/result" \
|
||||
-X POST "$api_url/v1/servers/compose-server-0001/result" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H 'Idempotency-Key: compose-result-key-123456' \
|
||||
-H 'Content-Type: application/json' -d "$result_body" | grep -qx 202
|
||||
|
||||
# An identical retry must be acknowledged without a second receipt.
|
||||
curl -fsS -o /dev/null -w '%{http_code}' \
|
||||
-X POST "$api_url/v1/servers/compose-server/result" \
|
||||
-X POST "$api_url/v1/servers/compose-server-0001/result" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H 'Idempotency-Key: compose-result-key-123456' \
|
||||
-H 'Content-Type: application/json' -d "$result_body" | grep -qx 202
|
||||
|
||||
"${compose[@]}" exec -T database psql -At -U cosmic_clash_test -d cosmic_clash_test -c "SELECT state FROM matches WHERE match_id = 'compose-match'" | grep -qx COMPLETED
|
||||
"${compose[@]}" exec -T database psql -At -U cosmic_clash_test -d cosmic_clash_test -c "SELECT count(*) FROM result_receipts WHERE match_id = 'compose-match'" | grep -qx 1
|
||||
"${compose[@]}" exec -T database psql -At -U cosmic_clash_test -d cosmic_clash_test -c "SELECT state FROM matches WHERE match_id = 'compose-match-0001'" | grep -qx COMPLETED
|
||||
"${compose[@]}" exec -T database psql -At -U cosmic_clash_test -d cosmic_clash_test -c "SELECT count(*) FROM result_receipts WHERE match_id = 'compose-match-0001'" | grep -qx 1
|
||||
|
||||
curl -fsS -o /dev/null -w '%{http_code}' \
|
||||
-X POST "$api_url/v1/servers/compose-server/shutdown" \
|
||||
-X POST "$api_url/v1/servers/compose-server-0001/shutdown" \
|
||||
-H "Authorization: Bearer $token" \
|
||||
-H 'Idempotency-Key: compose-shutdown-key-123456' \
|
||||
-H 'Content-Type: application/json' -d '{"reason":"server_draining"}' | grep -qx 204
|
||||
|
||||
"${compose[@]}" exec -T database psql -At -U cosmic_clash_test -d cosmic_clash_test -c "SELECT count(*) FROM audit_events WHERE action = 'SERVER_SHUTDOWN' AND aggregate_id = 'compose-match'" | grep -qx 1
|
||||
"${compose[@]}" exec -T database psql -At -U cosmic_clash_test -d cosmic_clash_test -c "SELECT count(*) FROM audit_events WHERE action = 'SERVER_SHUTDOWN' AND aggregate_id = 'compose-match-0001'" | grep -qx 1
|
||||
|
||||
"${compose[@]}" stop -t 12 game-server >/dev/null
|
||||
if "${compose[@]}" ps --status running --services | grep -qx game-server; then
|
||||
echo "allocated game-server did not stop after supervisor drain" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! "${compose[@]}" logs game-server | grep -q '"event":"server_draining"'; then
|
||||
echo "allocated game-server did not record a drain request" >&2
|
||||
exit 1
|
||||
fi
|
||||
"${compose[@]}" stop -t 10 control-plane >/dev/null
|
||||
echo "8.48 PASS: allocated Compose HTTP result/retry/shutdown and supervisor drain completed"
|
||||
|
||||
Reference in New Issue
Block a user