Remove code to restore directory subtrees (#826)
Not used and don't have the time to figure out the best way to bend the path struct API to get it to work nicely.
This commit is contained in:
parent
61f769cc15
commit
2b7d3877a4
@ -446,27 +446,12 @@ func (w Wrapper) collectItems(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
snapshotID string,
|
snapshotID string,
|
||||||
itemPath []string,
|
itemPath []string,
|
||||||
isDirectory bool,
|
|
||||||
) ([]data.Collection, error) {
|
) ([]data.Collection, error) {
|
||||||
e, err := w.getEntry(ctx, snapshotID, itemPath)
|
e, err := w.getEntry(ctx, snapshotID, itemPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// The paths passed below is the path up to (but not including) the
|
|
||||||
// file/directory passed.
|
|
||||||
if isDirectory {
|
|
||||||
dir, ok := e.(fs.Directory)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("requested object is not a directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := restoreSubtree(ctx, dir, itemPath[:len(itemPath)-1])
|
|
||||||
// For some reason tests error out if the multierror is nil but we don't
|
|
||||||
// call ErrorOrNil.
|
|
||||||
return c, err.ErrorOrNil()
|
|
||||||
}
|
|
||||||
|
|
||||||
f, ok := e.(fs.File)
|
f, ok := e.(fs.File)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("requested object is not a file")
|
return nil, errors.New("requested object is not a file")
|
||||||
@ -492,7 +477,7 @@ func (w Wrapper) RestoreSingleItem(
|
|||||||
snapshotID string,
|
snapshotID string,
|
||||||
itemPath []string,
|
itemPath []string,
|
||||||
) (data.Collection, error) {
|
) (data.Collection, error) {
|
||||||
c, err := w.collectItems(ctx, snapshotID, itemPath, false)
|
c, err := w.collectItems(ctx, snapshotID, itemPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -528,126 +513,6 @@ func restoreSingleItem(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func walkDirectory(
|
|
||||||
ctx context.Context,
|
|
||||||
dir fs.Directory,
|
|
||||||
) ([]fs.File, []fs.Directory, *multierror.Error) {
|
|
||||||
var errs *multierror.Error
|
|
||||||
|
|
||||||
files := []fs.File{}
|
|
||||||
dirs := []fs.Directory{}
|
|
||||||
|
|
||||||
err := dir.IterateEntries(ctx, func(innerCtx context.Context, e fs.Entry) error {
|
|
||||||
// Early exit on context cancel.
|
|
||||||
if err := innerCtx.Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch e := e.(type) {
|
|
||||||
case fs.Directory:
|
|
||||||
dirs = append(dirs, e)
|
|
||||||
case fs.File:
|
|
||||||
files = append(files, e)
|
|
||||||
default:
|
|
||||||
errs = multierror.Append(errs, errors.Errorf("unexpected item type %T", e))
|
|
||||||
logger.Ctx(ctx).Errorw(
|
|
||||||
"unexpected item type; skipping", "type", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
// If the iterator itself had an error add it to the list.
|
|
||||||
errs = multierror.Append(errs, errors.Wrap(err, "getting directory data"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return files, dirs, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
// restoreSubtree returns DataCollections for each subdirectory (or the
|
|
||||||
// directory itself) that contains files. The FullPath of each returned
|
|
||||||
// DataCollection is the path from the root of the kopia directory structure to
|
|
||||||
// the directory. The UUID of each DataStream in each DataCollection is the name
|
|
||||||
// of the kopia file the data is sourced from.
|
|
||||||
func restoreSubtree(
|
|
||||||
ctx context.Context,
|
|
||||||
dir fs.Directory,
|
|
||||||
relativePath []string,
|
|
||||||
) ([]data.Collection, *multierror.Error) {
|
|
||||||
var errs *multierror.Error
|
|
||||||
|
|
||||||
collections := []data.Collection{}
|
|
||||||
// Want a local copy of relativePath with our new element.
|
|
||||||
fullPath := append(append([]string{}, relativePath...), dir.Name())
|
|
||||||
|
|
||||||
files, dirs, err := walkDirectory(ctx, dir)
|
|
||||||
if err != nil {
|
|
||||||
errs = multierror.Append(
|
|
||||||
errs, errors.Wrapf(err, "walking directory %q", path.Join(fullPath...)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(files) > 0 {
|
|
||||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
|
||||||
errs = multierror.Append(errs, errors.WithStack(ctxErr))
|
|
||||||
return nil, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
streams := make([]data.Stream, 0, len(files))
|
|
||||||
|
|
||||||
for _, f := range files {
|
|
||||||
r, err := f.Open(ctx)
|
|
||||||
if err != nil {
|
|
||||||
fileFullPath := path.Join(append(append([]string{}, fullPath...), f.Name())...)
|
|
||||||
errs = multierror.Append(
|
|
||||||
errs, errors.Wrapf(err, "getting reader for file %q", fileFullPath))
|
|
||||||
|
|
||||||
logger.Ctx(ctx).Errorw(
|
|
||||||
"unable to get file reader; skipping", "path", fileFullPath)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
streams = append(streams, &kopiaDataStream{
|
|
||||||
reader: r,
|
|
||||||
uuid: f.Name(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
collections = append(collections, &kopiaDataCollection{
|
|
||||||
streams: streams,
|
|
||||||
path: fullPath,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range dirs {
|
|
||||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
|
||||||
errs = multierror.Append(errs, errors.WithStack(ctxErr))
|
|
||||||
return nil, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := restoreSubtree(ctx, d, fullPath)
|
|
||||||
if err != nil {
|
|
||||||
errs = multierror.Append(errs, errors.Wrapf(
|
|
||||||
err,
|
|
||||||
"traversing subdirectory %q",
|
|
||||||
path.Join(append(append([]string{}, fullPath...), d.Name())...),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
collections = append(collections, c...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return collections, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w Wrapper) RestoreDirectory(
|
|
||||||
ctx context.Context,
|
|
||||||
snapshotID string,
|
|
||||||
basePath []string,
|
|
||||||
) ([]data.Collection, error) {
|
|
||||||
return w.collectItems(ctx, snapshotID, basePath, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RestoreSingleItem looks up all paths- assuming each is an item declaration,
|
// RestoreSingleItem looks up all paths- assuming each is an item declaration,
|
||||||
// not a directory- in the snapshot with id snapshotID. The path should be the
|
// not a directory- in the snapshot with id snapshotID. The path should be the
|
||||||
// full path of the item from the root. Returns the results as a slice of single-
|
// full path of the item from the root. Returns the results as a slice of single-
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/kopia/kopia/fs"
|
"github.com/kopia/kopia/fs"
|
||||||
"github.com/kopia/kopia/fs/virtualfs"
|
|
||||||
"github.com/kopia/kopia/repo/manifest"
|
"github.com/kopia/kopia/repo/manifest"
|
||||||
"github.com/kopia/kopia/snapshot/snapshotfs"
|
"github.com/kopia/kopia/snapshot/snapshotfs"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -429,87 +428,6 @@ func (suite *KopiaUnitSuite) TestRestoreItem() {
|
|||||||
assert.Error(suite.T(), err)
|
assert.Error(suite.T(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *KopiaUnitSuite) TestRestoreDirectory_FailGettingReader() {
|
|
||||||
ctx := context.Background()
|
|
||||||
t := suite.T()
|
|
||||||
|
|
||||||
expectedStreamData := map[string][]byte{
|
|
||||||
path.Join(testInboxDir, testFileName): testFileData,
|
|
||||||
path.Join(testInboxDir, testFileName3): testFileData3,
|
|
||||||
}
|
|
||||||
|
|
||||||
dirs := virtualfs.NewStaticDirectory(testInboxDir, []fs.Entry{
|
|
||||||
&mockkopia.MockFile{
|
|
||||||
Entry: &mockkopia.MockEntry{
|
|
||||||
EntryName: testFileName,
|
|
||||||
EntryMode: mockkopia.DefaultPermissions,
|
|
||||||
},
|
|
||||||
Data: testFileData,
|
|
||||||
},
|
|
||||||
&mockkopia.MockFile{
|
|
||||||
Entry: &mockkopia.MockEntry{
|
|
||||||
EntryName: testFileName2,
|
|
||||||
EntryMode: mockkopia.DefaultPermissions,
|
|
||||||
},
|
|
||||||
OpenErr: assert.AnError,
|
|
||||||
},
|
|
||||||
&mockkopia.MockFile{
|
|
||||||
Entry: &mockkopia.MockEntry{
|
|
||||||
EntryName: testFileName3,
|
|
||||||
EntryMode: mockkopia.DefaultPermissions,
|
|
||||||
},
|
|
||||||
Data: testFileData3,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
collections, err := restoreSubtree(ctx, dirs, nil)
|
|
||||||
assert.Error(t, err)
|
|
||||||
|
|
||||||
assert.Len(t, collections, 1)
|
|
||||||
testForFiles(t, expectedStreamData, collections)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *KopiaUnitSuite) TestRestoreDirectory_FailWrongItemType() {
|
|
||||||
ctx := context.Background()
|
|
||||||
t := suite.T()
|
|
||||||
|
|
||||||
expectedStreamData := map[string][]byte{
|
|
||||||
path.Join(testEmailDir, testInboxDir, testFileName): testFileData,
|
|
||||||
path.Join(testEmailDir, testArchiveDir, testFileName3): testFileData3,
|
|
||||||
}
|
|
||||||
|
|
||||||
dirs := virtualfs.NewStaticDirectory(testEmailDir, []fs.Entry{
|
|
||||||
virtualfs.NewStaticDirectory(testInboxDir, []fs.Entry{
|
|
||||||
&mockkopia.MockFile{
|
|
||||||
Entry: &mockkopia.MockEntry{
|
|
||||||
EntryName: testFileName,
|
|
||||||
EntryMode: mockkopia.DefaultPermissions,
|
|
||||||
},
|
|
||||||
Data: testFileData,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
virtualfs.NewStaticDirectory("foo", []fs.Entry{
|
|
||||||
virtualfs.StreamingFileFromReader(
|
|
||||||
testFileName2, bytes.NewReader(testFileData2)),
|
|
||||||
}),
|
|
||||||
virtualfs.NewStaticDirectory(testArchiveDir, []fs.Entry{
|
|
||||||
&mockkopia.MockFile{
|
|
||||||
Entry: &mockkopia.MockEntry{
|
|
||||||
EntryName: testFileName3,
|
|
||||||
EntryMode: mockkopia.DefaultPermissions,
|
|
||||||
},
|
|
||||||
Data: testFileData3,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
collections, err := restoreSubtree(ctx, dirs, nil)
|
|
||||||
assert.Error(t, err)
|
|
||||||
|
|
||||||
assert.Len(t, collections, 2)
|
|
||||||
testForFiles(t, expectedStreamData, collections)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------
|
// ---------------
|
||||||
// integration tests that use kopia
|
// integration tests that use kopia
|
||||||
// ---------------
|
// ---------------
|
||||||
@ -840,82 +758,6 @@ func (suite *KopiaSimpleRepoIntegrationSuite) TestBackupAndRestoreSingleItem_Err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *KopiaSimpleRepoIntegrationSuite) TestBackupRestoreDirectory() {
|
|
||||||
table := []struct {
|
|
||||||
name string
|
|
||||||
dirPath []string
|
|
||||||
expectedFiles map[string][]byte
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"RecoverUser",
|
|
||||||
[]string{testTenant, testUser},
|
|
||||||
suite.allExpectedFiles,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"RecoverMail",
|
|
||||||
[]string{testTenant, testUser, testEmailDir},
|
|
||||||
suite.allExpectedFiles,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"RecoverInbox",
|
|
||||||
[]string{testTenant, testUser, testEmailDir, testInboxDir},
|
|
||||||
suite.inboxExpectedFiles,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"RecoverArchive",
|
|
||||||
[]string{testTenant, testUser, testEmailDir, testArchiveDir},
|
|
||||||
suite.archiveExpectedFiles,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range table {
|
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
|
||||||
collections, err := suite.w.RestoreDirectory(
|
|
||||||
suite.ctx, string(suite.snapshotID), test.dirPath)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
testForFiles(t, test.expectedFiles, collections)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *KopiaSimpleRepoIntegrationSuite) TestBackupRestoreDirectory_Errors() {
|
|
||||||
table := []struct {
|
|
||||||
name string
|
|
||||||
snapshotID string
|
|
||||||
dirPath []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"EmptyPath",
|
|
||||||
string(suite.snapshotID),
|
|
||||||
[]string{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"BadSnapshotID",
|
|
||||||
"foo",
|
|
||||||
[]string{testTenant, testUser, testEmailDir},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"NotADirectory",
|
|
||||||
string(suite.snapshotID),
|
|
||||||
append(testPath, testFileName),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"NonExistantDirectory",
|
|
||||||
string(suite.snapshotID),
|
|
||||||
[]string{testTenant, testUser, testEmailDir, "subdir"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range table {
|
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
|
||||||
_, err := suite.w.RestoreDirectory(
|
|
||||||
suite.ctx, test.snapshotID, test.dirPath)
|
|
||||||
assert.Error(t, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (suite *KopiaSimpleRepoIntegrationSuite) TestRestoreMultipleItems() {
|
func (suite *KopiaSimpleRepoIntegrationSuite) TestRestoreMultipleItems() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@ -996,11 +838,12 @@ func (suite *KopiaSimpleRepoIntegrationSuite) TestRestoreMultipleItems_Errors()
|
|||||||
func (suite *KopiaIntegrationSuite) TestDeleteSnapshot() {
|
func (suite *KopiaIntegrationSuite) TestDeleteSnapshot() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
|
|
||||||
|
dc1 := mockconnector.NewMockExchangeCollection(
|
||||||
|
[]string{"a-tenant", "user1", "emails"},
|
||||||
|
5,
|
||||||
|
)
|
||||||
collections := []data.Collection{
|
collections := []data.Collection{
|
||||||
mockconnector.NewMockExchangeCollection(
|
dc1,
|
||||||
[]string{"a-tenant", "user1", "emails"},
|
|
||||||
5,
|
|
||||||
),
|
|
||||||
mockconnector.NewMockExchangeCollection(
|
mockconnector.NewMockExchangeCollection(
|
||||||
[]string{"a-tenant", "user2", "emails"},
|
[]string{"a-tenant", "user2", "emails"},
|
||||||
42,
|
42,
|
||||||
@ -1014,8 +857,8 @@ func (suite *KopiaIntegrationSuite) TestDeleteSnapshot() {
|
|||||||
assert.NoError(t, suite.w.DeleteSnapshot(suite.ctx, snapshotID))
|
assert.NoError(t, suite.w.DeleteSnapshot(suite.ctx, snapshotID))
|
||||||
|
|
||||||
// assert the deletion worked
|
// assert the deletion worked
|
||||||
dirPath := []string{testTenant, testUser}
|
itemPath := []string{"a-tenant", "user1", "emails", dc1.Names[0]}
|
||||||
_, err = suite.w.RestoreDirectory(suite.ctx, snapshotID, dirPath)
|
_, err = suite.w.RestoreSingleItem(suite.ctx, snapshotID, itemPath)
|
||||||
assert.Error(t, err, "snapshot should be deleted")
|
assert.Error(t, err, "snapshot should be deleted")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user