From 5c7d2168fcd4c4902561446ff2c4ee470c3efb15 Mon Sep 17 00:00:00 2001 From: ashmrtn Date: Tue, 6 Dec 2022 18:32:49 -0800 Subject: [PATCH] Create path categories for metadata folders (#1718) ## Description Corso needs to store some transient information between backups so that it can perform more efficient incremental backups. This transient information will live at a path `tenant-id/service/user-id/categoryMetadata`. This patch adds an initializer, category enums, and tests for these new path locations ## Type of change - [x] :sunflower: Feature - [ ] :bug: Bugfix - [ ] :world_map: Documentation - [ ] :robot: Test - [ ] :computer: CI/Deployment - [ ] :hamster: Trivial/Minor ## Issue(s) * #1685 ## Test Plan - [ ] :muscle: Manual - [x] :zap: Unit test - [ ] :green_heart: E2E --- src/pkg/path/path.go | 52 +++++++++++++++- src/pkg/path/resource_path.go | 34 +++++++++-- src/pkg/path/resource_path_test.go | 95 ++++++++++++++++++++++++++++++ src/pkg/path/servicetype_string.go | 7 ++- 4 files changed, 180 insertions(+), 8 deletions(-) diff --git a/src/pkg/path/path.go b/src/pkg/path/path.go index 5a06d3d74..12326f053 100644 --- a/src/pkg/path/path.go +++ b/src/pkg/path/path.go @@ -250,7 +250,7 @@ func (pb Builder) join(start, end int) string { return join(pb.elements[start:end]) } -func (pb Builder) verifyPrefix(tenant, resourceOwner string) error { +func verifyInputValues(tenant, resourceOwner string) error { if len(tenant) == 0 { return errors.Wrap(errMissingSegment, "tenant") } @@ -259,6 +259,14 @@ func (pb Builder) verifyPrefix(tenant, resourceOwner string) error { return errors.Wrap(errMissingSegment, "resourceOwner") } + return nil +} + +func (pb Builder) verifyPrefix(tenant, resourceOwner string) error { + if err := verifyInputValues(tenant, resourceOwner); err != nil { + return err + } + if len(pb.elements) == 0 { return errors.New("missing path beyond prefix") } @@ -273,6 +281,48 @@ func (pb Builder) withPrefix(elements ...string) *Builder { return res } +func (pb Builder) ToServiceCategoryMetadataPath( + tenant, user string, + service ServiceType, + category CategoryType, + isItem bool, +) (Path, error) { + if err := validateServiceAndCategory(service, category); err != nil { + return nil, err + } + + if err := verifyInputValues(tenant, user); err != nil { + return nil, err + } + + if isItem && len(pb.elements) == 0 { + return nil, errors.New("missing path beyond prefix") + } + + metadataService := UnknownService + + switch service { + case ExchangeService: + metadataService = ExchangeMetadataService + case OneDriveService: + metadataService = OneDriveMetadataService + case SharePointService: + metadataService = SharePointMetadataService + } + + return &dataLayerResourcePath{ + Builder: *pb.withPrefix( + tenant, + metadataService.String(), + user, + category.String(), + ), + service: metadataService, + category: category, + hasItem: isItem, + }, nil +} + func (pb Builder) ToDataLayerExchangePathForCategory( tenant, user string, category CategoryType, diff --git a/src/pkg/path/resource_path.go b/src/pkg/path/resource_path.go index 6bd49834f..61ba1b5ff 100644 --- a/src/pkg/path/resource_path.go +++ b/src/pkg/path/resource_path.go @@ -6,14 +6,27 @@ import ( var ErrorUnknownService = errors.New("unknown service string") +// ServiceType denotes what service the path corresponds to. Metadata services +// are also included though they are only used for paths that house metadata for +// Corso backups. +// +// Metadata services are not considered valid service types for resource paths +// though they can be used for metadata paths. +// +// The order of the enums below can be changed, but the string representation of +// each enum must remain the same or migration code needs to be added to handle +// changes to the string format. type ServiceType int //go:generate stringer -type=ServiceType -linecomment const ( - UnknownService ServiceType = iota - ExchangeService // exchange - OneDriveService // onedrive - SharePointService // sharepoint + UnknownService ServiceType = iota + ExchangeService // exchange + OneDriveService // onedrive + SharePointService // sharepoint + ExchangeMetadataService // exchangeMetadata + OneDriveMetadataService // onedriveMetadata + SharePointMetadataService // sharepointMetadata ) func toServiceType(service string) ServiceType { @@ -24,6 +37,12 @@ func toServiceType(service string) ServiceType { return OneDriveService case SharePointService.String(): return SharePointService + case ExchangeMetadataService.String(): + return ExchangeMetadataService + case OneDriveMetadataService.String(): + return OneDriveMetadataService + case SharePointMetadataService.String(): + return SharePointMetadataService default: return UnknownService } @@ -31,6 +50,10 @@ func toServiceType(service string) ServiceType { var ErrorUnknownCategory = errors.New("unknown category string") +// CategoryType denotes what category of data the path corresponds to. The order +// of the enums below can be changed, but the string representation of each enum +// must remain the same or migration code needs to be added to handle changes to +// the string format. type CategoryType int //go:generate stringer -type=CategoryType -linecomment @@ -63,7 +86,8 @@ func ToCategoryType(category string) CategoryType { } } -// serviceCategories is a mapping of all valid service/category pairs. +// serviceCategories is a mapping of all valid service/category pairs for +// non-metadata paths. var serviceCategories = map[ServiceType]map[CategoryType]struct{}{ ExchangeService: { EmailCategory: {}, diff --git a/src/pkg/path/resource_path_test.go b/src/pkg/path/resource_path_test.go index e3f913eda..4ccfd03f8 100644 --- a/src/pkg/path/resource_path_test.go +++ b/src/pkg/path/resource_path_test.go @@ -233,6 +233,101 @@ func (suite *DataLayerResourcePath) TestDir() { } } +func (suite *DataLayerResourcePath) TestToServiceCategoryMetadataPath() { + tenant := "a-tenant" + user := "a-user" + table := []struct { + name string + service path.ServiceType + category path.CategoryType + postfix []string + expectedService path.ServiceType + check assert.ErrorAssertionFunc + }{ + { + name: "NoPostfixPasses", + service: path.ExchangeService, + category: path.EmailCategory, + expectedService: path.ExchangeMetadataService, + check: assert.NoError, + }, + { + name: "PostfixPasses", + service: path.ExchangeService, + category: path.EmailCategory, + postfix: []string{"a", "b"}, + expectedService: path.ExchangeMetadataService, + check: assert.NoError, + }, + { + name: "Fails", + service: path.ExchangeService, + category: path.FilesCategory, + check: assert.Error, + }, + { + name: "Passes", + service: path.ExchangeService, + category: path.ContactsCategory, + expectedService: path.ExchangeMetadataService, + check: assert.NoError, + }, + { + name: "Passes", + service: path.ExchangeService, + category: path.EventsCategory, + expectedService: path.ExchangeMetadataService, + check: assert.NoError, + }, + { + name: "Passes", + service: path.OneDriveService, + category: path.FilesCategory, + expectedService: path.OneDriveMetadataService, + check: assert.NoError, + }, + { + name: "Passes", + service: path.SharePointService, + category: path.LibrariesCategory, + expectedService: path.SharePointMetadataService, + check: assert.NoError, + }, + { + name: "Passes", + service: path.SharePointService, + category: path.ListsCategory, + expectedService: path.SharePointMetadataService, + check: assert.NoError, + }, + } + + for _, test := range table { + suite.T().Run(strings.Join([]string{ + test.name, + test.service.String(), + test.category.String(), + }, "_"), func(t *testing.T) { + pb := path.Builder{}.Append(test.postfix...) + p, err := pb.ToServiceCategoryMetadataPath( + tenant, + user, + test.service, + test.category, + false, + ) + + test.check(t, err) + + if err != nil { + return + } + + assert.Equal(t, test.expectedService, p.Service()) + }) + } +} + func (suite *DataLayerResourcePath) TestToExchangePathForCategory() { b := path.Builder{}.Append(rest...) table := []struct { diff --git a/src/pkg/path/servicetype_string.go b/src/pkg/path/servicetype_string.go index 6e1b81e4b..6d6b960d8 100644 --- a/src/pkg/path/servicetype_string.go +++ b/src/pkg/path/servicetype_string.go @@ -12,11 +12,14 @@ func _() { _ = x[ExchangeService-1] _ = x[OneDriveService-2] _ = x[SharePointService-3] + _ = x[ExchangeMetadataService-4] + _ = x[OneDriveMetadataService-5] + _ = x[SharePointMetadataService-6] } -const _ServiceType_name = "UnknownServiceexchangeonedrivesharepoint" +const _ServiceType_name = "UnknownServiceexchangeonedrivesharepointexchangeMetadataonedriveMetadatasharepointMetadata" -var _ServiceType_index = [...]uint8{0, 14, 22, 30, 40} +var _ServiceType_index = [...]uint8{0, 14, 22, 30, 40, 56, 72, 90} func (i ServiceType) String() string { if i < 0 || i >= ServiceType(len(_ServiceType_index)-1) {