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 }