adds store package for wrapping model_store (#346)
* adds store package for wrapping model_store Introduces the pkg/store package, which contains funcs for wrapping the model_store with common requests. This package choice was made for its combination of being in an accessible place, centralizing functionality and not introducing circular dependencies.
This commit is contained in:
parent
7f60c00466
commit
6c22d5c0ce
@ -246,12 +246,12 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
defer utils.CloseRepo(ctx, r)
|
||||
|
||||
rpd, err := r.BackupDetails(ctx, backupDetailsID)
|
||||
d, _, err := r.BackupDetails(ctx, backupDetailsID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to get backup details in the repository")
|
||||
}
|
||||
|
||||
print.Entries(rpd.Entries)
|
||||
print.Entries(d.Entries)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ type Printable interface {
|
||||
}
|
||||
|
||||
// Prints the backups to the terminal with stdout.
|
||||
func Backups(bs []*backup.Backup) {
|
||||
func Backups(bs []backup.Backup) {
|
||||
ps := []Printable{}
|
||||
for _, b := range bs {
|
||||
ps = append(ps, b)
|
||||
|
||||
@ -14,21 +14,11 @@ import (
|
||||
const stableIDKey = "stableID"
|
||||
|
||||
var (
|
||||
errNoModelStoreID = errors.New("model has no ModelStoreID")
|
||||
errNoStableID = errors.New("model has no StableID")
|
||||
errBadTagKey = errors.New("tag key overlaps with required key")
|
||||
errModelTypeMismatch = errors.New("model type doesn't match request")
|
||||
)
|
||||
|
||||
type modelType int
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=modelType
|
||||
const (
|
||||
UnknownModel = modelType(iota)
|
||||
BackupOpModel
|
||||
RestoreOpModel
|
||||
BackupModel
|
||||
BackupDetailsModel
|
||||
errNoModelStoreID = errors.New("model has no ModelStoreID")
|
||||
errNoStableID = errors.New("model has no StableID")
|
||||
errBadTagKey = errors.New("tag key overlaps with required key")
|
||||
errModelTypeMismatch = errors.New("model type doesn't match request")
|
||||
errUnrecognizedSchema = errors.New("unrecognized model schema")
|
||||
)
|
||||
|
||||
func NewModelStore(c *conn) (*ModelStore, error) {
|
||||
@ -54,20 +44,16 @@ func (ms *ModelStore) Close(ctx context.Context) error {
|
||||
return errors.Wrap(err, "closing ModelStore")
|
||||
}
|
||||
|
||||
// tagsForModel creates a copy of tags and adds a tag for the model type to it.
|
||||
// Returns an error if another tag has the same key as the model type or if a
|
||||
// tagsForModel creates a copy of tags and adds a tag for the model schema to it.
|
||||
// Returns an error if another tag has the same key as the model schema or if a
|
||||
// bad model type is given.
|
||||
func tagsForModel(t modelType, tags map[string]string) (map[string]string, error) {
|
||||
if t == UnknownModel {
|
||||
return nil, errors.New("bad model type")
|
||||
}
|
||||
|
||||
func tagsForModel(s model.Schema, tags map[string]string) (map[string]string, error) {
|
||||
if _, ok := tags[manifest.TypeLabelKey]; ok {
|
||||
return nil, errors.WithStack(errBadTagKey)
|
||||
}
|
||||
|
||||
res := make(map[string]string, len(tags)+1)
|
||||
res[manifest.TypeLabelKey] = t.String()
|
||||
res[manifest.TypeLabelKey] = s.String()
|
||||
for k, v := range tags {
|
||||
res[k] = v
|
||||
}
|
||||
@ -79,15 +65,19 @@ func tagsForModel(t modelType, tags map[string]string) (map[string]string, error
|
||||
// StableID to it. Returns an error if another tag has the same key as the model
|
||||
// type or if a bad model type is given.
|
||||
func tagsForModelWithID(
|
||||
t modelType,
|
||||
s model.Schema,
|
||||
id model.ID,
|
||||
tags map[string]string,
|
||||
) (map[string]string, error) {
|
||||
if !s.Valid() {
|
||||
return nil, errors.New("unrecognized model schema")
|
||||
}
|
||||
|
||||
if len(id) == 0 {
|
||||
return nil, errors.WithStack(errNoStableID)
|
||||
}
|
||||
|
||||
res, err := tagsForModel(t, tags)
|
||||
res, err := tagsForModel(s, tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -106,16 +96,20 @@ func tagsForModelWithID(
|
||||
func putInner(
|
||||
ctx context.Context,
|
||||
w repo.RepositoryWriter,
|
||||
t modelType,
|
||||
s model.Schema,
|
||||
m model.Model,
|
||||
create bool,
|
||||
) error {
|
||||
if !s.Valid() {
|
||||
return errors.WithStack(errUnrecognizedSchema)
|
||||
}
|
||||
|
||||
base := m.Base()
|
||||
if create {
|
||||
base.StableID = model.ID(uuid.NewString())
|
||||
}
|
||||
|
||||
tmpTags, err := tagsForModelWithID(t, base.StableID, base.Tags)
|
||||
tmpTags, err := tagsForModelWithID(s, base.StableID, base.Tags)
|
||||
if err != nil {
|
||||
// Will be wrapped at a higher layer.
|
||||
return err
|
||||
@ -135,15 +129,18 @@ func putInner(
|
||||
// given to this function can later be used to help lookup the model.
|
||||
func (ms *ModelStore) Put(
|
||||
ctx context.Context,
|
||||
t modelType,
|
||||
s model.Schema,
|
||||
m model.Model,
|
||||
) error {
|
||||
if !s.Valid() {
|
||||
return errors.WithStack(errUnrecognizedSchema)
|
||||
}
|
||||
err := repo.WriteSession(
|
||||
ctx,
|
||||
ms.c,
|
||||
repo.WriteSessionOptions{Purpose: "ModelStorePut"},
|
||||
func(innerCtx context.Context, w repo.RepositoryWriter) error {
|
||||
err := putInner(innerCtx, w, t, m, true)
|
||||
err := putInner(innerCtx, w, s, m, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -181,14 +178,18 @@ func baseModelFromMetadata(m *manifest.EntryMetadata) (*model.BaseModel, error)
|
||||
// Update, or Delete.
|
||||
func (ms *ModelStore) GetIDsForType(
|
||||
ctx context.Context,
|
||||
t modelType,
|
||||
s model.Schema,
|
||||
tags map[string]string,
|
||||
) ([]*model.BaseModel, error) {
|
||||
if !s.Valid() {
|
||||
return nil, errors.New("unrecognized model schema")
|
||||
}
|
||||
|
||||
if _, ok := tags[stableIDKey]; ok {
|
||||
return nil, errors.WithStack(errBadTagKey)
|
||||
}
|
||||
|
||||
tmpTags, err := tagsForModel(t, tags)
|
||||
tmpTags, err := tagsForModel(s, tags)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting model metadata")
|
||||
}
|
||||
@ -217,9 +218,13 @@ func (ms *ModelStore) GetIDsForType(
|
||||
// one model has the same StableID.
|
||||
func (ms *ModelStore) getModelStoreID(
|
||||
ctx context.Context,
|
||||
t modelType,
|
||||
s model.Schema,
|
||||
id model.ID,
|
||||
) (manifest.ID, error) {
|
||||
if !s.Valid() {
|
||||
return "", errors.New("unrecognized model schema")
|
||||
}
|
||||
|
||||
if len(id) == 0 {
|
||||
return "", errors.WithStack(errNoStableID)
|
||||
}
|
||||
@ -236,7 +241,7 @@ func (ms *ModelStore) getModelStoreID(
|
||||
if len(metadata) != 1 {
|
||||
return "", errors.New("multiple models with same StableID")
|
||||
}
|
||||
if metadata[0].Labels[manifest.TypeLabelKey] != t.String() {
|
||||
if metadata[0].Labels[manifest.TypeLabelKey] != s.String() {
|
||||
return "", errors.WithStack(errModelTypeMismatch)
|
||||
}
|
||||
|
||||
@ -249,16 +254,20 @@ func (ms *ModelStore) getModelStoreID(
|
||||
// or if multiple models have the same StableID.
|
||||
func (ms *ModelStore) Get(
|
||||
ctx context.Context,
|
||||
t modelType,
|
||||
s model.Schema,
|
||||
id model.ID,
|
||||
data model.Model,
|
||||
) error {
|
||||
modelID, err := ms.getModelStoreID(ctx, t, id)
|
||||
if !s.Valid() {
|
||||
return errors.WithStack(errUnrecognizedSchema)
|
||||
}
|
||||
|
||||
modelID, err := ms.getModelStoreID(ctx, s, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ms.GetWithModelStoreID(ctx, t, modelID, data)
|
||||
return ms.GetWithModelStoreID(ctx, s, modelID, data)
|
||||
}
|
||||
|
||||
// GetWithModelStoreID deserializes the model with the given ModelStoreID into
|
||||
@ -267,10 +276,14 @@ func (ms *ModelStore) Get(
|
||||
// expected.
|
||||
func (ms *ModelStore) GetWithModelStoreID(
|
||||
ctx context.Context,
|
||||
t modelType,
|
||||
s model.Schema,
|
||||
id manifest.ID,
|
||||
data model.Model,
|
||||
) error {
|
||||
if !s.Valid() {
|
||||
return errors.WithStack(errUnrecognizedSchema)
|
||||
}
|
||||
|
||||
if len(id) == 0 {
|
||||
return errors.WithStack(errNoModelStoreID)
|
||||
}
|
||||
@ -282,7 +295,7 @@ func (ms *ModelStore) GetWithModelStoreID(
|
||||
return errors.Wrap(err, "getting model data")
|
||||
}
|
||||
|
||||
if metadata.Labels[manifest.TypeLabelKey] != t.String() {
|
||||
if metadata.Labels[manifest.TypeLabelKey] != s.String() {
|
||||
return errors.WithStack(errModelTypeMismatch)
|
||||
}
|
||||
|
||||
@ -293,17 +306,21 @@ func (ms *ModelStore) GetWithModelStoreID(
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkPrevModelVersion compares the modelType and ModelStoreID in this model
|
||||
// checkPrevModelVersion compares the ModelType and ModelStoreID in this model
|
||||
// to model(s) previously stored in ModelStore that have the same StableID.
|
||||
// Returns an error if no models or more than one model has the same StableID or
|
||||
// the modelType or ModelStoreID differ between the stored model and the given
|
||||
// the ModelType or ModelStoreID differ between the stored model and the given
|
||||
// model.
|
||||
func (ms *ModelStore) checkPrevModelVersion(
|
||||
ctx context.Context,
|
||||
t modelType,
|
||||
s model.Schema,
|
||||
b *model.BaseModel,
|
||||
) error {
|
||||
id, err := ms.getModelStoreID(ctx, t, b.StableID)
|
||||
if !s.Valid() {
|
||||
return errors.WithStack(errUnrecognizedSchema)
|
||||
}
|
||||
|
||||
id, err := ms.getModelStoreID(ctx, s, b.StableID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -317,7 +334,7 @@ func (ms *ModelStore) checkPrevModelVersion(
|
||||
if meta.ID != b.ModelStoreID {
|
||||
return errors.New("updated model has different ModelStoreID")
|
||||
}
|
||||
if meta.Labels[manifest.TypeLabelKey] != t.String() {
|
||||
if meta.Labels[manifest.TypeLabelKey] != s.String() {
|
||||
return errors.New("updated model has different model type")
|
||||
}
|
||||
|
||||
@ -327,21 +344,25 @@ func (ms *ModelStore) checkPrevModelVersion(
|
||||
// Update adds the new version of the model with the given StableID to the model
|
||||
// store and deletes the version of the model with old ModelStoreID if the old
|
||||
// and new ModelStoreIDs do not match. Returns an error if another model has
|
||||
// the same StableID but a different modelType or ModelStoreID or there is no
|
||||
// the same StableID but a different ModelType or ModelStoreID or there is no
|
||||
// previous version of the model. If an error occurs no visible changes will be
|
||||
// made to the stored model.
|
||||
func (ms *ModelStore) Update(
|
||||
ctx context.Context,
|
||||
t modelType,
|
||||
s model.Schema,
|
||||
m model.Model,
|
||||
) error {
|
||||
if !s.Valid() {
|
||||
return errors.WithStack(errUnrecognizedSchema)
|
||||
}
|
||||
|
||||
base := m.Base()
|
||||
if len(base.ModelStoreID) == 0 {
|
||||
return errors.WithStack(errNoModelStoreID)
|
||||
}
|
||||
|
||||
// TODO(ashmrtnz): Can remove if bottleneck.
|
||||
if err := ms.checkPrevModelVersion(ctx, t, base); err != nil {
|
||||
if err := ms.checkPrevModelVersion(ctx, s, base); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -359,7 +380,7 @@ func (ms *ModelStore) Update(
|
||||
}
|
||||
}()
|
||||
|
||||
if innerErr = putInner(innerCtx, w, t, m, false); innerErr != nil {
|
||||
if innerErr = putInner(innerCtx, w, s, m, false); innerErr != nil {
|
||||
return innerErr
|
||||
}
|
||||
|
||||
@ -384,8 +405,12 @@ func (ms *ModelStore) Update(
|
||||
// Delete deletes the model with the given StableID. Turns into a noop if id is
|
||||
// not empty but the model does not exist. Returns an error if multiple models
|
||||
// have the same StableID.
|
||||
func (ms *ModelStore) Delete(ctx context.Context, t modelType, id model.ID) error {
|
||||
latest, err := ms.getModelStoreID(ctx, t, id)
|
||||
func (ms *ModelStore) Delete(ctx context.Context, s model.Schema, id model.ID) error {
|
||||
if !s.Valid() {
|
||||
return errors.WithStack(errUnrecognizedSchema)
|
||||
}
|
||||
|
||||
latest, err := ms.getModelStoreID(ctx, s, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, manifest.ErrNotFound) {
|
||||
return nil
|
||||
|
||||
@ -102,10 +102,10 @@ func (suite *ModelStoreIntegrationSuite) TestBadTagsErrors() {
|
||||
foo := &fooModel{Bar: uuid.NewString()}
|
||||
foo.Tags = test.tags
|
||||
|
||||
assert.Error(t, suite.m.Put(suite.ctx, BackupOpModel, foo))
|
||||
assert.Error(t, suite.m.Update(suite.ctx, BackupOpModel, foo))
|
||||
assert.Error(t, suite.m.Put(suite.ctx, model.BackupOpSchema, foo))
|
||||
assert.Error(t, suite.m.Update(suite.ctx, model.BackupOpSchema, foo))
|
||||
|
||||
_, err := suite.m.GetIDsForType(suite.ctx, BackupOpModel, test.tags)
|
||||
_, err := suite.m.GetIDsForType(suite.ctx, model.BackupOpSchema, test.tags)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
@ -113,7 +113,7 @@ func (suite *ModelStoreIntegrationSuite) TestBadTagsErrors() {
|
||||
|
||||
func (suite *ModelStoreIntegrationSuite) TestNoIDsErrors() {
|
||||
t := suite.T()
|
||||
theModelType := BackupOpModel
|
||||
theModelType := model.BackupOpSchema
|
||||
|
||||
noStableID := &fooModel{Bar: uuid.NewString()}
|
||||
noStableID.StableID = ""
|
||||
@ -138,13 +138,13 @@ func (suite *ModelStoreIntegrationSuite) TestBadModelTypeErrors() {
|
||||
|
||||
foo := &fooModel{Bar: uuid.NewString()}
|
||||
|
||||
assert.Error(t, suite.m.Put(suite.ctx, UnknownModel, foo))
|
||||
assert.Error(t, suite.m.Put(suite.ctx, model.UnknownSchema, foo))
|
||||
|
||||
require.NoError(t, suite.m.Put(suite.ctx, BackupOpModel, foo))
|
||||
require.NoError(t, suite.m.Put(suite.ctx, model.BackupOpSchema, foo))
|
||||
|
||||
_, err := suite.m.GetIDsForType(suite.ctx, UnknownModel, nil)
|
||||
_, err := suite.m.GetIDsForType(suite.ctx, model.UnknownSchema, nil)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "model type")
|
||||
assert.Contains(t, err.Error(), "schema")
|
||||
}
|
||||
|
||||
func (suite *ModelStoreIntegrationSuite) TestBadTypeErrors() {
|
||||
@ -152,51 +152,51 @@ func (suite *ModelStoreIntegrationSuite) TestBadTypeErrors() {
|
||||
|
||||
foo := &fooModel{Bar: uuid.NewString()}
|
||||
|
||||
require.NoError(t, suite.m.Put(suite.ctx, BackupOpModel, foo))
|
||||
require.NoError(t, suite.m.Put(suite.ctx, model.BackupOpSchema, foo))
|
||||
|
||||
returned := &fooModel{}
|
||||
assert.Error(t, suite.m.Get(suite.ctx, RestoreOpModel, foo.StableID, returned))
|
||||
assert.Error(t, suite.m.Get(suite.ctx, model.RestoreOpSchema, foo.StableID, returned))
|
||||
assert.Error(
|
||||
t, suite.m.GetWithModelStoreID(suite.ctx, RestoreOpModel, foo.ModelStoreID, returned))
|
||||
t, suite.m.GetWithModelStoreID(suite.ctx, model.RestoreOpSchema, foo.ModelStoreID, returned))
|
||||
|
||||
assert.Error(t, suite.m.Delete(suite.ctx, RestoreOpModel, foo.StableID))
|
||||
assert.Error(t, suite.m.Delete(suite.ctx, model.RestoreOpSchema, foo.StableID))
|
||||
}
|
||||
|
||||
func (suite *ModelStoreIntegrationSuite) TestPutGet() {
|
||||
table := []struct {
|
||||
t modelType
|
||||
s model.Schema
|
||||
check require.ErrorAssertionFunc
|
||||
hasErr bool
|
||||
}{
|
||||
{
|
||||
t: UnknownModel,
|
||||
s: model.UnknownSchema,
|
||||
check: require.Error,
|
||||
hasErr: true,
|
||||
},
|
||||
{
|
||||
t: BackupOpModel,
|
||||
s: model.BackupOpSchema,
|
||||
check: require.NoError,
|
||||
hasErr: false,
|
||||
},
|
||||
{
|
||||
t: RestoreOpModel,
|
||||
s: model.RestoreOpSchema,
|
||||
check: require.NoError,
|
||||
hasErr: false,
|
||||
},
|
||||
{
|
||||
t: BackupModel,
|
||||
s: model.BackupSchema,
|
||||
check: require.NoError,
|
||||
hasErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.t.String(), func(t *testing.T) {
|
||||
suite.T().Run(test.s.String(), func(t *testing.T) {
|
||||
foo := &fooModel{Bar: uuid.NewString()}
|
||||
// Avoid some silly test errors from comparing nil to empty map.
|
||||
foo.Tags = map[string]string{}
|
||||
|
||||
err := suite.m.Put(suite.ctx, test.t, foo)
|
||||
err := suite.m.Put(suite.ctx, test.s, foo)
|
||||
test.check(t, err)
|
||||
|
||||
if test.hasErr {
|
||||
@ -207,11 +207,11 @@ func (suite *ModelStoreIntegrationSuite) TestPutGet() {
|
||||
require.NotEmpty(t, foo.StableID)
|
||||
|
||||
returned := &fooModel{}
|
||||
err = suite.m.Get(suite.ctx, test.t, foo.StableID, returned)
|
||||
err = suite.m.Get(suite.ctx, test.s, foo.StableID, returned)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, foo, returned)
|
||||
|
||||
err = suite.m.GetWithModelStoreID(suite.ctx, test.t, foo.ModelStoreID, returned)
|
||||
err = suite.m.GetWithModelStoreID(suite.ctx, test.s, foo.ModelStoreID, returned)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, foo, returned)
|
||||
})
|
||||
@ -220,7 +220,7 @@ func (suite *ModelStoreIntegrationSuite) TestPutGet() {
|
||||
|
||||
func (suite *ModelStoreIntegrationSuite) TestPutGet_WithTags() {
|
||||
t := suite.T()
|
||||
theModelType := BackupOpModel
|
||||
theModelType := model.BackupOpSchema
|
||||
|
||||
foo := &fooModel{Bar: uuid.NewString()}
|
||||
foo.Tags = map[string]string{
|
||||
@ -245,51 +245,51 @@ func (suite *ModelStoreIntegrationSuite) TestPutGet_WithTags() {
|
||||
func (suite *ModelStoreIntegrationSuite) TestGet_NotFoundErrors() {
|
||||
t := suite.T()
|
||||
|
||||
assert.ErrorIs(t, suite.m.Get(suite.ctx, BackupOpModel, "baz", nil), manifest.ErrNotFound)
|
||||
assert.ErrorIs(t, suite.m.Get(suite.ctx, model.BackupOpSchema, "baz", nil), manifest.ErrNotFound)
|
||||
assert.ErrorIs(
|
||||
t, suite.m.GetWithModelStoreID(suite.ctx, BackupOpModel, "baz", nil), manifest.ErrNotFound)
|
||||
t, suite.m.GetWithModelStoreID(suite.ctx, model.BackupOpSchema, "baz", nil), manifest.ErrNotFound)
|
||||
}
|
||||
|
||||
func (suite *ModelStoreIntegrationSuite) TestPutGetOfType() {
|
||||
table := []struct {
|
||||
t modelType
|
||||
s model.Schema
|
||||
check require.ErrorAssertionFunc
|
||||
hasErr bool
|
||||
}{
|
||||
{
|
||||
t: UnknownModel,
|
||||
s: model.UnknownSchema,
|
||||
check: require.Error,
|
||||
hasErr: true,
|
||||
},
|
||||
{
|
||||
t: BackupOpModel,
|
||||
s: model.BackupOpSchema,
|
||||
check: require.NoError,
|
||||
hasErr: false,
|
||||
},
|
||||
{
|
||||
t: RestoreOpModel,
|
||||
s: model.RestoreOpSchema,
|
||||
check: require.NoError,
|
||||
hasErr: false,
|
||||
},
|
||||
{
|
||||
t: BackupModel,
|
||||
s: model.BackupSchema,
|
||||
check: require.NoError,
|
||||
hasErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.t.String(), func(t *testing.T) {
|
||||
suite.T().Run(test.s.String(), func(t *testing.T) {
|
||||
foo := &fooModel{Bar: uuid.NewString()}
|
||||
|
||||
err := suite.m.Put(suite.ctx, test.t, foo)
|
||||
err := suite.m.Put(suite.ctx, test.s, foo)
|
||||
test.check(t, err)
|
||||
|
||||
if test.hasErr {
|
||||
return
|
||||
}
|
||||
|
||||
ids, err := suite.m.GetIDsForType(suite.ctx, test.t, nil)
|
||||
ids, err := suite.m.GetIDsForType(suite.ctx, test.s, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, ids, 1)
|
||||
@ -322,7 +322,7 @@ func (suite *ModelStoreIntegrationSuite) TestPutUpdate() {
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
theModelType := BackupOpModel
|
||||
theModelType := model.BackupOpSchema
|
||||
|
||||
m := getModelStore(t, ctx)
|
||||
defer func() {
|
||||
@ -365,23 +365,23 @@ func (suite *ModelStoreIntegrationSuite) TestPutUpdate() {
|
||||
}
|
||||
|
||||
func (suite *ModelStoreIntegrationSuite) TestPutUpdate_FailsNotMatchingPrev() {
|
||||
startModelType := BackupOpModel
|
||||
startModelType := model.BackupOpSchema
|
||||
|
||||
table := []struct {
|
||||
name string
|
||||
t modelType
|
||||
s model.Schema
|
||||
mutator func(m *fooModel)
|
||||
}{
|
||||
{
|
||||
name: "DifferentModelStoreID",
|
||||
t: startModelType,
|
||||
s: startModelType,
|
||||
mutator: func(m *fooModel) {
|
||||
m.ModelStoreID = manifest.ID("bar")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DifferentModelType",
|
||||
t: RestoreOpModel,
|
||||
s: model.RestoreOpSchema,
|
||||
mutator: func(m *fooModel) {
|
||||
},
|
||||
},
|
||||
@ -402,14 +402,14 @@ func (suite *ModelStoreIntegrationSuite) TestPutUpdate_FailsNotMatchingPrev() {
|
||||
|
||||
test.mutator(foo)
|
||||
|
||||
assert.Error(t, m.Update(ctx, test.t, foo))
|
||||
assert.Error(t, m.Update(ctx, test.s, foo))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ModelStoreIntegrationSuite) TestPutDelete() {
|
||||
t := suite.T()
|
||||
theModelType := BackupOpModel
|
||||
theModelType := model.BackupOpSchema
|
||||
|
||||
foo := &fooModel{Bar: uuid.NewString()}
|
||||
|
||||
@ -425,7 +425,7 @@ func (suite *ModelStoreIntegrationSuite) TestPutDelete() {
|
||||
func (suite *ModelStoreIntegrationSuite) TestPutDelete_BadIDsNoop() {
|
||||
t := suite.T()
|
||||
|
||||
assert.NoError(t, suite.m.Delete(suite.ctx, BackupOpModel, "foo"))
|
||||
assert.NoError(t, suite.m.Delete(suite.ctx, model.BackupOpSchema, "foo"))
|
||||
assert.NoError(t, suite.m.DeleteWithModelStoreID(suite.ctx, "foo"))
|
||||
}
|
||||
|
||||
@ -472,7 +472,7 @@ func (suite *ModelStoreRegressionSuite) TestFailDuringWriteSessionHasNoVisibleEf
|
||||
// Avoid some silly test errors from comparing nil to empty map.
|
||||
foo.Tags = map[string]string{}
|
||||
|
||||
theModelType := BackupOpModel
|
||||
theModelType := model.BackupOpSchema
|
||||
|
||||
require.NoError(t, m.Put(ctx, theModelType, foo))
|
||||
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
// Code generated by "stringer -type=modelType"; DO NOT EDIT.
|
||||
|
||||
package kopia
|
||||
|
||||
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[UnknownModel-0]
|
||||
_ = x[BackupOpModel-1]
|
||||
_ = x[RestoreOpModel-2]
|
||||
_ = x[BackupModel-3]
|
||||
_ = x[BackupDetailsModel-4]
|
||||
}
|
||||
|
||||
const _modelType_name = "UnknownModelBackupOpModelRestoreOpModelBackupModelBackupDetailsModel"
|
||||
|
||||
var _modelType_index = [...]uint8{0, 12, 25, 39, 50, 68}
|
||||
|
||||
func (i modelType) String() string {
|
||||
if i < 0 || i >= modelType(len(_modelType_index)-1) {
|
||||
return "modelType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _modelType_name[_modelType_index[i]:_modelType_index[i+1]]
|
||||
}
|
||||
@ -4,7 +4,24 @@ import (
|
||||
"github.com/kopia/kopia/repo/manifest"
|
||||
)
|
||||
|
||||
type ID string
|
||||
type (
|
||||
ID string
|
||||
Schema int
|
||||
)
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=Schema
|
||||
const (
|
||||
UnknownSchema = Schema(iota)
|
||||
BackupOpSchema
|
||||
RestoreOpSchema
|
||||
BackupSchema
|
||||
BackupDetailsSchema
|
||||
)
|
||||
|
||||
// Valid returns true if the ModelType value fits within the iota range.
|
||||
func (mt Schema) Valid() bool {
|
||||
return mt > 0 && mt < BackupDetailsSchema+1
|
||||
}
|
||||
|
||||
type Model interface {
|
||||
// Returns a handle to the BaseModel for this model.
|
||||
|
||||
37
src/internal/model/model_test.go
Normal file
37
src/internal/model/model_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package model_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alcionai/corso/internal/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type ModelUnitSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestModelUnitSuite(t *testing.T) {
|
||||
suite.Run(t, new(ModelUnitSuite))
|
||||
}
|
||||
|
||||
func (suite *ModelUnitSuite) TestValid() {
|
||||
table := []struct {
|
||||
mt model.Schema
|
||||
expect assert.BoolAssertionFunc
|
||||
}{
|
||||
{model.UnknownSchema, assert.False},
|
||||
{model.BackupOpSchema, assert.True},
|
||||
{model.RestoreOpSchema, assert.True},
|
||||
{model.BackupSchema, assert.True},
|
||||
{model.BackupDetailsSchema, assert.True},
|
||||
{model.Schema(-1), assert.False},
|
||||
{model.Schema(100), assert.False},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.mt.String(), func(t *testing.T) {
|
||||
test.expect(t, test.mt.Valid())
|
||||
})
|
||||
}
|
||||
}
|
||||
27
src/internal/model/schema_string.go
Normal file
27
src/internal/model/schema_string.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Code generated by "stringer -type=Schema"; DO NOT EDIT.
|
||||
|
||||
package model
|
||||
|
||||
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[UnknownSchema-0]
|
||||
_ = x[BackupOpSchema-1]
|
||||
_ = x[RestoreOpSchema-2]
|
||||
_ = x[BackupSchema-3]
|
||||
_ = x[BackupDetailsSchema-4]
|
||||
}
|
||||
|
||||
const _Schema_name = "UnknownSchemaBackupOpSchemaRestoreOpSchemaBackupSchemaBackupDetailsSchema"
|
||||
|
||||
var _Schema_index = [...]uint8{0, 13, 27, 42, 54, 73}
|
||||
|
||||
func (i Schema) String() string {
|
||||
if i < 0 || i >= Schema(len(_Schema_index)-1) {
|
||||
return "Schema(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Schema_name[_Schema_index[i]:_Schema_index[i+1]]
|
||||
}
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/alcionai/corso/pkg/account"
|
||||
"github.com/alcionai/corso/pkg/backup"
|
||||
"github.com/alcionai/corso/pkg/selectors"
|
||||
"github.com/alcionai/corso/pkg/store"
|
||||
)
|
||||
|
||||
// BackupOperation wraps an operation with backup-specific props.
|
||||
@ -38,12 +39,12 @@ func NewBackupOperation(
|
||||
ctx context.Context,
|
||||
opts Options,
|
||||
kw *kopia.Wrapper,
|
||||
ms *kopia.ModelStore,
|
||||
sw *store.Wrapper,
|
||||
acct account.Account,
|
||||
selector selectors.Selector,
|
||||
) (BackupOperation, error) {
|
||||
op := BackupOperation{
|
||||
operation: newOperation(opts, kw, ms),
|
||||
operation: newOperation(opts, kw, sw),
|
||||
Selectors: selector,
|
||||
Version: "v0",
|
||||
account: acct,
|
||||
@ -109,14 +110,14 @@ func (op *BackupOperation) Run(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (op *BackupOperation) createBackupModels(ctx context.Context, snapID string, details *backup.Details) error {
|
||||
err := op.modelStore.Put(ctx, kopia.BackupDetailsModel, &details.DetailsModel)
|
||||
err := op.store.Put(ctx, model.BackupDetailsSchema, &details.DetailsModel)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating backupdetails model")
|
||||
}
|
||||
|
||||
bu := backup.New(snapID, string(details.ModelStoreID))
|
||||
|
||||
err = op.modelStore.Put(ctx, kopia.BackupModel, bu)
|
||||
err = op.store.Put(ctx, model.BackupSchema, bu)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating backup model")
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
ctesting "github.com/alcionai/corso/internal/testing"
|
||||
"github.com/alcionai/corso/pkg/account"
|
||||
"github.com/alcionai/corso/pkg/selectors"
|
||||
"github.com/alcionai/corso/pkg/store"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -37,7 +38,7 @@ func (suite *BackupOpSuite) TestBackupOperation_PersistResults() {
|
||||
|
||||
var (
|
||||
kw = &kopia.Wrapper{}
|
||||
ms = &kopia.ModelStore{}
|
||||
sw = &store.Wrapper{}
|
||||
acct = account.Account{}
|
||||
now = time.Now()
|
||||
stats = backupStats{
|
||||
@ -52,7 +53,7 @@ func (suite *BackupOpSuite) TestBackupOperation_PersistResults() {
|
||||
}
|
||||
)
|
||||
|
||||
op, err := NewBackupOperation(ctx, Options{}, kw, ms, acct, selectors.Selector{})
|
||||
op, err := NewBackupOperation(ctx, Options{}, kw, sw, acct, selectors.Selector{})
|
||||
require.NoError(t, err)
|
||||
|
||||
op.persistResults(now, &stats)
|
||||
@ -96,7 +97,7 @@ func (suite *BackupOpIntegrationSuite) SetupSuite() {
|
||||
|
||||
func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() {
|
||||
kw := &kopia.Wrapper{}
|
||||
ms := &kopia.ModelStore{}
|
||||
sw := &store.Wrapper{}
|
||||
acct, err := ctesting.NewM365Account()
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
@ -104,13 +105,13 @@ func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() {
|
||||
name string
|
||||
opts Options
|
||||
kw *kopia.Wrapper
|
||||
ms *kopia.ModelStore
|
||||
sw *store.Wrapper
|
||||
acct account.Account
|
||||
targets []string
|
||||
errCheck assert.ErrorAssertionFunc
|
||||
}{
|
||||
{"good", Options{}, kw, ms, acct, nil, assert.NoError},
|
||||
{"missing kopia", Options{}, nil, ms, acct, nil, assert.Error},
|
||||
{"good", Options{}, kw, sw, acct, nil, assert.NoError},
|
||||
{"missing kopia", Options{}, nil, sw, acct, nil, assert.Error},
|
||||
{"missing modelstore", Options{}, kw, nil, acct, nil, assert.Error},
|
||||
}
|
||||
for _, test := range table {
|
||||
@ -119,7 +120,7 @@ func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() {
|
||||
context.Background(),
|
||||
Options{},
|
||||
test.kw,
|
||||
test.ms,
|
||||
test.sw,
|
||||
test.acct,
|
||||
selectors.Selector{})
|
||||
test.errCheck(t, err)
|
||||
@ -146,22 +147,24 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run() {
|
||||
// to close here.
|
||||
defer k.Close(ctx)
|
||||
|
||||
w, err := kopia.NewWrapper(k)
|
||||
kw, err := kopia.NewWrapper(k)
|
||||
require.NoError(t, err)
|
||||
defer w.Close(ctx)
|
||||
defer kw.Close(ctx)
|
||||
|
||||
ms, err := kopia.NewModelStore(k)
|
||||
require.NoError(t, err)
|
||||
defer ms.Close(ctx)
|
||||
|
||||
sw := store.NewKopiaStore(ms)
|
||||
|
||||
sel := selectors.NewExchangeBackup()
|
||||
sel.Include(sel.Users(m365User))
|
||||
|
||||
bo, err := NewBackupOperation(
|
||||
ctx,
|
||||
Options{},
|
||||
w,
|
||||
ms,
|
||||
kw,
|
||||
sw,
|
||||
acct,
|
||||
sel.Selector)
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/alcionai/corso/internal/kopia"
|
||||
"github.com/alcionai/corso/pkg/store"
|
||||
)
|
||||
|
||||
type opStatus int
|
||||
@ -31,8 +32,8 @@ type operation struct {
|
||||
Options Options `json:"options"`
|
||||
Status opStatus `json:"status"`
|
||||
|
||||
kopia *kopia.Wrapper
|
||||
modelStore *kopia.ModelStore
|
||||
kopia *kopia.Wrapper
|
||||
store *store.Wrapper
|
||||
}
|
||||
|
||||
// Options configure some parameters of the operation
|
||||
@ -44,15 +45,15 @@ type Options struct {
|
||||
func newOperation(
|
||||
opts Options,
|
||||
kw *kopia.Wrapper,
|
||||
ms *kopia.ModelStore,
|
||||
sw *store.Wrapper,
|
||||
) operation {
|
||||
return operation{
|
||||
ID: uuid.New(),
|
||||
CreatedAt: time.Now(),
|
||||
Options: opts,
|
||||
kopia: kw,
|
||||
modelStore: ms,
|
||||
Status: InProgress,
|
||||
ID: uuid.New(),
|
||||
CreatedAt: time.Now(),
|
||||
Options: opts,
|
||||
kopia: kw,
|
||||
store: sw,
|
||||
Status: InProgress,
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +61,7 @@ func (op operation) validate() error {
|
||||
if op.kopia == nil {
|
||||
return errors.New("missing kopia connection")
|
||||
}
|
||||
if op.modelStore == nil {
|
||||
if op.store == nil {
|
||||
return errors.New("missing modelstore")
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/alcionai/corso/internal/kopia"
|
||||
"github.com/alcionai/corso/pkg/store"
|
||||
)
|
||||
|
||||
type OperationSuite struct {
|
||||
@ -25,20 +26,20 @@ func (suite *OperationSuite) TestNewOperation() {
|
||||
|
||||
func (suite *OperationSuite) TestOperation_Validate() {
|
||||
kwStub := &kopia.Wrapper{}
|
||||
msStub := &kopia.ModelStore{}
|
||||
swStub := &store.Wrapper{}
|
||||
table := []struct {
|
||||
name string
|
||||
kw *kopia.Wrapper
|
||||
ms *kopia.ModelStore
|
||||
sw *store.Wrapper
|
||||
errCheck assert.ErrorAssertionFunc
|
||||
}{
|
||||
{"good", kwStub, msStub, assert.NoError},
|
||||
{"missing kopia wrapper", nil, msStub, assert.Error},
|
||||
{"missing kopia modelstore", kwStub, nil, assert.Error},
|
||||
{"good", kwStub, swStub, assert.NoError},
|
||||
{"missing kopia wrapper", nil, swStub, assert.Error},
|
||||
{"missing store wrapper", kwStub, nil, assert.Error},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
op := newOperation(Options{}, test.kw, test.ms)
|
||||
op := newOperation(Options{}, test.kw, test.sw)
|
||||
test.errCheck(t, op.validate())
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/kopia/kopia/repo/manifest"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/alcionai/corso/internal/connector"
|
||||
@ -12,8 +11,8 @@ import (
|
||||
"github.com/alcionai/corso/internal/kopia"
|
||||
"github.com/alcionai/corso/internal/model"
|
||||
"github.com/alcionai/corso/pkg/account"
|
||||
"github.com/alcionai/corso/pkg/backup"
|
||||
"github.com/alcionai/corso/pkg/selectors"
|
||||
"github.com/alcionai/corso/pkg/store"
|
||||
)
|
||||
|
||||
// RestoreOperation wraps an operation with restore-specific props.
|
||||
@ -39,13 +38,13 @@ func NewRestoreOperation(
|
||||
ctx context.Context,
|
||||
opts Options,
|
||||
kw *kopia.Wrapper,
|
||||
ms *kopia.ModelStore,
|
||||
sw *store.Wrapper,
|
||||
acct account.Account,
|
||||
backupID model.ID,
|
||||
sel selectors.Selector,
|
||||
) (RestoreOperation, error) {
|
||||
op := RestoreOperation{
|
||||
operation: newOperation(opts, kw, ms),
|
||||
operation: newOperation(opts, kw, sw),
|
||||
BackupID: backupID,
|
||||
Selectors: sel,
|
||||
Version: "v0",
|
||||
@ -82,17 +81,9 @@ func (op *RestoreOperation) Run(ctx context.Context) error {
|
||||
defer op.persistResults(time.Now(), &stats)
|
||||
|
||||
// retrieve the restore point details
|
||||
bu := backup.Backup{}
|
||||
err := op.modelStore.Get(ctx, kopia.BackupModel, op.BackupID, &bu)
|
||||
d, b, err := op.store.GetDetailsFromBackupID(ctx, op.BackupID)
|
||||
if err != nil {
|
||||
stats.readErr = errors.Wrap(err, "retrieving restore point")
|
||||
return stats.readErr
|
||||
}
|
||||
|
||||
backup := backup.Details{}
|
||||
err = op.modelStore.GetWithModelStoreID(ctx, kopia.BackupDetailsModel, manifest.ID(bu.DetailsID), &backup)
|
||||
if err != nil {
|
||||
stats.readErr = errors.Wrap(err, "retrieving restore point details")
|
||||
stats.readErr = errors.Wrap(err, "getting backup details for restore")
|
||||
return stats.readErr
|
||||
}
|
||||
|
||||
@ -103,8 +94,8 @@ func (op *RestoreOperation) Run(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// format the details and retrieve the items from kopia
|
||||
fds := er.FilterDetails(&backup)
|
||||
dcs, err := op.kopia.RestoreMultipleItems(ctx, bu.SnapshotID, fds)
|
||||
fds := er.FilterDetails(d)
|
||||
dcs, err := op.kopia.RestoreMultipleItems(ctx, b.SnapshotID, fds)
|
||||
if err != nil {
|
||||
stats.readErr = errors.Wrap(err, "retrieving service data")
|
||||
return stats.readErr
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
ctesting "github.com/alcionai/corso/internal/testing"
|
||||
"github.com/alcionai/corso/pkg/account"
|
||||
"github.com/alcionai/corso/pkg/selectors"
|
||||
"github.com/alcionai/corso/pkg/store"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -38,7 +39,7 @@ func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() {
|
||||
|
||||
var (
|
||||
kw = &kopia.Wrapper{}
|
||||
ms = &kopia.ModelStore{}
|
||||
sw = &store.Wrapper{}
|
||||
acct = account.Account{}
|
||||
now = time.Now()
|
||||
stats = restoreStats{
|
||||
@ -51,7 +52,7 @@ func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() {
|
||||
}
|
||||
)
|
||||
|
||||
op, err := NewRestoreOperation(ctx, Options{}, kw, ms, acct, "foo", selectors.Selector{})
|
||||
op, err := NewRestoreOperation(ctx, Options{}, kw, sw, acct, "foo", selectors.Selector{})
|
||||
require.NoError(t, err)
|
||||
|
||||
op.persistResults(now, &stats)
|
||||
@ -90,7 +91,7 @@ func (suite *RestoreOpIntegrationSuite) SetupSuite() {
|
||||
|
||||
func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() {
|
||||
kw := &kopia.Wrapper{}
|
||||
ms := &kopia.ModelStore{}
|
||||
sw := &store.Wrapper{}
|
||||
acct, err := ctesting.NewM365Account()
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
@ -98,13 +99,13 @@ func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() {
|
||||
name string
|
||||
opts Options
|
||||
kw *kopia.Wrapper
|
||||
ms *kopia.ModelStore
|
||||
sw *store.Wrapper
|
||||
acct account.Account
|
||||
targets []string
|
||||
errCheck assert.ErrorAssertionFunc
|
||||
}{
|
||||
{"good", Options{}, kw, ms, acct, nil, assert.NoError},
|
||||
{"missing kopia", Options{}, nil, ms, acct, nil, assert.Error},
|
||||
{"good", Options{}, kw, sw, acct, nil, assert.NoError},
|
||||
{"missing kopia", Options{}, nil, sw, acct, nil, assert.Error},
|
||||
{"missing modelstore", Options{}, kw, nil, acct, nil, assert.Error},
|
||||
}
|
||||
for _, test := range table {
|
||||
@ -113,7 +114,7 @@ func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() {
|
||||
context.Background(),
|
||||
Options{},
|
||||
test.kw,
|
||||
test.ms,
|
||||
test.sw,
|
||||
test.acct,
|
||||
"backup-id",
|
||||
selectors.Selector{})
|
||||
@ -146,6 +147,8 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
|
||||
require.NoError(t, err)
|
||||
defer ms.Close(ctx)
|
||||
|
||||
sw := store.NewKopiaStore(ms)
|
||||
|
||||
bsel := selectors.NewExchangeBackup()
|
||||
bsel.Include(bsel.Users(m365User))
|
||||
|
||||
@ -153,7 +156,7 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
|
||||
ctx,
|
||||
Options{},
|
||||
w,
|
||||
ms,
|
||||
sw,
|
||||
acct,
|
||||
bsel.Selector)
|
||||
require.NoError(t, err)
|
||||
@ -167,7 +170,7 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
|
||||
ctx,
|
||||
Options{},
|
||||
w,
|
||||
ms,
|
||||
sw,
|
||||
acct,
|
||||
bo.Results.BackupID,
|
||||
rsel.Selector)
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kopia/kopia/repo/manifest"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/alcionai/corso/internal/kopia"
|
||||
@ -15,6 +14,7 @@ import (
|
||||
"github.com/alcionai/corso/pkg/backup"
|
||||
"github.com/alcionai/corso/pkg/selectors"
|
||||
"github.com/alcionai/corso/pkg/storage"
|
||||
"github.com/alcionai/corso/pkg/store"
|
||||
)
|
||||
|
||||
// Repository contains storage provider information.
|
||||
@ -133,7 +133,7 @@ func (r Repository) NewBackup(ctx context.Context, selector selectors.Selector)
|
||||
ctx,
|
||||
operations.Options{},
|
||||
r.dataLayer,
|
||||
r.modelStore,
|
||||
store.NewKopiaStore(r.modelStore),
|
||||
r.Account,
|
||||
selector)
|
||||
}
|
||||
@ -144,36 +144,20 @@ func (r Repository) NewRestore(ctx context.Context, backupID string, sel selecto
|
||||
ctx,
|
||||
operations.Options{},
|
||||
r.dataLayer,
|
||||
r.modelStore,
|
||||
store.NewKopiaStore(r.modelStore),
|
||||
r.Account,
|
||||
model.ID(backupID),
|
||||
sel)
|
||||
}
|
||||
|
||||
// backups lists backups in a respository
|
||||
func (r Repository) Backups(ctx context.Context) ([]*backup.Backup, error) {
|
||||
bms, err := r.modelStore.GetIDsForType(ctx, kopia.BackupModel, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bus := make([]*backup.Backup, 0, len(bms))
|
||||
for _, bm := range bms {
|
||||
bu := backup.Backup{}
|
||||
err := r.modelStore.GetWithModelStoreID(ctx, kopia.BackupModel, bm.ModelStoreID, &bu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bus = append(bus, &bu)
|
||||
}
|
||||
return bus, nil
|
||||
func (r Repository) Backups(ctx context.Context) ([]backup.Backup, error) {
|
||||
sw := store.NewKopiaStore(r.modelStore)
|
||||
return sw.GetBackups(ctx)
|
||||
}
|
||||
|
||||
// BackupDetails returns the specified backup details object
|
||||
func (r Repository) BackupDetails(ctx context.Context, rpDetailsID string) (*backup.Details, error) {
|
||||
bud := backup.Details{}
|
||||
err := r.modelStore.GetWithModelStoreID(ctx, kopia.BackupDetailsModel, manifest.ID(rpDetailsID), &bud)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &bud, nil
|
||||
func (r Repository) BackupDetails(ctx context.Context, backupID string) (*backup.Details, *backup.Backup, error) {
|
||||
sw := store.NewKopiaStore(r.modelStore)
|
||||
return sw.GetDetailsFromBackupID(ctx, model.ID(backupID))
|
||||
}
|
||||
|
||||
64
src/pkg/store/backup.go
Normal file
64
src/pkg/store/backup.go
Normal file
@ -0,0 +1,64 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kopia/kopia/repo/manifest"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/alcionai/corso/internal/model"
|
||||
"github.com/alcionai/corso/pkg/backup"
|
||||
)
|
||||
|
||||
// GetBackup gets a single backup by id.
|
||||
func (w Wrapper) GetBackup(ctx context.Context, backupID model.ID) (*backup.Backup, error) {
|
||||
b := backup.Backup{}
|
||||
err := w.Get(ctx, model.BackupSchema, backupID, &b)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting backup")
|
||||
}
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
// GetDetailsFromBackupID retrieves all backups in the model store.
|
||||
func (w Wrapper) GetBackups(ctx context.Context) ([]backup.Backup, error) {
|
||||
bms, err := w.GetIDsForType(ctx, model.BackupSchema, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bs := make([]backup.Backup, len(bms))
|
||||
for i, bm := range bms {
|
||||
b := backup.Backup{}
|
||||
err := w.GetWithModelStoreID(ctx, model.BackupSchema, bm.ModelStoreID, &b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bs[i] = b
|
||||
}
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
// GetDetails gets the backup details by ID.
|
||||
func (w Wrapper) GetDetails(ctx context.Context, detailsID manifest.ID) (*backup.Details, error) {
|
||||
d := backup.Details{}
|
||||
err := w.GetWithModelStoreID(ctx, model.BackupDetailsSchema, detailsID, &d)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting details")
|
||||
}
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
// GetDetailsFromBackupID retrieves the backup.Details within the specified backup.
|
||||
func (w Wrapper) GetDetailsFromBackupID(ctx context.Context, backupID model.ID) (*backup.Details, *backup.Backup, error) {
|
||||
b, err := w.GetBackup(ctx, backupID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
d, err := w.GetDetails(ctx, manifest.ID(b.DetailsID))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return d, b, nil
|
||||
}
|
||||
180
src/pkg/store/backup_test.go
Normal file
180
src/pkg/store/backup_test.go
Normal file
@ -0,0 +1,180 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kopia/kopia/repo/manifest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/alcionai/corso/internal/model"
|
||||
"github.com/alcionai/corso/pkg/backup"
|
||||
"github.com/alcionai/corso/pkg/store"
|
||||
storeMock "github.com/alcionai/corso/pkg/store/mock"
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// unit tests
|
||||
// ------------------------------------------------------------
|
||||
|
||||
var (
|
||||
detailsID = uuid.NewString()
|
||||
bu = backup.Backup{
|
||||
BaseModel: model.BaseModel{
|
||||
StableID: model.ID(uuid.NewString()),
|
||||
ModelStoreID: manifest.ID(uuid.NewString()),
|
||||
},
|
||||
CreationTime: time.Now(),
|
||||
SnapshotID: uuid.NewString(),
|
||||
DetailsID: detailsID,
|
||||
}
|
||||
deets = backup.Details{
|
||||
DetailsModel: backup.DetailsModel{
|
||||
BaseModel: model.BaseModel{
|
||||
StableID: model.ID(detailsID),
|
||||
ModelStoreID: manifest.ID(uuid.NewString()),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type StoreBackupUnitSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestStoreBackupUnitSuite(t *testing.T) {
|
||||
suite.Run(t, new(StoreBackupUnitSuite))
|
||||
}
|
||||
|
||||
func (suite *StoreBackupUnitSuite) TestGetBackup() {
|
||||
ctx := context.Background()
|
||||
|
||||
table := []struct {
|
||||
name string
|
||||
mock *storeMock.MockModelStore
|
||||
expect assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "gets backup",
|
||||
mock: storeMock.NewMock(&bu, nil, nil),
|
||||
expect: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "errors",
|
||||
mock: storeMock.NewMock(&bu, nil, assert.AnError),
|
||||
expect: assert.Error,
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
store := &store.Wrapper{test.mock}
|
||||
result, err := store.GetBackup(ctx, model.ID(uuid.NewString()))
|
||||
test.expect(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, bu.StableID, result.StableID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *StoreBackupUnitSuite) TestGetBackups() {
|
||||
ctx := context.Background()
|
||||
|
||||
table := []struct {
|
||||
name string
|
||||
mock *storeMock.MockModelStore
|
||||
expect assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "gets backups",
|
||||
mock: storeMock.NewMock(&bu, nil, nil),
|
||||
expect: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "errors",
|
||||
mock: storeMock.NewMock(&bu, nil, assert.AnError),
|
||||
expect: assert.Error,
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
sm := &store.Wrapper{test.mock}
|
||||
result, err := sm.GetBackups(ctx)
|
||||
test.expect(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, 1, len(result))
|
||||
assert.Equal(t, bu.StableID, result[0].StableID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *StoreBackupUnitSuite) TestGetDetails() {
|
||||
ctx := context.Background()
|
||||
|
||||
table := []struct {
|
||||
name string
|
||||
mock *storeMock.MockModelStore
|
||||
expect assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "gets details",
|
||||
mock: storeMock.NewMock(nil, &deets, nil),
|
||||
expect: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "errors",
|
||||
mock: storeMock.NewMock(nil, &deets, assert.AnError),
|
||||
expect: assert.Error,
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
sm := &store.Wrapper{test.mock}
|
||||
result, err := sm.GetDetails(ctx, manifest.ID(uuid.NewString()))
|
||||
test.expect(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, deets.StableID, result.StableID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *StoreBackupUnitSuite) TestGetDetailsFromBackupID() {
|
||||
ctx := context.Background()
|
||||
|
||||
table := []struct {
|
||||
name string
|
||||
mock *storeMock.MockModelStore
|
||||
expect assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "gets details from backup id",
|
||||
mock: storeMock.NewMock(&bu, &deets, nil),
|
||||
expect: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "errors",
|
||||
mock: storeMock.NewMock(&bu, &deets, assert.AnError),
|
||||
expect: assert.Error,
|
||||
},
|
||||
}
|
||||
for _, test := range table {
|
||||
suite.T().Run(test.name, func(t *testing.T) {
|
||||
store := &store.Wrapper{test.mock}
|
||||
dResult, bResult, err := store.GetDetailsFromBackupID(ctx, model.ID(uuid.NewString()))
|
||||
test.expect(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, deets.StableID, dResult.StableID)
|
||||
assert.Equal(t, bu.StableID, bResult.StableID)
|
||||
})
|
||||
}
|
||||
}
|
||||
145
src/pkg/store/mock/store_mock.go
Normal file
145
src/pkg/store/mock/store_mock.go
Normal file
@ -0,0 +1,145 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/kopia/kopia/repo/manifest"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/alcionai/corso/internal/model"
|
||||
"github.com/alcionai/corso/pkg/backup"
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// model wrapper model store
|
||||
// ------------------------------------------------------------
|
||||
|
||||
type MockModelStore struct {
|
||||
backup []byte
|
||||
details []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func NewMock(b *backup.Backup, d *backup.Details, err error) *MockModelStore {
|
||||
return &MockModelStore{
|
||||
backup: marshal(b),
|
||||
details: marshal(d),
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func marshal(a any) []byte {
|
||||
bs, _ := json.Marshal(a)
|
||||
return bs
|
||||
}
|
||||
|
||||
func unmarshal(b []byte, a any) {
|
||||
//nolint:errcheck
|
||||
json.Unmarshal(b, a)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// deleter iface
|
||||
// ------------------------------------------------------------
|
||||
|
||||
func (mms *MockModelStore) Delete(ctx context.Context, s model.Schema, id model.ID) error {
|
||||
return mms.err
|
||||
}
|
||||
|
||||
func (mms *MockModelStore) DeleteWithModelStoreID(ctx context.Context, id manifest.ID) error {
|
||||
return mms.err
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// getter iface
|
||||
// ------------------------------------------------------------
|
||||
|
||||
func (mms *MockModelStore) Get(
|
||||
ctx context.Context,
|
||||
s model.Schema,
|
||||
id model.ID,
|
||||
data model.Model,
|
||||
) error {
|
||||
if mms.err != nil {
|
||||
return mms.err
|
||||
}
|
||||
switch s {
|
||||
case model.BackupSchema:
|
||||
unmarshal(mms.backup, data)
|
||||
case model.BackupDetailsSchema:
|
||||
unmarshal(mms.details, data)
|
||||
default:
|
||||
return errors.Errorf("schema %s not supported by mock Get", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mms *MockModelStore) GetIDsForType(
|
||||
ctx context.Context,
|
||||
s model.Schema,
|
||||
tags map[string]string,
|
||||
) ([]*model.BaseModel, error) {
|
||||
if mms.err != nil {
|
||||
return nil, mms.err
|
||||
}
|
||||
switch s {
|
||||
case model.BackupSchema:
|
||||
b := backup.Backup{}
|
||||
unmarshal(mms.backup, &b)
|
||||
return []*model.BaseModel{&b.BaseModel}, nil
|
||||
case model.BackupDetailsSchema:
|
||||
d := backup.Details{}
|
||||
unmarshal(mms.backup, &d)
|
||||
return []*model.BaseModel{&d.BaseModel}, nil
|
||||
}
|
||||
return nil, errors.Errorf("schema %s not supported by mock GetIDsForType", s)
|
||||
}
|
||||
|
||||
func (mms *MockModelStore) GetWithModelStoreID(
|
||||
ctx context.Context,
|
||||
s model.Schema,
|
||||
id manifest.ID,
|
||||
data model.Model,
|
||||
) error {
|
||||
if mms.err != nil {
|
||||
return mms.err
|
||||
}
|
||||
switch s {
|
||||
case model.BackupSchema:
|
||||
unmarshal(mms.backup, data)
|
||||
case model.BackupDetailsSchema:
|
||||
unmarshal(mms.details, data)
|
||||
default:
|
||||
return errors.Errorf("schema %s not supported by mock GetWithModelStoreID", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// updater iface
|
||||
// ------------------------------------------------------------
|
||||
|
||||
func (mms *MockModelStore) Put(ctx context.Context, s model.Schema, m model.Model) error {
|
||||
switch s {
|
||||
case model.BackupSchema:
|
||||
mms.backup = marshal(m)
|
||||
case model.BackupDetailsSchema:
|
||||
mms.details = marshal(m)
|
||||
default:
|
||||
return errors.Errorf("schema %s not supported by mock Put", s)
|
||||
}
|
||||
return mms.err
|
||||
}
|
||||
|
||||
func (mms *MockModelStore) Update(ctx context.Context, s model.Schema, m model.Model) error {
|
||||
switch s {
|
||||
case model.BackupSchema:
|
||||
mms.backup = marshal(m)
|
||||
case model.BackupDetailsSchema:
|
||||
mms.details = marshal(m)
|
||||
default:
|
||||
return errors.Errorf("schema %s not supported by mock Update", s)
|
||||
}
|
||||
return mms.err
|
||||
}
|
||||
32
src/pkg/store/store.go
Normal file
32
src/pkg/store/store.go
Normal file
@ -0,0 +1,32 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kopia/kopia/repo/manifest"
|
||||
|
||||
"github.com/alcionai/corso/internal/kopia"
|
||||
"github.com/alcionai/corso/internal/model"
|
||||
)
|
||||
|
||||
var _ Storer = &kopia.ModelStore{}
|
||||
|
||||
type (
|
||||
Storer interface {
|
||||
Delete(ctx context.Context, s model.Schema, id model.ID) error
|
||||
DeleteWithModelStoreID(ctx context.Context, id manifest.ID) error
|
||||
Get(ctx context.Context, s model.Schema, id model.ID, data model.Model) error
|
||||
GetIDsForType(ctx context.Context, s model.Schema, tags map[string]string) ([]*model.BaseModel, error)
|
||||
GetWithModelStoreID(ctx context.Context, s model.Schema, id manifest.ID, data model.Model) error
|
||||
Put(ctx context.Context, s model.Schema, m model.Model) error
|
||||
Update(ctx context.Context, s model.Schema, m model.Model) error
|
||||
}
|
||||
)
|
||||
|
||||
type Wrapper struct {
|
||||
Storer
|
||||
}
|
||||
|
||||
func NewKopiaStore(kMS *kopia.ModelStore) *Wrapper {
|
||||
return &Wrapper{kMS}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user