migrate PV snapshot tests
Signed-off-by: Steve Kriss <krisss@vmware.com>pull/1574/head
parent
6513e8f30e
commit
e2bf39a027
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue