block uploader snapshot implementation

Signed-off-by: Lyndon-Li <lyonghui@vmware.com>
This commit is contained in:
Lyndon-Li
2026-06-25 08:31:32 +00:00
parent f4f897f669
commit 5bef38dc95
6 changed files with 132 additions and 8 deletions
+1
View File
@@ -0,0 +1 @@
Add snapshot operations for block uploader
+1
View File
@@ -25,6 +25,7 @@ import (
"github.com/pkg/errors"
)
// implement in following PRs
func openBlockDevice(path string, read bool) (*os.File, error) {
return nil, errors.New("Not implemented")
}
+5 -1
View File
@@ -25,6 +25,7 @@ import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/vmware-tanzu/velero/pkg/cbtservice"
"github.com/vmware-tanzu/velero/pkg/repository/udmrepo"
"github.com/vmware-tanzu/velero/pkg/uploader"
@@ -202,6 +203,9 @@ func Restore(ctx context.Context, blkup Uploader, rep udmrepo.BackupRepo, snapsh
log.Infof("Restore from snapshot %s, description %s, created time %v, tags %v", snapshotID, snapshot.Description, snapshot.EndTime, snapshot.Tags)
bitmap := cbt.NewBitmap(blockSize, uint64(snapshot.TotalSize), "", "", "")
bitmap.SetFull()
destPath, err := filepath.Abs(dest)
if err != nil {
return 0, errors.Wrapf(err, "invalid dest path '%s'", dest)
@@ -214,7 +218,7 @@ func Restore(ctx context.Context, blkup Uploader, rep udmrepo.BackupRepo, snapsh
return 0, errors.Wrapf(err, "error opening block device '%s'", destPath)
}
size, err := blkup.Restore(snapshot, destInfo{dev: destDev, path: destPath}, uploaderCfg)
size, err := blkup.Restore(snapshot, destInfo{dev: destDev, path: destPath}, bitmap.Iterator(), uploaderCfg)
if err != nil {
return 0, errors.Wrapf(err, "error restoring to block dev %s", destPath)
}
+8 -4
View File
@@ -46,8 +46,8 @@ func (m *mockUploader) Backup(src sourceInfo, parent udmrepo.ID, iter cbttypes.I
return args.Get(0).(udmrepo.Snapshot), args.Get(1).(int64), args.Error(2)
}
func (m *mockUploader) Restore(snap udmrepo.Snapshot, dest destInfo, cfg map[string]string) (int64, error) {
args := m.Called(snap, dest, cfg)
func (m *mockUploader) Restore(snap udmrepo.Snapshot, dest destInfo, iter cbttypes.Iterator, cfg map[string]string) (int64, error) {
args := m.Called(snap, dest, iter, cfg)
return args.Get(0).(int64), args.Error(1)
}
@@ -360,6 +360,8 @@ func TestGetParentBackupInfo(t *testing.T) {
setupMocks: func(repo *udmrepomocks.BackupRepo) {
repo.On("GetSnapshot", mock.Anything, udmrepo.ID("snap-valid")).
Return(validSnap, nil)
repo.On("ReadMetadata", mock.Anything, udmrepo.ID("root-obj")).
Return(&udmrepo.Metadata{SubObjects: []udmrepo.ObjectMetadata{{ID: "root-obj"}}}, nil)
},
expectedParent: "root-obj",
expectedCID: "cid-abc",
@@ -386,6 +388,8 @@ func TestGetParentBackupInfo(t *testing.T) {
setupMocks: func(repo *udmrepomocks.BackupRepo) {
repo.On("ListSnapshot", mock.Anything, realSource).
Return([]udmrepo.Snapshot{validSnap}, nil)
repo.On("ReadMetadata", mock.Anything, udmrepo.ID("root-obj")).
Return(&udmrepo.Metadata{SubObjects: []udmrepo.ObjectMetadata{{ID: "root-obj"}}}, nil)
},
expectedParent: "root-obj",
expectedCID: "cid-abc",
@@ -566,7 +570,7 @@ func TestRestore(t *testing.T) {
setupMocks: func(blkup *mockUploader, repo *udmrepomocks.BackupRepo) {
repo.On("GetSnapshot", mock.Anything, udmrepo.ID("snap-001")).
Return(storedSnap, nil)
blkup.On("Restore", mock.Anything, mock.Anything, mock.Anything).
blkup.On("Restore", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(int64(0), errors.New("restore I/O error"))
},
setupOpenDev: func(t *testing.T) *os.File {
@@ -579,7 +583,7 @@ func TestRestore(t *testing.T) {
setupMocks: func(blkup *mockUploader, repo *udmrepomocks.BackupRepo) {
repo.On("GetSnapshot", mock.Anything, udmrepo.ID("snap-001")).
Return(storedSnap, nil)
blkup.On("Restore", mock.Anything, mock.Anything, mock.Anything).
blkup.On("Restore", mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(int64(4096), nil)
},
setupOpenDev: func(t *testing.T) *os.File {
+5 -3
View File
@@ -22,6 +22,7 @@ import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/vmware-tanzu/velero/pkg/repository/udmrepo"
"github.com/vmware-tanzu/velero/pkg/uploader"
cbt "github.com/vmware-tanzu/velero/pkg/uploader/cbt/types"
@@ -46,9 +47,10 @@ type destInfo struct {
type Uploader interface {
Backup(sourceInfo, udmrepo.ID, cbt.Iterator, map[string]string) (udmrepo.Snapshot, int64, error)
Restore(udmrepo.Snapshot, destInfo, map[string]string) (int64, error)
Restore(udmrepo.Snapshot, destInfo, cbt.Iterator, map[string]string) (int64, error)
}
// implement in following PRs
func NewUploader(ctx context.Context, repoWriter udmrepo.BackupRepo, progress uploader.ProgressUpdater, log logrus.FieldLogger) Uploader {
return nil
}
@@ -60,11 +62,11 @@ func loadObjectFromSnapshot(ctx context.Context, rep udmrepo.BackupRepo, snapsho
parentMeta, err := rep.ReadMetadata(ctx, snapshot.RootObject.ID)
if err != nil {
return "", errors.Wrapf(err, "error readding snapshot metadata for %s", snapshot.Description)
return "", errors.Wrapf(err, "error reading snapshot metadata for %s", snapshot.Description)
}
if len(parentMeta.SubObjects) != 1 {
return "", errors.Wrapf(err, "unexpected number of bdev object (%d) for snapshot %s", len(parentMeta.SubObjects), snapshot.Description)
return "", errors.Errorf("unexpected number of bdev object (%d) for snapshot %s", len(parentMeta.SubObjects), snapshot.Description)
}
return parentMeta.SubObjects[0].ID, nil
+112
View File
@@ -0,0 +1,112 @@
/*
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 block
import (
"context"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/vmware-tanzu/velero/pkg/repository/udmrepo"
udmrepomocks "github.com/vmware-tanzu/velero/pkg/repository/udmrepo/mocks"
)
func TestLoadObjectFromSnapshot(t *testing.T) {
testCases := []struct {
name string
snapshot *udmrepo.Snapshot
setupMocks func(repo *udmrepomocks.BackupRepo)
expectedErrStr string
expectedID udmrepo.ID
}{
{
name: "nil snapshot",
snapshot: nil,
expectedErrStr: "snapshot is empty",
},
{
name: "ReadMetadata error",
snapshot: &udmrepo.Snapshot{
RootObject: udmrepo.ObjectMetadata{ID: "root-obj"},
},
setupMocks: func(repo *udmrepomocks.BackupRepo) {
repo.On("ReadMetadata", mock.Anything, udmrepo.ID("root-obj")).
Return(nil, errors.New("read error"))
},
expectedErrStr: "error reading snapshot metadata",
},
{
name: "unexpected number of subobjects (0)",
snapshot: &udmrepo.Snapshot{
RootObject: udmrepo.ObjectMetadata{ID: "root-obj"},
},
setupMocks: func(repo *udmrepomocks.BackupRepo) {
repo.On("ReadMetadata", mock.Anything, udmrepo.ID("root-obj")).
Return(&udmrepo.Metadata{SubObjects: []udmrepo.ObjectMetadata{}}, nil)
},
expectedErrStr: "unexpected number of bdev object",
},
{
name: "unexpected number of subobjects (2)",
snapshot: &udmrepo.Snapshot{
RootObject: udmrepo.ObjectMetadata{ID: "root-obj"},
},
setupMocks: func(repo *udmrepomocks.BackupRepo) {
repo.On("ReadMetadata", mock.Anything, udmrepo.ID("root-obj")).
Return(&udmrepo.Metadata{SubObjects: []udmrepo.ObjectMetadata{{ID: "obj-1"}, {ID: "obj-2"}}}, nil)
},
expectedErrStr: "unexpected number of bdev object",
},
{
name: "success",
snapshot: &udmrepo.Snapshot{
RootObject: udmrepo.ObjectMetadata{ID: "root-obj"},
},
setupMocks: func(repo *udmrepomocks.BackupRepo) {
repo.On("ReadMetadata", mock.Anything, udmrepo.ID("root-obj")).
Return(&udmrepo.Metadata{SubObjects: []udmrepo.ObjectMetadata{{ID: "bdev-obj"}}}, nil)
},
expectedID: "bdev-obj",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
mockRepo := udmrepomocks.NewBackupRepo(t)
if tc.setupMocks != nil {
tc.setupMocks(mockRepo)
}
id, err := loadObjectFromSnapshot(ctx, mockRepo, tc.snapshot)
if tc.expectedErrStr != "" {
require.Error(t, err)
assert.ErrorContains(t, err, tc.expectedErrStr)
assert.Empty(t, id)
} else {
require.NoError(t, err)
assert.Equal(t, tc.expectedID, id)
}
})
}
}