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

pkg/canary/daemonset: fix ready condition according to kubectl #529

Merged
merged 1 commit into from
Mar 27, 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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/aws/aws-sdk-go v1.29.29
github.com/davecgh/go-spew v1.1.1
github.com/google/go-cmp v0.4.0
github.com/pkg/errors v0.9.1
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is something to do with finalizer #495 (I am going to refactor in another PR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should run go mod tidy in CI and fail the build if go.mod changes to prevent things like this in the future.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

github.com/prometheus/client_golang v1.5.1
github.com/stretchr/testify v1.5.1
go.uber.org/zap v1.14.1
Expand Down
32 changes: 21 additions & 11 deletions pkg/canary/daemonset_ready.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,31 @@ func (c *DaemonSetController) IsCanaryReady(cd *flaggerv1.Canary) (bool, error)
}

// isDaemonSetReady determines if a daemonset is ready by checking the number of old version daemons
// reference: https://github.com/kubernetes/kubernetes/blob/5232ad4a00ec93942d0b2c6359ee6cd1201b46bc/pkg/kubectl/rollout_status.go#L110
func (c *DaemonSetController) isDaemonSetReady(cd *flaggerv1.Canary, daemonSet *appsv1.DaemonSet) (bool, error) {
if diff := daemonSet.Status.DesiredNumberScheduled - daemonSet.Status.UpdatedNumberScheduled; diff > 0 || daemonSet.Status.NumberUnavailable > 0 {
if daemonSet.Generation <= daemonSet.Status.ObservedGeneration {
// calculate conditions
newCond := daemonSet.Status.UpdatedNumberScheduled < daemonSet.Status.DesiredNumberScheduled
availableCond := daemonSet.Status.NumberAvailable < daemonSet.Status.DesiredNumberScheduled
if !newCond && !availableCond {
return true, nil
}

// check if deadline exceeded
from := cd.Status.LastTransitionTime
delta := time.Duration(cd.GetProgressDeadlineSeconds()) * time.Second
dl := from.Add(delta)
if dl.Before(time.Now()) {
if from.Add(delta).Before(time.Now()) {
return false, fmt.Errorf("exceeded its progressDeadlineSeconds: %d", cd.GetProgressDeadlineSeconds())
} else {
return true, fmt.Errorf(
"waiting for rollout to finish: desiredNumberScheduled=%d, updatedNumberScheduled=%d, numberUnavailable=%d",
daemonSet.Status.DesiredNumberScheduled,
daemonSet.Status.UpdatedNumberScheduled,
daemonSet.Status.NumberUnavailable,
)
}

// retryable
if newCond {
return true, fmt.Errorf("waiting for rollout to finish: %d out of %d new pods have been updated",
daemonSet.Status.UpdatedNumberScheduled, daemonSet.Status.DesiredNumberScheduled)
} else if availableCond {
return true, fmt.Errorf("waiting for rollout to finish: %d of %d updated pods are available",
daemonSet.Status.NumberAvailable, daemonSet.Status.DesiredNumberScheduled)
}
}
return true, nil
return true, fmt.Errorf("waiting for rollout to finish: observed daemonset generation less then desired generation")
}
71 changes: 45 additions & 26 deletions pkg/canary/daemonset_ready_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package canary

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -24,39 +25,57 @@ func TestDaemonSetController_IsReady(t *testing.T) {
}

func TestDaemonSetController_isDaemonSetReady(t *testing.T) {
ds := &appsv1.DaemonSet{
Status: appsv1.DaemonSetStatus{
DesiredNumberScheduled: 1,
UpdatedNumberScheduled: 1,
},
}

mocks := newDaemonSetFixture()
cd := &flaggerv1.Canary{}
cd.Spec.ProgressDeadlineSeconds = int32p(1e5)
cd.Status.LastTransitionTime = metav1.Now()

// ready
mocks := newDaemonSetFixture()
_, err := mocks.controller.isDaemonSetReady(cd, ds)
// observed generation is less than desired generation
ds := &appsv1.DaemonSet{Status: appsv1.DaemonSetStatus{}}
ds.Status.ObservedGeneration--
retyable, err := mocks.controller.isDaemonSetReady(cd, ds)
require.Error(t, err)
require.True(t, retyable)

// succeeded
ds = &appsv1.DaemonSet{Status: appsv1.DaemonSetStatus{
UpdatedNumberScheduled: 1,
DesiredNumberScheduled: 1,
NumberAvailable: 1,
}}
retyable, err = mocks.controller.isDaemonSetReady(cd, ds)
require.NoError(t, err)
require.True(t, retyable)

// not ready but retriable
ds.Status.NumberUnavailable++
retrieable, err := mocks.controller.isDaemonSetReady(cd, ds)
// deadline exceeded
ds = &appsv1.DaemonSet{Status: appsv1.DaemonSetStatus{
UpdatedNumberScheduled: 0,
DesiredNumberScheduled: 1,
}}
cd.Status.LastTransitionTime = metav1.Now()
cd.Spec.ProgressDeadlineSeconds = int32p(-1e6)
retyable, err = mocks.controller.isDaemonSetReady(cd, ds)
require.Error(t, err)
require.True(t, retrieable)
ds.Status.NumberUnavailable--
require.False(t, retyable)

ds.Status.DesiredNumberScheduled++
retrieable, err = mocks.controller.isDaemonSetReady(cd, ds)
// only newCond not satisfied
ds = &appsv1.DaemonSet{Status: appsv1.DaemonSetStatus{
UpdatedNumberScheduled: 0,
DesiredNumberScheduled: 1,
NumberAvailable: 1,
}}
cd.Spec.ProgressDeadlineSeconds = int32p(1e6)
retyable, err = mocks.controller.isDaemonSetReady(cd, ds)
require.Error(t, err)
require.True(t, retrieable)
require.True(t, retyable)
require.True(t, strings.Contains(err.Error(), "new pods"))

// not ready and not retriable
cd.Status.LastTransitionTime = metav1.Now()
cd.Spec.ProgressDeadlineSeconds = int32p(-1e5)
retrieable, err = mocks.controller.isDaemonSetReady(cd, ds)
// only availableCond not satisfied
ds = &appsv1.DaemonSet{Status: appsv1.DaemonSetStatus{
UpdatedNumberScheduled: 1,
DesiredNumberScheduled: 1,
NumberAvailable: 0,
}}
retyable, err = mocks.controller.isDaemonSetReady(cd, ds)
require.Error(t, err)
require.False(t, retrieable)

require.True(t, retyable)
require.True(t, strings.Contains(err.Error(), "available"))
}