github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/vpa/vparec_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 "testing" 22 23 "github.com/alecthomas/units" 24 "github.com/stretchr/testify/assert" 25 appsv1 "k8s.io/api/apps/v1" 26 v1 "k8s.io/api/core/v1" 27 "k8s.io/apimachinery/pkg/api/resource" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/client-go/tools/cache" 31 cliflag "k8s.io/component-base/cli/flag" 32 "k8s.io/utils/pointer" 33 34 apis "github.com/kubewharf/katalyst-api/pkg/apis/autoscaling/v1alpha1" 35 apiconsts "github.com/kubewharf/katalyst-api/pkg/consts" 36 katalystbase "github.com/kubewharf/katalyst-core/cmd/base" 37 "github.com/kubewharf/katalyst-core/cmd/katalyst-controller/app/options" 38 "github.com/kubewharf/katalyst-core/pkg/config/controller" 39 "github.com/kubewharf/katalyst-core/pkg/config/generic" 40 "github.com/kubewharf/katalyst-core/pkg/controller/vpa/util" 41 "github.com/kubewharf/katalyst-core/pkg/util/native" 42 ) 43 44 func TestVPARecControllerSyncVPA(t *testing.T) { 45 t.Parallel() 46 47 pod1 := makePod("pod1", 48 map[string]string{apiconsts.WorkloadAnnotationVPAEnabledKey: apiconsts.WorkloadAnnotationVPAEnabled}, 49 map[string]string{"workload": "sts1"}, 50 []metav1.OwnerReference{ 51 { 52 Kind: "StatefulSet", 53 APIVersion: "apps/v1", 54 Name: "sts1", 55 }, 56 }, 57 ) 58 pod1.Spec.Containers = []v1.Container{ 59 { 60 Name: "c1", 61 Resources: v1.ResourceRequirements{ 62 Limits: map[v1.ResourceName]resource.Quantity{ 63 v1.ResourceCPU: *resource.NewQuantity(4, resource.DecimalSI), 64 v1.ResourceMemory: *resource.NewQuantity(4*int64(units.GiB), resource.BinarySI), 65 }, 66 Requests: map[v1.ResourceName]resource.Quantity{ 67 v1.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI), 68 v1.ResourceMemory: *resource.NewQuantity(2*int64(units.GiB), resource.BinarySI), 69 }, 70 }, 71 }, 72 } 73 pod1.Status.QOSClass = v1.PodQOSBurstable 74 for _, tc := range []struct { 75 name string 76 vpaOld *apis.KatalystVerticalPodAutoscaler 77 vparecs []*apis.VerticalPodAutoscalerRecommendation 78 object runtime.Object 79 pods []*v1.Pod 80 vpaNew *apis.KatalystVerticalPodAutoscaler 81 }{ 82 { 83 name: "set vpa without resource policy", 84 vpaOld: &apis.KatalystVerticalPodAutoscaler{ 85 ObjectMeta: metav1.ObjectMeta{ 86 Name: "vpa1", 87 Namespace: "default", 88 UID: "vpauid1", 89 }, 90 Spec: apis.KatalystVerticalPodAutoscalerSpec{ 91 TargetRef: apis.CrossVersionObjectReference{ 92 Kind: "StatefulSet", 93 APIVersion: "apps/v1", 94 Name: "sts1", 95 }, 96 }, 97 }, 98 vpaNew: &apis.KatalystVerticalPodAutoscaler{ 99 ObjectMeta: metav1.ObjectMeta{ 100 Name: "vpa1", 101 Namespace: "default", 102 UID: "vpauid1", 103 }, 104 Spec: apis.KatalystVerticalPodAutoscalerSpec{ 105 TargetRef: apis.CrossVersionObjectReference{ 106 Kind: "StatefulSet", 107 APIVersion: "apps/v1", 108 Name: "sts1", 109 }, 110 }, 111 Status: apis.KatalystVerticalPodAutoscalerStatus{ 112 ContainerResources: []apis.ContainerResources{ 113 { 114 ContainerName: pointer.String("c1"), 115 Requests: &apis.ContainerResourceList{ 116 Target: map[v1.ResourceName]resource.Quantity{ 117 v1.ResourceCPU: resource.MustParse("1"), 118 v1.ResourceMemory: resource.MustParse("1Gi"), 119 }, 120 UncappedTarget: map[v1.ResourceName]resource.Quantity{ 121 v1.ResourceCPU: resource.MustParse("1"), 122 v1.ResourceMemory: resource.MustParse("1Gi"), 123 }, 124 }, 125 }, 126 }, 127 }, 128 }, 129 vparecs: []*apis.VerticalPodAutoscalerRecommendation{ 130 { 131 ObjectMeta: metav1.ObjectMeta{ 132 Name: "vparec1", 133 Namespace: "default", 134 UID: "vparecuid1", 135 OwnerReferences: []metav1.OwnerReference{ 136 { 137 Name: "vpa1", 138 UID: "vpauid1", 139 }, 140 }, 141 }, 142 Spec: apis.VerticalPodAutoscalerRecommendationSpec{ 143 ContainerRecommendations: []apis.RecommendedContainerResources{ 144 { 145 ContainerName: pointer.String("c1"), 146 Requests: &apis.RecommendedRequestResources{ 147 Resources: map[v1.ResourceName]resource.Quantity{ 148 v1.ResourceCPU: *resource.NewQuantity(1, resource.DecimalSI), 149 v1.ResourceMemory: *resource.NewQuantity(1*int64(units.GiB), resource.BinarySI), 150 }, 151 }, 152 }, 153 }, 154 }, 155 }, 156 }, 157 object: &appsv1.StatefulSet{ 158 TypeMeta: metav1.TypeMeta{ 159 Kind: "StatefulSet", 160 APIVersion: "apps/v1", 161 }, 162 ObjectMeta: metav1.ObjectMeta{ 163 Name: "sts1", 164 Namespace: "default", 165 }, 166 Spec: appsv1.StatefulSetSpec{ 167 Selector: &metav1.LabelSelector{ 168 MatchLabels: map[string]string{ 169 "workload": "sts1", 170 }, 171 }, 172 }, 173 }, 174 pods: []*v1.Pod{ 175 pod1, 176 }, 177 }, 178 { 179 name: "set vpa with resource policy", 180 vpaOld: &apis.KatalystVerticalPodAutoscaler{ 181 ObjectMeta: metav1.ObjectMeta{ 182 Name: "vpa1", 183 Namespace: "default", 184 UID: "vpauid1", 185 }, 186 Spec: apis.KatalystVerticalPodAutoscalerSpec{ 187 TargetRef: apis.CrossVersionObjectReference{ 188 Kind: "StatefulSet", 189 APIVersion: "apps/v1", 190 Name: "sts1", 191 }, 192 ResourcePolicy: apis.PodResourcePolicy{ 193 ContainerPolicies: []apis.ContainerResourcePolicy{ 194 { 195 ContainerName: pointer.String("c1"), 196 MinAllowed: v1.ResourceList{ 197 v1.ResourceCPU: resource.MustParse("2"), 198 v1.ResourceMemory: resource.MustParse("2Gi"), 199 }, 200 ControlledResources: []v1.ResourceName{v1.ResourceCPU, v1.ResourceMemory}, 201 ControlledValues: apis.ContainerControlledValuesRequestsAndLimits, 202 }, 203 }, 204 }, 205 }, 206 }, 207 vpaNew: &apis.KatalystVerticalPodAutoscaler{ 208 ObjectMeta: metav1.ObjectMeta{ 209 Name: "vpa1", 210 Namespace: "default", 211 UID: "vpauid1", 212 }, 213 Spec: apis.KatalystVerticalPodAutoscalerSpec{ 214 TargetRef: apis.CrossVersionObjectReference{ 215 Kind: "StatefulSet", 216 APIVersion: "apps/v1", 217 Name: "sts1", 218 }, 219 ResourcePolicy: apis.PodResourcePolicy{ 220 ContainerPolicies: []apis.ContainerResourcePolicy{ 221 { 222 ContainerName: pointer.String("c1"), 223 MinAllowed: v1.ResourceList{ 224 v1.ResourceCPU: resource.MustParse("2"), 225 v1.ResourceMemory: resource.MustParse("2Gi"), 226 }, 227 ControlledResources: []v1.ResourceName{v1.ResourceCPU, v1.ResourceMemory}, 228 ControlledValues: apis.ContainerControlledValuesRequestsAndLimits, 229 }, 230 }, 231 }, 232 }, 233 Status: apis.KatalystVerticalPodAutoscalerStatus{ 234 ContainerResources: []apis.ContainerResources{ 235 { 236 ContainerName: pointer.String("c1"), 237 Requests: &apis.ContainerResourceList{ 238 Target: map[v1.ResourceName]resource.Quantity{ 239 v1.ResourceCPU: resource.MustParse("2"), 240 v1.ResourceMemory: resource.MustParse("2Gi"), 241 }, 242 UncappedTarget: map[v1.ResourceName]resource.Quantity{ 243 v1.ResourceCPU: resource.MustParse("1"), 244 v1.ResourceMemory: resource.MustParse("1Gi"), 245 }, 246 LowerBound: map[v1.ResourceName]resource.Quantity{ 247 v1.ResourceCPU: resource.MustParse("2"), 248 v1.ResourceMemory: resource.MustParse("2Gi"), 249 }, 250 }, 251 }, 252 }, 253 }, 254 }, 255 vparecs: []*apis.VerticalPodAutoscalerRecommendation{ 256 { 257 ObjectMeta: metav1.ObjectMeta{ 258 Name: "vparec1", 259 Namespace: "default", 260 UID: "vparecuid1", 261 OwnerReferences: []metav1.OwnerReference{ 262 { 263 Name: "vpa1", 264 UID: "vpauid1", 265 }, 266 }, 267 }, 268 Spec: apis.VerticalPodAutoscalerRecommendationSpec{ 269 ContainerRecommendations: []apis.RecommendedContainerResources{ 270 { 271 ContainerName: pointer.String("c1"), 272 Requests: &apis.RecommendedRequestResources{ 273 Resources: map[v1.ResourceName]resource.Quantity{ 274 v1.ResourceCPU: *resource.NewQuantity(1, resource.DecimalSI), 275 v1.ResourceMemory: *resource.NewQuantity(1*int64(units.GiB), resource.BinarySI), 276 }, 277 }, 278 }, 279 }, 280 }, 281 }, 282 }, 283 object: &appsv1.StatefulSet{ 284 TypeMeta: metav1.TypeMeta{ 285 Kind: "StatefulSet", 286 APIVersion: "apps/v1", 287 }, 288 ObjectMeta: metav1.ObjectMeta{ 289 Name: "sts1", 290 Namespace: "default", 291 }, 292 Spec: appsv1.StatefulSetSpec{ 293 Selector: &metav1.LabelSelector{ 294 MatchLabels: map[string]string{ 295 "workload": "sts1", 296 }, 297 }, 298 }, 299 }, 300 pods: []*v1.Pod{ 301 pod1, 302 }, 303 }, 304 } { 305 tc := tc 306 t.Run(tc.name, func(t *testing.T) { 307 t.Parallel() 308 309 genericConfig := &generic.GenericConfiguration{} 310 controllerConf := &controller.GenericControllerConfiguration{ 311 DynamicGVResources: []string{"statefulsets.v1.apps"}, 312 } 313 fss := &cliflag.NamedFlagSets{} 314 vpaOptions := options.NewVPAOptions() 315 vpaOptions.AddFlags(fss) 316 vpaConf := controller.NewVPAConfig() 317 vpaOptions.ApplyTo(vpaConf) 318 controlCtx, err := katalystbase.GenerateFakeGenericContext(nil, []runtime.Object{tc.vpaOld}, []runtime.Object{tc.object}) 319 assert.NoError(t, err) 320 321 vparec, err := NewVPARecommendationController(context.TODO(), controlCtx, genericConfig, controllerConf, vpaConf) 322 assert.NoError(t, err) 323 324 workloadInformers := controlCtx.DynamicResourcesManager.GetDynamicInformers() 325 u, err := native.ToUnstructured(tc.object) 326 assert.NoError(t, err) 327 err = workloadInformers["statefulsets.v1.apps"].Informer.Informer().GetStore().Add(u) 328 assert.NoError(t, err) 329 330 vpaInformer := controlCtx.InternalInformerFactory.Autoscaling().V1alpha1().KatalystVerticalPodAutoscalers() 331 err = vpaInformer.Informer().GetStore().Add(tc.vpaOld) 332 assert.NoError(t, err) 333 334 vparecInformer := controlCtx.InternalInformerFactory.Autoscaling().V1alpha1().VerticalPodAutoscalerRecommendations() 335 for _, rec := range tc.vparecs { 336 err = vparecInformer.Informer().GetStore().Add(rec) 337 assert.NoError(t, err) 338 } 339 for _, pod := range tc.pods { 340 err = controlCtx.KubeInformerFactory.Core().V1().Pods().Informer().GetStore().Add(pod) 341 assert.NoError(t, err) 342 } 343 344 for _, vpaRec := range tc.vparecs { 345 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(vpaRec) 346 assert.NoError(t, err) 347 348 err = vparec.syncVPARec(key) 349 assert.NoError(t, err) 350 } 351 352 vpa, err := controlCtx.Client.InternalClient.AutoscalingV1alpha1(). 353 KatalystVerticalPodAutoscalers(tc.vpaNew.Namespace).Get(context.TODO(), tc.vpaNew.Name, metav1.GetOptions{}) 354 assert.NoError(t, err) 355 assert.Equal(t, tc.vpaNew, vpa) 356 }) 357 } 358 } 359 360 func TestVPARecControllerSyncVPARec(t *testing.T) { 361 t.Parallel() 362 363 for _, tc := range []struct { 364 name string 365 vparecOld *apis.VerticalPodAutoscalerRecommendation 366 vpa *apis.KatalystVerticalPodAutoscaler 367 vparecNew *apis.VerticalPodAutoscalerRecommendation 368 }{ 369 { 370 name: "set vpa with resource policy", 371 vpa: &apis.KatalystVerticalPodAutoscaler{ 372 ObjectMeta: metav1.ObjectMeta{ 373 Name: "vpa1", 374 Namespace: "default", 375 UID: "vpauid1", 376 }, 377 Spec: apis.KatalystVerticalPodAutoscalerSpec{ 378 TargetRef: apis.CrossVersionObjectReference{ 379 Kind: "StatefulSet", 380 APIVersion: "apps/v1", 381 Name: "sts1", 382 }, 383 ResourcePolicy: apis.PodResourcePolicy{ 384 ContainerPolicies: []apis.ContainerResourcePolicy{ 385 { 386 ContainerName: pointer.String("c1"), 387 MinAllowed: v1.ResourceList{ 388 v1.ResourceCPU: resource.MustParse("2"), 389 v1.ResourceMemory: resource.MustParse("2Gi"), 390 }, 391 ControlledResources: []v1.ResourceName{v1.ResourceCPU, v1.ResourceMemory}, 392 ControlledValues: apis.ContainerControlledValuesRequestsAndLimits, 393 }, 394 }, 395 }, 396 }, 397 Status: apis.KatalystVerticalPodAutoscalerStatus{ 398 ContainerResources: []apis.ContainerResources{ 399 { 400 ContainerName: pointer.String("c1"), 401 Requests: &apis.ContainerResourceList{ 402 Target: map[v1.ResourceName]resource.Quantity{ 403 v1.ResourceCPU: resource.MustParse("2"), 404 v1.ResourceMemory: resource.MustParse("2Gi"), 405 }, 406 UncappedTarget: map[v1.ResourceName]resource.Quantity{ 407 v1.ResourceCPU: resource.MustParse("1"), 408 v1.ResourceMemory: resource.MustParse("1Gi"), 409 }, 410 }, 411 }, 412 }, 413 Conditions: []apis.VerticalPodAutoscalerCondition{ 414 { 415 Type: apis.RecommendationUpdated, 416 Status: v1.ConditionTrue, 417 Reason: util.VPAConditionReasonUpdated, 418 }, 419 }, 420 }, 421 }, 422 vparecOld: &apis.VerticalPodAutoscalerRecommendation{ 423 ObjectMeta: metav1.ObjectMeta{ 424 Name: "vparec1", 425 Namespace: "default", 426 UID: "vparecuid1", 427 OwnerReferences: []metav1.OwnerReference{ 428 { 429 Name: "vpa1", 430 UID: "vpauid1", 431 }, 432 }, 433 }, 434 Spec: apis.VerticalPodAutoscalerRecommendationSpec{ 435 ContainerRecommendations: []apis.RecommendedContainerResources{ 436 { 437 ContainerName: pointer.String("c1"), 438 Requests: &apis.RecommendedRequestResources{ 439 Resources: map[v1.ResourceName]resource.Quantity{ 440 v1.ResourceCPU: *resource.NewQuantity(1, resource.DecimalSI), 441 v1.ResourceMemory: *resource.NewQuantity(1*int64(units.GiB), resource.BinarySI), 442 }, 443 }, 444 }, 445 }, 446 }, 447 }, 448 vparecNew: &apis.VerticalPodAutoscalerRecommendation{ 449 ObjectMeta: metav1.ObjectMeta{ 450 Name: "vparec1", 451 Namespace: "default", 452 UID: "vparecuid1", 453 OwnerReferences: []metav1.OwnerReference{ 454 { 455 Name: "vpa1", 456 UID: "vpauid1", 457 }, 458 }, 459 }, 460 Spec: apis.VerticalPodAutoscalerRecommendationSpec{ 461 ContainerRecommendations: []apis.RecommendedContainerResources{ 462 { 463 ContainerName: pointer.String("c1"), 464 Requests: &apis.RecommendedRequestResources{ 465 Resources: map[v1.ResourceName]resource.Quantity{ 466 v1.ResourceCPU: resource.MustParse("1"), 467 v1.ResourceMemory: resource.MustParse("1Gi"), 468 }, 469 }, 470 }, 471 }, 472 }, 473 Status: apis.VerticalPodAutoscalerRecommendationStatus{ 474 ContainerRecommendations: []apis.RecommendedContainerResources{ 475 { 476 ContainerName: pointer.String("c1"), 477 Requests: &apis.RecommendedRequestResources{ 478 Resources: v1.ResourceList{ 479 v1.ResourceCPU: resource.MustParse("2"), 480 v1.ResourceMemory: resource.MustParse("2Gi"), 481 }, 482 }, 483 }, 484 }, 485 Conditions: []apis.VerticalPodAutoscalerRecommendationCondition{ 486 { 487 Type: apis.RecommendationUpdatedToVPA, 488 Status: v1.ConditionTrue, 489 Reason: util.VPARecConditionReasonUpdated, 490 Message: "", 491 }, 492 }, 493 }, 494 }, 495 }, 496 } { 497 tc := tc 498 t.Run(tc.name, func(t *testing.T) { 499 t.Parallel() 500 501 genericConfig := &generic.GenericConfiguration{} 502 controllerConf := &controller.GenericControllerConfiguration{ 503 DynamicGVResources: []string{"statefulsets.v1.apps"}, 504 } 505 506 fss := &cliflag.NamedFlagSets{} 507 vpaOptions := options.NewVPAOptions() 508 vpaOptions.AddFlags(fss) 509 vpaConf := controller.NewVPAConfig() 510 vpaOptions.ApplyTo(vpaConf) 511 512 controlCtx, err := katalystbase.GenerateFakeGenericContext(nil, nil, nil) 513 assert.NoError(t, err) 514 515 vparecController, err := NewVPARecommendationController(context.TODO(), controlCtx, genericConfig, controllerConf, vpaConf) 516 assert.NoError(t, err) 517 518 vpaInformer := controlCtx.InternalInformerFactory.Autoscaling().V1alpha1().KatalystVerticalPodAutoscalers() 519 err = vpaInformer.Informer().GetStore().Add(tc.vpa) 520 assert.NoError(t, err) 521 _, err = controlCtx.Client.InternalClient.AutoscalingV1alpha1(). 522 KatalystVerticalPodAutoscalers(tc.vpa.Namespace). 523 Create(context.TODO(), tc.vpa, metav1.CreateOptions{}) 524 assert.NoError(t, err) 525 526 vparecInformer := controlCtx.InternalInformerFactory.Autoscaling().V1alpha1().VerticalPodAutoscalerRecommendations() 527 err = vparecInformer.Informer().GetStore().Add(tc.vparecOld) 528 assert.NoError(t, err) 529 _, err = controlCtx.Client.InternalClient.AutoscalingV1alpha1(). 530 VerticalPodAutoscalerRecommendations(tc.vparecOld.Namespace). 531 Create(context.TODO(), tc.vparecOld, metav1.CreateOptions{}) 532 assert.NoError(t, err) 533 534 controlCtx.StartInformer(vparecController.ctx) 535 synced := cache.WaitForCacheSync(vparecController.ctx.Done(), vparecController.syncedFunc...) 536 assert.True(t, synced) 537 538 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(tc.vpa) 539 assert.NoError(t, err) 540 541 err = vparecController.syncVPA(key) 542 assert.NoError(t, err) 543 544 vparec, err := controlCtx.Client.InternalClient.AutoscalingV1alpha1(). 545 VerticalPodAutoscalerRecommendations(tc.vparecNew.Namespace). 546 Get(context.TODO(), tc.vparecNew.Name, metav1.GetOptions{}) 547 assert.NoError(t, err) 548 vparec.Status.Conditions[0].LastTransitionTime = metav1.Time{} 549 assert.Equal(t, tc.vparecNew, vparec) 550 }) 551 } 552 }