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 }