github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/backup.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 operations
    21  
    22  import (
    23  	"fmt"
    24  	"strings"
    25  	"time"
    26  
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  
    30  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    31  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    32  	"github.com/1aal/kubeblocks/pkg/constant"
    33  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    34  	dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types"
    35  	"github.com/1aal/kubeblocks/pkg/dataprotection/utils"
    36  )
    37  
    38  const backupTimeLayout = "20060102150405"
    39  
    40  type BackupOpsHandler struct{}
    41  
    42  var _ OpsHandler = BackupOpsHandler{}
    43  
    44  func init() {
    45  	// ToClusterPhase is not defined, because 'backup' does not affect the cluster phase.
    46  	backupBehaviour := OpsBehaviour{
    47  		FromClusterPhases:                  appsv1alpha1.GetClusterUpRunningPhases(),
    48  		OpsHandler:                         BackupOpsHandler{},
    49  		ProcessingReasonInClusterCondition: ProcessingReasonBackup,
    50  	}
    51  
    52  	opsMgr := GetOpsManager()
    53  	opsMgr.RegisterOps(appsv1alpha1.BackupType, backupBehaviour)
    54  }
    55  
    56  // ActionStartedCondition the started condition when handling the backup request.
    57  func (b BackupOpsHandler) ActionStartedCondition(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (*metav1.Condition, error) {
    58  	return appsv1alpha1.NewBackupCondition(opsRes.OpsRequest), nil
    59  }
    60  
    61  // Action implements the backup action.
    62  // It will create a backup resource for cluster.
    63  func (b BackupOpsHandler) Action(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error {
    64  	opsRequest := opsRes.OpsRequest
    65  	cluster := opsRes.Cluster
    66  
    67  	// create backup
    68  	if backup, err := buildBackup(reqCtx, cli, opsRequest, cluster); err != nil {
    69  		return err
    70  	} else {
    71  		return cli.Create(reqCtx.Ctx, backup)
    72  	}
    73  }
    74  
    75  // ReconcileAction implements the backup reconcile action.
    76  // It will check the backup status and update the OpsRequest status.
    77  // If the backup is completed, it will return OpsSuccess
    78  // If the backup is failed, it will return OpsFailed
    79  func (b BackupOpsHandler) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (appsv1alpha1.OpsPhase, time.Duration, error) {
    80  	opsRequest := opsRes.OpsRequest
    81  	cluster := opsRes.Cluster
    82  
    83  	// get backup
    84  	backups := &dpv1alpha1.BackupList{}
    85  	if err := cli.List(reqCtx.Ctx, backups, client.InNamespace(cluster.Namespace), client.MatchingLabels(getBackupLabels(cluster.Name, opsRequest.Name))); err != nil {
    86  		return appsv1alpha1.OpsFailedPhase, 0, err
    87  	}
    88  
    89  	if len(backups.Items) == 0 {
    90  		return appsv1alpha1.OpsFailedPhase, 0, fmt.Errorf("backup not found")
    91  	}
    92  	// check backup status
    93  	phase := backups.Items[0].Status.Phase
    94  	if phase == dpv1alpha1.BackupPhaseCompleted {
    95  		return appsv1alpha1.OpsSucceedPhase, 0, nil
    96  	} else if phase == dpv1alpha1.BackupPhaseFailed {
    97  		return appsv1alpha1.OpsFailedPhase, 0, fmt.Errorf("backup failed")
    98  	}
    99  	return appsv1alpha1.OpsRunningPhase, 0, nil
   100  }
   101  
   102  // SaveLastConfiguration records last configuration to the OpsRequest.status.lastConfiguration
   103  func (b BackupOpsHandler) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error {
   104  	return nil
   105  }
   106  
   107  func buildBackup(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRequest *appsv1alpha1.OpsRequest, cluster *appsv1alpha1.Cluster) (*dpv1alpha1.Backup, error) {
   108  	var err error
   109  
   110  	backupSpec := opsRequest.Spec.BackupSpec
   111  	if backupSpec == nil {
   112  		backupSpec = &appsv1alpha1.BackupSpec{}
   113  	}
   114  
   115  	if len(backupSpec.BackupName) == 0 {
   116  		backupSpec.BackupName = strings.Join([]string{"backup", cluster.Namespace, cluster.Name, time.Now().Format(backupTimeLayout)}, "-")
   117  	}
   118  
   119  	backupSpec.BackupPolicyName, err = getDefaultBackupPolicy(reqCtx, cli, cluster, backupSpec.BackupPolicyName)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	backupPolicyList := &dpv1alpha1.BackupPolicyList{}
   125  	if err := cli.List(reqCtx.Ctx, backupPolicyList, client.InNamespace(cluster.Namespace),
   126  		client.MatchingLabels(map[string]string{
   127  			constant.AppInstanceLabelKey: cluster.Name,
   128  		})); err != nil {
   129  		return nil, err
   130  	}
   131  	defaultBackupMethod, backupMethodMap, err := utils.GetBackupMethodsFromBackupPolicy(backupPolicyList, backupSpec.BackupPolicyName)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	if backupSpec.BackupMethod == "" {
   136  		backupSpec.BackupMethod = defaultBackupMethod
   137  	}
   138  	if _, ok := backupMethodMap[backupSpec.BackupMethod]; !ok {
   139  		return nil, fmt.Errorf("backup method %s is not supported, please check cluster's backup policy", backupSpec.BackupMethod)
   140  	}
   141  
   142  	backup := &dpv1alpha1.Backup{
   143  		ObjectMeta: metav1.ObjectMeta{
   144  			Name:      backupSpec.BackupName,
   145  			Namespace: cluster.Namespace,
   146  			Labels:    getBackupLabels(cluster.Name, opsRequest.Name),
   147  		},
   148  		Spec: dpv1alpha1.BackupSpec{
   149  			BackupPolicyName: backupSpec.BackupPolicyName,
   150  			BackupMethod:     backupSpec.BackupMethod,
   151  		},
   152  	}
   153  
   154  	if backupSpec.DeletionPolicy != "" {
   155  		backup.Spec.DeletionPolicy = dpv1alpha1.BackupDeletionPolicy(backupSpec.DeletionPolicy)
   156  	}
   157  	if backupSpec.RetentionPeriod != "" {
   158  		retentionPeriod := dpv1alpha1.RetentionPeriod(backupSpec.RetentionPeriod)
   159  		if _, err := retentionPeriod.ToDuration(); err != nil {
   160  			return nil, err
   161  		}
   162  		backup.Spec.RetentionPeriod = retentionPeriod
   163  	}
   164  	if backupSpec.ParentBackupName != "" {
   165  		parentBackup := dpv1alpha1.Backup{}
   166  		if err := cli.Get(reqCtx.Ctx, client.ObjectKey{Name: backupSpec.ParentBackupName, Namespace: cluster.Namespace}, &parentBackup); err != nil {
   167  			return nil, err
   168  		}
   169  		// check parent backup exists and completed
   170  		if parentBackup.Status.Phase != dpv1alpha1.BackupPhaseCompleted {
   171  			return nil, fmt.Errorf("parent backup %s is not completed", backupSpec.ParentBackupName)
   172  		}
   173  		// check parent backup belongs to the cluster of the backup
   174  		if parentBackup.Labels[constant.AppInstanceLabelKey] != cluster.Name {
   175  			return nil, fmt.Errorf("parent backup %s is not belong to cluster %s", backupSpec.ParentBackupName, cluster.Name)
   176  		}
   177  		backup.Spec.ParentBackupName = backupSpec.ParentBackupName
   178  	}
   179  
   180  	return backup, nil
   181  }
   182  
   183  func getDefaultBackupPolicy(reqCtx intctrlutil.RequestCtx, cli client.Client, cluster *appsv1alpha1.Cluster, backupPolicy string) (string, error) {
   184  	// if backupPolicy is not empty, return it directly
   185  	if backupPolicy != "" {
   186  		return backupPolicy, nil
   187  	}
   188  
   189  	backupPolicyList := &dpv1alpha1.BackupPolicyList{}
   190  	if err := cli.List(reqCtx.Ctx, backupPolicyList, client.InNamespace(cluster.Namespace),
   191  		client.MatchingLabels(map[string]string{
   192  			constant.AppInstanceLabelKey: cluster.Name,
   193  		})); err != nil {
   194  		return "", err
   195  	}
   196  	defaultBackupPolices := &dpv1alpha1.BackupPolicyList{}
   197  	for _, backupPolicy := range backupPolicyList.Items {
   198  		if backupPolicy.GetAnnotations()[dptypes.DefaultBackupPolicyAnnotationKey] == "true" {
   199  			defaultBackupPolices.Items = append(defaultBackupPolices.Items, backupPolicy)
   200  		}
   201  	}
   202  
   203  	if len(defaultBackupPolices.Items) == 0 {
   204  		return "", fmt.Errorf(`not found any default backup policy for cluster "%s"`, cluster.Name)
   205  	}
   206  	if len(defaultBackupPolices.Items) > 1 {
   207  		return "", fmt.Errorf(`cluster "%s" has multiple default backup policies`, cluster.Name)
   208  	}
   209  
   210  	return defaultBackupPolices.Items[0].GetName(), nil
   211  }
   212  
   213  func getBackupLabels(cluster, request string) map[string]string {
   214  	return map[string]string{
   215  		constant.AppInstanceLabelKey:      cluster,
   216  		constant.BackupProtectionLabelKey: constant.BackupRetain,
   217  		constant.OpsRequestNameLabelKey:   request,
   218  		constant.OpsRequestTypeLabelKey:   string(appsv1alpha1.BackupType),
   219  	}
   220  }