mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
* Introduce new 'pkg/remotecmd' package This package allows to execute commands in remote packages and exposes an interface for managing processes associated to given Devfile commands. * Rely on 'pkg/libdevfile' as much as possible for Devfile command execution This requires passing a handler at the odo side, which in turns uses the 'pkg/remotecmd' package to run commands in remote containers. * Switch to running without Supervisord as PID 1 in containers To do this, the idea is to start the container component: 1- using the command/args defined in the Devfile 2- using whatever was defined in the container image if there is no command/args defined in the Devfile Then, once the container is started, we would execute the Devfile commands directly in the container component, just like a simple 'kubectl exec' command would do. Since this is a long-running command (and potentially never ending), we would need to run it in the background, i.e. in a side goroutine. Point 2) above requires implementing a temporary hack (as discussed in [1]), without us having to wait for [2] to be merged on the Devfile side. This temporary hack overrides the container entrypoint with "tail -f /dev/null" if the component defines no command or args (in which case we should have used whatever is defined in the image, per the specification). [1] https://github.com/redhat-developer/odo/pull/5768#issuecomment-1147190409 [2] https://github.com/devfile/registry/pull/102 * Rename K8s adapter struct 'client' field into 'kubeClient', as suggested in review * Rename sync adapter struct 'client' fields to better distinguish between them * Make sure messages displayed to users running 'odo dev' are the same * Update temporary hack log message Co-authored-by: Philippe Martin <contact@elol.fr> * Make sure to handle process output line by line, for performance purposes * Handle remote process output and errors in the Devfile command handler The implementation in kubeexec.go should remain as generic as possible * Keep retrying remote process status until timeout, rather than just waiting for 1 sec Now that the command is run via a goroutine, there might be some situations where we were checking the status just before the goroutine had a chance to start. * Handle remote process output and errors in the Devfile command handler The implementation in kubeexec.go should remain as generic as possible * Update kubeexec StopProcessForCommand implementation such that it relies on /proc to kill the parent children processes * Ignore missing children file in getProcessChildren * Unit-test methods in kubexec.go * Fix missing logs when build command does not pass when running 'odo dev' Also add integration test case * Fix spinner status when commands passed to exec_handler do not pass * Make sure to check process status right after stopping it The process just stopped might take longer to exit (it might have caught the signal and is performing additional cleanup) * Keep retrying remote process status until timeout, rather than just waiting for 1 sec Now that the command is run via a goroutine, there might be some situations where we were checking the status just before the goroutine had a chance to start. * Fix potential deadlock when reading output from remotecmd#ExecuteCommandAndGetOutput Rely on the same logic in ExecuteCommand * Add more unit tests * Remove block that used to check debug port from env info As commented out in [1], we don't store anymore the debug port value in the ENV file. [1] https://github.com/redhat-developer/odo/pull/5768#discussion_r893163382 * Rename 'getCommandFromFlag' into 'getCommandByName', as suggested in review * Make remotecmd package more generic This package no longer depends on Devfile-related packages. * Fix comments in libdevfile.go * Move errorIfTimeout struct field as parameter of RetryWithSchedule This boolean is tied to the given retry schedule, so it makes sense for it to be passed with the schedule. * Expose a single ExecuteCommand function that returns both stdout and stderr Co-authored-by: Philippe Martin <contact@elol.fr>
138 lines
3.8 KiB
Go
138 lines
3.8 KiB
Go
package task
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestRetryable_RetryWithSchedule(t *testing.T) {
|
|
var empty struct{}
|
|
for _, tt := range []struct {
|
|
name string
|
|
runner func(nbInvocations int) (exitCondition bool, result interface{}, err error)
|
|
errorIfTimeout bool
|
|
schedule []time.Duration
|
|
wantErr bool
|
|
wantInvocations int
|
|
}{
|
|
{
|
|
name: "no schedule with runner returning no error",
|
|
runner: func(_ int) (exitCondition bool, result interface{}, err error) {
|
|
return false, empty, nil
|
|
},
|
|
wantInvocations: 1,
|
|
},
|
|
{
|
|
name: "no schedule with runner returning an error",
|
|
runner: func(_ int) (exitCondition bool, result interface{}, err error) {
|
|
return false, empty, errors.New("some error")
|
|
},
|
|
wantErr: true,
|
|
wantInvocations: 1,
|
|
},
|
|
{
|
|
name: "schedule with runner returning no error and exit condition never matched",
|
|
runner: func(_ int) (exitCondition bool, result interface{}, err error) {
|
|
return false, empty, nil
|
|
},
|
|
schedule: []time.Duration{
|
|
10 * time.Millisecond,
|
|
30 * time.Millisecond,
|
|
50 * time.Millisecond,
|
|
},
|
|
wantInvocations: 3,
|
|
},
|
|
{
|
|
name: "schedule with runner returning an error and exit condition never matched",
|
|
runner: func(_ int) (exitCondition bool, result interface{}, err error) {
|
|
return false, empty, errors.New("some error")
|
|
},
|
|
schedule: []time.Duration{
|
|
10 * time.Millisecond,
|
|
30 * time.Millisecond,
|
|
50 * time.Millisecond,
|
|
},
|
|
wantInvocations: 3,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "schedule with runner returning no error and exit condition never matched and error if timeout set to true",
|
|
runner: func(_ int) (exitCondition bool, result interface{}, err error) {
|
|
return false, empty, nil
|
|
},
|
|
schedule: []time.Duration{
|
|
10 * time.Millisecond,
|
|
30 * time.Millisecond,
|
|
50 * time.Millisecond,
|
|
},
|
|
wantInvocations: 3,
|
|
errorIfTimeout: true,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "schedule with runner returning an error and exit condition never matched and error if timeout set to true",
|
|
runner: func(_ int) (exitCondition bool, result interface{}, err error) {
|
|
return false, empty, errors.New("some error")
|
|
},
|
|
schedule: []time.Duration{
|
|
10 * time.Millisecond,
|
|
30 * time.Millisecond,
|
|
50 * time.Millisecond,
|
|
},
|
|
wantInvocations: 3,
|
|
errorIfTimeout: true,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "schedule with runner return no error and matching exit condition after 2nd invocation",
|
|
runner: func(n int) (exitCondition bool, result interface{}, err error) {
|
|
if n == 2 {
|
|
return true, empty, nil
|
|
}
|
|
return false, empty, nil
|
|
},
|
|
schedule: []time.Duration{
|
|
10 * time.Millisecond,
|
|
30 * time.Millisecond,
|
|
50 * time.Millisecond,
|
|
100 * time.Millisecond,
|
|
},
|
|
wantInvocations: 2,
|
|
},
|
|
{
|
|
name: "schedule with runner return an error and matching exit condition after 2nd invocation",
|
|
runner: func(n int) (exitCondition bool, result interface{}, err error) {
|
|
err = errors.New("some error")
|
|
if n == 2 {
|
|
return true, empty, err
|
|
}
|
|
return false, empty, err
|
|
},
|
|
schedule: []time.Duration{
|
|
10 * time.Millisecond,
|
|
30 * time.Millisecond,
|
|
50 * time.Millisecond,
|
|
100 * time.Millisecond,
|
|
},
|
|
wantInvocations: 2,
|
|
wantErr: true,
|
|
},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var nbRunnerInvocations int
|
|
_, err := NewRetryable(tt.name, func() (exitCondition bool, result interface{}, err error) {
|
|
nbRunnerInvocations++
|
|
return tt.runner(nbRunnerInvocations)
|
|
}).RetryWithSchedule(tt.schedule, tt.errorIfTimeout)
|
|
|
|
if tt.wantErr != (err != nil) {
|
|
t.Errorf("unexpected error %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
if tt.wantInvocations != nbRunnerInvocations {
|
|
t.Errorf("expected %d, got %d", tt.wantInvocations, nbRunnerInvocations)
|
|
}
|
|
})
|
|
}
|
|
}
|