From 570ce85656615fe835976762ae74623f56523b27 Mon Sep 17 00:00:00 2001 From: Keepers Date: Tue, 14 Feb 2023 12:33:07 -0700 Subject: [PATCH] add clues/fault to exchange restore (#2491) ## Does this PR need a docs update or release note? - [ ] :no_entry: No ## Type of change - [x] :broom: Tech Debt/Cleanup ## Issue(s) * #1970 ## Test Plan - [x] :zap: Unit test - [x] :green_heart: E2E --- src/cmd/factory/impl/common.go | 3 +- src/cmd/factory/impl/exchange.go | 27 ++- src/internal/connector/data_collections.go | 3 +- src/internal/connector/exchange/attachment.go | 53 +++-- .../exchange/attachment_uploadable.go | 20 +- .../connector/exchange/restore_test.go | 6 +- .../connector/exchange/service_restore.go | 197 +++++++++--------- .../connector/graph_connector_test.go | 10 +- src/internal/connector/support/status.go | 1 + .../operations/backup_integration_test.go | 3 +- src/internal/operations/restore.go | 3 +- 11 files changed, 172 insertions(+), 154 deletions(-) diff --git a/src/cmd/factory/impl/common.go b/src/cmd/factory/impl/common.go index 66efe5f39..2e418e273 100644 --- a/src/cmd/factory/impl/common.go +++ b/src/cmd/factory/impl/common.go @@ -53,6 +53,7 @@ func generateAndRestoreItems( howMany int, dbf dataBuilderFunc, opts control.Options, + errs *fault.Errors, ) (*details.Details, error) { items := make([]item, 0, howMany) @@ -93,7 +94,7 @@ func generateAndRestoreItems( Infof(ctx, "Generating %d %s items in %s\n", howMany, cat, Destination) - return gc.RestoreDataCollections(ctx, backup.Version, acct, sel, dest, opts, dataColls) + return gc.RestoreDataCollections(ctx, backup.Version, acct, sel, dest, opts, dataColls, errs) } // ------------------------------------------------------------------------------------------ diff --git a/src/cmd/factory/impl/exchange.go b/src/cmd/factory/impl/exchange.go index 39e3c13a1..7e7418982 100644 --- a/src/cmd/factory/impl/exchange.go +++ b/src/cmd/factory/impl/exchange.go @@ -1,12 +1,15 @@ package impl import ( + "github.com/alcionai/clues" "github.com/spf13/cobra" . "github.com/alcionai/corso/src/cli/print" "github.com/alcionai/corso/src/cli/utils" "github.com/alcionai/corso/src/internal/connector/mockconnector" "github.com/alcionai/corso/src/pkg/control" + "github.com/alcionai/corso/src/pkg/fault" + "github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/selectors" ) @@ -42,6 +45,7 @@ func handleExchangeEmailFactory(cmd *cobra.Command, args []string) error { ctx = cmd.Context() service = path.ExchangeService category = path.EmailCategory + errs = fault.New(false) ) if utils.HasNoFlagsAndShownHelp(cmd) { @@ -69,11 +73,16 @@ func handleExchangeEmailFactory(cmd *cobra.Command, args []string) error { now, now, now, now) }, control.Options{}, - ) + errs) if err != nil { return Only(ctx, err) } + log := logger.Ctx(ctx) + for _, e := range errs.Errs() { + log.Errorw(e.Error(), clues.InErr(err).Slice()...) + } + deets.PrintEntries(ctx) return nil @@ -84,6 +93,7 @@ func handleExchangeCalendarEventFactory(cmd *cobra.Command, args []string) error ctx = cmd.Context() service = path.ExchangeService category = path.EventsCategory + errs = fault.New(false) ) if utils.HasNoFlagsAndShownHelp(cmd) { @@ -110,11 +120,16 @@ func handleExchangeCalendarEventFactory(cmd *cobra.Command, args []string) error now, now, false) }, control.Options{}, - ) + errs) if err != nil { return Only(ctx, err) } + log := logger.Ctx(ctx) + for _, e := range errs.Errs() { + log.Errorw(e.Error(), clues.InErr(err).Slice()...) + } + deets.PrintEntries(ctx) return nil @@ -125,6 +140,7 @@ func handleExchangeContactFactory(cmd *cobra.Command, args []string) error { ctx = cmd.Context() service = path.ExchangeService category = path.ContactsCategory + errs = fault.New(false) ) if utils.HasNoFlagsAndShownHelp(cmd) { @@ -156,11 +172,16 @@ func handleExchangeContactFactory(cmd *cobra.Command, args []string) error { ) }, control.Options{}, - ) + errs) if err != nil { return Only(ctx, err) } + log := logger.Ctx(ctx) + for _, e := range errs.Errs() { + log.Errorw(e.Error(), clues.InErr(err).Slice()...) + } + deets.PrintEntries(ctx) return nil diff --git a/src/internal/connector/data_collections.go b/src/internal/connector/data_collections.go index 06b7bec6a..182cca8ad 100644 --- a/src/internal/connector/data_collections.go +++ b/src/internal/connector/data_collections.go @@ -243,6 +243,7 @@ func (gc *GraphConnector) RestoreDataCollections( dest control.RestoreDestination, opts control.Options, dcs []data.RestoreCollection, + errs *fault.Errors, ) (*details.Details, error) { ctx, end := D.Span(ctx, "connector:restore") defer end() @@ -260,7 +261,7 @@ func (gc *GraphConnector) RestoreDataCollections( switch selector.Service { case selectors.ServiceExchange: - status, err = exchange.RestoreExchangeDataCollections(ctx, creds, gc.Service, dest, dcs, deets) + status, err = exchange.RestoreExchangeDataCollections(ctx, creds, gc.Service, dest, dcs, deets, errs) case selectors.ServiceOneDrive: status, err = onedrive.RestoreCollections(ctx, backupVersion, gc.Service, dest, opts, dcs, deets) case selectors.ServiceSharePoint: diff --git a/src/internal/connector/exchange/attachment.go b/src/internal/connector/exchange/attachment.go index b8a29dfe2..831a42fcf 100644 --- a/src/internal/connector/exchange/attachment.go +++ b/src/internal/connector/exchange/attachment.go @@ -8,6 +8,7 @@ import ( "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/pkg/errors" + "github.com/alcionai/clues" "github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/connector/uploadsession" @@ -46,46 +47,42 @@ func uploadAttachment( uploader attachmentUploadable, attachment models.Attachmentable, ) error { - logger.Ctx(ctx).Debugf("uploading attachment with size %d", *attachment.GetSize()) + attachmentType := attachmentType(attachment) + + ctx = clues.AddAll( + ctx, + "attachment_size", ptr.Val(attachment.GetSize()), + "attachment_id", ptr.Val(attachment.GetId()), + "attachment_name", ptr.Val(attachment.GetName()), // TODO: pii + "attachment_type", attachmentType, + "internal_item_type", getItemAttachmentItemType(attachment), + "uploader_item_id", uploader.getItemID()) + + logger.Ctx(ctx).Debugw("uploading attachment") - var ( - attachmentType = attachmentType(attachment) - err error - ) // Reference attachments that are inline() do not need to be recreated. The contents are part of the body. - if attachmentType == models.REFERENCE_ATTACHMENTTYPE && - attachment.GetIsInline() != nil && *attachment.GetIsInline() { - logger.Ctx(ctx).Debugf("skip uploading inline reference attachment: ", *attachment.GetName()) + if attachmentType == models.REFERENCE_ATTACHMENTTYPE && ptr.Val(attachment.GetIsInline()) { + logger.Ctx(ctx).Debug("skip uploading inline reference attachment: ", ptr.Val(attachment.GetName())) return nil } // item Attachments to be skipped until the completion of Issue #2353 if attachmentType == models.ITEM_ATTACHMENTTYPE { - prev := attachment - - attachment, err = support.ToItemAttachment(attachment) + a, err := support.ToItemAttachment(attachment) if err != nil { - name := ptr.Val(prev.GetName()) - msg := "item attachment restore not supported for this type. skipping upload." - - // TODO: (rkeepers) Update to support PII protection - logger.Ctx(ctx).Infow(msg, - "err", err, - "attachment_name", name, - "attachment_type", attachmentType, - "internal_item_type", getItemAttachmentItemType(prev), - "attachment_id", ptr.Val(prev.GetId()), - ) + logger.Ctx(ctx). + With("err", err). + Infow("item attachment restore not supported for this type. skipping upload.", clues.InErr(err).Slice()...) return nil } + + attachment = a } // For Item/Reference attachments *or* file attachments < 3MB, use the attachments endpoint - if attachmentType != models.FILE_ATTACHMENTTYPE || *attachment.GetSize() < largeAttachmentSize { - err := uploader.uploadSmallAttachment(ctx, attachment) - - return err + if attachmentType != models.FILE_ATTACHMENTTYPE || ptr.Val(attachment.GetSize()) < largeAttachmentSize { + return uploader.uploadSmallAttachment(ctx, attachment) } return uploadLargeAttachment(ctx, uploader, attachment) @@ -93,7 +90,9 @@ func uploadAttachment( // uploadLargeAttachment will upload the specified attachment by creating an upload session and // doing a chunked upload -func uploadLargeAttachment(ctx context.Context, uploader attachmentUploadable, +func uploadLargeAttachment( + ctx context.Context, + uploader attachmentUploadable, attachment models.Attachmentable, ) error { ab := attachmentBytes(attachment) diff --git a/src/internal/connector/exchange/attachment_uploadable.go b/src/internal/connector/exchange/attachment_uploadable.go index 5369dcfa6..4bc96f58e 100644 --- a/src/internal/connector/exchange/attachment_uploadable.go +++ b/src/internal/connector/exchange/attachment_uploadable.go @@ -3,12 +3,11 @@ package exchange import ( "context" + "github.com/alcionai/clues" "github.com/microsoftgraph/msgraph-sdk-go/models" msusers "github.com/microsoftgraph/msgraph-sdk-go/users" - "github.com/pkg/errors" "github.com/alcionai/corso/src/internal/connector/graph" - "github.com/alcionai/corso/src/internal/connector/support" ) // attachementUploadable represents structs that are able to upload small attachments directly to an item or use an @@ -45,7 +44,7 @@ func (mau *mailAttachmentUploader) uploadSmallAttachment(ctx context.Context, at Attachments(). Post(ctx, attach, nil) if err != nil { - return errors.Wrap(err, support.ConnectorStackErrorTrace(err)) + return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...) } return nil @@ -69,12 +68,7 @@ func (mau *mailAttachmentUploader) uploadSession( CreateUploadSession(). Post(ctx, session, nil) if err != nil { - return nil, errors.Wrapf( - err, - "failed to create attachment upload session for item %s. details: %s", - mau.itemID, - support.ConnectorStackErrorTrace(err), - ) + return nil, clues.Wrap(err, "uploading mail attachment").WithClues(ctx).WithAll(graph.ErrData(err)...) } return r, nil @@ -100,7 +94,7 @@ func (eau *eventAttachmentUploader) uploadSmallAttachment(ctx context.Context, a Attachments(). Post(ctx, attach, nil) if err != nil { - return errors.Wrap(err, support.ConnectorStackErrorTrace(err)) + return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...) } return nil @@ -122,11 +116,7 @@ func (eau *eventAttachmentUploader) uploadSession( CreateUploadSession(). Post(ctx, session, nil) if err != nil { - return nil, errors.Wrapf( - err, - "failed to create attachment upload session for event item %s. details: %s", - eau.itemID, support.ConnectorStackErrorTrace(err), - ) + return nil, clues.Wrap(err, "uploading event attachment").WithClues(ctx).WithAll(graph.ErrData(err)...) } return r, nil diff --git a/src/internal/connector/exchange/restore_test.go b/src/internal/connector/exchange/restore_test.go index ad0c6b192..962964e8c 100644 --- a/src/internal/connector/exchange/restore_test.go +++ b/src/internal/connector/exchange/restore_test.go @@ -17,6 +17,7 @@ import ( "github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/control" + "github.com/alcionai/corso/src/pkg/fault" "github.com/alcionai/corso/src/pkg/path" ) @@ -119,7 +120,8 @@ func (suite *ExchangeRestoreSuite) TestRestoreEvent() { suite.gs, control.Copy, calendarID, - userID) + userID, + fault.New(true)) assert.NoError(t, err, support.ConnectorStackErrorTrace(err)) assert.NotNil(t, info, "event item info") } @@ -346,7 +348,7 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() { service, destination, userID, - ) + fault.New(true)) assert.NoError(t, err, support.ConnectorStackErrorTrace(err)) assert.NotNil(t, info, "item info was not populated") assert.NotNil(t, deleters) diff --git a/src/internal/connector/exchange/service_restore.go b/src/internal/connector/exchange/service_restore.go index f9ed59823..6b5b06440 100644 --- a/src/internal/connector/exchange/service_restore.go +++ b/src/internal/connector/exchange/service_restore.go @@ -7,6 +7,7 @@ import ( "reflect" "runtime/trace" + "github.com/alcionai/clues" "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/pkg/errors" @@ -21,6 +22,7 @@ import ( "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/fault" "github.com/alcionai/corso/src/pkg/logger" "github.com/alcionai/corso/src/pkg/path" ) @@ -35,20 +37,21 @@ func RestoreExchangeObject( policy control.CollisionPolicy, service graph.Servicer, destination, user string, + errs *fault.Errors, ) (*details.ExchangeInfo, error) { if policy != control.Copy { - return nil, fmt.Errorf("restore policy: %s not supported for RestoreExchangeObject", policy) + return nil, clues.Wrap(clues.New(policy.String()), "policy not supported for Exchange restore").WithClues(ctx) } switch category { case path.EmailCategory: - return RestoreMailMessage(ctx, bits, service, control.Copy, destination, user) + return RestoreMailMessage(ctx, bits, service, control.Copy, destination, user, errs) case path.ContactsCategory: return RestoreExchangeContact(ctx, bits, service, control.Copy, destination, user) case path.EventsCategory: - return RestoreExchangeEvent(ctx, bits, service, control.Copy, destination, user) + return RestoreExchangeEvent(ctx, bits, service, control.Copy, destination, user, errs) default: - return nil, fmt.Errorf("type: %s not supported for RestoreExchangeObject", category) + return nil, clues.Wrap(clues.New(category.String()), "not supported for Exchange restore") } } @@ -67,22 +70,18 @@ func RestoreExchangeContact( ) (*details.ExchangeInfo, error) { contact, err := support.CreateContactFromBytes(bits) if err != nil { - return nil, errors.Wrap(err, "creating contact from bytes: RestoreExchangeContact") + return nil, clues.Wrap(err, "creating contact from bytes").WithClues(ctx) } + ctx = clues.Add(ctx, "item_id", ptr.Val(contact.GetId())) + response, err := service.Client().UsersById(user).ContactFoldersById(destination).Contacts().Post(ctx, contact, nil) if err != nil { - name := ptr.Val(contact.GetGivenName()) - - return nil, errors.Wrap( - err, - "uploading Contact during RestoreExchangeContact: "+name+" "+ - support.ConnectorStackErrorTrace(err), - ) + return nil, clues.Wrap(err, "uploading Contact").WithClues(ctx).WithAll(graph.ErrData(err)...) } if response == nil { - return nil, errors.New("msgraph contact post fail: REST response not received") + return nil, clues.New("nil response from post").WithClues(ctx) } info := api.ContactInfo(contact) @@ -103,17 +102,18 @@ func RestoreExchangeEvent( service graph.Servicer, cp control.CollisionPolicy, destination, user string, + errs *fault.Errors, ) (*details.ExchangeInfo, error) { event, err := support.CreateEventFromBytes(bits) if err != nil { - return nil, errors.Wrap(err, "creating event from bytes: RestoreExchangeEvent") + return nil, clues.Wrap(err, "creating event from bytes").WithClues(ctx) } - transformedEvent := support.ToEventSimplified(event) + ctx = clues.Add(ctx, "item_id", ptr.Val(event.GetId())) var ( - attached []models.Attachmentable - errs error + transformedEvent = support.ToEventSimplified(event) + attached []models.Attachmentable ) if *event.GetHasAttachments() { @@ -124,15 +124,11 @@ func RestoreExchangeEvent( response, err := service.Client().UsersById(user).CalendarsById(destination).Events().Post(ctx, transformedEvent, nil) if err != nil { - return nil, errors.Wrap(err, - fmt.Sprintf( - "uploading event during RestoreExchangeEvent: %s", - support.ConnectorStackErrorTrace(err)), - ) + return nil, clues.Wrap(err, "uploading event").WithClues(ctx).WithAll(graph.ErrData(err)...) } if response == nil { - return nil, errors.New("msgraph event post fail: REST response not received") + return nil, clues.New("nil response from post").WithClues(ctx) } uploader := &eventAttachmentUploader{ @@ -143,25 +139,19 @@ func RestoreExchangeEvent( } for _, attach := range attached { - if err := uploadAttachment(ctx, uploader, attach); err != nil { - errs = support.WrapAndAppend( - fmt.Sprintf( - "uploading attachment for message %s: %s", - ptr.Val(transformedEvent.GetId()), - support.ConnectorStackErrorTrace(err), - ), - err, - errs, - ) - + if errs.Err() != nil { break } + + if err := uploadAttachment(ctx, uploader, attach); err != nil { + errs.Add(err) + } } info := api.EventInfo(event) info.Size = int64(len(bits)) - return info, errs + return info, errs.Err() } // RestoreMailMessage utility function to place an exchange.Mail @@ -176,16 +166,21 @@ func RestoreMailMessage( service graph.Servicer, cp control.CollisionPolicy, destination, user string, + errs *fault.Errors, ) (*details.ExchangeInfo, error) { // Creates messageable object from original bytes originalMessage, err := support.CreateMessageFromBytes(bits) if err != nil { - return nil, errors.Wrap(err, "creating email from bytes: RestoreMailMessage") + return nil, clues.Wrap(err, "creating mail from bytes").WithClues(ctx) } - // Sets fields from original message from storage - clone := support.ToMessage(originalMessage) - valueID := MailRestorePropertyTag - enableValue := RestoreCanonicalEnableValue + + ctx = clues.Add(ctx, "item_id", ptr.Val(originalMessage.GetId())) + + var ( + clone = support.ToMessage(originalMessage) + valueID = MailRestorePropertyTag + enableValue = RestoreCanonicalEnableValue + ) // Set Extended Properties: // 1st: No transmission @@ -219,17 +214,8 @@ func RestoreMailMessage( clone.SetSingleValueExtendedProperties(svlep) - // Switch workflow based on collision policy - switch cp { - default: - logger.Ctx(ctx).DPanicw("restoreMailMessage received unrecognized restore policy; defaulting to copy", - "policy", cp) - fallthrough - case control.Copy: - err := SendMailToBackStore(ctx, service, user, destination, clone) - if err != nil { - return nil, err - } + if err := SendMailToBackStore(ctx, service, user, destination, clone, errs); err != nil { + return nil, err } info := api.MailInfo(clone) @@ -253,28 +239,23 @@ func SendMailToBackStore( service graph.Servicer, user, destination string, message models.Messageable, + errs *fault.Errors, ) error { - var ( - attached []models.Attachmentable - errs error - ) + attached := message.GetAttachments() // Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized - attached = message.GetAttachments() message.SetAttachments([]models.Attachmentable{}) - sentMessage, err := service.Client().UsersById(user).MailFoldersById(destination).Messages().Post(ctx, message, nil) + response, err := service.Client().UsersById(user).MailFoldersById(destination).Messages().Post(ctx, message, nil) if err != nil { - return errors.Wrap(err, - user+": failure sendMailAPI: Dest: "+destination+" Details: "+support.ConnectorStackErrorTrace(err), - ) + return clues.Wrap(err, "restoring mail").WithClues(ctx).WithAll(graph.ErrData(err)...) } - if sentMessage == nil { - return errors.New("message not Sent: blocked by server") + if response == nil { + return clues.New("nil response from post").WithClues(ctx) } - id := *sentMessage.GetId() + id := ptr.Val(response.GetId()) uploader := &mailAttachmentUploader{ userID: user, @@ -284,29 +265,28 @@ func SendMailToBackStore( } for _, attachment := range attached { + if errs.Err() != nil { + break + } + if err := uploadAttachment(ctx, uploader, attachment); err != nil { if ptr.Val(attachment.GetOdataType()) == "#microsoft.graph.itemAttachment" { name := ptr.Val(attachment.GetName()) - logger.Ctx(ctx).Infow( - "item attachment upload not successful. content not accepted by M365 server", - "Attachment Name", name) + logger.Ctx(ctx). + With("err", err, "attachment_name", name). + Infow("mail upload failed", clues.InErr(err).Slice()...) continue } - errs = support.WrapAndAppend( - fmt.Sprintf("uploading attachment for message %s: %s", - id, support.ConnectorStackErrorTrace(err)), - err, - errs, - ) + errs.Add(errors.Wrap(err, "uploading mail attachment")) break } } - return errs + return errs.Err() } // RestoreExchangeDataCollections restores M365 objects in data.RestoreCollection to MSFT @@ -319,22 +299,25 @@ func RestoreExchangeDataCollections( dest control.RestoreDestination, dcs []data.RestoreCollection, deets *details.Builder, + errs *fault.Errors, ) (*support.ConnectorOperationStatus, error) { var ( - // map of caches... but not yet... directoryCaches = make(map[string]map[path.CategoryType]graph.ContainerResolver) metrics support.CollectionMetrics - errs error + userID string // TODO policy to be updated from external source after completion of refactoring policy = control.Copy ) - errUpdater := func(id string, err error) { - errs = support.WrapAndAppend(id, err, errs) + if len(dcs) > 0 { + userID = dcs[0].FullPath().ResourceOwner() + ctx = clues.Add(ctx, "resource_owner", userID) // TODO: pii } for _, dc := range dcs { - userID := dc.FullPath().ResourceOwner() + if errs.Err() != nil { + return nil, errs.Err() + } userCaches := directoryCaches[userID] if userCaches == nil { @@ -349,11 +332,11 @@ func RestoreExchangeDataCollections( dest.ContainerName, userCaches) if err != nil { - errs = support.WrapAndAppend(dc.FullPath().ShortRef(), err, errs) + errs.Add(clues.Wrap(err, "creating destination").WithClues(ctx)) continue } - temp, canceled := restoreCollection(ctx, gs, dc, containerID, policy, deets, errUpdater) + temp, canceled := restoreCollection(ctx, gs, dc, containerID, policy, deets, errs) metrics.Combine(temp) @@ -362,14 +345,15 @@ func RestoreExchangeDataCollections( } } - status := support.CreateStatus(ctx, + status := support.CreateStatus( + ctx, support.Restore, len(dcs), metrics, - errs, + errs.Err(), dest.ContainerName) - return status, errs + return status, errs.Err() } // restoreCollection handles restoration of an individual collection. @@ -380,7 +364,7 @@ func restoreCollection( folderID string, policy control.CollisionPolicy, deets *details.Builder, - errUpdater func(string, error), + errs *fault.Errors, ) (support.CollectionMetrics, bool) { ctx, end := D.Span(ctx, "gc:exchange:restoreCollection", D.Label("path", dc.FullPath())) defer end() @@ -394,6 +378,12 @@ func restoreCollection( user = directory.ResourceOwner() ) + ctx = clues.AddAll( + ctx, + "full_path", directory, + "service", service, + "category", category) + colProgress, closer := observe.CollectionProgress( ctx, category.String(), @@ -405,34 +395,39 @@ func restoreCollection( for { select { case <-ctx.Done(): - errUpdater("context cancelled", ctx.Err()) + errs.Add(clues.Wrap(ctx.Err(), "context cancelled").WithClues(ctx)) return metrics, true case itemData, ok := <-items: - if !ok { + if !ok || errs.Err() != nil { return metrics, false } - metrics.Objects++ - trace.Log(ctx, "gc:exchange:restoreCollection:item", itemData.UUID()) + ictx := clues.Add(ctx, "item_id", itemData.UUID()) + trace.Log(ictx, "gc:exchange:restoreCollection:item", itemData.UUID()) + metrics.Objects++ buf := &bytes.Buffer{} _, err := buf.ReadFrom(itemData.ToReader()) if err != nil { - errUpdater(itemData.UUID()+": byteReadError during RestoreDataCollection", err) + errs.Add(clues.Wrap(err, "reading item bytes").WithClues(ictx)) continue } byteArray := buf.Bytes() - info, err := RestoreExchangeObject(ctx, byteArray, category, policy, gs, folderID, user) + info, err := RestoreExchangeObject( + ictx, + byteArray, + category, + policy, + gs, + folderID, + user, + errs) if err != nil { - // More information to be here - errUpdater( - itemData.UUID()+": failed to upload RestoreExchangeObject: "+service.String()+"-"+category.String(), - err) - + errs.Add(err) continue } @@ -441,7 +436,7 @@ func restoreCollection( itemPath, err := dc.FullPath().Append(itemData.UUID(), true) if err != nil { - logger.Ctx(ctx).DPanicw("transforming item to full path", "error", err) + errs.Add(clues.Wrap(err, "building full path with item").WithClues(ctx)) continue } @@ -487,7 +482,7 @@ func CreateContainerDestination( // TODO(rkeepers): pass the api client into this func, rather than generating one. ac, err := api.NewClient(creds) if err != nil { - return "", err + return "", clues.Stack(err).WithClues(ctx) } switch category { @@ -569,7 +564,7 @@ func CreateContainerDestination( newCache) default: - return "", fmt.Errorf("category: %s not support for exchange cache", category) + return "", clues.Wrap(fmt.Errorf("%T", category), "not support for exchange cache").WithClues(ctx) } } @@ -591,6 +586,8 @@ func establishMailRestoreLocation( folderID := rootFolderAlias pb := path.Builder{} + ctx = clues.Add(ctx, "is_new_cache", isNewCache) + for _, folder := range folders { pb = *pb.Append(folder) @@ -647,6 +644,8 @@ func establishContactsRestoreLocation( return cached, nil } + ctx = clues.Add(ctx, "is_new_cache", isNewCache) + temp, err := ac.Contacts().CreateContactFolder(ctx, user, folders[0]) if err != nil { return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) @@ -681,6 +680,8 @@ func establishEventsRestoreLocation( return cached, nil } + ctx = clues.Add(ctx, "is_new_cache", isNewCache) + temp, err := ac.Events().CreateCalendar(ctx, user, folders[0]) if err != nil { return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) diff --git a/src/internal/connector/graph_connector_test.go b/src/internal/connector/graph_connector_test.go index 6a7620dca..c5122becd 100644 --- a/src/internal/connector/graph_connector_test.go +++ b/src/internal/connector/graph_connector_test.go @@ -250,7 +250,7 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreFailsBadService() { ToggleFeatures: control.Toggles{EnablePermissionsBackup: true}, }, nil, - ) + fault.New(true)) assert.Error(t, err) assert.NotNil(t, deets) @@ -327,7 +327,7 @@ func (suite *GraphConnectorIntegrationSuite) TestEmptyCollections() { ToggleFeatures: control.Toggles{EnablePermissionsBackup: true}, }, test.col, - ) + fault.New(true)) require.NoError(t, err) assert.NotNil(t, deets) @@ -422,7 +422,7 @@ func runRestoreBackupTest( dest, opts, collections, - ) + fault.New(true)) require.NoError(t, err) assert.NotNil(t, deets) @@ -544,7 +544,7 @@ func runRestoreBackupTestVersion0( dest, opts, collections, - ) + fault.New(true)) require.NoError(t, err) assert.NotNil(t, deets) @@ -1515,7 +1515,7 @@ func (suite *GraphConnectorIntegrationSuite) TestMultiFolderBackupDifferentNames ToggleFeatures: control.Toggles{EnablePermissionsBackup: true}, }, collections, - ) + fault.New(true)) require.NoError(t, err) require.NotNil(t, deets) diff --git a/src/internal/connector/support/status.go b/src/internal/connector/support/status.go index 849fee9bc..1bdd9ec8b 100644 --- a/src/internal/connector/support/status.go +++ b/src/internal/connector/support/status.go @@ -63,6 +63,7 @@ func CreateStatus( } hasErrors := err != nil + // TODO(keeprs): remove numErr := GetNumberOfErrors(err) status := ConnectorOperationStatus{ diff --git a/src/internal/operations/backup_integration_test.go b/src/internal/operations/backup_integration_test.go index f1b03298f..58a7fb6de 100644 --- a/src/internal/operations/backup_integration_test.go +++ b/src/internal/operations/backup_integration_test.go @@ -347,7 +347,8 @@ func generateContainerOfItems( sel, dest, control.Options{RestorePermissions: true}, - dataColls) + dataColls, + fault.New(true)) require.NoError(t, err) return deets diff --git a/src/internal/operations/restore.go b/src/internal/operations/restore.go index d9cdfe1a3..e44a7cbc4 100644 --- a/src/internal/operations/restore.go +++ b/src/internal/operations/restore.go @@ -258,7 +258,8 @@ func (op *RestoreOperation) do( op.Selectors, op.Destination, op.Options, - dcs) + dcs, + op.Errors) if err != nil { return nil, errors.Wrap(err, "restoring collections") }