-
Notifications
You must be signed in to change notification settings - Fork 153
Expand file tree
/
Copy pathuntaint_controller_integration_test.go
More file actions
214 lines (185 loc) · 7.23 KB
/
Copy pathuntaint_controller_integration_test.go
File metadata and controls
214 lines (185 loc) · 7.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.
//go:build integration
// +build integration
package controller
import (
"context"
"fmt"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/DataDog/datadog-operator/api/datadoghq/common"
"github.com/DataDog/datadog-operator/pkg/constants"
"github.com/DataDog/datadog-operator/pkg/untaint"
)
// Suite-level timeouts (see suite_v2_test.go):
// ReadinessTimeout = 4s
// SchedulingTimeout = 4s
// `untaintTimeout` must comfortably exceed both so that Eventually has room.
const (
untaintTimeout = 20 * time.Second
untaintInterval = 200 * time.Millisecond
)
// makeTaintedNode creates a Node carrying the agent-not-ready taint with the
// given name. The returned function deletes the Node — call it in AfterEach
// (or defer) to clean up.
func makeTaintedNode(ctx context.Context, name string) func() {
node := &corev1.Node{
ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: corev1.NodeSpec{Taints: []corev1.Taint{untaint.AgentNotReadyTaint()}},
}
Expect(k8sClient.Create(ctx, node)).Should(Succeed())
return func() { _ = k8sClient.Delete(ctx, node) }
}
func makeAgentPod(ctx context.Context, name, ns, nodeName string) *corev1.Pod {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: ns,
Labels: map[string]string{
common.AgentDeploymentComponentLabelKey: constants.DefaultAgentResourceSuffix,
},
},
Spec: corev1.PodSpec{
NodeName: nodeName,
Containers: []corev1.Container{{Name: "agent", Image: "fake"}},
},
}
Expect(k8sClient.Create(ctx, pod)).Should(Succeed())
return pod
}
var _ = Describe("Untaint Controller", func() {
ctx := context.Background()
Context("When agent pod becomes Ready on a tainted node", func() {
// Each test creates a fresh node so it does not race with the
// controller's timeout paths (configured to seconds in the suite).
var (
nodeName string
cleanup func()
podName = "agent-pod-becomes-ready"
podNS = "default"
)
BeforeEach(func() {
nodeName = fmt.Sprintf("ready-path-node-%d", time.Now().UnixNano())
cleanup = makeTaintedNode(ctx, nodeName)
})
AfterEach(func() {
pod := &corev1.Pod{}
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: podNS, Name: podName}, pod); err == nil {
_ = k8sClient.Delete(ctx, pod)
}
cleanup()
})
It("Should remove the taint when an agent pod transitions to Ready", func() {
pod := makeAgentPod(ctx, podName, podNS, nodeName)
// Set pod NotReady WITH a Status.StartTime in the recent past so
// the controller stays on the readiness path and does not fire the
// readiness timeout before we make the pod Ready below.
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: podNS, Name: podName}, pod)).Should(Succeed())
start := metav1.Now()
pod.Status.StartTime = &start
pod.Status.Conditions = []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionFalse}}
Expect(k8sClient.Status().Update(ctx, pod)).Should(Succeed())
// Transition to Ready.
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: podNS, Name: podName}, pod)).Should(Succeed())
pod.Status.Conditions = []corev1.PodCondition{{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
}}
Expect(k8sClient.Status().Update(ctx, pod)).Should(Succeed())
Eventually(func() bool {
fresh := &corev1.Node{}
if err := k8sClient.Get(ctx, types.NamespacedName{Name: nodeName}, fresh); err != nil {
return false
}
return !hasTaint(fresh)
}, untaintTimeout, untaintInterval).Should(BeTrue(), "taint should be removed after agent becomes ready")
})
})
Context("Startup catch-up (Ready pod created on cache sync)", func() {
var (
nodeName string
cleanup func()
podName = "agent-pod-catchup"
podNS = "default"
)
BeforeEach(func() {
nodeName = fmt.Sprintf("catchup-node-%d", time.Now().UnixNano())
cleanup = makeTaintedNode(ctx, nodeName)
})
AfterEach(func() {
pod := &corev1.Pod{}
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: podNS, Name: podName}, pod); err == nil {
_ = k8sClient.Delete(ctx, pod)
}
cleanup()
})
It("Should remove the taint when a Ready pod is created", func() {
pod := makeAgentPod(ctx, podName, podNS, nodeName)
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: podNS, Name: podName}, pod)).Should(Succeed())
start := metav1.Now()
pod.Status.StartTime = &start
pod.Status.Conditions = []corev1.PodCondition{{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
}}
Expect(k8sClient.Status().Update(ctx, pod)).Should(Succeed())
Eventually(func() bool {
fresh := &corev1.Node{}
if err := k8sClient.Get(ctx, types.NamespacedName{Name: nodeName}, fresh); err != nil {
return false
}
return !hasTaint(fresh)
}, untaintTimeout, untaintInterval).Should(BeTrue())
})
})
Context("Scheduling timeout (no agent pod ever scheduled)", func() {
// Configured scheduling timeout in the suite is 4s; expect untaint
// within Eventually's window.
It("Should untaint a node that never gets an agent pod", func() {
nodeName := fmt.Sprintf("scheduling-timeout-node-%d", time.Now().UnixNano())
cleanup := makeTaintedNode(ctx, nodeName)
defer cleanup()
Eventually(func() bool {
fresh := &corev1.Node{}
if err := k8sClient.Get(ctx, types.NamespacedName{Name: nodeName}, fresh); err != nil {
return false
}
return !hasTaint(fresh)
}, untaintTimeout, untaintInterval).Should(BeTrue(), "scheduling timeout should untaint the node")
})
})
Context("Readiness timeout (agent pod exists but never becomes Ready)", func() {
It("Should untaint the node after the readiness timeout (policy=remove)", func() {
nodeName := fmt.Sprintf("readiness-timeout-node-%d", time.Now().UnixNano())
cleanup := makeTaintedNode(ctx, nodeName)
defer cleanup()
podName := "agent-pod-stuck-not-ready"
podNS := "default"
pod := makeAgentPod(ctx, podName, podNS, nodeName)
defer func() { _ = k8sClient.Delete(ctx, pod) }()
// Set Status.StartTime to a past moment so we are already over
// the suite-configured readiness window (4s).
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: podNS, Name: podName}, pod)).Should(Succeed())
past := metav1.NewTime(time.Now().Add(-1 * time.Minute))
pod.Status.StartTime = &past
pod.Status.Conditions = []corev1.PodCondition{{Type: corev1.PodReady, Status: corev1.ConditionFalse}}
Expect(k8sClient.Status().Update(ctx, pod)).Should(Succeed())
Eventually(func() bool {
fresh := &corev1.Node{}
if err := k8sClient.Get(ctx, types.NamespacedName{Name: nodeName}, fresh); err != nil {
return false
}
return !hasTaint(fresh)
}, untaintTimeout, untaintInterval).Should(BeTrue(), "readiness timeout should untaint the node")
})
})
})