feat: runtime manager
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"galaxy/lobby/internal/domain/engineimage"
|
||||
"galaxy/lobby/internal/telemetry"
|
||||
"galaxy/postgres"
|
||||
"galaxy/redisconn"
|
||||
@@ -49,6 +50,8 @@ const (
|
||||
raceNameDirectoryBackendEnvVar = "LOBBY_RACE_NAME_DIRECTORY_BACKEND"
|
||||
raceNameExpirationIntervalEnvVar = "LOBBY_RACE_NAME_EXPIRATION_INTERVAL"
|
||||
|
||||
engineImageTemplateEnvVar = "LOBBY_ENGINE_IMAGE_TEMPLATE"
|
||||
|
||||
otelServiceNameEnvVar = "OTEL_SERVICE_NAME"
|
||||
otelTracesExporterEnvVar = "OTEL_TRACES_EXPORTER"
|
||||
otelMetricsExporterEnvVar = "OTEL_METRICS_EXPORTER"
|
||||
@@ -78,6 +81,7 @@ const (
|
||||
defaultGMTimeout = 5 * time.Second
|
||||
defaultEnrollmentAutomationInterval = 30 * time.Second
|
||||
defaultRaceNameExpirationInterval = time.Hour
|
||||
defaultEngineImageTemplate = "galaxy/game:" + engineimage.VersionPlaceholder
|
||||
defaultOTelServiceName = "galaxy-lobby"
|
||||
|
||||
// RaceNameDirectoryBackendPostgres selects the PostgreSQL-backed
|
||||
@@ -134,6 +138,9 @@ type Config struct {
|
||||
// every pending_registration whose eligible_until has passed.
|
||||
PendingRegistration PendingRegistrationConfig
|
||||
|
||||
// RuntimeManager configures the Runtime Manager publisher contract.
|
||||
RuntimeManager RuntimeManagerConfig
|
||||
|
||||
// Telemetry configures the process-wide OpenTelemetry runtime.
|
||||
Telemetry TelemetryConfig
|
||||
}
|
||||
@@ -410,6 +417,27 @@ func (cfg PendingRegistrationConfig) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RuntimeManagerConfig configures the Lobby-side Runtime Manager
|
||||
// publisher contract. Lobby resolves the Docker image reference it
|
||||
// publishes on `runtime:start_jobs` from a per-game
|
||||
// `target_engine_version` and the configured EngineImageTemplate.
|
||||
type RuntimeManagerConfig struct {
|
||||
// EngineImageTemplate stores the Docker reference template applied
|
||||
// to a game's `target_engine_version`. The string must contain the
|
||||
// literal placeholder `{engine_version}`; Lobby fails fast at
|
||||
// startup otherwise.
|
||||
EngineImageTemplate string
|
||||
}
|
||||
|
||||
// Validate reports whether cfg stores a usable Runtime Manager
|
||||
// publisher configuration.
|
||||
func (cfg RuntimeManagerConfig) Validate() error {
|
||||
if _, err := engineimage.NewResolver(cfg.EngineImageTemplate); err != nil {
|
||||
return fmt.Errorf("engine image template: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TelemetryConfig configures the Game Lobby Service OpenTelemetry runtime.
|
||||
type TelemetryConfig struct {
|
||||
// ServiceName overrides the default OpenTelemetry service name.
|
||||
@@ -504,6 +532,9 @@ func DefaultConfig() Config {
|
||||
PendingRegistration: PendingRegistrationConfig{
|
||||
Interval: defaultRaceNameExpirationInterval,
|
||||
},
|
||||
RuntimeManager: RuntimeManagerConfig{
|
||||
EngineImageTemplate: defaultEngineImageTemplate,
|
||||
},
|
||||
Telemetry: TelemetryConfig{
|
||||
ServiceName: defaultOTelServiceName,
|
||||
TracesExporter: "none",
|
||||
|
||||
@@ -40,6 +40,7 @@ func TestDefaultConfig(t *testing.T) {
|
||||
assert.Equal(t, 5*time.Second, cfg.GM.Timeout)
|
||||
assert.Equal(t, 30*time.Second, cfg.EnrollmentAutomation.Interval)
|
||||
assert.Equal(t, time.Hour, cfg.PendingRegistration.Interval)
|
||||
assert.Equal(t, "galaxy/game:{engine_version}", cfg.RuntimeManager.EngineImageTemplate)
|
||||
assert.Equal(t, "galaxy-lobby", cfg.Telemetry.ServiceName)
|
||||
assert.Equal(t, "none", cfg.Telemetry.TracesExporter)
|
||||
assert.Equal(t, "none", cfg.Telemetry.MetricsExporter)
|
||||
@@ -114,6 +115,7 @@ func TestLoadFromEnvOverrides(t *testing.T) {
|
||||
t.Setenv("LOBBY_NOTIFICATION_INTENTS_STREAM", "alt:intents")
|
||||
t.Setenv("LOBBY_ENROLLMENT_AUTOMATION_INTERVAL", "45s")
|
||||
t.Setenv("LOBBY_RACE_NAME_EXPIRATION_INTERVAL", "15m")
|
||||
t.Setenv("LOBBY_ENGINE_IMAGE_TEMPLATE", "registry.example.com/galaxy/game:{engine_version}")
|
||||
t.Setenv("OTEL_SERVICE_NAME", "galaxy-lobby-test")
|
||||
|
||||
cfg, err := LoadFromEnv()
|
||||
@@ -129,6 +131,7 @@ func TestLoadFromEnvOverrides(t *testing.T) {
|
||||
assert.Equal(t, "alt:intents", cfg.Redis.NotificationIntentsStream)
|
||||
assert.Equal(t, 45*time.Second, cfg.EnrollmentAutomation.Interval)
|
||||
assert.Equal(t, 15*time.Minute, cfg.PendingRegistration.Interval)
|
||||
assert.Equal(t, "registry.example.com/galaxy/game:{engine_version}", cfg.RuntimeManager.EngineImageTemplate)
|
||||
assert.Equal(t, "galaxy-lobby-test", cfg.Telemetry.ServiceName)
|
||||
}
|
||||
|
||||
@@ -291,6 +294,34 @@ func TestEnrollmentAutomationConfigValidate(t *testing.T) {
|
||||
require.ErrorContains(t, EnrollmentAutomationConfig{}.Validate(), "interval must be positive")
|
||||
}
|
||||
|
||||
func TestRuntimeManagerConfigValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require.NoError(t, RuntimeManagerConfig{EngineImageTemplate: "galaxy/game:{engine_version}"}.Validate())
|
||||
require.ErrorContains(t,
|
||||
RuntimeManagerConfig{EngineImageTemplate: ""}.Validate(),
|
||||
"template must not be empty",
|
||||
)
|
||||
require.ErrorContains(t,
|
||||
RuntimeManagerConfig{EngineImageTemplate: "galaxy/game:1.0.0"}.Validate(),
|
||||
"placeholder",
|
||||
)
|
||||
}
|
||||
|
||||
func TestLoadFromEnvRejectsInvalidEngineImageTemplate(t *testing.T) {
|
||||
clearAllEnv(t)
|
||||
t.Setenv("LOBBY_REDIS_MASTER_ADDR", testRedisAddr)
|
||||
t.Setenv("LOBBY_REDIS_PASSWORD", testRedisSecret)
|
||||
t.Setenv("LOBBY_POSTGRES_PRIMARY_DSN", testDSN)
|
||||
t.Setenv("LOBBY_USER_SERVICE_BASE_URL", testUserBaseURL)
|
||||
t.Setenv("LOBBY_GM_BASE_URL", testGMBaseURL)
|
||||
t.Setenv("LOBBY_ENGINE_IMAGE_TEMPLATE", "galaxy/game:no-placeholder")
|
||||
|
||||
_, err := LoadFromEnv()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "LOBBY_ENGINE_IMAGE_TEMPLATE")
|
||||
}
|
||||
|
||||
func TestPendingRegistrationConfigValidate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -367,6 +398,7 @@ func clearAllEnv(t *testing.T) {
|
||||
enrollmentAutomationIntervalEnvVar,
|
||||
raceNameDirectoryBackendEnvVar,
|
||||
raceNameExpirationIntervalEnvVar,
|
||||
engineImageTemplateEnvVar,
|
||||
otelServiceNameEnvVar,
|
||||
otelTracesExporterEnvVar,
|
||||
otelMetricsExporterEnvVar,
|
||||
|
||||
@@ -108,6 +108,8 @@ func LoadFromEnv() (Config, error) {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
cfg.RuntimeManager.EngineImageTemplate = stringEnv(engineImageTemplateEnvVar, cfg.RuntimeManager.EngineImageTemplate)
|
||||
|
||||
cfg.Telemetry.ServiceName = stringEnv(otelServiceNameEnvVar, cfg.Telemetry.ServiceName)
|
||||
cfg.Telemetry.TracesExporter = normalizeExporterValue(stringEnv(otelTracesExporterEnvVar, cfg.Telemetry.TracesExporter))
|
||||
cfg.Telemetry.MetricsExporter = normalizeExporterValue(stringEnv(otelMetricsExporterEnvVar, cfg.Telemetry.MetricsExporter))
|
||||
|
||||
@@ -41,6 +41,9 @@ func (cfg Config) Validate() error {
|
||||
if err := cfg.PendingRegistration.Validate(); err != nil {
|
||||
return fmt.Errorf("%s: %w", raceNameExpirationIntervalEnvVar, err)
|
||||
}
|
||||
if err := cfg.RuntimeManager.Validate(); err != nil {
|
||||
return fmt.Errorf("%s: %w", engineImageTemplateEnvVar, err)
|
||||
}
|
||||
if err := cfg.Telemetry.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user