OneDrive Backup Operation (#738)

## Description

Wires up the OneDrive collection logic to `operation.Backup`

Includes an integration test that runs against the test domain

Two bug fixes:
- Skip the "root" item that is returned by the delta query
- Fix incorrect usage of the `filepath.SplitList` function which does
  not split a path into components. Instead use `strings.Split`. This
  is ok because the paths returned here are not OS specific.
  Regardless - this logic will be refactored when we use the `path` pkg.

## Type of change

Please check the type of change your PR introduces:
- [x] 🌻 Feature
- [x] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Test
- [ ] 🐹 Trivial/Minor

## Issue(s)
#548 

## Test Plan

<!-- How will this be tested prior to merging.-->

- [ ] 💪 Manual
- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Vaibhav Kamra 2022-09-07 16:47:12 -07:00 committed by GitHub
parent 6cdc691c6f
commit 1246f51b22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 114 additions and 7 deletions

View File

@ -16,11 +16,13 @@ import (
"github.com/alcionai/corso/src/internal/connector/exchange"
"github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/onedrive"
"github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/path"
"github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/control"
"github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/selectors"
)
@ -418,3 +420,52 @@ func IsNonRecoverableError(e error) bool {
var nonRecoverable support.NonRecoverableGCError
return errors.As(e, &nonRecoverable)
}
func (gc *GraphConnector) DataCollections(ctx context.Context, sels selectors.Selector) ([]data.Collection, error) {
switch sels.Service {
case selectors.ServiceExchange:
return gc.ExchangeDataCollection(ctx, sels)
case selectors.ServiceOneDrive:
return gc.OneDriveDataCollections(ctx, sels)
default:
return nil, errors.Errorf("Service %s not supported", sels)
}
}
// OneDriveDataCollections returns a set of DataCollection which represents the OneDrive data
// for the specified user
func (gc *GraphConnector) OneDriveDataCollections(
ctx context.Context,
selector selectors.Selector,
) ([]data.Collection, error) {
odb, err := selector.ToOneDriveBackup()
if err != nil {
return nil, errors.Wrap(err, "collecting onedrive data")
}
collections := []data.Collection{}
scopes := odb.DiscreteScopes(gc.GetUsers())
var errs error
// for each scope that includes oneDrive items, get all
for _, scope := range scopes {
for _, user := range scope.Get(selectors.OneDriveUser) {
logger.Ctx(ctx).With("user", user).Debug("Creating OneDrive collections")
odcs, err := onedrive.NewCollections(user, &gc.graphService, gc.UpdateStatus).Get(ctx)
if err != nil {
return nil, support.WrapAndAppend(user, err, errs)
}
collections = append(collections, odcs...)
}
}
for range collections {
gc.incrementAwaitingMessages()
}
return collections, errs
}

View File

@ -4,7 +4,7 @@ package onedrive
import (
"context"
"io"
"path/filepath"
"strings"
"github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/connector/support"
@ -77,7 +77,10 @@ func (oc *Collection) Items() <-chan data.Stream {
}
func (oc *Collection) FullPath() []string {
return filepath.SplitList(oc.folderPath)
path := oc.folderPath
// Remove leading `/` if any so that Split
// doesn't return a ""
return strings.Split(strings.TrimPrefix(path, "/"), "/")
}
// Item represents a single item retrieved from OneDrive

View File

@ -5,7 +5,6 @@ import (
"context"
"errors"
"io"
"path/filepath"
"sync"
"testing"
@ -60,7 +59,7 @@ func (suite *OneDriveCollectionSuite) TestOneDriveCollection() {
folderPath := "dir1/dir2/dir3"
coll := NewCollection(folderPath, "fakeDriveID", suite, suite.testStatusUpdater(&wg, &collStatus))
require.NotNil(suite.T(), coll)
assert.Equal(suite.T(), filepath.SplitList(folderPath), coll.FullPath())
assert.Equal(suite.T(), []string{"dir1", "dir2", "dir3"}, coll.FullPath())
testItemID := "fakeItemID"
testItemName := "itemName"

View File

@ -59,8 +59,8 @@ func (c *Collections) Get(ctx context.Context) ([]data.Collection, error) {
}
collections := make([]data.Collection, 0, len(c.collectionMap))
for _, c := range c.collectionMap {
collections = append(collections, c)
for _, coll := range c.collectionMap {
collections = append(collections, coll)
}
return collections, nil
@ -74,6 +74,10 @@ func (c *Collections) updateCollections(ctx context.Context, driveID string, ite
if err != nil {
return err
}
if item.GetRoot() != nil {
// Skip the root item
continue
}
if item.GetParentReference() == nil || item.GetParentReference().GetPath() == nil {
return errors.Errorf("item does not have a parent reference. item name : %s", *item.GetName())
}

View File

@ -106,7 +106,7 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
return err
}
cs, err := gc.ExchangeDataCollection(ctx, op.Selectors)
cs, err := gc.DataCollections(ctx, op.Selectors)
if err != nil {
err = errors.Wrap(err, "retrieving service data")
opStats.readErr = err

View File

@ -207,3 +207,53 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run() {
})
}
}
func (suite *BackupOpIntegrationSuite) TestBackupOneDrive_Run() {
t := suite.T()
ctx := context.Background()
m365UserID := tester.M365UserID(t)
acct := tester.NewM365Account(t)
// need to initialize the repository before we can test connecting to it.
st := tester.NewPrefixedS3Storage(t)
k := kopia.NewConn(st)
require.NoError(t, k.Initialize(ctx))
// kopiaRef comes with a count of 1 and Wrapper bumps it again so safe
// to close here.
defer k.Close(ctx)
kw, err := kopia.NewWrapper(k)
require.NoError(t, err)
defer kw.Close(ctx)
ms, err := kopia.NewModelStore(k)
require.NoError(t, err)
defer ms.Close(ctx)
sw := store.NewKopiaStore(ms)
sel := selectors.NewOneDriveBackup()
sel.Include(sel.Users([]string{m365UserID}))
bo, err := NewBackupOperation(
ctx,
control.Options{},
kw,
sw,
acct,
sel.Selector)
require.NoError(t, err)
require.NoError(t, bo.Run(ctx))
require.NotEmpty(t, bo.Results)
require.NotEmpty(t, bo.Results.BackupID)
assert.Equal(t, bo.Status, Completed)
assert.Equal(t, bo.Results.ItemsRead, bo.Results.ItemsWritten)
assert.NoError(t, bo.Results.ReadErrors)
assert.NoError(t, bo.Results.WriteErrors)
}