Delete VSC after backup completes.

Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>
pull/8684/head
Xun Jiang 2025-02-14 11:38:32 +08:00
parent 620a116e7f
commit eb77151f48
4 changed files with 49 additions and 148 deletions

View File

@ -0,0 +1 @@
Clean artifacts generated during CSI B/R.

View File

@ -137,7 +137,7 @@ func (p *volumeSnapshotBackupItemAction) Execute(
vsc = &snapshotv1api.VolumeSnapshotContent{}
}
csi.DeleteVolumeSnapshot(*vs, *vsc, backup, p.crClient, p.log)
csi.DeleteVolumeSnapshot(*vs, *vsc, p.crClient, p.log)
return item, nil, "", nil, nil
}

View File

@ -478,6 +478,7 @@ func IsVolumeSnapshotExists(
func SetVolumeSnapshotContentDeletionPolicy(
vscName string,
crClient crclient.Client,
policy snapshotv1api.DeletionPolicy,
) error {
vsc := new(snapshotv1api.VolumeSnapshotContent)
if err := crClient.Get(context.TODO(), crclient.ObjectKey{Name: vscName}, vsc); err != nil {
@ -485,7 +486,7 @@ func SetVolumeSnapshotContentDeletionPolicy(
}
originVSC := vsc.DeepCopy()
vsc.Spec.DeletionPolicy = snapshotv1api.VolumeSnapshotContentDelete
vsc.Spec.DeletionPolicy = policy
return crClient.Patch(context.TODO(), vsc, crclient.MergeFrom(originVSC))
}
@ -515,6 +516,7 @@ func CleanupVolumeSnapshot(
err := SetVolumeSnapshotContentDeletionPolicy(
*vs.Status.BoundVolumeSnapshotContentName,
crClient,
snapshotv1api.VolumeSnapshotContentDelete,
)
if err != nil {
log.Debugf("Failed to patch DeletionPolicy of volume snapshot %s/%s",
@ -530,144 +532,47 @@ func CleanupVolumeSnapshot(
}
}
// DeleteVolumeSnapshot handles the VolumeSnapshot instance deletion. It will make sure the VolumeSnapshotContent is
// recreated so that the physical snapshot is retained.
// DeleteVolumeSnapshot handles the VolumeSnapshot and VolumeSnapshotContent instance deletion.
func DeleteVolumeSnapshot(
vs snapshotv1api.VolumeSnapshot,
vsc snapshotv1api.VolumeSnapshotContent,
backup *velerov1api.Backup,
client crclient.Client,
logger logrus.FieldLogger,
) {
modifyVSCFlag := false
if vs.Status != nil &&
vs.Status.BoundVolumeSnapshotContentName != nil &&
len(*vs.Status.BoundVolumeSnapshotContentName) > 0 &&
vsc.Spec.DeletionPolicy == snapshotv1api.VolumeSnapshotContentDelete {
modifyVSCFlag = true
} else {
logger.Infof("Deleting Volumesnapshot %s/%s", vs.Namespace, vs.Name)
if vs.Status == nil ||
vs.Status.BoundVolumeSnapshotContentName == nil ||
len(*vs.Status.BoundVolumeSnapshotContentName) <= 0 {
logger.Errorf("VolumeSnapshot %s/%s is not ready. This is not expected.",
vs.Namespace, vs.Name)
return
}
// Change VolumeSnapshotContent's DeletionPolicy to Retain before deleting VolumeSnapshot,
// because VolumeSnapshotContent will be deleted by deleting VolumeSnapshot, when
// DeletionPolicy is set to Delete, but Velero needs VSC for cleaning snapshot on cloud
// in backup deletion.
if modifyVSCFlag {
logger.Debugf("Patching VolumeSnapshotContent %s", vsc.Name)
originVSC := vsc.DeepCopy()
vsc.Spec.DeletionPolicy = snapshotv1api.VolumeSnapshotContentRetain
err := client.Patch(
context.Background(),
&vsc,
crclient.MergeFrom(originVSC),
)
if err != nil {
logger.Errorf(
"fail to modify VolumeSnapshotContent %s DeletionPolicy to Retain: %s",
vsc.Name, err.Error(),
)
if vs.Status != nil && vs.Status.BoundVolumeSnapshotContentName != nil {
// Patch the DeletionPolicy of the VolumeSnapshotContent to set it to Retain.
// This ensures that the volume snapshot in the storage provider is kept.
if err := SetVolumeSnapshotContentDeletionPolicy(
vsc.Name,
client,
snapshotv1api.VolumeSnapshotContentRetain,
); err != nil {
logger.Warnf("Failed to patch DeletionPolicy of VolumeSnapshot %s/%s",
vs.Namespace, vs.Name)
return
}
defer func() {
logger.Debugf("Start to recreate VolumeSnapshotContent %s", vsc.Name)
err := recreateVolumeSnapshotContent(vsc, backup, client, logger)
if err != nil {
logger.Errorf(
"fail to recreate VolumeSnapshotContent %s: %s",
vsc.Name,
err.Error(),
)
}
}()
if err := client.Delete(context.TODO(), &vsc); err != nil {
logger.WithError(err).Warnf("Failed to delete the VolumeSnapshotContent %s", vsc.Name)
}
}
// Delete VolumeSnapshot from cluster
logger.Debugf("Deleting VolumeSnapshot %s/%s", vs.Namespace, vs.Name)
err := client.Delete(context.TODO(), &vs)
if err != nil {
logger.Errorf("fail to delete VolumeSnapshot %s/%s: %s",
vs.Namespace, vs.Name, err.Error())
if err := client.Delete(context.TODO(), &vs); err != nil {
logger.WithError(err).Warnf("Failed to delete VolumeSnapshot %s", vs.Namespace+"/"+vs.Name)
} else {
logger.Infof("Deleted VolumeSnapshot %s and VolumeSnapshotContent %s",
vs.Namespace+"/"+vs.Name, vsc.Name)
}
}
// recreateVolumeSnapshotContent will delete then re-create VolumeSnapshotContent,
// because some parameter in VolumeSnapshotContent Spec is immutable,
// e.g. VolumeSnapshotRef and Source.
// Source is updated to let csi-controller thinks the VSC is statically
// provisioned with VS.
// Set VolumeSnapshotRef's UID to nil will let the csi-controller finds out
// the related VS is gone, then VSC can be deleted.
func recreateVolumeSnapshotContent(
vsc snapshotv1api.VolumeSnapshotContent,
backup *velerov1api.Backup,
client crclient.Client,
log logrus.FieldLogger,
) error {
// Read resource timeout from backup annotation, if not set, use default value.
timeout, err := time.ParseDuration(
backup.Annotations[velerov1api.ResourceTimeoutAnnotation])
if err != nil {
log.Warnf("fail to parse resource timeout annotation %s: %s",
backup.Annotations[velerov1api.ResourceTimeoutAnnotation], err.Error())
timeout = 10 * time.Minute
}
log.Debugf("resource timeout is set to %s", timeout.String())
interval := 1 * time.Second
if err := client.Delete(context.TODO(), &vsc); err != nil {
return errors.Wrapf(err, "fail to delete VolumeSnapshotContent: %s", vsc.Name)
}
// Check VolumeSnapshotContents is already deleted, before re-creating it.
err = wait.PollUntilContextTimeout(
context.Background(),
interval,
timeout,
true,
func(ctx context.Context) (bool, error) {
tmpVSC := new(snapshotv1api.VolumeSnapshotContent)
if err := client.Get(ctx, crclient.ObjectKeyFromObject(&vsc), tmpVSC); err != nil {
if apierrors.IsNotFound(err) {
return true, nil
}
return false, errors.Wrapf(
err,
fmt.Sprintf("failed to get VolumeSnapshotContent %s", vsc.Name),
)
}
return false, nil
},
)
if err != nil {
return errors.Wrapf(err, "fail to retrieve VolumeSnapshotContent %s info", vsc.Name)
}
// Make the VolumeSnapshotContent static
vsc.Spec.Source = snapshotv1api.VolumeSnapshotContentSource{
SnapshotHandle: vsc.Status.SnapshotHandle,
}
// Set VolumeSnapshotRef to none exist one, because VolumeSnapshotContent
// validation webhook will check whether name and namespace are nil.
// external-snapshotter needs Source pointing to snapshot and VolumeSnapshot
// reference's UID to nil to determine the VolumeSnapshotContent is deletable.
vsc.Spec.VolumeSnapshotRef = corev1api.ObjectReference{
APIVersion: snapshotv1api.SchemeGroupVersion.String(),
Kind: "VolumeSnapshot",
Namespace: "ns-" + string(vsc.UID),
Name: "name-" + string(vsc.UID),
}
// ResourceVersion shouldn't exist for new creation.
vsc.ResourceVersion = ""
if err := client.Create(context.TODO(), &vsc); err != nil {
return errors.Wrapf(err, "fail to create VolumeSnapshotContent %s", vsc.Name)
}
return nil
}
// WaitUntilVSCHandleIsReady returns the VolumeSnapshotContent
// object associated with the volumesnapshot
func WaitUntilVSCHandleIsReady(

View File

@ -1379,12 +1379,14 @@ func TestSetVolumeSnapshotContentDeletionPolicy(t *testing.T) {
testCases := []struct {
name string
inputVSCName string
policy snapshotv1api.DeletionPolicy
objs []runtime.Object
expectError bool
}{
{
name: "should update DeletionPolicy of a VSC from retain to delete",
inputVSCName: "retainVSC",
policy: snapshotv1api.VolumeSnapshotContentDelete,
objs: []runtime.Object{
&snapshotv1api.VolumeSnapshotContent{
ObjectMeta: metav1.ObjectMeta{
@ -1400,6 +1402,7 @@ func TestSetVolumeSnapshotContentDeletionPolicy(t *testing.T) {
{
name: "should be a no-op updating if DeletionPolicy of a VSC is already Delete",
inputVSCName: "deleteVSC",
policy: snapshotv1api.VolumeSnapshotContentDelete,
objs: []runtime.Object{
&snapshotv1api.VolumeSnapshotContent{
ObjectMeta: metav1.ObjectMeta{
@ -1415,6 +1418,7 @@ func TestSetVolumeSnapshotContentDeletionPolicy(t *testing.T) {
{
name: "should update DeletionPolicy of a VSC with no DeletionPolicy",
inputVSCName: "nothingVSC",
policy: snapshotv1api.VolumeSnapshotContentDelete,
objs: []runtime.Object{
&snapshotv1api.VolumeSnapshotContent{
ObjectMeta: metav1.ObjectMeta{
@ -1436,7 +1440,7 @@ func TestSetVolumeSnapshotContentDeletionPolicy(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fakeClient := velerotest.NewFakeControllerRuntimeClient(t, tc.objs...)
err := SetVolumeSnapshotContentDeletionPolicy(tc.inputVSCName, fakeClient)
err := SetVolumeSnapshotContentDeletionPolicy(tc.inputVSCName, fakeClient, tc.policy)
if tc.expectError {
assert.Error(t, err)
} else {
@ -1450,7 +1454,7 @@ func TestSetVolumeSnapshotContentDeletionPolicy(t *testing.T) {
assert.NoError(t, err)
assert.Equal(
t,
snapshotv1api.VolumeSnapshotContentDelete,
tc.policy,
actual.Spec.DeletionPolicy,
)
}
@ -1460,11 +1464,10 @@ func TestSetVolumeSnapshotContentDeletionPolicy(t *testing.T) {
func TestDeleteVolumeSnapshots(t *testing.T) {
tests := []struct {
name string
vs snapshotv1api.VolumeSnapshot
vsc snapshotv1api.VolumeSnapshotContent
expectedVS snapshotv1api.VolumeSnapshot
expectedVSC snapshotv1api.VolumeSnapshotContent
name string
vs snapshotv1api.VolumeSnapshot
vsc snapshotv1api.VolumeSnapshotContent
keepVSAndVSC bool
}{
{
name: "VS is ReadyToUse, and VS has corresponding VSC. VS should be deleted.",
@ -1474,10 +1477,6 @@ func TestDeleteVolumeSnapshots(t *testing.T) {
vsc: *builder.ForVolumeSnapshotContent("vsc1").
DeletionPolicy(snapshotv1api.VolumeSnapshotContentDelete).
Status(&snapshotv1api.VolumeSnapshotContentStatus{}).Result(),
expectedVS: snapshotv1api.VolumeSnapshot{},
expectedVSC: *builder.ForVolumeSnapshotContent("vsc1").
DeletionPolicy(snapshotv1api.VolumeSnapshotContentRetain).
VolumeSnapshotRef("ns-", "name-").Result(),
},
{
name: "VS status is nil. VSC should not be modified.",
@ -1486,9 +1485,7 @@ func TestDeleteVolumeSnapshots(t *testing.T) {
vsc: *builder.ForVolumeSnapshotContent("vsc1").
DeletionPolicy(snapshotv1api.VolumeSnapshotContentDelete).
Status(&snapshotv1api.VolumeSnapshotContentStatus{}).Result(),
expectedVS: snapshotv1api.VolumeSnapshot{},
expectedVSC: *builder.ForVolumeSnapshotContent("vsc1").
DeletionPolicy(snapshotv1api.VolumeSnapshotContentDelete).Result(),
keepVSAndVSC: true,
},
}
@ -1499,10 +1496,8 @@ func TestDeleteVolumeSnapshots(t *testing.T) {
[]runtime.Object{&tc.vs, &tc.vsc}...,
)
logger := logging.DefaultLogger(logrus.DebugLevel, logging.FormatText)
backup := builder.ForBackup(velerov1api.DefaultNamespace, "backup-1").
DefaultVolumesToFsBackup(false).Result()
DeleteVolumeSnapshot(tc.vs, tc.vsc, backup, client, logger)
DeleteVolumeSnapshot(tc.vs, tc.vsc, client, logger)
vsList := new(snapshotv1api.VolumeSnapshotList)
err := client.List(
@ -1513,12 +1508,6 @@ func TestDeleteVolumeSnapshots(t *testing.T) {
},
)
require.NoError(t, err)
if tc.expectedVS.Name == "" {
require.Empty(t, vsList.Items)
} else {
require.Equal(t, tc.expectedVS.Status, vsList.Items[0].Status)
require.Equal(t, tc.expectedVS.Spec, vsList.Items[0].Spec)
}
vscList := new(snapshotv1api.VolumeSnapshotContentList)
err = client.List(
@ -1526,8 +1515,14 @@ func TestDeleteVolumeSnapshots(t *testing.T) {
vscList,
)
require.NoError(t, err)
require.Len(t, vscList.Items, 1)
require.Equal(t, tc.expectedVSC.Spec, vscList.Items[0].Spec)
if tc.keepVSAndVSC {
require.Equal(t, crclient.ObjectKeyFromObject(&tc.vs), crclient.ObjectKeyFromObject(&vsList.Items[0]))
require.Equal(t, crclient.ObjectKeyFromObject(&tc.vsc), crclient.ObjectKeyFromObject(&vscList.Items[0]))
} else {
require.Empty(t, vsList.Items)
require.Empty(t, vscList.Items)
}
})
}
}