Skip to content

Commit d2f72e0

Browse files
authored
[spaces 1/2] initial ListStorageSpaces implementation (#1802)
1 parent 66996cb commit d2f72e0

File tree

14 files changed

+470
-34
lines changed

14 files changed

+470
-34
lines changed

changelog/unreleased/list-spaces.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Enhancement: Introduce list spaces
2+
3+
The ListStorageSpaces call now allows listing all user homes and shared resources using a storage space id. The gateway will forward requests to a specific storage provider when a filter by id is given. Otherwise it will query all storage providers. Results will be deduplicated. Currently, only the decomposed fs storage driver implements the necessary logic to demonstrate the implmentation. A new `/dav/spaces` WebDAV endpoint to directly access a storage space is introduced in a separate PR.
4+
5+
https://github.com/cs3org/reva/pull/1802
6+
https://github.com/cs3org/reva/pull/1803

internal/grpc/services/gateway/storageprovider.go

+110-17
Original file line numberDiff line numberDiff line change
@@ -115,37 +115,130 @@ func (s *svc) CreateStorageSpace(ctx context.Context, req *provider.CreateStorag
115115

116116
func (s *svc) ListStorageSpaces(ctx context.Context, req *provider.ListStorageSpacesRequest) (*provider.ListStorageSpacesResponse, error) {
117117
log := appctx.GetLogger(ctx)
118-
// TODO: needs to be fixed
119118
var id *provider.StorageSpaceId
120119
for _, f := range req.Filters {
121120
if f.Type == provider.ListStorageSpacesRequest_Filter_TYPE_ID {
122121
id = f.GetId()
123122
}
124123
}
125-
parts := strings.SplitN(id.OpaqueId, "!", 2)
126-
if len(parts) != 2 {
124+
125+
var (
126+
providers []*registry.ProviderInfo
127+
err error
128+
)
129+
c, err := pool.GetStorageRegistryClient(s.c.StorageRegistryEndpoint)
130+
if err != nil {
131+
return nil, errors.Wrap(err, "gateway: error getting storage registry client")
132+
}
133+
134+
if id != nil {
135+
// query that specific storage provider
136+
parts := strings.SplitN(id.OpaqueId, "!", 2)
137+
if len(parts) != 2 {
138+
return &provider.ListStorageSpacesResponse{
139+
Status: status.NewInvalidArg(ctx, "space id must be separated by !"),
140+
}, nil
141+
}
142+
res, err := c.GetStorageProviders(ctx, &registry.GetStorageProvidersRequest{
143+
Ref: &provider.Reference{ResourceId: &provider.ResourceId{
144+
StorageId: parts[0], // FIXME REFERENCE the StorageSpaceId is a storageid + an opaqueid
145+
OpaqueId: parts[1],
146+
}},
147+
})
148+
if err != nil {
149+
return &provider.ListStorageSpacesResponse{
150+
Status: status.NewStatusFromErrType(ctx, "ListStorageSpaces filters: req "+req.String(), err),
151+
}, nil
152+
}
153+
if res.Status.Code != rpc.Code_CODE_OK {
154+
return &provider.ListStorageSpacesResponse{
155+
Status: res.Status,
156+
}, nil
157+
}
158+
providers = res.Providers
159+
} else {
160+
// get list of all storage providers
161+
res, err := c.ListStorageProviders(ctx, &registry.ListStorageProvidersRequest{})
162+
163+
if err != nil {
164+
return &provider.ListStorageSpacesResponse{
165+
Status: status.NewStatusFromErrType(ctx, "error listing providers", err),
166+
}, nil
167+
}
168+
if res.Status.Code != rpc.Code_CODE_OK {
169+
return &provider.ListStorageSpacesResponse{
170+
Status: res.Status,
171+
}, nil
172+
}
173+
174+
providers = make([]*registry.ProviderInfo, 0, len(res.Providers))
175+
// FIXME filter only providers that have an id set ... currently none have?
176+
// bug? only ProviderPath is set
177+
for i := range res.Providers {
178+
// use only providers whose path does not start with a /?
179+
if strings.HasPrefix(res.Providers[i].ProviderPath, "/") {
180+
continue
181+
}
182+
providers = append(providers, res.Providers[i])
183+
}
184+
}
185+
186+
spacesFromProviders := make([][]*provider.StorageSpace, len(providers))
187+
errors := make([]error, len(providers))
188+
189+
var wg sync.WaitGroup
190+
for i, p := range providers {
191+
wg.Add(1)
192+
go s.listStorageSpacesOnProvider(ctx, req, &spacesFromProviders[i], p, &errors[i], &wg)
193+
}
194+
wg.Wait()
195+
196+
uniqueSpaces := map[string]*provider.StorageSpace{}
197+
for i := range providers {
198+
if errors[i] != nil {
199+
if len(providers) > 1 {
200+
log.Debug().Err(errors[i]).Msg("skipping provider")
201+
continue
202+
}
203+
return &provider.ListStorageSpacesResponse{
204+
Status: status.NewStatusFromErrType(ctx, "error listing space", errors[i]),
205+
}, nil
206+
}
207+
for j := range spacesFromProviders[i] {
208+
uniqueSpaces[spacesFromProviders[i][j].Id.OpaqueId] = spacesFromProviders[i][j]
209+
}
210+
}
211+
spaces := make([]*provider.StorageSpace, 0, len(uniqueSpaces))
212+
for spaceID := range uniqueSpaces {
213+
spaces = append(spaces, uniqueSpaces[spaceID])
214+
}
215+
if len(spaces) == 0 {
127216
return &provider.ListStorageSpacesResponse{
128-
Status: status.NewInvalidArg(ctx, "space id must be separated by !"),
217+
Status: status.NewNotFound(ctx, "space not found"),
129218
}, nil
130219
}
131-
c, err := s.find(ctx, &provider.Reference{ResourceId: &provider.ResourceId{
132-
StorageId: parts[0], // FIXME REFERENCE the StorageSpaceId is a storageid + a opaqueid
133-
OpaqueId: parts[1],
134-
}})
220+
221+
return &provider.ListStorageSpacesResponse{
222+
Status: status.NewOK(ctx),
223+
StorageSpaces: spaces,
224+
}, nil
225+
}
226+
227+
func (s *svc) listStorageSpacesOnProvider(ctx context.Context, req *provider.ListStorageSpacesRequest, res *[]*provider.StorageSpace, p *registry.ProviderInfo, e *error, wg *sync.WaitGroup) {
228+
defer wg.Done()
229+
c, err := s.getStorageProviderClient(ctx, p)
135230
if err != nil {
136-
return &provider.ListStorageSpacesResponse{
137-
Status: status.NewStatusFromErrType(ctx, "error finding path", err),
138-
}, nil
231+
*e = errors.Wrap(err, "error connecting to storage provider="+p.Address)
232+
return
139233
}
140234

141-
res, err := c.ListStorageSpaces(ctx, req)
235+
r, err := c.ListStorageSpaces(ctx, req)
142236
if err != nil {
143-
log.Err(err).Msg("gateway: error listing storage space on storage provider")
144-
return &provider.ListStorageSpacesResponse{
145-
Status: status.NewInternal(ctx, err, "error calling ListStorageSpaces"),
146-
}, nil
237+
*e = errors.Wrap(err, "gateway: error calling ListStorageSpaces")
238+
return
147239
}
148-
return res, nil
240+
241+
*res = r.StorageSpaces
149242
}
150243

151244
func (s *svc) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) {

internal/grpc/services/storageprovider/storageprovider.go

+40-1
Original file line numberDiff line numberDiff line change
@@ -428,9 +428,48 @@ func (s *service) CreateStorageSpace(ctx context.Context, req *provider.CreateSt
428428
}, nil
429429
}
430430

431+
func hasNodeID(s *provider.StorageSpace) bool {
432+
return s != nil && s.Root != nil && s.Root.OpaqueId != ""
433+
}
434+
431435
func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStorageSpacesRequest) (*provider.ListStorageSpacesResponse, error) {
436+
log := appctx.GetLogger(ctx)
437+
spaces, err := s.storage.ListStorageSpaces(ctx, req.Filters)
438+
if err != nil {
439+
var st *rpc.Status
440+
switch err.(type) {
441+
case errtypes.IsNotFound:
442+
st = status.NewNotFound(ctx, "not found when listing spaces")
443+
case errtypes.PermissionDenied:
444+
st = status.NewPermissionDenied(ctx, err, "permission denied")
445+
case errtypes.NotSupported:
446+
st = status.NewUnimplemented(ctx, err, "not implemented")
447+
default:
448+
st = status.NewInternal(ctx, err, "error listing spaces")
449+
}
450+
return &provider.ListStorageSpacesResponse{
451+
Status: st,
452+
}, nil
453+
}
454+
455+
for i := range spaces {
456+
if hasNodeID(spaces[i]) {
457+
// fill in storagespace id if it is not set
458+
if spaces[i].Id == nil || spaces[i].Id.OpaqueId == "" {
459+
spaces[i].Id = &provider.StorageSpaceId{OpaqueId: s.mountID + "!" + spaces[i].Root.OpaqueId}
460+
}
461+
// fill in storage id if it is not set
462+
if spaces[i].Root.StorageId == "" {
463+
spaces[i].Root.StorageId = s.mountID
464+
}
465+
} else if spaces[i].Id == nil || spaces[i].Id.OpaqueId == "" {
466+
log.Warn().Str("service", "storageprovider").Str("driver", s.conf.Driver).Interface("space", spaces[i]).Msg("space is missing space id and root id")
467+
}
468+
}
469+
432470
return &provider.ListStorageSpacesResponse{
433-
Status: status.NewUnimplemented(ctx, errtypes.NotSupported("ListStorageSpaces not implemented"), "ListStorageSpaces not implemented"),
471+
Status: status.NewOK(ctx),
472+
StorageSpaces: spaces,
434473
}, nil
435474
}
436475

pkg/storage/fs/owncloud/owncloud.go

+4
Original file line numberDiff line numberDiff line change
@@ -2221,6 +2221,10 @@ func (fs *ocfs) RestoreRecycleItem(ctx context.Context, key string, restoreRef *
22212221
return fs.propagate(ctx, tgt)
22222222
}
22232223

2224+
func (fs *ocfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) {
2225+
return nil, errtypes.NotSupported("list storage spaces")
2226+
}
2227+
22242228
func (fs *ocfs) propagate(ctx context.Context, leafPath string) error {
22252229
var root string
22262230
if fs.c.EnableHome {

pkg/storage/fs/owncloudsql/owncloudsql.go

+5
Original file line numberDiff line numberDiff line change
@@ -2132,6 +2132,11 @@ func (fs *ocfs) HashFile(path string) (string, string, string, error) {
21322132
}
21332133
}
21342134

2135+
func (fs *ocfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) {
2136+
// TODO(corby): Implement
2137+
return nil, errtypes.NotSupported("list storage spaces")
2138+
}
2139+
21352140
func readChecksumIntoResourceChecksum(ctx context.Context, checksums, algo string, ri *provider.ResourceInfo) {
21362141
re := regexp.MustCompile(strings.ToUpper(algo) + `:(.*)`)
21372142
matches := re.FindStringSubmatch(checksums)

pkg/storage/fs/s3/s3.go

+4
Original file line numberDiff line numberDiff line change
@@ -661,3 +661,7 @@ func (fs *s3FS) ListRecycle(ctx context.Context) ([]*provider.RecycleItem, error
661661
func (fs *s3FS) RestoreRecycleItem(ctx context.Context, key string, restoreRef *provider.Reference) error {
662662
return errtypes.NotSupported("restore recycle")
663663
}
664+
665+
func (fs *s3FS) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) {
666+
return nil, errtypes.NotSupported("list storage spaces")
667+
}

pkg/storage/registry/static/static.go

+18-11
Original file line numberDiff line numberDiff line change
@@ -145,18 +145,25 @@ func (b *reg) FindProviders(ctx context.Context, ref *provider.Reference) ([]*re
145145

146146
// If the reference has a resource id set, use it to route
147147
if ref.ResourceId != nil {
148-
for prefix, rule := range b.c.Rules {
149-
addr := getProviderAddr(ctx, rule)
150-
r, err := regexp.Compile("^" + prefix + "$")
151-
if err != nil {
152-
continue
148+
if ref.ResourceId.StorageId != "" {
149+
for prefix, rule := range b.c.Rules {
150+
addr := getProviderAddr(ctx, rule)
151+
r, err := regexp.Compile("^" + prefix + "$")
152+
if err != nil {
153+
continue
154+
}
155+
// TODO(labkode): fill path info based on provider id, if path and storage id points to same id, take that.
156+
if m := r.FindString(ref.ResourceId.StorageId); m != "" {
157+
return []*registrypb.ProviderInfo{{
158+
ProviderId: ref.ResourceId.StorageId,
159+
Address: addr,
160+
}}, nil
161+
}
153162
}
154-
// TODO(labkode): fill path info based on provider id, if path and storage id points to same id, take that.
155-
if m := r.FindString(ref.ResourceId.StorageId); m != "" {
156-
return []*registrypb.ProviderInfo{{
157-
ProviderId: ref.ResourceId.StorageId,
158-
Address: addr,
159-
}}, nil
163+
// TODO if the storage id is not set but node id is set we could poll all storage providers to check if the node is known there
164+
// for now, say the reference is invalid
165+
if ref.ResourceId.OpaqueId != "" {
166+
return nil, errtypes.BadRequest("invalid reference " + ref.String())
160167
}
161168
}
162169
}

pkg/storage/storage.go

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type FS interface {
5656
Shutdown(ctx context.Context) error
5757
SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error
5858
UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error
59+
ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error)
5960
}
6061

6162
// Registry is the interface that storage registries implement

0 commit comments

Comments
 (0)