diff --git a/pkg/resourceinterpreter/configurableinterpreter/configmanager/accessor.go b/pkg/resourceinterpreter/configurableinterpreter/configmanager/accessor.go index 04d6a5c8d..be14dab64 100644 --- a/pkg/resourceinterpreter/configurableinterpreter/configmanager/accessor.go +++ b/pkg/resourceinterpreter/configurableinterpreter/configmanager/accessor.go @@ -19,7 +19,7 @@ type LuaScriptAccessor interface { GetStatusReflectionLuaScript() string GetStatusAggregationLuaScript() string GetHealthInterpretationLuaScript() string - GetDependencyInterpretationLuaScript() string + GetDependencyInterpretationLuaScripts() []string } // CustomAccessor provides a common interface to get custom interpreter configuration. @@ -28,13 +28,13 @@ type CustomAccessor interface { } type resourceCustomAccessor struct { - retention *configv1alpha1.LocalValueRetention - replicaResource *configv1alpha1.ReplicaResourceRequirement - replicaRevision *configv1alpha1.ReplicaRevision - statusReflection *configv1alpha1.StatusReflection - statusAggregation *configv1alpha1.StatusAggregation - healthInterpretation *configv1alpha1.HealthInterpretation - dependencyInterpretation *configv1alpha1.DependencyInterpretation + retention *configv1alpha1.LocalValueRetention + replicaResource *configv1alpha1.ReplicaResourceRequirement + replicaRevision *configv1alpha1.ReplicaRevision + statusReflection *configv1alpha1.StatusReflection + statusAggregation *configv1alpha1.StatusAggregation + healthInterpretation *configv1alpha1.HealthInterpretation + dependencyInterpretations []*configv1alpha1.DependencyInterpretation } // NewResourceCustomAccessor creates an accessor for resource interpreter customization. @@ -63,7 +63,7 @@ func (a *resourceCustomAccessor) Merge(rules configv1alpha1.CustomizationRules) a.setHealthInterpretation(rules.HealthInterpretation) } if rules.DependencyInterpretation != nil { - a.setDependencyInterpretation(rules.DependencyInterpretation) + a.appendDependencyInterpretation(rules.DependencyInterpretation) } } @@ -109,11 +109,18 @@ func (a *resourceCustomAccessor) GetHealthInterpretationLuaScript() string { return a.healthInterpretation.LuaScript } -func (a *resourceCustomAccessor) GetDependencyInterpretationLuaScript() string { - if a.dependencyInterpretation == nil { - return "" +func (a *resourceCustomAccessor) GetDependencyInterpretationLuaScripts() []string { + if a.dependencyInterpretations == nil { + return nil } - return a.dependencyInterpretation.LuaScript + + var scripts []string + for _, interpretation := range a.dependencyInterpretations { + if interpretation.LuaScript != "" { + scripts = append(scripts, interpretation.LuaScript) + } + } + return scripts } func (a *resourceCustomAccessor) setRetain(retention *configv1alpha1.LocalValueRetention) { @@ -182,13 +189,6 @@ func (a *resourceCustomAccessor) setHealthInterpretation(healthInterpretation *c } } -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 - } +func (a *resourceCustomAccessor) appendDependencyInterpretation(dependencyInterpretation *configv1alpha1.DependencyInterpretation) { + a.dependencyInterpretations = append(a.dependencyInterpretations, dependencyInterpretation) } diff --git a/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager.go b/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager.go index 2185f87c1..b7bb04814 100644 --- a/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager.go +++ b/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager.go @@ -25,7 +25,7 @@ var resourceInterpreterCustomizationsGVR = schema.GroupVersionResource{ // ConfigManager can list custom resource interpreter. type ConfigManager interface { - LuaScriptAccessors() map[schema.GroupVersionKind]CustomAccessor + CustomAccessors() map[schema.GroupVersionKind]CustomAccessor HasSynced() bool LoadConfig(customizations []*configv1alpha1.ResourceInterpreterCustomization) } @@ -37,8 +37,8 @@ type interpreterConfigManager struct { configuration atomic.Value } -// LuaScriptAccessors returns all cached configurations. -func (configManager *interpreterConfigManager) LuaScriptAccessors() map[schema.GroupVersionKind]CustomAccessor { +// CustomAccessors returns all cached configurations. +func (configManager *interpreterConfigManager) CustomAccessors() map[schema.GroupVersionKind]CustomAccessor { return configManager.configuration.Load().(map[schema.GroupVersionKind]CustomAccessor) } diff --git a/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager_test.go b/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager_test.go index 9cbfb5692..e0ce8b0c3 100644 --- a/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager_test.go +++ b/pkg/resourceinterpreter/configurableinterpreter/configmanager/manager_test.go @@ -122,7 +122,7 @@ func Test_interpreterConfigManager_LuaScriptAccessors(t *testing.T) { t.Errorf("informer has not been synced") } - gotAccessors := configManager.LuaScriptAccessors() + gotAccessors := configManager.CustomAccessors() for gvk, gotAccessor := range gotAccessors { wantAccessor, ok := tt.want[gvk] if !ok { diff --git a/pkg/resourceinterpreter/configurableinterpreter/configurable.go b/pkg/resourceinterpreter/configurableinterpreter/configurable.go index 406ca5ecc..882e167e3 100644 --- a/pkg/resourceinterpreter/configurableinterpreter/configurable.go +++ b/pkg/resourceinterpreter/configurableinterpreter/configurable.go @@ -32,114 +32,185 @@ func NewConfigurableInterpreter(informer genericmanager.SingleClusterInformerMan // HookEnabled tells if any hook exist for specific resource gvk and operation type. func (c *ConfigurableInterpreter) HookEnabled(kind schema.GroupVersionKind, operationType configv1alpha1.InterpreterOperation) bool { - _, exist := c.getInterpreter(kind, operationType) - return exist + accessor, exist := c.getCustomAccessor(kind) + if !exist { + return exist + } + + if operationType == configv1alpha1.InterpreterOperationInterpretDependency { + scripts := accessor.GetDependencyInterpretationLuaScripts() + return scripts != nil + } + + var script string + switch operationType { + case configv1alpha1.InterpreterOperationAggregateStatus: + script = accessor.GetStatusAggregationLuaScript() + case configv1alpha1.InterpreterOperationInterpretHealth: + script = accessor.GetHealthInterpretationLuaScript() + case configv1alpha1.InterpreterOperationInterpretReplica: + script = accessor.GetReplicaResourceLuaScript() + case configv1alpha1.InterpreterOperationInterpretStatus: + script = accessor.GetStatusReflectionLuaScript() + case configv1alpha1.InterpreterOperationRetain: + script = accessor.GetRetentionLuaScript() + case configv1alpha1.InterpreterOperationReviseReplica: + script = accessor.GetReplicaRevisionLuaScript() + } + return len(script) > 0 } // GetReplicas returns the desired replicas of the object as well as the requirements of each replica. func (c *ConfigurableInterpreter) GetReplicas(object *unstructured.Unstructured) (replicas int32, requires *workv1alpha2.ReplicaRequirements, enabled bool, err error) { klog.V(4).Infof("Get replicas for object: %v %s/%s with configurable interpreter.", object.GroupVersionKind(), object.GetNamespace(), object.GetName()) - luaScript, enabled := c.getInterpreter(object.GroupVersionKind(), configv1alpha1.InterpreterOperationInterpretReplica) + + accessor, enabled := c.getCustomAccessor(object.GroupVersionKind()) if !enabled { return } - replicas, requires, err = c.luaVM.GetReplicas(object, luaScript) + + script := accessor.GetReplicaResourceLuaScript() + if len(script) == 0 { + enabled = false + return + } + + replicas, requires, err = c.luaVM.GetReplicas(object, script) return } // ReviseReplica revises the replica of the given object. func (c *ConfigurableInterpreter) ReviseReplica(object *unstructured.Unstructured, replica int64) (revised *unstructured.Unstructured, enabled bool, err error) { klog.V(4).Infof("Revise replicas for object: %v %s/%s with configurable interpreter.", object.GroupVersionKind(), object.GetNamespace(), object.GetName()) - luaScript, enabled := c.getInterpreter(object.GroupVersionKind(), configv1alpha1.InterpreterOperationReviseReplica) + + accessor, enabled := c.getCustomAccessor(object.GroupVersionKind()) if !enabled { return } - revised, err = c.luaVM.ReviseReplica(object, replica, luaScript) + + script := accessor.GetReplicaRevisionLuaScript() + if len(script) == 0 { + enabled = false + return + } + + revised, err = c.luaVM.ReviseReplica(object, replica, script) return } // Retain returns the objects that based on the "desired" object but with values retained from the "observed" object. func (c *ConfigurableInterpreter) Retain(desired *unstructured.Unstructured, observed *unstructured.Unstructured) (retained *unstructured.Unstructured, enabled bool, err error) { klog.V(4).Infof("Retain object: %v %s/%s with configurable interpreter.", desired.GroupVersionKind(), desired.GetNamespace(), desired.GetName()) - luaScript, enabled := c.getInterpreter(desired.GroupVersionKind(), configv1alpha1.InterpreterOperationRetain) + + accessor, enabled := c.getCustomAccessor(desired.GroupVersionKind()) if !enabled { return } - retained, err = c.luaVM.Retain(desired, observed, luaScript) + + script := accessor.GetRetentionLuaScript() + if len(script) == 0 { + enabled = false + return + } + + retained, err = c.luaVM.Retain(desired, observed, script) return } // AggregateStatus returns the objects that based on the 'object' but with status aggregated. func (c *ConfigurableInterpreter) AggregateStatus(object *unstructured.Unstructured, aggregatedStatusItems []workv1alpha2.AggregatedStatusItem) (status *unstructured.Unstructured, enabled bool, err error) { klog.V(4).Infof("Aggregate status of object: %v %s/%s with configurable interpreter.", object.GroupVersionKind(), object.GetNamespace(), object.GetName()) - luaScript, enabled := c.getInterpreter(object.GroupVersionKind(), configv1alpha1.InterpreterOperationAggregateStatus) + accessor, enabled := c.getCustomAccessor(object.GroupVersionKind()) if !enabled { return } - status, err = c.luaVM.AggregateStatus(object, aggregatedStatusItems, luaScript) + + script := accessor.GetStatusAggregationLuaScript() + if len(script) == 0 { + enabled = false + return + } + + status, err = c.luaVM.AggregateStatus(object, aggregatedStatusItems, script) return } // GetDependencies returns the dependent resources of the given object. func (c *ConfigurableInterpreter) GetDependencies(object *unstructured.Unstructured) (dependencies []configv1alpha1.DependentObjectReference, enabled bool, err error) { klog.V(4).Infof("Get dependencies of object: %v %s/%s with configurable interpreter.", object.GroupVersionKind(), object.GetNamespace(), object.GetName()) - luaScript, enabled := c.getInterpreter(object.GroupVersionKind(), configv1alpha1.InterpreterOperationInterpretDependency) + + accessor, enabled := c.getCustomAccessor(object.GroupVersionKind()) if !enabled { return } - dependencies, err = c.luaVM.GetDependencies(object, luaScript) + + scripts := accessor.GetDependencyInterpretationLuaScripts() + if scripts == nil { + enabled = false + return + } + + referenceSet := newDependencySet() + for _, luaScript := range scripts { + var references []configv1alpha1.DependentObjectReference + references, err = c.luaVM.GetDependencies(object, luaScript) + if err != nil { + klog.Errorf("Failed to get DependentObjectReferences from object: %v %s/%s, error: %v", + object.GroupVersionKind(), object.GetNamespace(), object.GetName(), err) + return + } + referenceSet.insert(references...) + } + dependencies = referenceSet.list() return } // ReflectStatus returns the status of the object. func (c *ConfigurableInterpreter) ReflectStatus(object *unstructured.Unstructured) (status *runtime.RawExtension, enabled bool, err error) { klog.V(4).Infof("Reflect status of object: %v %s/%s with configurable interpreter.", object.GroupVersionKind(), object.GetNamespace(), object.GetName()) - luaScript, enabled := c.getInterpreter(object.GroupVersionKind(), configv1alpha1.InterpreterOperationInterpretStatus) + + accessor, enabled := c.getCustomAccessor(object.GroupVersionKind()) if !enabled { return } - status, err = c.luaVM.ReflectStatus(object, luaScript) + + script := accessor.GetStatusReflectionLuaScript() + if len(script) == 0 { + enabled = false + return + } + + status, err = c.luaVM.ReflectStatus(object, script) return } // InterpretHealth returns the health state of the object. func (c *ConfigurableInterpreter) InterpretHealth(object *unstructured.Unstructured) (health bool, enabled bool, err error) { klog.V(4).Infof("Get health status of object: %v %s/%s with configurable interpreter.", object.GroupVersionKind(), object.GetNamespace(), object.GetName()) - luaScript, enabled := c.getInterpreter(object.GroupVersionKind(), configv1alpha1.InterpreterOperationInterpretHealth) + + accessor, enabled := c.getCustomAccessor(object.GroupVersionKind()) if !enabled { return } - health, err = c.luaVM.InterpretHealth(object, luaScript) + + script := accessor.GetHealthInterpretationLuaScript() + if len(script) == 0 { + enabled = false + return + } + + health, err = c.luaVM.InterpretHealth(object, script) return } -func (c *ConfigurableInterpreter) getInterpreter(kind schema.GroupVersionKind, operationType configv1alpha1.InterpreterOperation) (string, bool) { +func (c *ConfigurableInterpreter) getCustomAccessor(kind schema.GroupVersionKind) (configmanager.CustomAccessor, bool) { if !c.configManager.HasSynced() { klog.Errorf("not yet ready to handle request") - return "", false + return nil, false } - accessors, exist := c.configManager.LuaScriptAccessors()[kind] - if !exist { - return "", false - } - var script string - switch operationType { - case configv1alpha1.InterpreterOperationAggregateStatus: - script = accessors.GetStatusAggregationLuaScript() - case configv1alpha1.InterpreterOperationInterpretHealth: - script = accessors.GetHealthInterpretationLuaScript() - case configv1alpha1.InterpreterOperationInterpretDependency: - script = accessors.GetDependencyInterpretationLuaScript() - case configv1alpha1.InterpreterOperationInterpretReplica: - script = accessors.GetReplicaResourceLuaScript() - case configv1alpha1.InterpreterOperationInterpretStatus: - script = accessors.GetStatusReflectionLuaScript() - case configv1alpha1.InterpreterOperationRetain: - script = accessors.GetRetentionLuaScript() - case configv1alpha1.InterpreterOperationReviseReplica: - script = accessors.GetReplicaRevisionLuaScript() - } - return script, len(script) > 0 + + accessor, exist := c.configManager.CustomAccessors()[kind] + return accessor, exist } // LoadConfig loads and stores rules from customizations diff --git a/pkg/resourceinterpreter/configurableinterpreter/dependencyset.go b/pkg/resourceinterpreter/configurableinterpreter/dependencyset.go new file mode 100644 index 000000000..4d91522d9 --- /dev/null +++ b/pkg/resourceinterpreter/configurableinterpreter/dependencyset.go @@ -0,0 +1,36 @@ +package configurableinterpreter + +import ( + "fmt" + "sort" + + configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1" +) + +type dependencySet map[configv1alpha1.DependentObjectReference]struct{} + +func newDependencySet(items ...configv1alpha1.DependentObjectReference) dependencySet { + s := make(dependencySet, len(items)) + s.insert(items...) + return s +} + +func (s dependencySet) insert(items ...configv1alpha1.DependentObjectReference) dependencySet { + for _, item := range items { + s[item] = struct{}{} + } + return s +} + +func (s dependencySet) list() []configv1alpha1.DependentObjectReference { + keys := make([]configv1alpha1.DependentObjectReference, len(s)) + index := 0 + for key := range s { + keys[index] = key + index++ + } + sort.Slice(keys, func(i, j int) bool { + return fmt.Sprintf("%s", keys[i]) < fmt.Sprintf("%s", keys[j]) + }) + return keys +} diff --git a/pkg/resourceinterpreter/configurableinterpreter/dependencyset_test.go b/pkg/resourceinterpreter/configurableinterpreter/dependencyset_test.go new file mode 100644 index 000000000..b5f65a863 --- /dev/null +++ b/pkg/resourceinterpreter/configurableinterpreter/dependencyset_test.go @@ -0,0 +1,69 @@ +package configurableinterpreter + +import ( + "reflect" + "testing" + + configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1" +) + +func Test_dependencySet_list(t *testing.T) { + tests := []struct { + name string + s dependencySet + want []configv1alpha1.DependentObjectReference + }{ + { + name: "empty set", + s: newDependencySet(), + want: []configv1alpha1.DependentObjectReference{}, + }, + { + name: "new with items", + s: newDependencySet([]configv1alpha1.DependentObjectReference{ + {APIVersion: "v1", Kind: "Secret", Namespace: "foo", Name: "foo"}, + }...), + want: []configv1alpha1.DependentObjectReference{ + {APIVersion: "v1", Kind: "Secret", Namespace: "foo", Name: "foo"}, + }, + }, + { + name: "insert with different items", + s: newDependencySet([]configv1alpha1.DependentObjectReference{ + {APIVersion: "v1", Kind: "Secret", Namespace: "foo", Name: "foo"}, + }...).insert( + []configv1alpha1.DependentObjectReference{ + {APIVersion: "v1", Kind: "Configmap", Namespace: "foo", Name: "foo"}, + }...), + want: []configv1alpha1.DependentObjectReference{ + {APIVersion: "v1", Kind: "Configmap", Namespace: "foo", Name: "foo"}, + {APIVersion: "v1", Kind: "Secret", Namespace: "foo", Name: "foo"}, + }, + }, + { + name: "insert with same items", + s: newDependencySet([]configv1alpha1.DependentObjectReference{ + {APIVersion: "v1", Kind: "Secret", Namespace: "foo", Name: "foo"}, + }...).insert( + []configv1alpha1.DependentObjectReference{ + {APIVersion: "v1", Kind: "Configmap", Namespace: "foo", Name: "foo"}, + }...).insert( + []configv1alpha1.DependentObjectReference{ + {APIVersion: "v1", Kind: "Secret", Namespace: "foo", Name: "foo"}, + {APIVersion: "apps/v1", Kind: "Deployment", Namespace: "foo", Name: "foo"}, + }...), + want: []configv1alpha1.DependentObjectReference{ + {APIVersion: "apps/v1", Kind: "Deployment", Namespace: "foo", Name: "foo"}, + {APIVersion: "v1", Kind: "Configmap", Namespace: "foo", Name: "foo"}, + {APIVersion: "v1", Kind: "Secret", Namespace: "foo", Name: "foo"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.list(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("dependencySet.list() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/webhook/resourceinterpretercustomization/helper.go b/pkg/webhook/resourceinterpretercustomization/helper.go index e9637001e..f5ae3995e 100644 --- a/pkg/webhook/resourceinterpretercustomization/helper.go +++ b/pkg/webhook/resourceinterpretercustomization/helper.go @@ -16,6 +16,10 @@ func validateCustomizationRule(oldRules, newRules *configv1alpha1.ResourceInterp return nil } for _, rule := range interpreter.AllResourceInterpreterCustomizationRules { + // skip InterpretDependency operation because it supports multiple rules. + if rule.Name() == string(configv1alpha1.InterpreterOperationInterpretDependency) { + continue + } oldScript := rule.GetScript(oldRules) newScript := rule.GetScript(newRules) if oldScript != "" && newScript != "" { diff --git a/pkg/webhook/resourceinterpretercustomization/helper_test.go b/pkg/webhook/resourceinterpretercustomization/helper_test.go index 03e0d46f0..622b60f92 100644 --- a/pkg/webhook/resourceinterpretercustomization/helper_test.go +++ b/pkg/webhook/resourceinterpretercustomization/helper_test.go @@ -256,7 +256,7 @@ func Test_validateCustomizationRule(t *testing.T) { }, }, }, - wantErr: true, + wantErr: false, }, } for _, tt := range tests { diff --git a/test/e2e/resourceinterpreter_test.go b/test/e2e/resourceinterpreter_test.go index 49085932b..548f824ca 100644 --- a/test/e2e/resourceinterpreter_test.go +++ b/test/e2e/resourceinterpreter_test.go @@ -282,327 +282,362 @@ var _ = framework.SerialDescribe("Resource interpreter customization testing", f // We only need to test any one of the member clusters. var targetCluster string - ginkgo.BeforeEach(func() { - targetCluster = framework.ClusterNames()[rand.Intn(len(framework.ClusterNames()))] - deployment = testhelper.NewDeployment(testNamespace, deploymentNamePrefix+rand.String(RandomStrLength)) - policy = testhelper.NewPropagationPolicy(testNamespace, deployment.Name, []policyv1alpha1.ResourceSelector{ - { - APIVersion: deployment.APIVersion, - Kind: deployment.Kind, - Name: deployment.Name, - }, - }, policyv1alpha1.Placement{ - ClusterAffinity: &policyv1alpha1.ClusterAffinity{ - ClusterNames: []string{targetCluster}, - }, - }) - }) - - ginkgo.JustBeforeEach(func() { - framework.CreateResourceInterpreterCustomization(karmadaClient, customization) - // Wait for resource interpreter informer synced. - time.Sleep(time.Second) - - framework.CreatePropagationPolicy(karmadaClient, policy) - framework.CreateDeployment(kubeClient, deployment) - ginkgo.DeferCleanup(func() { - framework.RemoveDeployment(kubeClient, deployment.Namespace, deployment.Name) - framework.RemovePropagationPolicy(karmadaClient, policy.Namespace, policy.Name) - framework.DeleteResourceInterpreterCustomization(karmadaClient, customization.Name) - }) - }) - - ginkgo.Context("InterpreterOperation InterpretReplica testing", func() { + ginkgo.When("Apply single ResourceInterpreterCustomization without DependencyInterpretation operation", func() { ginkgo.BeforeEach(func() { - customization = testhelper.NewResourceInterpreterCustomization( - "interpreter-customization"+rand.String(RandomStrLength), - configv1alpha1.CustomizationTarget{ - APIVersion: "apps/v1", - Kind: "Deployment", + targetCluster = framework.ClusterNames()[rand.Intn(len(framework.ClusterNames()))] + deployment = testhelper.NewDeployment(testNamespace, deploymentNamePrefix+rand.String(RandomStrLength)) + policy = testhelper.NewPropagationPolicy(testNamespace, deployment.Name, []policyv1alpha1.ResourceSelector{ + { + APIVersion: deployment.APIVersion, + Kind: deployment.Kind, + Name: deployment.Name, }, - configv1alpha1.CustomizationRules{ - ReplicaResource: &configv1alpha1.ReplicaResourceRequirement{ - LuaScript: ` -function GetReplicas(desiredObj) - replica = desiredObj.spec.replicas + 1 - requirement = {} - requirement.nodeClaim = {} - requirement.nodeClaim.nodeSelector = desiredObj.spec.template.spec.nodeSelector - requirement.nodeClaim.tolerations = desiredObj.spec.template.spec.tolerations - requirement.resourceRequest = desiredObj.spec.template.spec.containers[1].resources.limits - return replica, requirement -end`, - }, - }) - }) - - ginkgo.It("InterpretReplica testing", func() { - ginkgo.By("check if workload's replica is interpreted", func() { - resourceBindingName := names.GenerateBindingName(deployment.Kind, deployment.Name) - // Just for the current test case to distinguish the build-in logic. - expectedReplicas := *deployment.Spec.Replicas + 1 - expectedReplicaRequirements := &workv1alpha2.ReplicaRequirements{ - ResourceRequest: map[corev1.ResourceName]resource.Quantity{ - corev1.ResourceCPU: resource.MustParse("100m"), - }} - - gomega.Eventually(func(g gomega.Gomega) (bool, error) { - resourceBinding, err := karmadaClient.WorkV1alpha2().ResourceBindings(deployment.Namespace).Get(context.TODO(), resourceBindingName, metav1.GetOptions{}) - g.Expect(err).NotTo(gomega.HaveOccurred()) - - klog.Infof(fmt.Sprintf("ResourceBinding(%s/%s)'s replicas is %d, expected: %d.", - resourceBinding.Namespace, resourceBinding.Name, resourceBinding.Spec.Replicas, expectedReplicas)) - if resourceBinding.Spec.Replicas != expectedReplicas { - return false, nil - } - - klog.Infof(fmt.Sprintf("ResourceBinding(%s/%s)'s replicaRequirements is %+v, expected: %+v.", - resourceBinding.Namespace, resourceBinding.Name, resourceBinding.Spec.ReplicaRequirements, expectedReplicaRequirements)) - return reflect.DeepEqual(resourceBinding.Spec.ReplicaRequirements, expectedReplicaRequirements), nil - }, pollTimeout, pollInterval).Should(gomega.Equal(true)) - }) - }) - }) - - ginkgo.Context("InterpreterOperation ReviseReplica testing", func() { - ginkgo.BeforeEach(func() { - customization = testhelper.NewResourceInterpreterCustomization( - "interpreter-customization"+rand.String(RandomStrLength), - configv1alpha1.CustomizationTarget{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, - configv1alpha1.CustomizationRules{ - ReplicaRevision: &configv1alpha1.ReplicaRevision{ - LuaScript: ` -function ReviseReplica(obj, desiredReplica) - obj.spec.replicas = desiredReplica + 1 - return obj -end`, - }, - }) - }) - - ginkgo.BeforeEach(func() { - sumWeight := 0 - staticWeightLists := make([]policyv1alpha1.StaticClusterWeight, 0) - for index, clusterName := range framework.ClusterNames() { - staticWeightList := policyv1alpha1.StaticClusterWeight{ - TargetCluster: policyv1alpha1.ClusterAffinity{ - ClusterNames: []string{clusterName}, - }, - Weight: int64(index + 1), - } - sumWeight += index + 1 - staticWeightLists = append(staticWeightLists, staticWeightList) - } - deployment.Spec.Replicas = pointer.Int32Ptr(int32(sumWeight)) - policy.Spec.Placement = policyv1alpha1.Placement{ + }, policyv1alpha1.Placement{ ClusterAffinity: &policyv1alpha1.ClusterAffinity{ - ClusterNames: framework.ClusterNames(), + ClusterNames: []string{targetCluster}, }, - ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{ - ReplicaDivisionPreference: policyv1alpha1.ReplicaDivisionPreferenceWeighted, - ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDivided, - WeightPreference: &policyv1alpha1.ClusterPreferences{ - StaticWeightList: staticWeightLists, + }) + }) + + ginkgo.JustBeforeEach(func() { + framework.CreateResourceInterpreterCustomization(karmadaClient, customization) + // Wait for resource interpreter informer synced. + time.Sleep(time.Second) + + framework.CreatePropagationPolicy(karmadaClient, policy) + framework.CreateDeployment(kubeClient, deployment) + ginkgo.DeferCleanup(func() { + framework.RemoveDeployment(kubeClient, deployment.Namespace, deployment.Name) + framework.RemovePropagationPolicy(karmadaClient, policy.Namespace, policy.Name) + framework.DeleteResourceInterpreterCustomization(karmadaClient, customization.Name) + }) + }) + + ginkgo.Context("InterpreterOperation InterpretReplica testing", func() { + ginkgo.BeforeEach(func() { + customization = testhelper.NewResourceInterpreterCustomization( + "interpreter-customization"+rand.String(RandomStrLength), + configv1alpha1.CustomizationTarget{ + APIVersion: "apps/v1", + Kind: "Deployment", }, - }, - } - }) - - ginkgo.It("ReviseReplica testing", func() { - for index, clusterName := range framework.ClusterNames() { - framework.WaitDeploymentPresentOnClusterFitWith(clusterName, deployment.Namespace, deployment.Name, func(deployment *appsv1.Deployment) bool { - return *deployment.Spec.Replicas == int32(index+1)+1 - }) - } - }) - }) - - ginkgo.Context("InterpreterOperation Retain testing", func() { - ginkgo.BeforeEach(func() { - customization = testhelper.NewResourceInterpreterCustomization( - "interpreter-customization"+rand.String(RandomStrLength), - configv1alpha1.CustomizationTarget{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, - configv1alpha1.CustomizationRules{ - Retention: &configv1alpha1.LocalValueRetention{ - LuaScript: ` -function Retain(desiredObj, observedObj) - desiredObj.spec.paused = observedObj.spec.paused - return desiredObj -end`, - }, - }) - }) - - ginkgo.It("Retain testing", func() { - ginkgo.By("wait deployment exist on the member clusters", func() { - framework.WaitDeploymentPresentOnClusterFitWith(targetCluster, deployment.Namespace, deployment.Name, - func(_ *appsv1.Deployment) bool { - return true + configv1alpha1.CustomizationRules{ + ReplicaResource: &configv1alpha1.ReplicaResourceRequirement{ + LuaScript: ` + function GetReplicas(desiredObj) + replica = desiredObj.spec.replicas + 1 + requirement = {} + requirement.nodeClaim = {} + requirement.nodeClaim.nodeSelector = desiredObj.spec.template.spec.nodeSelector + requirement.nodeClaim.tolerations = desiredObj.spec.template.spec.tolerations + requirement.resourceRequest = desiredObj.spec.template.spec.containers[1].resources.limits + return replica, requirement + end`, + }, }) }) - ginkgo.By("update deployment on the control plane", func() { - // construct two values that need to be changed, and only one value is retained. - framework.UpdateDeploymentPaused(kubeClient, deployment, true) - framework.UpdateDeploymentReplicas(kubeClient, deployment, 2) - }) + ginkgo.It("InterpretReplica testing", func() { + ginkgo.By("check if workload's replica is interpreted", func() { + resourceBindingName := names.GenerateBindingName(deployment.Kind, deployment.Name) + // Just for the current test case to distinguish the build-in logic. + expectedReplicas := *deployment.Spec.Replicas + 1 + expectedReplicaRequirements := &workv1alpha2.ReplicaRequirements{ + ResourceRequest: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceCPU: resource.MustParse("100m"), + }} - ginkgo.By("check if deployment's spec.paused is retained", func() { - framework.WaitDeploymentPresentOnClusterFitWith(targetCluster, deployment.Namespace, deployment.Name, - func(deployment *appsv1.Deployment) bool { - return *deployment.Spec.Replicas == 2 && !deployment.Spec.Paused - }) - }) - }) - }) + gomega.Eventually(func(g gomega.Gomega) (bool, error) { + resourceBinding, err := karmadaClient.WorkV1alpha2().ResourceBindings(deployment.Namespace).Get(context.TODO(), resourceBindingName, metav1.GetOptions{}) + g.Expect(err).NotTo(gomega.HaveOccurred()) - ginkgo.Context("InterpreterOperation AggregateStatus testing", func() { - ginkgo.BeforeEach(func() { - customization = testhelper.NewResourceInterpreterCustomization( - "interpreter-customization"+rand.String(RandomStrLength), - configv1alpha1.CustomizationTarget{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, - configv1alpha1.CustomizationRules{ - StatusAggregation: &configv1alpha1.StatusAggregation{ - LuaScript: ` -function AggregateStatus(desiredObj, statusItems) - if statusItems == nil then - return desiredObj - end - if desiredObj.status == nil then - desiredObj.status = {} - end - replicas = 0 - for i = 1, #statusItems do - if statusItems[i].status ~= nil and statusItems[i].status.replicas ~= nil then - replicas = replicas + statusItems[i].status.replicas + 1 - end - end - desiredObj.status.replicas = replicas - return desiredObj -end`, - }, - }) - }) - ginkgo.It("AggregateStatus testing", func() { - ginkgo.By("check whether the deployment status can be correctly collected", func() { - // Only in the current case, a special example is constructed to distinguish the build-in logic. - wantedReplicas := *deployment.Spec.Replicas + 1 - gomega.Eventually(func() bool { - var currentDeployment *appsv1.Deployment - framework.WaitDeploymentGetByClientFitWith(kubeClient, deployment.Namespace, deployment.Name, func(deployment *appsv1.Deployment) bool { - currentDeployment = deployment - return true - }) - klog.Infof("deployment(%s/%s) replicas: %d, wanted replicas: %d", deployment.Namespace, deployment.Name, currentDeployment.Status.Replicas, wantedReplicas) - return currentDeployment.Status.Replicas == wantedReplicas - }, pollTimeout, pollInterval).Should(gomega.BeTrue()) - }) - }) - }) - - ginkgo.Context("InterpreterOperation InterpretStatus testing", func() { - ginkgo.BeforeEach(func() { - customization = testhelper.NewResourceInterpreterCustomization( - "interpreter-customization"+rand.String(RandomStrLength), - configv1alpha1.CustomizationTarget{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, - configv1alpha1.CustomizationRules{ - StatusReflection: &configv1alpha1.StatusReflection{ - LuaScript: ` -function ReflectStatus (observedObj) - if observedObj.status == nil then - return nil - end - return observedObj.status -end`, - }, - }) - }) - ginkgo.It("InterpretStatus testing", func() { - gomega.Eventually(func(g gomega.Gomega) bool { - deploy, err := kubeClient.AppsV1().Deployments(deployment.Namespace).Get(context.TODO(), deployment.Name, metav1.GetOptions{}) - g.Expect(err).NotTo(gomega.HaveOccurred()) - return deploy.Status.ReadyReplicas == *deploy.Spec.Replicas - }, pollTimeout, pollInterval).Should(gomega.BeTrue()) - - }) - }) - - ginkgo.Context("InterpreterOperation InterpretHealth testing", func() { - ginkgo.BeforeEach(func() { - customization = testhelper.NewResourceInterpreterCustomization( - "interpreter-customization"+rand.String(RandomStrLength), - configv1alpha1.CustomizationTarget{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, - configv1alpha1.CustomizationRules{ - HealthInterpretation: &configv1alpha1.HealthInterpretation{ - LuaScript: ` -function InterpretHealth(observedObj) - return observedObj.status.readyReplicas == observedObj.spec.replicas -end `, - }, - }) - }) - ginkgo.It("InterpretHealth testing", func() { - resourceBindingName := names.GenerateBindingName(deployment.Kind, deployment.Name) - SetReadyReplicas := func(readyReplicas int32) { - clusterClient := framework.GetClusterClient(targetCluster) - gomega.Expect(clusterClient).ShouldNot(gomega.BeNil()) - var memberDeployment *appsv1.Deployment - framework.WaitDeploymentPresentOnClusterFitWith(targetCluster, deployment.Namespace, deployment.Name, - func(deployment *appsv1.Deployment) bool { - memberDeployment = deployment - return true - }) - memberDeployment.Status.ReadyReplicas = readyReplicas - framework.UpdateDeploymentStatus(clusterClient, memberDeployment) - } - - CheckResult := func(result workv1alpha2.ResourceHealth) interface{} { - return func(g gomega.Gomega) (bool, error) { - rb, err := karmadaClient.WorkV1alpha2().ResourceBindings(deployment.Namespace).Get(context.TODO(), resourceBindingName, metav1.GetOptions{}) - g.Expect(err).NotTo(gomega.HaveOccurred()) - if len(rb.Status.AggregatedStatus) != 1 { - return false, nil - } - for _, status := range rb.Status.AggregatedStatus { - klog.Infof("resourceBinding(%s/%s) on cluster %s got %s, want %s ", deployment.Namespace, resourceBindingName, status.ClusterName, status.Health, result) - if status.Health != result { + klog.Infof(fmt.Sprintf("ResourceBinding(%s/%s)'s replicas is %d, expected: %d.", + resourceBinding.Namespace, resourceBinding.Name, resourceBinding.Spec.Replicas, expectedReplicas)) + if resourceBinding.Spec.Replicas != expectedReplicas { return false, nil } - } - return true, nil - } - } - ginkgo.By("deployment healthy", func() { - SetReadyReplicas(*deployment.Spec.Replicas) - gomega.Eventually(CheckResult(workv1alpha2.ResourceHealthy), pollTimeout, pollInterval).Should(gomega.BeTrue()) + klog.Infof(fmt.Sprintf("ResourceBinding(%s/%s)'s replicaRequirements is %+v, expected: %+v.", + resourceBinding.Namespace, resourceBinding.Name, resourceBinding.Spec.ReplicaRequirements, expectedReplicaRequirements)) + return reflect.DeepEqual(resourceBinding.Spec.ReplicaRequirements, expectedReplicaRequirements), nil + }, pollTimeout, pollInterval).Should(gomega.Equal(true)) + }) + }) + }) + + ginkgo.Context("InterpreterOperation ReviseReplica testing", func() { + ginkgo.BeforeEach(func() { + customization = testhelper.NewResourceInterpreterCustomization( + "interpreter-customization"+rand.String(RandomStrLength), + configv1alpha1.CustomizationTarget{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + configv1alpha1.CustomizationRules{ + ReplicaRevision: &configv1alpha1.ReplicaRevision{ + LuaScript: ` + function ReviseReplica(obj, desiredReplica) + obj.spec.replicas = desiredReplica + 1 + return obj + end`, + }, + }) + }) + + ginkgo.BeforeEach(func() { + sumWeight := 0 + staticWeightLists := make([]policyv1alpha1.StaticClusterWeight, 0) + for index, clusterName := range framework.ClusterNames() { + staticWeightList := policyv1alpha1.StaticClusterWeight{ + TargetCluster: policyv1alpha1.ClusterAffinity{ + ClusterNames: []string{clusterName}, + }, + Weight: int64(index + 1), + } + sumWeight += index + 1 + staticWeightLists = append(staticWeightLists, staticWeightList) + } + deployment.Spec.Replicas = pointer.Int32Ptr(int32(sumWeight)) + policy.Spec.Placement = policyv1alpha1.Placement{ + ClusterAffinity: &policyv1alpha1.ClusterAffinity{ + ClusterNames: framework.ClusterNames(), + }, + ReplicaScheduling: &policyv1alpha1.ReplicaSchedulingStrategy{ + ReplicaDivisionPreference: policyv1alpha1.ReplicaDivisionPreferenceWeighted, + ReplicaSchedulingType: policyv1alpha1.ReplicaSchedulingTypeDivided, + WeightPreference: &policyv1alpha1.ClusterPreferences{ + StaticWeightList: staticWeightLists, + }, + }, + } + }) + + ginkgo.It("ReviseReplica testing", func() { + for index, clusterName := range framework.ClusterNames() { + framework.WaitDeploymentPresentOnClusterFitWith(clusterName, deployment.Namespace, deployment.Name, func(deployment *appsv1.Deployment) bool { + return *deployment.Spec.Replicas == int32(index+1)+1 + }) + } + }) + }) + + ginkgo.Context("InterpreterOperation Retain testing", func() { + ginkgo.BeforeEach(func() { + customization = testhelper.NewResourceInterpreterCustomization( + "interpreter-customization"+rand.String(RandomStrLength), + configv1alpha1.CustomizationTarget{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + configv1alpha1.CustomizationRules{ + Retention: &configv1alpha1.LocalValueRetention{ + LuaScript: ` + function Retain(desiredObj, observedObj) + desiredObj.spec.paused = observedObj.spec.paused + return desiredObj + end`, + }, + }) + }) + + ginkgo.It("Retain testing", func() { + ginkgo.By("wait deployment exist on the member clusters", func() { + framework.WaitDeploymentPresentOnClusterFitWith(targetCluster, deployment.Namespace, deployment.Name, + func(_ *appsv1.Deployment) bool { + return true + }) + }) + + ginkgo.By("update deployment on the control plane", func() { + // construct two values that need to be changed, and only one value is retained. + framework.UpdateDeploymentPaused(kubeClient, deployment, true) + framework.UpdateDeploymentReplicas(kubeClient, deployment, 2) + }) + + ginkgo.By("check if deployment's spec.paused is retained", func() { + framework.WaitDeploymentPresentOnClusterFitWith(targetCluster, deployment.Namespace, deployment.Name, + func(deployment *appsv1.Deployment) bool { + return *deployment.Spec.Replicas == 2 && !deployment.Spec.Paused + }) + }) + }) + }) + + ginkgo.Context("InterpreterOperation AggregateStatus testing", func() { + ginkgo.BeforeEach(func() { + customization = testhelper.NewResourceInterpreterCustomization( + "interpreter-customization"+rand.String(RandomStrLength), + configv1alpha1.CustomizationTarget{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + configv1alpha1.CustomizationRules{ + StatusAggregation: &configv1alpha1.StatusAggregation{ + LuaScript: ` + function AggregateStatus(desiredObj, statusItems) + if statusItems == nil then + return desiredObj + end + if desiredObj.status == nil then + desiredObj.status = {} + end + replicas = 0 + for i = 1, #statusItems do + if statusItems[i].status ~= nil and statusItems[i].status.replicas ~= nil then + replicas = replicas + statusItems[i].status.replicas + 1 + end + end + desiredObj.status.replicas = replicas + return desiredObj + end`, + }, + }) + }) + ginkgo.It("AggregateStatus testing", func() { + ginkgo.By("check whether the deployment status can be correctly collected", func() { + // Only in the current case, a special example is constructed to distinguish the build-in logic. + wantedReplicas := *deployment.Spec.Replicas + 1 + gomega.Eventually(func() bool { + var currentDeployment *appsv1.Deployment + framework.WaitDeploymentGetByClientFitWith(kubeClient, deployment.Namespace, deployment.Name, func(deployment *appsv1.Deployment) bool { + currentDeployment = deployment + return true + }) + klog.Infof("deployment(%s/%s) replicas: %d, wanted replicas: %d", deployment.Namespace, deployment.Name, currentDeployment.Status.Replicas, wantedReplicas) + return currentDeployment.Status.Replicas == wantedReplicas + }, pollTimeout, pollInterval).Should(gomega.BeTrue()) + }) + }) + }) + + ginkgo.Context("InterpreterOperation InterpretStatus testing", func() { + ginkgo.BeforeEach(func() { + customization = testhelper.NewResourceInterpreterCustomization( + "interpreter-customization"+rand.String(RandomStrLength), + configv1alpha1.CustomizationTarget{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + configv1alpha1.CustomizationRules{ + StatusReflection: &configv1alpha1.StatusReflection{ + LuaScript: ` + function ReflectStatus (observedObj) + if observedObj.status == nil then + return nil + end + return observedObj.status + end`, + }, + }) + }) + ginkgo.It("InterpretStatus testing", func() { + gomega.Eventually(func(g gomega.Gomega) bool { + deploy, err := kubeClient.AppsV1().Deployments(deployment.Namespace).Get(context.TODO(), deployment.Name, metav1.GetOptions{}) + g.Expect(err).NotTo(gomega.HaveOccurred()) + return deploy.Status.ReadyReplicas == *deploy.Spec.Replicas + }, pollTimeout, pollInterval).Should(gomega.BeTrue()) + + }) + }) + + ginkgo.Context("InterpreterOperation InterpretHealth testing", func() { + ginkgo.BeforeEach(func() { + customization = testhelper.NewResourceInterpreterCustomization( + "interpreter-customization"+rand.String(RandomStrLength), + configv1alpha1.CustomizationTarget{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + configv1alpha1.CustomizationRules{ + HealthInterpretation: &configv1alpha1.HealthInterpretation{ + LuaScript: ` + function InterpretHealth(observedObj) + return observedObj.status.readyReplicas == observedObj.spec.replicas + end `, + }, + }) + }) + ginkgo.It("InterpretHealth testing", func() { + resourceBindingName := names.GenerateBindingName(deployment.Kind, deployment.Name) + SetReadyReplicas := func(readyReplicas int32) { + clusterClient := framework.GetClusterClient(targetCluster) + gomega.Expect(clusterClient).ShouldNot(gomega.BeNil()) + var memberDeployment *appsv1.Deployment + framework.WaitDeploymentPresentOnClusterFitWith(targetCluster, deployment.Namespace, deployment.Name, + func(deployment *appsv1.Deployment) bool { + memberDeployment = deployment + return true + }) + memberDeployment.Status.ReadyReplicas = readyReplicas + framework.UpdateDeploymentStatus(clusterClient, memberDeployment) + } + + CheckResult := func(result workv1alpha2.ResourceHealth) interface{} { + return func(g gomega.Gomega) (bool, error) { + rb, err := karmadaClient.WorkV1alpha2().ResourceBindings(deployment.Namespace).Get(context.TODO(), resourceBindingName, metav1.GetOptions{}) + g.Expect(err).NotTo(gomega.HaveOccurred()) + if len(rb.Status.AggregatedStatus) != 1 { + return false, nil + } + for _, status := range rb.Status.AggregatedStatus { + klog.Infof("resourceBinding(%s/%s) on cluster %s got %s, want %s ", deployment.Namespace, resourceBindingName, status.ClusterName, status.Health, result) + if status.Health != result { + return false, nil + } + } + return true, nil + } + } + + ginkgo.By("deployment healthy", func() { + SetReadyReplicas(*deployment.Spec.Replicas) + gomega.Eventually(CheckResult(workv1alpha2.ResourceHealthy), pollTimeout, pollInterval).Should(gomega.BeTrue()) + }) }) }) }) - ginkgo.Context("InterpreterOperation DependencyInterpretation testing", func() { + ginkgo.When("Apply multi ResourceInterpreterCustomization with DependencyInterpretation operation", func() { + var customizationAnother *configv1alpha1.ResourceInterpreterCustomization + + var configMapName string + var configMap *corev1.ConfigMap + var saName string var sa *corev1.ServiceAccount + ginkgo.BeforeEach(func() { + targetCluster = framework.ClusterNames()[rand.Intn(len(framework.ClusterNames()))] + + deployment = testhelper.NewDeployment(testNamespace, deploymentNamePrefix+rand.String(RandomStrLength)) + + configMapName = configMapNamePrefix + rand.String(RandomStrLength) + configMap = testhelper.NewConfigMap(testNamespace, configMapName, map[string]string{"user": "karmada"}) + deployment.Spec.Template.Spec.Volumes = []corev1.Volume{{ + Name: "vol-configmap", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: configMapName, + }}}}} + saName = saNamePrefix + rand.String(RandomStrLength) sa = testhelper.NewServiceaccount(testNamespace, saName) - deployment = testhelper.NewDeploymentWithServiceAccount(testNamespace, deployment.Name, saName) + deployment.Spec.Template.Spec.ServiceAccountName = saName + + policy = testhelper.NewPropagationPolicy(testNamespace, deployment.Name, []policyv1alpha1.ResourceSelector{ + { + APIVersion: deployment.APIVersion, + Kind: deployment.Kind, + Name: deployment.Name, + }, + }, policyv1alpha1.Placement{ + ClusterAffinity: &policyv1alpha1.ClusterAffinity{ + ClusterNames: []string{targetCluster}, + }, + }) policy.Spec.PropagateDeps = true + customization = testhelper.NewResourceInterpreterCustomization( "interpreter-customization"+rand.String(RandomStrLength), configv1alpha1.CustomizationTarget{ @@ -613,11 +648,11 @@ end `, DependencyInterpretation: &configv1alpha1.DependencyInterpretation{ LuaScript: ` function GetDependencies(desiredObj) - dependentSas = {} - refs = {} - if desiredObj.spec.template.spec.serviceAccountName ~= '' and desiredObj.spec.template.spec.serviceAccountName ~= 'default' then - dependentSas[desiredObj.spec.template.spec.serviceAccountName] = true - end + dependentSas = {} + refs = {} + if desiredObj.spec.template.spec.serviceAccountName ~= '' and desiredObj.spec.template.spec.serviceAccountName ~= 'default' then + dependentSas[desiredObj.spec.template.spec.serviceAccountName] = true + end local idx = 1 for key, value in pairs(dependentSas) do dependObj = {} @@ -632,25 +667,84 @@ function GetDependencies(desiredObj) end `, }, }) - }) - ginkgo.It("DependencyInterpretation testing", func() { - ginkgo.By("check if the serviceAccount is propagated automatically", func() { - framework.CreateServiceAccount(kubeClient, sa) - ginkgo.DeferCleanup(func() { - framework.RemoveServiceAccount(kubeClient, sa.GetNamespace(), sa.GetName()) - }) - framework.WaitDeploymentPresentOnClusterFitWith(targetCluster, deployment.Namespace, deployment.Name, - func(deployment *appsv1.Deployment) bool { - return true - }) - framework.WaitServiceAccountPresentOnClusterFitWith(targetCluster, deployment.Namespace, sa.GetName(), - func(sa *corev1.ServiceAccount) bool { - return true - }) + customizationAnother = testhelper.NewResourceInterpreterCustomization( + "interpreter-customization"+rand.String(RandomStrLength), + configv1alpha1.CustomizationTarget{ + APIVersion: "apps/v1", + Kind: "Deployment", + }, + configv1alpha1.CustomizationRules{ + DependencyInterpretation: &configv1alpha1.DependencyInterpretation{ + LuaScript: ` +function GetDependencies(desiredObj) + dependentSas = {} + refs = {} + if desiredObj.spec.template.spec.volumes == nil then + return refs + end + local idx = 1 + for index, volume in pairs(desiredObj.spec.template.spec.volumes) do + if volume.configMap ~= nil then + dependObj = {} + dependObj.apiVersion = 'v1' + dependObj.kind = 'ConfigMap' + dependObj.name = volume.configMap.name + dependObj.namespace = desiredObj.metadata.namespace + refs[idx] = dependObj + idx = idx + 1 + end + end + return refs +end `, + }, + }) + }) + + ginkgo.JustBeforeEach(func() { + framework.CreateResourceInterpreterCustomization(karmadaClient, customization) + framework.CreateResourceInterpreterCustomization(karmadaClient, customizationAnother) + // Wait for resource interpreter informer synced. + time.Sleep(time.Second) + + framework.CreateServiceAccount(kubeClient, sa) + framework.CreateConfigMap(kubeClient, configMap) + + framework.CreatePropagationPolicy(karmadaClient, policy) + framework.CreateDeployment(kubeClient, deployment) + + ginkgo.DeferCleanup(func() { + framework.RemoveServiceAccount(kubeClient, sa.Namespace, sa.Name) + framework.RemoveConfigMap(kubeClient, configMap.Namespace, configMap.Name) + + framework.RemoveDeployment(kubeClient, deployment.Namespace, deployment.Name) + framework.RemovePropagationPolicy(karmadaClient, policy.Namespace, policy.Name) + + framework.DeleteResourceInterpreterCustomization(karmadaClient, customization.Name) + framework.DeleteResourceInterpreterCustomization(karmadaClient, customizationAnother.Name) }) }) - }) + ginkgo.Context("InterpreterOperation DependencyInterpretation testing", func() { + ginkgo.It("DependencyInterpretation testing", func() { + ginkgo.By("check if the resources is propagated automatically", func() { + framework.WaitDeploymentPresentOnClusterFitWith(targetCluster, deployment.Namespace, deployment.Name, + func(deployment *appsv1.Deployment) bool { + return true + }) + framework.WaitServiceAccountPresentOnClusterFitWith(targetCluster, sa.Namespace, sa.Name, + func(sa *corev1.ServiceAccount) bool { + return true + }) + + framework.WaitConfigMapPresentOnClusterFitWith(targetCluster, configMap.Namespace, configMap.Name, + func(configmap *corev1.ConfigMap) bool { + return true + }) + }) + }) + + }) + }) })