github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/vpa/vpa_test.go (about) 1 /* 2 Copyright 2022 The Katalyst 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 vpa 18 19 import ( 20 "context" 21 "fmt" 22 "reflect" 23 "testing" 24 "time" 25 26 "github.com/stretchr/testify/assert" 27 appsv1 "k8s.io/api/apps/v1" 28 v1 "k8s.io/api/core/v1" 29 "k8s.io/apimachinery/pkg/api/resource" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/util/wait" 33 "k8s.io/client-go/tools/cache" 34 cliflag "k8s.io/component-base/cli/flag" 35 "k8s.io/utils/pointer" 36 37 apis "github.com/kubewharf/katalyst-api/pkg/apis/autoscaling/v1alpha1" 38 apiconsts "github.com/kubewharf/katalyst-api/pkg/consts" 39 katalystbase "github.com/kubewharf/katalyst-core/cmd/base" 40 "github.com/kubewharf/katalyst-core/cmd/katalyst-controller/app/options" 41 "github.com/kubewharf/katalyst-core/pkg/config/controller" 42 "github.com/kubewharf/katalyst-core/pkg/config/generic" 43 "github.com/kubewharf/katalyst-core/pkg/controller/vpa/util" 44 ) 45 46 var makePod = func(name string, annotations, labels map[string]string, owners []metav1.OwnerReference) *v1.Pod { 47 pod := &v1.Pod{ 48 ObjectMeta: metav1.ObjectMeta{ 49 Name: name, 50 Namespace: "default", 51 Annotations: annotations, 52 Labels: labels, 53 OwnerReferences: owners, 54 }, 55 } 56 return pod 57 } 58 59 func TestVPAControllerSyncVPA(t *testing.T) { 60 t.Parallel() 61 62 pod1 := makePod("pod1", 63 map[string]string{}, 64 map[string]string{"workload": "sts1"}, 65 []metav1.OwnerReference{ 66 { 67 Kind: "StatefulSet", 68 APIVersion: "apps/v1", 69 Name: "sts1", 70 }, 71 }, 72 ) 73 pod1.Spec.Containers = []v1.Container{ 74 { 75 Name: "c1", 76 Resources: v1.ResourceRequirements{ 77 Limits: map[v1.ResourceName]resource.Quantity{ 78 v1.ResourceCPU: resource.MustParse("4"), 79 v1.ResourceMemory: resource.MustParse("4Gi"), 80 }, 81 Requests: map[v1.ResourceName]resource.Quantity{ 82 v1.ResourceCPU: resource.MustParse("2"), 83 v1.ResourceMemory: resource.MustParse("2Gi"), 84 }, 85 }, 86 }, 87 } 88 pod1.Status.QOSClass = v1.PodQOSBurstable 89 90 newPod1 := pod1.DeepCopy() 91 newPod1.Annotations[apiconsts.PodAnnotationInplaceUpdateResourcesKey] = "{\"c1\":{\"requests\":{\"cpu\":\"1\",\"memory\":\"1Gi\"}}}" 92 93 vpa1 := &apis.KatalystVerticalPodAutoscaler{ 94 ObjectMeta: metav1.ObjectMeta{ 95 Name: "vpa1", 96 Namespace: "default", 97 UID: "vpauid1", 98 Annotations: map[string]string{ 99 apiconsts.VPAAnnotationWorkloadRetentionPolicyKey: apiconsts.VPAAnnotationWorkloadRetentionPolicyDelete, 100 }, 101 }, 102 Spec: apis.KatalystVerticalPodAutoscalerSpec{ 103 TargetRef: apis.CrossVersionObjectReference{ 104 Kind: "StatefulSet", 105 APIVersion: "apps/v1", 106 Name: "sts1", 107 }, 108 UpdatePolicy: apis.PodUpdatePolicy{ 109 PodUpdatingStrategy: apis.PodUpdatingStrategyInplace, 110 PodMatchingStrategy: apis.PodMatchingStrategyAll, 111 }, 112 }, 113 Status: apis.KatalystVerticalPodAutoscalerStatus{ 114 ContainerResources: []apis.ContainerResources{ 115 { 116 ContainerName: pointer.String("c1"), 117 Requests: &apis.ContainerResourceList{ 118 Target: map[v1.ResourceName]resource.Quantity{ 119 v1.ResourceCPU: resource.MustParse("1"), 120 v1.ResourceMemory: resource.MustParse("1Gi"), 121 }, 122 UncappedTarget: map[v1.ResourceName]resource.Quantity{ 123 v1.ResourceCPU: resource.MustParse("1"), 124 v1.ResourceMemory: resource.MustParse("1Gi"), 125 }, 126 }, 127 }, 128 }, 129 Conditions: []apis.VerticalPodAutoscalerCondition{ 130 { 131 Type: apis.RecommendationUpdated, 132 Status: v1.ConditionTrue, 133 Reason: util.VPAConditionReasonUpdated, 134 }, 135 }, 136 }, 137 } 138 vpaNew1 := vpa1.DeepCopy() 139 vpaNew1.OwnerReferences = nil 140 141 vpa2 := vpaNew1.DeepCopy() 142 vpa2.Annotations[apiconsts.VPAAnnotationWorkloadRetentionPolicyKey] = apiconsts.VPAAnnotationWorkloadRetentionPolicyRetain 143 vpanew2 := vpa2.DeepCopy() 144 vpanew2.OwnerReferences = []metav1.OwnerReference{ 145 { 146 APIVersion: "apps/v1", 147 Kind: "StatefulSet", 148 Name: "sts1", 149 UID: "", 150 }, 151 } 152 153 vpa3 := &apis.KatalystVerticalPodAutoscaler{ 154 ObjectMeta: metav1.ObjectMeta{ 155 Name: "vpa1", 156 Namespace: "default", 157 UID: "vpauid1", 158 Annotations: map[string]string{ 159 apiconsts.VPAAnnotationWorkloadRetentionPolicyKey: apiconsts.VPAAnnotationWorkloadRetentionPolicyDelete, 160 }, 161 }, 162 Spec: apis.KatalystVerticalPodAutoscalerSpec{ 163 TargetRef: apis.CrossVersionObjectReference{ 164 Kind: "StatefulSet", 165 APIVersion: "apps/v1", 166 Name: "sts1", 167 }, 168 UpdatePolicy: apis.PodUpdatePolicy{ 169 PodUpdatingStrategy: apis.PodUpdatingStrategyInplace, 170 PodMatchingStrategy: apis.PodMatchingStrategyAll, 171 }, 172 }, 173 Status: apis.KatalystVerticalPodAutoscalerStatus{ 174 ContainerResources: []apis.ContainerResources{ 175 { 176 ContainerName: pointer.String("c1"), 177 Limits: &apis.ContainerResourceList{ 178 Target: map[v1.ResourceName]resource.Quantity{}, 179 UncappedTarget: map[v1.ResourceName]resource.Quantity{}, 180 }, 181 }, 182 }, 183 Conditions: []apis.VerticalPodAutoscalerCondition{ 184 { 185 Type: apis.RecommendationUpdated, 186 Status: v1.ConditionTrue, 187 Reason: util.VPAConditionReasonUpdated, 188 }, 189 }, 190 }, 191 } 192 193 vpaNew3 := &apis.KatalystVerticalPodAutoscaler{ 194 ObjectMeta: metav1.ObjectMeta{ 195 Name: "vpa1", 196 Namespace: "default", 197 UID: "vpauid1", 198 Annotations: map[string]string{ 199 apiconsts.VPAAnnotationWorkloadRetentionPolicyKey: apiconsts.VPAAnnotationWorkloadRetentionPolicyDelete, 200 }, 201 }, 202 Spec: apis.KatalystVerticalPodAutoscalerSpec{ 203 TargetRef: apis.CrossVersionObjectReference{ 204 Kind: "StatefulSet", 205 APIVersion: "apps/v1", 206 Name: "sts1", 207 }, 208 UpdatePolicy: apis.PodUpdatePolicy{ 209 PodUpdatingStrategy: apis.PodUpdatingStrategyInplace, 210 PodMatchingStrategy: apis.PodMatchingStrategyAll, 211 }, 212 }, 213 Status: apis.KatalystVerticalPodAutoscalerStatus{ 214 ContainerResources: []apis.ContainerResources{ 215 { 216 ContainerName: pointer.String("c1"), 217 Limits: &apis.ContainerResourceList{ 218 Current: v1.ResourceList{ 219 v1.ResourceCPU: resource.MustParse("4"), 220 v1.ResourceMemory: resource.MustParse("4Gi"), 221 }, 222 Target: map[v1.ResourceName]resource.Quantity{}, 223 UncappedTarget: map[v1.ResourceName]resource.Quantity{}, 224 }, 225 }, 226 }, 227 Conditions: []apis.VerticalPodAutoscalerCondition{ 228 { 229 Type: apis.RecommendationUpdated, 230 Status: v1.ConditionTrue, 231 Reason: util.VPAConditionReasonUpdated, 232 }, 233 }, 234 }, 235 } 236 237 newPod3 := pod1.DeepCopy() 238 239 for _, tc := range []struct { 240 name string 241 object runtime.Object 242 pods []*v1.Pod 243 newPods []*v1.Pod 244 vpa *apis.KatalystVerticalPodAutoscaler 245 vpaNew *apis.KatalystVerticalPodAutoscaler 246 }{ 247 { 248 name: "delete owner reference", 249 vpa: vpa1, 250 vpaNew: vpaNew1, 251 object: &appsv1.StatefulSet{ 252 TypeMeta: metav1.TypeMeta{ 253 Kind: "StatefulSet", 254 APIVersion: "apps/v1", 255 }, 256 ObjectMeta: metav1.ObjectMeta{ 257 Name: "sts1", 258 Namespace: "default", 259 Annotations: map[string]string{ 260 apiconsts.WorkloadAnnotationVPAEnabledKey: apiconsts.WorkloadAnnotationVPAEnabled, 261 }, 262 }, 263 Spec: appsv1.StatefulSetSpec{ 264 Selector: &metav1.LabelSelector{ 265 MatchLabels: map[string]string{ 266 "workload": "sts1", 267 }, 268 }, 269 }, 270 }, 271 pods: []*v1.Pod{ 272 pod1, 273 }, 274 newPods: []*v1.Pod{ 275 newPod1, 276 }, 277 }, 278 { 279 name: "retain owner reference", 280 vpa: vpa2, 281 vpaNew: vpanew2, 282 object: &appsv1.StatefulSet{ 283 TypeMeta: metav1.TypeMeta{ 284 Kind: "StatefulSet", 285 APIVersion: "apps/v1", 286 }, 287 ObjectMeta: metav1.ObjectMeta{ 288 Name: "sts1", 289 Namespace: "default", 290 Annotations: map[string]string{ 291 apiconsts.WorkloadAnnotationVPAEnabledKey: apiconsts.WorkloadAnnotationVPAEnabled, 292 }, 293 }, 294 Spec: appsv1.StatefulSetSpec{ 295 Selector: &metav1.LabelSelector{ 296 MatchLabels: map[string]string{ 297 "workload": "sts1", 298 }, 299 }, 300 }, 301 }, 302 pods: []*v1.Pod{ 303 pod1, 304 }, 305 newPods: []*v1.Pod{ 306 newPod1, 307 }, 308 }, 309 { 310 name: "sync pod current", 311 vpa: vpa3, 312 vpaNew: vpaNew3, 313 object: &appsv1.StatefulSet{ 314 TypeMeta: metav1.TypeMeta{ 315 Kind: "StatefulSet", 316 APIVersion: "apps/v1", 317 }, 318 ObjectMeta: metav1.ObjectMeta{ 319 Name: "sts1", 320 Namespace: "default", 321 Annotations: map[string]string{ 322 apiconsts.WorkloadAnnotationVPAEnabledKey: apiconsts.WorkloadAnnotationVPAEnabled, 323 }, 324 }, 325 Spec: appsv1.StatefulSetSpec{ 326 Selector: &metav1.LabelSelector{ 327 MatchLabels: map[string]string{ 328 "workload": "sts1", 329 }, 330 }, 331 }, 332 }, 333 pods: []*v1.Pod{ 334 pod1, 335 }, 336 newPods: []*v1.Pod{ 337 newPod3, 338 }, 339 }, 340 } { 341 tc := tc 342 t.Run(tc.name, func(t *testing.T) { 343 t.Parallel() 344 345 fss := &cliflag.NamedFlagSets{} 346 vpaOptions := options.NewVPAOptions() 347 vpaOptions.AddFlags(fss) 348 vpaConf := controller.NewVPAConfig() 349 _ = vpaOptions.ApplyTo(vpaConf) 350 351 workloadGVResources := []string{"statefulsets.v1.apps"} 352 vpaConf.VPAWorkloadGVResources = workloadGVResources 353 354 genericConf := &generic.GenericConfiguration{} 355 controllerConf := &controller.GenericControllerConfiguration{ 356 DynamicGVResources: workloadGVResources, 357 } 358 359 controlCtx, err := katalystbase.GenerateFakeGenericContext(nil, nil, []runtime.Object{tc.object}) 360 assert.NoError(t, err) 361 362 vpaController, err := NewVPAController(context.TODO(), controlCtx, genericConf, controllerConf, vpaConf) 363 assert.NoError(t, err) 364 365 _, err = controlCtx.Client.InternalClient.AutoscalingV1alpha1(). 366 KatalystVerticalPodAutoscalers(tc.vpa.Namespace). 367 Create(context.TODO(), tc.vpa, metav1.CreateOptions{}) 368 assert.NoError(t, err) 369 370 for _, pod := range tc.pods { 371 _, err = controlCtx.Client.KubeClient.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) 372 assert.NoError(t, err) 373 } 374 375 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(tc.vpa) 376 assert.NoError(t, err) 377 378 controlCtx.StartInformer(vpaController.ctx) 379 synced := cache.WaitForCacheSync(vpaController.ctx.Done(), vpaController.syncedFunc...) 380 assert.True(t, synced) 381 382 err = vpaController.syncVPA(key) 383 assert.NoError(t, err) 384 385 for _, pod := range tc.newPods { 386 p, err := controlCtx.Client.KubeClient.CoreV1().Pods(pod.Namespace).Get(context.TODO(), pod.Name, metav1.GetOptions{}) 387 assert.NoError(t, err) 388 assert.Equal(t, pod, p) 389 } 390 391 vpa, err := controlCtx.Client.InternalClient.AutoscalingV1alpha1().KatalystVerticalPodAutoscalers(tc.vpaNew.Namespace). 392 Get(context.TODO(), tc.vpaNew.Name, metav1.GetOptions{}) 393 assert.NoError(t, err) 394 assert.Equal(t, tc.vpaNew.GetObjectMeta(), vpa.GetObjectMeta()) 395 }) 396 } 397 } 398 399 func TestVPAControllerSyncPod(t *testing.T) { 400 t.Parallel() 401 402 pod1 := makePod("pod1", 403 map[string]string{}, 404 map[string]string{"workload": "sts1"}, 405 []metav1.OwnerReference{ 406 { 407 Kind: "StatefulSet", 408 APIVersion: "apps/v1", 409 Name: "sts1", 410 }, 411 }, 412 ) 413 pod1.Spec.Containers = []v1.Container{ 414 { 415 Name: "c1", 416 Resources: v1.ResourceRequirements{ 417 Limits: map[v1.ResourceName]resource.Quantity{ 418 v1.ResourceCPU: resource.MustParse("4"), 419 v1.ResourceMemory: resource.MustParse("4Gi"), 420 }, 421 Requests: map[v1.ResourceName]resource.Quantity{ 422 v1.ResourceCPU: resource.MustParse("2"), 423 v1.ResourceMemory: resource.MustParse("2Gi"), 424 }, 425 }, 426 }, 427 } 428 pod1.Status.QOSClass = v1.PodQOSBurstable 429 430 newPod1 := pod1.DeepCopy() 431 newPod1.Annotations[apiconsts.PodAnnotationInplaceUpdateResourcesKey] = "{\"c1\":{\"requests\":{\"cpu\":\"1\",\"memory\":\"1Gi\"}}}" 432 for _, tc := range []struct { 433 name string 434 object runtime.Object 435 pod *v1.Pod 436 newPod *v1.Pod 437 vpa *apis.KatalystVerticalPodAutoscaler 438 }{ 439 { 440 name: "test1", 441 vpa: &apis.KatalystVerticalPodAutoscaler{ 442 ObjectMeta: metav1.ObjectMeta{ 443 Name: "vpa1", 444 Namespace: "default", 445 UID: "vpauid1", 446 }, 447 Spec: apis.KatalystVerticalPodAutoscalerSpec{ 448 TargetRef: apis.CrossVersionObjectReference{ 449 Kind: "StatefulSet", 450 APIVersion: "apps/v1", 451 Name: "sts1", 452 }, 453 UpdatePolicy: apis.PodUpdatePolicy{ 454 PodUpdatingStrategy: apis.PodUpdatingStrategyInplace, 455 PodMatchingStrategy: apis.PodMatchingStrategyAll, 456 }, 457 }, 458 Status: apis.KatalystVerticalPodAutoscalerStatus{ 459 ContainerResources: []apis.ContainerResources{ 460 { 461 ContainerName: pointer.String("c1"), 462 Requests: &apis.ContainerResourceList{ 463 Target: map[v1.ResourceName]resource.Quantity{ 464 v1.ResourceCPU: resource.MustParse("1"), 465 v1.ResourceMemory: resource.MustParse("1Gi"), 466 }, 467 UncappedTarget: map[v1.ResourceName]resource.Quantity{ 468 v1.ResourceCPU: resource.MustParse("1"), 469 v1.ResourceMemory: resource.MustParse("1Gi"), 470 }, 471 }, 472 }, 473 }, 474 Conditions: []apis.VerticalPodAutoscalerCondition{ 475 { 476 Type: apis.RecommendationUpdated, 477 Status: v1.ConditionTrue, 478 Reason: util.VPAConditionReasonUpdated, 479 }, 480 }, 481 }, 482 }, 483 object: &appsv1.StatefulSet{ 484 TypeMeta: metav1.TypeMeta{ 485 Kind: "StatefulSet", 486 APIVersion: "apps/v1", 487 }, 488 ObjectMeta: metav1.ObjectMeta{ 489 Name: "sts1", 490 Namespace: "default", 491 Annotations: map[string]string{ 492 apiconsts.WorkloadAnnotationVPAEnabledKey: apiconsts.WorkloadAnnotationVPAEnabled, 493 }, 494 }, 495 Spec: appsv1.StatefulSetSpec{ 496 Selector: &metav1.LabelSelector{ 497 MatchLabels: map[string]string{ 498 "workload": "sts1", 499 }, 500 }, 501 }, 502 }, 503 pod: pod1, 504 newPod: newPod1, 505 }, 506 } { 507 tc := tc 508 t.Run(tc.name, func(t *testing.T) { 509 t.Parallel() 510 511 fss := &cliflag.NamedFlagSets{} 512 vpaOptions := options.NewVPAOptions() 513 vpaOptions.AddFlags(fss) 514 vpaConf := controller.NewVPAConfig() 515 _ = vpaOptions.ApplyTo(vpaConf) 516 517 workloadGVResources := []string{"statefulsets.v1.apps"} 518 vpaConf.VPAWorkloadGVResources = workloadGVResources 519 520 genericConf := &generic.GenericConfiguration{} 521 generalConf := &controller.GenericControllerConfiguration{ 522 DynamicGVResources: workloadGVResources, 523 } 524 525 controlCtx, err := katalystbase.GenerateFakeGenericContext(nil, nil, []runtime.Object{tc.object}) 526 assert.NoError(t, err) 527 528 cxt, cancel := context.WithCancel(context.TODO()) 529 defer cancel() 530 vpaController, err := NewVPAController(cxt, controlCtx, genericConf, generalConf, vpaConf) 531 assert.NoError(t, err) 532 533 controlCtx.StartInformer(cxt) 534 go vpaController.Run() 535 536 synced := cache.WaitForCacheSync(vpaController.ctx.Done(), vpaController.syncedFunc...) 537 assert.True(t, synced) 538 539 _, err = controlCtx.Client.InternalClient.AutoscalingV1alpha1(). 540 KatalystVerticalPodAutoscalers(tc.vpa.Namespace). 541 Create(context.TODO(), tc.vpa, metav1.CreateOptions{}) 542 assert.NoError(t, err) 543 544 _, err = controlCtx.Client.KubeClient.CoreV1().Pods(tc.pod.Namespace).Create(context.TODO(), tc.pod, metav1.CreateOptions{}) 545 assert.NoError(t, err) 546 547 go vpaController.Run() 548 549 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(tc.vpa) 550 assert.NoError(t, err) 551 vpaController.vpaSyncQueue.Add(key) 552 553 err = wait.PollImmediate(time.Millisecond*200, time.Second*5, func() (bool, error) { 554 p, _ := controlCtx.Client.KubeClient.CoreV1().Pods(tc.newPod.Namespace).Get(context.TODO(), tc.newPod.Name, metav1.GetOptions{}) 555 eq := reflect.DeepEqual(tc.newPod, p) 556 return eq, nil 557 }) 558 assert.NoError(t, err) 559 560 p, err := controlCtx.Client.KubeClient.CoreV1().Pods(tc.newPod.Namespace).Get(context.TODO(), tc.newPod.Name, metav1.GetOptions{}) 561 assert.NoError(t, err) 562 assert.Equal(t, tc.newPod, p) 563 }) 564 } 565 } 566 567 func TestVPAControllerSyncWorkload(t *testing.T) { 568 t.Parallel() 569 570 pod1 := makePod("pod1", 571 map[string]string{}, 572 map[string]string{"workload": "sts1"}, 573 []metav1.OwnerReference{ 574 { 575 Kind: "StatefulSet", 576 APIVersion: "apps/v1", 577 Name: "sts1", 578 }, 579 }, 580 ) 581 pod1.Spec.Containers = []v1.Container{ 582 { 583 Name: "c1", 584 Resources: v1.ResourceRequirements{ 585 Limits: map[v1.ResourceName]resource.Quantity{ 586 v1.ResourceCPU: resource.MustParse("4"), 587 v1.ResourceMemory: resource.MustParse("4Gi"), 588 }, 589 Requests: map[v1.ResourceName]resource.Quantity{ 590 v1.ResourceCPU: resource.MustParse("2"), 591 v1.ResourceMemory: resource.MustParse("2Gi"), 592 }, 593 }, 594 }, 595 } 596 pod1.Status.QOSClass = v1.PodQOSBurstable 597 598 newPod1 := pod1.DeepCopy() 599 newPod1.Annotations[apiconsts.PodAnnotationInplaceUpdateResourcesKey] = "{\"c1\":{\"requests\":{\"cpu\":\"1\",\"memory\":\"1Gi\"}}}" 600 for _, tc := range []struct { 601 name string 602 object runtime.Object 603 pod *v1.Pod 604 newPod *v1.Pod 605 vpa *apis.KatalystVerticalPodAutoscaler 606 }{ 607 { 608 name: "test1", 609 vpa: &apis.KatalystVerticalPodAutoscaler{ 610 ObjectMeta: metav1.ObjectMeta{ 611 Name: "vpa1", 612 Namespace: "default", 613 UID: "vpauid1", 614 }, 615 Spec: apis.KatalystVerticalPodAutoscalerSpec{ 616 TargetRef: apis.CrossVersionObjectReference{ 617 Kind: "StatefulSet", 618 APIVersion: "apps/v1", 619 Name: "sts1", 620 }, 621 UpdatePolicy: apis.PodUpdatePolicy{ 622 PodUpdatingStrategy: apis.PodUpdatingStrategyInplace, 623 PodMatchingStrategy: apis.PodMatchingStrategyAll, 624 }, 625 }, 626 Status: apis.KatalystVerticalPodAutoscalerStatus{ 627 ContainerResources: []apis.ContainerResources{ 628 { 629 ContainerName: pointer.String("c1"), 630 Requests: &apis.ContainerResourceList{ 631 Target: map[v1.ResourceName]resource.Quantity{ 632 v1.ResourceCPU: resource.MustParse("1"), 633 v1.ResourceMemory: resource.MustParse("1Gi"), 634 }, 635 UncappedTarget: map[v1.ResourceName]resource.Quantity{ 636 v1.ResourceCPU: resource.MustParse("1"), 637 v1.ResourceMemory: resource.MustParse("1Gi"), 638 }, 639 }, 640 }, 641 }, 642 Conditions: []apis.VerticalPodAutoscalerCondition{ 643 { 644 Type: apis.RecommendationUpdated, 645 Status: v1.ConditionTrue, 646 Reason: util.VPAConditionReasonUpdated, 647 }, 648 }, 649 }, 650 }, 651 object: &appsv1.StatefulSet{ 652 TypeMeta: metav1.TypeMeta{ 653 Kind: "StatefulSet", 654 APIVersion: "apps/v1", 655 }, 656 ObjectMeta: metav1.ObjectMeta{ 657 Name: "sts1", 658 Namespace: "default", 659 Annotations: map[string]string{apiconsts.WorkloadAnnotationVPAEnabledKey: apiconsts.WorkloadAnnotationVPAEnabled}, 660 }, 661 Spec: appsv1.StatefulSetSpec{ 662 Selector: &metav1.LabelSelector{ 663 MatchLabels: map[string]string{ 664 "workload": "sts1", 665 }, 666 }, 667 }, 668 }, 669 pod: pod1, 670 newPod: newPod1, 671 }, 672 } { 673 tc := tc 674 t.Run(tc.name, func(t *testing.T) { 675 t.Parallel() 676 677 fss := &cliflag.NamedFlagSets{} 678 vpaOptions := options.NewVPAOptions() 679 vpaOptions.AddFlags(fss) 680 vpaConf := controller.NewVPAConfig() 681 _ = vpaOptions.ApplyTo(vpaConf) 682 683 workloadGVResources := []string{"statefulsets.v1.apps"} 684 vpaConf.VPAWorkloadGVResources = workloadGVResources 685 686 genericConf := &generic.GenericConfiguration{} 687 controllerConf := &controller.GenericControllerConfiguration{ 688 DynamicGVResources: workloadGVResources, 689 } 690 691 controlCtx, err := katalystbase.GenerateFakeGenericContext(nil, nil, []runtime.Object{tc.object}) 692 assert.NoError(t, err) 693 694 cxt, cancel := context.WithCancel(context.TODO()) 695 defer cancel() 696 vpaController, err := NewVPAController(cxt, controlCtx, genericConf, controllerConf, vpaConf) 697 assert.NoError(t, err) 698 699 controlCtx.StartInformer(cxt) 700 go vpaController.Run() 701 702 synced := cache.WaitForCacheSync(vpaController.ctx.Done(), vpaController.syncedFunc...) 703 assert.True(t, synced) 704 705 _, err = controlCtx.Client.InternalClient.AutoscalingV1alpha1(). 706 KatalystVerticalPodAutoscalers(tc.vpa.Namespace). 707 Create(context.TODO(), tc.vpa, metav1.CreateOptions{}) 708 assert.NoError(t, err) 709 710 _, err = controlCtx.Client.KubeClient.CoreV1().Pods(tc.pod.Namespace).Create(context.TODO(), tc.pod, metav1.CreateOptions{}) 711 assert.NoError(t, err) 712 713 err = wait.PollImmediate(time.Millisecond*20, time.Second*5, func() (bool, error) { 714 p, _ := controlCtx.Client.KubeClient.CoreV1().Pods(tc.newPod.Namespace).Get(context.TODO(), tc.newPod.Name, metav1.GetOptions{}) 715 eq := reflect.DeepEqual(tc.newPod, p) 716 return eq, nil 717 }) 718 assert.NoError(t, err) 719 720 p, err := controlCtx.Client.KubeClient.CoreV1().Pods(tc.newPod.Namespace).Get(context.TODO(), tc.newPod.Name, metav1.GetOptions{}) 721 assert.NoError(t, err) 722 assert.Equal(t, tc.newPod, p) 723 }) 724 } 725 } 726 727 func TestPodIndexerDuplicate(t *testing.T) { 728 t.Parallel() 729 730 vpaConf := controller.NewVPAConfig() 731 genericConf := &generic.GenericConfiguration{} 732 controllerConf := &controller.GenericControllerConfiguration{} 733 controlCtx, err := katalystbase.GenerateFakeGenericContext(nil, nil, nil) 734 assert.NoError(t, err) 735 736 vpaConf.VPAPodLabelIndexerKeys = []string{"test-1"} 737 738 _, err = NewVPAController(context.TODO(), controlCtx, genericConf, controllerConf, vpaConf) 739 assert.NoError(t, err) 740 741 _, err = NewVPAController(context.TODO(), controlCtx, genericConf, controllerConf, vpaConf) 742 assert.NoError(t, err) 743 744 _, err = NewResourceRecommendController(context.TODO(), controlCtx, genericConf, controllerConf, vpaConf) 745 assert.NoError(t, err) 746 747 indexers := controlCtx.KubeInformerFactory.Core().V1().Pods().Informer().GetIndexer().GetIndexers() 748 assert.Equal(t, 2, len(indexers)) 749 _, exist := indexers["test-1"] 750 assert.Equal(t, true, exist) 751 752 vpaConf.VPAPodLabelIndexerKeys = []string{"test-2"} 753 754 _, err = NewResourceRecommendController(context.TODO(), controlCtx, genericConf, controllerConf, vpaConf) 755 assert.NoError(t, err) 756 757 indexers = controlCtx.KubeInformerFactory.Core().V1().Pods().Informer().GetIndexer().GetIndexers() 758 assert.Equal(t, 3, len(indexers)) 759 } 760 761 func TestSyncPerformance(t *testing.T) { 762 t.Parallel() 763 764 var kubeObj, internalObj, dynamicObj []runtime.Object 765 766 internalObj = append(internalObj, &apis.KatalystVerticalPodAutoscaler{ 767 ObjectMeta: metav1.ObjectMeta{ 768 Name: "vpa1", 769 Namespace: "default", 770 UID: "uid-fake-vpa", 771 }, 772 Spec: apis.KatalystVerticalPodAutoscalerSpec{ 773 TargetRef: apis.CrossVersionObjectReference{ 774 Kind: "StatefulSet", 775 APIVersion: "apps/v1", 776 Name: "sts1", 777 }, 778 UpdatePolicy: apis.PodUpdatePolicy{ 779 PodUpdatingStrategy: apis.PodUpdatingStrategyInplace, 780 PodMatchingStrategy: apis.PodMatchingStrategyAll, 781 }, 782 }, 783 }) 784 785 dynamicObj = append(dynamicObj, &appsv1.StatefulSet{ 786 TypeMeta: metav1.TypeMeta{ 787 Kind: "StatefulSet", 788 APIVersion: "apps/v1", 789 }, 790 ObjectMeta: metav1.ObjectMeta{ 791 Name: "sts1", 792 Namespace: "default", 793 Annotations: map[string]string{ 794 apiconsts.WorkloadAnnotationVPAEnabledKey: apiconsts.WorkloadAnnotationVPAEnabled, 795 }, 796 }, 797 Spec: appsv1.StatefulSetSpec{ 798 Selector: &metav1.LabelSelector{ 799 MatchLabels: map[string]string{ 800 "workload": "sts1", 801 "test": "pod-1", 802 "test-mod-10": "1", 803 "test-mod-100": "1", 804 }, 805 }, 806 }, 807 }) 808 809 amount := 1 810 for i := 1; i <= amount; i++ { 811 name := fmt.Sprintf("pod-%v", i) 812 kubeObj = append(kubeObj, makePod(name, 813 map[string]string{}, 814 map[string]string{ 815 "test": name, 816 "test-mod-10": fmt.Sprintf("%v", i%10), 817 "test-mod-100": fmt.Sprintf("%v", i%100), 818 "workload": "sts1", 819 }, 820 []metav1.OwnerReference{ 821 { 822 Kind: "StatefulSet", 823 APIVersion: "apps/v1", 824 Name: "sts1", 825 }, 826 }, 827 )) 828 } 829 830 for _, tc := range []struct { 831 name string 832 vpaConf *controller.VPAConfig 833 }{ 834 { 835 name: "sync without indexer", 836 vpaConf: &controller.VPAConfig{ 837 VPAPodLabelIndexerKeys: []string{"test"}, 838 }, 839 }, 840 { 841 name: "sync with the same indexer", 842 vpaConf: &controller.VPAConfig{ 843 VPAPodLabelIndexerKeys: []string{"workload"}, 844 }, 845 }, 846 { 847 name: "sync with individual indexer", 848 vpaConf: &controller.VPAConfig{ 849 VPAPodLabelIndexerKeys: []string{"test"}, 850 }, 851 }, 852 } { 853 t.Logf("test cases: %v", tc.name) 854 855 workloadGVResources := []string{"statefulsets.v1.apps"} 856 tc.vpaConf.VPAWorkloadGVResources = workloadGVResources 857 858 genericConf := &generic.GenericConfiguration{} 859 controllerConf := &controller.GenericControllerConfiguration{ 860 DynamicGVResources: workloadGVResources, 861 } 862 863 controlCtx, err := katalystbase.GenerateFakeGenericContext(kubeObj, internalObj, dynamicObj) 864 assert.NoError(t, err) 865 866 vc, err := NewVPAController(context.TODO(), controlCtx, genericConf, controllerConf, tc.vpaConf) 867 assert.NoError(t, err) 868 869 for _, obj := range kubeObj { 870 err = controlCtx.KubeInformerFactory.Core().V1().Pods().Informer().GetStore().Add(obj) 871 assert.NoError(t, err) 872 } 873 for _, obj := range internalObj { 874 err = controlCtx.InternalInformerFactory.Autoscaling().V1alpha1().KatalystVerticalPodAutoscalers().Informer().GetStore().Add(obj) 875 assert.NoError(t, err) 876 } 877 878 controlCtx.StartInformer(vc.ctx) 879 synced := cache.WaitForCacheSync(vc.ctx.Done(), vc.syncedFunc...) 880 assert.True(t, synced) 881 882 err = vc.syncVPA("default" + "/" + "vpa1") 883 assert.NoError(t, err) 884 } 885 }