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  }