mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-07-29 11:32:42 +00:00
Merge pull request #608 from skriss/no-pv-snapshot-if-restic-backup
don't snapshot volumes that have been backed up with restic
This commit is contained in:
@@ -286,6 +286,7 @@ func (kb *kubernetesBackupper) Backup(backup *api.Backup, backupFile, logFile io
|
||||
resourceHooks,
|
||||
kb.snapshotService,
|
||||
resticBackupper,
|
||||
newPVCSnapshotTracker(),
|
||||
)
|
||||
|
||||
for _, group := range kb.discoveryHelper.Resources() {
|
||||
|
||||
@@ -542,6 +542,7 @@ func TestBackup(t *testing.T) {
|
||||
test.expectedHooks,
|
||||
mock.Anything,
|
||||
mock.Anything, // restic backupper
|
||||
mock.Anything, // pvc snapshot tracker
|
||||
).Return(groupBackupper)
|
||||
|
||||
for group, err := range test.backupGroupErrors {
|
||||
@@ -606,6 +607,7 @@ func TestBackupUsesNewCohabitatingResourcesForEachBackup(t *testing.T) {
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(&mockGroupBackupper{})
|
||||
|
||||
assert.NoError(t, b.Backup(&v1.Backup{}, &bytes.Buffer{}, &bytes.Buffer{}, nil))
|
||||
@@ -637,6 +639,7 @@ func TestBackupUsesNewCohabitatingResourcesForEachBackup(t *testing.T) {
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(&mockGroupBackupper{})
|
||||
|
||||
assert.NoError(t, b.Backup(&v1.Backup{}, &bytes.Buffer{}, &bytes.Buffer{}, nil))
|
||||
@@ -665,6 +668,7 @@ func (f *mockGroupBackupperFactory) newGroupBackupper(
|
||||
resourceHooks []resourceHook,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
) groupBackupper {
|
||||
args := f.Called(
|
||||
log,
|
||||
@@ -681,6 +685,7 @@ func (f *mockGroupBackupperFactory) newGroupBackupper(
|
||||
resourceHooks,
|
||||
snapshotService,
|
||||
resticBackupper,
|
||||
resticSnapshotTracker,
|
||||
)
|
||||
return args.Get(0).(groupBackupper)
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ type groupBackupperFactory interface {
|
||||
resourceHooks []resourceHook,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
) groupBackupper
|
||||
}
|
||||
|
||||
@@ -70,6 +71,7 @@ func (f *defaultGroupBackupperFactory) newGroupBackupper(
|
||||
resourceHooks []resourceHook,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
) groupBackupper {
|
||||
return &defaultGroupBackupper{
|
||||
log: log,
|
||||
@@ -86,6 +88,7 @@ func (f *defaultGroupBackupperFactory) newGroupBackupper(
|
||||
resourceHooks: resourceHooks,
|
||||
snapshotService: snapshotService,
|
||||
resticBackupper: resticBackupper,
|
||||
resticSnapshotTracker: resticSnapshotTracker,
|
||||
resourceBackupperFactory: &defaultResourceBackupperFactory{},
|
||||
}
|
||||
}
|
||||
@@ -108,6 +111,7 @@ type defaultGroupBackupper struct {
|
||||
resourceHooks []resourceHook
|
||||
snapshotService cloudprovider.SnapshotService
|
||||
resticBackupper restic.Backupper
|
||||
resticSnapshotTracker *pvcSnapshotTracker
|
||||
resourceBackupperFactory resourceBackupperFactory
|
||||
}
|
||||
|
||||
@@ -131,6 +135,7 @@ func (gb *defaultGroupBackupper) backupGroup(group *metav1.APIResourceList) erro
|
||||
gb.resourceHooks,
|
||||
gb.snapshotService,
|
||||
gb.resticBackupper,
|
||||
gb.resticSnapshotTracker,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -89,6 +89,7 @@ func TestBackupGroup(t *testing.T) {
|
||||
resourceHooks,
|
||||
nil, // snapshot service
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
).(*defaultGroupBackupper)
|
||||
|
||||
resourceBackupperFactory := &mockResourceBackupperFactory{}
|
||||
@@ -113,6 +114,7 @@ func TestBackupGroup(t *testing.T) {
|
||||
resourceHooks,
|
||||
nil,
|
||||
mock.Anything, // restic backupper
|
||||
mock.Anything, // pvc snapshot tracker
|
||||
).Return(resourceBackupper)
|
||||
|
||||
group := &metav1.APIResourceList{
|
||||
@@ -161,6 +163,7 @@ func (rbf *mockResourceBackupperFactory) newResourceBackupper(
|
||||
resourceHooks []resourceHook,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
) resourceBackupper {
|
||||
args := rbf.Called(
|
||||
log,
|
||||
@@ -176,6 +179,8 @@ func (rbf *mockResourceBackupperFactory) newResourceBackupper(
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
snapshotService,
|
||||
resticBackupper,
|
||||
resticSnapshotTracker,
|
||||
)
|
||||
return args.Get(0).(resourceBackupper)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -58,6 +57,7 @@ type itemBackupperFactory interface {
|
||||
discoveryHelper discovery.Helper,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
) ItemBackupper
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ func (f *defaultItemBackupperFactory) newItemBackupper(
|
||||
discoveryHelper discovery.Helper,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
) ItemBackupper {
|
||||
ib := &defaultItemBackupper{
|
||||
backup: backup,
|
||||
@@ -90,7 +91,8 @@ func (f *defaultItemBackupperFactory) newItemBackupper(
|
||||
itemHookHandler: &defaultItemHookHandler{
|
||||
podCommandExecutor: podCommandExecutor,
|
||||
},
|
||||
resticBackupper: resticBackupper,
|
||||
resticBackupper: resticBackupper,
|
||||
resticSnapshotTracker: resticSnapshotTracker,
|
||||
}
|
||||
|
||||
// this is for testing purposes
|
||||
@@ -104,17 +106,18 @@ type ItemBackupper interface {
|
||||
}
|
||||
|
||||
type defaultItemBackupper struct {
|
||||
backup *api.Backup
|
||||
namespaces *collections.IncludesExcludes
|
||||
resources *collections.IncludesExcludes
|
||||
backedUpItems map[itemKey]struct{}
|
||||
actions []resolvedAction
|
||||
tarWriter tarWriter
|
||||
resourceHooks []resourceHook
|
||||
dynamicFactory client.DynamicFactory
|
||||
discoveryHelper discovery.Helper
|
||||
snapshotService cloudprovider.SnapshotService
|
||||
resticBackupper restic.Backupper
|
||||
backup *api.Backup
|
||||
namespaces *collections.IncludesExcludes
|
||||
resources *collections.IncludesExcludes
|
||||
backedUpItems map[itemKey]struct{}
|
||||
actions []resolvedAction
|
||||
tarWriter tarWriter
|
||||
resourceHooks []resourceHook
|
||||
dynamicFactory client.DynamicFactory
|
||||
discoveryHelper discovery.Helper
|
||||
snapshotService cloudprovider.SnapshotService
|
||||
resticBackupper restic.Backupper
|
||||
resticSnapshotTracker *pvcSnapshotTracker
|
||||
|
||||
itemHookHandler itemHookHandler
|
||||
additionalItemBackupper ItemBackupper
|
||||
@@ -178,9 +181,30 @@ func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtim
|
||||
return err
|
||||
}
|
||||
|
||||
backupErrs := make([]error, 0)
|
||||
err = ib.executeActions(log, obj, groupResource, name, namespace, metadata)
|
||||
if err != nil {
|
||||
var (
|
||||
backupErrs []error
|
||||
pod *corev1api.Pod
|
||||
resticVolumesToBackup []string
|
||||
)
|
||||
|
||||
if groupResource == kuberesource.Pods {
|
||||
// pod needs to be initialized for the unstructured converter
|
||||
pod = new(corev1api.Pod)
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pod); err != nil {
|
||||
backupErrs = append(backupErrs, errors.WithStack(err))
|
||||
// nil it on error since it's not valid
|
||||
pod = nil
|
||||
} else {
|
||||
// get the volumes to backup using restic, and add any of them that are PVCs to the pvc snapshot
|
||||
// tracker, so that when we backup PVCs/PVs via an item action in the next step, we don't snapshot
|
||||
// PVs that will have their data backed up with restic.
|
||||
resticVolumesToBackup = restic.GetVolumesToBackup(pod)
|
||||
|
||||
ib.resticSnapshotTracker.Track(pod, resticVolumesToBackup)
|
||||
}
|
||||
}
|
||||
|
||||
if err := ib.executeActions(log, obj, groupResource, name, namespace, metadata); err != nil {
|
||||
log.WithError(err).Error("Error executing item actions")
|
||||
backupErrs = append(backupErrs, err)
|
||||
}
|
||||
@@ -188,24 +212,22 @@ func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtim
|
||||
if groupResource == kuberesource.PersistentVolumes {
|
||||
if ib.snapshotService == nil {
|
||||
log.Debug("Skipping Persistent Volume snapshot because they're not enabled.")
|
||||
} else {
|
||||
if err := ib.takePVSnapshot(obj, ib.backup, log); err != nil {
|
||||
backupErrs = append(backupErrs, err)
|
||||
}
|
||||
} else if err := ib.takePVSnapshot(obj, ib.backup, log); err != nil {
|
||||
backupErrs = append(backupErrs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if groupResource == kuberesource.Pods && len(restic.GetVolumesToBackup(metadata)) > 0 {
|
||||
var (
|
||||
updatedObj runtime.Unstructured
|
||||
errs []error
|
||||
)
|
||||
if groupResource == kuberesource.Pods && pod != nil {
|
||||
// this function will return partial results, so process volumeSnapshots
|
||||
// even if there are errors.
|
||||
volumeSnapshots, errs := ib.backupPodVolumes(log, pod, resticVolumesToBackup)
|
||||
|
||||
if updatedObj, errs = backupPodVolumes(log, ib.backup, obj, ib.resticBackupper); len(errs) > 0 {
|
||||
backupErrs = append(backupErrs, errs...)
|
||||
} else {
|
||||
obj = updatedObj
|
||||
// annotate the pod with the successful volume snapshots
|
||||
for volume, snapshot := range volumeSnapshots {
|
||||
restic.SetPodSnapshotAnnotation(metadata, volume, snapshot)
|
||||
}
|
||||
|
||||
backupErrs = append(backupErrs, errs...)
|
||||
}
|
||||
|
||||
log.Debug("Executing post hooks")
|
||||
@@ -248,37 +270,19 @@ func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtim
|
||||
return nil
|
||||
}
|
||||
|
||||
func backupPodVolumes(log logrus.FieldLogger, backup *api.Backup, obj runtime.Unstructured, backupper restic.Backupper) (runtime.Unstructured, []error) {
|
||||
if backupper == nil {
|
||||
// backupPodVolumes triggers restic backups of the specified pod volumes, and returns a map of volume name -> snapshot ID
|
||||
// for volumes that were successfully backed up, and a slice of any errors that were encountered.
|
||||
func (ib *defaultItemBackupper) backupPodVolumes(log logrus.FieldLogger, pod *corev1api.Pod, volumes []string) (map[string]string, []error) {
|
||||
if len(volumes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if ib.resticBackupper == nil {
|
||||
log.Warn("No restic backupper, not backing up pod's volumes")
|
||||
return obj, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
pod := new(corev1api.Pod)
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pod); err != nil {
|
||||
return nil, []error{errors.WithStack(err)}
|
||||
}
|
||||
|
||||
volumeSnapshots, errs := backupper.BackupPodVolumes(backup, pod, log)
|
||||
if len(errs) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
if len(volumeSnapshots) == 0 {
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// annotate the pod with the successful volume snapshots
|
||||
for volume, snapshot := range volumeSnapshots {
|
||||
restic.SetPodSnapshotAnnotation(pod, volume, snapshot)
|
||||
}
|
||||
|
||||
// convert annotated pod back to unstructured to return
|
||||
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(pod)
|
||||
if err != nil {
|
||||
return nil, []error{errors.WithStack(err)}
|
||||
}
|
||||
|
||||
return &unstructured.Unstructured{Object: unstructuredObj}, nil
|
||||
return ib.resticBackupper.BackupPodVolumes(ib.backup, pod, log)
|
||||
}
|
||||
|
||||
func (ib *defaultItemBackupper) executeActions(log logrus.FieldLogger, obj runtime.Unstructured, groupResource schema.GroupResource, name, namespace string, metadata metav1.Object) error {
|
||||
@@ -347,7 +351,7 @@ const zoneLabel = "failure-domain.beta.kubernetes.io/zone"
|
||||
// takePVSnapshot triggers a snapshot for the volume/disk underlying a PersistentVolume if the provided
|
||||
// backup has volume snapshots enabled and the PV is of a compatible type. Also records cloud
|
||||
// disk type and IOPS (if applicable) to be able to restore to current state later.
|
||||
func (ib *defaultItemBackupper) takePVSnapshot(pv runtime.Unstructured, backup *api.Backup, log logrus.FieldLogger) error {
|
||||
func (ib *defaultItemBackupper) takePVSnapshot(obj runtime.Unstructured, backup *api.Backup, log logrus.FieldLogger) error {
|
||||
log.Info("Executing takePVSnapshot")
|
||||
|
||||
if backup.Spec.SnapshotVolumes != nil && !*backup.Spec.SnapshotVolumes {
|
||||
@@ -355,7 +359,21 @@ func (ib *defaultItemBackupper) takePVSnapshot(pv runtime.Unstructured, backup *
|
||||
return nil
|
||||
}
|
||||
|
||||
metadata, err := meta.Accessor(pv)
|
||||
pv := new(corev1api.PersistentVolume)
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pv); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// If this PV is claimed, see if we've already taken a (restic) snapshot of the contents
|
||||
// of this PV. If so, don't take a snapshot.
|
||||
if pv.Spec.ClaimRef != nil {
|
||||
if ib.resticSnapshotTracker.Has(pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name) {
|
||||
log.Info("Skipping Persistent Volume snapshot because volume has already been backed up.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
metadata, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@@ -370,7 +388,7 @@ func (ib *defaultItemBackupper) takePVSnapshot(pv runtime.Unstructured, backup *
|
||||
log.Infof("label %q is not present on PersistentVolume", zoneLabel)
|
||||
}
|
||||
|
||||
volumeID, err := ib.snapshotService.GetVolumeID(pv)
|
||||
volumeID, err := ib.snapshotService.GetVolumeID(obj)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error getting volume ID for PersistentVolume")
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
func TestBackupItemSkips(t *testing.T) {
|
||||
@@ -163,6 +164,8 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
snapshottableVolumes map[string]api.VolumeBackupInfo
|
||||
snapshotError error
|
||||
additionalItemError error
|
||||
trackedPVCs sets.String
|
||||
expectedTrackedPVCs sets.String
|
||||
}{
|
||||
{
|
||||
name: "explicit namespace include",
|
||||
@@ -293,6 +296,32 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
"vol-abc123": {SnapshotID: "snapshot-1", AvailabilityZone: "us-east-1c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "takePVSnapshot is not invoked for PVs when their claim is tracked in the restic PVC tracker",
|
||||
namespaceIncludesExcludes: collections.NewIncludesExcludes().Includes("*"),
|
||||
item: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/zone": "us-east-1c"}}, "spec": {"claimRef": {"namespace": "pvc-ns", "name": "pvc"}, "awsElasticBlockStore": {"volumeID": "aws://us-east-1c/vol-abc123"}}}`,
|
||||
expectError: false,
|
||||
expectExcluded: false,
|
||||
expectedTarHeaderName: "resources/persistentvolumes/cluster/mypv.json",
|
||||
groupResource: "persistentvolumes",
|
||||
// empty snapshottableVolumes causes a snapshotService to be created, but no
|
||||
// snapshots are expected to be taken.
|
||||
snapshottableVolumes: map[string]api.VolumeBackupInfo{},
|
||||
trackedPVCs: sets.NewString(key("pvc-ns", "pvc"), key("another-pvc-ns", "another-pvc")),
|
||||
},
|
||||
{
|
||||
name: "takePVSnapshot is invoked for PVs when their claim is not tracked in the restic PVC tracker",
|
||||
namespaceIncludesExcludes: collections.NewIncludesExcludes().Includes("*"),
|
||||
item: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/zone": "us-east-1c"}}, "spec": {"claimRef": {"namespace": "pvc-ns", "name": "pvc"}, "awsElasticBlockStore": {"volumeID": "aws://us-east-1c/vol-abc123"}}}`,
|
||||
expectError: false,
|
||||
expectExcluded: false,
|
||||
expectedTarHeaderName: "resources/persistentvolumes/cluster/mypv.json",
|
||||
groupResource: "persistentvolumes",
|
||||
snapshottableVolumes: map[string]api.VolumeBackupInfo{
|
||||
"vol-abc123": {SnapshotID: "snapshot-1", AvailabilityZone: "us-east-1c"},
|
||||
},
|
||||
trackedPVCs: sets.NewString(key("another-pvc-ns", "another-pvc")),
|
||||
},
|
||||
{
|
||||
name: "backup fails when takePVSnapshot fails",
|
||||
namespaceIncludesExcludes: collections.NewIncludesExcludes().Includes("*"),
|
||||
@@ -304,6 +333,16 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
},
|
||||
snapshotError: fmt.Errorf("failure"),
|
||||
},
|
||||
{
|
||||
name: "pod's restic PVC volume backups (only) are tracked",
|
||||
item: `{"apiVersion": "v1", "kind": "Pod", "spec": {"volumes": [{"name": "volume-1", "persistentVolumeClaim": {"claimName": "bar"}},{"name": "volume-2", "persistentVolumeClaim": {"claimName": "baz"}},{"name": "volume-1", "emptyDir": {}}]}, "metadata":{"namespace":"foo","name":"bar", "annotations": {"backup.ark.heptio.com/backup-volumes": "volume-1,volume-2"}}}`,
|
||||
namespaceIncludesExcludes: collections.NewIncludesExcludes().Includes("*"),
|
||||
groupResource: "pods",
|
||||
expectError: false,
|
||||
expectExcluded: false,
|
||||
expectedTarHeaderName: "resources/pods/namespaces/foo/bar.json",
|
||||
expectedTrackedPVCs: sets.NewString(key("foo", "bar"), key("foo", "baz")),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -376,6 +415,7 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
discoveryHelper,
|
||||
nil, // snapshot service
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
).(*defaultItemBackupper)
|
||||
|
||||
var snapshotService *arktest.FakeSnapshotService
|
||||
@@ -388,6 +428,10 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
b.snapshotService = snapshotService
|
||||
}
|
||||
|
||||
if test.trackedPVCs != nil {
|
||||
b.resticSnapshotTracker.pvcs = test.trackedPVCs
|
||||
}
|
||||
|
||||
// make sure the podCommandExecutor was set correctly in the real hook handler
|
||||
assert.Equal(t, podCommandExecutor, b.itemHookHandler.(*defaultItemHookHandler).podCommandExecutor)
|
||||
|
||||
@@ -469,7 +513,7 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
}
|
||||
|
||||
if test.snapshottableVolumes != nil {
|
||||
require.Equal(t, 1, len(snapshotService.SnapshotsTaken))
|
||||
require.Equal(t, len(test.snapshottableVolumes), len(snapshotService.SnapshotsTaken))
|
||||
|
||||
var expectedBackups []api.VolumeBackupInfo
|
||||
for _, vbi := range test.snapshottableVolumes {
|
||||
@@ -483,6 +527,14 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
|
||||
assert.Equal(t, expectedBackups, actualBackups)
|
||||
}
|
||||
|
||||
if test.expectedTrackedPVCs != nil {
|
||||
require.Equal(t, len(test.expectedTrackedPVCs), len(b.resticSnapshotTracker.pvcs))
|
||||
|
||||
for key := range test.expectedTrackedPVCs {
|
||||
assert.True(t, b.resticSnapshotTracker.pvcs.Has(key))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright 2018 the Heptio Ark 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 backup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// pvcSnapshotTracker keeps track of persistent volume claims that have been snapshotted
|
||||
// with restic.
|
||||
type pvcSnapshotTracker struct {
|
||||
pvcs sets.String
|
||||
}
|
||||
|
||||
func newPVCSnapshotTracker() *pvcSnapshotTracker {
|
||||
return &pvcSnapshotTracker{
|
||||
pvcs: sets.NewString(),
|
||||
}
|
||||
}
|
||||
|
||||
// Track takes a pod and a list of volumes from that pod that were snapshotted, and
|
||||
// tracks each snapshotted volume that's a PVC.
|
||||
func (t *pvcSnapshotTracker) Track(pod *corev1api.Pod, snapshottedVolumes []string) {
|
||||
for _, volumeName := range snapshottedVolumes {
|
||||
// if the volume is a PVC, track it
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
if volume.Name == volumeName {
|
||||
if volume.PersistentVolumeClaim != nil {
|
||||
t.pvcs.Insert(key(pod.Namespace, volume.PersistentVolumeClaim.ClaimName))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Has returns true if the PVC with the specified namespace and name has been tracked.
|
||||
func (t *pvcSnapshotTracker) Has(namespace, name string) bool {
|
||||
return t.pvcs.Has(key(namespace, name))
|
||||
}
|
||||
|
||||
func key(namespace, name string) string {
|
||||
return fmt.Sprintf("%s/%s", namespace, name)
|
||||
}
|
||||
@@ -53,6 +53,7 @@ type resourceBackupperFactory interface {
|
||||
resourceHooks []resourceHook,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
) resourceBackupper
|
||||
}
|
||||
|
||||
@@ -73,6 +74,7 @@ func (f *defaultResourceBackupperFactory) newResourceBackupper(
|
||||
resourceHooks []resourceHook,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
) resourceBackupper {
|
||||
return &defaultResourceBackupper{
|
||||
log: log,
|
||||
@@ -89,6 +91,7 @@ func (f *defaultResourceBackupperFactory) newResourceBackupper(
|
||||
resourceHooks: resourceHooks,
|
||||
snapshotService: snapshotService,
|
||||
resticBackupper: resticBackupper,
|
||||
resticSnapshotTracker: resticSnapshotTracker,
|
||||
itemBackupperFactory: &defaultItemBackupperFactory{},
|
||||
}
|
||||
}
|
||||
@@ -112,6 +115,7 @@ type defaultResourceBackupper struct {
|
||||
resourceHooks []resourceHook
|
||||
snapshotService cloudprovider.SnapshotService
|
||||
resticBackupper restic.Backupper
|
||||
resticSnapshotTracker *pvcSnapshotTracker
|
||||
itemBackupperFactory itemBackupperFactory
|
||||
}
|
||||
|
||||
@@ -187,6 +191,7 @@ func (rb *defaultResourceBackupper) backupResource(
|
||||
rb.discoveryHelper,
|
||||
rb.snapshotService,
|
||||
rb.resticBackupper,
|
||||
rb.resticSnapshotTracker,
|
||||
)
|
||||
|
||||
namespacesToList := getNamespacesToList(rb.namespaces)
|
||||
|
||||
@@ -272,6 +272,7 @@ func TestBackupResource(t *testing.T) {
|
||||
resourceHooks,
|
||||
nil, // snapshot service
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
).(*defaultResourceBackupper)
|
||||
|
||||
itemBackupperFactory := &mockItemBackupperFactory{}
|
||||
@@ -295,6 +296,7 @@ func TestBackupResource(t *testing.T) {
|
||||
discoveryHelper,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(itemBackupper)
|
||||
|
||||
if len(test.listResponses) > 0 {
|
||||
@@ -438,6 +440,7 @@ func TestBackupResourceCohabitation(t *testing.T) {
|
||||
resourceHooks,
|
||||
nil, // snapshot service
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
).(*defaultResourceBackupper)
|
||||
|
||||
itemBackupperFactory := &mockItemBackupperFactory{}
|
||||
@@ -460,6 +463,7 @@ func TestBackupResourceCohabitation(t *testing.T) {
|
||||
discoveryHelper,
|
||||
mock.Anything, // snapshot service
|
||||
mock.Anything, // restic backupper
|
||||
mock.Anything, // pvc snapshot tracker
|
||||
).Return(itemBackupper)
|
||||
|
||||
client := &arktest.FakeDynamicClient{}
|
||||
@@ -519,6 +523,7 @@ func TestBackupResourceOnlyIncludesSpecifiedNamespaces(t *testing.T) {
|
||||
resourceHooks,
|
||||
nil, // snapshot service
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
).(*defaultResourceBackupper)
|
||||
|
||||
itemBackupperFactory := &mockItemBackupperFactory{}
|
||||
@@ -555,6 +560,7 @@ func TestBackupResourceOnlyIncludesSpecifiedNamespaces(t *testing.T) {
|
||||
discoveryHelper,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(itemBackupper)
|
||||
|
||||
client := &arktest.FakeDynamicClient{}
|
||||
@@ -622,6 +628,7 @@ func TestBackupResourceListAllNamespacesExcludesCorrectly(t *testing.T) {
|
||||
resourceHooks,
|
||||
nil, // snapshot service
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
).(*defaultResourceBackupper)
|
||||
|
||||
itemBackupperFactory := &mockItemBackupperFactory{}
|
||||
@@ -647,6 +654,7 @@ func TestBackupResourceListAllNamespacesExcludesCorrectly(t *testing.T) {
|
||||
discoveryHelper,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(itemBackupper)
|
||||
|
||||
client := &arktest.FakeDynamicClient{}
|
||||
@@ -684,6 +692,7 @@ func (ibf *mockItemBackupperFactory) newItemBackupper(
|
||||
discoveryHelper discovery.Helper,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
) ItemBackupper {
|
||||
args := ibf.Called(
|
||||
backup,
|
||||
@@ -698,6 +707,7 @@ func (ibf *mockItemBackupperFactory) newItemBackupper(
|
||||
discoveryHelper,
|
||||
snapshotService,
|
||||
resticBackupper,
|
||||
resticSnapshotTracker,
|
||||
)
|
||||
return args.Get(0).(ItemBackupper)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user