diff --git a/pkg/resourceinterpreter/configurableinterpreter/configmanager/accessor.go b/pkg/resourceinterpreter/configurableinterpreter/configmanager/accessor.go index ff987c3cf..04d6a5c8d 100644 --- a/pkg/resourceinterpreter/configurableinterpreter/configmanager/accessor.go +++ b/pkg/resourceinterpreter/configurableinterpreter/configmanager/accessor.go @@ -1,15 +1,12 @@ package configmanager import ( - "k8s.io/apimachinery/pkg/runtime/schema" - configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1" ) // CustomConfiguration provides base information about custom interpreter configuration type CustomConfiguration interface { - Name() string - TargetResource() schema.GroupVersionKind + Merge(rules configv1alpha1.CustomizationRules) } // LuaScriptAccessor provides a common interface to get custom interpreter lua script @@ -38,22 +35,35 @@ type resourceCustomAccessor struct { statusAggregation *configv1alpha1.StatusAggregation healthInterpretation *configv1alpha1.HealthInterpretation dependencyInterpretation *configv1alpha1.DependencyInterpretation - configurationName string - configurationTargetGVK schema.GroupVersionKind } -// NewResourceCustomAccessorAccessor creates an accessor for resource interpreter customization -func NewResourceCustomAccessorAccessor(customization *configv1alpha1.ResourceInterpreterCustomization) CustomAccessor { - return &resourceCustomAccessor{ - retention: customization.Spec.Customizations.Retention, - replicaResource: customization.Spec.Customizations.ReplicaResource, - replicaRevision: customization.Spec.Customizations.ReplicaRevision, - statusReflection: customization.Spec.Customizations.StatusReflection, - statusAggregation: customization.Spec.Customizations.StatusAggregation, - healthInterpretation: customization.Spec.Customizations.HealthInterpretation, - dependencyInterpretation: customization.Spec.Customizations.DependencyInterpretation, - configurationName: customization.Name, - configurationTargetGVK: schema.FromAPIVersionAndKind(customization.Spec.Target.APIVersion, customization.Spec.Target.Kind), +// NewResourceCustomAccessor creates an accessor for resource interpreter customization. +func NewResourceCustomAccessor() CustomAccessor { + return &resourceCustomAccessor{} +} + +// Merge merges the given CustomizationRules with the current rules, ignore if duplicates occur. +func (a *resourceCustomAccessor) Merge(rules configv1alpha1.CustomizationRules) { + if rules.Retention != nil { + a.setRetain(rules.Retention) + } + if rules.ReplicaResource != nil { + a.setReplicaResource(rules.ReplicaResource) + } + if rules.ReplicaRevision != nil { + a.setReplicaRevision(rules.ReplicaRevision) + } + if rules.StatusReflection != nil { + a.setStatusReflection(rules.StatusReflection) + } + if rules.StatusAggregation != nil { + a.setStatusAggregation(rules.StatusAggregation) + } + if rules.HealthInterpretation != nil { + a.setHealthInterpretation(rules.HealthInterpretation) + } + if rules.DependencyInterpretation != nil { + a.setDependencyInterpretation(rules.DependencyInterpretation) } } @@ -106,10 +116,79 @@ func (a *resourceCustomAccessor) GetDependencyInterpretationLuaScript() string { return a.dependencyInterpretation.LuaScript } -func (a *resourceCustomAccessor) Name() string { - return a.configurationName +func (a *resourceCustomAccessor) setRetain(retention *configv1alpha1.LocalValueRetention) { + if a.retention == nil { + a.retention = retention + return + } + + if retention.LuaScript != "" && a.retention.LuaScript == "" { + a.retention.LuaScript = retention.LuaScript + } } -func (a *resourceCustomAccessor) TargetResource() schema.GroupVersionKind { - return a.configurationTargetGVK +func (a *resourceCustomAccessor) setReplicaResource(replicaResource *configv1alpha1.ReplicaResourceRequirement) { + if a.replicaResource == nil { + a.replicaResource = replicaResource + return + } + + if replicaResource.LuaScript != "" && a.replicaResource.LuaScript == "" { + a.replicaResource.LuaScript = replicaResource.LuaScript + } +} + +func (a *resourceCustomAccessor) setReplicaRevision(replicaRevision *configv1alpha1.ReplicaRevision) { + if a.replicaRevision == nil { + a.replicaRevision = replicaRevision + return + } + + if replicaRevision.LuaScript != "" && a.replicaRevision.LuaScript == "" { + a.replicaRevision.LuaScript = replicaRevision.LuaScript + } +} + +func (a *resourceCustomAccessor) setStatusReflection(statusReflection *configv1alpha1.StatusReflection) { + if a.statusReflection == nil { + a.statusReflection = statusReflection + return + } + + if statusReflection.LuaScript != "" && a.statusReflection.LuaScript == "" { + a.statusReflection.LuaScript = statusReflection.LuaScript + } +} + +func (a *resourceCustomAccessor) setStatusAggregation(statusAggregation *configv1alpha1.StatusAggregation) { + if a.statusAggregation == nil { + a.statusAggregation = statusAggregation + return + } + + if statusAggregation.LuaScript != "" && a.statusAggregation.LuaScript == "" { + a.statusAggregation.LuaScript = statusAggregation.LuaScript + } +} + +func (a *resourceCustomAccessor) setHealthInterpretation(healthInterpretation *configv1alpha1.HealthInterpretation) { + if a.healthInterpretation == nil { + a.healthInterpretation = healthInterpretation + return + } + + if healthInterpretation.LuaScript != "" && a.healthInterpretation.LuaScript == "" { + a.healthInterpretation.LuaScript = healthInterpretation.LuaScript + } +} + +func (a *resourceCustomAccessor) setDependencyInterpretation(dependencyInterpretation *configv1alpha1.DependencyInterpretation) { + if a.dependencyInterpretation == nil { + a.dependencyInterpretation = dependencyInterpretation + return + } + + if dependencyInterpretation.LuaScript != "" && a.dependencyInterpretation.LuaScript == "" { + a.dependencyInterpretation.LuaScript = dependencyInterpretation.LuaScript + } } diff --git a/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager.go b/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager.go index c20daf69a..404e174d6 100644 --- a/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager.go +++ b/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager.go @@ -2,6 +2,7 @@ package configmanager import ( "fmt" + "sort" "sync/atomic" "k8s.io/apimachinery/pkg/labels" @@ -82,17 +83,34 @@ func (configManager *interpreterConfigManager) updateConfiguration() { utilruntime.HandleError(fmt.Errorf("error updating configuration: %v", err)) return } - configs := make(map[schema.GroupVersionKind]CustomAccessor, len(configurations)) - for _, c := range configurations { + configs := make([]*configv1alpha1.ResourceInterpreterCustomization, len(configurations)) + for index, c := range configurations { config := &configv1alpha1.ResourceInterpreterCustomization{} if err = helper.ConvertToTypedObject(c, config); err != nil { klog.Errorf("Failed to transform ResourceInterpreterCustomization: %w", err) return } - key := schema.FromAPIVersionAndKind(config.Spec.Target.APIVersion, config.Spec.Target.Kind) - configs[key] = NewResourceCustomAccessorAccessor(config) + configs[index] = config } - configManager.configuration.Store(configs) + + sort.Slice(configs, func(i, j int) bool { + return configs[i].Name < configs[j].Name + }) + + accessors := make(map[schema.GroupVersionKind]CustomAccessor) + for _, config := range configs { + key := schema.FromAPIVersionAndKind(config.Spec.Target.APIVersion, config.Spec.Target.Kind) + + var accessor CustomAccessor + var ok bool + if accessor, ok = accessors[key]; !ok { + accessor = NewResourceCustomAccessor() + } + accessor.Merge(config.Spec.Customizations) + accessors[key] = accessor + } + + configManager.configuration.Store(accessors) configManager.initialSynced.Store(true) } diff --git a/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager_test.go b/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager_test.go new file mode 100644 index 000000000..ba6720bb2 --- /dev/null +++ b/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager_test.go @@ -0,0 +1,132 @@ +package configmanager + +import ( + "reflect" + "testing" + + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic/fake" + + configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1" + "github.com/karmada-io/karmada/pkg/util/fedinformer/genericmanager" + "github.com/karmada-io/karmada/pkg/util/gclient" +) + +func Test_interpreterConfigManager_LuaScriptAccessors(t *testing.T) { + customization01 := &configv1alpha1.ResourceInterpreterCustomization{ + ObjectMeta: metav1.ObjectMeta{Name: "customization01"}, + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Target: configv1alpha1.CustomizationTarget{ + APIVersion: appsv1.SchemeGroupVersion.String(), + Kind: "Deployment", + }, + Customizations: configv1alpha1.CustomizationRules{ + Retention: &configv1alpha1.LocalValueRetention{LuaScript: "a=0"}, + ReplicaResource: &configv1alpha1.ReplicaResourceRequirement{LuaScript: "b=0"}, + }, + }, + } + customization02 := &configv1alpha1.ResourceInterpreterCustomization{ + ObjectMeta: metav1.ObjectMeta{Name: "customization02"}, + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Target: configv1alpha1.CustomizationTarget{ + APIVersion: appsv1.SchemeGroupVersion.String(), + Kind: "Deployment", + }, + Customizations: configv1alpha1.CustomizationRules{ + ReplicaRevision: &configv1alpha1.ReplicaRevision{LuaScript: "c=0"}, + StatusReflection: &configv1alpha1.StatusReflection{LuaScript: "d=0"}, + }, + }, + } + customization03 := &configv1alpha1.ResourceInterpreterCustomization{ + ObjectMeta: metav1.ObjectMeta{Name: "customization03"}, + Spec: configv1alpha1.ResourceInterpreterCustomizationSpec{ + Target: configv1alpha1.CustomizationTarget{ + APIVersion: appsv1.SchemeGroupVersion.String(), + Kind: "Deployment", + }, + Customizations: configv1alpha1.CustomizationRules{ + ReplicaResource: &configv1alpha1.ReplicaResourceRequirement{LuaScript: "b=1"}, + ReplicaRevision: &configv1alpha1.ReplicaRevision{LuaScript: "c=1"}, + }, + }, + } + deploymentGVK := schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"} + + type args struct { + customizations []runtime.Object + } + tests := []struct { + name string + args args + want map[schema.GroupVersionKind]CustomAccessor + }{ + { + name: "single ResourceInterpreterCustomization", + args: args{[]runtime.Object{customization01}}, + want: map[schema.GroupVersionKind]CustomAccessor{ + deploymentGVK: &resourceCustomAccessor{ + retention: &configv1alpha1.LocalValueRetention{LuaScript: "a=0"}, + replicaResource: &configv1alpha1.ReplicaResourceRequirement{LuaScript: "b=0"}, + }, + }, + }, + { + name: "multi ResourceInterpreterCustomization with no redundant operation", + args: args{[]runtime.Object{customization01, customization02}}, + want: map[schema.GroupVersionKind]CustomAccessor{ + deploymentGVK: &resourceCustomAccessor{ + retention: &configv1alpha1.LocalValueRetention{LuaScript: "a=0"}, + replicaResource: &configv1alpha1.ReplicaResourceRequirement{LuaScript: "b=0"}, + replicaRevision: &configv1alpha1.ReplicaRevision{LuaScript: "c=0"}, + statusReflection: &configv1alpha1.StatusReflection{LuaScript: "d=0"}, + }, + }, + }, + { + name: "multi ResourceInterpreterCustomization with redundant operation ", + args: args{[]runtime.Object{customization03, customization02, customization01}}, + want: map[schema.GroupVersionKind]CustomAccessor{ + deploymentGVK: &resourceCustomAccessor{ + retention: &configv1alpha1.LocalValueRetention{LuaScript: "a=0"}, + replicaResource: &configv1alpha1.ReplicaResourceRequirement{LuaScript: "b=0"}, + replicaRevision: &configv1alpha1.ReplicaRevision{LuaScript: "c=0"}, + statusReflection: &configv1alpha1.StatusReflection{LuaScript: "d=0"}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stopCh := make(chan struct{}) + defer close(stopCh) + + client := fake.NewSimpleDynamicClient(gclient.NewSchema(), tt.args.customizations...) + informer := genericmanager.NewSingleClusterInformerManager(client, 0, stopCh) + configManager := NewInterpreterConfigManager(informer) + + informer.Start() + defer informer.Stop() + + informer.WaitForCacheSync() + + if !configManager.HasSynced() { + t.Errorf("informer has not been synced") + } + gotAccessors := configManager.LuaScriptAccessors() + for gvk, gotAccessor := range gotAccessors { + wantAccessor, ok := tt.want[gvk] + if !ok { + t.Errorf("Can not find the target gvk %v", gvk) + } + if !reflect.DeepEqual(gotAccessor, wantAccessor) { + t.Errorf("LuaScriptAccessors() = %v, want %v", gotAccessor, wantAccessor) + } + } + }) + } +}