Retention update op (#3865)
Create new operation type to update retention parameters for immutable backups --- #### 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 - [x] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
a836e877af
commit
b12bb50a4a
77
src/internal/operations/retention_config.go
Normal file
77
src/internal/operations/retention_config.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package operations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/common/crash"
|
||||||
|
"github.com/alcionai/corso/src/internal/events"
|
||||||
|
"github.com/alcionai/corso/src/internal/kopia"
|
||||||
|
"github.com/alcionai/corso/src/internal/stats"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RetentionConfigOperation wraps an operation with restore-specific props.
|
||||||
|
type RetentionConfigOperation struct {
|
||||||
|
operation
|
||||||
|
Results RetentionConfigResults
|
||||||
|
rcOpts repository.Retention
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetentionConfigResults aggregate the details of the results of the operation.
|
||||||
|
type RetentionConfigResults struct {
|
||||||
|
stats.StartAndEndTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRetentionConfigOperation constructs and validates an operation to change
|
||||||
|
// retention parameters.
|
||||||
|
func NewRetentionConfigOperation(
|
||||||
|
ctx context.Context,
|
||||||
|
opts control.Options,
|
||||||
|
kw *kopia.Wrapper,
|
||||||
|
rcOpts repository.Retention,
|
||||||
|
bus events.Eventer,
|
||||||
|
) (RetentionConfigOperation, error) {
|
||||||
|
op := RetentionConfigOperation{
|
||||||
|
operation: newOperation(opts, bus, count.New(), kw, nil),
|
||||||
|
rcOpts: rcOpts,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't run validation because we don't populate the model store.
|
||||||
|
|
||||||
|
return op, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op *RetentionConfigOperation) Run(ctx context.Context) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if crErr := crash.Recovery(ctx, recover(), "retention_config"); crErr != nil {
|
||||||
|
err = crErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
op.Results.StartedAt = time.Now()
|
||||||
|
|
||||||
|
// TODO(ashmrtn): Send telemetry?
|
||||||
|
|
||||||
|
return op.do(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (op *RetentionConfigOperation) do(ctx context.Context) error {
|
||||||
|
defer func() {
|
||||||
|
op.Results.CompletedAt = time.Now()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := op.operation.kopia.SetRetentionParameters(ctx, op.rcOpts)
|
||||||
|
if err != nil {
|
||||||
|
op.Status = Failed
|
||||||
|
return clues.Wrap(err, "running retention config operation")
|
||||||
|
}
|
||||||
|
|
||||||
|
op.Status = Completed
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
74
src/internal/operations/retention_config_test.go
Normal file
74
src/internal/operations/retention_config_test.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package operations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
|
evmock "github.com/alcionai/corso/src/internal/events/mock"
|
||||||
|
"github.com/alcionai/corso/src/internal/kopia"
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
|
storeTD "github.com/alcionai/corso/src/pkg/storage/testdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RetentionConfigOpIntegrationSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetentionConfigOpIntegrationSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &RetentionConfigOpIntegrationSuite{
|
||||||
|
Suite: tester.NewIntegrationSuite(
|
||||||
|
t,
|
||||||
|
[][]string{storeTD.AWSStorageCredEnvs}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *RetentionConfigOpIntegrationSuite) TestRepoRetentionConfig() {
|
||||||
|
var (
|
||||||
|
t = suite.T()
|
||||||
|
// need to initialize the repository before we can test connecting to it.
|
||||||
|
st = storeTD.NewPrefixedS3Storage(t)
|
||||||
|
k = kopia.NewConn(st)
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx, flush := tester.NewContext(t)
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
err := k.Initialize(ctx, repository.Options{}, repository.Retention{})
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
kw, err := kopia.NewWrapper(k)
|
||||||
|
// kopiaRef comes with a count of 1 and Wrapper bumps it again so safe
|
||||||
|
// to close here.
|
||||||
|
k.Close(ctx)
|
||||||
|
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
defer kw.Close(ctx)
|
||||||
|
|
||||||
|
// Only set extend locks parameter as other retention options require a bucket
|
||||||
|
// with object locking enabled. There's more complete tests in the kopia
|
||||||
|
// package.
|
||||||
|
rco, err := NewRetentionConfigOperation(
|
||||||
|
ctx,
|
||||||
|
control.DefaultOptions(),
|
||||||
|
kw,
|
||||||
|
repository.Retention{
|
||||||
|
Extend: ptr.To(true),
|
||||||
|
},
|
||||||
|
evmock.NewBus())
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
err = rco.Run(ctx)
|
||||||
|
assert.NoError(t, err, clues.ToCore(err))
|
||||||
|
assert.Equal(t, Completed, rco.Status)
|
||||||
|
assert.NotZero(t, rco.Results.StartedAt)
|
||||||
|
assert.NotZero(t, rco.Results.CompletedAt)
|
||||||
|
assert.NotEqual(t, rco.Results.StartedAt, rco.Results.CompletedAt)
|
||||||
|
}
|
||||||
@ -84,6 +84,10 @@ type Repository interface {
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
mOpts ctrlRepo.Maintenance,
|
mOpts ctrlRepo.Maintenance,
|
||||||
) (operations.MaintenanceOperation, error)
|
) (operations.MaintenanceOperation, error)
|
||||||
|
NewRetentionConfig(
|
||||||
|
ctx context.Context,
|
||||||
|
rcOpts ctrlRepo.Retention,
|
||||||
|
) (operations.RetentionConfigOperation, error)
|
||||||
DeleteBackup(ctx context.Context, id string) error
|
DeleteBackup(ctx context.Context, id string) error
|
||||||
BackupGetter
|
BackupGetter
|
||||||
// ConnectToM365 establishes graph api connections
|
// ConnectToM365 establishes graph api connections
|
||||||
@ -420,6 +424,18 @@ func (r repository) NewMaintenance(
|
|||||||
r.Bus)
|
r.Bus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r repository) NewRetentionConfig(
|
||||||
|
ctx context.Context,
|
||||||
|
rcOpts ctrlRepo.Retention,
|
||||||
|
) (operations.RetentionConfigOperation, error) {
|
||||||
|
return operations.NewRetentionConfigOperation(
|
||||||
|
ctx,
|
||||||
|
r.Opts,
|
||||||
|
r.dataLayer,
|
||||||
|
rcOpts,
|
||||||
|
r.Bus)
|
||||||
|
}
|
||||||
|
|
||||||
// Backup retrieves a backup by id.
|
// Backup retrieves a backup by id.
|
||||||
func (r repository) Backup(ctx context.Context, id string) (*backup.Backup, error) {
|
func (r repository) Backup(ctx context.Context, id string) (*backup.Backup, error) {
|
||||||
return getBackup(ctx, id, store.NewKopiaStore(r.modelStore))
|
return getBackup(ctx, id, store.NewKopiaStore(r.modelStore))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user