github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/transformer_cluster_deletion.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package apps
    21  
    22  import (
    23  	"encoding/json"
    24  	"strings"
    25  	"time"
    26  
    27  	batchv1 "k8s.io/api/batch/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	policyv1 "k8s.io/api/policy/v1"
    30  	rbacv1 "k8s.io/api/rbac/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    34  
    35  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    36  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    37  	workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1"
    38  	"github.com/1aal/kubeblocks/pkg/constant"
    39  	"github.com/1aal/kubeblocks/pkg/controller/graph"
    40  	"github.com/1aal/kubeblocks/pkg/controller/model"
    41  )
    42  
    43  // ClusterDeletionTransformer handles cluster deletion
    44  type ClusterDeletionTransformer struct{}
    45  
    46  var _ graph.Transformer = &ClusterDeletionTransformer{}
    47  
    48  func (t *ClusterDeletionTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
    49  	transCtx, _ := ctx.(*clusterTransformContext)
    50  	cluster := transCtx.OrigCluster
    51  	if !cluster.IsDeleting() {
    52  		return nil
    53  	}
    54  
    55  	graphCli, _ := transCtx.Client.(model.GraphClient)
    56  
    57  	transCtx.Cluster.Status.Phase = appsv1alpha1.DeletingClusterPhase
    58  
    59  	// list all kinds to be deleted based on v1alpha1.TerminationPolicyType
    60  	var toDeleteNamespacedKinds, toDeleteNonNamespacedKinds []client.ObjectList
    61  	var toPreserveKinds []client.ObjectList
    62  	switch cluster.Spec.TerminationPolicy {
    63  	case appsv1alpha1.DoNotTerminate:
    64  		transCtx.EventRecorder.Eventf(cluster, corev1.EventTypeWarning, "DoNotTerminate",
    65  			"spec.terminationPolicy %s is preventing deletion.", cluster.Spec.TerminationPolicy)
    66  		return graph.ErrPrematureStop
    67  	case appsv1alpha1.Halt:
    68  		toDeleteNamespacedKinds, toDeleteNonNamespacedKinds = kindsForHalt()
    69  		toPreserveKinds = []client.ObjectList{
    70  			&corev1.PersistentVolumeClaimList{},
    71  			&corev1.SecretList{},
    72  			&corev1.ConfigMapList{},
    73  		}
    74  	case appsv1alpha1.Delete:
    75  		toDeleteNamespacedKinds, toDeleteNonNamespacedKinds = kindsForDelete()
    76  	case appsv1alpha1.WipeOut:
    77  		toDeleteNamespacedKinds, toDeleteNonNamespacedKinds = kindsForWipeOut()
    78  	}
    79  
    80  	transCtx.EventRecorder.Eventf(cluster, corev1.EventTypeNormal, constant.ReasonDeletingCR, "Deleting %s: %s",
    81  		strings.ToLower(cluster.GetObjectKind().GroupVersionKind().Kind), cluster.GetName())
    82  
    83  	// list all objects owned by this cluster in cache, and delete them all
    84  	// there is chance that objects leak occurs because of cache stale
    85  	// ignore the problem currently
    86  	// TODO: GC the leaked objects
    87  	ml := getAppInstanceML(*cluster)
    88  
    89  	preserveObjects := func() error {
    90  		if len(toPreserveKinds) == 0 {
    91  			return nil
    92  		}
    93  
    94  		objs, err := getClusterOwningNamespacedObjects(transCtx, *cluster, ml, toPreserveKinds)
    95  		if err != nil {
    96  			return err
    97  		}
    98  		// construct cluster spec JSON string
    99  		clusterSpec := cluster.DeepCopy()
   100  		clusterSpec.ObjectMeta = metav1.ObjectMeta{
   101  			Name: cluster.GetName(),
   102  			UID:  cluster.GetUID(),
   103  		}
   104  		clusterSpec.Status = appsv1alpha1.ClusterStatus{}
   105  		b, err := json.Marshal(*clusterSpec)
   106  		if err != nil {
   107  			return err
   108  		}
   109  		clusterJSON := string(b)
   110  		for _, o := range objs {
   111  			origObj := o.DeepCopyObject().(client.Object)
   112  			controllerutil.RemoveFinalizer(o, constant.DBClusterFinalizerName)
   113  			ownerRefs := o.GetOwnerReferences()
   114  			for i, ref := range ownerRefs {
   115  				if ref.Kind != appsv1alpha1.ClusterKind ||
   116  					!strings.Contains(ref.APIVersion, appsv1alpha1.GroupVersion.Group) {
   117  					continue
   118  				}
   119  				ownerRefs = append(ownerRefs[:i], ownerRefs[i+1:]...)
   120  				break
   121  			}
   122  			o.SetOwnerReferences(ownerRefs)
   123  			annot := o.GetAnnotations()
   124  			if annot == nil {
   125  				annot = map[string]string{}
   126  			}
   127  			// annotated last-applied Cluster spec
   128  			annot[constant.LastAppliedClusterAnnotationKey] = clusterJSON
   129  			o.SetAnnotations(annot)
   130  			graphCli.Update(dag, origObj, o)
   131  		}
   132  		return nil
   133  	}
   134  	// handle preserved objects update vertex
   135  	if err := preserveObjects(); err != nil {
   136  		return err
   137  	}
   138  
   139  	toDeleteObjs := func(objs clusterOwningObjects) []client.Object {
   140  		var delObjs []client.Object
   141  		for _, obj := range objs {
   142  			// retain backup for data protection even if the cluster is wiped out.
   143  			if strings.EqualFold(obj.GetLabels()[constant.BackupProtectionLabelKey], constant.BackupRetain) {
   144  				continue
   145  			}
   146  			delObjs = append(delObjs, obj)
   147  		}
   148  		return delObjs
   149  	}
   150  
   151  	// add namespaced objects deletion vertex
   152  	namespacedObjs, err := getClusterOwningNamespacedObjects(transCtx, *cluster, ml, toDeleteNamespacedKinds)
   153  	if err != nil {
   154  		return err
   155  	}
   156  	delObjs := toDeleteObjs(namespacedObjs)
   157  
   158  	// add non-namespaced objects deletion vertex
   159  	nonNamespacedObjs, err := getClusterOwningNonNamespacedObjects(transCtx, *cluster, ml, toDeleteNonNamespacedKinds)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	delObjs = append(delObjs, toDeleteObjs(nonNamespacedObjs)...)
   164  
   165  	for _, o := range delObjs {
   166  		graphCli.Delete(dag, o)
   167  	}
   168  	// set cluster action to noop until all the sub-resources deleted
   169  	if len(delObjs) == 0 {
   170  		graphCli.Delete(dag, cluster)
   171  	} else {
   172  		graphCli.Status(dag, cluster, transCtx.Cluster)
   173  		// requeue since pvc isn't owned by cluster, and deleting it won't trigger event
   174  		return newRequeueError(time.Second*1, "not all sub-resources deleted")
   175  	}
   176  
   177  	// fast return, that is stopping the plan.Build() stage and jump to plan.Execute() directly
   178  	return graph.ErrPrematureStop
   179  }
   180  
   181  func kindsForDoNotTerminate() ([]client.ObjectList, []client.ObjectList) {
   182  	return []client.ObjectList{}, []client.ObjectList{}
   183  }
   184  
   185  func kindsForHalt() ([]client.ObjectList, []client.ObjectList) {
   186  	namespacedKinds, nonNamespacedKinds := kindsForDoNotTerminate()
   187  	namespacedKindsPlus := []client.ObjectList{
   188  		&policyv1.PodDisruptionBudgetList{},
   189  		&corev1.ServiceAccountList{},
   190  		&rbacv1.RoleBindingList{},
   191  	}
   192  	nonNamespacedKindsPlus := []client.ObjectList{
   193  		&rbacv1.ClusterRoleBindingList{},
   194  	}
   195  	namespacedKindsPlus = append(namespacedKindsPlus, &workloads.ReplicatedStateMachineList{})
   196  
   197  	return append(namespacedKinds, namespacedKindsPlus...), append(nonNamespacedKinds, nonNamespacedKindsPlus...)
   198  }
   199  
   200  func kindsForDelete() ([]client.ObjectList, []client.ObjectList) {
   201  	namespacedKinds, nonNamespacedKinds := kindsForHalt()
   202  	namespacedKindsPlus := []client.ObjectList{
   203  		&corev1.SecretList{},
   204  		&corev1.ConfigMapList{},
   205  		&corev1.PersistentVolumeClaimList{},
   206  		&dpv1alpha1.BackupPolicyList{},
   207  		&dpv1alpha1.BackupScheduleList{},
   208  		&batchv1.JobList{},
   209  		&dpv1alpha1.RestoreList{},
   210  	}
   211  	return append(namespacedKinds, namespacedKindsPlus...), nonNamespacedKinds
   212  }
   213  
   214  func kindsForWipeOut() ([]client.ObjectList, []client.ObjectList) {
   215  	namespacedKinds, nonNamespacedKinds := kindsForDelete()
   216  	namespacedKindsPlus := []client.ObjectList{
   217  		&dpv1alpha1.BackupList{},
   218  	}
   219  	return append(namespacedKinds, namespacedKindsPlus...), nonNamespacedKinds
   220  }