github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/dataprotection/action/action_create_vs.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 action 21 22 import ( 23 "context" 24 "fmt" 25 "strings" 26 27 vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" 28 "github.com/pkg/errors" 29 corev1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/types" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 34 35 dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1" 36 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 37 dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types" 38 "github.com/1aal/kubeblocks/pkg/dataprotection/utils" 39 "github.com/1aal/kubeblocks/pkg/dataprotection/utils/boolptr" 40 ) 41 42 // CreateVolumeSnapshotAction is an action that creates the volume snapshot. 43 type CreateVolumeSnapshotAction struct { 44 // Name is the Name of the action. 45 Name string 46 47 // Owner is the owner of the volume snapshot. 48 Owner client.Object 49 50 // ObjectMeta is the metadata of the volume snapshot. 51 ObjectMeta metav1.ObjectMeta 52 53 // PersistentVolumeClaimWrappers is the list of persistent volume claims wrapper to snapshot. 54 PersistentVolumeClaimWrappers []PersistentVolumeClaimWrapper 55 } 56 57 type PersistentVolumeClaimWrapper struct { 58 VolumeName string 59 PersistentVolumeClaim corev1.PersistentVolumeClaim 60 } 61 62 func NewPersistentVolumeClaimWrapper(pvc corev1.PersistentVolumeClaim, volumeName string) PersistentVolumeClaimWrapper { 63 return PersistentVolumeClaimWrapper{PersistentVolumeClaim: pvc, VolumeName: volumeName} 64 } 65 66 var configVolumeSnapshotError = []string{ 67 "Failed to set default snapshot class with error", 68 "Failed to get snapshot class with error", 69 "Failed to create snapshot content with error cannot find CSI PersistentVolumeSource for volume", 70 } 71 72 func (c *CreateVolumeSnapshotAction) GetName() string { 73 return c.Name 74 } 75 76 func (c *CreateVolumeSnapshotAction) Type() dpv1alpha1.ActionType { 77 return dpv1alpha1.ActionTypeNone 78 } 79 80 func (c *CreateVolumeSnapshotAction) Execute(ctx Context) (*dpv1alpha1.ActionStatus, error) { 81 sb := newStatusBuilder(c) 82 handleErr := func(err error) (*dpv1alpha1.ActionStatus, error) { 83 return sb.withErr(err).build(), err 84 } 85 86 if err := c.validate(); err != nil { 87 return handleErr(err) 88 } 89 90 vsCli := intctrlutil.VolumeSnapshotCompatClient{ 91 Client: ctx.Client, 92 Ctx: ctx.Ctx, 93 } 94 95 var ( 96 ok bool 97 err error 98 snap *vsv1.VolumeSnapshot 99 ) 100 for _, w := range c.PersistentVolumeClaimWrappers { 101 key := client.ObjectKey{ 102 Namespace: w.PersistentVolumeClaim.Namespace, 103 Name: utils.GetBackupVolumeSnapshotName(c.ObjectMeta.Name, w.VolumeName), 104 } 105 // create volume snapshot 106 if err = c.createVolumeSnapshotIfNotExist(ctx, vsCli, &w.PersistentVolumeClaim, key); err != nil { 107 return handleErr(err) 108 } 109 110 ok, snap, err = ensureVolumeSnapshotReady(vsCli, key) 111 if err != nil { 112 return handleErr(err) 113 } 114 115 if !ok { 116 return sb.startTimestamp(&snap.CreationTimestamp).build(), nil 117 } 118 } 119 120 // volume snapshot is ready and status is not error 121 // TODO(ldm): now only support one volume to take snapshot, set its time, size to status 122 return sb.phase(dpv1alpha1.ActionPhaseCompleted). 123 totalSize(snap.Status.RestoreSize.String()). 124 timeRange(snap.Status.CreationTime, snap.Status.CreationTime). 125 build(), nil 126 } 127 128 func (c *CreateVolumeSnapshotAction) validate() error { 129 if len(c.PersistentVolumeClaimWrappers) == 0 { 130 return errors.New("persistent volume claims are required") 131 } 132 if len(c.PersistentVolumeClaimWrappers) > 1 { 133 return errors.New("only one persistent volume claim is supported") 134 } 135 return nil 136 } 137 138 // createVolumeSnapshotIfNotExist check volume snapshot exists, if not, create it. 139 func (c *CreateVolumeSnapshotAction) createVolumeSnapshotIfNotExist(ctx Context, 140 vsCli intctrlutil.VolumeSnapshotCompatClient, 141 pvc *corev1.PersistentVolumeClaim, 142 key client.ObjectKey) error { 143 var ( 144 err error 145 vscName string 146 ) 147 148 snap := &vsv1.VolumeSnapshot{} 149 exists, err := vsCli.CheckResourceExists(key, snap) 150 if err != nil { 151 return err 152 } 153 154 // if the volume snapshot already exists, skip creating it. 155 if exists { 156 return nil 157 } 158 159 c.ObjectMeta.Name = key.Name 160 c.ObjectMeta.Namespace = key.Namespace 161 162 // create volume snapshot 163 snap = &vsv1.VolumeSnapshot{ 164 ObjectMeta: c.ObjectMeta, 165 Spec: vsv1.VolumeSnapshotSpec{ 166 Source: vsv1.VolumeSnapshotSource{ 167 PersistentVolumeClaimName: &pvc.Name, 168 }, 169 }, 170 } 171 172 if vscName, err = c.getVolumeSnapshotClassName(ctx.Ctx, ctx.Client, vsCli, pvc.Spec.VolumeName); err != nil { 173 return err 174 } 175 176 if vscName != "" { 177 snap.Spec.VolumeSnapshotClassName = &vscName 178 } 179 180 controllerutil.AddFinalizer(snap, dptypes.DataProtectionFinalizerName) 181 if err = utils.SetControllerReference(c.Owner, snap, ctx.Scheme); err != nil { 182 return err 183 } 184 185 msg := fmt.Sprintf("creating volume snapshot %s/%s", snap.Namespace, snap.Name) 186 ctx.Recorder.Event(c.Owner, corev1.EventTypeNormal, "CreatingVolumeSnapshot", msg) 187 if err = vsCli.Create(snap); err != nil { 188 return err 189 } 190 return nil 191 } 192 193 func (c *CreateVolumeSnapshotAction) getVolumeSnapshotClassName( 194 ctx context.Context, 195 cli client.Client, 196 vsCli intctrlutil.VolumeSnapshotCompatClient, 197 pvName string) (string, error) { 198 pv := &corev1.PersistentVolume{} 199 if err := cli.Get(ctx, types.NamespacedName{Name: pvName}, pv); err != nil { 200 return "", err 201 } 202 if pv.Spec.CSI == nil { 203 return "", nil 204 } 205 vscList := vsv1.VolumeSnapshotClassList{} 206 if err := vsCli.List(&vscList); err != nil { 207 return "", err 208 } 209 for _, item := range vscList.Items { 210 if item.Driver == pv.Spec.CSI.Driver { 211 return item.Name, nil 212 } 213 } 214 return "", nil 215 } 216 217 func ensureVolumeSnapshotReady( 218 vsCli intctrlutil.VolumeSnapshotCompatClient, 219 key client.ObjectKey) (bool, *vsv1.VolumeSnapshot, error) { 220 snap := &vsv1.VolumeSnapshot{} 221 // not found, continue the creation process 222 exists, err := vsCli.CheckResourceExists(key, snap) 223 if err != nil { 224 return false, nil, err 225 } 226 if exists && snap.Status != nil { 227 // check if snapshot status throws an error, e.g. csi does not support volume snapshot 228 if isVolumeSnapshotConfigError(snap) { 229 return false, nil, errors.New(*snap.Status.Error.Message) 230 } 231 if boolptr.IsSetToTrue(snap.Status.ReadyToUse) { 232 return true, snap, nil 233 } 234 } 235 return false, snap, nil 236 } 237 238 func isVolumeSnapshotConfigError(snap *vsv1.VolumeSnapshot) bool { 239 if snap.Status == nil || snap.Status.Error == nil || snap.Status.Error.Message == nil { 240 return false 241 } 242 for _, errMsg := range configVolumeSnapshotError { 243 if strings.Contains(*snap.Status.Error.Message, errMsg) { 244 return true 245 } 246 } 247 return false 248 } 249 250 var _ Action = &CreateVolumeSnapshotAction{}