first pass on compliance with the reason
establishes behavior around using the reasoner interface in a world where paths can contain multiple services. Primarily focused on ensuring the reasoner clearly guides maintainers towards proper usage.
This commit is contained in:
parent
48aae5d485
commit
3d15a0d649
@ -111,6 +111,12 @@ func (bb *backupBases) ClearAssistBases() {
|
|||||||
bb.assistBases = nil
|
bb.assistBases = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BaseKeyServiceCategory makes a backup base key using
|
||||||
|
// the reasoner's Service and Category.
|
||||||
|
func BaseKeyServiceCategory(br identity.Reasoner) string {
|
||||||
|
return br.Service().String() + br.Category().String()
|
||||||
|
}
|
||||||
|
|
||||||
// MergeBackupBases reduces the two BackupBases into a single BackupBase.
|
// MergeBackupBases reduces the two BackupBases into a single BackupBase.
|
||||||
// Assumes the passed in BackupBases represents a prior backup version (across
|
// Assumes the passed in BackupBases represents a prior backup version (across
|
||||||
// some migration that disrupts lookup), and that the BackupBases used to call
|
// some migration that disrupts lookup), and that the BackupBases used to call
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/backup"
|
"github.com/alcionai/corso/src/pkg/backup"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/identity"
|
"github.com/alcionai/corso/src/pkg/backup/identity"
|
||||||
|
idMock "github.com/alcionai/corso/src/pkg/backup/identity/mock"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -460,18 +461,12 @@ func (suite *BackupBasesUnitSuite) TestMergeBackupBases() {
|
|||||||
|
|
||||||
bb := makeBackupBases(test.merge, test.assist)
|
bb := makeBackupBases(test.merge, test.assist)
|
||||||
other := makeBackupBases(test.otherMerge, test.otherAssist)
|
other := makeBackupBases(test.otherMerge, test.otherAssist)
|
||||||
expected := test.expect()
|
|
||||||
|
|
||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
got := bb.MergeBackupBases(
|
got := bb.MergeBackupBases(ctx, other, BaseKeyServiceCategory)
|
||||||
ctx,
|
AssertBackupBasesEqual(t, test.expect(), got)
|
||||||
other,
|
|
||||||
func(r identity.Reasoner) string {
|
|
||||||
return r.Service().String() + r.Category().String()
|
|
||||||
})
|
|
||||||
AssertBackupBasesEqual(t, expected, got)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -843,3 +838,37 @@ func (suite *BackupBasesUnitSuite) TestFixupAndVerify() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *BackupBasesUnitSuite) TestBaseKeyServiceCategory() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
service path.ServiceType
|
||||||
|
category path.CategoryType
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unknown",
|
||||||
|
service: path.UnknownService,
|
||||||
|
category: path.UnknownCategory,
|
||||||
|
expect: "unknown",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "known service",
|
||||||
|
service: path.ExchangeService,
|
||||||
|
category: path.EmailCategory,
|
||||||
|
expect: "exchangeEmail",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
result := BaseKeyServiceCategory(idMock.Reason{
|
||||||
|
TenantID: "tid",
|
||||||
|
Cat: test.category,
|
||||||
|
Svc: test.service,
|
||||||
|
})
|
||||||
|
assert.Equal(t, test.expect, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -70,11 +70,14 @@ func (r reason) Category() path.CategoryType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r reason) SubtreePath() (path.Path, error) {
|
func (r reason) SubtreePath() (path.Path, error) {
|
||||||
p, err := path.BuildPrefix(
|
srs, err := path.NewServiceResources(
|
||||||
r.Tenant(),
|
|
||||||
r.ProtectedResource(),
|
|
||||||
r.Service(),
|
r.Service(),
|
||||||
r.Category())
|
r.ProtectedResource())
|
||||||
|
if err != nil {
|
||||||
|
return nil, clues.Wrap(err, "building path service prefix")
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := path.BuildPrefix(r.Tenant(), srs, r.Category())
|
||||||
|
|
||||||
return p, clues.Wrap(err, "building path").OrNil()
|
return p, clues.Wrap(err, "building path").OrNil()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -196,14 +196,17 @@ func (cp *corsoProgress) FinishedFile(relativePath string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := clues.Add(
|
||||||
|
cp.ctx,
|
||||||
|
"services", path.ServiceResourcesToServices(d.repoPath.ServiceResources()),
|
||||||
|
"category", d.repoPath.Category().String())
|
||||||
|
|
||||||
// These items were sourced from a base snapshot or were cached in kopia so we
|
// These items were sourced from a base snapshot or were cached in kopia so we
|
||||||
// never had to materialize their details in-memory.
|
// never had to materialize their details in-memory.
|
||||||
if d.info == nil || d.cached {
|
if d.info == nil || d.cached {
|
||||||
if d.prevPath == nil {
|
if d.prevPath == nil {
|
||||||
cp.errs.AddRecoverable(cp.ctx, clues.New("item sourced from previous backup with no previous path").
|
cp.errs.AddRecoverable(cp.ctx, clues.New("item sourced from previous backup with no previous path").
|
||||||
With(
|
WithClues(ctx).
|
||||||
"service", d.repoPath.Service().String(),
|
|
||||||
"category", d.repoPath.Category().String()).
|
|
||||||
Label(fault.LabelForceNoBackupCreation))
|
Label(fault.LabelForceNoBackupCreation))
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -219,9 +222,7 @@ func (cp *corsoProgress) FinishedFile(relativePath string, err error) {
|
|||||||
d.locationPath)
|
d.locationPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cp.errs.AddRecoverable(cp.ctx, clues.Wrap(err, "adding item to merge list").
|
cp.errs.AddRecoverable(cp.ctx, clues.Wrap(err, "adding item to merge list").
|
||||||
With(
|
WithClues(ctx).
|
||||||
"service", d.repoPath.Service().String(),
|
|
||||||
"category", d.repoPath.Category().String()).
|
|
||||||
Label(fault.LabelForceNoBackupCreation))
|
Label(fault.LabelForceNoBackupCreation))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,9 +236,7 @@ func (cp *corsoProgress) FinishedFile(relativePath string, err error) {
|
|||||||
*d.info)
|
*d.info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cp.errs.AddRecoverable(cp.ctx, clues.New("adding item to details").
|
cp.errs.AddRecoverable(cp.ctx, clues.New("adding item to details").
|
||||||
With(
|
WithClues(ctx).
|
||||||
"service", d.repoPath.Service().String(),
|
|
||||||
"category", d.repoPath.Category().String()).
|
|
||||||
Label(fault.LabelForceNoBackupCreation))
|
Label(fault.LabelForceNoBackupCreation))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@ -80,12 +80,7 @@ func getManifestsAndMetadata(
|
|||||||
// 3. the current reasons contain all the necessary manifests.
|
// 3. the current reasons contain all the necessary manifests.
|
||||||
// Note: This is not relevant for assist backups, since they are newly introduced
|
// Note: This is not relevant for assist backups, since they are newly introduced
|
||||||
// and they don't exist with fallback reasons.
|
// and they don't exist with fallback reasons.
|
||||||
bb = bb.MergeBackupBases(
|
bb = bb.MergeBackupBases(ctx, fbb, kopia.BaseKeyServiceCategory)
|
||||||
ctx,
|
|
||||||
fbb,
|
|
||||||
func(r identity.Reasoner) string {
|
|
||||||
return r.Service().String() + r.Category().String()
|
|
||||||
})
|
|
||||||
|
|
||||||
if !getMetadata {
|
if !getMetadata {
|
||||||
return bb, nil, false, nil
|
return bb, nil, false, nil
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/backup"
|
"github.com/alcionai/corso/src/pkg/backup"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/identity"
|
"github.com/alcionai/corso/src/pkg/backup/identity"
|
||||||
|
idMock "github.com/alcionai/corso/src/pkg/backup/identity/mock"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/repository"
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
"github.com/alcionai/corso/src/pkg/count"
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
@ -244,7 +245,11 @@ func checkBackupIsInManifests(
|
|||||||
for _, category := range categories {
|
for _, category := range categories {
|
||||||
t.Run(category.String(), func(t *testing.T) {
|
t.Run(category.String(), func(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
r = kopia.NewReason("", resourceOwner, sel.PathService(), category)
|
r = idMock.Reason{
|
||||||
|
Cat: category,
|
||||||
|
Svc: sel.PathService(),
|
||||||
|
Resource: resourceOwner,
|
||||||
|
}
|
||||||
tags = map[string]string{kopia.TagBackupCategory: ""}
|
tags = map[string]string{kopia.TagBackupCategory: ""}
|
||||||
found bool
|
found bool
|
||||||
)
|
)
|
||||||
|
|||||||
@ -5,9 +5,20 @@ import "github.com/alcionai/corso/src/pkg/path"
|
|||||||
// Reasoner describes the parts of the backup that make up its
|
// Reasoner describes the parts of the backup that make up its
|
||||||
// data identity: the tenant, protected resources, services, and
|
// data identity: the tenant, protected resources, services, and
|
||||||
// categories which are held within the backup.
|
// categories which are held within the backup.
|
||||||
|
//
|
||||||
|
// Reasoner only recognizes the "primary" protected resource and
|
||||||
|
// service. IE: subservice resources and services are not recognized
|
||||||
|
// as part of the backup Reason.
|
||||||
type Reasoner interface {
|
type Reasoner interface {
|
||||||
Tenant() string
|
Tenant() string
|
||||||
|
// ProtectedResource represents the Primary protected resource.
|
||||||
|
// IE: if a path or backup supports subservices, this value
|
||||||
|
// should only provide the first service's resource, and not the
|
||||||
|
// resource for any subservice.
|
||||||
ProtectedResource() string
|
ProtectedResource() string
|
||||||
|
// Service represents the Primary service.
|
||||||
|
// IE: if a path or backup supports subservices, this value
|
||||||
|
// should only provide the first service; not a subservice.
|
||||||
Service() path.ServiceType
|
Service() path.ServiceType
|
||||||
Category() path.CategoryType
|
Category() path.CategoryType
|
||||||
// SubtreePath returns the path prefix for data in existing backups that have
|
// SubtreePath returns the path prefix for data in existing backups that have
|
||||||
|
|||||||
47
src/pkg/backup/identity/mock/reasoner.go
Normal file
47
src/pkg/backup/identity/mock/reasoner.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reason struct {
|
||||||
|
TenantID string
|
||||||
|
Cat path.CategoryType
|
||||||
|
Svc path.ServiceType
|
||||||
|
Resource string
|
||||||
|
SubtreeErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Reason) Tenant() string {
|
||||||
|
return r.TenantID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Reason) Category() path.CategoryType {
|
||||||
|
return r.Cat
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Reason) Service() path.ServiceType {
|
||||||
|
return r.Svc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Reason) ProtectedResource() string {
|
||||||
|
return r.Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Reason) SubtreePath() (path.Path, error) {
|
||||||
|
if r.SubtreeErr != nil {
|
||||||
|
return nil, r.SubtreeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := path.BuildPrefix(
|
||||||
|
r.Tenant(),
|
||||||
|
[]path.ServiceResource{{
|
||||||
|
ProtectedResource: r.Resource,
|
||||||
|
Service: r.Svc,
|
||||||
|
}},
|
||||||
|
r.Category())
|
||||||
|
|
||||||
|
return p, clues.Wrap(err, "building path").OrNil()
|
||||||
|
}
|
||||||
@ -113,9 +113,10 @@ func (suite *ServiceCategoryUnitSuite) TestToServiceType() {
|
|||||||
}
|
}
|
||||||
for _, test := range table {
|
for _, test := range table {
|
||||||
suite.Run(test.name, func() {
|
suite.Run(test.name, func() {
|
||||||
t := suite.T()
|
assert.Equal(
|
||||||
|
suite.T(),
|
||||||
assert.Equal(t, test.expected, toServiceType(test.service))
|
test.expected,
|
||||||
|
ToServiceType(test.service))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,6 +85,16 @@ func ServiceResourcesToResources(srs []ServiceResource) []string {
|
|||||||
return prs
|
return prs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ServiceResourcesToServices(srs []ServiceResource) []ServiceType {
|
||||||
|
sts := make([]ServiceType, len(srs))
|
||||||
|
|
||||||
|
for i := range srs {
|
||||||
|
sts[i] = srs[i].Service
|
||||||
|
}
|
||||||
|
|
||||||
|
return sts
|
||||||
|
}
|
||||||
|
|
||||||
func ServiceResourcesMatchServices(srs []ServiceResource, sts []ServiceType) bool {
|
func ServiceResourcesMatchServices(srs []ServiceResource, sts []ServiceType) bool {
|
||||||
return slices.EqualFunc(srs, sts, func(sr ServiceResource, st ServiceType) bool {
|
return slices.EqualFunc(srs, sts, func(sr ServiceResource, st ServiceType) bool {
|
||||||
return sr.Service == st
|
return sr.Service == st
|
||||||
@ -125,7 +135,7 @@ func elementsToServiceResources(elems Elements) ([]ServiceResource, int, error)
|
|||||||
)
|
)
|
||||||
|
|
||||||
for j := 1; i < len(elems); i, j = i+2, j+2 {
|
for j := 1; i < len(elems); i, j = i+2, j+2 {
|
||||||
service := toServiceType(elems[i])
|
service := ToServiceType(elems[i])
|
||||||
if service == UnknownService {
|
if service == UnknownService {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
return nil, -1, clues.Wrap(errMissingSegment, "service")
|
return nil, -1, clues.Wrap(errMissingSegment, "service")
|
||||||
|
|||||||
@ -33,7 +33,7 @@ const (
|
|||||||
GroupsMetadataService // groupsMetadata
|
GroupsMetadataService // groupsMetadata
|
||||||
)
|
)
|
||||||
|
|
||||||
func toServiceType(service string) ServiceType {
|
func ToServiceType(service string) ServiceType {
|
||||||
s := strings.ToLower(service)
|
s := strings.ToLower(service)
|
||||||
|
|
||||||
switch s {
|
switch s {
|
||||||
|
|||||||
@ -3,6 +3,8 @@ package selectors
|
|||||||
import (
|
import (
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/idname"
|
"github.com/alcionai/corso/src/internal/common/idname"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/identity"
|
"github.com/alcionai/corso/src/pkg/backup/identity"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -38,11 +40,12 @@ func (br backupReason) Category() path.CategoryType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (br backupReason) SubtreePath() (path.Path, error) {
|
func (br backupReason) SubtreePath() (path.Path, error) {
|
||||||
return path.BuildPrefix(
|
srs, err := path.NewServiceResources(br.service, br.resource)
|
||||||
br.tenant,
|
if err != nil {
|
||||||
br.resource,
|
return nil, clues.Wrap(err, "building path prefix services")
|
||||||
br.service,
|
}
|
||||||
br.category)
|
|
||||||
|
return path.BuildPrefix(br.tenant, srs, br.category)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br backupReason) key() string {
|
func (br backupReason) key() string {
|
||||||
@ -59,6 +62,14 @@ type servicerCategorizerProvider interface {
|
|||||||
idname.Provider
|
idname.Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// produces the Reasoner basis described by the selector.
|
||||||
|
// In cases of reasons with subservices (ie, multiple
|
||||||
|
// services described by a backup or path), the selector
|
||||||
|
// will only ever generate a ServiceResource for the first
|
||||||
|
// service+resource pair in the set.
|
||||||
|
//
|
||||||
|
// TODO: it may be possible, if necessary, to add subservice
|
||||||
|
// recognition to the service via additional scopes.
|
||||||
func reasonsFor(
|
func reasonsFor(
|
||||||
sel servicerCategorizerProvider,
|
sel servicerCategorizerProvider,
|
||||||
tenantID string,
|
tenantID string,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user