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?

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

#### Type of change

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

#### Test Plan

- [ ] 💪 Manual
- [ ]  Unit test
- [x] 💚 E2E
This commit is contained in:
ashmrtn 2023-11-01 10:22:49 -07:00 committed by GitHub
parent 77a417b1ce
commit e46cf645e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 102 additions and 9 deletions

View File

@ -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):
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])

View File

@ -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,

View File

@ -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"`
}