Fix run package profile and log ingestion

This commit is contained in:
npc0-hue
2026-08-30 23:59:18 +08:00
parent 8686419fb2
commit 8194671656
5 changed files with 94 additions and 2 deletions
@@ -234,6 +234,7 @@ func (svc *CoreService) runDistributionPackageContext(distribution domain.RunDis
} else if !errors.Is(bindingErr, repo.ErrNotFound) { } else if !errors.Is(bindingErr, repo.ErrNotFound) {
return runDistributionPackageContext{}, bindingErr return runDistributionPackageContext{}, bindingErr
} }
profileKey = defaultRunDistributionProfileKey(plugin, profileKey)
plan, err := runAutonomousLifecyclePlan(distribution, instance, plugin, profileKey, bindings) plan, err := runAutonomousLifecyclePlan(distribution, instance, plugin, profileKey, bindings)
if err != nil { if err != nil {
return runDistributionPackageContext{}, err return runDistributionPackageContext{}, err
@@ -245,6 +246,20 @@ func (svc *CoreService) runDistributionPackageContext(distribution domain.RunDis
return runDistributionPackageContext{profileKey: profileKey, workspaceSeed: seed, autonomousLifecycle: plan}, nil return runDistributionPackageContext{profileKey: profileKey, workspaceSeed: seed, autonomousLifecycle: plan}, nil
} }
func defaultRunDistributionProfileKey(plugin domain.GamePlugin, profileKey string) string {
profileKey = strings.TrimSpace(profileKey)
if profileKey != "" {
if profile, ok := runtimeLifecycleProfileForKey(plugin.RuntimeProfiles, profileKey); ok {
return profile.Key
}
return profileKey
}
if len(plugin.RuntimeProfiles.LifecycleProfiles) == 0 {
return ""
}
return plugin.RuntimeProfiles.LifecycleProfiles[0].Key
}
func runAutonomousLifecyclePlan(distribution domain.RunDistribution, instance domain.ServerInstance, plugin domain.GamePlugin, profileKey string, bindings map[string]string) (*domain.RunAutonomousLifecyclePlan, error) { func runAutonomousLifecyclePlan(distribution domain.RunDistribution, instance domain.ServerInstance, plugin domain.GamePlugin, profileKey string, bindings map[string]string) (*domain.RunAutonomousLifecyclePlan, error) {
plan := &domain.RunAutonomousLifecyclePlan{ plan := &domain.RunAutonomousLifecyclePlan{
SchemaVersion: "1", SchemaVersion: "1",
@@ -214,6 +214,55 @@ func hasAutonomousLogSource(sources []domain.RunAutonomousLogSource, kind string
return false return false
} }
func TestCoreServiceRunDistributionDefaultsEmptyDeploymentProfileToPluginLifecycleProfile(t *testing.T) {
svc := newTestCoreService()
plugin := scumDeploymentTestPlugin()
plugin.Name = "SCUM"
plugin.Version = "0.1.1"
plugin.ServerType = "scum"
plugin.Status = domain.GamePluginStatusInstalled
plugin.SupportedOS = []string{"windows"}
plugin.DeclaredPermissions = []string{"server.run.distribution"}
plugin.LifecycleActions = domain.PluginLifecycleActions{Install: "actions/install.json", Start: "actions/start.json", Stop: "actions/stop.json", Status: "actions/status.json"}
plugin.RuntimeProfiles.LifecycleProfiles = []domain.RuntimeLifecycleProfile{{Key: "run-local", Mode: "local-process", Capabilities: []string{domain.LifecycleCapabilityInstall, domain.LifecycleCapabilityStart, domain.LifecycleCapabilityStop, domain.LifecycleCapabilityStatus}, ActionRefs: domain.PluginLifecycleActions{Install: "actions/install.json", Start: "actions/start.json", Stop: "actions/stop.json", Status: "actions/status.json"}, Platforms: []string{"windows"}}}
requireManualSCUMRuntimeBindings(&plugin, "run-local")
if err := svc.store.GamePlugins().Create(plugin); err != nil {
t.Fatalf("create SCUM plugin: %v", err)
}
session := createServiceUserAndLogin(t, svc, domain.User{ID: "scum-default-profile-owner", DisplayName: "SCUM Default Profile Owner", Email: "scum-default-profile@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password"})
created, err := svc.CreateServerInstanceWorkflowForSession(session, domain.ServerLifecycleCreate{
ID: "scum-default-profile-package", PluginID: plugin.ID, Name: "SCUM Default Profile Package", IdempotencyKey: "scum-default-profile-package-create",
Deployment: domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeGuided, ServerRoot: `D:\scum-default-profile`, CreateInputs: map[string]string{"serverName": "Moon", "gamePort": "27000", "queryPort": "27015", "maxPlayers": "128"}},
})
if err != nil {
t.Fatalf("create SCUM guided draft without explicit profile: %v", err)
}
if created.Instance.Deployment.ProfileKey != "run-local" {
t.Fatalf("expected create defaults to persist plugin lifecycle profile, got %+v", created.Instance.Deployment)
}
legacy := created.Instance
legacy.Deployment.ProfileKey = ""
if err := svc.store.ServerInstances().Update(legacy); err != nil {
t.Fatalf("simulate legacy empty deployment profile: %v", err)
}
inputs := make(chan domain.DistributionBuildInput, 1)
svc.ConfigureDistributionBuilder(captureDistributionBuilder{inputs: inputs, payload: []byte("profile-default-build")})
if _, err := svc.GenerateRunDistributionForSession(session, domain.RunDistributionGenerateRequest{ServerInstanceID: legacy.ID, TargetOS: "windows", TargetArch: "amd64", IdempotencyKey: "scum-default-profile-package"}); err != nil {
t.Fatalf("generate Run distribution with empty deployment profile: %v", err)
}
var platformInput domain.DistributionBuildInput
select {
case platformInput = <-inputs:
case <-time.After(time.Second):
t.Fatal("platform builder did not receive profile-defaulted input")
}
plan := platformInput.AutonomousLifecycle
if platformInput.ProfileKey != "run-local" || plan == nil || plan.ProfileKey != "run-local" || plan.Deployment == nil || plan.Deployment.ProfileKey != "run-local" {
t.Fatalf("expected package context to default empty deployment profile to run-local, input=%+v plan=%+v", platformInput, plan)
}
}
func TestCoreServiceBuildsWithoutRegisteredDistributionWorker(t *testing.T) { func TestCoreServiceBuildsWithoutRegisteredDistributionWorker(t *testing.T) {
svc, session, instance := newDistributionTestFixture(t) svc, session, instance := newDistributionTestFixture(t)
bootstrapEndpoint, err := svc.store.RunEndpoints().Get(instance.RunEndpointID) bootstrapEndpoint, err := svc.store.RunEndpoints().Get(instance.RunEndpointID)
+5 -1
View File
@@ -150,7 +150,7 @@ func (svc *CoreService) ensureRunLogStreamForBatch(batch domain.LogBatchIngest,
expectedStreamID = runSessionLogStreamID(batch.RunEndpointID, batch.ServerInstanceID, batch.LogSessionID, batch.StreamKey) expectedStreamID = runSessionLogStreamID(batch.RunEndpointID, batch.ServerInstanceID, batch.LogSessionID, batch.StreamKey)
} }
if batch.LogStreamID != expectedStreamID { if batch.LogStreamID != expectedStreamID {
if !legacyAutonomousLogStream(batch) { if !legacySessionRunLogStream(batch) && !legacyAutonomousLogStream(batch) {
return repo.ErrNotFound return repo.ErrNotFound
} }
} }
@@ -168,6 +168,10 @@ func (svc *CoreService) ensureRunLogStreamForBatch(batch domain.LogBatchIngest,
return err return err
} }
func legacySessionRunLogStream(batch domain.LogBatchIngest) bool {
return batch.LogSessionID != "" && batch.LogStreamID == runLogStreamID(batch.RunEndpointID, batch.ServerInstanceID, batch.StreamKey)
}
func legacyAutonomousLogStream(batch domain.LogBatchIngest) bool { func legacyAutonomousLogStream(batch domain.LogBatchIngest) bool {
jobID, ok := jobIDFromLogBatch(batch) jobID, ok := jobIDFromLogBatch(batch)
return ok && strings.HasPrefix(jobID, "autonomous-") return ok && strings.HasPrefix(jobID, "autonomous-")
+22
View File
@@ -463,6 +463,28 @@ func TestCoreServiceAcceptsSessionMetadataOnLegacyAutonomousStreamID(t *testing.
} }
} }
func TestCoreServiceAcceptsSessionMetadataOnLegacyRunStreamID(t *testing.T) {
svc, sessionToken := newRegisteredLogIngestService(t)
batch := validLogBatch(t, sessionToken, 1, 1)
batch.LogStreamID = runLogStreamID("run-local", "server-1", "stdout")
batch.LogSessionID = "session-a"
batch.SessionStartedAt = time.Date(2026, 7, 3, 12, 30, 0, 0, time.UTC)
ack, err := svc.IngestLogBatch(batch)
if err != nil {
t.Fatalf("ingest session-scoped legacy run stream: %v", err)
}
if !ack.Accepted || ack.LogStreamID != batch.LogStreamID || ack.LatestSeq != batch.LastSeq {
t.Fatalf("unexpected legacy run session ack: %+v", ack)
}
stream, err := svc.GetLogStream(batch.LogStreamID)
if err != nil {
t.Fatalf("get session-scoped legacy run stream: %v", err)
}
if stream.LogSessionID != batch.LogSessionID || !stream.SessionStartedAt.Equal(batch.SessionStartedAt) {
t.Fatalf("session metadata was not persisted: %+v", stream)
}
}
func TestCoreServiceRejectsOutOfOrderAndConflictingLogBatches(t *testing.T) { func TestCoreServiceRejectsOutOfOrderAndConflictingLogBatches(t *testing.T) {
svc, sessionToken := newRegisteredLogIngestService(t) svc, sessionToken := newRegisteredLogIngestService(t)
createLogStreamFixture(t, svc) createLogStreamFixture(t, svc)
+2
View File
@@ -57,7 +57,9 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
instance.State = domain.ServerInstanceStateDraft instance.State = domain.ServerInstanceStateDraft
} }
if instance.Deployment.Mode != "" { if instance.Deployment.Mode != "" {
if strings.TrimSpace(create.ProfileKey) != "" {
instance.Deployment.ProfileKey = create.ProfileKey instance.Deployment.ProfileKey = create.ProfileKey
}
instance.Deployment.RuntimeBindings = domain.CopyStringMap(create.Bindings) instance.Deployment.RuntimeBindings = domain.CopyStringMap(create.Bindings)
instance.Deployment.Revision = maxInt(1, instance.Deployment.Revision) instance.Deployment.Revision = maxInt(1, instance.Deployment.Revision)
instance.Deployment.UpdatedAt = stamp instance.Deployment.UpdatedAt = stamp