Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(serving): Add an error window for ReadyCondition #644

Merged
merged 3 commits into from
Feb 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions pkg/wait/wait_for_ready.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import (
"knative.dev/pkg/apis"
)

// Window for how long a ReadyCondition == false has to stay
// for being considered as an error
var ErrorWindow = 2 * time.Second

// Callbacks and configuration used while waiting
type waitForReadyConfig struct {
watchMaker WatchMaker
Expand Down Expand Up @@ -87,7 +91,7 @@ func (w *waitForReadyConfig) Wait(name string, timeout time.Duration, msgCallbac
floatingTimeout := timeout
for {
start := time.Now()
retry, timeoutReached, err := w.waitForReadyCondition(start, name, floatingTimeout, msgCallback)
retry, timeoutReached, err := w.waitForReadyCondition(start, name, floatingTimeout, ErrorWindow, msgCallback)
if err != nil {
return err, time.Since(start)
}
Expand All @@ -104,18 +108,31 @@ func (w *waitForReadyConfig) Wait(name string, timeout time.Duration, msgCallbac
}
}

func (w *waitForReadyConfig) waitForReadyCondition(start time.Time, name string, timeout time.Duration, msgCallback MessageCallback) (retry bool, timeoutReached bool, err error) {
func (w *waitForReadyConfig) waitForReadyCondition(start time.Time, name string, timeout time.Duration, errorWindow time.Duration, msgCallback MessageCallback) (retry bool, timeoutReached bool, err error) {

watcher, err := w.watchMaker(name, timeout)
if err != nil {
return false, false, err
}

defer watcher.Stop()

// channel used to transport the error
errChan := make(chan error)
var errorTimer *time.Timer
// Stop error timer if it has been started because of
// a ConditionReady has been set to false
defer (func() {
if errorTimer != nil {
errorTimer.Stop()
}
})()

for {
select {
case <-time.After(timeout):
return false, true, nil
case err = <-errChan:
return false, false, err
case event, ok := <-watcher.ResultChan():
if !ok || event.Object == nil {
return true, false, nil
Expand All @@ -140,7 +157,15 @@ func (w *waitForReadyConfig) waitForReadyCondition(start time.Time, name string,
case corev1.ConditionTrue:
return false, false, nil
case corev1.ConditionFalse:
return false, false, fmt.Errorf("%s: %s", cond.Reason, cond.Message)
// Fire up a timer waiting for the error window duration to still allow to reconcile
// to a true condition even after the condition went to false. If this is not the case within
// this window, then an error is returned.
// If there is already a timer running, we just log.
if errorTimer != nil {
errorTimer = time.AfterFunc(errorWindow, func() {
errChan <- fmt.Errorf("%s: %s", cond.Reason, cond.Message)
})
}
}
if cond.Message != "" {
msgCallback(time.Since(start), cond.Message)
Expand Down
9 changes: 9 additions & 0 deletions pkg/wait/wait_for_ready_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func prepareTestCases(name string) []waitForReadyTestCase {
tc(peError, name, true),
tc(peWrongGeneration, name, true),
tc(peTimeout, name, true),
tc(peReadyFalseWithinErrorWindow, name, false),
}
}

Expand Down Expand Up @@ -131,3 +132,11 @@ func peWrongGeneration(name string) ([]watch.Event, int) {
{watch.Modified, CreateTestServiceWithConditions(name, corev1.ConditionTrue, corev1.ConditionTrue, "", "", 1, 2)},
}, len(messages)
}

func peReadyFalseWithinErrorWindow(name string) ([]watch.Event, int) {
messages := pMessages(1)
return []watch.Event{
{watch.Added, CreateTestServiceWithConditions(name, corev1.ConditionFalse, corev1.ConditionFalse, "Route not ready", messages[0])},
{watch.Modified, CreateTestServiceWithConditions(name, corev1.ConditionTrue, corev1.ConditionTrue, "Route ready", "")},
}, len(messages)
}