SDK option and logic to drop assist bases (#3983)

Add a way for SDK users to drop
kopia-assisted incremental bases
thus forcing item data redownload
if the item wasn't sourced from
a merge base

---

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

- [ ]  Yes, it's included
- [ ] 🕐 Yes, but in a later PR
- [x]  No

#### Type of change

- [x] 🌻 Feature
- [ ] 🐛 Bugfix
- [ ] 🗺️ Documentation
- [ ] 🤖 Supportability/Tests
- [ ] 💻 CI/Deployment
- [x] 🧹 Tech Debt/Cleanup

#### Issue(s)

* #2360

#### Test Plan

- [x] 💪 Manual
- [x]  Unit test
- [ ] 💚 E2E
This commit is contained in:
ashmrtn 2023-08-09 10:21:55 -07:00 committed by GitHub
parent 0da8c48c5a
commit ffa155a80d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 162 additions and 22 deletions

View File

@ -57,6 +57,9 @@ type BackupOperation struct {
// when true, this allows for incremental backups instead of full data pulls // when true, this allows for incremental backups instead of full data pulls
incremental bool incremental bool
// When true, disables kopia-assisted incremental backups. This forces
// downloading and hashing all item data for items not in the merge base(s).
disableAssistBackup bool
} }
// BackupResults aggregate the details of the result of the operation. // BackupResults aggregate the details of the result of the operation.
@ -79,14 +82,15 @@ func NewBackupOperation(
bus events.Eventer, bus events.Eventer,
) (BackupOperation, error) { ) (BackupOperation, error) {
op := BackupOperation{ op := BackupOperation{
operation: newOperation(opts, bus, count.New(), kw, sw), operation: newOperation(opts, bus, count.New(), kw, sw),
ResourceOwner: owner, ResourceOwner: owner,
Selectors: selector, Selectors: selector,
Version: "v0", Version: "v0",
BackupVersion: version.Backup, BackupVersion: version.Backup,
account: acct, account: acct,
incremental: useIncrementalBackup(selector, opts), incremental: useIncrementalBackup(selector, opts),
bp: bp, disableAssistBackup: opts.ToggleFeatures.ForceItemDataDownload,
bp: bp,
} }
if err := op.validate(); err != nil { if err := op.validate(); err != nil {
@ -180,7 +184,8 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
"resource_owner_name", clues.Hide(op.ResourceOwner.Name()), "resource_owner_name", clues.Hide(op.ResourceOwner.Name()),
"backup_id", op.Results.BackupID, "backup_id", op.Results.BackupID,
"service", op.Selectors.Service, "service", op.Selectors.Service,
"incremental", op.incremental) "incremental", op.incremental,
"disable_assist_backup", op.disableAssistBackup)
op.bus.Event( op.bus.Event(
ctx, ctx,
@ -301,7 +306,8 @@ func (op *BackupOperation) do(
op.kopia, op.kopia,
reasons, fallbackReasons, reasons, fallbackReasons,
op.account.ID(), op.account.ID(),
op.incremental) op.incremental,
op.disableAssistBackup)
if err != nil { if err != nil {
return nil, clues.Wrap(err, "producing manifests and metadata") return nil, clues.Wrap(err, "producing manifests and metadata")
} }
@ -312,6 +318,10 @@ func (op *BackupOperation) do(
lastBackupVersion = mans.MinBackupVersion() lastBackupVersion = mans.MinBackupVersion()
} }
// TODO(ashmrtn): This should probably just return a collection that deletes
// the entire subtree instead of returning an additional bool. That way base
// selection is controlled completely by flags and merging is controlled
// completely by collections.
cs, ssmb, canUsePreviousBackup, err := produceBackupDataCollections( cs, ssmb, canUsePreviousBackup, err := produceBackupDataCollections(
ctx, ctx,
op.bp, op.bp,

View File

@ -15,11 +15,44 @@ import (
"github.com/alcionai/corso/src/pkg/path" "github.com/alcionai/corso/src/pkg/path"
) )
// calls kopia to retrieve prior backup manifests, metadata collections to supply backup heuristics.
// TODO(ashmrtn): Make this a helper function that always returns as much as
// possible and call in another function that drops metadata and/or
// kopia-assisted incremental bases based on flag values.
func produceManifestsAndMetadata( func produceManifestsAndMetadata(
ctx context.Context,
bf inject.BaseFinder,
rp inject.RestoreProducer,
reasons, fallbackReasons []kopia.Reasoner,
tenantID string,
getMetadata, dropAssistBases bool,
) (kopia.BackupBases, []data.RestoreCollection, bool, error) {
bb, meta, useMergeBases, err := getManifestsAndMetadata(
ctx,
bf,
rp,
reasons,
fallbackReasons,
tenantID,
getMetadata)
if err != nil {
return nil, nil, false, clues.Stack(err)
}
if !useMergeBases || !getMetadata {
logger.Ctx(ctx).Debug("full backup requested, dropping merge bases")
bb.ClearMergeBases()
}
if dropAssistBases {
logger.Ctx(ctx).Debug("no caching requested, dropping assist bases")
bb.ClearAssistBases()
}
return bb, meta, useMergeBases, nil
}
// getManifestsAndMetadata calls kopia to retrieve prior backup manifests,
// metadata collections to supply backup heuristics.
func getManifestsAndMetadata(
ctx context.Context, ctx context.Context,
bf inject.BaseFinder, bf inject.BaseFinder,
rp inject.RestoreProducer, rp inject.RestoreProducer,
@ -52,12 +85,6 @@ func produceManifestsAndMetadata(
}) })
if !getMetadata { if !getMetadata {
logger.Ctx(ctx).Debug("full backup requested, dropping merge bases")
// TODO(ashmrtn): If this function is moved to be a helper function then
// move this change to the bases to the caller of this function.
bb.ClearMergeBases()
return bb, nil, false, nil return bb, nil, false, nil
} }

View File

@ -254,6 +254,7 @@ func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata() {
rp mockRestoreProducer rp mockRestoreProducer
reasons []kopia.Reasoner reasons []kopia.Reasoner
getMeta bool getMeta bool
dropAssist bool
assertErr assert.ErrorAssertionFunc assertErr assert.ErrorAssertionFunc
assertB assert.BoolAssertionFunc assertB assert.BoolAssertionFunc
expectDCS []mockColl expectDCS []mockColl
@ -390,6 +391,36 @@ func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata() {
makeMan("id2", "checkpoint", path.EmailCategory), makeMan("id2", "checkpoint", path.EmailCategory),
), ),
}, },
{
name: "one valid man, extra incomplete man, no assist bases",
bf: &mockBackupFinder{
data: map[string]kopia.BackupBases{
ro: kopia.NewMockBackupBases().WithMergeBases(
makeMan("id1", "", path.EmailCategory),
).WithAssistBases(
makeMan("id2", "checkpoint", path.EmailCategory),
),
},
},
rp: mockRestoreProducer{
collsByID: map[string][]data.RestoreCollection{
"id1": {data.NoFetchRestoreCollection{Collection: mockColl{id: "id1"}}},
"id2": {data.NoFetchRestoreCollection{Collection: mockColl{id: "id2"}}},
},
},
reasons: []kopia.Reasoner{
kopia.NewReason("", ro, path.ExchangeService, path.EmailCategory),
},
getMeta: true,
dropAssist: true,
assertErr: assert.NoError,
assertB: assert.True,
expectDCS: []mockColl{{id: "id1"}},
expectMans: kopia.NewMockBackupBases().WithMergeBases(
makeMan("id1", "", path.EmailCategory),
).
ClearMockAssistBases(),
},
{ {
name: "multiple valid mans", name: "multiple valid mans",
bf: &mockBackupFinder{ bf: &mockBackupFinder{
@ -452,7 +483,8 @@ func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata() {
&test.rp, &test.rp,
test.reasons, nil, test.reasons, nil,
tid, tid,
test.getMeta) test.getMeta,
test.dropAssist)
test.assertErr(t, err, clues.ToCore(err)) test.assertErr(t, err, clues.ToCore(err))
test.assertB(t, b) test.assertB(t, b)
@ -551,6 +583,7 @@ func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata_Fallb
reasons []kopia.Reasoner reasons []kopia.Reasoner
fallbackReasons []kopia.Reasoner fallbackReasons []kopia.Reasoner
getMeta bool getMeta bool
dropAssist bool
assertErr assert.ErrorAssertionFunc assertErr assert.ErrorAssertionFunc
assertB assert.BoolAssertionFunc assertB assert.BoolAssertionFunc
expectDCS []mockColl expectDCS []mockColl
@ -604,6 +637,35 @@ func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata_Fallb
makeBackup(fbro, "fb_id1", path.EmailCategory), makeBackup(fbro, "fb_id1", path.EmailCategory),
), ),
}, },
{
name: "only fallbacks, no assist",
bf: &mockBackupFinder{
data: map[string]kopia.BackupBases{
fbro: kopia.NewMockBackupBases().WithMergeBases(
makeMan(fbro, "fb_id1", "", path.EmailCategory),
).WithBackups(
makeBackup(fbro, "fb_id1", path.EmailCategory),
),
},
},
rp: mockRestoreProducer{
collsByID: map[string][]data.RestoreCollection{
"fb_id1": {data.NoFetchRestoreCollection{Collection: mockColl{id: "fb_id1"}}},
},
},
fallbackReasons: []kopia.Reasoner{fbEmailReason},
getMeta: true,
dropAssist: true,
assertErr: assert.NoError,
assertB: assert.True,
expectDCS: []mockColl{{id: "fb_id1"}},
expectMans: kopia.NewMockBackupBases().WithMergeBases(
makeMan(fbro, "fb_id1", "", path.EmailCategory),
).WithBackups(
makeBackup(fbro, "fb_id1", path.EmailCategory),
).
ClearMockAssistBases(),
},
{ {
name: "complete mans and fallbacks", name: "complete mans and fallbacks",
bf: &mockBackupFinder{ bf: &mockBackupFinder{
@ -734,6 +796,40 @@ func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata_Fallb
makeMan(ro, "id2", "checkpoint", path.EmailCategory), makeMan(ro, "id2", "checkpoint", path.EmailCategory),
), ),
}, },
{
name: "incomplete mans and complete fallbacks, no assist bases",
bf: &mockBackupFinder{
data: map[string]kopia.BackupBases{
ro: kopia.NewMockBackupBases().WithAssistBases(
makeMan(ro, "id2", "checkpoint", path.EmailCategory),
),
fbro: kopia.NewMockBackupBases().WithMergeBases(
makeMan(fbro, "fb_id1", "", path.EmailCategory),
).WithBackups(
makeBackup(fbro, "fb_id1", path.EmailCategory),
),
},
},
rp: mockRestoreProducer{
collsByID: map[string][]data.RestoreCollection{
"id2": {data.NoFetchRestoreCollection{Collection: mockColl{id: "id2"}}},
"fb_id1": {data.NoFetchRestoreCollection{Collection: mockColl{id: "fb_id1"}}},
},
},
reasons: []kopia.Reasoner{emailReason},
fallbackReasons: []kopia.Reasoner{fbEmailReason},
getMeta: true,
dropAssist: true,
assertErr: assert.NoError,
assertB: assert.True,
expectDCS: []mockColl{{id: "fb_id1"}},
expectMans: kopia.NewMockBackupBases().WithMergeBases(
makeMan(fbro, "fb_id1", "", path.EmailCategory),
).WithBackups(
makeBackup(fbro, "fb_id1", path.EmailCategory),
).
ClearMockAssistBases(),
},
{ {
name: "complete mans and incomplete fallbacks", name: "complete mans and incomplete fallbacks",
bf: &mockBackupFinder{ bf: &mockBackupFinder{
@ -887,7 +983,8 @@ func (suite *OperationsManifestsUnitSuite) TestProduceManifestsAndMetadata_Fallb
&test.rp, &test.rp,
test.reasons, test.fallbackReasons, test.reasons, test.fallbackReasons,
tid, tid,
test.getMeta) test.getMeta,
test.dropAssist)
test.assertErr(t, err, clues.ToCore(err)) test.assertErr(t, err, clues.ToCore(err))
test.assertB(t, b) test.assertB(t, b)

View File

@ -62,6 +62,12 @@ type Toggles struct {
// DisableIncrementals prevents backups from using incremental lookups, // DisableIncrementals prevents backups from using incremental lookups,
// forcing a new, complete backup of all data regardless of prior state. // forcing a new, complete backup of all data regardless of prior state.
DisableIncrementals bool `json:"exchangeIncrementals,omitempty"` DisableIncrementals bool `json:"exchangeIncrementals,omitempty"`
// ForceItemDataDownload disables finding cached items in previous failed
// backups (i.e. kopia-assisted incrementals). Data dedupe will still occur
// since that is based on content hashes. Items that have not changed since
// the previous backup (i.e. in the merge base) will not be redownloaded. Use
// DisableIncrementals to control that behavior.
ForceItemDataDownload bool `json:"forceItemDataDownload,omitempty"`
// DisableDelta prevents backups from using delta based lookups, // DisableDelta prevents backups from using delta based lookups,
// forcing a backup by enumerating all items. This is different // forcing a backup by enumerating all items. This is different
// from DisableIncrementals in that this does not even makes use of // from DisableIncrementals in that this does not even makes use of