utilize old drive name when restoring

utilizes the old drive name in case the drive
was deleted between backup and restore.
Priority is first to use drives whose ids match the
backup id; second, to use drives whose names
match the backup drive name; third, to fall
back to a new, arbitrary name.
This commit is contained in:
ryanfkeepers 2023-07-18 14:40:21 -06:00
parent 3866bfee3b
commit 8db03d4cd7
2 changed files with 151 additions and 43 deletions

View File

@ -258,7 +258,7 @@ func RestoreCollection(
return metrics, clues.Wrap(err, "creating drive path").WithClues(ctx) return metrics, clues.Wrap(err, "creating drive path").WithClues(ctx)
} }
err = ensureDriveExists( di, err := ensureDriveExists(
ctx, ctx,
rh, rh,
caches, caches,
@ -269,6 +269,12 @@ func RestoreCollection(
return metrics, clues.Wrap(err, "ensuring drive exists") return metrics, clues.Wrap(err, "ensuring drive exists")
} }
// clobber the drivePath details with the details retrieved
// in the ensure func, as they might have changed to reflect
// a different drive as a restore location.
drivePath.DriveID = di.id
drivePath.Root = di.rootFolderID
// Assemble folder hierarchy we're going to restore into (we recreate the folder hierarchy // Assemble folder hierarchy we're going to restore into (we recreate the folder hierarchy
// from the backup under this the restore folder instead of root) // from the backup under this the restore folder instead of root)
// i.e. Restore into `<restoreContainerName>/<original folder path>` // i.e. Restore into `<restoreContainerName>/<original folder path>`
@ -1215,51 +1221,63 @@ func ensureDriveExists(
pdagrf PostDriveAndGetRootFolderer, pdagrf PostDriveAndGetRootFolderer,
caches *restoreCaches, caches *restoreCaches,
drivePath *path.DrivePath, drivePath *path.DrivePath,
protectedResourceID, driveName string, protectedResourceID, fallbackDriveName string,
) error { ) (driveInfo, error) {
driveID := drivePath.DriveID driveID := drivePath.DriveID
// the drive might already be cached // the drive might already be cached by ID. it's okay
if _, ok := caches.DriveIDToDriveInfo[driveID]; ok { // if the name has changed. the ID is a better reference
return nil // anyway.
if di, ok := caches.DriveIDToDriveInfo[driveID]; ok {
return di, nil
} }
var ( var (
newDriveName = driveName newDriveName = fallbackDriveName
newDrive models.Driveable newDrive models.Driveable
err error err error
) )
if _, ok := caches.DriveNameToDriveInfo[newDriveName]; ok { // if the drive wasn't found by ID, maybe we can find a
newDriveName = fmt.Sprintf("%s %d", driveName, 1) // drive with the same name but different ID.
// start by looking up the old drive's name
oldName, ok := caches.BackupDriveIDName.NameOf(driveID)
if ok {
// check for drives that currently have the same name
if di, ok := caches.DriveNameToDriveInfo[oldName]; ok {
return di, nil
}
// if no current drives have the same name, we'll make
// a new drive with that name.
newDriveName = oldName
} }
// if not, double check that the name won't collide by looking nextDriveName := newDriveName
// up drives by name until we can make some name like `foo N` that
// doesn't collide with `foo` or other values of N in `foo N`.
// Ex: foo -> foo 1 -> foo 2 -> ... -> foo N
//
// For sharepoint, document libraries can collide by name with // For sharepoint, document libraries can collide by name with
// item types beyond just drive. Lists, for example, cannot share // item types beyond just drive. Lists, for example, cannot share
// names with document libraries. In those cases it's not enough // names with document libraries (they're the same type, actually).
// to compare the names of drives; we also need to continue this // In those cases we need to rename the drive until we can create
// loop until we can create a drive without error. // one without a collision.
for i := 2; ; i++ { for i := 1; ; i++ {
ictx := clues.Add(ctx, "new_drive_name", clues.Hide(newDriveName)) ictx := clues.Add(ctx, "new_drive_name", clues.Hide(nextDriveName))
newDrive, err = pdagrf.PostDrive(ictx, protectedResourceID, newDriveName) newDrive, err = pdagrf.PostDrive(ictx, protectedResourceID, nextDriveName)
if err != nil && !errors.Is(err, graph.ErrItemAlreadyExistsConflict) { if err != nil && !errors.Is(err, graph.ErrItemAlreadyExistsConflict) {
return clues.Wrap(err, "creating new drive") return driveInfo{}, clues.Wrap(err, "creating new drive")
} }
if err == nil { if err == nil {
break break
} }
newDriveName = fmt.Sprintf("%s %d", driveName, i) nextDriveName = fmt.Sprintf("%s %d", newDriveName, i)
} }
err = caches.AddDrive(ctx, newDrive, pdagrf) if err := caches.AddDrive(ctx, newDrive, pdagrf); err != nil {
return driveInfo{}, clues.Wrap(err, "adding drive to cache").OrNil()
}
return clues.Wrap(err, "adding drive to cache").OrNil() return caches.DriveIDToDriveInfo[ptr.Val(newDrive.GetId())], nil
} }

