Create wrapper around kopia maintenance (#3224)
Create a wrapper function around the kopia maintenance operation. Will allow users to run maintenance, though they still need to be careful not to run maintenance concurrently if they override the username and hostname that kopia uses --- #### 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 #### Issue(s) * #3077 #### Test Plan - [ ] 💪 Manual - [x] ⚡ Unit test - [ ] 💚 E2E
This commit is contained in:
parent
631e1a3b61
commit
b6ef2d5266
@ -44,6 +44,7 @@ require (
|
|||||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
github.com/dnaeon/go-vcr v1.2.0 // indirect
|
github.com/dnaeon/go-vcr v1.2.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
|
github.com/gofrs/flock v0.8.1 // indirect
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
|||||||
@ -131,6 +131,7 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
|
|||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||||
|
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import (
|
|||||||
"github.com/kopia/kopia/snapshot/snapshotfs"
|
"github.com/kopia/kopia/snapshot/snapshotfs"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/storage"
|
"github.com/alcionai/corso/src/pkg/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ func NewConn(s storage.Storage) *conn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *conn) Initialize(ctx context.Context, opts control.RepoOptions) error {
|
func (w *conn) Initialize(ctx context.Context, opts repository.Options) error {
|
||||||
bst, err := blobStoreByProvider(ctx, w.storage)
|
bst, err := blobStoreByProvider(ctx, w.storage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clues.Wrap(err, "initializing storage")
|
return clues.Wrap(err, "initializing storage")
|
||||||
@ -110,7 +110,7 @@ func (w *conn) Initialize(ctx context.Context, opts control.RepoOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *conn) Connect(ctx context.Context, opts control.RepoOptions) error {
|
func (w *conn) Connect(ctx context.Context, opts repository.Options) error {
|
||||||
bst, err := blobStoreByProvider(ctx, w.storage)
|
bst, err := blobStoreByProvider(ctx, w.storage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clues.Wrap(err, "initializing storage")
|
return clues.Wrap(err, "initializing storage")
|
||||||
@ -134,7 +134,7 @@ func (w *conn) Connect(ctx context.Context, opts control.RepoOptions) error {
|
|||||||
|
|
||||||
func (w *conn) commonConnect(
|
func (w *conn) commonConnect(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
opts control.RepoOptions,
|
opts repository.Options,
|
||||||
configDir string,
|
configDir string,
|
||||||
bst blob.Storage,
|
bst blob.Storage,
|
||||||
password, compressor string,
|
password, compressor string,
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/storage"
|
"github.com/alcionai/corso/src/pkg/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ func openKopiaRepo(
|
|||||||
st := tester.NewPrefixedS3Storage(t)
|
st := tester.NewPrefixedS3Storage(t)
|
||||||
|
|
||||||
k := NewConn(st)
|
k := NewConn(st)
|
||||||
if err := k.Initialize(ctx, control.RepoOptions{}); err != nil {
|
if err := k.Initialize(ctx, repository.Options{}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,13 +79,13 @@ func (suite *WrapperIntegrationSuite) TestRepoExistsError() {
|
|||||||
st := tester.NewPrefixedS3Storage(t)
|
st := tester.NewPrefixedS3Storage(t)
|
||||||
k := NewConn(st)
|
k := NewConn(st)
|
||||||
|
|
||||||
err := k.Initialize(ctx, control.RepoOptions{})
|
err := k.Initialize(ctx, repository.Options{})
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
err = k.Close(ctx)
|
err = k.Close(ctx)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
err = k.Initialize(ctx, control.RepoOptions{})
|
err = k.Initialize(ctx, repository.Options{})
|
||||||
assert.Error(t, err, clues.ToCore(err))
|
assert.Error(t, err, clues.ToCore(err))
|
||||||
assert.ErrorIs(t, err, ErrorRepoAlreadyExists)
|
assert.ErrorIs(t, err, ErrorRepoAlreadyExists)
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ func (suite *WrapperIntegrationSuite) TestBadProviderErrors() {
|
|||||||
st.Provider = storage.ProviderUnknown
|
st.Provider = storage.ProviderUnknown
|
||||||
k := NewConn(st)
|
k := NewConn(st)
|
||||||
|
|
||||||
err := k.Initialize(ctx, control.RepoOptions{})
|
err := k.Initialize(ctx, repository.Options{})
|
||||||
assert.Error(t, err, clues.ToCore(err))
|
assert.Error(t, err, clues.ToCore(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ func (suite *WrapperIntegrationSuite) TestConnectWithoutInitErrors() {
|
|||||||
st := tester.NewPrefixedS3Storage(t)
|
st := tester.NewPrefixedS3Storage(t)
|
||||||
k := NewConn(st)
|
k := NewConn(st)
|
||||||
|
|
||||||
err := k.Connect(ctx, control.RepoOptions{})
|
err := k.Connect(ctx, repository.Options{})
|
||||||
assert.Error(t, err, clues.ToCore(err))
|
assert.Error(t, err, clues.ToCore(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,7 +358,7 @@ func (suite *WrapperIntegrationSuite) TestConfigDefaultsSetOnInitAndNotOnConnect
|
|||||||
err = k.Close(ctx)
|
err = k.Close(ctx)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
err = k.Connect(ctx, control.RepoOptions{})
|
err = k.Connect(ctx, repository.Options{})
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -386,7 +386,7 @@ func (suite *WrapperIntegrationSuite) TestInitAndConnWithTempDirectory() {
|
|||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
// Re-open with Connect.
|
// Re-open with Connect.
|
||||||
err = k.Connect(ctx, control.RepoOptions{})
|
err = k.Connect(ctx, repository.Options{})
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
err = k.Close(ctx)
|
err = k.Close(ctx)
|
||||||
@ -397,7 +397,7 @@ func (suite *WrapperIntegrationSuite) TestSetUserAndHost() {
|
|||||||
ctx, flush := tester.NewContext()
|
ctx, flush := tester.NewContext()
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
opts := control.RepoOptions{
|
opts := repository.Options{
|
||||||
User: "foo",
|
User: "foo",
|
||||||
Host: "bar",
|
Host: "bar",
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/model"
|
"github.com/alcionai/corso/src/internal/model"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/backup"
|
"github.com/alcionai/corso/src/pkg/backup"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fooModel struct {
|
type fooModel struct {
|
||||||
@ -804,7 +804,7 @@ func openConnAndModelStore(
|
|||||||
st := tester.NewPrefixedS3Storage(t)
|
st := tester.NewPrefixedS3Storage(t)
|
||||||
c := NewConn(st)
|
c := NewConn(st)
|
||||||
|
|
||||||
err := c.Initialize(ctx, control.RepoOptions{})
|
err := c.Initialize(ctx, repository.Options{})
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -823,7 +823,7 @@ func reconnectToModelStore(
|
|||||||
ctx context.Context, //revive:disable-line:context-as-argument
|
ctx context.Context, //revive:disable-line:context-as-argument
|
||||||
c *conn,
|
c *conn,
|
||||||
) *ModelStore {
|
) *ModelStore {
|
||||||
err := c.Connect(ctx, control.RepoOptions{})
|
err := c.Connect(ctx, repository.Options{})
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
@ -7,15 +7,19 @@ import (
|
|||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/kopia/kopia/fs"
|
"github.com/kopia/kopia/fs"
|
||||||
"github.com/kopia/kopia/repo"
|
"github.com/kopia/kopia/repo"
|
||||||
|
"github.com/kopia/kopia/repo/maintenance"
|
||||||
"github.com/kopia/kopia/repo/manifest"
|
"github.com/kopia/kopia/repo/manifest"
|
||||||
"github.com/kopia/kopia/snapshot"
|
"github.com/kopia/kopia/snapshot"
|
||||||
"github.com/kopia/kopia/snapshot/policy"
|
"github.com/kopia/kopia/snapshot/policy"
|
||||||
"github.com/kopia/kopia/snapshot/snapshotfs"
|
"github.com/kopia/kopia/snapshot/snapshotfs"
|
||||||
|
"github.com/kopia/kopia/snapshot/snapshotmaintenance"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
"github.com/alcionai/corso/src/internal/diagnostics"
|
"github.com/alcionai/corso/src/internal/diagnostics"
|
||||||
|
"github.com/alcionai/corso/src/internal/observe"
|
||||||
"github.com/alcionai/corso/src/internal/stats"
|
"github.com/alcionai/corso/src/internal/stats"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -515,3 +519,128 @@ func isErrEntryNotFound(err error) bool {
|
|||||||
return strings.Contains(err.Error(), "entry not found") &&
|
return strings.Contains(err.Error(), "entry not found") &&
|
||||||
!strings.Contains(err.Error(), "parent is not a directory")
|
!strings.Contains(err.Error(), "parent is not a directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w Wrapper) Maintenance(
|
||||||
|
ctx context.Context,
|
||||||
|
opts repository.Maintenance,
|
||||||
|
) error {
|
||||||
|
kopiaSafety, err := translateSafety(opts.Safety)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "identifying safety level")
|
||||||
|
}
|
||||||
|
|
||||||
|
mode, err := translateMode(opts.Type)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "identifying maintenance mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
currentOwner := w.c.ClientOptions().UsernameAtHost()
|
||||||
|
|
||||||
|
ctx = clues.Add(
|
||||||
|
ctx,
|
||||||
|
"kopia_safety", kopiaSafety,
|
||||||
|
"kopia_maintenance_mode", mode,
|
||||||
|
"force", opts.Force,
|
||||||
|
"current_local_owner", clues.Hide(currentOwner))
|
||||||
|
|
||||||
|
dr, ok := w.c.Repository.(repo.DirectRepository)
|
||||||
|
if !ok {
|
||||||
|
return clues.New("unable to get valid handle to repo").WithClues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Below write session options pulled from kopia's CLI code that runs
|
||||||
|
// maintenance.
|
||||||
|
err = repo.DirectWriteSession(
|
||||||
|
ctx,
|
||||||
|
dr,
|
||||||
|
repo.WriteSessionOptions{
|
||||||
|
Purpose: "Corso maintenance",
|
||||||
|
},
|
||||||
|
func(ctx context.Context, dw repo.DirectRepositoryWriter) error {
|
||||||
|
params, err := maintenance.GetParams(ctx, w.c)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "getting maintenance user@host").WithClues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to do some fixup here as the user/host may not have been set.
|
||||||
|
if len(params.Owner) == 0 || (params.Owner != currentOwner && opts.Force) {
|
||||||
|
observe.Message(
|
||||||
|
ctx,
|
||||||
|
"updating maintenance user@host to ",
|
||||||
|
clues.Hide(currentOwner))
|
||||||
|
|
||||||
|
if err := w.setMaintenanceParams(ctx, dw, params, currentOwner); err != nil {
|
||||||
|
return clues.Wrap(err, "updating maintenance parameters").
|
||||||
|
WithClues(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = clues.Add(ctx, "expected_owner", clues.Hide(params.Owner))
|
||||||
|
|
||||||
|
logger.Ctx(ctx).Info("running kopia maintenance")
|
||||||
|
|
||||||
|
err = snapshotmaintenance.Run(ctx, dw, mode, opts.Force, kopiaSafety)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "running kopia maintenance").WithClues(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func translateSafety(
|
||||||
|
s repository.MaintenanceSafety,
|
||||||
|
) (maintenance.SafetyParameters, error) {
|
||||||
|
switch s {
|
||||||
|
case repository.FullMaintenanceSafety:
|
||||||
|
return maintenance.SafetyFull, nil
|
||||||
|
case repository.NoMaintenanceSafety:
|
||||||
|
return maintenance.SafetyNone, nil
|
||||||
|
default:
|
||||||
|
return maintenance.SafetyParameters{}, clues.New("bad safety value").
|
||||||
|
With("input_safety", s.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func translateMode(t repository.MaintenanceType) (maintenance.Mode, error) {
|
||||||
|
switch t {
|
||||||
|
case repository.CompleteMaintenance:
|
||||||
|
return maintenance.ModeFull, nil
|
||||||
|
|
||||||
|
case repository.MetadataMaintenance:
|
||||||
|
return maintenance.ModeQuick, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return maintenance.ModeNone, clues.New("bad maintenance type").
|
||||||
|
With("input_maintenance_type", t.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setMaintenanceUserHost sets the user and host for maintenance to the the
|
||||||
|
// user and host in the kopia config.
|
||||||
|
func (w Wrapper) setMaintenanceParams(
|
||||||
|
ctx context.Context,
|
||||||
|
drw repo.DirectRepositoryWriter,
|
||||||
|
p *maintenance.Params,
|
||||||
|
userAtHost string,
|
||||||
|
) error {
|
||||||
|
// This will source user/host from the kopia config file or fallback to
|
||||||
|
// fetching the values from the OS.
|
||||||
|
p.Owner = userAtHost
|
||||||
|
// Disable automatic maintenance for now since it can start matching on the
|
||||||
|
// user/host of at least one machine now.
|
||||||
|
p.QuickCycle.Enabled = false
|
||||||
|
p.FullCycle.Enabled = false
|
||||||
|
|
||||||
|
err := maintenance.SetParams(ctx, drw, p)
|
||||||
|
if err != nil {
|
||||||
|
return clues.Wrap(err, "setting maintenance user/host")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/alcionai/clues"
|
"github.com/alcionai/clues"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/kopia/kopia/repo"
|
"github.com/kopia/kopia/repo"
|
||||||
|
"github.com/kopia/kopia/repo/maintenance"
|
||||||
"github.com/kopia/kopia/repo/manifest"
|
"github.com/kopia/kopia/repo/manifest"
|
||||||
"github.com/kopia/kopia/snapshot"
|
"github.com/kopia/kopia/snapshot"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -23,6 +24,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/data/mock"
|
"github.com/alcionai/corso/src/internal/data/mock"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -143,7 +145,127 @@ func (suite *KopiaUnitSuite) TestCloseWithoutInitDoesNotPanic() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------
|
// ---------------
|
||||||
// integration tests that use kopia
|
// integration tests that use kopia.
|
||||||
|
// ---------------
|
||||||
|
type BasicKopiaIntegrationSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicKopiaIntegrationSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &BasicKopiaIntegrationSuite{
|
||||||
|
Suite: tester.NewIntegrationSuite(
|
||||||
|
t,
|
||||||
|
[][]string{tester.AWSStorageCredEnvs},
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMaintenance checks that different username/hostname pairs will or won't
|
||||||
|
// cause maintenance to run. It treats kopia maintenance as a black box and
|
||||||
|
// only checks the returned error.
|
||||||
|
func (suite *BasicKopiaIntegrationSuite) TestMaintenance_FirstRun_NoChanges() {
|
||||||
|
ctx, flush := tester.NewContext()
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
k, err := openKopiaRepo(t, ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
w := &Wrapper{k}
|
||||||
|
|
||||||
|
opts := repository.Maintenance{
|
||||||
|
Safety: repository.FullMaintenanceSafety,
|
||||||
|
Type: repository.MetadataMaintenance,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Maintenance(ctx, opts)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BasicKopiaIntegrationSuite) TestMaintenance_WrongUser_NoForce_Fails() {
|
||||||
|
ctx, flush := tester.NewContext()
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
k, err := openKopiaRepo(t, ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
w := &Wrapper{k}
|
||||||
|
|
||||||
|
mOpts := repository.Maintenance{
|
||||||
|
Safety: repository.FullMaintenanceSafety,
|
||||||
|
Type: repository.MetadataMaintenance,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will set the user.
|
||||||
|
err = w.Maintenance(ctx, mOpts)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
err = k.Close(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
opts := repository.Options{
|
||||||
|
User: "foo",
|
||||||
|
Host: "bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = k.Connect(ctx, opts)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
var notOwnedErr maintenance.NotOwnedError
|
||||||
|
|
||||||
|
err = w.Maintenance(ctx, mOpts)
|
||||||
|
assert.ErrorAs(t, err, ¬OwnedErr, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *BasicKopiaIntegrationSuite) TestMaintenance_WrongUser_Force_Succeeds() {
|
||||||
|
ctx, flush := tester.NewContext()
|
||||||
|
defer flush()
|
||||||
|
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
k, err := openKopiaRepo(t, ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
w := &Wrapper{k}
|
||||||
|
|
||||||
|
mOpts := repository.Maintenance{
|
||||||
|
Safety: repository.FullMaintenanceSafety,
|
||||||
|
Type: repository.MetadataMaintenance,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will set the user.
|
||||||
|
err = w.Maintenance(ctx, mOpts)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
err = k.Close(ctx)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
opts := repository.Options{
|
||||||
|
User: "foo",
|
||||||
|
Host: "bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = k.Connect(ctx, opts)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
mOpts.Force = true
|
||||||
|
|
||||||
|
// This will set the user.
|
||||||
|
err = w.Maintenance(ctx, mOpts)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
|
mOpts.Force = false
|
||||||
|
|
||||||
|
// Running without force should succeed now.
|
||||||
|
err = w.Maintenance(ctx, mOpts)
|
||||||
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------
|
||||||
|
// integration tests that use kopia and initialize a repo
|
||||||
// ---------------
|
// ---------------
|
||||||
type KopiaIntegrationSuite struct {
|
type KopiaIntegrationSuite struct {
|
||||||
tester.Suite
|
tester.Suite
|
||||||
|
|||||||
@ -41,6 +41,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/backup"
|
"github.com/alcionai/corso/src/pkg/backup"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
@ -83,7 +84,7 @@ func prepNewTestBackupOp(
|
|||||||
k = kopia.NewConn(st)
|
k = kopia.NewConn(st)
|
||||||
)
|
)
|
||||||
|
|
||||||
err := k.Initialize(ctx, control.RepoOptions{})
|
err := k.Initialize(ctx, repository.Options{})
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
// kopiaRef comes with a count of 1 and Wrapper bumps it again so safe
|
// kopiaRef comes with a count of 1 and Wrapper bumps it again so safe
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
"github.com/alcionai/corso/src/pkg/store"
|
"github.com/alcionai/corso/src/pkg/store"
|
||||||
)
|
)
|
||||||
@ -175,7 +176,7 @@ func (suite *RestoreOpIntegrationSuite) SetupSuite() {
|
|||||||
|
|
||||||
suite.acct = tester.NewM365Account(t)
|
suite.acct = tester.NewM365Account(t)
|
||||||
|
|
||||||
err := k.Initialize(ctx, control.RepoOptions{})
|
err := k.Initialize(ctx, repository.Options{})
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
suite.kopiaCloser = func(ctx context.Context) {
|
suite.kopiaCloser = func(ctx context.Context) {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/kopia"
|
"github.com/alcionai/corso/src/internal/kopia"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
@ -42,7 +42,7 @@ func (suite *StreamStoreIntgSuite) SetupSubTest() {
|
|||||||
st := tester.NewPrefixedS3Storage(t)
|
st := tester.NewPrefixedS3Storage(t)
|
||||||
|
|
||||||
k := kopia.NewConn(st)
|
k := kopia.NewConn(st)
|
||||||
require.NoError(t, k.Initialize(ctx, control.RepoOptions{}))
|
require.NoError(t, k.Initialize(ctx, repository.Options{}))
|
||||||
|
|
||||||
suite.kcloser = func() { k.Close(ctx) }
|
suite.kcloser = func() { k.Close(ctx) }
|
||||||
|
|
||||||
|
|||||||
@ -2,18 +2,19 @@ package control
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/alcionai/corso/src/internal/common"
|
"github.com/alcionai/corso/src/internal/common"
|
||||||
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options holds the optional configurations for a process
|
// Options holds the optional configurations for a process
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Collision CollisionPolicy `json:"-"`
|
Collision CollisionPolicy `json:"-"`
|
||||||
DisableMetrics bool `json:"disableMetrics"`
|
DisableMetrics bool `json:"disableMetrics"`
|
||||||
FailureHandling FailureBehavior `json:"failureHandling"`
|
FailureHandling FailureBehavior `json:"failureHandling"`
|
||||||
RestorePermissions bool `json:"restorePermissions"`
|
RestorePermissions bool `json:"restorePermissions"`
|
||||||
SkipReduce bool `json:"skipReduce"`
|
SkipReduce bool `json:"skipReduce"`
|
||||||
ToggleFeatures Toggles `json:"toggleFeatures"`
|
ToggleFeatures Toggles `json:"toggleFeatures"`
|
||||||
Parallelism Parallelism `json:"parallelism"`
|
Parallelism Parallelism `json:"parallelism"`
|
||||||
Repo RepoOptions `json:"repo"`
|
Repo repository.Options `json:"repo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FailureBehavior string
|
type FailureBehavior string
|
||||||
@ -34,12 +35,6 @@ const (
|
|||||||
BestEffort FailureBehavior = "best-effort"
|
BestEffort FailureBehavior = "best-effort"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Repo represents options that are specific to the repo storing backed up data.
|
|
||||||
type RepoOptions struct {
|
|
||||||
User string `json:"user"`
|
|
||||||
Host string `json:"host"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defaults provides an Options with the default values set.
|
// Defaults provides an Options with the default values set.
|
||||||
func Defaults() Options {
|
func Defaults() Options {
|
||||||
return Options{
|
return Options{
|
||||||
|
|||||||
24
src/pkg/control/repository/maintenancesafety_string.go
Normal file
24
src/pkg/control/repository/maintenancesafety_string.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Code generated by "stringer -type=MaintenanceSafety -linecomment"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[FullMaintenanceSafety-0]
|
||||||
|
_ = x[NoMaintenanceSafety-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _MaintenanceSafety_name = "FullMaintenanceSafetyNoMaintenanceSafety"
|
||||||
|
|
||||||
|
var _MaintenanceSafety_index = [...]uint8{0, 21, 40}
|
||||||
|
|
||||||
|
func (i MaintenanceSafety) String() string {
|
||||||
|
if i < 0 || i >= MaintenanceSafety(len(_MaintenanceSafety_index)-1) {
|
||||||
|
return "MaintenanceSafety(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _MaintenanceSafety_name[_MaintenanceSafety_index[i]:_MaintenanceSafety_index[i+1]]
|
||||||
|
}
|
||||||
24
src/pkg/control/repository/maintenancetype_string.go
Normal file
24
src/pkg/control/repository/maintenancetype_string.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Code generated by "stringer -type=MaintenanceType -linecomment"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[CompleteMaintenance-0]
|
||||||
|
_ = x[MetadataMaintenance-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _MaintenanceType_name = "completemetadata"
|
||||||
|
|
||||||
|
var _MaintenanceType_index = [...]uint8{0, 8, 16}
|
||||||
|
|
||||||
|
func (i MaintenanceType) String() string {
|
||||||
|
if i < 0 || i >= MaintenanceType(len(_MaintenanceType_index)-1) {
|
||||||
|
return "MaintenanceType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _MaintenanceType_name[_MaintenanceType_index[i]:_MaintenanceType_index[i+1]]
|
||||||
|
}
|
||||||
37
src/pkg/control/repository/repo.go
Normal file
37
src/pkg/control/repository/repo.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
// Repo represents options that are specific to the repo storing backed up data.
|
||||||
|
type Options struct {
|
||||||
|
User string `json:"user"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Maintenance struct {
|
||||||
|
Type MaintenanceType `json:"type"`
|
||||||
|
Safety MaintenanceSafety `json:"safety"`
|
||||||
|
Force bool `json:"force"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Maintenance flags
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type MaintenanceType int
|
||||||
|
|
||||||
|
// Can't be reordered as we rely on iota for numbering.
|
||||||
|
//
|
||||||
|
//go:generate stringer -type=MaintenanceType -linecomment
|
||||||
|
const (
|
||||||
|
CompleteMaintenance MaintenanceType = iota // complete
|
||||||
|
MetadataMaintenance // metadata
|
||||||
|
)
|
||||||
|
|
||||||
|
type MaintenanceSafety int
|
||||||
|
|
||||||
|
// Can't be reordered as we rely on iota for numbering.
|
||||||
|
//
|
||||||
|
//go:generate stringer -type=MaintenanceSafety -linecomment
|
||||||
|
const (
|
||||||
|
FullMaintenanceSafety MaintenanceSafety = iota
|
||||||
|
NoMaintenanceSafety
|
||||||
|
)
|
||||||
@ -20,7 +20,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/backup"
|
"github.com/alcionai/corso/src/pkg/backup"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
rep "github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
@ -237,10 +237,10 @@ func (suite *RepositoryModelIntgSuite) SetupSuite() {
|
|||||||
|
|
||||||
require.NotNil(t, k)
|
require.NotNil(t, k)
|
||||||
|
|
||||||
err = k.Initialize(ctx, control.RepoOptions{})
|
err = k.Initialize(ctx, rep.Options{})
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
err = k.Connect(ctx, control.RepoOptions{})
|
err = k.Connect(ctx, rep.Options{})
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
suite.kopiaCloser = func(ctx context.Context) {
|
suite.kopiaCloser = func(ctx context.Context) {
|
||||||
@ -287,8 +287,8 @@ func (suite *RepositoryModelIntgSuite) TestGetRepositoryModel() {
|
|||||||
k = kopia.NewConn(s)
|
k = kopia.NewConn(s)
|
||||||
)
|
)
|
||||||
|
|
||||||
require.NoError(t, k.Initialize(ctx, control.RepoOptions{}))
|
require.NoError(t, k.Initialize(ctx, rep.Options{}))
|
||||||
require.NoError(t, k.Connect(ctx, control.RepoOptions{}))
|
require.NoError(t, k.Connect(ctx, rep.Options{}))
|
||||||
|
|
||||||
defer k.Close(ctx)
|
defer k.Close(ctx)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user