Define itemoperations.json format and update DownloadRequest API.

This is to support uploading async operation metadata to
object storage to support progress monitoring.

Signed-off-by: Scott Seago <sseago@redhat.com>
This commit is contained in:
Scott Seago
2023-01-09 17:56:01 -05:00
parent 722aead2fd
commit 70b4238013
13 changed files with 349 additions and 174 deletions

View File

@@ -0,0 +1 @@
Define itemoperations.json format and update DownloadRequest API

View File

@@ -46,10 +46,11 @@ spec:
- BackupLog
- BackupContents
- BackupVolumeSnapshots
- BackupItemSnapshots
- BackupItemOperations
- BackupResourceList
- RestoreLog
- RestoreResults
- RestoreItemOperations
- CSIBackupVolumeSnapshots
- CSIBackupVolumeSnapshotContents
type: string

File diff suppressed because one or more lines are too long

View File

@@ -46,8 +46,4 @@ const (
// APIGroupVersionsFeatureFlag is the feature flag string that defines whether or not to handle multiple API Group Versions
APIGroupVersionsFeatureFlag = "EnableAPIGroupVersions"
// UploadProgressFeatureFlag is the feature flag string that defines whether or not upload progress monitoring is enabled
// and whether or not ItemSnapshotters should be invoked
UploadProgressFeatureFlag = "EnableUploadProgress"
)

View File

@@ -25,17 +25,18 @@ type DownloadRequestSpec struct {
}
// DownloadTargetKind represents what type of file to download.
// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemSnapshots;BackupResourceList;RestoreLog;RestoreResults;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents
// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemOperations;BackupResourceList;RestoreLog;RestoreResults;RestoreItemOperations;CSIBackupVolumeSnapshots;CSIBackupVolumeSnapshotContents
type DownloadTargetKind string
const (
DownloadTargetKindBackupLog DownloadTargetKind = "BackupLog"
DownloadTargetKindBackupContents DownloadTargetKind = "BackupContents"
DownloadTargetKindBackupVolumeSnapshots DownloadTargetKind = "BackupVolumeSnapshots"
DownloadTargetKindBackupItemSnapshots DownloadTargetKind = "BackupItemSnapshots"
DownloadTargetKindBackupItemOperations DownloadTargetKind = "BackupItemOperations"
DownloadTargetKindBackupResourceList DownloadTargetKind = "BackupResourceList"
DownloadTargetKindRestoreLog DownloadTargetKind = "RestoreLog"
DownloadTargetKindRestoreResults DownloadTargetKind = "RestoreResults"
DownloadTargetKindRestoreItemOperations DownloadTargetKind = "RestoreItemOperations"
DownloadTargetKindCSIBackupVolumeSnapshots DownloadTargetKind = "CSIBackupVolumeSnapshots"
DownloadTargetKindCSIBackupVolumeSnapshotContents DownloadTargetKind = "CSIBackupVolumeSnapshotContents"
)

View File

