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>
70 lines
2.2 KiB
Go
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
|
|
}
|