diff --git a/go.mod b/go.mod index 817fd57df8..81445e4c48 100644 --- a/go.mod +++ b/go.mod @@ -148,6 +148,7 @@ require ( ) replace ( + github.com/kubewharf/katalyst-api => github.com/luomingmeng/katalyst-api v0.0.0-20240308022858-a519e64b4344 k8s.io/api => k8s.io/api v0.24.6 k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.24.6 k8s.io/apimachinery => k8s.io/apimachinery v0.24.6 diff --git a/go.sum b/go.sum index 251b21e046..be3b11ba8d 100644 --- a/go.sum +++ b/go.sum @@ -548,8 +548,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kubewharf/katalyst-api v0.4.1-0.20240222122824-be538f641f58 h1:D9dCR5EIR0k0Qil2A5biZjrubagRkEr7fyov6fb2ApY= -github.com/kubewharf/katalyst-api v0.4.1-0.20240222122824-be538f641f58/go.mod h1:Y2IeIorxQamF2a3oa0+URztl5QCSty6Jj3zD83R8J9k= github.com/kubewharf/kubelet v1.24.6-kubewharf.8 h1:2e89T/nZTgzaVhyRsZuwEdRk8V8kJXs4PRkgfeG4Ai4= github.com/kubewharf/kubelet v1.24.6-kubewharf.8/go.mod h1:MxbSZUx3wXztFneeelwWWlX7NAAStJ6expqq7gY2J3c= github.com/kyoh86/exportloopref v0.1.7/go.mod h1:h1rDl2Kdj97+Kwh4gdz3ujE7XHmH51Q0lUiZ1z4NLj8= @@ -561,6 +559,8 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lpabon/godbc v0.1.1/go.mod h1:Jo9QV0cf3U6jZABgiJ2skINAXb9j8m51r07g4KI92ZA= +github.com/luomingmeng/katalyst-api v0.0.0-20240308022858-a519e64b4344 h1:cQQH57vkK9oP1y5CXHhwr4qQ/DmJfwf70LHhj/sUfws= +github.com/luomingmeng/katalyst-api v0.0.0-20240308022858-a519e64b4344/go.mod h1:Y2IeIorxQamF2a3oa0+URztl5QCSty6Jj3zD83R8J9k= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= diff --git a/pkg/controller/spd/indicator-plugin/manager.go b/pkg/controller/spd/indicator-plugin/manager.go index 23e23b89ae..527790fac2 100644 --- a/pkg/controller/spd/indicator-plugin/manager.go +++ b/pkg/controller/spd/indicator-plugin/manager.go @@ -34,8 +34,9 @@ const ( // IndicatorUpdater is used by IndicatorPlugin as a unified implementation // to trigger indicator updating logic. type IndicatorUpdater interface { - // UpdateBusinessIndicatorSpec + UpdateSystemIndicatorSpec + UpdateBusinessIndicatorStatus + // UpdateExtendedIndicatorSpec + UpdateBusinessIndicatorSpec + UpdateSystemIndicatorSpec + UpdateBusinessIndicatorStatus // for indicator add functions, IndicatorUpdater will try to merge them in local stores. + UpdateExtendedIndicatorSpec(_ types.NamespacedName, _ []apiworkload.ServiceExtendedIndicatorSpec) UpdateBusinessIndicatorSpec(_ types.NamespacedName, _ []apiworkload.ServiceBusinessIndicatorSpec) UpdateSystemIndicatorSpec(_ types.NamespacedName, _ []apiworkload.ServiceSystemIndicatorSpec) UpdateBusinessIndicatorStatus(_ types.NamespacedName, _ []apiworkload.ServiceBusinessIndicatorStatus) @@ -78,6 +79,24 @@ func NewIndicatorManager() *IndicatorManager { } } +func (u *IndicatorManager) UpdateExtendedIndicatorSpec(nn types.NamespacedName, indicators []apiworkload.ServiceExtendedIndicatorSpec) { + u.specMtx.Lock() + + insert := false + if _, ok := u.specMap[nn]; !ok { + insert = true + u.specMap[nn] = initServiceProfileDescriptorSpec() + } + for _, indicator := range indicators { + util.InsertSPDExtendedIndicatorSpec(u.specMap[nn], &indicator) + } + u.specMtx.Unlock() + + if insert { + u.specQueue <- nn + } +} + func (u *IndicatorManager) UpdateBusinessIndicatorSpec(nn types.NamespacedName, indicators []apiworkload.ServiceBusinessIndicatorSpec) { u.specMtx.Lock() @@ -173,6 +192,7 @@ func (u *IndicatorManager) GetIndicatorStatus(nn types.NamespacedName) *apiworkl func initServiceProfileDescriptorSpec() *apiworkload.ServiceProfileDescriptorSpec { return &apiworkload.ServiceProfileDescriptorSpec{ + ExtendedIndicator: []apiworkload.ServiceExtendedIndicatorSpec{}, BusinessIndicator: []apiworkload.ServiceBusinessIndicatorSpec{}, SystemIndicator: []apiworkload.ServiceSystemIndicatorSpec{}, } diff --git a/pkg/controller/spd/indicator-plugin/plugin.go b/pkg/controller/spd/indicator-plugin/plugin.go index a8744c07df..686b40b5e9 100644 --- a/pkg/controller/spd/indicator-plugin/plugin.go +++ b/pkg/controller/spd/indicator-plugin/plugin.go @@ -41,6 +41,7 @@ type IndicatorPlugin interface { // is not supported by any indicator plugin, the controller will clear in CR. GetSupportedBusinessIndicatorSpec() []apiworkload.ServiceBusinessIndicatorName GetSupportedSystemIndicatorSpec() []apiworkload.ServiceSystemIndicatorName + GetSupportedExtendedIndicatorSpec() []string GetSupportedBusinessIndicatorStatus() []apiworkload.ServiceBusinessIndicatorName } @@ -48,6 +49,7 @@ type DummyIndicatorPlugin struct { SystemSpecNames []apiworkload.ServiceSystemIndicatorName BusinessSpecNames []apiworkload.ServiceBusinessIndicatorName BusinessStatusNames []apiworkload.ServiceBusinessIndicatorName + ExtendedSpecNames []string } var _ IndicatorPlugin = DummyIndicatorPlugin{} @@ -60,6 +62,9 @@ func (d DummyIndicatorPlugin) GetSupportedBusinessIndicatorSpec() []apiworkload. func (d DummyIndicatorPlugin) GetSupportedSystemIndicatorSpec() []apiworkload.ServiceSystemIndicatorName { return d.SystemSpecNames } +func (d DummyIndicatorPlugin) GetSupportedExtendedIndicatorSpec() []string { + return d.ExtendedSpecNames +} func (d DummyIndicatorPlugin) GetSupportedBusinessIndicatorStatus() []apiworkload.ServiceBusinessIndicatorName { return d.BusinessStatusNames } diff --git a/pkg/controller/spd/spd.go b/pkg/controller/spd/spd.go index bf86b6a3eb..79b9703a7a 100644 --- a/pkg/controller/spd/spd.go +++ b/pkg/controller/spd/spd.go @@ -95,6 +95,7 @@ type SPDController struct { indicatorManager *indicator_plugin.IndicatorManager indicatorPlugins map[string]indicator_plugin.IndicatorPlugin indicatorsSpecBusiness map[apiworkload.ServiceBusinessIndicatorName]interface{} + indicatorsSpecExtended map[string]interface{} indicatorsSpecSystem map[apiworkload.ServiceSystemIndicatorName]interface{} indicatorsStatusBusiness map[apiworkload.ServiceBusinessIndicatorName]interface{} } @@ -247,6 +248,7 @@ func (sc *SPDController) initializeIndicatorPlugins(controlCtx *katalystbase.Gen sc.indicatorPlugins = make(map[string]indicator_plugin.IndicatorPlugin) sc.indicatorsSpecBusiness = make(map[apiworkload.ServiceBusinessIndicatorName]interface{}) sc.indicatorsSpecSystem = make(map[apiworkload.ServiceSystemIndicatorName]interface{}) + sc.indicatorsSpecExtended = make(map[string]interface{}) sc.indicatorsStatusBusiness = make(map[apiworkload.ServiceBusinessIndicatorName]interface{}) initializers := indicator_plugin.GetPluginInitializers() @@ -266,6 +268,9 @@ func (sc *SPDController) initializeIndicatorPlugins(controlCtx *katalystbase.Gen for _, name := range plugin.GetSupportedSystemIndicatorSpec() { sc.indicatorsSpecSystem[name] = struct{}{} } + for _, name := range plugin.GetSupportedExtendedIndicatorSpec() { + sc.indicatorsSpecExtended[name] = struct{}{} + } for _, name := range plugin.GetSupportedBusinessIndicatorStatus() { sc.indicatorsStatusBusiness[name] = struct{}{} } @@ -596,7 +601,11 @@ func (sc *SPDController) getOrCreateSPDForWorkload(workload *unstructured.Unstru AggMetrics: []apiworkload.AggPodMetrics{}, }, } - sc.updateBaselineSentinel(spd) + + err := sc.updateBaselineSentinel(spd) + if err != nil { + return nil, err + } return sc.spdControl.CreateSPD(sc.ctx, spd, metav1.CreateOptions{}) } diff --git a/pkg/controller/spd/spd_baseline.go b/pkg/controller/spd/spd_baseline.go index 7bb0dd29e4..53965838fb 100644 --- a/pkg/controller/spd/spd_baseline.go +++ b/pkg/controller/spd/spd_baseline.go @@ -37,46 +37,80 @@ func (sc *SPDController) updateBaselineSentinel(spd *v1alpha1.ServiceProfileDesc return nil } - if spd.Spec.BaselinePercent == nil || *spd.Spec.BaselinePercent >= consts.SPDBaselinePercentMax || *spd.Spec.BaselinePercent <= consts.SPDBaselinePercentMin { + // delete baseline sentinel annotation if baseline percent or extended indicator not set + if spd.Spec.BaselinePercent == nil && len(spd.Spec.ExtendedIndicator) == 0 { + util.SetSPDBaselineSentinel(spd, nil) + util.SetSPDExtendedBaselineSentinel(spd, nil) return nil } - podMeta, err := sc.calculateBaselineSentinel(spd) + podMetaList, err := sc.getSPDPodMetaList(spd) if err != nil { return err } - util.SetSPDBaselineSentinel(spd, &podMeta) + + // calculate baseline sentinel + baselineSentinel := calculateBaselineSentinel(podMetaList, spd.Spec.BaselinePercent) + + // calculate extended baseline sentinel for each extended indicator + extendedBaselineSentinel := make(map[string]util.SPDBaselinePodMeta) + for _, indicator := range spd.Spec.ExtendedIndicator { + sentinel := calculateBaselineSentinel(podMetaList, indicator.BaselinePercent) + if sentinel == nil { + continue + } + + extendedBaselineSentinel[indicator.Name] = *sentinel + } + + util.SetSPDBaselineSentinel(spd, baselineSentinel) + util.SetSPDExtendedBaselineSentinel(spd, extendedBaselineSentinel) return nil } -// calculateBaselineSentinel returns the sentinel one for a list of pods -// referenced by the SPD. If one pod's createTime is less than the sentinel pod -func (sc *SPDController) calculateBaselineSentinel(spd *v1alpha1.ServiceProfileDescriptor) (util.SPDBaselinePodMeta, error) { +// getSPDPodMetaList get spd pod meta list in order +func (sc *SPDController) getSPDPodMetaList(spd *v1alpha1.ServiceProfileDescriptor) ([]util.SPDBaselinePodMeta, error) { gvr, _ := meta.UnsafeGuessKindToResource(schema.FromAPIVersionAndKind(spd.Spec.TargetRef.APIVersion, spd.Spec.TargetRef.Kind)) workloadLister, ok := sc.workloadLister[gvr] if !ok { - return util.SPDBaselinePodMeta{}, fmt.Errorf("without workload lister for gvr %v", gvr) + return nil, fmt.Errorf("without workload lister for gvr %v", gvr) } podList, err := util.GetPodListForSPD(spd, sc.podIndexer, sc.conf.SPDPodLabelIndexerKeys, workloadLister, sc.podLister) if err != nil { - return util.SPDBaselinePodMeta{}, err + return nil, err } podList = native.FilterPods(podList, func(pod *v1.Pod) (bool, error) { return native.PodIsActive(pod), nil }) if len(podList) == 0 { - return util.SPDBaselinePodMeta{}, nil + return nil, nil } - bcList := make([]util.SPDBaselinePodMeta, 0, len(podList)) + podMetaList := make([]util.SPDBaselinePodMeta, 0, len(podList)) for _, p := range podList { - bcList = append(bcList, util.GetPodMeta(p)) + podMetaList = append(podMetaList, util.GetPodMeta(p)) } - sort.SliceStable(bcList, func(i, j int) bool { - return bcList[i].Cmp(bcList[j]) < 0 + sort.SliceStable(podMetaList, func(i, j int) bool { + return podMetaList[i].Cmp(podMetaList[j]) < 0 }) - baselineIndex := int(math.Floor(float64(len(bcList)-1) * float64(*spd.Spec.BaselinePercent) / 100)) - return bcList[baselineIndex], nil + + return podMetaList, nil +} + +// calculateBaselineSentinel returns the sentinel one for a list of pods +// referenced by the SPD. If one pod's createTime is less than the sentinel pod +func calculateBaselineSentinel(podMetaList []util.SPDBaselinePodMeta, baselinePercent *int32) *util.SPDBaselinePodMeta { + if baselinePercent == nil || *baselinePercent >= consts.SPDBaselinePercentMax || + *baselinePercent <= consts.SPDBaselinePercentMin { + return nil + } + + if len(podMetaList) == 0 { + return nil + } + + baselineIndex := int(math.Floor(float64(len(podMetaList)-1) * float64(*baselinePercent) / 100)) + return &podMetaList[baselineIndex] } diff --git a/pkg/controller/spd/spd_baseline_test.go b/pkg/controller/spd/spd_baseline_test.go index 3e77d7ec80..8b581337cd 100644 --- a/pkg/controller/spd/spd_baseline_test.go +++ b/pkg/controller/spd/spd_baseline_test.go @@ -177,9 +177,6 @@ func TestSPDController_updateBaselinePercentile(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Namespace: "default", Name: "spd1", - Annotations: map[string]string{ - consts.SPDAnnotationBaselineSentinelKey: util.SPDBaselinePodMeta{}.String(), - }, }, Spec: apiworkload.ServiceProfileDescriptorSpec{ TargetRef: apis.CrossVersionObjectReference{ @@ -288,6 +285,15 @@ func TestSPDController_updateBaselinePercentile(t *testing.T) { APIVersion: stsGVK.GroupVersion().String(), }, BaselinePercent: pointer.Int32(50), + ExtendedIndicator: []apiworkload.ServiceExtendedIndicatorSpec{ + { + Name: "TestExtended", + BaselinePercent: pointer.Int32(50), + Indicators: runtime.RawExtension{ + Object: &apiworkload.TestExtendedIndicators{}, + }, + }, + }, }, Status: apiworkload.ServiceProfileDescriptorStatus{}, }, @@ -297,7 +303,8 @@ func TestSPDController_updateBaselinePercentile(t *testing.T) { Namespace: "default", Name: "spd1", Annotations: map[string]string{ - consts.SPDAnnotationBaselineSentinelKey: "{\"timeStamp\":\"2023-08-01T00:00:01Z\",\"podName\":\"pod2\"}", + consts.SPDAnnotationBaselineSentinelKey: "{\"timeStamp\":\"2023-08-01T00:00:01Z\",\"podName\":\"pod2\"}", + consts.SPDAnnotationExtendedBaselineSentinelKey: "{\"TestExtended\":{\"timeStamp\":\"2023-08-01T00:00:01Z\",\"podName\":\"pod2\"}}", }, }, Spec: apiworkload.ServiceProfileDescriptorSpec{ @@ -307,6 +314,15 @@ func TestSPDController_updateBaselinePercentile(t *testing.T) { APIVersion: stsGVK.GroupVersion().String(), }, BaselinePercent: pointer.Int32(50), + ExtendedIndicator: []apiworkload.ServiceExtendedIndicatorSpec{ + { + Name: "TestExtended", + BaselinePercent: pointer.Int32(50), + Indicators: runtime.RawExtension{ + Object: &apiworkload.TestExtendedIndicators{}, + }, + }, + }, }, Status: apiworkload.ServiceProfileDescriptorStatus{}, }, diff --git a/pkg/controller/spd/spd_indicator.go b/pkg/controller/spd/spd_indicator.go index 04f91436a8..5ed1ad289f 100644 --- a/pkg/controller/spd/spd_indicator.go +++ b/pkg/controller/spd/spd_indicator.go @@ -186,6 +186,9 @@ func (sc *SPDController) mergeIndicatorSpec(spd *apiworkload.ServiceProfileDescr for _, indicator := range expected.SystemIndicator { util.InsertSPDSystemIndicatorSpec(&spd.Spec, &indicator) } + for _, indicator := range expected.ExtendedIndicator { + util.InsertSPDExtendedIndicatorSpec(&spd.Spec, &indicator) + } for i := 0; i < len(spd.Spec.BusinessIndicator); i++ { if _, ok := sc.indicatorsSpecBusiness[spd.Spec.BusinessIndicator[i].Name]; !ok { @@ -200,6 +203,13 @@ func (sc *SPDController) mergeIndicatorSpec(spd *apiworkload.ServiceProfileDescr spd.Spec.SystemIndicator = append(spd.Spec.SystemIndicator[:i], spd.Spec.SystemIndicator[i+1:]...) } } + + for i := 0; i < len(spd.Spec.ExtendedIndicator); i++ { + if _, ok := sc.indicatorsSpecExtended[spd.Spec.ExtendedIndicator[i].Name]; !ok { + klog.Infof("skip spec extended %v for spd %v", spd.Spec.ExtendedIndicator[i].Name, spd.Name) + spd.Spec.ExtendedIndicator = append(spd.Spec.ExtendedIndicator[:i], spd.Spec.ExtendedIndicator[i+1:]...) + } + } } func (sc *SPDController) mergeIndicatorStatus(spd *apiworkload.ServiceProfileDescriptor, expected apiworkload.ServiceProfileDescriptorStatus) { diff --git a/pkg/controller/spd/spd_test.go b/pkg/controller/spd/spd_test.go index 09a66b9050..d99bbdae2d 100644 --- a/pkg/controller/spd/spd_test.go +++ b/pkg/controller/spd/spd_test.go @@ -535,6 +535,16 @@ func TestIndicatorUpdater(t *testing.T) { APIVersion: stsGVK.GroupVersion().String(), }, BaselinePercent: pointer.Int32(20), + ExtendedIndicator: []apiworkload.ServiceExtendedIndicatorSpec{ + { + Name: "TestExtended", + Indicators: runtime.RawExtension{ + Object: &apiworkload.TestExtendedIndicators{ + Indicators: &apiworkload.TestIndicators{}, + }, + }, + }, + }, BusinessIndicator: []apiworkload.ServiceBusinessIndicatorSpec{ { Name: "business-1", @@ -614,6 +624,9 @@ func TestIndicatorUpdater(t *testing.T) { } d1 := indicator_plugin.DummyIndicatorPlugin{ + ExtendedSpecNames: []string{ + "TestExtended", + }, SystemSpecNames: []apiworkload.ServiceSystemIndicatorName{ "system-1", }, @@ -671,6 +684,17 @@ func TestIndicatorUpdater(t *testing.T) { synced := cache.WaitForCacheSync(ctx.Done(), sc.syncedFunc...) assert.True(t, synced) + sc.indicatorManager.UpdateExtendedIndicatorSpec(nn, []apiworkload.ServiceExtendedIndicatorSpec{ + { + Name: "TestExtended", + Indicators: runtime.RawExtension{ + Object: &apiworkload.TestExtendedIndicators{ + Indicators: &apiworkload.TestIndicators{}, + }, + }, + }, + }) + sc.indicatorManager.UpdateBusinessIndicatorSpec(nn, []apiworkload.ServiceBusinessIndicatorSpec{ { Name: "business-1", @@ -753,6 +777,7 @@ func TestIndicatorUpdater(t *testing.T) { newSPD, err := controlCtx.Client.InternalClient.WorkloadV1alpha1(). ServiceProfileDescriptors("default").Get(ctx, "spd1", metav1.GetOptions{}) assert.NoError(t, err) + assert.Equal(t, expectedSpd.Spec.ExtendedIndicator, newSPD.Spec.ExtendedIndicator) assert.Equal(t, expectedSpd.Spec.BusinessIndicator, newSPD.Spec.BusinessIndicator) assert.Equal(t, expectedSpd.Spec.SystemIndicator, newSPD.Spec.SystemIndicator) assert.Equal(t, expectedSpd.Status.BusinessStatus, newSPD.Status.BusinessStatus) diff --git a/pkg/metaserver/spd/manager.go b/pkg/metaserver/spd/manager.go index b381c20832..52fafd9a06 100644 --- a/pkg/metaserver/spd/manager.go +++ b/pkg/metaserver/spd/manager.go @@ -19,11 +19,15 @@ package spd import ( "context" "fmt" + "reflect" + "strings" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + workloadapis "github.com/kubewharf/katalyst-api/pkg/apis/workload/v1alpha1" "github.com/kubewharf/katalyst-core/pkg/util" ) @@ -57,6 +61,9 @@ type ServiceProfilingManager interface { // ServiceBaseline returns whether this pod is baseline ServiceBaseline(ctx context.Context, pod *v1.Pod) (bool, error) + // ServiceExtendedIndicator load the extended indicators and return whether the pod is baseline for the extended indicators + ServiceExtendedIndicator(ctx context.Context, pod *v1.Pod, indicators interface{}) (bool, error) + // Run starts the service profiling manager Run(ctx context.Context) } @@ -70,7 +77,11 @@ type DummyServiceProfilingManager struct { podProfiles map[types.UID]DummyPodServiceProfile } -func (d *DummyServiceProfilingManager) ServiceBaseline(ctx context.Context, pod *v1.Pod) (bool, error) { +func (d *DummyServiceProfilingManager) ServiceExtendedIndicator(_ context.Context, _ *v1.Pod, _ interface{}) (bool, error) { + return false, nil +} + +func (d *DummyServiceProfilingManager) ServiceBaseline(_ context.Context, _ *v1.Pod) (bool, error) { return false, nil } @@ -106,6 +117,50 @@ type serviceProfilingManager struct { fetcher SPDFetcher } +func (m *serviceProfilingManager) ServiceExtendedIndicator(ctx context.Context, pod *v1.Pod, indicators interface{}) (bool, error) { + spd, err := m.fetcher.GetSPD(ctx, pod) + if err != nil { + return false, err + } + + extendedBaselineSentinel, err := util.GetSPDExtendedBaselineSentinel(spd) + if err != nil { + return false, err + } + + name, o, err := util.GetExtendedIndicator(indicators) + if err != nil { + return false, err + } + + for _, indicator := range spd.Spec.ExtendedIndicator { + if indicator.Name != name { + continue + } + + object := indicator.Indicators.Object + if object == nil { + return false, fmt.Errorf("%s inidators object is nil", name) + } + + t := reflect.TypeOf(indicators) + if t.Kind() != reflect.Ptr { + return false, fmt.Errorf("indicators must be pointers to structs") + } + + v := reflect.ValueOf(object) + if !v.CanConvert(t) { + return false, fmt.Errorf("%s indicators object cannot convert to %v", name, t.Name()) + } + + reflect.ValueOf(indicators).Elem().Set(v.Convert(t).Elem()) + return util.IsExtendedBaselinePod(pod, indicator.BaselinePercent, extendedBaselineSentinel, name) + } + + return false, errors.NewNotFound(schema.GroupResource{Group: workloadapis.GroupName, + Resource: strings.ToLower(o.GetObjectKind().GroupVersionKind().Kind)}, name) +} + func (m *serviceProfilingManager) ServiceBaseline(ctx context.Context, pod *v1.Pod) (bool, error) { spd, err := m.fetcher.GetSPD(ctx, pod) if err != nil && !errors.IsNotFound(err) { @@ -114,16 +169,17 @@ func (m *serviceProfilingManager) ServiceBaseline(ctx context.Context, pod *v1.P return false, nil } - baselinePod, enable, err := util.IsBaselinePod(pod, spd) + baselineSentinel, err := util.GetSPDBaselineSentinel(spd) if err != nil { return false, err } - if enable { - return baselinePod, nil + isBaseline, err := util.IsBaselinePod(pod, spd.Spec.BaselinePercent, baselineSentinel) + if err != nil { + return false, err } - return false, nil + return isBaseline, nil } func NewServiceProfilingManager(fetcher SPDFetcher) ServiceProfilingManager { diff --git a/pkg/metaserver/spd/manager_test.go b/pkg/metaserver/spd/manager_test.go index 0cc62075eb..a4dcd2fe95 100644 --- a/pkg/metaserver/spd/manager_test.go +++ b/pkg/metaserver/spd/manager_test.go @@ -422,3 +422,321 @@ func Test_serviceProfilingManager_ServiceSystemPerformanceTarget(t *testing.T) { }) } } + +func Test_serviceProfilingManager_ServiceExtendedIndicator(t *testing.T) { + t.Parallel() + + type fields struct { + nodeName string + spd *workloadapis.ServiceProfileDescriptor + cnc *v1alpha1.CustomNodeConfig + } + type args struct { + pod *v1.Pod + } + tests := []struct { + name string + fields fields + args args + want *workloadapis.TestExtendedIndicators + isBaseline bool + wantErr bool + }{ + { + name: "without baseline", + fields: fields{ + nodeName: "node-1", + spd: &workloadapis.ServiceProfileDescriptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "spd-1", + Namespace: "default", + Annotations: map[string]string{ + pkgconsts.ServiceProfileDescriptorAnnotationKeyConfigHash: "3c7e3ff3f218", + }, + }, + Spec: workloadapis.ServiceProfileDescriptorSpec{ + ExtendedIndicator: []workloadapis.ServiceExtendedIndicatorSpec{ + { + Name: "TestExtended", + Indicators: runtime.RawExtension{ + Object: &workloadapis.TestExtendedIndicators{ + Indicators: &workloadapis.TestIndicators{}, + }, + }, + }, + }, + }, + }, + cnc: &v1alpha1.CustomNodeConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-1", + }, + Status: v1alpha1.CustomNodeConfigStatus{ + ServiceProfileConfigList: []v1alpha1.TargetConfig{ + { + ConfigName: "spd-1", + ConfigNamespace: "default", + Hash: "3c7e3ff3f218", + }, + }, + }, + }, + }, + args: args{ + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + Annotations: map[string]string{ + consts.PodAnnotationSPDNameKey: "spd-1", + }, + }, + }, + }, + want: &workloadapis.TestExtendedIndicators{ + Indicators: &workloadapis.TestIndicators{}, + }, + }, + { + name: "with baseline", + fields: fields{ + nodeName: "node-1", + spd: &workloadapis.ServiceProfileDescriptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "spd-1", + Namespace: "default", + Annotations: map[string]string{ + pkgconsts.ServiceProfileDescriptorAnnotationKeyConfigHash: "3c7e3ff3f218", + consts.SPDAnnotationExtendedBaselineSentinelKey: "{\"TestExtended\":{\"timeStamp\":\"2023-08-01T00:00:01Z\",\"podName\":\"pod1\"}}", + }, + }, + Spec: workloadapis.ServiceProfileDescriptorSpec{ + ExtendedIndicator: []workloadapis.ServiceExtendedIndicatorSpec{ + { + Name: "TestExtended", + BaselinePercent: pointer.Int32(10), + Indicators: runtime.RawExtension{ + Object: &workloadapis.TestExtendedIndicators{ + Indicators: &workloadapis.TestIndicators{}, + }, + }, + }, + }, + }, + }, + cnc: &v1alpha1.CustomNodeConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-1", + }, + Status: v1alpha1.CustomNodeConfigStatus{ + ServiceProfileConfigList: []v1alpha1.TargetConfig{ + { + ConfigName: "spd-1", + ConfigNamespace: "default", + Hash: "3c7e3ff3f218", + }, + }, + }, + }, + }, + args: args{ + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + Annotations: map[string]string{ + consts.PodAnnotationSPDNameKey: "spd-1", + }, + }, + }, + }, + want: &workloadapis.TestExtendedIndicators{ + Indicators: &workloadapis.TestIndicators{}, + }, + isBaseline: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir, err := ioutil.TempDir("", "checkpoint-Test_serviceProfilingManager_ServiceExtendedIndicator") + require.NoError(t, err) + defer os.RemoveAll(dir) + + conf := generateTestConfiguration(t, tt.fields.nodeName, dir) + genericCtx, err := katalyst_base.GenerateFakeGenericContext(nil, []runtime.Object{ + tt.fields.spd, + tt.fields.cnc, + }) + require.NoError(t, err) + + cncFetcher := cnc.NewCachedCNCFetcher(conf.BaseConfiguration, conf.CNCConfiguration, genericCtx.Client.InternalClient.ConfigV1alpha1().CustomNodeConfigs()) + s, err := NewSPDFetcher(genericCtx.Client, metrics.DummyMetrics{}, cncFetcher, conf) + require.NoError(t, err) + require.NotNil(t, s) + + m := NewServiceProfilingManager(s) + require.NoError(t, err) + + // first get spd add pod spd key to cache + _, _ = s.GetSPD(context.Background(), tt.args.pod) + go m.Run(context.Background()) + time.Sleep(1 * time.Second) + + got := &workloadapis.TestExtendedIndicators{} + isBaseline, err := m.ServiceExtendedIndicator(context.Background(), tt.args.pod, got) + if (err != nil) != tt.wantErr { + t.Errorf("ServiceExtendedIndicator() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !apiequality.Semantic.DeepEqual(got, tt.want) { + t.Errorf("ServiceExtendedIndicator() got = %v, want %v", got, tt.want) + } + if isBaseline != tt.isBaseline { + t.Errorf("ServiceExtendedIndicator() isBaseline = %v, want %v", isBaseline, tt.isBaseline) + } + }) + } +} + +func Test_serviceProfilingManager_ServiceBaseline(t *testing.T) { + t.Parallel() + + type fields struct { + nodeName string + spd *workloadapis.ServiceProfileDescriptor + cnc *v1alpha1.CustomNodeConfig + } + type args struct { + pod *v1.Pod + } + tests := []struct { + name string + fields fields + args args + isBaseline bool + wantErr bool + }{ + { + name: "without baseline", + fields: fields{ + nodeName: "node-1", + spd: &workloadapis.ServiceProfileDescriptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "spd-1", + Namespace: "default", + Annotations: map[string]string{ + pkgconsts.ServiceProfileDescriptorAnnotationKeyConfigHash: "3c7e3ff3f218", + }, + }, + Spec: workloadapis.ServiceProfileDescriptorSpec{}, + }, + cnc: &v1alpha1.CustomNodeConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-1", + }, + Status: v1alpha1.CustomNodeConfigStatus{ + ServiceProfileConfigList: []v1alpha1.TargetConfig{ + { + ConfigName: "spd-1", + ConfigNamespace: "default", + Hash: "3c7e3ff3f218", + }, + }, + }, + }, + }, + args: args{ + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + Annotations: map[string]string{ + consts.PodAnnotationSPDNameKey: "spd-1", + }, + }, + }, + }, + }, + { + name: "with baseline", + fields: fields{ + nodeName: "node-1", + spd: &workloadapis.ServiceProfileDescriptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "spd-1", + Namespace: "default", + Annotations: map[string]string{ + pkgconsts.ServiceProfileDescriptorAnnotationKeyConfigHash: "3c7e3ff3f218", + consts.SPDAnnotationBaselineSentinelKey: "{\"timeStamp\":\"2023-08-01T00:00:01Z\",\"podName\":\"pod1\"}", + }, + }, + Spec: workloadapis.ServiceProfileDescriptorSpec{ + BaselinePercent: pointer.Int32(10), + }, + }, + cnc: &v1alpha1.CustomNodeConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-1", + }, + Status: v1alpha1.CustomNodeConfigStatus{ + ServiceProfileConfigList: []v1alpha1.TargetConfig{ + { + ConfigName: "spd-1", + ConfigNamespace: "default", + Hash: "3c7e3ff3f218", + }, + }, + }, + }, + }, + args: args{ + pod: &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: "default", + Annotations: map[string]string{ + consts.PodAnnotationSPDNameKey: "spd-1", + }, + }, + }, + }, + isBaseline: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dir, err := ioutil.TempDir("", "checkpoint-Test_serviceProfilingManager_ServiceBaseline") + require.NoError(t, err) + defer os.RemoveAll(dir) + + conf := generateTestConfiguration(t, tt.fields.nodeName, dir) + genericCtx, err := katalyst_base.GenerateFakeGenericContext(nil, []runtime.Object{ + tt.fields.spd, + tt.fields.cnc, + }) + require.NoError(t, err) + + cncFetcher := cnc.NewCachedCNCFetcher(conf.BaseConfiguration, conf.CNCConfiguration, genericCtx.Client.InternalClient.ConfigV1alpha1().CustomNodeConfigs()) + s, err := NewSPDFetcher(genericCtx.Client, metrics.DummyMetrics{}, cncFetcher, conf) + require.NoError(t, err) + require.NotNil(t, s) + + m := NewServiceProfilingManager(s) + require.NoError(t, err) + + // first get spd add pod spd key to cache + _, _ = s.GetSPD(context.Background(), tt.args.pod) + go m.Run(context.Background()) + time.Sleep(1 * time.Second) + + isBaseline, err := m.ServiceBaseline(context.Background(), tt.args.pod) + if (err != nil) != tt.wantErr { + t.Errorf("ServiceBaseline() error = %v, wantErr %v", err, tt.wantErr) + return + } + if isBaseline != tt.isBaseline { + t.Errorf("ServiceBaseline() isBaseline = %v, want %v", isBaseline, tt.isBaseline) + } + }) + } +} diff --git a/pkg/util/spd.go b/pkg/util/spd.go index f083d57e59..c87757a3fb 100644 --- a/pkg/util/spd.go +++ b/pkg/util/spd.go @@ -19,6 +19,8 @@ package util import ( "encoding/json" "fmt" + "reflect" + "strings" "github.com/pkg/errors" core "k8s.io/api/core/v1" @@ -252,6 +254,26 @@ func InsertSPDSystemIndicatorSpec(spec *apiworkload.ServiceProfileDescriptorSpec spec.SystemIndicator = append(spec.SystemIndicator, *serviceSystemIndicatorSpec) } +func InsertSPDExtendedIndicatorSpec(spec *apiworkload.ServiceProfileDescriptorSpec, + serviceExtendedIndicatorSpec *apiworkload.ServiceExtendedIndicatorSpec) { + if spec == nil || serviceExtendedIndicatorSpec == nil { + return + } + + if spec.ExtendedIndicator == nil { + spec.ExtendedIndicator = []apiworkload.ServiceExtendedIndicatorSpec{} + } + + for i := range spec.ExtendedIndicator { + if spec.ExtendedIndicator[i].Name == serviceExtendedIndicatorSpec.Name { + spec.ExtendedIndicator[i].BaselinePercent = serviceExtendedIndicatorSpec.BaselinePercent + spec.ExtendedIndicator[i].Indicators = serviceExtendedIndicatorSpec.Indicators + return + } + } + spec.ExtendedIndicator = append(spec.ExtendedIndicator, *serviceExtendedIndicatorSpec) +} + func InsertSPDBusinessIndicatorStatus(status *apiworkload.ServiceProfileDescriptorStatus, serviceBusinessIndicatorStatus *apiworkload.ServiceBusinessIndicatorStatus) { if status == nil || serviceBusinessIndicatorStatus == nil { @@ -303,11 +325,16 @@ func CalculateSPDHash(spd *apiworkload.ServiceProfileDescriptor) (string, error) } spdCopy := &apiworkload.ServiceProfileDescriptor{} + spdCopy.Annotations = make(map[string]string) + if sentinel, ok := spd.Annotations[apiconsts.SPDAnnotationBaselineSentinelKey]; ok { - spd.Annotations = map[string]string{ - apiconsts.SPDAnnotationBaselineSentinelKey: sentinel, - } + spdCopy.Annotations[apiconsts.SPDAnnotationBaselineSentinelKey] = sentinel } + + if sentinel, ok := spd.Annotations[apiconsts.SPDAnnotationExtendedBaselineSentinelKey]; ok { + spdCopy.Annotations[apiconsts.SPDAnnotationExtendedBaselineSentinelKey] = sentinel + } + spdCopy.Spec = spd.Spec spdCopy.Status = spd.Status data, err := json.Marshal(spdCopy) @@ -331,3 +358,46 @@ func GetPodSPDName(pod *core.Pod) (string, error) { return spdName, nil } + +// GetExtendedIndicatorSpec get extended indicator spec by baseline percent and indicators. +// The indicators must be a pointer to a struct that has a suffix "Indicators" in its name +// and the indicators must be an implement of runtime.Object and use AddKnownTypes add to scheme +// with the same group and version as the spd +func GetExtendedIndicatorSpec(baselinePercent *int32, indicators interface{}) (*apiworkload.ServiceExtendedIndicatorSpec, error) { + name, o, err := GetExtendedIndicator(indicators) + if err != nil { + return nil, err + } + + return &apiworkload.ServiceExtendedIndicatorSpec{ + Name: name, + BaselinePercent: baselinePercent, + Indicators: runtime.RawExtension{ + Object: o, + }, + }, nil +} + +// GetExtendedIndicator get extended indicator name and object +func GetExtendedIndicator(indicators interface{}) (string, runtime.Object, error) { + if indicators == nil { + return "", nil, fmt.Errorf("extended indicators is nil") + } + + t := reflect.TypeOf(indicators) + if t.Kind() != reflect.Ptr { + return "", nil, fmt.Errorf("extended indicators must be pointers to structs") + } + + o, ok := indicators.(runtime.Object) + if !ok { + return "", nil, fmt.Errorf("extended indicators must be an implement of runtime.Object") + } + + name := t.Elem().Name() + if !strings.HasSuffix(name, apiworkload.ExtendedIndicatorSuffix) { + return "", nil, fmt.Errorf("extended indicators must have suffix 'Indicators'") + } + + return strings.TrimSuffix(name, apiworkload.ExtendedIndicatorSuffix), o, nil +} diff --git a/pkg/util/spd_baseline.go b/pkg/util/spd_baseline.go index e5295de559..f1265e1dea 100644 --- a/pkg/util/spd_baseline.go +++ b/pkg/util/spd_baseline.go @@ -57,34 +57,47 @@ func (c SPDBaselinePodMeta) String() string { return string(d) } -// IsBaselinePod check whether a pod is baseline pod and whether -// the spd baseline is enabled -func IsBaselinePod(pod *v1.Pod, spd *v1alpha1.ServiceProfileDescriptor) (bool, bool, error) { - if pod == nil || spd == nil { - return false, false, fmt.Errorf("pod or spd is nil") +// IsBaselinePod check whether a pod is baseline pod +func IsBaselinePod(pod *v1.Pod, baselinePercent *int32, baselineSentinel *SPDBaselinePodMeta) (bool, error) { + if pod == nil { + return false, fmt.Errorf("pod is nil") } // if spd baseline percent not config means baseline is disabled - if spd.Spec.BaselinePercent == nil { - return false, false, nil + if baselinePercent == nil { + return false, nil + } else if *baselinePercent >= consts.SPDBaselinePercentMax { + return true, nil + } else if *baselinePercent <= consts.SPDBaselinePercentMin { + return false, nil } - if *spd.Spec.BaselinePercent >= consts.SPDBaselinePercentMax { - return true, true, nil - } else if *spd.Spec.BaselinePercent <= consts.SPDBaselinePercentMin { - return false, true, nil + + if baselineSentinel == nil { + return false, fmt.Errorf("baseline percent is already set but baseline sentinel is nil") } - bp, err := GetSPDBaselineSentinel(spd) - if err != nil { - return false, false, err + pm := GetPodMeta(pod) + if pm.Cmp(*baselineSentinel) <= 0 { + return true, nil } - bc := GetPodMeta(pod) - if bc.Cmp(bp) <= 0 { - return true, true, nil + return false, nil +} + +// IsExtendedBaselinePod check whether a pod is baseline pod by extended indicator +func IsExtendedBaselinePod(pod *v1.Pod, baselinePercent *int32, podMetaMap map[string]SPDBaselinePodMeta, name string) (bool, error) { + var baselineSentinel *SPDBaselinePodMeta + sentinel, ok := podMetaMap[name] + if ok { + baselineSentinel = &sentinel } - return false, true, nil + isBaseline, err := IsBaselinePod(pod, baselinePercent, baselineSentinel) + if err != nil { + return false, err + } + + return isBaseline, nil } // GetPodMeta get the baseline coefficient of this pod @@ -96,15 +109,19 @@ func GetPodMeta(pod *v1.Pod) SPDBaselinePodMeta { } // GetSPDBaselineSentinel get the baseline sentinel pod of this spd -func GetSPDBaselineSentinel(spd *v1alpha1.ServiceProfileDescriptor) (SPDBaselinePodMeta, error) { +func GetSPDBaselineSentinel(spd *v1alpha1.ServiceProfileDescriptor) (*SPDBaselinePodMeta, error) { s, ok := spd.Annotations[consts.SPDAnnotationBaselineSentinelKey] if !ok { - return SPDBaselinePodMeta{}, fmt.Errorf("spd baseline percentile not found") + return nil, nil + } + + bs := SPDBaselinePodMeta{} + err := json.Unmarshal([]byte(s), &bs) + if err != nil { + return nil, err } - bc := SPDBaselinePodMeta{} - err := json.Unmarshal([]byte(s), &bc) - return bc, err + return &bs, err } // SetSPDBaselineSentinel set the baseline percentile of this spd, if percentile is nil means delete it @@ -125,3 +142,44 @@ func SetSPDBaselineSentinel(spd *v1alpha1.ServiceProfileDescriptor, podMeta *SPD spd.Annotations[consts.SPDAnnotationBaselineSentinelKey] = podMeta.String() return } + +// GetSPDExtendedBaselineSentinel get the extended baseline sentinel pod of this spd +func GetSPDExtendedBaselineSentinel(spd *v1alpha1.ServiceProfileDescriptor) (map[string]SPDBaselinePodMeta, error) { + s, ok := spd.Annotations[consts.SPDAnnotationExtendedBaselineSentinelKey] + if !ok { + return nil, nil + } + + bs := map[string]SPDBaselinePodMeta{} + err := json.Unmarshal([]byte(s), &bs) + if err != nil { + return nil, err + } + + return bs, err +} + +// SetSPDExtendedBaselineSentinel set the extended baseline sentinel of this spd, if percentile is nil means delete it +func SetSPDExtendedBaselineSentinel(spd *v1alpha1.ServiceProfileDescriptor, podMetaMap map[string]SPDBaselinePodMeta) { + if spd == nil { + return + } + + if podMetaMap == nil || len(podMetaMap) == 0 { + delete(spd.Annotations, consts.SPDAnnotationExtendedBaselineSentinelKey) + return + } + + if spd.Annotations == nil { + spd.Annotations = make(map[string]string) + } + + extendedBaselineSentinel, err := json.Marshal(podMetaMap) + if err != nil { + spd.Annotations[consts.SPDAnnotationExtendedBaselineSentinelKey] = "" + } else { + spd.Annotations[consts.SPDAnnotationExtendedBaselineSentinelKey] = string(extendedBaselineSentinel) + } + + return +} diff --git a/pkg/util/spd_baseline_test.go b/pkg/util/spd_baseline_test.go index 37c47ce9f5..a487204829 100644 --- a/pkg/util/spd_baseline_test.go +++ b/pkg/util/spd_baseline_test.go @@ -182,15 +182,15 @@ func TestIsBaselinePod(t *testing.T) { t.Parallel() type args struct { - pod *v1.Pod - spd *v1alpha1.ServiceProfileDescriptor + pod *v1.Pod + baselinePercent *int32 + baselineSentinel *SPDBaselinePodMeta } tests := []struct { - name string - args args - wantIsBaselinePod bool - wantBaselineEnabled bool - wantErr bool + name string + args args + wantIsBaselinePod bool + wantErr bool }{ { name: "baseline pod", @@ -201,23 +201,13 @@ func TestIsBaselinePod(t *testing.T) { CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)), }, }, - spd: &v1alpha1.ServiceProfileDescriptor{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-spd", - Annotations: map[string]string{ - consts.SPDAnnotationBaselineSentinelKey: SPDBaselinePodMeta{ - TimeStamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)), - PodName: "test-pod", - }.String(), - }, - }, - Spec: v1alpha1.ServiceProfileDescriptorSpec{ - BaselinePercent: pointer.Int32(10), - }, + baselinePercent: pointer.Int32(10), + baselineSentinel: &SPDBaselinePodMeta{ + TimeStamp: metav1.NewTime(time.Date(2023, time.August, 1, 0, 0, 0, 0, time.UTC)), + PodName: "test-pod", }, }, - wantIsBaselinePod: true, - wantBaselineEnabled: true, + wantIsBaselinePod: true, }, { name: "not baseline pod", @@ -228,23 +218,13 @@ func TestIsBaselinePod(t *testing.T) { CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 2, 0, 0, 0, 0, time.UTC)), }, }, - spd: &v1alpha1.ServiceProfileDescriptor{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-spd", - Annotations: map[string]string{ - consts.SPDAnnotationBaselineSentinelKey: SPDBaselinePodMeta{ - TimeStamp: metav1.Time{}, - PodName: "", - }.String(), - }, - }, - Spec: v1alpha1.ServiceProfileDescriptorSpec{ - BaselinePercent: pointer.Int32(10), - }, + baselinePercent: pointer.Int32(10), + baselineSentinel: &SPDBaselinePodMeta{ + TimeStamp: metav1.Time{}, + PodName: "", }, }, - wantIsBaselinePod: false, - wantBaselineEnabled: true, + wantIsBaselinePod: false, }, { name: "baseline disabled", @@ -255,14 +235,10 @@ func TestIsBaselinePod(t *testing.T) { CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 2, 0, 0, 0, 0, time.UTC)), }, }, - spd: &v1alpha1.ServiceProfileDescriptor{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-spd", - }, - }, + baselinePercent: nil, + baselineSentinel: nil, }, - wantIsBaselinePod: false, - wantBaselineEnabled: false, + wantIsBaselinePod: false, }, { name: "baseline 100%", @@ -273,17 +249,10 @@ func TestIsBaselinePod(t *testing.T) { CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 2, 0, 0, 0, 0, time.UTC)), }, }, - spd: &v1alpha1.ServiceProfileDescriptor{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-spd", - }, - Spec: v1alpha1.ServiceProfileDescriptorSpec{ - BaselinePercent: pointer.Int32(100), - }, - }, + baselinePercent: pointer.Int32(100), + baselineSentinel: nil, }, - wantIsBaselinePod: true, - wantBaselineEnabled: true, + wantIsBaselinePod: true, }, { name: "baseline 0%", @@ -294,22 +263,15 @@ func TestIsBaselinePod(t *testing.T) { CreationTimestamp: metav1.NewTime(time.Date(2023, time.August, 2, 0, 0, 0, 0, time.UTC)), }, }, - spd: &v1alpha1.ServiceProfileDescriptor{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-spd", - }, - Spec: v1alpha1.ServiceProfileDescriptorSpec{ - BaselinePercent: pointer.Int32(0), - }, - }, + baselinePercent: pointer.Int32(0), + baselineSentinel: nil, }, - wantIsBaselinePod: false, - wantBaselineEnabled: true, + wantIsBaselinePod: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, got1, err := IsBaselinePod(tt.args.pod, tt.args.spd) + got, err := IsBaselinePod(tt.args.pod, tt.args.baselinePercent, tt.args.baselineSentinel) if (err != nil) != tt.wantErr { t.Errorf("IsBaselinePod() error = %v, wantErr %v", err, tt.wantErr) return @@ -317,9 +279,6 @@ func TestIsBaselinePod(t *testing.T) { if got != tt.wantIsBaselinePod { t.Errorf("IsBaselinePod() got = %v, want %v", got, tt.wantIsBaselinePod) } - if got1 != tt.wantBaselineEnabled { - t.Errorf("IsBaselinePod() got1 = %v, want %v", got1, tt.wantBaselineEnabled) - } }) } } diff --git a/pkg/util/spd_test.go b/pkg/util/spd_test.go index 4074a60572..f8466a774b 100644 --- a/pkg/util/spd_test.go +++ b/pkg/util/spd_test.go @@ -17,6 +17,7 @@ limitations under the License. package util import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -29,6 +30,7 @@ import ( "k8s.io/client-go/dynamic/dynamicinformer" dynamicfake "k8s.io/client-go/dynamic/fake" "k8s.io/client-go/tools/cache" + "k8s.io/utils/pointer" apis "github.com/kubewharf/katalyst-api/pkg/apis/autoscaling/v1alpha1" apiworkload "github.com/kubewharf/katalyst-api/pkg/apis/workload/v1alpha1" @@ -171,3 +173,149 @@ func TestGetSPDForPod(t *testing.T) { assert.Nil(t, s) assert.Error(t, err) } + +func TestGetExtendedIndicatorSpec(t *testing.T) { + type args struct { + baselinePercent *int32 + indicators interface{} + } + tests := []struct { + name string + args args + want *apiworkload.ServiceExtendedIndicatorSpec + wantErr assert.ErrorAssertionFunc + }{ + { + name: "nil indicators", + args: args{ + baselinePercent: nil, + indicators: nil, + }, + want: nil, + wantErr: assert.Error, + }, + { + name: "test extended indicator spec", + args: args{ + baselinePercent: nil, + indicators: &apiworkload.TestExtendedIndicators{ + Indicators: &apiworkload.TestIndicators{}, + }, + }, + want: &apiworkload.ServiceExtendedIndicatorSpec{ + Name: "TestExtended", + BaselinePercent: nil, + Indicators: runtime.RawExtension{ + Object: &apiworkload.TestExtendedIndicators{ + Indicators: &apiworkload.TestIndicators{}, + }, + }, + }, + wantErr: assert.NoError, + }, + { + name: "test not pointer", + args: args{ + baselinePercent: nil, + indicators: apiworkload.TestExtendedIndicators{}, + }, + want: nil, + wantErr: assert.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetExtendedIndicatorSpec(tt.args.baselinePercent, tt.args.indicators) + if !tt.wantErr(t, err, fmt.Sprintf("GetExtendedIndicatorSpec(%v, %v)", tt.args.baselinePercent, tt.args.indicators)) { + return + } + assert.Equalf(t, tt.want, got, "GetExtendedIndicatorSpec(%v, %v)", tt.args.baselinePercent, tt.args.indicators) + }) + } +} + +func TestCalculateSPDHash(t *testing.T) { + type args struct { + spd *apiworkload.ServiceProfileDescriptor + } + tests := []struct { + name string + args args + want string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "nil spd", + args: args{ + spd: nil, + }, + want: "", + wantErr: assert.Error, + }, + { + name: "test calculate spd hash", + args: args{ + spd: &apiworkload.ServiceProfileDescriptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "spd1", + Namespace: "default", + }, + Spec: apiworkload.ServiceProfileDescriptorSpec{ + BusinessIndicator: []apiworkload.ServiceBusinessIndicatorSpec{ + { + Name: "indicator1", + }, + }, + }, + }, + }, + want: "aad83840e233", + wantErr: assert.NoError, + }, + { + name: "test calculate spd hash with baseline", + args: args{ + spd: &apiworkload.ServiceProfileDescriptor{ + ObjectMeta: metav1.ObjectMeta{ + Name: "spd1", + Namespace: "default", + Annotations: map[string]string{ + apiconsts.SPDAnnotationBaselineSentinelKey: "{\"timeStamp\":\"1970-01-20T13:40:48Z\",\"podName\":\"test-spd\"}", + apiconsts.SPDAnnotationExtendedBaselineSentinelKey: "{\"TestExtended\":{\"timeStamp\":\"2023-08-01T00:00:01Z\",\"podName\":\"pod1\"}}", + }, + }, + Spec: apiworkload.ServiceProfileDescriptorSpec{ + BaselinePercent: pointer.Int32(80), + BusinessIndicator: []apiworkload.ServiceBusinessIndicatorSpec{ + { + Name: "indicator1", + }, + }, + ExtendedIndicator: []apiworkload.ServiceExtendedIndicatorSpec{ + { + Name: "TestExtended", + BaselinePercent: pointer.Int32(20), + Indicators: runtime.RawExtension{ + Object: &apiworkload.TestExtendedIndicators{ + Indicators: &apiworkload.TestIndicators{}, + }, + }, + }, + }, + }, + }, + }, + want: "3c853b718b3e", + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := CalculateSPDHash(tt.args.spd) + if !tt.wantErr(t, err, fmt.Sprintf("CalculateSPDHash(%v)", tt.args.spd)) { + return + } + assert.Equalf(t, tt.want, got, "CalculateSPDHash(%v)", tt.args.spd) + }) + } +}