feat: runtime manager

This commit is contained in:
Ilia Denisov
2026-04-28 20:39:18 +02:00
committed by GitHub
parent e0a99b346b
commit a7cee15115
289 changed files with 45660 additions and 2207 deletions
@@ -85,6 +85,18 @@ const (
// NotificationTypeLobbyRaceNameRegistrationDenied identifies the
// `lobby.race_name.registration_denied` notification.
NotificationTypeLobbyRaceNameRegistrationDenied = notificationintent.NotificationTypeLobbyRaceNameRegistrationDenied
// NotificationTypeRuntimeImagePullFailed identifies the
// `runtime.image_pull_failed` notification.
NotificationTypeRuntimeImagePullFailed = notificationintent.NotificationTypeRuntimeImagePullFailed
// NotificationTypeRuntimeContainerStartFailed identifies the
// `runtime.container_start_failed` notification.
NotificationTypeRuntimeContainerStartFailed = notificationintent.NotificationTypeRuntimeContainerStartFailed
// NotificationTypeRuntimeStartConfigInvalid identifies the
// `runtime.start_config_invalid` notification.
NotificationTypeRuntimeStartConfigInvalid = notificationintent.NotificationTypeRuntimeStartConfigInvalid
)
// Producer identifies one supported upstream producer.
@@ -99,6 +111,9 @@ const (
// ProducerGameLobby identifies Game Lobby.
ProducerGameLobby = notificationintent.ProducerGameLobby
// ProducerRuntimeManager identifies Runtime Manager.
ProducerRuntimeManager = notificationintent.ProducerRuntimeManager
)
// AudienceKind identifies one supported target-audience kind.
+38 -14
View File
@@ -46,10 +46,13 @@ const (
userServiceBaseURLEnvVar = "NOTIFICATION_USER_SERVICE_BASE_URL"
userServiceTimeoutEnvVar = "NOTIFICATION_USER_SERVICE_TIMEOUT"
adminEmailsGeoReviewRecommendedEnvVar = "NOTIFICATION_ADMIN_EMAILS_GEO_REVIEW_RECOMMENDED"
adminEmailsGameGenerationFailedEnvVar = "NOTIFICATION_ADMIN_EMAILS_GAME_GENERATION_FAILED"
adminEmailsLobbyRuntimePausedAfterEnvVar = "NOTIFICATION_ADMIN_EMAILS_LOBBY_RUNTIME_PAUSED_AFTER_START"
adminEmailsLobbyApplicationSubmittedEnvVar = "NOTIFICATION_ADMIN_EMAILS_LOBBY_APPLICATION_SUBMITTED"
adminEmailsGeoReviewRecommendedEnvVar = "NOTIFICATION_ADMIN_EMAILS_GEO_REVIEW_RECOMMENDED"
adminEmailsGameGenerationFailedEnvVar = "NOTIFICATION_ADMIN_EMAILS_GAME_GENERATION_FAILED"
adminEmailsLobbyRuntimePausedAfterEnvVar = "NOTIFICATION_ADMIN_EMAILS_LOBBY_RUNTIME_PAUSED_AFTER_START"
adminEmailsLobbyApplicationSubmittedEnvVar = "NOTIFICATION_ADMIN_EMAILS_LOBBY_APPLICATION_SUBMITTED"
adminEmailsRuntimeImagePullFailedEnvVar = "NOTIFICATION_ADMIN_EMAILS_RUNTIME_IMAGE_PULL_FAILED"
adminEmailsRuntimeContainerStartFailedEnvVar = "NOTIFICATION_ADMIN_EMAILS_RUNTIME_CONTAINER_START_FAILED"
adminEmailsRuntimeStartConfigInvalidEnvVar = "NOTIFICATION_ADMIN_EMAILS_RUNTIME_START_CONFIG_INVALID"
otelServiceNameEnvVar = "OTEL_SERVICE_NAME"
otelTracesExporterEnvVar = "OTEL_TRACES_EXPORTER"
@@ -60,18 +63,18 @@ const (
otelStdoutTracesEnabledEnvVar = "NOTIFICATION_OTEL_STDOUT_TRACES_ENABLED"
otelStdoutMetricsEnabledEnvVar = "NOTIFICATION_OTEL_STDOUT_METRICS_ENABLED"
defaultShutdownTimeout = 5 * time.Second
defaultLogLevel = "info"
defaultInternalHTTPAddr = ":8092"
defaultReadHeaderTimeout = 2 * time.Second
defaultReadTimeout = 10 * time.Second
defaultIdleTimeout = time.Minute
defaultShutdownTimeout = 5 * time.Second
defaultLogLevel = "info"
defaultInternalHTTPAddr = ":8092"
defaultReadHeaderTimeout = 2 * time.Second
defaultReadTimeout = 10 * time.Second
defaultIdleTimeout = time.Minute
defaultIntentsStream = "notification:intents"
defaultIntentsReadBlockTimeout = 2 * time.Second
defaultGatewayClientEventsStream = "gateway:client-events"
defaultIntentsStream = "notification:intents"
defaultIntentsReadBlockTimeout = 2 * time.Second
defaultGatewayClientEventsStream = "gateway:client-events"
defaultGatewayClientEventsStreamMaxLen int64 = 1024
defaultMailDeliveryCommandsStream = "mail:delivery_commands"
defaultMailDeliveryCommandsStream = "mail:delivery_commands"
defaultPushRetryMaxAttempts = 3
defaultEmailRetryMaxAttempts = 7
@@ -352,6 +355,18 @@ type AdminRoutingConfig struct {
// LobbyApplicationSubmitted stores recipients for public
// `lobby.application.submitted` notifications.
LobbyApplicationSubmitted []string
// RuntimeImagePullFailed stores recipients for
// `runtime.image_pull_failed`.
RuntimeImagePullFailed []string
// RuntimeContainerStartFailed stores recipients for
// `runtime.container_start_failed`.
RuntimeContainerStartFailed []string
// RuntimeStartConfigInvalid stores recipients for
// `runtime.start_config_invalid`.
RuntimeStartConfigInvalid []string
}
// Validate reports whether cfg stores valid normalized administrator email
@@ -369,6 +384,15 @@ func (cfg AdminRoutingConfig) Validate() error {
if err := validateNormalizedEmailList("lobby.application.submitted", cfg.LobbyApplicationSubmitted); err != nil {
return err
}
if err := validateNormalizedEmailList("runtime.image_pull_failed", cfg.RuntimeImagePullFailed); err != nil {
return err
}
if err := validateNormalizedEmailList("runtime.container_start_failed", cfg.RuntimeContainerStartFailed); err != nil {
return err
}
if err := validateNormalizedEmailList("runtime.start_config_invalid", cfg.RuntimeStartConfigInvalid); err != nil {
return err
}
return nil
}
+12 -5
View File
@@ -19,11 +19,11 @@ const (
envRedisTLSEnabled = "NOTIFICATION_REDIS_TLS_ENABLED"
envRedisUsername = "NOTIFICATION_REDIS_USERNAME"
envPostgresPrimaryDSN = "NOTIFICATION_POSTGRES_PRIMARY_DSN"
envPostgresOpTimeout = "NOTIFICATION_POSTGRES_OPERATION_TIMEOUT"
envPostgresMaxOpenConns = "NOTIFICATION_POSTGRES_MAX_OPEN_CONNS"
envPostgresMaxIdleConns = "NOTIFICATION_POSTGRES_MAX_IDLE_CONNS"
envPostgresConnMaxLife = "NOTIFICATION_POSTGRES_CONN_MAX_LIFETIME"
envPostgresPrimaryDSN = "NOTIFICATION_POSTGRES_PRIMARY_DSN"
envPostgresOpTimeout = "NOTIFICATION_POSTGRES_OPERATION_TIMEOUT"
envPostgresMaxOpenConns = "NOTIFICATION_POSTGRES_MAX_OPEN_CONNS"
envPostgresMaxIdleConns = "NOTIFICATION_POSTGRES_MAX_IDLE_CONNS"
envPostgresConnMaxLife = "NOTIFICATION_POSTGRES_CONN_MAX_LIFETIME"
)
const (
@@ -104,6 +104,9 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
t.Setenv(adminEmailsGameGenerationFailedEnvVar, "ops@example.com")
t.Setenv(adminEmailsLobbyRuntimePausedAfterEnvVar, "pause@example.com, PAUSE@example.com")
t.Setenv(adminEmailsLobbyApplicationSubmittedEnvVar, "owner@example.com, OWNER@example.com")
t.Setenv(adminEmailsRuntimeImagePullFailedEnvVar, "image-pull-ops@example.com, IMAGE-PULL-OPS@example.com")
t.Setenv(adminEmailsRuntimeContainerStartFailedEnvVar, "container-start-ops@example.com")
t.Setenv(adminEmailsRuntimeStartConfigInvalidEnvVar, "start-config-ops@example.com, START-CONFIG-OPS@example.com")
t.Setenv(otelServiceNameEnvVar, "custom-notification")
t.Setenv(otelTracesExporterEnvVar, "otlp")
t.Setenv(otelMetricsExporterEnvVar, "otlp")
@@ -172,6 +175,9 @@ func TestLoadFromEnvAppliesOverrides(t *testing.T) {
GameGenerationFailed: []string{"ops@example.com"},
LobbyRuntimePausedAfterStart: []string{"pause@example.com"},
LobbyApplicationSubmitted: []string{"owner@example.com"},
RuntimeImagePullFailed: []string{"image-pull-ops@example.com"},
RuntimeContainerStartFailed: []string{"container-start-ops@example.com"},
RuntimeStartConfigInvalid: []string{"start-config-ops@example.com"},
}, cfg.AdminRouting)
require.Equal(t, TelemetryConfig{
ServiceName: "custom-notification",
@@ -295,6 +301,7 @@ func TestLoadFromEnvRejectsInvalidConfiguration(t *testing.T) {
{name: "invalid admin email", envName: adminEmailsGeoReviewRecommendedEnvVar, envVal: "broken-email", want: "invalid email address"},
{name: "blank admin email slot", envName: adminEmailsGameGenerationFailedEnvVar, envVal: "ops@example.com, , second@example.com", want: "must not be empty"},
{name: "invalid public application admin email", envName: adminEmailsLobbyApplicationSubmittedEnvVar, envVal: "Owner <owner@example.com>", want: "must not include a display name"},
{name: "invalid runtime image pull admin email", envName: adminEmailsRuntimeImagePullFailedEnvVar, envVal: "broken-runtime-email", want: "invalid email address"},
{name: "nonpositive gateway client events stream max len", envName: gatewayClientEventsStreamMaxEnvVar, envVal: "0", want: "must be positive"},
{name: "backoff min above max", envName: routeBackoffMinEnvVar, envVal: "10m", want: "must not exceed"},
}
+12
View File
@@ -128,6 +128,18 @@ func LoadFromEnv() (Config, error) {
if err != nil {
return Config{}, err
}
cfg.AdminRouting.RuntimeImagePullFailed, err = emailListEnv(adminEmailsRuntimeImagePullFailedEnvVar, cfg.AdminRouting.RuntimeImagePullFailed)
if err != nil {
return Config{}, err
}
cfg.AdminRouting.RuntimeContainerStartFailed, err = emailListEnv(adminEmailsRuntimeContainerStartFailedEnvVar, cfg.AdminRouting.RuntimeContainerStartFailed)
if err != nil {
return Config{}, err
}
cfg.AdminRouting.RuntimeStartConfigInvalid, err = emailListEnv(adminEmailsRuntimeStartConfigInvalidEnvVar, cfg.AdminRouting.RuntimeStartConfigInvalid)
if err != nil {
return Config{}, err
}
cfg.Telemetry.ServiceName = stringEnv(otelServiceNameEnvVar, cfg.Telemetry.ServiceName)
cfg.Telemetry.TracesExporter = normalizeExporterValue(stringEnv(otelTracesExporterEnvVar, cfg.Telemetry.TracesExporter))
@@ -809,6 +809,12 @@ func (service *Service) adminEmailsFor(notificationType intentstream.Notificatio
return append([]string(nil), service.adminRouting.LobbyRuntimePausedAfterStart...)
case intentstream.NotificationTypeLobbyApplicationSubmitted:
return append([]string(nil), service.adminRouting.LobbyApplicationSubmitted...)
case intentstream.NotificationTypeRuntimeImagePullFailed:
return append([]string(nil), service.adminRouting.RuntimeImagePullFailed...)
case intentstream.NotificationTypeRuntimeContainerStartFailed:
return append([]string(nil), service.adminRouting.RuntimeContainerStartFailed...)
case intentstream.NotificationTypeRuntimeStartConfigInvalid:
return append([]string(nil), service.adminRouting.RuntimeStartConfigInvalid...)
default:
return nil
}