Delete VSC after backup completes.
Signed-off-by: Xun Jiang <xun.jiang@broadcom.com>pull/8684/head
parent
620a116e7f
commit
eb77151f48
|
@ -0,0 +1 @@
|
|||
Clean artifacts generated during CSI B/R.
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue