add deets to gc restore ops (#1029)
## Description In order for a restore operation to accurately print out the list of items that were restored, it needs to track item details within the GC restoration like is done within kopia in backup. These details will not be stored in a modelStore. But they will get printed out on the CLI for end users. Part 1 of multiple. ## Type of change - [x] 🌻 Feature ## Issue(s) * #977 ## Test Plan - [x] 💪 Manual - [x] 💚 E2E
This commit is contained in:
parent
dd34ecd5f7
commit
d0560500d2
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -288,6 +289,7 @@ func RestoreExchangeDataCollections(
|
|||||||
gs graph.Service,
|
gs graph.Service,
|
||||||
dest control.RestoreDestination,
|
dest control.RestoreDestination,
|
||||||
dcs []data.Collection,
|
dcs []data.Collection,
|
||||||
|
deets *details.Details,
|
||||||
) (*support.ConnectorOperationStatus, error) {
|
) (*support.ConnectorOperationStatus, error) {
|
||||||
var (
|
var (
|
||||||
pathCounter = map[string]bool{}
|
pathCounter = map[string]bool{}
|
||||||
@ -303,7 +305,7 @@ func RestoreExchangeDataCollections(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, dc := range dcs {
|
for _, dc := range dcs {
|
||||||
temp, root, canceled := restoreCollection(ctx, gs, dc, rootFolder, pathCounter, dest, policy, errUpdater)
|
temp, root, canceled := restoreCollection(ctx, gs, dc, rootFolder, pathCounter, dest, policy, deets, errUpdater)
|
||||||
|
|
||||||
metrics.Combine(temp)
|
metrics.Combine(temp)
|
||||||
|
|
||||||
@ -333,6 +335,7 @@ func restoreCollection(
|
|||||||
pathCounter map[string]bool,
|
pathCounter map[string]bool,
|
||||||
dest control.RestoreDestination,
|
dest control.RestoreDestination,
|
||||||
policy control.CollisionPolicy,
|
policy control.CollisionPolicy,
|
||||||
|
deets *details.Details,
|
||||||
errUpdater func(string, error),
|
errUpdater func(string, error),
|
||||||
) (support.CollectionMetrics, string, bool) {
|
) (support.CollectionMetrics, string, bool) {
|
||||||
defer trace.StartRegion(ctx, "gc:exchange:restoreCollection").End()
|
defer trace.StartRegion(ctx, "gc:exchange:restoreCollection").End()
|
||||||
@ -392,6 +395,18 @@ func restoreCollection(
|
|||||||
|
|
||||||
metrics.TotalBytes += int64(len(byteArray))
|
metrics.TotalBytes += int64(len(byteArray))
|
||||||
metrics.Successes++
|
metrics.Successes++
|
||||||
|
|
||||||
|
itemPath, err := dc.FullPath().Append(itemData.UUID(), true)
|
||||||
|
if err != nil {
|
||||||
|
logger.Ctx(ctx).DPanicw("transforming item to full path", "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
deets.Add(
|
||||||
|
itemPath.String(),
|
||||||
|
itemPath.ShortRef(),
|
||||||
|
"",
|
||||||
|
details.ItemInfo{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
@ -252,19 +253,20 @@ func (gc *GraphConnector) RestoreDataCollections(
|
|||||||
selector selectors.Selector,
|
selector selectors.Selector,
|
||||||
dest control.RestoreDestination,
|
dest control.RestoreDestination,
|
||||||
dcs []data.Collection,
|
dcs []data.Collection,
|
||||||
) error {
|
) (*details.Details, error) {
|
||||||
gc.region = trace.StartRegion(ctx, "connector:restore")
|
gc.region = trace.StartRegion(ctx, "connector:restore")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
status *support.ConnectorOperationStatus
|
status *support.ConnectorOperationStatus
|
||||||
err error
|
err error
|
||||||
|
deets = &details.Details{}
|
||||||
)
|
)
|
||||||
|
|
||||||
switch selector.Service {
|
switch selector.Service {
|
||||||
case selectors.ServiceExchange:
|
case selectors.ServiceExchange:
|
||||||
status, err = exchange.RestoreExchangeDataCollections(ctx, gc.graphService, dest, dcs)
|
status, err = exchange.RestoreExchangeDataCollections(ctx, gc.graphService, dest, dcs, deets)
|
||||||
case selectors.ServiceOneDrive:
|
case selectors.ServiceOneDrive:
|
||||||
status, err = onedrive.RestoreCollections(ctx, gc, dest, dcs)
|
status, err = onedrive.RestoreCollections(ctx, gc, dest, dcs, deets)
|
||||||
default:
|
default:
|
||||||
err = errors.Errorf("restore data from service %s not supported", selector.Service.String())
|
err = errors.Errorf("restore data from service %s not supported", selector.Service.String())
|
||||||
}
|
}
|
||||||
@ -272,7 +274,7 @@ func (gc *GraphConnector) RestoreDataCollections(
|
|||||||
gc.incrementAwaitingMessages()
|
gc.incrementAwaitingMessages()
|
||||||
gc.UpdateStatus(status)
|
gc.UpdateStatus(status)
|
||||||
|
|
||||||
return err
|
return deets, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// createCollection - utility function that retrieves M365
|
// createCollection - utility function that retrieves M365
|
||||||
|
|||||||
@ -190,7 +190,9 @@ func (suite *DisconnectedGraphConnectorSuite) TestRestoreFailsBadService() {
|
|||||||
}
|
}
|
||||||
dest := control.DefaultRestoreDestination(common.SimpleDateTimeFormatOneDrive)
|
dest := control.DefaultRestoreDestination(common.SimpleDateTimeFormatOneDrive)
|
||||||
|
|
||||||
assert.Error(t, gc.RestoreDataCollections(ctx, sel, dest, nil))
|
deets, err := gc.RestoreDataCollections(ctx, sel, dest, nil)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.NotNil(t, deets)
|
||||||
|
|
||||||
status := gc.AwaitStatus()
|
status := gc.AwaitStatus()
|
||||||
assert.Equal(t, 0, status.ObjectCount)
|
assert.Equal(t, 0, status.ObjectCount)
|
||||||
|
|||||||
@ -408,8 +408,9 @@ func (suite *GraphConnectorIntegrationSuite) TestEmptyCollections() {
|
|||||||
suite.T().Run(test.name, func(t *testing.T) {
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
err := suite.connector.RestoreDataCollections(ctx, test.sel, dest, test.col)
|
deets, err := suite.connector.RestoreDataCollections(ctx, test.sel, dest, test.col)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, deets)
|
||||||
|
|
||||||
stats := suite.connector.AwaitStatus()
|
stats := suite.connector.AwaitStatus()
|
||||||
assert.Zero(t, stats.ObjectCount)
|
assert.Zero(t, stats.ObjectCount)
|
||||||
@ -687,13 +688,17 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreAndBackup() {
|
|||||||
|
|
||||||
restoreGC := loadConnector(ctx, t)
|
restoreGC := loadConnector(ctx, t)
|
||||||
restoreSel := getSelectorWith(test.service)
|
restoreSel := getSelectorWith(test.service)
|
||||||
err := restoreGC.RestoreDataCollections(ctx, restoreSel, dest, collections)
|
deets, err := restoreGC.RestoreDataCollections(ctx, restoreSel, dest, collections)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
assert.NotNil(t, deets)
|
||||||
|
|
||||||
status := restoreGC.AwaitStatus()
|
status := restoreGC.AwaitStatus()
|
||||||
assert.Equal(t, test.expectedRestoreFolders, status.FolderCount, "status.FolderCount")
|
assert.Equal(t, test.expectedRestoreFolders, status.FolderCount, "status.FolderCount")
|
||||||
assert.Equal(t, totalItems, status.ObjectCount, "status.ObjectCount")
|
assert.Equal(t, totalItems, status.ObjectCount, "status.ObjectCount")
|
||||||
assert.Equal(t, totalItems, status.Successful, "status.Successful")
|
assert.Equal(t, totalItems, status.Successful, "status.Successful")
|
||||||
|
assert.Equal(
|
||||||
|
t, totalItems, len(deets.Entries),
|
||||||
|
"details entries contains same item count as total successful items restored")
|
||||||
|
|
||||||
t.Logf("Restore complete\n")
|
t.Logf("Restore complete\n")
|
||||||
|
|
||||||
@ -822,14 +827,18 @@ func (suite *GraphConnectorIntegrationSuite) TestMultiFolderBackupDifferentNames
|
|||||||
)
|
)
|
||||||
|
|
||||||
restoreGC := loadConnector(ctx, t)
|
restoreGC := loadConnector(ctx, t)
|
||||||
err := restoreGC.RestoreDataCollections(ctx, restoreSel, dest, collections)
|
deets, err := restoreGC.RestoreDataCollections(ctx, restoreSel, dest, collections)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, deets)
|
||||||
|
|
||||||
status := restoreGC.AwaitStatus()
|
status := restoreGC.AwaitStatus()
|
||||||
// Always just 1 because it's just 1 collection.
|
// Always just 1 because it's just 1 collection.
|
||||||
assert.Equal(t, 1, status.FolderCount, "status.FolderCount")
|
assert.Equal(t, 1, status.FolderCount, "status.FolderCount")
|
||||||
assert.Equal(t, totalItems, status.ObjectCount, "status.ObjectCount")
|
assert.Equal(t, totalItems, status.ObjectCount, "status.ObjectCount")
|
||||||
assert.Equal(t, totalItems, status.Successful, "status.Successful")
|
assert.Equal(t, totalItems, status.Successful, "status.Successful")
|
||||||
|
assert.Equal(
|
||||||
|
t, totalItems, len(deets.Entries),
|
||||||
|
"details entries contains same item count as total successful items restored")
|
||||||
|
|
||||||
t.Logf("Restore complete\n")
|
t.Logf("Restore complete\n")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/connector/graph"
|
"github.com/alcionai/corso/src/internal/connector/graph"
|
||||||
"github.com/alcionai/corso/src/internal/connector/support"
|
"github.com/alcionai/corso/src/internal/connector/support"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -50,6 +51,7 @@ func RestoreCollections(
|
|||||||
service graph.Service,
|
service graph.Service,
|
||||||
dest control.RestoreDestination,
|
dest control.RestoreDestination,
|
||||||
dcs []data.Collection,
|
dcs []data.Collection,
|
||||||
|
deets *details.Details,
|
||||||
) (*support.ConnectorOperationStatus, error) {
|
) (*support.ConnectorOperationStatus, error) {
|
||||||
var (
|
var (
|
||||||
restoreMetrics support.CollectionMetrics
|
restoreMetrics support.CollectionMetrics
|
||||||
@ -62,7 +64,7 @@ func RestoreCollections(
|
|||||||
|
|
||||||
// Iterate through the data collections and restore the contents of each
|
// Iterate through the data collections and restore the contents of each
|
||||||
for _, dc := range dcs {
|
for _, dc := range dcs {
|
||||||
temp, canceled := restoreCollection(ctx, service, dc, dest.ContainerName, errUpdater)
|
temp, canceled := restoreCollection(ctx, service, dc, dest.ContainerName, deets, errUpdater)
|
||||||
|
|
||||||
restoreMetrics.Combine(temp)
|
restoreMetrics.Combine(temp)
|
||||||
|
|
||||||
@ -89,6 +91,7 @@ func restoreCollection(
|
|||||||
service graph.Service,
|
service graph.Service,
|
||||||
dc data.Collection,
|
dc data.Collection,
|
||||||
restoreContainerName string,
|
restoreContainerName string,
|
||||||
|
deets *details.Details,
|
||||||
errUpdater func(string, error),
|
errUpdater func(string, error),
|
||||||
) (support.CollectionMetrics, bool) {
|
) (support.CollectionMetrics, bool) {
|
||||||
defer trace.StartRegion(ctx, "gc:oneDrive:restoreCollection").End()
|
defer trace.StartRegion(ctx, "gc:oneDrive:restoreCollection").End()
|
||||||
@ -139,12 +142,31 @@ func restoreCollection(
|
|||||||
|
|
||||||
metrics.TotalBytes += int64(len(copyBuffer))
|
metrics.TotalBytes += int64(len(copyBuffer))
|
||||||
|
|
||||||
err := restoreItem(ctx, service, itemData, drivePath.driveID, restoreFolderID, copyBuffer)
|
err := restoreItem(ctx,
|
||||||
|
service,
|
||||||
|
itemData,
|
||||||
|
drivePath.driveID,
|
||||||
|
restoreFolderID,
|
||||||
|
copyBuffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errUpdater(itemData.UUID(), err)
|
errUpdater(itemData.UUID(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
itemPath, err := dc.FullPath().Append(itemData.UUID(), true)
|
||||||
|
if err != nil {
|
||||||
|
logger.Ctx(ctx).DPanicw("transforming item to full path", "error", err)
|
||||||
|
errUpdater(itemData.UUID(), err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
deets.Add(
|
||||||
|
itemPath.String(),
|
||||||
|
itemPath.ShortRef(),
|
||||||
|
"",
|
||||||
|
details.ItemInfo{})
|
||||||
|
|
||||||
metrics.Successes++
|
metrics.Successes++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,7 +218,11 @@ func createRestoreFolders(ctx context.Context, service graph.Service, driveID st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// restoreItem will create a new item in the specified `parentFolderID` and upload the data.Stream
|
// restoreItem will create a new item in the specified `parentFolderID` and upload the data.Stream
|
||||||
func restoreItem(ctx context.Context, service graph.Service, itemData data.Stream, driveID, parentFolderID string,
|
func restoreItem(
|
||||||
|
ctx context.Context,
|
||||||
|
service graph.Service,
|
||||||
|
itemData data.Stream,
|
||||||
|
driveID, parentFolderID string,
|
||||||
copyBuffer []byte,
|
copyBuffer []byte,
|
||||||
) error {
|
) error {
|
||||||
defer trace.StartRegion(ctx, "gc:oneDrive:restoreItem").End()
|
defer trace.StartRegion(ctx, "gc:oneDrive:restoreItem").End()
|
||||||
|
|||||||
@ -209,7 +209,8 @@ func (op *RestoreOperation) Run(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = gc.RestoreDataCollections(ctx, op.Selectors, op.Destination, dcs)
|
// TODO: return details and print in CLI
|
||||||
|
_, err = gc.RestoreDataCollections(ctx, op.Selectors, op.Destination, dcs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "restoring service data")
|
err = errors.Wrap(err, "restoring service data")
|
||||||
opStats.writeErr = err
|
opStats.writeErr = err
|
||||||
@ -225,7 +226,7 @@ func (op *RestoreOperation) Run(ctx context.Context) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writes the restoreOperation outcome to the modelStore.
|
// persists details and statistics about the restore operation.
|
||||||
func (op *RestoreOperation) persistResults(
|
func (op *RestoreOperation) persistResults(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
started time.Time,
|
started time.Time,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user