Fix declared server config file reads
This commit is contained in:
@@ -691,7 +691,7 @@ func assignmentFromJob(job domain.Job, leaseToken string) domain.RunJobAssignmen
|
||||
State: job.State,
|
||||
Progress: domain.RunJobProgressReport{Percent: job.Progress.Percent, Phase: job.Progress.Phase, Message: job.Progress.Message},
|
||||
ResultRef: job.ResultRef,
|
||||
ExecutionInput: domain.JobExecutionInput{WorkspaceScope: job.ExecutionInput.WorkspaceScope, Content: job.ExecutionInput.Content, ExpectedVersion: job.ExecutionInput.ExpectedVersion, ExpectedChecksum: job.ExecutionInput.ExpectedChecksum, MaxReadBytes: job.ExecutionInput.MaxReadBytes, RemoteAdapterKey: job.ExecutionInput.RemoteAdapterKey, RemoteAdapterKind: job.ExecutionInput.RemoteAdapterKind, TimeoutSeconds: job.ExecutionInput.TimeoutSeconds, PluginID: job.ExecutionInput.PluginID, LifecycleOperation: job.ExecutionInput.LifecycleOperation, TargetVersion: job.ExecutionInput.TargetVersion, Inputs: domain.CopyStringMap(job.ExecutionInput.Inputs), LogSource: domain.CopyRuntimeLogSourcePtr(job.ExecutionInput.LogSource), LogSources: domain.CopyRuntimeLogSources(job.ExecutionInput.LogSources), DLLExtensions: append([]domain.RuntimeDLLExtensionPlan(nil), job.ExecutionInput.DLLExtensions...), SourceRCON: domain.CopyRuntimeSourceRCONPlan(job.ExecutionInput.SourceRCON), Deployment: deploymentPlanForDispatchValue(job.ExecutionInput.Deployment), ServerDeploymentPlan: domain.CopyServerDeploymentPlan(job.ExecutionInput.ServerDeploymentPlan)},
|
||||
ExecutionInput: domain.JobExecutionInput{WorkspaceScope: job.ExecutionInput.WorkspaceScope, Content: job.ExecutionInput.Content, FileTargetKey: job.ExecutionInput.FileTargetKey, ExpectedVersion: job.ExecutionInput.ExpectedVersion, ExpectedChecksum: job.ExecutionInput.ExpectedChecksum, MaxReadBytes: job.ExecutionInput.MaxReadBytes, RemoteAdapterKey: job.ExecutionInput.RemoteAdapterKey, RemoteAdapterKind: job.ExecutionInput.RemoteAdapterKind, TimeoutSeconds: job.ExecutionInput.TimeoutSeconds, PluginID: job.ExecutionInput.PluginID, LifecycleOperation: job.ExecutionInput.LifecycleOperation, TargetVersion: job.ExecutionInput.TargetVersion, Inputs: domain.CopyStringMap(job.ExecutionInput.Inputs), LogSource: domain.CopyRuntimeLogSourcePtr(job.ExecutionInput.LogSource), LogSources: domain.CopyRuntimeLogSources(job.ExecutionInput.LogSources), DLLExtensions: append([]domain.RuntimeDLLExtensionPlan(nil), job.ExecutionInput.DLLExtensions...), SourceRCON: domain.CopyRuntimeSourceRCONPlan(job.ExecutionInput.SourceRCON), Deployment: deploymentPlanForDispatchValue(job.ExecutionInput.Deployment), ServerDeploymentPlan: domain.CopyServerDeploymentPlan(job.ExecutionInput.ServerDeploymentPlan)},
|
||||
LeaseToken: leaseToken,
|
||||
Attempt: job.Attempt,
|
||||
FencingToken: 0,
|
||||
|
||||
@@ -2114,6 +2114,7 @@ func (svc *CoreService) GetDeclaredFileReadSnapshotForSession(sessionID string,
|
||||
}
|
||||
var completed *domain.Job
|
||||
var pending *domain.Job
|
||||
var terminalFailure *domain.Job
|
||||
for i := range jobs {
|
||||
job := jobs[i]
|
||||
if job.Capability != domain.JobCapabilityFilesRead || job.TargetKey != fileKey {
|
||||
@@ -2129,6 +2130,11 @@ func (svc *CoreService) GetDeclaredFileReadSnapshotForSession(sessionID string,
|
||||
if declaredFileReadPendingState(job.State) && (pending == nil || newerJob(job, *pending)) {
|
||||
copy := job
|
||||
pending = ©
|
||||
continue
|
||||
}
|
||||
if (job.State == domain.JobStateFailed || job.State == domain.JobStateCancelled) && (terminalFailure == nil || newerJob(job, *terminalFailure)) {
|
||||
copy := job
|
||||
terminalFailure = ©
|
||||
}
|
||||
}
|
||||
base := domain.DeclaredFileReadSnapshot{ServerInstanceID: instance.ID, PluginID: plugin.ID, Key: fileKey}
|
||||
@@ -2152,6 +2158,16 @@ func (svc *CoreService) GetDeclaredFileReadSnapshotForSession(sessionID string,
|
||||
base.Reason = "等待运行端完成文件读取。"
|
||||
return base, nil
|
||||
}
|
||||
if terminalFailure != nil {
|
||||
base.State = "failed"
|
||||
if terminalFailure.State == domain.JobStateCancelled {
|
||||
base.State = "cancelled"
|
||||
}
|
||||
base.JobID = terminalFailure.ID
|
||||
base.ReadAt = jobCompletedAt(*terminalFailure)
|
||||
base.Reason = declaredFileReadFailureReason(*terminalFailure)
|
||||
return base, nil
|
||||
}
|
||||
base.State = "not-read"
|
||||
base.Reason = "尚未读取此文件。"
|
||||
return base, nil
|
||||
@@ -2166,6 +2182,24 @@ func declaredFileReadPendingState(state domain.JobState) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func declaredFileReadFailureReason(job domain.Job) string {
|
||||
prefix := "Run 文件读取失败"
|
||||
if job.State == domain.JobStateCancelled {
|
||||
prefix = "Run 文件读取已取消"
|
||||
}
|
||||
detail := strings.TrimSpace(job.CancelReason)
|
||||
if detail == "" {
|
||||
detail = strings.TrimSpace(job.Progress.Message)
|
||||
}
|
||||
if detail == "" {
|
||||
detail = strings.TrimSpace(job.ExecutionResult.Summary)
|
||||
}
|
||||
if detail == "" {
|
||||
return prefix + "。"
|
||||
}
|
||||
return prefix + ":" + detail
|
||||
}
|
||||
|
||||
func newerJob(left domain.Job, right domain.Job) bool {
|
||||
leftTime, rightTime := jobCompletedAt(left), jobCompletedAt(right)
|
||||
if !leftTime.Equal(rightTime) {
|
||||
@@ -2293,14 +2327,18 @@ func (svc *CoreService) DispatchFileOperationForSession(sessionID string, reques
|
||||
return domain.FileOperationDispatchResult{}, ErrForbidden
|
||||
}
|
||||
}
|
||||
pluginID := strings.TrimSpace(request.PluginID)
|
||||
if pluginID == "" {
|
||||
pluginID = instance.PluginID
|
||||
}
|
||||
plugin, err := svc.store.GamePlugins().Get(pluginID)
|
||||
if err != nil {
|
||||
return domain.FileOperationDispatchResult{}, err
|
||||
}
|
||||
if plugin.ID != instance.PluginID {
|
||||
return domain.FileOperationDispatchResult{}, validationError("pluginId must match server instance")
|
||||
}
|
||||
if request.PluginID != "" {
|
||||
plugin, err := svc.store.GamePlugins().Get(request.PluginID)
|
||||
if err != nil {
|
||||
return domain.FileOperationDispatchResult{}, err
|
||||
}
|
||||
if plugin.ID != instance.PluginID {
|
||||
return domain.FileOperationDispatchResult{}, validationError("pluginId must match server instance")
|
||||
}
|
||||
if plugin.Status != domain.GamePluginStatusInstalled {
|
||||
return domain.FileOperationDispatchResult{}, validationError("plugin must be installed")
|
||||
}
|
||||
@@ -2311,6 +2349,13 @@ func (svc *CoreService) DispatchFileOperationForSession(sessionID string, reques
|
||||
return domain.FileOperationDispatchResult{}, ErrForbidden
|
||||
}
|
||||
}
|
||||
fileTargetKey := ""
|
||||
if request.Operation != domain.FileOperationList {
|
||||
file, declared, allowed := declaredPluginFileRequest(plugin.FileWorkspace, request)
|
||||
if declared && allowed {
|
||||
fileTargetKey = file.TargetKey
|
||||
}
|
||||
}
|
||||
capability := domain.JobCapabilityFilesRead
|
||||
message := "file read queued"
|
||||
if request.Operation == domain.FileOperationList {
|
||||
@@ -2328,7 +2373,7 @@ func (svc *CoreService) DispatchFileOperationForSession(sessionID string, reques
|
||||
Capability: capability,
|
||||
TargetKey: request.Key,
|
||||
InputRef: request.InputRef,
|
||||
ExecutionInput: domain.JobExecutionInput{WorkspaceScope: svc.runtimeProfileScope(instance.ID), Content: content, ExpectedVersion: request.ExpectedConfigVersion, ExpectedChecksum: request.ExpectedChecksum, MaxReadBytes: 64 * 1024, Deployment: deploymentPlanForDispatch(instance.Deployment)},
|
||||
ExecutionInput: domain.JobExecutionInput{WorkspaceScope: svc.runtimeProfileScope(instance.ID), Content: content, FileTargetKey: fileTargetKey, ExpectedVersion: request.ExpectedConfigVersion, ExpectedChecksum: request.ExpectedChecksum, MaxReadBytes: 64 * 1024, PluginID: plugin.ID, Deployment: deploymentPlanForDispatch(instance.Deployment)},
|
||||
IdempotencyKey: request.IdempotencyKey,
|
||||
Progress: domain.JobProgress{Percent: 0, Message: message},
|
||||
})
|
||||
@@ -2337,7 +2382,7 @@ func (svc *CoreService) DispatchFileOperationForSession(sessionID string, reques
|
||||
}
|
||||
return domain.CopyFileOperationDispatchResult(domain.FileOperationDispatchResult{
|
||||
ServerInstanceID: request.ServerInstanceID,
|
||||
PluginID: request.PluginID,
|
||||
PluginID: plugin.ID,
|
||||
Operation: request.Operation,
|
||||
Key: request.Key,
|
||||
InputRef: request.InputRef,
|
||||
|
||||
@@ -1019,7 +1019,7 @@ func TestPluginFileWorkspaceDoesNotConstrainServerFileDispatch(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("dispatch declared file read: %v", err)
|
||||
}
|
||||
if allowed.Job.TargetKey != "scum-server-settings" || allowed.Job.Capability != domain.JobCapabilityFilesRead || allowed.Job.ExecutionInput.Deployment == nil || allowed.Job.ExecutionInput.Deployment.ServerRoot != `C:\scumserver` {
|
||||
if allowed.Job.TargetKey != "scum-server-settings" || allowed.Job.ExecutionInput.FileTargetKey != "SCUM/Saved/Config/WindowsServer/ServerSettings.ini" || allowed.Job.Capability != domain.JobCapabilityFilesRead || allowed.Job.ExecutionInput.Deployment == nil || allowed.Job.ExecutionInput.Deployment.ServerRoot != `C:\scumserver` {
|
||||
t.Fatalf("unexpected declared file dispatch: %+v", allowed)
|
||||
}
|
||||
unknown, err := svc.DispatchFileOperationForSession(ownerSession, domain.FileOperationDispatchRequest{
|
||||
@@ -1313,8 +1313,8 @@ func TestDeclaredFileReadSnapshotProjectionStatesAndPassThroughContent(t *testin
|
||||
}
|
||||
createDeclaredFileReadJob(t, svc, instance, endpoint, "job-file-snapshot-cancelled", domain.JobStateCancelled, 3, "")
|
||||
snapshot, err = svc.GetDeclaredFileReadSnapshotForSession(ownerSession, instance.ID, "scum-server-settings")
|
||||
if err != nil || snapshot.State != "not-read" {
|
||||
t.Fatalf("failed/cancelled reads must not mask not-read, snapshot=%+v err=%v", snapshot, err)
|
||||
if err != nil || snapshot.State != "cancelled" || !strings.Contains(snapshot.Reason, "fixture read cancelled") {
|
||||
t.Fatalf("failed/cancelled reads must surface the latest Run failure, snapshot=%+v err=%v", snapshot, err)
|
||||
}
|
||||
createDeclaredFileReadJob(t, svc, instance, endpoint, "job-file-snapshot-success-old", domain.JobStateSucceeded, 4, "ServerName=Old\nRconPassword=secret\n")
|
||||
createDeclaredFileReadJob(t, svc, instance, endpoint, "job-file-snapshot-failed-newer", domain.JobStateFailed, 5, "")
|
||||
@@ -1344,7 +1344,7 @@ func scumTestFileWorkspace() domain.PluginFileWorkspace {
|
||||
{Key: "scum-logs", Label: "日志文件", Scope: "logs"},
|
||||
},
|
||||
Files: []domain.PluginLogicalFile{
|
||||
{Key: "scum-server-settings", DirectoryKey: "scum-config", Label: "ServerSettings.ini", Kind: "config", Editable: true},
|
||||
{Key: "scum-server-settings", DirectoryKey: "scum-config", Label: "ServerSettings.ini", Kind: "config", TargetKey: "SCUM/Saved/Config/WindowsServer/ServerSettings.ini", Editable: true},
|
||||
{Key: "scum-chat-log", DirectoryKey: "scum-logs", Label: "Chat.log", Kind: "log", StreamKey: "scum.chat"},
|
||||
},
|
||||
ConfigFields: []domain.PluginConfigField{
|
||||
@@ -1374,6 +1374,11 @@ func createDeclaredFileReadJob(t *testing.T, svc *CoreService, instance domain.S
|
||||
}
|
||||
if state == domain.JobStateSucceeded {
|
||||
job.ExecutionResult = domain.JobExecutionResult{Kind: "file.read", Version: minuteOffset, Checksum: validator.BytesChecksum([]byte(content)), SizeBytes: int64(len(content)), Content: content}
|
||||
} else if state == domain.JobStateFailed {
|
||||
job.Progress.Message = "fixture read failed"
|
||||
job.ExecutionResult.Summary = "file_read_failed"
|
||||
} else if state == domain.JobStateCancelled {
|
||||
job.CancelReason = "fixture read cancelled"
|
||||
}
|
||||
if err := svc.store.Jobs().Update(job); err != nil {
|
||||
t.Fatalf("update declared file read job: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user