Skip to content
Draft
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
137 changes: 85 additions & 52 deletions internal/grpc/services/storageprovider/storageprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,11 @@ func (s *Service) SetLock(ctx context.Context, req *provider.SetLockRequest) (*p
Status: status.NewPermissionDenied(ctx, nil, "no permission to lock the share"),
}, nil
}
res, err := s.Storage.SetLock(ctx, req.Ref, req.Lock)
if err != nil {
return &provider.SetLockResponse{
Status: status.NewStatusFromErrType(ctx, "set lock", err),
}, nil
// non-decomposedfs drivers may return nil result; set SpaceOwner only when present
lockResult, err := s.Storage.SetLock(ctx, req.Ref, req.Lock)
if lockResult != nil {
storagespace.ContextSetSpaceOwner(ctx, lockResult.SpaceOwner)
}
storagespace.ContextSetSpaceOwner(ctx, res.SpaceOwner)

return &provider.SetLockResponse{
Status: status.NewStatusFromErrType(ctx, "set lock", err),
Expand Down Expand Up @@ -285,13 +283,11 @@ func (s *Service) Unlock(ctx context.Context, req *provider.UnlockRequest) (*pro
}, nil
}

res, err := s.Storage.Unlock(ctx, req.Ref, req.Lock)
if err != nil {
return &provider.UnlockResponse{
Status: status.NewStatusFromErrType(ctx, "unlock", err),
}, nil
// non-decomposedfs drivers may return nil result; set SpaceOwner only when present
unlockResult, err := s.Storage.Unlock(ctx, req.Ref, req.Lock)
if unlockResult != nil {
storagespace.ContextSetSpaceOwner(ctx, unlockResult.SpaceOwner)
}
storagespace.ContextSetSpaceOwner(ctx, res.SpaceOwner)

return &provider.UnlockResponse{
Status: status.NewStatusFromErrType(ctx, "unlock", err),
Expand Down Expand Up @@ -625,7 +621,34 @@ func (s *Service) UpdateStorageSpace(ctx context.Context, req *provider.UpdateSt
}

func (s *Service) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) (*provider.DeleteStorageSpaceResponse, error) {
result, err := s.Storage.DeleteStorageSpace(ctx, req)
// pre-fetch spacename+grants before deletion: non-decomposedfs drivers don't populate DeleteStorageSpaceResult
idraw, _ := storagespace.ParseID(req.Id.GetOpaqueId())
idraw.OpaqueId = idraw.GetSpaceId()
id := &provider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&idraw)}

spaces, err := s.Storage.ListStorageSpaces(ctx, []*provider.ListStorageSpacesRequest_Filter{{Type: provider.ListStorageSpacesRequest_Filter_TYPE_ID, Term: &provider.ListStorageSpacesRequest_Filter_Id{Id: id}}}, true)
if err != nil {
var st *rpc.Status
switch err.(type) {
case errtypes.IsNotFound:
st = status.NewNotFound(ctx, "space not found")
case errtypes.PermissionDenied:
st = status.NewPermissionDenied(ctx, err, "permission denied")
case errtypes.BadRequest:
st = status.NewInvalid(ctx, err.Error())
default:
st = status.NewInternal(ctx, "error deleting space: "+req.Id.String())
}
return &provider.DeleteStorageSpaceResponse{
Status: st,
}, nil
} else if len(spaces) != 1 {
return &provider.DeleteStorageSpaceResponse{
Status: status.NewNotFound(ctx, "space not found"),
}, nil
}

deleteSpaceResult, err := s.Storage.DeleteStorageSpace(ctx, req)
if err != nil {
var st *rpc.Status
switch err.(type) {
Expand All @@ -648,14 +671,13 @@ func (s *Service) DeleteStorageSpace(ctx context.Context, req *provider.DeleteSt
Status: st,
}, nil
}

if result != nil {
storagespace.ContextSetDeleteStorageSpaceResult(ctx, result)
if deleteSpaceResult == nil {
// driver didn't populate the result; fill SpaceName from the pre-fetched space so SpaceDeleted event is not empty
deleteSpaceResult = &storage.DeleteStorageSpaceResult{SpaceName: spaces[0].GetName()}
}
storagespace.ContextSetDeleteStorageSpaceResult(ctx, deleteSpaceResult)

return &provider.DeleteStorageSpaceResponse{
Status: status.NewOK(ctx),
}, nil
return &provider.DeleteStorageSpaceResponse{Status: status.NewOK(ctx)}, nil
}

func (s *Service) CreateContainer(ctx context.Context, req *provider.CreateContainerRequest) (*provider.CreateContainerResponse, error) {
Expand All @@ -666,13 +688,10 @@ func (s *Service) CreateContainer(ctx context.Context, req *provider.CreateConta
}
}

res, err := s.Storage.CreateDir(ctx, req.Ref)
if err != nil {
return &provider.CreateContainerResponse{
Status: status.NewStatusFromErrType(ctx, "create container", err),
}, nil
createDirResult, err := s.Storage.CreateDir(ctx, req.Ref)
if createDirResult != nil {
storagespace.ContextSetSpaceOwner(ctx, createDirResult.SpaceOwner)
}
storagespace.ContextSetSpaceOwner(ctx, res.SpaceOwner)

return &provider.CreateContainerResponse{
Status: status.NewStatusFromErrType(ctx, "create container", err),
Expand All @@ -689,13 +708,10 @@ func (s *Service) TouchFile(ctx context.Context, req *provider.TouchFileRequest)
mtime = utils.ReadPlainFromOpaque(req.Opaque, "X-OC-Mtime")
}

res, err := s.Storage.TouchFile(ctx, req.Ref, utils.ExistsInOpaque(req.Opaque, "markprocessing"), mtime)
if err != nil {
return &provider.TouchFileResponse{
Status: status.NewStatusFromErrType(ctx, "touch file", err),
}, nil
touchResult, err := s.Storage.TouchFile(ctx, req.Ref, utils.ExistsInOpaque(req.Opaque, "markprocessing"), mtime)
if touchResult != nil {
storagespace.ContextSetSpaceOwner(ctx, touchResult.SpaceOwner)
}
storagespace.ContextSetSpaceOwner(ctx, res.SpaceOwner)

return &provider.TouchFileResponse{
Status: status.NewStatusFromErrType(ctx, "touch file", err),
Expand All @@ -720,24 +736,46 @@ func (s *Service) Delete(ctx context.Context, req *provider.DeleteRequest) (*pro
}
}

result, err := s.Storage.Delete(ctx, req.Ref)
md, err := s.Storage.GetMD(ctx, req.Ref, []string{}, []string{"id", "status"})
if err != nil {
return &provider.DeleteResponse{
Status: status.NewStatusFromErrType(ctx, "can't stat resource to delete", err),
}, nil
}

if err == nil && result != nil {
storagespace.ContextSetDeleteResult(ctx, result)
if utils.ReadPlainFromOpaque(md.GetOpaque(), "status") == "processing" {
return &provider.DeleteResponse{
Status: &rpc.Status{
Code: rpc.Code_CODE_TOO_EARLY,
Message: "file is processing",
},
Opaque: &typesv1beta1.Opaque{
Map: map[string]*typesv1beta1.OpaqueEntry{
"status": {Decoder: "plain", Value: []byte("processing")},
},
},
}, nil
}

deleteResult, err := s.Storage.Delete(ctx, req.Ref)
storagespace.ContextSetDeleteResult(ctx, deleteResult)

return &provider.DeleteResponse{
Status: status.NewStatusFromErrType(ctx, "delete", err),
Opaque: &typesv1beta1.Opaque{
Map: map[string]*typesv1beta1.OpaqueEntry{
"opaque_id": {Decoder: "plain", Value: []byte(md.Id.OpaqueId)},
},
},
}, nil
}

func (s *Service) Move(ctx context.Context, req *provider.MoveRequest) (*provider.MoveResponse, error) {
ctx = ctxpkg.ContextSetLockID(ctx, req.LockId)

result, err := s.Storage.Move(ctx, req.Source, req.Destination)
if err == nil && result != nil {
storagespace.ContextSetMoveResult(ctx, result)
}
moveResult, err := s.Storage.Move(ctx, req.Source, req.Destination)
storagespace.ContextSetMoveResult(ctx, moveResult)

return &provider.MoveResponse{
Status: status.NewStatusFromErrType(ctx, "move", err),
}, nil
Expand Down Expand Up @@ -849,13 +887,10 @@ func (s *Service) ListFileVersions(ctx context.Context, req *provider.ListFileVe
func (s *Service) RestoreFileVersion(ctx context.Context, req *provider.RestoreFileVersionRequest) (*provider.RestoreFileVersionResponse, error) {
ctx = ctxpkg.ContextSetLockID(ctx, req.LockId)

res, err := s.Storage.RestoreRevision(ctx, req.Ref, req.Key)
if err != nil {
return &provider.RestoreFileVersionResponse{
Status: status.NewStatusFromErrType(ctx, "restore file version", err),
}, nil
restoreRevResult, err := s.Storage.RestoreRevision(ctx, req.Ref, req.Key)
if restoreRevResult != nil {
storagespace.ContextSetSpaceOwner(ctx, restoreRevResult.SpaceOwner)
}
storagespace.ContextSetSpaceOwner(ctx, res.SpaceOwner)

return &provider.RestoreFileVersionResponse{
Status: status.NewStatusFromErrType(ctx, "restore file version", err),
Expand Down Expand Up @@ -954,17 +989,15 @@ func (s *Service) RestoreRecycleItem(ctx context.Context, req *provider.RestoreR

// TODO(labkode): CRITICAL: fill recycle info with storage provider.
key, relativePath := splitKeyAndPath(req.GetKey())
writeRes, err := s.Storage.RestoreRecycleItem(ctx, req.Ref, key, relativePath, req.RestoreRef)
if err != nil {
return &provider.RestoreRecycleItemResponse{
Status: status.NewStatusFromErrType(ctx, "restore recycle item", err),
}, nil
restoreItemResult, err := s.Storage.RestoreRecycleItem(ctx, req.Ref, key, relativePath, req.RestoreRef)
if restoreItemResult != nil {
storagespace.ContextSetSpaceOwner(ctx, restoreItemResult.SpaceOwner)
}
storagespace.ContextSetSpaceOwner(ctx, writeRes.SpaceOwner)

return &provider.RestoreRecycleItemResponse{
res := &provider.RestoreRecycleItemResponse{
Status: status.NewStatusFromErrType(ctx, "restore recycle item", err),
}, nil
}
return res, nil
}

func (s *Service) PurgeRecycle(ctx context.Context, req *provider.PurgeRecycleRequest) (*provider.PurgeRecycleResponse, error) {
Expand Down
130 changes: 130 additions & 0 deletions internal/grpc/services/storageprovider/storageprovider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package storageprovider

import (
"context"
"net/url"

rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/owncloud/reva/v2/pkg/storage"
"github.com/owncloud/reva/v2/pkg/storagespace"
)

// stubFS overrides only the methods under test; the embedded storage.FS satisfies the rest.
type stubFS struct {
storage.FS
listSpaces func(ctx context.Context, filters []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error)
deleteSpace func(ctx context.Context, req *provider.DeleteStorageSpaceRequest) (*storage.DeleteStorageSpaceResult, error)
}

func (s *stubFS) ListStorageSpaces(ctx context.Context, filters []*provider.ListStorageSpacesRequest_Filter, unrestricted bool) ([]*provider.StorageSpace, error) {
return s.listSpaces(ctx, filters, unrestricted)
}

func (s *stubFS) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) (*storage.DeleteStorageSpaceResult, error) {
if s.deleteSpace != nil {
return s.deleteSpace(ctx, req)
}
return nil, nil
}

func (s *stubFS) Shutdown(_ context.Context) error { return nil }

// CreateReference is called by the storageprovider on some paths; stub to avoid nil-embed panic.
func (s *stubFS) CreateReference(_ context.Context, _ string, _ *url.URL) error { return nil }

var _ = Describe("DeleteStorageSpace", func() {
var (
svc *Service
ctx context.Context
)

space := &provider.StorageSpace{
Id: &provider.StorageSpaceId{OpaqueId: "providerid$spaceid!spaceid"},
Name: "My Space",
Root: &provider.ResourceId{StorageId: "providerid", SpaceId: "spaceid", OpaqueId: "spaceid"},
}

req := &provider.DeleteStorageSpaceRequest{
Id: &provider.StorageSpaceId{OpaqueId: "providerid$spaceid!spaceid"},
}

listFound := func(_ context.Context, _ []*provider.ListStorageSpacesRequest_Filter, _ bool) ([]*provider.StorageSpace, error) {
return []*provider.StorageSpace{space}, nil
}

BeforeEach(func() {
ctx = storagespace.ContextRegisterDeleteStorageSpaceResultSlot(context.Background())
})

Context("when driver returns nil result (non-decomposedfs path)", func() {
BeforeEach(func() {
svc = &Service{Storage: &stubFS{listSpaces: listFound}}
})

It("populates SpaceName from pre-fetched space so SpaceDeleted event is not empty", func() {
res, err := svc.DeleteStorageSpace(ctx, req)

Expect(err).ToNot(HaveOccurred())
Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK))

result := storagespace.ContextGetDeleteStorageSpaceResult(ctx)
Expect(result).ToNot(BeNil())
Expect(result.SpaceName).To(Equal("My Space"))
Expect(result.FinalMembers).To(BeNil())
})

It("does not carry spacename/grants in response opaque — consumers read from context slot", func() {
res, err := svc.DeleteStorageSpace(ctx, req)

Expect(err).ToNot(HaveOccurred())
Expect(res.Opaque).To(BeNil())
})
})

Context("when driver returns a populated result (decomposedfs path)", func() {
finalMembers := map[string]provider.ResourcePermissions{
"user-1": {Stat: true},
}

BeforeEach(func() {
svc = &Service{Storage: &stubFS{
listSpaces: listFound,
deleteSpace: func(_ context.Context, _ *provider.DeleteStorageSpaceRequest) (*storage.DeleteStorageSpaceResult, error) {
return &storage.DeleteStorageSpaceResult{SpaceName: "Decomposed Space", FinalMembers: finalMembers}, nil
},
}}
})

It("uses driver result as-is, preserving FinalMembers", func() {
res, err := svc.DeleteStorageSpace(ctx, req)

Expect(err).ToNot(HaveOccurred())
Expect(res.Status.Code).To(Equal(rpc.Code_CODE_OK))

result := storagespace.ContextGetDeleteStorageSpaceResult(ctx)
Expect(result).ToNot(BeNil())
Expect(result.SpaceName).To(Equal("Decomposed Space"))
Expect(result.FinalMembers).To(HaveKey("user-1"))
})
})

Context("when space is not found", func() {
BeforeEach(func() {
svc = &Service{Storage: &stubFS{
listSpaces: func(_ context.Context, _ []*provider.ListStorageSpacesRequest_Filter, _ bool) ([]*provider.StorageSpace, error) {
return []*provider.StorageSpace{}, nil
},
}}
})

It("returns NOT_FOUND without calling DeleteStorageSpace", func() {
res, err := svc.DeleteStorageSpace(ctx, req)

Expect(err).ToNot(HaveOccurred())
Expect(res.Status.Code).To(Equal(rpc.Code_CODE_NOT_FOUND))
})
})
})
13 changes: 13 additions & 0 deletions internal/grpc/services/storageprovider/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package storageprovider

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestStorageprovider(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Storageprovider Suite")
}
Loading