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

Report quota per storage space #2152

Merged
merged 16 commits into from
Oct 14, 2021
Merged
7 changes: 7 additions & 0 deletions changelog/unreleased/get-quota-storage-space.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Enhancement: Add a reference parameter to the getQuota request

Implementation of [cs3org/cs3apis#147](https://github.com/cs3org/cs3apis/pull/147)

Make the cs3apis accept a Reference in the getQuota Request to limit the call to a specific storage space.

https://github.com/cs3org/reva/issues/2152
2 changes: 1 addition & 1 deletion internal/grpc/services/gateway/storageprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -2135,7 +2135,7 @@ func (s *svc) GetQuota(ctx context.Context, req *gateway.GetQuotaRequest) (*prov

res, err := c.GetQuota(ctx, &provider.GetQuotaRequest{
Opaque: req.GetOpaque(),
// Ref: req.GetRef(), // TODO send which storage space ... or root
Ref: req.GetRef(),
})
if err != nil {
return nil, errors.Wrap(err, "gateway: error calling GetQuota")
Expand Down
12 changes: 11 additions & 1 deletion internal/grpc/services/storageprovider/storageprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1181,7 +1181,17 @@ func (s *service) CreateSymlink(ctx context.Context, req *provider.CreateSymlink
}

func (s *service) GetQuota(ctx context.Context, req *provider.GetQuotaRequest) (*provider.GetQuotaResponse, error) {
total, used, err := s.storage.GetQuota(ctx)
newRef, err := s.unwrap(ctx, req.Ref)
if err != nil {
return &provider.GetQuotaResponse{
Status: status.NewInternal(ctx, err, "error unwrapping path"),
}, nil
}
newReq := &provider.GetQuotaRequest{
Ref: newRef,
Opaque: req.Opaque,
}
total, used, err := s.storage.GetQuota(ctx, newReq)
if err != nil {
var st *rpc.Status
switch err.(type) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/fs/nextcloud/nextcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ func (nc *StorageDriver) ListGrants(ctx context.Context, ref *provider.Reference
}

// GetQuota as defined in the storage.FS interface
func (nc *StorageDriver) GetQuota(ctx context.Context) (uint64, uint64, error) {
func (nc *StorageDriver) GetQuota(ctx context.Context, req *provider.GetQuotaRequest) (uint64, uint64, error) {
log := appctx.GetLogger(ctx)
log.Info().Msg("GetQuota")

Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/fs/nextcloud/nextcloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,7 @@ var _ = Describe("Nextcloud", func() {
It("calls the GetQuota endpoint", func() {
nc, called, teardown := setUpNextcloudServer()
defer teardown()
maxBytes, maxFiles, err := nc.GetQuota(ctx)
maxBytes, maxFiles, err := nc.GetQuota(ctx, nil)
Expect(err).ToNot(HaveOccurred())
Expect(maxBytes).To(Equal(uint64(456)))
Expect(maxFiles).To(Equal(uint64(123)))
Expand Down
3 changes: 2 additions & 1 deletion pkg/storage/fs/owncloud/owncloud_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"strings"
"syscall"

provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
)

Expand Down Expand Up @@ -68,7 +69,7 @@ func calcEtag(ctx context.Context, fi os.FileInfo) string {
return fmt.Sprintf("\"%s\"", strings.Trim(etag, "\""))
}

func (fs *ocfs) GetQuota(ctx context.Context) (uint64, uint64, error) {
func (fs *ocfs) GetQuota(ctx context.Context, req *provider.GetQuotaRequest) (uint64, uint64, error) {
// TODO quota of which storage space?
// we could use the logged in user, but when a user has access to multiple storages this falls short
// for now return quota of root
Expand Down
3 changes: 2 additions & 1 deletion pkg/storage/fs/owncloud/owncloud_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"os"
"strings"

provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
"golang.org/x/sys/windows"
)
Expand All @@ -55,7 +56,7 @@ func calcEtag(ctx context.Context, fi os.FileInfo) string {
return fmt.Sprintf("\"%s\"", strings.Trim(etag, "\""))
}

func (fs *ocfs) GetQuota(ctx context.Context) (uint64, uint64, error) {
func (fs *ocfs) GetQuota(ctx context.Context, req *provider.GetQuotaRequest) (uint64, uint64, error) {
// TODO quota of which storage space?
// we could use the logged in user, but when a user has access to multiple storages this falls short
// for now return quota of root
Expand Down
3 changes: 2 additions & 1 deletion pkg/storage/fs/owncloudsql/owncloudsql_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"strings"
"syscall"

provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
)

Expand Down Expand Up @@ -68,7 +69,7 @@ func calcEtag(ctx context.Context, fi os.FileInfo) string {
return strings.Trim(etag, "\"")
}

func (fs *owncloudsqlfs) GetQuota(ctx context.Context) (uint64, uint64, error) {
func (fs *owncloudsqlfs) GetQuota(ctx context.Context, req *provider.GetQuotaRequest) (uint64, uint64, error) {
// TODO quota of which storage space?
// we could use the logged in user, but when a user has access to multiple storages this falls short
// for now return quota of root
Expand Down
3 changes: 2 additions & 1 deletion pkg/storage/fs/owncloudsql/owncloudsql_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"os"
"strings"

provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
"golang.org/x/sys/windows"
)
Expand All @@ -55,7 +56,7 @@ func calcEtag(ctx context.Context, fi os.FileInfo) string {
return fmt.Sprintf("\"%s\"", strings.Trim(etag, "\""))
}

func (fs *owncloudsqlfs) GetQuota(ctx context.Context) (uint64, uint64, error) {
func (fs *owncloudsqlfs) GetQuota(ctx context.Context, req *provider.GetQuotaRequest) (uint64, uint64, error) {
// TODO quota of which storage space?
// we could use the logged in user, but when a user has access to multiple storages this falls short
// for now return quota of root
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/fs/s3/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ func (fs *s3FS) UpdateGrant(ctx context.Context, ref *provider.Reference, g *pro
return errtypes.NotSupported("s3: operation not supported")
}

func (fs *s3FS) GetQuota(ctx context.Context) (uint64, uint64, error) {
func (fs *s3FS) GetQuota(ctx context.Context, req *provider.GetQuotaRequest) (uint64, uint64, error) {
return 0, 0, nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type FS interface {
RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error)
GetQuota(ctx context.Context) ( /*TotalBytes*/ uint64 /*UsedBytes*/, uint64, error)
GetQuota(ctx context.Context, req *provider.GetQuotaRequest) ( /*TotalBytes*/ uint64 /*UsedBytes*/, uint64, error)
micbar marked this conversation as resolved.
Show resolved Hide resolved
CreateReference(ctx context.Context, path string, targetURI *url.URL) error
Shutdown(ctx context.Context) error
SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error
Expand Down
20 changes: 15 additions & 5 deletions pkg/storage/utils/decomposedfs/decomposedfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,16 @@ func (fs *Decomposedfs) Shutdown(ctx context.Context) error {

// GetQuota returns the quota available
// TODO Document in the cs3 should we return quota or free space?
func (fs *Decomposedfs) GetQuota(ctx context.Context) (total uint64, inUse uint64, err error) {
func (fs *Decomposedfs) GetQuota(ctx context.Context, req *provider.GetQuotaRequest) (total uint64, inUse uint64, err error) {
var n *node.Node
if n, err = fs.lu.HomeOrRootNode(ctx); err != nil {
return 0, 0, err
if req.Ref != nil {
if n, err = fs.lu.NodeFromResource(ctx, req.Ref); err != nil {
return 0, 0, err
}
} else {
if n, err = fs.lu.HomeOrRootNode(ctx); err != nil {
return 0, 0, err
}
}

if !n.Exists {
Expand Down Expand Up @@ -206,7 +212,7 @@ func (fs *Decomposedfs) CreateHome(ctx context.Context) (err error) {
return
}

if fs.o.TreeTimeAccounting {
if fs.o.TreeTimeAccounting || fs.o.TreeSizeAccounting {
homePath := h.InternalPath()
// mark the home node as the end of propagation
if err = xattr.Set(homePath, xattrs.PropagationAttr, []byte("1")); err != nil {
Expand All @@ -215,6 +221,10 @@ func (fs *Decomposedfs) CreateHome(ctx context.Context) (err error) {
}
}

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

// add storage space
if err := fs.createStorageSpace(ctx, "personal", h.ID); err != nil {
return err
Expand Down Expand Up @@ -289,7 +299,7 @@ func (fs *Decomposedfs) CreateDir(ctx context.Context, ref *provider.Reference)

err = fs.tp.CreateDir(ctx, n)

if fs.o.TreeTimeAccounting {
if fs.o.TreeTimeAccounting || fs.o.TreeSizeAccounting {
nodePath := n.InternalPath()
// mark the home node as the end of propagation
if err = xattr.Set(nodePath, xattrs.PropagationAttr, []byte("1")); err != nil {
Expand Down
39 changes: 24 additions & 15 deletions pkg/storage/utils/decomposedfs/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,26 @@ func (lu *Lookup) NodeFromResource(ctx context.Context, ref *provider.Reference)
if ref.ResourceId != nil {
// check if a storage space reference is used
// currently, the decomposed fs uses the root node id as the space id
n, err := lu.NodeFromID(ctx, ref.ResourceId)
spaceRoot, err := lu.NodeFromID(ctx, ref.ResourceId)
if err != nil {
return nil, err
}

p := filepath.Clean(ref.Path)
if p != "." {
// walk the relative path
n, err = lu.WalkPath(ctx, n, p, false, func(ctx context.Context, n *node.Node) error {
return nil
})
if err != nil {
return nil, err
n := spaceRoot
// is this a relative reference?
if ref.Path != "" {
p := filepath.Clean(ref.Path)
if p != "." {
// walk the relative path
n, err = lu.WalkPath(ctx, n, p, false, func(ctx context.Context, n *node.Node) error {
return nil
})
if err != nil {
return nil, err
}
}
return n, nil
// use reference id as space root for relative references
n.SpaceRoot = spaceRoot
}

return n, nil
}

Expand All @@ -78,23 +81,27 @@ func (lu *Lookup) NodeFromPath(ctx context.Context, fn string, followReferences
log := appctx.GetLogger(ctx)
log.Debug().Interface("fn", fn).Msg("NodeFromPath()")

n, err := lu.HomeOrRootNode(ctx)
root, err := lu.HomeOrRootNode(ctx)
if err != nil {
return nil, err
}

n := root
// TODO collect permissions of the current user on every segment
fn = filepath.Clean(fn)
if fn != "/" && fn != "." {
n, err = lu.WalkPath(ctx, n, fn, followReferences, func(ctx context.Context, n *node.Node) error {
log.Debug().Interface("node", n).Msg("NodeFromPath() walk")
if n.SpaceRoot != nil && n.SpaceRoot != root {
root = n.SpaceRoot
}
return nil
})
if err != nil {
return nil, err
}
}

n.SpaceRoot = root
return n, nil
}

Expand Down Expand Up @@ -129,7 +136,9 @@ func (lu *Lookup) Path(ctx context.Context, n *node.Node) (p string, err error)

// RootNode returns the root node of the storage
func (lu *Lookup) RootNode(ctx context.Context) (*node.Node, error) {
return node.New("root", "", "", 0, "", nil, lu), nil
n := node.New("root", "", "", 0, "", nil, lu)
n.Exists = true
return n, nil
}

// HomeNode returns the home node of a user
Expand Down
46 changes: 30 additions & 16 deletions pkg/storage/utils/decomposedfs/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,14 @@ const (

// Node represents a node in the tree and provides methods to get a Parent or Child instance
type Node struct {
ParentID string
ID string
Name string
Blobsize int64
BlobID string
owner *userpb.UserId
Exists bool
ParentID string
ID string
Name string
Blobsize int64
BlobID string
owner *userpb.UserId
Exists bool
SpaceRoot *Node

lu PathLookup
}
Expand Down Expand Up @@ -191,6 +192,10 @@ func ReadNode(ctx context.Context, lu PathLookup, id string) (n *Node, err error
default:
return nil, errtypes.InternalError(err.Error())
}
// check if this is a space root
if _, err = xattr.Get(nodePath, xattrs.SpaceNameAttr); err == nil {
n.SpaceRoot = n
}
// lookup name in extended attributes
if attrBytes, err = xattr.Get(nodePath, xattrs.NameAttr); err == nil {
n.Name = string(attrBytes)
Expand Down Expand Up @@ -239,9 +244,10 @@ func (n *Node) Child(ctx context.Context, name string) (*Node, error) {
if err != nil {
if os.IsNotExist(err) || isNotDir(err) {
c := &Node{
lu: n.lu,
ParentID: n.ID,
Name: name,
lu: n.lu,
ParentID: n.ID,
Name: name,
SpaceRoot: n.SpaceRoot,
}
return c, nil // if the file does not exist we return a node that has Exists = false
}
Expand All @@ -268,8 +274,9 @@ func (n *Node) Parent() (p *Node, err error) {
return nil, fmt.Errorf("Decomposedfs: root has no parent")
}
p = &Node{
lu: n.lu,
ID: n.ParentID,
lu: n.lu,
ID: n.ParentID,
SpaceRoot: n.SpaceRoot,
}

parentPath := n.lu.InternalPath(n.ParentID)
Expand Down Expand Up @@ -608,11 +615,18 @@ func (n *Node) AsResourceInfo(ctx context.Context, rp *provider.ResourcePermissi
// quota
if _, ok := mdKeysMap[QuotaKey]; (nodeType == provider.ResourceType_RESOURCE_TYPE_CONTAINER) && returnAllKeys || ok {
var quotaPath string
if r, err := n.lu.HomeOrRootNode(ctx); err == nil {
quotaPath = r.InternalPath()
readQuotaIntoOpaque(ctx, quotaPath, ri)
if n.SpaceRoot == nil {
root, err := n.lu.HomeOrRootNode(ctx)
if err == nil {
quotaPath = root.InternalPath()
} else {
sublog.Debug().Err(err).Msg("error determining the space root node for quota")
}
} else {
sublog.Error().Err(err).Msg("error determining home or root node for quota")
quotaPath = n.SpaceRoot.InternalPath()
}
if quotaPath != "" {
readQuotaIntoOpaque(ctx, quotaPath, ri)
}
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/storage/utils/decomposedfs/spaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.Cr
return nil, err
}

// always enable propagation on the storage space root
nodePath := n.InternalPath()
// mark the space root node as the end of propagation
if err = xattr.Set(nodePath, xattrs.PropagationAttr, []byte("1")); err != nil {
appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not mark node to propagate")
return nil, err
}

if err := fs.createHiddenSpaceFolder(ctx, n); err != nil {
return nil, err
}
Expand Down
8 changes: 6 additions & 2 deletions pkg/storage/utils/decomposedfs/tree/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -578,8 +578,12 @@ func (t *Tree) Propagate(ctx context.Context, n *node.Node) (err error) {
// is propagation enabled for the parent node?

var root *node.Node
if root, err = t.lookup.HomeOrRootNode(ctx); err != nil {
return
if n.SpaceRoot == nil {
if root, err = t.lookup.HomeOrRootNode(ctx); err != nil {
return
}
} else {
root = n.SpaceRoot
}

// use a sync time and don't rely on the mtime of the current node, as the stat might not change when a rename happened too quickly
Expand Down
Loading