retry build watch on error/expiration
@@ -4,8 +4,11 @@ import (
"fmt"
"time"
+ "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
buildapi "github.com/openshift/origin/pkg/build/apis/build"
@@ -18,30 +21,63 @@ var (
ErrBuildDeleted = fmt.Errorf("build was deleted")
)
+type ErrWatchError struct {
+ error
+}
+
// WaitForRunningBuild waits until the specified build is no longer New or Pending. Returns true if
// the build ran within timeout, false if it did not, and an error if any other error state occurred.
// The last observed Build state is returned.
func WaitForRunningBuild(buildClient buildtypedclient.BuildsGetter, build *buildapi.Build, timeout time.Duration) (*buildapi.Build, bool, error) {
fieldSelector := fields.OneTermEqualSelector("metadata.name", build.Name)
- options := metav1.ListOptions{FieldSelector: fieldSelector.String(), ResourceVersion: build.ResourceVersion}
+ options := metav1.ListOptions{FieldSelector: fieldSelector.String(), ResourceVersion: "0"}
+
+ done := make(chan interface{}, 1)
+ var resultBuild *buildapi.Build
+ var success bool
+ var resultErr error
+
+ deadline := time.Now().Add(timeout)
+ go func() {
+ defer close(done)
+ defer utilruntime.HandleCrash()
+
+ for time.Now().Before(deadline) {
+
+ // make sure the build has not been deleted before we start trying to watch on it because
+ // we won't get a watch event for it if it's been deleted (because we are watching starting
+ // at resource version 0).
+ _, err := buildClient.Builds(build.Namespace).Get(build.Name, metav1.GetOptions{})
+ if err != nil {
+ resultErr = err
+ if errors.IsNotFound(err) {
+ resultErr = ErrBuildDeleted
+ }
+ return
+ }
+
w, err := buildClient.Builds(build.Namespace).Watch(options)
if err != nil {
- return build, false, err
+ resultErr = err
+ return
}
- observed := build
_, err = watch.Until(timeout, w, func(event watch.Event) (bool, error) {
+ if event.Type == watch.Error {
+ return false, ErrWatchError{fmt.Errorf("watch event type error: %v", event)}
+ }
obj, ok := event.Object.(*buildapi.Build)
if !ok {
return false, fmt.Errorf("received unknown object while watching for builds: %T", event.Object)
}
- observed = obj
if event.Type == watch.Deleted {
return false, ErrBuildDeleted
}
+
switch obj.Status.Phase {
case buildapi.BuildPhaseRunning, buildapi.BuildPhaseComplete, buildapi.BuildPhaseFailed, buildapi.BuildPhaseError, buildapi.BuildPhaseCancelled:
+ resultBuild = obj
return true, nil
case buildapi.BuildPhaseNew, buildapi.BuildPhasePending:
default:
@@ -50,9 +86,26 @@ func WaitForRunningBuild(buildClient buildtypedclient.BuildsGetter, build *build
return false, nil
})
+
if err != nil {
- return nil, false, err
+ if _, ok := err.(ErrWatchError); ok {
+ continue
+ }
+ resultErr = err
+ success = false
+ resultBuild = nil
+ return
+ }
+ success = true
+ return
+ }
+ }()
+
+ select {
+ case <-time.After(timeout):
+ return nil, false, wait.ErrWaitTimeout
+ case <-done:
+ return resultBuild, success, resultErr
}
- return observed, true, nil
}
