Repository: karmada-io/karmada

Issue:
Service access will be interrupted when CrossCluster MCS is used and the cluster's status is abnormal
<!-- 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 CrossCluster MCS is used for cross-cluster service access, when the provider cluster's status is abnormal (For example upgrade the cluster) . but the service is normal, the MCS-controller clears the work of the MCS. As a result, the service access will be interrupted.

**What you expected to happen**:
When the CrossCluster MCS is used and the cluster status is abnormal, the mcs not automatically be cleared. 
https://github.com/karmada-io/karmada/blob/138537bdb201239b949408d8be65250b4e5afc4b/pkg/controllers/multiclusterservice/mcs_controller.go#L152，In this line, check whether the provider cluster status is abnormal. If the provider cluster status is abnormal, the work of the MCS and EPS will be deleted. If the cluster status is abnormal but the service is normal, service access will be interrupted. Therefore, do not delete the work of the MCS and EPS when the provider cluster status is abnormal. Access failures may occur when the cluster and services are both abnormal. However, the status of the cluster and services should be determined separately. It is not assumed that the services are also abnormal when the cluster status is abnormal.

**How to reproduce it (as minimally and precisely as possible)**:
Create an CrossCluster MCS. If the provider cluster's status is abnormal, service access from the consumer cluster will be interrupted.

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

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


Discussion:
@XiShanYongYe-Chang: /assign@XiShanYongYe-Chang: I think it's more like a feature.
/kind feature
/remove-kind bug

Solution patch:
diff --git a/README.md b/README.md
index a76bf2eed..271b596a4 100644
--- a/README.md
+++ b/README.md
@@ -186,12 +186,12 @@ nginx   2/2     2            2           20s
 
 ## Kubernetes compatibility
 
-|                       | Kubernetes 1.16 | Kubernetes 1.17 | Kubernetes 1.18 | Kubernetes 1.19 | Kubernetes 1.20 | Kubernetes 1.21 | Kubernetes 1.22 | Kubernetes 1.23 | Kubernetes 1.24 | Kubernetes 1.25 | Kubernetes 1.26 | Kubernetes 1.27 | Kubernetes 1.28 | Kubernetes 1.29 |
-|-----------------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|
-| Karmada v1.7          | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               |
-| Karmada v1.8          | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               |
-| Karmada v1.9          | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               |
-| Karmada HEAD (master) | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               |
+|                       | Kubernetes 1.16 | Kubernetes 1.17 | Kubernetes 1.18 | Kubernetes 1.19 | Kubernetes 1.20 | Kubernetes 1.21 | Kubernetes 1.22 | Kubernetes 1.23 | Kubernetes 1.24 | Kubernetes 1.25 | Kubernetes 1.26 | Kubernetes 1.27 | Kubernetes 1.28 | Kubernetes 1.29 | Kubernetes 1.30 | Kubernetes 1.31 | Kubernetes 1.32 |
+|-----------------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|-----------------|
+| Karmada v1.11         | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               |
+| Karmada v1.12         | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               |
+| Karmada v1.13         | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               |
+| Karmada HEAD (master) | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               | ✓               |
 
 Key:
 * `✓` Karmada and the Kubernetes version are exactly compatible.
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/docs/CHANGELOG/CHANGELOG-1.11.md b/docs/CHANGELOG/CHANGELOG-1.11.md
index 890acf4fc..aa9f2c925 100644
--- a/docs/CHANGELOG/CHANGELOG-1.11.md
+++ b/docs/CHANGELOG/CHANGELOG-1.11.md
@@ -2,49 +2,55 @@
 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
 **Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*
 
