Skip to content

Commit

Permalink
Add backup VolumeInfo's object storage's put and get methods.
Browse files Browse the repository at this point in the history
Signed-off-by: Xun Jiang <[email protected]>
  • Loading branch information
Xun Jiang committed Nov 9, 2023
1 parent 85815ee commit 4789664
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 18 deletions.
6 changes: 3 additions & 3 deletions design/pv_backup_info.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type VolumeInfoV1 struct {
StartTimestamp *metav1.Time // Snapshot starts timestamp.

CSISnapshotInfo CSISnapshotInfo
SnapshotDataMoveInfo SnapshotDataMoveInfo
SnapshotDataMovementInfo SnapshotDataMovementInfo
NativeSnapshotInfo VeleroNativeSnapshotInfo
PVBInfo PodVolumeBackupInfo
PVInfo PVInfo
Expand All @@ -65,8 +65,8 @@ type CSISnapshotInfo struct {
VSCName string // The name of the VolumeSnapshotContent.
}

// SnapshotDataMoveInfo is used for displaying the snapshot data mover status.
type SnapshotDataMoveInfo struct {
// SnapshotDataMovementInfo is used for displaying the snapshot data mover status.
type SnapshotDataMovementInfo struct {
DataMover string // The data mover used by the backup. The valid values are `velero` and ``(equals to `velero`).
UploaderType string // The type of the uploader that uploads the snapshot data. The valid values are `kopia` and `restic`. It's useful for file-system backup and snapshot data mover.
RetainedSnapshot string // The name or ID of the snapshot associated object(SAO). SAO is used to support local snapshots for the snapshot data mover, e.g. it could be a VolumeSnapshot for CSI snapshot data movement.
Expand Down
8 changes: 8 additions & 0 deletions pkg/controller/backup_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,7 @@ func persistBackup(backup *pkgbackup.Request,
) []error {
persistErrs := []error{}
backupJSON := new(bytes.Buffer)
volumeInfos := make([]volume.VolumeInfoV1, 0)

if err := encode.To(backup.Backup, "json", backupJSON); err != nil {
persistErrs = append(persistErrs, errors.Wrap(err, "error encoding backup"))
Expand Down Expand Up @@ -895,6 +896,11 @@ func persistBackup(backup *pkgbackup.Request,
persistErrs = append(persistErrs, errs...)
}

volumeInfoJSON, errs := encode.ToJSONGzip(volumeInfos, "backup volumes information")
if errs != nil {
persistErrs = append(persistErrs, errs...)
}

Check warning on line 902 in pkg/controller/backup_controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controller/backup_controller.go#L901-L902

Added lines #L901 - L902 were not covered by tests

if len(persistErrs) > 0 {
// Don't upload the JSON files or backup tarball if encoding to json fails.
backupJSON = nil
Expand All @@ -906,6 +912,7 @@ func persistBackup(backup *pkgbackup.Request,
csiSnapshotContentsJSON = nil
csiSnapshotClassesJSON = nil
backupResult = nil
volumeInfoJSON = nil

Check warning on line 915 in pkg/controller/backup_controller.go

View check run for this annotation

Codecov / codecov/patch

pkg/controller/backup_controller.go#L915

Added line #L915 was not covered by tests
}

backupInfo := persistence.BackupInfo{
Expand All @@ -921,6 +928,7 @@ func persistBackup(backup *pkgbackup.Request,
CSIVolumeSnapshots: csiSnapshotJSON,
CSIVolumeSnapshotContents: csiSnapshotContentsJSON,
CSIVolumeSnapshotClasses: csiSnapshotClassesJSON,
BackupVolumeInfo: volumeInfoJSON,
}
if err := backupStore.PutBackup(backupInfo); err != nil {
persistErrs = append(persistErrs, err)
Expand Down
46 changes: 45 additions & 1 deletion pkg/persistence/object_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ limitations under the License.
package persistence

import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"strings"
"time"
Expand Down Expand Up @@ -50,7 +52,8 @@ type BackupInfo struct {
BackupResourceList,
CSIVolumeSnapshots,
CSIVolumeSnapshotContents,
CSIVolumeSnapshotClasses io.Reader
CSIVolumeSnapshotClasses,
BackupVolumeInfo io.Reader
}

// BackupStore defines operations for creating, retrieving, and deleting
Expand Down Expand Up @@ -270,6 +273,7 @@ func (s *objectBackupStore) PutBackup(info BackupInfo) error {
s.layout.getCSIVolumeSnapshotContentsKey(info.Name): info.CSIVolumeSnapshotContents,
s.layout.getCSIVolumeSnapshotClassesKey(info.Name): info.CSIVolumeSnapshotClasses,
s.layout.getBackupResultsKey(info.Name): info.BackupResults,
s.layout.getBackupVolumeInfoKey(info.Name): info.BackupVolumeInfo,
}

for key, reader := range backupObjs {
Expand Down Expand Up @@ -491,6 +495,46 @@ func (s *objectBackupStore) GetPodVolumeBackups(name string) ([]*velerov1api.Pod
return podVolumeBackups, nil
}

func (s *objectBackupStore) GetBackupVolumeInfos(name string) (*volume.VolumeInfos, error) {
var volumeInfos *volume.VolumeInfos
var volumeInfoVersion volume.VolumeInfoVersion

res, err := tryGet(s.objectStore, s.bucket, s.layout.getBackupVolumeInfoKey(name))
if err != nil {
return volumeInfos, err
}

Check warning on line 505 in pkg/persistence/object_store.go

View check run for this annotation

Codecov / codecov/patch

pkg/persistence/object_store.go#L504-L505

Added lines #L504 - L505 were not covered by tests
if res == nil {
return volumeInfos, nil
}
defer res.Close()

// The res's reader is a stream reader. If cannot be read twice.
// So copy it before using.
var copyReader bytes.Buffer
teeReader := io.TeeReader(res, &copyReader)

if err := decode(teeReader, &volumeInfoVersion); err != nil {
return volumeInfos, err
}

Check warning on line 518 in pkg/persistence/object_store.go

View check run for this annotation

Codecov / codecov/patch

pkg/persistence/object_store.go#L517-L518

Added lines #L517 - L518 were not covered by tests

switch volumeInfoVersion.Version {
case volume.VolumeInfoVersionV1:
volumeInfos = new(volume.VolumeInfos)
volumeInfos.Version = volume.VolumeInfoVersionV1
volumeInfosV1 := new(volume.VolumeInfosV1)
if err := decode(&copyReader, &volumeInfosV1); err != nil {
return volumeInfos, err
}

Check warning on line 527 in pkg/persistence/object_store.go

View check run for this annotation

Codecov / codecov/patch

pkg/persistence/object_store.go#L526-L527

Added lines #L526 - L527 were not covered by tests

volumeInfos.VolumeInfosV1 = volumeInfosV1.Infos

default:
return volumeInfos, fmt.Errorf("unknown backup VolumeInfo version: %s", volumeInfoVersion.Version)
}

return volumeInfos, nil
}

func (s *objectBackupStore) GetBackupContents(name string) (io.ReadCloser, error) {
return s.objectStore.GetObject(s.bucket, s.layout.getBackupContentsKey(name))
}
Expand Down
4 changes: 4 additions & 0 deletions pkg/persistence/object_store_layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,7 @@ func (l *ObjectStoreLayout) getCSIVolumeSnapshotClassesKey(backup string) string
func (l *ObjectStoreLayout) getBackupResultsKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-results.gz", backup))
}

func (l *ObjectStoreLayout) getBackupVolumeInfoKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-volumeinfos.json.gz", backup))
}
100 changes: 92 additions & 8 deletions pkg/persistence/object_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ func TestPutBackup(t *testing.T) {
snapshots io.Reader
backupItemOperations io.Reader
resourceList io.Reader
backupVolumeInfo io.Reader
expectedErr string
expectedKeys []string
}{
Expand All @@ -239,6 +240,7 @@ func TestPutBackup(t *testing.T) {
snapshots: newStringReadSeeker("snapshots"),
backupItemOperations: newStringReadSeeker("backupItemOperations"),
resourceList: newStringReadSeeker("resourceList"),
backupVolumeInfo: newStringReadSeeker("backupVolumeInfo"),
expectedErr: "",
expectedKeys: []string{
"backups/backup-1/velero-backup.json",
Expand All @@ -248,6 +250,7 @@ func TestPutBackup(t *testing.T) {
"backups/backup-1/backup-1-volumesnapshots.json.gz",
"backups/backup-1/backup-1-itemoperations.json.gz",
"backups/backup-1/backup-1-resource-list.json.gz",
"backups/backup-1/backup-1-volumeinfos.json.gz",
},
},
{
Expand All @@ -260,6 +263,7 @@ func TestPutBackup(t *testing.T) {
snapshots: newStringReadSeeker("snapshots"),
backupItemOperations: newStringReadSeeker("backupItemOperations"),
resourceList: newStringReadSeeker("resourceList"),
backupVolumeInfo: newStringReadSeeker("backupVolumeInfo"),
expectedErr: "",
expectedKeys: []string{
"prefix-1/backups/backup-1/velero-backup.json",
Expand All @@ -269,6 +273,7 @@ func TestPutBackup(t *testing.T) {
"prefix-1/backups/backup-1/backup-1-volumesnapshots.json.gz",
"prefix-1/backups/backup-1/backup-1-itemoperations.json.gz",
"prefix-1/backups/backup-1/backup-1-resource-list.json.gz",
"prefix-1/backups/backup-1/backup-1-volumeinfos.json.gz",
},
},
{
Expand All @@ -280,6 +285,7 @@ func TestPutBackup(t *testing.T) {
snapshots: newStringReadSeeker("snapshots"),
backupItemOperations: newStringReadSeeker("backupItemOperations"),
resourceList: newStringReadSeeker("resourceList"),
backupVolumeInfo: newStringReadSeeker("backupVolumeInfo"),
expectedErr: "error readers return errors",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
},
Expand All @@ -291,6 +297,7 @@ func TestPutBackup(t *testing.T) {
snapshots: newStringReadSeeker("snapshots"),
backupItemOperations: newStringReadSeeker("backupItemOperations"),
resourceList: newStringReadSeeker("resourceList"),
backupVolumeInfo: newStringReadSeeker("backupVolumeInfo"),
expectedErr: "error readers return errors",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
},
Expand All @@ -303,6 +310,7 @@ func TestPutBackup(t *testing.T) {
snapshots: newStringReadSeeker("snapshots"),
backupItemOperations: newStringReadSeeker("backupItemOperations"),
resourceList: newStringReadSeeker("resourceList"),
backupVolumeInfo: newStringReadSeeker("backupVolumeInfo"),
expectedErr: "",
expectedKeys: []string{
"backups/backup-1/velero-backup.json",
Expand All @@ -311,23 +319,26 @@ func TestPutBackup(t *testing.T) {
"backups/backup-1/backup-1-volumesnapshots.json.gz",
"backups/backup-1/backup-1-itemoperations.json.gz",
"backups/backup-1/backup-1-resource-list.json.gz",
"backups/backup-1/backup-1-volumeinfos.json.gz",
},
},
{
name: "data should be uploaded even when metadata is nil",
metadata: nil,
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "",
name: "data should be uploaded even when metadata is nil",
metadata: nil,
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
resourceList: newStringReadSeeker("resourceList"),
backupVolumeInfo: newStringReadSeeker("backupVolumeInfo"),
expectedErr: "",
expectedKeys: []string{
"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-resource-list.json.gz",
"backups/backup-1/backup-1-volumeinfos.json.gz",
},
},
}
Expand All @@ -345,6 +356,7 @@ func TestPutBackup(t *testing.T) {
VolumeSnapshots: tc.snapshots,
BackupItemOperations: tc.backupItemOperations,
BackupResourceList: tc.resourceList,
BackupVolumeInfo: tc.backupVolumeInfo,
}
err := harness.PutBackup(backupInfo)

Expand Down Expand Up @@ -1045,6 +1057,78 @@ func TestNewObjectBackupStoreGetterConfig(t *testing.T) {
}
}

func TestGetBackupVolumeInfos(t *testing.T) {
tests := []struct {
name string
volumeInfo *volume.VolumeInfosV1
expectedErr string
expectedResult []volume.VolumeInfoV1
}{
{
name: "No VolumeInfos, expect no error.",
},
{
name: "Invalid VolumeInfo version, should fail.",
volumeInfo: &volume.VolumeInfosV1{
Version: "invalid",
},
expectedErr: "unknown backup VolumeInfo version: invalid",
},
{
name: "Valid VolumeInfo version, should pass.",
volumeInfo: &volume.VolumeInfosV1{
Version: "1",
Infos: []volume.VolumeInfoV1{
{
PVCName: "pvcName",
PVName: "pvName",
Skipped: true,
SnapshotDataMoved: false,
},
},
},
expectedResult: []volume.VolumeInfoV1{
{
PVCName: "pvcName",
PVName: "pvName",
Skipped: true,
SnapshotDataMoved: false,
},
},
},
}

harness := newObjectBackupStoreTestHarness("test-bucket", "")

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if tc.volumeInfo != nil {
obj := new(bytes.Buffer)
gzw := gzip.NewWriter(obj)

require.NoError(t, json.NewEncoder(gzw).Encode(tc.volumeInfo))
require.NoError(t, gzw.Close())
harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-volumeinfos.json.gz", obj)
}

result, err := harness.GetBackupVolumeInfos("test-backup")
if tc.expectedErr != "" {
require.Equal(t, tc.expectedErr, err.Error())
} else {
if err != nil {
fmt.Println(err.Error())
}
require.NoError(t, err)
}

if len(tc.expectedResult) > 0 {
require.Equal(t, tc.expectedResult, result.VolumeInfosV1)
}

})
}
}

func encodeToBytes(obj runtime.Object) []byte {
res, err := encode.Encode(obj, "json")
if err != nil {
Expand Down
7 changes: 5 additions & 2 deletions pkg/volume/volume_info_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ type CSISnapshotInfo struct {
VSCName string `json:"vscName"`
}

// SnapshotDataMoveInfo is used for displaying the snapshot data mover status.
type SnapshotDataMoveInfo struct {
// SnapshotDataMovementInfo is used for displaying the snapshot data mover status.
type SnapshotDataMovementInfo struct {
// The data mover used by the backup. The valid values are `velero` and ``(equals to `velero`).
DataMover string `json:"dataMover"`

Expand All @@ -43,6 +43,9 @@ type SnapshotDataMoveInfo struct {
}

// VeleroNativeSnapshotInfo is used for displaying the Velero native snapshot status.
// A Velero Native Snapshot is a cloud storage snapshot taken by the Velero native
// plugins, e.g. velero-plugin-for-aws, velero-plugin-for-gcp, and
// velero-plugin-for-microsoft-azure.
type VeleroNativeSnapshotInfo struct {
// It's the storage provider's snapshot ID for the Velero-native snapshot.
SnapshotHandle string `json:"snapshotHandle"`
Expand Down
8 changes: 4 additions & 4 deletions pkg/volume/volume_info_v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ type VolumeInfoV1 struct {
// Snapshot starts timestamp.
StartTimestamp *metav1.Time `json:"startTimestamp,omitempty"`

CSISnapshotInfo CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"`
SnapshotDataMoveInfo SnapshotDataMoveInfo `json:"snapshotDataMoveInfo,omitempty"`
NativeSnapshotInfo VeleroNativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"`
PVBInfo PodVolumeBackupInfo `json:"pvbInfo,omitempty"`
CSISnapshotInfo CSISnapshotInfo `json:"csiSnapshotInfo,omitempty"`
SnapshotDataMovementInfo SnapshotDataMovementInfo `json:"snapshotDataMovementInfo,omitempty"`
NativeSnapshotInfo VeleroNativeSnapshotInfo `json:"nativeSnapshotInfo,omitempty"`
PVBInfo PodVolumeBackupInfo `json:"pvbInfo,omitempty"`
}

0 comments on commit 4789664

Please sign in to comment.