Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Space registry multiple spaces per provider #2340

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"`
kobergj marked this conversation as resolved.
Show resolved Hide resolved
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)
kobergj marked this conversation as resolved.
Show resolved Hide resolved
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