github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/dataprotection/restore/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 restore
    21  
    22  import (
    23  	"fmt"
    24  	"strings"
    25  	"time"
    26  
    27  	batchv1 "k8s.io/api/batch/v1"
    28  	corev1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/api/meta"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	"k8s.io/client-go/tools/record"
    33  	"sigs.k8s.io/controller-runtime/pkg/client"
    34  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    35  
    36  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    37  	"github.com/1aal/kubeblocks/pkg/constant"
    38  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    39  	dperrors "github.com/1aal/kubeblocks/pkg/dataprotection/errors"
    40  	dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types"
    41  	"github.com/1aal/kubeblocks/pkg/dataprotection/utils"
    42  )
    43  
    44  func SetRestoreCondition(restore *dpv1alpha1.Restore, status metav1.ConditionStatus, conditionType, reason, message string) {
    45  	condition := metav1.Condition{
    46  		Type:    conditionType,
    47  		Reason:  reason,
    48  		Message: message,
    49  		Status:  status,
    50  	}
    51  	meta.SetStatusCondition(&restore.Status.Conditions, condition)
    52  }
    53  
    54  // SetRestoreValidationCondition sets restore condition which type is ConditionTypeRestoreValidationPassed.
    55  func SetRestoreValidationCondition(restore *dpv1alpha1.Restore, reason, message string) {
    56  	status := metav1.ConditionFalse
    57  	if reason == ReasonValidateSuccessfully {
    58  		status = metav1.ConditionTrue
    59  	}
    60  	SetRestoreCondition(restore, status, ConditionTypeRestoreValidationPassed, reason, message)
    61  }
    62  
    63  // SetRestoreStageCondition sets restore stage condition.
    64  func SetRestoreStageCondition(restore *dpv1alpha1.Restore, stage dpv1alpha1.RestoreStage, reason, message string) {
    65  	status := metav1.ConditionFalse
    66  	if reason == ReasonSucceed {
    67  		status = metav1.ConditionTrue
    68  	}
    69  	conditionType := ConditionTypeRestorePreparedData
    70  	if stage == dpv1alpha1.PostReady {
    71  		conditionType = ConditionTypeRestorePostReady
    72  	}
    73  	SetRestoreCondition(restore, status, conditionType, reason, message)
    74  }
    75  
    76  func FindRestoreStatusAction(actions []dpv1alpha1.RestoreStatusAction, key string) *dpv1alpha1.RestoreStatusAction {
    77  	for i := range actions {
    78  		if actions[i].ObjectKey == key {
    79  			return &actions[i]
    80  		}
    81  	}
    82  	return nil
    83  }
    84  
    85  func SetRestoreStatusAction(actions *[]dpv1alpha1.RestoreStatusAction,
    86  	statusAction dpv1alpha1.RestoreStatusAction) {
    87  	if actions == nil {
    88  		return
    89  	}
    90  	if statusAction.Message == "" {
    91  		switch statusAction.Status {
    92  		case dpv1alpha1.RestoreActionProcessing:
    93  			statusAction.Message = fmt.Sprintf(`"%s" is processing`, statusAction.ObjectKey)
    94  		case dpv1alpha1.RestoreActionCompleted:
    95  			statusAction.Message = fmt.Sprintf(`successfully processed the "%s"`, statusAction.ObjectKey)
    96  		case dpv1alpha1.RestoreActionFailed:
    97  			statusAction.Message = fmt.Sprintf(`"%s" is failed, you can describe it or logs the ownered pod to get more informations`, statusAction.ObjectKey)
    98  		}
    99  	}
   100  	if statusAction.Status != dpv1alpha1.RestoreActionProcessing {
   101  		statusAction.EndTime = metav1.Now()
   102  	}
   103  	existingAction := FindRestoreStatusAction(*actions, statusAction.ObjectKey)
   104  	if existingAction == nil {
   105  		statusAction.StartTime = metav1.Now()
   106  		*actions = append(*actions, statusAction)
   107  		return
   108  	}
   109  	if existingAction.Status != statusAction.Status {
   110  		existingAction.Status = statusAction.Status
   111  		existingAction.EndTime = statusAction.EndTime
   112  		existingAction.Message = statusAction.Message
   113  	}
   114  }
   115  
   116  func GetRestoreActionsCountForPrepareData(config *dpv1alpha1.PrepareDataConfig) int {
   117  	if config == nil {
   118  		return 0
   119  	}
   120  	count := 1
   121  	if config.RestoreVolumeClaimsTemplate != nil {
   122  		count = int(config.RestoreVolumeClaimsTemplate.Replicas)
   123  	}
   124  	return count
   125  }
   126  
   127  func BuildRestoreLabels(restoreName string) map[string]string {
   128  	return map[string]string{
   129  		constant.AppManagedByLabelKey: constant.AppName,
   130  		DataProtectionLabelRestoreKey: restoreName,
   131  	}
   132  }
   133  
   134  func GetRestoreDuration(status dpv1alpha1.RestoreStatus) *metav1.Duration {
   135  	if status.CompletionTimestamp == nil || status.StartTimestamp == nil {
   136  		return nil
   137  	}
   138  	return &metav1.Duration{Duration: status.CompletionTimestamp.Sub(status.StartTimestamp.Time).Round(time.Second)}
   139  }
   140  
   141  func getTimeFormat(envs []corev1.EnvVar) string {
   142  	for _, env := range envs {
   143  		if env.Name == dptypes.DPTimeFormat {
   144  			return env.Value
   145  		}
   146  	}
   147  	return time.RFC3339
   148  }
   149  
   150  func compareWithBackupStopTime(backupI, backupJ dpv1alpha1.Backup) bool {
   151  	endTimeI := backupI.GetEndTime()
   152  	endTimeJ := backupJ.GetEndTime()
   153  	if endTimeI.IsZero() {
   154  		return false
   155  	}
   156  	if endTimeJ.IsZero() {
   157  		return true
   158  	}
   159  	if endTimeI.Equal(endTimeJ) {
   160  		return backupI.Name < backupJ.Name
   161  	}
   162  	return endTimeI.Before(endTimeJ)
   163  }
   164  
   165  func buildJobKeyForActionStatus(jobName string) string {
   166  	return fmt.Sprintf("%s/%s", constant.JobKind, jobName)
   167  }
   168  
   169  func getMountPathWithSourceVolume(backup *dpv1alpha1.Backup, volumeSource string) string {
   170  	backupMethod := backup.Status.BackupMethod
   171  	if backupMethod != nil && backupMethod.TargetVolumes != nil {
   172  		for _, v := range backupMethod.TargetVolumes.VolumeMounts {
   173  			if v.Name == volumeSource {
   174  				return v.MountPath
   175  			}
   176  		}
   177  	}
   178  	return ""
   179  }
   180  
   181  func restoreJobHasCompleted(statusActions []dpv1alpha1.RestoreStatusAction, jobName string) bool {
   182  	jobKey := buildJobKeyForActionStatus(jobName)
   183  	for i := range statusActions {
   184  		if statusActions[i].ObjectKey == jobKey && statusActions[i].Status == dpv1alpha1.RestoreActionCompleted {
   185  			return true
   186  		}
   187  	}
   188  	return false
   189  }
   190  
   191  func deleteRestoreJob(reqCtx intctrlutil.RequestCtx, cli client.Client, jobKey string, namespace string) error {
   192  	jobName := strings.ReplaceAll(jobKey, fmt.Sprintf("%s/", constant.JobKind), "")
   193  	job := &batchv1.Job{}
   194  	if err := cli.Get(reqCtx.Ctx, types.NamespacedName{Name: jobName, Namespace: namespace}, job); err != nil {
   195  		return client.IgnoreNotFound(err)
   196  	}
   197  	if controllerutil.ContainsFinalizer(job, dptypes.DataProtectionFinalizerName) {
   198  		patch := client.MergeFrom(job.DeepCopy())
   199  		controllerutil.RemoveFinalizer(job, dptypes.DataProtectionFinalizerName)
   200  		if err := cli.Patch(reqCtx.Ctx, job, patch); err != nil {
   201  			return err
   202  		}
   203  	}
   204  	return intctrlutil.BackgroundDeleteObject(cli, reqCtx.Ctx, job)
   205  }
   206  
   207  // ValidateAndInitRestoreMGR validate if the restore CR is valid and init the restore manager.
   208  func ValidateAndInitRestoreMGR(reqCtx intctrlutil.RequestCtx,
   209  	cli client.Client,
   210  	recorder record.EventRecorder,
   211  	restoreMgr *RestoreManager) error {
   212  
   213  	// get backupActionSet based on the specified backup name.
   214  	backupName := restoreMgr.Restore.Spec.Backup.Name
   215  	backupSet, err := restoreMgr.GetBackupActionSetByNamespaced(reqCtx, cli, backupName, restoreMgr.Restore.Spec.Backup.Namespace)
   216  	if err != nil {
   217  		return err
   218  	}
   219  
   220  	// TODO: check if there is permission for cross namespace recovery.
   221  
   222  	// check if the backup is completed exclude continuous backup.
   223  	backupType := utils.GetBackupType(backupSet.ActionSet, &backupSet.UseVolumeSnapshot)
   224  	if backupType != dpv1alpha1.BackupTypeContinuous && backupSet.Backup.Status.Phase != dpv1alpha1.BackupPhaseCompleted {
   225  		err = intctrlutil.NewFatalError(fmt.Sprintf(`phase of backup "%s" is not completed`, backupName))
   226  		return err
   227  	}
   228  
   229  	// build backupActionSets of prepareData and postReady stage based on the specified backup's type.
   230  	switch backupType {
   231  	case dpv1alpha1.BackupTypeFull:
   232  		restoreMgr.SetBackupSets(*backupSet)
   233  	case dpv1alpha1.BackupTypeIncremental:
   234  		err = restoreMgr.BuildIncrementalBackupActionSets(reqCtx, cli, *backupSet)
   235  	case dpv1alpha1.BackupTypeDifferential:
   236  		err = restoreMgr.BuildDifferentialBackupActionSets(reqCtx, cli, *backupSet)
   237  	case dpv1alpha1.BackupTypeContinuous:
   238  		err = intctrlutil.NewErrorf(dperrors.ErrorTypeWaitForExternalHandler, "wait for external handler to do handle the Point-In-Time recovery.")
   239  		recorder.Event(restoreMgr.Restore, corev1.EventTypeWarning, string(dperrors.ErrorTypeWaitForExternalHandler), err.Error())
   240  	default:
   241  		err = intctrlutil.NewFatalError(fmt.Sprintf("backup type of %s is empty", backupName))
   242  	}
   243  	return err
   244  }
   245  
   246  func cutJobName(jobName string) string {
   247  	l := len(jobName)
   248  	if l > 63 {
   249  		return fmt.Sprintf("%s-%s", jobName[:57], jobName[l-5:l])
   250  	}
   251  	return jobName
   252  }