Skip to content

Commit

Permalink
Space registry multiple spaces per provider (#2340)
Browse files Browse the repository at this point in the history
* spaces-registry: rename rule to provider

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* remove alias config

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* multiple spaces per provider

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* fix integration tests, incorporate feedback

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* drop unneded config option

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* always return type of space to let registry build correct path

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* spaces registry: treat not found as empty list

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* add feedback

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
  • Loading branch information
butonic authored Dec 8, 2021
1 parent 3486c31 commit 3dc4c24
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 169 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/multiple-spaces-per-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Change: Allow multiple space configurations per provider

The spaces registry can now use multiple space configurations to allow personal and project spaces on the same provider

https://github.com/cs3org/reva/pull/2340
256 changes: 143 additions & 113 deletions pkg/storage/registry/spaces/spaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,31 @@ func init() {
pkgregistry.Register("spaces", NewDefault)
}

type provider struct {
Mapping string `mapstructure:"mapping"`
MountPath string `mapstructure:"mount_path"`
Aliases map[string]string `mapstructure:"aliases"`
AllowedUserAgents []string `mapstructure:"allowed_user_agents"`
PathTemplate string `mapstructure:"path_template"`
template *template.Template
type spaceConfig struct {
// MountPoint determines where a space is mounted. Can be a regex
// It is used to determine which storage provider is responsible when only a path is given in the request
MountPoint string `mapstructure:"mount_point"`
// PathTemplate is used to build the path of an individual space. Layouts can access {{.Space...}} and {{.CurrentUser...}}
PathTemplate string `mapstructure:"path_template"`
template *template.Template
// filters
SpaceType string `mapstructure:"space_type"`
SpaceOwnerSelf bool `mapstructure:"space_owner_self"`
SpaceID string `mapstructure:"space_id"`
OwnerIsCurrentUser bool `mapstructure:"owner_is_current_user"`
ID string `mapstructure:"id"`
// TODO description?
}

// SpacePath generates a layout based on space data.
func (sc *spaceConfig) SpacePath(currentUser *userpb.User, space *providerpb.StorageSpace) (string, error) {
b := bytes.Buffer{}
if err := sc.template.Execute(&b, templateData{CurrentUser: currentUser, Space: space}); err != nil {
return "", err
}
return b.String(), nil
}

type provider struct {
// Spaces is a map from space type to space config
Spaces map[string]*spaceConfig `mapstructure:"spaces"`
}

type templateData struct {
Expand All @@ -75,15 +89,6 @@ type StorageProviderClient interface {
ListStorageSpaces(ctx context.Context, in *providerpb.ListStorageSpacesRequest, opts ...grpc.CallOption) (*providerpb.ListStorageSpacesResponse, error)
}

// WithSpace generates a layout based on space data.
func (p *provider) ProviderPath(u *userpb.User, s *providerpb.StorageSpace) (string, error) {
b := bytes.Buffer{}
if err := p.template.Execute(&b, templateData{CurrentUser: u, Space: s}); err != nil {
return "", err
}
return b.String(), nil
}

type config struct {
Providers map[string]*provider `mapstructure:"providers"`
HomeTemplate string `mapstructure:"home_template"`
Expand All @@ -98,27 +103,38 @@ func (c *config) init() {
if len(c.Providers) == 0 {
c.Providers = map[string]*provider{
sharedconf.GetGatewaySVC(""): {
MountPath: "/",
Spaces: map[string]*spaceConfig{
"personal": {MountPoint: "/users", PathTemplate: "/users/{{.Space.Owner.Id.OpaqueId}}"},
"project": {MountPoint: "/projects", PathTemplate: "/projects/{{.Space.Name}}"},
"share": {MountPoint: "/users/{{.CurrentUser.Id.OpaqueId}}/Shares", PathTemplate: "/users/{{.CurrentUser.Id.OpaqueId}}/Shares/{{.Space.Name}}"},
"public": {MountPoint: "/public"},
},
},
}
}

// cleanup rule paths
for _, rule := range c.Providers {
// if the path template is not explicitly set use the mountpath as path template
if rule.PathTemplate == "" && strings.HasPrefix(rule.MountPath, "/") {
// TODO err if the path is a regex
rule.PathTemplate = rule.MountPath
}
// cleanup space paths
for _, provider := range c.Providers {
for _, space := range provider.Spaces {

// cleanup path template
rule.PathTemplate = filepath.Clean(rule.PathTemplate)
if space.MountPoint == "" {
space.MountPoint = "/"
}

// compile given template tpl
var err error
rule.template, err = template.New("path_template").Funcs(sprig.TxtFuncMap()).Parse(rule.PathTemplate)
if err != nil {
logger.New().Fatal().Err(err).Interface("rule", rule).Msg("error parsing template")
// if the path template is not explicitly set use the mount point as path template
if space.PathTemplate == "" {
space.PathTemplate = space.MountPoint
}

// cleanup path templates
space.PathTemplate = filepath.Join("/", space.PathTemplate)

// compile given template tpl
var err error
space.template, err = template.New("path_template").Funcs(sprig.TxtFuncMap()).Parse(space.PathTemplate)
if err != nil {
logger.New().Fatal().Err(err).Interface("space", space).Msg("error parsing template")
}
}

// TODO connect to provider, (List Spaces,) ListContainerStream
Expand All @@ -142,9 +158,8 @@ func New(m map[string]interface{}, getClientFunc GetStorageProviderServiceClient
}
c.init()
r := &registry{
c: c,
resources: make(map[string][]*registrypb.ProviderInfo),
//aliases: make(map[string]map[string]*spaceAndProvider),
c: c,
resources: make(map[string][]*registrypb.ProviderInfo),
resourceNameCache: make(map[string]string),
getStorageProviderServiceClient: getClientFunc,
}
Expand Down Expand Up @@ -172,43 +187,43 @@ type registry struct {
// the template to use when determining the home provider
homeTemplate *template.Template
// a map of resources to providers
resources map[string][]*registrypb.ProviderInfo
// a map of paths/aliases to spaces and providers
// aliases map[string]map[string]*spaceAndProvider
resources map[string][]*registrypb.ProviderInfo
resourceNameCache map[string]string

getStorageProviderServiceClient GetStorageProviderServiceClientFunc
}

// GetProvider return the storage provider for the given spaces according to the rule configuration
func (r *registry) GetProvider(ctx context.Context, space *providerpb.StorageSpace) (*registrypb.ProviderInfo, error) {
for address, rule := range r.c.Providers {
mountPath := ""
var err error
if space.SpaceType != "" && rule.SpaceType != space.SpaceType {
continue
}
if space.Owner != nil {
mountPath, err = rule.ProviderPath(nil, space)
if err != nil {
for address, provider := range r.c.Providers {
for spaceType, sc := range provider.Spaces {
spacePath := ""
var err error
if space.SpaceType != "" && spaceType != space.SpaceType {
continue
}
match, err := regexp.MatchString(rule.MountPath, mountPath)
if err != nil {
continue
if space.Owner != nil {
spacePath, err = sc.SpacePath(nil, space)
if err != nil {
continue
}
match, err := regexp.MatchString(sc.MountPoint, spacePath)
if err != nil {
continue
}
if !match {
continue
}
}
if !match {
pi := &registrypb.ProviderInfo{Address: address}
opaque, err := spacePathsToOpaque(map[string]string{"unused": spacePath})
if err != nil {
appctx.GetLogger(ctx).Debug().Err(err).Msg("marshaling space paths map failed, continuing")
continue
}
pi.Opaque = opaque
return pi, nil // return the first match we find
}
pi := &registrypb.ProviderInfo{Address: address}
opaque, err := spacePathsToOpaque(map[string]string{"unused": mountPath})
if err != nil {
appctx.GetLogger(ctx).Debug().Err(err).Msg("marshaling space paths map failed, continuing")
continue
}
pi.Opaque = opaque
return pi, nil
}
return nil, errtypes.NotFound("no provider found for space")
}
Expand Down Expand Up @@ -269,53 +284,62 @@ func (r *registry) ListProviders(ctx context.Context, filters map[string]string)
// for share spaces the res.StorageId tells the registry the spaceid and res.OpaqueId is a node in that space
func (r *registry) findProvidersForResource(ctx context.Context, id string) []*registrypb.ProviderInfo {
currentUser := ctxpkg.ContextMustGetUser(ctx)
for address, rule := range r.c.Providers {
for address, provider := range r.c.Providers {
p := &registrypb.ProviderInfo{
Address: address,
ProviderId: id,
}
filters := []*providerpb.ListStorageSpacesRequest_Filter{}
if rule.SpaceType != "" {
filters := []*providerpb.ListStorageSpacesRequest_Filter{{
Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_ID,
Term: &providerpb.ListStorageSpacesRequest_Filter_Id{
Id: &providerpb.StorageSpaceId{
OpaqueId: id,
},
},
}}
for spaceType := range provider.Spaces {
// add filter to id based request if it is configured
filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE,
Term: &providerpb.ListStorageSpacesRequest_Filter_SpaceType{
SpaceType: rule.SpaceType,
SpaceType: spaceType,
},
})
}
filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_ID,
Term: &providerpb.ListStorageSpacesRequest_Filter_Id{
Id: &providerpb.StorageSpaceId{
OpaqueId: id,
},
},
})
spaces, err := r.findStorageSpaceOnProvider(ctx, address, filters)
if err != nil {
appctx.GetLogger(ctx).Debug().Err(err).Interface("rule", rule).Msg("findStorageSpaceOnProvider by id failed, continuing")
appctx.GetLogger(ctx).Debug().Err(err).Interface("provider", provider).Msg("findStorageSpaceOnProvider by id failed, continuing")
continue
}

if len(spaces) > 0 {
space := spaces[0] // there shouldn't be multiple
providerPath, err := rule.ProviderPath(currentUser, space)
if err != nil {
appctx.GetLogger(ctx).Error().Err(err).Interface("rule", rule).Interface("space", space).Msg("failed to execute template, continuing")
continue
}

spacePaths := map[string]string{
space.Id.OpaqueId: providerPath,
}
p.Opaque, err = spacePathsToOpaque(spacePaths)
if err != nil {
appctx.GetLogger(ctx).Debug().Err(err).Msg("marshaling space paths map failed, continuing")
continue
switch len(spaces) {
case 0:
// nothing to do, will continue with next provider
case 1:
space := spaces[0]
for spaceType, sc := range provider.Spaces {
if spaceType == space.SpaceType {
providerPath, err := sc.SpacePath(currentUser, space)
if err != nil {
appctx.GetLogger(ctx).Error().Err(err).Interface("provider", provider).Interface("space", space).Msg("failed to execute template, continuing")
continue
}
spacePaths := map[string]string{
space.Id.OpaqueId: providerPath,
}
p.Opaque, err = spacePathsToOpaque(spacePaths)
if err != nil {
appctx.GetLogger(ctx).Debug().Err(err).Msg("marshaling space paths map failed, continuing")
continue
}
return []*registrypb.ProviderInfo{p}
}
}
return []*registrypb.ProviderInfo{p}
default:
// there should not be multiple spaces with the same id per provider
appctx.GetLogger(ctx).Error().Err(err).Interface("provider", provider).Interface("spaces", spaces).Msg("multiple spaces returned, ignoring")
}

}
return []*registrypb.ProviderInfo{}
}
Expand All @@ -329,49 +353,55 @@ func (r *registry) findProvidersForAbsolutePathReference(ctx context.Context, pa
var deepestMountSpace *providerpb.StorageSpace
var deepestMountPathProvider *registrypb.ProviderInfo
providers := map[string]map[string]string{}
for address, rule := range r.c.Providers {
for address, provider := range r.c.Providers {
p := &registrypb.ProviderInfo{
Address: address,
}
var spaces []*providerpb.StorageSpace
var err error
filters := []*providerpb.ListStorageSpacesRequest_Filter{}
if rule.SpaceOwnerSelf {
filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_OWNER,
Term: &providerpb.ListStorageSpacesRequest_Filter_Owner{
Owner: currentUser.Id,
},
})
}
if rule.SpaceType != "" {
for spaceType, sc := range provider.Spaces {

filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE,
Term: &providerpb.ListStorageSpacesRequest_Filter_SpaceType{
SpaceType: rule.SpaceType,
},
})
}
if rule.SpaceID != "" {
filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_ID,
Term: &providerpb.ListStorageSpacesRequest_Filter_Id{
Id: &providerpb.StorageSpaceId{OpaqueId: rule.SpaceID},
SpaceType: spaceType,
},
})
if sc.OwnerIsCurrentUser {
filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_OWNER,
Term: &providerpb.ListStorageSpacesRequest_Filter_Owner{
Owner: currentUser.Id,
},
})
}
if sc.ID != "" {
filters = append(filters, &providerpb.ListStorageSpacesRequest_Filter{
Type: providerpb.ListStorageSpacesRequest_Filter_TYPE_ID,
Term: &providerpb.ListStorageSpacesRequest_Filter_Id{
Id: &providerpb.StorageSpaceId{OpaqueId: sc.ID},
},
})
}
}

spaces, err = r.findStorageSpaceOnProvider(ctx, p.Address, filters)
if err != nil {
appctx.GetLogger(ctx).Debug().Err(err).Interface("rule", rule).Msg("findStorageSpaceOnProvider failed, continuing")
appctx.GetLogger(ctx).Debug().Err(err).Interface("provider", provider).Msg("findStorageSpaceOnProvider failed, continuing")
continue
}

spacePaths := map[string]string{}
for _, space := range spaces {
spacePath, err := rule.ProviderPath(currentUser, space)
var sc *spaceConfig
var ok bool
if sc, ok = provider.Spaces[space.SpaceType]; !ok {
continue
}
spacePath, err := sc.SpacePath(currentUser, space)
if err != nil {
appctx.GetLogger(ctx).Error().Err(err).Interface("rule", rule).Interface("space", space).Msg("failed to execute template, continuing")
appctx.GetLogger(ctx).Error().Err(err).Interface("provider", provider).Interface("space", space).Msg("failed to execute template, continuing")
continue
}

Expand Down Expand Up @@ -450,7 +480,7 @@ func (r *registry) findStorageSpaceOnProvider(ctx context.Context, addr string,
if err != nil {
return nil, err
}
if res.Status.Code != rpc.Code_CODE_OK {
if res.Status.Code != rpc.Code_CODE_OK && res.Status.Code != rpc.Code_CODE_NOT_FOUND {
return nil, status.NewErrorFromCode(res.Status.Code, "spaces registry")
}
return res.StorageSpaces, nil
Expand Down
Loading

0 comments on commit 3dc4c24

Please sign in to comment.