Migrate restic backup tests (#1703)
* migrate pkg/backup restic tests to new structure Signed-off-by: Steve Kriss <krisss@vmware.com> * rename backup_new_test.go to backup_test.go Signed-off-by: Steve Kriss <krisss@vmware.com> * use pod volume backup builder Signed-off-by: Steve Kriss <krisss@vmware.com>pull/1706/head
parent
b24e940399
commit
bf247836e6
|
@ -20,6 +20,7 @@ import (
|
|||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -44,6 +45,7 @@ import (
|
|||
"github.com/heptio/velero/pkg/discovery"
|
||||
"github.com/heptio/velero/pkg/kuberesource"
|
||||
"github.com/heptio/velero/pkg/plugin/velero"
|
||||
"github.com/heptio/velero/pkg/restic"
|
||||
"github.com/heptio/velero/pkg/test"
|
||||
kubeutil "github.com/heptio/velero/pkg/util/kube"
|
||||
testutil "github.com/heptio/velero/pkg/util/test"
|
||||
|
@ -2017,6 +2019,108 @@ func TestBackupWithHooks(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type fakeResticBackupperFactory struct {
|
||||
podVolumeBackups []*velerov1.PodVolumeBackup
|
||||
}
|
||||
|
||||
func (f *fakeResticBackupperFactory) NewBackupper(context.Context, *velerov1.Backup) (restic.Backupper, error) {
|
||||
return &fakeResticBackupper{
|
||||
podVolumeBackups: f.podVolumeBackups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type fakeResticBackupper struct {
|
||||
podVolumeBackups []*velerov1.PodVolumeBackup
|
||||
}
|
||||
|
||||
func (b *fakeResticBackupper) BackupPodVolumes(backup *velerov1.Backup, pod *corev1.Pod, _ logrus.FieldLogger) ([]*velerov1.PodVolumeBackup, []error) {
|
||||
return b.podVolumeBackups, nil
|
||||
}
|
||||
|
||||
// TestBackupWithRestic runs backups of pods that are annotated for restic backup,
|
||||
// and ensures that the restic backupper is called, that the returned PodVolumeBackups
|
||||
// are added to the Request object, and that when PVCs are backed up with restic, the
|
||||
// claimed PVs are not also snapshotted using a VolumeSnapshotter.
|
||||
func TestBackupWithRestic(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
backup *velerov1.Backup
|
||||
apiResources []*test.APIResource
|
||||
vsl *velerov1.VolumeSnapshotLocation
|
||||
snapshotterGetter volumeSnapshotterGetter
|
||||
want []*velerov1.PodVolumeBackup
|
||||
}{
|
||||
{
|
||||
name: "a pod annotated for restic backup should result in pod volume backups being returned",
|
||||
backup: defaultBackup().Backup(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.Pods(
|
||||
test.NewPod("ns-1", "pod-1", test.WithAnnotations("backup.velero.io/backup-volumes", "foo")),
|
||||
),
|
||||
},
|
||||
want: []*velerov1.PodVolumeBackup{
|
||||
NewNamedPodVolumeBackupBuilder("velero", "pvb-1").PodVolumeBackup(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "when PVC pod volumes are backed up using restic, their claimed PVs are not also snapshotted",
|
||||
backup: defaultBackup().Backup(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.Pods(
|
||||
test.NewPod("ns-1", "pod-1",
|
||||
test.WithAnnotations("backup.velero.io/backup-volumes", "vol-1,vol-2"),
|
||||
test.WithVolume(test.NewVolume("vol-1", test.WithPVCSource("pvc-1"))),
|
||||
test.WithVolume(test.NewVolume("vol-2", test.WithPVCSource("pvc-2"))),
|
||||
),
|
||||
),
|
||||
test.PVCs(
|
||||
test.NewPVC("ns-1", "pvc-1", test.WithPVName("pv-1")),
|
||||
test.NewPVC("ns-1", "pvc-2", test.WithPVName("pv-2")),
|
||||
),
|
||||
test.PVs(
|
||||
test.NewPV("pv-1", test.WithClaimRef("ns-1", "pvc-1")),
|
||||
test.NewPV("pv-2", test.WithClaimRef("ns-1", "pvc-2")),
|
||||
),
|
||||
},
|
||||
vsl: newSnapshotLocation("velero", "default", "default"),
|
||||
snapshotterGetter: map[string]velero.VolumeSnapshotter{
|
||||
"default": new(fakeVolumeSnapshotter).
|
||||
WithVolume("pv-1", "vol-1", "", "type-1", 100, false).
|
||||
WithVolume("pv-2", "vol-2", "", "type-1", 100, false),
|
||||
},
|
||||
want: []*velerov1.PodVolumeBackup{
|
||||
NewNamedPodVolumeBackupBuilder("velero", "pvb-1").PodVolumeBackup(),
|
||||
NewNamedPodVolumeBackupBuilder("velero", "pvb-2").PodVolumeBackup(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var (
|
||||
h = newHarness(t)
|
||||
req = &Request{Backup: tc.backup, SnapshotLocations: []*velerov1.VolumeSnapshotLocation{tc.vsl}}
|
||||
backupFile = bytes.NewBuffer([]byte{})
|
||||
)
|
||||
|
||||
h.backupper.resticBackupperFactory = &fakeResticBackupperFactory{
|
||||
podVolumeBackups: tc.want,
|
||||
}
|
||||
|
||||
for _, resource := range tc.apiResources {
|
||||
h.addItems(t, resource)
|
||||
}
|
||||
|
||||
require.NoError(t, h.backupper.Backup(h.log, req, backupFile, nil, tc.snapshotterGetter))
|
||||
|
||||
assert.Equal(t, tc.want, req.PodVolumeBackups)
|
||||
|
||||
// this assumes that we don't have any test cases where some PVs should be snapshotted using a VolumeSnapshotter
|
||||
assert.Nil(t, req.VolumeSnapshots)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// pluggableAction is a backup item action that can be plugged with an Execute
|
||||
// function body at runtime.
|
||||
type pluggableAction struct {
|
|
@ -1,288 +0,0 @@
|
|||
/*
|
||||
Copyright 2017, 2019 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 backup
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
|
||||
"github.com/heptio/velero/pkg/plugin/velero"
|
||||
"github.com/heptio/velero/pkg/util/collections"
|
||||
velerotest "github.com/heptio/velero/pkg/util/test"
|
||||
)
|
||||
|
||||
func TestBackupItemNoSkips(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
item string
|
||||
namespaceIncludesExcludes *collections.IncludesExcludes
|
||||
expectError bool
|
||||
expectExcluded bool
|
||||
expectedTarHeaderName string
|
||||
tarWriteError bool
|
||||
tarHeaderWriteError bool
|
||||
groupResource string
|
||||
snapshottableVolumes map[string]velerotest.VolumeBackupInfo
|
||||
snapshotError error
|
||||
trackedPVCs sets.String
|
||||
expectedTrackedPVCs sets.String
|
||||
}{
|
||||
{
|
||||
name: "tar header write error",
|
||||
item: `{"metadata":{"name":"bar"},"spec":{"color":"green"},"status":{"foo":"bar"}}`,
|
||||
expectError: true,
|
||||
tarHeaderWriteError: true,
|
||||
},
|
||||
{
|
||||
name: "tar write error",
|
||||
item: `{"metadata":{"name":"bar"},"spec":{"color":"green"},"status":{"foo":"bar"}}`,
|
||||
expectError: true,
|
||||
tarWriteError: true,
|
||||
},
|
||||
{
|
||||
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 volumeSnapshotter to be created, but no
|
||||
// snapshots are expected to be taken.
|
||||
snapshottableVolumes: map[string]velerotest.VolumeBackupInfo{},
|
||||
trackedPVCs: sets.NewString(key("pvc-ns", "pvc"), key("another-pvc-ns", "another-pvc")),
|
||||
},
|
||||
{
|
||||
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.velero.io/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 {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
backup = new(Request)
|
||||
groupResource = schema.ParseGroupResource("resource.group")
|
||||
backedUpItems = make(map[itemKey]struct{})
|
||||
w = &fakeTarWriter{}
|
||||
)
|
||||
|
||||
backup.Backup = new(velerov1api.Backup)
|
||||
backup.NamespaceIncludesExcludes = collections.NewIncludesExcludes()
|
||||
backup.ResourceIncludesExcludes = collections.NewIncludesExcludes()
|
||||
backup.SnapshotLocations = []*velerov1api.VolumeSnapshotLocation{
|
||||
newSnapshotLocation("velero", "default", "default"),
|
||||
}
|
||||
|
||||
if test.groupResource != "" {
|
||||
groupResource = schema.ParseGroupResource(test.groupResource)
|
||||
}
|
||||
|
||||
item, err := velerotest.GetAsMap(test.item)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
namespaces := test.namespaceIncludesExcludes
|
||||
if namespaces == nil {
|
||||
namespaces = collections.NewIncludesExcludes()
|
||||
}
|
||||
|
||||
if test.tarHeaderWriteError {
|
||||
w.writeHeaderError = errors.New("error")
|
||||
}
|
||||
if test.tarWriteError {
|
||||
w.writeError = errors.New("error")
|
||||
}
|
||||
|
||||
podCommandExecutor := &velerotest.MockPodCommandExecutor{}
|
||||
defer podCommandExecutor.AssertExpectations(t)
|
||||
|
||||
dynamicFactory := &velerotest.FakeDynamicFactory{}
|
||||
defer dynamicFactory.AssertExpectations(t)
|
||||
|
||||
discoveryHelper := velerotest.NewFakeDiscoveryHelper(true, nil)
|
||||
|
||||
volumeSnapshotterGetter := make(volumeSnapshotterGetter)
|
||||
|
||||
b := (&defaultItemBackupperFactory{}).newItemBackupper(
|
||||
backup,
|
||||
backedUpItems,
|
||||
podCommandExecutor,
|
||||
w,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
volumeSnapshotterGetter,
|
||||
).(*defaultItemBackupper)
|
||||
|
||||
var volumeSnapshotter *velerotest.FakeVolumeSnapshotter
|
||||
if test.snapshottableVolumes != nil {
|
||||
volumeSnapshotter = &velerotest.FakeVolumeSnapshotter{
|
||||
SnapshottableVolumes: test.snapshottableVolumes,
|
||||
VolumeID: "vol-abc123",
|
||||
Error: test.snapshotError,
|
||||
}
|
||||
|
||||
volumeSnapshotterGetter["default"] = volumeSnapshotter
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
itemHookHandler := &mockItemHookHandler{}
|
||||
defer itemHookHandler.AssertExpectations(t)
|
||||
b.itemHookHandler = itemHookHandler
|
||||
|
||||
obj := &unstructured.Unstructured{Object: item}
|
||||
itemHookHandler.On("handleHooks", mock.Anything, groupResource, obj, backup.ResourceHooks, hookPhasePre).Return(nil)
|
||||
itemHookHandler.On("handleHooks", mock.Anything, groupResource, obj, backup.ResourceHooks, hookPhasePost).Return(nil)
|
||||
|
||||
err = b.backupItem(velerotest.NewLogger(), obj, groupResource)
|
||||
gotError := err != nil
|
||||
if e, a := test.expectError, gotError; e != a {
|
||||
t.Fatalf("error: expected %t, got %t: %v", e, a, err)
|
||||
}
|
||||
if test.expectError {
|
||||
return
|
||||
}
|
||||
|
||||
if test.expectExcluded {
|
||||
if len(w.headers) > 0 {
|
||||
t.Errorf("unexpected header write")
|
||||
}
|
||||
if len(w.data) > 0 {
|
||||
t.Errorf("unexpected data write")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to JSON for comparing number of bytes to the tar header
|
||||
itemJSON, err := json.Marshal(&item)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.Equal(t, 1, len(w.headers), "headers")
|
||||
assert.Equal(t, test.expectedTarHeaderName, w.headers[0].Name, "header.name")
|
||||
assert.Equal(t, int64(len(itemJSON)), w.headers[0].Size, "header.size")
|
||||
assert.Equal(t, byte(tar.TypeReg), w.headers[0].Typeflag, "header.typeflag")
|
||||
assert.Equal(t, int64(0755), w.headers[0].Mode, "header.mode")
|
||||
assert.False(t, w.headers[0].ModTime.IsZero(), "header.modTime set")
|
||||
assert.Equal(t, 1, len(w.data), "# of data")
|
||||
|
||||
actual, err := velerotest.GetAsMap(string(w.data[0]))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if e, a := item, actual; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("data: expected %s, got %s", e, a)
|
||||
}
|
||||
|
||||
if test.snapshottableVolumes != nil {
|
||||
require.Equal(t, len(test.snapshottableVolumes), len(volumeSnapshotter.SnapshotsTaken))
|
||||
}
|
||||
|
||||
if len(test.snapshottableVolumes) > 0 {
|
||||
require.Len(t, backup.VolumeSnapshots, 1)
|
||||
snapshot := backup.VolumeSnapshots[0]
|
||||
|
||||
assert.Equal(t, test.snapshottableVolumes["vol-abc123"].SnapshotID, snapshot.Status.ProviderSnapshotID)
|
||||
assert.Equal(t, test.snapshottableVolumes["vol-abc123"].Type, snapshot.Spec.VolumeType)
|
||||
assert.Equal(t, test.snapshottableVolumes["vol-abc123"].Iops, snapshot.Spec.VolumeIOPS)
|
||||
assert.Equal(t, test.snapshottableVolumes["vol-abc123"].AvailabilityZone, snapshot.Spec.VolumeAZ)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type addAnnotationAction struct{}
|
||||
|
||||
func (a *addAnnotationAction) Execute(item runtime.Unstructured, backup *velerov1api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) {
|
||||
// since item actions run out-of-proc, do a deep-copy here to simulate passing data
|
||||
// across a process boundary.
|
||||
copy := item.(*unstructured.Unstructured).DeepCopy()
|
||||
|
||||
metadata, err := meta.Accessor(copy)
|
||||
if err != nil {
|
||||
return copy, nil, nil
|
||||
}
|
||||
|
||||
annotations := metadata.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
annotations["foo"] = "bar"
|
||||
metadata.SetAnnotations(annotations)
|
||||
|
||||
return copy, nil, nil
|
||||
}
|
||||
|
||||
func (a *addAnnotationAction) AppliesTo() (velero.ResourceSelector, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
type fakeTarWriter struct {
|
||||
closeCalled bool
|
||||
headers []*tar.Header
|
||||
data [][]byte
|
||||
writeHeaderError error
|
||||
writeError error
|
||||
}
|
||||
|
||||
func (w *fakeTarWriter) Close() error { return nil }
|
||||
|
||||
func (w *fakeTarWriter) Write(data []byte) (int, error) {
|
||||
w.data = append(w.data, data)
|
||||
return 0, w.writeError
|
||||
}
|
||||
|
||||
func (w *fakeTarWriter) WriteHeader(header *tar.Header) error {
|
||||
w.headers = append(w.headers, header)
|
||||
return w.writeHeaderError
|
||||
}
|
Loading…
Reference in New Issue