Repository: karmada-io/karmada
Pull request #6363: FederatedResourceQuota: skip creating redundant ResourceQuotas to member clusters
Author: login: zhzhuang-zju
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/L
description: Denotes a PR that changes 100-499 lines, ignoring generated files.
name: lgtm
description: Indicates that a PR is ready to be merged.
Created: 2025-05-10T02:53:01Z | Merged: 2025-05-13T08:15:27Z

**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**:
Adjust the behavior of the legacy staticAssignments to skip creating redundant ResourceQuotas to member clusters

**Which issue(s) this PR fixes**:
Parts of #6350

**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

```




Diff:
diff --git a/pkg/controllers/federatedresourcequota/federated_resource_quota_status_controller.go b/pkg/controllers/federatedresourcequota/federated_resource_quota_status_controller.go
index 153a3ee8ead4..9be5067fa638 100644
--- a/pkg/controllers/federatedresourcequota/federated_resource_quota_status_controller.go
+++ b/pkg/controllers/federatedresourcequota/federated_resource_quota_status_controller.go
@@ -124,8 +124,14 @@ func (c *StatusController) SetupWithManager(mgr controllerruntime.Manager) error
 			}
 			return !reflect.DeepEqual(objOld.Status, objNew.Status)
 		},
-		DeleteFunc: func(event.DeleteEvent) bool {
-			return false
+		DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
+			obj := deleteEvent.Object.(*workv1alpha1.Work)
+			_, namespaceExist := obj.GetLabels()[util.FederatedResourceQuotaNamespaceLabel]
+			_, nameExist := obj.GetLabels()[util.FederatedResourceQuotaNameLabel]
+			if !namespaceExist || !nameExist {
+				return false
+			}
+			return true
 		},
 		GenericFunc: func(event.GenericEvent) bool {
 			return false
diff --git a/pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller.go b/pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller.go
index 822322722871..0dcdb64f272e 100644
--- a/pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller.go
+++ b/pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller.go
@@ -18,6 +18,7 @@ package federatedresourcequota
 
 import (
 	"context"
+	"reflect"
 
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -77,6 +78,11 @@ func (c *SyncController) Reconcile(ctx context.Context, req controllerruntime.Re
 		return controllerruntime.Result{}, err
 	}
 
+	if err := c.cleanUpOrphanWorks(ctx, quota); err != nil {
+		klog.Errorf("Failed to cleanup orphan works for federatedResourceQuota(%s), error: %v", req.NamespacedName.String(), err)
+		return controllerruntime.Result{}, err
+	}
+
 	clusterList := &clusterv1alpha1.ClusterList{}
 	if err := c.Client.List(ctx, clusterList); err != nil {
 		klog.Errorf("Failed to list clusters, error: %v", err)
@@ -96,8 +102,15 @@ func (c *SyncController) Reconcile(ctx context.Context, req controllerruntime.Re
 // SetupWithManager creates a controller and register to controller manager.
 func (c *SyncController) SetupWithManager(mgr controllerruntime.Manager) error {
 	fn := handler.MapFunc(
-		func(ctx context.Context, _ client.Object) []reconcile.Request {
+		func(ctx context.Context, obj client.Object) []reconcile.Request {
 			var requests []reconcile.Request
+			var clusterName string
+			switch obj.(type) {
+			case *clusterv1alpha1.Cluster:
+				clusterName = obj.GetName()
+			default:
+				return nil
+			}
 
 			FederatedResourceQuotaList := &policyv1alpha1.FederatedResourceQuotaList{}
 			if err := c.Client.List(ctx, FederatedResourceQuotaList); err != nil {
@@ -105,6 +118,10 @@ func (c *SyncController) SetupWithManager(mgr controllerruntime.Manager) error {
 			}
 
 			for _, federatedResourceQuota := range FederatedResourceQuotaList.Items {
+				hard := extractClusterHardResourceList(federatedResourceQuota.Spec, clusterName)
+				if hard == nil {
+					continue
+				}
 				requests = append(requests, reconcile.Request{
 					NamespacedName: types.NamespacedName{
 						Namespace: federatedResourceQuota.Namespace,
@@ -132,9 +149,32 @@ func (c *SyncController) SetupWithManager(mgr controllerruntime.Manager) error {
 		},
 	})
 
+	frqPredicate := builder.WithPredicates(predicate.Funcs{
+		// TODO(zhzhuang-zju): Should ignore the create event in case of the FRQ's .spec.staticAssignments is nil, as
+		// no longer sync empty ResourceQuotas by default starting from v1.14. Here temporarily omitting this filter to
+		// ensure that during upgrades from older versions, there is an opportunity to clean up legacy ResourceQuotas synced
+		// from previous versions.
+		CreateFunc: func(event.CreateEvent) bool {
+			return true
+		},
+		UpdateFunc: func(updateEvent event.UpdateEvent) bool {
+			oldObj := updateEvent.ObjectOld.(*policyv1alpha1.FederatedResourceQuota)
+			newObj := updateEvent.ObjectNew.(*policyv1alpha1.FederatedResourceQuota)
+			return !reflect.DeepEqual(oldObj.Spec.StaticAssignments, newObj.Spec.StaticAssignments)
+		},
+		// TODO(zhzhuang-zju): Should ignore the delete event in case of the FRQ's .spec.staticAssignments is nil, as
+		// no longer sync empty ResourceQuotas by default starting from v1.14. Here temporarily omitting this filter to
+		// ensure that during upgrades from older versions, there is an opportunity to clean up legacy ResourceQuotas synced
+		// from previous versions.
+		DeleteFunc: func(event.DeleteEvent) bool {
+			return true
+		},
+		GenericFunc: func(event.GenericEvent) bool { return false },
+	})
+
 	return controllerruntime.NewControllerManagedBy(mgr).
 		Named(SyncControllerName).
-		For(&policyv1alpha1.FederatedResourceQuota{}).
+		For(&policyv1alpha1.FederatedResourceQuota{}, frqPredicate).
 		Watches(&clusterv1alpha1.Cluster{}, handler.EnqueueRequestsFromMapFunc(fn), clusterPredicate).
 		WithOptions(controller.Options{
 			RateLimiter: ratelimiterflag.DefaultControllerRateLimiter[controllerruntime.Request](c.RateLimiterOptions),
@@ -164,15 +204,45 @@ func (c *SyncController) cleanUpWorks(ctx context.Context, namespace, name strin
 	return errors.NewAggregate(errs)
 }
 
+func (c *SyncController) cleanUpOrphanWorks(ctx context.Context, quota *policyv1alpha1.FederatedResourceQuota) error {
+	var errs []error
+	workList := &workv1alpha1.WorkList{}
+	if err := c.List(ctx, workList, client.MatchingLabels{
+		util.FederatedResourceQuotaNamespaceLabel: quota.GetNamespace(),
+		util.FederatedResourceQuotaNameLabel:      quota.GetName(),
+	}); err != nil {
+		klog.Errorf("Failed to list works, err: %v", err)
+		return err
+	}
+
+	for index := range workList.Items {
+		work := &workList.Items[index]
+		if !isOrphanQuotaWork(work, quota.Spec) {
+			continue
+		}
+		if err := c.Delete(ctx, work); err != nil && !apierrors.IsNotFound(err) {
+			klog.Errorf("Failed to delete work(%s): %v", klog.KObj(work).String(), err)
+			errs = append(errs, err)
+		}
+	}
+
+	return errors.NewAggregate(errs)
+}
+
 func (c *SyncController) buildWorks(ctx context.Context, quota *policyv1alpha1.FederatedResourceQuota, clusters []clusterv1alpha1.Cluster) error {
 	var errs []error
 	for _, cluster := range clusters {
+		hard := extractClusterHardResourceList(quota.Spec, cluster.Name)
+		if hard == nil {
+			continue
+		}
+
 		resourceQuota := &corev1.ResourceQuota{}
 		resourceQuota.APIVersion = "v1"
 		resourceQuota.Kind = "ResourceQuota"
 		resourceQuota.Namespace = quota.Namespace
 		resourceQuota.Name = quota.Name
-		resourceQuota.Spec.Hard = extractClusterHardResourceList(quota.Spec, cluster.Name)
+		resourceQuota.Spec.Hard = hard
 
 		resourceQuotaObj, err := helper.ToUnstructured(resourceQuota)
 		if err != nil {
@@ -208,3 +278,13 @@ func extractClusterHardResourceList(spec policyv1alpha1.FederatedResourceQuotaSp
 	}
 	return nil
 }
+
+func isOrphanQuotaWork(work *workv1alpha1.Work, spec policyv1alpha1.FederatedResourceQuotaSpec) bool {
+	for _, assignment := range spec.StaticAssignments {
+		if names.GenerateExecutionSpaceName(assignment.ClusterName) == work.GetNamespace() {
+			return false
+		}
+	}
+
+	return true
+}
diff --git a/pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller_test.go b/pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller_test.go
index 2029362c6bda..6f8147815e0b 100644
--- a/pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller_test.go
+++ b/pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller_test.go
@@ -33,6 +33,7 @@ import (
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 	workv1alpha1 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha1"
 	"github.com/karmada-io/karmada/pkg/util"
+	"github.com/karmada-io/karmada/pkg/util/names"
 )
 
 // setupTest initializes a test environment with the given runtime objects
@@ -132,7 +133,7 @@ func TestBuildWorks(t *testing.T) {
 		expectedWorks int
 	}{
 		{
-			name: "Successfully build works for all clusters",
+			name: "Successfully build works for each cluster declared in StaticAssignments",
 			quota: &policyv1alpha1.FederatedResourceQuota{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "test-quota",
@@ -163,24 +164,18 @@ func TestBuildWorks(t *testing.T) {
 			expectedWorks: 2,
 		},
 		{
-			name: "No clusters available",
+			name: "No clusters declared in StaticAssignments",
 			quota: &policyv1alpha1.FederatedResourceQuota{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "test-quota",
 					Namespace: "default",
 				},
-				Spec: policyv1alpha1.FederatedResourceQuotaSpec{
-					StaticAssignments: []policyv1alpha1.StaticClusterAssignment{
-						{
-							ClusterName: "cluster1",
-							Hard: corev1.ResourceList{
-								corev1.ResourceCPU: resource.MustParse("1"),
-							},
-						},
-					},
-				},
+				Spec: policyv1alpha1.FederatedResourceQuotaSpec{},
+			},
+			clusters: []clusterv1alpha1.Cluster{
+				{ObjectMeta: metav1.ObjectMeta{Name: "cluster1"}},
+				{ObjectMeta: metav1.ObjectMeta{Name: "cluster2"}},
 			},
-			clusters:      []clusterv1alpha1.Cluster{},
 			expectedError: false,
 			expectedWorks: 0,
 		},
@@ -292,3 +287,128 @@ func TestExtractClusterHardResourceList(t *testing.T) {
 		})
 	}
 }
+
+func TestCleanUpOrphanWorks(t *testing.T) {
+	tests := []struct {
+		name           string
+		quota          policyv1alpha1.FederatedResourceQuota
+		totalWorks     []runtime.Object
+		remainingWorks []workv1alpha1.Work
+	}{
+		{
+			name: "Clean up orphan works",
+			quota: policyv1alpha1.FederatedResourceQuota{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test-quota",
+					Namespace: "default",
+				},
+				Spec: policyv1alpha1.FederatedResourceQuotaSpec{
+					StaticAssignments: []policyv1alpha1.StaticClusterAssignment{
+						{
+							ClusterName: "cluster1",
+							Hard: corev1.ResourceList{
+								corev1.ResourceCPU: resource.MustParse("1"),
+							},
+						},
+						{
+							ClusterName: "cluster2",
+							Hard: corev1.ResourceList{
+								corev1.ResourceCPU: resource.MustParse("2"),
+							},
+						},
+					},
+				},
+				Status: policyv1alpha1.FederatedResourceQuotaStatus{
+					AggregatedStatus: []policyv1alpha1.ClusterQuotaStatus{
+						{
+							ClusterName: "cluster1",
+						},
+						{
+							ClusterName: "cluster2",
+						},
+						{
+							ClusterName: "cluster3",
+						},
+					},
+				},
+			},
+			totalWorks: []runtime.Object{
+				&workv1alpha1.Work{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      names.GenerateWorkName("ResourceQuota", "test-quota", "default"),
+						Namespace: names.GenerateExecutionSpaceName("cluster1"),
+						Labels: map[string]string{
+							util.FederatedResourceQuotaNamespaceLabel: "default",
+							util.FederatedResourceQuotaNameLabel:      "test-quota",
+						},
+					},
+				},
+				&workv1alpha1.Work{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      names.GenerateWorkName("ResourceQuota", "test-quota", "default"),
+						Namespace: names.GenerateExecutionSpaceName("cluster2"),
+						Labels: map[string]string{
+							util.FederatedResourceQuotaNamespaceLabel: "default",
+							util.FederatedResourceQuotaNameLabel:      "test-quota",
+						},
+					},
+				},
+				&workv1alpha1.Work{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      names.GenerateWorkName("ResourceQuota", "test-quota", "default"),
+						Namespace: names.GenerateExecutionSpaceName("cluster3"),
+						Labels: map[string]string{
+							util.FederatedResourceQuotaNamespaceLabel: "default",
+							util.FederatedResourceQuotaNameLabel:      "test-quota",
+						},
+					},
+				},
+			},
+			remainingWorks: []workv1alpha1.Work{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      names.GenerateWorkName("ResourceQuota", "test-quota", "default"),
+						Namespace: names.GenerateExecutionSpaceName("cluster1"),
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      names.GenerateWorkName("ResourceQuota", "test-quota", "default"),
+						Namespace: names.GenerateExecutionSpaceName("cluster2"),
+					},
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			fakeClient, controller := setupTest(t, tt.totalWorks...)
+			err := controller.cleanUpOrphanWorks(context.Background(), &tt.quota)
+			if err != nil {
+				t.Errorf("Failed to cleanup the orphan works: %s", err.Error())
+			}
+
+			// Verify the number of created works
+			workList := &workv1alpha1.WorkList{}
+			err = fakeClient.List(context.Background(), workList, client.MatchingLabels{
+				util.FederatedResourceQuotaNamespaceLabel: tt.quota.Namespace,
+				util.FederatedResourceQuotaNameLabel:      tt.quota.Name,
+			})
+			if err != nil {
+				t.Errorf("Failed to list works: %s", err.Error())
+			}
+
+			var actualRemainingWorks []workv1alpha1.Work
+			for _, item := range workList.Items {
+				actualRemainingWorks = append(actualRemainingWorks, workv1alpha1.Work{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      item.GetName(),
+						Namespace: item.GetNamespace(),
+					},
+				})
+			}
+			assert.ElementsMatch(t, tt.remainingWorks, actualRemainingWorks)
+		})
+	}
+}
diff --git a/test/e2e/suites/base/federatedresourcequota_test.go b/test/e2e/suites/base/federatedresourcequota_test.go
index 9287691deab4..93104595ba58 100644
--- a/test/e2e/suites/base/federatedresourcequota_test.go
+++ b/test/e2e/suites/base/federatedresourcequota_test.go
@@ -20,9 +20,11 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"time"
 
 	"github.com/onsi/ginkgo/v2"
 	"github.com/onsi/gomega"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/rand"
@@ -38,6 +40,8 @@ import (
 	"github.com/karmada-io/karmada/test/helper"
 )
 
+const waitTimeout = 3 * time.Second
+
 var _ = framework.SerialDescribe("FederatedResourceQuota auto-provision testing", func() {
 	var frqNamespace, frqName string
 	var federatedResourceQuota *policyv1alpha1.FederatedResourceQuota
@@ -46,70 +50,101 @@ var _ = framework.SerialDescribe("FederatedResourceQuota auto-provision testing"
 	ginkgo.BeforeEach(func() {
 		frqNamespace = testNamespace
 		frqName = federatedResourceQuotaPrefix + rand.String(RandomStrLength)
-		federatedResourceQuota = helper.NewFederatedResourceQuota(frqNamespace, frqName, framework.ClusterNames())
 
 		defaultConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag().WithDiscoveryBurst(300).WithDiscoveryQPS(50.0)
 		defaultConfigFlags.Context = &karmadaContext
 		f = cmdutil.NewFactory(defaultConfigFlags)
 	})
 
-	ginkgo.Context("create a federatedResourceQuota", func() {
-		ginkgo.AfterEach(func() {
-			framework.RemoveFederatedResourceQuota(karmadaClient, frqNamespace, frqName)
-		})
-
-		ginkgo.It("federatedResourceQuota should be propagated to member clusters", func() {
+	ginkgo.It("CURD a federatedResourceQuota", func() {
+		var clusters = framework.ClusterNames()
+		federatedResourceQuota = helper.NewFederatedResourceQuota(frqNamespace, frqName, framework.ClusterNames())
+		ginkgo.By("[Create] federatedResourceQuota should be propagated to member clusters", func() {
 			framework.CreateFederatedResourceQuota(karmadaClient, federatedResourceQuota)
-			framework.WaitResourceQuotaPresentOnClusters(framework.ClusterNames(), frqNamespace, frqName)
+			framework.WaitResourceQuotaPresentOnClusters(clusters, frqNamespace, frqName)
 		})
-	})
 
-	ginkgo.Context("delete a federatedResourceQuota", func() {
-		ginkgo.BeforeEach(func() {
-			framework.CreateFederatedResourceQuota(karmadaClient, federatedResourceQuota)
-			framework.WaitResourceQuotaPresentOnClusters(framework.ClusterNames(), frqNamespace, frqName)
+		ginkgo.By("[Update] federatedResourceQuota should be propagated to member clusters according to the new staticAssignments", func() {
+			clusters = []string{framework.ClusterNames()[0]}
+			federatedResourceQuota = helper.NewFederatedResourceQuota(frqNamespace, frqName, clusters)
+			patch := []map[string]interface{}{
+				{
+					"op":    "replace",
+					"path":  "/spec/staticAssignments",
+					"value": federatedResourceQuota.Spec.StaticAssignments,
+				},
+			}
+			framework.UpdateFederatedResourceQuotaWithPatch(karmadaClient, frqNamespace, frqName, patch, types.JSONPatchType)
+			framework.WaitResourceQuotaPresentOnClusters(clusters, frqNamespace, frqName)
+			framework.WaitResourceQuotaDisappearOnClusters(framework.ClusterNames()[1:], frqNamespace, frqName)
 		})
 
-		ginkgo.It("federatedResourceQuota should be removed from member clusters", func() {
+		ginkgo.By("[Delete] federatedResourceQuota should be removed from member clusters", func() {
 			framework.RemoveFederatedResourceQuota(karmadaClient, frqNamespace, frqName)
-			framework.WaitResourceQuotaDisappearOnClusters(framework.ClusterNames(), frqNamespace, frqName)
+			framework.WaitResourceQuotaDisappearOnClusters(clusters, frqNamespace, frqName)
 		})
 	})
 
 	framework.SerialContext("join new cluster", ginkgo.Labels{NeedCreateCluster}, func() {
-		var clusterName string
+		var clusterNameInStaticAssignment string
+		var clusterNameNotInStaticAssignment string
 		var homeDir string
-		var kubeConfigPath string
-		var controlPlane string
-		var clusterContext string
+		var kubeConfigPathInStaticAssignment string
+		var kubeConfigPathNotInStaticAssignment string
+		var controlPlaneInStaticAssignment string
+		var controlPlaneNotInStaticAssignment string
+		var clusterContextInStaticAssignment string
+		var clusterContextNotInStaticAssignment string
 
 		ginkgo.BeforeEach(func() {
-			clusterName = "member-e2e-" + rand.String(RandomStrLength)
+			clusterNameInStaticAssignment = "member-e2e-" + rand.String(RandomStrLength)
+			clusterNameNotInStaticAssignment = "member-e2e-" + rand.String(RandomStrLength)
 			homeDir = os.Getenv("HOME")
-			kubeConfigPath = fmt.Sprintf("%s/.kube/%s.config", homeDir, clusterName)
-			controlPlane = fmt.Sprintf("%s-control-plane", clusterName)
-			clusterContext = fmt.Sprintf("kind-%s", clusterName)
+			kubeConfigPathInStaticAssignment = fmt.Sprintf("%s/.kube/%s.config", homeDir, clusterNameInStaticAssignment)
+			kubeConfigPathNotInStaticAssignment = fmt.Sprintf("%s/.kube/%s.config", homeDir, clusterNameNotInStaticAssignment)
+			controlPlaneInStaticAssignment = fmt.Sprintf("%s-control-plane", clusterNameInStaticAssignment)
+			controlPlaneNotInStaticAssignment = fmt.Sprintf("%s-control-plane", clusterNameNotInStaticAssignment)
+			clusterContextInStaticAssignment = fmt.Sprintf("kind-%s", clusterNameInStaticAssignment)
+			clusterContextNotInStaticAssignment = fmt.Sprintf("kind-%s", clusterNameNotInStaticAssignment)
 		})
 
 		ginkgo.BeforeEach(func() {
+			clusterNames := append(framework.ClusterNames(), clusterNameInStaticAssignment)
+			federatedResourceQuota = helper.NewFederatedResourceQuota(frqNamespace, frqName, clusterNames)
 			framework.CreateFederatedResourceQuota(karmadaClient, federatedResourceQuota)
 		})
 
 		ginkgo.BeforeEach(func() {
-			ginkgo.By(fmt.Sprintf("Creating cluster: %s", clusterName), func() {
-				err := createCluster(clusterName, kubeConfigPath, controlPlane, clusterContext)
+			ginkgo.By(fmt.Sprintf("Creating cluster: %s", clusterNameInStaticAssignment), func() {
+				err := createCluster(clusterNameInStaticAssignment, kubeConfigPathInStaticAssignment, controlPlaneInStaticAssignment, clusterContextInStaticAssignment)
+				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
+			})
+			ginkgo.By(fmt.Sprintf("Creating cluster: %s", clusterNameNotInStaticAssignment), func() {
+				err := createCluster(clusterNameNotInStaticAssignment, kubeConfigPathNotInStaticAssignment, controlPlaneNotInStaticAssignment, clusterContextNotInStaticAssignment)
 				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
 			})
 		})
 
 		ginkgo.AfterEach(func() {
-			ginkgo.By(fmt.Sprintf("Unjoinning cluster: %s", clusterName), func() {
+			ginkgo.By(fmt.Sprintf("Unjoinning cluster: %s", clusterNameInStaticAssignment), func() {
+				opts := unjoin.CommandUnjoinOption{
+					DryRun:            false,
+					ClusterNamespace:  "karmada-cluster",
+					ClusterName:       clusterNameInStaticAssignment,
+					ClusterContext:    clusterContextInStaticAssignment,
+					ClusterKubeConfig: kubeConfigPathInStaticAssignment,
+					Wait:              5 * options.DefaultKarmadactlCommandDuration,
+				}
+				err := opts.Run(f)
+				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
+			})
+			ginkgo.By(fmt.Sprintf("Unjoinning cluster: %s", clusterNameNotInStaticAssignment), func() {
 				opts := unjoin.CommandUnjoinOption{
 					DryRun:            false,
 					ClusterNamespace:  "karmada-cluster",
-					ClusterName:       clusterName,
-					ClusterContext:    clusterContext,
-					ClusterKubeConfig: kubeConfigPath,
+					ClusterName:       clusterNameNotInStaticAssignment,
+					ClusterContext:    clusterContextNotInStaticAssignment,
+					ClusterKubeConfig: kubeConfigPathNotInStaticAssignment,
 					Wait:              5 * options.DefaultKarmadactlCommandDuration,
 				}
 				err := opts.Run(f)
@@ -118,10 +153,15 @@ var _ = framework.SerialDescribe("FederatedResourceQuota auto-provision testing"
 		})
 
 		ginkgo.AfterEach(func() {
-			ginkgo.By(fmt.Sprintf("Deleting clusters: %s", clusterName), func() {
-				err := deleteCluster(clusterName, kubeConfigPath)
+			ginkgo.By(fmt.Sprintf("Deleting clusters: %s", clusterNameInStaticAssignment), func() {
+				err := deleteCluster(clusterNameInStaticAssignment, kubeConfigPathInStaticAssignment)
+				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
+				_ = os.Remove(kubeConfigPathInStaticAssignment)
+			})
+			ginkgo.By(fmt.Sprintf("Deleting clusters: %s", clusterNameNotInStaticAssignment), func() {
+				err := deleteCluster(clusterNameNotInStaticAssignment, kubeConfigPathNotInStaticAssignment)
 				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
-				_ = os.Remove(kubeConfigPath)
+				_ = os.Remove(kubeConfigPathNotInStaticAssignment)
 			})
 		})
 
@@ -129,21 +169,21 @@ var _ = framework.SerialDescribe("FederatedResourceQuota auto-provision testing"
 			framework.RemoveFederatedResourceQuota(karmadaClient, frqNamespace, frqName)
 		})
 
-		ginkgo.It("federatedResourceQuota should be propagated to new joined clusters", func() {
-			ginkgo.By(fmt.Sprintf("Joining cluster: %s", clusterName), func() {
+		ginkgo.It("federatedResourceQuota should only be propagated to newly joined cluster if the new cluster is declared in the StaticAssignment", func() {
+			ginkgo.By(fmt.Sprintf("Joining cluster: %s", clusterNameInStaticAssignment), func() {
 				opts := join.CommandJoinOption{
 					DryRun:            false,
 					ClusterNamespace:  "karmada-cluster",
-					ClusterName:       clusterName,
-					ClusterContext:    clusterContext,
-					ClusterKubeConfig: kubeConfigPath,
+					ClusterName:       clusterNameInStaticAssignment,
+					ClusterContext:    clusterContextInStaticAssignment,
+					ClusterKubeConfig: kubeConfigPathInStaticAssignment,
 				}
 				err := opts.Run(f)
 				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
 			})
 
-			ginkgo.By(fmt.Sprintf("waiting federatedResourceQuota(%s/%s) present on cluster: %s", frqNamespace, frqName, clusterName), func() {
-				clusterClient, err := util.NewClusterClientSet(clusterName, controlPlaneClient, nil)
+			ginkgo.By(fmt.Sprintf("waiting federatedResourceQuota(%s/%s) present on cluster: %s", frqNamespace, frqName, clusterNameInStaticAssignment), func() {
+				clusterClient, err := util.NewClusterClientSet(clusterNameInStaticAssignment, controlPlaneClient, nil)
 				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
 
 				gomega.Eventually(func(g gomega.Gomega) (bool, error) {
@@ -152,6 +192,27 @@ var _ = framework.SerialDescribe("FederatedResourceQuota auto-provision testing"
 					return true, nil
 				}, pollTimeout, pollInterval).Should(gomega.Equal(true))
 			})
+
+			ginkgo.By(fmt.Sprintf("Joining cluster: %s", clusterNameNotInStaticAssignment), func() {
+				opts := join.CommandJoinOption{
+					DryRun:            false,
+					ClusterNamespace:  "karmada-cluster",
+					ClusterName:       clusterNameNotInStaticAssignment,
+					ClusterContext:    clusterContextNotInStaticAssignment,
+					ClusterKubeConfig: kubeConfigPathNotInStaticAssignment,
+				}
+				err := opts.Run(f)
+				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
+			})
+
+			ginkgo.By(fmt.Sprintf("check if federatedResourceQuota(%s/%s) present on cluster: %s", frqNamespace, frqName, clusterNameNotInStaticAssignment), func() {
+				clusterClient, err := util.NewClusterClientSet(clusterNameNotInStaticAssignment, controlPlaneClient, nil)
+				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
+
+				time.Sleep(waitTimeout)
+				_, err = clusterClient.KubeClient.CoreV1().ResourceQuotas(frqNamespace).Get(context.TODO(), frqName, metav1.GetOptions{})
+				gomega.Expect(apierrors.IsNotFound(err)).Should(gomega.Equal(true))
+			})
 		})
 	})
 })
diff --git a/test/helper/policy.go b/test/helper/policy.go
index 2a1b5a66ac9b..e59633cf02fe 100644
--- a/test/helper/policy.go
+++ b/test/helper/policy.go
@@ -162,6 +162,16 @@ func NewClusterOverridePolicyByOverrideRules(policyName string, rsSelectors []po
 
 // NewFederatedResourceQuota will build a demo FederatedResourceQuota object.
 func NewFederatedResourceQuota(ns, name string, clusterNames []string) *policyv1alpha1.FederatedResourceQuota {
+	var staticAssignments []policyv1alpha1.StaticClusterAssignment
+	for i := range clusterNames {
+		staticAssignments = append(staticAssignments, policyv1alpha1.StaticClusterAssignment{
+			ClusterName: clusterNames[i],
+			Hard: map[corev1.ResourceName]resource.Quantity{
+				"cpu":    resource.MustParse("1"),
+				"memory": resource.MustParse("2Gi"),
+			},
+		})
+	}
 	return &policyv1alpha1.FederatedResourceQuota{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: ns,
@@ -172,22 +182,7 @@ func NewFederatedResourceQuota(ns, name string, clusterNames []string) *policyv1
 				"cpu":    resource.MustParse("8"),
 				"memory": resource.MustParse("16Gi"),
 			},
-			StaticAssignments: []policyv1alpha1.StaticClusterAssignment{
-				{
-					ClusterName: clusterNames[0],
-					Hard: map[corev1.ResourceName]resource.Quantity{
-						"cpu":    resource.MustParse("1"),
-						"memory": resource.MustParse("2Gi"),
-					},
-				},
-				{
-					ClusterName: clusterNames[1],
-					Hard: map[corev1.ResourceName]resource.Quantity{
-						"cpu":    resource.MustParse("1"),
-						"memory": resource.MustParse("2Gi"),
-					},
-				},
-			},
+			StaticAssignments: staticAssignments,
 		},
 	}
 }


Reviews:
user: login: RainbowMango
body: /assign
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-10T08:31:00Z
user: login: RainbowMango
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-10T09:23:43Z
user: login: zhzhuang-zju
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T11:15:17Z
user: login: RainbowMango
body: Generally looks good to me except some nits.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T07:09:21Z
user: login: zhzhuang-zju
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T07:15:04Z
user: login: RainbowMango
body: /lgtm
/approve
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-13T07:27:27Z

Review comments:
diff_hunk: @@ -96,15 +100,26 @@ func (c *SyncController) Reconcile(ctx context.Context, req controllerruntime.Re
 // SetupWithManager creates a controller and register to controller manager.
 func (c *SyncController) SetupWithManager(mgr controllerruntime.Manager) error {
 	fn := handler.MapFunc(
-		func(ctx context.Context, _ client.Object) []reconcile.Request {
+		func(ctx context.Context, a client.Object) []reconcile.Request {
path: pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller.go
user: login: RainbowMango
body: ```suggestion
		func(ctx context.Context, obj client.Object) []reconcile.Request {
```
created_at: 2025-05-10T08:59:35Z
author_association: MEMBER
diff_hunk: @@ -164,15 +179,40 @@ func (c *SyncController) cleanUpWorks(ctx context.Context, namespace, name strin
 	return errors.NewAggregate(errs)
 }
 
-func (c *SyncController) buildWorks(ctx context.Context, quota *policyv1alpha1.FederatedResourceQuota, clusters []clusterv1alpha1.Cluster) error {
+func (c *SyncController) cleanUpOrphanWorks(ctx context.Context, quota *policyv1alpha1.FederatedResourceQuota) error {
+	var errs []error
+	workList := &workv1alpha1.WorkList{}
+	if err := c.List(ctx, workList, client.MatchingLabels{
+		util.FederatedResourceQuotaNamespaceLabel: quota.GetNamespace(),
+		util.FederatedResourceQuotaNameLabel:      quota.GetName(),
+	}); err != nil {
+		klog.Errorf("Failed to list works, err: %v", err)
+		return err
+	}
path: pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller.go
user: login: RainbowMango
body: Given that listing all works is an extremely expensive operation, we should minimize the reconcile frequency by introducing an event handler that only responds to 
- `create event`: only when `.spec.staticAssignments` not nil
- `update event`: only when `.spec.staticAssignments` changed
- `delete event`: only when `.spec.staticAssignments` not nil


created_at: 2025-05-10T09:12:41Z
author_association: MEMBER
diff_hunk: @@ -83,7 +83,11 @@ func (c *SyncController) Reconcile(ctx context.Context, req controllerruntime.Re
 		return controllerruntime.Result{}, err
 	}
 
-	if err := c.buildWorks(ctx, quota, clusterList.Items); err != nil {
+	if err := c.cleanUpOrphanWorks(ctx, quota); err != nil {
path: pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller.go
user: login: RainbowMango
body: Move `cleanUpOrphanWorks` ahead of `Client.List(ctx, clusterList)`, so that if something wrong happened when cleaning the works could save the effort of listing clusters.
created_at: 2025-05-10T09:18:57Z
author_association: MEMBER
diff_hunk: @@ -164,15 +179,40 @@ func (c *SyncController) cleanUpWorks(ctx context.Context, namespace, name strin
 	return errors.NewAggregate(errs)
 }
 
-func (c *SyncController) buildWorks(ctx context.Context, quota *policyv1alpha1.FederatedResourceQuota, clusters []clusterv1alpha1.Cluster) error {
+func (c *SyncController) cleanUpOrphanWorks(ctx context.Context, quota *policyv1alpha1.FederatedResourceQuota) error {
+	var errs []error
+	workList := &workv1alpha1.WorkList{}
+	if err := c.List(ctx, workList, client.MatchingLabels{
+		util.FederatedResourceQuotaNamespaceLabel: quota.GetNamespace(),
+		util.FederatedResourceQuotaNameLabel:      quota.GetName(),
+	}); err != nil {
+		klog.Errorf("Failed to list works, err: %v", err)
+		return err
+	}
+
+	for index := range workList.Items {
+		work := &workList.Items[index]
+		if !isOrphanQuotaWork(work, quota.Spec) {
+			continue
+		}
+		if err := c.Delete(ctx, work); err != nil && !apierrors.IsNotFound(err) {
+			klog.Errorf("Failed to delete work(%s): %v", klog.KObj(work).String(), err)
+			errs = append(errs, err)
+		}
+	}
+
+	return errors.NewAggregate(errs)
+}
+
+func (c *SyncController) buildWorks(ctx context.Context, quota *policyv1alpha1.FederatedResourceQuota, clusters []string) error {
 	var errs []error
 	for _, cluster := range clusters {
 		resourceQuota := &corev1.ResourceQuota{}
 		resourceQuota.APIVersion = "v1"
 		resourceQuota.Kind = "ResourceQuota"
 		resourceQuota.Namespace = quota.Namespace
 		resourceQuota.Name = quota.Name
-		resourceQuota.Spec.Hard = extractClusterHardResourceList(quota.Spec, cluster.Name)
path: pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller.go
user: login: RainbowMango
body: It's quite easy to ignore the non-existent cluster in staticAssignments by checking the return value of `extractClusterHardResourceList(quota.Spec, cluster.Name)`.

I mean we don't need `extractExistedClusters()`.

created_at: 2025-05-10T09:23:25Z
author_association: MEMBER
diff_hunk: @@ -164,15 +179,40 @@ func (c *SyncController) cleanUpWorks(ctx context.Context, namespace, name strin
 	return errors.NewAggregate(errs)
 }
 
-func (c *SyncController) buildWorks(ctx context.Context, quota *policyv1alpha1.FederatedResourceQuota, clusters []clusterv1alpha1.Cluster) error {
+func (c *SyncController) cleanUpOrphanWorks(ctx context.Context, quota *policyv1alpha1.FederatedResourceQuota) error {
+	var errs []error
+	workList := &workv1alpha1.WorkList{}
+	if err := c.List(ctx, workList, client.MatchingLabels{
+		util.FederatedResourceQuotaNamespaceLabel: quota.GetNamespace(),
+		util.FederatedResourceQuotaNameLabel:      quota.GetName(),
+	}); err != nil {
+		klog.Errorf("Failed to list works, err: %v", err)
+		return err
+	}
path: pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller.go
user: login: zhzhuang-zju
body: Good concern. However, when upgrading from a lower version to v1.14.0, there may be orphan works that were not cleaned up
created_at: 2025-05-10T11:15:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -132,9 +149,26 @@ func (c *SyncController) SetupWithManager(mgr controllerruntime.Manager) error {
 		},
 	})
 
+	frqPredicate := builder.WithPredicates(predicate.Funcs{
+		// TODO(zhzhuang-zju) To maintain compatibility during upgrades, filtering for work creation events will not be added for now. Once version v1.15 is released, we will be able to exclude works where obj.Spec.StaticAssignments is nil.
path: pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller.go
user: login: RainbowMango
body: ```suggestion
		// TODO(zhzhuang-zju): Should ignore the create event in case of the FRQ's .spec.staticAssignments is nil, as
		// no longer sync empty ResourceQuotas by default starting from v1.14. Here temporarily omitting this filter to
		// ensure that during upgrades from older versions, there is an opportunity to clean up legacy ResourceQuotas synced
		// from previous versions.
```
We are not filter `work` here.
created_at: 2025-05-13T07:05:47Z
author_association: MEMBER
diff_hunk: @@ -132,9 +149,26 @@ func (c *SyncController) SetupWithManager(mgr controllerruntime.Manager) error {
 		},
 	})
 
+	frqPredicate := builder.WithPredicates(predicate.Funcs{
+		// TODO(zhzhuang-zju) To maintain compatibility during upgrades, filtering for work creation events will not be added for now. Once version v1.15 is released, we will be able to exclude works where obj.Spec.StaticAssignments is nil.
+		CreateFunc: func(event.CreateEvent) bool {
+			return true
+		},
+		UpdateFunc: func(updateEvent event.UpdateEvent) bool {
+			oldObj := updateEvent.ObjectOld.(*policyv1alpha1.FederatedResourceQuota)
+			newObj := updateEvent.ObjectNew.(*policyv1alpha1.FederatedResourceQuota)
+			return !reflect.DeepEqual(oldObj.Spec.StaticAssignments, newObj.Spec.StaticAssignments)
+		},
+		// TODO(zhzhuang-zju) To maintain compatibility during upgrades, filtering for work deletion events will not be added for now. Once version v1.15 is released, we will be able to exclude works where obj.Spec.StaticAssignments is nil.
path: pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller.go
user: login: RainbowMango
body: ```suggestion
		// TODO(zhzhuang-zju): Should ignore the delete event in case of the FRQ's .spec.staticAssignments is nil, as
		// no longer sync empty ResourceQuotas by default starting from v1.14. Here temporarily omitting this filter to
		// ensure that during upgrades from older versions, there is an opportunity to clean up legacy ResourceQuotas synced
		// from previous versions.
```
created_at: 2025-05-13T07:06:06Z
author_association: MEMBER
diff_hunk: @@ -132,9 +149,26 @@ func (c *SyncController) SetupWithManager(mgr controllerruntime.Manager) error {
 		},
 	})
 
+	frqPredicate := builder.WithPredicates(predicate.Funcs{
+		// TODO(zhzhuang-zju) To maintain compatibility during upgrades, filtering for work creation events will not be added for now. Once version v1.15 is released, we will be able to exclude works where obj.Spec.StaticAssignments is nil.
path: pkg/controllers/federatedresourcequota/federated_resource_quota_sync_controller.go
user: login: zhzhuang-zju
body: Thanks for pointing this out, done~
created_at: 2025-05-13T07:15:04Z
author_association: CONTRIBUTOR

PR comments:
user: login: zhzhuang-zju
created_at: 2025-05-10T03:04:14Z
body: <strike> @RainbowMango @mszacillo Do we need to verify whether the clusters in staticAssignments exist? For example, Karmada manages three member clusters: member1, member2, and member3, the staticAssignments of a FRQ is:
```yaml
  staticAssignments:
  - clusterName: member4
    hard:
      cpu: 40m
```
Should we report an error, or ignore this configuration? If we ignore it, but member4 registers to Karmada later, would we need to respond to the cluster create event and create a ResourceQuota for the member? </strike>
author_association: CONTRIBUTOR
user: login: codecov-commenter
created_at: 2025-05-10T03:10:53Z
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/6363?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 `30.43478%` with `48 lines` in your changes missing coverage. Please review.
:white_check_mark: Project coverage is 49.34%. Comparing base ([`932ceb3`](https://app.codecov.io/gh/karmada-io/karmada/commit/932ceb3ac977bf8767fd1bdf6bb54b7a34080546?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io)) to head ([`f6eff10`](https://app.codecov.io/gh/karmada-io/karmada/commit/f6eff100ef6fbd73759a881ddcca2fc031ae5130?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io)).
:warning: Report is 752 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/karmada-io/karmada/pull/6363?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 |
|---|---|---|
| [...equota/federated\_resource\_quota\_sync\_controller.go](https://app.codecov.io/gh/karmada-io/karmada/pull/6363?src=pr&el=tree&filepath=pkg%2Fcontrollers%2Ffederatedresourcequota%2Ffederated_resource_quota_sync_controller.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io#diff-cGtnL2NvbnRyb2xsZXJzL2ZlZGVyYXRlZHJlc291cmNlcXVvdGEvZmVkZXJhdGVkX3Jlc291cmNlX3F1b3RhX3N5bmNfY29udHJvbGxlci5nbw==) | 34.42% | [38 Missing and 2 partials :warning: ](https://app.codecov.io/gh/karmada-io/karmada/pull/6363?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) |
| [...uota/federated\_resource\_quota\_status\_controller.go](https://app.codecov.io/gh/karmada-io/karmada/pull/6363?src=pr&el=tree&filepath=pkg%2Fcontrollers%2Ffederatedresourcequota%2Ffederated_resource_quota_status_controller.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io#diff-cGtnL2NvbnRyb2xsZXJzL2ZlZGVyYXRlZHJlc291cmNlcXVvdGEvZmVkZXJhdGVkX3Jlc291cmNlX3F1b3RhX3N0YXR1c19jb250cm9sbGVyLmdv) | 0.00% | [8 Missing :warning: ](https://app.codecov.io/gh/karmada-io/karmada/pull/6363?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    #6363      +/-   ##
==========================================
- Coverage   49.35%   49.34%   -0.01%     
==========================================
  Files         678      678              
  Lines       55050    55122      +72     
==========================================
+ Hits        27169    27200      +31     
- Misses      26114    26153      +39     
- Partials     1767     1769       +2     
```

| [Flag](https://app.codecov.io/gh/karmada-io/karmada/pull/6363/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/6363/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=karmada-io) | `49.34% <30.43%> (-0.01%)` | :arrow_down: |

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/6363?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: karmada-bot
created_at: 2025-05-13T07:27:33Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/karmada-io/karmada/pull/6363#pullrequestreview-2835578200" 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:

- ~~[pkg/controllers/OWNERS](https://github.com/karmada-io/karmada/blob/master/pkg/controllers/OWNERS)~~ [RainbowMango]
- ~~[test/OWNERS](https://github.com/karmada-io/karmada/blob/master/test/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