retain kopia-assist when not on incrementals (#1941)

## Description

Adds a flag to the BackupCollections interface
that identifies whether the caller is running an
incremental backup or not.  If they are, kopia
will utilize the previous base snapshots when
building the directory tree.

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

- [x]  No 

## Type of change

- [x] 🌻 Feature

## Issue(s)

* #1901

## Test Plan

- [x] 💚 E2E
This commit is contained in:
Keepers 2022-12-23 13:14:05 -07:00 committed by GitHub
parent a4791af7bf
commit 9e9cb43b58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 84 additions and 52 deletions

View File

@ -554,7 +554,7 @@ func (suite *DataCollectionsIntegrationSuite) TestEventsSerializationRegression(
control.Options{}, control.Options{},
newStatusUpdater(t, &wg)) newStatusUpdater(t, &wg))
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(collections), 2) require.Len(t, collections, 2)
wg.Add(len(collections)) wg.Add(len(collections))

View File

@ -122,6 +122,7 @@ func (w Wrapper) BackupCollections(
service path.ServiceType, service path.ServiceType,
oc *OwnersCats, oc *OwnersCats,
tags map[string]string, tags map[string]string,
buildTreeWithBase bool,
) (*BackupStats, *details.Builder, map[string]path.Path, error) { ) (*BackupStats, *details.Builder, map[string]path.Path, error) {
if w.c == nil { if w.c == nil {
return nil, nil, nil, errNotConnected return nil, nil, nil, errNotConnected
@ -140,7 +141,15 @@ func (w Wrapper) BackupCollections(
toMerge: map[string]path.Path{}, toMerge: map[string]path.Path{},
} }
dirTree, err := inflateDirTree(ctx, w.c, previousSnapshots, collections, progress) // When running an incremental backup, we need to pass the prior
// snapshot bases into inflateDirTree so that the new snapshot
// includes historical data.
var base []IncrementalBase
if buildTreeWithBase {
base = previousSnapshots
}
dirTree, err := inflateDirTree(ctx, w.c, base, collections, progress)
if err != nil { if err != nil {
return nil, nil, nil, errors.Wrap(err, "building kopia directories") return nil, nil, nil, errors.Wrap(err, "building kopia directories")
} }

View File

@ -272,6 +272,7 @@ func (suite *KopiaIntegrationSuite) TestBackupCollections() {
path.ExchangeService, path.ExchangeService,
oc, oc,
customTags, customTags,
true,
) )
assert.NoError(t, err) assert.NoError(t, err)
@ -353,6 +354,7 @@ func (suite *KopiaIntegrationSuite) TestRestoreAfterCompressionChange() {
path.ExchangeService, path.ExchangeService,
oc, oc,
nil, nil,
true,
) )
require.NoError(t, err) require.NoError(t, err)
@ -435,6 +437,7 @@ func (suite *KopiaIntegrationSuite) TestBackupCollections_ReaderError() {
path.ExchangeService, path.ExchangeService,
oc, oc,
nil, nil,
true,
) )
require.NoError(t, err) require.NoError(t, err)
@ -480,6 +483,7 @@ func (suite *KopiaIntegrationSuite) TestBackupCollectionsHandlesNoCollections()
path.UnknownService, path.UnknownService,
&OwnersCats{}, &OwnersCats{},
nil, nil,
true,
) )
require.NoError(t, err) require.NoError(t, err)
@ -641,6 +645,7 @@ func (suite *KopiaSimpleRepoIntegrationSuite) SetupTest() {
path.ExchangeService, path.ExchangeService,
oc, oc,
nil, nil,
false,
) )
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, stats.ErrorCount, 0) require.Equal(t, stats.ErrorCount, 0)

View File

