k8s.io/kubernetes@v1.29.3/pkg/controller/controller_ref_manager_test.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // act as a cluster-scoped controller 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 }