add clues/fault to exchange restore (#2491)

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

- [ ]  No 

## Type of change

- [x] 🧹 Tech Debt/Cleanup

## Issue(s)

* #1970

## Test Plan

- [x]  Unit test
- [x] 💚 E2E
This commit is contained in:
Keepers 2023-02-14 12:33:07 -07:00 committed by GitHub
parent 31c9195a12
commit 570ce85656
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 172 additions and 154 deletions

View File

@ -53,6 +53,7 @@ func generateAndRestoreItems(
howMany int, howMany int,
dbf dataBuilderFunc, dbf dataBuilderFunc,
opts control.Options, opts control.Options,
errs *fault.Errors,
) (*details.Details, error) { ) (*details.Details, error) {
items := make([]item, 0, howMany) items := make([]item, 0, howMany)
@ -93,7 +94,7 @@ func generateAndRestoreItems(
Infof(ctx, "Generating %d %s items in %s\n", howMany, cat, Destination) 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)
} }
// ------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------

View File

@ -1,12 +1,15 @@
package impl package impl
import ( import (
"github.com/alcionai/clues"
"github.com/spf13/cobra" "github.com/spf13/cobra"
. "github.com/alcionai/corso/src/cli/print" . "github.com/alcionai/corso/src/cli/print"
"github.com/alcionai/corso/src/cli/utils" "github.com/alcionai/corso/src/cli/utils"
"github.com/alcionai/corso/src/internal/connector/mockconnector" "github.com/alcionai/corso/src/internal/connector/mockconnector"
"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/logger"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
"github.com/alcionai/corso/src/pkg/selectors" "github.com/alcionai/corso/src/pkg/selectors"
) )
@ -42,6 +45,7 @@ func handleExchangeEmailFactory(cmd *cobra.Command, args []string) error {
ctx = cmd.Context() ctx = cmd.Context()
service = path.ExchangeService service = path.ExchangeService
category = path.EmailCategory category = path.EmailCategory
errs = fault.New(false)
) )
if utils.HasNoFlagsAndShownHelp(cmd) { if utils.HasNoFlagsAndShownHelp(cmd) {
@ -69,11 +73,16 @@ func handleExchangeEmailFactory(cmd *cobra.Command, args []string) error {
now, now, now, now) now, now, now, now)
}, },
control.Options{}, control.Options{},
) errs)
if err != nil { if err != nil {
return Only(ctx, err) return Only(ctx, err)
} }
log := logger.Ctx(ctx)
for _, e := range errs.Errs() {
log.Errorw(e.Error(), clues.InErr(err).Slice()...)
}
deets.PrintEntries(ctx) deets.PrintEntries(ctx)
return nil return nil
@ -84,6 +93,7 @@ func handleExchangeCalendarEventFactory(cmd *cobra.Command, args []string) error
ctx = cmd.Context() ctx = cmd.Context()
service = path.ExchangeService service = path.ExchangeService
category = path.EventsCategory category = path.EventsCategory
errs = fault.New(false)
) )
if utils.HasNoFlagsAndShownHelp(cmd) { if utils.HasNoFlagsAndShownHelp(cmd) {
@ -110,11 +120,16 @@ func handleExchangeCalendarEventFactory(cmd *cobra.Command, args []string) error
now, now, false) now, now, false)
}, },
control.Options{}, control.Options{},
) errs)
if err != nil { if err != nil {
return Only(ctx, err) return Only(ctx, err)
} }
log := logger.Ctx(ctx)
for _, e := range errs.Errs() {
log.Errorw(e.Error(), clues.InErr(err).Slice()...)
}
deets.PrintEntries(ctx) deets.PrintEntries(ctx)
return nil return nil
@ -125,6 +140,7 @@ func handleExchangeContactFactory(cmd *cobra.Command, args []string) error {
ctx = cmd.Context() ctx = cmd.Context()
service = path.ExchangeService service = path.ExchangeService
category = path.ContactsCategory category = path.ContactsCategory
errs = fault.New(false)
) )
if utils.HasNoFlagsAndShownHelp(cmd) { if utils.HasNoFlagsAndShownHelp(cmd) {
@ -156,11 +172,16 @@ func handleExchangeContactFactory(cmd *cobra.Command, args []string) error {
) )
}, },
control.Options{}, control.Options{},
) errs)
if err != nil { if err != nil {
return Only(ctx, err) return Only(ctx, err)
} }
log := logger.Ctx(ctx)
for _, e := range errs.Errs() {
log.Errorw(e.Error(), clues.InErr(err).Slice()...)
}
deets.PrintEntries(ctx) deets.PrintEntries(ctx)
return nil return nil

