368 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			368 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
/*
 | 
						|
Copyright 2024 The Karmada Authors.
 | 
						|
 | 
						|
Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
you may not use this file except in compliance with the License.
 | 
						|
You may obtain a copy of the License at
 | 
						|
 | 
						|
    http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
Unless required by applicable law or agreed to in writing, software
 | 
						|
distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
See the License for the specific language governing permissions and
 | 
						|
limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package e2e
 | 
						|
 | 
						|
import (
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/onsi/ginkgo/v2"
 | 
						|
	appsv1 "k8s.io/api/apps/v1"
 | 
						|
	corev1 "k8s.io/api/core/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/util/rand"
 | 
						|
	"k8s.io/utils/pointer"
 | 
						|
 | 
						|
	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 | 
						|
	"github.com/karmada-io/karmada/pkg/util"
 | 
						|
	"github.com/karmada-io/karmada/test/e2e/framework"
 | 
						|
	testhelper "github.com/karmada-io/karmada/test/helper"
 | 
						|
)
 | 
						|
 | 
						|
const waitIntervalForLazyPolicyTest = 3 * time.Second
 | 
						|
 | 
						|
// e2e test for https://github.com/karmada-io/karmada/blob/release-1.9/docs/proposals/scheduling/activation-preference/lazy-activation-preference.md#test-plan
 | 
						|
var _ = ginkgo.Describe("Lazy activation policy testing", func() {
 | 
						|
	var namespace string
 | 
						|
	var deploymentName, configMapName, policyName, policyHigherPriorityName string
 | 
						|
	var originalCluster, modifiedCluster string
 | 
						|
	var deployment *appsv1.Deployment
 | 
						|
	var configMap *corev1.ConfigMap
 | 
						|
	var policy, policyHigherPriority *policyv1alpha1.PropagationPolicy
 | 
						|
 | 
						|
	ginkgo.BeforeEach(func() {
 | 
						|
		namespace = testNamespace
 | 
						|
		deploymentName = deploymentNamePrefix + rand.String(RandomStrLength)
 | 
						|
		configMapName = deploymentName
 | 
						|
		policyName = deploymentName
 | 
						|
		policyHigherPriorityName = deploymentName + "higherpriority"
 | 
						|
		originalCluster = framework.ClusterNames()[0]
 | 
						|
		modifiedCluster = framework.ClusterNames()[1]
 | 
						|
 | 
						|
		deployment = testhelper.NewDeployment(namespace, deploymentName)
 | 
						|
		configMap = testhelper.NewConfigMap(namespace, configMapName, map[string]string{"test": "test"})
 | 
						|
		policy = testhelper.NewLazyPropagationPolicy(namespace, policyName, []policyv1alpha1.ResourceSelector{
 | 
						|
			{
 | 
						|
				APIVersion: deployment.APIVersion,
 | 
						|
				Kind:       deployment.Kind,
 | 
						|
				Name:       deploymentName,
 | 
						|
			}}, policyv1alpha1.Placement{
 | 
						|
			ClusterAffinity: &policyv1alpha1.ClusterAffinity{
 | 
						|
				ClusterNames: []string{originalCluster},
 | 
						|
			},
 | 
						|
		})
 | 
						|
		policyHigherPriority = testhelper.NewLazyPropagationPolicy(namespace, policyHigherPriorityName, []policyv1alpha1.ResourceSelector{
 | 
						|
			{
 | 
						|
				APIVersion: deployment.APIVersion,
 | 
						|
				Kind:       deployment.Kind,
 | 
						|
				Name:       deploymentName,
 | 
						|
			}}, policyv1alpha1.Placement{
 | 
						|
			ClusterAffinity: &policyv1alpha1.ClusterAffinity{
 | 
						|
				ClusterNames: []string{modifiedCluster},
 | 
						|
			},
 | 
						|
		})
 | 
						|
		policyHigherPriority.Spec.Priority = pointer.Int32(2)
 | 
						|
		policyHigherPriority.Spec.Preemption = policyv1alpha1.PreemptAlways
 | 
						|
	})
 | 
						|
 | 
						|
	ginkgo.Context("1. Policy created before resource", func() {
 | 
						|
		ginkgo.JustBeforeEach(func() {
 | 
						|
			framework.CreatePropagationPolicy(karmadaClient, policy)
 | 
						|
			waitPropagatePolicyReconciled(namespace, policyName)
 | 
						|
			framework.CreateDeployment(kubeClient, deployment)
 | 
						|
 | 
						|
			ginkgo.DeferCleanup(func() {
 | 
						|
				framework.RemovePropagationPolicyIfExist(karmadaClient, namespace, policyName)
 | 
						|
				framework.RemoveDeployment(kubeClient, namespace, deploymentName)
 | 
						|
				framework.WaitDeploymentDisappearOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
				framework.WaitDeploymentDisappearOnCluster(modifiedCluster, namespace, deploymentName)
 | 
						|
			})
 | 
						|
		})
 | 
						|
 | 
						|
		// Simple Case 1 (Policy created before resource)
 | 
						|
		// refer: https://github.com/karmada-io/karmada/blob/release-1.9/docs/proposals/scheduling/activation-preference/lazy-activation-preference.md#simple-case-1-policy-created-before-resource
 | 
						|
		ginkgo.It("Simple Case 1 (Policy created before resource)", func() {
 | 
						|
			ginkgo.By("step 1: deployment propagate success when policy created before it", func() {
 | 
						|
				waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
			})
 | 
						|
 | 
						|
			ginkgo.By("step 2: after policy deleted, deployment still keep previous propagation states", func() {
 | 
						|
				framework.RemovePropagationPolicy(karmadaClient, namespace, policyName)
 | 
						|
				// wait to distinguish whether the state will not change or have no time to change
 | 
						|
				time.Sleep(waitIntervalForLazyPolicyTest)
 | 
						|
				waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
			})
 | 
						|
		})
 | 
						|
 | 
						|
		// Simple Case 3 (Lazy to immediate)
 | 
						|
		// refer: https://github.com/karmada-io/karmada/blob/release-1.9/docs/proposals/scheduling/activation-preference/lazy-activation-preference.md#simple-case-3-lazy-to-immediate
 | 
						|
		ginkgo.It("Simple Case 3 (Lazy to immediate)", func() {
 | 
						|
			ginkgo.By("step 1: deployment propagate success when policy created before it", func() {
 | 
						|
				waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
			})
 | 
						|
 | 
						|
			ginkgo.By("step 2: after policy updated (cluster=member2, remove lazy activationPreference field), the propagation of deployment changed", func() {
 | 
						|
				// 1. remove lazy activationPreference field
 | 
						|
				policy.Spec.ActivationPreference = ""
 | 
						|
				// 2. update policy placement with clusterAffinities
 | 
						|
				changePlacementTargetCluster(policy, modifiedCluster)
 | 
						|
				// 3. the propagation of deployment changed
 | 
						|
				framework.WaitDeploymentDisappearOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
				waitDeploymentPresentOnCluster(modifiedCluster, namespace, deploymentName)
 | 
						|
			})
 | 
						|
		})
 | 
						|
 | 
						|
		ginkgo.Context("Immediate to lazy", func() {
 | 
						|
			ginkgo.BeforeEach(func() {
 | 
						|
				// remove lazy activationPreference field
 | 
						|
				policy.Spec.ActivationPreference = ""
 | 
						|
			})
 | 
						|
 | 
						|
			// Simple Case 4 (Immediate to lazy)
 | 
						|
			// refer: https://github.com/karmada-io/karmada/blob/release-1.9/docs/proposals/scheduling/activation-preference/lazy-activation-preference.md#simple-case-4-immediate-to-lazy
 | 
						|
			ginkgo.It("Simple Case 4 (Immediate to lazy)", func() {
 | 
						|
				ginkgo.By("step 1: deployment propagate success", func() {
 | 
						|
					waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
				})
 | 
						|
 | 
						|
				ginkgo.By("step 2: after policy updated (cluster=member2, activationPreference=lazy), the propagation of deployment unchanged", func() {
 | 
						|
					// 1. activationPreference=lazy
 | 
						|
					policy.Spec.ActivationPreference = policyv1alpha1.LazyActivation
 | 
						|
					// 2. update policy placement with clusterAffinities
 | 
						|
					changePlacementTargetCluster(policy, modifiedCluster)
 | 
						|
					// 3. wait to distinguish whether the state will not change or have no time to change
 | 
						|
					time.Sleep(waitIntervalForLazyPolicyTest)
 | 
						|
					waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
					framework.WaitDeploymentDisappearOnCluster(modifiedCluster, namespace, deploymentName)
 | 
						|
				})
 | 
						|
 | 
						|
				ginkgo.By("step3: resource would propagate when itself updated", func() {
 | 
						|
					// 1. update annotation of the deployment
 | 
						|
					updateDeploymentManually(deployment)
 | 
						|
					// 2. the propagation of deployment changed
 | 
						|
					framework.WaitDeploymentDisappearOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
					waitDeploymentPresentOnCluster(modifiedCluster, namespace, deploymentName)
 | 
						|
				})
 | 
						|
			})
 | 
						|
		})
 | 
						|
 | 
						|
		// Combined Case 4 (Policy preemption)
 | 
						|
		// refer: https://github.com/karmada-io/karmada/blob/release-1.9/docs/proposals/scheduling/activation-preference/lazy-activation-preference.md#combined-case-4-policy-preemption
 | 
						|
		ginkgo.It("Policy preemption", func() {
 | 
						|
			ginkgo.By("step 1: deployment propagate success when policy created before it", func() {
 | 
						|
				waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
			})
 | 
						|
 | 
						|
			ginkgo.By("step 2: create PP2 (match nginx, cluster=member2, lazy, priority=2, preemption=true)", func() {
 | 
						|
				framework.CreatePropagationPolicy(karmadaClient, policyHigherPriority)
 | 
						|
				// 1. annotation of policy name changed
 | 
						|
				framework.WaitDeploymentFitWith(kubeClient, namespace, deploymentName, func(deployment *appsv1.Deployment) bool {
 | 
						|
					policyNameAnnotation := util.GetAnnotationValue(deployment.GetAnnotations(), policyv1alpha1.PropagationPolicyNameAnnotation)
 | 
						|
					return policyNameAnnotation == policyHigherPriorityName
 | 
						|
				})
 | 
						|
				// 2. wait to distinguish whether the state will not change or have no time to change
 | 
						|
				time.Sleep(waitIntervalForLazyPolicyTest)
 | 
						|
				// propagation unchanged
 | 
						|
				waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
				framework.WaitDeploymentDisappearOnCluster(modifiedCluster, namespace, deploymentName)
 | 
						|
			})
 | 
						|
 | 
						|
			ginkgo.By("step 3: update deployment", func() {
 | 
						|
				// 1. update annotation of the deployment
 | 
						|
				updateDeploymentManually(deployment)
 | 
						|
				// 2. the propagation of deployment changed
 | 
						|
				waitDeploymentPresentOnCluster(modifiedCluster, namespace, deploymentName)
 | 
						|
				framework.WaitDeploymentDisappearOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
			})
 | 
						|
 | 
						|
			ginkgo.By("step 4: clean up", func() {
 | 
						|
				framework.RemovePropagationPolicyIfExist(karmadaClient, namespace, policyHigherPriorityName)
 | 
						|
			})
 | 
						|
		})
 | 
						|
 | 
						|
		ginkgo.Context("Propagate dependencies", func() {
 | 
						|
			ginkgo.BeforeEach(func() {
 | 
						|
				policy.Spec.PropagateDeps = true
 | 
						|
				mountConfigMapToDeployment(deployment, configMapName)
 | 
						|
			})
 | 
						|
 | 
						|
			ginkgo.JustBeforeEach(func() {
 | 
						|
				framework.CreateConfigMap(kubeClient, configMap)
 | 
						|
				ginkgo.DeferCleanup(func() {
 | 
						|
					framework.RemoveConfigMap(kubeClient, namespace, configMapName)
 | 
						|
				})
 | 
						|
			})
 | 
						|
 | 
						|
			// Combined Case 5 (Propagate dependencies)
 | 
						|
			// refer: https://github.com/karmada-io/karmada/blob/release-1.9/docs/proposals/scheduling/activation-preference/lazy-activation-preference.md#combined-case-5-propagate-dependencies
 | 
						|
			ginkgo.It("Combined Case 5 (Propagate dependencies)", func() {
 | 
						|
				ginkgo.By("step 1: deployment and its dependencies could propagate success.", func() {
 | 
						|
					waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
					waitConfigMapPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
				})
 | 
						|
 | 
						|
				ginkgo.By("step 2: change of lazy policy will not take effect", func() {
 | 
						|
					changePlacementTargetCluster(policy, modifiedCluster)
 | 
						|
					// wait to distinguish whether the policy will not take effect or have no time to take effect
 | 
						|
					time.Sleep(waitIntervalForLazyPolicyTest)
 | 
						|
					waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
					waitConfigMapPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
				})
 | 
						|
 | 
						|
				ginkgo.By("step 3: lazy policy take effect when deployment updated, dependencies can also been propagated.", func() {
 | 
						|
					updateDeploymentManually(deployment)
 | 
						|
					waitDeploymentPresentOnCluster(modifiedCluster, namespace, deploymentName)
 | 
						|
					waitConfigMapPresentOnCluster(modifiedCluster, namespace, deploymentName)
 | 
						|
				})
 | 
						|
			})
 | 
						|
		})
 | 
						|
	})
 | 
						|
 | 
						|
	ginkgo.Context("2. Policy created after resource", func() {
 | 
						|
		ginkgo.JustBeforeEach(func() {
 | 
						|
			framework.CreateDeployment(kubeClient, deployment)
 | 
						|
			waitDeploymentReconciled(namespace, deploymentName)
 | 
						|
			framework.CreatePropagationPolicy(karmadaClient, policy)
 | 
						|
 | 
						|
			ginkgo.DeferCleanup(func() {
 | 
						|
				framework.RemovePropagationPolicy(karmadaClient, namespace, policyName)
 | 
						|
				framework.RemoveDeployment(kubeClient, namespace, deploymentName)
 | 
						|
				framework.WaitDeploymentDisappearOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
			})
 | 
						|
		})
 | 
						|
 | 
						|
		// Simple Case 2 (Policy created after resource)
 | 
						|
		// refer: https://github.com/karmada-io/karmada/blob/release-1.9/docs/proposals/scheduling/activation-preference/lazy-activation-preference.md#simple-case-2-policy-created-after-resource
 | 
						|
		ginkgo.It("Simple Case 2 (Policy created after resource)", func() {
 | 
						|
			ginkgo.By("step1: deployment would not propagate when lazy policy created after deployment", func() {
 | 
						|
				// wait to distinguish whether the deployment will not propagate or have no time to propagate
 | 
						|
				time.Sleep(waitIntervalForLazyPolicyTest)
 | 
						|
				framework.WaitDeploymentDisappearOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
			})
 | 
						|
 | 
						|
			ginkgo.By("step2: resource would propagate when itself updated", func() {
 | 
						|
				updateDeploymentManually(deployment)
 | 
						|
				waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
			})
 | 
						|
		})
 | 
						|
 | 
						|
		ginkgo.Context("Propagate dependencies", func() {
 | 
						|
			ginkgo.BeforeEach(func() {
 | 
						|
				mountConfigMapToDeployment(deployment, configMapName)
 | 
						|
				framework.CreateConfigMap(kubeClient, configMap)
 | 
						|
				ginkgo.DeferCleanup(func() {
 | 
						|
					framework.RemoveConfigMap(kubeClient, namespace, configMapName)
 | 
						|
				})
 | 
						|
			})
 | 
						|
 | 
						|
			// Combined Case 6 (Propagate dependencies)
 | 
						|
			// refer: https://github.com/karmada-io/karmada/blob/release-1.9/docs/proposals/scheduling/activation-preference/lazy-activation-preference.md#combined-case-6-propagate-dependencies
 | 
						|
			ginkgo.It("Combined Case 6 (Propagate dependencies)", func() {
 | 
						|
				ginkgo.By("step 1: resources would not propagate when lazy policy created after resources", func() {
 | 
						|
					// wait to distinguish whether the resource will not propagate or have no time to propagate
 | 
						|
					time.Sleep(waitIntervalForLazyPolicyTest)
 | 
						|
					framework.WaitDeploymentDisappearOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
					framework.WaitConfigMapDisappearOnCluster(originalCluster, namespace, configMapName)
 | 
						|
				})
 | 
						|
 | 
						|
				ginkgo.By("step 2: configMap not propagate with deployment since policy PropagateDeps unset", func() {
 | 
						|
					updateDeploymentManually(deployment)
 | 
						|
					waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
					framework.WaitConfigMapDisappearOnCluster(originalCluster, namespace, configMapName)
 | 
						|
				})
 | 
						|
 | 
						|
				ginkgo.By("step 3: set PropagateDeps of a lazy policy would not take effect immediately", func() {
 | 
						|
					setPolicyPropagateDeps(policy)
 | 
						|
					// wait to distinguish whether the policy will not take effect or have no time to take effect
 | 
						|
					time.Sleep(waitIntervalForLazyPolicyTest)
 | 
						|
					waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
					framework.WaitConfigMapDisappearOnCluster(originalCluster, namespace, configMapName)
 | 
						|
				})
 | 
						|
 | 
						|
				ginkgo.By("step 4: set PropagateDeps of a lazy policy take effect when deployment itself updated", func() {
 | 
						|
					updateDeploymentManually(deployment)
 | 
						|
					waitDeploymentPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
					waitConfigMapPresentOnCluster(originalCluster, namespace, deploymentName)
 | 
						|
				})
 | 
						|
			})
 | 
						|
		})
 | 
						|
	})
 | 
						|
})
 | 
						|
 | 
						|
// updateDeploymentManually manually update deployment
 | 
						|
func updateDeploymentManually(deployment *appsv1.Deployment) {
 | 
						|
	framework.AppendDeploymentAnnotations(kubeClient, deployment, map[string]string{"reconcileAt": time.Now().Format(time.RFC3339)})
 | 
						|
}
 | 
						|
 | 
						|
// waitDeploymentPresentOnCluster wait deployment present on cluster
 | 
						|
func waitDeploymentPresentOnCluster(cluster, namespace, name string) {
 | 
						|
	framework.WaitDeploymentPresentOnClusterFitWith(cluster, namespace, name, func(_ *appsv1.Deployment) bool {
 | 
						|
		return true
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// waitDeploymentReconciled wait reconciliation of deployment finished
 | 
						|
func waitDeploymentReconciled(namespace, name string) {
 | 
						|
	framework.WaitDeploymentFitWith(kubeClient, namespace, name, func(_ *appsv1.Deployment) bool {
 | 
						|
		// when applying deployment and policy sequentially, we expect deployment to perform the reconcile process before policy,
 | 
						|
		// but the order is actually uncertain, so we sleep a while to wait reconciliation of deployment finished.
 | 
						|
		time.Sleep(waitIntervalForLazyPolicyTest)
 | 
						|
		return true
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// waitPropagatePolicyReconciled wait reconciliation of PropagatePolicy finished
 | 
						|
func waitPropagatePolicyReconciled(namespace, name string) {
 | 
						|
	framework.WaitPropagationPolicyFitWith(karmadaClient, namespace, name, func(_ *policyv1alpha1.PropagationPolicy) bool {
 | 
						|
		// when applying policy and deployment sequentially, we expect policy to perform the reconcile process before deployment,
 | 
						|
		// but the order is actually uncertain, so we sleep a while to wait reconciliation of policy finished.
 | 
						|
		time.Sleep(waitIntervalForLazyPolicyTest)
 | 
						|
		return true
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// waitConfigMapPresentOnCluster wait configmap present on cluster
 | 
						|
func waitConfigMapPresentOnCluster(cluster, namespace, name string) {
 | 
						|
	framework.WaitConfigMapPresentOnClusterFitWith(cluster, namespace, name, func(_ *corev1.ConfigMap) bool {
 | 
						|
		return true
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// mountConfigMapToDeployment mount ConfigMap to Deployment
 | 
						|
func mountConfigMapToDeployment(deployment *appsv1.Deployment, configMapName string) {
 | 
						|
	volumes := []corev1.Volume{{
 | 
						|
		Name: "vol-configmap",
 | 
						|
		VolumeSource: corev1.VolumeSource{
 | 
						|
			ConfigMap: &corev1.ConfigMapVolumeSource{
 | 
						|
				LocalObjectReference: corev1.LocalObjectReference{
 | 
						|
					Name: configMapName,
 | 
						|
				}}}}}
 | 
						|
	deployment.Spec.Template.Spec.Volumes = volumes
 | 
						|
}
 | 
						|
 | 
						|
// changePlacementTargetCluster change policy target cluster to @modifiedCluster
 | 
						|
func changePlacementTargetCluster(policy *policyv1alpha1.PropagationPolicy, modifiedCluster string) {
 | 
						|
	policySpec := policy.Spec
 | 
						|
	policySpec.Placement.ClusterAffinity = &policyv1alpha1.ClusterAffinity{ClusterNames: []string{modifiedCluster}}
 | 
						|
	framework.UpdatePropagationPolicyWithSpec(karmadaClient, policy.Namespace, policy.Name, policySpec)
 | 
						|
}
 | 
						|
 | 
						|
// setPolicyPropagateDeps set PropagateDeps of policy to true
 | 
						|
func setPolicyPropagateDeps(policy *policyv1alpha1.PropagationPolicy) {
 | 
						|
	policySpec := policy.Spec
 | 
						|
	policySpec.PropagateDeps = true
 | 
						|
	framework.UpdatePropagationPolicyWithSpec(karmadaClient, policy.Namespace, policy.Name, policySpec)
 | 
						|
}
 |