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  }