migrate PV snapshot tests

Signed-off-by: Steve Kriss <krisss@vmware.com>
pull/1574/head
Steve Kriss 2019-06-04 13:31:34 -06:00
parent 6513e8f30e
commit e2bf39a027
2 changed files with 394 additions and 199 deletions

View File

@ -43,7 +43,6 @@ import (
dynamicfake "k8s.io/client-go/dynamic/fake"
kubefake "k8s.io/client-go/kubernetes/fake"
v1 "github.com/heptio/velero/pkg/apis/velero/v1"
velerov1 "github.com/heptio/velero/pkg/apis/velero/v1"
"github.com/heptio/velero/pkg/client"
"github.com/heptio/velero/pkg/discovery"
@ -52,6 +51,7 @@ import (
"github.com/heptio/velero/pkg/plugin/velero"
"github.com/heptio/velero/pkg/test"
kubeutil "github.com/heptio/velero/pkg/util/kube"
"github.com/heptio/velero/pkg/volume"
)
// TestBackupResourceFiltering runs backups with different combinations
@ -627,11 +627,11 @@ func TestBackupResourceOrdering(t *testing.T) {
type recordResourcesAction struct {
selector velero.ResourceSelector
ids []string
backups []v1.Backup
backups []velerov1.Backup
additionalItems []velero.ResourceIdentifier
}
func (a *recordResourcesAction) Execute(item runtime.Unstructured, backup *v1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) {
func (a *recordResourcesAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) {
metadata, err := meta.Accessor(item)
if err != nil {
return item, a.additionalItems, err
@ -1254,6 +1254,382 @@ func TestBackupActionAdditionalItems(t *testing.T) {
}
}
// volumeSnapshotterGetter is a simple implementation of the VolumeSnapshotterGetter
// interface that returns velero.VolumeSnapshotters from a map if they exist.
type volumeSnapshotterGetter map[string]velero.VolumeSnapshotter
func (vsg volumeSnapshotterGetter) GetVolumeSnapshotter(name string) (velero.VolumeSnapshotter, error) {
snapshotter, ok := vsg[name]
if !ok {
return nil, errors.New("volume snapshotter not found")
}
return snapshotter, nil
}
func int64Ptr(val int) *int64 {
i := int64(val)
return &i
}
type volumeIdentifier struct {
volumeID string
volumeAZ string
}
type volumeInfo struct {
volumeType string
iops *int64
snapshotErr bool
}
// fakeVolumeSnapshotter is a test fake for the velero.VolumeSnapshotter interface.
type fakeVolumeSnapshotter struct {
// PVVolumeNames is a map from PV name to volume ID, used as the basis
// for the GetVolumeID method.
PVVolumeNames map[string]string
// Volumes is a map from volume identifier (volume ID + AZ) to a struct
// of volume info, used for the GetVolumeInfo and CreateSnapshot methods.
Volumes map[volumeIdentifier]*volumeInfo
}
// WithVolume is a test helper for registering persistent volumes that the
// fakeVolumeSnapshotter should handle.
func (vs *fakeVolumeSnapshotter) WithVolume(pvName, id, az, volumeType string, iops int, snapshotErr bool) *fakeVolumeSnapshotter {
if vs.PVVolumeNames == nil {
vs.PVVolumeNames = make(map[string]string)
}
vs.PVVolumeNames[pvName] = id
if vs.Volumes == nil {
vs.Volumes = make(map[volumeIdentifier]*volumeInfo)
}
identifier := volumeIdentifier{
volumeID: id,
volumeAZ: az,
}
vs.Volumes[identifier] = &volumeInfo{
volumeType: volumeType,
iops: int64Ptr(iops),
snapshotErr: snapshotErr,
}
return vs
}
// Init is a no-op.
func (*fakeVolumeSnapshotter) Init(config map[string]string) error {
return nil
}
// GetVolumeID looks up the PV name in the PVVolumeNames map and returns the result
// if found, or an error otherwise.
func (vs *fakeVolumeSnapshotter) GetVolumeID(pv runtime.Unstructured) (string, error) {
obj := pv.(*unstructured.Unstructured)
volumeID, ok := vs.PVVolumeNames[obj.GetName()]
if !ok {
return "", errors.New("unsupported volume type")
}
return volumeID, nil
}
// CreateSnapshot looks up the volume in the Volume map. If it's not found, an error is
// returned; if snapshotErr is true on the result, an error is returned; otherwise,
// a snapshotID of "<volumeID>-snapshot" is returned.
func (vs *fakeVolumeSnapshotter) CreateSnapshot(volumeID, volumeAZ string, tags map[string]string) (snapshotID string, err error) {
vi, ok := vs.Volumes[volumeIdentifier{volumeID: volumeID, volumeAZ: volumeAZ}]
if !ok {
return "", errors.New("volume not found")
}
if vi.snapshotErr {
return "", errors.New("error calling CreateSnapshot")
}
return volumeID + "-snapshot", nil
}
// GetVolumeInfo returns volume info if it exists in the Volumes map
// for the specified volume ID and AZ, or an error otherwise.
func (vs *fakeVolumeSnapshotter) GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) {
vi, ok := vs.Volumes[volumeIdentifier{volumeID: volumeID, volumeAZ: volumeAZ}]
if !ok {
return "", nil, errors.New("volume not found")
}
return vi.volumeType, vi.iops, nil
}
// CreateVolumeFromSnapshot panics because it's not expected to be used for backups.
func (*fakeVolumeSnapshotter) CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ string, iops *int64) (volumeID string, err error) {
panic("CreateVolumeFromSnapshot should not be used for backups")
}
// SetVolumeID panics because it's not expected to be used for backups.
func (*fakeVolumeSnapshotter) SetVolumeID(pv runtime.Unstructured, volumeID string) (runtime.Unstructured, error) {
panic("SetVolumeID should not be used for backups")
}
// DeleteSnapshot panics because it's not expected to be used for backups.
func (*fakeVolumeSnapshotter) DeleteSnapshot(snapshotID string) error {
panic("DeleteSnapshot should not be used for backups")
}
// TestBackupWithSnapshots runs backups with volume snapshot locations and volume snapshotters
// configured and verifies that snapshots are created as appropriate. Verification is done by
// looking at the backup request's VolumeSnapshots field. This test uses the fakeVolumeSnapshotter
// struct in place of real volume snapshotters.
func TestBackupWithSnapshots(t *testing.T) {
tests := []struct {
name string
req *Request
vsls []*velerov1.VolumeSnapshotLocation
apiResources []*apiResource
snapshotterGetter volumeSnapshotterGetter
want []*volume.Snapshot
}{
{
name: "persistent volume with no zone annotation creates a snapshot",
req: &Request{
Backup: defaultBackup().Backup(),
SnapshotLocations: []*velerov1.VolumeSnapshotLocation{
newSnapshotLocation("velero", "default", "default"),
},
},
apiResources: []*apiResource{
pvs(
newPV("pv-1"),
),
},
snapshotterGetter: map[string]velero.VolumeSnapshotter{
"default": new(fakeVolumeSnapshotter).WithVolume("pv-1", "vol-1", "", "type-1", 100, false),
},
want: []*volume.Snapshot{
{
Spec: volume.SnapshotSpec{
BackupName: "backup-1",
Location: "default",
PersistentVolumeName: "pv-1",
ProviderVolumeID: "vol-1",
VolumeType: "type-1",
VolumeIOPS: int64Ptr(100),
},
Status: volume.SnapshotStatus{
Phase: volume.SnapshotPhaseCompleted,
ProviderSnapshotID: "vol-1-snapshot",
},
},
},
},
{
name: "persistent volume with zone annotation creates a snapshot",
req: &Request{
Backup: defaultBackup().Backup(),
SnapshotLocations: []*velerov1.VolumeSnapshotLocation{
newSnapshotLocation("velero", "default", "default"),
},
},
apiResources: []*apiResource{
pvs(
withLabel(newPV("pv-1"), "failure-domain.beta.kubernetes.io/zone", "zone-1"),
),
},
snapshotterGetter: map[string]velero.VolumeSnapshotter{
"default": new(fakeVolumeSnapshotter).WithVolume("pv-1", "vol-1", "zone-1", "type-1", 100, false),
},
want: []*volume.Snapshot{
{
Spec: volume.SnapshotSpec{
BackupName: "backup-1",
Location: "default",
PersistentVolumeName: "pv-1",
ProviderVolumeID: "vol-1",
VolumeAZ: "zone-1",
VolumeType: "type-1",
VolumeIOPS: int64Ptr(100),
},
Status: volume.SnapshotStatus{
Phase: volume.SnapshotPhaseCompleted,
ProviderSnapshotID: "vol-1-snapshot",
},
},
},
},
{
name: "error returned from CreateSnapshot results in a failed snapshot",
req: &Request{
Backup: defaultBackup().Backup(),
SnapshotLocations: []*velerov1.VolumeSnapshotLocation{
newSnapshotLocation("velero", "default", "default"),
},
},
apiResources: []*apiResource{
pvs(
newPV("pv-1"),
),
},
snapshotterGetter: map[string]velero.VolumeSnapshotter{
"default": new(fakeVolumeSnapshotter).WithVolume("pv-1", "vol-1", "", "type-1", 100, true),
},
want: []*volume.Snapshot{
{
Spec: volume.SnapshotSpec{
BackupName: "backup-1",
Location: "default",
PersistentVolumeName: "pv-1",
ProviderVolumeID: "vol-1",
VolumeType: "type-1",
VolumeIOPS: int64Ptr(100),
},
Status: volume.SnapshotStatus{
Phase: volume.SnapshotPhaseFailed,
},
},
},
},
{
name: "backup with SnapshotVolumes=false does not create any snapshots",
req: &Request{
Backup: defaultBackup().SnapshotVolumes(false).Backup(),
SnapshotLocations: []*velerov1.VolumeSnapshotLocation{
newSnapshotLocation("velero", "default", "default"),
},
},
apiResources: []*apiResource{
pvs(
newPV("pv-1"),
),
},
snapshotterGetter: map[string]velero.VolumeSnapshotter{
"default": new(fakeVolumeSnapshotter).WithVolume("pv-1", "vol-1", "", "type-1", 100, false),
},
want: nil,
},
{
name: "backup with no volume snapshot locations does not create any snapshots",
req: &Request{
Backup: defaultBackup().Backup(),
},
apiResources: []*apiResource{
pvs(
newPV("pv-1"),
),
},
snapshotterGetter: map[string]velero.VolumeSnapshotter{
"default": new(fakeVolumeSnapshotter).WithVolume("pv-1", "vol-1", "", "type-1", 100, false),
},
want: nil,
},
{
name: "backup with no volume snapshotters does not create any snapshots",
req: &Request{
Backup: defaultBackup().Backup(),
SnapshotLocations: []*velerov1.VolumeSnapshotLocation{
newSnapshotLocation("velero", "default", "default"),
},
},
apiResources: []*apiResource{
pvs(
newPV("pv-1"),
),
},
snapshotterGetter: map[string]velero.VolumeSnapshotter{},
want: nil,
},
{
name: "unsupported persistent volume type does not create any snapshots",
req: &Request{
Backup: defaultBackup().Backup(),
SnapshotLocations: []*velerov1.VolumeSnapshotLocation{
newSnapshotLocation("velero", "default", "default"),
},
},
apiResources: []*apiResource{
pvs(
newPV("pv-1"),
),
},
snapshotterGetter: map[string]velero.VolumeSnapshotter{
"default": new(fakeVolumeSnapshotter),
},
want: nil,
},
{
name: "when there are multiple volumes, snapshot locations, and snapshotters, volumes are matched to the right snapshotters",
req: &Request{
Backup: defaultBackup().Backup(),
SnapshotLocations: []*velerov1.VolumeSnapshotLocation{
newSnapshotLocation("velero", "default", "default"),
newSnapshotLocation("velero", "another", "another"),
},
},
apiResources: []*apiResource{
pvs(
newPV("pv-1"),
newPV("pv-2"),
),
},
snapshotterGetter: map[string]velero.VolumeSnapshotter{
"default": new(fakeVolumeSnapshotter).WithVolume("pv-1", "vol-1", "", "type-1", 100, false),
"another": new(fakeVolumeSnapshotter).WithVolume("pv-2", "vol-2", "", "type-2", 100, false),
},
want: []*volume.Snapshot{
{
Spec: volume.SnapshotSpec{
BackupName: "backup-1",
Location: "default",
PersistentVolumeName: "pv-1",
ProviderVolumeID: "vol-1",
VolumeType: "type-1",
VolumeIOPS: int64Ptr(100),
},
Status: volume.SnapshotStatus{
Phase: volume.SnapshotPhaseCompleted,
ProviderSnapshotID: "vol-1-snapshot",
},
},
{
Spec: volume.SnapshotSpec{
BackupName: "backup-1",
Location: "another",
PersistentVolumeName: "pv-2",
ProviderVolumeID: "vol-2",
VolumeType: "type-2",
VolumeIOPS: int64Ptr(100),
},
Status: volume.SnapshotStatus{
Phase: volume.SnapshotPhaseCompleted,
ProviderSnapshotID: "vol-2-snapshot",
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var (
h = newHarness(t)
backupFile = bytes.NewBuffer([]byte{})
)
for _, resource := range tc.apiResources {
h.addItems(t, resource.group, resource.version, resource.name, resource.shortName, resource.namespaced, resource.items...)
}
err := h.backupper.Backup(h.log, tc.req, backupFile, nil, tc.snapshotterGetter)
assert.NoError(t, err)
assert.Equal(t, tc.want, tc.req.VolumeSnapshots)
})
}
}
// pluggableAction is a backup item action that can be plugged with an Execute
// function body at runtime.
type pluggableAction struct {
@ -1474,6 +1850,18 @@ func newPV(name string) *corev1.PersistentVolume {
}
}
func newSnapshotLocation(ns, name, provider string) *velerov1.VolumeSnapshotLocation {
return &velerov1.VolumeSnapshotLocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
},
Spec: velerov1.VolumeSnapshotLocationSpec{
Provider: provider,
},
}
}
func defaultBackup() *Builder {
return NewNamedBuilder(velerov1.DefaultNamespace, "backup-1")
}

View File

@ -19,17 +19,14 @@ package backup
import (
"archive/tar"
"encoding/json"
"fmt"
"reflect"
"testing"
"time"
"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"
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"
@ -71,27 +68,6 @@ func TestBackupItemNoSkips(t *testing.T) {
expectError: true,
tarWriteError: true,
},
{
name: "takePVSnapshot is not invoked for PVs when volumeSnapshotter == nil",
namespaceIncludesExcludes: collections.NewIncludesExcludes().Includes("*"),
item: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/zone": "us-east-1c"}}, "spec": {"awsElasticBlockStore": {"volumeID": "aws://us-east-1c/vol-abc123"}}}`,
expectError: false,
expectExcluded: false,
expectedTarHeaderName: "resources/persistentvolumes/cluster/mypv.json",
groupResource: "persistentvolumes",
},
{
name: "takePVSnapshot is invoked for PVs when volumeSnapshotter != nil",
namespaceIncludesExcludes: collections.NewIncludesExcludes().Includes("*"),
item: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/zone": "us-east-1c"}}, "spec": {"awsElasticBlockStore": {"volumeID": "aws://us-east-1c/vol-abc123"}}}`,
expectError: false,
expectExcluded: false,
expectedTarHeaderName: "resources/persistentvolumes/cluster/mypv.json",
groupResource: "persistentvolumes",
snapshottableVolumes: map[string]velerotest.VolumeBackupInfo{
"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("*"),
@ -105,30 +81,6 @@ func TestBackupItemNoSkips(t *testing.T) {
snapshottableVolumes: map[string]velerotest.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]velerotest.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("*"),
item: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/zone": "us-east-1c"}}, "spec": {"awsElasticBlockStore": {"volumeID": "aws://us-east-1c/vol-abc123"}}}`,
expectError: true,
groupResource: "persistentvolumes",
snapshottableVolumes: map[string]velerotest.VolumeBackupInfo{
"vol-abc123": {SnapshotID: "snapshot-1", AvailabilityZone: "us-east-1c"},
},
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.velero.io/backup-volumes": "volume-1,volume-2"}}}`,
@ -154,7 +106,7 @@ func TestBackupItemNoSkips(t *testing.T) {
backup.NamespaceIncludesExcludes = collections.NewIncludesExcludes()
backup.ResourceIncludesExcludes = collections.NewIncludesExcludes()
backup.SnapshotLocations = []*v1.VolumeSnapshotLocation{
new(v1.VolumeSnapshotLocation),
newSnapshotLocation("velero", "default", "default"),
}
if test.groupResource != "" {
@ -186,7 +138,7 @@ func TestBackupItemNoSkips(t *testing.T) {
discoveryHelper := velerotest.NewFakeDiscoveryHelper(true, nil)
volumeSnapshotterGetter := &volumeSnapshotterGetter{}
volumeSnapshotterGetter := make(volumeSnapshotterGetter)
b := (&defaultItemBackupperFactory{}).newItemBackupper(
backup,
@ -208,7 +160,7 @@ func TestBackupItemNoSkips(t *testing.T) {
Error: test.snapshotError,
}
volumeSnapshotterGetter.volumeSnapshotter = volumeSnapshotter
volumeSnapshotterGetter["default"] = volumeSnapshotter
}
if test.trackedPVCs != nil {
@ -291,17 +243,6 @@ func TestBackupItemNoSkips(t *testing.T) {
}
}
type volumeSnapshotterGetter struct {
volumeSnapshotter velero.VolumeSnapshotter
}
func (b *volumeSnapshotterGetter) GetVolumeSnapshotter(name string) (velero.VolumeSnapshotter, error) {
if b.volumeSnapshotter != nil {
return b.volumeSnapshotter, nil
}
return nil, errors.New("plugin not found")
}
type addAnnotationAction struct{}
func (a *addAnnotationAction) Execute(item runtime.Unstructured, backup *v1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) {
@ -396,140 +337,6 @@ func TestResticAnnotationsPersist(t *testing.T) {
assert.EqualValues(t, expected.Object, actual)
}
func TestTakePVSnapshot(t *testing.T) {
iops := int64(1000)
tests := []struct {
name string
snapshotEnabled bool
pv string
ttl time.Duration
expectError bool
expectedVolumeID string
expectedSnapshotsTaken int
volumeInfo map[string]velerotest.VolumeBackupInfo
}{
{
name: "snapshot disabled",
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}}`,
snapshotEnabled: false,
},
{
name: "unsupported PV source type",
snapshotEnabled: true,
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"unsupportedPVSource": {}}}`,
expectError: false,
},
{
name: "without iops",
snapshotEnabled: true,
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/zone": "us-east-1c"}}, "spec": {"awsElasticBlockStore": {"volumeID": "aws://us-east-1c/vol-abc123"}}}`,
expectError: false,
expectedSnapshotsTaken: 1,
expectedVolumeID: "vol-abc123",
ttl: 5 * time.Minute,
volumeInfo: map[string]velerotest.VolumeBackupInfo{
"vol-abc123": {Type: "gp", SnapshotID: "snap-1", AvailabilityZone: "us-east-1c"},
},
},
{
name: "with iops",
snapshotEnabled: true,
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/zone": "us-east-1c"}}, "spec": {"awsElasticBlockStore": {"volumeID": "aws://us-east-1c/vol-abc123"}}}`,
expectError: false,
expectedSnapshotsTaken: 1,
expectedVolumeID: "vol-abc123",
ttl: 5 * time.Minute,
volumeInfo: map[string]velerotest.VolumeBackupInfo{
"vol-abc123": {Type: "io1", Iops: &iops, SnapshotID: "snap-1", AvailabilityZone: "us-east-1c"},
},
},
{
name: "create snapshot error",
snapshotEnabled: true,
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"gcePersistentDisk": {"pdName": "pd-abc123"}}}`,
expectedVolumeID: "pd-abc123",
expectError: true,
},
{
name: "PV with label metadata but no failureDomainZone",
snapshotEnabled: true,
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv", "labels": {"failure-domain.beta.kubernetes.io/region": "us-east-1"}}, "spec": {"awsElasticBlockStore": {"volumeID": "aws://us-east-1c/vol-abc123"}}}`,
expectError: false,
expectedSnapshotsTaken: 1,
expectedVolumeID: "vol-abc123",
ttl: 5 * time.Minute,
volumeInfo: map[string]velerotest.VolumeBackupInfo{
"vol-abc123": {Type: "gp", SnapshotID: "snap-1"},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
backup := &v1.Backup{
ObjectMeta: metav1.ObjectMeta{
Namespace: v1.DefaultNamespace,
Name: "mybackup",
},
Spec: v1.BackupSpec{
SnapshotVolumes: &test.snapshotEnabled,
TTL: metav1.Duration{Duration: test.ttl},
},
}
volumeSnapshotter := &velerotest.FakeVolumeSnapshotter{
SnapshottableVolumes: test.volumeInfo,
VolumeID: test.expectedVolumeID,
}
ib := &defaultItemBackupper{
backupRequest: &Request{
Backup: backup,
SnapshotLocations: []*v1.VolumeSnapshotLocation{new(v1.VolumeSnapshotLocation)},
},
volumeSnapshotterGetter: &volumeSnapshotterGetter{volumeSnapshotter: volumeSnapshotter},
}
pv, err := velerotest.GetAsMap(test.pv)
if err != nil {
t.Fatal(err)
}
// method under test
err = ib.takePVSnapshot(&unstructured.Unstructured{Object: pv}, velerotest.NewLogger())
gotErr := err != nil
if e, a := test.expectError, gotErr; e != a {
t.Errorf("error: expected %v, got %v", e, a)
}
if test.expectError {
return
}
if !test.snapshotEnabled {
// don't need to check anything else if snapshots are disabled
return
}
// we should have exactly one snapshot taken
require.Equal(t, test.expectedSnapshotsTaken, volumeSnapshotter.SnapshotsTaken.Len())
if test.expectedSnapshotsTaken > 0 {
require.Len(t, ib.backupRequest.VolumeSnapshots, 1)
snapshot := ib.backupRequest.VolumeSnapshots[0]
snapshotID, _ := volumeSnapshotter.SnapshotsTaken.PopAny()
assert.Equal(t, snapshotID, snapshot.Status.ProviderSnapshotID)
assert.Equal(t, test.volumeInfo[test.expectedVolumeID].Type, snapshot.Spec.VolumeType)
assert.Equal(t, test.volumeInfo[test.expectedVolumeID].Iops, snapshot.Spec.VolumeIOPS)
assert.Equal(t, test.volumeInfo[test.expectedVolumeID].AvailabilityZone, snapshot.Spec.VolumeAZ)
}
})
}
}
type fakeTarWriter struct {
closeCalled bool
headers []*tar.Header