227 lines
7.9 KiB
Go
227 lines
7.9 KiB
Go
package service
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"browser.local/platform/domain"
|
|
"browser.local/platform/validator"
|
|
)
|
|
|
|
func (svc *CoreService) ReportRunLifecycle(report domain.RunLifecycleReport) (domain.RunLifecycleReportResult, error) {
|
|
report = domain.CopyRunLifecycleReport(report)
|
|
if err := validator.ValidateRunLifecycleReport(report); err != nil {
|
|
return domain.RunLifecycleReportResult{}, err
|
|
}
|
|
if _, err := svc.validatedRunSession(report.RunEndpointID, report.SessionToken); err != nil {
|
|
return domain.RunLifecycleReportResult{}, err
|
|
}
|
|
instance, err := svc.store.ServerInstances().Get(report.ServerInstanceID)
|
|
if err != nil {
|
|
return domain.RunLifecycleReportResult{}, err
|
|
}
|
|
if instance.RunEndpointID != report.RunEndpointID {
|
|
return domain.RunLifecycleReportResult{}, validationError("runEndpointId must match server instance")
|
|
}
|
|
if instance.State == domain.ServerInstanceStateDeleted {
|
|
return domain.RunLifecycleReportResult{}, validationError("server instance must not be deleted")
|
|
}
|
|
|
|
stamp := svc.now()
|
|
nextState, projected := lifecycleProjectedState(report.Capability, report.State, report.ExecutionResult)
|
|
staleObservation := lifecycleObservationIsStale(instance, report)
|
|
if !projected || staleObservation {
|
|
projected = false
|
|
nextState = instance.State
|
|
}
|
|
if projected {
|
|
instance.State = nextState
|
|
if report.ManagedProcessID != "" {
|
|
instance.LifecycleProcessID = report.ManagedProcessID
|
|
instance.LifecycleObservationSeq = report.ObservationSeq
|
|
instance.LifecycleObservedAt = report.ObservedAt
|
|
if instance.LifecycleObservedAt.IsZero() {
|
|
instance.LifecycleObservedAt = stamp
|
|
}
|
|
}
|
|
instance.UpdatedAt = stamp
|
|
if err := validator.ValidateServerInstance(instance); err != nil {
|
|
return domain.RunLifecycleReportResult{}, err
|
|
}
|
|
if err := svc.store.ServerInstances().Update(instance); err != nil {
|
|
return domain.RunLifecycleReportResult{}, err
|
|
}
|
|
svc.publishLogProcessState(instance)
|
|
}
|
|
if report.State == domain.JobStateSucceeded && !staleObservation {
|
|
plugin, pluginErr := svc.store.GamePlugins().Get(instance.PluginID)
|
|
if pluginErr != nil {
|
|
return domain.RunLifecycleReportResult{}, pluginErr
|
|
}
|
|
if err := svc.projectPluginLifecycleState(instance, plugin, report.Capability, report.ExecutionResult, stamp); err != nil {
|
|
return domain.RunLifecycleReportResult{}, err
|
|
}
|
|
}
|
|
return domain.CopyRunLifecycleReportResult(domain.RunLifecycleReportResult{Accepted: true, RunEndpointID: report.RunEndpointID, ServerInstanceID: report.ServerInstanceID, ProjectedState: nextState, ServerTime: stamp}), nil
|
|
}
|
|
|
|
func lifecycleObservationIsStale(instance domain.ServerInstance, report domain.RunLifecycleReport) bool {
|
|
if report.ManagedProcessID == "" || instance.LifecycleProcessID == "" {
|
|
return false
|
|
}
|
|
if report.ManagedProcessID == instance.LifecycleProcessID {
|
|
return report.ObservationSeq <= instance.LifecycleObservationSeq
|
|
}
|
|
return !report.ObservedAt.IsZero() && !instance.LifecycleObservedAt.IsZero() && report.ObservedAt.Before(instance.LifecycleObservedAt)
|
|
}
|
|
|
|
func (svc *CoreService) projectRemoteAdapterJobResult(job domain.Job, stamp time.Time) error {
|
|
if !strings.HasPrefix(job.Capability, "remote.") || job.ServerInstanceID == "" || !isTerminalJobState(job.State) {
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (svc *CoreService) projectLifecycleJobResult(job domain.Job, stamp time.Time) error {
|
|
if job.Capability == domain.JobCapabilityConfigWrite {
|
|
if job.State != domain.JobStateSucceeded {
|
|
return nil
|
|
}
|
|
instance, err := svc.store.ServerInstances().Get(job.ServerInstanceID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
instance.ConfigKey = job.TargetKey
|
|
instance.ConfigContent = job.ExecutionInput.Content
|
|
instance.ConfigChecksum = job.ExecutionResult.Checksum
|
|
instance.ConfigVersion = job.ExecutionResult.Version
|
|
instance.ConfigUpdatedAt = stamp
|
|
instance.UpdatedAt = stamp
|
|
if err := validator.ValidateServerInstance(instance); err != nil {
|
|
return err
|
|
}
|
|
if err := svc.store.ServerInstances().Update(instance); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
nextState, ok := lifecycleProjectedState(job.Capability, job.State, job.ExecutionResult)
|
|
if !ok || job.ServerInstanceID == "" {
|
|
if job.State == domain.JobStateSucceeded && job.ServerInstanceID != "" {
|
|
return svc.projectPluginLifecycleStateForJob(job, stamp)
|
|
}
|
|
return nil
|
|
}
|
|
instance, err := svc.store.ServerInstances().Get(job.ServerInstanceID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
instance.State = nextState
|
|
instance.UpdatedAt = stamp
|
|
if err := validator.ValidateServerInstance(instance); err != nil {
|
|
return err
|
|
}
|
|
if err := svc.store.ServerInstances().Update(instance); err != nil {
|
|
return err
|
|
}
|
|
svc.publishLogProcessState(instance)
|
|
if job.State == domain.JobStateSucceeded {
|
|
plugin, err := svc.store.GamePlugins().Get(instance.PluginID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := svc.projectPluginLifecycleState(instance, plugin, job.Capability, job.ExecutionResult, stamp); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (svc *CoreService) projectPluginLifecycleStateForJob(job domain.Job, stamp time.Time) error {
|
|
instance, err := svc.store.ServerInstances().Get(job.ServerInstanceID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
plugin, err := svc.store.GamePlugins().Get(instance.PluginID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return svc.projectPluginLifecycleState(instance, plugin, job.Capability, job.ExecutionResult, stamp)
|
|
}
|
|
|
|
func (svc *CoreService) projectServerDeploymentProgress(job domain.Job, stamp time.Time) error {
|
|
if job.ExecutionInput.Deployment == nil || job.ServerInstanceID == "" {
|
|
return nil
|
|
}
|
|
instance, err := svc.store.ServerInstances().Get(job.ServerInstanceID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
projection := domain.CopyServerDeploymentProjection(instance.DeploymentProjection)
|
|
projection.State = "running"
|
|
projection.Operation = job.ExecutionInput.LifecycleOperation
|
|
switch job.Progress.Phase {
|
|
case "preflight":
|
|
projection.PreflightState = "running"
|
|
case "scan", "discover", "discovery":
|
|
projection.DiscoveryState = "running"
|
|
case "install", "configure", "mapping":
|
|
projection.MappingState = "running"
|
|
case "start", "health":
|
|
projection.VerificationState = "running"
|
|
}
|
|
projection.UpdatedAt = stamp
|
|
instance.DeploymentProjection = projection
|
|
instance.UpdatedAt = stamp
|
|
return svc.store.ServerInstances().Update(instance)
|
|
}
|
|
|
|
func lifecycleProjectedState(capability string, jobState domain.JobState, result domain.JobExecutionResult) (domain.ServerInstanceState, bool) {
|
|
if capability != domain.LifecycleCapabilityInstall && capability != domain.LifecycleCapabilityStart && capability != domain.LifecycleCapabilityStop && capability != domain.LifecycleCapabilityStatus {
|
|
return "", false
|
|
}
|
|
if capability == domain.LifecycleCapabilityStatus {
|
|
if jobState != domain.JobStateSucceeded {
|
|
return "", false
|
|
}
|
|
return lifecycleStateFromProcessResult(result)
|
|
}
|
|
if jobState == domain.JobStateFailed || jobState == domain.JobStateCancelled {
|
|
return domain.ServerInstanceStateFailed, true
|
|
}
|
|
if jobState != domain.JobStateSucceeded {
|
|
return "", false
|
|
}
|
|
switch capability {
|
|
case domain.LifecycleCapabilityInstall:
|
|
return domain.ServerInstanceStateReady, true
|
|
case domain.LifecycleCapabilityStart:
|
|
return lifecycleStateFromProcessResult(result)
|
|
case domain.LifecycleCapabilityStop:
|
|
if state, ok := lifecycleStateFromProcessResult(result); ok {
|
|
return state, true
|
|
}
|
|
return domain.ServerInstanceStateStopped, true
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
func lifecycleStateFromProcessResult(result domain.JobExecutionResult) (domain.ServerInstanceState, bool) {
|
|
switch strings.TrimSpace(result.ProcessState) {
|
|
case "running":
|
|
return domain.ServerInstanceStateRunning, true
|
|
case "stopped", "not-started":
|
|
return domain.ServerInstanceStateStopped, true
|
|
case "exited":
|
|
switch strings.TrimSpace(result.ExitClassification) {
|
|
case "requested-stop", "forced-stop", "already-stopped":
|
|
return domain.ServerInstanceStateStopped, true
|
|
default:
|
|
return domain.ServerInstanceStateFailed, true
|
|
}
|
|
default:
|
|
return "", false
|
|
}
|
|
}
|