Repository: karmada-io/karmada

Issue:
A new pull-mode cluster may overwrite the existing member clusters.
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!
-->


**What happened**:
When the agent starts, it will verify the legitimacy of the member cluster. Once verification is complete, it will either create or update the cluster object in the control plane. The rules for verifying the legitimacy of the member cluster are as follows:
https://github.com/karmada-io/karmada/blob/f2ff5d0abbc1c405ec388daf39eb9d4852d4128a/cmd/agent/app/agent.go#L167-L179

1. Conduct a uniqueness check on the UUID of the cluster being registered.
2. If the uniqueness check fails, check if the names of the two clusters are consistent. This is necessary to address scenarios where the agent restarts. 

Consequently, there is no uniqueness check performed on the name of the member cluster being registered. If a new pull-mode member cluster is registered with the same name as an existing cluster object in the control plane, the newly registered cluster may overwrite the existing member cluster.

**What you expected to happen**:

**How to reproduce it (as minimally and precisely as possible)**:
```bash
# local-up
$ hack/local-up-karmada.sh
$ karmadactl get cluster
NAME      VERSION   MODE   READY     AGE
member1   v1.31.0   Push   True      24d
member2   v1.31.0   Push   True      24d
member3   v1.31.0   Pull   True      17s
# create new cluster 
$ hack/create-cluster.sh member6 ~/.kube/member6.config
$ kubectl --kubeconfig ~/.kube/member6.config config rename-context member6 member2
# register the new cluster
$ hack/deploy-karmada-agent.sh ~/.kube/karmada.config karmada-apiserver ~/.kube/member6.config member2
# check the cluster
$ karmadactl get cluster
NAME      VERSION   MODE   READY   AGE
member1   v1.31.0   Push   True    24d
member2   v1.31.2   Pull   True    24d
member3   v1.31.0   Pull   True    5m4s
```
At this time, member2 has been overwritten.

```bash
$ karmadactl get pods --operation-scope members --clusters member2 
error: cluster(member2) is inaccessible, please check authorization or network
```
Moreover, since the secrets such as the impersonator in the control plane are not updated, it will cause malfunctions in functions like the cluster proxy.

**Anything else we need to know?**:

**Environment**:
- Karmada version:
- kubectl-karmada or karmadactl version (the result of `kubectl-karmada version` or `karmadactl version`):
- Others:


Discussion:
@zhzhuang-zju: /assign

Solution patch:
diff --git a/charts/karmada/README.md b/charts/karmada/README.md
index 21ddb6091..1fe78d1ed 100644
--- a/charts/karmada/README.md
+++ b/charts/karmada/README.md
@@ -108,9 +108,9 @@ The command removes all the Kubernetes components associated with the chart and
 > **Note**: There are some RBAC resources that are used by the `preJob` that can not be deleted by the `uninstall` command above. You might have to clean them manually with tools like `kubectl`.  You can clean them by commands:
 
 ```console
-kubectl delete sa/karmada-pre-job -nkarmada-system
-kubectl delete clusterRole/karmada-pre-job
-kubectl delete clusterRoleBinding/karmada-pre-job
+kubectl delete sa/karmada-hook-job -nkarmada-system
+kubectl delete clusterRole/karmada-hook-job
+kubectl delete clusterRoleBinding/karmada-hook-job
 kubectl delete ns karmada-system
 ```
 
diff --git a/cmd/agent/app/agent.go b/cmd/agent/app/agent.go
index fc35ef8c5..fa67234f0 100644
--- a/cmd/agent/app/agent.go
+++ b/cmd/agent/app/agent.go
@@ -164,22 +164,15 @@ func run(ctx context.Context, opts *options.Options) error {
 		ClusterConfig:      clusterConfig,
 	}
 
-	id, err := util.ObtainClusterID(clusterKubeClient)
+	registerOption.ClusterID, err = util.ObtainClusterID(clusterKubeClient)
 	if err != nil {
 		return err
 	}
 
