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 }