github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/transformer_halt_recovering.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 "fmt" 25 26 "github.com/authzed/controller-idioms/hash" 27 corev1 "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/api/meta" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 32 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 33 "github.com/1aal/kubeblocks/pkg/constant" 34 "github.com/1aal/kubeblocks/pkg/controller/graph" 35 ) 36 37 type HaltRecoveryTransformer struct{} 38 39 func (t *HaltRecoveryTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error { 40 transCtx, _ := ctx.(*clusterTransformContext) 41 cluster := transCtx.Cluster 42 43 if cluster.Status.ObservedGeneration != 0 { 44 // skip handling for cluster.status.observedGeneration > 0 45 return nil 46 } 47 48 listOptions := []client.ListOption{ 49 client.InNamespace(cluster.Namespace), 50 client.MatchingLabels{ 51 constant.AppInstanceLabelKey: cluster.Name, 52 }, 53 } 54 55 pvcList := &corev1.PersistentVolumeClaimList{} 56 if err := transCtx.Client.List(transCtx.Context, pvcList, listOptions...); err != nil { 57 return newRequeueError(requeueDuration, err.Error()) 58 } 59 60 if len(pvcList.Items) == 0 { 61 return nil 62 } 63 64 emitError := func(newCondition metav1.Condition) error { 65 if newCondition.LastTransitionTime.IsZero() { 66 newCondition.LastTransitionTime = metav1.Now() 67 } 68 newCondition.Status = metav1.ConditionFalse 69 oldCondition := meta.FindStatusCondition(cluster.Status.Conditions, newCondition.Type) 70 if oldCondition == nil { 71 cluster.Status.Conditions = append(cluster.Status.Conditions, newCondition) 72 } else { 73 *oldCondition = newCondition 74 } 75 transCtx.EventRecorder.Event(transCtx.Cluster, corev1.EventTypeWarning, newCondition.Reason, newCondition.Message) 76 return graph.ErrPrematureStop 77 } 78 79 // halt recovering from last applied record stored in pvc's annotation 80 l, ok := pvcList.Items[0].Annotations[constant.LastAppliedClusterAnnotationKey] 81 if !ok || l == "" { 82 return emitError(metav1.Condition{ 83 Type: appsv1alpha1.ConditionTypeHaltRecovery, 84 Reason: "UncleanedResources", 85 Message: fmt.Sprintf("found uncleaned resources, requires manual deletion, check with `kubectl -n %s get pvc,secret,cm -l %s=%s`", 86 cluster.Namespace, constant.AppInstanceLabelKey, cluster.Name), 87 }) 88 } 89 90 lc := &appsv1alpha1.Cluster{} 91 if err := json.Unmarshal([]byte(l), lc); err != nil { 92 return newRequeueError(requeueDuration, err.Error()) 93 } 94 95 // skip if same cluster UID 96 if lc.UID == cluster.UID { 97 return nil 98 } 99 100 // check clusterDefRef equality 101 if cluster.Spec.ClusterDefRef != lc.Spec.ClusterDefRef { 102 return emitError(metav1.Condition{ 103 Type: appsv1alpha1.ConditionTypeHaltRecovery, 104 Reason: "HaltRecoveryFailed", 105 Message: fmt.Sprintf("not equal to last applied cluster.spec.clusterDefRef %s", lc.Spec.ClusterDefRef), 106 }) 107 } 108 109 // check clusterVersionRef equality but allow clusters.apps.kubeblocks.io/allow-inconsistent-cv=true annotation override 110 if cluster.Spec.ClusterVersionRef != lc.Spec.ClusterVersionRef && 111 cluster.Annotations[constant.HaltRecoveryAllowInconsistentCVAnnotKey] != trueVal { 112 return emitError(metav1.Condition{ 113 Type: appsv1alpha1.ConditionTypeHaltRecovery, 114 Reason: "HaltRecoveryFailed", 115 Message: fmt.Sprintf("not equal to last applied cluster.spec.clusterVersionRef %s; add '%s=true' annotation if void this check", 116 lc.Spec.ClusterVersionRef, constant.HaltRecoveryAllowInconsistentCVAnnotKey), 117 }) 118 } 119 120 // check component len equality 121 if l := len(lc.Spec.ComponentSpecs); l != len(cluster.Spec.ComponentSpecs) { 122 return emitError(metav1.Condition{ 123 Type: appsv1alpha1.ConditionTypeHaltRecovery, 124 Reason: "HaltRecoveryFailed", 125 Message: fmt.Sprintf("inconsistent spec.componentSpecs counts to last applied cluster.spec.componentSpecs (len=%d)", l), 126 }) 127 } 128 129 // check every components' equality 130 for _, comp := range cluster.Spec.ComponentSpecs { 131 found := false 132 for _, lastUsedComp := range lc.Spec.ComponentSpecs { 133 // only need to verify [name, componentDefRef, replicas] for equality 134 if comp.Name != lastUsedComp.Name { 135 continue 136 } 137 if comp.ComponentDefRef != lastUsedComp.ComponentDefRef { 138 return emitError(metav1.Condition{ 139 Type: appsv1alpha1.ConditionTypeHaltRecovery, 140 Reason: "HaltRecoveryFailed", 141 Message: fmt.Sprintf("not equal to last applied cluster.spec.componentSpecs[%s].componentDefRef=%s", 142 comp.Name, lastUsedComp.ComponentDefRef), 143 }) 144 } 145 if comp.Replicas != lastUsedComp.Replicas { 146 return emitError(metav1.Condition{ 147 Type: appsv1alpha1.ConditionTypeHaltRecovery, 148 Reason: "HaltRecoveryFailed", 149 Message: fmt.Sprintf("not equal to last applied cluster.spec.componentSpecs[%s].replicas=%d", 150 comp.Name, lastUsedComp.Replicas), 151 }) 152 } 153 154 // following only check resource related spec., will skip check if HaltRecoveryAllowInconsistentResAnnotKey 155 // annotation is specified 156 if cluster.Annotations[constant.HaltRecoveryAllowInconsistentResAnnotKey] == trueVal { 157 found = true 158 break 159 } 160 if !isVolumeClaimTemplatesEqual(comp.VolumeClaimTemplates, lastUsedComp.VolumeClaimTemplates) { 161 objJSON, _ := json.Marshal(&lastUsedComp.VolumeClaimTemplates) 162 return emitError(metav1.Condition{ 163 Type: appsv1alpha1.ConditionTypeHaltRecovery, 164 Reason: "HaltRecoveryFailed", 165 Message: fmt.Sprintf("not equal to last applied cluster.spec.componentSpecs[%s].volumeClaimTemplates=%s; add '%s=true' annotation to void this check", 166 comp.Name, objJSON, constant.HaltRecoveryAllowInconsistentResAnnotKey), 167 }) 168 } 169 170 if lastUsedComp.ClassDefRef != nil { 171 if comp.ClassDefRef == nil || hash.Object(*comp.ClassDefRef) != hash.Object(*lastUsedComp.ClassDefRef) { 172 objJSON, _ := json.Marshal(lastUsedComp.ClassDefRef) 173 return emitError(metav1.Condition{ 174 Type: appsv1alpha1.ConditionTypeHaltRecovery, 175 Reason: "HaltRecoveryFailed", 176 Message: fmt.Sprintf("not equal to last applied cluster.spec.componentSpecs[%s].classDefRef=%s; add '%s=true' annotation to void this check", 177 comp.Name, objJSON, constant.HaltRecoveryAllowInconsistentResAnnotKey), 178 }) 179 } 180 } else if comp.ClassDefRef != nil { 181 return emitError(metav1.Condition{ 182 Type: appsv1alpha1.ConditionTypeHaltRecovery, 183 Reason: "HaltRecoveryFailed", 184 Message: fmt.Sprintf("not equal to last applied cluster.spec.componentSpecs[%s].classDefRef=null; add '%s=true' annotation to void this check", 185 comp.Name, constant.HaltRecoveryAllowInconsistentResAnnotKey), 186 }) 187 } 188 189 if !isResourceRequirementsEqual(comp.Resources, lastUsedComp.Resources) { 190 objJSON, _ := json.Marshal(&lastUsedComp.Resources) 191 return emitError(metav1.Condition{ 192 Type: appsv1alpha1.ConditionTypeHaltRecovery, 193 Reason: "HaltRecoveryFailed", 194 Message: fmt.Sprintf("not equal to last applied cluster.spec.componentSpecs[%s].resources=%s; add '%s=true' annotation to void this check", 195 comp.Name, objJSON, constant.HaltRecoveryAllowInconsistentResAnnotKey), 196 }) 197 } 198 199 found = true 200 break 201 } 202 if !found { 203 return emitError(metav1.Condition{ 204 Type: appsv1alpha1.ConditionTypeHaltRecovery, 205 Reason: "HaltRecoveryFailed", 206 Message: fmt.Sprintf("cluster.spec.componentSpecs[%s] not found in last applied cluster", 207 comp.Name), 208 }) 209 } 210 } 211 return nil 212 } 213 214 var _ graph.Transformer = &HaltRecoveryTransformer{}