Add public kopia function to restore a single item (#225)

Returns a DataCollection to the caller on success. Also update tests to
use new public function where appropriate.
This commit is contained in:
ashmrtn 2022-06-22 13:14:00 -07:00 committed by GitHub
parent 622e8eab95
commit 756b429362
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 98 additions and 33 deletions

View File

@ -8,6 +8,7 @@ import (
"github.com/kopia/kopia/fs/virtualfs"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/blob"
"github.com/kopia/kopia/repo/manifest"
"github.com/kopia/kopia/snapshot"
"github.com/kopia/kopia/snapshot/policy"
"github.com/kopia/kopia/snapshot/snapshotfs"
@ -363,6 +364,32 @@ func (kw KopiaWrapper) makeSnapshotWithRoot(
return &res, nil
}
// RestoreSingleItem looks up the item at the given path in the snapshot with id
// snapshotID. The path should be the full path of the item from the root.
// If the item is a file in kopia then it returns a DataCollection with the item
// as its sole element and DataCollection.FullPath() set to
// split(dirname(itemPath), "/"). If the item does not exist in kopia or is not
// a file an error is returned. The UUID of the returned DataStreams will be the
// name of the kopia file the data is sourced from.
func (kw KopiaWrapper) RestoreSingleItem(
ctx context.Context,
snapshotID string,
itemPath []string,
) (connector.DataCollection, error) {
manifest, err := snapshot.LoadSnapshot(ctx, kw.rep, manifest.ID(snapshotID))
if err != nil {
return nil, errors.Wrap(err, "getting snapshot handle")
}
rootDirEntry, err := snapshotfs.SnapshotRoot(kw.rep, manifest)
if err != nil {
return nil, errors.Wrap(err, "getting root directory")
}
// Fine if rootDirEntry is nil, will be checked in called function.
return kw.restoreSingleItem(ctx, rootDirEntry, itemPath[1:])
}
// restoreSingleItem looks up the item at the given path starting from rootDir
// where rootDir is the root of a snapshot. If the item is a file in kopia then
// it returns a DataCollection with the item as its sole element and

View File

@ -10,9 +10,8 @@ import (
"github.com/kopia/kopia/fs"
"github.com/kopia/kopia/fs/virtualfs"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/manifest"
"github.com/kopia/kopia/snapshot"
"github.com/kopia/kopia/snapshot/snapshotfs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
@ -295,14 +294,12 @@ func (suite *KopiaIntegrationSuite) TestBackupCollections() {
assert.False(suite.T(), stats.Incomplete)
}
// TODO(ashmrtn): Update this once we have a helper for getting the snapshot
// root.
func getSnapshotRoot(
func getSnapshotID(
t *testing.T,
ctx context.Context,
rep repo.Repository,
rootName string,
) fs.Entry {
) manifest.ID {
si := snapshot.SourceInfo{
Host: kTestHost,
UserName: kTestUser,
@ -313,16 +310,10 @@ func getSnapshotRoot(
require.NoError(t, err)
require.Len(t, manifests, 1)
rootDirEntry, err := snapshotfs.SnapshotRoot(rep, manifests[0])
require.NoError(t, err)
rootDir, ok := rootDirEntry.(fs.Directory)
require.True(t, ok)
return rootDir
return manifests[0].ID
}
func setupSimpleRepo(t *testing.T, ctx context.Context, k *KopiaWrapper) {
func setupSimpleRepo(t *testing.T, ctx context.Context, k *KopiaWrapper) manifest.ID {
collections := []connector.DataCollection{
&singleItemCollection{
path: testPath,
@ -340,6 +331,8 @@ func setupSimpleRepo(t *testing.T, ctx context.Context, k *KopiaWrapper) {
require.Equal(t, stats.IgnoredErrorCount, 0)
require.Equal(t, stats.ErrorCount, 0)
require.False(t, stats.Incomplete)
return getSnapshotID(t, ctx, k.rep, testPath[0])
}
func (suite *KopiaIntegrationSuite) TestBackupAndRestoreSingleItem() {
@ -353,11 +346,13 @@ func (suite *KopiaIntegrationSuite) TestBackupAndRestoreSingleItem() {
assert.NoError(t, k.Close(ctx))
}()
setupSimpleRepo(t, ctx, k)
id := setupSimpleRepo(t, ctx, k)
rootDir := getSnapshotRoot(t, ctx, k.rep, testTenant)
c, err := k.restoreSingleItem(ctx, rootDir, append(testPath[1:], testFileUUID))
c, err := k.RestoreSingleItem(
ctx,
string(id),
append(testPath, testFileUUID),
)
require.NoError(t, err)
assert.Equal(t, c.FullPath(), testPath)
@ -373,7 +368,64 @@ func (suite *KopiaIntegrationSuite) TestBackupAndRestoreSingleItem() {
assert.Equal(t, buf, testFileData)
}
// TestBackupAndRestoreSingleItem_Errors exercises the public RestoreSingleItem
// function.
func (suite *KopiaIntegrationSuite) TestBackupAndRestoreSingleItem_Errors() {
table := []struct {
name string
snapshotIDFunc func(manifest.ID) manifest.ID
path []string
}{
{
"NoSnapshot",
func(manifest.ID) manifest.ID {
return manifest.ID("foo")
},
append(testPath, testFileUUID),
},
{
"TargetNotAFile",
func(m manifest.ID) manifest.ID {
return m
},
testPath[:2],
},
{
"NonExistentFile",
func(m manifest.ID) manifest.ID {
return m
},
append(testPath, "foo"),
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
ctx := context.Background()
timeOfTest := ctesting.LogTimeOfTest(t)
k, err := openKopiaRepo(ctx, "backup-restore-single-item-error-"+test.name+"-"+timeOfTest)
require.NoError(t, err)
defer func() {
assert.NoError(t, k.Close(ctx))
}()
id := setupSimpleRepo(t, ctx, k)
_, err = k.RestoreSingleItem(
ctx,
string(test.snapshotIDFunc(id)),
test.path,
)
require.Error(t, err)
})
}
}
// TestBackupAndRestoreSingleItem_Errors2 exercises some edge cases in the
// package-private restoreSingleItem function. It helps ensure kopia behaves the
// way we expect.
func (suite *KopiaIntegrationSuite) TestBackupAndRestoreSingleItem_Errors2() {
table := []struct {
name string
rootDirFunc func(*testing.T, context.Context, *KopiaWrapper) fs.Entry
@ -393,20 +445,6 @@ func (suite *KopiaIntegrationSuite) TestBackupAndRestoreSingleItem_Errors() {
},
append(testPath[1:], testFileUUID),
},
{
"TargetNotAFile",
func(t *testing.T, ctx context.Context, k *KopiaWrapper) fs.Entry {
return getSnapshotRoot(t, ctx, k.rep, testPath[0])
},
[]string{testPath[1]},
},
{
"NonExistentFile",
func(t *testing.T, ctx context.Context, k *KopiaWrapper) fs.Entry {
return getSnapshotRoot(t, ctx, k.rep, testPath[0])
},
append(testPath[1:], "foo"),
},
}
for _, test := range table {
@ -414,7 +452,7 @@ func (suite *KopiaIntegrationSuite) TestBackupAndRestoreSingleItem_Errors() {
ctx := context.Background()
timeOfTest := ctesting.LogTimeOfTest(t)
k, err := openKopiaRepo(ctx, "backup-restore-single-item-error-"+test.name+"-"+timeOfTest)
k, err := openKopiaRepo(ctx, "backup-restore-single-item-error2-"+test.name+"-"+timeOfTest)
require.NoError(t, err)
defer func() {
assert.NoError(t, k.Close(ctx))