github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/dataprotection/utils.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 dataprotection 21 22 import ( 23 "context" 24 "fmt" 25 "sort" 26 "strings" 27 "sync" 28 29 corev1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/client-go/tools/record" 33 ctrl "sigs.k8s.io/controller-runtime" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 36 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 37 dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1" 38 "github.com/1aal/kubeblocks/pkg/constant" 39 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 40 dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types" 41 ) 42 43 var ( 44 errNoDefaultBackupRepo = fmt.Errorf("no default BackupRepo found") 45 ) 46 47 func getBackupPolicyByName( 48 reqCtx intctrlutil.RequestCtx, 49 cli client.Client, 50 name string) (*dpv1alpha1.BackupPolicy, error) { 51 backupPolicy := &dpv1alpha1.BackupPolicy{} 52 key := client.ObjectKey{ 53 Namespace: reqCtx.Req.Namespace, 54 Name: name, 55 } 56 if err := cli.Get(reqCtx.Ctx, key, backupPolicy); err != nil { 57 return nil, err 58 } 59 return backupPolicy, nil 60 } 61 62 // getActionSetByName gets the ActionSet by name. 63 func getActionSetByName(reqCtx intctrlutil.RequestCtx, 64 cli client.Client, name string) (*dpv1alpha1.ActionSet, error) { 65 if name == "" { 66 return nil, nil 67 } 68 as := &dpv1alpha1.ActionSet{} 69 if err := cli.Get(reqCtx.Ctx, client.ObjectKey{Name: name}, as); err != nil { 70 reqCtx.Log.Error(err, "failed to get ActionSet for backup.", "ActionSet", name) 71 return nil, err 72 } 73 return as, nil 74 } 75 76 func getBackupMethodByName(name string, backupPolicy *dpv1alpha1.BackupPolicy) *dpv1alpha1.BackupMethod { 77 for _, m := range backupPolicy.Spec.BackupMethods { 78 if m.Name == name { 79 return &m 80 } 81 } 82 return nil 83 } 84 85 // getTargetPods gets the target pods by BackupPolicy. If podName is not empty, 86 // it will return the pod which name is podName. Otherwise, it will return the 87 // pods which are selected by BackupPolicy selector and strategy. 88 func getTargetPods(reqCtx intctrlutil.RequestCtx, 89 cli client.Client, podName string, 90 backupPolicy *dpv1alpha1.BackupPolicy) ([]*corev1.Pod, error) { 91 selector := backupPolicy.Spec.Target.PodSelector 92 if selector == nil || selector.LabelSelector == nil { 93 return nil, nil 94 } 95 96 labelSelector, err := metav1.LabelSelectorAsSelector(selector.LabelSelector) 97 if err != nil { 98 return nil, err 99 } 100 pods := &corev1.PodList{} 101 if err = cli.List(reqCtx.Ctx, pods, 102 client.InNamespace(reqCtx.Req.Namespace), 103 client.MatchingLabelsSelector{Selector: labelSelector}); err != nil { 104 return nil, err 105 } 106 107 if len(pods.Items) == 0 { 108 return nil, fmt.Errorf("failed to find target pods by backup policy %s/%s", 109 backupPolicy.Namespace, backupPolicy.Name) 110 } 111 112 var targetPods []*corev1.Pod 113 if podName != "" { 114 for _, pod := range pods.Items { 115 if pod.Name == podName { 116 targetPods = append(targetPods, &pod) 117 break 118 } 119 } 120 if len(targetPods) > 0 { 121 return targetPods, nil 122 } 123 } 124 125 strategy := selector.Strategy 126 sort.Sort(intctrlutil.ByPodName(pods.Items)) 127 // if pod selection strategy is Any, always return first pod 128 switch strategy { 129 case dpv1alpha1.PodSelectionStrategyAny: 130 if len(pods.Items) > 0 { 131 targetPods = append(targetPods, &pods.Items[0]) 132 } 133 case dpv1alpha1.PodSelectionStrategyAll: 134 for i := range pods.Items { 135 targetPods = append(targetPods, &pods.Items[i]) 136 } 137 } 138 139 return targetPods, nil 140 } 141 142 // getCluster gets the cluster and will ignore the error. 143 func getCluster(ctx context.Context, 144 cli client.Client, 145 targetPod *corev1.Pod) *appsv1alpha1.Cluster { 146 clusterName := targetPod.Labels[constant.AppInstanceLabelKey] 147 if len(clusterName) == 0 { 148 return nil 149 } 150 cluster := &appsv1alpha1.Cluster{} 151 if err := cli.Get(ctx, client.ObjectKey{ 152 Namespace: targetPod.Namespace, 153 Name: clusterName, 154 }, cluster); err != nil { 155 // should not affect the backup status 156 return nil 157 } 158 return cluster 159 } 160 161 func getClusterLabelKeys() []string { 162 return []string{constant.AppInstanceLabelKey, constant.KBAppComponentLabelKey} 163 } 164 165 // sendWarningEventForError sends warning event for backup controller error 166 func sendWarningEventForError(recorder record.EventRecorder, backup *dpv1alpha1.Backup, err error) { 167 controllerErr := intctrlutil.UnwrapControllerError(err) 168 if controllerErr != nil { 169 recorder.Eventf(backup, corev1.EventTypeWarning, string(controllerErr.Type), err.Error()) 170 } else { 171 recorder.Eventf(backup, corev1.EventTypeWarning, "FailedCreatedBackup", 172 "Creating backup failed, error: %s", err.Error()) 173 } 174 } 175 176 func getDefaultBackupRepo(ctx context.Context, cli client.Client) (*dpv1alpha1.BackupRepo, error) { 177 backupRepoList := &dpv1alpha1.BackupRepoList{} 178 if err := cli.List(ctx, backupRepoList); err != nil { 179 return nil, err 180 } 181 var defaultRepo *dpv1alpha1.BackupRepo 182 for idx := range backupRepoList.Items { 183 repo := &backupRepoList.Items[idx] 184 // skip non-default repo 185 if !(repo.Annotations[dptypes.DefaultBackupRepoAnnotationKey] == trueVal && 186 repo.Status.Phase == dpv1alpha1.BackupRepoReady) { 187 continue 188 } 189 if defaultRepo != nil { 190 return nil, fmt.Errorf("multiple default BackupRepo found, both %s and %s are default", 191 defaultRepo.Name, repo.Name) 192 } 193 defaultRepo = repo 194 } 195 if defaultRepo == nil { 196 return nil, errNoDefaultBackupRepo 197 } 198 return defaultRepo, nil 199 } 200 201 // ============================================================================ 202 // refObjectMapper 203 // ============================================================================ 204 205 // refObjectMapper is a helper struct that maintains the mapping between referent objects and referenced objects. 206 // A referent object is an object that has a reference to another object in its spec. 207 // A referenced object is an object that is referred by one or more referent objects. 208 // It is mainly used in the controller Watcher() to trigger the reconciliation of the 209 // objects that have references to other objects when those objects change. 210 // For example, if object A has a reference to object B, and object B changes, 211 // the refObjectMapper can generate a request for object A to be reconciled. 212 type refObjectMapper struct { 213 mu sync.Mutex 214 once sync.Once 215 ref map[string]string // key is the referent, value is the referenced object. 216 invert map[string][]string // invert map, key is the referenced object, value is the list of referent. 217 } 218 219 // init initializes the ref and invert maps lazily if they are nil. 220 func (r *refObjectMapper) init() { 221 r.once.Do(func() { 222 r.ref = make(map[string]string) 223 r.invert = make(map[string][]string) 224 }) 225 } 226 227 // setRef sets or updates the mapping between a referent object and a referenced object. 228 func (r *refObjectMapper) setRef(referent client.Object, referencedKey types.NamespacedName) { 229 r.init() 230 r.mu.Lock() 231 defer r.mu.Unlock() 232 left := toFlattenName(client.ObjectKeyFromObject(referent)) 233 right := toFlattenName(referencedKey) 234 if oldRight, ok := r.ref[left]; ok { 235 r.removeInvertLocked(left, oldRight) 236 } 237 r.addInvertLocked(left, right) 238 r.ref[left] = right 239 } 240 241 // removeRef removes the mapping for a given referent object. 242 func (r *refObjectMapper) removeRef(referent client.Object) { 243 r.init() 244 r.mu.Lock() 245 defer r.mu.Unlock() 246 left := toFlattenName(client.ObjectKeyFromObject(referent)) 247 if right, ok := r.ref[left]; ok { 248 r.removeInvertLocked(left, right) 249 delete(r.ref, left) 250 } 251 } 252 253 // mapToRequests returns a list of requests for the referent objects that have a reference to a given referenced object. 254 func (r *refObjectMapper) mapToRequests(referenced client.Object) []ctrl.Request { 255 r.mu.Lock() 256 defer r.mu.Unlock() 257 right := toFlattenName(client.ObjectKeyFromObject(referenced)) 258 l := r.invert[right] 259 var ret []ctrl.Request 260 for _, v := range l { 261 name, namespace := fromFlattenName(v) 262 ret = append(ret, ctrl.Request{NamespacedName: client.ObjectKey{Namespace: namespace, Name: name}}) 263 } 264 return ret 265 } 266 267 // addInvertLocked adds a pair of referent and referenced objects to the invert map. 268 // It assumes the lock is already held by the caller. 269 func (r *refObjectMapper) addInvertLocked(left string, right string) { 270 // no duplicated item in the list 271 l := r.invert[right] 272 r.invert[right] = append(l, left) 273 } 274 275 // removeInvertLocked removes a pair of referent and referenced objects from the invert map. 276 // It assumes the lock is already held by the caller. 277 func (r *refObjectMapper) removeInvertLocked(left string, right string) { 278 l := r.invert[right] 279 for i, v := range l { 280 if v == left { 281 l[i] = l[len(l)-1] 282 r.invert[right] = l[:len(l)-1] 283 return 284 } 285 } 286 } 287 288 func toFlattenName(key types.NamespacedName) string { 289 return key.Namespace + "/" + key.Name 290 } 291 292 func fromFlattenName(flatten string) (name string, namespace string) { 293 parts := strings.SplitN(flatten, "/", 2) 294 if len(parts) == 2 { 295 namespace = parts[0] 296 name = parts[1] 297 } else { 298 name = flatten 299 } 300 return 301 } 302 303 // restore functions 304 305 func getPopulatePVCName(pvcUID types.UID) string { 306 return fmt.Sprintf("%s-%s", populatePodPrefix, pvcUID) 307 }