View File

@ -243,6 +243,7 @@ func (gc *GraphConnector) RestoreDataCollections(
dest control.RestoreDestination, dest control.RestoreDestination,
opts control.Options, opts control.Options,
dcs []data.RestoreCollection, dcs []data.RestoreCollection,
errs *fault.Errors,
) (*details.Details, error) { ) (*details.Details, error) {
ctx, end := D.Span(ctx, "connector:restore") ctx, end := D.Span(ctx, "connector:restore")
defer end() defer end()
@ -260,7 +261,7 @@ func (gc *GraphConnector) RestoreDataCollections(
switch selector.Service { switch selector.Service {
case selectors.ServiceExchange: 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: case selectors.ServiceOneDrive:
status, err = onedrive.RestoreCollections(ctx, backupVersion, gc.Service, dest, opts, dcs, deets) status, err = onedrive.RestoreCollections(ctx, backupVersion, gc.Service, dest, opts, dcs, deets)
case selectors.ServiceSharePoint: case selectors.ServiceSharePoint:

View File

@ -8,6 +8,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/clues"
"github.com/alcionai/corso/src/internal/common/ptr" "github.com/alcionai/corso/src/internal/common/ptr"
"github.com/alcionai/corso/src/internal/connector/support" "github.com/alcionai/corso/src/internal/connector/support"
"github.com/alcionai/corso/src/internal/connector/uploadsession" "github.com/alcionai/corso/src/internal/connector/uploadsession"
@ -46,46 +47,42 @@ func uploadAttachment(
uploader attachmentUploadable, uploader attachmentUploadable,
attachment models.Attachmentable, attachment models.Attachmentable,
) error { ) 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. // Reference attachments that are inline() do not need to be recreated. The contents are part of the body.
if attachmentType == models.REFERENCE_ATTACHMENTTYPE && if attachmentType == models.REFERENCE_ATTACHMENTTYPE && ptr.Val(attachment.GetIsInline()) {
attachment.GetIsInline() != nil && *attachment.GetIsInline() { logger.Ctx(ctx).Debug("skip uploading inline reference attachment: ", ptr.Val(attachment.GetName()))
logger.Ctx(ctx).Debugf("skip uploading inline reference attachment: ", *attachment.GetName())
return nil return nil
} }
// item Attachments to be skipped until the completion of Issue #2353 // item Attachments to be skipped until the completion of Issue #2353
if attachmentType == models.ITEM_ATTACHMENTTYPE { if attachmentType == models.ITEM_ATTACHMENTTYPE {
prev := attachment a, err := support.ToItemAttachment(attachment)
attachment, err = support.ToItemAttachment(attachment)
if err != nil { if err != nil {
name := ptr.Val(prev.GetName()) logger.Ctx(ctx).
msg := "item attachment restore not supported for this type. skipping upload." With("err", err).
Infow("item attachment restore not supported for this type. skipping upload.", clues.InErr(err).Slice()...)
// 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()),
)
return nil return nil
} }
attachment = a
} }
// For Item/Reference attachments *or* file attachments < 3MB, use the attachments endpoint // For Item/Reference attachments *or* file attachments < 3MB, use the attachments endpoint
if attachmentType != models.FILE_ATTACHMENTTYPE || *attachment.GetSize() < largeAttachmentSize { if attachmentType != models.FILE_ATTACHMENTTYPE || ptr.Val(attachment.GetSize()) < largeAttachmentSize {
err := uploader.uploadSmallAttachment(ctx, attachment) return uploader.uploadSmallAttachment(ctx, attachment)
return err
} }
return uploadLargeAttachment(ctx, uploader, attachment) return uploadLargeAttachment(ctx, uploader, attachment)
@ -93,7 +90,9 @@ func uploadAttachment(
// uploadLargeAttachment will upload the specified attachment by creating an upload session and // uploadLargeAttachment will upload the specified attachment by creating an upload session and
// doing a chunked upload // doing a chunked upload
func uploadLargeAttachment(ctx context.Context, uploader attachmentUploadable, func uploadLargeAttachment(
ctx context.Context,
uploader attachmentUploadable,
attachment models.Attachmentable, attachment models.Attachmentable,
) error { ) error {
ab := attachmentBytes(attachment) ab := attachmentBytes(attachment)

View File

@ -3,12 +3,11 @@ package exchange
import ( import (
"context" "context"
"github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
msusers "github.com/microsoftgraph/msgraph-sdk-go/users" 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/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 // 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(). Attachments().
Post(ctx, attach, nil) Post(ctx, attach, nil)
if err != nil { if err != nil {
return errors.Wrap(err, support.ConnectorStackErrorTrace(err)) return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
return nil return nil
@ -69,12 +68,7 @@ func (mau *mailAttachmentUploader) uploadSession(
CreateUploadSession(). CreateUploadSession().
Post(ctx, session, nil) Post(ctx, session, nil)
if err != nil { if err != nil {
return nil, errors.Wrapf( return nil, clues.Wrap(err, "uploading mail attachment").WithClues(ctx).WithAll(graph.ErrData(err)...)
err,
"failed to create attachment upload session for item %s. details: %s",
mau.itemID,
support.ConnectorStackErrorTrace(err),
)
} }
return r, nil return r, nil
@ -100,7 +94,7 @@ func (eau *eventAttachmentUploader) uploadSmallAttachment(ctx context.Context, a
Attachments(). Attachments().
Post(ctx, attach, nil) Post(ctx, attach, nil)
if err != nil { if err != nil {
return errors.Wrap(err, support.ConnectorStackErrorTrace(err)) return clues.Stack(err).WithClues(ctx).WithAll(graph.ErrData(err)...)
} }
return nil return nil
@ -122,11 +116,7 @@ func (eau *eventAttachmentUploader) uploadSession(
CreateUploadSession(). CreateUploadSession().
Post(ctx, session, nil) Post(ctx, session, nil)
if err != nil { if err != nil {
return nil, errors.Wrapf( return nil, clues.Wrap(err, "uploading event attachment").WithClues(ctx).WithAll(graph.ErrData(err)...)
err,
"failed to create attachment upload session for event item %s. details: %s",
eau.itemID, support.ConnectorStackErrorTrace(err),
)
} }
return r, nil return r, nil

View File

@ -17,6 +17,7 @@ import (
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/account" "github.com/alcionai/corso/src/pkg/account"
"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/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -119,7 +120,8 @@ func (suite *ExchangeRestoreSuite) TestRestoreEvent() {
suite.gs, suite.gs,
control.Copy, control.Copy,
calendarID, calendarID,
userID) userID,
fault.New(true))
assert.NoError(t, err, support.ConnectorStackErrorTrace(err)) assert.NoError(t, err, support.ConnectorStackErrorTrace(err))
assert.NotNil(t, info, "event item info") assert.NotNil(t, info, "event item info")
} }
@ -346,7 +348,7 @@ func (suite *ExchangeRestoreSuite) TestRestoreExchangeObject() {
service, service,
destination, destination,
userID, userID,
) fault.New(true))
assert.NoError(t, err, support.ConnectorStackErrorTrace(err)) assert.NoError(t, err, support.ConnectorStackErrorTrace(err))
assert.NotNil(t, info, "item info was not populated") assert.NotNil(t, info, "item info was not populated")
assert.NotNil(t, deleters) assert.NotNil(t, deleters)

View File

@ -7,6 +7,7 @@ import (
"reflect" "reflect"
"runtime/trace" "runtime/trace"
"github.com/alcionai/clues"
"github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -21,6 +22,7 @@ import (
"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/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/logger" "github.com/alcionai/corso/src/pkg/logger"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -35,20 +37,21 @@ func RestoreExchangeObject(
policy control.CollisionPolicy, policy control.CollisionPolicy,
service graph.Servicer, service graph.Servicer,
destination, user string, destination, user string,
errs *fault.Errors,
) (*details.ExchangeInfo, error) { ) (*details.ExchangeInfo, error) {
if policy != control.Copy { 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 { switch category {
case path.EmailCategory: 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: case path.ContactsCategory:
return RestoreExchangeContact(ctx, bits, service, control.Copy, destination, user) return RestoreExchangeContact(ctx, bits, service, control.Copy, destination, user)
case path.EventsCategory: case path.EventsCategory:
return RestoreExchangeEvent(ctx, bits, service, control.Copy, destination, user) return RestoreExchangeEvent(ctx, bits, service, control.Copy, destination, user, errs)
default: 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) { ) (*details.ExchangeInfo, error) {
contact, err := support.CreateContactFromBytes(bits) contact, err := support.CreateContactFromBytes(bits)
if err != nil { 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) response, err := service.Client().UsersById(user).ContactFoldersById(destination).Contacts().Post(ctx, contact, nil)
if err != nil { if err != nil {
name := ptr.Val(contact.GetGivenName()) return nil, clues.Wrap(err, "uploading Contact").WithClues(ctx).WithAll(graph.ErrData(err)...)
return nil, errors.Wrap(
err,
"uploading Contact during RestoreExchangeContact: "+name+" "+
support.ConnectorStackErrorTrace(err),
)
} }
if response == nil { 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) info := api.ContactInfo(contact)
@ -103,17 +102,18 @@ func RestoreExchangeEvent(
service graph.Servicer, service graph.Servicer,
cp control.CollisionPolicy, cp control.CollisionPolicy,
destination, user string, destination, user string,
errs *fault.Errors,
) (*details.ExchangeInfo, error) { ) (*details.ExchangeInfo, error) {
event, err := support.CreateEventFromBytes(bits) event, err := support.CreateEventFromBytes(bits)
if err != nil { 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 ( var (
attached []models.Attachmentable transformedEvent = support.ToEventSimplified(event)
errs error attached []models.Attachmentable
) )
if *event.GetHasAttachments() { if *event.GetHasAttachments() {
@ -124,15 +124,11 @@ func RestoreExchangeEvent(
response, err := service.Client().UsersById(user).CalendarsById(destination).Events().Post(ctx, transformedEvent, nil) response, err := service.Client().UsersById(user).CalendarsById(destination).Events().Post(ctx, transformedEvent, nil)
if err != nil { if err != nil {
return nil, errors.Wrap(err, return nil, clues.Wrap(err, "uploading event").WithClues(ctx).WithAll(graph.ErrData(err)...)
fmt.Sprintf(
"uploading event during RestoreExchangeEvent: %s",
support.ConnectorStackErrorTrace(err)),
)
} }
if response == nil { 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{ uploader := &eventAttachmentUploader{
@ -143,25 +139,19 @@ func RestoreExchangeEvent(
} }
for _, attach := range attached { for _, attach := range attached {
if err := uploadAttachment(ctx, uploader, attach); err != nil { if errs.Err() != nil {
errs = support.WrapAndAppend(
fmt.Sprintf(
"uploading attachment for message %s: %s",
ptr.Val(transformedEvent.GetId()),
support.ConnectorStackErrorTrace(err),
),
err,
errs,
)
break break
} }
if err := uploadAttachment(ctx, uploader, attach); err != nil {
errs.Add(err)
}
} }
info := api.EventInfo(event) info := api.EventInfo(event)
info.Size = int64(len(bits)) info.Size = int64(len(bits))
return info, errs return info, errs.Err()
} }
// RestoreMailMessage utility function to place an exchange.Mail // RestoreMailMessage utility function to place an exchange.Mail
@ -176,16 +166,21 @@ func RestoreMailMessage(
service graph.Servicer, service graph.Servicer,
cp control.CollisionPolicy, cp control.CollisionPolicy,
destination, user string, destination, user string,
errs *fault.Errors,
) (*details.ExchangeInfo, error) { ) (*details.ExchangeInfo, error) {
// Creates messageable object from original bytes // Creates messageable object from original bytes
originalMessage, err := support.CreateMessageFromBytes(bits) originalMessage, err := support.CreateMessageFromBytes(bits)
if err != nil { 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) ctx = clues.Add(ctx, "item_id", ptr.Val(originalMessage.GetId()))
valueID := MailRestorePropertyTag
enableValue := RestoreCanonicalEnableValue var (
clone = support.ToMessage(originalMessage)
valueID = MailRestorePropertyTag
enableValue = RestoreCanonicalEnableValue
)
// Set Extended Properties: // Set Extended Properties:
// 1st: No transmission // 1st: No transmission
@ -219,17 +214,8 @@ func RestoreMailMessage(
clone.SetSingleValueExtendedProperties(svlep) clone.SetSingleValueExtendedProperties(svlep)
// Switch workflow based on collision policy if err := SendMailToBackStore(ctx, service, user, destination, clone, errs); err != nil {
switch cp { return nil, err
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
}
} }
info := api.MailInfo(clone) info := api.MailInfo(clone)
@ -253,28 +239,23 @@ func SendMailToBackStore(
service graph.Servicer, service graph.Servicer,
user, destination string, user, destination string,
message models.Messageable, message models.Messageable,
errs *fault.Errors,
) error { ) error {
var ( attached := message.GetAttachments()
attached []models.Attachmentable
errs error
)
// Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized // Item.Attachments --> HasAttachments doesn't always have a value populated when deserialized
attached = message.GetAttachments()
message.SetAttachments([]models.Attachmentable{}) 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 { if err != nil {
return errors.Wrap(err, return clues.Wrap(err, "restoring mail").WithClues(ctx).WithAll(graph.ErrData(err)...)
user+": failure sendMailAPI: Dest: "+destination+" Details: "+support.ConnectorStackErrorTrace(err),
)
} }
if sentMessage == nil { if response == nil {
return errors.New("message not Sent: blocked by server") return clues.New("nil response from post").WithClues(ctx)
} }
id := *sentMessage.GetId() id := ptr.Val(response.GetId())
uploader := &mailAttachmentUploader{ uploader := &mailAttachmentUploader{
userID: user, userID: user,
@ -284,29 +265,28 @@ func SendMailToBackStore(
} }
for _, attachment := range attached { for _, attachment := range attached {
if errs.Err() != nil {
break
}
if err := uploadAttachment(ctx, uploader, attachment); err != nil { if err := uploadAttachment(ctx, uploader, attachment); err != nil {
if ptr.Val(attachment.GetOdataType()) == "#microsoft.graph.itemAttachment" { if ptr.Val(attachment.GetOdataType()) == "#microsoft.graph.itemAttachment" {
name := ptr.Val(attachment.GetName()) name := ptr.Val(attachment.GetName())
logger.Ctx(ctx).Infow( logger.Ctx(ctx).
"item attachment upload not successful. content not accepted by M365 server", With("err", err, "attachment_name", name).
"Attachment Name", name) Infow("mail upload failed", clues.InErr(err).Slice()...)
continue continue
} }
errs = support.WrapAndAppend( errs.Add(errors.Wrap(err, "uploading mail attachment"))
fmt.Sprintf("uploading attachment for message %s: %s",
id, support.ConnectorStackErrorTrace(err)),
err,
errs,
)
break break
} }
} }
return errs return errs.Err()
} }
// RestoreExchangeDataCollections restores M365 objects in data.RestoreCollection to MSFT // RestoreExchangeDataCollections restores M365 objects in data.RestoreCollection to MSFT
@ -319,22 +299,25 @@ func RestoreExchangeDataCollections(
dest control.RestoreDestination, dest control.RestoreDestination,
dcs []data.RestoreCollection, dcs []data.RestoreCollection,
deets *details.Builder, deets *details.Builder,
errs *fault.Errors,
) (*support.ConnectorOperationStatus, error) { ) (*support.ConnectorOperationStatus, error) {
var ( var (
// map of caches... but not yet...
directoryCaches = make(map[string]map[path.CategoryType]graph.ContainerResolver) directoryCaches = make(map[string]map[path.CategoryType]graph.ContainerResolver)
metrics support.CollectionMetrics metrics support.CollectionMetrics
errs error userID string
// TODO policy to be updated from external source after completion of refactoring // TODO policy to be updated from external source after completion of refactoring
policy = control.Copy policy = control.Copy
) )
errUpdater := func(id string, err error) { if len(dcs) > 0 {
errs = support.WrapAndAppend(id, err, errs) userID = dcs[0].FullPath().ResourceOwner()
ctx = clues.Add(ctx, "resource_owner", userID) // TODO: pii
} }
for _, dc := range dcs { for _, dc := range dcs {
userID := dc.FullPath().ResourceOwner() if errs.Err() != nil {
return nil, errs.Err()
}
userCaches := directoryCaches[userID] userCaches := directoryCaches[userID]
if userCaches == nil { if userCaches == nil {
@ -349,11 +332,11 @@ func RestoreExchangeDataCollections(
dest.ContainerName, dest.ContainerName,
userCaches) userCaches)
if err != nil { if err != nil {
errs = support.WrapAndAppend(dc.FullPath().ShortRef(), err, errs) errs.Add(clues.Wrap(err, "creating destination").WithClues(ctx))
continue continue
} }
temp, canceled := restoreCollection(ctx, gs, dc, containerID, policy, deets, errUpdater) temp, canceled := restoreCollection(ctx, gs, dc, containerID, policy, deets, errs)
metrics.Combine(temp) metrics.Combine(temp)
@ -362,14 +345,15 @@ func RestoreExchangeDataCollections(
} }
} }
status := support.CreateStatus(ctx, status := support.CreateStatus(
ctx,
support.Restore, support.Restore,
len(dcs), len(dcs),
metrics, metrics,
errs, errs.Err(),
dest.ContainerName) dest.ContainerName)
return status, errs return status, errs.Err()
} }
// restoreCollection handles restoration of an individual collection. // restoreCollection handles restoration of an individual collection.
@ -380,7 +364,7 @@ func restoreCollection(
folderID string, folderID string,
policy control.CollisionPolicy, policy control.CollisionPolicy,
deets *details.Builder, deets *details.Builder,
errUpdater func(string, error), errs *fault.Errors,
) (support.CollectionMetrics, bool) { ) (support.CollectionMetrics, bool) {
ctx, end := D.Span(ctx, "gc:exchange:restoreCollection", D.Label("path", dc.FullPath())) ctx, end := D.Span(ctx, "gc:exchange:restoreCollection", D.Label("path", dc.FullPath()))
defer end() defer end()
@ -394,6 +378,12 @@ func restoreCollection(
user = directory.ResourceOwner() user = directory.ResourceOwner()
) )
ctx = clues.AddAll(
ctx,
"full_path", directory,
"service", service,
"category", category)
colProgress, closer := observe.CollectionProgress( colProgress, closer := observe.CollectionProgress(
ctx, ctx,
category.String(), category.String(),
@ -405,34 +395,39 @@ func restoreCollection(
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
errUpdater("context cancelled", ctx.Err()) errs.Add(clues.Wrap(ctx.Err(), "context cancelled").WithClues(ctx))
return metrics, true return metrics, true
case itemData, ok := <-items: case itemData, ok := <-items:
if !ok { if !ok || errs.Err() != nil {
return metrics, false 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{} buf := &bytes.Buffer{}
_, err := buf.ReadFrom(itemData.ToReader()) _, err := buf.ReadFrom(itemData.ToReader())
if err != nil { if err != nil {
errUpdater(itemData.UUID()+": byteReadError during RestoreDataCollection", err) errs.Add(clues.Wrap(err, "reading item bytes").WithClues(ictx))
continue continue
} }
byteArray := buf.Bytes() 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 { if err != nil {
// More information to be here errs.Add(err)
errUpdater(
itemData.UUID()+": failed to upload RestoreExchangeObject: "+service.String()+"-"+category.String(),
err)
continue continue
} }
@ -441,7 +436,7 @@ func restoreCollection(
itemPath, err := dc.FullPath().Append(itemData.UUID(), true) itemPath, err := dc.FullPath().Append(itemData.UUID(), true)
if err != nil { 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 continue
} }
@ -487,7 +482,7 @@ func CreateContainerDestination(
// TODO(rkeepers): pass the api client into this func, rather than generating one. // TODO(rkeepers): pass the api client into this func, rather than generating one.
ac, err := api.NewClient(creds) ac, err := api.NewClient(creds)
if err != nil { if err != nil {
return "", err return "", clues.Stack(err).WithClues(ctx)
} }
switch category { switch category {
@ -569,7 +564,7 @@ func CreateContainerDestination(
newCache) newCache)
default: 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 folderID := rootFolderAlias
pb := path.Builder{} pb := path.Builder{}
ctx = clues.Add(ctx, "is_new_cache", isNewCache)
for _, folder := range folders { for _, folder := range folders {
pb = *pb.Append(folder) pb = *pb.Append(folder)
@ -647,6 +644,8 @@ func establishContactsRestoreLocation(
return cached, nil return cached, nil
} }
ctx = clues.Add(ctx, "is_new_cache", isNewCache)
temp, err := ac.Contacts().CreateContactFolder(ctx, user, folders[0]) temp, err := ac.Contacts().CreateContactFolder(ctx, user, folders[0])
if err != nil { if err != nil {
return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err))
@ -681,6 +680,8 @@ func establishEventsRestoreLocation(
return cached, nil return cached, nil
} }
ctx = clues.Add(ctx, "is_new_cache", isNewCache)
temp, err := ac.Events().CreateCalendar(ctx, user, folders[0]) temp, err := ac.Events().CreateCalendar(ctx, user, folders[0])
if err != nil { if err != nil {
return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err)) return "", errors.Wrap(err, support.ConnectorStackErrorTrace(err))

View File

@ -250,7 +250,7 @@ func (suite *GraphConnectorIntegrationSuite) TestRestoreFailsBadService() {
ToggleFeatures: control.Toggles{EnablePermissionsBackup: true}, ToggleFeatures: control.Toggles{EnablePermissionsBackup: true},
}, },
nil, nil,
) fault.New(true))
assert.Error(t, err) assert.Error(t, err)
assert.NotNil(t, deets) assert.NotNil(t, deets)
@ -327,7 +327,7 @@ func (suite *GraphConnectorIntegrationSuite) TestEmptyCollections() {
ToggleFeatures: control.Toggles{EnablePermissionsBackup: true}, ToggleFeatures: control.Toggles{EnablePermissionsBackup: true},
}, },
test.col, test.col,
) fault.New(true))
require.NoError(t, err) require.NoError(t, err)
assert.NotNil(t, deets) assert.NotNil(t, deets)
@ -422,7 +422,7 @@ func runRestoreBackupTest(
dest, dest,
opts, opts,
collections, collections,
) fault.New(true))
require.NoError(t, err) require.NoError(t, err)
assert.NotNil(t, deets) assert.NotNil(t, deets)
@ -544,7 +544,7 @@ func runRestoreBackupTestVersion0(
dest, dest,
opts, opts,
collections, collections,
) fault.New(true))
require.NoError(t, err) require.NoError(t, err)
assert.NotNil(t, deets) assert.NotNil(t, deets)
@ -1515,7 +1515,7 @@ func (suite *GraphConnectorIntegrationSuite) TestMultiFolderBackupDifferentNames
ToggleFeatures: control.Toggles{EnablePermissionsBackup: true}, ToggleFeatures: control.Toggles{EnablePermissionsBackup: true},
}, },
collections, collections,
) fault.New(true))
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, deets) require.NotNil(t, deets)

View File

@ -63,6 +63,7 @@ func CreateStatus(
} }
hasErrors := err != nil hasErrors := err != nil
// TODO(keeprs): remove
numErr := GetNumberOfErrors(err) numErr := GetNumberOfErrors(err)
status := ConnectorOperationStatus{ status := ConnectorOperationStatus{

View File

@ -347,7 +347,8 @@ func generateContainerOfItems(
sel, sel,
dest, dest,
control.Options{RestorePermissions: true}, control.Options{RestorePermissions: true},
dataColls) dataColls,
fault.New(true))
require.NoError(t, err) require.NoError(t, err)
return deets return deets

View File

@ -258,7 +258,8 @@ func (op *RestoreOperation) do(
op.Selectors, op.Selectors,
op.Destination, op.Destination,
op.Options, op.Options,
dcs) dcs,
op.Errors)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "restoring collections") return nil, errors.Wrap(err, "restoring collections")
} }