github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controllerutil/pod_utils_test.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package controllerutil 21 22 import ( 23 "encoding/json" 24 "sort" 25 "strings" 26 "testing" 27 "time" 28 29 . "github.com/onsi/ginkgo/v2" 30 . "github.com/onsi/gomega" 31 32 appsv1 "k8s.io/api/apps/v1" 33 corev1 "k8s.io/api/core/v1" 34 "k8s.io/apimachinery/pkg/api/resource" 35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 36 metautil "k8s.io/apimachinery/pkg/util/intstr" 37 38 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 39 "github.com/1aal/kubeblocks/pkg/constant" 40 testk8s "github.com/1aal/kubeblocks/pkg/testutil/k8s" 41 ) 42 43 type TestResourceUnit struct { 44 pvc corev1.PersistentVolumeClaimSpec 45 container corev1.Container 46 expectMemorySize int64 47 expectCPU int 48 expectStorageSize int64 49 } 50 51 func TestPodIsReady(t *testing.T) { 52 set := testk8s.NewFakeStatefulSet("foo", 3) 53 pod := testk8s.NewFakeStatefulSetPod(set, 1) 54 pod.Status.Conditions = []corev1.PodCondition{ 55 { 56 Type: corev1.PodReady, 57 Status: corev1.ConditionTrue, 58 }, 59 } 60 pod.Labels = map[string]string{constant.RoleLabelKey: "leader"} 61 if !PodIsReadyWithLabel(*pod) { 62 t.Errorf("isReady returned false negative") 63 } 64 65 pod.DeletionTimestamp = &metav1.Time{Time: time.Now()} 66 if PodIsReadyWithLabel(*pod) { 67 t.Errorf("isReady returned false positive") 68 } 69 70 pod.Labels = nil 71 if PodIsReadyWithLabel(*pod) { 72 t.Errorf("isReady returned false positive") 73 } 74 75 pod.Status.Conditions = nil 76 if PodIsReadyWithLabel(*pod) { 77 t.Errorf("isReady returned false positive") 78 } 79 80 pod.Status.Conditions = []corev1.PodCondition{} 81 if PodIsReadyWithLabel(*pod) { 82 t.Errorf("isReady returned false positive") 83 } 84 } 85 86 func TestPodIsControlledByLatestRevision(t *testing.T) { 87 set := testk8s.NewFakeStatefulSet("foo", 3) 88 pod := testk8s.NewFakeStatefulSetPod(set, 1) 89 pod.Labels = map[string]string{ 90 appsv1.ControllerRevisionHashLabelKey: "test", 91 } 92 set.Generation = 1 93 set.Status.UpdateRevision = "test" 94 if PodIsControlledByLatestRevision(pod, set) { 95 t.Errorf("PodIsControlledByLatestRevision returned false positive") 96 } 97 set.Status.ObservedGeneration = 1 98 if !PodIsControlledByLatestRevision(pod, set) { 99 t.Errorf("PodIsControlledByLatestRevision returned false positive") 100 } 101 } 102 103 func TestGetPodRevision(t *testing.T) { 104 set := testk8s.NewFakeStatefulSet("foo", 3) 105 pod := testk8s.NewFakeStatefulSetPod(set, 1) 106 if GetPodRevision(pod) != "" { 107 t.Errorf("revision should be empty") 108 } 109 110 pod.Labels = make(map[string]string, 0) 111 pod.Labels[appsv1.StatefulSetRevisionLabel] = "bar" 112 113 if GetPodRevision(pod) != "bar" { 114 t.Errorf("revision not matched") 115 } 116 } 117 118 var _ = Describe("pod utils", func() { 119 120 var ( 121 statefulSet *appsv1.StatefulSet 122 pod *corev1.Pod 123 configTemplates = []appsv1alpha1.ComponentConfigSpec{ 124 { 125 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 126 Name: "xxxxx", 127 VolumeName: "config1", 128 }, 129 }, 130 { 131 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 132 Name: "xxxxx2", 133 VolumeName: "config2", 134 }, 135 }, 136 } 137 138 foundInitContainerConfigTemplates = []appsv1alpha1.ComponentConfigSpec{ 139 { 140 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 141 Name: "xxxxx", 142 VolumeName: "config1_init_container", 143 }, 144 }, 145 { 146 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 147 Name: "xxxxx2", 148 VolumeName: "config2_init_container", 149 }, 150 }, 151 } 152 153 notFoundConfigTemplates = []appsv1alpha1.ComponentConfigSpec{ 154 { 155 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 156 Name: "xxxxx", 157 VolumeName: "config1_not_fount", 158 }, 159 }, 160 { 161 ComponentTemplateSpec: appsv1alpha1.ComponentTemplateSpec{ 162 Name: "xxxxx2", 163 VolumeName: "config2_not_fount", 164 }, 165 }, 166 } 167 ) 168 169 const ( 170 testContainers = ` 171 { 172 "name": "mysql", 173 "imagePullPolicy": "IfNotPresent", 174 "ports": [ 175 { 176 "containerPort": 3306, 177 "protocol": "TCP", 178 "name": "mysql" 179 }, 180 { 181 "containerPort": 13306, 182 "protocol": "TCP", 183 "name": "paxos" 184 } 185 ], 186 "volumeMounts": [ 187 { 188 "mountPath": "/data/config", 189 "name": "config1" 190 }, 191 { 192 "mountPath": "/data/config", 193 "name": "config2" 194 }, 195 { 196 "mountPath": "/data", 197 "name": "data" 198 }, 199 { 200 "mountPath": "/log", 201 "name": "log" 202 } 203 ], 204 "env": [ 205 { 206 "name": "MYSQL_ROOT_PASSWORD", 207 "valueFrom": { 208 "secretKeyRef": { 209 "name": "$(CONN_CREDENTIAL_SECRET_NAME)", 210 "key": "password" 211 } 212 } 213 } 214 ] 215 } 216 ` 217 ) 218 219 BeforeEach(func() { 220 // Add any steup steps that needs to be executed before each test 221 statefulSet = &appsv1.StatefulSet{} 222 statefulSet.ObjectMeta.Name = "stateful_test" 223 statefulSet.ObjectMeta.Namespace = "stateful_test_ns" 224 225 container := corev1.Container{} 226 if err := json.Unmarshal([]byte(testContainers), &container); err != nil { 227 Fail("convert container failed!") 228 } 229 230 container2 := container.DeepCopy() 231 container2.Name = "mysql2" 232 container2.VolumeMounts[1].Name += "_not_found" 233 container3 := container.DeepCopy() 234 container3.Name = "mysql3" 235 container3.VolumeMounts[0].Name += "_not_found" 236 container3.EnvFrom = []corev1.EnvFromSource{ 237 { 238 ConfigMapRef: &corev1.ConfigMapEnvSource{ 239 LocalObjectReference: corev1.LocalObjectReference{Name: "test-config-env"}, 240 }, 241 }, 242 } 243 244 container4 := container.DeepCopy() 245 container4.Name = "mysql4" 246 container4.VolumeMounts = nil 247 container4.EnvFrom = []corev1.EnvFromSource{ 248 { 249 ConfigMapRef: &corev1.ConfigMapEnvSource{ 250 LocalObjectReference: corev1.LocalObjectReference{Name: "test-config-env"}, 251 }, 252 }, 253 } 254 255 statefulSet.Spec.Template.Spec.Containers = []corev1.Container{ 256 *container2, *container3, container} 257 258 // init container 259 initContainer := container.DeepCopy() 260 initContainer.Name = "init_mysql" 261 initContainer2 := container.DeepCopy() 262 initContainer2.Name = "init_mysql_2" 263 initContainer3 := container.DeepCopy() 264 initContainer3.Name = "init_mysql_3" 265 initContainer.VolumeMounts[0].Name += "_init_container" 266 initContainer.VolumeMounts[1].Name += "_init_container" 267 statefulSet.Spec.Template.Spec.InitContainers = []corev1.Container{ 268 *initContainer, *initContainer2, *initContainer3} 269 270 // init pod 271 pod = &corev1.Pod{} 272 pod.ObjectMeta.Name = "pod_test" 273 pod.ObjectMeta.Namespace = "pod_test_ns" 274 pod.Spec.Containers = []corev1.Container{container, *container2, *container3, *container4} 275 pod.Spec.Volumes = []corev1.Volume{ 276 { 277 Name: "config1", 278 VolumeSource: corev1.VolumeSource{ 279 ConfigMap: &corev1.ConfigMapVolumeSource{ 280 LocalObjectReference: corev1.LocalObjectReference{Name: "stateful_test-config1"}, 281 }, 282 }, 283 }, 284 { 285 Name: "config2", 286 VolumeSource: corev1.VolumeSource{ 287 ConfigMap: &corev1.ConfigMapVolumeSource{ 288 LocalObjectReference: corev1.LocalObjectReference{Name: "stateful_test-config2"}, 289 }, 290 }, 291 }, 292 } 293 294 }) 295 296 // for test GetContainerByConfigSpec 297 Context("GetContainerByConfigSpec test", func() { 298 // found name: mysql3 299 It("Should succeed with no error", func() { 300 podSpec := &statefulSet.Spec.Template.Spec 301 Expect(GetContainerByConfigSpec(podSpec, configTemplates)).To(Equal(&podSpec.Containers[2])) 302 }) 303 // found name: init_mysql 304 It("Should succeed with no error", func() { 305 podSpec := &statefulSet.Spec.Template.Spec 306 Expect(GetContainerByConfigSpec(podSpec, foundInitContainerConfigTemplates)).To(Equal(&podSpec.InitContainers[0])) 307 }) 308 // not found container 309 It("Should fail", func() { 310 podSpec := &statefulSet.Spec.Template.Spec 311 Expect(GetContainerByConfigSpec(podSpec, notFoundConfigTemplates)).To(BeNil(), "get container is nil!") 312 }) 313 }) 314 315 // for test GetVolumeMountName 316 Context("GetPodContainerWithVolumeMount test", func() { 317 It("Should succeed with no error", func() { 318 mountedContainers := GetPodContainerWithVolumeMount(&pod.Spec, "config1") 319 Expect(len(mountedContainers)).To(Equal(2)) 320 Expect(mountedContainers[0].Name).To(Equal("mysql")) 321 Expect(mountedContainers[1].Name).To(Equal("mysql2")) 322 323 // 324 mountedContainers = GetPodContainerWithVolumeMount(&pod.Spec, "config2") 325 Expect(len(mountedContainers)).To(Equal(2)) 326 Expect(mountedContainers[0].Name).To(Equal("mysql")) 327 Expect(mountedContainers[1].Name).To(Equal("mysql3")) 328 }) 329 It("Should fail", func() { 330 Expect(len(GetPodContainerWithVolumeMount(&pod.Spec, "not_exist_cm"))).To(Equal(0)) 331 332 emptyPod := corev1.Pod{} 333 emptyPod.ObjectMeta.Name = "empty_test" 334 emptyPod.ObjectMeta.Namespace = "empty_test_ns" 335 Expect(GetPodContainerWithVolumeMount(&emptyPod.Spec, "not_exist_cm")).To(BeNil()) 336 337 }) 338 }) 339 340 // for test GetContainerWithVolumeMount 341 Context("GetVolumeMountName test", func() { 342 It("Should succeed with no error", func() { 343 volume := GetVolumeMountName(pod.Spec.Volumes, "stateful_test-config1") 344 Expect(volume).NotTo(BeNil()) 345 Expect(volume.Name).To(Equal("config1")) 346 347 Expect(GetVolumeMountName(pod.Spec.Volumes, "stateful_test-config1")).To(Equal(&pod.Spec.Volumes[0])) 348 }) 349 It("Should fail", func() { 350 Expect(GetVolumeMountName(pod.Spec.Volumes, "not_exist_resource")).To(BeNil()) 351 }) 352 }) 353 354 // for test MemorySize or CoreNum 355 Context("Get Resource test", func() { 356 It("Resource exists limit", func() { 357 testResources := []TestResourceUnit{ 358 // memory unit: Gi 359 { 360 container: corev1.Container{ 361 Resources: corev1.ResourceRequirements{ 362 Limits: corev1.ResourceList{ 363 corev1.ResourceMemory: resource.MustParse("10Gi"), 364 corev1.ResourceCPU: resource.MustParse("6"), 365 corev1.ResourceStorage: resource.MustParse("100G"), 366 }, 367 }, 368 }, 369 expectMemorySize: 10 * 1024 * 1024 * 1024, 370 expectCPU: 6, 371 }, 372 // memory unit: G 373 { 374 container: corev1.Container{ 375 Resources: corev1.ResourceRequirements{ 376 Limits: corev1.ResourceList{ 377 corev1.ResourceMemory: resource.MustParse("10G"), 378 corev1.ResourceCPU: resource.MustParse("16"), 379 corev1.ResourceStorage: resource.MustParse("100G"), 380 }, 381 }, 382 }, 383 expectMemorySize: 10 * 1000 * 1000 * 1000, 384 expectCPU: 16, 385 }, 386 // memory unit: no 387 { 388 container: corev1.Container{ 389 Resources: corev1.ResourceRequirements{ 390 Limits: corev1.ResourceList{ 391 corev1.ResourceMemory: resource.MustParse("1024000"), 392 corev1.ResourceCPU: resource.MustParse("26"), 393 corev1.ResourceStorage: resource.MustParse("100G"), 394 }, 395 }, 396 }, 397 expectMemorySize: 1024000, 398 expectCPU: 26, 399 }, 400 } 401 402 for i := range testResources { 403 Expect(GetMemorySize(testResources[i].container)).To(BeEquivalentTo(testResources[i].expectMemorySize)) 404 Expect(GetCoreNum(testResources[i].container)).To(BeEquivalentTo(testResources[i].expectCPU)) 405 } 406 }) 407 It("Resource not limit", func() { 408 container := corev1.Container{} 409 Expect(GetMemorySize(container)).To(BeEquivalentTo(0)) 410 Expect(GetCoreNum(container)).To(BeEquivalentTo(0)) 411 }) 412 }) 413 414 // for test MemorySize or CoreNum 415 Context("Get pvc test", func() { 416 It("Resource exists request", func() { 417 testResources := []TestResourceUnit{ 418 // memory unit: Gi 419 { 420 pvc: corev1.PersistentVolumeClaimSpec{ 421 Resources: corev1.ResourceRequirements{ 422 Requests: corev1.ResourceList{ 423 corev1.ResourceStorage: resource.MustParse("100Gi"), 424 }, 425 }, 426 }, 427 expectStorageSize: 100 * 1024 * 1024 * 1024, 428 }, 429 // memory unit: G 430 { 431 pvc: corev1.PersistentVolumeClaimSpec{ 432 Resources: corev1.ResourceRequirements{ 433 Requests: corev1.ResourceList{ 434 corev1.ResourceStorage: resource.MustParse("100G"), 435 }, 436 }, 437 }, 438 expectStorageSize: 100 * 1000 * 1000 * 1000, 439 }, 440 // memory unit: no 441 { 442 pvc: corev1.PersistentVolumeClaimSpec{ 443 Resources: corev1.ResourceRequirements{ 444 Requests: corev1.ResourceList{ 445 corev1.ResourceStorage: resource.MustParse("10000"), 446 }, 447 }, 448 }, 449 expectStorageSize: 10000, 450 }, 451 } 452 453 for i := range testResources { 454 Expect(GetStorageSizeFromPersistentVolume(corev1.PersistentVolumeClaimTemplate{ 455 Spec: testResources[i].pvc, 456 })).To(BeEquivalentTo(testResources[i].expectStorageSize)) 457 } 458 }) 459 It("Resource not request", func() { 460 pvcTpl := corev1.PersistentVolumeClaimTemplate{} 461 Expect(GetStorageSizeFromPersistentVolume(pvcTpl)).To(BeEquivalentTo(-1)) 462 }) 463 }) 464 465 Context("testGetContainerID", func() { 466 It("Should succeed with no error", func() { 467 pods := []*corev1.Pod{{ 468 Status: corev1.PodStatus{ 469 ContainerStatuses: []corev1.ContainerStatus{ 470 { 471 Name: "a", 472 ContainerID: "docker://27d1586d53ef9a6af5bd983831d13b6a38128119fadcdc22894d7b2397758eb5", 473 }, 474 { 475 Name: "b", 476 ContainerID: "docker://6f5ca0f22cd151943ba1b70f618591ad482cdbbc019ed58d7adf4c04f6d0ca7a", 477 }, 478 }, 479 }, 480 }, { 481 Status: corev1.PodStatus{ 482 ContainerStatuses: []corev1.ContainerStatus{}, 483 }, 484 }} 485 486 type args struct { 487 pod *corev1.Pod 488 containerName string 489 } 490 tests := []struct { 491 name string 492 args args 493 want string 494 }{{ 495 name: "test1", 496 args: args{ 497 pod: pods[0], 498 containerName: "b", 499 }, 500 want: "6f5ca0f22cd151943ba1b70f618591ad482cdbbc019ed58d7adf4c04f6d0ca7a", 501 }, { 502 name: "test2", 503 args: args{ 504 pod: pods[0], 505 containerName: "f", 506 }, 507 want: "", 508 }, { 509 name: "test3", 510 args: args{ 511 pod: pods[1], 512 containerName: "a", 513 }, 514 want: "", 515 }} 516 for _, tt := range tests { 517 Expect(GetContainerID(tt.args.pod, tt.args.containerName)).Should(BeEquivalentTo(tt.want)) 518 } 519 520 }) 521 }) 522 523 Context("common funcs test", func() { 524 It("GetContainersByConfigmap Should succeed with no error", func() { 525 type args struct { 526 containers []corev1.Container 527 volumeName string 528 envFrom string 529 filters []containerNameFilter 530 } 531 tests := []struct { 532 name string 533 args args 534 want []string 535 }{{ 536 name: "test1", 537 args: args{ 538 containers: pod.Spec.Containers, 539 volumeName: "config1", 540 }, 541 want: []string{"mysql", "mysql2"}, 542 }, { 543 name: "test1", 544 args: args{ 545 containers: pod.Spec.Containers, 546 volumeName: "config1", 547 filters: []containerNameFilter{ 548 func(name string) bool { 549 return name != "mysql" 550 }, 551 }, 552 }, 553 want: []string{"mysql"}, 554 }, { 555 name: "test1", 556 args: args{ 557 containers: pod.Spec.Containers, 558 volumeName: "config2", 559 filters: []containerNameFilter{ 560 func(name string) bool { 561 return strings.HasPrefix(name, "mysql") 562 }, 563 }, 564 }, 565 want: []string{}, 566 }, { 567 name: "test_env", 568 args: args{ 569 containers: pod.Spec.Containers, 570 volumeName: "not-config2", 571 envFrom: "test-config-env", 572 filters: []containerNameFilter{ 573 func(name string) bool { 574 return false 575 }, 576 }, 577 }, 578 want: []string{"mysql3", "mysql4"}, 579 }} 580 for _, tt := range tests { 581 Expect(GetContainersByConfigmap(tt.args.containers, tt.args.volumeName, tt.args.envFrom, tt.args.filters...)).Should(BeEquivalentTo(tt.want)) 582 } 583 584 }) 585 586 It("GetIntOrPercentValue Should succeed with no error", func() { 587 fn := func(v metautil.IntOrString) *metautil.IntOrString { return &v } 588 tests := []struct { 589 name string 590 args *metautil.IntOrString 591 want int 592 isPercent bool 593 wantErr bool 594 }{{ 595 name: "test", 596 args: fn(metautil.FromString("10")), 597 want: 0, 598 isPercent: false, 599 wantErr: true, 600 }, { 601 name: "test", 602 args: fn(metautil.FromString("10%")), 603 want: 10, 604 isPercent: true, 605 wantErr: false, 606 }, { 607 name: "test", 608 args: fn(metautil.FromInt(60)), 609 want: 60, 610 isPercent: false, 611 wantErr: false, 612 }} 613 614 for _, tt := range tests { 615 val, isPercent, err := GetIntOrPercentValue(tt.args) 616 Expect(err != nil).Should(BeEquivalentTo(tt.wantErr)) 617 Expect(val).Should(BeEquivalentTo(tt.want)) 618 Expect(isPercent).Should(BeEquivalentTo(tt.isPercent)) 619 } 620 }) 621 }) 622 Context("test sort by pod name", func() { 623 It("Should succeed with no error", func() { 624 pods := []corev1.Pod{{ 625 ObjectMeta: metav1.ObjectMeta{Name: "pod-2"}, 626 }, { 627 ObjectMeta: metav1.ObjectMeta{Name: "pod-3"}, 628 }, { 629 ObjectMeta: metav1.ObjectMeta{Name: "pod-0"}, 630 }, { 631 ObjectMeta: metav1.ObjectMeta{Name: "pod-1"}, 632 }} 633 sort.Sort(ByPodName(pods)) 634 Expect(pods[0].Name).Should(Equal("pod-0")) 635 Expect(pods[3].Name).Should(Equal("pod-3")) 636 }) 637 }) 638 })