From 1a0575b2b78e9ce2198d6eb932181d17543cf6c1 Mon Sep 17 00:00:00 2001 From: zhzhuang-zju Date: Thu, 31 Oct 2024 15:33:21 +0800 Subject: [PATCH] Enhanced forced deletion on karmadactl unjoin Signed-off-by: zhzhuang-zju --- pkg/karmadactl/unjoin/unjoin.go | 91 ++++++++++++++++++++++++- pkg/karmadactl/unregister/unregister.go | 2 +- pkg/karmadactl/util/cluster.go | 12 ++-- 3 files changed, 95 insertions(+), 10 deletions(-) diff --git a/pkg/karmadactl/unjoin/unjoin.go b/pkg/karmadactl/unjoin/unjoin.go index 92faf95644c4..bd06e0bd8eef 100644 --- a/pkg/karmadactl/unjoin/unjoin.go +++ b/pkg/karmadactl/unjoin/unjoin.go @@ -17,15 +17,18 @@ limitations under the License. package unjoin import ( + "context" "fmt" "time" "github.com/spf13/cobra" "github.com/spf13/pflag" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kubeclient "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/klog/v2" "k8s.io/kubectl/pkg/util/templates" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" karmadaclientset "github.com/karmada-io/karmada/pkg/generated/clientset/versioned" "github.com/karmada-io/karmada/pkg/karmadactl/options" @@ -147,7 +150,7 @@ func (j *CommandUnjoinOption) AddFlags(flags *pflag.FlagSet) { flags.StringVar(&j.ClusterKubeConfig, "cluster-kubeconfig", "", "Path of the cluster's kubeconfig.") flags.BoolVar(&j.forceDeletion, "force", false, - "Delete cluster and secret resources even if resources in the cluster targeted for unjoin are not removed successfully.") + "If true, when the unjoin process fails due to a timeout, force unjoin the member cluster by removing the finalizer from the related work, namespace, and cluster resources. Note that forcing the unjoin of a member cluster may result in residual resources and requires confirmation.") flags.DurationVar(&j.Wait, "wait", 60*time.Second, "wait for the unjoin command execution process(default 60s), if there is no success after this time, timeout will be returned.") flags.BoolVar(&j.DryRun, "dry-run", false, "Run the command in dry-run mode, without making any server requests.") } @@ -181,11 +184,11 @@ func (j *CommandUnjoinOption) Run(f cmdutil.Factory) error { // RunUnJoinCluster unJoin the cluster from karmada. func (j *CommandUnjoinOption) RunUnJoinCluster(controlPlaneRestConfig, clusterConfig *rest.Config) error { controlPlaneKarmadaClient := karmadaclientset.NewForConfigOrDie(controlPlaneRestConfig) + controlPlaneKubeClient := kubeclient.NewForConfigOrDie(controlPlaneRestConfig) // delete the cluster object in host cluster that associates the unjoining cluster - err := cmdutil.DeleteClusterObject(controlPlaneKarmadaClient, j.ClusterName, j.Wait, j.DryRun) + err := j.deleteClusterObject(controlPlaneKubeClient, controlPlaneKarmadaClient) if err != nil { - klog.Errorf("Failed to delete cluster object. cluster name: %s, error: %v", j.ClusterName, err) return err } @@ -221,6 +224,33 @@ func (j *CommandUnjoinOption) RunUnJoinCluster(controlPlaneRestConfig, clusterCo return nil } +func (j *CommandUnjoinOption) deleteClusterObject(controlPlaneKubeClient *kubeclient.Clientset, controlPlaneKarmadaClient *karmadaclientset.Clientset) error { + isTimeout, err := cmdutil.DeleteClusterObject(controlPlaneKarmadaClient, j.ClusterName, j.Wait, j.DryRun) + if j.forceDeletion && err != nil && isTimeout { + klog.Infof("Start forced deletion. cluster name: %s", j.ClusterName) + executionSpaceName := names.GenerateExecutionSpaceName(j.ClusterName) + err = removeWorkFinalizer(executionSpaceName, controlPlaneKarmadaClient) + if err != nil { + klog.Errorf("Force deletion. Failed to remove the finalizer of Work, error: %v", err) + } + + err = removeExecutionSpaceFinalizer(executionSpaceName, controlPlaneKubeClient) + if err != nil { + klog.Errorf("Force deletion. Failed to remove the finalizer of Namespace(%s), error: %v", executionSpaceName, err) + } + + err = removeClusterFinalizer(j.ClusterName, controlPlaneKarmadaClient) + if err != nil { + klog.Errorf("Force deletion. Failed to remove the finalizer of Cluster(%s), error: %v", j.ClusterName, err) + } + + klog.Infof("Forced deletion is complete.") + return nil + } + + return err +} + // deleteRBACResources deletes the cluster role, cluster rolebindings from the unjoining cluster. func deleteRBACResources(clusterKubeClient kubeclient.Interface, unjoiningClusterName string, forceDeletion, dryRun bool) error { if dryRun { @@ -284,3 +314,58 @@ func deleteNamespaceFromUnjoinCluster(clusterKubeClient kubeclient.Interface, na return nil } + +// removeWorkFinalizer removes the finalizer of works from the executionSpace +func removeWorkFinalizer(executionSpaceName string, controlPlaneKarmadaClient *karmadaclientset.Clientset) error { + list, err := controlPlaneKarmadaClient.WorkV1alpha1().Works(executionSpaceName).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return fmt.Errorf("failed to list work in executionSpace %s", executionSpaceName) + } + + for i := range list.Items { + work := &list.Items[i] + if !controllerutil.ContainsFinalizer(work, util.ExecutionControllerFinalizer) { + continue + } + controllerutil.RemoveFinalizer(work, util.ExecutionControllerFinalizer) + _, err = controlPlaneKarmadaClient.WorkV1alpha1().Works(executionSpaceName).Update(context.TODO(), work, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("failed to remove the finalizer of work(%s/%s)", executionSpaceName, work.GetName()) + } + } + return nil +} + +// removeExecutionSpaceFinalizer removes the finalizer of executionSpace +func removeExecutionSpaceFinalizer(executionSpaceName string, controlPlaneKubeClient *kubeclient.Clientset) error { + executionSpace, err := controlPlaneKubeClient.CoreV1().Namespaces().Get(context.TODO(), executionSpaceName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get Namespace(%s)", executionSpaceName) + } + + if !controllerutil.ContainsFinalizer(executionSpace, "kubernetes") { + return nil + } + + controllerutil.RemoveFinalizer(executionSpace, "kubernetes") + _, err = controlPlaneKubeClient.CoreV1().Namespaces().Update(context.TODO(), executionSpace, metav1.UpdateOptions{}) + + return err +} + +// removeClusterFinalizer removes the finalizer of cluster object +func removeClusterFinalizer(clusterName string, controlPlaneKarmadaClient *karmadaclientset.Clientset) error { + cluster, err := controlPlaneKarmadaClient.ClusterV1alpha1().Clusters().Get(context.TODO(), clusterName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get Cluster(%s)", clusterName) + } + + if !controllerutil.ContainsFinalizer(cluster, util.ClusterControllerFinalizer) { + return nil + } + + controllerutil.RemoveFinalizer(cluster, util.ClusterControllerFinalizer) + _, err = controlPlaneKarmadaClient.ClusterV1alpha1().Clusters().Update(context.TODO(), cluster, metav1.UpdateOptions{}) + + return err +} diff --git a/pkg/karmadactl/unregister/unregister.go b/pkg/karmadactl/unregister/unregister.go index f3361539c262..7cfde1be5106 100644 --- a/pkg/karmadactl/unregister/unregister.go +++ b/pkg/karmadactl/unregister/unregister.go @@ -305,7 +305,7 @@ func (j *CommandUnregisterOption) RunUnregisterCluster() error { } // 1. delete the cluster object from the Karmada control plane - if err := cmdutil.DeleteClusterObject(j.ControlPlaneClient, j.ClusterName, j.Wait, j.DryRun); err != nil { + if _, err := cmdutil.DeleteClusterObject(j.ControlPlaneClient, j.ClusterName, j.Wait, j.DryRun); err != nil { klog.Errorf("Failed to delete cluster object. cluster name: %s, error: %v", j.ClusterName, err) return err } diff --git a/pkg/karmadactl/util/cluster.go b/pkg/karmadactl/util/cluster.go index ff578c63211c..dfffdc910421 100644 --- a/pkg/karmadactl/util/cluster.go +++ b/pkg/karmadactl/util/cluster.go @@ -31,18 +31,18 @@ import ( // DeleteClusterObject deletes the cluster object from the Karmada control plane. func DeleteClusterObject(controlPlaneKarmadaClient karmadaclientset.Interface, clusterName string, - timeout time.Duration, dryRun bool) error { + timeout time.Duration, dryRun bool) (isTimeOut bool, errs error) { if dryRun { - return nil + return false, nil } err := controlPlaneKarmadaClient.ClusterV1alpha1().Clusters().Delete(context.TODO(), clusterName, metav1.DeleteOptions{}) if apierrors.IsNotFound(err) { - return fmt.Errorf("no cluster object %s found in karmada control Plane", clusterName) + return false, fmt.Errorf("no cluster object %s found in karmada control Plane", clusterName) } if err != nil { klog.Errorf("Failed to delete cluster object. cluster name: %s, error: %v", clusterName, err) - return err + return false, err } // make sure the given cluster object has been deleted @@ -60,8 +60,8 @@ func DeleteClusterObject(controlPlaneKarmadaClient karmadaclientset.Interface, c }) if err != nil { klog.Errorf("Failed to delete cluster object. cluster name: %s, error: %v", clusterName, err) - return err + return true, err } - return nil + return false, nil }