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

Pseudo-transactionalize sharing #5029

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions changelog/unreleased/pseudo-tx-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Enhancement: pseudo-transactionalize sharing

Currently, sharing is not transactionalized: setting ACLs and writing the share to the db is completely independent. In the current situation, shares are written to the db before setting the ACL, letting users falsely believe that they successfully shared a resource, even if setting the ACL afterwards fails. his enhancement improves the situation by doing the least reliable (setting ACLs on EOS) first:
a) first pinging the db
b) writing the ACLs
c) writing to the db

https://github.com/cs3org/reva/pull/5029
71 changes: 46 additions & 25 deletions internal/grpc/services/gateway/usershareprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"context"
"fmt"
"path"
"strings"

userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
Expand All @@ -37,34 +38,42 @@ import (
"github.com/pkg/errors"
)

// TODO(labkode): add multi-phase commit logic when commit share or commit ref is enabled.
func (s *svc) CreateShare(ctx context.Context, req *collaboration.CreateShareRequest) (*collaboration.CreateShareResponse, error) {
if s.isSharedFolder(ctx, req.ResourceInfo.GetPath()) {
return nil, errtypes.AlreadyExists("gateway: can't share the share folder itself")
}

c, err := pool.GetUserShareProviderClient(pool.Endpoint(s.c.UserShareProviderEndpoint))
log := appctx.GetLogger(ctx)

shareClient, err := pool.GetUserShareProviderClient(pool.Endpoint(s.c.UserShareProviderEndpoint))
if err != nil {
return &collaboration.CreateShareResponse{
Status: status.NewInternal(ctx, err, "error getting user share provider client"),
}, nil
}

// TODO the user share manager needs to be able to decide if the current user is allowed to create that share (and not eg. incerase permissions)
// jfd: AFAICT this can only be determined by a storage driver - either the storage provider is queried first or the share manager needs to access the storage using a storage driver
res, err := c.CreateShare(ctx, req)
if err != nil {
return nil, errors.Wrap(err, "gateway: error calling CreateShare")
}
if res.Status.Code != rpc.Code_CODE_OK {
return res, nil
}
// First we ping the db
// --------------------
// See ADR-REVA-003
_, err = shareClient.GetShare(ctx, &collaboration.GetShareRequest{
Ref: &collaboration.ShareReference{
Spec: &collaboration.ShareReference_Id{
Id: &collaboration.ShareId{
OpaqueId: "0",
},
},
},
})

// if we don't need to commit we return earlier
if !s.c.CommitShareToStorageGrant && !s.c.CommitShareToStorageRef {
return res, nil
// We expect a "not found" error when querying ID 0
// error checking is kind of ugly, because we lose the original error object over grpc
if !strings.HasSuffix(err.Error(), errtypes.NotFound("0").Error()) {
return nil, errtypes.InternalError("ShareManager is not online")
}

// Then we set ACLs on the storage layer
// -------------------------------------

// TODO(labkode): if both commits are enabled they could be done concurrently.
if s.c.CommitShareToStorageGrant {
// If the share is a denial we call denyGrant instead.
Expand All @@ -76,20 +85,32 @@ func (s *svc) CreateShare(ctx context.Context, req *collaboration.CreateShareReq
if denyGrantStatus.Code != rpc.Code_CODE_OK {
return &collaboration.CreateShareResponse{
Status: denyGrantStatus,
}, err
}, nil
}
} else {
addGrantStatus, err := s.addGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, req.Grant.Permissions.Permissions)
if err != nil {
log.Error().Err(err).Msg("Failed to Create Share: error during addGrant")
return nil, errors.Wrap(err, "gateway: error adding grant to storage")
}
if addGrantStatus.Code != rpc.Code_CODE_OK {
Copy link
Member

@glpatcern glpatcern Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So following the discussion today, we could log here the addGrantStatus.Message before returning (or return it as part of the response, and then make sure it is logged), hoping to learn why the storage fails at times to create the share.

return &collaboration.CreateShareResponse{
Status: addGrantStatus,
}, nil
}
return res, nil
}
}

addGrantStatus, err := s.addGrant(ctx, req.ResourceInfo.Id, req.Grant.Grantee, req.Grant.Permissions.Permissions)
if err != nil {
return nil, errors.Wrap(err, "gateway: error adding grant to storage")
}
if addGrantStatus.Code != rpc.Code_CODE_OK {
return &collaboration.CreateShareResponse{
Status: addGrantStatus,
}, err
}
// Then we commit to the db
// ------------------------
res, err := shareClient.CreateShare(ctx, req)

if err != nil {
log.Error().Msg("Failed to Create Share but ACLs are already set")
return nil, errors.Wrap(err, "gateway: error calling CreateShare")
}
if res.Status.Code != rpc.Code_CODE_OK {
return nil, errors.New("ShareClient returned error: " + res.Status.Code.String() + ": " + res.Status.Message)
}

return res, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func (s *service) GetShare(ctx context.Context, req *collaboration.GetShareReque
if err != nil {
return &collaboration.GetShareResponse{
Status: status.NewInternal(ctx, err, "error getting share"),
}, nil
}, err
jessegeens marked this conversation as resolved.
Show resolved Hide resolved
}

return &collaboration.GetShareResponse{
Expand Down
Loading