update operation/backup errs (#2239)

## Description

Begins updating operations/backup with the new
error handling procedures.  For backwards
compatibility, errors are currently duplicated in
the old stats.Errs and the new Errors struct.

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

- [x]  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-01-31 15:25:02 -07:00 committed by GitHub
parent f3b2e9a632
commit 070b8fddee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 374 additions and 318 deletions

View File

@ -144,6 +144,7 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
err = op.persistResults(startTime, &opStats) err = op.persistResults(startTime, &opStats)
if err != nil { if err != nil {
op.Errors.Fail(errors.Wrap(err, "persisting backup results"))
return return
} }
@ -153,7 +154,8 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
opStats.k.SnapshotID, opStats.k.SnapshotID,
backupDetails.Details()) backupDetails.Details())
if err != nil { if err != nil {
opStats.writeErr = err op.Errors.Fail(errors.Wrap(err, "persisting backup"))
opStats.writeErr = op.Errors.Err()
} }
}() }()
@ -164,21 +166,27 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
reasons, reasons,
tenantID, tenantID,
uib, uib,
) op.Errors)
if err != nil { if err != nil {
opStats.readErr = errors.Wrap(err, "connecting to M365") op.Errors.Fail(errors.Wrap(err, "collecting manifest heuristics"))
opStats.readErr = op.Errors.Err()
return opStats.readErr return opStats.readErr
} }
gc, err := connectToM365(ctx, op.Selectors, op.account) gc, err := connectToM365(ctx, op.Selectors, op.account)
if err != nil { if err != nil {
opStats.readErr = errors.Wrap(err, "connecting to M365") op.Errors.Fail(errors.Wrap(err, "connecting to m365"))
opStats.readErr = op.Errors.Err()
return opStats.readErr return opStats.readErr
} }
cs, err := produceBackupDataCollections(ctx, gc, op.Selectors, mdColls, op.Options) cs, err := produceBackupDataCollections(ctx, gc, op.Selectors, mdColls, op.Options)
if err != nil { if err != nil {
opStats.readErr = errors.Wrap(err, "retrieving data to backup") op.Errors.Fail(errors.Wrap(err, "retrieving data to backup"))
opStats.readErr = op.Errors.Err()
return opStats.readErr return opStats.readErr
} }
@ -194,7 +202,9 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
op.Results.BackupID, op.Results.BackupID,
uib && canUseMetaData) uib && canUseMetaData)
if err != nil { if err != nil {
opStats.writeErr = errors.Wrap(err, "backing up service data") op.Errors.Fail(errors.Wrap(err, "backing up service data"))
opStats.writeErr = op.Errors.Err()
return opStats.writeErr return opStats.writeErr
} }
@ -211,12 +221,15 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
toMerge, toMerge,
backupDetails, backupDetails,
); err != nil { ); err != nil {
opStats.writeErr = errors.Wrap(err, "merging backup details") op.Errors.Fail(errors.Wrap(err, "merging backup details"))
opStats.writeErr = op.Errors.Err()
return opStats.writeErr return opStats.writeErr
} }
opStats.gc = gc.AwaitStatus() opStats.gc = gc.AwaitStatus()
// TODO(keepers): remove when fault.Errors handles all iterable error aggregation.
if opStats.gc.ErrorCount > 0 { if opStats.gc.ErrorCount > 0 {
merr := multierror.Append(opStats.readErr, errors.Wrap(opStats.gc.Err, "retrieving data")) merr := multierror.Append(opStats.readErr, errors.Wrap(opStats.gc.Err, "retrieving data"))
opStats.readErr = merr.ErrorOrNil() opStats.readErr = merr.ErrorOrNil()
@ -307,7 +320,9 @@ func selectorToReasons(sel selectors.Selector) []kopia.Reason {
return reasons return reasons
} }
func builderFromReason(tenant string, r kopia.Reason) (*path.Builder, error) { func builderFromReason(ctx context.Context, tenant string, r kopia.Reason) (*path.Builder, error) {
ctx = clues.Add(ctx, "category", r.Category.String())
// This is hacky, but we want the path package to format the path the right // This is hacky, but we want the path package to format the path the right
// way (e.x. proper order for service, category, etc), but we don't care about // way (e.x. proper order for service, category, etc), but we don't care about
// the folders after the prefix. // the folders after the prefix.
@ -319,12 +334,7 @@ func builderFromReason(tenant string, r kopia.Reason) (*path.Builder, error) {
false, false,
) )
if err != nil { if err != nil {
return nil, errors.Wrapf( return nil, clues.Wrap(err, "building path").WithMap(clues.Values(ctx))
err,
"building path for service %s category %s",
r.Service.String(),
r.Category.String(),
)
} }
return p.ToBuilder().Dir(), nil return p.ToBuilder().Dir(), nil
@ -367,7 +377,7 @@ func consumeBackupDataCollections(
categories := map[string]struct{}{} categories := map[string]struct{}{}
for _, reason := range m.Reasons { for _, reason := range m.Reasons {
pb, err := builderFromReason(tenantID, reason) pb, err := builderFromReason(ctx, tenantID, reason)
if err != nil { if err != nil {
return nil, nil, nil, errors.Wrap(err, "getting subtree paths for bases") return nil, nil, nil, errors.Wrap(err, "getting subtree paths for bases")
} }
@ -461,6 +471,8 @@ func mergeDetails(
var addedEntries int var addedEntries int
for _, man := range mans { for _, man := range mans {
mctx := clues.Add(ctx, "manifest_id", man.ID)
// For now skip snapshots that aren't complete. We will need to revisit this // For now skip snapshots that aren't complete. We will need to revisit this
// when we tackle restartability. // when we tackle restartability.
if len(man.IncompleteReason) > 0 { if len(man.IncompleteReason) > 0 {
@ -469,9 +481,11 @@ func mergeDetails(
bID, ok := man.GetTag(kopia.TagBackupID) bID, ok := man.GetTag(kopia.TagBackupID)
if !ok { if !ok {
return errors.Errorf("no backup ID in snapshot manifest with ID %s", man.ID) return clues.New("no backup ID in snapshot manifest").WithMap(clues.Values(mctx))
} }
mctx = clues.Add(mctx, "manifest_backup_id", bID)
_, baseDeets, err := getBackupAndDetailsFromID( _, baseDeets, err := getBackupAndDetailsFromID(
ctx, ctx,
model.StableID(bID), model.StableID(bID),
@ -479,18 +493,15 @@ func mergeDetails(
detailsStore, detailsStore,
) )
if err != nil { if err != nil {
return errors.Wrapf(err, "backup fetching base details for backup %s", bID) return clues.New("fetching base details for backup").WithMap(clues.Values(mctx))
} }
for _, entry := range baseDeets.Items() { for _, entry := range baseDeets.Items() {
rr, err := path.FromDataLayerPath(entry.RepoRef, true) rr, err := path.FromDataLayerPath(entry.RepoRef, true)
if err != nil { if err != nil {
return errors.Wrapf( return clues.New("parsing base item info path").
err, WithMap(clues.Values(mctx)).
"parsing base item info path %s in backup %s", With("repo_ref", entry.RepoRef) // todo: pii
entry.RepoRef,
bID,
)
} }
// Although this base has an entry it may not be the most recent. Check // Although this base has an entry it may not be the most recent. Check
@ -513,11 +524,7 @@ func mergeDetails(
// Fixup paths in the item. // Fixup paths in the item.
item := entry.ItemInfo item := entry.ItemInfo
if err := details.UpdateItem(&item, newPath); err != nil { if err := details.UpdateItem(&item, newPath); err != nil {
return errors.Wrapf( return clues.New("updating item details").WithMap(clues.Values(mctx))
err,
"updating item info for entry from backup %s",
bID,
)
} }
// TODO(ashmrtn): This may need updated if we start using this merge // TODO(ashmrtn): This may need updated if we start using this merge
@ -542,11 +549,9 @@ func mergeDetails(
} }
if addedEntries != len(shortRefsFromPrevBackup) { if addedEntries != len(shortRefsFromPrevBackup) {
return errors.Errorf( return clues.New("incomplete migration of backup details").
"incomplete migration of backup details: found %v of %v expected items", WithMap(clues.Values(ctx)).
addedEntries, WithAll("item_count", addedEntries, "expected_item_count", len(shortRefsFromPrevBackup))
len(shortRefsFromPrevBackup),
)
} }
return nil return nil
@ -568,6 +573,7 @@ func (op *BackupOperation) persistResults(
if opStats.readErr != nil || opStats.writeErr != nil { if opStats.readErr != nil || opStats.writeErr != nil {
op.Status = Failed op.Status = Failed
// TODO(keepers): replace with fault.Errors handling.
return multierror.Append( return multierror.Append(
errors.New("errors prevented the operation from processing"), errors.New("errors prevented the operation from processing"),
opStats.readErr, opStats.readErr,
@ -594,15 +600,18 @@ func (op *BackupOperation) createBackupModels(
snapID string, snapID string,
backupDetails *details.Details, backupDetails *details.Details,
) error { ) error {
ctx = clues.Add(ctx, "snapshot_id", snapID)
if backupDetails == nil { if backupDetails == nil {
return errors.New("no backup details to record") return clues.New("no backup details to record").WithMap(clues.Values(ctx))
} }
detailsID, err := detailsStore.WriteBackupDetails(ctx, backupDetails) detailsID, err := detailsStore.WriteBackupDetails(ctx, backupDetails)
if err != nil { if err != nil {
return errors.Wrap(err, "creating backupdetails model") return clues.Wrap(err, "creating backupDetails model").WithMap(clues.Values(ctx))
} }
ctx = clues.Add(ctx, "details_id", detailsID)
b := backup.New( b := backup.New(
snapID, detailsID, op.Status.String(), snapID, detailsID, op.Status.String(),
op.Results.BackupID, op.Results.BackupID,
@ -612,9 +621,8 @@ func (op *BackupOperation) createBackupModels(
op.Errors, op.Errors,
) )
err = op.store.Put(ctx, model.BackupSchema, b) if err = op.store.Put(ctx, model.BackupSchema, b); err != nil {
if err != nil { return clues.Wrap(err, "creating backup model").WithMap(clues.Values(ctx))
return errors.Wrap(err, "creating backup model")
} }
dur := op.Results.CompletedAt.Sub(op.Results.StartedAt) dur := op.Results.CompletedAt.Sub(op.Results.StartedAt)

View File

@ -432,258 +432,6 @@ func (suite *BackupOpSuite) TestBackupOperation_PersistResults() {
} }
} }
func (suite *BackupOpSuite) TestBackupOperation_VerifyDistinctBases() {
const user = "a-user"
table := []struct {
name string
input []*kopia.ManifestEntry
errCheck assert.ErrorAssertionFunc
}{
{
name: "SingleManifestMultipleReasons",
input: []*kopia.ManifestEntry{
{
Manifest: &snapshot.Manifest{
ID: "id1",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EventsCategory,
},
},
},
},
errCheck: assert.NoError,
},
{
name: "MultipleManifestsDistinctReason",
input: []*kopia.ManifestEntry{
{
Manifest: &snapshot.Manifest{
ID: "id1",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
{
Manifest: &snapshot.Manifest{
ID: "id2",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EventsCategory,
},
},
},
},
errCheck: assert.NoError,
},
{
name: "MultipleManifestsSameReason",
input: []*kopia.ManifestEntry{
{
Manifest: &snapshot.Manifest{
ID: "id1",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
{
Manifest: &snapshot.Manifest{
ID: "id2",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
},
errCheck: assert.Error,
},
{
name: "MultipleManifestsSameReasonOneIncomplete",
input: []*kopia.ManifestEntry{
{
Manifest: &snapshot.Manifest{
ID: "id1",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
{
Manifest: &snapshot.Manifest{
ID: "id2",
IncompleteReason: "checkpoint",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
},
errCheck: assert.NoError,
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
test.errCheck(t, verifyDistinctBases(test.input))
})
}
}
func (suite *BackupOpSuite) TestBackupOperation_CollectMetadata() {
var (
tenant = "a-tenant"
resourceOwner = "a-user"
fileNames = []string{
"delta",
"paths",
}
emailDeltaPath = makeMetadataPath(
suite.T(),
tenant,
path.ExchangeService,
resourceOwner,
path.EmailCategory,
fileNames[0],
)
emailPathsPath = makeMetadataPath(
suite.T(),
tenant,
path.ExchangeService,
resourceOwner,
path.EmailCategory,
fileNames[1],
)
contactsDeltaPath = makeMetadataPath(
suite.T(),
tenant,
path.ExchangeService,
resourceOwner,
path.ContactsCategory,
fileNames[0],
)
contactsPathsPath = makeMetadataPath(
suite.T(),
tenant,
path.ExchangeService,
resourceOwner,
path.ContactsCategory,
fileNames[1],
)
)
table := []struct {
name string
inputMan *kopia.ManifestEntry
inputFiles []string
expected []path.Path
}{
{
name: "SingleReasonSingleFile",
inputMan: &kopia.ManifestEntry{
Manifest: &snapshot.Manifest{},
Reasons: []kopia.Reason{
{
ResourceOwner: resourceOwner,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
inputFiles: []string{fileNames[0]},
expected: []path.Path{emailDeltaPath},
},
{
name: "SingleReasonMultipleFiles",
inputMan: &kopia.ManifestEntry{
Manifest: &snapshot.Manifest{},
Reasons: []kopia.Reason{
{
ResourceOwner: resourceOwner,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
inputFiles: fileNames,
expected: []path.Path{emailDeltaPath, emailPathsPath},
},
{
name: "MultipleReasonsMultipleFiles",
inputMan: &kopia.ManifestEntry{
Manifest: &snapshot.Manifest{},
Reasons: []kopia.Reason{
{
ResourceOwner: resourceOwner,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
{
ResourceOwner: resourceOwner,
Service: path.ExchangeService,
Category: path.ContactsCategory,
},
},
},
inputFiles: fileNames,
expected: []path.Path{
emailDeltaPath,
emailPathsPath,
contactsDeltaPath,
contactsPathsPath,
},
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
ctx, flush := tester.NewContext()
defer flush()
mr := &mockRestorer{}
_, err := collectMetadata(ctx, mr, test.inputMan, test.inputFiles, tenant)
assert.NoError(t, err)
checkPaths(t, test.expected, mr.gotPaths)
})
}
}
func (suite *BackupOpSuite) TestBackupOperation_ConsumeBackupDataCollections_Paths() { func (suite *BackupOpSuite) TestBackupOperation_ConsumeBackupDataCollections_Paths() {
var ( var (
tenant = "a-tenant" tenant = "a-tenant"

View File

@ -3,7 +3,7 @@ package operations
import ( import (
"context" "context"
multierror "github.com/hashicorp/go-multierror" "github.com/alcionai/clues"
"github.com/kopia/kopia/repo/manifest" "github.com/kopia/kopia/repo/manifest"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -12,6 +12,7 @@ import (
"github.com/alcionai/corso/src/internal/kopia" "github.com/alcionai/corso/src/internal/kopia"
"github.com/alcionai/corso/src/internal/model" "github.com/alcionai/corso/src/internal/model"
"github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/backup"
"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"
) )
@ -44,6 +45,7 @@ func produceManifestsAndMetadata(
reasons []kopia.Reason, reasons []kopia.Reason,
tenantID string, tenantID string,
getMetadata bool, getMetadata bool,
errs fault.Adder,
) ([]*kopia.ManifestEntry, []data.Collection, bool, error) { ) ([]*kopia.ManifestEntry, []data.Collection, bool, error) {
var ( var (
metadataFiles = graph.AllMetadataFileNames() metadataFiles = graph.AllMetadataFileNames()
@ -68,12 +70,10 @@ func produceManifestsAndMetadata(
// //
// TODO(ashmrtn): This may need updating if we start sourcing item backup // TODO(ashmrtn): This may need updating if we start sourcing item backup
// details from previous snapshots when using kopia-assisted incrementals. // details from previous snapshots when using kopia-assisted incrementals.
if err := verifyDistinctBases(ms); err != nil { if err := verifyDistinctBases(ctx, ms, errs); err != nil {
logger.Ctx(ctx).Warnw( logger.Ctx(ctx).With("error", err).Infow(
"base snapshot collision, falling back to full backup", "base snapshot collision, falling back to full backup",
"error", clues.Slice(ctx)...)
err,
)
return ms, nil, false, nil return ms, nil, false, nil
} }
@ -83,40 +83,41 @@ func produceManifestsAndMetadata(
continue continue
} }
mctx := clues.Add(ctx, "manifest_id", man.ID)
bID, ok := man.GetTag(kopia.TagBackupID) bID, ok := man.GetTag(kopia.TagBackupID)
if !ok { if !ok {
return nil, nil, false, errors.New("snapshot manifest missing backup ID") err = clues.New("snapshot manifest missing backup ID").WithMap(clues.Values(mctx))
return nil, nil, false, err
} }
dID, _, err := gdi.GetDetailsIDFromBackupID(ctx, model.StableID(bID)) mctx = clues.Add(mctx, "manifest_backup_id", man.ID)
dID, _, err := gdi.GetDetailsIDFromBackupID(mctx, model.StableID(bID))
if err != nil { if err != nil {
// if no backup exists for any of the complete manifests, we want // if no backup exists for any of the complete manifests, we want
// to fall back to a complete backup. // to fall back to a complete backup.
if errors.Is(err, kopia.ErrNotFound) { if errors.Is(err, kopia.ErrNotFound) {
logger.Ctx(ctx).Infow( logger.Ctx(ctx).Infow("backup missing, falling back to full backup", clues.Slice(mctx)...)
"backup missing, falling back to full backup",
"backup_id", bID)
return ms, nil, false, nil return ms, nil, false, nil
} }
return nil, nil, false, errors.Wrap(err, "retrieving prior backup data") return nil, nil, false, errors.Wrap(err, "retrieving prior backup data")
} }
mctx = clues.Add(mctx, "manifest_details_id", dID)
// if no detailsID exists for any of the complete manifests, we want // if no detailsID exists for any of the complete manifests, we want
// to fall back to a complete backup. This is a temporary prevention // to fall back to a complete backup. This is a temporary prevention
// mechanism to keep backups from falling into a perpetually bad state. // mechanism to keep backups from falling into a perpetually bad state.
// This makes an assumption that the ID points to a populated set of // This makes an assumption that the ID points to a populated set of
// details; we aren't doing the work to look them up. // details; we aren't doing the work to look them up.
if len(dID) == 0 { if len(dID) == 0 {
logger.Ctx(ctx).Infow( logger.Ctx(ctx).Infow("backup missing details ID, falling back to full backup", clues.Slice(mctx)...)
"backup missing details ID, falling back to full backup",
"backup_id", bID)
return ms, nil, false, nil return ms, nil, false, nil
} }
colls, err := collectMetadata(ctx, mr, man, metadataFiles, tenantID) colls, err := collectMetadata(mctx, mr, man, metadataFiles, tenantID)
if err != nil && !errors.Is(err, kopia.ErrNotFound) { if err != nil && !errors.Is(err, kopia.ErrNotFound) {
// prior metadata isn't guaranteed to exist. // prior metadata isn't guaranteed to exist.
// if it doesn't, we'll just have to do a // if it doesn't, we'll just have to do a
@ -134,9 +135,9 @@ func produceManifestsAndMetadata(
// of manifests, that each manifest's Reason (owner, service, category) is only // of manifests, that each manifest's Reason (owner, service, category) is only
// included once. If a reason is duplicated by any two manifests, an error is // included once. If a reason is duplicated by any two manifests, an error is
// returned. // returned.
func verifyDistinctBases(mans []*kopia.ManifestEntry) error { func verifyDistinctBases(ctx context.Context, mans []*kopia.ManifestEntry, errs fault.Adder) error {
var ( var (
errs *multierror.Error failed bool
reasons = map[string]manifest.ID{} reasons = map[string]manifest.ID{}
) )
@ -155,10 +156,11 @@ func verifyDistinctBases(mans []*kopia.ManifestEntry) error {
reasonKey := reason.ResourceOwner + reason.Service.String() + reason.Category.String() reasonKey := reason.ResourceOwner + reason.Service.String() + reason.Category.String()
if b, ok := reasons[reasonKey]; ok { if b, ok := reasons[reasonKey]; ok {
errs = multierror.Append(errs, errors.Errorf( failed = true
"multiple base snapshots source data for %s %s. IDs: %s, %s",
reason.Service, reason.Category, b, man.ID, errs.Add(clues.New("manifests have overlapping reasons").
)) WithMap(clues.Values(ctx)).
With("other_manifest_id", b))
continue continue
} }
@ -167,7 +169,11 @@ func verifyDistinctBases(mans []*kopia.ManifestEntry) error {
} }
} }
return errs.ErrorOrNil() if failed {
return clues.New("multiple base snapshots qualify").WithMap(clues.Values(ctx))
}
return nil
} }
// collectMetadata retrieves all metadata files associated with the manifest. // collectMetadata retrieves all metadata files associated with the manifest.
@ -191,7 +197,9 @@ func collectMetadata(
reason.Category, reason.Category,
true) true)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "building metadata path") return nil, clues.
Wrap(err, "building metadata path").
WithAll("metadata_file", fn, "category", reason.Category)
} }
paths = append(paths, p) paths = append(paths, p)

View File

@ -14,6 +14,7 @@ import (
"github.com/alcionai/corso/src/internal/model" "github.com/alcionai/corso/src/internal/model"
"github.com/alcionai/corso/src/internal/tester" "github.com/alcionai/corso/src/internal/tester"
"github.com/alcionai/corso/src/pkg/backup" "github.com/alcionai/corso/src/pkg/backup"
"github.com/alcionai/corso/src/pkg/fault/mock"
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
@ -400,7 +401,10 @@ func (suite *OperationsManifestsUnitSuite) TestVerifyDistinctBases() {
} }
for _, test := range table { for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) { suite.T().Run(test.name, func(t *testing.T) {
err := verifyDistinctBases(test.mans) ctx, flush := tester.NewContext()
defer flush()
err := verifyDistinctBases(ctx, test.mans, mock.NewAdder())
test.expect(t, err) test.expect(t, err)
}) })
} }
@ -646,6 +650,8 @@ func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata() {
ctx, flush := tester.NewContext() ctx, flush := tester.NewContext()
defer flush() defer flush()
ma := mock.NewAdder()
mans, dcs, b, err := produceManifestsAndMetadata( mans, dcs, b, err := produceManifestsAndMetadata(
ctx, ctx,
&test.mr, &test.mr,
@ -653,7 +659,7 @@ func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata() {
test.reasons, test.reasons,
tid, tid,
test.getMeta, test.getMeta,
) ma)
test.assertErr(t, err) test.assertErr(t, err)
test.assertB(t, b) test.assertB(t, b)
@ -683,3 +689,270 @@ func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata() {
}) })
} }
} }
// ---------------------------------------------------------------------------
// older tests
// ---------------------------------------------------------------------------
type BackupManifestSuite struct {
suite.Suite
}
func TestBackupManifestSuite(t *testing.T) {
suite.Run(t, new(BackupOpSuite))
}
func (suite *BackupManifestSuite) TestBackupOperation_VerifyDistinctBases() {
const user = "a-user"
table := []struct {
name string
input []*kopia.ManifestEntry
errCheck assert.ErrorAssertionFunc
}{
{
name: "SingleManifestMultipleReasons",
input: []*kopia.ManifestEntry{
{
Manifest: &snapshot.Manifest{
ID: "id1",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EventsCategory,
},
},
},
},
errCheck: assert.NoError,
},
{
name: "MultipleManifestsDistinctReason",
input: []*kopia.ManifestEntry{
{
Manifest: &snapshot.Manifest{
ID: "id1",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
{
Manifest: &snapshot.Manifest{
ID: "id2",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EventsCategory,
},
},
},
},
errCheck: assert.NoError,
},
{
name: "MultipleManifestsSameReason",
input: []*kopia.ManifestEntry{
{
Manifest: &snapshot.Manifest{
ID: "id1",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
{
Manifest: &snapshot.Manifest{
ID: "id2",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
},
errCheck: assert.Error,
},
{
name: "MultipleManifestsSameReasonOneIncomplete",
input: []*kopia.ManifestEntry{
{
Manifest: &snapshot.Manifest{
ID: "id1",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
{
Manifest: &snapshot.Manifest{
ID: "id2",
IncompleteReason: "checkpoint",
},
Reasons: []kopia.Reason{
{
ResourceOwner: user,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
},
errCheck: assert.NoError,
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
ctx, flush := tester.NewContext()
defer flush()
test.errCheck(t, verifyDistinctBases(ctx, test.input, mock.NewAdder()))
})
}
}
func (suite *BackupManifestSuite) TestBackupOperation_CollectMetadata() {
var (
tenant = "a-tenant"
resourceOwner = "a-user"
fileNames = []string{
"delta",
"paths",
}
emailDeltaPath = makeMetadataPath(
suite.T(),
tenant,
path.ExchangeService,
resourceOwner,
path.EmailCategory,
fileNames[0],
)
emailPathsPath = makeMetadataPath(
suite.T(),
tenant,
path.ExchangeService,
resourceOwner,
path.EmailCategory,
fileNames[1],
)
contactsDeltaPath = makeMetadataPath(
suite.T(),
tenant,
path.ExchangeService,
resourceOwner,
path.ContactsCategory,
fileNames[0],
)
contactsPathsPath = makeMetadataPath(
suite.T(),
tenant,
path.ExchangeService,
resourceOwner,
path.ContactsCategory,
fileNames[1],
)
)
table := []struct {
name string
inputMan *kopia.ManifestEntry
inputFiles []string
expected []path.Path
}{
{
name: "SingleReasonSingleFile",
inputMan: &kopia.ManifestEntry{
Manifest: &snapshot.Manifest{},
Reasons: []kopia.Reason{
{
ResourceOwner: resourceOwner,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
inputFiles: []string{fileNames[0]},
expected: []path.Path{emailDeltaPath},
},
{
name: "SingleReasonMultipleFiles",
inputMan: &kopia.ManifestEntry{
Manifest: &snapshot.Manifest{},
Reasons: []kopia.Reason{
{
ResourceOwner: resourceOwner,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
},
},
inputFiles: fileNames,
expected: []path.Path{emailDeltaPath, emailPathsPath},
},
{
name: "MultipleReasonsMultipleFiles",
inputMan: &kopia.ManifestEntry{
Manifest: &snapshot.Manifest{},
Reasons: []kopia.Reason{
{
ResourceOwner: resourceOwner,
Service: path.ExchangeService,
Category: path.EmailCategory,
},
{
ResourceOwner: resourceOwner,
Service: path.ExchangeService,
Category: path.ContactsCategory,
},
},
},
inputFiles: fileNames,
expected: []path.Path{
emailDeltaPath,
emailPathsPath,
contactsDeltaPath,
contactsPathsPath,
},
},
}
for _, test := range table {
suite.T().Run(test.name, func(t *testing.T) {
ctx, flush := tester.NewContext()
defer flush()
mr := &mockRestorer{}
_, err := collectMetadata(ctx, mr, test.inputMan, test.inputFiles, tenant)
assert.NoError(t, err)
checkPaths(t, test.expected, mr.gotPaths)
})
}
}

View File

@ -96,7 +96,9 @@ func (e *Errors) setErr(err error) *Errors {
return e return e
} }
// TODO: introduce Adder interface type Adder interface {
Add(err error) *Errors
}
// Add appends the error to the slice of recoverable and // Add appends the error to the slice of recoverable and
// iterated errors (ie: errors.errs). If failFast is true, // iterated errors (ie: errors.errs). If failFast is true,

View File

@ -0,0 +1,17 @@
package mock
import "github.com/alcionai/corso/src/pkg/fault"
// Adder mocks an adder interface for testing.
type Adder struct {
Errs []error
}
func NewAdder() *Adder {
return &Adder{Errs: []error{}}
}
func (ma *Adder) Add(err error) *fault.Errors {
ma.Errs = append(ma.Errs, err)
return fault.New(false)
}