mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-07 13:55:20 +00:00
96
pkg/uploader/kopia/progress_test.go
Normal file
96
pkg/uploader/kopia/progress_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
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 kopia
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/uploader"
|
||||
)
|
||||
|
||||
type fakeProgressUpdater struct{}
|
||||
|
||||
func (f *fakeProgressUpdater) UpdateProgress(p *uploader.Progress) {}
|
||||
|
||||
func TestThrottle_ShouldOutput(t *testing.T) {
|
||||
testCases := []struct {
|
||||
interval time.Duration
|
||||
throttle int64
|
||||
expectedOutput bool
|
||||
}{
|
||||
{interval: time.Second, expectedOutput: true},
|
||||
{interval: time.Second, throttle: time.Now().UnixNano() + int64(time.Nanosecond*10000), expectedOutput: false},
|
||||
}
|
||||
p := new(Progress)
|
||||
for _, tc := range testCases {
|
||||
// Setup
|
||||
p.InitThrottle(tc.interval)
|
||||
p.outputThrottle.throttle = int64(tc.throttle)
|
||||
// Perform the test
|
||||
|
||||
output := p.outputThrottle.ShouldOutput()
|
||||
|
||||
// Verify the result
|
||||
if output != tc.expectedOutput {
|
||||
t.Errorf("Expected ShouldOutput to return %v, but got %v", tc.expectedOutput, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgress(t *testing.T) {
|
||||
fileName := "test-filename"
|
||||
var numBytes int64 = 1
|
||||
testCases := []struct {
|
||||
interval time.Duration
|
||||
throttle int64
|
||||
}{
|
||||
{interval: time.Second},
|
||||
{interval: time.Second, throttle: time.Now().UnixNano() + int64(time.Nanosecond*10000)},
|
||||
}
|
||||
p := new(Progress)
|
||||
p.Log = logrus.New()
|
||||
p.Updater = &fakeProgressUpdater{}
|
||||
for _, tc := range testCases {
|
||||
// Setup
|
||||
p.InitThrottle(tc.interval)
|
||||
p.outputThrottle.throttle = int64(tc.throttle)
|
||||
p.InitThrottle(time.Duration(time.Second))
|
||||
// All below calls put together for the implementation are empty or just very simple and just want to cover testing
|
||||
// If wanting to write unit tests for some functions could remove it and with writing new function alone
|
||||
p.UpdateProgress()
|
||||
p.UploadedBytes(numBytes)
|
||||
p.Error("test-path", nil, true)
|
||||
p.Error("test-path", errors.New("processing error"), false)
|
||||
p.UploadStarted()
|
||||
p.EstimatedDataSize(1, numBytes)
|
||||
p.CachedFile(fileName, numBytes)
|
||||
p.HashedBytes(numBytes)
|
||||
p.HashingFile(fileName)
|
||||
p.ExcludedFile(fileName, numBytes)
|
||||
p.ExcludedDir(fileName)
|
||||
p.FinishedHashingFile(fileName, numBytes)
|
||||
p.StartedDirectory(fileName)
|
||||
p.FinishedDirectory(fileName)
|
||||
p.UploadFinished()
|
||||
p.ProgressBytes(numBytes, numBytes)
|
||||
p.FinishedFile(fileName, nil)
|
||||
}
|
||||
}
|
||||
204
pkg/uploader/kopia/shim_test.go
Normal file
204
pkg/uploader/kopia/shim_test.go
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
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 kopia
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kopia/kopia/repo"
|
||||
"github.com/kopia/kopia/repo/content"
|
||||
"github.com/kopia/kopia/repo/manifest"
|
||||
"github.com/kopia/kopia/repo/object"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/repository/udmrepo"
|
||||
"github.com/vmware-tanzu/velero/pkg/repository/udmrepo/mocks"
|
||||
)
|
||||
|
||||
func TestShimRepo(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
backupRepo := &mocks.BackupRepo{}
|
||||
backupRepo.On("Time").Return(time.Time{})
|
||||
shim := NewShimRepo(backupRepo)
|
||||
// All below calls put together for the implementation are empty or just very simple, and just want to cover testing
|
||||
// If wanting to write unit tests for some functions could remove it and with writing new function alone
|
||||
shim.VerifyObject(ctx, object.ID{})
|
||||
shim.Time()
|
||||
shim.ClientOptions()
|
||||
shim.Refresh(ctx)
|
||||
shim.ContentInfo(ctx, content.ID{})
|
||||
shim.PrefetchContents(ctx, []content.ID{}, "hint")
|
||||
shim.PrefetchObjects(ctx, []object.ID{}, "hint")
|
||||
shim.UpdateDescription("desc")
|
||||
shim.NewWriter(ctx, repo.WriteSessionOptions{})
|
||||
shim.ReplaceManifests(ctx, map[string]string{}, nil)
|
||||
shim.OnSuccessfulFlush(func(ctx context.Context, w repo.RepositoryWriter) error { return nil })
|
||||
|
||||
backupRepo.On("Close", mock.Anything).Return(nil)
|
||||
NewShimRepo(backupRepo).Close(ctx)
|
||||
|
||||
var id udmrepo.ID
|
||||
backupRepo.On("PutManifest", mock.Anything, mock.Anything).Return(id, nil)
|
||||
NewShimRepo(backupRepo).PutManifest(ctx, map[string]string{}, nil)
|
||||
|
||||
var mf manifest.ID
|
||||
backupRepo.On("DeleteManifest", mock.Anything, mock.Anything).Return(nil)
|
||||
NewShimRepo(backupRepo).DeleteManifest(ctx, mf)
|
||||
|
||||
backupRepo.On("Flush", mock.Anything).Return(nil)
|
||||
NewShimRepo(backupRepo).Flush(ctx)
|
||||
|
||||
var objID object.ID
|
||||
backupRepo.On("ConcatenateObjects", mock.Anything, mock.Anything).Return(objID)
|
||||
NewShimRepo(backupRepo).ConcatenateObjects(ctx, []object.ID{})
|
||||
|
||||
backupRepo.On("NewObjectWriter", mock.Anything, mock.Anything).Return(nil)
|
||||
NewShimRepo(backupRepo).NewObjectWriter(ctx, object.WriterOptions{})
|
||||
}
|
||||
|
||||
func TestOpenObject(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
backupRepo *mocks.BackupRepo
|
||||
isOpenObjectError bool
|
||||
isReaderNil bool
|
||||
}{
|
||||
{
|
||||
name: "Success",
|
||||
backupRepo: func() *mocks.BackupRepo {
|
||||
backupRepo := &mocks.BackupRepo{}
|
||||
backupRepo.On("OpenObject", mock.Anything, mock.Anything).Return(&shimObjectReader{}, nil)
|
||||
return backupRepo
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "Open object error",
|
||||
backupRepo: func() *mocks.BackupRepo {
|
||||
backupRepo := &mocks.BackupRepo{}
|
||||
backupRepo.On("OpenObject", mock.Anything, mock.Anything).Return(&shimObjectReader{}, errors.New("Error open object"))
|
||||
return backupRepo
|
||||
}(),
|
||||
isOpenObjectError: true,
|
||||
},
|
||||
{
|
||||
name: "Get nil reader",
|
||||
backupRepo: func() *mocks.BackupRepo {
|
||||
backupRepo := &mocks.BackupRepo{}
|
||||
backupRepo.On("OpenObject", mock.Anything, mock.Anything).Return(nil, nil)
|
||||
return backupRepo
|
||||
}(),
|
||||
isReaderNil: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
reader, err := NewShimRepo(tc.backupRepo).OpenObject(ctx, object.ID{})
|
||||
if tc.isOpenObjectError {
|
||||
assert.Contains(t, err.Error(), "failed to open object")
|
||||
} else if tc.isReaderNil {
|
||||
assert.Nil(t, reader)
|
||||
} else {
|
||||
assert.NotNil(t, reader)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindManifests(t *testing.T) {
|
||||
meta := []*udmrepo.ManifestEntryMetadata{}
|
||||
tests := []struct {
|
||||
name string
|
||||
backupRepo *mocks.BackupRepo
|
||||
isGetManifestError bool
|
||||
}{
|
||||
{
|
||||
name: "Success",
|
||||
backupRepo: func() *mocks.BackupRepo {
|
||||
backupRepo := &mocks.BackupRepo{}
|
||||
backupRepo.On("FindManifests", mock.Anything, mock.Anything).Return(meta, nil)
|
||||
return backupRepo
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "Failed to find manifest",
|
||||
isGetManifestError: true,
|
||||
backupRepo: func() *mocks.BackupRepo {
|
||||
backupRepo := &mocks.BackupRepo{}
|
||||
backupRepo.On("FindManifests", mock.Anything, mock.Anything).Return(meta,
|
||||
errors.New("failed to find manifest"))
|
||||
return backupRepo
|
||||
}(),
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
_, err := NewShimRepo(tc.backupRepo).FindManifests(ctx, map[string]string{})
|
||||
if tc.isGetManifestError {
|
||||
assert.Contains(t, err.Error(), "failed")
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShimObjReader(t *testing.T) {
|
||||
reader := new(shimObjectReader)
|
||||
objReader := &mocks.ObjectReader{}
|
||||
reader.repoReader = objReader
|
||||
// All below calls put together for the implementation are empty or just very simple, and just want to cover testing
|
||||
// If wanting to write unit tests for some functions could remove it and with writing new function alone
|
||||
objReader.On("Seek", mock.Anything, mock.Anything).Return(int64(0), nil)
|
||||
reader.Seek(int64(0), 0)
|
||||
|
||||
objReader.On("Read", mock.Anything).Return(0, nil)
|
||||
reader.Read(nil)
|
||||
|
||||
objReader.On("Close").Return(nil)
|
||||
reader.Close()
|
||||
|
||||
objReader.On("Length").Return(int64(0))
|
||||
reader.Length()
|
||||
}
|
||||
|
||||
func TestShimObjWriter(t *testing.T) {
|
||||
writer := new(shimObjectWriter)
|
||||
objWriter := &mocks.ObjectWriter{}
|
||||
writer.repoWriter = objWriter
|
||||
// All below calls put together for the implementation are empty or just very simple, and just want to cover testing
|
||||
// If wanting to write unit tests for some functions could remove it and with writing new function alone
|
||||
var id udmrepo.ID
|
||||
objWriter.On("Checkpoint").Return(id, nil)
|
||||
writer.Checkpoint()
|
||||
|
||||
objWriter.On("Result").Return(id, nil)
|
||||
writer.Result()
|
||||
|
||||
objWriter.On("Write", mock.Anything).Return(0, nil)
|
||||
writer.Write(nil)
|
||||
|
||||
objWriter.On("Close").Return(nil)
|
||||
writer.Close()
|
||||
}
|
||||
@@ -47,6 +47,9 @@ import (
|
||||
var applyRetentionPolicyFunc = policy.ApplyRetentionPolicy
|
||||
var saveSnapshotFunc = snapshot.SaveSnapshot
|
||||
var loadSnapshotFunc = snapshot.LoadSnapshot
|
||||
var listSnapshotsFunc = snapshot.ListSnapshots
|
||||
var filesystemEntryFunc = snapshotfs.FilesystemEntryFromIDWithPath
|
||||
var restoreEntryFunc = restore.Entry
|
||||
|
||||
// SnapshotUploader which mainly used for UT test that could overwrite Upload interface
|
||||
type SnapshotUploader interface {
|
||||
@@ -84,7 +87,7 @@ func setupDefaultPolicy() *policy.Tree {
|
||||
}
|
||||
|
||||
// Backup backup specific sourcePath and update progress
|
||||
func Backup(ctx context.Context, fsUploader *snapshotfs.Uploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string,
|
||||
func Backup(ctx context.Context, fsUploader SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string,
|
||||
forceFull bool, parentSnapshot string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) {
|
||||
if fsUploader == nil {
|
||||
return nil, false, errors.New("get empty kopia uploader")
|
||||
@@ -254,7 +257,7 @@ func reportSnapshotStatus(manifest *snapshot.Manifest, policyTree *policy.Tree)
|
||||
// findPreviousSnapshotManifest returns the list of previous snapshots for a given source, including
|
||||
// last complete snapshot following it.
|
||||
func findPreviousSnapshotManifest(ctx context.Context, rep repo.Repository, sourceInfo snapshot.SourceInfo, snapshotTags map[string]string, noLaterThan *fs.UTCTimestamp, log logrus.FieldLogger) ([]*snapshot.Manifest, error) {
|
||||
man, err := snapshot.ListSnapshots(ctx, rep, sourceInfo)
|
||||
man, err := listSnapshotsFunc(ctx, rep, sourceInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -312,7 +315,7 @@ func Restore(ctx context.Context, rep repo.RepositoryWriter, progress *Progress,
|
||||
|
||||
log.Infof("Restore from snapshot %s, description %s, created time %v, tags %v", snapshotID, snapshot.Description, snapshot.EndTime.ToTime(), snapshot.Tags)
|
||||
|
||||
rootEntry, err := snapshotfs.FilesystemEntryFromIDWithPath(kopiaCtx, rep, snapshotID, false)
|
||||
rootEntry, err := filesystemEntryFunc(kopiaCtx, rep, snapshotID, false)
|
||||
if err != nil {
|
||||
return 0, 0, errors.Wrapf(err, "Unable to get filesystem entry for snapshot %v", snapshotID)
|
||||
}
|
||||
@@ -335,7 +338,7 @@ func Restore(ctx context.Context, rep repo.RepositoryWriter, progress *Progress,
|
||||
return 0, 0, errors.Wrap(err, "error to init output")
|
||||
}
|
||||
|
||||
stat, err := restore.Entry(kopiaCtx, rep, output, rootEntry, restore.Options{
|
||||
stat, err := restoreEntryFunc(kopiaCtx, rep, output, rootEntry, restore.Options{
|
||||
Parallel: runtime.NumCPU(),
|
||||
RestoreDirEntryAtDepth: math.MaxInt32,
|
||||
Cancel: cancleCh,
|
||||
|
||||
@@ -18,15 +18,23 @@ package kopia
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kopia/kopia/fs"
|
||||
"github.com/kopia/kopia/repo"
|
||||
"github.com/kopia/kopia/repo/manifest"
|
||||
"github.com/kopia/kopia/snapshot"
|
||||
"github.com/kopia/kopia/snapshot/restore"
|
||||
"github.com/kopia/kopia/snapshot/snapshotfs"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
repomocks "github.com/vmware-tanzu/velero/pkg/repository/mocks"
|
||||
"github.com/vmware-tanzu/velero/pkg/uploader"
|
||||
uploadermocks "github.com/vmware-tanzu/velero/pkg/uploader/mocks"
|
||||
)
|
||||
|
||||
@@ -179,5 +187,545 @@ func TestSnapshotSource(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestReportSnapshotStatus(t *testing.T) {
|
||||
testCases := []struct {
|
||||
shouldError bool
|
||||
expectedResult string
|
||||
expectedSize int64
|
||||
directorySummary *fs.DirectorySummary
|
||||
expectedErrors []string
|
||||
}{
|
||||
{
|
||||
shouldError: false,
|
||||
expectedResult: "sample-manifest-id",
|
||||
expectedSize: 1024,
|
||||
directorySummary: &fs.DirectorySummary{
|
||||
TotalFileSize: 1024,
|
||||
},
|
||||
},
|
||||
{
|
||||
shouldError: true,
|
||||
expectedResult: "",
|
||||
expectedSize: 0,
|
||||
directorySummary: &fs.DirectorySummary{
|
||||
FailedEntries: []*fs.EntryWithError{
|
||||
{
|
||||
EntryPath: "/path/to/file.txt",
|
||||
Error: "Unknown file error",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErrors: []string{"Error when processing /path/to/file.txt: Unknown file error"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
manifest := &snapshot.Manifest{
|
||||
ID: manifest.ID("sample-manifest-id"),
|
||||
Stats: snapshot.Stats{
|
||||
TotalFileSize: 1024,
|
||||
},
|
||||
RootEntry: &snapshot.DirEntry{
|
||||
DirSummary: tc.directorySummary,
|
||||
},
|
||||
}
|
||||
|
||||
result, size, err := reportSnapshotStatus(manifest, setupDefaultPolicy())
|
||||
|
||||
switch {
|
||||
case tc.shouldError && err == nil:
|
||||
t.Errorf("expected error, but got nil")
|
||||
case !tc.shouldError && err != nil:
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
case tc.shouldError && err != nil:
|
||||
expectedErr := strings.Join(tc.expectedErrors, "\n")
|
||||
if err.Error() != expectedErr {
|
||||
t.Errorf("unexpected error: got %v, want %v", err, expectedErr)
|
||||
}
|
||||
}
|
||||
|
||||
if result != tc.expectedResult {
|
||||
t.Errorf("unexpected result: got %v, want %v", result, tc.expectedResult)
|
||||
}
|
||||
|
||||
if size != tc.expectedSize {
|
||||
t.Errorf("unexpected size: got %v, want %v", size, tc.expectedSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindPreviousSnapshotManifest(t *testing.T) {
|
||||
// Prepare test data
|
||||
sourceInfo := snapshot.SourceInfo{
|
||||
UserName: "user1",
|
||||
Host: "host1",
|
||||
Path: "/path/to/dir1",
|
||||
}
|
||||
snapshotTags := map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
}
|
||||
noLaterThan := fs.UTCTimestampFromTime(time.Now())
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
listSnapshotsFunc func(ctx context.Context, rep repo.Repository, si snapshot.SourceInfo) ([]*snapshot.Manifest, error)
|
||||
expectedSnapshots []*snapshot.Manifest
|
||||
expectedError error
|
||||
}{
|
||||
// No matching snapshots
|
||||
{
|
||||
name: "No matching snapshots",
|
||||
listSnapshotsFunc: func(ctx context.Context, rep repo.Repository, si snapshot.SourceInfo) ([]*snapshot.Manifest, error) {
|
||||
return []*snapshot.Manifest{}, nil
|
||||
},
|
||||
expectedSnapshots: []*snapshot.Manifest{},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Error getting manifest",
|
||||
listSnapshotsFunc: func(ctx context.Context, rep repo.Repository, si snapshot.SourceInfo) ([]*snapshot.Manifest, error) {
|
||||
return []*snapshot.Manifest{}, errors.New("Error getting manifest")
|
||||
},
|
||||
expectedSnapshots: []*snapshot.Manifest{},
|
||||
expectedError: errors.New("Error getting manifest"),
|
||||
},
|
||||
// Only one matching snapshot
|
||||
{
|
||||
name: "One matching snapshot",
|
||||
listSnapshotsFunc: func(ctx context.Context, rep repo.Repository, si snapshot.SourceInfo) ([]*snapshot.Manifest, error) {
|
||||
return []*snapshot.Manifest{
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value",
|
||||
"anotherCustomTag": "123",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectedSnapshots: []*snapshot.Manifest{
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value",
|
||||
"anotherCustomTag": "123",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
// Multiple matching snapshots
|
||||
{
|
||||
name: "Multiple matching snapshots",
|
||||
listSnapshotsFunc: func(ctx context.Context, rep repo.Repository, si snapshot.SourceInfo) ([]*snapshot.Manifest, error) {
|
||||
return []*snapshot.Manifest{
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value1",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value2",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectedSnapshots: []*snapshot.Manifest{
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value1",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
// Snapshot with different requestor
|
||||
{
|
||||
name: "Snapshot with different requestor",
|
||||
listSnapshotsFunc: func(ctx context.Context, rep repo.Repository, si snapshot.SourceInfo) ([]*snapshot.Manifest, error) {
|
||||
return []*snapshot.Manifest{
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user2",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value",
|
||||
"snapshotRequestor": "user2",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectedSnapshots: []*snapshot.Manifest{},
|
||||
expectedError: nil,
|
||||
},
|
||||
// Snapshot with different uploader
|
||||
{
|
||||
name: "Snapshot with different uploader",
|
||||
listSnapshotsFunc: func(ctx context.Context, rep repo.Repository, si snapshot.SourceInfo) ([]*snapshot.Manifest, error) {
|
||||
return []*snapshot.Manifest{
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader2",
|
||||
"otherTag": "value",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader2",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectedSnapshots: []*snapshot.Manifest{},
|
||||
expectedError: nil,
|
||||
},
|
||||
// Snapshot with a later start time
|
||||
{
|
||||
name: "Snapshot with a later start time",
|
||||
listSnapshotsFunc: func(ctx context.Context, rep repo.Repository, si snapshot.SourceInfo) ([]*snapshot.Manifest, error) {
|
||||
return []*snapshot.Manifest{
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
StartTime: fs.UTCTimestampFromTime(time.Now().Add(time.Hour)),
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectedSnapshots: []*snapshot.Manifest{},
|
||||
expectedError: nil,
|
||||
},
|
||||
// Snapshot with incomplete reason
|
||||
{
|
||||
name: "Snapshot with incomplete reason",
|
||||
listSnapshotsFunc: func(ctx context.Context, rep repo.Repository, si snapshot.SourceInfo) ([]*snapshot.Manifest, error) {
|
||||
return []*snapshot.Manifest{
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
IncompleteReason: "reason",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectedSnapshots: []*snapshot.Manifest{},
|
||||
expectedError: nil,
|
||||
},
|
||||
// Multiple snapshots with some matching conditions
|
||||
{
|
||||
name: "Multiple snapshots with matching conditions",
|
||||
listSnapshotsFunc: func(ctx context.Context, rep repo.Repository, si snapshot.SourceInfo) ([]*snapshot.Manifest, error) {
|
||||
return []*snapshot.Manifest{
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value1",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value2",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
StartTime: fs.UTCTimestampFromTime(time.Now().Add(-time.Hour)),
|
||||
IncompleteReason: "reason",
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value3",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
StartTime: fs.UTCTimestampFromTime(time.Now().Add(-time.Hour)),
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectedSnapshots: []*snapshot.Manifest{
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value3",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
StartTime: fs.UTCTimestampFromTime(time.Now().Add(-time.Hour)),
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
// Snapshot with manifest SnapshotRequestorTag not found
|
||||
{
|
||||
name: "Snapshot with manifest SnapshotRequestorTag not found",
|
||||
listSnapshotsFunc: func(ctx context.Context, rep repo.Repository, si snapshot.SourceInfo) ([]*snapshot.Manifest, error) {
|
||||
return []*snapshot.Manifest{
|
||||
{
|
||||
Tags: map[string]string{
|
||||
"requestor": "user1",
|
||||
uploader.SnapshotUploaderTag: "uploader1",
|
||||
"otherTag": "value",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
IncompleteReason: "reason",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectedSnapshots: []*snapshot.Manifest{},
|
||||
expectedError: nil,
|
||||
},
|
||||
// Snapshot with manifest SnapshotRequestorTag not found
|
||||
{
|
||||
name: "Snapshot with manifest SnapshotUploaderTag not found",
|
||||
listSnapshotsFunc: func(ctx context.Context, rep repo.Repository, si snapshot.SourceInfo) ([]*snapshot.Manifest, error) {
|
||||
return []*snapshot.Manifest{
|
||||
{
|
||||
Tags: map[string]string{
|
||||
uploader.SnapshotRequestorTag: "user1",
|
||||
"uploader": "uploader1",
|
||||
"otherTag": "value",
|
||||
"snapshotRequestor": "user1",
|
||||
"snapshotUploader": "uploader1",
|
||||
},
|
||||
IncompleteReason: "reason",
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
expectedSnapshots: []*snapshot.Manifest{},
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var repo repo.Repository
|
||||
listSnapshotsFunc = tc.listSnapshotsFunc
|
||||
snapshots, err := findPreviousSnapshotManifest(context.Background(), repo, sourceInfo, snapshotTags, &noLaterThan, logrus.New())
|
||||
|
||||
// Check if the returned error matches the expected error
|
||||
if tc.expectedError != nil {
|
||||
assert.Contains(t, err.Error(), tc.expectedError.Error())
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
// Check the number of returned snapshots
|
||||
if len(snapshots) != len(tc.expectedSnapshots) {
|
||||
t.Errorf("Expected %d snapshots, got %d", len(tc.expectedSnapshots), len(snapshots))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackup(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
sourcePath string
|
||||
forceFull bool
|
||||
parentSnapshot string
|
||||
tags map[string]string
|
||||
isEmptyUploader bool
|
||||
isSnapshotSourceError bool
|
||||
expectedError error
|
||||
expectedEmpty bool
|
||||
}
|
||||
manifest := &snapshot.Manifest{
|
||||
ID: "test",
|
||||
RootEntry: &snapshot.DirEntry{},
|
||||
}
|
||||
// Define test cases
|
||||
testCases := []testCase{
|
||||
{
|
||||
name: "Successful backup",
|
||||
sourcePath: "/",
|
||||
tags: map[string]string{},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Empty fsUploader",
|
||||
isEmptyUploader: true,
|
||||
sourcePath: "/",
|
||||
tags: nil,
|
||||
expectedError: errors.New("get empty kopia uploader"),
|
||||
},
|
||||
{
|
||||
name: "Unable to read directory",
|
||||
sourcePath: "/invalid/path",
|
||||
tags: nil,
|
||||
expectedError: errors.New("Unable to read dir"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := injectSnapshotFuncs()
|
||||
args := []mockArgs{
|
||||
{methodName: "LoadSnapshot", returns: []interface{}{manifest, nil}},
|
||||
{methodName: "SaveSnapshot", returns: []interface{}{manifest.ID, nil}},
|
||||
{methodName: "TreeForSource", returns: []interface{}{nil, nil}},
|
||||
{methodName: "ApplyRetentionPolicy", returns: []interface{}{nil, nil}},
|
||||
{methodName: "SetPolicy", returns: []interface{}{nil}},
|
||||
{methodName: "Upload", returns: []interface{}{manifest, nil}},
|
||||
{methodName: "Flush", returns: []interface{}{nil}},
|
||||
}
|
||||
MockFuncs(s, args)
|
||||
if tc.isSnapshotSourceError {
|
||||
s.repoWriterMock.On("FindManifests", mock.Anything, mock.Anything).Return(nil, errors.New("Failed to get manifests"))
|
||||
s.repoWriterMock.On("Flush", mock.Anything).Return(errors.New("Failed to get manifests"))
|
||||
} else {
|
||||
s.repoWriterMock.On("FindManifests", mock.Anything, mock.Anything).Return(nil, nil)
|
||||
}
|
||||
|
||||
var isSnapshotEmpty bool
|
||||
var snapshotInfo *uploader.SnapshotInfo
|
||||
var err error
|
||||
if tc.isEmptyUploader {
|
||||
snapshotInfo, isSnapshotEmpty, err = Backup(context.Background(), nil, s.repoWriterMock, tc.sourcePath, "", tc.forceFull, tc.parentSnapshot, tc.tags, &logrus.Logger{})
|
||||
} else {
|
||||
snapshotInfo, isSnapshotEmpty, err = Backup(context.Background(), s.uploderMock, s.repoWriterMock, tc.sourcePath, "", tc.forceFull, tc.parentSnapshot, tc.tags, &logrus.Logger{})
|
||||
}
|
||||
// Check if the returned error matches the expected error
|
||||
if tc.expectedError != nil {
|
||||
assert.Contains(t, err.Error(), tc.expectedError.Error())
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.expectedEmpty, isSnapshotEmpty)
|
||||
|
||||
if err == nil {
|
||||
assert.NotNil(t, snapshotInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestore(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
snapshotID string
|
||||
invalidManifestType bool
|
||||
filesystemEntryFunc func(ctx context.Context, rep repo.Repository, rootID string, consistentAttributes bool) (fs.Entry, error)
|
||||
restoreEntryFunc func(ctx context.Context, rep repo.Repository, output restore.Output, rootEntry fs.Entry, options restore.Options) (restore.Stats, error)
|
||||
dest string
|
||||
expectedBytes int64
|
||||
expectedCount int32
|
||||
expectedError error
|
||||
}
|
||||
|
||||
// Define test cases
|
||||
testCases := []testCase{
|
||||
{
|
||||
name: "manifest is not a snapshot",
|
||||
invalidManifestType: true,
|
||||
dest: "/path/to/destination",
|
||||
expectedError: errors.New("Unable to load snapshot"),
|
||||
},
|
||||
{
|
||||
name: "Failed to get filesystem entry",
|
||||
snapshotID: "snapshot-123",
|
||||
expectedError: errors.New("Unable to get filesystem entry"),
|
||||
},
|
||||
{
|
||||
name: "Failed to restore with filesystem entry",
|
||||
filesystemEntryFunc: func(ctx context.Context, rep repo.Repository, rootID string, consistentAttributes bool) (fs.Entry, error) {
|
||||
return snapshotfs.EntryFromDirEntry(rep, &snapshot.DirEntry{Type: snapshot.EntryTypeFile}), nil
|
||||
},
|
||||
restoreEntryFunc: func(ctx context.Context, rep repo.Repository, output restore.Output, rootEntry fs.Entry, options restore.Options) (restore.Stats, error) {
|
||||
return restore.Stats{}, errors.New("Unable to get filesystem entry")
|
||||
},
|
||||
snapshotID: "snapshot-123",
|
||||
expectedError: errors.New("Unable to get filesystem entry"),
|
||||
},
|
||||
{
|
||||
name: "Expect successful",
|
||||
filesystemEntryFunc: func(ctx context.Context, rep repo.Repository, rootID string, consistentAttributes bool) (fs.Entry, error) {
|
||||
return snapshotfs.EntryFromDirEntry(rep, &snapshot.DirEntry{Type: snapshot.EntryTypeFile}), nil
|
||||
},
|
||||
restoreEntryFunc: func(ctx context.Context, rep repo.Repository, output restore.Output, rootEntry fs.Entry, options restore.Options) (restore.Stats, error) {
|
||||
return restore.Stats{}, nil
|
||||
},
|
||||
snapshotID: "snapshot-123",
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
em := &manifest.EntryMetadata{
|
||||
ID: "test",
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.invalidManifestType {
|
||||
em.Labels[manifest.TypeLabelKey] = ""
|
||||
} else {
|
||||
em.Labels[manifest.TypeLabelKey] = snapshot.ManifestType
|
||||
}
|
||||
|
||||
if tc.filesystemEntryFunc != nil {
|
||||
filesystemEntryFunc = tc.filesystemEntryFunc
|
||||
}
|
||||
|
||||
if tc.restoreEntryFunc != nil {
|
||||
restoreEntryFunc = tc.restoreEntryFunc
|
||||
}
|
||||
|
||||
repoWriterMock := &repomocks.RepositoryWriter{}
|
||||
repoWriterMock.On("GetManifest", mock.Anything, mock.Anything, mock.Anything).Return(em, nil)
|
||||
repoWriterMock.On("OpenObject", mock.Anything, mock.Anything).Return(em, nil)
|
||||
|
||||
progress := new(Progress)
|
||||
bytesRestored, fileCount, err := Restore(context.Background(), repoWriterMock, progress, tc.snapshotID, tc.dest, logrus.New(), nil)
|
||||
|
||||
// Check if the returned error matches the expected error
|
||||
if tc.expectedError != nil {
|
||||
assert.Contains(t, err.Error(), tc.expectedError.Error())
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
// Check the number of bytes restored
|
||||
assert.Equal(t, tc.expectedBytes, bytesRestored)
|
||||
|
||||
// Check the number of files restored
|
||||
assert.Equal(t, tc.expectedCount, fileCount)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
// BackupFunc mainly used to make testing more convenient
|
||||
var BackupFunc = kopia.Backup
|
||||
var RestoreFunc = kopia.Restore
|
||||
var BackupRepoServiceCreateFunc = service.Create
|
||||
|
||||
// kopiaProvider recorded info related with kopiaProvider
|
||||
type kopiaProvider struct {
|
||||
@@ -73,7 +74,7 @@ func NewKopiaUploaderProvider(
|
||||
return nil, errors.Wrapf(err, "error to get repo options")
|
||||
}
|
||||
|
||||
repoSvc := service.Create(log)
|
||||
repoSvc := BackupRepoServiceCreateFunc(log)
|
||||
log.WithField("repoUID", repoUID).Info("Opening backup repo")
|
||||
|
||||
kp.bkRepo, err = repoSvc.Open(ctx, *repoOpt)
|
||||
|
||||
@@ -18,48 +18,76 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kopia/kopia/repo"
|
||||
"github.com/kopia/kopia/snapshot/snapshotfs"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
"github.com/vmware-tanzu/velero/internal/credentials/mocks"
|
||||
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/repository"
|
||||
udmrepo "github.com/vmware-tanzu/velero/pkg/repository/udmrepo"
|
||||
udmrepomocks "github.com/vmware-tanzu/velero/pkg/repository/udmrepo/mocks"
|
||||
"github.com/vmware-tanzu/velero/pkg/uploader"
|
||||
"github.com/vmware-tanzu/velero/pkg/uploader/kopia"
|
||||
)
|
||||
|
||||
type FakeBackupProgressUpdater struct {
|
||||
PodVolumeBackup *velerov1api.PodVolumeBackup
|
||||
Log logrus.FieldLogger
|
||||
Ctx context.Context
|
||||
Cli client.Client
|
||||
}
|
||||
|
||||
func (f *FakeBackupProgressUpdater) UpdateProgress(p *uploader.Progress) {}
|
||||
|
||||
type FakeRestoreProgressUpdater struct {
|
||||
PodVolumeRestore *velerov1api.PodVolumeRestore
|
||||
Log logrus.FieldLogger
|
||||
Ctx context.Context
|
||||
Cli client.Client
|
||||
}
|
||||
|
||||
func (f *FakeRestoreProgressUpdater) UpdateProgress(p *uploader.Progress) {}
|
||||
|
||||
func TestRunBackup(t *testing.T) {
|
||||
var kp kopiaProvider
|
||||
kp.log = logrus.New()
|
||||
updater := FakeBackupProgressUpdater{PodVolumeBackup: &velerov1api.PodVolumeBackup{}, Log: kp.log, Ctx: context.Background(), Cli: fake.NewClientBuilder().WithScheme(scheme.Scheme).Build()}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
hookBackupFunc func(ctx context.Context, fsUploader *snapshotfs.Uploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error)
|
||||
hookBackupFunc func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error)
|
||||
notError bool
|
||||
}{
|
||||
{
|
||||
name: "success to backup",
|
||||
hookBackupFunc: func(ctx context.Context, fsUploader *snapshotfs.Uploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) {
|
||||
hookBackupFunc: func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) {
|
||||
return &uploader.SnapshotInfo{}, false, nil
|
||||
},
|
||||
notError: true,
|
||||
},
|
||||
{
|
||||
name: "get error to backup",
|
||||
hookBackupFunc: func(ctx context.Context, fsUploader *snapshotfs.Uploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) {
|
||||
hookBackupFunc: func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) {
|
||||
return &uploader.SnapshotInfo{}, false, errors.New("failed to backup")
|
||||
},
|
||||
notError: false,
|
||||
},
|
||||
{
|
||||
name: "got empty snapshot",
|
||||
hookBackupFunc: func(ctx context.Context, fsUploader *snapshotfs.Uploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) {
|
||||
hookBackupFunc: func(ctx context.Context, fsUploader kopia.SnapshotUploader, repoWriter repo.RepositoryWriter, sourcePath string, realSource string, forceFull bool, parentSnapshot string, tags map[string]string, log logrus.FieldLogger) (*uploader.SnapshotInfo, bool, error) {
|
||||
return nil, true, errors.New("snapshot is empty")
|
||||
},
|
||||
notError: false,
|
||||
@@ -117,20 +145,227 @@ func TestRunRestore(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type FakeBackupProgressUpdater struct {
|
||||
PodVolumeBackup *velerov1api.PodVolumeBackup
|
||||
Log logrus.FieldLogger
|
||||
Ctx context.Context
|
||||
Cli client.Client
|
||||
func TestCheckContext(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
finishChan chan struct{}
|
||||
restoreChan chan struct{}
|
||||
uploader *snapshotfs.Uploader
|
||||
expectCancel bool
|
||||
expectBackup bool
|
||||
expectRestore bool
|
||||
}{
|
||||
{
|
||||
name: "FinishChan",
|
||||
finishChan: make(chan struct{}),
|
||||
restoreChan: make(chan struct{}),
|
||||
uploader: &snapshotfs.Uploader{},
|
||||
expectCancel: false,
|
||||
expectBackup: false,
|
||||
expectRestore: false,
|
||||
},
|
||||
{
|
||||
name: "nil uploader",
|
||||
finishChan: make(chan struct{}),
|
||||
restoreChan: make(chan struct{}),
|
||||
uploader: nil,
|
||||
expectCancel: true,
|
||||
expectBackup: false,
|
||||
expectRestore: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
if tc.expectBackup {
|
||||
go func() {
|
||||
wg.Wait()
|
||||
tc.restoreChan <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
cancel()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
kp := &kopiaProvider{log: logrus.New()}
|
||||
kp.CheckContext(ctx, tc.finishChan, tc.restoreChan, tc.uploader)
|
||||
|
||||
if tc.expectCancel && tc.uploader != nil {
|
||||
t.Error("Expected the uploader to be cancelled")
|
||||
}
|
||||
|
||||
if tc.expectBackup && tc.uploader == nil && len(tc.restoreChan) > 0 {
|
||||
t.Error("Expected the restore channel to be closed")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeBackupProgressUpdater) UpdateProgress(p *uploader.Progress) {}
|
||||
func TestGetPassword(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
empytSecret bool
|
||||
credGetterFunc func(*mocks.SecretStore, *v1.SecretKeySelector)
|
||||
expectError bool
|
||||
expectedPass string
|
||||
}{
|
||||
{
|
||||
name: "valid credentials interface",
|
||||
credGetterFunc: func(ss *mocks.SecretStore, selector *v1.SecretKeySelector) {
|
||||
ss.On("Get", selector).Return("test", nil)
|
||||
},
|
||||
expectError: false,
|
||||
expectedPass: "test",
|
||||
},
|
||||
{
|
||||
name: "empty from secret",
|
||||
empytSecret: true,
|
||||
expectError: true,
|
||||
expectedPass: "",
|
||||
},
|
||||
{
|
||||
name: "ErrorGettingPassword",
|
||||
credGetterFunc: func(ss *mocks.SecretStore, selector *v1.SecretKeySelector) {
|
||||
ss.On("Get", selector).Return("", errors.New("error getting password"))
|
||||
},
|
||||
expectError: true,
|
||||
expectedPass: "",
|
||||
},
|
||||
}
|
||||
|
||||
type FakeRestoreProgressUpdater struct {
|
||||
PodVolumeRestore *velerov1api.PodVolumeRestore
|
||||
Log logrus.FieldLogger
|
||||
Ctx context.Context
|
||||
Cli client.Client
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Mock CredentialGetter
|
||||
credGetter := &credentials.CredentialGetter{}
|
||||
mockCredGetter := &mocks.SecretStore{}
|
||||
if !tc.empytSecret {
|
||||
credGetter.FromSecret = mockCredGetter
|
||||
}
|
||||
repoKeySelector := &v1.SecretKeySelector{LocalObjectReference: v1.LocalObjectReference{Name: "velero-repo-credentials"}, Key: "repository-password"}
|
||||
|
||||
if tc.credGetterFunc != nil {
|
||||
tc.credGetterFunc(mockCredGetter, repoKeySelector)
|
||||
}
|
||||
|
||||
kp := &kopiaProvider{
|
||||
credGetter: credGetter,
|
||||
}
|
||||
|
||||
password, err := kp.GetPassword(nil)
|
||||
if tc.expectError {
|
||||
assert.Error(t, err, "Expected an error")
|
||||
} else {
|
||||
assert.NoError(t, err, "Expected no error")
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.expectedPass, password, "Expected password to match")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeRestoreProgressUpdater) UpdateProgress(p *uploader.Progress) {}
|
||||
func (m *MockCredentialGetter) GetCredentials() (string, error) {
|
||||
args := m.Called()
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
|
||||
// MockRepoSvc is a mock implementation of the RepoService interface.
|
||||
type MockRepoSvc struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockRepoSvc) Open(ctx context.Context, opts udmrepo.RepoOptions) (udmrepo.BackupRepo, error) {
|
||||
args := m.Called(ctx, opts)
|
||||
return args.Get(0).(udmrepo.BackupRepo), args.Error(1)
|
||||
}
|
||||
|
||||
func TestNewKopiaUploaderProvider(t *testing.T) {
|
||||
requestorType := "testRequestor"
|
||||
ctx := context.Background()
|
||||
backupRepo := repository.NewBackupRepository(velerov1api.DefaultNamespace, repository.BackupRepositoryKey{VolumeNamespace: "fake-volume-ns-02", BackupLocation: "fake-bsl-02", RepositoryType: "fake-repository-type-02"})
|
||||
mockLog := logrus.New()
|
||||
|
||||
// Define test cases
|
||||
testCases := []struct {
|
||||
name string
|
||||
mockCredGetter *mocks.SecretStore
|
||||
mockBackupRepoService udmrepo.BackupRepoService
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "Success",
|
||||
mockCredGetter: func() *mocks.SecretStore {
|
||||
mockCredGetter := &mocks.SecretStore{}
|
||||
mockCredGetter.On("Get", mock.Anything).Return("test", nil)
|
||||
return mockCredGetter
|
||||
}(),
|
||||
mockBackupRepoService: func() udmrepo.BackupRepoService {
|
||||
backupRepoService := &udmrepomocks.BackupRepoService{}
|
||||
var backupRepo udmrepo.BackupRepo
|
||||
backupRepoService.On("Open", context.Background(), mock.Anything).Return(backupRepo, nil)
|
||||
return backupRepoService
|
||||
}(),
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "Error to get repo options",
|
||||
mockCredGetter: func() *mocks.SecretStore {
|
||||
mockCredGetter := &mocks.SecretStore{}
|
||||
mockCredGetter.On("Get", mock.Anything).Return("test", errors.New("failed to get password"))
|
||||
return mockCredGetter
|
||||
}(),
|
||||
mockBackupRepoService: func() udmrepo.BackupRepoService {
|
||||
backupRepoService := &udmrepomocks.BackupRepoService{}
|
||||
var backupRepo udmrepo.BackupRepo
|
||||
backupRepoService.On("Open", context.Background(), mock.Anything).Return(backupRepo, nil)
|
||||
return backupRepoService
|
||||
}(),
|
||||
expectedError: "error to get repo options",
|
||||
},
|
||||
{
|
||||
name: "Error open repository service",
|
||||
mockCredGetter: func() *mocks.SecretStore {
|
||||
mockCredGetter := &mocks.SecretStore{}
|
||||
mockCredGetter.On("Get", mock.Anything).Return("test", nil)
|
||||
return mockCredGetter
|
||||
}(),
|
||||
mockBackupRepoService: func() udmrepo.BackupRepoService {
|
||||
backupRepoService := &udmrepomocks.BackupRepoService{}
|
||||
var backupRepo udmrepo.BackupRepo
|
||||
backupRepoService.On("Open", context.Background(), mock.Anything).Return(backupRepo, errors.New("failed to init repository"))
|
||||
return backupRepoService
|
||||
}(),
|
||||
expectedError: "Failed to find kopia repository",
|
||||
},
|
||||
}
|
||||
|
||||
// Iterate through test cases
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
credGetter := &credentials.CredentialGetter{FromSecret: tc.mockCredGetter}
|
||||
BackupRepoServiceCreateFunc = func(logger logrus.FieldLogger) udmrepo.BackupRepoService {
|
||||
return tc.mockBackupRepoService
|
||||
}
|
||||
// Call the function being tested.
|
||||
_, err := NewKopiaUploaderProvider(requestorType, ctx, credGetter, backupRepo, mockLog)
|
||||
|
||||
// Assertions
|
||||
if tc.expectedError != "" {
|
||||
assert.Contains(t, err.Error(), tc.expectedError)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
// Verify that the expected methods were called on the mocks.
|
||||
tc.mockCredGetter.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
98
pkg/uploader/provider/provider_test.go
Normal file
98
pkg/uploader/provider/provider_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
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 provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/vmware-tanzu/velero/internal/credentials"
|
||||
"github.com/vmware-tanzu/velero/internal/credentials/mocks"
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
"github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme"
|
||||
)
|
||||
|
||||
type NewUploaderProviderTestCase struct {
|
||||
Description string
|
||||
UploaderType string
|
||||
RequestorType string
|
||||
ExpectedError string
|
||||
needFromFile bool
|
||||
}
|
||||
|
||||
func TestNewUploaderProvider(t *testing.T) {
|
||||
// Mock objects or dependencies
|
||||
ctx := context.Background()
|
||||
client := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build()
|
||||
repoIdentifier := "repoIdentifier"
|
||||
bsl := &velerov1api.BackupStorageLocation{}
|
||||
backupRepo := &velerov1api.BackupRepository{}
|
||||
credGetter := &credentials.CredentialGetter{}
|
||||
repoKeySelector := &v1.SecretKeySelector{}
|
||||
log := logrus.New()
|
||||
|
||||
testCases := []NewUploaderProviderTestCase{
|
||||
{
|
||||
Description: "When requestorType is empty, it should return an error",
|
||||
UploaderType: "kopia",
|
||||
RequestorType: "",
|
||||
ExpectedError: "requestor type is empty",
|
||||
},
|
||||
{
|
||||
Description: "When FileStore credential is uninitialized, it should return an error",
|
||||
UploaderType: "kopia",
|
||||
RequestorType: "requestor",
|
||||
ExpectedError: "uninitialized FileStore credentail",
|
||||
},
|
||||
{
|
||||
Description: "When uploaderType is kopia, it should return a KopiaUploaderProvider",
|
||||
UploaderType: "kopia",
|
||||
RequestorType: "requestor",
|
||||
needFromFile: true,
|
||||
ExpectedError: "invalid credentials interface",
|
||||
},
|
||||
{
|
||||
Description: "When uploaderType is not kopia, it should return a ResticUploaderProvider",
|
||||
UploaderType: "restic",
|
||||
RequestorType: "requestor",
|
||||
needFromFile: true,
|
||||
ExpectedError: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
if testCase.needFromFile {
|
||||
mockFileGetter := &mocks.FileStore{}
|
||||
mockFileGetter.On("Path", &v1.SecretKeySelector{}).Return("", nil)
|
||||
credGetter.FromFile = mockFileGetter
|
||||
|
||||
}
|
||||
_, err := NewUploaderProvider(ctx, client, testCase.UploaderType, testCase.RequestorType, repoIdentifier, bsl, backupRepo, credGetter, repoKeySelector, log)
|
||||
if testCase.ExpectedError == "" {
|
||||
assert.Nil(t, err)
|
||||
} else {
|
||||
assert.Contains(t, err.Error(), testCase.ExpectedError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -33,9 +33,14 @@ import (
|
||||
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
|
||||
)
|
||||
|
||||
// ResticBackupCMDFunc and ResticRestoreCMDFunc are mainly used to make testing more convenient
|
||||
var ResticBackupCMDFunc = restic.BackupCommand
|
||||
var ResticRestoreCMDFunc = restic.RestoreCommand
|
||||
// resticBackupCMDFunc and resticRestoreCMDFunc are mainly used to make testing more convenient
|
||||
var resticBackupCMDFunc = restic.BackupCommand
|
||||
var resticBackupFunc = restic.RunBackup
|
||||
var resticGetSnapshotFunc = restic.GetSnapshotCommand
|
||||
var resticGetSnapshotIDFunc = restic.GetSnapshotID
|
||||
var resticRestoreCMDFunc = restic.RestoreCommand
|
||||
var resticTempCACertFileFunc = restic.TempCACertFile
|
||||
var resticCmdEnvFunc = restic.CmdEnv
|
||||
|
||||
type resticProvider struct {
|
||||
repoIdentifier string
|
||||
@@ -68,13 +73,13 @@ func NewResticUploaderProvider(
|
||||
|
||||
// if there's a caCert on the ObjectStorage, write it to disk so that it can be passed to restic
|
||||
if bsl.Spec.ObjectStorage != nil && bsl.Spec.ObjectStorage.CACert != nil {
|
||||
provider.caCertFile, err = restic.TempCACertFile(bsl.Spec.ObjectStorage.CACert, bsl.Name, filesystem.NewFileSystem())
|
||||
provider.caCertFile, err = resticTempCACertFileFunc(bsl.Spec.ObjectStorage.CACert, bsl.Name, filesystem.NewFileSystem())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error create temp cert file")
|
||||
}
|
||||
}
|
||||
|
||||
provider.cmdEnv, err = restic.CmdEnv(bsl, credGetter.FromFile)
|
||||
provider.cmdEnv, err = resticCmdEnvFunc(bsl, credGetter.FromFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error generating repository cmnd env")
|
||||
}
|
||||
@@ -134,7 +139,7 @@ func (rp *resticProvider) RunBackup(
|
||||
"parentSnapshot": parentSnapshot,
|
||||
})
|
||||
|
||||
backupCmd := ResticBackupCMDFunc(rp.repoIdentifier, rp.credentialsFile, path, tags)
|
||||
backupCmd := resticBackupCMDFunc(rp.repoIdentifier, rp.credentialsFile, path, tags)
|
||||
backupCmd.Env = rp.cmdEnv
|
||||
backupCmd.CACertFile = rp.caCertFile
|
||||
if len(rp.extraFlags) != 0 {
|
||||
@@ -145,7 +150,7 @@ func (rp *resticProvider) RunBackup(
|
||||
backupCmd.ExtraFlags = append(backupCmd.ExtraFlags, fmt.Sprintf("--parent=%s", parentSnapshot))
|
||||
}
|
||||
|
||||
summary, stderrBuf, err := restic.RunBackup(backupCmd, log, updater)
|
||||
summary, stderrBuf, err := resticBackupFunc(backupCmd, log, updater)
|
||||
if err != nil {
|
||||
if strings.Contains(stderrBuf, "snapshot is empty") {
|
||||
log.Debugf("Restic backup got empty dir with %s path", path)
|
||||
@@ -154,13 +159,13 @@ func (rp *resticProvider) RunBackup(
|
||||
return "", false, errors.WithStack(fmt.Errorf("error running restic backup command %s with error: %v stderr: %v", backupCmd.String(), err, stderrBuf))
|
||||
}
|
||||
// GetSnapshotID
|
||||
snapshotIDCmd := restic.GetSnapshotCommand(rp.repoIdentifier, rp.credentialsFile, tags)
|
||||
snapshotIDCmd := resticGetSnapshotFunc(rp.repoIdentifier, rp.credentialsFile, tags)
|
||||
snapshotIDCmd.Env = rp.cmdEnv
|
||||
snapshotIDCmd.CACertFile = rp.caCertFile
|
||||
if len(rp.extraFlags) != 0 {
|
||||
snapshotIDCmd.ExtraFlags = append(snapshotIDCmd.ExtraFlags, rp.extraFlags...)
|
||||
}
|
||||
snapshotID, err := restic.GetSnapshotID(snapshotIDCmd)
|
||||
snapshotID, err := resticGetSnapshotIDFunc(snapshotIDCmd)
|
||||
if err != nil {
|
||||
return "", false, errors.WithStack(fmt.Errorf("error getting snapshot id with error: %v", err))
|
||||
}
|
||||
@@ -183,7 +188,7 @@ func (rp *resticProvider) RunRestore(
|
||||
"volumePath": volumePath,
|
||||
})
|
||||
|
||||
restoreCmd := ResticRestoreCMDFunc(rp.repoIdentifier, rp.credentialsFile, snapshotID, volumePath)
|
||||
restoreCmd := resticRestoreCMDFunc(rp.repoIdentifier, rp.credentialsFile, snapshotID, volumePath)
|
||||
restoreCmd.Env = rp.cmdEnv
|
||||
restoreCmd.CACertFile = rp.caCertFile
|
||||
if len(rp.extraFlags) != 0 {
|
||||
|
||||
@@ -18,89 +18,369 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"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/generated/clientset/versioned/scheme"
|
||||
"github.com/vmware-tanzu/velero/pkg/restic"
|
||||
"github.com/vmware-tanzu/velero/pkg/uploader"
|
||||
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
|
||||
)
|
||||
|
||||
func TestResticRunBackup(t *testing.T) {
|
||||
var rp resticProvider
|
||||
rp.log = logrus.New()
|
||||
updater := FakeBackupProgressUpdater{PodVolumeBackup: &velerov1api.PodVolumeBackup{}, Log: rp.log, Ctx: context.Background(), Cli: fake.NewClientBuilder().WithScheme(scheme.Scheme).Build()}
|
||||
testCases := []struct {
|
||||
name string
|
||||
hookBackupFunc func(repoIdentifier string, passwordFile string, path string, tags map[string]string) *restic.Command
|
||||
hookRunBackupFunc func(backupCmd *restic.Command, log logrus.FieldLogger, updater uploader.ProgressUpdater) (string, string, error)
|
||||
errorHandleFunc func(err error) bool
|
||||
name string
|
||||
nilUpdater bool
|
||||
parentSnapshot string
|
||||
rp *resticProvider
|
||||
hookBackupFunc func(string, string, string, map[string]string) *restic.Command
|
||||
hookResticBackupFunc func(*restic.Command, logrus.FieldLogger, uploader.ProgressUpdater) (string, string, error)
|
||||
hookResticGetSnapshotFunc func(string, string, map[string]string) *restic.Command
|
||||
hookResticGetSnapshotIDFunc func(*restic.Command) (string, error)
|
||||
errorHandleFunc func(err error) bool
|
||||
}{
|
||||
{
|
||||
name: "wrong restic execute command",
|
||||
name: "nil uploader",
|
||||
rp: &resticProvider{log: logrus.New()},
|
||||
nilUpdater: true,
|
||||
hookBackupFunc: func(repoIdentifier string, passwordFile string, path string, tags map[string]string) *restic.Command {
|
||||
return &restic.Command{Command: "date"}
|
||||
},
|
||||
errorHandleFunc: func(err error) bool {
|
||||
return strings.Contains(err.Error(), "executable file not found in")
|
||||
return strings.Contains(err.Error(), "Need to initial backup progress updater first")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wrong parsing json summary content",
|
||||
name: "wrong restic execute command",
|
||||
rp: &resticProvider{log: logrus.New()},
|
||||
hookBackupFunc: func(repoIdentifier string, passwordFile string, path string, tags map[string]string) *restic.Command {
|
||||
return &restic.Command{Command: "version"}
|
||||
return &restic.Command{Command: "date"}
|
||||
},
|
||||
errorHandleFunc: func(err error) bool {
|
||||
return strings.Contains(err.Error(), "executable file not found in")
|
||||
return strings.Contains(err.Error(), "error running")
|
||||
},
|
||||
}, {
|
||||
name: "has parent snapshot",
|
||||
rp: &resticProvider{log: logrus.New()},
|
||||
parentSnapshot: "parentSnapshot",
|
||||
hookBackupFunc: func(repoIdentifier string, passwordFile string, path string, tags map[string]string) *restic.Command {
|
||||
return &restic.Command{Command: "date"}
|
||||
},
|
||||
hookResticBackupFunc: func(*restic.Command, logrus.FieldLogger, uploader.ProgressUpdater) (string, string, error) {
|
||||
return "", "", nil
|
||||
},
|
||||
|
||||
hookResticGetSnapshotIDFunc: func(*restic.Command) (string, error) { return "test-snapshot-id", nil },
|
||||
errorHandleFunc: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "has extra flags",
|
||||
rp: &resticProvider{log: logrus.New(), extraFlags: []string{"testFlags"}},
|
||||
hookBackupFunc: func(string, string, string, map[string]string) *restic.Command {
|
||||
return &restic.Command{Command: "date"}
|
||||
},
|
||||
hookResticBackupFunc: func(*restic.Command, logrus.FieldLogger, uploader.ProgressUpdater) (string, string, error) {
|
||||
return "", "", nil
|
||||
},
|
||||
hookResticGetSnapshotIDFunc: func(*restic.Command) (string, error) { return "test-snapshot-id", nil },
|
||||
errorHandleFunc: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "failed to get snapshot id",
|
||||
rp: &resticProvider{log: logrus.New(), extraFlags: []string{"testFlags"}},
|
||||
hookBackupFunc: func(string, string, string, map[string]string) *restic.Command {
|
||||
return &restic.Command{Command: "date"}
|
||||
},
|
||||
hookResticBackupFunc: func(*restic.Command, logrus.FieldLogger, uploader.ProgressUpdater) (string, string, error) {
|
||||
return "", "", nil
|
||||
},
|
||||
hookResticGetSnapshotIDFunc: func(*restic.Command) (string, error) {
|
||||
return "test-snapshot-id", errors.New("failed to get snapshot id")
|
||||
},
|
||||
errorHandleFunc: func(err error) bool {
|
||||
return strings.Contains(err.Error(), "failed to get snapshot id")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ResticBackupCMDFunc = tc.hookBackupFunc
|
||||
_, _, err := rp.RunBackup(context.Background(), "var", "", nil, false, "", &updater)
|
||||
rp.log.Infof("test name %v error %v", tc.name, err)
|
||||
var err error
|
||||
parentSnapshot := tc.parentSnapshot
|
||||
if tc.hookBackupFunc != nil {
|
||||
resticBackupCMDFunc = tc.hookBackupFunc
|
||||
}
|
||||
if tc.hookResticBackupFunc != nil {
|
||||
resticBackupFunc = tc.hookResticBackupFunc
|
||||
}
|
||||
if tc.hookResticGetSnapshotFunc != nil {
|
||||
resticGetSnapshotFunc = tc.hookResticGetSnapshotFunc
|
||||
}
|
||||
if tc.hookResticGetSnapshotIDFunc != nil {
|
||||
resticGetSnapshotIDFunc = tc.hookResticGetSnapshotIDFunc
|
||||
}
|
||||
if !tc.nilUpdater {
|
||||
updater := FakeBackupProgressUpdater{PodVolumeBackup: &velerov1api.PodVolumeBackup{}, Log: tc.rp.log, Ctx: context.Background(), Cli: fake.NewClientBuilder().WithScheme(scheme.Scheme).Build()}
|
||||
_, _, err = tc.rp.RunBackup(context.Background(), "var", "", map[string]string{}, false, parentSnapshot, &updater)
|
||||
} else {
|
||||
_, _, err = tc.rp.RunBackup(context.Background(), "var", "", map[string]string{}, false, parentSnapshot, nil)
|
||||
}
|
||||
|
||||
tc.rp.log.Infof("test name %v error %v", tc.name, err)
|
||||
require.Equal(t, true, tc.errorHandleFunc(err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResticRunRestore(t *testing.T) {
|
||||
var rp resticProvider
|
||||
rp.log = logrus.New()
|
||||
updater := FakeBackupProgressUpdater{PodVolumeBackup: &velerov1api.PodVolumeBackup{}, Log: rp.log, Ctx: context.Background(), Cli: fake.NewClientBuilder().WithScheme(scheme.Scheme).Build()}
|
||||
ResticRestoreCMDFunc = func(repoIdentifier, passwordFile, snapshotID, target string) *restic.Command {
|
||||
resticRestoreCMDFunc = func(repoIdentifier, passwordFile, snapshotID, target string) *restic.Command {
|
||||
return &restic.Command{Args: []string{""}}
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
rp *resticProvider
|
||||
nilUpdater bool
|
||||
hookResticRestoreFunc func(repoIdentifier, passwordFile, snapshotID, target string) *restic.Command
|
||||
errorHandleFunc func(err error) bool
|
||||
}{
|
||||
{
|
||||
name: "wrong restic execute command",
|
||||
name: "wrong restic execute command",
|
||||
rp: &resticProvider{log: logrus.New()},
|
||||
nilUpdater: true,
|
||||
errorHandleFunc: func(err error) bool {
|
||||
return strings.Contains(err.Error(), "Need to initial backup progress updater first")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "has extral flags",
|
||||
rp: &resticProvider{log: logrus.New(), extraFlags: []string{"test-extra-flags"}},
|
||||
hookResticRestoreFunc: func(repoIdentifier, passwordFile, snapshotID, target string) *restic.Command {
|
||||
return &restic.Command{Args: []string{"date"}}
|
||||
},
|
||||
errorHandleFunc: func(err error) bool {
|
||||
return strings.Contains(err.Error(), "executable file not found ")
|
||||
return strings.Contains(err.Error(), "error running command")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wrong restic execute command",
|
||||
rp: &resticProvider{log: logrus.New()},
|
||||
hookResticRestoreFunc: func(repoIdentifier, passwordFile, snapshotID, target string) *restic.Command {
|
||||
return &restic.Command{Args: []string{"date"}}
|
||||
},
|
||||
errorHandleFunc: func(err error) bool {
|
||||
return strings.Contains(err.Error(), "error running command")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ResticRestoreCMDFunc = tc.hookResticRestoreFunc
|
||||
err := rp.RunRestore(context.Background(), "", "var", &updater)
|
||||
rp.log.Infof("test name %v error %v", tc.name, err)
|
||||
resticRestoreCMDFunc = tc.hookResticRestoreFunc
|
||||
var err error
|
||||
if !tc.nilUpdater {
|
||||
updater := FakeBackupProgressUpdater{PodVolumeBackup: &velerov1api.PodVolumeBackup{}, Log: tc.rp.log, Ctx: context.Background(), Cli: fake.NewClientBuilder().WithScheme(scheme.Scheme).Build()}
|
||||
err = tc.rp.RunRestore(context.Background(), "", "var", &updater)
|
||||
} else {
|
||||
err = tc.rp.RunRestore(context.Background(), "", "var", nil)
|
||||
}
|
||||
|
||||
tc.rp.log.Infof("test name %v error %v", tc.name, err)
|
||||
require.Equal(t, true, tc.errorHandleFunc(err))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestClose(t *testing.T) {
|
||||
t.Run("Delete existing credentials file", func(t *testing.T) {
|
||||
// Create temporary files for the credentials and caCert
|
||||
credentialsFile, err := ioutil.TempFile("", "credentialsFile")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(credentialsFile.Name())
|
||||
|
||||
caCertFile, err := ioutil.TempFile("", "caCertFile")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(caCertFile.Name())
|
||||
rp := &resticProvider{
|
||||
credentialsFile: credentialsFile.Name(),
|
||||
caCertFile: caCertFile.Name(),
|
||||
}
|
||||
// Test deleting an existing credentials file
|
||||
err = rp.Close(context.Background())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
_, err = os.Stat(rp.credentialsFile)
|
||||
if !os.IsNotExist(err) {
|
||||
t.Errorf("expected credentials file to be deleted, got error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Delete existing caCert file", func(t *testing.T) {
|
||||
// Create temporary files for the credentials and caCert
|
||||
caCertFile, err := ioutil.TempFile("", "caCertFile")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(caCertFile.Name())
|
||||
rp := &resticProvider{
|
||||
credentialsFile: "",
|
||||
caCertFile: "",
|
||||
}
|
||||
err = rp.Close(context.Background())
|
||||
// Test deleting an existing caCert file
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
_, err = os.Stat(rp.caCertFile)
|
||||
if !os.IsNotExist(err) {
|
||||
t.Errorf("expected caCert file to be deleted, got error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type MockCredentialGetter struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockCredentialGetter) Path(selector *v1.SecretKeySelector) (string, error) {
|
||||
args := m.Called(selector)
|
||||
return args.Get(0).(string), args.Error(1)
|
||||
}
|
||||
|
||||
func TestNewResticUploaderProvider(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
emptyBSL bool
|
||||
mockCredFunc func(*MockCredentialGetter, *v1.SecretKeySelector)
|
||||
resticCmdEnvFunc func(backupLocation *velerov1api.BackupStorageLocation, credentialFileStore credentials.FileStore) ([]string, error)
|
||||
resticTempCACertFileFunc func(caCert []byte, bsl string, fs filesystem.Interface) (string, error)
|
||||
checkFunc func(provider Provider, err error)
|
||||
}{
|
||||
{
|
||||
name: "No error in creating temp credentials file",
|
||||
mockCredFunc: func(credGetter *MockCredentialGetter, repoKeySelector *v1.SecretKeySelector) {
|
||||
credGetter.On("Path", repoKeySelector).Return("temp-credentials", nil)
|
||||
},
|
||||
checkFunc: func(provider Provider, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, provider)
|
||||
},
|
||||
}, {
|
||||
name: "Error in creating temp credentials file",
|
||||
mockCredFunc: func(credGetter *MockCredentialGetter, repoKeySelector *v1.SecretKeySelector) {
|
||||
credGetter.On("Path", repoKeySelector).Return("", errors.New("error creating temp credentials file"))
|
||||
},
|
||||
checkFunc: func(provider Provider, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, provider)
|
||||
},
|
||||
}, {
|
||||
name: "ObjectStorage with CACert present and creating CACert file failed",
|
||||
mockCredFunc: func(credGetter *MockCredentialGetter, repoKeySelector *v1.SecretKeySelector) {
|
||||
credGetter.On("Path", repoKeySelector).Return("temp-credentials", nil)
|
||||
},
|
||||
resticTempCACertFileFunc: func(caCert []byte, bsl string, fs filesystem.Interface) (string, error) {
|
||||
return "", errors.New("error writing CACert file")
|
||||
},
|
||||
checkFunc: func(provider Provider, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, provider)
|
||||
},
|
||||
}, {
|
||||
name: "Generating repository cmd failed",
|
||||
mockCredFunc: func(credGetter *MockCredentialGetter, repoKeySelector *v1.SecretKeySelector) {
|
||||
credGetter.On("Path", repoKeySelector).Return("temp-credentials", nil)
|
||||
},
|
||||
resticTempCACertFileFunc: func(caCert []byte, bsl string, fs filesystem.Interface) (string, error) {
|
||||
return "test-ca", nil
|
||||
},
|
||||
resticCmdEnvFunc: func(backupLocation *velerov1api.BackupStorageLocation, credentialFileStore credentials.FileStore) ([]string, error) {
|
||||
return nil, errors.New("error generating repository cmnd env")
|
||||
},
|
||||
checkFunc: func(provider Provider, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, provider)
|
||||
},
|
||||
}, {
|
||||
name: "New provider with not nil bsl",
|
||||
mockCredFunc: func(credGetter *MockCredentialGetter, repoKeySelector *v1.SecretKeySelector) {
|
||||
credGetter.On("Path", repoKeySelector).Return("temp-credentials", nil)
|
||||
},
|
||||
resticTempCACertFileFunc: func(caCert []byte, bsl string, fs filesystem.Interface) (string, error) {
|
||||
return "test-ca", nil
|
||||
},
|
||||
resticCmdEnvFunc: func(backupLocation *velerov1api.BackupStorageLocation, credentialFileStore credentials.FileStore) ([]string, error) {
|
||||
return nil, nil
|
||||
},
|
||||
checkFunc: func(provider Provider, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, provider)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "New provider with nil bsl",
|
||||
emptyBSL: true,
|
||||
mockCredFunc: func(credGetter *MockCredentialGetter, repoKeySelector *v1.SecretKeySelector) {
|
||||
credGetter.On("Path", repoKeySelector).Return("temp-credentials", nil)
|
||||
},
|
||||
resticTempCACertFileFunc: func(caCert []byte, bsl string, fs filesystem.Interface) (string, error) {
|
||||
return "test-ca", nil
|
||||
},
|
||||
resticCmdEnvFunc: func(backupLocation *velerov1api.BackupStorageLocation, credentialFileStore credentials.FileStore) ([]string, error) {
|
||||
return nil, nil
|
||||
},
|
||||
checkFunc: func(provider Provider, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, provider)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
repoIdentifier := "my-repo"
|
||||
bsl := &velerov1api.BackupStorageLocation{}
|
||||
if !tc.emptyBSL {
|
||||
bsl = builder.ForBackupStorageLocation("test-ns", "test-name").CACert([]byte("my-cert")).Result()
|
||||
}
|
||||
credGetter := &credentials.CredentialGetter{}
|
||||
repoKeySelector := &v1.SecretKeySelector{}
|
||||
log := logrus.New()
|
||||
|
||||
// Mock CredentialGetter
|
||||
mockCredGetter := &MockCredentialGetter{}
|
||||
credGetter.FromFile = mockCredGetter
|
||||
tc.mockCredFunc(mockCredGetter, repoKeySelector)
|
||||
if tc.resticCmdEnvFunc != nil {
|
||||
resticCmdEnvFunc = tc.resticCmdEnvFunc
|
||||
}
|
||||
if tc.resticTempCACertFileFunc != nil {
|
||||
resticTempCACertFileFunc = tc.resticTempCACertFileFunc
|
||||
}
|
||||
tc.checkFunc(NewResticUploaderProvider(repoIdentifier, bsl, credGetter, repoKeySelector, log))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user