Properly restore PVs with a reclaim policy of Retain and restic backups (#1713)

* reorganize persistent volume restore code for clarity

Signed-off-by: Steve Kriss <krisss@vmware.com>
This commit is contained in:
Steve Kriss
2019-08-06 13:40:35 -07:00
committed by KubeKween
parent 4accb8512a
commit 80692a8a39
5 changed files with 101 additions and 66 deletions
+66 -23
View File
@@ -874,41 +874,48 @@ func (ctx *context) restoreItem(obj *unstructured.Unstructured, groupResource sc
}
if groupResource == kuberesource.PersistentVolumes {
var hasSnapshot bool
for _, snapshot := range ctx.volumeSnapshots {
if snapshot.Spec.PersistentVolumeName == name {
hasSnapshot = true
break
switch {
case hasSnapshot(name, ctx.volumeSnapshots):
// Check if the PV exists in the cluster before attempting to create
// a volume from the snapshot, in order to avoid orphaned volumes (GH #609)
shouldRestoreSnapshot, err := ctx.shouldRestore(name, resourceClient)
if err != nil {
addToResult(&errs, namespace, errors.Wrapf(err, "error waiting on in-cluster persistentvolume %s", name))
return warnings, errs
}
}
if !hasSnapshot && hasDeleteReclaimPolicy(obj.Object) {
ctx.log.Infof("Not restoring PV because it doesn't have a snapshot and its reclaim policy is Delete.")
if shouldRestoreSnapshot {
ctx.log.Infof("Restoring persistent volume from snapshot.")
updatedObj, err := ctx.pvRestorer.executePVAction(obj)
if err != nil {
addToResult(&errs, namespace, fmt.Errorf("error executing PVAction for %s: %v", resourceID, err))
return warnings, errs
}
obj = updatedObj
}
case hasResticBackup(obj, ctx):
ctx.log.Infof("Dynamically re-provisioning persistent volume because it has a restic backup to be restored.")
ctx.pvsToProvision.Insert(name)
return warnings, errs
}
// Check if the PV exists in the cluster before attempting to create
// a volume from the snapshot, in order to avoid orphaned volumes (GH #609)
shouldRestoreSnapshot, err := ctx.shouldRestore(name, resourceClient)
if err != nil {
addToResult(&errs, namespace, errors.Wrapf(err, "error waiting on in-cluster persistentvolume %s", name))
// return early because we don't want to restore the PV itself, we want to dynamically re-provision it.
return warnings, errs
}
case hasDeleteReclaimPolicy(obj.Object):
ctx.log.Infof("Dynamically re-provisioning persistent volume because it doesn't have a snapshot and its reclaim policy is Delete.")
ctx.pvsToProvision.Insert(name)
// PV's existence will be recorded later. Just skip the volume restore logic.
if shouldRestoreSnapshot {
// restore the PV from snapshot (if applicable)
// return early because we don't want to restore the PV itself, we want to dynamically re-provision it.
return warnings, errs
default:
ctx.log.Infof("Restoring persistent volume as-is because it doesn't have a snapshot and its reclaim policy is not Delete.")
// we call the pvRestorer here to clear out the PV's claimRef, so it can be re-claimed
// when its PVC is restored.
updatedObj, err := ctx.pvRestorer.executePVAction(obj)
if err != nil {
addToResult(&errs, namespace, fmt.Errorf("error executing PVAction for %s: %v", resourceID, err))
return warnings, errs
}
obj = updatedObj
} else if err != nil {
addToResult(&errs, namespace, fmt.Errorf("error checking existence for PV %s: %v", name, err))
return warnings, errs
}
}
@@ -1125,6 +1132,42 @@ func restorePodVolumeBackups(ctx *context, createdObj *unstructured.Unstructured
}
}
func hasSnapshot(pvName string, snapshots []*volume.Snapshot) bool {
for _, snapshot := range snapshots {
if snapshot.Spec.PersistentVolumeName == pvName {
return true
}
}
return false
}
func hasResticBackup(unstructuredPV *unstructured.Unstructured, ctx *context) bool {
if len(ctx.podVolumeBackups) == 0 {
return false
}
pv := new(v1.PersistentVolume)
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredPV.Object, pv); err != nil {
ctx.log.WithError(err).Warnf("Unable to convert PV from unstructured to structured")
return false
}
if pv.Spec.ClaimRef == nil {
return false
}
var found bool
for _, pvb := range ctx.podVolumeBackups {
if pvb.Spec.Pod.Namespace == pv.Spec.ClaimRef.Namespace && pvb.GetAnnotations()[restic.PVCNameAnnotation] == pv.Spec.ClaimRef.Name {
found = true
break
}
}
return found
}
func hasDeleteReclaimPolicy(obj map[string]interface{}) bool {
policy, _, _ := unstructured.NestedString(obj, "spec", "persistentVolumeReclaimPolicy")
return policy == string(v1.PersistentVolumeReclaimDelete)
+4 -36
View File
@@ -1826,15 +1826,7 @@ func TestRestorePersistentVolumes(t *testing.T) {
},
},
volumeSnapshotLocations: []*velerov1api.VolumeSnapshotLocation{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: velerov1api.DefaultNamespace,
Name: "default",
},
Spec: velerov1api.VolumeSnapshotLocationSpec{
Provider: "provider-1",
},
},
builder.ForVolumeSnapshotLocation(velerov1api.DefaultNamespace, "default").Provider("provider-1").Result(),
},
volumeSnapshotterGetter: map[string]velero.VolumeSnapshotter{
"provider-1": &volumeSnapshotter{
@@ -1883,15 +1875,7 @@ func TestRestorePersistentVolumes(t *testing.T) {
},
},
volumeSnapshotLocations: []*velerov1api.VolumeSnapshotLocation{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: velerov1api.DefaultNamespace,
Name: "default",
},
Spec: velerov1api.VolumeSnapshotLocationSpec{
Provider: "provider-1",
},
},
builder.ForVolumeSnapshotLocation(velerov1api.DefaultNamespace, "default").Provider("provider-1").Result(),
},
volumeSnapshotterGetter: map[string]velero.VolumeSnapshotter{
"provider-1": &volumeSnapshotter{
@@ -1945,15 +1929,7 @@ func TestRestorePersistentVolumes(t *testing.T) {
},
},
volumeSnapshotLocations: []*velerov1api.VolumeSnapshotLocation{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: velerov1api.DefaultNamespace,
Name: "default",
},
Spec: velerov1api.VolumeSnapshotLocationSpec{
Provider: "provider-1",
},
},
builder.ForVolumeSnapshotLocation(velerov1api.DefaultNamespace, "default").Provider("provider-1").Result(),
},
volumeSnapshotterGetter: map[string]velero.VolumeSnapshotter{
// the volume snapshotter fake is not configured with any snapshotID -> volumeID
@@ -2005,15 +1981,7 @@ func TestRestorePersistentVolumes(t *testing.T) {
},
},
volumeSnapshotLocations: []*velerov1api.VolumeSnapshotLocation{
{
ObjectMeta: metav1.ObjectMeta{
Namespace: velerov1api.DefaultNamespace,
Name: "default",
},
Spec: velerov1api.VolumeSnapshotLocationSpec{
Provider: "provider-1",
},
},
builder.ForVolumeSnapshotLocation(velerov1api.DefaultNamespace, "default").Provider("provider-1").Result(),
},
volumeSnapshotterGetter: map[string]velero.VolumeSnapshotter{
// the volume snapshotter fake is not configured with any snapshotID -> volumeID