From 07525bd593e8c1d64d115ddf44d067dd7c3b9d3c Mon Sep 17 00:00:00 2001 From: Adnan Abdulhussein Date: Mon, 5 Aug 2019 10:15:55 -0700 Subject: [PATCH] store backup resource list metadata in object storage (#1709) * move backedUpItems to pkg/backup.Request struct Signed-off-by: Adnan Abdulhussein * construct resource itemKey field from gvk Signed-off-by: Adnan Abdulhussein * store backup resource list metadata in object storage Signed-off-by: Adnan Abdulhussein * remove debug log Signed-off-by: Adnan Abdulhussein * fix formatting Signed-off-by: Adnan Abdulhussein * add missing license blocks and split BackupInfo struct lines Signed-off-by: Adnan Abdulhussein * add test for checking BackedUpItems matches tarball contents Signed-off-by: Adnan Abdulhussein * add comment to explain test Signed-off-by: Adnan Abdulhussein --- pkg/backup/backup.go | 9 +--- pkg/backup/backup_test.go | 50 ++++++++++++++++++++++ pkg/backup/group_backupper.go | 5 --- pkg/backup/item_backupper.go | 21 ++++++---- pkg/backup/item_backupper_test.go | 47 +++++++++++++++++++++ pkg/backup/request.go | 39 +++++++++++++++++ pkg/backup/request_test.go | 58 ++++++++++++++++++++++++++ pkg/backup/resource_backupper.go | 5 --- pkg/builder/role_builder.go | 57 +++++++++++++++++++++++++ pkg/controller/backup_controller.go | 26 ++++++++---- pkg/persistence/object_store.go | 21 +++++++++- pkg/persistence/object_store_layout.go | 4 ++ pkg/persistence/object_store_test.go | 23 +++++++--- 13 files changed, 323 insertions(+), 42 deletions(-) create mode 100644 pkg/backup/item_backupper_test.go create mode 100644 pkg/backup/request_test.go create mode 100644 pkg/builder/role_builder.go diff --git a/pkg/backup/backup.go b/pkg/backup/backup.go index d244f764e..9f548473c 100644 --- a/pkg/backup/backup.go +++ b/pkg/backup/backup.go @@ -60,12 +60,6 @@ type kubernetesBackupper struct { resticTimeout time.Duration } -type itemKey struct { - resource string - namespace string - name string -} - type resolvedAction struct { velero.BackupItemAction @@ -240,6 +234,8 @@ func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Req return err } + backupRequest.BackedUpItems = map[itemKey]struct{}{} + podVolumeTimeout := kb.resticTimeout if val := backupRequest.Annotations[api.PodVolumeOperationTimeoutAnnotation]; val != "" { parsed, err := time.ParseDuration(val) @@ -266,7 +262,6 @@ func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Req backupRequest, kb.dynamicFactory, kb.discoveryHelper, - make(map[itemKey]struct{}), cohabitatingResources(), kb.podCommandExecutor, tw, diff --git a/pkg/backup/backup_test.go b/pkg/backup/backup_test.go index 02a22ced5..eda3b1809 100644 --- a/pkg/backup/backup_test.go +++ b/pkg/backup/backup_test.go @@ -53,6 +53,56 @@ import ( "github.com/heptio/velero/pkg/volume" ) +func TestBackedUpItemsMatchesTarballContents(t *testing.T) { + // TODO: figure out if this can be replaced with the restmapper + // (https://github.com/kubernetes/apimachinery/blob/035e418f1ad9b6da47c4e01906a0cfe32f4ee2e7/pkg/api/meta/restmapper.go) + gvkToResource := map[string]string{ + "v1/Pod": "pods", + "apps/v1/Deployment": "deployments.apps", + "v1/PersistentVolume": "persistentvolumes", + } + + h := newHarness(t) + req := &Request{Backup: defaultBackup().Result()} + backupFile := bytes.NewBuffer([]byte{}) + + apiResources := []*test.APIResource{ + test.Pods( + builder.ForPod("foo", "bar").Result(), + builder.ForPod("zoo", "raz").Result(), + ), + test.Deployments( + builder.ForDeployment("foo", "bar").Result(), + builder.ForDeployment("zoo", "raz").Result(), + ), + test.PVs( + builder.ForPersistentVolume("bar").Result(), + builder.ForPersistentVolume("baz").Result(), + ), + } + for _, resource := range apiResources { + h.addItems(t, resource) + } + + h.backupper.Backup(h.log, req, backupFile, nil, nil) + + // go through BackedUpItems after the backup to assemble the list of files we + // expect to see in the tarball and compare to see if they match + var expectedFiles []string + for item := range req.BackedUpItems { + file := "resources/" + gvkToResource[item.resource] + if item.namespace != "" { + file = file + "/namespaces/" + item.namespace + } else { + file = file + "/cluster" + } + file = file + "/" + item.name + ".json" + expectedFiles = append(expectedFiles, file) + } + + assertTarballContents(t, backupFile, append(expectedFiles, "metadata/version")...) +} + // TestBackupResourceFiltering runs backups with different combinations // of resource filters (included/excluded resources, included/excluded // namespaces, label selectors, "include cluster resources" flag), and diff --git a/pkg/backup/group_backupper.go b/pkg/backup/group_backupper.go index 237f96e64..cee6ce8b3 100644 --- a/pkg/backup/group_backupper.go +++ b/pkg/backup/group_backupper.go @@ -37,7 +37,6 @@ type groupBackupperFactory interface { backupRequest *Request, dynamicFactory client.DynamicFactory, discoveryHelper discovery.Helper, - backedUpItems map[itemKey]struct{}, cohabitatingResources map[string]*cohabitatingResource, podCommandExecutor podexec.PodCommandExecutor, tarWriter tarWriter, @@ -54,7 +53,6 @@ func (f *defaultGroupBackupperFactory) newGroupBackupper( backupRequest *Request, dynamicFactory client.DynamicFactory, discoveryHelper discovery.Helper, - backedUpItems map[itemKey]struct{}, cohabitatingResources map[string]*cohabitatingResource, podCommandExecutor podexec.PodCommandExecutor, tarWriter tarWriter, @@ -67,7 +65,6 @@ func (f *defaultGroupBackupperFactory) newGroupBackupper( backupRequest: backupRequest, dynamicFactory: dynamicFactory, discoveryHelper: discoveryHelper, - backedUpItems: backedUpItems, cohabitatingResources: cohabitatingResources, podCommandExecutor: podCommandExecutor, tarWriter: tarWriter, @@ -88,7 +85,6 @@ type defaultGroupBackupper struct { backupRequest *Request dynamicFactory client.DynamicFactory discoveryHelper discovery.Helper - backedUpItems map[itemKey]struct{} cohabitatingResources map[string]*cohabitatingResource podCommandExecutor podexec.PodCommandExecutor tarWriter tarWriter @@ -120,7 +116,6 @@ func (gb *defaultGroupBackupper) backupGroup(group *metav1.APIResourceList) erro gb.backupRequest, gb.dynamicFactory, gb.discoveryHelper, - gb.backedUpItems, gb.cohabitatingResources, gb.podCommandExecutor, gb.tarWriter, diff --git a/pkg/backup/item_backupper.go b/pkg/backup/item_backupper.go index 53455e500..00abca956 100644 --- a/pkg/backup/item_backupper.go +++ b/pkg/backup/item_backupper.go @@ -19,6 +19,7 @@ package backup import ( "archive/tar" "encoding/json" + "fmt" "path/filepath" "time" @@ -46,7 +47,6 @@ import ( type itemBackupperFactory interface { newItemBackupper( backup *Request, - backedUpItems map[itemKey]struct{}, podCommandExecutor podexec.PodCommandExecutor, tarWriter tarWriter, dynamicFactory client.DynamicFactory, @@ -61,7 +61,6 @@ type defaultItemBackupperFactory struct{} func (f *defaultItemBackupperFactory) newItemBackupper( backupRequest *Request, - backedUpItems map[itemKey]struct{}, podCommandExecutor podexec.PodCommandExecutor, tarWriter tarWriter, dynamicFactory client.DynamicFactory, @@ -72,7 +71,6 @@ func (f *defaultItemBackupperFactory) newItemBackupper( ) ItemBackupper { ib := &defaultItemBackupper{ backupRequest: backupRequest, - backedUpItems: backedUpItems, tarWriter: tarWriter, dynamicFactory: dynamicFactory, discoveryHelper: discoveryHelper, @@ -97,7 +95,6 @@ type ItemBackupper interface { type defaultItemBackupper struct { backupRequest *Request - backedUpItems map[itemKey]struct{} tarWriter tarWriter dynamicFactory client.DynamicFactory discoveryHelper discovery.Helper @@ -149,19 +146,18 @@ func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtim log.Info("Skipping item because it's being deleted.") return nil } + key := itemKey{ - resource: groupResource.String(), + resource: resourceKey(obj), namespace: namespace, name: name, } - if _, exists := ib.backedUpItems[key]; exists { + if _, exists := ib.backupRequest.BackedUpItems[key]; exists { log.Info("Skipping item because it's already been backed up.") return nil } - ib.backedUpItems[key] = struct{}{} - - log.Debug(obj.GetObjectKind().GroupVersionKind().GroupVersion().String() + "/" + obj.GetObjectKind().GroupVersionKind().Kind) + ib.backupRequest.BackedUpItems[key] = struct{}{} log.Info("Backing up item") @@ -485,3 +481,10 @@ func volumeSnapshot(backup *api.Backup, volumeName, volumeID, volumeType, az, lo }, } } + +// resourceKey returns a string representing the object's GroupVersionKind (e.g. +// apps/v1/Deployment). +func resourceKey(obj runtime.Unstructured) string { + gvk := obj.GetObjectKind().GroupVersionKind() + return fmt.Sprintf("%s/%s", gvk.GroupVersion().String(), gvk.Kind) +} diff --git a/pkg/backup/item_backupper_test.go b/pkg/backup/item_backupper_test.go new file mode 100644 index 000000000..7200e27d5 --- /dev/null +++ b/pkg/backup/item_backupper_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2019 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package backup + +import ( + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/heptio/velero/pkg/builder" +) + +func Test_resourceKey(t *testing.T) { + tests := []struct { + resource metav1.Object + want string + }{ + {resource: builder.ForPod("default", "test").Result(), want: "v1/Pod"}, + {resource: builder.ForDeployment("default", "test").Result(), want: "apps/v1/Deployment"}, + {resource: builder.ForPersistentVolume("test").Result(), want: "v1/PersistentVolume"}, + {resource: builder.ForRole("default", "test").Result(), want: "rbac.authorization.k8s.io/v1/Role"}, + } + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + content, _ := runtime.DefaultUnstructuredConverter.ToUnstructured(tt.resource) + unstructured := &unstructured.Unstructured{Object: content} + assert.Equal(t, tt.want, resourceKey(unstructured)) + }) + } +} diff --git a/pkg/backup/request.go b/pkg/backup/request.go index 495442b55..5ec9bc778 100644 --- a/pkg/backup/request.go +++ b/pkg/backup/request.go @@ -1,11 +1,35 @@ +/* +Copyright 2019 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package backup import ( + "fmt" + velerov1api "github.com/heptio/velero/pkg/apis/velero/v1" "github.com/heptio/velero/pkg/util/collections" "github.com/heptio/velero/pkg/volume" ) +type itemKey struct { + resource string + namespace string + name string +} + // 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 { @@ -20,4 +44,19 @@ type Request struct { VolumeSnapshots []*volume.Snapshot PodVolumeBackups []*velerov1api.PodVolumeBackup + BackedUpItems map[itemKey]struct{} +} + +// BackupResourceList returns the list of backed up resources grouped by the API +// Version and Kind +func (r *Request) BackupResourceList() map[string][]string { + resources := map[string][]string{} + for i := range r.BackedUpItems { + entry := i.name + if i.namespace != "" { + entry = fmt.Sprintf("%s/%s", i.namespace, i.name) + } + resources[i.resource] = append(resources[i.resource], entry) + } + return resources } diff --git a/pkg/backup/request_test.go b/pkg/backup/request_test.go new file mode 100644 index 000000000..f37434059 --- /dev/null +++ b/pkg/backup/request_test.go @@ -0,0 +1,58 @@ +/* +Copyright 2019 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package backup + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRequest_BackupResourceList(t *testing.T) { + items := []itemKey{ + { + resource: "apps/v1/Deployment", + name: "my-deploy", + namespace: "default", + }, + { + resource: "v1/Pod", + name: "pod1", + namespace: "ns1", + }, + { + resource: "v1/Pod", + name: "pod2", + namespace: "ns2", + }, + { + resource: "v1/PersistentVolume", + name: "my-pv", + }, + } + backedUpItems := map[itemKey]struct{}{} + for _, it := range items { + backedUpItems[it] = struct{}{} + } + + req := Request{BackedUpItems: backedUpItems} + assert.Equal(t, req.BackupResourceList(), map[string][]string{ + "apps/v1/Deployment": {"default/my-deploy"}, + "v1/Pod": {"ns1/pod1", "ns2/pod2"}, + "v1/PersistentVolume": {"my-pv"}, + }) +} diff --git a/pkg/backup/resource_backupper.go b/pkg/backup/resource_backupper.go index de78a25d3..771b4494f 100644 --- a/pkg/backup/resource_backupper.go +++ b/pkg/backup/resource_backupper.go @@ -40,7 +40,6 @@ type resourceBackupperFactory interface { backupRequest *Request, dynamicFactory client.DynamicFactory, discoveryHelper discovery.Helper, - backedUpItems map[itemKey]struct{}, cohabitatingResources map[string]*cohabitatingResource, podCommandExecutor podexec.PodCommandExecutor, tarWriter tarWriter, @@ -57,7 +56,6 @@ func (f *defaultResourceBackupperFactory) newResourceBackupper( backupRequest *Request, dynamicFactory client.DynamicFactory, discoveryHelper discovery.Helper, - backedUpItems map[itemKey]struct{}, cohabitatingResources map[string]*cohabitatingResource, podCommandExecutor podexec.PodCommandExecutor, tarWriter tarWriter, @@ -70,7 +68,6 @@ func (f *defaultResourceBackupperFactory) newResourceBackupper( backupRequest: backupRequest, dynamicFactory: dynamicFactory, discoveryHelper: discoveryHelper, - backedUpItems: backedUpItems, cohabitatingResources: cohabitatingResources, podCommandExecutor: podCommandExecutor, tarWriter: tarWriter, @@ -91,7 +88,6 @@ type defaultResourceBackupper struct { backupRequest *Request dynamicFactory client.DynamicFactory discoveryHelper discovery.Helper - backedUpItems map[itemKey]struct{} cohabitatingResources map[string]*cohabitatingResource podCommandExecutor podexec.PodCommandExecutor tarWriter tarWriter @@ -156,7 +152,6 @@ func (rb *defaultResourceBackupper) backupResource(group *metav1.APIResourceList itemBackupper := rb.itemBackupperFactory.newItemBackupper( rb.backupRequest, - rb.backedUpItems, rb.podCommandExecutor, rb.tarWriter, rb.dynamicFactory, diff --git a/pkg/builder/role_builder.go b/pkg/builder/role_builder.go new file mode 100644 index 000000000..4c167b2bc --- /dev/null +++ b/pkg/builder/role_builder.go @@ -0,0 +1,57 @@ +/* +Copyright 2019 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package builder + +import ( + rbacv1api "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// RoleBuilder builds Role objects. +type RoleBuilder struct { + object *rbacv1api.Role +} + +// ForRole is the constructor for a RoleBuilder. +func ForRole(ns, name string) *RoleBuilder { + return &RoleBuilder{ + object: &rbacv1api.Role{ + TypeMeta: metav1.TypeMeta{ + APIVersion: rbacv1api.SchemeGroupVersion.String(), + Kind: "Role", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: name, + }, + }, + } +} + +// Result returns the built Role. +func (b *RoleBuilder) Result() *rbacv1api.Role { + return b.object +} + +// ObjectMeta applies functional options to the Role's ObjectMeta. +func (b *RoleBuilder) ObjectMeta(opts ...ObjectMetaOpt) *RoleBuilder { + for _, opt := range opts { + opt(b.object) + } + + return b +} diff --git a/pkg/controller/backup_controller.go b/pkg/controller/backup_controller.go index 9e79a82f9..e9b49b83c 100644 --- a/pkg/controller/backup_controller.go +++ b/pkg/controller/backup_controller.go @@ -570,7 +570,6 @@ func persistBackup(backup *pkgbackup.Request, backupContents, backupLog *os.File volumeSnapshots := new(bytes.Buffer) gzw := gzip.NewWriter(volumeSnapshots) - defer gzw.Close() if err := json.NewEncoder(gzw).Encode(backup.VolumeSnapshots); err != nil { errs = append(errs, errors.Wrap(err, "error encoding list of volume snapshots")) @@ -581,7 +580,6 @@ func persistBackup(backup *pkgbackup.Request, backupContents, backupLog *os.File podVolumeBackups := new(bytes.Buffer) gzw = gzip.NewWriter(podVolumeBackups) - defer gzw.Close() if err := json.NewEncoder(gzw).Encode(backup.PodVolumeBackups); err != nil { errs = append(errs, errors.Wrap(err, "error encoding pod volume backups")) @@ -590,20 +588,32 @@ func persistBackup(backup *pkgbackup.Request, backupContents, backupLog *os.File errs = append(errs, errors.Wrap(err, "error closing gzip writer")) } + backupResourceList := new(bytes.Buffer) + gzw = gzip.NewWriter(backupResourceList) + + if err := json.NewEncoder(gzw).Encode(backup.BackupResourceList()); err != nil { + errs = append(errs, errors.Wrap(err, "error encoding backup resource list")) + } + if err := gzw.Close(); err != nil { + errs = append(errs, errors.Wrap(err, "error closing gzip writer")) + } + if len(errs) > 0 { // Don't upload the JSON files or backup tarball if encoding to json fails. backupJSON = nil backupContents = nil volumeSnapshots = nil + backupResourceList = nil } backupInfo := persistence.BackupInfo{ - Name: backup.Name, - Metadata: backupJSON, - Contents: backupContents, - Log: backupLog, - PodVolumeBackups: podVolumeBackups, - VolumeSnapshots: volumeSnapshots, + Name: backup.Name, + Metadata: backupJSON, + Contents: backupContents, + Log: backupLog, + PodVolumeBackups: podVolumeBackups, + VolumeSnapshots: volumeSnapshots, + BackupResourceList: backupResourceList, } if err := backupStore.PutBackup(backupInfo); err != nil { errs = append(errs, err) diff --git a/pkg/persistence/object_store.go b/pkg/persistence/object_store.go index c1cd5b912..e46e7742c 100644 --- a/pkg/persistence/object_store.go +++ b/pkg/persistence/object_store.go @@ -36,8 +36,13 @@ import ( ) type BackupInfo struct { - Name string - Metadata, Contents, Log, PodVolumeBackups, VolumeSnapshots io.Reader + Name string + Metadata, + Contents, + Log, + PodVolumeBackups, + VolumeSnapshots, + BackupResourceList io.Reader } // BackupStore defines operations for creating, retrieving, and deleting @@ -231,6 +236,18 @@ func (s *objectBackupStore) PutBackup(info BackupInfo) error { return kerrors.NewAggregate(errs) } + if err := seekAndPutObject(s.objectStore, s.bucket, s.layout.getBackupResourceListKey(info.Name), info.BackupResourceList); err != nil { + errs := []error{err} + + deleteErr := s.objectStore.DeleteObject(s.bucket, s.layout.getBackupContentsKey(info.Name)) + errs = append(errs, deleteErr) + + deleteErr = s.objectStore.DeleteObject(s.bucket, s.layout.getBackupMetadataKey(info.Name)) + errs = append(errs, deleteErr) + + return kerrors.NewAggregate(errs) + } + if err := s.putRevision(); err != nil { s.logger.WithField("backup", info.Name).WithError(err).Warn("Error updating backup store revision") } diff --git a/pkg/persistence/object_store_layout.go b/pkg/persistence/object_store_layout.go index f032e0d28..40fe610c7 100644 --- a/pkg/persistence/object_store_layout.go +++ b/pkg/persistence/object_store_layout.go @@ -91,6 +91,10 @@ func (l *ObjectStoreLayout) getBackupVolumeSnapshotsKey(backup string) string { return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-volumesnapshots.json.gz", backup)) } +func (l *ObjectStoreLayout) getBackupResourceListKey(backup string) string { + return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-resource-list.json.gz", backup)) +} + func (l *ObjectStoreLayout) getRestoreLogKey(restore string) string { return path.Join(l.subdirs["restores"], restore, fmt.Sprintf("restore-%s-logs.gz", restore)) } diff --git a/pkg/persistence/object_store_test.go b/pkg/persistence/object_store_test.go index c597c6a53..8f84376d6 100644 --- a/pkg/persistence/object_store_test.go +++ b/pkg/persistence/object_store_test.go @@ -217,6 +217,7 @@ func TestPutBackup(t *testing.T) { log io.Reader podVolumeBackup io.Reader snapshots io.Reader + resourceList io.Reader expectedErr string expectedKeys []string }{ @@ -227,6 +228,7 @@ func TestPutBackup(t *testing.T) { log: newStringReadSeeker("log"), podVolumeBackup: newStringReadSeeker("podVolumeBackup"), snapshots: newStringReadSeeker("snapshots"), + resourceList: newStringReadSeeker("resourceList"), expectedErr: "", expectedKeys: []string{ "backups/backup-1/velero-backup.json", @@ -234,6 +236,7 @@ func TestPutBackup(t *testing.T) { "backups/backup-1/backup-1-logs.gz", "backups/backup-1/backup-1-podvolumebackups.json.gz", "backups/backup-1/backup-1-volumesnapshots.json.gz", + "backups/backup-1/backup-1-resource-list.json.gz", "metadata/revision", }, }, @@ -245,6 +248,7 @@ func TestPutBackup(t *testing.T) { log: newStringReadSeeker("log"), podVolumeBackup: newStringReadSeeker("podVolumeBackup"), snapshots: newStringReadSeeker("snapshots"), + resourceList: newStringReadSeeker("resourceList"), expectedErr: "", expectedKeys: []string{ "prefix-1/backups/backup-1/velero-backup.json", @@ -252,6 +256,7 @@ func TestPutBackup(t *testing.T) { "prefix-1/backups/backup-1/backup-1-logs.gz", "prefix-1/backups/backup-1/backup-1-podvolumebackups.json.gz", "prefix-1/backups/backup-1/backup-1-volumesnapshots.json.gz", + "prefix-1/backups/backup-1/backup-1-resource-list.json.gz", "prefix-1/metadata/revision", }, }, @@ -262,6 +267,7 @@ func TestPutBackup(t *testing.T) { log: newStringReadSeeker("log"), podVolumeBackup: newStringReadSeeker("podVolumeBackup"), snapshots: newStringReadSeeker("snapshots"), + resourceList: newStringReadSeeker("resourceList"), expectedErr: "error readers return errors", expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"}, }, @@ -271,6 +277,7 @@ func TestPutBackup(t *testing.T) { contents: new(errorReader), log: newStringReadSeeker("log"), snapshots: newStringReadSeeker("snapshots"), + resourceList: newStringReadSeeker("resourceList"), expectedErr: "error readers return errors", expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"}, }, @@ -281,12 +288,14 @@ func TestPutBackup(t *testing.T) { log: new(errorReader), podVolumeBackup: newStringReadSeeker("podVolumeBackup"), snapshots: newStringReadSeeker("snapshots"), + resourceList: newStringReadSeeker("resourceList"), expectedErr: "", expectedKeys: []string{ "backups/backup-1/velero-backup.json", "backups/backup-1/backup-1.tar.gz", "backups/backup-1/backup-1-podvolumebackups.json.gz", "backups/backup-1/backup-1-volumesnapshots.json.gz", + "backups/backup-1/backup-1-resource-list.json.gz", "metadata/revision", }, }, @@ -297,6 +306,7 @@ func TestPutBackup(t *testing.T) { log: newStringReadSeeker("log"), podVolumeBackup: newStringReadSeeker("podVolumeBackup"), snapshots: newStringReadSeeker("snapshots"), + resourceList: newStringReadSeeker("resourceList"), expectedErr: "", expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"}, }, @@ -307,12 +317,13 @@ func TestPutBackup(t *testing.T) { harness := newObjectBackupStoreTestHarness("foo", tc.prefix) backupInfo := BackupInfo{ - Name: "backup-1", - Metadata: tc.metadata, - Contents: tc.contents, - Log: tc.log, - PodVolumeBackups: tc.podVolumeBackup, - VolumeSnapshots: tc.snapshots, + Name: "backup-1", + Metadata: tc.metadata, + Contents: tc.contents, + Log: tc.log, + PodVolumeBackups: tc.podVolumeBackup, + VolumeSnapshots: tc.snapshots, + BackupResourceList: tc.resourceList, } err := harness.PutBackup(backupInfo)