1
16
17 package controller
18
19 import (
20 "context"
21 "reflect"
22 "strings"
23 "testing"
24
25 "github.com/google/go-cmp/cmp"
26 apps "k8s.io/api/apps/v1"
27 "k8s.io/api/core/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/labels"
30 "k8s.io/apimachinery/pkg/runtime/schema"
31 "k8s.io/apimachinery/pkg/types"
32 )
33
34 var (
35 productionLabel = map[string]string{"type": "production"}
36 testLabel = map[string]string{"type": "testing"}
37 productionLabelSelector = labels.Set{"type": "production"}.AsSelector()
38 controllerUID = "123"
39 )
40
41 func newPod(podName string, label map[string]string, owner metav1.Object) *v1.Pod {
42 pod := &v1.Pod{
43 ObjectMeta: metav1.ObjectMeta{
44 Name: podName,
45 Labels: label,
46 Namespace: metav1.NamespaceDefault,
47 },
48 Spec: v1.PodSpec{
49 Containers: []v1.Container{
50 {
51 Image: "foo/bar",
52 },
53 },
54 },
55 }
56 if owner != nil {
57 pod.OwnerReferences = []metav1.OwnerReference{*metav1.NewControllerRef(owner, apps.SchemeGroupVersion.WithKind("Fake"))}
58 }
59 return pod
60 }
61
62 func TestClaimPods(t *testing.T) {
63 controllerKind := schema.GroupVersionKind{}
64 type test struct {
65 name string
66 manager *PodControllerRefManager
67 pods []*v1.Pod
68 claimed []*v1.Pod
69 patches int
70 }
71 var tests = []test{
72 func() test {
73 controller := v1.ReplicationController{}
74 controller.Namespace = metav1.NamespaceDefault
75 return test{
76 name: "Claim pods with correct label",
77 manager: NewPodControllerRefManager(&FakePodControl{},
78 &controller,
79 productionLabelSelector,
80 controllerKind,
81 func(ctx context.Context) error { return nil }),
82 pods: []*v1.Pod{newPod("pod1", productionLabel, nil), newPod("pod2", testLabel, nil)},
83 claimed: []*v1.Pod{newPod("pod1", productionLabel, nil)},
84 patches: 1,
85 }
86 }(),
87 func() test {
88 controller := v1.ReplicationController{}
89 controller.Namespace = metav1.NamespaceDefault
90 controller.UID = types.UID(controllerUID)
91 now := metav1.Now()
92 controller.DeletionTimestamp = &now
93 return test{
94 name: "Controller marked for deletion can not claim pods",
95 manager: NewPodControllerRefManager(&FakePodControl{},
96 &controller,
97 productionLabelSelector,
98 controllerKind,
99 func(ctx context.Context) error { return nil }),
100 pods: []*v1.Pod{newPod("pod1", productionLabel, nil), newPod("pod2", productionLabel, nil)},
101 claimed: nil,
102 }
103 }(),
104 func() test {
105 controller := v1.ReplicationController{}
106 controller.Namespace = metav1.NamespaceDefault
107 controller.UID = types.UID(controllerUID)
108 now := metav1.Now()
109 controller.DeletionTimestamp = &now
110 return test{
111 name: "Controller marked for deletion can not claim new pods",
112 manager: NewPodControllerRefManager(&FakePodControl{},
113 &controller,
114 productionLabelSelector,
115 controllerKind,
116 func(ctx context.Context) error { return nil }),
117 pods: []*v1.Pod{newPod("pod1", productionLabel, &controller), newPod("pod2", productionLabel, nil)},
118 claimed: []*v1.Pod{newPod("pod1", productionLabel, &controller)},
119 }
120 }(),
121 func() test {
122 controller := v1.ReplicationController{}
123 controller2 := v1.ReplicationController{}
124 controller.UID = types.UID(controllerUID)
125 controller.Namespace = metav1.NamespaceDefault
126 controller2.UID = types.UID("AAAAA")
127 controller2.Namespace = metav1.NamespaceDefault
128 return test{
129 name: "Controller can not claim pods owned by another controller",
130 manager: NewPodControllerRefManager(&FakePodControl{},
131 &controller,
132 productionLabelSelector,
133 controllerKind,
134 func(ctx context.Context) error { return nil }),
135 pods: []*v1.Pod{newPod("pod1", productionLabel, &controller), newPod("pod2", productionLabel, &controller2)},
136 claimed: []*v1.Pod{newPod("pod1", productionLabel, &controller)},
137 }
138 }(),
139 func() test {
140 controller := v1.ReplicationController{}
141 controller.Namespace = metav1.NamespaceDefault
142 controller.UID = types.UID(controllerUID)
143 return test{
144 name: "Controller releases claimed pods when selector doesn't match",
145 manager: NewPodControllerRefManager(&FakePodControl{},
146 &controller,
147 productionLabelSelector,
148 controllerKind,
149 func(ctx context.Context) error { return nil }),
150 pods: []*v1.Pod{newPod("pod1", productionLabel, &controller), newPod("pod2", testLabel, &controller)},
151 claimed: []*v1.Pod{newPod("pod1", productionLabel, &controller)},
152 patches: 1,
153 }
154 }(),
155 func() test {
156 controller := v1.ReplicationController{}
157 controller.Namespace = metav1.NamespaceDefault
158 controller.UID = types.UID(controllerUID)
159 podToDelete1 := newPod("pod1", productionLabel, &controller)
160 podToDelete2 := newPod("pod2", productionLabel, nil)
161 now := metav1.Now()
162 podToDelete1.DeletionTimestamp = &now
163 podToDelete2.DeletionTimestamp = &now
164
165 return test{
166 name: "Controller does not claim orphaned pods marked for deletion",
167 manager: NewPodControllerRefManager(&FakePodControl{},
168 &controller,
169 productionLabelSelector,
170 controllerKind,
171 func(ctx context.Context) error { return nil }),
172 pods: []*v1.Pod{podToDelete1, podToDelete2},
173 claimed: []*v1.Pod{podToDelete1},
174 }
175 }(),
176 func() test {
177 controller := v1.ReplicationController{}
178 controller.Namespace = metav1.NamespaceDefault
179 controller.UID = types.UID(controllerUID)
180 return test{
181 name: "Controller claims or release pods according to selector with finalizers",
182 manager: NewPodControllerRefManager(&FakePodControl{},
183 &controller,
184 productionLabelSelector,
185 controllerKind,
186 func(ctx context.Context) error { return nil },
187 "foo-finalizer", "bar-finalizer"),
188 pods: []*v1.Pod{newPod("pod1", productionLabel, &controller), newPod("pod2", testLabel, &controller), newPod("pod3", productionLabel, nil)},
189 claimed: []*v1.Pod{newPod("pod1", productionLabel, &controller), newPod("pod3", productionLabel, nil)},
190 patches: 2,
191 }
192 }(),
193 func() test {
194 controller := v1.ReplicationController{}
195 controller.Namespace = metav1.NamespaceDefault
196 controller.UID = types.UID(controllerUID)
197 pod1 := newPod("pod1", productionLabel, nil)
198 pod2 := newPod("pod2", productionLabel, nil)
199 pod2.Namespace = "fakens"
200 return test{
201 name: "Controller does not claim pods of different namespace",
202 manager: NewPodControllerRefManager(&FakePodControl{},
203 &controller,
204 productionLabelSelector,
205 controllerKind,
206 func(ctx context.Context) error { return nil }),
207 pods: []*v1.Pod{pod1, pod2},
208 claimed: []*v1.Pod{pod1},
209 patches: 1,
210 }
211 }(),
212 func() test {
213
214 controller := v1.ReplicationController{}
215 controller.Namespace = ""
216 controller.UID = types.UID(controllerUID)
217 pod1 := newPod("pod1", productionLabel, nil)
218 pod2 := newPod("pod2", productionLabel, nil)
219 pod2.Namespace = "fakens"
220 return test{
221 name: "Cluster scoped controller claims pods of specified namespace",
222 manager: NewPodControllerRefManager(&FakePodControl{},
223 &controller,
224 productionLabelSelector,
225 controllerKind,
226 func(ctx context.Context) error { return nil }),
227 pods: []*v1.Pod{pod1, pod2},
228 claimed: []*v1.Pod{pod1, pod2},
229 patches: 2,
230 }
231 }(),
232 }
233 for _, test := range tests {
234 t.Run(test.name, func(t *testing.T) {
235 claimed, err := test.manager.ClaimPods(context.TODO(), test.pods)
236 if err != nil {
237 t.Fatalf("Unexpected error: %v", err)
238 }
239 if diff := cmp.Diff(test.claimed, claimed); diff != "" {
240 t.Errorf("Claimed wrong pods (-want,+got):\n%s", diff)
241 }
242 fakePodControl, ok := test.manager.podControl.(*FakePodControl)
243 if !ok {
244 return
245 }
246 if p := len(fakePodControl.Patches); p != test.patches {
247 t.Errorf("ClaimPods issues %d patches, want %d", p, test.patches)
248 }
249 for _, p := range fakePodControl.Patches {
250 patch := string(p)
251 if uid := string(test.manager.Controller.GetUID()); !strings.Contains(patch, uid) {
252 t.Errorf("Patch doesn't contain controller UID %s", uid)
253 }
254 for _, f := range test.manager.finalizers {
255 if !strings.Contains(patch, f) {
256 t.Errorf("Patch doesn't contain finalizer %s, %q", patch, f)
257 }
258 }
259 }
260 })
261 }
262 }
263
264 func TestGeneratePatchBytesForDelete(t *testing.T) {
265 tests := []struct {
266 name string
267 ownerUID []types.UID
268 dependentUID types.UID
269 finalizers []string
270 want []byte
271 }{
272 {
273 name: "check the structure of patch bytes",
274 ownerUID: []types.UID{"ss1"},
275 dependentUID: "ss2",
276 finalizers: []string{},
277 want: []byte(`{"metadata":{"uid":"ss2","ownerReferences":[{"$patch":"delete","uid":"ss1"}]}}`),
278 },
279 {
280 name: "check if parent uid is escaped",
281 ownerUID: []types.UID{`ss1"hello`},
282 dependentUID: "ss2",
283 finalizers: []string{},
284 want: []byte(`{"metadata":{"uid":"ss2","ownerReferences":[{"$patch":"delete","uid":"ss1\"hello"}]}}`),
285 },
286 {
287 name: "check if revision uid uid is escaped",
288 ownerUID: []types.UID{`ss1`},
289 dependentUID: `ss2"hello`,
290 finalizers: []string{},
291 want: []byte(`{"metadata":{"uid":"ss2\"hello","ownerReferences":[{"$patch":"delete","uid":"ss1"}]}}`),
292 },
293 {
294 name: "check the structure of patch bytes with multiple owners",
295 ownerUID: []types.UID{"ss1", "ss2"},
296 dependentUID: "ss2",
297 finalizers: []string{},
298 want: []byte(`{"metadata":{"uid":"ss2","ownerReferences":[{"$patch":"delete","uid":"ss1"},{"$patch":"delete","uid":"ss2"}]}}`),
299 },
300 {
301 name: "check the structure of patch bytes with a finalizer and multiple owners",
302 ownerUID: []types.UID{"ss1", "ss2"},
303 dependentUID: "ss2",
304 finalizers: []string{"f1"},
305 want: []byte(`{"metadata":{"uid":"ss2","ownerReferences":[{"$patch":"delete","uid":"ss1"},{"$patch":"delete","uid":"ss2"}],"$deleteFromPrimitiveList/finalizers":["f1"]}}`),
306 },
307 }
308 for _, tt := range tests {
309 t.Run(tt.name, func(t *testing.T) {
310 got, _ := GenerateDeleteOwnerRefStrategicMergeBytes(tt.dependentUID, tt.ownerUID, tt.finalizers...)
311 if !reflect.DeepEqual(got, tt.want) {
312 t.Errorf("generatePatchBytesForDelete() got = %s, want %s", got, tt.want)
313 }
314 })
315 }
316 }
317
View as plain text