github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/spd/spd_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  	"encoding/json"
    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/apimachinery/pkg/runtime/schema"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	"k8s.io/client-go/tools/cache"
    33  	"k8s.io/utils/pointer"
    34  
    35  	apis "github.com/kubewharf/katalyst-api/pkg/apis/autoscaling/v1alpha1"
    36  	apiworkload "github.com/kubewharf/katalyst-api/pkg/apis/workload/v1alpha1"
    37  	apiconsts "github.com/kubewharf/katalyst-api/pkg/consts"
    38  	katalystbase "github.com/kubewharf/katalyst-core/cmd/base"
    39  	"github.com/kubewharf/katalyst-core/pkg/config/controller"
    40  	"github.com/kubewharf/katalyst-core/pkg/config/generic"
    41  	"github.com/kubewharf/katalyst-core/pkg/consts"
    42  	indicator_plugin "github.com/kubewharf/katalyst-core/pkg/controller/spd/indicator-plugin"
    43  	"github.com/kubewharf/katalyst-core/pkg/util/native"
    44  )
    45  
    46  var (
    47  	stsGVK = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"}
    48  	stsGVR = schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"}
    49  )
    50  
    51  func TestSPDController_Run(t *testing.T) {
    52  	t.Parallel()
    53  
    54  	type fields struct {
    55  		pod      *v1.Pod
    56  		workload *appsv1.StatefulSet
    57  		spd      *apiworkload.ServiceProfileDescriptor
    58  	}
    59  	tests := []struct {
    60  		name         string
    61  		fields       fields
    62  		wantWorkload *appsv1.StatefulSet
    63  		wantSPD      *apiworkload.ServiceProfileDescriptor
    64  	}{
    65  		{
    66  			name: "delete unwanted spd",
    67  			fields: fields{
    68  				pod: &v1.Pod{
    69  					ObjectMeta: metav1.ObjectMeta{
    70  						Name:      "pod1",
    71  						Namespace: "default",
    72  						OwnerReferences: []metav1.OwnerReference{
    73  							{
    74  								APIVersion: "apps/v1",
    75  								Kind:       "StatefulSet",
    76  								Name:       "sts1",
    77  							},
    78  						},
    79  						Annotations: map[string]string{
    80  							apiconsts.PodAnnotationSPDNameKey: "spd1",
    81  						},
    82  						Labels: map[string]string{
    83  							"workload": "sts1",
    84  						},
    85  					},
    86  				},
    87  				workload: &appsv1.StatefulSet{
    88  					TypeMeta: metav1.TypeMeta{
    89  						Kind:       "StatefulSet",
    90  						APIVersion: "apps/v1",
    91  					},
    92  					ObjectMeta: metav1.ObjectMeta{
    93  						Name:        "sts1",
    94  						Namespace:   "default",
    95  						Annotations: map[string]string{},
    96  					},
    97  					Spec: appsv1.StatefulSetSpec{
    98  						Selector: &metav1.LabelSelector{
    99  							MatchLabels: map[string]string{
   100  								"workload": "sts1",
   101  							},
   102  						},
   103  					},
   104  				},
   105  				spd: &apiworkload.ServiceProfileDescriptor{
   106  					ObjectMeta: metav1.ObjectMeta{
   107  						Namespace: "default",
   108  						Name:      "sts1",
   109  					},
   110  					Spec: apiworkload.ServiceProfileDescriptorSpec{
   111  						TargetRef: apis.CrossVersionObjectReference{
   112  							Kind:       stsGVK.Kind,
   113  							Name:       "sts1",
   114  							APIVersion: stsGVK.GroupVersion().String(),
   115  						},
   116  					},
   117  					Status: apiworkload.ServiceProfileDescriptorStatus{},
   118  				},
   119  			},
   120  			wantWorkload: &appsv1.StatefulSet{
   121  				TypeMeta: metav1.TypeMeta{
   122  					Kind:       "StatefulSet",
   123  					APIVersion: "apps/v1",
   124  				},
   125  				ObjectMeta: metav1.ObjectMeta{
   126  					Name:      "sts1",
   127  					Namespace: "default",
   128  				},
   129  				Spec: appsv1.StatefulSetSpec{
   130  					Selector: &metav1.LabelSelector{
   131  						MatchLabels: map[string]string{
   132  							"workload": "sts1",
   133  						},
   134  					},
   135  				},
   136  			},
   137  			wantSPD: nil,
   138  		},
   139  		{
   140  			name: "auto create spd",
   141  			fields: fields{
   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  							apiconsts.WorkloadAnnotationSPDEnableKey: apiconsts.WorkloadAnnotationSPDEnabled,
   152  						},
   153  					},
   154  					Spec: appsv1.StatefulSetSpec{
   155  						Selector: &metav1.LabelSelector{
   156  							MatchLabels: map[string]string{
   157  								"workload": "sts1",
   158  							},
   159  						},
   160  						Template: v1.PodTemplateSpec{
   161  							ObjectMeta: metav1.ObjectMeta{
   162  								Annotations: map[string]string{
   163  									"katalyst.kubewharf.io/qos_level": "dedicated_cores",
   164  								},
   165  							},
   166  							Spec: v1.PodSpec{},
   167  						},
   168  					},
   169  				},
   170  				spd: nil,
   171  			},
   172  			wantWorkload: &appsv1.StatefulSet{
   173  				TypeMeta: metav1.TypeMeta{
   174  					Kind:       "StatefulSet",
   175  					APIVersion: "apps/v1",
   176  				},
   177  				ObjectMeta: metav1.ObjectMeta{
   178  					Name:      "sts1",
   179  					Namespace: "default",
   180  					Annotations: map[string]string{
   181  						apiconsts.WorkloadAnnotationSPDEnableKey: apiconsts.WorkloadAnnotationSPDEnabled,
   182  					},
   183  				},
   184  				Spec: appsv1.StatefulSetSpec{
   185  					Selector: &metav1.LabelSelector{
   186  						MatchLabels: map[string]string{
   187  							"workload": "sts1",
   188  						},
   189  					},
   190  					Template: v1.PodTemplateSpec{
   191  						ObjectMeta: metav1.ObjectMeta{
   192  							Annotations: map[string]string{
   193  								"katalyst.kubewharf.io/qos_level": "dedicated_cores",
   194  							},
   195  						},
   196  						Spec: v1.PodSpec{},
   197  					},
   198  				},
   199  			},
   200  			wantSPD: &apiworkload.ServiceProfileDescriptor{
   201  				ObjectMeta: metav1.ObjectMeta{
   202  					Namespace: "default",
   203  					Name:      "sts1",
   204  					Annotations: map[string]string{
   205  						consts.ServiceProfileDescriptorAnnotationKeyConfigHash: "51131be1b092",
   206  					},
   207  					OwnerReferences: []metav1.OwnerReference{
   208  						{
   209  							APIVersion: "apps/v1",
   210  							Kind:       "StatefulSet",
   211  							Name:       "sts1",
   212  						},
   213  					},
   214  				},
   215  				Spec: apiworkload.ServiceProfileDescriptorSpec{
   216  					TargetRef: apis.CrossVersionObjectReference{
   217  						Kind:       stsGVK.Kind,
   218  						Name:       "sts1",
   219  						APIVersion: stsGVK.GroupVersion().String(),
   220  					},
   221  					BaselinePercent: pointer.Int32(100),
   222  				},
   223  				Status: apiworkload.ServiceProfileDescriptorStatus{
   224  					AggMetrics: []apiworkload.AggPodMetrics{},
   225  				},
   226  			},
   227  		},
   228  		{
   229  			name: "auto create spd(dedicated_cores)",
   230  			fields: fields{
   231  				workload: &appsv1.StatefulSet{
   232  					TypeMeta: metav1.TypeMeta{
   233  						Kind:       "StatefulSet",
   234  						APIVersion: "apps/v1",
   235  					},
   236  					ObjectMeta: metav1.ObjectMeta{
   237  						Name:      "sts1",
   238  						Namespace: "default",
   239  						Annotations: map[string]string{
   240  							apiconsts.WorkloadAnnotationSPDEnableKey: apiconsts.WorkloadAnnotationSPDEnabled,
   241  						},
   242  					},
   243  					Spec: appsv1.StatefulSetSpec{
   244  						Selector: &metav1.LabelSelector{
   245  							MatchLabels: map[string]string{
   246  								"workload": "sts1",
   247  							},
   248  						},
   249  						Template: v1.PodTemplateSpec{
   250  							ObjectMeta: metav1.ObjectMeta{
   251  								Annotations: map[string]string{"katalyst.kubewharf.io/qos_level": "dedicated_cores"},
   252  							},
   253  							Spec: v1.PodSpec{},
   254  						},
   255  					},
   256  				},
   257  				spd: nil,
   258  			},
   259  			wantWorkload: &appsv1.StatefulSet{
   260  				TypeMeta: metav1.TypeMeta{
   261  					Kind:       "StatefulSet",
   262  					APIVersion: "apps/v1",
   263  				},
   264  				ObjectMeta: metav1.ObjectMeta{
   265  					Name:      "sts1",
   266  					Namespace: "default",
   267  					Annotations: map[string]string{
   268  						apiconsts.WorkloadAnnotationSPDEnableKey: apiconsts.WorkloadAnnotationSPDEnabled,
   269  					},
   270  				},
   271  				Spec: appsv1.StatefulSetSpec{
   272  					Selector: &metav1.LabelSelector{
   273  						MatchLabels: map[string]string{
   274  							"workload": "sts1",
   275  						},
   276  					},
   277  					Template: v1.PodTemplateSpec{
   278  						ObjectMeta: metav1.ObjectMeta{
   279  							Annotations: map[string]string{"katalyst.kubewharf.io/qos_level": "dedicated_cores"},
   280  						},
   281  						Spec: v1.PodSpec{},
   282  					},
   283  				},
   284  			},
   285  			wantSPD: &apiworkload.ServiceProfileDescriptor{
   286  				ObjectMeta: metav1.ObjectMeta{
   287  					Namespace: "default",
   288  					Name:      "sts1",
   289  					Annotations: map[string]string{
   290  						consts.ServiceProfileDescriptorAnnotationKeyConfigHash: "51131be1b092",
   291  					},
   292  					OwnerReferences: []metav1.OwnerReference{
   293  						{
   294  							APIVersion: "apps/v1",
   295  							Kind:       "StatefulSet",
   296  							Name:       "sts1",
   297  						},
   298  					},
   299  				},
   300  				Spec: apiworkload.ServiceProfileDescriptorSpec{
   301  					TargetRef: apis.CrossVersionObjectReference{
   302  						Kind:       stsGVK.Kind,
   303  						Name:       "sts1",
   304  						APIVersion: stsGVK.GroupVersion().String(),
   305  					},
   306  					BaselinePercent: pointer.Int32(100),
   307  				},
   308  				Status: apiworkload.ServiceProfileDescriptorStatus{
   309  					AggMetrics: []apiworkload.AggPodMetrics{},
   310  				},
   311  			},
   312  		},
   313  		{
   314  			name: "auto create spd(shared_cores)",
   315  			fields: fields{
   316  				workload: &appsv1.StatefulSet{
   317  					TypeMeta: metav1.TypeMeta{
   318  						Kind:       "StatefulSet",
   319  						APIVersion: "apps/v1",
   320  					},
   321  					ObjectMeta: metav1.ObjectMeta{
   322  						Name:      "sts1",
   323  						Namespace: "default",
   324  						Annotations: map[string]string{
   325  							apiconsts.WorkloadAnnotationSPDEnableKey: apiconsts.WorkloadAnnotationSPDEnabled,
   326  						},
   327  					},
   328  					Spec: appsv1.StatefulSetSpec{
   329  						Selector: &metav1.LabelSelector{
   330  							MatchLabels: map[string]string{
   331  								"workload": "sts1",
   332  							},
   333  						},
   334  						Template: v1.PodTemplateSpec{
   335  							ObjectMeta: metav1.ObjectMeta{
   336  								Annotations: map[string]string{"katalyst.kubewharf.io/qos_level": "shared_cores"},
   337  							},
   338  							Spec: v1.PodSpec{},
   339  						},
   340  					},
   341  				},
   342  				spd: nil,
   343  			},
   344  			wantWorkload: &appsv1.StatefulSet{
   345  				TypeMeta: metav1.TypeMeta{
   346  					Kind:       "StatefulSet",
   347  					APIVersion: "apps/v1",
   348  				},
   349  				ObjectMeta: metav1.ObjectMeta{
   350  					Name:      "sts1",
   351  					Namespace: "default",
   352  					Annotations: map[string]string{
   353  						apiconsts.WorkloadAnnotationSPDEnableKey: apiconsts.WorkloadAnnotationSPDEnabled,
   354  					},
   355  				},
   356  				Spec: appsv1.StatefulSetSpec{
   357  					Selector: &metav1.LabelSelector{
   358  						MatchLabels: map[string]string{
   359  							"workload": "sts1",
   360  						},
   361  					},
   362  					Template: v1.PodTemplateSpec{
   363  						ObjectMeta: metav1.ObjectMeta{
   364  							Annotations: map[string]string{"katalyst.kubewharf.io/qos_level": "shared_cores"},
   365  						},
   366  						Spec: v1.PodSpec{},
   367  					},
   368  				},
   369  			},
   370  			wantSPD: &apiworkload.ServiceProfileDescriptor{
   371  				ObjectMeta: metav1.ObjectMeta{
   372  					Namespace: "default",
   373  					Name:      "sts1",
   374  					Annotations: map[string]string{
   375  						consts.ServiceProfileDescriptorAnnotationKeyConfigHash: "a62e4c90e3ed",
   376  					},
   377  					OwnerReferences: []metav1.OwnerReference{
   378  						{
   379  							APIVersion: "apps/v1",
   380  							Kind:       "StatefulSet",
   381  							Name:       "sts1",
   382  						},
   383  					},
   384  				},
   385  				Spec: apiworkload.ServiceProfileDescriptorSpec{
   386  					TargetRef: apis.CrossVersionObjectReference{
   387  						Kind:       stsGVK.Kind,
   388  						Name:       "sts1",
   389  						APIVersion: stsGVK.GroupVersion().String(),
   390  					},
   391  					BaselinePercent: pointer.Int32(0),
   392  				},
   393  				Status: apiworkload.ServiceProfileDescriptorStatus{
   394  					AggMetrics: []apiworkload.AggPodMetrics{},
   395  				},
   396  			},
   397  		},
   398  	}
   399  	for _, tt := range tests {
   400  		tt := tt
   401  		t.Run(tt.name, func(t *testing.T) {
   402  			t.Parallel()
   403  			spdConfig := &controller.SPDConfig{
   404  				SPDWorkloadGVResources: []string{"statefulsets.v1.apps"},
   405  				BaselinePercent: map[string]int64{
   406  					apiconsts.PodAnnotationQoSLevelSharedCores:    0,
   407  					apiconsts.PodAnnotationQoSLevelDedicatedCores: 100,
   408  				},
   409  			}
   410  			genericConfig := &generic.GenericConfiguration{}
   411  			controllerConf := &controller.GenericControllerConfiguration{
   412  				DynamicGVResources: []string{"statefulsets.v1.apps"},
   413  			}
   414  
   415  			ctx := context.TODO()
   416  			controlCtx, err := katalystbase.GenerateFakeGenericContext([]runtime.Object{tt.fields.pod},
   417  				[]runtime.Object{tt.fields.spd}, []runtime.Object{tt.fields.workload})
   418  			assert.NoError(t, err)
   419  
   420  			spdController, err := NewSPDController(ctx, controlCtx, genericConfig, controllerConf, spdConfig, generic.NewQoSConfiguration(), struct{}{})
   421  			assert.NoError(t, err)
   422  
   423  			controlCtx.StartInformer(ctx)
   424  			go spdController.Run()
   425  			synced := cache.WaitForCacheSync(ctx.Done(), spdController.syncedFunc...)
   426  			assert.True(t, synced)
   427  			time.Sleep(1 * time.Second)
   428  
   429  			targetSPD := tt.fields.spd
   430  			if targetSPD == nil {
   431  				targetSPD = tt.wantSPD
   432  			}
   433  			newSPD, _ := controlCtx.Client.InternalClient.WorkloadV1alpha1().
   434  				ServiceProfileDescriptors(targetSPD.Namespace).Get(ctx, targetSPD.Name, metav1.GetOptions{})
   435  			assert.Equal(t, tt.wantSPD, newSPD)
   436  
   437  			newObject, _ := controlCtx.Client.DynamicClient.Resource(stsGVR).
   438  				Namespace(tt.fields.workload.GetNamespace()).Get(ctx, tt.fields.workload.GetName(), metav1.GetOptions{})
   439  
   440  			newWorkload := &appsv1.StatefulSet{}
   441  			err = runtime.DefaultUnstructuredConverter.FromUnstructured(newObject.UnstructuredContent(), newWorkload)
   442  			assert.NoError(t, err)
   443  			assert.Equal(t, tt.wantWorkload, newWorkload)
   444  		})
   445  	}
   446  }
   447  
   448  func TestPodIndexerDuplicate(t *testing.T) {
   449  	t.Parallel()
   450  
   451  	spdConf := controller.NewSPDConfig()
   452  	genericConfig := &generic.GenericConfiguration{}
   453  	controllerConf := &controller.GenericControllerConfiguration{}
   454  	controlCtx, err := katalystbase.GenerateFakeGenericContext(nil, nil, nil)
   455  	assert.NoError(t, err)
   456  
   457  	spdConf.SPDPodLabelIndexerKeys = []string{"test-1"}
   458  
   459  	_, err = NewSPDController(context.TODO(), controlCtx, genericConfig, controllerConf, spdConf, nil, struct{}{})
   460  	assert.NoError(t, err)
   461  
   462  	_, err = NewSPDController(context.TODO(), controlCtx, genericConfig, controllerConf, spdConf, nil, struct{}{})
   463  	assert.NoError(t, err)
   464  
   465  	indexers := controlCtx.KubeInformerFactory.Core().V1().Pods().Informer().GetIndexer().GetIndexers()
   466  	assert.Equal(t, 2, len(indexers))
   467  	_, exist := indexers["test-1"]
   468  	assert.Equal(t, true, exist)
   469  }
   470  
   471  func TestIndicatorUpdater(t *testing.T) {
   472  	t.Parallel()
   473  
   474  	var current float32 = 8.3
   475  	var value float32 = 23.1
   476  
   477  	workload := &appsv1.StatefulSet{
   478  		TypeMeta: metav1.TypeMeta{
   479  			Kind:       "StatefulSet",
   480  			APIVersion: "apps/v1",
   481  		},
   482  		ObjectMeta: metav1.ObjectMeta{
   483  			Name:      "sts1",
   484  			Namespace: "default",
   485  			Annotations: map[string]string{
   486  				apiconsts.WorkloadAnnotationSPDEnableKey: apiconsts.WorkloadAnnotationSPDEnabled,
   487  			},
   488  		},
   489  		Spec: appsv1.StatefulSetSpec{
   490  			Selector: &metav1.LabelSelector{
   491  				MatchLabels: map[string]string{
   492  					"workload": "sts1",
   493  				},
   494  			},
   495  		},
   496  	}
   497  
   498  	spd := &apiworkload.ServiceProfileDescriptor{
   499  		ObjectMeta: metav1.ObjectMeta{
   500  			Namespace:       "default",
   501  			Name:            "spd1",
   502  			ResourceVersion: "1",
   503  		},
   504  		Spec: apiworkload.ServiceProfileDescriptorSpec{
   505  			TargetRef: apis.CrossVersionObjectReference{
   506  				Kind:       stsGVK.Kind,
   507  				Name:       "sts1",
   508  				APIVersion: stsGVK.GroupVersion().String(),
   509  			},
   510  			BusinessIndicator: []apiworkload.ServiceBusinessIndicatorSpec{
   511  				{
   512  					Name: "none-exist-b",
   513  					Indicators: []apiworkload.Indicator{
   514  						{
   515  							IndicatorLevel: apiworkload.IndicatorLevelLowerBound,
   516  							Value:          10.2,
   517  						},
   518  					},
   519  				},
   520  			},
   521  			SystemIndicator: []apiworkload.ServiceSystemIndicatorSpec{
   522  				{
   523  					Name: "none-exist-s",
   524  					Indicators: []apiworkload.Indicator{
   525  						{
   526  							IndicatorLevel: apiworkload.IndicatorLevelUpperBound,
   527  							Value:          10.5,
   528  						},
   529  					},
   530  				},
   531  				{
   532  					Name: "system-3",
   533  					Indicators: []apiworkload.Indicator{
   534  						{
   535  							IndicatorLevel: apiworkload.IndicatorLevelUpperBound,
   536  							Value:          4.5,
   537  						},
   538  					},
   539  				},
   540  			},
   541  		},
   542  		Status: apiworkload.ServiceProfileDescriptorStatus{
   543  			BusinessStatus: []apiworkload.ServiceBusinessIndicatorStatus{
   544  				{
   545  					Name:    "none-exist-status",
   546  					Current: &current,
   547  				},
   548  				{
   549  					Name:    "system-2",
   550  					Current: &current,
   551  				},
   552  			},
   553  		},
   554  	}
   555  
   556  	expectedSpd := &apiworkload.ServiceProfileDescriptor{
   557  		ObjectMeta: metav1.ObjectMeta{
   558  			Namespace: "default",
   559  			Name:      "spd1",
   560  		},
   561  		Spec: apiworkload.ServiceProfileDescriptorSpec{
   562  			TargetRef: apis.CrossVersionObjectReference{
   563  				Kind:       stsGVK.Kind,
   564  				Name:       "sts1",
   565  				APIVersion: stsGVK.GroupVersion().String(),
   566  			},
   567  			BaselinePercent: pointer.Int32(20),
   568  			ExtendedIndicator: []apiworkload.ServiceExtendedIndicatorSpec{
   569  				{
   570  					Name: "TestExtended",
   571  					Indicators: runtime.RawExtension{
   572  						Raw: func() []byte {
   573  							data, _ := json.Marshal(&apiworkload.TestExtendedIndicators{
   574  								Indicators: &apiworkload.TestIndicators{},
   575  							})
   576  							return data
   577  						}(),
   578  					},
   579  				},
   580  			},
   581  			BusinessIndicator: []apiworkload.ServiceBusinessIndicatorSpec{
   582  				{
   583  					Name: "business-1",
   584  					Indicators: []apiworkload.Indicator{
   585  						{
   586  							IndicatorLevel: apiworkload.IndicatorLevelLowerBound,
   587  							Value:          10.2,
   588  						},
   589  					},
   590  				},
   591  				{
   592  					Name: "business-2",
   593  					Indicators: []apiworkload.Indicator{
   594  						{
   595  							IndicatorLevel: apiworkload.IndicatorLevelUpperBound,
   596  							Value:          18.3,
   597  						},
   598  					},
   599  				},
   600  				{
   601  					Name: "business-3",
   602  					Indicators: []apiworkload.Indicator{
   603  						{
   604  							IndicatorLevel: apiworkload.IndicatorLevelUpperBound,
   605  							Value:          16.8,
   606  						},
   607  					},
   608  				},
   609  			},
   610  			SystemIndicator: []apiworkload.ServiceSystemIndicatorSpec{
   611  				{
   612  					Name: "system-3",
   613  					Indicators: []apiworkload.Indicator{
   614  						{
   615  							IndicatorLevel: apiworkload.IndicatorLevelUpperBound,
   616  							Value:          4.5,
   617  						},
   618  					},
   619  				},
   620  				{
   621  					Name: "system-1",
   622  					Indicators: []apiworkload.Indicator{
   623  						{
   624  							IndicatorLevel: apiworkload.IndicatorLevelLowerBound,
   625  							Value:          10.5,
   626  						},
   627  						{
   628  							IndicatorLevel: apiworkload.IndicatorLevelUpperBound,
   629  							Value:          10.5,
   630  						},
   631  					},
   632  				},
   633  				{
   634  					Name: "system-2",
   635  					Indicators: []apiworkload.Indicator{
   636  						{
   637  							IndicatorLevel: apiworkload.IndicatorLevelUpperBound,
   638  							Value:          10.5,
   639  						},
   640  					},
   641  				},
   642  			},
   643  		},
   644  		Status: apiworkload.ServiceProfileDescriptorStatus{
   645  			BusinessStatus: []apiworkload.ServiceBusinessIndicatorStatus{
   646  				{
   647  					Name:    "system-2",
   648  					Current: &value,
   649  				},
   650  			},
   651  		},
   652  	}
   653  
   654  	nn := types.NamespacedName{
   655  		Namespace: "default",
   656  		Name:      "spd1",
   657  	}
   658  
   659  	d1 := indicator_plugin.DummyIndicatorPlugin{
   660  		ExtendedSpecNames: []string{
   661  			"TestExtended",
   662  		},
   663  		SystemSpecNames: []apiworkload.ServiceSystemIndicatorName{
   664  			"system-1",
   665  		},
   666  		BusinessSpecNames: []apiworkload.ServiceBusinessIndicatorName{
   667  			"business-1",
   668  			"business-2",
   669  		},
   670  		BusinessStatusNames: []apiworkload.ServiceBusinessIndicatorName{
   671  			"business-2",
   672  		},
   673  	}
   674  	d2 := indicator_plugin.DummyIndicatorPlugin{
   675  		SystemSpecNames: []apiworkload.ServiceSystemIndicatorName{
   676  			"system-2",
   677  			"system-3",
   678  		},
   679  		BusinessSpecNames: []apiworkload.ServiceBusinessIndicatorName{
   680  			"business-3",
   681  		},
   682  		BusinessStatusNames: []apiworkload.ServiceBusinessIndicatorName{
   683  			"business-3",
   684  		},
   685  	}
   686  
   687  	indicator_plugin.RegisterPluginInitializer("d1", func(_ context.Context, _ *controller.SPDConfig,
   688  		_ interface{}, _ map[schema.GroupVersionResource]native.DynamicInformer, _ *katalystbase.GenericContext,
   689  		_ indicator_plugin.IndicatorUpdater,
   690  	) (indicator_plugin.IndicatorPlugin, error) {
   691  		return d1, nil
   692  	})
   693  	indicator_plugin.RegisterPluginInitializer("d2", func(_ context.Context, _ *controller.SPDConfig,
   694  		_ interface{}, _ map[schema.GroupVersionResource]native.DynamicInformer, _ *katalystbase.GenericContext,
   695  		_ indicator_plugin.IndicatorUpdater,
   696  	) (indicator_plugin.IndicatorPlugin, error) {
   697  		return d2, nil
   698  	})
   699  
   700  	spdConfig := &controller.SPDConfig{
   701  		SPDWorkloadGVResources: []string{"statefulsets.v1.apps"},
   702  		IndicatorPlugins:       []string{"d1", "d2"},
   703  	}
   704  	genericConfig := &generic.GenericConfiguration{}
   705  	controllerConf := &controller.GenericControllerConfiguration{
   706  		DynamicGVResources: []string{"statefulsets.v1.apps"},
   707  	}
   708  
   709  	ctx := context.TODO()
   710  	controlCtx, err := katalystbase.GenerateFakeGenericContext([]runtime.Object{},
   711  		[]runtime.Object{spd}, []runtime.Object{workload})
   712  	assert.NoError(t, err)
   713  
   714  	sc, err := NewSPDController(ctx, controlCtx, genericConfig, controllerConf, spdConfig, nil, struct{}{})
   715  	assert.NoError(t, err)
   716  
   717  	controlCtx.StartInformer(ctx)
   718  	go sc.Run()
   719  	synced := cache.WaitForCacheSync(ctx.Done(), sc.syncedFunc...)
   720  	assert.True(t, synced)
   721  
   722  	sc.indicatorManager.UpdateExtendedIndicatorSpec(nn, []apiworkload.ServiceExtendedIndicatorSpec{
   723  		{
   724  			Name: "TestExtended",
   725  			Indicators: runtime.RawExtension{
   726  				Object: &apiworkload.TestExtendedIndicators{
   727  					Indicators: &apiworkload.TestIndicators{},
   728  				},
   729  			},
   730  		},
   731  	})
   732  
   733  	sc.indicatorManager.UpdateBusinessIndicatorSpec(nn, []apiworkload.ServiceBusinessIndicatorSpec{
   734  		{
   735  			Name: "business-1",
   736  			Indicators: []apiworkload.Indicator{
   737  				{
   738  					IndicatorLevel: apiworkload.IndicatorLevelLowerBound,
   739  					Value:          10.2,
   740  				},
   741  			},
   742  		},
   743  		{
   744  			Name: "business-2",
   745  			Indicators: []apiworkload.Indicator{
   746  				{
   747  					IndicatorLevel: apiworkload.IndicatorLevelUpperBound,
   748  					Value:          18.3,
   749  				},
   750  			},
   751  		},
   752  	})
   753  	sc.indicatorManager.UpdateBusinessIndicatorSpec(nn, []apiworkload.ServiceBusinessIndicatorSpec{
   754  		{
   755  			Name: "business-3",
   756  			Indicators: []apiworkload.Indicator{
   757  				{
   758  					IndicatorLevel: apiworkload.IndicatorLevelUpperBound,
   759  					Value:          13.3,
   760  				},
   761  			},
   762  		},
   763  		{
   764  			Name: "business-3",
   765  			Indicators: []apiworkload.Indicator{
   766  				{
   767  					IndicatorLevel: apiworkload.IndicatorLevelUpperBound,
   768  					Value:          16.8,
   769  				},
   770  			},
   771  		},
   772  	})
   773  
   774  	sc.indicatorManager.UpdateSystemIndicatorSpec(nn, []apiworkload.ServiceSystemIndicatorSpec{
   775  		{
   776  			Name: "system-1",
   777  			Indicators: []apiworkload.Indicator{
   778  				{
   779  					IndicatorLevel: apiworkload.IndicatorLevelLowerBound,
   780  					Value:          10.5,
   781  				},
   782  				{
   783  					IndicatorLevel: apiworkload.IndicatorLevelUpperBound,
   784  					Value:          10.5,
   785  				},
   786  			},
   787  		},
   788  	})
   789  	sc.indicatorManager.UpdateSystemIndicatorSpec(nn, []apiworkload.ServiceSystemIndicatorSpec{
   790  		{
   791  			Name: "system-2",
   792  			Indicators: []apiworkload.Indicator{
   793  				{
   794  					IndicatorLevel: apiworkload.IndicatorLevelUpperBound,
   795  					Value:          10.5,
   796  				},
   797  			},
   798  		},
   799  	})
   800  
   801  	sc.indicatorManager.UpdateBusinessIndicatorStatus(nn, []apiworkload.ServiceBusinessIndicatorStatus{
   802  		{
   803  			Name:    "system-1",
   804  			Current: &value,
   805  		},
   806  		{
   807  			Name:    "system-2",
   808  			Current: &value,
   809  		},
   810  	})
   811  	time.Sleep(time.Second)
   812  	newSPD, err := controlCtx.Client.InternalClient.WorkloadV1alpha1().
   813  		ServiceProfileDescriptors("default").Get(ctx, "spd1", metav1.GetOptions{})
   814  	assert.NoError(t, err)
   815  	assert.Equal(t, expectedSpd.Spec.ExtendedIndicator, newSPD.Spec.ExtendedIndicator)
   816  	assert.Equal(t, expectedSpd.Spec.BusinessIndicator, newSPD.Spec.BusinessIndicator)
   817  	assert.Equal(t, expectedSpd.Spec.SystemIndicator, newSPD.Spec.SystemIndicator)
   818  	assert.Equal(t, expectedSpd.Status.BusinessStatus, newSPD.Status.BusinessStatus)
   819  }