@ -92,6 +92,10 @@ type detailsWriter interface {
WriteBackupDetails(context.Context, *details.Details) (string, error) WriteBackupDetails(context.Context, *details.Details) (string, error)
} }
// ---------------------------------------------------------------------------
// Primary Controller
// ---------------------------------------------------------------------------
// Run begins a synchronous backup operation. // Run begins a synchronous backup operation.
func (op *BackupOperation) Run(ctx context.Context) (err error) { func (op *BackupOperation) Run(ctx context.Context) (err error) {
ctx, end := D.Span(ctx, "operations:backup:run") ctx, end := D.Span(ctx, "operations:backup:run")
@ -104,6 +108,8 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
tenantID = op.account.ID() tenantID = op.account.ID()
startTime = time.Now() startTime = time.Now()
detailsStore = streamstore.New(op.kopia, tenantID, op.Selectors.PathService()) detailsStore = streamstore.New(op.kopia, tenantID, op.Selectors.PathService())
oc = selectorToOwnersCats(op.Selectors)
uib = useIncrementalBackup(op.Selectors, op.Options)
) )
op.Results.BackupID = model.StableID(uuid.NewString()) op.Results.BackupID = model.StableID(uuid.NewString())
@ -132,17 +138,13 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
ctx, ctx,
detailsStore, detailsStore,
opStats.k.SnapshotID, opStats.k.SnapshotID,
backupDetails.Details(), backupDetails.Details())
)
if err != nil { if err != nil {
opStats.writeErr = err opStats.writeErr = err
} }
}() }()
oc := selectorToOwnersCats(op.Selectors) mans, mdColls, err := produceManifestsAndMetadata(ctx, op.kopia, op.store, oc, tenantID, uib)
srm := shouldRetrieveMetadata(op.Selectors, op.Options)
mans, mdColls, err := produceManifestsAndMetadata(ctx, op.kopia, op.store, oc, tenantID, srm)
if err != nil { if err != nil {
opStats.readErr = errors.Wrap(err, "connecting to M365") opStats.readErr = errors.Wrap(err, "connecting to M365")
return opStats.readErr return opStats.readErr
@ -168,7 +170,8 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
oc, oc,
mans, mans,
cs, cs,
op.Results.BackupID) op.Results.BackupID,
uib)
if err != nil { if err != nil {
opStats.writeErr = errors.Wrap(err, "backing up service data") opStats.writeErr = errors.Wrap(err, "backing up service data")
return opStats.writeErr return opStats.writeErr
@ -199,12 +202,50 @@ func (op *BackupOperation) Run(ctx context.Context) (err error) {
return err return err
} }
// checker to see if conditions are correct for retrieving metadata like delta tokens // checker to see if conditions are correct for incremental backup behavior such as
// and previous paths. // retrieving metadata like delta tokens and previous paths.
func shouldRetrieveMetadata(sel selectors.Selector, opts control.Options) bool { func useIncrementalBackup(sel selectors.Selector, opts control.Options) bool {
return opts.EnabledFeatures.ExchangeIncrementals && sel.Service == selectors.ServiceExchange return opts.EnabledFeatures.ExchangeIncrementals && sel.Service == selectors.ServiceExchange
} }
// ---------------------------------------------------------------------------
// Producer funcs
// ---------------------------------------------------------------------------
// calls the producer to generate collections of data to backup
func produceBackupDataCollections(
ctx context.Context,
gc *connector.GraphConnector,
sel selectors.Selector,
metadata []data.Collection,
ctrlOpts control.Options,
) ([]data.Collection, error) {
complete, closer := observe.MessageWithCompletion("Discovering items to backup:")
defer func() {
complete <- struct{}{}
close(complete)
closer()
}()
return gc.DataCollections(ctx, sel, metadata, ctrlOpts)
}
// ---------------------------------------------------------------------------
// Consumer funcs
// ---------------------------------------------------------------------------
type backuper interface {
BackupCollections(
ctx context.Context,
bases []kopia.IncrementalBase,
cs []data.Collection,
service path.ServiceType,
oc *kopia.OwnersCats,
tags map[string]string,
buildTreeWithBase bool,
) (*kopia.BackupStats, *details.Builder, map[string]path.Path, error)
}
// calls kopia to retrieve prior backup manifests, metadata collections to supply backup heuristics. // calls kopia to retrieve prior backup manifests, metadata collections to supply backup heuristics.
func produceManifestsAndMetadata( func produceManifestsAndMetadata(
ctx context.Context, ctx context.Context,
@ -257,15 +298,6 @@ func produceManifestsAndMetadata(
return ms, collections, err return ms, collections, err
} }
type restorer interface {
RestoreMultipleItems(
ctx context.Context,
snapshotID string,
paths []path.Path,
bc kopia.ByteCounter,
) ([]data.Collection, error)
}
func collectMetadata( func collectMetadata(
ctx context.Context, ctx context.Context,
r restorer, r restorer,
@ -327,24 +359,6 @@ func selectorToOwnersCats(sel selectors.Selector) *kopia.OwnersCats {
return oc return oc
} }
// calls the producer to generate collections of data to backup
func produceBackupDataCollections(
ctx context.Context,
gc *connector.GraphConnector,
sel selectors.Selector,
metadata []data.Collection,
ctrlOpts control.Options,
) ([]data.Collection, error) {
complete, closer := observe.MessageWithCompletion("Discovering items to backup:")
defer func() {
complete <- struct{}{}
close(complete)
closer()
}()
return gc.DataCollections(ctx, sel, metadata, ctrlOpts)
}
func builderFromReason(tenant string, r kopia.Reason) (*path.Builder, error) { func builderFromReason(tenant string, r kopia.Reason) (*path.Builder, error) {
// 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
@ -368,17 +382,6 @@ func builderFromReason(tenant string, r kopia.Reason) (*path.Builder, error) {
return p.ToBuilder().Dir(), nil return p.ToBuilder().Dir(), nil
} }
type backuper interface {
BackupCollections(
ctx context.Context,
bases []kopia.IncrementalBase,
cs []data.Collection,
service path.ServiceType,
oc *kopia.OwnersCats,
tags map[string]string,
) (*kopia.BackupStats, *details.Builder, map[string]path.Path, error)
}
// calls kopia to backup the collections of data // calls kopia to backup the collections of data
func consumeBackupDataCollections( func consumeBackupDataCollections(
ctx context.Context, ctx context.Context,
@ -389,6 +392,7 @@ func consumeBackupDataCollections(
mans []*kopia.ManifestEntry, mans []*kopia.ManifestEntry,
cs []data.Collection, cs []data.Collection,
backupID model.StableID, backupID model.StableID,
isIncremental bool,
) (*kopia.BackupStats, *details.Builder, map[string]path.Path, error) { ) (*kopia.BackupStats, *details.Builder, map[string]path.Path, error) {
complete, closer := observe.MessageWithCompletion("Backing up data:") complete, closer := observe.MessageWithCompletion("Backing up data:")
defer func() { defer func() {
@ -422,7 +426,7 @@ func consumeBackupDataCollections(
}) })
} }
return bu.BackupCollections(ctx, bases, cs, sel.PathService(), oc, tags) return bu.BackupCollections(ctx, bases, cs, sel.PathService(), oc, tags, isIncremental)
} }
func matchesReason(reasons []kopia.Reason, p path.Path) bool { func matchesReason(reasons []kopia.Reason, p path.Path) bool {

View File

@ -289,6 +289,7 @@ type mockBackuper struct {
service path.ServiceType, service path.ServiceType,
oc *kopia.OwnersCats, oc *kopia.OwnersCats,
tags map[string]string, tags map[string]string,
buildTreeWithBase bool,
) )
} }
@ -299,9 +300,10 @@ func (mbu mockBackuper) BackupCollections(
service path.ServiceType, service path.ServiceType,
oc *kopia.OwnersCats, oc *kopia.OwnersCats,
tags map[string]string, tags map[string]string,
buildTreeWithBase bool,
) (*kopia.BackupStats, *details.Builder, map[string]path.Path, error) { ) (*kopia.BackupStats, *details.Builder, map[string]path.Path, error) {
if mbu.checkFunc != nil { if mbu.checkFunc != nil {
mbu.checkFunc(bases, cs, service, oc, tags) mbu.checkFunc(bases, cs, service, oc, tags, buildTreeWithBase)
} }
return &kopia.BackupStats{}, &details.Builder{}, nil, nil return &kopia.BackupStats{}, &details.Builder{}, nil, nil
@ -440,6 +442,7 @@ func (suite *BackupOpSuite) TestBackupOperation_ConsumeBackupDataCollections_Pat
service path.ServiceType, service path.ServiceType,
oc *kopia.OwnersCats, oc *kopia.OwnersCats,
tags map[string]string, tags map[string]string,
buildTreeWithBase bool,
) { ) {
assert.ElementsMatch(t, test.expected, bases) assert.ElementsMatch(t, test.expected, bases)
}, },
@ -455,6 +458,7 @@ func (suite *BackupOpSuite) TestBackupOperation_ConsumeBackupDataCollections_Pat
test.inputMan, test.inputMan,
nil, nil,
model.StableID(""), model.StableID(""),
true,
) )
}) })
} }

View File

@ -95,6 +95,15 @@ type restoreStats struct {
restoreID string restoreID string
} }
type restorer interface {
RestoreMultipleItems(
ctx context.Context,
snapshotID string,
paths []path.Path,
bc kopia.ByteCounter,
) ([]data.Collection, error)
}
// Run begins a synchronous restore operation. // Run begins a synchronous restore operation.
func (op *RestoreOperation) Run(ctx context.Context) (restoreDetails *details.Details, err error) { func (op *RestoreOperation) Run(ctx context.Context) (restoreDetails *details.Details, err error) {
ctx, end := D.Span(ctx, "operations:restore:run") ctx, end := D.Span(ctx, "operations:restore:run")

View File

@ -80,6 +80,7 @@ func (ss *streamStore) WriteBackupDetails(
ss.service, ss.service,
nil, nil,
nil, nil,
false,
) )
if err != nil { if err != nil {
return "", nil return "", nil