From 683f163df127a7a6e5cac3d2d96d9731c362849d Mon Sep 17 00:00:00 2001 From: ashmrtn <3891298+ashmrtn@users.noreply.github.com> Date: Thu, 25 Jan 2024 16:09:35 -0800 Subject: [PATCH] Code and tests to validate retention config (#5117) Basic validation checks that make sure maintenance and retention options are consistent with each other. This at least makes sure retention is either enabled and maintenance extends locks or nothing is locked at all. Going to be part of a set of PRs for validating the kopia config --- #### 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 - [x] :robot: Supportability/Tests - [ ] :computer: CI/Deployment - [ ] :broom: Tech Debt/Cleanup #### Test Plan - [ ] :muscle: Manual - [x] :zap: Unit test - [ ] :green_heart: E2E --- src/internal/kopia/retention/opts.go | 31 ++++++++ src/internal/kopia/retention/opts_test.go | 94 +++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/src/internal/kopia/retention/opts.go b/src/internal/kopia/retention/opts.go index 07371797a..1e23270f3 100644 --- a/src/internal/kopia/retention/opts.go +++ b/src/internal/kopia/retention/opts.go @@ -136,3 +136,34 @@ func (r *Opts) setBlobConfigMode( return nil } + +// Verify checks that the config info in r passes kopia's retention validation +// checks when it comes to locking durations and that if retention is requested +// in the blob config blob then lock extension is also configured to run during +// maintenance. If rentention is not enabled in the blob config blob then lock +// extension should be disabled during maintenance. +func (r Opts) Verify(ctx context.Context) error { + if !r.blobCfg.IsRetentionEnabled() { + if r.params.ExtendObjectLocks { + return clues.NewWC( + ctx, + "retention disabled but maintenance lock extension enabled") + } + + // Both disabled. + return nil + } + + // Rest of function handles case where retention is enabled in the blob config + // blob. + if !r.params.ExtendObjectLocks { + return clues.NewWC( + ctx, + "retention enabled but maintenance lock extension disabled") + } + + return clues.Stack(maintenance.CheckExtendRetention( + ctx, + r.blobCfg, + &r.params)).OrNil() +} diff --git a/src/internal/kopia/retention/opts_test.go b/src/internal/kopia/retention/opts_test.go index 8b250c79a..5de4cf68d 100644 --- a/src/internal/kopia/retention/opts_test.go +++ b/src/internal/kopia/retention/opts_test.go @@ -202,3 +202,97 @@ func (suite *OptsUnitSuite) TestSet() { }) } } + +func (suite *OptsUnitSuite) TestVerify() { + mode := blob.Governance + fullCycleInterval := time.Hour * 24 + duration := 2 * fullCycleInterval + + table := []struct { + name string + input *retention.Opts + expectErr assert.ErrorAssertionFunc + }{ + { + name: "ValidDisabled", + input: retention.OptsFromConfigs( + format.BlobStorageConfiguration{}, + maintenance.Params{ + FullCycle: maintenance.CycleParams{ + Interval: fullCycleInterval, + }, + ExtendObjectLocks: false, + }), + expectErr: assert.NoError, + }, + { + name: "ValidEnabled", + input: retention.OptsFromConfigs( + format.BlobStorageConfiguration{ + RetentionMode: mode, + RetentionPeriod: duration, + }, + maintenance.Params{ + FullCycle: maintenance.CycleParams{ + Interval: fullCycleInterval, + }, + ExtendObjectLocks: true, + }), + expectErr: assert.NoError, + }, + { + name: "InvalidDuration", + input: retention.OptsFromConfigs( + format.BlobStorageConfiguration{ + RetentionMode: mode, + RetentionPeriod: fullCycleInterval, + }, + maintenance.Params{ + FullCycle: maintenance.CycleParams{ + Interval: fullCycleInterval, + }, + ExtendObjectLocks: true, + }), + expectErr: assert.Error, + }, + { + name: "InvalidNotExtending", + input: retention.OptsFromConfigs( + format.BlobStorageConfiguration{ + RetentionMode: mode, + RetentionPeriod: duration, + }, + maintenance.Params{ + FullCycle: maintenance.CycleParams{ + Interval: fullCycleInterval, + }, + ExtendObjectLocks: false, + }), + expectErr: assert.Error, + }, + { + name: "InvalidNotConfigured", + input: retention.OptsFromConfigs( + format.BlobStorageConfiguration{}, + maintenance.Params{ + FullCycle: maintenance.CycleParams{ + Interval: fullCycleInterval, + }, + ExtendObjectLocks: true, + }), + expectErr: assert.Error, + }, + } + + for _, test := range table { + suite.Run(test.name, func() { + t := suite.T() + + ctx, flush := tester.NewContext(t) + t.Cleanup(flush) + + err := test.input.Verify(ctx) + test.expectErr(t, err, clues.ToCore(err)) + }) + } +}