feed backup drive names into restore (#3842)

adds a cache on the m365 controller
which, through a new interface func,
writes metadata about the backed up
drive ids and names to a cache.  That cache
gets passed into drive-based restores
for more granular usage. Utilization of the cached info coming in the
next change.
This commit is contained in:
Keepers 2023-07-19 10:52:56 -06:00 committed by GitHub
parent 875eded902
commit 3866bfee3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 175 additions and 20 deletions

View File

@ -40,6 +40,11 @@ type Cacher interface {
ProviderForName(id string) Provider ProviderForName(id string) Provider
} }
type CacheBuilder interface {
Add(id, name string)
Cacher
}
var _ Cacher = &cache{} var _ Cacher = &cache{}
type cache struct { type cache struct {
@ -47,17 +52,29 @@ type cache struct {
nameToID map[string]string nameToID map[string]string
} }
func NewCache(idToName map[string]string) cache { func NewCache(idToName map[string]string) *cache {
nti := make(map[string]string, len(idToName)) c := cache{
idToName: map[string]string{},
for id, name := range idToName { nameToID: map[string]string{},
nti[name] = id
} }
return cache{ if len(idToName) > 0 {
idToName: idToName, nti := make(map[string]string, len(idToName))
nameToID: nti,
for id, name := range idToName {
nti[name] = id
}
c.idToName = idToName
c.nameToID = nti
} }
return &c
}
func (c *cache) Add(id, name string) {
c.idToName[id] = name
c.nameToID[name] = id
} }
// IDOf returns the id associated with the given name. // IDOf returns the id associated with the given name.

View File

@ -14,6 +14,7 @@ import (
"github.com/alcionai/corso/src/internal/m365/support" "github.com/alcionai/corso/src/internal/m365/support"
"github.com/alcionai/corso/src/internal/operations/inject" "github.com/alcionai/corso/src/internal/operations/inject"
"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/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/services/m365/api" "github.com/alcionai/corso/src/pkg/services/m365/api"
@ -47,6 +48,11 @@ type Controller struct {
// mutex used to synchronize updates to `status` // mutex used to synchronize updates to `status`
mu sync.Mutex mu sync.Mutex
status support.ControllerOperationStatus // contains the status of the last run status status support.ControllerOperationStatus // contains the status of the last run status
// backupDriveIDNames is populated on restore. It maps the backup's
// drive names to their id. Primarily for use when creating or looking
// up a new drive.
backupDriveIDNames idname.CacheBuilder
} }
func NewController( func NewController(
@ -142,6 +148,20 @@ func (ctrl *Controller) incrementAwaitingMessages() {
ctrl.wg.Add(1) ctrl.wg.Add(1)
} }
func (ctrl *Controller) CacheItemInfo(dii details.ItemInfo) {
if ctrl.backupDriveIDNames == nil {
ctrl.backupDriveIDNames = idname.NewCache(map[string]string{})
}
if dii.SharePoint != nil {
ctrl.backupDriveIDNames.Add(dii.SharePoint.DriveID, dii.SharePoint.DriveName)
}
if dii.OneDrive != nil {
ctrl.backupDriveIDNames.Add(dii.OneDrive.DriveID, dii.OneDrive.DriveName)
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Resource Lookup Handling // Resource Lookup Handling
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/alcionai/corso/src/internal/common/idname"
inMock "github.com/alcionai/corso/src/internal/common/idname/mock" inMock "github.com/alcionai/corso/src/internal/common/idname/mock"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
exchMock "github.com/alcionai/corso/src/internal/m365/exchange/mock" exchMock "github.com/alcionai/corso/src/internal/m365/exchange/mock"
@ -22,6 +23,7 @@ import (
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/internal/tester/tconfig" "github.com/alcionai/corso/src/internal/tester/tconfig"
"github.com/alcionai/corso/src/internal/version" "github.com/alcionai/corso/src/internal/version"
"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/control/testdata" "github.com/alcionai/corso/src/pkg/control/testdata"
"github.com/alcionai/corso/src/pkg/count" "github.com/alcionai/corso/src/pkg/count"
@ -260,6 +262,82 @@ func (suite *ControllerUnitSuite) TestController_Wait() {
assert.Equal(t, int64(4), result.Bytes) assert.Equal(t, int64(4), result.Bytes)
} }
func (suite *ControllerUnitSuite) TestController_CacheItemInfo() {
var (
odid = "od-id"
odname = "od-name"
spid = "sp-id"
spname = "sp-name"
// intentionally declared outside the test loop
ctrl = &Controller{
wg: &sync.WaitGroup{},
region: &trace.Region{},
backupDriveIDNames: idname.NewCache(nil),
}
)
table := []struct {
name string
service path.ServiceType
cat path.CategoryType
dii details.ItemInfo
expectID string
expectName string
}{
{
name: "exchange",
dii: details.ItemInfo{
Exchange: &details.ExchangeInfo{},
},
expectID: "",
expectName: "",
},
{
name: "folder",
dii: details.ItemInfo{
Folder: &details.FolderInfo{},
},
expectID: "",
expectName: "",
},
{
name: "onedrive",
dii: details.ItemInfo{
OneDrive: &details.OneDriveInfo{
DriveID: odid,
DriveName: odname,
},
},
expectID: odid,
expectName: odname,
},
{
name: "sharepoint",
dii: details.ItemInfo{
SharePoint: &details.SharePointInfo{
DriveID: spid,
DriveName: spname,
},
},
expectID: spid,
expectName: spname,
},
}
for _, test := range table {
suite.Run(test.name, func() {
t := suite.T()
ctrl.CacheItemInfo(test.dii)
name, _ := ctrl.backupDriveIDNames.NameOf(test.expectID)
assert.Equal(t, test.expectName, name)
id, _ := ctrl.backupDriveIDNames.IDOf(test.expectName)
assert.Equal(t, test.expectID, id)
})
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Integration tests // Integration tests
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -69,3 +69,5 @@ func (ctrl Controller) ConsumeRestoreCollections(
) (*details.Details, error) { ) (*details.Details, error) {
return ctrl.Deets, ctrl.Err return ctrl.Deets, ctrl.Err
} }
func (ctrl Controller) CacheItemInfo(dii details.ItemInfo) {}

View File

@ -361,7 +361,7 @@ func (suite *OneDriveIntgSuite) TestCreateGetDeleteFolder() {
Folders: folderElements, Folders: folderElements,
} }
caches := NewRestoreCaches() caches := NewRestoreCaches(nil)
caches.DriveIDToDriveInfo[driveID] = driveInfo{rootFolderID: ptr.Val(rootFolder.GetId())} caches.DriveIDToDriveInfo[driveID] = driveInfo{rootFolderID: ptr.Val(rootFolder.GetId())}
rh := NewRestoreHandler(suite.ac) rh := NewRestoreHandler(suite.ac)

View File

@ -15,6 +15,7 @@ import (
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/alcionai/corso/src/internal/common/idname"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/diagnostics" "github.com/alcionai/corso/src/internal/diagnostics"
@ -44,6 +45,7 @@ type driveInfo struct {
} }
type restoreCaches struct { type restoreCaches struct {
BackupDriveIDName idname.Cacher
collisionKeyToItemID map[string]api.DriveItemIDType collisionKeyToItemID map[string]api.DriveItemIDType
DriveIDToDriveInfo map[string]driveInfo DriveIDToDriveInfo map[string]driveInfo
DriveNameToDriveInfo map[string]driveInfo DriveNameToDriveInfo map[string]driveInfo
@ -110,8 +112,16 @@ type GetDrivePagerAndRootFolderer interface {
NewDrivePagerer NewDrivePagerer
} }
func NewRestoreCaches() *restoreCaches { func NewRestoreCaches(
backupDriveIDNames idname.Cacher,
) *restoreCaches {
// avoid nil panics
if backupDriveIDNames == nil {
backupDriveIDNames = idname.NewCache(nil)
}
return &restoreCaches{ return &restoreCaches{
BackupDriveIDName: backupDriveIDNames,
collisionKeyToItemID: map[string]api.DriveItemIDType{}, collisionKeyToItemID: map[string]api.DriveItemIDType{},
DriveIDToDriveInfo: map[string]driveInfo{}, DriveIDToDriveInfo: map[string]driveInfo{},
DriveNameToDriveInfo: map[string]driveInfo{}, DriveNameToDriveInfo: map[string]driveInfo{},
@ -136,6 +146,7 @@ func ConsumeRestoreCollections(
backupVersion int, backupVersion int,
restoreCfg control.RestoreConfig, restoreCfg control.RestoreConfig,
opts control.Options, opts control.Options,
backupDriveIDNames idname.Cacher,
dcs []data.RestoreCollection, dcs []data.RestoreCollection,
deets *details.Builder, deets *details.Builder,
errs *fault.Bus, errs *fault.Bus,
@ -144,7 +155,7 @@ func ConsumeRestoreCollections(
var ( var (
restoreMetrics support.CollectionMetrics restoreMetrics support.CollectionMetrics
el = errs.Local() el = errs.Local()
caches = NewRestoreCaches() caches = NewRestoreCaches(backupDriveIDNames)
protectedResourceID = dcs[0].FullPath().ResourceOwner() protectedResourceID = dcs[0].FullPath().ResourceOwner()
fallbackDriveName = "" // onedrive cannot create drives fallbackDriveName = "" // onedrive cannot create drives
) )

View File

@ -492,7 +492,7 @@ func (suite *RestoreUnitSuite) TestRestoreItem_collisionHandling() {
mndi.SetId(ptr.To(mndiID)) mndi.SetId(ptr.To(mndiID))
var ( var (
caches = NewRestoreCaches() caches = NewRestoreCaches(nil)
rh = &mock.RestoreHandler{ rh = &mock.RestoreHandler{
PostItemResp: models.NewDriveItem(), PostItemResp: models.NewDriveItem(),
DeleteItemErr: test.deleteErr, DeleteItemErr: test.deleteErr,
@ -671,7 +671,7 @@ func (suite *RestoreUnitSuite) TestRestoreCaches_AddDrive() {
ctx, flush := tester.NewContext(t) ctx, flush := tester.NewContext(t)
defer flush() defer flush()
rc := NewRestoreCaches() rc := NewRestoreCaches(nil)
err := rc.AddDrive(ctx, md, test.mock) err := rc.AddDrive(ctx, md, test.mock)
test.expectErr(t, err, clues.ToCore(err)) test.expectErr(t, err, clues.ToCore(err))
@ -773,7 +773,7 @@ func (suite *RestoreUnitSuite) TestRestoreCaches_Populate() {
pager: test.mock, pager: test.mock,
} }
rc := NewRestoreCaches() rc := NewRestoreCaches(nil)
err := rc.Populate(ctx, gdparf, "shmoo") err := rc.Populate(ctx, gdparf, "shmoo")
test.expectErr(t, err, clues.ToCore(err)) test.expectErr(t, err, clues.ToCore(err))
@ -849,7 +849,7 @@ func (suite *RestoreUnitSuite) TestEnsureDriveExists() {
} }
populatedCache := func(id string) *restoreCaches { populatedCache := func(id string) *restoreCaches {
rc := NewRestoreCaches() rc := NewRestoreCaches(nil)
di := driveInfo{ di := driveInfo{
id: id, id: id,
name: name, name: name,
@ -886,7 +886,7 @@ func (suite *RestoreUnitSuite) TestEnsureDriveExists() {
postErr: []error{nil}, postErr: []error{nil},
grf: grf, grf: grf,
}, },
rc: NewRestoreCaches(), rc: NewRestoreCaches(nil),
expectErr: require.NoError, expectErr: require.NoError,
expectName: name, expectName: name,
}, },
@ -897,7 +897,7 @@ func (suite *RestoreUnitSuite) TestEnsureDriveExists() {
postErr: []error{assert.AnError}, postErr: []error{assert.AnError},
grf: grf, grf: grf,
}, },
rc: NewRestoreCaches(), rc: NewRestoreCaches(nil),
expectErr: require.Error, expectErr: require.Error,
expectName: "", expectName: "",
skipValueChecks: true, skipValueChecks: true,
@ -920,7 +920,7 @@ func (suite *RestoreUnitSuite) TestEnsureDriveExists() {
postErr: []error{graph.ErrItemAlreadyExistsConflict, nil}, postErr: []error{graph.ErrItemAlreadyExistsConflict, nil},
grf: grf, grf: grf,
}, },
rc: NewRestoreCaches(), rc: NewRestoreCaches(nil),
expectErr: require.NoError, expectErr: require.NoError,
expectName: name + " 1", expectName: name + " 1",
}, },

View File

@ -54,6 +54,7 @@ func (ctrl *Controller) ConsumeRestoreCollections(
backupVersion, backupVersion,
restoreCfg, restoreCfg,
opts, opts,
ctrl.backupDriveIDNames,
dcs, dcs,
deets, deets,
errs, errs,
@ -65,6 +66,7 @@ func (ctrl *Controller) ConsumeRestoreCollections(
ctrl.AC, ctrl.AC,
restoreCfg, restoreCfg,
opts, opts,
ctrl.backupDriveIDNames,
dcs, dcs,
deets, deets,
errs, errs,

View File

@ -11,6 +11,7 @@ import (
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/alcionai/corso/src/internal/common/dttm" "github.com/alcionai/corso/src/internal/common/dttm"
"github.com/alcionai/corso/src/internal/common/idname"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/diagnostics" "github.com/alcionai/corso/src/internal/diagnostics"
@ -34,6 +35,7 @@ func ConsumeRestoreCollections(
ac api.Client, ac api.Client,
restoreCfg control.RestoreConfig, restoreCfg control.RestoreConfig,
opts control.Options, opts control.Options,
backupDriveIDNames idname.Cacher,
dcs []data.RestoreCollection, dcs []data.RestoreCollection,
deets *details.Builder, deets *details.Builder,
errs *fault.Bus, errs *fault.Bus,
@ -43,7 +45,7 @@ func ConsumeRestoreCollections(
lrh = libraryRestoreHandler{ac} lrh = libraryRestoreHandler{ac}
protectedResourceID = dcs[0].FullPath().ResourceOwner() protectedResourceID = dcs[0].FullPath().ResourceOwner()
restoreMetrics support.CollectionMetrics restoreMetrics support.CollectionMetrics
caches = onedrive.NewRestoreCaches() caches = onedrive.NewRestoreCaches(backupDriveIDNames)
el = errs.Local() el = errs.Local()
) )

View File

@ -46,6 +46,17 @@ type (
) (*details.Details, error) ) (*details.Details, error)
Wait() *data.CollectionStats Wait() *data.CollectionStats
CacheItemInfoer
}
CacheItemInfoer interface {
// CacheItemInfo is used by the consumer to cache metadata that is
// sourced from per-item info, but may be valuable to the restore at
// large.
// Ex: pairing drive ids with drive names as they appeared at the time
// of backup.
CacheItemInfo(v details.ItemInfo)
} }
RepoMaintenancer interface { RepoMaintenancer interface {

View File

@ -219,7 +219,13 @@ func (op *RestoreOperation) do(
observe.Message(ctx, "Restoring", observe.Bullet, clues.Hide(bup.Selector.DiscreteOwner)) observe.Message(ctx, "Restoring", observe.Bullet, clues.Hide(bup.Selector.DiscreteOwner))
paths, err := formatDetailsForRestoration(ctx, bup.Version, op.Selectors, deets, op.Errors) paths, err := formatDetailsForRestoration(
ctx,
bup.Version,
op.Selectors,
deets,
op.rc,
op.Errors)
if err != nil { if err != nil {
return nil, clues.Wrap(err, "formatting paths from details") return nil, clues.Wrap(err, "formatting paths from details")
} }
@ -359,6 +365,7 @@ func formatDetailsForRestoration(
backupVersion int, backupVersion int,
sel selectors.Selector, sel selectors.Selector,
deets *details.Details, deets *details.Details,
cii inject.CacheItemInfoer,
errs *fault.Bus, errs *fault.Bus,
) ([]path.RestorePaths, error) { ) ([]path.RestorePaths, error) {
fds, err := sel.Reduce(ctx, deets, errs) fds, err := sel.Reduce(ctx, deets, errs)
@ -366,6 +373,11 @@ func formatDetailsForRestoration(
return nil, err return nil, err
} }
// allow restore controllers to iterate over item metadata
for _, ent := range fds.Entries {
cii.CacheItemInfo(ent.ItemInfo)
}
paths, err := pathtransformer.GetPaths(ctx, backupVersion, fds.Items(), errs) paths, err := pathtransformer.GetPaths(ctx, backupVersion, fds.Items(), errs)
if err != nil { if err != nil {
return nil, clues.Wrap(err, "getting restore paths") return nil, clues.Wrap(err, "getting restore paths")