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

improve GetQuota behavior in decomposedfs #2666

Merged
merged 2 commits into from
Mar 24, 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
7 changes: 7 additions & 0 deletions changelog/unreleased/quota-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Change: Improve quota handling

GetQuota now returns 0 when no quota was set instead of the disk size.
Also added a new return value for the remaining space which will either be quota - used bytes or if no quota was set the free disk size.

https://github.com/owncloud/ocis/issues/3233
https://github.com/cs3org/reva/pull/2666
11 changes: 10 additions & 1 deletion internal/grpc/services/storageprovider/storageprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (

rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/cs3org/reva/v2/pkg/appctx"
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/errtypes"
Expand Down Expand Up @@ -1044,7 +1045,7 @@ 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, req.Ref)
total, used, remaining, err := s.storage.GetQuota(ctx, req.Ref)
if err != nil {
var st *rpc.Status
switch err.(type) {
Expand All @@ -1067,6 +1068,14 @@ func (s *service) GetQuota(ctx context.Context, req *provider.GetQuotaRequest) (
}

res := &provider.GetQuotaResponse{
Opaque: &typesv1beta1.Opaque{
Map: map[string]*typesv1beta1.OpaqueEntry{
"remaining": {
Decoder: "plain",
Value: []byte(strconv.FormatUint(remaining, 10)),
},
},
},
Status: status.NewOK(ctx),
TotalBytes: total,
UsedBytes: used,
Expand Down
12 changes: 8 additions & 4 deletions pkg/storage/fs/nextcloud/nextcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -692,21 +692,25 @@ 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, ref *provider.Reference) (uint64, uint64, error) {
func (nc *StorageDriver) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, uint64, error) {
log := appctx.GetLogger(ctx)
log.Info().Msg("GetQuota")

_, respBody, err := nc.do(ctx, Action{"GetQuota", ""})
if err != nil {
return 0, 0, err
return 0, 0, 0, err
}

var respMap map[string]interface{}
err = json.Unmarshal(respBody, &respMap)
if err != nil {
return 0, 0, err
return 0, 0, 0, err
}
return uint64(respMap["totalBytes"].(float64)), uint64(respMap["usedBytes"].(float64)), err

total := uint64(respMap["totalBytes"].(float64))
used := uint64(respMap["usedBytes"].(float64))
remaining := total - used
return total, used, remaining, err
}

// CreateReference as defined in the storage.FS interface
Expand Down
3 changes: 2 additions & 1 deletion pkg/storage/fs/nextcloud/nextcloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -875,10 +875,11 @@ var _ = Describe("Nextcloud", func() {
It("calls the GetQuota endpoint", func() {
nc, called, teardown := setUpNextcloudServer()
defer teardown()
maxBytes, maxFiles, err := nc.GetQuota(ctx, nil)
maxBytes, maxFiles, remaining, err := nc.GetQuota(ctx, nil)
Expect(err).ToNot(HaveOccurred())
Expect(maxBytes).To(Equal(uint64(456)))
Expect(maxFiles).To(Equal(uint64(123)))
Expect(remaining).To(Equal(uint64(333)))
checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/GetQuota `)
})
})
Expand Down
7 changes: 4 additions & 3 deletions pkg/storage/fs/owncloudsql/owncloudsql_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,17 @@ func calcEtag(ctx context.Context, fi os.FileInfo) string {
return strings.Trim(etag, "\"")
}

func (fs *owncloudsqlfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, error) {
func (fs *owncloudsqlfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, 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
stat := syscall.Statfs_t{}
err := syscall.Statfs(fs.toInternalPath(ctx, "/"), &stat)
if err != nil {
return 0, 0, err
return 0, 0, 0, err
}
total := stat.Blocks * uint64(stat.Bsize) // Total data blocks in filesystem
used := (stat.Blocks - stat.Bavail) * uint64(stat.Bsize) // Free blocks available to unprivileged user
return total, used, nil
remaining := total - used
return total, used, remaining, nil
}
4 changes: 2 additions & 2 deletions pkg/storage/fs/owncloudsql/owncloudsql_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,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, ref *provider.Reference) (uint64, uint64, error) {
func (fs *owncloudsqlfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, 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 All @@ -72,5 +72,5 @@ func (fs *owncloudsqlfs) GetQuota(ctx context.Context, ref *provider.Reference)
}

used := total - free
return total, used, nil
return total, used, free, nil
}
4 changes: 2 additions & 2 deletions pkg/storage/fs/s3/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ 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, ref *provider.Reference) (uint64, uint64, error) {
return 0, 0, nil
func (fs *s3FS) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, uint64, error) {
return 0, 0, 0, nil
}

func (fs *s3FS) SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error {
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,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, ref *provider.Reference) ( /*TotalBytes*/ uint64 /*UsedBytes*/, uint64, error)
GetQuota(ctx context.Context, ref *provider.Reference) ( /*TotalBytes*/ uint64 /*UsedBytes*/, uint64 /*RemainingBytes*/, uint64, error)
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
45 changes: 27 additions & 18 deletions pkg/storage/utils/decomposedfs/decomposedfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,58 +148,67 @@ 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, ref *provider.Reference) (total uint64, inUse uint64, err error) {
func (fs *Decomposedfs) GetQuota(ctx context.Context, ref *provider.Reference) (total uint64, inUse uint64, remaining uint64, err error) {
var n *node.Node
if ref == nil {
err = errtypes.BadRequest("no space given")
return 0, 0, err
return 0, 0, 0, err
}
if n, err = fs.lu.NodeFromResource(ctx, ref); err != nil {
return 0, 0, err
return 0, 0, 0, err
}

if !n.Exists {
err = errtypes.NotFound(filepath.Join(n.ParentID, n.Name))
return 0, 0, err
return 0, 0, 0, err
}

rp, err := fs.p.AssemblePermissions(ctx, n)
switch {
case err != nil:
return 0, 0, errtypes.InternalError(err.Error())
return 0, 0, 0, errtypes.InternalError(err.Error())
case !rp.GetQuota:
return 0, 0, errtypes.PermissionDenied(n.ID)
return 0, 0, 0, errtypes.PermissionDenied(n.ID)
}

ri, err := n.AsResourceInfo(ctx, &rp, []string{"treesize", "quota"}, true)
if err != nil {
return 0, 0, err
return 0, 0, 0, err
}

quotaStr := node.QuotaUnknown
if ri.Opaque != nil && ri.Opaque.Map != nil && ri.Opaque.Map["quota"] != nil && ri.Opaque.Map["quota"].Decoder == "plain" {
quotaStr = string(ri.Opaque.Map["quota"].Value)
}

avail, err := node.GetAvailableSize(n.InternalPath())
remaining, err = node.GetAvailableSize(n.InternalPath())
if err != nil {
return 0, 0, err
return 0, 0, 0, err
}
total = avail + ri.Size

switch {
case quotaStr == node.QuotaUncalculated, quotaStr == node.QuotaUnknown, quotaStr == node.QuotaUnlimited:
// best we can do is return current total
// TODO indicate unlimited total? -> in opaque data?
switch quotaStr {
case node.QuotaUncalculated, node.QuotaUnknown:
// best we can do is return current total
// TODO indicate unlimited total? -> in opaque data?
case node.QuotaUnlimited:
total = 0
default:
if quota, err := strconv.ParseUint(quotaStr, 10, 64); err == nil {
if total > quota {
total = quota
total, err = strconv.ParseUint(quotaStr, 10, 64)
if err != nil {
return 0, 0, 0, err
}

if total <= remaining {
// Prevent overflowing
if ri.Size >= total {
remaining = 0
} else {
remaining = total - ri.Size
}
}
}

return total, ri.Size, nil
return total, ri.Size, remaining, nil
}

// CreateHome creates a new home node for the given user
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/utils/decomposedfs/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ const (
UserShareType = "0"
QuotaKey = "quota"

QuotaUnlimited = "0"
QuotaUncalculated = "-1"
QuotaUnknown = "-2"
QuotaUnlimited = "-3"

// TrashIDDelimiter represents the characters used to separate the nodeid and the deletion time.
TrashIDDelimiter = ".T."
Expand Down
5 changes: 3 additions & 2 deletions pkg/storage/utils/decomposedfs/recycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ var _ = Describe("Recycle", func() {
})

It("they do not count towards the quota anymore", func() {
_, used, err := env.Fs.GetQuota(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes})
_, used, _, err := env.Fs.GetQuota(env.Ctx, &provider.Reference{ResourceId: env.SpaceRootRes})
Expect(err).ToNot(HaveOccurred())
Expect(used).To(Equal(uint64(0)))
})
Expand Down Expand Up @@ -296,10 +296,11 @@ var _ = Describe("Recycle", func() {
err = env.Fs.RestoreRecycleItem(env.Ctx, &provider.Reference{ResourceId: projectID}, items[0].Key, "/", nil)
Expect(err).ToNot(HaveOccurred())

max, used, err := env.Fs.GetQuota(env.Ctx, &provider.Reference{ResourceId: projectID})
max, used, remaining, err := env.Fs.GetQuota(env.Ctx, &provider.Reference{ResourceId: projectID})
Expect(err).ToNot(HaveOccurred())
Expect(max).To(Equal(uint64(2000)))
Expect(used).To(Equal(uint64(3234)))
Expect(remaining).To(Equal(uint64(0)))
})

})
Expand Down
14 changes: 8 additions & 6 deletions pkg/storage/utils/eosfs/eosfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1029,29 +1029,31 @@ func (fs *eosfs) CreateStorageSpace(ctx context.Context, req *provider.CreateSto
return nil, errtypes.NotSupported("unimplemented: CreateStorageSpace")
}

func (fs *eosfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, error) {
func (fs *eosfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, uint64, error) {
u, err := getUser(ctx)
if err != nil {
return 0, 0, errors.Wrap(err, "eosfs: no user in ctx")
return 0, 0, 0, errors.Wrap(err, "eosfs: no user in ctx")
}
// lightweight accounts don't have quota nodes, so we're passing an empty string as path
auth, err := fs.getUserAuth(ctx, u, "")
if err != nil {
return 0, 0, errors.Wrap(err, "eosfs: error getting uid and gid for user")
return 0, 0, 0, errors.Wrap(err, "eosfs: error getting uid and gid for user")
}

rootAuth, err := fs.getRootAuth(ctx)
if err != nil {
return 0, 0, err
return 0, 0, 0, err
}

qi, err := fs.c.GetQuota(ctx, auth.Role.UID, rootAuth, fs.conf.QuotaNode)
if err != nil {
err := errors.Wrap(err, "eosfs: error getting quota")
return 0, 0, err
return 0, 0, 0, err
}

return qi.AvailableBytes, qi.UsedBytes, nil
remaining := qi.AvailableBytes - qi.UsedBytes

return qi.AvailableBytes, qi.UsedBytes, remaining, nil
}

func (fs *eosfs) GetHome(ctx context.Context) (string, error) {
Expand Down
7 changes: 4 additions & 3 deletions pkg/storage/utils/localfs/localfs_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,17 @@ func calcEtag(ctx context.Context, fi os.FileInfo) string {
return fmt.Sprintf("\"%s\"", strings.Trim(etag, "\""))
}

func (fs *localfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, error) {
func (fs *localfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, 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
stat := syscall.Statfs_t{}
err := syscall.Statfs(fs.wrap(ctx, "/"), &stat)
if err != nil {
return 0, 0, err
return 0, 0, 0, err
}
total := stat.Blocks * uint64(stat.Bsize) // Total data blocks in filesystem
used := (stat.Blocks - stat.Bavail) * uint64(stat.Bsize) // Free blocks available to unprivileged user
return total, used, nil
remaining := stat.Bavail * uint64(stat.Bsize)
return total, used, remaining, nil
}
4 changes: 2 additions & 2 deletions pkg/storage/utils/localfs/localfs_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func calcEtag(ctx context.Context, fi os.FileInfo) string {
return fmt.Sprintf("\"%s\"", strings.Trim(etag, "\""))
}

func (fs *localfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, uint64, error) {
func (fs *localfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint64, 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 All @@ -72,5 +72,5 @@ func (fs *localfs) GetQuota(ctx context.Context, ref *provider.Reference) (uint6
}

used := total - free
return total, used, nil
return total, used, free, nil
}