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

decomposedfs: shard nodes per space #2554

Merged
merged 7 commits into from
Mar 1, 2022
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
8 changes: 4 additions & 4 deletions .drone.star
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ def virtualViews():
"PATH_TO_CORE": "/drone/src/tmp/testrunner",
"TEST_SERVER_URL": "http://revad-services:20180",
"OCIS_REVA_DATA_ROOT": "/drone/src/tmp/reva/data/",
"DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/nodes/root/* /drone/src/tmp/reva/data/nodes/*-*-*-* /drone/src/tmp/reva/data/blobs/*",
"DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/spaces/* /drone/src/tmp/reva/data/blobs/* /drone/src/tmp/reva/data/spacetypes/*",
"STORAGE_DRIVER": "OCIS",
"SKELETON_DIR": "/drone/src/tmp/testing/data/apiSkeleton",
"TEST_REVA": "true",
Expand Down Expand Up @@ -751,7 +751,7 @@ def litmusOcisSpacesDav():
"commands": [
# The spaceid is randomly generated during the first login so we need this hack to construct the correct url.
"curl -s -k -u einstein:relativity -I http://revad-services:20080/remote.php/dav/files/einstein",
"export LITMUS_URL=http://revad-services:20080/remote.php/dav/spaces/$(ls /drone/src/tmp/reva/data/spaces/personal/)",
"export LITMUS_URL=http://revad-services:20080/remote.php/dav/spaces/$(ls /drone/src/tmp/reva/data/spacetypes/personal/)",
"/usr/local/bin/litmus-wrapper",
],
},
Expand Down Expand Up @@ -813,7 +813,7 @@ def ocisIntegrationTests(parallelRuns, skipExceptParts = []):
"environment": {
"TEST_SERVER_URL": "http://revad-services:20080",
"OCIS_REVA_DATA_ROOT": "/drone/src/tmp/reva/data/",
"DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/nodes/root/* /drone/src/tmp/reva/data/nodes/*-*-*-* /drone/src/tmp/reva/data/blobs/* /drone/src/tmp/reva/data/spaces/*/*",
"DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/spaces/* /drone/src/tmp/reva/data/blobs/* /drone/src/tmp/reva/data/spacetypes/*",
"STORAGE_DRIVER": "OCIS",
"SKELETON_DIR": "/drone/src/tmp/testing/data/apiSkeleton",
"TEST_WITH_LDAP": "true",
Expand Down Expand Up @@ -890,7 +890,7 @@ def s3ngIntegrationTests(parallelRuns, skipExceptParts = []):
"environment": {
"TEST_SERVER_URL": "http://revad-services:20080",
"OCIS_REVA_DATA_ROOT": "/drone/src/tmp/reva/data/",
"DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/nodes/root/* /drone/src/tmp/reva/data/nodes/*-*-*-* /drone/src/tmp/reva/data/blobs/* /drone/src/tmp/reva/data/spaces/*/*",
"DELETE_USER_DATA_CMD": "rm -rf /drone/src/tmp/reva/data/spaces/* /drone/src/tmp/reva/data/blobs/* /drone/src/tmp/reva/data/spacetypes/*",
"STORAGE_DRIVER": "S3NG",
"SKELETON_DIR": "/drone/src/tmp/testing/data/apiSkeleton",
"TEST_WITH_LDAP": "true",
Expand Down
5 changes: 5 additions & 0 deletions changelog/unreleased/decomposedfs-shard-nodes-per-space.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Change: shard nodes per space in decomposedfs

The decomposedfs changas the on disk layout to shard nodes per space.

https://github.com/cs3org/reva/pull/2554
18 changes: 14 additions & 4 deletions pkg/permission/manager/demo/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,18 @@ func New(c map[string]interface{}) (permission.Manager, error) {
type manager struct {
}

func (m manager) CheckPermission(permission string, subject string, ref *provider.Reference) bool {
// We can currently return true all the time.
// Once we beginn testing roles we need to somehow check the roles of the users here
return false
func (m manager) CheckPermission(perm string, subject string, ref *provider.Reference) bool {
switch perm {
case permission.CreateSpace:
// TODO Users can only create their own personal space
// TODO guest accounts cannot create spaces
return true
case permission.ListAllSpaces:
// TODO introduce an admin role to allow listing all spaces
return false
default:
// We can currently return false all the time.
// Once we beginn testing roles we need to somehow check the roles of the users here
return false
}
}
7 changes: 7 additions & 0 deletions pkg/permission/permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ import (
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
)

const (
// ListAllSpaces is the hardcoded name for the list all spaces permission
ListAllSpaces string = "list-all-spaces"
// CreateSpace is the hardcoded name for the create space permission
CreateSpace string = "create-space"
)

// Manager defines the interface for the permission service driver
type Manager interface {
CheckPermission(permission string, subject string, ref *provider.Reference) bool
Expand Down
4 changes: 3 additions & 1 deletion pkg/storage/fs/nextcloud/nextcloud_server_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ var responses = map[string]Response{
`POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateHome `: {200, ``, serverStateHome},
`POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateHome {}`: {200, ``, serverStateHome},
`POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateStorageSpace {"owner":{"id":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"username":"einstein"},"type":"personal","name":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"}`: {200, `{"status":{"code":1}}`, serverStateHome},
`POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateStorageSpace {"owner":{"id":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1},"username":"einstein"},"type":"personal"}`: {200, `{"status":{"code":1}}`, serverStateHome},

`POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/CreateReference {"path":"/Shares/reference","url":"scheme://target"}`: {200, `[]`, serverStateReference},

Expand Down Expand Up @@ -103,7 +104,8 @@ var responses = map[string]Response{

`POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/GetPathByID {"storage_id":"00000000-0000-0000-0000-000000000000","opaque_id":"fileid-/some/path"} EMPTY`: {200, "/subdir", serverStateEmpty},

`POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/InitiateUpload {"ref":{"path":"/file"},"uploadLength":0,"metadata":{}}`: {200, `{"simple": "yes","tus": "yes"}`, serverStateEmpty},
`POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/InitiateUpload {"ref":{"path":"/file"},"uploadLength":0,"metadata":{}}`: {200, `{"simple": "yes","tus": "yes"}`, serverStateEmpty},
`POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/InitiateUpload {"ref":{"resource_id":{"storage_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"},"path":"/versionedFile"},"uploadLength":0,"metadata":{}}`: {200, `{"simple": "yes","tus": "yes"}`, serverStateEmpty},

`POST /apps/sciencemesh/~f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c/api/storage/ListFolder {"ref":{"path":"/"},"mdKeys":null}`: {200, `[{"opaque":{},"type":2,"id":{"opaque_id":"fileid-/subdir"},"checksum":{},"etag":"deadbeef","mime_type":"text/plain","mtime":{"seconds":1234567890},"path":"/subdir","permission_set":{},"size":12345,"canonical_metadata":{},"owner":{"opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c"},"arbitrary_metadata":{"metadata":{"da":"ta","some":"arbi","trary":"meta"}}}]`, serverStateEmpty},

Expand Down
125 changes: 45 additions & 80 deletions pkg/storage/utils/decomposedfs/decomposedfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package decomposedfs

// go:generate mockery -name PermissionsChecker
// go:generate mockery -name CS3PermissionsClient
// go:generate mockery -name Tree

import (
Expand All @@ -33,15 +34,17 @@ import (
"strings"
"syscall"

userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
cs3permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/logger"
"github.com/cs3org/reva/pkg/sharedconf"
"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/pkg/storage"
"github.com/cs3org/reva/pkg/storage/utils/chunking"
"github.com/cs3org/reva/pkg/storage/utils/decomposedfs/lookup"
"github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node"
"github.com/cs3org/reva/pkg/storage/utils/decomposedfs/options"
"github.com/cs3org/reva/pkg/storage/utils/decomposedfs/tree"
Expand All @@ -51,6 +54,7 @@ import (
"github.com/cs3org/reva/pkg/utils"
"github.com/pkg/errors"
"go.opentelemetry.io/otel/codes"
"google.golang.org/grpc"
)

// PermissionsChecker defines an interface for checking permissions on a Node
Expand All @@ -59,9 +63,14 @@ type PermissionsChecker interface {
HasPermission(ctx context.Context, n *node.Node, check func(*provider.ResourcePermissions) bool) (can bool, err error)
}

// CS3PermissionsClient defines an interface for checking permissions against the CS3 permissions service
type CS3PermissionsClient interface {
CheckPermission(ctx context.Context, in *cs3permissions.CheckPermissionRequest, opts ...grpc.CallOption) (*cs3permissions.CheckPermissionResponse, error)
}

// Tree is used to manage a tree hierarchy
type Tree interface {
Setup(owner *userpb.UserId, propagateToRoot bool) error
Setup() error

GetMD(ctx context.Context, node *node.Node) (os.FileInfo, error)
ListFolder(ctx context.Context, node *node.Node) ([]*node.Node, error)
Expand All @@ -82,11 +91,12 @@ type Tree interface {

// Decomposedfs provides the base for decomposed filesystem implementations
type Decomposedfs struct {
lu *Lookup
tp Tree
o *options.Options
p PermissionsChecker
chunkHandler *chunking.ChunkHandler
lu *lookup.Lookup
tp Tree
o *options.Options
p PermissionsChecker
chunkHandler *chunking.ChunkHandler
permissionsClient CS3PermissionsClient
}

// NewDefault returns an instance with default components
Expand All @@ -96,42 +106,38 @@ func NewDefault(m map[string]interface{}, bs tree.Blobstore) (storage.FS, error)
return nil, err
}

lu := &Lookup{}
lu := &lookup.Lookup{}
p := node.NewPermissions(lu)

lu.Options = o

tp := tree.New(o.Root, o.TreeTimeAccounting, o.TreeSizeAccounting, lu, bs)

o.GatewayAddr = sharedconf.GetGatewaySVC(o.GatewayAddr)
return New(o, lu, p, tp)
}
permissionsClient, err := pool.GetPermissionsClient(o.PermissionsSVC)
if err != nil {
return nil, err
}

// when enable home is false we want propagation to root if tree size or mtime accounting is enabled
func enablePropagationForRoot(o *options.Options) bool {
return (o.TreeSizeAccounting || o.TreeTimeAccounting)
return New(o, lu, p, tp, permissionsClient)
}

// New returns an implementation of the storage.FS interface that talks to
// a local filesystem.
func New(o *options.Options, lu *Lookup, p PermissionsChecker, tp Tree) (storage.FS, error) {
err := tp.Setup(&userpb.UserId{
OpaqueId: o.Owner,
Idp: o.OwnerIDP,
Type: userpb.UserType(userpb.UserType_value[o.OwnerType]),
}, enablePropagationForRoot(o))
func New(o *options.Options, lu *lookup.Lookup, p PermissionsChecker, tp Tree, permissionsClient CS3PermissionsClient) (storage.FS, error) {
err := tp.Setup()
if err != nil {
logger.New().Error().Err(err).
Msg("could not setup tree")
return nil, errors.Wrap(err, "could not setup tree")
}

return &Decomposedfs{
tp: tp,
lu: lu,
o: o,
p: p,
chunkHandler: chunking.NewChunkHandler(filepath.Join(o.Root, "uploads")),
tp: tp,
lu: lu,
o: o,
p: p,
chunkHandler: chunking.NewChunkHandler(filepath.Join(o.Root, "uploads")),
permissionsClient: permissionsClient,
}, nil
}

Expand All @@ -144,14 +150,12 @@ func (fs *Decomposedfs) Shutdown(ctx context.Context) error {
// TODO Document in the cs3 should we return quota or free space?
func (fs *Decomposedfs) GetQuota(ctx context.Context, ref *provider.Reference) (total uint64, inUse uint64, err error) {
var n *node.Node
if ref != nil {
if n, err = fs.lu.NodeFromResource(ctx, ref); err != nil {
return 0, 0, err
}
} else {
if n, err = fs.lu.RootNode(ctx); err != nil {
return 0, 0, err
}
if ref == nil {
err = errtypes.BadRequest("no space given")
return 0, 0, err
}
if n, err = fs.lu.NodeFromResource(ctx, ref); err != nil {
return 0, 0, err
}

if !n.Exists {
Expand Down Expand Up @@ -204,57 +208,18 @@ func (fs *Decomposedfs) CreateHome(ctx context.Context) (err error) {
return errtypes.NotSupported("Decomposedfs: CreateHome() home supported disabled")
}

var n, h *node.Node
if n, err = fs.lu.RootNode(ctx); err != nil {
return
}
h, err = fs.lu.WalkPath(ctx, n, fs.lu.mustGetUserLayout(ctx), false, func(ctx context.Context, n *node.Node) error {
if !n.Exists {
if err := fs.tp.CreateDir(ctx, n); err != nil {
return err
}
}
return nil
u := ctxpkg.ContextMustGetUser(ctx)
res, err := fs.CreateStorageSpace(ctx, &provider.CreateStorageSpaceRequest{
Type: spaceTypePersonal,
Owner: u,
})

// make sure to delete the created directory if things go wrong
defer func() {
if err != nil {
// do not catch the error to not shadow the original error
if tmpErr := fs.tp.Delete(ctx, n); tmpErr != nil {
appctx.GetLogger(ctx).Error().Err(tmpErr).Msg("Can not revert file system change after error")
}
}
}()

if err != nil {
return
}

// update the owner
u := ctxpkg.ContextMustGetUser(ctx)
if err = h.WriteAllNodeMetadata(u.Id); err != nil {
return
}

if fs.o.TreeTimeAccounting || fs.o.TreeSizeAccounting {
// mark the home node as the end of propagation
if err = h.SetMetadata(xattrs.PropagationAttr, "1"); err != nil {
appctx.GetLogger(ctx).Error().Err(err).Interface("node", h).Msg("could not mark home as propagation root")
return
}
}

if err := h.SetMetadata(xattrs.SpaceNameAttr, u.DisplayName); err != nil {
return err
}

// add storage space
if err := fs.createStorageSpace(ctx, spaceTypePersonal, h.ID); err != nil {
return err
if res.Status.Code != rpcv1beta1.Code_CODE_OK {
return errtypes.NewErrtypeFromStatus(res.Status)
}

return
return nil
}

// The os not exists error is buried inside the xattr error,
Expand Down
15 changes: 5 additions & 10 deletions pkg/storage/utils/decomposedfs/grants.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,12 @@ func (fs *Decomposedfs) AddGrant(ctx context.Context, ref *provider.Reference, g
return err
}

owner, err := node.Owner()
if err != nil {
return err
}

owner := node.Owner()
// If the owner is empty and there are no grantees then we are dealing with a just created project space.
// In this case we don't need to check for permissions and just add the grant since this will be the project
// manager.
// When the owner is empty but grants are set then we do want to check the grants.
if !(len(grantees) == 0 && owner.OpaqueId == "") {
if !(len(grantees) == 0 && (owner == nil || owner.OpaqueId == "")) {
ok, err := fs.p.HasPermission(ctx, node, func(rp *provider.ResourcePermissions) bool {
// TODO remove AddGrant or UpdateGrant grant from CS3 api, redundant? tracked in https://github.com/cs3org/cs3apis/issues/92
return rp.AddGrant || rp.UpdateGrant
Expand All @@ -91,7 +87,7 @@ func (fs *Decomposedfs) AddGrant(ctx context.Context, ref *provider.Reference, g

// when a grant is added to a space, do not add a new space under "shares"
if spaceGrant := ctx.Value(utils.SpaceGrant); spaceGrant == nil {
err := fs.createStorageSpace(ctx, spaceTypeShare, node.ID)
err := fs.linkStorageSpaceType(ctx, spaceTypeShare, node.ID)
if err != nil {
return err
}
Expand Down Expand Up @@ -122,7 +118,7 @@ func (fs *Decomposedfs) ListGrants(ctx context.Context, ref *provider.Reference)
}

log := appctx.GetLogger(ctx)
np := fs.lu.InternalPath(node.ID)
np := node.InternalPath()
var attrs []string
if attrs, err = xattr.List(np); err != nil {
log.Error().Err(err).Msg("error listing attributes")
Expand Down Expand Up @@ -174,8 +170,7 @@ func (fs *Decomposedfs) RemoveGrant(ctx context.Context, ref *provider.Reference
attr = xattrs.GrantUserAcePrefix + g.Grantee.GetUserId().OpaqueId
}

np := fs.lu.InternalPath(node.ID)
if err = xattr.Remove(np, attr); err != nil {
if err = xattrs.Remove(node.InternalPath(), attr); err != nil {
return
}

Expand Down
7 changes: 3 additions & 4 deletions pkg/storage/utils/decomposedfs/grants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ package decomposedfs_test
import (
"io/fs"
"os"
"path"
"path/filepath"

userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
Expand Down Expand Up @@ -115,7 +114,7 @@ var _ = Describe("Grants", func() {
err = env.Fs.AddGrant(env.Ctx, ref, grant)
Expect(err).ToNot(HaveOccurred())

localPath := path.Join(env.Root, "nodes", n.ID)
localPath := n.InternalPath()
attr, err := xattr.Get(localPath, xattrs.GrantUserAcePrefix+grant.Grantee.GetUserId().OpaqueId)
Expect(err).ToNot(HaveOccurred())
Expect(string(attr)).To(Equal("\x00t=A:f=:p=rw"))
Expand All @@ -125,8 +124,8 @@ var _ = Describe("Grants", func() {
err := env.Fs.AddGrant(env.Ctx, ref, grant)
Expect(err).ToNot(HaveOccurred())

spacesPath := filepath.Join(env.Root, "spaces")
tfs.root = spacesPath
spaceTypesPath := filepath.Join(env.Root, "spacetypes")
tfs.root = spaceTypesPath
entries, err := fs.ReadDir(tfs, "share")
Expect(err).ToNot(HaveOccurred())
Expect(len(entries)).To(BeNumerically(">=", 1))
Expand Down
Loading