mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
fix(multiplayer): propagate allocated playlist
This commit is contained in:
@@ -1436,3 +1436,5 @@ The workload-authenticated `POST /servers/{serverId}/shutdown` contract is now e
|
||||
The allocated supervisor now calls that shutdown acknowledgment during signal-bound controlled drain, using the same workload credential and a deterministic idempotency key after the local drain request succeeds. The lifecycle test verifies the drain-before-ack ordering, credential separation, and bounded graceful child exit; live pod termination and control-plane outage behavior remain deployment gates.
|
||||
|
||||
Allocator-selected region, build, protocol, and transport now travel with the allocation as Agones annotations and override stale child launch flags immediately before an allocated process starts. The overlay rejects control characters and preserves direct-server command behavior; focused supervisor/allocator tests cover precedence and annotation payloads, while live Agones passthrough remains an infrastructure gate.
|
||||
|
||||
The same allocation path now carries the matcher-selected playlist, preventing a ranked match from inheriting the Fleet’s casual default. Durable allocation claims return the playlist, the worker includes it in Fleet selection metadata, Agones copies it to the allocated GameServer, and the supervisor overrides `--playlist` before launch; the existing compatibility tests remain green.
|
||||
|
||||
@@ -177,6 +177,9 @@ func (c Client) Allocate(ctx context.Context, request domain.AllocationRequest,
|
||||
"cosmic-clash.io/protocol": strconv.Itoa(request.Protocol),
|
||||
"cosmic-clash.io/transport": request.Transport,
|
||||
}
|
||||
if playlist := labels["cosmic-clash.io/playlist"]; playlist == string(domain.Casual) || playlist == string(domain.Ranked) {
|
||||
body.Spec.Metadata.Annotations["cosmic-clash.io/playlist"] = playlist
|
||||
}
|
||||
if len(c.WorkloadSecret) > 0 {
|
||||
ttl := c.WorkloadTokenTTL
|
||||
if ttl <= 0 {
|
||||
|
||||
@@ -59,10 +59,14 @@ func (w Worker) RunOnce(ctx context.Context) (bool, error) {
|
||||
// template. They are derived only from the durable match plan, never client
|
||||
// input or mutable worker configuration.
|
||||
func AllocationLabels(request domain.AllocationRequest) map[string]string {
|
||||
return map[string]string{
|
||||
labels := map[string]string{
|
||||
"cosmic-clash.io/region": request.Region,
|
||||
"cosmic-clash.io/build": request.Build,
|
||||
"cosmic-clash.io/protocol": strconv.Itoa(request.Protocol),
|
||||
"cosmic-clash.io/transport": request.Transport,
|
||||
}
|
||||
if request.Playlist != "" {
|
||||
labels["cosmic-clash.io/playlist"] = string(request.Playlist)
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
@@ -85,3 +85,10 @@ func TestAllocationLabelsMirrorFleetCompatibilityTuple(t *testing.T) {
|
||||
t.Fatalf("labels=%v want=%v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocationLabelsCarryPlaylistWhenKnown(t *testing.T) {
|
||||
labels := AllocationLabels(domain.AllocationRequest{Playlist: domain.Ranked, Region: "EU", Build: "build-1", Protocol: 1, Transport: "enet"})
|
||||
if labels["cosmic-clash.io/playlist"] != string(domain.Ranked) {
|
||||
t.Fatalf("playlist label = %q, want %q", labels["cosmic-clash.io/playlist"], domain.Ranked)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ type ReadyServer struct {
|
||||
type AllocationRequest struct {
|
||||
AllocationID string
|
||||
MatchID string
|
||||
Playlist Playlist
|
||||
Region string
|
||||
Build string
|
||||
Protocol int
|
||||
|
||||
@@ -30,7 +30,7 @@ UPDATE matches m
|
||||
SET allocation_id = 'allocation-' || candidate.match_id, allocation_claimed_at = $2
|
||||
FROM candidate
|
||||
WHERE m.match_id = candidate.match_id
|
||||
RETURNING m.match_id, m.region, m.protocol_version, m.allocation_id`
|
||||
RETURNING m.match_id, m.playlist, m.region, m.protocol_version, m.allocation_id`
|
||||
|
||||
const AllocatingMatchBuildSQL = `SELECT client_build
|
||||
FROM queue_tickets q
|
||||
@@ -200,10 +200,10 @@ func ClaimAllocatingMatch(ctx context.Context, db *sql.DB, transport string, now
|
||||
var item PendingAllocation
|
||||
found := false
|
||||
err := RunSerializable(ctx, db, DefaultSerializableAttempts, func(ctx context.Context, tx *sql.Tx) error {
|
||||
var matchID, region string
|
||||
var matchID, playlist, region string
|
||||
var protocol int
|
||||
var claimedID string
|
||||
err := tx.QueryRowContext(ctx, ClaimAllocatingMatchSQL, now.Add(-AllocationClaimLease), now).Scan(&matchID, ®ion, &protocol, &claimedID)
|
||||
err := tx.QueryRowContext(ctx, ClaimAllocatingMatchSQL, now.Add(-AllocationClaimLease), now).Scan(&matchID, &playlist, ®ion, &protocol, &claimedID)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil
|
||||
}
|
||||
@@ -233,7 +233,7 @@ func ClaimAllocatingMatch(ctx context.Context, db *sql.DB, transport string, now
|
||||
if build == "" {
|
||||
return fmt.Errorf("allocating match has no participants")
|
||||
}
|
||||
item.Request = domain.AllocationRequest{AllocationID: claimedID, MatchID: matchID, Region: region, Build: build, Protocol: protocol, Transport: transport}
|
||||
item.Request = domain.AllocationRequest{AllocationID: claimedID, MatchID: matchID, Playlist: domain.Playlist(playlist), Region: region, Build: build, Protocol: protocol, Transport: transport}
|
||||
found = true
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
func TestAllocationMatchClaimSQLFencesConcurrentWorkers(t *testing.T) {
|
||||
checks := map[string][]string{
|
||||
ClaimAllocatingMatchSQL: {"FOR UPDATE SKIP LOCKED", "allocation_id = 'allocation-' || candidate.match_id", "allocation_claimed_at <= $1", "ORDER BY created_at, match_id"},
|
||||
ClaimAllocatingMatchSQL: {"FOR UPDATE SKIP LOCKED", "allocation_id = 'allocation-' || candidate.match_id", "allocation_claimed_at <= $1", "ORDER BY created_at, match_id", "m.playlist"},
|
||||
AllocatingMatchBuildSQL: {"match_participants", "queue_tickets", "ORDER BY q.client_build"},
|
||||
BindAllocatedMatchParticipantsSQL: {"allocation_id = $2", "server_id IS NULL", "SET server_id = $3", "FROM allocations", "state = 'ALLOCATING'", "revision = revision + 1"},
|
||||
ReleaseAllocatedMatchClaimSQL: {"allocation_id = $2", "allocation_id = NULL", "allocation_claimed_at = NULL"},
|
||||
|
||||
@@ -343,6 +343,7 @@ func withAllocatedCompatibility(command []string, gameServer GameServer) ([]stri
|
||||
return command, nil
|
||||
}
|
||||
for annotation, flag := range map[string]string{
|
||||
"cosmic-clash.io/playlist": "playlist",
|
||||
"cosmic-clash.io/region": "region",
|
||||
"cosmic-clash.io/build": "client-build",
|
||||
"cosmic-clash.io/protocol": "protocol-version",
|
||||
|
||||
Reference in New Issue
Block a user