mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-19 06:26:44 +00:00
update backup code to work with volume snapshot locations
Signed-off-by: Steve Kriss <steve@heptio.com>
This commit is contained in:
+22
-26
@@ -46,7 +46,7 @@ import (
|
||||
type Backupper interface {
|
||||
// Backup takes a backup using the specification in the api.Backup and writes backup and log data
|
||||
// to the given writers.
|
||||
Backup(logger logrus.FieldLogger, backup *api.Backup, backupFile io.Writer, actions []ItemAction) error
|
||||
Backup(logger logrus.FieldLogger, backup *Request, backupFile io.Writer, actions []ItemAction, blockStoreGetter BlockStoreGetter) error
|
||||
}
|
||||
|
||||
// kubernetesBackupper implements Backupper.
|
||||
@@ -55,7 +55,6 @@ type kubernetesBackupper struct {
|
||||
discoveryHelper discovery.Helper
|
||||
podCommandExecutor podexec.PodCommandExecutor
|
||||
groupBackupperFactory groupBackupperFactory
|
||||
blockStore cloudprovider.BlockStore
|
||||
resticBackupperFactory restic.BackupperFactory
|
||||
resticTimeout time.Duration
|
||||
}
|
||||
@@ -93,7 +92,6 @@ func NewKubernetesBackupper(
|
||||
discoveryHelper discovery.Helper,
|
||||
dynamicFactory client.DynamicFactory,
|
||||
podCommandExecutor podexec.PodCommandExecutor,
|
||||
blockStore cloudprovider.BlockStore,
|
||||
resticBackupperFactory restic.BackupperFactory,
|
||||
resticTimeout time.Duration,
|
||||
) (Backupper, error) {
|
||||
@@ -102,7 +100,6 @@ func NewKubernetesBackupper(
|
||||
dynamicFactory: dynamicFactory,
|
||||
podCommandExecutor: podCommandExecutor,
|
||||
groupBackupperFactory: &defaultGroupBackupperFactory{},
|
||||
blockStore: blockStore,
|
||||
resticBackupperFactory: resticBackupperFactory,
|
||||
resticTimeout: resticTimeout,
|
||||
}, nil
|
||||
@@ -209,41 +206,43 @@ func getResourceHook(hookSpec api.BackupResourceHookSpec, discoveryHelper discov
|
||||
return h, nil
|
||||
}
|
||||
|
||||
type BlockStoreGetter interface {
|
||||
GetBlockStore(name string) (cloudprovider.BlockStore, error)
|
||||
}
|
||||
|
||||
// Backup backs up the items specified in the Backup, placing them in a gzip-compressed tar file
|
||||
// written to backupFile. The finalized api.Backup is written to metadata.
|
||||
func (kb *kubernetesBackupper) Backup(logger logrus.FieldLogger, backup *api.Backup, backupFile io.Writer, actions []ItemAction) error {
|
||||
func (kb *kubernetesBackupper) Backup(logger logrus.FieldLogger, backupRequest *Request, backupFile io.Writer, actions []ItemAction, blockStoreGetter BlockStoreGetter) error {
|
||||
gzippedData := gzip.NewWriter(backupFile)
|
||||
defer gzippedData.Close()
|
||||
|
||||
tw := tar.NewWriter(gzippedData)
|
||||
defer tw.Close()
|
||||
|
||||
log := logger.WithField("backup", kubeutil.NamespaceAndName(backup))
|
||||
log := logger.WithField("backup", kubeutil.NamespaceAndName(backupRequest))
|
||||
log.Info("Starting backup")
|
||||
|
||||
namespaceIncludesExcludes := getNamespaceIncludesExcludes(backup)
|
||||
log.Infof("Including namespaces: %s", namespaceIncludesExcludes.IncludesString())
|
||||
log.Infof("Excluding namespaces: %s", namespaceIncludesExcludes.ExcludesString())
|
||||
backupRequest.NamespaceIncludesExcludes = getNamespaceIncludesExcludes(backupRequest.Backup)
|
||||
log.Infof("Including namespaces: %s", backupRequest.NamespaceIncludesExcludes.IncludesString())
|
||||
log.Infof("Excluding namespaces: %s", backupRequest.NamespaceIncludesExcludes.ExcludesString())
|
||||
|
||||
resourceIncludesExcludes := getResourceIncludesExcludes(kb.discoveryHelper, backup.Spec.IncludedResources, backup.Spec.ExcludedResources)
|
||||
log.Infof("Including resources: %s", resourceIncludesExcludes.IncludesString())
|
||||
log.Infof("Excluding resources: %s", resourceIncludesExcludes.ExcludesString())
|
||||
backupRequest.ResourceIncludesExcludes = getResourceIncludesExcludes(kb.discoveryHelper, backupRequest.Spec.IncludedResources, backupRequest.Spec.ExcludedResources)
|
||||
log.Infof("Including resources: %s", backupRequest.ResourceIncludesExcludes.IncludesString())
|
||||
log.Infof("Excluding resources: %s", backupRequest.ResourceIncludesExcludes.ExcludesString())
|
||||
|
||||
resourceHooks, err := getResourceHooks(backup.Spec.Hooks.Resources, kb.discoveryHelper)
|
||||
var err error
|
||||
backupRequest.ResourceHooks, err = getResourceHooks(backupRequest.Spec.Hooks.Resources, kb.discoveryHelper)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
backedUpItems := make(map[itemKey]struct{})
|
||||
var errs []error
|
||||
|
||||
resolvedActions, err := resolveActions(actions, kb.discoveryHelper)
|
||||
backupRequest.ResolvedActions, err = resolveActions(actions, kb.discoveryHelper)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
podVolumeTimeout := kb.resticTimeout
|
||||
if val := backup.Annotations[api.PodVolumeOperationTimeoutAnnotation]; val != "" {
|
||||
if val := backupRequest.Annotations[api.PodVolumeOperationTimeoutAnnotation]; val != "" {
|
||||
parsed, err := time.ParseDuration(val)
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Errorf("Unable to parse pod volume timeout annotation %s, using server value.", val)
|
||||
@@ -257,7 +256,7 @@ func (kb *kubernetesBackupper) Backup(logger logrus.FieldLogger, backup *api.Bac
|
||||
|
||||
var resticBackupper restic.Backupper
|
||||
if kb.resticBackupperFactory != nil {
|
||||
resticBackupper, err = kb.resticBackupperFactory.NewBackupper(ctx, backup)
|
||||
resticBackupper, err = kb.resticBackupperFactory.NewBackupper(ctx, backupRequest.Backup)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@@ -265,22 +264,19 @@ func (kb *kubernetesBackupper) Backup(logger logrus.FieldLogger, backup *api.Bac
|
||||
|
||||
gb := kb.groupBackupperFactory.newGroupBackupper(
|
||||
log,
|
||||
backup,
|
||||
namespaceIncludesExcludes,
|
||||
resourceIncludesExcludes,
|
||||
backupRequest,
|
||||
kb.dynamicFactory,
|
||||
kb.discoveryHelper,
|
||||
backedUpItems,
|
||||
make(map[itemKey]struct{}),
|
||||
cohabitatingResources(),
|
||||
resolvedActions,
|
||||
kb.podCommandExecutor,
|
||||
tw,
|
||||
resourceHooks,
|
||||
kb.blockStore,
|
||||
resticBackupper,
|
||||
newPVCSnapshotTracker(),
|
||||
blockStoreGetter,
|
||||
)
|
||||
|
||||
var errs []error
|
||||
for _, group := range kb.discoveryHelper.Resources() {
|
||||
if err := gb.backupGroup(group); err != nil {
|
||||
errs = append(errs, err)
|
||||
|
||||
+40
-85
@@ -38,7 +38,6 @@ import (
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/discovery"
|
||||
"github.com/heptio/ark/pkg/podexec"
|
||||
"github.com/heptio/ark/pkg/restic"
|
||||
@@ -372,17 +371,16 @@ func parseLabelSelectorOrDie(s string) labels.Selector {
|
||||
|
||||
func TestBackup(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
backup *v1.Backup
|
||||
expectedNamespaces *collections.IncludesExcludes
|
||||
expectedResources *collections.IncludesExcludes
|
||||
expectedLabelSelector string
|
||||
expectedHooks []resourceHook
|
||||
backupGroupErrors map[*metav1.APIResourceList]error
|
||||
expectedError error
|
||||
name string
|
||||
backup *v1.Backup
|
||||
expectedNamespaces *collections.IncludesExcludes
|
||||
expectedResources *collections.IncludesExcludes
|
||||
expectedHooks []resourceHook
|
||||
backupGroupErrors map[*metav1.APIResourceList]error
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "happy path, no actions, no label selector, no hooks, no errors",
|
||||
name: "happy path, no actions, no hooks, no errors",
|
||||
backup: &v1.Backup{
|
||||
Spec: v1.BackupSpec{
|
||||
// cm - shortcut in legacy api group
|
||||
@@ -402,25 +400,6 @@ func TestBackup(t *testing.T) {
|
||||
rbacGroup: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "label selector",
|
||||
backup: &v1.Backup{
|
||||
Spec: v1.BackupSpec{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"a": "b"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedNamespaces: collections.NewIncludesExcludes(),
|
||||
expectedResources: collections.NewIncludesExcludes(),
|
||||
expectedHooks: []resourceHook{},
|
||||
expectedLabelSelector: "a=b",
|
||||
backupGroupErrors: map[*metav1.APIResourceList]error{
|
||||
v1Group: nil,
|
||||
certificatesGroup: nil,
|
||||
rbacGroup: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "backupGroup errors",
|
||||
backup: &v1.Backup{},
|
||||
@@ -488,6 +467,10 @@ func TestBackup(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
req := &Request{
|
||||
Backup: test.backup,
|
||||
}
|
||||
|
||||
discoveryHelper := &arktest.FakeDiscoveryHelper{
|
||||
Mapper: &arktest.FakeMapper{
|
||||
Resources: map[schema.GroupVersionResource]schema.GroupVersionResource{
|
||||
@@ -503,77 +486,66 @@ func TestBackup(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
dynamicFactory := &arktest.FakeDynamicFactory{}
|
||||
dynamicFactory := new(arktest.FakeDynamicFactory)
|
||||
|
||||
podCommandExecutor := &arktest.MockPodCommandExecutor{}
|
||||
defer podCommandExecutor.AssertExpectations(t)
|
||||
|
||||
b, err := NewKubernetesBackupper(
|
||||
discoveryHelper,
|
||||
dynamicFactory,
|
||||
podCommandExecutor,
|
||||
nil,
|
||||
nil, // restic backupper factory
|
||||
0, // restic timeout
|
||||
)
|
||||
require.NoError(t, err)
|
||||
kb := b.(*kubernetesBackupper)
|
||||
|
||||
groupBackupperFactory := &mockGroupBackupperFactory{}
|
||||
defer groupBackupperFactory.AssertExpectations(t)
|
||||
kb.groupBackupperFactory = groupBackupperFactory
|
||||
|
||||
groupBackupper := &mockGroupBackupper{}
|
||||
defer groupBackupper.AssertExpectations(t)
|
||||
|
||||
groupBackupperFactory.On("newGroupBackupper",
|
||||
mock.Anything, // log
|
||||
test.backup,
|
||||
test.expectedNamespaces,
|
||||
test.expectedResources,
|
||||
req,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
map[itemKey]struct{}{}, // backedUpItems
|
||||
cohabitatingResources(),
|
||||
mock.Anything,
|
||||
kb.podCommandExecutor,
|
||||
podCommandExecutor,
|
||||
mock.Anything, // tarWriter
|
||||
test.expectedHooks,
|
||||
mock.Anything,
|
||||
mock.Anything, // restic backupper
|
||||
mock.Anything, // pvc snapshot tracker
|
||||
mock.Anything, // block store getter
|
||||
).Return(groupBackupper)
|
||||
|
||||
for group, err := range test.backupGroupErrors {
|
||||
groupBackupper.On("backupGroup", group).Return(err)
|
||||
}
|
||||
|
||||
var backupFile bytes.Buffer
|
||||
kb := &kubernetesBackupper{
|
||||
discoveryHelper: discoveryHelper,
|
||||
dynamicFactory: dynamicFactory,
|
||||
podCommandExecutor: podCommandExecutor,
|
||||
groupBackupperFactory: groupBackupperFactory,
|
||||
}
|
||||
|
||||
err = b.Backup(logging.DefaultLogger(logrus.DebugLevel), test.backup, &backupFile, nil)
|
||||
err := kb.Backup(logging.DefaultLogger(logrus.DebugLevel), req, new(bytes.Buffer), nil, nil)
|
||||
|
||||
assert.Equal(t, test.expectedNamespaces, req.NamespaceIncludesExcludes)
|
||||
assert.Equal(t, test.expectedResources, req.ResourceIncludesExcludes)
|
||||
assert.Equal(t, test.expectedHooks, req.ResourceHooks)
|
||||
|
||||
if test.expectedError != nil {
|
||||
assert.EqualError(t, err, test.expectedError.Error())
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackupUsesNewCohabitatingResourcesForEachBackup(t *testing.T) {
|
||||
discoveryHelper := &arktest.FakeDiscoveryHelper{
|
||||
Mapper: &arktest.FakeMapper{
|
||||
Resources: map[schema.GroupVersionResource]schema.GroupVersionResource{},
|
||||
},
|
||||
groupBackupperFactory := &mockGroupBackupperFactory{}
|
||||
kb := &kubernetesBackupper{
|
||||
discoveryHelper: new(arktest.FakeDiscoveryHelper),
|
||||
groupBackupperFactory: groupBackupperFactory,
|
||||
}
|
||||
|
||||
b, err := NewKubernetesBackupper(discoveryHelper, nil, nil, nil, nil, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
kb := b.(*kubernetesBackupper)
|
||||
groupBackupperFactory := &mockGroupBackupperFactory{}
|
||||
kb.groupBackupperFactory = groupBackupperFactory
|
||||
defer groupBackupperFactory.AssertExpectations(t)
|
||||
|
||||
// assert that newGroupBackupper() is called with the result of cohabitatingResources()
|
||||
// passed as an argument.
|
||||
@@ -582,9 +554,7 @@ func TestBackupUsesNewCohabitatingResourcesForEachBackup(t *testing.T) {
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
discoveryHelper,
|
||||
kb.discoveryHelper,
|
||||
mock.Anything,
|
||||
firstCohabitatingResources,
|
||||
mock.Anything,
|
||||
@@ -592,12 +562,9 @@ func TestBackupUsesNewCohabitatingResourcesForEachBackup(t *testing.T) {
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(&mockGroupBackupper{})
|
||||
|
||||
assert.NoError(t, b.Backup(arktest.NewLogger(), &v1.Backup{}, &bytes.Buffer{}, nil))
|
||||
groupBackupperFactory.AssertExpectations(t)
|
||||
assert.NoError(t, kb.Backup(arktest.NewLogger(), &Request{Backup: &v1.Backup{}}, &bytes.Buffer{}, nil, nil))
|
||||
|
||||
// mutate the cohabitatingResources map that was used in the first backup to simulate
|
||||
// the first backup process having done so.
|
||||
@@ -614,9 +581,7 @@ func TestBackupUsesNewCohabitatingResourcesForEachBackup(t *testing.T) {
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
discoveryHelper,
|
||||
kb.discoveryHelper,
|
||||
mock.Anything,
|
||||
secondCohabitatingResources,
|
||||
mock.Anything,
|
||||
@@ -624,16 +589,13 @@ func TestBackupUsesNewCohabitatingResourcesForEachBackup(t *testing.T) {
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(&mockGroupBackupper{})
|
||||
|
||||
assert.NoError(t, b.Backup(arktest.NewLogger(), &v1.Backup{}, &bytes.Buffer{}, nil))
|
||||
assert.NoError(t, kb.Backup(arktest.NewLogger(), &Request{Backup: new(v1.Backup)}, new(bytes.Buffer), nil, nil))
|
||||
assert.NotEqual(t, firstCohabitatingResources, secondCohabitatingResources)
|
||||
for _, resource := range secondCohabitatingResources {
|
||||
assert.False(t, resource.seen)
|
||||
}
|
||||
groupBackupperFactory.AssertExpectations(t)
|
||||
}
|
||||
|
||||
type mockGroupBackupperFactory struct {
|
||||
@@ -642,36 +604,29 @@ type mockGroupBackupperFactory struct {
|
||||
|
||||
func (f *mockGroupBackupperFactory) newGroupBackupper(
|
||||
log logrus.FieldLogger,
|
||||
backup *v1.Backup,
|
||||
namespaces, resources *collections.IncludesExcludes,
|
||||
backup *Request,
|
||||
dynamicFactory client.DynamicFactory,
|
||||
discoveryHelper discovery.Helper,
|
||||
backedUpItems map[itemKey]struct{},
|
||||
cohabitatingResources map[string]*cohabitatingResource,
|
||||
actions []resolvedAction,
|
||||
podCommandExecutor podexec.PodCommandExecutor,
|
||||
tarWriter tarWriter,
|
||||
resourceHooks []resourceHook,
|
||||
blockStore cloudprovider.BlockStore,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
blockStoreGetter BlockStoreGetter,
|
||||
) groupBackupper {
|
||||
args := f.Called(
|
||||
log,
|
||||
backup,
|
||||
namespaces,
|
||||
resources,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
backedUpItems,
|
||||
cohabitatingResources,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
blockStore,
|
||||
resticBackupper,
|
||||
resticSnapshotTracker,
|
||||
blockStoreGetter,
|
||||
)
|
||||
return args.Get(0).(groupBackupper)
|
||||
}
|
||||
|
||||
@@ -27,31 +27,25 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
kuberrs "k8s.io/apimachinery/pkg/util/errors"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/discovery"
|
||||
"github.com/heptio/ark/pkg/podexec"
|
||||
"github.com/heptio/ark/pkg/restic"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
)
|
||||
|
||||
type groupBackupperFactory interface {
|
||||
newGroupBackupper(
|
||||
log logrus.FieldLogger,
|
||||
backup *v1.Backup,
|
||||
namespaces, resources *collections.IncludesExcludes,
|
||||
backupRequest *Request,
|
||||
dynamicFactory client.DynamicFactory,
|
||||
discoveryHelper discovery.Helper,
|
||||
backedUpItems map[itemKey]struct{},
|
||||
cohabitatingResources map[string]*cohabitatingResource,
|
||||
actions []resolvedAction,
|
||||
podCommandExecutor podexec.PodCommandExecutor,
|
||||
tarWriter tarWriter,
|
||||
resourceHooks []resourceHook,
|
||||
blockStore cloudprovider.BlockStore,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
blockStoreGetter BlockStoreGetter,
|
||||
) groupBackupper
|
||||
}
|
||||
|
||||
@@ -59,36 +53,30 @@ type defaultGroupBackupperFactory struct{}
|
||||
|
||||
func (f *defaultGroupBackupperFactory) newGroupBackupper(
|
||||
log logrus.FieldLogger,
|
||||
backup *v1.Backup,
|
||||
namespaces, resources *collections.IncludesExcludes,
|
||||
backupRequest *Request,
|
||||
dynamicFactory client.DynamicFactory,
|
||||
discoveryHelper discovery.Helper,
|
||||
backedUpItems map[itemKey]struct{},
|
||||
cohabitatingResources map[string]*cohabitatingResource,
|
||||
actions []resolvedAction,
|
||||
podCommandExecutor podexec.PodCommandExecutor,
|
||||
tarWriter tarWriter,
|
||||
resourceHooks []resourceHook,
|
||||
blockStore cloudprovider.BlockStore,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
blockStoreGetter BlockStoreGetter,
|
||||
) groupBackupper {
|
||||
return &defaultGroupBackupper{
|
||||
log: log,
|
||||
backup: backup,
|
||||
namespaces: namespaces,
|
||||
resources: resources,
|
||||
dynamicFactory: dynamicFactory,
|
||||
discoveryHelper: discoveryHelper,
|
||||
backedUpItems: backedUpItems,
|
||||
cohabitatingResources: cohabitatingResources,
|
||||
actions: actions,
|
||||
podCommandExecutor: podCommandExecutor,
|
||||
tarWriter: tarWriter,
|
||||
resourceHooks: resourceHooks,
|
||||
blockStore: blockStore,
|
||||
resticBackupper: resticBackupper,
|
||||
resticSnapshotTracker: resticSnapshotTracker,
|
||||
log: log,
|
||||
backupRequest: backupRequest,
|
||||
dynamicFactory: dynamicFactory,
|
||||
discoveryHelper: discoveryHelper,
|
||||
backedUpItems: backedUpItems,
|
||||
cohabitatingResources: cohabitatingResources,
|
||||
podCommandExecutor: podCommandExecutor,
|
||||
tarWriter: tarWriter,
|
||||
resticBackupper: resticBackupper,
|
||||
resticSnapshotTracker: resticSnapshotTracker,
|
||||
blockStoreGetter: blockStoreGetter,
|
||||
|
||||
resourceBackupperFactory: &defaultResourceBackupperFactory{},
|
||||
}
|
||||
}
|
||||
@@ -99,20 +87,17 @@ type groupBackupper interface {
|
||||
|
||||
type defaultGroupBackupper struct {
|
||||
log logrus.FieldLogger
|
||||
backup *v1.Backup
|
||||
namespaces, resources *collections.IncludesExcludes
|
||||
backupRequest *Request
|
||||
dynamicFactory client.DynamicFactory
|
||||
discoveryHelper discovery.Helper
|
||||
backedUpItems map[itemKey]struct{}
|
||||
cohabitatingResources map[string]*cohabitatingResource
|
||||
actions []resolvedAction
|
||||
podCommandExecutor podexec.PodCommandExecutor
|
||||
tarWriter tarWriter
|
||||
resourceHooks []resourceHook
|
||||
blockStore cloudprovider.BlockStore
|
||||
resticBackupper restic.Backupper
|
||||
resticSnapshotTracker *pvcSnapshotTracker
|
||||
resourceBackupperFactory resourceBackupperFactory
|
||||
blockStoreGetter BlockStoreGetter
|
||||
}
|
||||
|
||||
// backupGroup backs up a single API group.
|
||||
@@ -122,20 +107,16 @@ func (gb *defaultGroupBackupper) backupGroup(group *metav1.APIResourceList) erro
|
||||
log = gb.log.WithField("group", group.GroupVersion)
|
||||
rb = gb.resourceBackupperFactory.newResourceBackupper(
|
||||
log,
|
||||
gb.backup,
|
||||
gb.namespaces,
|
||||
gb.resources,
|
||||
gb.backupRequest,
|
||||
gb.dynamicFactory,
|
||||
gb.discoveryHelper,
|
||||
gb.backedUpItems,
|
||||
gb.cohabitatingResources,
|
||||
gb.actions,
|
||||
gb.podCommandExecutor,
|
||||
gb.tarWriter,
|
||||
gb.resourceHooks,
|
||||
gb.blockStore,
|
||||
gb.resticBackupper,
|
||||
gb.resticSnapshotTracker,
|
||||
gb.blockStoreGetter,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -19,104 +19,44 @@ package backup
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/discovery"
|
||||
"github.com/heptio/ark/pkg/podexec"
|
||||
"github.com/heptio/ark/pkg/restic"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
arktest "github.com/heptio/ark/pkg/util/test"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func TestBackupGroup(t *testing.T) {
|
||||
backup := &v1.Backup{}
|
||||
func TestBackupGroupBacksUpCorrectResourcesInCorrectOrder(t *testing.T) {
|
||||
resourceBackupperFactory := new(mockResourceBackupperFactory)
|
||||
resourceBackupper := new(mockResourceBackupper)
|
||||
|
||||
namespaces := collections.NewIncludesExcludes().Includes("a")
|
||||
resources := collections.NewIncludesExcludes().Includes("b")
|
||||
|
||||
dynamicFactory := &arktest.FakeDynamicFactory{}
|
||||
defer dynamicFactory.AssertExpectations(t)
|
||||
|
||||
discoveryHelper := arktest.NewFakeDiscoveryHelper(true, nil)
|
||||
|
||||
backedUpItems := map[itemKey]struct{}{
|
||||
{resource: "a", namespace: "b", name: "c"}: {},
|
||||
}
|
||||
|
||||
cohabitatingResources := map[string]*cohabitatingResource{
|
||||
"a": {
|
||||
resource: "a",
|
||||
groupResource1: schema.GroupResource{Group: "g1", Resource: "a"},
|
||||
groupResource2: schema.GroupResource{Group: "g2", Resource: "a"},
|
||||
},
|
||||
}
|
||||
|
||||
actions := []resolvedAction{
|
||||
{
|
||||
ItemAction: newFakeAction("pods"),
|
||||
resourceIncludesExcludes: collections.NewIncludesExcludes().Includes("pods"),
|
||||
},
|
||||
}
|
||||
|
||||
podCommandExecutor := &arktest.MockPodCommandExecutor{}
|
||||
defer podCommandExecutor.AssertExpectations(t)
|
||||
|
||||
tarWriter := &fakeTarWriter{}
|
||||
|
||||
resourceHooks := []resourceHook{
|
||||
{name: "myhook"},
|
||||
}
|
||||
|
||||
gb := (&defaultGroupBackupperFactory{}).newGroupBackupper(
|
||||
arktest.NewLogger(),
|
||||
backup,
|
||||
namespaces,
|
||||
resources,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
backedUpItems,
|
||||
cohabitatingResources,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
nil, // snapshot service
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
).(*defaultGroupBackupper)
|
||||
|
||||
resourceBackupperFactory := &mockResourceBackupperFactory{}
|
||||
defer resourceBackupperFactory.AssertExpectations(t)
|
||||
gb.resourceBackupperFactory = resourceBackupperFactory
|
||||
|
||||
resourceBackupper := &mockResourceBackupper{}
|
||||
defer resourceBackupper.AssertExpectations(t)
|
||||
|
||||
resourceBackupperFactory.On("newResourceBackupper",
|
||||
mock.Anything,
|
||||
backup,
|
||||
namespaces,
|
||||
resources,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
backedUpItems,
|
||||
cohabitatingResources,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
nil,
|
||||
mock.Anything, // restic backupper
|
||||
mock.Anything, // pvc snapshot tracker
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
).Return(resourceBackupper)
|
||||
|
||||
gb := &defaultGroupBackupper{
|
||||
log: arktest.NewLogger(),
|
||||
resourceBackupperFactory: resourceBackupperFactory,
|
||||
}
|
||||
|
||||
group := &metav1.APIResourceList{
|
||||
GroupVersion: "v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
@@ -126,9 +66,7 @@ func TestBackupGroup(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
expectedOrder := []string{"pods", "persistentvolumeclaims", "persistentvolumes"}
|
||||
var actualOrder []string
|
||||
|
||||
runFunc := func(args mock.Arguments) {
|
||||
actualOrder = append(actualOrder, args.Get(1).(metav1.APIResource).Name)
|
||||
}
|
||||
@@ -137,11 +75,10 @@ func TestBackupGroup(t *testing.T) {
|
||||
resourceBackupper.On("backupResource", group, metav1.APIResource{Name: "persistentvolumeclaims"}).Return(nil).Run(runFunc)
|
||||
resourceBackupper.On("backupResource", group, metav1.APIResource{Name: "persistentvolumes"}).Return(nil).Run(runFunc)
|
||||
|
||||
err := gb.backupGroup(group)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, gb.backupGroup(group))
|
||||
|
||||
// make sure PVs were last
|
||||
assert.Equal(t, expectedOrder, actualOrder)
|
||||
assert.Equal(t, []string{"pods", "persistentvolumeclaims", "persistentvolumes"}, actualOrder)
|
||||
}
|
||||
|
||||
type mockResourceBackupperFactory struct {
|
||||
@@ -150,37 +87,29 @@ type mockResourceBackupperFactory struct {
|
||||
|
||||
func (rbf *mockResourceBackupperFactory) newResourceBackupper(
|
||||
log logrus.FieldLogger,
|
||||
backup *v1.Backup,
|
||||
namespaces *collections.IncludesExcludes,
|
||||
resources *collections.IncludesExcludes,
|
||||
backup *Request,
|
||||
dynamicFactory client.DynamicFactory,
|
||||
discoveryHelper discovery.Helper,
|
||||
backedUpItems map[itemKey]struct{},
|
||||
cohabitatingResources map[string]*cohabitatingResource,
|
||||
actions []resolvedAction,
|
||||
podCommandExecutor podexec.PodCommandExecutor,
|
||||
tarWriter tarWriter,
|
||||
resourceHooks []resourceHook,
|
||||
blockStore cloudprovider.BlockStore,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
blockStoreGetter BlockStoreGetter,
|
||||
) resourceBackupper {
|
||||
args := rbf.Called(
|
||||
log,
|
||||
backup,
|
||||
namespaces,
|
||||
resources,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
backedUpItems,
|
||||
cohabitatingResources,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
blockStore,
|
||||
resticBackupper,
|
||||
resticSnapshotTracker,
|
||||
blockStoreGetter,
|
||||
)
|
||||
return args.Get(0).(resourceBackupper)
|
||||
}
|
||||
|
||||
@@ -40,58 +40,48 @@ import (
|
||||
"github.com/heptio/ark/pkg/kuberesource"
|
||||
"github.com/heptio/ark/pkg/podexec"
|
||||
"github.com/heptio/ark/pkg/restic"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
)
|
||||
|
||||
type itemBackupperFactory interface {
|
||||
newItemBackupper(
|
||||
backup *api.Backup,
|
||||
namespaces, resources *collections.IncludesExcludes,
|
||||
backup *Request,
|
||||
backedUpItems map[itemKey]struct{},
|
||||
actions []resolvedAction,
|
||||
podCommandExecutor podexec.PodCommandExecutor,
|
||||
tarWriter tarWriter,
|
||||
resourceHooks []resourceHook,
|
||||
dynamicFactory client.DynamicFactory,
|
||||
discoveryHelper discovery.Helper,
|
||||
blockStore cloudprovider.BlockStore,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
blockStoreGetter BlockStoreGetter,
|
||||
) ItemBackupper
|
||||
}
|
||||
|
||||
type defaultItemBackupperFactory struct{}
|
||||
|
||||
func (f *defaultItemBackupperFactory) newItemBackupper(
|
||||
backup *api.Backup,
|
||||
namespaces, resources *collections.IncludesExcludes,
|
||||
backupRequest *Request,
|
||||
backedUpItems map[itemKey]struct{},
|
||||
actions []resolvedAction,
|
||||
podCommandExecutor podexec.PodCommandExecutor,
|
||||
tarWriter tarWriter,
|
||||
resourceHooks []resourceHook,
|
||||
dynamicFactory client.DynamicFactory,
|
||||
discoveryHelper discovery.Helper,
|
||||
blockStore cloudprovider.BlockStore,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
blockStoreGetter BlockStoreGetter,
|
||||
) ItemBackupper {
|
||||
ib := &defaultItemBackupper{
|
||||
backup: backup,
|
||||
namespaces: namespaces,
|
||||
resources: resources,
|
||||
backedUpItems: backedUpItems,
|
||||
actions: actions,
|
||||
tarWriter: tarWriter,
|
||||
resourceHooks: resourceHooks,
|
||||
dynamicFactory: dynamicFactory,
|
||||
discoveryHelper: discoveryHelper,
|
||||
blockStore: blockStore,
|
||||
backupRequest: backupRequest,
|
||||
backedUpItems: backedUpItems,
|
||||
tarWriter: tarWriter,
|
||||
dynamicFactory: dynamicFactory,
|
||||
discoveryHelper: discoveryHelper,
|
||||
resticBackupper: resticBackupper,
|
||||
resticSnapshotTracker: resticSnapshotTracker,
|
||||
blockStoreGetter: blockStoreGetter,
|
||||
|
||||
itemHookHandler: &defaultItemHookHandler{
|
||||
podCommandExecutor: podCommandExecutor,
|
||||
},
|
||||
resticBackupper: resticBackupper,
|
||||
resticSnapshotTracker: resticSnapshotTracker,
|
||||
}
|
||||
|
||||
// this is for testing purposes
|
||||
@@ -105,21 +95,18 @@ type ItemBackupper interface {
|
||||
}
|
||||
|
||||
type defaultItemBackupper struct {
|
||||
backup *api.Backup
|
||||
namespaces *collections.IncludesExcludes
|
||||
resources *collections.IncludesExcludes
|
||||
backupRequest *Request
|
||||
backedUpItems map[itemKey]struct{}
|
||||
actions []resolvedAction
|
||||
tarWriter tarWriter
|
||||
resourceHooks []resourceHook
|
||||
dynamicFactory client.DynamicFactory
|
||||
discoveryHelper discovery.Helper
|
||||
blockStore cloudprovider.BlockStore
|
||||
resticBackupper restic.Backupper
|
||||
resticSnapshotTracker *pvcSnapshotTracker
|
||||
blockStoreGetter BlockStoreGetter
|
||||
|
||||
itemHookHandler itemHookHandler
|
||||
additionalItemBackupper ItemBackupper
|
||||
itemHookHandler itemHookHandler
|
||||
additionalItemBackupper ItemBackupper
|
||||
snapshotLocationBlockStores map[string]cloudprovider.BlockStore
|
||||
}
|
||||
|
||||
// backupItem backs up an individual item to tarWriter. The item may be excluded based on the
|
||||
@@ -140,19 +127,19 @@ func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtim
|
||||
|
||||
// NOTE: we have to re-check namespace & resource includes/excludes because it's possible that
|
||||
// backupItem can be invoked by a custom action.
|
||||
if namespace != "" && !ib.namespaces.ShouldInclude(namespace) {
|
||||
if namespace != "" && !ib.backupRequest.NamespaceIncludesExcludes.ShouldInclude(namespace) {
|
||||
log.Info("Excluding item because namespace is excluded")
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE: we specifically allow namespaces to be backed up even if IncludeClusterResources is
|
||||
// false.
|
||||
if namespace == "" && groupResource != kuberesource.Namespaces && ib.backup.Spec.IncludeClusterResources != nil && !*ib.backup.Spec.IncludeClusterResources {
|
||||
if namespace == "" && groupResource != kuberesource.Namespaces && ib.backupRequest.Spec.IncludeClusterResources != nil && !*ib.backupRequest.Spec.IncludeClusterResources {
|
||||
log.Info("Excluding item because resource is cluster-scoped and backup.spec.includeClusterResources is false")
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ib.resources.ShouldInclude(groupResource.String()) {
|
||||
if !ib.backupRequest.ResourceIncludesExcludes.ShouldInclude(groupResource.String()) {
|
||||
log.Info("Excluding item because resource is excluded")
|
||||
return nil
|
||||
}
|
||||
@@ -176,7 +163,7 @@ func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtim
|
||||
log.Info("Backing up resource")
|
||||
|
||||
log.Debug("Executing pre hooks")
|
||||
if err := ib.itemHookHandler.handleHooks(log, groupResource, obj, ib.resourceHooks, hookPhasePre); err != nil {
|
||||
if err := ib.itemHookHandler.handleHooks(log, groupResource, obj, ib.backupRequest.ResourceHooks, hookPhasePre); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -210,7 +197,7 @@ func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtim
|
||||
|
||||
// if there was an error running actions, execute post hooks and return
|
||||
log.Debug("Executing post hooks")
|
||||
if err := ib.itemHookHandler.handleHooks(log, groupResource, obj, ib.resourceHooks, hookPhasePost); err != nil {
|
||||
if err := ib.itemHookHandler.handleHooks(log, groupResource, obj, ib.backupRequest.ResourceHooks, hookPhasePost); err != nil {
|
||||
backupErrs = append(backupErrs, err)
|
||||
}
|
||||
|
||||
@@ -222,9 +209,7 @@ func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtim
|
||||
}
|
||||
|
||||
if groupResource == kuberesource.PersistentVolumes {
|
||||
if ib.blockStore == nil {
|
||||
log.Debug("Skipping Persistent Volume snapshot because they're not enabled.")
|
||||
} else if err := ib.takePVSnapshot(obj, ib.backup, log); err != nil {
|
||||
if err := ib.takePVSnapshot(obj, log); err != nil {
|
||||
backupErrs = append(backupErrs, err)
|
||||
}
|
||||
}
|
||||
@@ -243,7 +228,7 @@ func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtim
|
||||
}
|
||||
|
||||
log.Debug("Executing post hooks")
|
||||
if err := ib.itemHookHandler.handleHooks(log, groupResource, obj, ib.resourceHooks, hookPhasePost); err != nil {
|
||||
if err := ib.itemHookHandler.handleHooks(log, groupResource, obj, ib.backupRequest.ResourceHooks, hookPhasePost); err != nil {
|
||||
backupErrs = append(backupErrs, err)
|
||||
}
|
||||
|
||||
@@ -294,7 +279,7 @@ func (ib *defaultItemBackupper) backupPodVolumes(log logrus.FieldLogger, pod *co
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return ib.resticBackupper.BackupPodVolumes(ib.backup, pod, log)
|
||||
return ib.resticBackupper.BackupPodVolumes(ib.backupRequest.Backup, pod, log)
|
||||
}
|
||||
|
||||
func (ib *defaultItemBackupper) executeActions(
|
||||
@@ -304,7 +289,7 @@ func (ib *defaultItemBackupper) executeActions(
|
||||
name, namespace string,
|
||||
metadata metav1.Object,
|
||||
) (runtime.Unstructured, error) {
|
||||
for _, action := range ib.actions {
|
||||
for _, action := range ib.backupRequest.ResolvedActions {
|
||||
if !action.resourceIncludesExcludes.ShouldInclude(groupResource.String()) {
|
||||
log.Debug("Skipping action because it does not apply to this resource")
|
||||
continue
|
||||
@@ -322,7 +307,7 @@ func (ib *defaultItemBackupper) executeActions(
|
||||
|
||||
log.Info("Executing custom action")
|
||||
|
||||
updatedItem, additionalItemIdentifiers, err := action.Execute(obj, ib.backup)
|
||||
updatedItem, additionalItemIdentifiers, err := action.Execute(obj, ib.backupRequest.Backup)
|
||||
if err != nil {
|
||||
// We want this to show up in the log file at the place where the error occurs. When we return
|
||||
// the error, it get aggregated with all the other ones at the end of the backup, making it
|
||||
@@ -358,6 +343,30 @@ func (ib *defaultItemBackupper) executeActions(
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// blockStore instantiates and initializes a BlockStore given a VolumeSnapshotLocation,
|
||||
// or returns an existing one if one's already been initialized for the location.
|
||||
func (ib *defaultItemBackupper) blockStore(snapshotLocation *api.VolumeSnapshotLocation) (cloudprovider.BlockStore, error) {
|
||||
if bs, ok := ib.snapshotLocationBlockStores[snapshotLocation.Name]; ok {
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
bs, err := ib.blockStoreGetter.GetBlockStore(snapshotLocation.Spec.Provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := bs.Init(snapshotLocation.Spec.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ib.snapshotLocationBlockStores == nil {
|
||||
ib.snapshotLocationBlockStores = make(map[string]cloudprovider.BlockStore)
|
||||
}
|
||||
ib.snapshotLocationBlockStores[snapshotLocation.Name] = bs
|
||||
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
// zoneLabel is the label that stores availability-zone info
|
||||
// on PVs
|
||||
const zoneLabel = "failure-domain.beta.kubernetes.io/zone"
|
||||
@@ -365,10 +374,10 @@ 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(obj runtime.Unstructured, backup *api.Backup, log logrus.FieldLogger) error {
|
||||
func (ib *defaultItemBackupper) takePVSnapshot(obj runtime.Unstructured, log logrus.FieldLogger) error {
|
||||
log.Info("Executing takePVSnapshot")
|
||||
|
||||
if backup.Spec.SnapshotVolumes != nil && !*backup.Spec.SnapshotVolumes {
|
||||
if ib.backupRequest.Spec.SnapshotVolumes != nil && !*ib.backupRequest.Spec.SnapshotVolumes {
|
||||
log.Info("Backup has volume snapshots disabled; skipping volume snapshot action.")
|
||||
return nil
|
||||
}
|
||||
@@ -402,11 +411,38 @@ func (ib *defaultItemBackupper) takePVSnapshot(obj runtime.Unstructured, backup
|
||||
log.Infof("label %q is not present on PersistentVolume", zoneLabel)
|
||||
}
|
||||
|
||||
volumeID, err := ib.blockStore.GetVolumeID(obj)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error getting volume ID for PersistentVolume")
|
||||
var (
|
||||
volumeID string
|
||||
blockStore cloudprovider.BlockStore
|
||||
)
|
||||
|
||||
for _, snapshotLocation := range ib.backupRequest.SnapshotLocations {
|
||||
bs, err := ib.blockStore(snapshotLocation)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("volumeSnapshotLocation", snapshotLocation).Error("Error getting block store for volume snapshot location")
|
||||
continue
|
||||
}
|
||||
|
||||
log := log.WithFields(map[string]interface{}{
|
||||
"volumeSnapshotLocation": snapshotLocation.Name,
|
||||
"persistentVolume": metadata.GetName(),
|
||||
})
|
||||
|
||||
if volumeID, err = bs.GetVolumeID(obj); err != nil {
|
||||
log.WithError(err).Errorf("Error attempting to get volume ID for persistent volume")
|
||||
continue
|
||||
}
|
||||
if volumeID == "" {
|
||||
log.Infof("No volume ID returned by block store for persistent volume")
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("Got volume ID for persistent volume")
|
||||
blockStore = bs
|
||||
break
|
||||
}
|
||||
if volumeID == "" {
|
||||
|
||||
if blockStore == nil {
|
||||
log.Info("PersistentVolume is not a supported volume type for snapshots, skipping.")
|
||||
return nil
|
||||
}
|
||||
@@ -414,29 +450,29 @@ func (ib *defaultItemBackupper) takePVSnapshot(obj runtime.Unstructured, backup
|
||||
log = log.WithField("volumeID", volumeID)
|
||||
|
||||
tags := map[string]string{
|
||||
"ark.heptio.com/backup": backup.Name,
|
||||
"ark.heptio.com/backup": ib.backupRequest.Name,
|
||||
"ark.heptio.com/pv": metadata.GetName(),
|
||||
}
|
||||
|
||||
log.Info("Snapshotting PersistentVolume")
|
||||
snapshotID, err := ib.blockStore.CreateSnapshot(volumeID, pvFailureDomainZone, tags)
|
||||
snapshotID, err := blockStore.CreateSnapshot(volumeID, pvFailureDomainZone, tags)
|
||||
if err != nil {
|
||||
// log+error on purpose - log goes to the per-backup log file, error goes to the backup
|
||||
log.WithError(err).Error("error creating snapshot")
|
||||
return errors.WithMessage(err, "error creating snapshot")
|
||||
}
|
||||
|
||||
volumeType, iops, err := ib.blockStore.GetVolumeInfo(volumeID, pvFailureDomainZone)
|
||||
volumeType, iops, err := blockStore.GetVolumeInfo(volumeID, pvFailureDomainZone)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("error getting volume info")
|
||||
return errors.WithMessage(err, "error getting volume info")
|
||||
}
|
||||
|
||||
if backup.Status.VolumeBackups == nil {
|
||||
backup.Status.VolumeBackups = make(map[string]*api.VolumeBackupInfo)
|
||||
if ib.backupRequest.Status.VolumeBackups == nil {
|
||||
ib.backupRequest.Status.VolumeBackups = make(map[string]*api.VolumeBackupInfo)
|
||||
}
|
||||
|
||||
backup.Status.VolumeBackups[name] = &api.VolumeBackupInfo{
|
||||
ib.backupRequest.Status.VolumeBackups[name] = &api.VolumeBackupInfo{
|
||||
SnapshotID: snapshotID,
|
||||
Type: volumeType,
|
||||
Iops: iops,
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
resticmocks "github.com/heptio/ark/pkg/restic/mocks"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
arktest "github.com/heptio/ark/pkg/util/test"
|
||||
@@ -107,10 +108,13 @@ func TestBackupItemSkips(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.testName, func(t *testing.T) {
|
||||
req := &Request{
|
||||
NamespaceIncludesExcludes: test.namespaces,
|
||||
ResourceIncludesExcludes: test.resources,
|
||||
}
|
||||
|
||||
ib := &defaultItemBackupper{
|
||||
namespaces: test.namespaces,
|
||||
resources: test.resources,
|
||||
backupRequest: req,
|
||||
backedUpItems: test.backedUpItems,
|
||||
}
|
||||
|
||||
@@ -134,13 +138,15 @@ func TestBackupItemSkips(t *testing.T) {
|
||||
func TestBackupItemSkipsClusterScopedResourceWhenIncludeClusterResourcesFalse(t *testing.T) {
|
||||
f := false
|
||||
ib := &defaultItemBackupper{
|
||||
backup: &v1.Backup{
|
||||
Spec: v1.BackupSpec{
|
||||
IncludeClusterResources: &f,
|
||||
backupRequest: &Request{
|
||||
Backup: &v1.Backup{
|
||||
Spec: v1.BackupSpec{
|
||||
IncludeClusterResources: &f,
|
||||
},
|
||||
},
|
||||
NamespaceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
ResourceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
},
|
||||
namespaces: collections.NewIncludesExcludes(),
|
||||
resources: collections.NewIncludesExcludes(),
|
||||
}
|
||||
|
||||
u := arktest.UnstructuredOrDie(`{"apiVersion":"v1","kind":"Foo","metadata":{"name":"bar"}}`)
|
||||
@@ -350,15 +356,20 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
actions []resolvedAction
|
||||
action *fakeAction
|
||||
backup = &v1.Backup{}
|
||||
backup = new(Request)
|
||||
groupResource = schema.ParseGroupResource("resource.group")
|
||||
backedUpItems = make(map[itemKey]struct{})
|
||||
resources = collections.NewIncludesExcludes()
|
||||
w = &fakeTarWriter{}
|
||||
)
|
||||
|
||||
backup.Backup = new(v1.Backup)
|
||||
backup.NamespaceIncludesExcludes = collections.NewIncludesExcludes()
|
||||
backup.ResourceIncludesExcludes = collections.NewIncludesExcludes()
|
||||
backup.SnapshotLocations = []*v1.VolumeSnapshotLocation{
|
||||
new(v1.VolumeSnapshotLocation),
|
||||
}
|
||||
|
||||
if test.groupResource != "" {
|
||||
groupResource = schema.ParseGroupResource(test.groupResource)
|
||||
}
|
||||
@@ -384,7 +395,7 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
action = &fakeAction{
|
||||
additionalItems: test.customActionAdditionalItemIdentifiers,
|
||||
}
|
||||
actions = []resolvedAction{
|
||||
backup.ResolvedActions = []resolvedAction{
|
||||
{
|
||||
ItemAction: action,
|
||||
namespaceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
@@ -394,8 +405,6 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
resourceHooks := []resourceHook{}
|
||||
|
||||
podCommandExecutor := &arktest.MockPodCommandExecutor{}
|
||||
defer podCommandExecutor.AssertExpectations(t)
|
||||
|
||||
@@ -404,20 +413,18 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
|
||||
discoveryHelper := arktest.NewFakeDiscoveryHelper(true, nil)
|
||||
|
||||
blockStoreGetter := &blockStoreGetter{}
|
||||
|
||||
b := (&defaultItemBackupperFactory{}).newItemBackupper(
|
||||
backup,
|
||||
namespaces,
|
||||
resources,
|
||||
backedUpItems,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
w,
|
||||
resourceHooks,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
nil, // snapshot service
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
blockStoreGetter,
|
||||
).(*defaultItemBackupper)
|
||||
|
||||
var blockStore *arktest.FakeBlockStore
|
||||
@@ -427,7 +434,8 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
VolumeID: "vol-abc123",
|
||||
Error: test.snapshotError,
|
||||
}
|
||||
b.blockStore = blockStore
|
||||
|
||||
blockStoreGetter.blockStore = blockStore
|
||||
}
|
||||
|
||||
if test.trackedPVCs != nil {
|
||||
@@ -446,8 +454,8 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
b.additionalItemBackupper = additionalItemBackupper
|
||||
|
||||
obj := &unstructured.Unstructured{Object: item}
|
||||
itemHookHandler.On("handleHooks", mock.Anything, groupResource, obj, resourceHooks, hookPhasePre).Return(nil)
|
||||
itemHookHandler.On("handleHooks", mock.Anything, groupResource, obj, resourceHooks, hookPhasePost).Return(nil)
|
||||
itemHookHandler.On("handleHooks", mock.Anything, groupResource, obj, backup.ResourceHooks, hookPhasePre).Return(nil)
|
||||
itemHookHandler.On("handleHooks", mock.Anything, groupResource, obj, backup.ResourceHooks, hookPhasePost).Return(nil)
|
||||
|
||||
for i, item := range test.customActionAdditionalItemIdentifiers {
|
||||
if test.additionalItemError != nil && i > 0 {
|
||||
@@ -511,7 +519,7 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
}
|
||||
|
||||
require.Equal(t, 1, len(action.backups), "unexpected custom action backups: %#v", action.backups)
|
||||
assert.Equal(t, backup, &(action.backups[0]), "backup")
|
||||
assert.Equal(t, backup.Backup, &(action.backups[0]), "backup")
|
||||
}
|
||||
|
||||
if test.snapshottableVolumes != nil {
|
||||
@@ -541,6 +549,17 @@ func TestBackupItemNoSkips(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type blockStoreGetter struct {
|
||||
blockStore cloudprovider.BlockStore
|
||||
}
|
||||
|
||||
func (b *blockStoreGetter) GetBlockStore(name string) (cloudprovider.BlockStore, error) {
|
||||
if b.blockStore != nil {
|
||||
return b.blockStore, nil
|
||||
}
|
||||
return nil, errors.New("plugin not found")
|
||||
}
|
||||
|
||||
type addAnnotationAction struct{}
|
||||
|
||||
func (a *addAnnotationAction) Execute(item runtime.Unstructured, backup *v1.Backup) (runtime.Unstructured, []ResourceIdentifier, error) {
|
||||
@@ -578,28 +597,29 @@ func TestItemActionModificationsToItemPersist(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
actions = []resolvedAction{
|
||||
{
|
||||
ItemAction: &addAnnotationAction{},
|
||||
namespaceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
resourceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
selector: labels.Everything(),
|
||||
req = &Request{
|
||||
NamespaceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
ResourceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
ResolvedActions: []resolvedAction{
|
||||
{
|
||||
ItemAction: &addAnnotationAction{},
|
||||
namespaceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
resourceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
selector: labels.Everything(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
b = (&defaultItemBackupperFactory{}).newItemBackupper(
|
||||
&v1.Backup{},
|
||||
collections.NewIncludesExcludes(),
|
||||
collections.NewIncludesExcludes(),
|
||||
req,
|
||||
make(map[itemKey]struct{}),
|
||||
actions,
|
||||
nil,
|
||||
w,
|
||||
nil,
|
||||
&arktest.FakeDynamicFactory{},
|
||||
arktest.NewFakeDiscoveryHelper(true, nil),
|
||||
nil,
|
||||
nil,
|
||||
newPVCSnapshotTracker(),
|
||||
nil,
|
||||
).(*defaultItemBackupper)
|
||||
)
|
||||
|
||||
@@ -633,29 +653,29 @@ func TestResticAnnotationsPersist(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
actions = []resolvedAction{
|
||||
{
|
||||
ItemAction: &addAnnotationAction{},
|
||||
namespaceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
resourceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
selector: labels.Everything(),
|
||||
req = &Request{
|
||||
NamespaceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
ResourceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
ResolvedActions: []resolvedAction{
|
||||
{
|
||||
ItemAction: &addAnnotationAction{},
|
||||
namespaceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
resourceIncludesExcludes: collections.NewIncludesExcludes(),
|
||||
selector: labels.Everything(),
|
||||
},
|
||||
},
|
||||
}
|
||||
resticBackupper = &resticmocks.Backupper{}
|
||||
b = (&defaultItemBackupperFactory{}).newItemBackupper(
|
||||
&v1.Backup{},
|
||||
collections.NewIncludesExcludes(),
|
||||
collections.NewIncludesExcludes(),
|
||||
req,
|
||||
make(map[itemKey]struct{}),
|
||||
actions,
|
||||
nil,
|
||||
w,
|
||||
nil,
|
||||
&arktest.FakeDynamicFactory{},
|
||||
arktest.NewFakeDiscoveryHelper(true, nil),
|
||||
nil,
|
||||
resticBackupper,
|
||||
newPVCSnapshotTracker(),
|
||||
nil,
|
||||
).(*defaultItemBackupper)
|
||||
)
|
||||
|
||||
@@ -793,7 +813,13 @@ func TestTakePVSnapshot(t *testing.T) {
|
||||
VolumeID: test.expectedVolumeID,
|
||||
}
|
||||
|
||||
ib := &defaultItemBackupper{blockStore: blockStore}
|
||||
ib := &defaultItemBackupper{
|
||||
backupRequest: &Request{
|
||||
Backup: backup,
|
||||
SnapshotLocations: []*v1.VolumeSnapshotLocation{new(v1.VolumeSnapshotLocation)},
|
||||
},
|
||||
blockStoreGetter: &blockStoreGetter{blockStore: blockStore},
|
||||
}
|
||||
|
||||
pv, err := arktest.GetAsMap(test.pv)
|
||||
if err != nil {
|
||||
@@ -801,7 +827,7 @@ func TestTakePVSnapshot(t *testing.T) {
|
||||
}
|
||||
|
||||
// method under test
|
||||
err = ib.takePVSnapshot(&unstructured.Unstructured{Object: pv}, backup, arktest.NewLogger())
|
||||
err = ib.takePVSnapshot(&unstructured.Unstructured{Object: pv}, arktest.NewLogger())
|
||||
|
||||
gotErr := err != nil
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package backup
|
||||
|
||||
import (
|
||||
arkv1api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
)
|
||||
|
||||
// Request is a request for a backup, with all references to other objects
|
||||
// materialized (e.g. backup/snapshot locations, includes/excludes, etc.)
|
||||
type Request struct {
|
||||
*arkv1api.Backup
|
||||
|
||||
StorageLocation *arkv1api.BackupStorageLocation
|
||||
SnapshotLocations []*arkv1api.VolumeSnapshotLocation
|
||||
NamespaceIncludesExcludes *collections.IncludesExcludes
|
||||
ResourceIncludesExcludes *collections.IncludesExcludes
|
||||
ResourceHooks []resourceHook
|
||||
ResolvedActions []resolvedAction
|
||||
}
|
||||
@@ -27,9 +27,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
kuberrs "k8s.io/apimachinery/pkg/util/errors"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/discovery"
|
||||
"github.com/heptio/ark/pkg/kuberesource"
|
||||
"github.com/heptio/ark/pkg/podexec"
|
||||
@@ -40,20 +38,16 @@ import (
|
||||
type resourceBackupperFactory interface {
|
||||
newResourceBackupper(
|
||||
log logrus.FieldLogger,
|
||||
backup *api.Backup,
|
||||
namespaces *collections.IncludesExcludes,
|
||||
resources *collections.IncludesExcludes,
|
||||
backupRequest *Request,
|
||||
dynamicFactory client.DynamicFactory,
|
||||
discoveryHelper discovery.Helper,
|
||||
backedUpItems map[itemKey]struct{},
|
||||
cohabitatingResources map[string]*cohabitatingResource,
|
||||
actions []resolvedAction,
|
||||
podCommandExecutor podexec.PodCommandExecutor,
|
||||
tarWriter tarWriter,
|
||||
resourceHooks []resourceHook,
|
||||
blockStore cloudprovider.BlockStore,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
blockStoreGetter BlockStoreGetter,
|
||||
) resourceBackupper
|
||||
}
|
||||
|
||||
@@ -61,38 +55,31 @@ type defaultResourceBackupperFactory struct{}
|
||||
|
||||
func (f *defaultResourceBackupperFactory) newResourceBackupper(
|
||||
log logrus.FieldLogger,
|
||||
backup *api.Backup,
|
||||
namespaces *collections.IncludesExcludes,
|
||||
resources *collections.IncludesExcludes,
|
||||
backupRequest *Request,
|
||||
dynamicFactory client.DynamicFactory,
|
||||
discoveryHelper discovery.Helper,
|
||||
backedUpItems map[itemKey]struct{},
|
||||
cohabitatingResources map[string]*cohabitatingResource,
|
||||
actions []resolvedAction,
|
||||
podCommandExecutor podexec.PodCommandExecutor,
|
||||
tarWriter tarWriter,
|
||||
resourceHooks []resourceHook,
|
||||
blockStore cloudprovider.BlockStore,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
blockStoreGetter BlockStoreGetter,
|
||||
) resourceBackupper {
|
||||
return &defaultResourceBackupper{
|
||||
log: log,
|
||||
backup: backup,
|
||||
namespaces: namespaces,
|
||||
resources: resources,
|
||||
backupRequest: backupRequest,
|
||||
dynamicFactory: dynamicFactory,
|
||||
discoveryHelper: discoveryHelper,
|
||||
backedUpItems: backedUpItems,
|
||||
actions: actions,
|
||||
cohabitatingResources: cohabitatingResources,
|
||||
podCommandExecutor: podCommandExecutor,
|
||||
tarWriter: tarWriter,
|
||||
resourceHooks: resourceHooks,
|
||||
blockStore: blockStore,
|
||||
resticBackupper: resticBackupper,
|
||||
resticSnapshotTracker: resticSnapshotTracker,
|
||||
itemBackupperFactory: &defaultItemBackupperFactory{},
|
||||
blockStoreGetter: blockStoreGetter,
|
||||
|
||||
itemBackupperFactory: &defaultItemBackupperFactory{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,21 +89,17 @@ type resourceBackupper interface {
|
||||
|
||||
type defaultResourceBackupper struct {
|
||||
log logrus.FieldLogger
|
||||
backup *api.Backup
|
||||
namespaces *collections.IncludesExcludes
|
||||
resources *collections.IncludesExcludes
|
||||
backupRequest *Request
|
||||
dynamicFactory client.DynamicFactory
|
||||
discoveryHelper discovery.Helper
|
||||
backedUpItems map[itemKey]struct{}
|
||||
cohabitatingResources map[string]*cohabitatingResource
|
||||
actions []resolvedAction
|
||||
podCommandExecutor podexec.PodCommandExecutor
|
||||
tarWriter tarWriter
|
||||
resourceHooks []resourceHook
|
||||
blockStore cloudprovider.BlockStore
|
||||
resticBackupper restic.Backupper
|
||||
resticSnapshotTracker *pvcSnapshotTracker
|
||||
itemBackupperFactory itemBackupperFactory
|
||||
blockStoreGetter BlockStoreGetter
|
||||
}
|
||||
|
||||
// backupResource backs up all the objects for a given group-version-resource.
|
||||
@@ -142,8 +125,8 @@ func (rb *defaultResourceBackupper) backupResource(
|
||||
// If the resource we are backing up is NOT namespaces, and it is cluster-scoped, check to see if
|
||||
// we should include it based on the IncludeClusterResources setting.
|
||||
if gr != kuberesource.Namespaces && clusterScoped {
|
||||
if rb.backup.Spec.IncludeClusterResources == nil {
|
||||
if !rb.namespaces.IncludeEverything() {
|
||||
if rb.backupRequest.Spec.IncludeClusterResources == nil {
|
||||
if !rb.backupRequest.NamespaceIncludesExcludes.IncludeEverything() {
|
||||
// when IncludeClusterResources == nil (auto), only directly
|
||||
// back up cluster-scoped resources if we're doing a full-cluster
|
||||
// (all namespaces) backup. Note that in the case of a subset of
|
||||
@@ -154,13 +137,13 @@ func (rb *defaultResourceBackupper) backupResource(
|
||||
log.Info("Skipping resource because it's cluster-scoped and only specific namespaces are included in the backup")
|
||||
return nil
|
||||
}
|
||||
} else if !*rb.backup.Spec.IncludeClusterResources {
|
||||
} else if !*rb.backupRequest.Spec.IncludeClusterResources {
|
||||
log.Info("Skipping resource because it's cluster-scoped")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if !rb.resources.ShouldInclude(grString) {
|
||||
if !rb.backupRequest.ResourceIncludesExcludes.ShouldInclude(grString) {
|
||||
log.Infof("Resource is excluded")
|
||||
return nil
|
||||
}
|
||||
@@ -179,22 +162,18 @@ func (rb *defaultResourceBackupper) backupResource(
|
||||
}
|
||||
|
||||
itemBackupper := rb.itemBackupperFactory.newItemBackupper(
|
||||
rb.backup,
|
||||
rb.namespaces,
|
||||
rb.resources,
|
||||
rb.backupRequest,
|
||||
rb.backedUpItems,
|
||||
rb.actions,
|
||||
rb.podCommandExecutor,
|
||||
rb.tarWriter,
|
||||
rb.resourceHooks,
|
||||
rb.dynamicFactory,
|
||||
rb.discoveryHelper,
|
||||
rb.blockStore,
|
||||
rb.resticBackupper,
|
||||
rb.resticSnapshotTracker,
|
||||
rb.blockStoreGetter,
|
||||
)
|
||||
|
||||
namespacesToList := getNamespacesToList(rb.namespaces)
|
||||
namespacesToList := getNamespacesToList(rb.backupRequest.NamespaceIncludesExcludes)
|
||||
|
||||
// Check if we're backing up namespaces, and only certain ones
|
||||
if gr == kuberesource.Namespaces && namespacesToList[0] != "" {
|
||||
@@ -204,8 +183,8 @@ func (rb *defaultResourceBackupper) backupResource(
|
||||
}
|
||||
|
||||
var labelSelector labels.Selector
|
||||
if rb.backup.Spec.LabelSelector != nil {
|
||||
labelSelector, err = metav1.LabelSelectorAsSelector(rb.backup.Spec.LabelSelector)
|
||||
if rb.backupRequest.Spec.LabelSelector != nil {
|
||||
labelSelector, err = metav1.LabelSelectorAsSelector(rb.backupRequest.Spec.LabelSelector)
|
||||
if err != nil {
|
||||
// This should never happen...
|
||||
return errors.Wrap(err, "invalid label selector")
|
||||
@@ -246,7 +225,7 @@ func (rb *defaultResourceBackupper) backupResource(
|
||||
}
|
||||
|
||||
var labelSelector string
|
||||
if selector := rb.backup.Spec.LabelSelector; selector != nil {
|
||||
if selector := rb.backupRequest.Spec.LabelSelector; selector != nil {
|
||||
labelSelector = metav1.FormatLabelSelector(selector)
|
||||
}
|
||||
|
||||
@@ -276,7 +255,7 @@ func (rb *defaultResourceBackupper) backupResource(
|
||||
continue
|
||||
}
|
||||
|
||||
if gr == kuberesource.Namespaces && !rb.namespaces.ShouldInclude(metadata.GetName()) {
|
||||
if gr == kuberesource.Namespaces && !rb.backupRequest.NamespaceIncludesExcludes.ShouldInclude(metadata.GetName()) {
|
||||
log.WithField("name", metadata.GetName()).Info("skipping namespace because it is excluded")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/discovery"
|
||||
"github.com/heptio/ark/pkg/kuberesource"
|
||||
"github.com/heptio/ark/pkg/podexec"
|
||||
@@ -220,10 +219,23 @@ func TestBackupResource(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
backup := &v1.Backup{
|
||||
Spec: v1.BackupSpec{
|
||||
IncludeClusterResources: test.includeClusterResources,
|
||||
req := &Request{
|
||||
Backup: &v1.Backup{
|
||||
Spec: v1.BackupSpec{
|
||||
IncludeClusterResources: test.includeClusterResources,
|
||||
},
|
||||
},
|
||||
ResolvedActions: []resolvedAction{
|
||||
{
|
||||
ItemAction: newFakeAction("pods"),
|
||||
resourceIncludesExcludes: collections.NewIncludesExcludes().Includes("pods"),
|
||||
},
|
||||
},
|
||||
ResourceHooks: []resourceHook{
|
||||
{name: "myhook"},
|
||||
},
|
||||
ResourceIncludesExcludes: test.resources,
|
||||
NamespaceIncludesExcludes: test.namespaces,
|
||||
}
|
||||
|
||||
dynamicFactory := &arktest.FakeDynamicFactory{}
|
||||
@@ -240,17 +252,6 @@ func TestBackupResource(t *testing.T) {
|
||||
"networkpolicies": newCohabitatingResource("networkpolicies", "extensions", "networking.k8s.io"),
|
||||
}
|
||||
|
||||
actions := []resolvedAction{
|
||||
{
|
||||
ItemAction: newFakeAction("pods"),
|
||||
resourceIncludesExcludes: collections.NewIncludesExcludes().Includes("pods"),
|
||||
},
|
||||
}
|
||||
|
||||
resourceHooks := []resourceHook{
|
||||
{name: "myhook"},
|
||||
}
|
||||
|
||||
podCommandExecutor := &arktest.MockPodCommandExecutor{}
|
||||
defer podCommandExecutor.AssertExpectations(t)
|
||||
|
||||
@@ -259,20 +260,16 @@ func TestBackupResource(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
rb := (&defaultResourceBackupperFactory{}).newResourceBackupper(
|
||||
arktest.NewLogger(),
|
||||
backup,
|
||||
test.namespaces,
|
||||
test.resources,
|
||||
req,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
backedUpItems,
|
||||
cohabitatingResources,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
nil, // snapshot service
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
nil,
|
||||
).(*defaultResourceBackupper)
|
||||
|
||||
itemBackupperFactory := &mockItemBackupperFactory{}
|
||||
@@ -284,14 +281,10 @@ func TestBackupResource(t *testing.T) {
|
||||
defer itemBackupper.AssertExpectations(t)
|
||||
|
||||
itemBackupperFactory.On("newItemBackupper",
|
||||
backup,
|
||||
test.namespaces,
|
||||
test.resources,
|
||||
req,
|
||||
backedUpItems,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
mock.Anything,
|
||||
@@ -382,19 +375,29 @@ func TestBackupResourceCohabitation(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
backup := &v1.Backup{
|
||||
Spec: v1.BackupSpec{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
req := &Request{
|
||||
Backup: &v1.Backup{
|
||||
Spec: v1.BackupSpec{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NamespaceIncludesExcludes: collections.NewIncludesExcludes().Includes("*"),
|
||||
ResourceIncludesExcludes: collections.NewIncludesExcludes().Includes("*"),
|
||||
ResolvedActions: []resolvedAction{
|
||||
{
|
||||
ItemAction: newFakeAction("pods"),
|
||||
resourceIncludesExcludes: collections.NewIncludesExcludes().Includes("pods"),
|
||||
},
|
||||
},
|
||||
ResourceHooks: []resourceHook{
|
||||
{name: "myhook"},
|
||||
},
|
||||
}
|
||||
|
||||
namespaces := collections.NewIncludesExcludes().Includes("*")
|
||||
resources := collections.NewIncludesExcludes().Includes("*")
|
||||
|
||||
dynamicFactory := &arktest.FakeDynamicFactory{}
|
||||
defer dynamicFactory.AssertExpectations(t)
|
||||
|
||||
@@ -409,17 +412,6 @@ func TestBackupResourceCohabitation(t *testing.T) {
|
||||
"networkpolicies": newCohabitatingResource("networkpolicies", "extensions", "networking.k8s.io"),
|
||||
}
|
||||
|
||||
actions := []resolvedAction{
|
||||
{
|
||||
ItemAction: newFakeAction("pods"),
|
||||
resourceIncludesExcludes: collections.NewIncludesExcludes().Includes("pods"),
|
||||
},
|
||||
}
|
||||
|
||||
resourceHooks := []resourceHook{
|
||||
{name: "myhook"},
|
||||
}
|
||||
|
||||
podCommandExecutor := &arktest.MockPodCommandExecutor{}
|
||||
defer podCommandExecutor.AssertExpectations(t)
|
||||
|
||||
@@ -427,20 +419,16 @@ func TestBackupResourceCohabitation(t *testing.T) {
|
||||
|
||||
rb := (&defaultResourceBackupperFactory{}).newResourceBackupper(
|
||||
arktest.NewLogger(),
|
||||
backup,
|
||||
namespaces,
|
||||
resources,
|
||||
req,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
backedUpItems,
|
||||
cohabitatingResources,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
nil, // snapshot service
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
nil,
|
||||
).(*defaultResourceBackupper)
|
||||
|
||||
itemBackupperFactory := &mockItemBackupperFactory{}
|
||||
@@ -451,19 +439,15 @@ func TestBackupResourceCohabitation(t *testing.T) {
|
||||
defer itemBackupper.AssertExpectations(t)
|
||||
|
||||
itemBackupperFactory.On("newItemBackupper",
|
||||
backup,
|
||||
namespaces,
|
||||
resources,
|
||||
req,
|
||||
backedUpItems,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
mock.Anything, // snapshot service
|
||||
mock.Anything, // restic backupper
|
||||
mock.Anything, // pvc snapshot tracker
|
||||
nil,
|
||||
).Return(itemBackupper)
|
||||
|
||||
client := &arktest.FakeDynamicClient{}
|
||||
@@ -471,7 +455,7 @@ func TestBackupResourceCohabitation(t *testing.T) {
|
||||
|
||||
// STEP 1: make sure the initial backup goes through
|
||||
dynamicFactory.On("ClientForGroupVersionResource", test.groupVersion1, test.apiResource, "").Return(client, nil)
|
||||
client.On("List", metav1.ListOptions{LabelSelector: metav1.FormatLabelSelector(backup.Spec.LabelSelector)}).Return(&unstructured.UnstructuredList{}, nil)
|
||||
client.On("List", metav1.ListOptions{LabelSelector: metav1.FormatLabelSelector(req.Backup.Spec.LabelSelector)}).Return(&unstructured.UnstructuredList{}, nil)
|
||||
|
||||
// STEP 2: do the backup
|
||||
err := rb.backupResource(test.apiGroup1, test.apiResource)
|
||||
@@ -485,10 +469,11 @@ func TestBackupResourceCohabitation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackupResourceOnlyIncludesSpecifiedNamespaces(t *testing.T) {
|
||||
backup := &v1.Backup{}
|
||||
|
||||
namespaces := collections.NewIncludesExcludes().Includes("ns-1")
|
||||
resources := collections.NewIncludesExcludes().Includes("*")
|
||||
req := &Request{
|
||||
Backup: &v1.Backup{},
|
||||
NamespaceIncludesExcludes: collections.NewIncludesExcludes().Includes("ns-1"),
|
||||
ResourceIncludesExcludes: collections.NewIncludesExcludes().Includes("*"),
|
||||
}
|
||||
|
||||
backedUpItems := map[itemKey]struct{}{}
|
||||
|
||||
@@ -499,10 +484,6 @@ func TestBackupResourceOnlyIncludesSpecifiedNamespaces(t *testing.T) {
|
||||
|
||||
cohabitatingResources := map[string]*cohabitatingResource{}
|
||||
|
||||
actions := []resolvedAction{}
|
||||
|
||||
resourceHooks := []resourceHook{}
|
||||
|
||||
podCommandExecutor := &arktest.MockPodCommandExecutor{}
|
||||
defer podCommandExecutor.AssertExpectations(t)
|
||||
|
||||
@@ -510,20 +491,16 @@ func TestBackupResourceOnlyIncludesSpecifiedNamespaces(t *testing.T) {
|
||||
|
||||
rb := (&defaultResourceBackupperFactory{}).newResourceBackupper(
|
||||
arktest.NewLogger(),
|
||||
backup,
|
||||
namespaces,
|
||||
resources,
|
||||
req,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
backedUpItems,
|
||||
cohabitatingResources,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
nil, // snapshot service
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
nil,
|
||||
).(*defaultResourceBackupper)
|
||||
|
||||
itemBackupperFactory := &mockItemBackupperFactory{}
|
||||
@@ -534,27 +511,19 @@ func TestBackupResourceOnlyIncludesSpecifiedNamespaces(t *testing.T) {
|
||||
defer itemHookHandler.AssertExpectations(t)
|
||||
|
||||
itemBackupper := &defaultItemBackupper{
|
||||
backup: backup,
|
||||
namespaces: namespaces,
|
||||
resources: resources,
|
||||
backupRequest: req,
|
||||
backedUpItems: backedUpItems,
|
||||
actions: actions,
|
||||
tarWriter: tarWriter,
|
||||
resourceHooks: resourceHooks,
|
||||
dynamicFactory: dynamicFactory,
|
||||
discoveryHelper: discoveryHelper,
|
||||
itemHookHandler: itemHookHandler,
|
||||
}
|
||||
|
||||
itemBackupperFactory.On("newItemBackupper",
|
||||
backup,
|
||||
namespaces,
|
||||
resources,
|
||||
req,
|
||||
backedUpItems,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
mock.Anything,
|
||||
@@ -570,8 +539,8 @@ func TestBackupResourceOnlyIncludesSpecifiedNamespaces(t *testing.T) {
|
||||
ns1 := arktest.UnstructuredOrDie(`{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"ns-1"}}`)
|
||||
client.On("Get", "ns-1", metav1.GetOptions{}).Return(ns1, nil)
|
||||
|
||||
itemHookHandler.On("handleHooks", mock.Anything, schema.GroupResource{Group: "", Resource: "namespaces"}, ns1, resourceHooks, hookPhasePre).Return(nil)
|
||||
itemHookHandler.On("handleHooks", mock.Anything, schema.GroupResource{Group: "", Resource: "namespaces"}, ns1, resourceHooks, hookPhasePost).Return(nil)
|
||||
itemHookHandler.On("handleHooks", mock.Anything, schema.GroupResource{Group: "", Resource: "namespaces"}, ns1, req.ResourceHooks, hookPhasePre).Return(nil)
|
||||
itemHookHandler.On("handleHooks", mock.Anything, schema.GroupResource{Group: "", Resource: "namespaces"}, ns1, req.ResourceHooks, hookPhasePost).Return(nil)
|
||||
|
||||
err := rb.backupResource(v1Group, namespacesResource)
|
||||
require.NoError(t, err)
|
||||
@@ -581,19 +550,20 @@ func TestBackupResourceOnlyIncludesSpecifiedNamespaces(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBackupResourceListAllNamespacesExcludesCorrectly(t *testing.T) {
|
||||
backup := &v1.Backup{
|
||||
Spec: v1.BackupSpec{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
req := &Request{
|
||||
Backup: &v1.Backup{
|
||||
Spec: v1.BackupSpec{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
NamespaceIncludesExcludes: collections.NewIncludesExcludes().Excludes("ns-1"),
|
||||
ResourceIncludesExcludes: collections.NewIncludesExcludes().Includes("*"),
|
||||
}
|
||||
|
||||
namespaces := collections.NewIncludesExcludes().Excludes("ns-1")
|
||||
resources := collections.NewIncludesExcludes().Includes("*")
|
||||
|
||||
backedUpItems := map[itemKey]struct{}{}
|
||||
|
||||
dynamicFactory := &arktest.FakeDynamicFactory{}
|
||||
@@ -603,10 +573,6 @@ func TestBackupResourceListAllNamespacesExcludesCorrectly(t *testing.T) {
|
||||
|
||||
cohabitatingResources := map[string]*cohabitatingResource{}
|
||||
|
||||
actions := []resolvedAction{}
|
||||
|
||||
resourceHooks := []resourceHook{}
|
||||
|
||||
podCommandExecutor := &arktest.MockPodCommandExecutor{}
|
||||
defer podCommandExecutor.AssertExpectations(t)
|
||||
|
||||
@@ -614,20 +580,16 @@ func TestBackupResourceListAllNamespacesExcludesCorrectly(t *testing.T) {
|
||||
|
||||
rb := (&defaultResourceBackupperFactory{}).newResourceBackupper(
|
||||
arktest.NewLogger(),
|
||||
backup,
|
||||
namespaces,
|
||||
resources,
|
||||
req,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
backedUpItems,
|
||||
cohabitatingResources,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
nil, // snapshot service
|
||||
nil, // restic backupper
|
||||
newPVCSnapshotTracker(),
|
||||
nil,
|
||||
).(*defaultResourceBackupper)
|
||||
|
||||
itemBackupperFactory := &mockItemBackupperFactory{}
|
||||
@@ -641,14 +603,10 @@ func TestBackupResourceListAllNamespacesExcludesCorrectly(t *testing.T) {
|
||||
defer itemBackupper.AssertExpectations(t)
|
||||
|
||||
itemBackupperFactory.On("newItemBackupper",
|
||||
backup,
|
||||
namespaces,
|
||||
resources,
|
||||
req,
|
||||
backedUpItems,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
mock.Anything,
|
||||
@@ -667,7 +625,7 @@ func TestBackupResourceListAllNamespacesExcludesCorrectly(t *testing.T) {
|
||||
list := &unstructured.UnstructuredList{
|
||||
Items: []unstructured.Unstructured{*ns1, *ns2},
|
||||
}
|
||||
client.On("List", metav1.ListOptions{LabelSelector: metav1.FormatLabelSelector(backup.Spec.LabelSelector)}).Return(list, nil)
|
||||
client.On("List", metav1.ListOptions{LabelSelector: metav1.FormatLabelSelector(req.Backup.Spec.LabelSelector)}).Return(list, nil)
|
||||
|
||||
itemBackupper.On("backupItem", mock.AnythingOfType("*logrus.Entry"), ns2, kuberesource.Namespaces).Return(nil)
|
||||
|
||||
@@ -680,33 +638,26 @@ type mockItemBackupperFactory struct {
|
||||
}
|
||||
|
||||
func (ibf *mockItemBackupperFactory) newItemBackupper(
|
||||
backup *v1.Backup,
|
||||
namespaces, resources *collections.IncludesExcludes,
|
||||
backup *Request,
|
||||
backedUpItems map[itemKey]struct{},
|
||||
actions []resolvedAction,
|
||||
podCommandExecutor podexec.PodCommandExecutor,
|
||||
tarWriter tarWriter,
|
||||
resourceHooks []resourceHook,
|
||||
dynamicFactory client.DynamicFactory,
|
||||
discoveryHelper discovery.Helper,
|
||||
blockStore cloudprovider.BlockStore,
|
||||
resticBackupper restic.Backupper,
|
||||
resticSnapshotTracker *pvcSnapshotTracker,
|
||||
blockStoreGetter BlockStoreGetter,
|
||||
) ItemBackupper {
|
||||
args := ibf.Called(
|
||||
backup,
|
||||
namespaces,
|
||||
resources,
|
||||
backedUpItems,
|
||||
actions,
|
||||
podCommandExecutor,
|
||||
tarWriter,
|
||||
resourceHooks,
|
||||
dynamicFactory,
|
||||
discoveryHelper,
|
||||
blockStore,
|
||||
resticBackupper,
|
||||
resticSnapshotTracker,
|
||||
blockStoreGetter,
|
||||
)
|
||||
return args.Get(0).(ItemBackupper)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user