create a new http client on every delete (#2970)

in order to avoid errors cascading from graph API's
populated 204 responses containing `0/r/n/r/n`,
in particular in response to deletes, this change
hacks in the creation of a unique http client for
each delete call.

This is dangerous to do, since we already know
that generating clients per calls can leak
consumption of system resources such as sockets.
This change presumes that runtime deletes are
minimally, if ever, called during standard backup
and restore.  The permission deletion in onedrive
is the stand-out exception that might need some
additional investigation.

---

#### Does this PR need a docs update or release note?

- [x]  No

#### Type of change

- [x] 🤖 Supportability/Tests

#### Issue(s)

* #2707

#### Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-03-30 09:56:53 -06:00 committed by GitHub
parent d99b125087
commit 25cffcdf8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 100 additions and 24 deletions

View File

@ -218,7 +218,7 @@ func (gc *GraphConnector) RestoreDataCollections(
case selectors.ServiceExchange: case selectors.ServiceExchange:
status, err = exchange.RestoreExchangeDataCollections(ctx, creds, gc.Service, dest, dcs, deets, errs) status, err = exchange.RestoreExchangeDataCollections(ctx, creds, gc.Service, dest, dcs, deets, errs)
case selectors.ServiceOneDrive: case selectors.ServiceOneDrive:
status, err = onedrive.RestoreCollections(ctx, backupVersion, gc.Service, dest, opts, dcs, deets, errs) status, err = onedrive.RestoreCollections(ctx, creds, backupVersion, gc.Service, dest, opts, dcs, deets, errs)
case selectors.ServiceSharePoint: case selectors.ServiceSharePoint:
status, err = sharepoint.RestoreCollections(ctx, backupVersion, creds, gc.Service, dest, dcs, deets, errs) status, err = sharepoint.RestoreCollections(ctx, backupVersion, creds, gc.Service, dest, dcs, deets, errs)
default: default:

View File

@ -60,7 +60,14 @@ func (c Contacts) DeleteContainer(
ctx context.Context, ctx context.Context,
user, folderID string, user, folderID string,
) error { ) error {
err := c.stable.Client().UsersById(user).ContactFoldersById(folderID).Delete(ctx, nil) // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707
srv, err := newService(c.Credentials)
if err != nil {
return graph.Stack(ctx, err)
}
err = srv.Client().UsersById(user).ContactFoldersById(folderID).Delete(ctx, nil)
if err != nil { if err != nil {
return graph.Stack(ctx, err) return graph.Stack(ctx, err)
} }

View File

@ -62,7 +62,14 @@ func (c Events) DeleteContainer(
ctx context.Context, ctx context.Context,
user, calendarID string, user, calendarID string,
) error { ) error {
err := c.stable.Client().UsersById(user).CalendarsById(calendarID).Delete(ctx, nil) // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707
srv, err := newService(c.Credentials)
if err != nil {
return graph.Stack(ctx, err)
}
err = srv.Client().UsersById(user).CalendarsById(calendarID).Delete(ctx, nil)
if err != nil { if err != nil {
return graph.Stack(ctx, err) return graph.Stack(ctx, err)
} }

View File

@ -89,7 +89,14 @@ func (c Mail) DeleteContainer(
ctx context.Context, ctx context.Context,
user, folderID string, user, folderID string,
) error { ) error {
err := c.stable.Client().UsersById(user).MailFoldersById(folderID).Delete(ctx, nil) // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707
srv, err := newService(c.Credentials)
if err != nil {
return graph.Stack(ctx, err)
}
err = srv.Client().UsersById(user).MailFoldersById(folderID).Delete(ctx, nil)
if err != nil { if err != nil {
return graph.Stack(ctx, err) return graph.Stack(ctx, err)
} }

View File

@ -367,6 +367,8 @@ func GetAllFolders(
return res, el.Failure() return res, el.Failure()
} }
// deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707
func DeleteItem( func DeleteItem(
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,

View File

@ -317,7 +317,9 @@ func (suite *OneDriveSuite) TestCreateGetDeleteFolder() {
defer func() { defer func() {
for _, id := range folderIDs { for _, id := range folderIDs {
err := DeleteItem(ctx, gs, driveID, id) // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707
err := DeleteItem(ctx, loadTestService(t), driveID, id)
if err != nil { if err != nil {
logger.Ctx(ctx).Warnw("deleting folder", "id", id, "error", err) logger.Ctx(ctx).Warnw("deleting folder", "id", id, "error", err)
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/alcionai/corso/src/internal/connector/graph" "github.com/alcionai/corso/src/internal/connector/graph"
"github.com/alcionai/corso/src/internal/data" "github.com/alcionai/corso/src/internal/data"
"github.com/alcionai/corso/src/internal/version" "github.com/alcionai/corso/src/internal/version"
"github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -88,6 +89,7 @@ func getCollectionMetadata(
// Passing nil for the permissions results in just creating the folder(s). // Passing nil for the permissions results in just creating the folder(s).
func createRestoreFoldersWithPermissions( func createRestoreFoldersWithPermissions(
ctx context.Context, ctx context.Context,
creds account.M365Config,
service graph.Servicer, service graph.Servicer,
drivePath *path.DrivePath, drivePath *path.DrivePath,
restoreFolders []string, restoreFolders []string,
@ -105,6 +107,7 @@ func createRestoreFoldersWithPermissions(
err = RestorePermissions( err = RestorePermissions(
ctx, ctx,
creds,
service, service,
drivePath.DriveID, drivePath.DriveID,
id, id,
@ -169,6 +172,7 @@ func diffPermissions(
// the necessary permissions on onedrive objects. // the necessary permissions on onedrive objects.
func RestorePermissions( func RestorePermissions(
ctx context.Context, ctx context.Context,
creds account.M365Config,
service graph.Servicer, service graph.Servicer,
driveID string, driveID string,
itemID string, itemID string,
@ -189,7 +193,17 @@ func RestorePermissions(
permAdded, permRemoved := diffPermissions(currentPermissions, meta.Permissions) permAdded, permRemoved := diffPermissions(currentPermissions, meta.Permissions)
for _, p := range permRemoved { for _, p := range permRemoved {
err := service.Client(). // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707
// this is bad citizenship, and could end up consuming a lot of
// system resources if servicers leak client connections (sockets, etc).
a, err := graph.CreateAdapter(creds.AzureTenantID, creds.AzureClientID, creds.AzureClientSecret)
if err != nil {
return graph.Wrap(ctx, err, "creating delete client")
}
err = graph.NewService(a).
Client().
DrivesById(driveID). DrivesById(driveID).
ItemsById(itemID). ItemsById(itemID).
PermissionsById(p.ID). PermissionsById(p.ID).

View File

@ -18,6 +18,7 @@ import (
"github.com/alcionai/corso/src/internal/diagnostics" "github.com/alcionai/corso/src/internal/diagnostics"
"github.com/alcionai/corso/src/internal/observe" "github.com/alcionai/corso/src/internal/observe"
"github.com/alcionai/corso/src/internal/version" "github.com/alcionai/corso/src/internal/version"
"github.com/alcionai/corso/src/pkg/account"
"github.com/alcionai/corso/src/pkg/backup/details" "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/fault" "github.com/alcionai/corso/src/pkg/fault"
@ -33,6 +34,7 @@ const copyBufferSize = 5 * 1024 * 1024
// RestoreCollections will restore the specified data collections into OneDrive // RestoreCollections will restore the specified data collections into OneDrive
func RestoreCollections( func RestoreCollections(
ctx context.Context, ctx context.Context,
creds account.M365Config,
backupVersion int, backupVersion int,
service graph.Servicer, service graph.Servicer,
dest control.RestoreDestination, dest control.RestoreDestination,
@ -80,6 +82,7 @@ func RestoreCollections(
metrics, folderMetas, err = RestoreCollection( metrics, folderMetas, err = RestoreCollection(
ictx, ictx,
creds,
backupVersion, backupVersion,
service, service,
dc, dc,
@ -121,6 +124,7 @@ func RestoreCollections(
// - error, if any besides recoverable // - error, if any besides recoverable
func RestoreCollection( func RestoreCollection(
ctx context.Context, ctx context.Context,
creds account.M365Config,
backupVersion int, backupVersion int,
service graph.Servicer, service graph.Servicer,
dc data.RestoreCollection, dc data.RestoreCollection,
@ -177,6 +181,7 @@ func RestoreCollection(
// Create restore folders and get the folder ID of the folder the data stream will be restored in // Create restore folders and get the folder ID of the folder the data stream will be restored in
restoreFolderID, err := createRestoreFoldersWithPermissions( restoreFolderID, err := createRestoreFoldersWithPermissions(
ctx, ctx,
creds,
service, service,
drivePath, drivePath,
restoreFolderElements, restoreFolderElements,
@ -209,6 +214,7 @@ func RestoreCollection(
itemInfo, skipped, err := restoreItem( itemInfo, skipped, err := restoreItem(
ctx, ctx,
creds,
dc, dc,
backupVersion, backupVersion,
source, source,
@ -260,6 +266,7 @@ func RestoreCollection(
// returns the item info, a bool (true = restore was skipped), and an error // returns the item info, a bool (true = restore was skipped), and an error
func restoreItem( func restoreItem(
ctx context.Context, ctx context.Context,
creds account.M365Config,
dc data.RestoreCollection, dc data.RestoreCollection,
backupVersion int, backupVersion int,
source driveSource, source driveSource,
@ -327,6 +334,7 @@ func restoreItem(
itemInfo, err := restoreV1File( itemInfo, err := restoreV1File(
ctx, ctx,
source, source,
creds,
service, service,
drivePath, drivePath,
dc, dc,
@ -346,6 +354,7 @@ func restoreItem(
itemInfo, err := restoreV6File( itemInfo, err := restoreV6File(
ctx, ctx,
source, source,
creds,
service, service,
drivePath, drivePath,
dc, dc,
@ -392,6 +401,7 @@ type fileFetcher interface {
func restoreV1File( func restoreV1File(
ctx context.Context, ctx context.Context,
source driveSource, source driveSource,
creds account.M365Config,
service graph.Servicer, service graph.Servicer,
drivePath *path.DrivePath, drivePath *path.DrivePath,
fetcher fileFetcher, fetcher fileFetcher,
@ -431,6 +441,7 @@ func restoreV1File(
err = RestorePermissions( err = RestorePermissions(
ctx, ctx,
creds,
service, service,
drivePath.DriveID, drivePath.DriveID,
itemID, itemID,
@ -445,6 +456,7 @@ func restoreV1File(
func restoreV6File( func restoreV6File(
ctx context.Context, ctx context.Context,
source driveSource, source driveSource,
creds account.M365Config,
service graph.Servicer, service graph.Servicer,
drivePath *path.DrivePath, drivePath *path.DrivePath,
fetcher fileFetcher, fetcher fileFetcher,
@ -495,11 +507,11 @@ func restoreV6File(
err = RestorePermissions( err = RestorePermissions(
ctx, ctx,
creds,
service, service,
drivePath.DriveID, drivePath.DriveID,
itemID, itemID,
meta, meta)
)
if err != nil { if err != nil {
return details.ItemInfo{}, clues.Wrap(err, "restoring item permissions") return details.ItemInfo{}, clues.Wrap(err, "restoring item permissions")
} }

View File

@ -155,6 +155,8 @@ func fetchPageOptions() *betasites.ItemPagesRequestBuilderGetRequestConfiguratio
// DeleteSitePage removes the selected page from the SharePoint Site // DeleteSitePage removes the selected page from the SharePoint Site
// https://learn.microsoft.com/en-us/graph/api/sitepage-delete?view=graph-rest-beta // https://learn.microsoft.com/en-us/graph/api/sitepage-delete?view=graph-rest-beta
// deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707
func DeleteSitePage( func DeleteSitePage(
ctx context.Context, ctx context.Context,
serv *dapi.BetaService, serv *dapi.BetaService,

View File

@ -256,6 +256,7 @@ func (suite *SharePointCollectionSuite) TestRestoreLocation() {
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
driveID := ptr.Val(siteDrive.GetId()) driveID := ptr.Val(siteDrive.GetId())
err = onedrive.DeleteItem(ctx, service, driveID, folderID) err = onedrive.DeleteItem(ctx, service, driveID, folderID)
assert.NoError(t, err, clues.ToCore(err)) assert.NoError(t, err, clues.ToCore(err))
} }

View File

@ -389,6 +389,8 @@ func fetchColumnLinks(
} }
// DeleteList removes a list object from a site. // DeleteList removes a list object from a site.
// deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707
func DeleteList( func DeleteList(
ctx context.Context, ctx context.Context,
gs graph.Servicer, gs graph.Servicer,

View File

@ -69,6 +69,7 @@ func RestoreCollections(
case path.LibrariesCategory: case path.LibrariesCategory:
metrics, _, err = onedrive.RestoreCollection( metrics, _, err = onedrive.RestoreCollection(
ictx, ictx,
creds,
backupVersion, backupVersion,
service, service,
dc, dc,

View File

@ -1183,7 +1183,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_oneDriveIncrementals() {
genDests = []string{container1, container2} genDests = []string{container1, container2}
) )
m365, err := acct.M365Config() creds, err := acct.M365Config()
require.NoError(t, err, clues.ToCore(err)) require.NoError(t, err, clues.ToCore(err))
gc, err := connector.NewGraphConnector( gc, err := connector.NewGraphConnector(
@ -1215,7 +1215,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_oneDriveIncrementals() {
acct, acct,
path.FilesCategory, path.FilesCategory,
selectors.NewOneDriveRestore(owners).Selector, selectors.NewOneDriveRestore(owners).Selector,
m365.AzureTenantID, suite.user, driveID, destName, creds.AzureTenantID, suite.user, driveID, destName,
2, 2,
// Use an old backup version so we don't need metadata files. // Use an old backup version so we don't need metadata files.
0, 0,
@ -1299,6 +1299,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_oneDriveIncrementals() {
driveItem.SetFile(models.NewFile()) driveItem.SetFile(models.NewFile())
err = onedrive.RestorePermissions( err = onedrive.RestorePermissions(
ctx, ctx,
creds,
gc.Service, gc.Service,
driveID, driveID,
*newFile.GetId(), *newFile.GetId(),
@ -1310,8 +1311,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_oneDriveIncrementals() {
EntityID: suite.user, EntityID: suite.user,
}, },
}, },
}, })
)
require.NoErrorf(t, err, "add permission to file %v", clues.ToCore(err)) require.NoErrorf(t, err, "add permission to file %v", clues.ToCore(err))
}, },
itemsRead: 1, // .data file for newitem itemsRead: 1, // .data file for newitem
@ -1325,14 +1325,14 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_oneDriveIncrementals() {
driveItem.SetFile(models.NewFile()) driveItem.SetFile(models.NewFile())
err = onedrive.RestorePermissions( err = onedrive.RestorePermissions(
ctx, ctx,
creds,
gc.Service, gc.Service,
driveID, driveID,
*newFile.GetId(), *newFile.GetId(),
onedrive.Metadata{ onedrive.Metadata{
SharingMode: onedrive.SharingModeCustom, SharingMode: onedrive.SharingModeCustom,
Permissions: []onedrive.UserPermission{}, Permissions: []onedrive.UserPermission{},
}, })
)
require.NoError(t, err, "add permission to file", clues.ToCore(err)) require.NoError(t, err, "add permission to file", clues.ToCore(err))
}, },
itemsRead: 1, // .data file for newitem itemsRead: 1, // .data file for newitem
@ -1347,6 +1347,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_oneDriveIncrementals() {
driveItem.SetFile(models.NewFile()) driveItem.SetFile(models.NewFile())
err = onedrive.RestorePermissions( err = onedrive.RestorePermissions(
ctx, ctx,
creds,
gc.Service, gc.Service,
driveID, driveID,
targetContainer, targetContainer,
@ -1358,8 +1359,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_oneDriveIncrementals() {
EntityID: suite.user, EntityID: suite.user,
}, },
}, },
}, })
)
require.NoError(t, err, "add permission to file", clues.ToCore(err)) require.NoError(t, err, "add permission to file", clues.ToCore(err))
}, },
itemsRead: 0, itemsRead: 0,
@ -1374,14 +1374,14 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_oneDriveIncrementals() {
driveItem.SetFile(models.NewFile()) driveItem.SetFile(models.NewFile())
err = onedrive.RestorePermissions( err = onedrive.RestorePermissions(
ctx, ctx,
creds,
gc.Service, gc.Service,
driveID, driveID,
targetContainer, targetContainer,
onedrive.Metadata{ onedrive.Metadata{
SharingMode: onedrive.SharingModeCustom, SharingMode: onedrive.SharingModeCustom,
Permissions: []onedrive.UserPermission{}, Permissions: []onedrive.UserPermission{},
}, })
)
require.NoError(t, err, "add permission to file", clues.ToCore(err)) require.NoError(t, err, "add permission to file", clues.ToCore(err))
}, },
itemsRead: 0, itemsRead: 0,
@ -1447,7 +1447,9 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_oneDriveIncrementals() {
{ {
name: "delete file", name: "delete file",
updateUserData: func(t *testing.T) { updateUserData: func(t *testing.T) {
err = gc.Service. // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707
err = newDeleteServicer(t).
Client(). Client().
DrivesById(driveID). DrivesById(driveID).
ItemsById(ptr.Val(newFile.GetId())). ItemsById(ptr.Val(newFile.GetId())).
@ -1506,7 +1508,9 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_oneDriveIncrementals() {
name: "delete a folder", name: "delete a folder",
updateUserData: func(t *testing.T) { updateUserData: func(t *testing.T) {
container := containerIDs[container2] container := containerIDs[container2]
err := gc.Service. // deletes require unique http clients
// https://github.com/alcionai/corso/issues/2707
err = newDeleteServicer(t).
Client(). Client().
DrivesById(driveID). DrivesById(driveID).
ItemsById(container). ItemsById(container).
@ -1527,7 +1531,7 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_oneDriveIncrementals() {
acct, acct,
path.FilesCategory, path.FilesCategory,
selectors.NewOneDriveRestore(owners).Selector, selectors.NewOneDriveRestore(owners).Selector,
m365.AzureTenantID, suite.user, driveID, container3, creds.AzureTenantID, suite.user, driveID, container3,
2, 2,
0, 0,
fileDBF) fileDBF)
@ -1568,11 +1572,10 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_oneDriveIncrementals() {
incBO.Results.BackupID, incBO.Results.BackupID,
kw, kw,
ms, ms,
m365.AzureTenantID, creds.AzureTenantID,
suite.user, suite.user,
path.OneDriveService, path.OneDriveService,
categories, categories)
)
// do some additional checks to ensure the incremental dealt with fewer items. // do some additional checks to ensure the incremental dealt with fewer items.
// +2 on read/writes to account for metadata: 1 delta and 1 path. // +2 on read/writes to account for metadata: 1 delta and 1 path.
@ -1611,3 +1614,19 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run_sharePoint() {
runAndCheckBackup(t, ctx, &bo, mb, false) runAndCheckBackup(t, ctx, &bo, mb, false)
checkBackupIsInManifests(t, ctx, kw, &bo, sel.Selector, suite.site, path.LibrariesCategory) checkBackupIsInManifests(t, ctx, kw, &bo, sel.Selector, suite.site, path.LibrariesCategory)
} }
// ---------------------------------------------------------------------------
// helpers
// ---------------------------------------------------------------------------
func newDeleteServicer(t *testing.T) graph.Servicer {
acct := tester.NewM365Account(t)
m365, err := acct.M365Config()
require.NoError(t, err, clues.ToCore(err))
a, err := graph.CreateAdapter(acct.ID(), m365.AzureClientID, m365.AzureClientSecret)
require.NoError(t, err, clues.ToCore(err))
return graph.NewService(a)
}