-	ok, name, err := util.IsClusterIdentifyUnique(karmadaClient, id)
-	if err != nil {
+	if err = registerOption.Validate(karmadaClient, true); err != nil {
 		return err
 	}
 
-	if !ok && opts.ClusterName != name {
-		return fmt.Errorf("the same cluster has been registered with name %s", name)
-	}
-
-	registerOption.ClusterID = id
-
 	clusterSecret, impersonatorSecret, err := util.ObtainCredentialsFromMemberCluster(clusterKubeClient, registerOption)
 	if err != nil {
 		return err
diff --git a/operator/config/samples/karmada.yaml b/operator/config/samples/karmada.yaml
index 466eb588a..f8ee59fc3 100644
--- a/operator/config/samples/karmada.yaml
+++ b/operator/config/samples/karmada.yaml
@@ -6,7 +6,7 @@ metadata:
 spec:
   crdTarball:
     httpSource: 
-      url: "https://github.com/karmada-io/karmada/releases/download/v1.12.2/crds.tar.gz"
+      url: "https://github.com/karmada-io/karmada/releases/download/v1.13.0/crds.tar.gz"
   components:
     etcd:
       local:
diff --git a/pkg/dependenciesdistributor/dependencies_distributor.go b/pkg/dependenciesdistributor/dependencies_distributor.go
index 749cee4ab..ea1f0d105 100644
--- a/pkg/dependenciesdistributor/dependencies_distributor.go
+++ b/pkg/dependenciesdistributor/dependencies_distributor.go
@@ -22,6 +22,7 @@ import (
 	"fmt"
 
 	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/equality"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -151,7 +152,9 @@ func (d *DependenciesDistributor) OnUpdate(oldObj, newObj interface{}) {
 		klog.V(4).Infof("Ignore update event of object (%s, kind=%s, %s) as specification no change", unstructuredOldObj.GetAPIVersion(), unstructuredOldObj.GetKind(), names.NamespacedKey(unstructuredOldObj.GetNamespace(), unstructuredOldObj.GetName()))
 		return
 	}
-	d.OnAdd(oldObj)
+	if !equality.Semantic.DeepEqual(unstructuredOldObj.GetLabels(), unstructuredNewObj.GetLabels()) {
+		d.OnAdd(oldObj)
+	}
 	d.OnAdd(newObj)
 }
 
@@ -214,7 +217,6 @@ func matchesWithBindingDependencies(resourceTemplateKey *LabelsKey, independentB
 			independentBinding.Namespace, independentBinding.Name, dependencies, err)
 		return false
 	}
-
 	if len(dependenciesSlice) == 0 {
 		return false
 	}
