From f84d8e34d6fbce3331c287f0af76611ed10d5cc5 Mon Sep 17 00:00:00 2001 From: ashmrtn <3891298+ashmrtn@users.noreply.github.com> Date: Mon, 6 Jun 2022 15:59:21 -0700 Subject: [PATCH] Backupwriter skeleton code (#141) Add skeleton and test for backing up data with kopia Full code for launching kopia snapshots is present but code for building the directory structure to hand kopia is not. Therefore, no data will be backed up right now. --- src/internal/kopia/kopia.go | 82 ++++++++++++++++++++- src/internal/kopia/kopia_test.go | 26 ++++++- src/internal/testing/integration_runners.go | 1 + 3 files changed, 104 insertions(+), 5 deletions(-) diff --git a/src/internal/kopia/kopia.go b/src/internal/kopia/kopia.go index e162808ac..9e76ddd22 100644 --- a/src/internal/kopia/kopia.go +++ b/src/internal/kopia/kopia.go @@ -3,21 +3,32 @@ package kopia import ( "context" + "github.com/kopia/kopia/fs" + "github.com/kopia/kopia/fs/virtualfs" "github.com/kopia/kopia/repo" "github.com/kopia/kopia/repo/blob" "github.com/kopia/kopia/snapshot" + "github.com/kopia/kopia/snapshot/policy" + "github.com/kopia/kopia/snapshot/snapshotfs" "github.com/pkg/errors" + "github.com/alcionai/corso/internal/connector" "github.com/alcionai/corso/pkg/storage" ) const ( defaultKopiaConfigFilePath = "/tmp/repository.config" + + // TODO(ashmrtnz): These should be some values from upper layer corso, + // possibly corresponding to who is making the backup. + kTestHost = "a-test-machine" + kTestUser = "testUser" ) var ( - errInit = errors.New("initializing repo") - errConnect = errors.New("connecting repo") + errInit = errors.New("initializing repo") + errConnect = errors.New("connecting repo") + errNotConnected = errors.New("not connected to repo") ) type BackupStats struct { @@ -35,7 +46,7 @@ func manifestToStats(man *snapshot.Manifest) BackupStats { TotalDirectoryCount: int(man.Stats.TotalDirectoryCount), IgnoredErrorCount: int(man.Stats.IgnoredErrorCount), ErrorCount: int(man.Stats.ErrorCount), - Incomplete: man.IncompleteReason == "", + Incomplete: man.IncompleteReason != "", IncompleteReason: man.IncompleteReason, } } @@ -148,3 +159,68 @@ func (kw *KopiaWrapper) open(ctx context.Context, password string) error { kw.rep = rep return nil } + +func inflateDirTree(ctx context.Context, collections []connector.DataCollection) (fs.Directory, error) { + // TODO(ashmrtnz): Implement when virtualfs.StreamingDirectory is available. + return virtualfs.NewStaticDirectory("sample-dir", []fs.Entry{}), nil +} + +func (kw KopiaWrapper) BackupCollections( + ctx context.Context, + collections []connector.DataCollection, +) (*BackupStats, error) { + if kw.rep == nil { + return nil, errNotConnected + } + + dirTree, err := inflateDirTree(ctx, collections) + if err != nil { + return nil, errors.Wrap(err, "building kopia directories") + } + + stats, err := kw.makeSnapshotWithRoot(ctx, dirTree) + if err != nil { + return nil, err + } + + return stats, nil +} + +func (kw KopiaWrapper) makeSnapshotWithRoot( + ctx context.Context, + root fs.Directory, +) (*BackupStats, error) { + si := snapshot.SourceInfo{ + Host: kTestHost, + UserName: kTestUser, + // TODO(ashmrtnz): will this be something useful for snapshot lookups later? + Path: root.Name(), + } + ctx, rw, err := kw.rep.NewWriter(ctx, repo.WriteSessionOptions{}) + if err != nil { + return nil, errors.Wrap(err, "get repo writer") + } + + policyTree, err := policy.TreeForSource(ctx, kw.rep, si) + if err != nil { + return nil, errors.Wrap(err, "get policy tree") + } + + u := snapshotfs.NewUploader(rw) + + man, err := u.Upload(ctx, root, policyTree, si) + if err != nil { + return nil, errors.Wrap(err, "uploading data") + } + + if _, err := snapshot.SaveSnapshot(ctx, rw, man); err != nil { + return nil, errors.Wrap(err, "saving snapshot") + } + + if err := rw.Flush(ctx); err != nil { + return nil, errors.Wrap(err, "flushing writer") + } + + res := manifestToStats(man) + return &res, nil +} diff --git a/src/internal/kopia/kopia_test.go b/src/internal/kopia/kopia_test.go index 2f05ec9e1..6d54b7809 100644 --- a/src/internal/kopia/kopia_test.go +++ b/src/internal/kopia/kopia_test.go @@ -50,8 +50,11 @@ type KopiaIntegrationSuite struct { } func TestKopiaIntegrationSuite(t *testing.T) { - if err := ctesting.RunOnAny(ctesting.CORSO_CI_TESTS); err != nil { - t.Skip(err) + if err := ctesting.RunOnAny( + ctesting.CORSO_CI_TESTS, + ctesting.CORSO_KOPIA_WRAPPER_TESTS, + ); err != nil { + t.Skip() } suite.Run(t, new(KopiaIntegrationSuite)) @@ -71,3 +74,22 @@ func (suite *KopiaIntegrationSuite) TestCloseTwiceDoesNotCrash() { assert.Nil(suite.T(), k.rep) assert.NoError(suite.T(), k.Close(ctx)) } + +func (suite *KopiaIntegrationSuite) TestBackupCollections() { + ctx := context.Background() + timeOfTest := ctesting.LogTimeOfTest(suite.T()) + + k, err := openKopiaRepo(ctx, "init-s3-"+timeOfTest) + assert.NoError(suite.T(), err) + defer func() { + assert.NoError(suite.T(), k.Close(ctx)) + }() + + stats, err := k.BackupCollections(ctx, nil) + assert.NoError(suite.T(), err) + assert.Equal(suite.T(), stats.TotalFileCount, 0) + assert.Equal(suite.T(), stats.TotalDirectoryCount, 1) + assert.Equal(suite.T(), stats.IgnoredErrorCount, 0) + assert.Equal(suite.T(), stats.ErrorCount, 0) + assert.False(suite.T(), stats.Incomplete) +} diff --git a/src/internal/testing/integration_runners.go b/src/internal/testing/integration_runners.go index 0b7d8e31b..099e7808d 100644 --- a/src/internal/testing/integration_runners.go +++ b/src/internal/testing/integration_runners.go @@ -13,6 +13,7 @@ const ( CORSO_CI_TESTS = "CORSO_CI_TESTS" CORSO_GRAPH_CONNECTOR_TESTS = "CORSO_GRAPH_CONNECTOR_TESTS" CORSO_REPOSITORY_TESTS = "CORSO_REPOSITORY_TESTS" + CORSO_KOPIA_WRAPPER_TESTS = "CORSO_KOPIA_WRAPPER_TESTS" ) // RunOnAny takes in a list of env variable names and returns