support multiple dependencyInterpreter lua script for one gvk

Signed-off-by: changzhen <changzhen5@huawei.com>
This commit is contained in:
changzhen 2022-11-30 09:10:27 +08:00
parent 37cca1cd8d
commit 5ed2100ee1
9 changed files with 660 additions and 386 deletions

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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 != "" {

View File

@ -256,7 +256,7 @@ func Test_validateCustomizationRule(t *testing.T) {
},
},
},
wantErr: true,
wantErr: false,
},
}
for _, tt := range tests {

View File

@ -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
})
})
})
})
})
})