113 lines
4.5 KiB
Go
113 lines
4.5 KiB
Go
// Package ports defines the stable interfaces that connect Runtime
|
|
// Manager use cases to external state and external services.
|
|
package ports
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"galaxy/rtmanager/internal/domain/runtime"
|
|
)
|
|
|
|
// RuntimeRecordStore stores runtime records and exposes the operations
|
|
// used by the service layer (Stages 13+) and the workers (Stages 15-18).
|
|
// Adapters must preserve domain semantics:
|
|
//
|
|
// - Get returns runtime.ErrNotFound when no record exists for gameID.
|
|
// - Upsert installs a record verbatim; the caller is responsible for
|
|
// domain validation through runtime.RuntimeRecord.Validate.
|
|
// - UpdateStatus applies one transition through a compare-and-swap
|
|
// guard on (status, current_container_id) and returns
|
|
// runtime.ErrConflict on a stale CAS.
|
|
// - List returns every record currently stored, regardless of status.
|
|
// - ListByStatus returns every record currently indexed under status.
|
|
type RuntimeRecordStore interface {
|
|
// Get returns the record identified by gameID. It returns
|
|
// runtime.ErrNotFound when no record exists.
|
|
Get(ctx context.Context, gameID string) (runtime.RuntimeRecord, error)
|
|
|
|
// Upsert inserts record when no row exists for record.GameID and
|
|
// otherwise overwrites every column verbatim. The start service uses
|
|
// Upsert to install fresh records on start, the inner start of
|
|
// restart and patch, and the reconcile_adopt path.
|
|
Upsert(ctx context.Context, record runtime.RuntimeRecord) error
|
|
|
|
// UpdateStatus applies one status transition in a compare-and-swap
|
|
// fashion. The adapter must first call runtime.Transition to reject
|
|
// invalid pairs without touching the store, then verify that the
|
|
// stored status equals input.ExpectedFrom, and (when
|
|
// input.ExpectedContainerID is non-empty) that the stored
|
|
// current_container_id equals it. The adapter derives stopped_at /
|
|
// removed_at and updates last_op_at from input.Now per the
|
|
// destination status.
|
|
UpdateStatus(ctx context.Context, input UpdateStatusInput) error
|
|
|
|
// List returns every runtime record currently stored. Used by the
|
|
// internal REST list endpoint; the v1 working set is bounded by the
|
|
// games tracked by Lobby and is small enough to return in one
|
|
// response (pagination is not supported). The order is
|
|
// adapter-defined; callers may reorder as needed.
|
|
List(ctx context.Context) ([]runtime.RuntimeRecord, error)
|
|
|
|
// ListByStatus returns every record currently indexed under status.
|
|
// The order is adapter-defined; callers may reorder as needed.
|
|
ListByStatus(ctx context.Context, status runtime.Status) ([]runtime.RuntimeRecord, error)
|
|
}
|
|
|
|
// UpdateStatusInput stores the arguments required to apply one status
|
|
// transition through a RuntimeRecordStore. The adapter is responsible
|
|
// for translating the destination status into the matching column
|
|
// updates (stopped_at / removed_at / current_container_id NULLing) and
|
|
// for the CAS guard.
|
|
type UpdateStatusInput struct {
|
|
// GameID identifies the record to mutate.
|
|
GameID string
|
|
|
|
// ExpectedFrom stores the status the caller believes the record
|
|
// currently has. A mismatch results in runtime.ErrConflict.
|
|
ExpectedFrom runtime.Status
|
|
|
|
// ExpectedContainerID is an optional CAS guard. When non-empty, the
|
|
// adapter rejects the update with runtime.ErrConflict if the stored
|
|
// current_container_id does not equal it. Used by stop / cleanup /
|
|
// reconcile to protect against concurrent restart races. Empty
|
|
// disables the container-id CAS while keeping the status CAS.
|
|
ExpectedContainerID string
|
|
|
|
// To stores the destination status.
|
|
To runtime.Status
|
|
|
|
// Now stores the wall-clock used to derive stopped_at / removed_at
|
|
// and last_op_at depending on To.
|
|
Now time.Time
|
|
}
|
|
|
|
// Validate reports whether input contains a structurally valid status
|
|
// transition request. Adapters call Validate before touching the store.
|
|
func (input UpdateStatusInput) Validate() error {
|
|
if strings.TrimSpace(input.GameID) == "" {
|
|
return fmt.Errorf("update runtime status: game id must not be empty")
|
|
}
|
|
if !input.ExpectedFrom.IsKnown() {
|
|
return fmt.Errorf(
|
|
"update runtime status: expected from status %q is unsupported",
|
|
input.ExpectedFrom,
|
|
)
|
|
}
|
|
if !input.To.IsKnown() {
|
|
return fmt.Errorf(
|
|
"update runtime status: to status %q is unsupported",
|
|
input.To,
|
|
)
|
|
}
|
|
if err := runtime.Transition(input.ExpectedFrom, input.To); err != nil {
|
|
return fmt.Errorf("update runtime status: %w", err)
|
|
}
|
|
if input.Now.IsZero() {
|
|
return fmt.Errorf("update runtime status: now must not be zero")
|
|
}
|
|
return nil
|
|
}
|