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{}