mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-06 05:25:40 +00:00
245 lines
9.0 KiB
Go
245 lines
9.0 KiB
Go
/*
|
|
Copyright The Velero Contributors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package csi
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
jsonpatch "github.com/evanphx/json-patch/v5"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
|
|
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
|
|
"github.com/vmware-tanzu/velero/pkg/util/stringptr"
|
|
"github.com/vmware-tanzu/velero/pkg/util/stringslice"
|
|
|
|
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1"
|
|
snapshotter "github.com/kubernetes-csi/external-snapshotter/client/v7/clientset/versioned/typed/volumesnapshot/v1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
)
|
|
|
|
const (
|
|
waitInternal = 2 * time.Second
|
|
volumeSnapshotContentProtectFinalizer = "velero.io/volume-snapshot-content-protect-finalizer"
|
|
)
|
|
|
|
// WaitVolumeSnapshotReady waits a VS to become ready to use until the timeout reaches
|
|
func WaitVolumeSnapshotReady(ctx context.Context, snapshotClient snapshotter.SnapshotV1Interface,
|
|
volumeSnapshot string, volumeSnapshotNS string, timeout time.Duration, log logrus.FieldLogger) (*snapshotv1api.VolumeSnapshot, error) {
|
|
var updated *snapshotv1api.VolumeSnapshot
|
|
errMessage := sets.NewString()
|
|
|
|
err := wait.PollUntilContextTimeout(ctx, waitInternal, timeout, true, func(ctx context.Context) (bool, error) {
|
|
tmpVS, err := snapshotClient.VolumeSnapshots(volumeSnapshotNS).Get(ctx, volumeSnapshot, metav1.GetOptions{})
|
|
if err != nil {
|
|
return false, errors.Wrapf(err, fmt.Sprintf("error to get volumesnapshot %s/%s", volumeSnapshotNS, volumeSnapshot))
|
|
}
|
|
|
|
if tmpVS.Status == nil {
|
|
return false, nil
|
|
}
|
|
|
|
if tmpVS.Status.Error != nil {
|
|
errMessage.Insert(stringptr.GetString(tmpVS.Status.Error.Message))
|
|
}
|
|
|
|
if !boolptr.IsSetToTrue(tmpVS.Status.ReadyToUse) {
|
|
return false, nil
|
|
}
|
|
|
|
updated = tmpVS
|
|
return true, nil
|
|
})
|
|
|
|
if wait.Interrupted(err) {
|
|
err = errors.Errorf("volume snapshot is not ready until timeout, errors: %v", errMessage.List())
|
|
}
|
|
|
|
if errMessage.Len() > 0 {
|
|
log.Warnf("Some errors happened during waiting for ready snapshot, errors: %v", errMessage.List())
|
|
}
|
|
|
|
return updated, err
|
|
}
|
|
|
|
// GetVolumeSnapshotContentForVolumeSnapshot returns the volumesnapshotcontent object associated with the volumesnapshot
|
|
func GetVolumeSnapshotContentForVolumeSnapshot(volSnap *snapshotv1api.VolumeSnapshot, snapshotClient snapshotter.SnapshotV1Interface) (*snapshotv1api.VolumeSnapshotContent, error) {
|
|
if volSnap.Status == nil || volSnap.Status.BoundVolumeSnapshotContentName == nil {
|
|
return nil, errors.Errorf("invalid snapshot info in volume snapshot %s", volSnap.Name)
|
|
}
|
|
|
|
vsc, err := snapshotClient.VolumeSnapshotContents().Get(context.TODO(), *volSnap.Status.BoundVolumeSnapshotContentName, metav1.GetOptions{})
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error getting volume snapshot content from API")
|
|
}
|
|
|
|
return vsc, nil
|
|
}
|
|
|
|
// RetainVSC updates the VSC's deletion policy to Retain and add a finalier and then return the update VSC
|
|
func RetainVSC(ctx context.Context, snapshotClient snapshotter.SnapshotV1Interface,
|
|
vsc *snapshotv1api.VolumeSnapshotContent) (*snapshotv1api.VolumeSnapshotContent, error) {
|
|
if vsc.Spec.DeletionPolicy == snapshotv1api.VolumeSnapshotContentRetain {
|
|
return vsc, nil
|
|
}
|
|
|
|
return patchVSC(ctx, snapshotClient, vsc, func(updated *snapshotv1api.VolumeSnapshotContent) {
|
|
updated.Spec.DeletionPolicy = snapshotv1api.VolumeSnapshotContentRetain
|
|
updated.Finalizers = append(updated.Finalizers, volumeSnapshotContentProtectFinalizer)
|
|
})
|
|
}
|
|
|
|
// DeleteVolumeSnapshotContentIfAny deletes a VSC by name if it exists, and log an error when the deletion fails
|
|
func DeleteVolumeSnapshotContentIfAny(ctx context.Context, snapshotClient snapshotter.SnapshotV1Interface, vscName string, log logrus.FieldLogger) {
|
|
err := snapshotClient.VolumeSnapshotContents().Delete(ctx, vscName, metav1.DeleteOptions{})
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
log.WithError(err).Debugf("Abort deleting VSC, it doesn't exist %s", vscName)
|
|
} else {
|
|
log.WithError(err).Errorf("Failed to delete volume snapshot content %s", vscName)
|
|
}
|
|
}
|
|
}
|
|
|
|
// EnsureDeleteVS asserts the existence of a VS by name, deletes it and waits for its disappearance and returns errors on any failure
|
|
func EnsureDeleteVS(ctx context.Context, snapshotClient snapshotter.SnapshotV1Interface,
|
|
vsName string, vsNamespace string, timeout time.Duration) error {
|
|
err := snapshotClient.VolumeSnapshots(vsNamespace).Delete(ctx, vsName, metav1.DeleteOptions{})
|
|
if err != nil {
|
|
return errors.Wrap(err, "error to delete volume snapshot")
|
|
}
|
|
|
|
err = wait.PollUntilContextTimeout(ctx, waitInternal, timeout, true, func(ctx context.Context) (bool, error) {
|
|
_, err := snapshotClient.VolumeSnapshots(vsNamespace).Get(ctx, vsName, metav1.GetOptions{})
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, errors.Wrapf(err, fmt.Sprintf("error to get VolumeSnapshot %s", vsName))
|
|
}
|
|
|
|
return false, nil
|
|
})
|
|
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error to assure VolumeSnapshot is deleted, %s", vsName)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func RemoveVSCProtect(ctx context.Context, snapshotClient snapshotter.SnapshotV1Interface, vscName string, timeout time.Duration) error {
|
|
err := wait.PollUntilContextTimeout(ctx, waitInternal, timeout, true, func(ctx context.Context) (bool, error) {
|
|
vsc, err := snapshotClient.VolumeSnapshotContents().Get(ctx, vscName, metav1.GetOptions{})
|
|
if err != nil {
|
|
return false, errors.Wrapf(err, "error to get VolumeSnapshotContent %s", vscName)
|
|
}
|
|
|
|
vsc.Finalizers = stringslice.Except(vsc.Finalizers, volumeSnapshotContentProtectFinalizer)
|
|
|
|
_, err = snapshotClient.VolumeSnapshotContents().Update(ctx, vsc, metav1.UpdateOptions{})
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
|
|
if !apierrors.IsConflict(err) {
|
|
return false, errors.Wrapf(err, "error to update VolumeSnapshotContent %s", vscName)
|
|
}
|
|
|
|
return false, nil
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
// EnsureDeleteVSC asserts the existence of a VSC by name, deletes it and waits for its disappearance and returns errors on any failure
|
|
func EnsureDeleteVSC(ctx context.Context, snapshotClient snapshotter.SnapshotV1Interface,
|
|
vscName string, timeout time.Duration) error {
|
|
err := snapshotClient.VolumeSnapshotContents().Delete(ctx, vscName, metav1.DeleteOptions{})
|
|
if err != nil && !apierrors.IsNotFound(err) {
|
|
return errors.Wrap(err, "error to delete volume snapshot content")
|
|
}
|
|
err = wait.PollUntilContextTimeout(ctx, waitInternal, timeout, true, func(ctx context.Context) (bool, error) {
|
|
_, err := snapshotClient.VolumeSnapshotContents().Get(ctx, vscName, metav1.GetOptions{})
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
return true, nil
|
|
}
|
|
|
|
return false, errors.Wrapf(err, fmt.Sprintf("error to get VolumeSnapshotContent %s", vscName))
|
|
}
|
|
|
|
return false, nil
|
|
})
|
|
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error to assure VolumeSnapshotContent is deleted, %s", vscName)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteVolumeSnapshotIfAny deletes a VS by name if it exists, and log an error when the deletion fails
|
|
func DeleteVolumeSnapshotIfAny(ctx context.Context, snapshotClient snapshotter.SnapshotV1Interface, vsName string, vsNamespace string, log logrus.FieldLogger) {
|
|
err := snapshotClient.VolumeSnapshots(vsNamespace).Delete(ctx, vsName, metav1.DeleteOptions{})
|
|
if err != nil {
|
|
if apierrors.IsNotFound(err) {
|
|
log.WithError(err).Debugf("Abort deleting volume snapshot, it doesn't exist %s/%s", vsNamespace, vsName)
|
|
} else {
|
|
log.WithError(err).Errorf("Failed to delete volume snapshot %s/%s", vsNamespace, vsName)
|
|
}
|
|
}
|
|
}
|
|
|
|
func patchVSC(ctx context.Context, snapshotClient snapshotter.SnapshotV1Interface,
|
|
vsc *snapshotv1api.VolumeSnapshotContent, updateFunc func(*snapshotv1api.VolumeSnapshotContent)) (*snapshotv1api.VolumeSnapshotContent, error) {
|
|
origBytes, err := json.Marshal(vsc)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error marshaling original VSC")
|
|
}
|
|
|
|
updated := vsc.DeepCopy()
|
|
updateFunc(updated)
|
|
|
|
updatedBytes, err := json.Marshal(updated)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error marshaling updated VSC")
|
|
}
|
|
|
|
patchBytes, err := jsonpatch.CreateMergePatch(origBytes, updatedBytes)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error creating json merge patch for VSC")
|
|
}
|
|
|
|
patched, err := snapshotClient.VolumeSnapshotContents().Patch(ctx, vsc.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{})
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error patching VSC")
|
|
}
|
|
|
|
return patched, nil
|
|
}
|