diff --git a/pkg/karmadactl/cmdinit/kubernetes/deploy.go b/pkg/karmadactl/cmdinit/kubernetes/deploy.go
index b81a67a6a..8fd1d7910 100644
--- a/pkg/karmadactl/cmdinit/kubernetes/deploy.go
+++ b/pkg/karmadactl/cmdinit/kubernetes/deploy.go
@@ -129,7 +129,7 @@ func init() {
 	karmadaRelease = releaseVer.ReleaseVersion()
 
 	DefaultCrdURL = fmt.Sprintf("https://github.com/karmada-io/karmada/releases/download/%s/crds.tar.gz", releaseVer.ReleaseVersion())
-	DefaultInitImage = "docker.io/alpine:3.21.0"
+	DefaultInitImage = "docker.io/alpine:3.21.3"
 	DefaultKarmadaSchedulerImage = fmt.Sprintf("docker.io/karmada/karmada-scheduler:%s", releaseVer.ReleaseVersion())
 	DefaultKarmadaControllerManagerImage = fmt.Sprintf("docker.io/karmada/karmada-controller-manager:%s", releaseVer.ReleaseVersion())
 	DefaultKarmadaWebhookImage = fmt.Sprintf("docker.io/karmada/karmada-webhook:%s", releaseVer.ReleaseVersion())
@@ -722,7 +722,7 @@ func (i *CommandInitOption) kubeControllerManagerImage() string {
 // get etcd-init image
 func (i *CommandInitOption) etcdInitImage() string {
 	if i.ImageRegistry != "" && i.EtcdInitImage == DefaultInitImage {
-		return i.ImageRegistry + "/alpine:3.21.0"
+		return i.ImageRegistry + "/alpine:3.21.3"
 	}
 	return i.EtcdInitImage
 }
diff --git a/pkg/karmadactl/join/join.go b/pkg/karmadactl/join/join.go
index 9803051fe..21097b4a2 100644
--- a/pkg/karmadactl/join/join.go
+++ b/pkg/karmadactl/join/join.go
@@ -228,22 +228,15 @@ func (j *CommandJoinOption) RunJoinCluster(controlPlaneRestConfig, clusterConfig
 		ClusterConfig:      clusterConfig,
 	}
 
-	id, err := util.ObtainClusterID(clusterKubeClient)
+	registerOption.ClusterID, err = util.ObtainClusterID(clusterKubeClient)
 	if err != nil {
 		return err
 	}
 
-	ok, name, err := util.IsClusterIdentifyUnique(karmadaClient, id)
-	if err != nil {
+	if err = registerOption.Validate(karmadaClient, false); err != nil {
 		return err
 	}
 
-	if !ok {
-		return fmt.Errorf("the same cluster has been registered with name %s", name)
-	}
-
-	registerOption.ClusterID = id
-
 	clusterSecret, impersonatorSecret, err := util.ObtainCredentialsFromMemberCluster(clusterKubeClient, registerOption)
 	if err != nil {
 		return err
diff --git a/pkg/util/cluster.go b/pkg/util/cluster.go
index 974b3615f..a57e39b58 100644
--- a/pkg/util/cluster.go
+++ b/pkg/util/cluster.go
@@ -52,6 +52,7 @@ const (
 type ClusterRegisterOption struct {
 	ClusterNamespace   string
 	ClusterName        string
+	ClusterID          string
 	ReportSecrets      []string
 	ClusterAPIEndpoint string
 	ProxyServerAddress string
@@ -64,11 +65,10 @@ type ClusterRegisterOption struct {
 	ClusterConfig      *rest.Config
 	Secret             corev1.Secret
 	ImpersonatorSecret corev1.Secret
-	ClusterID          string
 }
 
 // IsKubeCredentialsEnabled represents whether report secret
-func (r ClusterRegisterOption) IsKubeCredentialsEnabled() bool {
+func (r *ClusterRegisterOption) IsKubeCredentialsEnabled() bool {
 	for _, sct := range r.ReportSecrets {
 		if sct == KubeCredentials {
 			return true
@@ -78,7 +78,7 @@ func (r ClusterRegisterOption) IsKubeCredentialsEnabled() bool {
 }
 
 // IsKubeImpersonatorEnabled represents whether report impersonator secret
-func (r ClusterRegisterOption) IsKubeImpersonatorEnabled() bool {
+func (r *ClusterRegisterOption) IsKubeImpersonatorEnabled() bool {
 	for _, sct := range r.ReportSecrets {
 		if sct == KubeImpersonator {
 			return true
@@ -87,6 +87,49 @@ func (r ClusterRegisterOption) IsKubeImpersonatorEnabled() bool {
 	return false
 }
 
+// Validate validates the cluster register option, including clusterID, cluster name and so on.
+func (r *ClusterRegisterOption) Validate(karmadaClient karmadaclientset.Interface, isAgent bool) error {
+	clusterList, err := karmadaClient.ClusterV1alpha1().Clusters().List(context.TODO(), metav1.ListOptions{})
+	if err != nil {
+		return err
+	}
+	clusterIDUsed, clusterNameUsed, sameCluster := r.validateCluster(clusterList)
+	if err != nil {
+		return err
+	}
+
+	if isAgent && sameCluster {
+		return nil
+	}
+
+	if clusterIDUsed || clusterNameUsed {
+		return fmt.Errorf("the cluster ID %s or the cluster name %s has been registered", r.ClusterID, r.ClusterName)
+	}
+
+	return nil
+}
+
+// validateCluster validates the cluster register option whether the cluster name and cluster ID are unique.
+// 1. When registering a cluster for the first time, the metrics `clusterIDUsed` and `clusterNameUsed` can be used
+// to check if the cluster ID and the cluster name have already been used, which can avoid duplicate registrations.
+// 2. In cases where the agent is restarted, the metric `sameCluster` can be used to determine if the cluster
+// specified in the `RegisterOption` has already been registered, aiming to achieve the purpose of re-entering and updating the cluster.
+func (r *ClusterRegisterOption) validateCluster(clusterList *clusterv1alpha1.ClusterList) (clusterIDUsed, clusterNameUsed, sameCluster bool) {
+	for _, cluster := range clusterList.Items {
+		if cluster.Spec.ID == r.ClusterID && cluster.GetName() == r.ClusterName {
+			return true, true, true
+		}
+		if cluster.Spec.ID == r.ClusterID {
+			clusterIDUsed = true
+		}
+		if cluster.GetName() == r.ClusterName {
+			clusterNameUsed = true
+		}
+	}
+
+	return clusterIDUsed, clusterNameUsed, false
+}
+
 // IsClusterReady tells whether the cluster status in 'Ready' condition.
 func IsClusterReady(clusterStatus *clusterv1alpha1.ClusterStatus) bool {
 	return meta.IsStatusConditionTrue(clusterStatus.Conditions, clusterv1alpha1.ClusterConditionReady)
@@ -208,21 +251,6 @@ func ObtainClusterID(clusterKubeClient kubernetes.Interface) (string, error) {
 	return string(ns.UID), nil
 }
 
-// IsClusterIdentifyUnique checks whether the ClusterID exists in the karmada control plane.
-func IsClusterIdentifyUnique(controlPlaneClient karmadaclientset.Interface, id string) (bool, string, error) {
-	clusterList, err := controlPlaneClient.ClusterV1alpha1().Clusters().List(context.TODO(), metav1.ListOptions{})
-	if err != nil {
-		return false, "", err
-	}
-
-	for _, cluster := range clusterList.Items {
-		if cluster.Spec.ID == id {
-			return false, cluster.Name, nil
-		}
-	}
-	return true, "", nil
-}
-
 // ClusterAccessCredentialChanged checks whether the cluster access credential changed
 func ClusterAccessCredentialChanged(newSpec, oldSpec clusterv1alpha1.ClusterSpec) bool {
 	if oldSpec.APIEndpoint == newSpec.APIEndpoint &&



Test patch:
diff --git a/pkg/dependenciesdistributor/dependencies_distributor_test.go b/pkg/dependenciesdistributor/dependencies_distributor_test.go
index 31443d69b..63a55592d 100644
--- a/pkg/dependenciesdistributor/dependencies_distributor_test.go
+++ b/pkg/dependenciesdistributor/dependencies_distributor_test.go
@@ -114,7 +114,7 @@ func Test_OnUpdate(t *testing.T) {
 					},
 				},
 			},
-			wantQueueSize: 2,
+			wantQueueSize: 1,
 		},
 		{
 			name: "do not update the object, no specification changed",
@@ -140,6 +140,130 @@ func Test_OnUpdate(t *testing.T) {
 			},
 			wantQueueSize: 0,
 		},
+		{
+			name: "no specification changed, labels changed",
+			args: args{
+				oldObj: &corev1.Node{
+					TypeMeta: metav1.TypeMeta{
+						Kind:       "Node",
+						APIVersion: "v1",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "bar",
+						Labels: map[string]string{
+							"app": "test",
+						},
+					},
+				},
+				newObj: &corev1.Node{
+					TypeMeta: metav1.TypeMeta{
+						Kind:       "Node",
+						APIVersion: "v1",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "bar",
+						Labels: map[string]string{
+							"app": "new-test",
+						},
+					},
+				},
+			},
+			wantQueueSize: 2,
+		},
+		{
+			name: "specification changed, labels not changed",
+			args: args{
+				oldObj: &corev1.Node{
+					TypeMeta: metav1.TypeMeta{
+						Kind:       "Node",
+						APIVersion: "v1",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "bar",
+						Labels: map[string]string{
+							"app": "test",
+						},
+					},
+				},
+				newObj: &corev1.Node{
+					TypeMeta: metav1.TypeMeta{
+						Kind:       "Node",
+						APIVersion: "v1",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "foo",
+						Labels: map[string]string{
+							"app": "test",
+						},
+					},
+				},
+			},
+			wantQueueSize: 1,
+		},
+		{
+			name: "specification changed,  more than two elements in labels and not changed",
+			args: args{
+				oldObj: &corev1.Node{
+					TypeMeta: metav1.TypeMeta{
+						Kind:       "Node",
+						APIVersion: "v1",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "bar",
+						Labels: map[string]string{
+							"app":    "test",
+							"online": "svc",
+						},
+					},
+				},
+				newObj: &corev1.Node{
+					TypeMeta: metav1.TypeMeta{
+						Kind:       "Node",
+						APIVersion: "v1",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "foo",
+						Labels: map[string]string{
+							"online": "svc",
+							"app":    "test",
+						},
+					},
+				},
+			},
+			wantQueueSize: 1,
+		},
+		{
+			name: "specification changed,  more than two elements in labels and changed",
+			args: args{
+				oldObj: &corev1.Node{
+					TypeMeta: metav1.TypeMeta{
+						Kind:       "Node",
+						APIVersion: "v1",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "bar",
+						Labels: map[string]string{
+							"app":    "test",
+							"online": "svc",
+						},
+					},
+				},
+				newObj: &corev1.Node{
+					TypeMeta: metav1.TypeMeta{
+						Kind:       "Node",
+						APIVersion: "v1",
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "foo",
+						Labels: map[string]string{
+							"online": "svc1",
+							"app":    "test",
+						},
+					},
+				},
+			},
+			wantQueueSize: 2,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/pkg/karmadactl/cmdinit/kubernetes/deploy_test.go b/pkg/karmadactl/cmdinit/kubernetes/deploy_test.go
index 91bd3389f..03bfdd5fa 100644
--- a/pkg/karmadactl/cmdinit/kubernetes/deploy_test.go
+++ b/pkg/karmadactl/cmdinit/kubernetes/deploy_test.go
@@ -401,7 +401,7 @@ func TestEtcdInitImage(t *testing.T) {
 				ImageRegistry: "my-registry",
 				EtcdInitImage: DefaultInitImage,
 			},
-			expected: "my-registry/alpine:3.21.0",
+			expected: "my-registry/alpine:3.21.3",
 		},
 		{
 			name: "EtcdInitImage is set to a non-default value",
diff --git a/pkg/karmadactl/join/join_test.go b/pkg/karmadactl/join/join_test.go
index f13fedb7f..b9e768849 100644
--- a/pkg/karmadactl/join/join_test.go
+++ b/pkg/karmadactl/join/join_test.go
@@ -105,7 +105,7 @@ func TestRunJoinCluster(t *testing.T) {
 		prep                                 func(karmadaClient karmadaclientset.Interface, controlKubeClient kubeclient.Interface, clusterKubeClient kubeclient.Interface, opts *CommandJoinOption, clusterID types.UID, clusterName string) error
 		verify                               func(karmadaClient karmadaclientset.Interface, controlKubeClient kubeclient.Interface, clusterKubeClint kubeclient.Interface, opts *CommandJoinOption, clusterID types.UID) error
 		wantErr                              bool
-		errMsg                               string
+		errMsg                               func(opts *CommandJoinOption, clusterID types.UID) string
 	}{
 		{
 			name:                "RunJoinCluster_RegisterTheSameClusterWithSameID_TheSameClusterHasBeenRegistered",
@@ -136,7 +136,9 @@ func TestRunJoinCluster(t *testing.T) {
 				return nil
 			},
 			wantErr: true,
-			errMsg:  "the same cluster has been registered with name member1",
+			errMsg: func(opts *CommandJoinOption, clusterID types.UID) string {
+				return fmt.Sprintf("the cluster ID %s or the cluster name %s has been registered", clusterID, opts.ClusterName)
+			},
 		},
 		{
 			name: "RunJoinCluster_RegisterClusterInControllerPlane_ClusterRegisteredInControllerPlane",
@@ -170,8 +172,8 @@ func TestRunJoinCluster(t *testing.T) {
 			if err != nil && !test.wantErr {
 				t.Errorf("unexpected error, got: %v", err)
 			}
-			if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) {
-				t.Errorf("expected error message %s to be in %s", test.errMsg, err.Error())
+			if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg(test.joinOpts, test.clusterID)) {
+				t.Errorf("expected error message %s to be in %s", test.errMsg(test.joinOpts, test.clusterID), err.Error())
 			}
 			if err := test.verify(test.karmadaClient, test.controlKubeClient, test.clusterKubeClient, test.joinOpts, test.clusterID); err != nil {
 				t.Errorf("failed to verify joining the cluster, got error: %v", err)
diff --git a/pkg/resourceinterpreter/default/native/aggregatestatus.go b/pkg/resourceinterpreter/default/native/aggregatestatus.go
index c26617088..a9e5101cb 100644
--- a/pkg/resourceinterpreter/default/native/aggregatestatus.go
+++ b/pkg/resourceinterpreter/default/native/aggregatestatus.go
@@ -145,13 +145,6 @@ func aggregateServiceStatus(object *unstructured.Unstructured, aggregatedStatusI
 		klog.V(3).Infof("Grab service(%s/%s) status from cluster(%s), loadBalancer status: %v",
 			service.Namespace, service.Name, item.ClusterName, temp.LoadBalancer)
 
-		// Set cluster name as Hostname by default to indicate the status is collected from which member cluster.
-		for i := range temp.LoadBalancer.Ingress {
-			if temp.LoadBalancer.Ingress[i].Hostname == "" {
-				temp.LoadBalancer.Ingress[i].Hostname = item.ClusterName
-			}
-		}
-
 		newStatus.LoadBalancer.Ingress = append(newStatus.LoadBalancer.Ingress, temp.LoadBalancer.Ingress...)
 	}
 
@@ -184,13 +177,6 @@ func aggregateIngressStatus(object *unstructured.Unstructured, aggregatedStatusI
 		klog.V(3).Infof("Grab ingress(%s/%s) status from cluster(%s), loadBalancer status: %v",
 			ingress.Namespace, ingress.Name, item.ClusterName, temp.LoadBalancer)
 
-		// Set cluster name as Hostname by default to indicate the status is collected from which member cluster.
-		for i := range temp.LoadBalancer.Ingress {
-			if temp.LoadBalancer.Ingress[i].Hostname == "" {
-				temp.LoadBalancer.Ingress[i].Hostname = item.ClusterName
-			}
-		}
-
 		newStatus.LoadBalancer.Ingress = append(newStatus.LoadBalancer.Ingress, temp.LoadBalancer.Ingress...)
 	}
 
diff --git a/pkg/resourceinterpreter/default/native/aggregatestatus_test.go b/pkg/resourceinterpreter/default/native/aggregatestatus_test.go
index 41f6f2e84..41ad664bf 100644
--- a/pkg/resourceinterpreter/default/native/aggregatestatus_test.go
+++ b/pkg/resourceinterpreter/default/native/aggregatestatus_test.go
@@ -129,7 +129,7 @@ func TestAggregateServiceStatus(t *testing.T) {
 	newServiceLoadBalancer := &corev1.Service{
 		TypeMeta: metav1.TypeMeta{Kind: "Service", APIVersion: corev1.SchemeGroupVersion.String()},
 		Spec:     corev1.ServiceSpec{Type: corev1.ServiceTypeLoadBalancer},
-		Status:   corev1.ServiceStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{{IP: "8.8.8.8", Hostname: "member1"}}}},
+		Status:   corev1.ServiceStatus{LoadBalancer: corev1.LoadBalancerStatus{Ingress: []corev1.LoadBalancerIngress{{IP: "8.8.8.8"}}}},
 	}
 	oldObjServiceLoadBalancer, _ := helper.ToUnstructured(oldServiceLoadBalancer)
 	newObjServiceLoadBalancer, _ := helper.ToUnstructured(newServiceLoadBalancer)
@@ -192,7 +192,7 @@ func TestAggregateIngressStatus(t *testing.T) {
 	}
 	newIngress := &networkingv1.Ingress{
 		TypeMeta: metav1.TypeMeta{Kind: "Ingress", APIVersion: networkingv1.SchemeGroupVersion.String()},
-		Status:   networkingv1.IngressStatus{LoadBalancer: networkingv1.IngressLoadBalancerStatus{Ingress: []networkingv1.IngressLoadBalancerIngress{{IP: "8.8.8.8", Hostname: "member1"}}}},
+		Status:   networkingv1.IngressStatus{LoadBalancer: networkingv1.IngressLoadBalancerStatus{Ingress: []networkingv1.IngressLoadBalancerIngress{{IP: "8.8.8.8"}}}},
 	}
 	oldObj, _ := helper.ToUnstructured(oldIngress)
 	newObj, _ := helper.ToUnstructured(newIngress)
diff --git a/pkg/util/cluster_test.go b/pkg/util/cluster_test.go
index e4f7ab85b..0ea8fc6bf 100644
--- a/pkg/util/cluster_test.go
+++ b/pkg/util/cluster_test.go
@@ -22,7 +22,6 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/kubernetes/fake"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -54,11 +53,6 @@ func withSyncMode(cluster *clusterv1alpha1.Cluster, syncMode clusterv1alpha1.Clu
 	return cluster
 }
 
-func withID(cluster *clusterv1alpha1.Cluster, id string) *clusterv1alpha1.Cluster {
-	cluster.Spec.ID = id
-	return cluster
-}
-
 func TestCreateOrUpdateClusterObject(t *testing.T) {
 	type args struct {
 		controlPlaneClient karmadaclientset.Interface
@@ -167,48 +161,6 @@ func TestCreateOrUpdateClusterObject(t *testing.T) {
 	}
 }
 
-func TestIsClusterIDUnique(t *testing.T) {
-	tests := []struct {
-		name           string
-		existedCluster []runtime.Object
-		id             string
-		want           bool
-		clustername    string
-	}{
-		{
-			name: "no cluster", id: "1", want: true,
-			existedCluster: []runtime.Object{},
-		},
-		{
-			name: "existed id", id: "1", want: false, clustername: "cluster-1",
-			existedCluster: []runtime.Object{withID(newCluster("cluster-1"), "1")},
-		},
-		{
-			name: "unique id", id: "2", want: true,
-			existedCluster: []runtime.Object{withID(newCluster("cluster-1"), "1")},
-		},
-	}
-
-	for _, tc := range tests {
-		t.Run(tc.name, func(t *testing.T) {
-			fakeClient := karmadaclientsetfake.NewSimpleClientset(tc.existedCluster...)
-
-			ok, name, err := IsClusterIdentifyUnique(fakeClient, tc.id)
-			if err != nil {
-				t.Fatal(err)
-			}
-
-			if ok != tc.want {
-				t.Errorf("expected value: %v, but got: %v", tc.want, ok)
-			}
-
-			if !ok && name != tc.clustername {
-				t.Errorf("expected clustername: %v, but got: %v", tc.clustername, name)
-			}
-		})
-	}
-}
-
 func TestClusterRegisterOption_IsKubeCredentialsEnabled(t *testing.T) {
 	type fields struct {
 		ReportSecrets []string
@@ -323,6 +275,106 @@ func TestClusterRegisterOption_IsKubeImpersonatorEnabled(t *testing.T) {
 	}
 }
 
+func TestClusterRegisterOption_ValidateCluster(t *testing.T) {
+	registeredClusterList := &clusterv1alpha1.ClusterList{
+		Items: []clusterv1alpha1.Cluster{
+			{
+				Spec: clusterv1alpha1.ClusterSpec{
+					ID: "1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "member1",
+				},
+			},
+			{
+				Spec: clusterv1alpha1.ClusterSpec{
+					ID: "2",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "member2",
+				},
+			},
+			{
+				Spec: clusterv1alpha1.ClusterSpec{
+					ID: "3",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "member3",
+				},
+			},
+		},
+	}
+
+	testItems := []struct {
+		name                    string
+		clusterList             *clusterv1alpha1.ClusterList
+		opts                    ClusterRegisterOption
+		expectedClusterIDUsed   bool
+		expectedClusterNameUsed bool
+		expectedSameCluster     bool
+	}{
+		{
+			name:        "registering a brand new cluster",
+			clusterList: registeredClusterList,
+			opts: ClusterRegisterOption{
+				ClusterID:   "4",
+				ClusterName: "member4",
+			},
+			expectedClusterIDUsed:   false,
+			expectedClusterNameUsed: false,
+			expectedSameCluster:     false,
+		},
+		{
+			name:        "clusterName is used",
+			clusterList: registeredClusterList,
+			opts: ClusterRegisterOption{
+				ClusterID:   "4",
+				ClusterName: "member2",
+			},
+			expectedClusterIDUsed:   false,
+			expectedClusterNameUsed: true,
+			expectedSameCluster:     false,
+		},
+		{
+			name:        "clusterID is used",
+			clusterList: registeredClusterList,
+			opts: ClusterRegisterOption{
+				ClusterID:   "2",
+				ClusterName: "member4",
+			},
+			expectedClusterIDUsed:   true,
+			expectedClusterNameUsed: false,
+			expectedSameCluster:     false,
+		},
+		{
+			name:        "the same cluster",
+			clusterList: registeredClusterList,
+			opts: ClusterRegisterOption{
+				ClusterID:   "2",
+				ClusterName: "member2",
+			},
+			expectedClusterIDUsed:   true,
+			expectedClusterNameUsed: true,
+			expectedSameCluster:     true,
+		},
+	}
+
+	for _, item := range testItems {
+		t.Run(item.name, func(t *testing.T) {
+			clusterIDUsed, clusterNameUsed, sameCluster := item.opts.validateCluster(item.clusterList)
+			if clusterIDUsed != item.expectedClusterIDUsed {
+				t.Errorf("clusterNameUsed = %v, want %v", clusterIDUsed, item.expectedClusterIDUsed)
+			}
+			if clusterNameUsed != item.expectedClusterNameUsed {
+				t.Errorf("clusterNameUsed = %v, want %v", clusterNameUsed, item.expectedClusterNameUsed)
+			}
+			if sameCluster != item.expectedSameCluster {
+				t.Errorf("clusterNameUsed = %v, want %v", sameCluster, item.expectedSameCluster)
+			}
+		})
+	}
+}
+
 func TestCreateClusterObject(t *testing.T) {
 	type args struct {
 		controlPlaneClient karmadaclientset.Interface
diff --git a/test/e2e/suites/base/resource_test.go b/test/e2e/suites/base/resource_test.go
index b62e8676f..a88135501 100644
--- a/test/e2e/suites/base/resource_test.go
+++ b/test/e2e/suites/base/resource_test.go
@@ -175,9 +175,8 @@ var _ = ginkgo.Describe("[resource-status collection] resource status collection
 					ingresses := []corev1.LoadBalancerIngress{{IP: fmt.Sprintf("172.19.1.%d", index+6)}}
 					for _, ingress := range ingresses {
 						svcLoadBalancer.Ingress = append(svcLoadBalancer.Ingress, corev1.LoadBalancerIngress{
-							IP:       ingress.IP,
-							Hostname: clusterName,
-							IPMode:   ingress.IPMode,
+							IP:     ingress.IP,
+							IPMode: ingress.IPMode,
 						})
 					}
 
@@ -323,8 +322,7 @@ var _ = ginkgo.Describe("[resource-status collection] resource status collection
 					ingresses := []networkingv1.IngressLoadBalancerIngress{{IP: fmt.Sprintf("172.19.2.%d", index+6)}}
 					for _, ingress := range ingresses {
 						ingLoadBalancer.Ingress = append(ingLoadBalancer.Ingress, networkingv1.IngressLoadBalancerIngress{
-							IP:       ingress.IP,
-							Hostname: clusterName,
+							IP: ingress.IP,
 						})
 					}