Repository: karmada-io/karmada
Pull request #6368: Add the ClusterTaintPolicy controller to handle taints on the clusters
Author: login: XiShanYongYe-Chang
Labels: name: kind/feature
description: Categorizes issue or PR as related to a new feature.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: size/XL
description: Denotes a PR that changes 500-999 lines, ignoring generated files.
name: lgtm
description: Indicates that a PR is ready to be merged.
Created: 2025-05-13T06:35:50Z | Merged: 2025-05-17T02:27:29Z

**What type of PR is this?**

/kind feature
<!--
Add one of the following kinds:

/kind api-change
/kind bug
/kind cleanup
/kind deprecation
/kind design
/kind documentation
/kind failing-test
/kind feature
/kind flake

-->

**What this PR does / why we need it**:

Add the ClusterTaintPolicy controller to handle taints on the clusters

**Which issue(s) this PR fixes**:
Part of #6317

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required.
-->
```release-note
`karmada-controller-manager`: Added the `clustertaintpolicy` controller to manage cluster taint based on ClusterTaintPolicy.
```




Diff:
diff --git a/cmd/controller-manager/app/controllermanager.go b/cmd/controller-manager/app/controllermanager.go
index 801efc3e6615..6b92b094c3d2 100644
--- a/cmd/controller-manager/app/controllermanager.go
+++ b/cmd/controller-manager/app/controllermanager.go
@@ -68,6 +68,7 @@ import (
 	"github.com/karmada-io/karmada/pkg/controllers/namespace"
 	"github.com/karmada-io/karmada/pkg/controllers/remediation"
 	"github.com/karmada-io/karmada/pkg/controllers/status"
+	"github.com/karmada-io/karmada/pkg/controllers/taint"
 	"github.com/karmada-io/karmada/pkg/controllers/unifiedauth"
 	"github.com/karmada-io/karmada/pkg/controllers/workloadrebalancer"
 	"github.com/karmada-io/karmada/pkg/dependenciesdistributor"
@@ -246,6 +247,7 @@ func init() {
 	controllers["remedy"] = startRemedyController
 	controllers["workloadRebalancer"] = startWorkloadRebalancerController
 	controllers["agentcsrapproving"] = startAgentCSRApprovingController
+	controllers["clustertaintpolicy"] = startClusterTaintPolicyController
 }
 
 func startClusterController(ctx controllerscontext.Context) (enabled bool, err error) {
@@ -773,6 +775,22 @@ func startAgentCSRApprovingController(ctx controllerscontext.Context) (enabled b
 	return true, nil
 }
 
+func startClusterTaintPolicyController(ctx controllerscontext.Context) (enabled bool, err error) {
+	if !features.FeatureGate.Enabled(features.Failover) {
+		return false, nil
+	}
+
+	clusterTaintPolicyController := taint.ClusterTaintPolicyController{
+		Client:             ctx.Mgr.GetClient(),
+		EventRecorder:      ctx.Mgr.GetEventRecorderFor(taint.ControllerName),
+		RateLimiterOptions: ctx.Opts.RateLimiterOptions,
+	}
+	if err := clusterTaintPolicyController.SetupWithManager(ctx.Mgr); err != nil {
+		return false, err
+	}
+	return true, nil
+}
+
 // setupControllers initialize controllers and setup one by one.
 func setupControllers(ctx context.Context, mgr controllerruntime.Manager, opts *options.Options) {
 	restConfig := mgr.GetConfig()
diff --git a/pkg/controllers/cluster/cluster_controller.go b/pkg/controllers/cluster/cluster_controller.go
index acf167176400..e432aefb547b 100644
--- a/pkg/controllers/cluster/cluster_controller.go
+++ b/pkg/controllers/cluster/cluster_controller.go
@@ -20,7 +20,6 @@ import (
 	"context"
 	"fmt"
 	"reflect"
-	"strings"
 	"sync"
 	"time"
 
@@ -693,36 +692,7 @@ func (c *Controller) updateClusterTaints(ctx context.Context, taintsToAdd, taint
 		c.EventRecorder.Event(cluster, corev1.EventTypeWarning, events.EventReasonTaintClusterFailed, err.Error())
 		return err
 	}
-	msg := fmt.Sprintf("Taint cluster succeed: %s.", generateEventMessage(taints))
+	msg := fmt.Sprintf("Taint cluster succeed: %s.", utilhelper.GenerateTaintsMessage(taints))
 	c.EventRecorder.Event(cluster, corev1.EventTypeNormal, events.EventReasonTaintClusterSucceed, msg)
 	return nil
 }
-
-func generateEventMessage(taints []corev1.Taint) string {
-	if len(taints) == 0 {
-		return "cluster now does not have taints"
-	}
-
-	var msg string
-	for i, taint := range taints {
-		if i != 0 {
-			msg += ","
-		}
-		if taint.Value != "" {
-			msg += strings.Join([]string{`{`,
-				`Key:` + fmt.Sprintf("%v", taint.Key) + `,`,
-				`Value:` + fmt.Sprintf("%v", taint.Value) + `,`,
-				`Effect:` + fmt.Sprintf("%v", taint.Effect),
-				`}`,
-			}, "")
-		} else {
-			msg += strings.Join([]string{`{`,
-				`Key:` + fmt.Sprintf("%v", taint.Key) + `,`,
-				`Effect:` + fmt.Sprintf("%v", taint.Effect),
-				`}`,
-			}, "")
-		}
-	}
-
-	return fmt.Sprintf("cluster now has taints([%s])", msg)
-}
diff --git a/pkg/controllers/taint/clustertaintpolicy_controller.go b/pkg/controllers/taint/clustertaintpolicy_controller.go
new file mode 100644
index 000000000000..a0dd8851a203
--- /dev/null
+++ b/pkg/controllers/taint/clustertaintpolicy_controller.go
@@ -0,0 +1,244 @@
+/*
+Copyright 2025 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 taint
+
+import (
+	"context"
+	"fmt"
+	"reflect"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/record"
+	"k8s.io/klog/v2"
+	"k8s.io/utils/ptr"
+	controllerruntime "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/builder"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
+	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
+	"github.com/karmada-io/karmada/pkg/events"
+	"github.com/karmada-io/karmada/pkg/sharedcli/ratelimiterflag"
+	"github.com/karmada-io/karmada/pkg/util"
+	utilhelper "github.com/karmada-io/karmada/pkg/util/helper"
+)
+
+// ControllerName is the controller name that will be used when reporting events.
+const ControllerName = "cluster-taint-policy-controller"
+
+// ClusterTaintPolicyController is to sync ClusterTaintPolicy.
+type ClusterTaintPolicyController struct {
+	client.Client
+	EventRecorder      record.EventRecorder
+	RateLimiterOptions ratelimiterflag.Options
+}
+
+// Reconcile performs a full reconciliation for the object referred to by the Request.
+// The Controller will requeue the Request to be processed again if an error is non-nil or
+// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
+func (c *ClusterTaintPolicyController) Reconcile(ctx context.Context, req controllerruntime.Request) (controllerruntime.Result, error) {
+	klog.V(4).Infof("Reconciling Cluster %s.", req.Name)
+
+	clusterObj := &clusterv1alpha1.Cluster{}
+	if err := c.Client.Get(ctx, req.NamespacedName, clusterObj); err != nil {
+		klog.Errorf("Failed to get Cluster(%s), error: %v", req.Name, err)
+		return controllerruntime.Result{}, client.IgnoreNotFound(err)
+	}
+
+	if !clusterObj.DeletionTimestamp.IsZero() {
+		klog.V(4).Infof("Cluster(%s) is deleting.", req.Name)
+		return controllerruntime.Result{}, nil
+	}
+
+	clusterTaintPolicyList := &policyv1alpha1.ClusterTaintPolicyList{}
+	listOption := &client.ListOptions{UnsafeDisableDeepCopy: ptr.To(true)}
+	if err := c.Client.List(ctx, clusterTaintPolicyList, listOption); err != nil {
+		klog.Errorf("Failed to list ClusterTaintPolicy, error: %v", err)
+		return controllerruntime.Result{}, err
+	}
+
+	clusterCopyObj := clusterObj.DeepCopy()
+	for _, policy := range clusterTaintPolicyList.Items {
+		if policy.Spec.TargetClusters != nil && !util.ClusterMatches(clusterCopyObj, *policy.Spec.TargetClusters) {
+			continue
+		}
+
+		if conditionMatches(clusterCopyObj.Status.Conditions, policy.Spec.MatchConditions) {
+			clusterCopyObj.Spec.Taints = addTaintsOnCluster(clusterCopyObj, policy.Spec.Taints, metav1.Now())
+		} else {
+			clusterCopyObj.Spec.Taints = removeTaintsFromCluster(clusterCopyObj, policy.Spec.Taints)
+		}
+	}
+
+	if !reflect.DeepEqual(clusterObj.Spec.Taints, clusterCopyObj.Spec.Taints) {
+		objPatch := client.MergeFrom(clusterObj)
+		err := c.Client.Patch(ctx, clusterCopyObj, objPatch)
+		if err != nil {
+			klog.Errorf("Failed to patch Cluster(%s), error: %v", req.Name, err)
+			c.EventRecorder.Event(clusterCopyObj, corev1.EventTypeWarning, events.EventReasonTaintClusterFailed, err.Error())
+			return controllerruntime.Result{}, err
+		}
+
+		msg := fmt.Sprintf("Update Cluster(%s) with taints(%v) succeed", req.Name,
+			utilhelper.GenerateTaintsMessage(clusterCopyObj.Spec.Taints))
+		klog.Info(msg)
+		c.EventRecorder.Event(clusterCopyObj, corev1.EventTypeNormal, events.EventReasonTaintClusterSucceed, msg)
+		return controllerruntime.Result{}, nil
+	}
+	klog.V(4).Infof("Cluster(%s) taints are up to date.", req.Name)
+	return controllerruntime.Result{}, nil
+}
+
+// conditionMatches returns true if the conditions matches the matchConditions.
+// The match conditions are ANDed.
+// If the match conditions are empty, it will return false.
+func conditionMatches(conditions []metav1.Condition, matchConditions []policyv1alpha1.MatchCondition) bool {
+	if len(matchConditions) == 0 {
+		return false
+	}
+
+	matches := true
+	for _, matchCondition := range matchConditions {
+		match := false
+		for _, clusterCondition := range conditions {
+			if clusterCondition.Type != matchCondition.ConditionType {
+				continue
+			}
+
+			switch matchCondition.Operator {
+			case policyv1alpha1.MatchConditionOpIn:
+				for _, value := range matchCondition.StatusValues {
+					if clusterCondition.Status == value {
+						match = true
+						break
+					}
+				}
+			case policyv1alpha1.MatchConditionOpNotIn:
+				match = true
+				for _, value := range matchCondition.StatusValues {
+					if clusterCondition.Status == value {
+						match = false
+						break
+					}
+				}
+			default:
+				klog.Errorf("Unsupported MatchCondition operator: %s", matchCondition.Operator)
+				return false
+			}
+		}
+
+		if !match {
+			matches = false
+			break
+		}
+	}
+	return matches
+}
+
+// addTaintsOnCluster adds taints on the cluster.
+// If the taint already exists, it will not be added.
+// Use now to set the TimeAdded field of the taint.
+// It's convenient for testing.
+func addTaintsOnCluster(cluster *clusterv1alpha1.Cluster, taints []policyv1alpha1.Taint, now metav1.Time) []corev1.Taint {
+	clusterTaints := cluster.Spec.Taints
+	for _, taint := range taints {
+		exist := false
+		for _, clusterTaint := range clusterTaints {
+			if clusterTaint.Effect == taint.Effect &&
+				clusterTaint.Key == taint.Key {
+				exist = true
+				break
+			}
+		}
+
+		if !exist {
+			clusterTaints = append(clusterTaints, corev1.Taint{
+				Key:       taint.Key,
+				Value:     taint.Value,
+				Effect:    taint.Effect,
+				TimeAdded: &now,
+			})
+		}
+	}
+
+	return clusterTaints
+}
+
+// removeTaintsFromCluster removes taints from the cluster.
+// If the taint does not exist, it will not be removed.
+func removeTaintsFromCluster(cluster *clusterv1alpha1.Cluster, taints []policyv1alpha1.Taint) []corev1.Taint {
+	clusterTaints := cluster.Spec.Taints
+	for _, taint := range taints {
+		for index, clusterTaint := range clusterTaints {
+			if clusterTaint.Effect == taint.Effect &&
+				clusterTaint.Key == taint.Key {
+				clusterTaints = append(clusterTaints[:index], clusterTaints[index+1:]...)
+				break
+			}
+		}
+	}
+	return clusterTaints
+}
+
+// SetupWithManager creates a controller and register to controller manager.
+func (c *ClusterTaintPolicyController) SetupWithManager(mgr controllerruntime.Manager) error {
+	clusterStatusConditionPredicateFn := predicate.Funcs{
+		CreateFunc: func(_ event.CreateEvent) bool {
+			// After controller reboot, it will reconcile all clusters.
+			return true
+		},
+		UpdateFunc: func(event event.UpdateEvent) bool {
+			oldCluster := event.ObjectOld.(*clusterv1alpha1.Cluster)
+			newCluster := event.ObjectNew.(*clusterv1alpha1.Cluster)
+			return !reflect.DeepEqual(oldCluster.Status.Conditions, newCluster.Status.Conditions)
+		},
+		DeleteFunc: func(_ event.DeleteEvent) bool { return false },
+	}
+
+	clusterTaintPolicyMapFunc := handler.MapFunc(
+		func(ctx context.Context, policyObj client.Object) []reconcile.Request {
+			clusterList := &clusterv1alpha1.ClusterList{}
+			listOption := &client.ListOptions{UnsafeDisableDeepCopy: ptr.To(true)}
+			if err := c.Client.List(ctx, clusterList, listOption); err != nil {
+				klog.Errorf("Failed to list Cluster, error: %v", err)
+				return nil
+			}
+
+			policy := policyObj.(*policyv1alpha1.ClusterTaintPolicy)
+			var requests []reconcile.Request
+			for _, cluster := range clusterList.Items {
+				if policy.Spec.TargetClusters == nil ||
+					util.ClusterMatches(&cluster, *policy.Spec.TargetClusters) {
+					requests = append(requests, reconcile.Request{NamespacedName: client.ObjectKey{Name: cluster.Name}})
+				}
+			}
+			return requests
+		})
+
+	return controllerruntime.NewControllerManagedBy(mgr).
+		Named(ControllerName).
+		For(&clusterv1alpha1.Cluster{}, builder.WithPredicates(clusterStatusConditionPredicateFn)).
+		Watches(&policyv1alpha1.ClusterTaintPolicy{}, handler.EnqueueRequestsFromMapFunc(clusterTaintPolicyMapFunc)).
+		WithOptions(controller.Options{RateLimiter: ratelimiterflag.DefaultControllerRateLimiter[controllerruntime.Request](c.RateLimiterOptions)}).
+		Complete(c)
+}
diff --git a/pkg/controllers/taint/clustertaintpolicy_controller_test.go b/pkg/controllers/taint/clustertaintpolicy_controller_test.go
new file mode 100644
index 000000000000..feb2c011d5ae
--- /dev/null
+++ b/pkg/controllers/taint/clustertaintpolicy_controller_test.go
@@ -0,0 +1,297 @@
+/*
+Copyright 2025 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 taint
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
+	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
+)
+
+func TestConditionMatches(t *testing.T) {
+	tests := []struct {
+		name            string
+		conditions      []metav1.Condition
+		matchConditions []policyv1alpha1.MatchCondition
+		expected        bool
+	}{
+		{
+			name: "empty match conditions",
+			conditions: []metav1.Condition{
+				{Type: "Ready", Status: "True"},
+			},
+			matchConditions: []policyv1alpha1.MatchCondition{},
+			expected:        false,
+		},
+		{
+			name: "match: operator is In",
+			conditions: []metav1.Condition{
+				{Type: "Ready", Status: "True"},
+			},
+			matchConditions: []policyv1alpha1.MatchCondition{
+				{
+					ConditionType: "Ready",
+					Operator:      policyv1alpha1.MatchConditionOpIn,
+					StatusValues:  []metav1.ConditionStatus{metav1.ConditionTrue},
+				},
+			},
+			expected: true,
+		},
+		{
+			name: "don't match: operator is In",
+			conditions: []metav1.Condition{
+				{Type: "Ready", Status: "False"},
+			},
+			matchConditions: []policyv1alpha1.MatchCondition{
+				{
+					ConditionType: "Ready",
+					Operator:      policyv1alpha1.MatchConditionOpIn,
+					StatusValues:  []metav1.ConditionStatus{metav1.ConditionTrue},
+				},
+			},
+			expected: false,
+		},
+		{
+			name: "match: operator is NotIn",
+			conditions: []metav1.Condition{
+				{Type: "Ready", Status: "False"},
+			},
+			matchConditions: []policyv1alpha1.MatchCondition{
+				{
+					ConditionType: "Ready",
+					Operator:      policyv1alpha1.MatchConditionOpNotIn,
+					StatusValues:  []metav1.ConditionStatus{metav1.ConditionTrue, metav1.ConditionUnknown},
+				},
+			},
+			expected: true,
+		},
+		{
+			name: "don't match: operator is NotIn",
+			conditions: []metav1.Condition{
+				{Type: "Ready", Status: "False"},
+			},
+			matchConditions: []policyv1alpha1.MatchCondition{
+				{
+					ConditionType: "Ready",
+					Operator:      policyv1alpha1.MatchConditionOpNotIn,
+					StatusValues:  []metav1.ConditionStatus{metav1.ConditionFalse},
+				},
+			},
+			expected: false,
+		},
+		{
+			name: "match: multiple conditions",
+			conditions: []metav1.Condition{
+				{Type: "Ready", Status: "Unknown"},
+				{Type: "NetworkReady", Status: "False"},
+				{Type: "StorageReady", Status: "True"},
+			},
+			matchConditions: []policyv1alpha1.MatchCondition{
+				{
+					ConditionType: "Ready",
+					Operator:      policyv1alpha1.MatchConditionOpIn,
+					StatusValues:  []metav1.ConditionStatus{metav1.ConditionFalse, metav1.ConditionUnknown},
+				},
+				{
+					ConditionType: "NetworkReady",
+					Operator:      policyv1alpha1.MatchConditionOpNotIn,
+					StatusValues:  []metav1.ConditionStatus{metav1.ConditionTrue},
+				},
+			},
+			expected: true,
+		},
+		{
+			name: "don't match: multiple conditions",
+			conditions: []metav1.Condition{
+				{Type: "Ready", Status: "True"},
+				{Type: "NetworkReady", Status: "False"},
+				{Type: "StorageReady", Status: "True"},
+			},
+			matchConditions: []policyv1alpha1.MatchCondition{
+				{
+					ConditionType: "Ready",
+					Operator:      policyv1alpha1.MatchConditionOpIn,
+					StatusValues:  []metav1.ConditionStatus{metav1.ConditionFalse, metav1.ConditionUnknown},
+				},
+				{
+					ConditionType: "NetworkReady",
+					Operator:      policyv1alpha1.MatchConditionOpNotIn,
+					StatusValues:  []metav1.ConditionStatus{metav1.ConditionTrue},
+				},
+			},
+			expected: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := conditionMatches(tt.conditions, tt.matchConditions)
+			if result != tt.expected {
+				t.Errorf("test failed: %s, expected: %v, actual: %v", tt.name, tt.expected, result)
+			}
+		})
+	}
+}
+
+func TestAddTaintsOnCluster(t *testing.T) {
+	now := metav1.Now()
+	oneMinuteAgo := metav1.NewTime(now.Add(time.Duration(-1) * time.Minute))
+
+	tests := []struct {
+		name           string
+		cluster        *clusterv1alpha1.Cluster
+		taints         []policyv1alpha1.Taint
+		now            metav1.Time
+		expectedTaints []corev1.Taint
+	}{
+		{
+			name: "add new taint",
+			cluster: &clusterv1alpha1.Cluster{
+				Spec: clusterv1alpha1.ClusterSpec{
+					Taints: []corev1.Taint{},
+				},
+			},
+			taints: []policyv1alpha1.Taint{
+				{Key: "key1", Value: "value1", Effect: corev1.TaintEffectNoSchedule},
+			},
+			now: now,
+			expectedTaints: []corev1.Taint{
+				{Key: "key1", Value: "value1", Effect: corev1.TaintEffectNoSchedule, TimeAdded: &now},
+			},
+		},
+		{
+			name: "add the existing taint, don't update TimeAdded",
+			cluster: &clusterv1alpha1.Cluster{
+				Spec: clusterv1alpha1.ClusterSpec{
+					Taints: []corev1.Taint{
+						{Key: "key1", Value: "value1", Effect: corev1.TaintEffectNoSchedule, TimeAdded: &oneMinuteAgo},
+					},
+				},
+			},
+			taints: []policyv1alpha1.Taint{
+				{Key: "key1", Value: "value1", Effect: corev1.TaintEffectNoSchedule},
+			},
+			now: now,
+			expectedTaints: []corev1.Taint{
+				{Key: "key1", Value: "value1", Effect: corev1.TaintEffectNoSchedule, TimeAdded: &oneMinuteAgo},
+			},
+		},
+		{
+			name: "add multi taints",
+			cluster: &clusterv1alpha1.Cluster{
+				Spec: clusterv1alpha1.ClusterSpec{
+					Taints: []corev1.Taint{
+						{Key: "key2", Value: "value2", Effect: corev1.TaintEffectPreferNoSchedule, TimeAdded: &oneMinuteAgo},
+					},
+				},
+			},
+			taints: []policyv1alpha1.Taint{
+				{Key: "key1", Value: "value1", Effect: corev1.TaintEffectNoSchedule},
+				{Key: "key3", Value: "value3", Effect: corev1.TaintEffectNoExecute},
+			},
+			now: now,
+			expectedTaints: []corev1.Taint{
+				{Key: "key2", Value: "value2", Effect: corev1.TaintEffectPreferNoSchedule, TimeAdded: &oneMinuteAgo},
+				{Key: "key1", Value: "value1", Effect: corev1.TaintEffectNoSchedule, TimeAdded: &now},
+				{Key: "key3", Value: "value3", Effect: corev1.TaintEffectNoExecute, TimeAdded: &now},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := addTaintsOnCluster(tt.cluster, tt.taints, tt.now)
+			if !reflect.DeepEqual(result, tt.expectedTaints) {
+				t.Errorf("test failed, expected: %v, actual: %v", tt.expectedTaints, result)
+			}
+		})
+	}
+}
+
+func TestRemoveTaintsOnCluster(t *testing.T) {
+	now := metav1.Now()
+	oneMinuteAgo := metav1.NewTime(now.Add(time.Duration(-1) * time.Minute))
+
+	tests := []struct {
+		name           string
+		cluster        *clusterv1alpha1.Cluster
+		taintsToRemove []policyv1alpha1.Taint
+		expected       []corev1.Taint
+	}{
+		{
+			name: "remove a taint",
+			cluster: &clusterv1alpha1.Cluster{
+				Spec: clusterv1alpha1.ClusterSpec{
+					Taints: []corev1.Taint{
+						{Key: "key1", Effect: corev1.TaintEffectNoSchedule, TimeAdded: &oneMinuteAgo},
+					},
+				},
+			},
+			taintsToRemove: []policyv1alpha1.Taint{
+				{Key: "key1", Effect: corev1.TaintEffectNoSchedule},
+			},
+			expected: []corev1.Taint{},
+		},
+		{
+			name: "remove multiple matched taints",
+			cluster: &clusterv1alpha1.Cluster{
+				Spec: clusterv1alpha1.ClusterSpec{
+					Taints: []corev1.Taint{
+						{Key: "key1", Effect: corev1.TaintEffectNoSchedule, TimeAdded: &oneMinuteAgo},
+						{Key: "key2", Effect: corev1.TaintEffectNoExecute, TimeAdded: &now},
+					},
+				},
+			},
+			taintsToRemove: []policyv1alpha1.Taint{
+				{Key: "key1", Effect: corev1.TaintEffectNoSchedule},
+				{Key: "key2", Effect: corev1.TaintEffectNoExecute},
+			},
+			expected: []corev1.Taint{},
+		},
+		{
+			name: "no matched taints",
+			cluster: &clusterv1alpha1.Cluster{
+				Spec: clusterv1alpha1.ClusterSpec{
+					Taints: []corev1.Taint{
+						{Key: "key1", Effect: corev1.TaintEffectNoSchedule, TimeAdded: &oneMinuteAgo},
+					},
+				},
+			},
+			taintsToRemove: []policyv1alpha1.Taint{
+				{Key: "key2", Effect: corev1.TaintEffectNoExecute},
+			},
+			expected: []corev1.Taint{
+				{Key: "key1", Effect: corev1.TaintEffectNoSchedule, TimeAdded: &oneMinuteAgo},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			result := removeTaintsFromCluster(tt.cluster, tt.taintsToRemove)
+			if !reflect.DeepEqual(result, tt.expected) {
+				t.Errorf("test failed, expected: %v, actual: %v", tt.expected, result)
+			}
+		})
+	}
+}
diff --git a/pkg/util/helper/taint.go b/pkg/util/helper/taint.go
index 7217e161140b..66beecf9105a 100644
--- a/pkg/util/helper/taint.go
+++ b/pkg/util/helper/taint.go
@@ -17,6 +17,8 @@ limitations under the License.
 package helper
 
 import (
+	"fmt"
+	"strings"
 	"time"
 
 	corev1 "k8s.io/api/core/v1"
@@ -209,3 +211,33 @@ func NewUnreachableToleration(tolerationSeconds int64) *corev1.Toleration {
 		TolerationSeconds: &tolerationSeconds,
 	}
 }
+
+// GenerateTaintsMessage returns a string that describes the taints of a cluster.
+func GenerateTaintsMessage(taints []corev1.Taint) string {
+	if len(taints) == 0 {
+		return "cluster now does not have taints"
+	}
+
+	var msg string
+	for i, taint := range taints {
+		if i != 0 {
+			msg += ","
+		}
+		if taint.Value != "" {
+			msg += strings.Join([]string{`{`,
+				`Key:` + fmt.Sprintf("%v", taint.Key) + `,`,
+				`Value:` + fmt.Sprintf("%v", taint.Value) + `,`,
+				`Effect:` + fmt.Sprintf("%v", taint.Effect),
+				`}`,
+			}, "")
+		} else {
+			msg += strings.Join([]string{`{`,
+				`Key:` + fmt.Sprintf("%v", taint.Key) + `,`,
+				`Effect:` + fmt.Sprintf("%v", taint.Effect),
+				`}`,
+			}, "")
+		}
+	}
+
+	return fmt.Sprintf("cluster now has taints([%s])", msg)
+}


Reviews:
user: login: RainbowMango
body: /assign
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T02:57:34Z
user: login: RainbowMango
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T03:25:00Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds a new ClusterTaintPolicy controller to manage taints on clusters, along with the associated helper functions and controller integrations.  
- Introduces a GenerateTaintsMessage helper for a standardized taint description.  
- Implements the ClusterTaintPolicy controller with reconciliation logic based on cluster match conditions.  
- Updates the cluster controller to use the new helper and integrates the new controller into the controller manager.

### Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

| File                                              | Description                                              |
| ------------------------------------------------- | -------------------------------------------------------- |
| pkg/util/helper/taint.go                           | Added GenerateTaintsMessage to format cluster taint info |
| pkg/controllers/taint/clustertaintpolicy_controller.go | New controller to apply taint policies on clusters       |
| pkg/controllers/cluster/cluster_controller.go      | Replaced local event message generation with new helper   |
| cmd/controller-manager/app/controllermanager.go    | Integrated the new ClusterTaintPolicy controller          |




state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:27:04Z
user: login: XiShanYongYe-Chang
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T06:33:25Z
user: login: RainbowMango
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T06:57:22Z
user: login: XiShanYongYe-Chang
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T07:02:23Z
user: login: RainbowMango
body: /lgtm
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T08:55:09Z
user: login: RainbowMango
body: /lgtm
/approve
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-17T02:08:58Z

Review comments:
diff_hunk: @@ -758,6 +760,18 @@ func startAgentCSRApprovingController(ctx controllerscontext.Context) (enabled b
 	return true, nil
 }
 
+func startClusterTaintPolicyController(ctx controllerscontext.Context) (enabled bool, err error) {
+	clusterTaintPolicyController := taint.ClusterTaintPolicyController{
path: cmd/controller-manager/app/controllermanager.go
user: login: RainbowMango
body: Have you ever thought of introducing a feature gate? Or use the legacy `Failover`?
created_at: 2025-05-16T03:09:48Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,241 @@
+/*
+Copyright 2025 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 taint
+
+import (
+	"context"
+	"fmt"
+	"reflect"
+	"sort"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/record"
+	"k8s.io/klog/v2"
+	"k8s.io/utils/ptr"
+	controllerruntime "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
+	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
+	"github.com/karmada-io/karmada/pkg/events"
+	"github.com/karmada-io/karmada/pkg/sharedcli/ratelimiterflag"
+	"github.com/karmada-io/karmada/pkg/util"
+	utilhelper "github.com/karmada-io/karmada/pkg/util/helper"
+)
+
+// ControllerName is the controller name that will be used when reporting events.
+const ControllerName = "cluster-taint-policy-controller"
+
+// ClusterTaintPolicyController is to sync ClusterTaintPolicy.
+type ClusterTaintPolicyController struct {
+	client.Client
+	EventRecorder      record.EventRecorder
+	RateLimiterOptions ratelimiterflag.Options
+}
+
+// Reconcile performs a full reconciliation for the object referred to by the Request.
+// The Controller will requeue the Request to be processed again if an error is non-nil or
+// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
+func (c *ClusterTaintPolicyController) Reconcile(ctx context.Context, req controllerruntime.Request) (controllerruntime.Result, error) {
+	klog.V(4).Infof("Reconciling Cluster %s.", req.Name)
+
+	clusterObj := &clusterv1alpha1.Cluster{}
+	if err := c.Client.Get(ctx, req.NamespacedName, clusterObj); err != nil {
+		klog.Errorf("Failed to get Cluster(%s), error: %v", req.Name, err)
+		return controllerruntime.Result{}, client.IgnoreNotFound(err)
+	}
+
+	if !clusterObj.DeletionTimestamp.IsZero() {
+		klog.V(4).Infof("Cluster(%s) is deleting.", req.Name)
+		return controllerruntime.Result{}, nil
+	}
+
+	clusterTaintPolicyList := &policyv1alpha1.ClusterTaintPolicyList{}
+	listOption := &client.ListOptions{UnsafeDisableDeepCopy: ptr.To(true)}
+	if err := c.Client.List(ctx, clusterTaintPolicyList, listOption); err != nil {
+		klog.Errorf("Failed to list ClusterTaintPolicy, error: %v", err)
+		return controllerruntime.Result{}, err
+	}
+
+	clusterCopyObj := clusterObj.DeepCopy()
+	for _, policy := range clusterTaintPolicyList.Items {
+		if policy.Spec.TargetClusters != nil && !util.ClusterMatches(clusterCopyObj, *policy.Spec.TargetClusters) {
+			continue
+		}
+
+		clusterCopyObj.Spec.Taints = taintClusterWithPolicy(clusterCopyObj, policy)
+	}
+
+	if !reflect.DeepEqual(clusterObj.Spec.Taints, clusterCopyObj.Spec.Taints) {
+		objPatch := client.MergeFrom(clusterObj)
+		err := c.Client.Patch(ctx, clusterCopyObj, objPatch)
+		if err != nil {
+			klog.Errorf("Failed to patch Cluster(%s), error: %v", req.Name, err)
+			c.EventRecorder.Event(clusterCopyObj, corev1.EventTypeWarning, events.EventReasonTaintClusterFailed, err.Error())
+			return controllerruntime.Result{}, err
+		}
+
+		msg := fmt.Sprintf("Taint Cluster(%s) with taints(%v) succeed", req.Name, utilhelper.GenerateTaintsMessage(clusterCopyObj.Spec.Taints))
+		klog.V(4).Info(msg)
+		c.EventRecorder.Event(clusterCopyObj, corev1.EventTypeNormal, events.EventReasonTaintClusterSucceed, msg)
+		return controllerruntime.Result{}, nil
+	}
+	klog.V(4).Infof("Cluster(%s) taints are up to date.", req.Name)
+	return controllerruntime.Result{}, nil
+}
+
+func taintClusterWithPolicy(cluster *clusterv1alpha1.Cluster, policy policyv1alpha1.ClusterTaintPolicy) []corev1.Taint {
+	if conditionMatches(cluster, policy.Spec.MatchConditions) {
+		return addTaintsOnCluster(cluster, policy.Spec.Taints, metav1.Now())
+	}
+	return removeTaintsOnCluster(cluster, policy.Spec.Taints)
+}
+
+func conditionMatches(cluster *clusterv1alpha1.Cluster, matchConditions []policyv1alpha1.MatchCondition) bool {
+	matches := true
+	for _, matchCondition := range matchConditions {
+		match := false
+		for _, clusterCondition := range cluster.Status.Conditions {
+			if clusterCondition.Type != matchCondition.ConditionType {
+				continue
+			}
+
+			switch matchCondition.Operator {
+			case policyv1alpha1.MatchConditionOpIn:
+				for _, value := range matchCondition.StatusValues {
+					if clusterCondition.Status == value {
+						match = true
+						break
+					}
+				}
+			case policyv1alpha1.MatchConditionOpNotIn:
+				match = true
+				for _, value := range matchCondition.StatusValues {
+					if clusterCondition.Status == value {
+						match = false
+						break
+					}
+				}
+			default:
+				klog.Errorf("Unsupported MatchCondition operator: %s", matchCondition.Operator)
+				return false
+			}
+		}
+
+		if !match {
+			matches = false
+			break
+		}
+	}
+	return matches
+}
+
+func addTaintsOnCluster(cluster *clusterv1alpha1.Cluster, taints []policyv1alpha1.Taint, now metav1.Time) []corev1.Taint {
+	clusterTaints := cluster.Spec.Taints
+	for _, taint := range taints {
+		exist := false
+		for _, clusterTaint := range clusterTaints {
+			if clusterTaint.Effect == taint.Effect &&
+				clusterTaint.Key == taint.Key {
+				exist = true
+				break
+			}
+		}
+
+		if !exist {
+			clusterTaints = append(clusterTaints, corev1.Taint{
+				Key:       taint.Key,
+				Value:     taint.Value,
+				Effect:    taint.Effect,
+				TimeAdded: &now,
+			})
+		}
+	}
+
+	sort.Slice(clusterTaints, func(i, j int) bool {
+		if clusterTaints[i].Effect != clusterTaints[j].Effect {
+			return clusterTaints[i].Effect < clusterTaints[j].Effect
+		}
+		return clusterTaints[i].Key < clusterTaints[j].Key
+	})
+
+	return clusterTaints
+}
+
+func removeTaintsOnCluster(cluster *clusterv1alpha1.Cluster, taints []policyv1alpha1.Taint) []corev1.Taint {
+	clusterTaints := cluster.Spec.Taints
+	for _, taint := range taints {
+		for index, clusterTaint := range clusterTaints {
+			if clusterTaint.Effect == taint.Effect &&
+				clusterTaint.Key == taint.Key {
+				clusterTaints = append(clusterTaints[:index], clusterTaints[index+1:]...)
+				break
+			}
+		}
+	}
+	return clusterTaints
+}
+
+// SetupWithManager creates a controller and register to controller manager.
+func (c *ClusterTaintPolicyController) SetupWithManager(mgr controllerruntime.Manager) error {
+	clusterStatusConditionPredicateFn := predicate.Funcs{
+		CreateFunc: func(_ event.CreateEvent) bool {
+			// After controller reboot, it will reconcile all clusters.
+			return true
+		},
+		UpdateFunc: func(event event.UpdateEvent) bool {
+			oldCluster := event.ObjectOld.(*clusterv1alpha1.Cluster)
+			newCluster := event.ObjectNew.(*clusterv1alpha1.Cluster)
+			return reflect.DeepEqual(oldCluster.Status.Conditions, newCluster.Status.Conditions)
path: pkg/controllers/taint/clustertaintpolicy_controller.go
user: login: Copilot
body: The UpdateFunc predicate returns true when the cluster conditions remain unchanged, which may prevent triggering reconciliation on meaningful updates. Consider revising the logic to trigger a reconciliation when the conditions change.
```suggestion
			return !reflect.DeepEqual(oldCluster.Status.Conditions, newCluster.Status.Conditions)
```
created_at: 2025-05-16T06:27:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -209,3 +211,33 @@ func NewUnreachableToleration(tolerationSeconds int64) *corev1.Toleration {
 		TolerationSeconds: &tolerationSeconds,
 	}
 }
+
+// GenerateTaintsMessage returns a string that describes the taints of a cluster.
+func GenerateTaintsMessage(taints []corev1.Taint) string {
+	if len(taints) == 0 {
+		return "cluster now does not have taints"
+	}
+
+	var msg string
+	for i, taint := range taints {
+		if i != 0 {
+			msg += ","
+		}
+		if taint.Value != "" {
+			msg += strings.Join([]string{`{`,
+				`Key:` + fmt.Sprintf("%v", taint.Key) + `,`,
+				`Value:` + fmt.Sprintf("%v", taint.Value) + `,`,
+				`Effect:` + fmt.Sprintf("%v", taint.Effect),
+				`}`,
+			}, "")
+		} else {
+			msg += strings.Join([]string{`{`,
+				`Key:` + fmt.Sprintf("%v", taint.Key) + `,`,
+				`Effect:` + fmt.Sprintf("%v", taint.Effect),
+				`}`,
+			}, "")
+		}
+	}
+
+	return fmt.Sprintf("cluster now has taints([%s])", msg)
path: pkg/util/helper/taint.go
user: login: Copilot
body: [nitpick] Consider using a strings.Builder for concatenating the taint messages in GenerateTaintsMessage to improve performance and readability when processing a larger number of taints.
```suggestion
	var builder strings.Builder
	for i, taint := range taints {
		if i != 0 {
			builder.WriteString(",")
		}
		if taint.Value != "" {
			builder.WriteString("{")
			builder.WriteString("Key:" + fmt.Sprintf("%v", taint.Key) + ",")
			builder.WriteString("Value:" + fmt.Sprintf("%v", taint.Value) + ",")
			builder.WriteString("Effect:" + fmt.Sprintf("%v", taint.Effect))
			builder.WriteString("}")
		} else {
			builder.WriteString("{")
			builder.WriteString("Key:" + fmt.Sprintf("%v", taint.Key) + ",")
			builder.WriteString("Effect:" + fmt.Sprintf("%v", taint.Effect))
			builder.WriteString("}")
		}
	}

	return fmt.Sprintf("cluster now has taints([%s])", builder.String())
```
created_at: 2025-05-16T06:27:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -758,6 +760,18 @@ func startAgentCSRApprovingController(ctx controllerscontext.Context) (enabled b
 	return true, nil
 }
 
+func startClusterTaintPolicyController(ctx controllerscontext.Context) (enabled bool, err error) {
+	clusterTaintPolicyController := taint.ClusterTaintPolicyController{
path: cmd/controller-manager/app/controllermanager.go
user: login: XiShanYongYe-Chang
body: Use the legacy `Failover` to enable the controller.
created_at: 2025-05-16T06:33:25Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,246 @@
+/*
+Copyright 2025 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 taint
+
+import (
+	"context"
+	"fmt"
+	"reflect"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/record"
+	"k8s.io/klog/v2"
+	"k8s.io/utils/ptr"
+	controllerruntime "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/builder"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
+	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
+	"github.com/karmada-io/karmada/pkg/events"
+	"github.com/karmada-io/karmada/pkg/sharedcli/ratelimiterflag"
+	"github.com/karmada-io/karmada/pkg/util"
+	utilhelper "github.com/karmada-io/karmada/pkg/util/helper"
+)
+
+// ControllerName is the controller name that will be used when reporting events.
+const ControllerName = "cluster-taint-policy-controller"
+
+// ClusterTaintPolicyController is to sync ClusterTaintPolicy.
+type ClusterTaintPolicyController struct {
+	client.Client
+	EventRecorder      record.EventRecorder
+	RateLimiterOptions ratelimiterflag.Options
+}
+
+// Reconcile performs a full reconciliation for the object referred to by the Request.
+// The Controller will requeue the Request to be processed again if an error is non-nil or
+// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
+func (c *ClusterTaintPolicyController) Reconcile(ctx context.Context, req controllerruntime.Request) (controllerruntime.Result, error) {
+	klog.V(4).Infof("Reconciling Cluster %s.", req.Name)
+
+	clusterObj := &clusterv1alpha1.Cluster{}
+	if err := c.Client.Get(ctx, req.NamespacedName, clusterObj); err != nil {
+		klog.Errorf("Failed to get Cluster(%s), error: %v", req.Name, err)
+		return controllerruntime.Result{}, client.IgnoreNotFound(err)
+	}
+
+	if !clusterObj.DeletionTimestamp.IsZero() {
+		klog.V(4).Infof("Cluster(%s) is deleting.", req.Name)
+		return controllerruntime.Result{}, nil
+	}
+
+	clusterTaintPolicyList := &policyv1alpha1.ClusterTaintPolicyList{}
+	listOption := &client.ListOptions{UnsafeDisableDeepCopy: ptr.To(true)}
+	if err := c.Client.List(ctx, clusterTaintPolicyList, listOption); err != nil {
+		klog.Errorf("Failed to list ClusterTaintPolicy, error: %v", err)
+		return controllerruntime.Result{}, err
+	}
+
+	clusterCopyObj := clusterObj.DeepCopy()
+	for _, policy := range clusterTaintPolicyList.Items {
+		if policy.Spec.TargetClusters != nil && !util.ClusterMatches(clusterCopyObj, *policy.Spec.TargetClusters) {
+			continue
+		}
+
+		clusterCopyObj.Spec.Taints = taintClusterWithPolicy(clusterCopyObj, policy)
path: pkg/controllers/taint/clustertaintpolicy_controller.go
user: login: RainbowMango
body: How about changing this pattern:
```golang
		if conditionMatches(clusterCopyObj, policy.Spec.MatchConditions) {
			addTaintsOnCluster(clusterCopyObj, policy.Spec.Taints, metav1.Now())
		} else {
			removeTaintsOnCluster(clusterCopyObj, policy.Spec.Taints)
		}
```
created_at: 2025-05-16T06:34:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,246 @@
+/*
+Copyright 2025 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 taint
+
+import (
+	"context"
+	"fmt"
+	"reflect"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/record"
+	"k8s.io/klog/v2"
+	"k8s.io/utils/ptr"
+	controllerruntime "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/builder"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
+	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
+	"github.com/karmada-io/karmada/pkg/events"
+	"github.com/karmada-io/karmada/pkg/sharedcli/ratelimiterflag"
+	"github.com/karmada-io/karmada/pkg/util"
+	utilhelper "github.com/karmada-io/karmada/pkg/util/helper"
+)
+
+// ControllerName is the controller name that will be used when reporting events.
+const ControllerName = "cluster-taint-policy-controller"
+
+// ClusterTaintPolicyController is to sync ClusterTaintPolicy.
+type ClusterTaintPolicyController struct {
+	client.Client
+	EventRecorder      record.EventRecorder
+	RateLimiterOptions ratelimiterflag.Options
+}
+
+// Reconcile performs a full reconciliation for the object referred to by the Request.
+// The Controller will requeue the Request to be processed again if an error is non-nil or
+// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
+func (c *ClusterTaintPolicyController) Reconcile(ctx context.Context, req controllerruntime.Request) (controllerruntime.Result, error) {
+	klog.V(4).Infof("Reconciling Cluster %s.", req.Name)
+
+	clusterObj := &clusterv1alpha1.Cluster{}
+	if err := c.Client.Get(ctx, req.NamespacedName, clusterObj); err != nil {
+		klog.Errorf("Failed to get Cluster(%s), error: %v", req.Name, err)
+		return controllerruntime.Result{}, client.IgnoreNotFound(err)
+	}
+
+	if !clusterObj.DeletionTimestamp.IsZero() {
+		klog.V(4).Infof("Cluster(%s) is deleting.", req.Name)
+		return controllerruntime.Result{}, nil
+	}
+
+	clusterTaintPolicyList := &policyv1alpha1.ClusterTaintPolicyList{}
+	listOption := &client.ListOptions{UnsafeDisableDeepCopy: ptr.To(true)}
+	if err := c.Client.List(ctx, clusterTaintPolicyList, listOption); err != nil {
+		klog.Errorf("Failed to list ClusterTaintPolicy, error: %v", err)
+		return controllerruntime.Result{}, err
+	}
+
+	clusterCopyObj := clusterObj.DeepCopy()
+	for _, policy := range clusterTaintPolicyList.Items {
+		if policy.Spec.TargetClusters != nil && !util.ClusterMatches(clusterCopyObj, *policy.Spec.TargetClusters) {
+			continue
+		}
+
+		clusterCopyObj.Spec.Taints = taintClusterWithPolicy(clusterCopyObj, policy)
+	}
+
+	if !reflect.DeepEqual(clusterObj.Spec.Taints, clusterCopyObj.Spec.Taints) {
+		objPatch := client.MergeFrom(clusterObj)
+		err := c.Client.Patch(ctx, clusterCopyObj, objPatch)
+		if err != nil {
+			klog.Errorf("Failed to patch Cluster(%s), error: %v", req.Name, err)
+			c.EventRecorder.Event(clusterCopyObj, corev1.EventTypeWarning, events.EventReasonTaintClusterFailed, err.Error())
+			return controllerruntime.Result{}, err
+		}
+
+		msg := fmt.Sprintf("Taint Cluster(%s) with taints(%v) succeed", req.Name, utilhelper.GenerateTaintsMessage(clusterCopyObj.Spec.Taints))
+		klog.V(4).Info(msg)
+		c.EventRecorder.Event(clusterCopyObj, corev1.EventTypeNormal, events.EventReasonTaintClusterSucceed, msg)
+		return controllerruntime.Result{}, nil
+	}
+	klog.V(4).Infof("Cluster(%s) taints are up to date.", req.Name)
+	return controllerruntime.Result{}, nil
+}
+
+func taintClusterWithPolicy(cluster *clusterv1alpha1.Cluster, policy policyv1alpha1.ClusterTaintPolicy) []corev1.Taint {
+	if conditionMatches(cluster, policy.Spec.MatchConditions) {
+		return addTaintsOnCluster(cluster, policy.Spec.Taints, metav1.Now())
+	}
+	return removeTaintsOnCluster(cluster, policy.Spec.Taints)
+}
+
+// conditionMatches returns true if the cluster matches the match conditions.
+// The match conditions are ANDed.
+// If the match conditions are empty, it will return false.
+func conditionMatches(cluster *clusterv1alpha1.Cluster, matchConditions []policyv1alpha1.MatchCondition) bool {
path: pkg/controllers/taint/clustertaintpolicy_controller.go
user: login: RainbowMango
body: ```suggestion
func conditionMatches(Conditions []metav1.Condition, matchConditions []policyv1alpha1.MatchCondition) bool {
```
update the comments accordingly if it is accepted.
created_at: 2025-05-16T06:36:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,246 @@
+/*
+Copyright 2025 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 taint
+
+import (
+	"context"
+	"fmt"
+	"reflect"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/record"
+	"k8s.io/klog/v2"
+	"k8s.io/utils/ptr"
+	controllerruntime "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/builder"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
+	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
+	"github.com/karmada-io/karmada/pkg/events"
+	"github.com/karmada-io/karmada/pkg/sharedcli/ratelimiterflag"
+	"github.com/karmada-io/karmada/pkg/util"
+	utilhelper "github.com/karmada-io/karmada/pkg/util/helper"
+)
+
+// ControllerName is the controller name that will be used when reporting events.
+const ControllerName = "cluster-taint-policy-controller"
+
+// ClusterTaintPolicyController is to sync ClusterTaintPolicy.
+type ClusterTaintPolicyController struct {
+	client.Client
+	EventRecorder      record.EventRecorder
+	RateLimiterOptions ratelimiterflag.Options
+}
+
+// Reconcile performs a full reconciliation for the object referred to by the Request.
+// The Controller will requeue the Request to be processed again if an error is non-nil or
+// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
+func (c *ClusterTaintPolicyController) Reconcile(ctx context.Context, req controllerruntime.Request) (controllerruntime.Result, error) {
+	klog.V(4).Infof("Reconciling Cluster %s.", req.Name)
+
+	clusterObj := &clusterv1alpha1.Cluster{}
+	if err := c.Client.Get(ctx, req.NamespacedName, clusterObj); err != nil {
+		klog.Errorf("Failed to get Cluster(%s), error: %v", req.Name, err)
+		return controllerruntime.Result{}, client.IgnoreNotFound(err)
+	}
+
+	if !clusterObj.DeletionTimestamp.IsZero() {
+		klog.V(4).Infof("Cluster(%s) is deleting.", req.Name)
+		return controllerruntime.Result{}, nil
+	}
+
+	clusterTaintPolicyList := &policyv1alpha1.ClusterTaintPolicyList{}
+	listOption := &client.ListOptions{UnsafeDisableDeepCopy: ptr.To(true)}
+	if err := c.Client.List(ctx, clusterTaintPolicyList, listOption); err != nil {
+		klog.Errorf("Failed to list ClusterTaintPolicy, error: %v", err)
+		return controllerruntime.Result{}, err
+	}
+
+	clusterCopyObj := clusterObj.DeepCopy()
+	for _, policy := range clusterTaintPolicyList.Items {
+		if policy.Spec.TargetClusters != nil && !util.ClusterMatches(clusterCopyObj, *policy.Spec.TargetClusters) {
+			continue
+		}
+
+		clusterCopyObj.Spec.Taints = taintClusterWithPolicy(clusterCopyObj, policy)
+	}
+
+	if !reflect.DeepEqual(clusterObj.Spec.Taints, clusterCopyObj.Spec.Taints) {
+		objPatch := client.MergeFrom(clusterObj)
+		err := c.Client.Patch(ctx, clusterCopyObj, objPatch)
+		if err != nil {
+			klog.Errorf("Failed to patch Cluster(%s), error: %v", req.Name, err)
+			c.EventRecorder.Event(clusterCopyObj, corev1.EventTypeWarning, events.EventReasonTaintClusterFailed, err.Error())
+			return controllerruntime.Result{}, err
+		}
+
+		msg := fmt.Sprintf("Taint Cluster(%s) with taints(%v) succeed", req.Name, utilhelper.GenerateTaintsMessage(clusterCopyObj.Spec.Taints))
+		klog.V(4).Info(msg)
+		c.EventRecorder.Event(clusterCopyObj, corev1.EventTypeNormal, events.EventReasonTaintClusterSucceed, msg)
+		return controllerruntime.Result{}, nil
+	}
+	klog.V(4).Infof("Cluster(%s) taints are up to date.", req.Name)
+	return controllerruntime.Result{}, nil
+}
+
+func taintClusterWithPolicy(cluster *clusterv1alpha1.Cluster, policy policyv1alpha1.ClusterTaintPolicy) []corev1.Taint {
+	if conditionMatches(cluster, policy.Spec.MatchConditions) {
+		return addTaintsOnCluster(cluster, policy.Spec.Taints, metav1.Now())
+	}
+	return removeTaintsOnCluster(cluster, policy.Spec.Taints)
+}
+
+// conditionMatches returns true if the cluster matches the match conditions.
+// The match conditions are ANDed.
+// If the match conditions are empty, it will return false.
+func conditionMatches(cluster *clusterv1alpha1.Cluster, matchConditions []policyv1alpha1.MatchCondition) bool {
+	if len(matchConditions) == 0 {
+		return false
+	}
+
+	matches := true
+	for _, matchCondition := range matchConditions {
+		match := false
+		for _, clusterCondition := range cluster.Status.Conditions {
+			if clusterCondition.Type != matchCondition.ConditionType {
+				continue
+			}
+
+			switch matchCondition.Operator {
+			case policyv1alpha1.MatchConditionOpIn:
+				for _, value := range matchCondition.StatusValues {
+					if clusterCondition.Status == value {
+						match = true
+						break
+					}
+				}
+			case policyv1alpha1.MatchConditionOpNotIn:
+				match = true
+				for _, value := range matchCondition.StatusValues {
+					if clusterCondition.Status == value {
+						match = false
+						break
+					}
+				}
+			default:
+				klog.Errorf("Unsupported MatchCondition operator: %s", matchCondition.Operator)
+				return false
+			}
+		}
+
+		if !match {
+			matches = false
+			break
+		}
+	}
+	return matches
+}
+
+// addTaintsOnCluster adds taints on the cluster.
+// If the taint already exists, it will not be added.
+// Use now to set the TimeAdded field of the taint.
+// It's convenient for testing.
+func addTaintsOnCluster(cluster *clusterv1alpha1.Cluster, taints []policyv1alpha1.Taint, now metav1.Time) []corev1.Taint {
+	clusterTaints := cluster.Spec.Taints
+	for _, taint := range taints {
+		exist := false
+		for _, clusterTaint := range clusterTaints {
+			if clusterTaint.Effect == taint.Effect &&
+				clusterTaint.Key == taint.Key {
+				exist = true
+				break
+			}
+		}
+
+		if !exist {
+			clusterTaints = append(clusterTaints, corev1.Taint{
+				Key:       taint.Key,
+				Value:     taint.Value,
+				Effect:    taint.Effect,
+				TimeAdded: &now,
+			})
+		}
+	}
+
+	return clusterTaints
+}
+
+// removeTaintsOnCluster removes taints on the cluster.
+// If the taint does not exist, it will not be removed.
+func removeTaintsOnCluster(cluster *clusterv1alpha1.Cluster, taints []policyv1alpha1.Taint) []corev1.Taint {
path: pkg/controllers/taint/clustertaintpolicy_controller.go
user: login: RainbowMango
body: ```suggestion
func removeTaintsFromCluster(cluster *clusterv1alpha1.Cluster, taints []policyv1alpha1.Taint) []corev1.Taint {
```
created_at: 2025-05-16T06:37:13Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,246 @@
+/*
+Copyright 2025 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 taint
+
+import (
+	"context"
+	"fmt"
+	"reflect"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/record"
+	"k8s.io/klog/v2"
+	"k8s.io/utils/ptr"
+	controllerruntime "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/builder"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/controller"
+	"sigs.k8s.io/controller-runtime/pkg/event"
+	"sigs.k8s.io/controller-runtime/pkg/handler"
+	"sigs.k8s.io/controller-runtime/pkg/predicate"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
+	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
+	"github.com/karmada-io/karmada/pkg/events"
+	"github.com/karmada-io/karmada/pkg/sharedcli/ratelimiterflag"
+	"github.com/karmada-io/karmada/pkg/util"
+	utilhelper "github.com/karmada-io/karmada/pkg/util/helper"
+)
+
+// ControllerName is the controller name that will be used when reporting events.
+const ControllerName = "cluster-taint-policy-controller"
+
+// ClusterTaintPolicyController is to sync ClusterTaintPolicy.
+type ClusterTaintPolicyController struct {
+	client.Client
+	EventRecorder      record.EventRecorder
+	RateLimiterOptions ratelimiterflag.Options
+}
+
+// Reconcile performs a full reconciliation for the object referred to by the Request.
+// The Controller will requeue the Request to be processed again if an error is non-nil or
+// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
+func (c *ClusterTaintPolicyController) Reconcile(ctx context.Context, req controllerruntime.Request) (controllerruntime.Result, error) {
+	klog.V(4).Infof("Reconciling Cluster %s.", req.Name)
+
+	clusterObj := &clusterv1alpha1.Cluster{}
+	if err := c.Client.Get(ctx, req.NamespacedName, clusterObj); err != nil {
+		klog.Errorf("Failed to get Cluster(%s), error: %v", req.Name, err)
+		return controllerruntime.Result{}, client.IgnoreNotFound(err)
+	}
+
+	if !clusterObj.DeletionTimestamp.IsZero() {
+		klog.V(4).Infof("Cluster(%s) is deleting.", req.Name)
+		return controllerruntime.Result{}, nil
+	}
+
+	clusterTaintPolicyList := &policyv1alpha1.ClusterTaintPolicyList{}
+	listOption := &client.ListOptions{UnsafeDisableDeepCopy: ptr.To(true)}
+	if err := c.Client.List(ctx, clusterTaintPolicyList, listOption); err != nil {
+		klog.Errorf("Failed to list ClusterTaintPolicy, error: %v", err)
+		return controllerruntime.Result{}, err
+	}
+
+	clusterCopyObj := clusterObj.DeepCopy()
+	for _, policy := range clusterTaintPolicyList.Items {
+		if policy.Spec.TargetClusters != nil && !util.ClusterMatches(clusterCopyObj, *policy.Spec.TargetClusters) {
+			continue
+		}
+
+		clusterCopyObj.Spec.Taints = taintClusterWithPolicy(clusterCopyObj, policy)
+	}
+
+	if !reflect.DeepEqual(clusterObj.Spec.Taints, clusterCopyObj.Spec.Taints) {
+		objPatch := client.MergeFrom(clusterObj)
+		err := c.Client.Patch(ctx, clusterCopyObj, objPatch)
+		if err != nil {
+			klog.Errorf("Failed to patch Cluster(%s), error: %v", req.Name, err)
+			c.EventRecorder.Event(clusterCopyObj, corev1.EventTypeWarning, events.EventReasonTaintClusterFailed, err.Error())
+			return controllerruntime.Result{}, err
+		}
+
+		msg := fmt.Sprintf("Taint Cluster(%s) with taints(%v) succeed", req.Name, utilhelper.GenerateTaintsMessage(clusterCopyObj.Spec.Taints))
+		klog.V(4).Info(msg)
path: pkg/controllers/taint/clustertaintpolicy_controller.go
user: login: RainbowMango
body: This could be untaints as well. But the log only says taint...

And it deserves a log with a default level for taints updates.
created_at: 2025-05-16T06:46:01Z
author_association: MEMBER
diff_hunk: @@ -209,3 +211,33 @@ func NewUnreachableToleration(tolerationSeconds int64) *corev1.Toleration {
 		TolerationSeconds: &tolerationSeconds,
 	}
 }
+
+// GenerateTaintsMessage returns a string that describes the taints of a cluster.
+func GenerateTaintsMessage(taints []corev1.Taint) string {
+	if len(taints) == 0 {
+		return "cluster now does not have taints"
+	}
+
+	var msg string
+	for i, taint := range taints {
+		if i != 0 {
+			msg += ","
+		}
+		if taint.Value != "" {
+			msg += strings.Join([]string{`{`,
+				`Key:` + fmt.Sprintf("%v", taint.Key) + `,`,
+				`Value:` + fmt.Sprintf("%v", taint.Value) + `,`,
+				`Effect:` + fmt.Sprintf("%v", taint.Effect),
+				`}`,
+			}, "")
+		} else {
+			msg += strings.Join([]string{`{`,
+				`Key:` + fmt.Sprintf("%v", taint.Key) + `,`,
+				`Effect:` + fmt.Sprintf("%v", taint.Effect),
+				`}`,
+			}, "")
+		}
+	}
+
+	return fmt.Sprintf("cluster now has taints([%s])", msg)
path: pkg/util/helper/taint.go
user: login: XiShanYongYe-Chang
body: Good suggestion, but in this pr I just move the function site, I want to refactor it in a single pr.
created_at: 2025-05-16T07:02:23Z
author_association: MEMBER

PR comments:
user: login: XiShanYongYe-Chang
created_at: 2025-05-13T06:36:32Z
body: Waiting for #6319 
author_association: MEMBER
user: login: codecov-commenter
created_at: 2025-05-13T07:06:27Z
body: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/karmada-io/karmada/pull/6368?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) Report
:x: Patch coverage is `31.57895%` with `117 lines` in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 49.27%. Comparing base ([`5730c74`](https://app.codecov.io/gh/karmada-io/karmada/commit/5730c74159164a1a88705aba0bda480bf271f463?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io)) to head ([`3a72ee9`](https://app.codecov.io/gh/karmada-io/karmada/commit/3a72ee9d68fefd38d68c96bc14041338001ec4de?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io)).
:warning: Report is 951 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/karmada-io/karmada/pull/6368?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) | Patch % | Lines |
|---|---|---|
| [...controllers/taint/clustertaintpolicy\_controller.go](https://app.codecov.io/gh/karmada-io/karmada/pull/6368?src=pr&el=tree&filepath=pkg%2Fcontrollers%2Ftaint%2Fclustertaintpolicy_controller.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io#diff-cGtnL2NvbnRyb2xsZXJzL3RhaW50L2NsdXN0ZXJ0YWludHBvbGljeV9jb250cm9sbGVyLmdv) | 40.15% | [79 Missing :warning: ](https://app.codecov.io/gh/karmada-io/karmada/pull/6368?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) |
| [pkg/util/helper/taint.go](https://app.codecov.io/gh/karmada-io/karmada/pull/6368?src=pr&el=tree&filepath=pkg%2Futil%2Fhelper%2Ftaint.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io#diff-cGtnL3V0aWwvaGVscGVyL3RhaW50Lmdv) | 0.00% | [24 Missing :warning: ](https://app.codecov.io/gh/karmada-io/karmada/pull/6368?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) |
| [cmd/controller-manager/app/controllermanager.go](https://app.codecov.io/gh/karmada-io/karmada/pull/6368?src=pr&el=tree&filepath=cmd%2Fcontroller-manager%2Fapp%2Fcontrollermanager.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io#diff-Y21kL2NvbnRyb2xsZXItbWFuYWdlci9hcHAvY29udHJvbGxlcm1hbmFnZXIuZ28=) | 0.00% | [14 Missing :warning: ](https://app.codecov.io/gh/karmada-io/karmada/pull/6368?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) |
:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>



```diff
@@            Coverage Diff             @@
##           master    #6368      +/-   ##
==========================================
+ Coverage   49.21%   49.27%   +0.06%     
==========================================
  Files         684      686       +2     
  Lines       55327    55833     +506     
==========================================
+ Hits        27228    27514     +286     
- Misses      26326    26521     +195     
- Partials     1773     1798      +25     
```

| [Flag](https://app.codecov.io/gh/karmada-io/karmada/pull/6368/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/karmada-io/karmada/pull/6368/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) | `49.27% <31.57%> (+0.06%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io#carryforward-flags-in-the-pull-request-comment) to find out more.
</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/karmada-io/karmada/pull/6368?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io).
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: RainbowMango
created_at: 2025-05-15T10:19:33Z
body: Please rebase this 
author_association: MEMBER
user: login: XiShanYongYe-Chang
created_at: 2025-05-15T12:31:31Z
body: /hold
Under Testing.
author_association: MEMBER
user: login: karmada-bot
created_at: 2025-05-17T02:09:09Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/karmada-io/karmada/pull/6368#pullrequestreview-2847856480" title="Approved">RainbowMango</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=karmada-io%2Fkarmada).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[cmd/OWNERS](https://github.com/karmada-io/karmada/blob/master/cmd/OWNERS)~~ [RainbowMango]
- ~~[pkg/controllers/OWNERS](https://github.com/karmada-io/karmada/blob/master/pkg/controllers/OWNERS)~~ [RainbowMango]
- ~~[pkg/util/OWNERS](https://github.com/karmada-io/karmada/blob/master/pkg/util/OWNERS)~~ [RainbowMango]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR
user: login: RainbowMango
created_at: 2025-05-17T02:26:48Z
body: /hold cancel

author_association: MEMBER