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)
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
rpd, err := r.BackupDetails(ctx, backupDetailsID)
|
d, _, err := r.BackupDetails(ctx, backupDetailsID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Failed to get backup details in the repository")
|
return errors.Wrap(err, "Failed to get backup details in the repository")
|
||||||
}
|
}
|
||||||
|
|
||||||
print.Entries(rpd.Entries)
|
print.Entries(d.Entries)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ type Printable interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prints the backups to the terminal with stdout.
|
// Prints the backups to the terminal with stdout.
|
||||||
func Backups(bs []*backup.Backup) {
|
func Backups(bs []backup.Backup) {
|
||||||
ps := []Printable{}
|
ps := []Printable{}
|
||||||
for _, b := range bs {
|
for _, b := range bs {
|
||||||
ps = append(ps, b)
|
ps = append(ps, b)
|
||||||
|
|||||||
@ -14,21 +14,11 @@ import (
|
|||||||
const stableIDKey = "stableID"
|
const stableIDKey = "stableID"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errNoModelStoreID = errors.New("model has no ModelStoreID")
|
errNoModelStoreID = errors.New("model has no ModelStoreID")
|
||||||
errNoStableID = errors.New("model has no StableID")
|
errNoStableID = errors.New("model has no StableID")
|
||||||
errBadTagKey = errors.New("tag key overlaps with required key")
|
errBadTagKey = errors.New("tag key overlaps with required key")
|
||||||
errModelTypeMismatch = errors.New("model type doesn't match request")
|
errModelTypeMismatch = errors.New("model type doesn't match request")
|
||||||
)
|
errUnrecognizedSchema = errors.New("unrecognized model schema")
|
||||||
|
|
||||||
type modelType int
|
|
||||||
|
|
||||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=modelType
|
|
||||||
const (
|
|
||||||
UnknownModel = modelType(iota)
|
|
||||||
BackupOpModel
|
|
||||||
RestoreOpModel
|
|
||||||
BackupModel
|
|
||||||
BackupDetailsModel
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewModelStore(c *conn) (*ModelStore, error) {
|
func NewModelStore(c *conn) (*ModelStore, error) {
|
||||||
@ -54,20 +44,16 @@ func (ms *ModelStore) Close(ctx context.Context) error {
|
|||||||
return errors.Wrap(err, "closing ModelStore")
|
return errors.Wrap(err, "closing ModelStore")
|
||||||
}
|
}
|
||||||
|
|
||||||
// tagsForModel creates a copy of tags and adds a tag for the model type to it.
|
// 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 type or if a
|
// Returns an error if another tag has the same key as the model schema or if a
|
||||||
// bad model type is given.
|
// bad model type is given.
|
||||||
func tagsForModel(t modelType, tags map[string]string) (map[string]string, error) {
|
func tagsForModel(s model.Schema, tags map[string]string) (map[string]string, error) {
|
||||||
if t == UnknownModel {
|
|
||||||
return nil, errors.New("bad model type")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := tags[manifest.TypeLabelKey]; ok {
|
if _, ok := tags[manifest.TypeLabelKey]; ok {
|
||||||
return nil, errors.WithStack(errBadTagKey)
|
return nil, errors.WithStack(errBadTagKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := make(map[string]string, len(tags)+1)
|
res := make(map[string]string, len(tags)+1)
|
||||||
res[manifest.TypeLabelKey] = t.String()
|
res[manifest.TypeLabelKey] = s.String()
|
||||||
for k, v := range tags {
|
for k, v := range tags {
|
||||||
res[k] = v
|
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
|
// 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.
|
// type or if a bad model type is given.
|
||||||
func tagsForModelWithID(
|
func tagsForModelWithID(
|
||||||
t modelType,
|
s model.Schema,
|
||||||
id model.ID,
|
id model.ID,
|
||||||
tags map[string]string,
|
tags map[string]string,
|
||||||
) (map[string]string, error) {
|
) (map[string]string, error) {
|
||||||
|
if !s.Valid() {
|
||||||
|
return nil, errors.New("unrecognized model schema")
|
||||||
|
}
|
||||||
|
|
||||||
if len(id) == 0 {
|
if len(id) == 0 {
|
||||||
return nil, errors.WithStack(errNoStableID)
|
return nil, errors.WithStack(errNoStableID)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := tagsForModel(t, tags)
|
res, err := tagsForModel(s, tags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -106,16 +96,20 @@ func tagsForModelWithID(
|
|||||||
func putInner(
|
func putInner(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
w repo.RepositoryWriter,
|
w repo.RepositoryWriter,
|
||||||
t modelType,
|
s model.Schema,
|
||||||
m model.Model,
|
m model.Model,
|
||||||
create bool,
|
create bool,
|
||||||
) error {
|
) error {
|
||||||
|
if !s.Valid() {
|
||||||
|
return errors.WithStack(errUnrecognizedSchema)
|
||||||
|
}
|
||||||
|
|
||||||
base := m.Base()
|
base := m.Base()
|
||||||
if create {
|
if create {
|
||||||
base.StableID = model.ID(uuid.NewString())
|
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 {
|
if err != nil {
|
||||||
// Will be wrapped at a higher layer.
|
// Will be wrapped at a higher layer.
|
||||||
return err
|
return err
|
||||||
@ -135,15 +129,18 @@ func putInner(
|
|||||||
// given to this function can later be used to help lookup the model.
|
// given to this function can later be used to help lookup the model.
|
||||||
func (ms *ModelStore) Put(
|
func (ms *ModelStore) Put(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
t modelType,
|
s model.Schema,
|
||||||
m model.Model,
|
m model.Model,
|
||||||
) error {
|
) error {
|
||||||
|
if !s.Valid() {
|
||||||
|
return errors.WithStack(errUnrecognizedSchema)
|
||||||
|
}
|
||||||
err := repo.WriteSession(
|
err := repo.WriteSession(
|
||||||
ctx,
|
ctx,
|
||||||
ms.c,
|
ms.c,
|
||||||
repo.WriteSessionOptions{Purpose: "ModelStorePut"},
|
repo.WriteSessionOptions{Purpose: "ModelStorePut"},
|
||||||
func(innerCtx context.Context, w repo.RepositoryWriter) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -181,14 +178,18 @@ func baseModelFromMetadata(m *manifest.EntryMetadata) (*model.BaseModel, error)
|
|||||||
// Update, or Delete.
|
// Update, or Delete.
|
||||||
func (ms *ModelStore) GetIDsForType(
|
func (ms *ModelStore) GetIDsForType(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
t modelType,
|
s model.Schema,
|
||||||
tags map[string]string,
|
tags map[string]string,
|
||||||
) ([]*model.BaseModel, error) {
|
) ([]*model.BaseModel, error) {
|
||||||
|
if !s.Valid() {
|
||||||
|
return nil, errors.New("unrecognized model schema")
|
||||||
|
}
|
||||||
|
|
||||||
if _, ok := tags[stableIDKey]; ok {
|
if _, ok := tags[stableIDKey]; ok {
|
||||||
return nil, errors.WithStack(errBadTagKey)
|
return nil, errors.WithStack(errBadTagKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpTags, err := tagsForModel(t, tags)
|
tmpTags, err := tagsForModel(s, tags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "getting model metadata")
|
return nil, errors.Wrap(err, "getting model metadata")
|
||||||
}
|
}
|
||||||
@ -217,9 +218,13 @@ func (ms *ModelStore) GetIDsForType(
|
|||||||
// one model has the same StableID.
|
// one model has the same StableID.
|
||||||
func (ms *ModelStore) getModelStoreID(
|
func (ms *ModelStore) getModelStoreID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
t modelType,
|
s model.Schema,
|
||||||
id model.ID,
|
id model.ID,
|
||||||
) (manifest.ID, error) {
|
) (manifest.ID, error) {
|
||||||
|
if !s.Valid() {
|
||||||
|
return "", errors.New("unrecognized model schema")
|
||||||
|
}
|
||||||
|
|
||||||
if len(id) == 0 {
|
if len(id) == 0 {
|
||||||
return "", errors.WithStack(errNoStableID)
|
return "", errors.WithStack(errNoStableID)
|
||||||
}
|
}
|
||||||
@ -236,7 +241,7 @@ func (ms *ModelStore) getModelStoreID(
|
|||||||
if len(metadata) != 1 {
|
if len(metadata) != 1 {
|
||||||
return "", errors.New("multiple models with same StableID")
|
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)
|
return "", errors.WithStack(errModelTypeMismatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,16 +254,20 @@ func (ms *ModelStore) getModelStoreID(
|
|||||||
// or if multiple models have the same StableID.
|
// or if multiple models have the same StableID.
|
||||||
func (ms *ModelStore) Get(
|
func (ms *ModelStore) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
t modelType,
|
s model.Schema,
|
||||||
id model.ID,
|
id model.ID,
|
||||||
data model.Model,
|
data model.Model,
|
||||||
) error {
|
) 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 {
|
if err != nil {
|
||||||
return err
|
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
|
// GetWithModelStoreID deserializes the model with the given ModelStoreID into
|
||||||
@ -267,10 +276,14 @@ func (ms *ModelStore) Get(
|
|||||||
// expected.
|
// expected.
|
||||||
func (ms *ModelStore) GetWithModelStoreID(
|
func (ms *ModelStore) GetWithModelStoreID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
t modelType,
|
s model.Schema,
|
||||||
id manifest.ID,
|
id manifest.ID,
|
||||||
data model.Model,
|
data model.Model,
|
||||||
) error {
|
) error {
|
||||||
|
if !s.Valid() {
|
||||||
|
return errors.WithStack(errUnrecognizedSchema)
|
||||||
|
}
|
||||||
|
|
||||||
if len(id) == 0 {
|
if len(id) == 0 {
|
||||||
return errors.WithStack(errNoModelStoreID)
|
return errors.WithStack(errNoModelStoreID)
|
||||||
}
|
}
|
||||||
@ -282,7 +295,7 @@ func (ms *ModelStore) GetWithModelStoreID(
|
|||||||
return errors.Wrap(err, "getting model data")
|
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)
|
return errors.WithStack(errModelTypeMismatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,17 +306,21 @@ func (ms *ModelStore) GetWithModelStoreID(
|
|||||||
return nil
|
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.
|
// 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
|
// 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.
|
// model.
|
||||||
func (ms *ModelStore) checkPrevModelVersion(
|
func (ms *ModelStore) checkPrevModelVersion(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
t modelType,
|
s model.Schema,
|
||||||
b *model.BaseModel,
|
b *model.BaseModel,
|
||||||
) error {
|
) 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -317,7 +334,7 @@ func (ms *ModelStore) checkPrevModelVersion(
|
|||||||
if meta.ID != b.ModelStoreID {
|
if meta.ID != b.ModelStoreID {
|
||||||
return errors.New("updated model has different 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")
|
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
|
// 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
|
// 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
|
// 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
|
// previous version of the model. If an error occurs no visible changes will be
|
||||||
// made to the stored model.
|
// made to the stored model.
|
||||||
func (ms *ModelStore) Update(
|
func (ms *ModelStore) Update(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
t modelType,
|
s model.Schema,
|
||||||
m model.Model,
|
m model.Model,
|
||||||
) error {
|
) error {
|
||||||
|
if !s.Valid() {
|
||||||
|
return errors.WithStack(errUnrecognizedSchema)
|
||||||
|
}
|
||||||
|
|
||||||
base := m.Base()
|
base := m.Base()
|
||||||
if len(base.ModelStoreID) == 0 {
|
if len(base.ModelStoreID) == 0 {
|
||||||
return errors.WithStack(errNoModelStoreID)
|
return errors.WithStack(errNoModelStoreID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(ashmrtnz): Can remove if bottleneck.
|
// 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
|
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
|
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
|
// 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
|
// not empty but the model does not exist. Returns an error if multiple models
|
||||||
// have the same StableID.
|
// have the same StableID.
|
||||||
func (ms *ModelStore) Delete(ctx context.Context, t modelType, id model.ID) error {
|
func (ms *ModelStore) Delete(ctx context.Context, s model.Schema, id model.ID) error {
|
||||||
latest, err := ms.getModelStoreID(ctx, t, id)
|
if !s.Valid() {
|
||||||
|
return errors.WithStack(errUnrecognizedSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
latest, err := ms.getModelStoreID(ctx, s, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, manifest.ErrNotFound) {
|
if errors.Is(err, manifest.ErrNotFound) {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -102,10 +102,10 @@ func (suite *ModelStoreIntegrationSuite) TestBadTagsErrors() {
|
|||||||
foo := &fooModel{Bar: uuid.NewString()}
|
foo := &fooModel{Bar: uuid.NewString()}
|
||||||
foo.Tags = test.tags
|
foo.Tags = test.tags
|
||||||
|
|
||||||
assert.Error(t, suite.m.Put(suite.ctx, BackupOpModel, foo))
|
assert.Error(t, suite.m.Put(suite.ctx, model.BackupOpSchema, foo))
|
||||||
assert.Error(t, suite.m.Update(suite.ctx, BackupOpModel, 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)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -113,7 +113,7 @@ func (suite *ModelStoreIntegrationSuite) TestBadTagsErrors() {
|
|||||||
|
|
||||||
func (suite *ModelStoreIntegrationSuite) TestNoIDsErrors() {
|
func (suite *ModelStoreIntegrationSuite) TestNoIDsErrors() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
theModelType := BackupOpModel
|
theModelType := model.BackupOpSchema
|
||||||
|
|
||||||
noStableID := &fooModel{Bar: uuid.NewString()}
|
noStableID := &fooModel{Bar: uuid.NewString()}
|
||||||
noStableID.StableID = ""
|
noStableID.StableID = ""
|
||||||
@ -138,13 +138,13 @@ func (suite *ModelStoreIntegrationSuite) TestBadModelTypeErrors() {
|
|||||||
|
|
||||||
foo := &fooModel{Bar: uuid.NewString()}
|
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)
|
require.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "model type")
|
assert.Contains(t, err.Error(), "schema")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ModelStoreIntegrationSuite) TestBadTypeErrors() {
|
func (suite *ModelStoreIntegrationSuite) TestBadTypeErrors() {
|
||||||
@ -152,51 +152,51 @@ func (suite *ModelStoreIntegrationSuite) TestBadTypeErrors() {
|
|||||||
|
|
||||||
foo := &fooModel{Bar: uuid.NewString()}
|
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{}
|
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(
|
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() {
|
func (suite *ModelStoreIntegrationSuite) TestPutGet() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
t modelType
|
s model.Schema
|
||||||
check require.ErrorAssertionFunc
|
check require.ErrorAssertionFunc
|
||||||
hasErr bool
|
hasErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
t: UnknownModel,
|
s: model.UnknownSchema,
|
||||||
check: require.Error,
|
check: require.Error,
|
||||||
hasErr: true,
|
hasErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
t: BackupOpModel,
|
s: model.BackupOpSchema,
|
||||||
check: require.NoError,
|
check: require.NoError,
|
||||||
hasErr: false,
|
hasErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
t: RestoreOpModel,
|
s: model.RestoreOpSchema,
|
||||||
check: require.NoError,
|
check: require.NoError,
|
||||||
hasErr: false,
|
hasErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
t: BackupModel,
|
s: model.BackupSchema,
|
||||||
check: require.NoError,
|
check: require.NoError,
|
||||||
hasErr: false,
|
hasErr: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range table {
|
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()}
|
foo := &fooModel{Bar: uuid.NewString()}
|
||||||
// Avoid some silly test errors from comparing nil to empty map.
|
// Avoid some silly test errors from comparing nil to empty map.
|
||||||
foo.Tags = map[string]string{}
|
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)
|
test.check(t, err)
|
||||||
|
|
||||||
if test.hasErr {
|
if test.hasErr {
|
||||||
@ -207,11 +207,11 @@ func (suite *ModelStoreIntegrationSuite) TestPutGet() {
|
|||||||
require.NotEmpty(t, foo.StableID)
|
require.NotEmpty(t, foo.StableID)
|
||||||
|
|
||||||
returned := &fooModel{}
|
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)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, foo, returned)
|
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)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, foo, returned)
|
assert.Equal(t, foo, returned)
|
||||||
})
|
})
|
||||||
@ -220,7 +220,7 @@ func (suite *ModelStoreIntegrationSuite) TestPutGet() {
|
|||||||
|
|
||||||
func (suite *ModelStoreIntegrationSuite) TestPutGet_WithTags() {
|
func (suite *ModelStoreIntegrationSuite) TestPutGet_WithTags() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
theModelType := BackupOpModel
|
theModelType := model.BackupOpSchema
|
||||||
|
|
||||||
foo := &fooModel{Bar: uuid.NewString()}
|
foo := &fooModel{Bar: uuid.NewString()}
|
||||||
foo.Tags = map[string]string{
|
foo.Tags = map[string]string{
|
||||||
@ -245,51 +245,51 @@ func (suite *ModelStoreIntegrationSuite) TestPutGet_WithTags() {
|
|||||||
func (suite *ModelStoreIntegrationSuite) TestGet_NotFoundErrors() {
|
func (suite *ModelStoreIntegrationSuite) TestGet_NotFoundErrors() {
|
||||||
t := suite.T()
|
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(
|
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() {
|
func (suite *ModelStoreIntegrationSuite) TestPutGetOfType() {
|
||||||
table := []struct {
|
table := []struct {
|
||||||
t modelType
|
s model.Schema
|
||||||
check require.ErrorAssertionFunc
|
check require.ErrorAssertionFunc
|
||||||
hasErr bool
|
hasErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
t: UnknownModel,
|
s: model.UnknownSchema,
|
||||||
check: require.Error,
|
check: require.Error,
|
||||||
hasErr: true,
|
hasErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
t: BackupOpModel,
|
s: model.BackupOpSchema,
|
||||||
check: require.NoError,
|
check: require.NoError,
|
||||||
hasErr: false,
|
hasErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
t: RestoreOpModel,
|
s: model.RestoreOpSchema,
|
||||||
check: require.NoError,
|
check: require.NoError,
|
||||||
hasErr: false,
|
hasErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
t: BackupModel,
|
s: model.BackupSchema,
|
||||||
check: require.NoError,
|
check: require.NoError,
|
||||||
hasErr: false,
|
hasErr: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range table {
|
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()}
|
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)
|
test.check(t, err)
|
||||||
|
|
||||||
if test.hasErr {
|
if test.hasErr {
|
||||||
return
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Len(t, ids, 1)
|
assert.Len(t, ids, 1)
|
||||||
@ -322,7 +322,7 @@ func (suite *ModelStoreIntegrationSuite) TestPutUpdate() {
|
|||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
suite.T().Run(test.name, func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
theModelType := BackupOpModel
|
theModelType := model.BackupOpSchema
|
||||||
|
|
||||||
m := getModelStore(t, ctx)
|
m := getModelStore(t, ctx)
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -365,23 +365,23 @@ func (suite *ModelStoreIntegrationSuite) TestPutUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (suite *ModelStoreIntegrationSuite) TestPutUpdate_FailsNotMatchingPrev() {
|
func (suite *ModelStoreIntegrationSuite) TestPutUpdate_FailsNotMatchingPrev() {
|
||||||
startModelType := BackupOpModel
|
startModelType := model.BackupOpSchema
|
||||||
|
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
t modelType
|
s model.Schema
|
||||||
mutator func(m *fooModel)
|
mutator func(m *fooModel)
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "DifferentModelStoreID",
|
name: "DifferentModelStoreID",
|
||||||
t: startModelType,
|
s: startModelType,
|
||||||
mutator: func(m *fooModel) {
|
mutator: func(m *fooModel) {
|
||||||
m.ModelStoreID = manifest.ID("bar")
|
m.ModelStoreID = manifest.ID("bar")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "DifferentModelType",
|
name: "DifferentModelType",
|
||||||
t: RestoreOpModel,
|
s: model.RestoreOpSchema,
|
||||||
mutator: func(m *fooModel) {
|
mutator: func(m *fooModel) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -402,14 +402,14 @@ func (suite *ModelStoreIntegrationSuite) TestPutUpdate_FailsNotMatchingPrev() {
|
|||||||
|
|
||||||
test.mutator(foo)
|
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() {
|
func (suite *ModelStoreIntegrationSuite) TestPutDelete() {
|
||||||
t := suite.T()
|
t := suite.T()
|
||||||
theModelType := BackupOpModel
|
theModelType := model.BackupOpSchema
|
||||||
|
|
||||||
foo := &fooModel{Bar: uuid.NewString()}
|
foo := &fooModel{Bar: uuid.NewString()}
|
||||||
|
|
||||||
@ -425,7 +425,7 @@ func (suite *ModelStoreIntegrationSuite) TestPutDelete() {
|
|||||||
func (suite *ModelStoreIntegrationSuite) TestPutDelete_BadIDsNoop() {
|
func (suite *ModelStoreIntegrationSuite) TestPutDelete_BadIDsNoop() {
|
||||||
t := suite.T()
|
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"))
|
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.
|
// Avoid some silly test errors from comparing nil to empty map.
|
||||||
foo.Tags = map[string]string{}
|
foo.Tags = map[string]string{}
|
||||||
|
|
||||||
theModelType := BackupOpModel
|
theModelType := model.BackupOpSchema
|
||||||
|
|
||||||
require.NoError(t, m.Put(ctx, theModelType, foo))
|
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"
|
"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 {
|
type Model interface {
|
||||||
// Returns a handle to the BaseModel for this model.
|
// 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/account"
|
||||||
"github.com/alcionai/corso/pkg/backup"
|
"github.com/alcionai/corso/pkg/backup"
|
||||||
"github.com/alcionai/corso/pkg/selectors"
|
"github.com/alcionai/corso/pkg/selectors"
|
||||||
|
"github.com/alcionai/corso/pkg/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BackupOperation wraps an operation with backup-specific props.
|
// BackupOperation wraps an operation with backup-specific props.
|
||||||
@ -38,12 +39,12 @@ func NewBackupOperation(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
opts Options,
|
opts Options,
|
||||||
kw *kopia.Wrapper,
|
kw *kopia.Wrapper,
|
||||||
ms *kopia.ModelStore,
|
sw *store.Wrapper,
|
||||||
acct account.Account,
|
acct account.Account,
|
||||||
selector selectors.Selector,
|
selector selectors.Selector,
|
||||||
) (BackupOperation, error) {
|
) (BackupOperation, error) {
|
||||||
op := BackupOperation{
|
op := BackupOperation{
|
||||||
operation: newOperation(opts, kw, ms),
|
operation: newOperation(opts, kw, sw),
|
||||||
Selectors: selector,
|
Selectors: selector,
|
||||||
Version: "v0",
|
Version: "v0",
|
||||||
account: acct,
|
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 {
|
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 {
|
if err != nil {
|
||||||
return errors.Wrap(err, "creating backupdetails model")
|
return errors.Wrap(err, "creating backupdetails model")
|
||||||
}
|
}
|
||||||
|
|
||||||
bu := backup.New(snapID, string(details.ModelStoreID))
|
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 {
|
if err != nil {
|
||||||
return errors.Wrap(err, "creating backup model")
|
return errors.Wrap(err, "creating backup model")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import (
|
|||||||
ctesting "github.com/alcionai/corso/internal/testing"
|
ctesting "github.com/alcionai/corso/internal/testing"
|
||||||
"github.com/alcionai/corso/pkg/account"
|
"github.com/alcionai/corso/pkg/account"
|
||||||
"github.com/alcionai/corso/pkg/selectors"
|
"github.com/alcionai/corso/pkg/selectors"
|
||||||
|
"github.com/alcionai/corso/pkg/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -37,7 +38,7 @@ func (suite *BackupOpSuite) TestBackupOperation_PersistResults() {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
kw = &kopia.Wrapper{}
|
kw = &kopia.Wrapper{}
|
||||||
ms = &kopia.ModelStore{}
|
sw = &store.Wrapper{}
|
||||||
acct = account.Account{}
|
acct = account.Account{}
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
stats = backupStats{
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
op.persistResults(now, &stats)
|
op.persistResults(now, &stats)
|
||||||
@ -96,7 +97,7 @@ func (suite *BackupOpIntegrationSuite) SetupSuite() {
|
|||||||
|
|
||||||
func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() {
|
func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() {
|
||||||
kw := &kopia.Wrapper{}
|
kw := &kopia.Wrapper{}
|
||||||
ms := &kopia.ModelStore{}
|
sw := &store.Wrapper{}
|
||||||
acct, err := ctesting.NewM365Account()
|
acct, err := ctesting.NewM365Account()
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
|
|
||||||
@ -104,13 +105,13 @@ func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() {
|
|||||||
name string
|
name string
|
||||||
opts Options
|
opts Options
|
||||||
kw *kopia.Wrapper
|
kw *kopia.Wrapper
|
||||||
ms *kopia.ModelStore
|
sw *store.Wrapper
|
||||||
acct account.Account
|
acct account.Account
|
||||||
targets []string
|
targets []string
|
||||||
errCheck assert.ErrorAssertionFunc
|
errCheck assert.ErrorAssertionFunc
|
||||||
}{
|
}{
|
||||||
{"good", Options{}, kw, ms, acct, nil, assert.NoError},
|
{"good", Options{}, kw, sw, acct, nil, assert.NoError},
|
||||||
{"missing kopia", Options{}, nil, ms, acct, nil, assert.Error},
|
{"missing kopia", Options{}, nil, sw, acct, nil, assert.Error},
|
||||||
{"missing modelstore", Options{}, kw, nil, acct, nil, assert.Error},
|
{"missing modelstore", Options{}, kw, nil, acct, nil, assert.Error},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
@ -119,7 +120,7 @@ func (suite *BackupOpIntegrationSuite) TestNewBackupOperation() {
|
|||||||
context.Background(),
|
context.Background(),
|
||||||
Options{},
|
Options{},
|
||||||
test.kw,
|
test.kw,
|
||||||
test.ms,
|
test.sw,
|
||||||
test.acct,
|
test.acct,
|
||||||
selectors.Selector{})
|
selectors.Selector{})
|
||||||
test.errCheck(t, err)
|
test.errCheck(t, err)
|
||||||
@ -146,22 +147,24 @@ func (suite *BackupOpIntegrationSuite) TestBackup_Run() {
|
|||||||
// to close here.
|
// to close here.
|
||||||
defer k.Close(ctx)
|
defer k.Close(ctx)
|
||||||
|
|
||||||
w, err := kopia.NewWrapper(k)
|
kw, err := kopia.NewWrapper(k)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer w.Close(ctx)
|
defer kw.Close(ctx)
|
||||||
|
|
||||||
ms, err := kopia.NewModelStore(k)
|
ms, err := kopia.NewModelStore(k)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer ms.Close(ctx)
|
defer ms.Close(ctx)
|
||||||
|
|
||||||
|
sw := store.NewKopiaStore(ms)
|
||||||
|
|
||||||
sel := selectors.NewExchangeBackup()
|
sel := selectors.NewExchangeBackup()
|
||||||
sel.Include(sel.Users(m365User))
|
sel.Include(sel.Users(m365User))
|
||||||
|
|
||||||
bo, err := NewBackupOperation(
|
bo, err := NewBackupOperation(
|
||||||
ctx,
|
ctx,
|
||||||
Options{},
|
Options{},
|
||||||
w,
|
kw,
|
||||||
ms,
|
sw,
|
||||||
acct,
|
acct,
|
||||||
sel.Selector)
|
sel.Selector)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/alcionai/corso/internal/kopia"
|
"github.com/alcionai/corso/internal/kopia"
|
||||||
|
"github.com/alcionai/corso/pkg/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
type opStatus int
|
type opStatus int
|
||||||
@ -31,8 +32,8 @@ type operation struct {
|
|||||||
Options Options `json:"options"`
|
Options Options `json:"options"`
|
||||||
Status opStatus `json:"status"`
|
Status opStatus `json:"status"`
|
||||||
|
|
||||||
kopia *kopia.Wrapper
|
kopia *kopia.Wrapper
|
||||||
modelStore *kopia.ModelStore
|
store *store.Wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options configure some parameters of the operation
|
// Options configure some parameters of the operation
|
||||||
@ -44,15 +45,15 @@ type Options struct {
|
|||||||
func newOperation(
|
func newOperation(
|
||||||
opts Options,
|
opts Options,
|
||||||
kw *kopia.Wrapper,
|
kw *kopia.Wrapper,
|
||||||
ms *kopia.ModelStore,
|
sw *store.Wrapper,
|
||||||
) operation {
|
) operation {
|
||||||
return operation{
|
return operation{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
Options: opts,
|
Options: opts,
|
||||||
kopia: kw,
|
kopia: kw,
|
||||||
modelStore: ms,
|
store: sw,
|
||||||
Status: InProgress,
|
Status: InProgress,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +61,7 @@ func (op operation) validate() error {
|
|||||||
if op.kopia == nil {
|
if op.kopia == nil {
|
||||||
return errors.New("missing kopia connection")
|
return errors.New("missing kopia connection")
|
||||||
}
|
}
|
||||||
if op.modelStore == nil {
|
if op.store == nil {
|
||||||
return errors.New("missing modelstore")
|
return errors.New("missing modelstore")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/internal/kopia"
|
"github.com/alcionai/corso/internal/kopia"
|
||||||
|
"github.com/alcionai/corso/pkg/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OperationSuite struct {
|
type OperationSuite struct {
|
||||||
@ -25,20 +26,20 @@ func (suite *OperationSuite) TestNewOperation() {
|
|||||||
|
|
||||||
func (suite *OperationSuite) TestOperation_Validate() {
|
func (suite *OperationSuite) TestOperation_Validate() {
|
||||||
kwStub := &kopia.Wrapper{}
|
kwStub := &kopia.Wrapper{}
|
||||||
msStub := &kopia.ModelStore{}
|
swStub := &store.Wrapper{}
|
||||||
table := []struct {
|
table := []struct {
|
||||||
name string
|
name string
|
||||||
kw *kopia.Wrapper
|
kw *kopia.Wrapper
|
||||||
ms *kopia.ModelStore
|
sw *store.Wrapper
|
||||||
errCheck assert.ErrorAssertionFunc
|
errCheck assert.ErrorAssertionFunc
|
||||||
}{
|
}{
|
||||||
{"good", kwStub, msStub, assert.NoError},
|
{"good", kwStub, swStub, assert.NoError},
|
||||||
{"missing kopia wrapper", nil, msStub, assert.Error},
|
{"missing kopia wrapper", nil, swStub, assert.Error},
|
||||||
{"missing kopia modelstore", kwStub, nil, assert.Error},
|
{"missing store wrapper", kwStub, nil, assert.Error},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.T().Run(test.name, func(t *testing.T) {
|
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())
|
test.errCheck(t, op.validate())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kopia/kopia/repo/manifest"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/alcionai/corso/internal/connector"
|
"github.com/alcionai/corso/internal/connector"
|
||||||
@ -12,8 +11,8 @@ import (
|
|||||||
"github.com/alcionai/corso/internal/kopia"
|
"github.com/alcionai/corso/internal/kopia"
|
||||||
"github.com/alcionai/corso/internal/model"
|
"github.com/alcionai/corso/internal/model"
|
||||||
"github.com/alcionai/corso/pkg/account"
|
"github.com/alcionai/corso/pkg/account"
|
||||||
"github.com/alcionai/corso/pkg/backup"
|
|
||||||
"github.com/alcionai/corso/pkg/selectors"
|
"github.com/alcionai/corso/pkg/selectors"
|
||||||
|
"github.com/alcionai/corso/pkg/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RestoreOperation wraps an operation with restore-specific props.
|
// RestoreOperation wraps an operation with restore-specific props.
|
||||||
@ -39,13 +38,13 @@ func NewRestoreOperation(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
opts Options,
|
opts Options,
|
||||||
kw *kopia.Wrapper,
|
kw *kopia.Wrapper,
|
||||||
ms *kopia.ModelStore,
|
sw *store.Wrapper,
|
||||||
acct account.Account,
|
acct account.Account,
|
||||||
backupID model.ID,
|
backupID model.ID,
|
||||||
sel selectors.Selector,
|
sel selectors.Selector,
|
||||||
) (RestoreOperation, error) {
|
) (RestoreOperation, error) {
|
||||||
op := RestoreOperation{
|
op := RestoreOperation{
|
||||||
operation: newOperation(opts, kw, ms),
|
operation: newOperation(opts, kw, sw),
|
||||||
BackupID: backupID,
|
BackupID: backupID,
|
||||||
Selectors: sel,
|
Selectors: sel,
|
||||||
Version: "v0",
|
Version: "v0",
|
||||||
@ -82,17 +81,9 @@ func (op *RestoreOperation) Run(ctx context.Context) error {
|
|||||||
defer op.persistResults(time.Now(), &stats)
|
defer op.persistResults(time.Now(), &stats)
|
||||||
|
|
||||||
// retrieve the restore point details
|
// retrieve the restore point details
|
||||||
bu := backup.Backup{}
|
d, b, err := op.store.GetDetailsFromBackupID(ctx, op.BackupID)
|
||||||
err := op.modelStore.Get(ctx, kopia.BackupModel, op.BackupID, &bu)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stats.readErr = errors.Wrap(err, "retrieving restore point")
|
stats.readErr = errors.Wrap(err, "getting backup details for restore")
|
||||||
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")
|
|
||||||
return stats.readErr
|
return stats.readErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,8 +94,8 @@ func (op *RestoreOperation) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// format the details and retrieve the items from kopia
|
// format the details and retrieve the items from kopia
|
||||||
fds := er.FilterDetails(&backup)
|
fds := er.FilterDetails(d)
|
||||||
dcs, err := op.kopia.RestoreMultipleItems(ctx, bu.SnapshotID, fds)
|
dcs, err := op.kopia.RestoreMultipleItems(ctx, b.SnapshotID, fds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stats.readErr = errors.Wrap(err, "retrieving service data")
|
stats.readErr = errors.Wrap(err, "retrieving service data")
|
||||||
return stats.readErr
|
return stats.readErr
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import (
|
|||||||
ctesting "github.com/alcionai/corso/internal/testing"
|
ctesting "github.com/alcionai/corso/internal/testing"
|
||||||
"github.com/alcionai/corso/pkg/account"
|
"github.com/alcionai/corso/pkg/account"
|
||||||
"github.com/alcionai/corso/pkg/selectors"
|
"github.com/alcionai/corso/pkg/selectors"
|
||||||
|
"github.com/alcionai/corso/pkg/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@ -38,7 +39,7 @@ func (suite *RestoreOpSuite) TestRestoreOperation_PersistResults() {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
kw = &kopia.Wrapper{}
|
kw = &kopia.Wrapper{}
|
||||||
ms = &kopia.ModelStore{}
|
sw = &store.Wrapper{}
|
||||||
acct = account.Account{}
|
acct = account.Account{}
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
stats = restoreStats{
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
op.persistResults(now, &stats)
|
op.persistResults(now, &stats)
|
||||||
@ -90,7 +91,7 @@ func (suite *RestoreOpIntegrationSuite) SetupSuite() {
|
|||||||
|
|
||||||
func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() {
|
func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() {
|
||||||
kw := &kopia.Wrapper{}
|
kw := &kopia.Wrapper{}
|
||||||
ms := &kopia.ModelStore{}
|
sw := &store.Wrapper{}
|
||||||
acct, err := ctesting.NewM365Account()
|
acct, err := ctesting.NewM365Account()
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
|
|
||||||
@ -98,13 +99,13 @@ func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() {
|
|||||||
name string
|
name string
|
||||||
opts Options
|
opts Options
|
||||||
kw *kopia.Wrapper
|
kw *kopia.Wrapper
|
||||||
ms *kopia.ModelStore
|
sw *store.Wrapper
|
||||||
acct account.Account
|
acct account.Account
|
||||||
targets []string
|
targets []string
|
||||||
errCheck assert.ErrorAssertionFunc
|
errCheck assert.ErrorAssertionFunc
|
||||||
}{
|
}{
|
||||||
{"good", Options{}, kw, ms, acct, nil, assert.NoError},
|
{"good", Options{}, kw, sw, acct, nil, assert.NoError},
|
||||||
{"missing kopia", Options{}, nil, ms, acct, nil, assert.Error},
|
{"missing kopia", Options{}, nil, sw, acct, nil, assert.Error},
|
||||||
{"missing modelstore", Options{}, kw, nil, acct, nil, assert.Error},
|
{"missing modelstore", Options{}, kw, nil, acct, nil, assert.Error},
|
||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
@ -113,7 +114,7 @@ func (suite *RestoreOpIntegrationSuite) TestNewRestoreOperation() {
|
|||||||
context.Background(),
|
context.Background(),
|
||||||
Options{},
|
Options{},
|
||||||
test.kw,
|
test.kw,
|
||||||
test.ms,
|
test.sw,
|
||||||
test.acct,
|
test.acct,
|
||||||
"backup-id",
|
"backup-id",
|
||||||
selectors.Selector{})
|
selectors.Selector{})
|
||||||
@ -146,6 +147,8 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer ms.Close(ctx)
|
defer ms.Close(ctx)
|
||||||
|
|
||||||
|
sw := store.NewKopiaStore(ms)
|
||||||
|
|
||||||
bsel := selectors.NewExchangeBackup()
|
bsel := selectors.NewExchangeBackup()
|
||||||
bsel.Include(bsel.Users(m365User))
|
bsel.Include(bsel.Users(m365User))
|
||||||
|
|
||||||
@ -153,7 +156,7 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
|
|||||||
ctx,
|
ctx,
|
||||||
Options{},
|
Options{},
|
||||||
w,
|
w,
|
||||||
ms,
|
sw,
|
||||||
acct,
|
acct,
|
||||||
bsel.Selector)
|
bsel.Selector)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -167,7 +170,7 @@ func (suite *RestoreOpIntegrationSuite) TestRestore_Run() {
|
|||||||
ctx,
|
ctx,
|
||||||
Options{},
|
Options{},
|
||||||
w,
|
w,
|
||||||
ms,
|
sw,
|
||||||
acct,
|
acct,
|
||||||
bo.Results.BackupID,
|
bo.Results.BackupID,
|
||||||
rsel.Selector)
|
rsel.Selector)
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/kopia/kopia/repo/manifest"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/alcionai/corso/internal/kopia"
|
"github.com/alcionai/corso/internal/kopia"
|
||||||
@ -15,6 +14,7 @@ import (
|
|||||||
"github.com/alcionai/corso/pkg/backup"
|
"github.com/alcionai/corso/pkg/backup"
|
||||||
"github.com/alcionai/corso/pkg/selectors"
|
"github.com/alcionai/corso/pkg/selectors"
|
||||||
"github.com/alcionai/corso/pkg/storage"
|
"github.com/alcionai/corso/pkg/storage"
|
||||||
|
"github.com/alcionai/corso/pkg/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Repository contains storage provider information.
|
// Repository contains storage provider information.
|
||||||
@ -133,7 +133,7 @@ func (r Repository) NewBackup(ctx context.Context, selector selectors.Selector)
|
|||||||
ctx,
|
ctx,
|
||||||
operations.Options{},
|
operations.Options{},
|
||||||
r.dataLayer,
|
r.dataLayer,
|
||||||
r.modelStore,
|
store.NewKopiaStore(r.modelStore),
|
||||||
r.Account,
|
r.Account,
|
||||||
selector)
|
selector)
|
||||||
}
|
}
|
||||||
@ -144,36 +144,20 @@ func (r Repository) NewRestore(ctx context.Context, backupID string, sel selecto
|
|||||||
ctx,
|
ctx,
|
||||||
operations.Options{},
|
operations.Options{},
|
||||||
r.dataLayer,
|
r.dataLayer,
|
||||||
r.modelStore,
|
store.NewKopiaStore(r.modelStore),
|
||||||
r.Account,
|
r.Account,
|
||||||
model.ID(backupID),
|
model.ID(backupID),
|
||||||
sel)
|
sel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// backups lists backups in a respository
|
// backups lists backups in a respository
|
||||||
func (r Repository) Backups(ctx context.Context) ([]*backup.Backup, error) {
|
func (r Repository) Backups(ctx context.Context) ([]backup.Backup, error) {
|
||||||
bms, err := r.modelStore.GetIDsForType(ctx, kopia.BackupModel, nil)
|
sw := store.NewKopiaStore(r.modelStore)
|
||||||
if err != nil {
|
return sw.GetBackups(ctx)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackupDetails returns the specified backup details object
|
// BackupDetails returns the specified backup details object
|
||||||
func (r Repository) BackupDetails(ctx context.Context, rpDetailsID string) (*backup.Details, error) {
|
func (r Repository) BackupDetails(ctx context.Context, backupID string) (*backup.Details, *backup.Backup, error) {
|
||||||
bud := backup.Details{}
|
sw := store.NewKopiaStore(r.modelStore)
|
||||||
err := r.modelStore.GetWithModelStoreID(ctx, kopia.BackupDetailsModel, manifest.ID(rpDetailsID), &bud)
|
return sw.GetDetailsFromBackupID(ctx, model.ID(backupID))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &bud, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
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