github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/spd/spd_baseline_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 spd
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	appsv1 "k8s.io/api/apps/v1"
    27  	v1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/client-go/tools/cache"
    31  	"k8s.io/utils/pointer"
    32  
    33  	apis "github.com/kubewharf/katalyst-api/pkg/apis/autoscaling/v1alpha1"
    34  	apiworkload "github.com/kubewharf/katalyst-api/pkg/apis/workload/v1alpha1"
    35  	"github.com/kubewharf/katalyst-api/pkg/consts"
    36  	katalystbase "github.com/kubewharf/katalyst-core/cmd/base"
    37  	"github.com/kubewharf/katalyst-core/pkg/config/controller"
    38  	"github.com/kubewharf/katalyst-core/pkg/config/generic"
    39  	"github.com/kubewharf/katalyst-core/pkg/util"
    40  )
    41  
    42  func TestSPDController_updateBaselinePercentile(t *testing.T) {
    43  	t.Parallel()
    44  
    45  	type fields struct {
    46  		podList  []runtime.Object
    47  		workload *appsv1.StatefulSet
    48  		spd      *apiworkload.ServiceProfileDescriptor
    49  	}
    50  	tests := []struct {
    51  		name    string
    52  		fields  fields
    53  		wantSPD *apiworkload.ServiceProfileDescriptor
    54  		wantErr assert.ErrorAssertionFunc
    55  	}{
    56  		{
    57  			name: "one pod",
    58  			fields: fields{
    59  				podList: []runtime.Object{
    60  					&v1.Pod{
    61  						ObjectMeta: metav1.ObjectMeta{
    62  							Name:      "pod1",
    63  							Namespace: "default",
    64  							OwnerReferences: []metav1.OwnerReference{
    65  								{
    66  									APIVersion: "apps/v1",
    67  									Kind:       "StatefulSet",
    68  									Name:       "sts1",
    69  								},
    70  							},
    71  							Annotations: map[string]string{
    72  								consts.PodAnnotationSPDNameKey: "spd1",
    73  							},
    74  							Labels: map[string]string{
    75  								"workload": "sts1",
    76  							},
    77  							CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)),
    78  						},
    79  					},
    80  				},
    81  				workload: &appsv1.StatefulSet{
    82  					TypeMeta: metav1.TypeMeta{
    83  						Kind:       "StatefulSet",
    84  						APIVersion: "apps/v1",
    85  					},
    86  					ObjectMeta: metav1.ObjectMeta{
    87  						Name:        "sts1",
    88  						Namespace:   "default",
    89  						Annotations: map[string]string{},
    90  					},
    91  					Spec: appsv1.StatefulSetSpec{
    92  						Selector: &metav1.LabelSelector{
    93  							MatchLabels: map[string]string{
    94  								"workload": "sts1",
    95  							},
    96  						},
    97  					},
    98  				},
    99  				spd: &apiworkload.ServiceProfileDescriptor{
   100  					ObjectMeta: metav1.ObjectMeta{
   101  						Namespace: "default",
   102  						Name:      "spd1",
   103  					},
   104  					Spec: apiworkload.ServiceProfileDescriptorSpec{
   105  						TargetRef: apis.CrossVersionObjectReference{
   106  							Kind:       stsGVK.Kind,
   107  							Name:       "sts1",
   108  							APIVersion: stsGVK.GroupVersion().String(),
   109  						},
   110  						BaselinePercent: pointer.Int32(50),
   111  					},
   112  					Status: apiworkload.ServiceProfileDescriptorStatus{},
   113  				},
   114  			},
   115  			wantSPD: &apiworkload.ServiceProfileDescriptor{
   116  				ObjectMeta: metav1.ObjectMeta{
   117  					Namespace: "default",
   118  					Name:      "spd1",
   119  					Annotations: map[string]string{
   120  						consts.SPDAnnotationBaselineSentinelKey: util.SPDBaselinePodMeta{
   121  							TimeStamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)),
   122  							PodName:   "pod1",
   123  						}.String(),
   124  					},
   125  				},
   126  				Spec: apiworkload.ServiceProfileDescriptorSpec{
   127  					TargetRef: apis.CrossVersionObjectReference{
   128  						Kind:       stsGVK.Kind,
   129  						Name:       "sts1",
   130  						APIVersion: stsGVK.GroupVersion().String(),
   131  					},
   132  					BaselinePercent: pointer.Int32(50),
   133  				},
   134  				Status: apiworkload.ServiceProfileDescriptorStatus{},
   135  			},
   136  			wantErr: assert.NoError,
   137  		},
   138  		{
   139  			name: "none pod",
   140  			fields: fields{
   141  				podList: []runtime.Object{},
   142  				workload: &appsv1.StatefulSet{
   143  					TypeMeta: metav1.TypeMeta{
   144  						Kind:       "StatefulSet",
   145  						APIVersion: "apps/v1",
   146  					},
   147  					ObjectMeta: metav1.ObjectMeta{
   148  						Name:        "sts1",
   149  						Namespace:   "default",
   150  						Annotations: map[string]string{},
   151  					},
   152  					Spec: appsv1.StatefulSetSpec{
   153  						Selector: &metav1.LabelSelector{
   154  							MatchLabels: map[string]string{
   155  								"workload": "sts1",
   156  							},
   157  						},
   158  					},
   159  				},
   160  				spd: &apiworkload.ServiceProfileDescriptor{
   161  					ObjectMeta: metav1.ObjectMeta{
   162  						Namespace: "default",
   163  						Name:      "spd1",
   164  					},
   165  					Spec: apiworkload.ServiceProfileDescriptorSpec{
   166  						TargetRef: apis.CrossVersionObjectReference{
   167  							Kind:       stsGVK.Kind,
   168  							Name:       "sts1",
   169  							APIVersion: stsGVK.GroupVersion().String(),
   170  						},
   171  						BaselinePercent: pointer.Int32(50),
   172  					},
   173  					Status: apiworkload.ServiceProfileDescriptorStatus{},
   174  				},
   175  			},
   176  			wantSPD: &apiworkload.ServiceProfileDescriptor{
   177  				ObjectMeta: metav1.ObjectMeta{
   178  					Namespace: "default",
   179  					Name:      "spd1",
   180  				},
   181  				Spec: apiworkload.ServiceProfileDescriptorSpec{
   182  					TargetRef: apis.CrossVersionObjectReference{
   183  						Kind:       stsGVK.Kind,
   184  						Name:       "sts1",
   185  						APIVersion: stsGVK.GroupVersion().String(),
   186  					},
   187  					BaselinePercent: pointer.Int32(50),
   188  				},
   189  				Status: apiworkload.ServiceProfileDescriptorStatus{},
   190  			},
   191  			wantErr: assert.NoError,
   192  		},
   193  		{
   194  			name: "three pod for 50% baseline percent",
   195  			fields: fields{
   196  				podList: []runtime.Object{
   197  					&v1.Pod{
   198  						ObjectMeta: metav1.ObjectMeta{
   199  							Name:      "pod1",
   200  							Namespace: "default",
   201  							OwnerReferences: []metav1.OwnerReference{
   202  								{
   203  									APIVersion: "apps/v1",
   204  									Kind:       "StatefulSet",
   205  									Name:       "sts1",
   206  								},
   207  							},
   208  							Annotations: map[string]string{
   209  								consts.PodAnnotationSPDNameKey: "spd1",
   210  							},
   211  							Labels: map[string]string{
   212  								"workload": "sts1",
   213  							},
   214  							CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)),
   215  						},
   216  					},
   217  					&v1.Pod{
   218  						ObjectMeta: metav1.ObjectMeta{
   219  							Name:      "pod2",
   220  							Namespace: "default",
   221  							OwnerReferences: []metav1.OwnerReference{
   222  								{
   223  									APIVersion: "apps/v1",
   224  									Kind:       "StatefulSet",
   225  									Name:       "sts1",
   226  								},
   227  							},
   228  							Annotations: map[string]string{
   229  								consts.PodAnnotationSPDNameKey: "spd1",
   230  							},
   231  							Labels: map[string]string{
   232  								"workload": "sts1",
   233  							},
   234  							CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 1, 0, time.UTC)),
   235  						},
   236  					},
   237  					&v1.Pod{
   238  						ObjectMeta: metav1.ObjectMeta{
   239  							Name:      "pod3",
   240  							Namespace: "default",
   241  							OwnerReferences: []metav1.OwnerReference{
   242  								{
   243  									APIVersion: "apps/v1",
   244  									Kind:       "StatefulSet",
   245  									Name:       "sts1",
   246  								},
   247  							},
   248  							Annotations: map[string]string{
   249  								consts.PodAnnotationSPDNameKey: "spd1",
   250  							},
   251  							Labels: map[string]string{
   252  								"workload": "sts1",
   253  							},
   254  							CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 2, 0, time.UTC)),
   255  						},
   256  					},
   257  				},
   258  				workload: &appsv1.StatefulSet{
   259  					TypeMeta: metav1.TypeMeta{
   260  						Kind:       "StatefulSet",
   261  						APIVersion: "apps/v1",
   262  					},
   263  					ObjectMeta: metav1.ObjectMeta{
   264  						Name:        "sts1",
   265  						Namespace:   "default",
   266  						Annotations: map[string]string{},
   267  					},
   268  					Spec: appsv1.StatefulSetSpec{
   269  						Selector: &metav1.LabelSelector{
   270  							MatchLabels: map[string]string{
   271  								"workload": "sts1",
   272  							},
   273  						},
   274  					},
   275  				},
   276  				spd: &apiworkload.ServiceProfileDescriptor{
   277  					ObjectMeta: metav1.ObjectMeta{
   278  						Namespace: "default",
   279  						Name:      "spd1",
   280  					},
   281  					Spec: apiworkload.ServiceProfileDescriptorSpec{
   282  						TargetRef: apis.CrossVersionObjectReference{
   283  							Kind:       stsGVK.Kind,
   284  							Name:       "sts1",
   285  							APIVersion: stsGVK.GroupVersion().String(),
   286  						},
   287  						BaselinePercent: pointer.Int32(50),
   288  						ExtendedIndicator: []apiworkload.ServiceExtendedIndicatorSpec{
   289  							{
   290  								Name:            "TestExtended",
   291  								BaselinePercent: pointer.Int32(50),
   292  								Indicators: runtime.RawExtension{
   293  									Object: &apiworkload.TestExtendedIndicators{},
   294  								},
   295  							},
   296  						},
   297  					},
   298  					Status: apiworkload.ServiceProfileDescriptorStatus{},
   299  				},
   300  			},
   301  			wantSPD: &apiworkload.ServiceProfileDescriptor{
   302  				ObjectMeta: metav1.ObjectMeta{
   303  					Namespace: "default",
   304  					Name:      "spd1",
   305  					Annotations: map[string]string{
   306  						consts.SPDAnnotationBaselineSentinelKey:         "{\"timeStamp\":\"2023-08-01T00:00:01Z\",\"podName\":\"pod2\"}",
   307  						consts.SPDAnnotationExtendedBaselineSentinelKey: "{\"TestExtended\":{\"timeStamp\":\"2023-08-01T00:00:01Z\",\"podName\":\"pod2\"}}",
   308  					},
   309  				},
   310  				Spec: apiworkload.ServiceProfileDescriptorSpec{
   311  					TargetRef: apis.CrossVersionObjectReference{
   312  						Kind:       stsGVK.Kind,
   313  						Name:       "sts1",
   314  						APIVersion: stsGVK.GroupVersion().String(),
   315  					},
   316  					BaselinePercent: pointer.Int32(50),
   317  					ExtendedIndicator: []apiworkload.ServiceExtendedIndicatorSpec{
   318  						{
   319  							Name:            "TestExtended",
   320  							BaselinePercent: pointer.Int32(50),
   321  							Indicators: runtime.RawExtension{
   322  								Object: &apiworkload.TestExtendedIndicators{},
   323  							},
   324  						},
   325  					},
   326  				},
   327  				Status: apiworkload.ServiceProfileDescriptorStatus{},
   328  			},
   329  			wantErr: assert.NoError,
   330  		},
   331  		{
   332  			name: "three pod for 100% baseline percent",
   333  			fields: fields{
   334  				podList: []runtime.Object{
   335  					&v1.Pod{
   336  						ObjectMeta: metav1.ObjectMeta{
   337  							Name:      "pod1",
   338  							Namespace: "default",
   339  							OwnerReferences: []metav1.OwnerReference{
   340  								{
   341  									APIVersion: "apps/v1",
   342  									Kind:       "StatefulSet",
   343  									Name:       "sts1",
   344  								},
   345  							},
   346  							Annotations: map[string]string{
   347  								consts.PodAnnotationSPDNameKey: "spd1",
   348  							},
   349  							Labels: map[string]string{
   350  								"workload": "sts1",
   351  							},
   352  							CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)),
   353  						},
   354  					},
   355  					&v1.Pod{
   356  						ObjectMeta: metav1.ObjectMeta{
   357  							Name:      "pod2",
   358  							Namespace: "default",
   359  							OwnerReferences: []metav1.OwnerReference{
   360  								{
   361  									APIVersion: "apps/v1",
   362  									Kind:       "StatefulSet",
   363  									Name:       "sts1",
   364  								},
   365  							},
   366  							Annotations: map[string]string{
   367  								consts.PodAnnotationSPDNameKey: "spd1",
   368  							},
   369  							Labels: map[string]string{
   370  								"workload": "sts1",
   371  							},
   372  							CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 1, 0, time.UTC)),
   373  						},
   374  					},
   375  					&v1.Pod{
   376  						ObjectMeta: metav1.ObjectMeta{
   377  							Name:      "pod3",
   378  							Namespace: "default",
   379  							OwnerReferences: []metav1.OwnerReference{
   380  								{
   381  									APIVersion: "apps/v1",
   382  									Kind:       "StatefulSet",
   383  									Name:       "sts1",
   384  								},
   385  							},
   386  							Annotations: map[string]string{
   387  								consts.PodAnnotationSPDNameKey: "spd1",
   388  							},
   389  							Labels: map[string]string{
   390  								"workload": "sts1",
   391  							},
   392  							CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 2, 0, time.UTC)),
   393  						},
   394  					},
   395  				},
   396  				workload: &appsv1.StatefulSet{
   397  					TypeMeta: metav1.TypeMeta{
   398  						Kind:       "StatefulSet",
   399  						APIVersion: "apps/v1",
   400  					},
   401  					ObjectMeta: metav1.ObjectMeta{
   402  						Name:        "sts1",
   403  						Namespace:   "default",
   404  						Annotations: map[string]string{},
   405  					},
   406  					Spec: appsv1.StatefulSetSpec{
   407  						Selector: &metav1.LabelSelector{
   408  							MatchLabels: map[string]string{
   409  								"workload": "sts1",
   410  							},
   411  						},
   412  					},
   413  				},
   414  				spd: &apiworkload.ServiceProfileDescriptor{
   415  					ObjectMeta: metav1.ObjectMeta{
   416  						Namespace: "default",
   417  						Name:      "spd1",
   418  					},
   419  					Spec: apiworkload.ServiceProfileDescriptorSpec{
   420  						TargetRef: apis.CrossVersionObjectReference{
   421  							Kind:       stsGVK.Kind,
   422  							Name:       "sts1",
   423  							APIVersion: stsGVK.GroupVersion().String(),
   424  						},
   425  						BaselinePercent: pointer.Int32(100),
   426  					},
   427  					Status: apiworkload.ServiceProfileDescriptorStatus{},
   428  				},
   429  			},
   430  			wantSPD: &apiworkload.ServiceProfileDescriptor{
   431  				ObjectMeta: metav1.ObjectMeta{
   432  					Namespace: "default",
   433  					Name:      "spd1",
   434  				},
   435  				Spec: apiworkload.ServiceProfileDescriptorSpec{
   436  					TargetRef: apis.CrossVersionObjectReference{
   437  						Kind:       stsGVK.Kind,
   438  						Name:       "sts1",
   439  						APIVersion: stsGVK.GroupVersion().String(),
   440  					},
   441  					BaselinePercent: pointer.Int32(100),
   442  				},
   443  				Status: apiworkload.ServiceProfileDescriptorStatus{},
   444  			},
   445  			wantErr: assert.NoError,
   446  		},
   447  		{
   448  			name: "three pod for 0% baseline percent",
   449  			fields: fields{
   450  				podList: []runtime.Object{
   451  					&v1.Pod{
   452  						ObjectMeta: metav1.ObjectMeta{
   453  							Name:      "pod1",
   454  							Namespace: "default",
   455  							OwnerReferences: []metav1.OwnerReference{
   456  								{
   457  									APIVersion: "apps/v1",
   458  									Kind:       "StatefulSet",
   459  									Name:       "sts1",
   460  								},
   461  							},
   462  							Annotations: map[string]string{
   463  								consts.PodAnnotationSPDNameKey: "spd1",
   464  							},
   465  							Labels: map[string]string{
   466  								"workload": "sts1",
   467  							},
   468  							CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)),
   469  						},
   470  					},
   471  					&v1.Pod{
   472  						ObjectMeta: metav1.ObjectMeta{
   473  							Name:      "pod2",
   474  							Namespace: "default",
   475  							OwnerReferences: []metav1.OwnerReference{
   476  								{
   477  									APIVersion: "apps/v1",
   478  									Kind:       "StatefulSet",
   479  									Name:       "sts1",
   480  								},
   481  							},
   482  							Annotations: map[string]string{
   483  								consts.PodAnnotationSPDNameKey: "spd1",
   484  							},
   485  							Labels: map[string]string{
   486  								"workload": "sts1",
   487  							},
   488  							CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 1, 0, time.UTC)),
   489  						},
   490  					},
   491  					&v1.Pod{
   492  						ObjectMeta: metav1.ObjectMeta{
   493  							Name:      "pod3",
   494  							Namespace: "default",
   495  							OwnerReferences: []metav1.OwnerReference{
   496  								{
   497  									APIVersion: "apps/v1",
   498  									Kind:       "StatefulSet",
   499  									Name:       "sts1",
   500  								},
   501  							},
   502  							Annotations: map[string]string{
   503  								consts.PodAnnotationSPDNameKey: "spd1",
   504  							},
   505  							Labels: map[string]string{
   506  								"workload": "sts1",
   507  							},
   508  							CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 2, 0, time.UTC)),
   509  						},
   510  					},
   511  				},
   512  				workload: &appsv1.StatefulSet{
   513  					TypeMeta: metav1.TypeMeta{
   514  						Kind:       "StatefulSet",
   515  						APIVersion: "apps/v1",
   516  					},
   517  					ObjectMeta: metav1.ObjectMeta{
   518  						Name:        "sts1",
   519  						Namespace:   "default",
   520  						Annotations: map[string]string{},
   521  					},
   522  					Spec: appsv1.StatefulSetSpec{
   523  						Selector: &metav1.LabelSelector{
   524  							MatchLabels: map[string]string{
   525  								"workload": "sts1",
   526  							},
   527  						},
   528  					},
   529  				},
   530  				spd: &apiworkload.ServiceProfileDescriptor{
   531  					ObjectMeta: metav1.ObjectMeta{
   532  						Namespace: "default",
   533  						Name:      "spd1",
   534  					},
   535  					Spec: apiworkload.ServiceProfileDescriptorSpec{
   536  						TargetRef: apis.CrossVersionObjectReference{
   537  							Kind:       stsGVK.Kind,
   538  							Name:       "sts1",
   539  							APIVersion: stsGVK.GroupVersion().String(),
   540  						},
   541  						BaselinePercent: pointer.Int32(0),
   542  					},
   543  					Status: apiworkload.ServiceProfileDescriptorStatus{},
   544  				},
   545  			},
   546  			wantSPD: &apiworkload.ServiceProfileDescriptor{
   547  				ObjectMeta: metav1.ObjectMeta{
   548  					Namespace: "default",
   549  					Name:      "spd1",
   550  				},
   551  				Spec: apiworkload.ServiceProfileDescriptorSpec{
   552  					TargetRef: apis.CrossVersionObjectReference{
   553  						Kind:       stsGVK.Kind,
   554  						Name:       "sts1",
   555  						APIVersion: stsGVK.GroupVersion().String(),
   556  					},
   557  					BaselinePercent: pointer.Int32(0),
   558  				},
   559  				Status: apiworkload.ServiceProfileDescriptorStatus{},
   560  			},
   561  			wantErr: assert.NoError,
   562  		},
   563  	}
   564  	for _, tt := range tests {
   565  		tt := tt
   566  		t.Run(tt.name, func(t *testing.T) {
   567  			t.Parallel()
   568  
   569  			spdConfig := &controller.SPDConfig{
   570  				SPDWorkloadGVResources: []string{"statefulsets.v1.apps"},
   571  			}
   572  			genericConfig := &generic.GenericConfiguration{}
   573  			controllerConf := &controller.GenericControllerConfiguration{
   574  				DynamicGVResources: []string{"statefulsets.v1.apps"},
   575  			}
   576  
   577  			ctx := context.TODO()
   578  			controlCtx, err := katalystbase.GenerateFakeGenericContext(tt.fields.podList,
   579  				[]runtime.Object{tt.fields.spd}, []runtime.Object{tt.fields.workload})
   580  			assert.NoError(t, err)
   581  
   582  			spdController, err := NewSPDController(ctx, controlCtx, genericConfig, controllerConf, spdConfig, nil, struct{}{})
   583  			assert.NoError(t, err)
   584  
   585  			controlCtx.StartInformer(ctx)
   586  			go spdController.Run()
   587  			synced := cache.WaitForCacheSync(ctx.Done(), spdController.syncedFunc...)
   588  			assert.True(t, synced)
   589  			time.Sleep(1 * time.Second)
   590  
   591  			tt.wantErr(t, spdController.updateBaselineSentinel(tt.fields.spd), fmt.Sprintf("updateBaselineSentinel(%v)", tt.fields.spd))
   592  			assert.Equal(t, tt.wantSPD, tt.fields.spd)
   593  		})
   594  	}
   595  }