add a clusterEvicted plugin
Signed-off-by: Poor12 <shentiecheng@huawei.com>
This commit is contained in:
parent
0f84dd76a9
commit
80eab7bcb3
|
@ -0,0 +1,42 @@
|
||||||
|
package clustereviction
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
||||||
|
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
|
||||||
|
"github.com/karmada-io/karmada/pkg/scheduler/framework"
|
||||||
|
"github.com/karmada-io/karmada/pkg/util/helper"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Name is the name of the plugin used in the plugin registry and configurations.
|
||||||
|
Name = "ClusterEviction"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClusterEviction is a plugin that checks if the target cluster is in the GracefulEvictionTasks which means it is in the process of eviction.
|
||||||
|
type ClusterEviction struct{}
|
||||||
|
|
||||||
|
var _ framework.FilterPlugin = &ClusterEviction{}
|
||||||
|
|
||||||
|
// New instantiates the ClusterEviction plugin.
|
||||||
|
func New() (framework.Plugin, error) {
|
||||||
|
return &ClusterEviction{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the plugin name.
|
||||||
|
func (p *ClusterEviction) Name() string {
|
||||||
|
return Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter checks if the target cluster is in the GracefulEvictionTasks which means it is in the process of eviction.
|
||||||
|
func (p *ClusterEviction) Filter(_ context.Context, bindingSpec *workv1alpha2.ResourceBindingSpec, _ *workv1alpha2.ResourceBindingStatus, cluster *clusterv1alpha1.Cluster) *framework.Result {
|
||||||
|
if helper.ClusterInGracefulEvictionTasks(bindingSpec.GracefulEvictionTasks, cluster.Name) {
|
||||||
|
klog.V(2).Infof("Cluster(%s) is in the process of eviction.", cluster.Name)
|
||||||
|
return framework.NewResult(framework.Unschedulable, "cluster(s) is in the process of eviction")
|
||||||
|
}
|
||||||
|
|
||||||
|
return framework.NewResult(framework.Success)
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package plugins
|
||||||
import (
|
import (
|
||||||
"github.com/karmada-io/karmada/pkg/scheduler/framework/plugins/apienablement"
|
"github.com/karmada-io/karmada/pkg/scheduler/framework/plugins/apienablement"
|
||||||
"github.com/karmada-io/karmada/pkg/scheduler/framework/plugins/clusteraffinity"
|
"github.com/karmada-io/karmada/pkg/scheduler/framework/plugins/clusteraffinity"
|
||||||
|
"github.com/karmada-io/karmada/pkg/scheduler/framework/plugins/clustereviction"
|
||||||
"github.com/karmada-io/karmada/pkg/scheduler/framework/plugins/clusterlocality"
|
"github.com/karmada-io/karmada/pkg/scheduler/framework/plugins/clusterlocality"
|
||||||
"github.com/karmada-io/karmada/pkg/scheduler/framework/plugins/spreadconstraint"
|
"github.com/karmada-io/karmada/pkg/scheduler/framework/plugins/spreadconstraint"
|
||||||
"github.com/karmada-io/karmada/pkg/scheduler/framework/plugins/tainttoleration"
|
"github.com/karmada-io/karmada/pkg/scheduler/framework/plugins/tainttoleration"
|
||||||
|
@ -17,5 +18,6 @@ func NewInTreeRegistry() runtime.Registry {
|
||||||
clusteraffinity.Name: clusteraffinity.New,
|
clusteraffinity.Name: clusteraffinity.New,
|
||||||
spreadconstraint.Name: spreadconstraint.New,
|
spreadconstraint.Name: spreadconstraint.New,
|
||||||
clusterlocality.Name: clusterlocality.New,
|
clusterlocality.Name: clusterlocality.New,
|
||||||
|
clustereviction.Name: clustereviction.New,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package helper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
||||||
|
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsAPIEnabled checks if target API (or CRD) referencing by groupVersion and kind has been installed.
|
// IsAPIEnabled checks if target API (or CRD) referencing by groupVersion and kind has been installed.
|
||||||
|
@ -21,3 +22,14 @@ func IsAPIEnabled(APIEnablements []clusterv1alpha1.APIEnablement, groupVersion s
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClusterInGracefulEvictionTasks checks if the target cluster is in the GracefulEvictionTasks which means it is in the process of eviction.
|
||||||
|
func ClusterInGracefulEvictionTasks(gracefulEvictionTasks []workv1alpha2.GracefulEvictionTask, clusterName string) bool {
|
||||||
|
for _, task := range gracefulEvictionTasks {
|
||||||
|
if task.FromCluster == clusterName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
||||||
|
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsAPIEnabled(t *testing.T) {
|
func TestIsAPIEnabled(t *testing.T) {
|
||||||
|
@ -72,3 +73,48 @@ func TestIsAPIEnabled(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClusterInGracefulEvictionTasks(t *testing.T) {
|
||||||
|
gracefulEvictionTasks := []workv1alpha2.GracefulEvictionTask{
|
||||||
|
{
|
||||||
|
FromCluster: ClusterMember1,
|
||||||
|
Producer: workv1alpha2.EvictionProducerTaintManager,
|
||||||
|
Reason: workv1alpha2.EvictionReasonTaintUntolerated,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
FromCluster: ClusterMember2,
|
||||||
|
Producer: workv1alpha2.EvictionProducerTaintManager,
|
||||||
|
Reason: workv1alpha2.EvictionReasonTaintUntolerated,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
gracefulEvictionTasks []workv1alpha2.GracefulEvictionTask
|
||||||
|
targetCluster string
|
||||||
|
expect bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "targetCluster is in the process of eviction",
|
||||||
|
gracefulEvictionTasks: gracefulEvictionTasks,
|
||||||
|
targetCluster: ClusterMember1,
|
||||||
|
expect: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "targetCluster is not in the process of eviction",
|
||||||
|
gracefulEvictionTasks: gracefulEvictionTasks,
|
||||||
|
targetCluster: ClusterMember3,
|
||||||
|
expect: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
tc := test
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := ClusterInGracefulEvictionTasks(tc.gracefulEvictionTasks, tc.targetCluster)
|
||||||
|
if result != tc.expect {
|
||||||
|
t.Errorf("expected: %v, but got: %v", tc.expect, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/util/rand"
|
"k8s.io/apimachinery/pkg/util/rand"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
"k8s.io/utils/pointer"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
||||||
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
||||||
|
@ -141,6 +142,106 @@ var _ = framework.SerialDescribe("failover testing", func() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ginkgo.Context("Taint cluster testing", func() {
|
||||||
|
var policyNamespace, policyName string
|
||||||
|
var deploymentNamespace, deploymentName string
|
||||||
|
var deployment *appsv1.Deployment
|
||||||
|
var taint corev1.Taint
|
||||||
|
var maxGroups, minGroups, numOfFailedClusters int
|
||||||
|
var policy *policyv1alpha1.PropagationPolicy
|
||||||
|
maxGroups = 1
|
||||||
|
minGroups = 1
|
||||||
|
numOfFailedClusters = 1
|
||||||
|
|
||||||
|
ginkgo.BeforeEach(func() {
|
||||||
|
policyNamespace = testNamespace
|
||||||
|
policyName = deploymentNamePrefix + rand.String(RandomStrLength)
|
||||||
|
deploymentNamespace = testNamespace
|
||||||
|
deploymentName = policyName
|
||||||
|
deployment = testhelper.NewDeployment(deploymentNamespace, deploymentName)
|
||||||
|
|
||||||
|
policy = testhelper.NewPropagationPolicy(policyNamespace, policyName, []policyv1alpha1.ResourceSelector{
|
||||||
|
{
|
||||||
|
APIVersion: deployment.APIVersion,
|
||||||
|
Kind: deployment.Kind,
|
||||||
|
Name: deployment.Name,
|
||||||
|
},
|
||||||
|
}, policyv1alpha1.Placement{
|
||||||
|
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
|
||||||
|
ClusterNames: framework.ClusterNames(),
|
||||||
|
},
|
||||||
|
ClusterTolerations: []corev1.Toleration{
|
||||||
|
{
|
||||||
|
Key: "fail-test",
|
||||||
|
Effect: corev1.TaintEffectNoExecute,
|
||||||
|
Operator: corev1.TolerationOpExists,
|
||||||
|
TolerationSeconds: pointer.Int64(3),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SpreadConstraints: []policyv1alpha1.SpreadConstraint{
|
||||||
|
{
|
||||||
|
SpreadByField: policyv1alpha1.SpreadByFieldCluster,
|
||||||
|
MaxGroups: maxGroups,
|
||||||
|
MinGroups: minGroups,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
taint = corev1.Taint{
|
||||||
|
Key: "fail-test",
|
||||||
|
Effect: corev1.TaintEffectNoExecute,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.BeforeEach(func() {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("taint cluster", func() {
|
||||||
|
var disabledClusters []string
|
||||||
|
targetClusterNames := framework.ExtractTargetClustersFrom(controlPlaneClient, deployment)
|
||||||
|
ginkgo.By("taint one cluster", func() {
|
||||||
|
temp := numOfFailedClusters
|
||||||
|
for _, targetClusterName := range targetClusterNames {
|
||||||
|
if temp > 0 {
|
||||||
|
klog.Infof("Taint one cluster(%s).", targetClusterName)
|
||||||
|
err := taintCluster(controlPlaneClient, targetClusterName, taint)
|
||||||
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
||||||
|
disabledClusters = append(disabledClusters, targetClusterName)
|
||||||
|
temp--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.By("check whether deployment of failed cluster is rescheduled to other available cluster", func() {
|
||||||
|
gomega.Eventually(func() int {
|
||||||
|
targetClusterNames = framework.ExtractTargetClustersFrom(controlPlaneClient, deployment)
|
||||||
|
for _, targetClusterName := range targetClusterNames {
|
||||||
|
// the target cluster should be overwritten to another available cluster
|
||||||
|
if !testhelper.IsExclude(targetClusterName, disabledClusters) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(targetClusterNames)
|
||||||
|
}, pollTimeout, pollInterval).Should(gomega.Equal(minGroups))
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.By("recover not ready cluster", func() {
|
||||||
|
for _, disabledCluster := range disabledClusters {
|
||||||
|
fmt.Printf("cluster %s is waiting for recovering\n", disabledCluster)
|
||||||
|
err := recoverTaintedCluster(controlPlaneClient, disabledCluster, taint)
|
||||||
|
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// disableCluster will set wrong API endpoint of current cluster
|
// disableCluster will set wrong API endpoint of current cluster
|
||||||
|
@ -167,6 +268,49 @@ func disableCluster(c client.Client, clusterName string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// taintCluster will taint cluster
|
||||||
|
func taintCluster(c client.Client, clusterName string, taint corev1.Taint) error {
|
||||||
|
err := wait.PollImmediate(pollInterval, pollTimeout, func() (done bool, err error) {
|
||||||
|
clusterObj := &clusterv1alpha1.Cluster{}
|
||||||
|
if err := c.Get(context.TODO(), client.ObjectKey{Name: clusterName}, clusterObj); err != nil {
|
||||||
|
if apierrors.IsConflict(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
clusterObj.Spec.Taints = append(clusterObj.Spec.Taints, taint)
|
||||||
|
if err := c.Update(context.TODO(), clusterObj); err != nil {
|
||||||
|
if apierrors.IsConflict(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// recoverTaintedCluster will recover the taint of the disabled cluster
|
||||||
|
func recoverTaintedCluster(c client.Client, clusterName string, taint corev1.Taint) error {
|
||||||
|
err := wait.PollImmediate(pollInterval, pollTimeout, func() (done bool, err error) {
|
||||||
|
clusterObj := &clusterv1alpha1.Cluster{}
|
||||||
|
if err := c.Get(context.TODO(), client.ObjectKey{Name: clusterName}, clusterObj); err != nil {
|
||||||
|
if apierrors.IsConflict(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if err := helper.UpdateClusterControllerTaint(context.TODO(), c, nil, []*corev1.Taint{&taint}, clusterObj); err != nil {
|
||||||
|
if apierrors.IsConflict(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// recoverCluster will recover API endpoint of the disable cluster
|
// recoverCluster will recover API endpoint of the disable cluster
|
||||||
func recoverCluster(c client.Client, clusterName string, originalAPIEndpoint string) error {
|
func recoverCluster(c client.Client, clusterName string, originalAPIEndpoint string) error {
|
||||||
err := wait.PollImmediate(pollInterval, pollTimeout, func() (done bool, err error) {
|
err := wait.PollImmediate(pollInterval, pollTimeout, func() (done bool, err error) {
|
||||||
|
|
Loading…
Reference in New Issue