From 2b5c7e82336d8f7e627e0404700297d538fcc128 Mon Sep 17 00:00:00 2001 From: Berger Eugene Date: Fri, 8 Dec 2023 16:25:56 +0200 Subject: [PATCH] Issue #111 - fix transitive dependency skip on failure --- .../process-compose-transitive-dep.yaml | 22 ++++++++++ issues/issue_111/process-compose-probes.yaml | 42 +++++++++++++++++++ issues/issue_111/process-compose.yaml | 26 ++++++++++++ src/app/process.go | 28 ++++++++++--- src/app/project_runner.go | 19 +++++---- src/app/system_test.go | 26 ++++++++++++ src/types/process.go | 1 + 7 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 fixtures-code/process-compose-transitive-dep.yaml create mode 100644 issues/issue_111/process-compose-probes.yaml create mode 100644 issues/issue_111/process-compose.yaml diff --git a/fixtures-code/process-compose-transitive-dep.yaml b/fixtures-code/process-compose-transitive-dep.yaml new file mode 100644 index 0000000..7bad81f --- /dev/null +++ b/fixtures-code/process-compose-transitive-dep.yaml @@ -0,0 +1,22 @@ +version: "0.5" + +log_level: debug +log_length: 1000 + +processes: + procA: + command: "exit 1" + + procB: + command: echo "I shouldn't run" + depends_on: + procA: + condition: process_completed_successfully + + procC: + command: echo "I shouldn't run" + depends_on: + procB: + condition: process_completed_successfully + + diff --git a/issues/issue_111/process-compose-probes.yaml b/issues/issue_111/process-compose-probes.yaml new file mode 100644 index 0000000..4647ab3 --- /dev/null +++ b/issues/issue_111/process-compose-probes.yaml @@ -0,0 +1,42 @@ +version: "0.5" + +log_level: debug +log_length: 1000 + +processes: + procA: + command: "exit 1" + readiness_probe: + exec: + command: "pidof process-compose" + initial_delay_seconds: 1 + period_seconds: 1 + timeout_seconds: 1 + success_threshold: 1 + failure_threshold: 20 + + procB: + command: echo "I shouldn't run" + readiness_probe: + exec: + command: "pidof process-compose" + initial_delay_seconds: 1 + period_seconds: 1 + timeout_seconds: 1 + success_threshold: 1 + failure_threshold: 20 + depends_on: + procA: + condition: process_healthy + + procC: + command: echo "I shouldn't run" + depends_on: + procB: + condition: process_healthy + + pc_log: + command: "tail -f -n100 process-compose-${USER}.log" + working_dir: "/tmp" + namespace: debug + diff --git a/issues/issue_111/process-compose.yaml b/issues/issue_111/process-compose.yaml new file mode 100644 index 0000000..b55876d --- /dev/null +++ b/issues/issue_111/process-compose.yaml @@ -0,0 +1,26 @@ +version: "0.5" + +log_level: debug +log_length: 1000 + +processes: + procA: + command: "exit 1" + + procB: + command: echo "I shouldn't run" + depends_on: + procA: + condition: process_completed_successfully + + procC: + command: echo "I shouldn't run" + depends_on: + procB: + condition: process_completed_successfully + + pc_log: + command: "tail -f -n100 process-compose-${USER}.log" + working_dir: "/tmp" + namespace: debug + diff --git a/src/app/process.go b/src/app/process.go index 1a488d4..5de448b 100644 --- a/src/app/process.go +++ b/src/app/process.go @@ -33,6 +33,7 @@ const ( type Process struct { sync.Mutex globalEnv []string + confMtx sync.Mutex procConf *types.ProcessConfig procState *types.ProcessState stateMtx sync.Mutex @@ -134,11 +135,11 @@ func (p *Process) run() int { time.Sleep(50 * time.Millisecond) _ = p.command.Wait() p.Lock() - p.procState.ExitCode = p.command.ExitCode() + p.setExitCode(p.command.ExitCode()) p.Unlock() log.Info(). Str("process", p.getName()). - Int("exit_code", p.procState.ExitCode). + Int("exit_code", p.getExitCode()). Msg("Exited") if p.isDaemonLaunched() { @@ -164,7 +165,7 @@ func (p *Process) run() int { } } p.onProcessEnd(types.ProcessStateCompleted) - return p.procState.ExitCode + return p.getExitCode() } func (p *Process) getProcessStarter() func() error { @@ -227,7 +228,7 @@ func (p *Process) getProcessEnvironment() []string { func (p *Process) isRestartable() bool { p.Lock() - exitCode := p.procState.ExitCode + exitCode := p.getExitCode() p.Unlock() if p.procConf.RestartPolicy.Restart == types.RestartPolicyNo || p.procConf.RestartPolicy.Restart == "" { @@ -263,7 +264,7 @@ func (p *Process) waitForCompletion() int { for !p.done { p.procCond.Wait() } - return p.procState.ExitCode + return p.getExitCode() } func (p *Process) waitUntilReady() bool { @@ -274,13 +275,14 @@ func (p *Process) waitUntilReady() bool { return true } log.Error().Msgf("Process %s was aborted and won't become ready", p.getName()) + p.setExitCode(1) return false } } } func (p *Process) wontRun() { - p.onProcessEnd(types.ProcessStateCompleted) + p.onProcessEnd(types.ProcessStateSkipped) } // perform graceful process shutdown if defined in configuration @@ -506,6 +508,8 @@ func (p *Process) setStateAndRun(state string, runnable func() error) error { func (p *Process) onStateChange(state string) { switch state { + case types.ProcessStateSkipped: + p.setExitCode(1) case types.ProcessStateRestarting: fallthrough case types.ProcessStateLaunching: @@ -621,3 +625,15 @@ func (p *Process) getOpenPorts(ports *types.ProcessPorts) error { } return nil } + +func (p *Process) getExitCode() int { + defer p.confMtx.Unlock() + p.confMtx.Lock() + return p.procState.ExitCode +} + +func (p *Process) setExitCode(code int) { + defer p.confMtx.Unlock() + p.confMtx.Lock() + p.procState.ExitCode = code +} diff --git a/src/app/project_runner.go b/src/app/project_runner.go index 8b2f19e..5d923dc 100644 --- a/src/app/project_runner.go +++ b/src/app/project_runner.go @@ -109,18 +109,18 @@ func (p *ProjectRunner) runProcess(config *types.ProcessConfig) { ) p.addRunningProcess(process) p.waitGroup.Add(1) - go func() { - defer p.removeRunningProcess(process) + go func(proc *Process) { + defer p.removeRunningProcess(proc) defer p.waitGroup.Done() - if err = p.waitIfNeeded(process.procConf); err != nil { + if err = p.waitIfNeeded(proc.procConf); err != nil { log.Error().Msgf("Error: %s", err.Error()) - log.Error().Msgf("Error: process %s won't run", process.getName()) - process.wontRun() + log.Error().Msgf("Error: process %s won't run", proc.getName()) + proc.wontRun() } else { - exitCode := process.run() - p.onProcessEnd(exitCode, process.procConf) + exitCode := proc.run() + p.onProcessEnd(exitCode, proc.procConf) } - }() + }(process) } func (p *ProjectRunner) waitIfNeeded(process *types.ProcessConfig) error { @@ -145,7 +145,10 @@ func (p *ProjectRunner) waitIfNeeded(process *types.ProcessConfig) error { } } + } else { + log.Error().Msgf("Error: process %s depends on %s, but it isn't running", process.ReplicaName, k) } + } return nil } diff --git a/src/app/system_test.go b/src/app/system_test.go index 1129f1a..730e491 100644 --- a/src/app/system_test.go +++ b/src/app/system_test.go @@ -281,3 +281,29 @@ func TestSystem_TestComposeScale(t *testing.T) { } }) } + +func TestSystem_TestTransitiveDependency(t *testing.T) { + fixture1 := filepath.Join("..", "..", "fixtures-code", "process-compose-transitive-dep.yaml") + t.Run(fixture1, func(t *testing.T) { + project, err := loader.Load(&loader.LoaderOptions{ + FileNames: []string{fixture1}, + }) + if err != nil { + t.Errorf(err.Error()) + return + } + runner, err := NewProjectRunner(&ProjectOpts{ + project: project, + processesToRun: []string{}, + mainProcessArgs: []string{}, + }) + runner.Run() + + states, err := runner.GetProcessesState() + for _, state := range states.States { + if state.ExitCode != 1 { + t.Errorf("process %s exit code is not 1", state.Name) + } + } + }) +} diff --git a/src/types/process.go b/src/types/process.go index 7bf2132..bad3797 100644 --- a/src/types/process.go +++ b/src/types/process.go @@ -123,6 +123,7 @@ const ( ProcessStateRestarting = "Restarting" ProcessStateTerminating = "Terminating" ProcessStateCompleted = "Completed" + ProcessStateSkipped = "Skipped" ProcessStateError = "Error" )