-- [v1.11.7](#v1117)
-  - [Downloads for v1.11.7](#downloads-for-v1117)
-  - [Changelog since v1.11.6](#changelog-since-v1116)
+- [v1.11.8](#v1118)
+  - [Downloads for v1.11.8](#downloads-for-v1118)
+  - [Changelog since v1.11.7](#changelog-since-v1117)
     - [Changes by Kind](#changes-by-kind)
       - [Bug Fixes](#bug-fixes)
       - [Others](#others)
-- [v1.11.6](#v1116)
-  - [Downloads for v1.11.6](#downloads-for-v1116)
-  - [Changelog since v1.11.5](#changelog-since-v1115)
+- [v1.11.7](#v1117)
+  - [Downloads for v1.11.7](#downloads-for-v1117)
+  - [Changelog since v1.11.6](#changelog-since-v1116)
     - [Changes by Kind](#changes-by-kind-1)
       - [Bug Fixes](#bug-fixes-1)
       - [Others](#others-1)
-- [v1.11.5](#v1115)
-  - [Downloads for v1.11.5](#downloads-for-v1115)
-  - [Changelog since v1.11.4](#changelog-since-v1114)
+- [v1.11.6](#v1116)
+  - [Downloads for v1.11.6](#downloads-for-v1116)
+  - [Changelog since v1.11.5](#changelog-since-v1115)
     - [Changes by Kind](#changes-by-kind-2)
       - [Bug Fixes](#bug-fixes-2)
       - [Others](#others-2)
+- [v1.11.5](#v1115)
+  - [Downloads for v1.11.5](#downloads-for-v1115)
+  - [Changelog since v1.11.4](#changelog-since-v1114)
+    - [Changes by Kind](#changes-by-kind-3)
+      - [Bug Fixes](#bug-fixes-3)
+      - [Others](#others-3)
 - [v1.11.4](#v1114)
   - [Downloads for v1.11.4](#downloads-for-v1114)
   - [Changelog since v1.11.3](#changelog-since-v1113)
-    - [Changes by Kind](#changes-by-kind-3)
+    - [Changes by Kind](#changes-by-kind-4)
       - [Urgent Upgrade Notes](#urgent-upgrade-notes)
-      - [Bug Fixes](#bug-fixes-3)
-      - [Others](#others-3)
+      - [Bug Fixes](#bug-fixes-4)
+      - [Others](#others-4)
 - [v1.11.3](#v1113)
   - [Downloads for v1.11.3](#downloads-for-v1113)
   - [Changelog since v1.11.2](#changelog-since-v1112)
-    - [Changes by Kind](#changes-by-kind-4)
-      - [Bug Fixes](#bug-fixes-4)
-      - [Others](#others-4)
-- [v1.11.2](#v1112)
-  - [Downloads for v1.11.2](#downloads-for-v1112)
-  - [Changelog since v1.11.1](#changelog-since-v1111)
     - [Changes by Kind](#changes-by-kind-5)
       - [Bug Fixes](#bug-fixes-5)
       - [Others](#others-5)
-- [v1.11.1](#v1111)
-  - [Downloads for v1.11.1](#downloads-for-v1111)
-  - [Changelog since v1.11.0](#changelog-since-v1110)
+- [v1.11.2](#v1112)
+  - [Downloads for v1.11.2](#downloads-for-v1112)
+  - [Changelog since v1.11.1](#changelog-since-v1111)
     - [Changes by Kind](#changes-by-kind-6)
       - [Bug Fixes](#bug-fixes-6)
       - [Others](#others-6)
+- [v1.11.1](#v1111)
+  - [Downloads for v1.11.1](#downloads-for-v1111)
+  - [Changelog since v1.11.0](#changelog-since-v1110)
+    - [Changes by Kind](#changes-by-kind-7)
+      - [Bug Fixes](#bug-fixes-7)
+      - [Others](#others-7)
 - [v1.11.0](#v1110)
   - [Downloads for v1.11.0](#downloads-for-v1110)
   - [What's New](#whats-new)
@@ -55,7 +61,7 @@
   - [Other Notable Changes](#other-notable-changes)
     - [API Changes](#api-changes)
     - [Deprecation](#deprecation)
-    - [Bug Fixes](#bug-fixes-7)
+    - [Bug Fixes](#bug-fixes-8)
     - [Security](#security)
     - [Features & Enhancements](#features--enhancements)
   - [Other](#other)
@@ -66,6 +72,20 @@
 
 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
 
+# v1.11.8
+## Downloads for v1.11.8
+
+Download v1.11.8 in the [v1.11.8 release page](https://github.com/karmada-io/karmada/releases/tag/v1.11.8).
+
+## Changelog since v1.11.7
+### Changes by Kind
+#### Bug Fixes
+- `karmada-agent`: Fixed a panic issue where the agent does not need to report secret when registering cluster. ([#6224](https://github.com/karmada-io/karmada/pull/6224), @jabellard)
+- `karmada-controller-manager`: Fixed the issue that the gracefulEvictionTask of ResourceBinding can not be cleared in case of schedule fails. ([#6236](https://github.com/karmada-io/karmada/pull/6236), @XiShanYongYe-Chang)
+
+#### Others
+None.
+
 # v1.11.7
 ## Downloads for v1.11.7
 
diff --git a/docs/CHANGELOG/CHANGELOG-1.12.md b/docs/CHANGELOG/CHANGELOG-1.12.md
index ba088fe90..ac6bd6f7a 100644
--- a/docs/CHANGELOG/CHANGELOG-1.12.md
+++ b/docs/CHANGELOG/CHANGELOG-1.12.md
@@ -2,30 +2,36 @@
 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
 **Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*
 
-- [v1.12.4](#v1124)
-  - [Downloads for v1.12.4](#downloads-for-v1124)
-  - [Changelog since v1.12.3](#changelog-since-v1123)
+- [v1.12.5](#v1125)
+  - [Downloads for v1.12.5](#downloads-for-v1125)
+  - [Changelog since v1.12.4](#changelog-since-v1124)
     - [Changes by Kind](#changes-by-kind)
       - [Bug Fixes](#bug-fixes)
       - [Others](#others)
-- [v1.12.3](#v1123)
-  - [Downloads for v1.12.3](#downloads-for-v1123)
-  - [Changelog since v1.12.2](#changelog-since-v1122)
+- [v1.12.4](#v1124)
+  - [Downloads for v1.12.4](#downloads-for-v1124)
+  - [Changelog since v1.12.3](#changelog-since-v1123)
     - [Changes by Kind](#changes-by-kind-1)
       - [Bug Fixes](#bug-fixes-1)
       - [Others](#others-1)
-- [v1.12.2](#v1122)
-  - [Downloads for v1.12.2](#downloads-for-v1122)
-  - [Changelog since v1.12.1](#changelog-since-v1121)
+- [v1.12.3](#v1123)
+  - [Downloads for v1.12.3](#downloads-for-v1123)
+  - [Changelog since v1.12.2](#changelog-since-v1122)
     - [Changes by Kind](#changes-by-kind-2)
       - [Bug Fixes](#bug-fixes-2)
       - [Others](#others-2)
-- [v1.12.1](#v1121)
-  - [Downloads for v1.12.1](#downloads-for-v1121)
-  - [Changelog since v1.12.0](#changelog-since-v1120)
+- [v1.12.2](#v1122)
+  - [Downloads for v1.12.2](#downloads-for-v1122)
+  - [Changelog since v1.12.1](#changelog-since-v1121)
     - [Changes by Kind](#changes-by-kind-3)
       - [Bug Fixes](#bug-fixes-3)
       - [Others](#others-3)
+- [v1.12.1](#v1121)
+  - [Downloads for v1.12.1](#downloads-for-v1121)
+  - [Changelog since v1.12.0](#changelog-since-v1120)
+    - [Changes by Kind](#changes-by-kind-4)
+      - [Bug Fixes](#bug-fixes-4)
+      - [Others](#others-4)
 - [v1.12.0](#v1120)
   - [Downloads for v1.12.0](#downloads-for-v1120)
   - [What's New](#whats-new)
@@ -36,7 +42,7 @@
   - [Other Notable Changes](#other-notable-changes)
     - [API Changes](#api-changes)
     - [Deprecation](#deprecation)
-    - [Bug Fixes](#bug-fixes-4)
+    - [Bug Fixes](#bug-fixes-5)
     - [Security](#security)
     - [Features & Enhancements](#features--enhancements)
   - [Other](#other)
@@ -48,11 +54,11 @@
   - [Downloads for v1.12.0-beta.0](#downloads-for-v1120-beta0)
   - [Changelog since v1.12.0-alpha.1](#changelog-since-v1120-alpha1)
   - [Urgent Update Notes](#urgent-update-notes)
-  - [Changes by Kind](#changes-by-kind-4)
+  - [Changes by Kind](#changes-by-kind-5)
     - [API Changes](#api-changes-1)
     - [Features & Enhancements](#features--enhancements-1)
     - [Deprecation](#deprecation-1)
-    - [Bug Fixes](#bug-fixes-5)
+    - [Bug Fixes](#bug-fixes-6)
     - [Security](#security-1)
   - [Other](#other-1)
     - [Dependencies](#dependencies-1)
@@ -62,11 +68,11 @@
   - [Downloads for v1.12.0-alpha.1](#downloads-for-v1120-alpha1)
   - [Changelog since v1.11.0](#changelog-since-v1110)
   - [Urgent Update Notes](#urgent-update-notes-1)
-  - [Changes by Kind](#changes-by-kind-5)
+  - [Changes by Kind](#changes-by-kind-6)
     - [API Changes](#api-changes-2)
     - [Features & Enhancements](#features--enhancements-2)
     - [Deprecation](#deprecation-2)
-    - [Bug Fixes](#bug-fixes-6)
+    - [Bug Fixes](#bug-fixes-7)
     - [Security](#security-2)
   - [Other](#other-2)
     - [Dependencies](#dependencies-2)
@@ -75,6 +81,21 @@
 
 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
 
+# v1.12.5
+## Downloads for v1.12.5
+
+Download v1.12.5 in the [v1.12.5 release page](https://github.com/karmada-io/karmada/releases/tag/v1.12.5).
+
+## Changelog since v1.12.4
+### Changes by Kind
+#### Bug Fixes
+- `karmada-agent`: Fixed a panic issue where the agent does not need to report secret when registering cluster. ([#6223](https://github.com/karmada-io/karmada/pull/6223), @jabellard)
+- `karmada-controller-manager`: Fixed the issue that the gracefulEvictionTask of ResourceBinding can not be cleared in case of schedule fails. ([#6235](https://github.com/karmada-io/karmada/pull/6235), @XiShanYongYe-Chang)
+- `helm`: Fixed the issue where the required ServiceAccount was missing when the certificate mode was set to custom. ([#6241](https://github.com/karmada-io/karmada/pull/6241), @seanlaii)
+
+#### Others
+None.
+
 # v1.12.4
 ## Downloads for v1.12.4
 
diff --git a/docs/CHANGELOG/CHANGELOG-1.13.md b/docs/CHANGELOG/CHANGELOG-1.13.md
index 21d789333..3f06e1bc9 100644
--- a/docs/CHANGELOG/CHANGELOG-1.13.md
+++ b/docs/CHANGELOG/CHANGELOG-1.13.md
@@ -2,6 +2,12 @@
 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
 **Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*
 
+- [v1.13.1](#v1131)
+  - [Downloads for v1.13.1](#downloads-for-v1131)
+  - [Changelog since v1.13.0](#changelog-since-v1130)
+    - [Changes by Kind](#changes-by-kind)
+      - [Bug Fixes](#bug-fixes)
+      - [Others](#others)
 - [v1.13.0](#v1130)
   - [Downloads for v1.13.0](#downloads-for-v1130)
   - [Urgent Update Notes](#urgent-update-notes)
@@ -14,7 +20,7 @@
   - [Other Notable Changes](#other-notable-changes)
     - [API Changes](#api-changes)
     - [Deprecation](#deprecation)
-    - [Bug Fixes](#bug-fixes)
+    - [Bug Fixes](#bug-fixes-1)
     - [Security](#security)
     - [Features & Enhancements](#features--enhancements)
   - [Other](#other)
@@ -26,11 +32,11 @@
   - [Downloads for v1.13.0-rc.0](#downloads-for-v1130-rc0)
   - [Changelog since v1.13.0-beta.0](#changelog-since-v1130-beta0)
   - [Urgent Update Notes](#urgent-update-notes-1)
-  - [Changes by Kind](#changes-by-kind)
+  - [Changes by Kind](#changes-by-kind-1)
     - [API Changes](#api-changes-1)
     - [Features & Enhancements](#features--enhancements-1)
     - [Deprecation](#deprecation-1)
-    - [Bug Fixes](#bug-fixes-1)
+    - [Bug Fixes](#bug-fixes-2)
     - [Security](#security-1)
   - [Other](#other-1)
     - [Dependencies](#dependencies-1)
@@ -40,11 +46,11 @@
   - [Downloads for v1.13.0-beta.0](#downloads-for-v1130-beta0)
   - [Changelog since v1.13.0-alpha.2](#changelog-since-v1130-alpha2)
   - [Urgent Update Notes](#urgent-update-notes-2)
-  - [Changes by Kind](#changes-by-kind-1)
+  - [Changes by Kind](#changes-by-kind-2)
     - [API Changes](#api-changes-2)
     - [Features & Enhancements](#features--enhancements-2)
     - [Deprecation](#deprecation-2)
-    - [Bug Fixes](#bug-fixes-2)
+    - [Bug Fixes](#bug-fixes-3)
     - [Security](#security-2)
   - [Other](#other-2)
     - [Dependencies](#dependencies-2)
@@ -54,11 +60,11 @@
   - [Downloads for v1.13.0-alpha.2](#downloads-for-v1130-alpha2)
   - [Changelog since v1.13.0-alpha.1](#changelog-since-v1130-alpha1)
   - [Urgent Update Notes](#urgent-update-notes-3)
-  - [Changes by Kind](#changes-by-kind-2)
+  - [Changes by Kind](#changes-by-kind-3)
     - [API Changes](#api-changes-3)
     - [Features & Enhancements](#features--enhancements-3)
     - [Deprecation](#deprecation-3)
-    - [Bug Fixes](#bug-fixes-3)
+    - [Bug Fixes](#bug-fixes-4)
     - [Security](#security-3)
   - [Other](#other-3)
     - [Dependencies](#dependencies-3)
@@ -68,11 +74,11 @@
   - [Downloads for v1.13.0-alpha.1](#downloads-for-v1130-alpha1)
   - [Changelog since v1.12.0](#changelog-since-v1120)
   - [Urgent Update Notes](#urgent-update-notes-4)
-  - [Changes by Kind](#changes-by-kind-3)
+  - [Changes by Kind](#changes-by-kind-4)
     - [API Changes](#api-changes-4)
     - [Features & Enhancements](#features--enhancements-4)
     - [Deprecation](#deprecation-4)
-    - [Bug Fixes](#bug-fixes-4)
+    - [Bug Fixes](#bug-fixes-5)
     - [Security](#security-4)
   - [Other](#other-4)
     - [Dependencies](#dependencies-4)
@@ -81,6 +87,22 @@
 
 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
 
+# v1.13.1
+## Downloads for v1.13.1
+
+Download v1.13.1 in the [v1.13.1 release page](https://github.com/karmada-io/karmada/releases/tag/v1.13.1).
+
+## Changelog since v1.13.0
+### Changes by Kind
+#### Bug Fixes
+- `karmada-operator`: The `karmada-app` label key previously used for control plane components and for the components of the operator itself has been changed to the more idiomatic `app.kubernetes.io/name` label key. ([#6200](https://github.com/karmada-io/karmada/pull/6200), @jabellard)
+- `karmada-agent`: Fixed a panic issue where the agent does not need to report secret when registering cluster. ([#6222](https://github.com/karmada-io/karmada/pull/6222), @jabellard)
+- `karmada-controller-manager`: Fixed the issue that the gracefulEvictionTask of ResourceBinding can not be cleared in case of schedule fails. ([#6234](https://github.com/karmada-io/karmada/pull/6234), @XiShanYongYe-Chang)
+- `helm`: Fixed the issue where the required ServiceAccount was missing when the certificate mode was set to custom. ([#6242](https://github.com/karmada-io/karmada/pull/6242), @seanlaii)
+
+#### Others
+None.
+
 # v1.13.0
 ## Downloads for v1.13.0
 
diff --git a/docs/CHANGELOG/CHANGELOG-1.14.md b/docs/CHANGELOG/CHANGELOG-1.14.md
new file mode 100644
index 000000000..2e8b0c929
--- /dev/null
+++ b/docs/CHANGELOG/CHANGELOG-1.14.md
@@ -0,0 +1,66 @@
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*
+
+- [v1.14.0-alpha.1](#v1140-alpha1)
+  - [Downloads for v1.14.0-alpha.1](#downloads-for-v1140-alpha1)
+  - [Changelog since v1.13.0](#changelog-since-v1130)
+  - [Urgent Update Notes](#urgent-update-notes)
+  - [Changes by Kind](#changes-by-kind)
+    - [API Changes](#api-changes)
+    - [Features & Enhancements](#features--enhancements)
+    - [Deprecation](#deprecation)
+    - [Bug Fixes](#bug-fixes)
+    - [Security](#security)
+  - [Other](#other)
+    - [Dependencies](#dependencies)
+    - [Helm Charts](#helm-charts)
+    - [Instrumentation](#instrumentation)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+# v1.14.0-alpha.1
+## Downloads for v1.14.0-alpha.1
+
+Download v1.14.0-alpha.1 in the [v1.14.0-alpha.1 release page](https://github.com/karmada-io/karmada/releases/tag/v1.14.0-alpha.1).
+
+## Changelog since v1.13.0
+
+## Urgent Update Notes
+
+## Changes by Kind
+
+### API Changes
+- Introduced `spec.customCertificate.leafCertValidityDays` field in `Karmada` API to specify a custom validity period in days for control plane leaf certificates (e.g., API server certificate). When not explicitly set, the default validity period of 1 year is used. ([#6193](https://github.com/karmada-io/karmada/pull/6193), @jabellard)
+
+### Features & Enhancements
+- `karmada-controller-manager`: All controller reconcile frequency will honor a unified rate limiter configuration(--rate-limiter-*). ([#6145](https://github.com/karmada-io/karmada/pull/6145), @CharlesQQ)
+- `karmada-agent`: All controller reconcile frequency will honor a unified rate limiter configuration(--rate-limiter-*). ([#6145](https://github.com/karmada-io/karmada/pull/6145), @CharlesQQ)
+
+### Deprecation
+- `karmadactl`: The flag `--ca-cert-path` of `register`, which has been deprecated in release-1.13, now has been removed. ([#6191](https://github.com/karmada-io/karmada/pull/6191), @husnialhamdani)
+- The label `propagation.karmada.io/instruction` now has been deprecated in favor of the field(`.spec.suspendDispatching`) in Work API, the label will be removed in future releases. ([#6043](https://github.com/karmada-io/karmada/pull/6043), @vie-serendipity)
+
+### Bug Fixes
+- `karmada-agent`: Fixed a panic issue where the agent does not need to report secret when registering cluster. ([#6214](https://github.com/karmada-io/karmada/pull/6214), @jabellard)
+- `karmada-operator`: The `karmada-app` label key previously used for control plane components and for the components of the operator itself has been changed to the more idiomatic `app.kubernetes.io/name` label key. ([#6180](https://github.com/karmada-io/karmada/pull/6180), @jabellard)
+- `karmada-controller-manager`: Fixed the issue that the gracefulEvictionTask of ResourceBinding can not be cleared in case of schedule fails. ([#6227](https://github.com/karmada-io/karmada/pull/6227), @XiShanYongYe-Chang)
+- `helm`: Fixed the issue where the required ServiceAccount was missing when the certificate mode was set to custom. ([#6188](https://github.com/karmada-io/karmada/pull/6188), @seanlaii)
+- Unify the rate limiter for different clients in each component to access karmada-apiserver. Access may be restricted in large-scale environments compared to before the modification. Administrators can avoid this situation by adjusting upward the rate limit parameters 'kube-api-qps' and 'kube-api-burst' for each component to access karmada-apiserver, and adjusting 'cluster-api-qps' and 'cluster-api-burst' for scheduler-estimator to access member cluster apiserver. ([#6095](https://github.com/karmada-io/karmada/pull/6095), @zach593)
+
+### Security
+None.
+
+## Other
+### Dependencies
+- Karmada is now built with Golang v1.23.7. ([#6218](https://github.com/karmada-io/karmada/pull/6218), @seanlaii)
+- Bump golang.org/x/net to 0.37.0. ([#6225](https://github.com/karmada-io/karmada/pull/6225), @seanlaii)
+- Bump mockery to v2.53.3 to remove the hashicorp packages. ([#6212](https://github.com/karmada-io/karmada/pull/6212), @seanlaii)
+
+### Helm Charts
+- `HELM Chart`: OpenID Connect based auth can be configured for the Karmada API server when installing via Helm. ([#6159](https://github.com/karmada-io/karmada/pull/6159), @tw-mnewman)
+- `Helm chart`: Added helm index for 1.13 release. ([#6196](https://github.com/karmada-io/karmada/pull/6196), @zhzhuang-zju)
+
+### Instrumentation
+- `karmada-controller-manager`: metric `recreate_resource_to_cluster` has been merged into `create_resource_to_cluster`. ([#6148](https://github.com/karmada-io/karmada/pull/6148), @zach593)
+- `karmada-operator`: Introduced `karmada_build_info` metrics to emit the build info, as well as a bunch of Go runtime metrics. ([#6044](https://github.com/karmada-io/karmada/pull/6044), @dongjiang1989)
diff --git a/pkg/controllers/multiclusterservice/mcs_controller.go b/pkg/controllers/multiclusterservice/mcs_controller.go
index 438902783..edc8fc5b4 100644
--- a/pkg/controllers/multiclusterservice/mcs_controller.go
+++ b/pkg/controllers/multiclusterservice/mcs_controller.go
@@ -149,7 +149,7 @@ func (c *MCSController) retrieveMultiClusterService(ctx context.Context, mcs *ne
 			continue
 		}
 
-		if providerClusters.Has(clusterName) && c.IsClusterReady(ctx, clusterName) {
+		if providerClusters.Has(clusterName) {
 			continue
 		}
 
@@ -524,17 +524,6 @@ func (c *MCSController) updateMultiClusterServiceStatus(ctx context.Context, mcs
 	})
 }
 
-// IsClusterReady checks whether the cluster is ready.
-func (c *MCSController) IsClusterReady(ctx context.Context, clusterName string) bool {
-	cluster := &clusterv1alpha1.Cluster{}
-	if err := c.Client.Get(ctx, types.NamespacedName{Name: clusterName}, cluster); err != nil {
-		klog.Errorf("Failed to get cluster object(%s):%v", clusterName, err)
-		return false
-	}
-
-	return util.IsClusterReady(&cluster.Status)
-}
-
 // SetupWithManager creates a controller and register to controller manager.
 func (c *MCSController) SetupWithManager(mgr controllerruntime.Manager) error {
 	mcsPredicateFunc := predicate.Funcs{
diff --git a/pkg/karmadactl/deinit/deinit.go b/pkg/karmadactl/deinit/deinit.go
index e66520de9..9587a5237 100644
--- a/pkg/karmadactl/deinit/deinit.go
+++ b/pkg/karmadactl/deinit/deinit.go
@@ -57,7 +57,7 @@ type CommandDeInitOption struct {
 	Force          bool
 	PurgeNamespace bool
 
-	KubeClientSet *kubernetes.Clientset
+	KubeClientSet kubernetes.Interface
 }
 
 // NewCmdDeInit removes Karmada from Kubernetes
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..f524f9305 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,45 @@ 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 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 +247,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/controllers/multiclusterservice/mcs_controller_test.go b/pkg/controllers/multiclusterservice/mcs_controller_test.go
index 0008277a0..5a80f263f 100644
--- a/pkg/controllers/multiclusterservice/mcs_controller_test.go
+++ b/pkg/controllers/multiclusterservice/mcs_controller_test.go
@@ -727,72 +727,6 @@ func TestClaimMultiClusterServiceForService(t *testing.T) {
 	}
 }
 
-func TestIsClusterReady(t *testing.T) {
-	tests := []struct {
-		name          string
-		clusterName   string
-		clusterObj    *clusterv1alpha1.Cluster
-		expectedReady bool
-	}{
-		{
-			name:        "cluster is ready",
-			clusterName: "ready-cluster",
-			clusterObj: &clusterv1alpha1.Cluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "ready-cluster",
-				},
-				Status: clusterv1alpha1.ClusterStatus{
-					Conditions: []metav1.Condition{
-						{
-							Type:   clusterv1alpha1.ClusterConditionReady,
-							Status: metav1.ConditionTrue,
-						},
-					},
-				},
-			},
-			expectedReady: true,
-		},
-		{
-			name:        "cluster is not ready",
-			clusterName: "not-ready-cluster",
-			clusterObj: &clusterv1alpha1.Cluster{
-				ObjectMeta: metav1.ObjectMeta{
-					Name: "not-ready-cluster",
-				},
-				Status: clusterv1alpha1.ClusterStatus{
-					Conditions: []metav1.Condition{
-						{
-							Type:   clusterv1alpha1.ClusterConditionReady,
-							Status: metav1.ConditionFalse,
-						},
-					},
-				},
-			},
-			expectedReady: false,
-		},
-		{
-			name:          "cluster does not exist",
-			clusterName:   "non-existent-cluster",
-			clusterObj:    nil,
-			expectedReady: false,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			var objects []runtime.Object
-			if tt.clusterObj != nil {
-				objects = append(objects, tt.clusterObj)
-			}
-
-			controller := newFakeController(objects...)
-
-			ready := controller.IsClusterReady(context.Background(), tt.clusterName)
-			assert.Equal(t, tt.expectedReady, ready, "IsClusterReady() result does not match expected")
-		})
-	}
-}
-
 func TestServiceHasCrossClusterMultiClusterService(t *testing.T) {
 	tests := []struct {
 		name     string
diff --git a/pkg/karmadactl/deinit/deinit_test.go b/pkg/karmadactl/deinit/deinit_test.go
new file mode 100644
index 000000000..cfe57c94b
--- /dev/null
+++ b/pkg/karmadactl/deinit/deinit_test.go
@@ -0,0 +1,212 @@
+/*
+Copyright 2024 The Karmada Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package deinit
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	appsv1 "k8s.io/api/apps/v1"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	clientset "k8s.io/client-go/kubernetes"
+	fakeclientset "k8s.io/client-go/kubernetes/fake"
+
+	"github.com/karmada-io/karmada/pkg/util/names"
+)
+
+type resource struct {
+	name         string
+	resourceType string
+}
+
+func TestDeInitKarmada(t *testing.T) {
+	labels := map[string]string{karmadaBootstrappingLabelKey: "app-defaults"}
+	tests := []struct {
+		name       string
+		deInitOpts *CommandDeInitOption
+		resources  []resource
+		prep       func(client clientset.Interface, namespace string, resources []resource) error
+		verify     func(client clientset.Interface, namespace string, resources []resource) error
+		wantErr    bool
+	}{
+		{
+			name: "DeInitKarmada_DeInitKarmadaAfterItIsInitialized_KarmadaDeinit",
+			deInitOpts: &CommandDeInitOption{
+				KubeClientSet:  fakeclientset.NewClientset(),
+				Namespace:      names.NamespaceKarmadaSystem,
+				PurgeNamespace: true,
+				Force:          true,
+			},
+			resources: []resource{
+				{"test-deployment-1", "deployment"},
+				{"test-deployment-2", "deployment"},
+				{"test-statefulset-1", "statefulset"},
+				{"test-statefulset-2", "statefulset"},
+				{"test-service-1", "service"},
+				{"test-service-2", "service"},
+				{"test-secret-1", "secret"},
+				{"test-secret-2", "secret"},
+				{"test-node-1", "node"},
+				{"test-node-2", "node"},
+			},
+			prep: func(client clientset.Interface, namespace string, resources []resource) error {
+				ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
+				if ns, err := client.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}); err != nil {
+					return fmt.Errorf("failed to create karmada namespace %s, got error: %v", ns.GetName(), err)
+				}
+				for _, resource := range resources {
+					if err := createResource(client, namespace, resource.name, labels, resource.resourceType); err != nil {
+						return fmt.Errorf("failed to create %s %s in namespace %s, got error: %v", resource.resourceType, resource.name, namespace, err)
+					}
+				}
+				return nil
+			},
+			verify: func(client clientset.Interface, namespace string, resources []resource) error {
+				for _, resource := range resources {
+					if err := verifyResource(client, namespace, resource.name, resource.resourceType); err != nil {
+						return err
+					}
+				}
+				if ns, err := client.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}); err == nil {
+					return fmt.Errorf("expected namespace %s to be deleted, but it still exists", ns.GetName())
+				}
+				return nil
+			},
+			wantErr: false,
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if err := test.prep(test.deInitOpts.KubeClientSet, test.deInitOpts.Namespace, test.resources); err != nil {
+				t.Fatalf("failed to prep test environment, got: %v", err)
+			}
+			err := test.deInitOpts.Run()
+			if err == nil && test.wantErr {
+				t.Fatal("expected an error, but got none")
+			}
+			if err != nil && !test.wantErr {
+				t.Errorf("unexpected error, got: %v", err)
+			}
+			if err := test.verify(test.deInitOpts.KubeClientSet, test.deInitOpts.Namespace, test.resources); err != nil {
+				t.Errorf("failed to verify the deinit of Karmada, got error: %v", err)
+			}
+		})
+	}
+}
+
+// createResource creates a Kubernetes resource of the specified type in the given namespace.
+// Supported resource types include "deployment", "statefulset", "service", "secret", and "node".
+// It sets the provided name and labels for the resource.
+// Returns an error if the resource creation fails or if the resource type is unsupported.
+func createResource(client clientset.Interface, namespace, name string, labels map[string]string, resourceType string) error {
+	switch resourceType {
+	case "deployment":
+		resource := &appsv1.Deployment{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:   name,
+				Labels: labels,
+			},
+		}
+		_, err := client.AppsV1().Deployments(namespace).Create(context.TODO(), resource, metav1.CreateOptions{})
+		return err
+	case "statefulset":
+		resource := &appsv1.StatefulSet{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:   name,
+				Labels: labels,
+			},
+		}
+		_, err := client.AppsV1().StatefulSets(namespace).Create(context.TODO(), resource, metav1.CreateOptions{})
+		return err
+	case "service":
+		resource := &corev1.Service{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:   name,
+				Labels: labels,
+			},
+		}
+		_, err := client.CoreV1().Services(namespace).Create(context.TODO(), resource, metav1.CreateOptions{})
+		return err
+	case "secret":
+		resource := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:   name,
+				Labels: labels,
+			},
+		}
+		_, err := client.CoreV1().Secrets(namespace).Create(context.TODO(), resource, metav1.CreateOptions{})
+		return err
+	case "node":
+		resource := &corev1.Node{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:   name,
+				Labels: map[string]string{karmadaNodeLabel: ""},
+			},
+		}
+		_, err := client.CoreV1().Nodes().Create(context.TODO(), resource, metav1.CreateOptions{})
+		return err
+	default:
+		return fmt.Errorf("unsupported resource type: %s", resourceType)
+	}
+}
+
+// getResource retrieves a Kubernetes resource of the specified type from the given namespace.
+// Supported resource types include "deployment", "statefulset", "service", "secret", and "node".
+// For "node" resources, checks if the expected label is removed and returns an error if not.
+// Returns an error if the resource retrieval fails or if the resource type is unsupported.
+func getResource(client clientset.Interface, namespace, name string, resourceType string) error {
+	switch resourceType {
+	case "deployment":
+		_, err := client.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+		return err
+	case "statefulset":
+		_, err := client.AppsV1().StatefulSets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+		return err
+	case "service":
+		_, err := client.CoreV1().Services(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+		return err
+	case "secret":
+		_, err := client.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+		return err
+	case "node":
+		node, err := client.CoreV1().Nodes().Get(context.TODO(), name, metav1.GetOptions{})
+		if err != nil {
+			return err
+		}
+		if _, ok := node.Labels[karmadaNodeLabel]; ok {
+			return fmt.Errorf("expected node label %s to be removed from node %s", karmadaNodeLabel, name)
+		}
+		return nil
+	default:
+		return fmt.Errorf("unsupported resource type: %s", resourceType)
+	}
+}
+
+// verifyResource ensures the specified Kubernetes resource is deleted, except for "node" resources,
+// where it checks for errors in retrieval. Returns an error if the resource still exists or verification fails.
+func verifyResource(client clientset.Interface, namespace, resourceName, resourceType string) error {
+	err := getResource(client, namespace, resourceName, resourceType)
+	if resourceType != "node" && err == nil {
+		return fmt.Errorf("expected %s %s in namespace %s to be deleted, but it still exists", resourceType, resourceName, namespace)
+	}
+	if resourceType == "node" && err != nil {
+		return err
+	}
+	return nil
+}
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/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