From e46cf645e5ca6f7c24f55453e05e184f43de2316 Mon Sep 17 00:00:00 2001 From: ashmrtn <3891298+ashmrtn@users.noreply.github.com> Date: Wed, 1 Nov 2023 10:22:49 -0700 Subject: [PATCH] Create and tag preview backups (#4595) Add an option to request a preview backup and tag the resulting backup as a preview if the flag is set. Preview backups must complete successfully with no errors in order to be tagged This does not update the item selection logic, so right now preview backups will contain all items that normal backups do. Item selection will be refined in upcoming PRs --- #### Does this PR need a docs update or release note? - [ ] :white_check_mark: Yes, it's included - [ ] :clock1: Yes, but in a later PR - [x] :no_entry: No #### Type of change - [x] :sunflower: Feature - [ ] :bug: Bugfix - [ ] :world_map: Documentation - [ ] :robot: Supportability/Tests - [ ] :computer: CI/Deployment - [ ] :broom: Tech Debt/Cleanup #### Test Plan - [ ] :muscle: Manual - [ ] :zap: Unit test - [x] :green_heart: E2E --- src/internal/operations/backup.go | 41 ++++++++++++---- src/internal/operations/backup_test.go | 65 +++++++++++++++++++++++++- src/pkg/control/options.go | 5 ++ 3 files changed, 102 insertions(+), 9 deletions(-) diff --git a/src/internal/operations/backup.go b/src/internal/operations/backup.go index 80cd4055a..937e6733d 100644 --- a/src/internal/operations/backup.go +++ b/src/internal/operations/backup.go @@ -934,24 +934,49 @@ func (op *BackupOperation) createBackupModels( model.ServiceTag: op.Selectors.PathService().String(), } - // Add tags to mark this backup as either assist or merge. This is used to: + // Add tags to mark this backup as preview, assist, or merge. This is used to: // 1. Filter assist backups by tag during base selection process - // 2. Differentiate assist backups from merge backups - if isMergeBackup( + // 2. Differentiate assist backups, merge backups, and preview backups. + // + // model.BackupTypeTag has more info about how these tags are used. + switch { + case op.Options.ToggleFeatures.PreviewBackup: + // Preview backups need to be successful and without errors to be considered + // valid. Just reuse the merge base check for that since it has the same + // requirements. + if !isMergeBackup( + snapID, + ssid, + op.Options.FailureHandling, + op.Errors) { + return clues.New("failed preview backup").WithClues(ctx) + } + + tags[model.BackupTypeTag] = model.PreviewBackup + + case isMergeBackup( snapID, ssid, op.Options.FailureHandling, - op.Errors) { + op.Errors): tags[model.BackupTypeTag] = model.MergeBackup - } else if isAssistBackup( + + case isAssistBackup( opStats.hasNewDetailEntries, snapID, ssid, op.Options.FailureHandling, - op.Errors) { + op.Errors): tags[model.BackupTypeTag] = model.AssistBackup - } else { - return clues.New("backup is neither assist nor merge").WithClues(ctx) + + default: + return clues.New("unable to determine backup type due to operation errors"). + WithClues(ctx) + } + + // Additional defensive check to make sure we tag things as expected above. + if len(tags[model.BackupTypeTag]) == 0 { + return clues.New("empty backup type tag").WithClues(ctx) } ctx = clues.Add(ctx, model.BackupTypeTag, tags[model.BackupTypeTag]) diff --git a/src/internal/operations/backup_test.go b/src/internal/operations/backup_test.go index 26b8612c5..d8286c60e 100644 --- a/src/internal/operations/backup_test.go +++ b/src/internal/operations/backup_test.go @@ -1649,7 +1649,6 @@ func (suite *AssistBackupIntegrationSuite) TestBackupTypesForFailureModes() { var ( acct = tconfig.NewM365Account(suite.T()) tenantID = acct.Config[account.AzureTenantIDKey] - opts = control.DefaultOptions() osel = selectors.NewOneDriveBackup([]string{userID}) ) @@ -1667,6 +1666,7 @@ func (suite *AssistBackupIntegrationSuite) TestBackupTypesForFailureModes() { collFunc func() []data.BackupCollection injectNonRecoverableErr bool failurePolicy control.FailurePolicy + previewBackup bool expectRunErr assert.ErrorAssertionFunc expectBackupTag string expectFaults func(t *testing.T, errs *fault.Bus) @@ -1829,6 +1829,67 @@ func (suite *AssistBackupIntegrationSuite) TestBackupTypesForFailureModes() { assert.Greater(t, len(errs.Recovered()), 0, "recovered errors") }, }, + + { + name: "preview, fail after recovery, no errors", + collFunc: func() []data.BackupCollection { + bc := []data.BackupCollection{ + makeBackupCollection( + tmp, + locPath, + []dataMock.Item{ + makeMockItem("file1", nil, time.Now(), false, nil), + makeMockItem("file2", nil, time.Now(), false, nil), + }), + } + + return bc + }, + failurePolicy: control.FailAfterRecovery, + previewBackup: true, + expectRunErr: assert.NoError, + expectBackupTag: model.PreviewBackup, + expectFaults: func(t *testing.T, errs *fault.Bus) { + assert.NoError(t, errs.Failure(), clues.ToCore(errs.Failure())) + assert.Empty(t, errs.Recovered(), "recovered errors") + }, + }, + { + name: "preview, fail after recovery, non-recoverable errors", + collFunc: func() []data.BackupCollection { + return nil + }, + injectNonRecoverableErr: true, + failurePolicy: control.FailAfterRecovery, + previewBackup: true, + expectRunErr: assert.Error, + expectFaults: func(t *testing.T, errs *fault.Bus) { + assert.Error(t, errs.Failure(), clues.ToCore(errs.Failure())) + }, + }, + { + name: "preview, fail after recovery, recoverable errors", + collFunc: func() []data.BackupCollection { + bc := []data.BackupCollection{ + makeBackupCollection( + tmp, + locPath, + []dataMock.Item{ + makeMockItem("file1", nil, time.Now(), false, nil), + makeMockItem("file2", nil, time.Now(), false, assert.AnError), + }), + } + + return bc + }, + failurePolicy: control.FailAfterRecovery, + previewBackup: true, + expectRunErr: assert.Error, + expectFaults: func(t *testing.T, errs *fault.Bus) { + assert.Error(t, errs.Failure(), clues.ToCore(errs.Failure())) + assert.Greater(t, len(errs.Recovered()), 0, "recovered errors") + }, + }, } for _, test := range table { suite.Run(test.name, func() { @@ -1856,7 +1917,9 @@ func (suite *AssistBackupIntegrationSuite) TestBackupTypesForFailureModes() { cs = append(cs, mc) bp := opMock.NewMockBackupProducer(cs, data.CollectionStats{}, test.injectNonRecoverableErr) + opts := control.DefaultOptions() opts.FailureHandling = test.failurePolicy + opts.ToggleFeatures.PreviewBackup = test.previewBackup bo, err := NewBackupOperation( ctx, diff --git a/src/pkg/control/options.go b/src/pkg/control/options.go index 0f7d559aa..7db2d9038 100644 --- a/src/pkg/control/options.go +++ b/src/pkg/control/options.go @@ -86,4 +86,9 @@ type Toggles struct { // DisableConcurrencyLimiter removes concurrency limits when communicating with // graph API. This flag is only relevant for exchange backups for now DisableConcurrencyLimiter bool `json:"disableConcurrencyLimiter,omitempty"` + + // PreviewBackup denotes that this backup contains a subset of information for + // the protected resource. PreviewBackups are used to demonstrate value by + // being quick to create. + PreviewBackup bool `json:"previewBackup"` }