@@ -0,0 +1,44 @@
/*
Copyright 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 itemoperation
// BackupOperation stores information about an async item operation
// started by a BackupItemAction plugin (v2 or later)
type BackupOperation struct {
Spec BackupOperationSpec `json:"spec"`
Status OperationStatus `json:"status"`
}
type BackupOperationSpec struct {
// BackupName is the name of the Velero backup this item operation
// is associated with.
BackupName string `json:"backupName"`
// BackupUID is the UID of the Velero backup this item operation
// is associated with.
BackupUID string `json:"backupUID"`
// BackupItemAction is the name of the BackupItemAction plugin that started the operation
BackupItemAction string `json:"backupItemAction"`
// Kubernetes resource identifier for the item
ResourceIdentifier string "json:resourceIdentifier"
// OperationID returned by the BIA plugin
OperationID string "json:operationID"
}

View File

@@ -0,0 +1,44 @@
/*
Copyright 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 itemoperation
// RestoreOperation stores information about an async item operation
// started by a RestoreItemAction plugin (v2 or later)
type RestoreOperation struct {
Spec RestoreOperationSpec `json:"spec"`
Status OperationStatus `json:"status"`
}
type RestoreOperationSpec struct {
// RestoreName is the name of the Velero restore this item operation
// is associated with.
RestoreName string `json:"restoreName"`
// RestoreUID is the UID of the Velero restore this item operation
// is associated with.
RestoreUID string `json:"restoreUID"`
// RestoreItemAction is the name of the RestoreItemAction plugin that started the operation
RestoreItemAction string `json:"restoreItemAction"`
// Kubernetes resource identifier for the item
ResourceIdentifier string "json:resourceIdentifier"
// OperationID returned by the RIA plugin
OperationID string "json:operationID"
}

View File

@@ -0,0 +1,67 @@
/*
Copyright 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 itemoperation
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// OperationPhase is the lifecycle phase of a Velero item operation
type OperationPhase string
type OperationStatus struct {
// Phase is the current state of the item operation.
Phase OperationPhase `json:"phase,omitempty"`
// Error displays the reason for a failed operation
Error string `json:"error,omitempty"`
// Amount of operation completed (measured in OperationUnits)
// i.e. number of bytes transferred for a volume
NCompleted int64 `json:"nCompleted,omitempty"`
// Total Amount of operation (measured in OperationUnits)
// i.e. volume size in bytes
NTotal int64 `json:"nTotal,omitempty"`
// Units that NCompleted,NTotal are measured in
// i.e. "bytes"
OperationUnits int64 `json:"nTotal,omitempty"`
// Started records the time the item operation was started, if known
// +optional
// +nullable
Started *metav1.Time `json:"start,omitempty"`
// Updated records the time the item operation was updated, if known.
// +optional
// +nullable
Updated *metav1.Time `json:"updated,omitempty"`
}
const (
// OperationPhaseNew means the item operation has been created and started
// by the plugin
OperationPhaseInProgress OperationPhase = "New"
// OperationPhaseCompleted means the item operation was successfully completed
// and can be used for restore.
OperationPhaseCompleted OperationPhase = "Completed"
// OperationPhaseFailed means the item operation ended with an error.
OperationPhaseFailed OperationPhase = "Failed"
)

View File

@@ -24,6 +24,7 @@ import (
snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/itemoperation"
persistence "github.com/vmware-tanzu/velero/pkg/persistence"
volume "github.com/vmware-tanzu/velero/pkg/volume"
)
@@ -274,6 +275,34 @@ func (_m *BackupStore) PutRestoreResults(backup string, restore string, results
return r0
}
// PutRestoreItemOperations provides a mock function with given fields: backup, restore, restoreItemOperations
func (_m *BackupStore) PutRestoreItemOperations(backup string, restore string, restoreItemOperations io.Reader) error {
ret := _m.Called(backup, restore, restoreItemOperations)
var r0 error
if rf, ok := ret.Get(0).(func(string, string, io.Reader) error); ok {
r0 = rf(backup, restore, restoreItemOperations)
} else {
r0 = ret.Error(0)
}
return r0
}
// PutBackupItemOperations provides a mock function with given fields: backup, backupItemOperations
func (_m *BackupStore) PutBackupItemOperations(backup string, backupItemOperations io.Reader) error {
ret := _m.Called(backup, backupItemOperations)
var r0 error
if rf, ok := ret.Get(0).(func(string, io.Reader) error); ok {
r0 = rf(backup, backupItemOperations)
} else {
r0 = ret.Error(0)
}
return r0
}
func (_m *BackupStore) GetCSIVolumeSnapshots(backup string) ([]*snapshotv1api.VolumeSnapshot, error) {
panic("Not implemented")
return nil, nil
@@ -289,6 +318,12 @@ func (_m *BackupStore) GetCSIVolumeSnapshotClasses(backup string) ([]*snapshotv1
return nil, nil
}
func (_m *BackupStore) GetItemSnapshots(name string) ([]*volume.ItemSnapshot, error) {
func (_m *BackupStore) GetBackupItemOperations(name string) ([]*itemoperation.BackupOperation, error) {
panic("implement me")
return nil, nil
}
func (_m *BackupStore) GetRestoreItemOperations(name string) ([]*itemoperation.RestoreOperation, error) {
panic("implement me")
return nil, nil
}

View File

@@ -33,6 +33,7 @@ import (
"github.com/vmware-tanzu/velero/internal/credentials"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme"
"github.com/vmware-tanzu/velero/pkg/itemoperation"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"github.com/vmware-tanzu/velero/pkg/volume"
)
@@ -44,7 +45,7 @@ type BackupInfo struct {
Log,
PodVolumeBackups,
VolumeSnapshots,
ItemSnapshots,
BackupItemOperations,
BackupResourceList,
CSIVolumeSnapshots,
CSIVolumeSnapshotContents,
@@ -59,8 +60,9 @@ type BackupStore interface {
ListBackups() ([]string, error)
PutBackup(info BackupInfo) error
PutBackupItemOperations(backup string, backupItemOperations io.Reader) error
GetBackupMetadata(name string) (*velerov1api.Backup, error)
GetItemSnapshots(name string) ([]*volume.ItemSnapshot, error)
GetBackupItemOperations(name string) ([]*itemoperation.BackupOperation, error)
GetBackupVolumeSnapshots(name string) ([]*volume.Snapshot, error)
GetPodVolumeBackups(name string) ([]*velerov1api.PodVolumeBackup, error)
GetBackupContents(name string) (io.ReadCloser, error)
@@ -75,6 +77,8 @@ type BackupStore interface {
PutRestoreLog(backup, restore string, log io.Reader) error
PutRestoreResults(backup, restore string, results io.Reader) error
PutRestoreItemOperations(backup, restore string, restoreItemOperations io.Reader) error
GetRestoreItemOperations(name string) ([]*itemoperation.RestoreOperation, error)
DeleteRestore(name string) error
GetDownloadURL(target velerov1api.DownloadTarget) (string, error)
@@ -256,7 +260,7 @@ func (s *objectBackupStore) PutBackup(info BackupInfo) error {
var backupObjs = map[string]io.Reader{
s.layout.getPodVolumeBackupsKey(info.Name): info.PodVolumeBackups,
s.layout.getBackupVolumeSnapshotsKey(info.Name): info.VolumeSnapshots,
s.layout.getItemSnapshotsKey(info.Name): info.ItemSnapshots,
s.layout.getBackupItemOperationsKey(info.Name): info.BackupItemOperations,
s.layout.getBackupResourceListKey(info.Name): info.BackupResourceList,
s.layout.getCSIVolumeSnapshotKey(info.Name): info.CSIVolumeSnapshots,
s.layout.getCSIVolumeSnapshotContentsKey(info.Name): info.CSIVolumeSnapshotContents,
@@ -329,11 +333,11 @@ func (s *objectBackupStore) GetBackupVolumeSnapshots(name string) ([]*volume.Sna
return volumeSnapshots, nil
}
func (s *objectBackupStore) GetItemSnapshots(name string) ([]*volume.ItemSnapshot, error) {
// if the itemsnapshots file doesn't exist, we don't want to return an error, since
// a legacy backup or a backup with no snapshots would not have this file, so check for
func (s *objectBackupStore) GetBackupItemOperations(name string) ([]*itemoperation.BackupOperation, error) {
// if the itemoperations file doesn't exist, we don't want to return an error, since
// a legacy backup or a backup with no async operations would not have this file, so check for
// its existence before attempting to get its contents.
res, err := tryGet(s.objectStore, s.bucket, s.layout.getItemSnapshotsKey(name))
res, err := tryGet(s.objectStore, s.bucket, s.layout.getBackupItemOperationsKey(name))
if err != nil {
return nil, err
}
@@ -342,12 +346,33 @@ func (s *objectBackupStore) GetItemSnapshots(name string) ([]*volume.ItemSnapsho
}
defer res.Close()
var itemSnapshots []*volume.ItemSnapshot
if err := decode(res, &itemSnapshots); err != nil {
var backupItemOperations []*itemoperation.BackupOperation
if err := decode(res, &backupItemOperations); err != nil {
return nil, err
}
return itemSnapshots, nil
return backupItemOperations, nil
}
func (s *objectBackupStore) GetRestoreItemOperations(name string) ([]*itemoperation.RestoreOperation, error) {
// if the itemoperations file doesn't exist, we don't want to return an error, since
// a legacy restore or a restore with no async operations would not have this file, so check for
// its existence before attempting to get its contents.
res, err := tryGet(s.objectStore, s.bucket, s.layout.getRestoreItemOperationsKey(name))
if err != nil {
return nil, err
}
if res == nil {
return nil, nil
}
defer res.Close()
var restoreItemOperations []*itemoperation.RestoreOperation
if err := decode(res, &restoreItemOperations); err != nil {
return nil, err
}
return restoreItemOperations, nil
}
// tryGet returns the object with the given key if it exists, nil if it does not exist,
@@ -510,6 +535,14 @@ func (s *objectBackupStore) PutRestoreResults(backup string, restore string, res
return s.objectStore.PutObject(s.bucket, s.layout.getRestoreResultsKey(restore), results)
}
func (s *objectBackupStore) PutRestoreItemOperations(backup string, restore string, restoreItemOperations io.Reader) error {
return seekAndPutObject(s.objectStore, s.bucket, s.layout.getRestoreItemOperationsKey(restore), restoreItemOperations)
}
func (s *objectBackupStore) PutBackupItemOperations(backup string, backupItemOperations io.Reader) error {
return seekAndPutObject(s.objectStore, s.bucket, s.layout.getBackupItemOperationsKey(backup), backupItemOperations)
}
func (s *objectBackupStore) GetDownloadURL(target velerov1api.DownloadTarget) (string, error) {
switch target.Kind {
case velerov1api.DownloadTargetKindBackupContents:
@@ -518,8 +551,10 @@ func (s *objectBackupStore) GetDownloadURL(target velerov1api.DownloadTarget) (s
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupLogKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindBackupVolumeSnapshots:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupVolumeSnapshotsKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindBackupItemSnapshots:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getItemSnapshotsKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindBackupItemOperations:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupItemOperationsKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindRestoreItemOperations:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getRestoreItemOperationsKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindBackupResourceList:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupResourceListKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindRestoreLog:

View File

@@ -89,8 +89,8 @@ func (l *ObjectStoreLayout) getBackupVolumeSnapshotsKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-volumesnapshots.json.gz", backup))
}
func (l *ObjectStoreLayout) getItemSnapshotsKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-itemsnapshots.json.gz", backup))
func (l *ObjectStoreLayout) getBackupItemOperationsKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-itemoperations.json.gz", backup))
}
func (l *ObjectStoreLayout) getBackupResourceListKey(backup string) string {
@@ -105,6 +105,10 @@ func (l *ObjectStoreLayout) getRestoreResultsKey(restore string) string {
return path.Join(l.subdirs["restores"], restore, fmt.Sprintf("restore-%s-results.gz", restore))
}
func (l *ObjectStoreLayout) getRestoreItemOperationsKey(restore string) string {
return path.Join(l.subdirs["restores"], restore, fmt.Sprintf("restore-%s-itemoperations.json.gz", restore))
}
func (l *ObjectStoreLayout) getCSIVolumeSnapshotKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-csi-volumesnapshots.json.gz", backup))
}

View File

@@ -36,6 +36,7 @@ import (
"github.com/vmware-tanzu/velero/internal/credentials"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/itemoperation"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
providermocks "github.com/vmware-tanzu/velero/pkg/plugin/velero/mocks"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
@@ -216,98 +217,98 @@ func TestListBackups(t *testing.T) {
func TestPutBackup(t *testing.T) {
tests := []struct {
name string
prefix string
metadata io.Reader
contents io.Reader
log io.Reader
podVolumeBackup io.Reader
snapshots io.Reader
itemSnapshots io.Reader
resourceList io.Reader
expectedErr string
expectedKeys []string
name string
prefix string
metadata io.Reader
contents io.Reader
log io.Reader
podVolumeBackup io.Reader
snapshots io.Reader
backupItemOperations io.Reader
resourceList io.Reader
expectedErr string
expectedKeys []string
}{
{
name: "normal case",
metadata: newStringReadSeeker("metadata"),
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
itemSnapshots: newStringReadSeeker("itemSnapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "",
name: "normal case",
metadata: newStringReadSeeker("metadata"),
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
backupItemOperations: newStringReadSeeker("backupItemOperations"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "",
expectedKeys: []string{
"backups/backup-1/velero-backup.json",
"backups/backup-1/backup-1.tar.gz",
"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-itemsnapshots.json.gz",
"backups/backup-1/backup-1-itemoperations.json.gz",
"backups/backup-1/backup-1-resource-list.json.gz",
},
},
{
name: "normal case with backup store prefix",
prefix: "prefix-1/",
metadata: newStringReadSeeker("metadata"),
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
itemSnapshots: newStringReadSeeker("itemSnapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "",
name: "normal case with backup store prefix",
prefix: "prefix-1/",
metadata: newStringReadSeeker("metadata"),
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
backupItemOperations: newStringReadSeeker("backupItemOperations"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "",
expectedKeys: []string{
"prefix-1/backups/backup-1/velero-backup.json",
"prefix-1/backups/backup-1/backup-1.tar.gz",
"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-itemsnapshots.json.gz",
"prefix-1/backups/backup-1/backup-1-itemoperations.json.gz",
"prefix-1/backups/backup-1/backup-1-resource-list.json.gz",
},
},
{
name: "error on metadata upload does not upload data",
metadata: new(errorReader),
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
itemSnapshots: newStringReadSeeker("itemSnapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "error readers return errors",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
name: "error on metadata upload does not upload data",
metadata: new(errorReader),
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
backupItemOperations: newStringReadSeeker("backupItemOperations"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "error readers return errors",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
},
{
name: "error on data upload deletes metadata",
metadata: newStringReadSeeker("metadata"),
contents: new(errorReader),
log: newStringReadSeeker("log"),
snapshots: newStringReadSeeker("snapshots"),
itemSnapshots: newStringReadSeeker("itemSnapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "error readers return errors",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
name: "error on data upload deletes metadata",
metadata: newStringReadSeeker("metadata"),
contents: new(errorReader),
log: newStringReadSeeker("log"),
snapshots: newStringReadSeeker("snapshots"),
backupItemOperations: newStringReadSeeker("backupItemOperations"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "error readers return errors",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
},
{
name: "error on log upload is ok",
metadata: newStringReadSeeker("foo"),
contents: newStringReadSeeker("bar"),
log: new(errorReader),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
itemSnapshots: newStringReadSeeker("itemSnapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "",
name: "error on log upload is ok",
metadata: newStringReadSeeker("foo"),
contents: newStringReadSeeker("bar"),
log: new(errorReader),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
backupItemOperations: newStringReadSeeker("backupItemOperations"),
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-itemsnapshots.json.gz",
"backups/backup-1/backup-1-itemoperations.json.gz",
"backups/backup-1/backup-1-resource-list.json.gz",
},
},
@@ -335,14 +336,14 @@ 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,
ItemSnapshots: tc.itemSnapshots,
BackupResourceList: tc.resourceList,
Name: "backup-1",
Metadata: tc.metadata,
Contents: tc.contents,
Log: tc.log,
PodVolumeBackups: tc.podVolumeBackup,
VolumeSnapshots: tc.snapshots,
BackupItemOperations: tc.backupItemOperations,
BackupResourceList: tc.resourceList,
}
err := harness.PutBackup(backupInfo)
@@ -442,30 +443,30 @@ func TestGetBackupVolumeSnapshots(t *testing.T) {
assert.EqualValues(t, snapshots, res)
}
func TestGetItemSnapshots(t *testing.T) {
func TestGetBackupItemOperations(t *testing.T) {
harness := newObjectBackupStoreTestHarness("test-bucket", "")
// volumesnapshots file not found should not error
// itemoperations file not found should not error
harness.objectStore.PutObject(harness.bucket, "backups/test-backup/velero-backup.json", newStringReadSeeker("foo"))
res, err := harness.GetItemSnapshots("test-backup")
res, err := harness.GetBackupItemOperations("test-backup")
assert.NoError(t, err)
assert.Nil(t, res)
// volumesnapshots file containing invalid data should error
harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-itemsnapshots.json.gz", newStringReadSeeker("foo"))
res, err = harness.GetItemSnapshots("test-backup")
// itemoperations file containing invalid data should error
harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-itemoperations.json.gz", newStringReadSeeker("foo"))
res, err = harness.GetBackupItemOperations("test-backup")
assert.NotNil(t, err)
// volumesnapshots file containing gzipped json data should return correctly
snapshots := []*volume.ItemSnapshot{
// itemoperations file containing gzipped json data should return correctly
operations := []*itemoperation.BackupOperation{
{
Spec: volume.ItemSnapshotSpec{
Spec: itemoperation.BackupOperationSpec{
BackupName: "test-backup",
ResourceIdentifier: "item-1",
},
},
{
Spec: volume.ItemSnapshotSpec{
Spec: itemoperation.BackupOperationSpec{
BackupName: "test-backup",
ResourceIdentifier: "item-2",
},
@@ -475,13 +476,13 @@ func TestGetItemSnapshots(t *testing.T) {
obj := new(bytes.Buffer)
gzw := gzip.NewWriter(obj)
require.NoError(t, json.NewEncoder(gzw).Encode(snapshots))
require.NoError(t, json.NewEncoder(gzw).Encode(operations))
require.NoError(t, gzw.Close())
require.NoError(t, harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-itemsnapshots.json.gz", obj))
require.NoError(t, harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-itemoperations.json.gz", obj))
res, err = harness.GetItemSnapshots("test-backup")
res, err = harness.GetBackupItemOperations("test-backup")
assert.NoError(t, err)
assert.EqualValues(t, snapshots, res)
assert.EqualValues(t, operations, res)
}
func TestGetBackupContents(t *testing.T) {
@@ -564,7 +565,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindBackupContents: "backups/my-backup/my-backup.tar.gz",
velerov1api.DownloadTargetKindBackupLog: "backups/my-backup/my-backup-logs.gz",
velerov1api.DownloadTargetKindBackupVolumeSnapshots: "backups/my-backup/my-backup-volumesnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemSnapshots: "backups/my-backup/my-backup-itemsnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemOperations: "backups/my-backup/my-backup-itemoperations.json.gz",
velerov1api.DownloadTargetKindBackupResourceList: "backups/my-backup/my-backup-resource-list.json.gz",
},
},
@@ -576,7 +577,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindBackupContents: "velero-backups/backups/my-backup/my-backup.tar.gz",
velerov1api.DownloadTargetKindBackupLog: "velero-backups/backups/my-backup/my-backup-logs.gz",
velerov1api.DownloadTargetKindBackupVolumeSnapshots: "velero-backups/backups/my-backup/my-backup-volumesnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemSnapshots: "velero-backups/backups/my-backup/my-backup-itemsnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemOperations: "velero-backups/backups/my-backup/my-backup-itemoperations.json.gz",
velerov1api.DownloadTargetKindBackupResourceList: "velero-backups/backups/my-backup/my-backup-resource-list.json.gz",
},
},
@@ -587,7 +588,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindBackupContents: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902.tar.gz",
velerov1api.DownloadTargetKindBackupLog: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-logs.gz",
velerov1api.DownloadTargetKindBackupVolumeSnapshots: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-volumesnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemSnapshots: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-itemsnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemOperations: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-itemoperations.json.gz",
velerov1api.DownloadTargetKindBackupResourceList: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-resource-list.json.gz",
},
},
@@ -598,7 +599,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindBackupContents: "backups/my-backup-20170913154901/my-backup-20170913154901.tar.gz",
velerov1api.DownloadTargetKindBackupLog: "backups/my-backup-20170913154901/my-backup-20170913154901-logs.gz",
velerov1api.DownloadTargetKindBackupVolumeSnapshots: "backups/my-backup-20170913154901/my-backup-20170913154901-volumesnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemSnapshots: "backups/my-backup-20170913154901/my-backup-20170913154901-itemsnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemOperations: "backups/my-backup-20170913154901/my-backup-20170913154901-itemoperations.json.gz",
velerov1api.DownloadTargetKindBackupResourceList: "backups/my-backup-20170913154901/my-backup-20170913154901-resource-list.json.gz",
},
},
@@ -610,7 +611,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindBackupContents: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901.tar.gz",
velerov1api.DownloadTargetKindBackupLog: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-logs.gz",
velerov1api.DownloadTargetKindBackupVolumeSnapshots: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-volumesnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemSnapshots: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-itemsnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemOperations: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-itemoperations.json.gz",
velerov1api.DownloadTargetKindBackupResourceList: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-resource-list.json.gz",
},
},
@@ -618,8 +619,9 @@ func TestGetDownloadURL(t *testing.T) {
name: "restore",
targetName: "my-backup",
expectedKeyByKind: map[velerov1api.DownloadTargetKind]string{
velerov1api.DownloadTargetKindRestoreLog: "restores/my-backup/restore-my-backup-logs.gz",
velerov1api.DownloadTargetKindRestoreResults: "restores/my-backup/restore-my-backup-results.gz",
velerov1api.DownloadTargetKindRestoreLog: "restores/my-backup/restore-my-backup-logs.gz",
velerov1api.DownloadTargetKindRestoreResults: "restores/my-backup/restore-my-backup-results.gz",
velerov1api.DownloadTargetKindRestoreItemOperations: "restores/my-backup/restore-my-backup-itemoperations.json.gz",
},
},
{
@@ -627,16 +629,18 @@ func TestGetDownloadURL(t *testing.T) {
targetName: "my-backup",
prefix: "velero-backups/",
expectedKeyByKind: map[velerov1api.DownloadTargetKind]string{
velerov1api.DownloadTargetKindRestoreLog: "velero-backups/restores/my-backup/restore-my-backup-logs.gz",
velerov1api.DownloadTargetKindRestoreResults: "velero-backups/restores/my-backup/restore-my-backup-results.gz",
velerov1api.DownloadTargetKindRestoreLog: "velero-backups/restores/my-backup/restore-my-backup-logs.gz",
velerov1api.DownloadTargetKindRestoreResults: "velero-backups/restores/my-backup/restore-my-backup-results.gz",
velerov1api.DownloadTargetKindRestoreItemOperations: "velero-backups/restores/my-backup/restore-my-backup-itemoperations.json.gz",
},
},
{
name: "restore with multiple dashes",
targetName: "b-cool-20170913154901-20170913154902",
expectedKeyByKind: map[velerov1api.DownloadTargetKind]string{
velerov1api.DownloadTargetKindRestoreLog: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-logs.gz",
velerov1api.DownloadTargetKindRestoreResults: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-results.gz",
velerov1api.DownloadTargetKindRestoreLog: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-logs.gz",
velerov1api.DownloadTargetKindRestoreResults: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-results.gz",
velerov1api.DownloadTargetKindRestoreItemOperations: "restores/b-cool-20170913154901-20170913154902/restore-b-cool-20170913154901-20170913154902-itemoperations.json.gz",
},
},
}

View File

@@ -1,57 +0,0 @@
/*
Copyright 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 volume
import isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
// ItemSnapshot stores information about an item snapshot (includes volumes and other Astrolabe objects) taken as
// part of a Velero backup.
type ItemSnapshot struct {
Spec ItemSnapshotSpec `json:"spec"`
Status ItemSnapshotStatus `json:"status"`
}
type ItemSnapshotSpec struct {
// ItemSnapshotter is the name of the ItemSnapshotter plugin that took the snapshot
ItemSnapshotter string `json:"itemSnapshotter"`
// BackupName is the name of the Velero backup this snapshot
// is associated with.
BackupName string `json:"backupName"`
// BackupUID is the UID of the Velero backup this snapshot
// is associated with.
BackupUID string `json:"backupUID"`
// Location is the name of the location where this snapshot is stored.
Location string `json:"location"`
// Kubernetes resource identifier for the item
ResourceIdentifier string "json:resourceIdentifier"
}
type ItemSnapshotStatus struct {
// ProviderSnapshotID is the ID of the snapshot taken by the ItemSnapshotter
ProviderSnapshotID string `json:"providerSnapshotID,omitempty"`
// Metadata is the metadata returned with the snapshot to be returned to the ItemSnapshotter at restore time
Metadata map[string]string `json:"metadata,omitempty"`
// Phase is the current state of the ItemSnapshot.
Phase isv1.SnapshotPhase `json:"phase,omitempty"`
}