View File

@ -11,6 +11,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"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/m365/graph" "github.com/alcionai/corso/src/internal/m365/graph"
odConsts "github.com/alcionai/corso/src/internal/m365/onedrive/consts" odConsts "github.com/alcionai/corso/src/internal/m365/onedrive/consts"
@ -827,7 +828,9 @@ func (m *mockPDAGRF) GetRootFolder(
func (suite *RestoreUnitSuite) TestEnsureDriveExists() { func (suite *RestoreUnitSuite) TestEnsureDriveExists() {
rfID := "this-is-id" rfID := "this-is-id"
driveID := "another-id" driveID := "another-id"
oldID := "old-id"
name := "name" name := "name"
otherName := "other name"
rf := models.NewDriveItem() rf := models.NewDriveItem()
rf.SetId(&rfID) rf.SetId(&rfID)
@ -848,6 +851,12 @@ func (suite *RestoreUnitSuite) TestEnsureDriveExists() {
Folders: path.Elements{}, Folders: path.Elements{},
} }
oldDP := &path.DrivePath{
DriveID: oldID,
Root: "root:",
Folders: path.Elements{},
}
populatedCache := func(id string) *restoreCaches { populatedCache := func(id string) *restoreCaches {
rc := NewRestoreCaches(nil) rc := NewRestoreCaches(nil)
di := driveInfo{ di := driveInfo{
@ -860,38 +869,91 @@ func (suite *RestoreUnitSuite) TestEnsureDriveExists() {
return rc return rc
} }
oldDriveIDNames := idname.NewCache(nil)
oldDriveIDNames.Add(oldID, name)
idSwitchedCache := func() *restoreCaches {
rc := NewRestoreCaches(oldDriveIDNames)
di := driveInfo{
id: "diff",
name: name,
}
rc.DriveIDToDriveInfo["diff"] = di
rc.DriveNameToDriveInfo[name] = di
return rc
}
table := []struct { table := []struct {
name string name string
dp *path.DrivePath
mock *mockPDAGRF mock *mockPDAGRF
rc *restoreCaches rc *restoreCaches
expectErr require.ErrorAssertionFunc expectErr require.ErrorAssertionFunc
fallbackName string
expectName string expectName string
expectID string
skipValueChecks bool skipValueChecks bool
}{ }{
{ {
name: "drive already in cache", name: "drive already in cache",
dp: dp,
mock: &mockPDAGRF{ mock: &mockPDAGRF{
postResp: []models.Driveable{makeMD()}, postResp: []models.Driveable{makeMD()},
postErr: []error{nil}, postErr: []error{nil},
grf: grf, grf: grf,
}, },
rc: populatedCache(driveID), rc: populatedCache(driveID),
expectErr: require.NoError, expectErr: require.NoError,
expectName: name, fallbackName: name,
expectName: name,
expectID: driveID,
}, },
{ {
name: "drive created", name: "drive with same name but different id exists",
dp: oldDP,
mock: &mockPDAGRF{ mock: &mockPDAGRF{
postResp: []models.Driveable{makeMD()}, postResp: []models.Driveable{makeMD()},
postErr: []error{nil}, postErr: []error{nil},
grf: grf, grf: grf,
}, },
rc: NewRestoreCaches(nil), rc: idSwitchedCache(),
expectErr: require.NoError, expectErr: require.NoError,
expectName: name, fallbackName: otherName,
expectName: name,
expectID: "diff",
},
{
name: "drive created with old name",
dp: oldDP,
mock: &mockPDAGRF{
postResp: []models.Driveable{makeMD()},
postErr: []error{nil},
grf: grf,
},
rc: NewRestoreCaches(oldDriveIDNames),
expectErr: require.NoError,
fallbackName: otherName,
expectName: name,
expectID: driveID,
},
{
name: "drive created with fallback name",
dp: dp,
mock: &mockPDAGRF{
postResp: []models.Driveable{makeMD()},
postErr: []error{nil},
grf: grf,
},
rc: NewRestoreCaches(nil),
expectErr: require.NoError,
fallbackName: otherName,
expectName: otherName,
expectID: driveID,
}, },
{ {
name: "error creating drive", name: "error creating drive",
dp: dp,
mock: &mockPDAGRF{ mock: &mockPDAGRF{
postResp: []models.Driveable{nil}, postResp: []models.Driveable{nil},
postErr: []error{assert.AnError}, postErr: []error{assert.AnError},
@ -899,41 +961,66 @@ func (suite *RestoreUnitSuite) TestEnsureDriveExists() {
}, },
rc: NewRestoreCaches(nil), rc: NewRestoreCaches(nil),
expectErr: require.Error, expectErr: require.Error,
fallbackName: name,
expectName: "", expectName: "",
skipValueChecks: true, skipValueChecks: true,
expectID: driveID,
}, },
{ {
name: "drive name already exists", name: "drive name already exists",
dp: dp,
mock: &mockPDAGRF{ mock: &mockPDAGRF{
postResp: []models.Driveable{makeMD()}, postResp: []models.Driveable{makeMD()},
postErr: []error{nil}, postErr: []error{nil},
grf: grf, grf: grf,
}, },
rc: populatedCache("beaux"), rc: populatedCache("beaux"),
expectErr: require.NoError, expectErr: require.NoError,
expectName: name + " 1", fallbackName: name,
expectName: name,
expectID: driveID,
}, },
{ {
name: "list with name already exists", name: "list with name already exists",
dp: dp,
mock: &mockPDAGRF{ mock: &mockPDAGRF{
postResp: []models.Driveable{nil, makeMD()}, postResp: []models.Driveable{nil, makeMD()},
postErr: []error{graph.ErrItemAlreadyExistsConflict, nil}, postErr: []error{graph.ErrItemAlreadyExistsConflict, nil},
grf: grf, grf: grf,
}, },
rc: NewRestoreCaches(nil), rc: NewRestoreCaches(nil),
expectErr: require.NoError, expectErr: require.NoError,
expectName: name + " 1", fallbackName: name,
expectName: name + " 1",
expectID: driveID,
},
{
name: "list with old name already exists",
dp: oldDP,
mock: &mockPDAGRF{
postResp: []models.Driveable{nil, makeMD()},
postErr: []error{graph.ErrItemAlreadyExistsConflict, nil},
grf: grf,
},
rc: NewRestoreCaches(oldDriveIDNames),
expectErr: require.NoError,
fallbackName: name,
expectName: name + " 1",
expectID: driveID,
}, },
{ {
name: "drive and list with name already exist", name: "drive and list with name already exist",
dp: dp,
mock: &mockPDAGRF{ mock: &mockPDAGRF{
postResp: []models.Driveable{nil, makeMD()}, postResp: []models.Driveable{nil, makeMD()},
postErr: []error{graph.ErrItemAlreadyExistsConflict, nil}, postErr: []error{graph.ErrItemAlreadyExistsConflict, nil},
grf: grf, grf: grf,
}, },
rc: populatedCache("regard"), rc: populatedCache(driveID),
expectErr: require.NoError, expectErr: require.NoError,
expectName: name + " 2", fallbackName: name,
expectName: name,
expectID: driveID,
}, },
} }
for _, test := range table { for _, test := range table {
@ -945,16 +1032,19 @@ func (suite *RestoreUnitSuite) TestEnsureDriveExists() {
rc := test.rc rc := test.rc
err := ensureDriveExists( di, err := ensureDriveExists(
ctx, ctx,
test.mock, test.mock,
rc, rc,
dp, test.dp,
"prID", "prID",
name) test.fallbackName)
test.expectErr(t, err, clues.ToCore(err)) test.expectErr(t, err, clues.ToCore(err))
if !test.skipValueChecks { if !test.skipValueChecks {
assert.Equal(t, test.expectName, di.name, "ensured drive has expected name")
assert.Equal(t, test.expectID, di.id, "ensured drive has expected id")
nameResult := rc.DriveNameToDriveInfo[test.expectName] nameResult := rc.DriveNameToDriveInfo[test.expectName]
assert.Equal(t, test.expectName, nameResult.name, "found drive entry with expected name") assert.Equal(t, test.expectName, nameResult.name, "found drive entry with expected name")
} }