Skip to content

Commit 165e960

Browse files
committed
Simplify deployment status check to reduce flapping.
The deployment status logic (originally based on kubectl's rollout status) can impact its ClusterServiceVersion's status (e.g. by sending it to Failed/ComponentUnhealthy or triggering reinstallation). If these ClusterServiceVersion status updates are generated when no changes have been made to the Deployment spec and when the Deployment itself has condition Available: True, then the usefulness of ClusterServiceVersion statuses as a signal is reduced. This patch changes the deployment status logic so that it is considered "done" when a) there are no replicas from earlier generations, and b) the Deployment has condition Available: true. Signed-off-by: Ben Luddy <[email protected]>
1 parent 1e0b2ba commit 165e960

File tree

2 files changed

+65
-114
lines changed

2 files changed

+65
-114
lines changed

pkg/controller/install/status_viewer.go

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package install
22

3-
// See kubernetes/pkg/kubectl/rollout_status.go
4-
53
import (
64
"fmt"
75

@@ -15,29 +13,26 @@ const TimedOutReason = "ProgressDeadlineExceeded"
1513
func DeploymentStatus(deployment *appsv1.Deployment) (string, bool, error) {
1614
if deployment.Generation <= deployment.Status.ObservedGeneration {
1715
// check if deployment has timed out
18-
cond := getDeploymentCondition(deployment.Status, appsv1.DeploymentProgressing)
19-
if cond != nil && cond.Reason == TimedOutReason {
16+
progressing := getDeploymentCondition(deployment.Status, appsv1.DeploymentProgressing)
17+
if progressing != nil && progressing.Reason == TimedOutReason {
2018
return "", false, fmt.Errorf("deployment %q exceeded its progress deadline", deployment.Name)
2119
}
22-
// not all replicas are up yet
23-
if deployment.Spec.Replicas != nil && deployment.Status.UpdatedReplicas < *deployment.Spec.Replicas {
24-
return fmt.Sprintf("Waiting for rollout to finish: %d out of %d new replicas have been updated...\n", deployment.Status.UpdatedReplicas, *deployment.Spec.Replicas), false, nil
25-
}
26-
// waiting for old replicas to be cleaned up
20+
2721
if deployment.Status.Replicas > deployment.Status.UpdatedReplicas {
28-
return fmt.Sprintf("Waiting for rollout to finish: %d old replicas are pending termination...\n", deployment.Status.Replicas-deployment.Status.UpdatedReplicas), false, nil
22+
return fmt.Sprintf("deployment %q waiting for %d outdated replica(s) to be terminated", deployment.Name, deployment.Status.Replicas-deployment.Status.UpdatedReplicas), false, nil
2923
}
30-
if c := getDeploymentCondition(deployment.Status, appsv1.DeploymentAvailable); c == nil || c.Status != corev1.ConditionTrue {
31-
msg := fmt.Sprintf("deployment %q missing condition %q", deployment.Name, appsv1.DeploymentAvailable)
32-
if c != nil {
33-
msg = fmt.Sprintf("deployment %q not available: %s", deployment.Name, c.Message)
24+
25+
if available := getDeploymentCondition(deployment.Status, appsv1.DeploymentAvailable); available == nil || available.Status != corev1.ConditionTrue {
26+
msg := fmt.Sprintf("missing condition %q", appsv1.DeploymentAvailable)
27+
if available != nil {
28+
msg = available.Message
3429
}
35-
return fmt.Sprintf("Waiting for rollout to finish: %s\n", msg), false, nil
30+
return fmt.Sprintf("deployment %q not available: %s", deployment.Name, msg), false, nil
3631
}
37-
// deployment is finished
38-
return fmt.Sprintf("deployment %q successfully rolled out\n", deployment.Name), true, nil
32+
33+
return fmt.Sprintf("deployment %q is up-to-date and available", deployment.Name), true, nil
3934
}
40-
return fmt.Sprintf("Waiting for deployment spec update to be observed...\n"), false, nil
35+
return fmt.Sprintf("waiting for spec update of deployment %q to be observed...", deployment.Name), false, nil
4136
}
4237

4338
func getDeploymentCondition(status appsv1.DeploymentStatus, condType appsv1.DeploymentConditionType) *appsv1.DeploymentCondition {

pkg/controller/install/status_viewer_test.go

Lines changed: 52 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -4,140 +4,106 @@ import (
44
"fmt"
55
"testing"
66

7+
"github.com/stretchr/testify/assert"
78
apps "k8s.io/api/apps/v1"
89
core "k8s.io/api/core/v1"
910
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1011
)
1112

1213
func TestDeploymentStatusViewerStatus(t *testing.T) {
1314
tests := []struct {
14-
generation int64
15-
specReplicas int32
16-
status apps.DeploymentStatus
17-
msg string
18-
done bool
15+
generation int64
16+
status apps.DeploymentStatus
17+
err error
18+
msg string
19+
done bool
1920
}{
2021
{
21-
generation: 0,
22-
specReplicas: 1,
2322
status: apps.DeploymentStatus{
24-
ObservedGeneration: 1,
25-
Replicas: 1,
26-
UpdatedReplicas: 0,
27-
AvailableReplicas: 1,
28-
UnavailableReplicas: 0,
2923
Conditions: []apps.DeploymentCondition{
3024
{
3125
Type: apps.DeploymentProgressing,
32-
Reason: "NotTimedOut",
26+
Reason: TimedOutReason,
3327
},
3428
},
3529
},
36-
37-
msg: "Waiting for rollout to finish: 0 out of 1 new replicas have been updated...\n",
30+
err: fmt.Errorf("deployment \"foo\" exceeded its progress deadline"),
3831
done: false,
3932
},
4033
{
41-
generation: 0,
42-
specReplicas: 1,
4334
status: apps.DeploymentStatus{
44-
ObservedGeneration: 1,
45-
Replicas: 1,
46-
UpdatedReplicas: 0,
47-
AvailableReplicas: 1,
48-
UnavailableReplicas: 0,
35+
Conditions: []apps.DeploymentCondition{
36+
{
37+
Type: apps.DeploymentProgressing,
38+
Reason: "NotTimedOut",
39+
},
40+
{
41+
Type: apps.DeploymentAvailable,
42+
Status: core.ConditionTrue,
43+
},
44+
},
45+
},
46+
msg: "deployment \"foo\" is up-to-date and available",
47+
done: true,
48+
},
49+
{
50+
generation: 1,
51+
status: apps.DeploymentStatus{
52+
ObservedGeneration: 0,
4953
},
50-
51-
msg: "Waiting for rollout to finish: 0 out of 1 new replicas have been updated...\n",
54+
msg: "waiting for spec update of deployment \"foo\" to be observed...",
5255
done: false,
5356
},
5457
{
55-
generation: 1,
56-
specReplicas: 1,
5758
status: apps.DeploymentStatus{
58-
ObservedGeneration: 1,
59-
Replicas: 2,
60-
UpdatedReplicas: 1,
61-
AvailableReplicas: 2,
62-
UnavailableReplicas: 0,
59+
Replicas: 5,
60+
UpdatedReplicas: 3,
6361
},
64-
65-
msg: "Waiting for rollout to finish: 1 old replicas are pending termination...\n",
62+
msg: "deployment \"foo\" waiting for 2 outdated replica(s) to be terminated",
6663
done: false,
6764
},
6865
{
69-
generation: 1,
70-
specReplicas: 2,
66+
status: apps.DeploymentStatus{},
67+
msg: fmt.Sprintf("deployment \"foo\" not available: missing condition %q", apps.DeploymentAvailable),
68+
done: false,
69+
},
70+
{
7171
status: apps.DeploymentStatus{
72-
ObservedGeneration: 1,
73-
Replicas: 2,
74-
UpdatedReplicas: 2,
75-
AvailableReplicas: 1,
76-
UnavailableReplicas: 1,
7772
Conditions: []apps.DeploymentCondition{
7873
{
7974
Type: apps.DeploymentAvailable,
8075
Status: core.ConditionFalse,
81-
Message: "Deployment does not have minimum availability.",
76+
Message: "test message",
8277
},
8378
},
8479
},
85-
86-
msg: "Waiting for rollout to finish: deployment \"foo\" not available: Deployment does not have minimum availability.\n",
80+
msg: "deployment \"foo\" not available: test message",
8781
done: false,
8882
},
8983
{
90-
generation: 1,
91-
specReplicas: 2,
9284
status: apps.DeploymentStatus{
93-
ObservedGeneration: 1,
94-
Replicas: 2,
95-
UpdatedReplicas: 2,
96-
AvailableReplicas: 1,
97-
UnavailableReplicas: 1,
9885
Conditions: []apps.DeploymentCondition{
9986
{
100-
Type: apps.DeploymentAvailable,
101-
Status: core.ConditionTrue,
87+
Type: apps.DeploymentAvailable,
88+
Status: core.ConditionUnknown,
89+
Message: "test message",
10290
},
10391
},
10492
},
105-
106-
msg: "deployment \"foo\" successfully rolled out\n",
107-
done: true,
93+
msg: "deployment \"foo\" not available: test message",
94+
done: false,
10895
},
10996
{
110-
generation: 1,
111-
specReplicas: 2,
11297
status: apps.DeploymentStatus{
113-
ObservedGeneration: 1,
114-
Replicas: 2,
115-
UpdatedReplicas: 2,
116-
AvailableReplicas: 1,
117-
UnavailableReplicas: 1,
11898
Conditions: []apps.DeploymentCondition{
11999
{
120-
Type: "Fooing",
100+
Type: apps.DeploymentAvailable,
121101
Status: core.ConditionTrue,
122102
},
123103
},
124104
},
125-
msg: "Waiting for rollout to finish: deployment \"foo\" missing condition \"Available\"\n",
126-
done: false,
127-
},
128-
{
129-
generation: 2,
130-
specReplicas: 2,
131-
status: apps.DeploymentStatus{
132-
ObservedGeneration: 1,
133-
Replicas: 2,
134-
UpdatedReplicas: 2,
135-
AvailableReplicas: 2,
136-
UnavailableReplicas: 0,
137-
},
138-
139-
msg: "Waiting for deployment spec update to be observed...\n",
140-
done: false,
105+
msg: "deployment \"foo\" is up-to-date and available",
106+
done: true,
141107
},
142108
}
143109

@@ -147,29 +113,19 @@ func TestDeploymentStatusViewerStatus(t *testing.T) {
147113
ObjectMeta: metav1.ObjectMeta{
148114
Namespace: "bar",
149115
Name: "foo",
150-
UID: "8764ae47-9092-11e4-8393-42010af018ff",
151116
Generation: test.generation,
152117
},
153-
Spec: apps.DeploymentSpec{
154-
Replicas: &test.specReplicas,
155-
},
156118
Status: test.status,
157119
}
158120
msg, done, err := DeploymentStatus(d)
159-
if err != nil {
160-
t.Fatalf("DeploymentStatusViewer.Status(): %v", err)
161-
}
162-
if done != test.done || msg != test.msg {
163-
t.Errorf("DeploymentStatusViewer.Status() for deployment with generation %d, %d replicas specified, and status %+v returned %q, %t, want %q, %t",
164-
test.generation,
165-
test.specReplicas,
166-
test.status,
167-
msg,
168-
done,
169-
test.msg,
170-
test.done,
171-
)
121+
assert := assert.New(t)
122+
if test.err == nil {
123+
assert.NoError(err)
124+
} else {
125+
assert.EqualError(err, test.err.Error())
172126
}
127+
assert.Equal(test.done, done)
128+
assert.Equal(test.msg, msg)
173129
})
174130
}
175131
}

0 commit comments

Comments
 (0)