Files
odo/pkg/task/retry.go
Armel Soro 74516a9f58 Add support for command/args fields in container components (#5768)
* 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>
2022-06-17 10:51:02 +00:00

70 lines
2.2 KiB
Go

package task
import (
"fmt"
"time"
"k8s.io/klog"
)
// Retryable represents a task that can be retried.
type Retryable struct {
// description of the task
description string
// runner is the actual function that is expected to be retried.
runner Runner
}
// Runner is a function that will get invoked via RetryWithSchedule. If exitCondition is false, the function will get invoked again, until
// the given timeout schedule expires. It then returns a result of any type along with a potential error.
type Runner func() (exitCondition bool, result interface{}, err error)
// NewRetryable creates and returns a new Retryable task.
func NewRetryable(description string, runner Runner) Retryable {
return Retryable{
description: description,
runner: runner,
}
}
// RetryWithSchedule invokes the retryable runner function, and keeps retrying until this runner returns an exitCondition that evaluates to false,
// or the given timeout expires. The timeout schedule can be a seen as a backoff schedule, in the sense that before recalling the runner function,
// RetryWithSchedule waits for each duration defined in the given schedule.
// If the exitCondition is not true after all retries, the behavior is governed by the errorIfTimeout parameter.
// If errorIfTimeout is true, then an error is returned.
func (r Retryable) RetryWithSchedule(schedule []time.Duration, errorIfTimeout bool) (interface{}, error) {
var err error
var result interface{}
if len(schedule) == 0 {
_, result, err = r.runner()
return result, err
}
var exitCondition bool
var totalWaitTime float64
for _, s := range schedule {
seconds := s.Seconds()
klog.V(3).Infof("waiting for %0.f second(s) before trying task %q", seconds, r.description)
time.Sleep(s)
totalWaitTime += seconds
exitCondition, result, err = r.runner()
if exitCondition {
break
}
}
if !exitCondition {
msg := "aborted retrying task %q which is still not ok after %0.f second(s)"
if errorIfTimeout {
if err != nil {
return result, fmt.Errorf(msg+": %w", r.description, totalWaitTime, err)
}
return result, fmt.Errorf(msg, r.description, totalWaitTime)
}
klog.V(3).Infof(msg, r.description, totalWaitTime)
}
return result, err
}