Skip to content

Commit

Permalink
initial quota support
Browse files Browse the repository at this point in the history
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
  • Loading branch information
butonic committed Feb 1, 2021
1 parent d919d59 commit 99864e4
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 37 deletions.
18 changes: 15 additions & 3 deletions internal/grpc/services/gateway/storageprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1762,9 +1762,21 @@ func (s *svc) PurgeRecycle(ctx context.Context, req *gateway.PurgeRecycleRequest
return res, nil
}

func (s *svc) GetQuota(ctx context.Context, _ *gateway.GetQuotaRequest) (*provider.GetQuotaResponse, error) {
res := &provider.GetQuotaResponse{
Status: status.NewUnimplemented(ctx, nil, "GetQuota not yet implemented"),
func (s *svc) GetQuota(ctx context.Context, req *gateway.GetQuotaRequest) (*provider.GetQuotaResponse, error) {
// lookup storage by treating the key as a path. It has been prefixed with the storage path in ListRecycle
c, err := s.find(ctx, req.Ref)
if err != nil {
return &provider.GetQuotaResponse{
Status: status.NewStatusFromErrType(ctx, "GetQuota ref="+req.Ref.String(), err),
}, nil
}

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

res := &provider.GetQuotaResponse{
Status: status.NewOK(ctx),
TotalBytes: uint64(total),
UsedBytes: uint64(used),
TotalBytes: total,
UsedBytes: used,
}
return res, nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Handler struct {
func (h *Handler) Init(c *config.Config) {
h.UserHandler = new(user.Handler)
h.UsersHandler = new(users.Handler)
h.UsersHandler.Init(c)
h.CapabilitiesHandler = new(capabilities.Handler)
h.CapabilitiesHandler.Init(c)
}
Expand Down
85 changes: 71 additions & 14 deletions internal/http/services/owncloud/ocs/handlers/cloud/users/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,28 @@ import (
"fmt"
"net/http"

gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/internal/http/services/owncloud/ocdav"
"github.com/cs3org/reva/internal/http/services/owncloud/ocs/config"
"github.com/cs3org/reva/internal/http/services/owncloud/ocs/response"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/pkg/rhttp/router"
ctxuser "github.com/cs3org/reva/pkg/user"
)

// The UsersHandler renders user data for the user id given in the url path
// Handler renders user data for the user id given in the url path
type Handler struct {
gatewayAddr string
}

// Init initializes this and any contained handlers
func (h *Handler) Init(c *config.Config) error {
h.gatewayAddr = c.GatewaySvc
return nil
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Expand All @@ -53,19 +68,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
head, r.URL.Path = router.ShiftPath(r.URL.Path)
switch head {
case "":
response.WriteOCSSuccess(w, r, &Users{
// FIXME query storages? cache a summary?
// TODO use list of storages to allow clients to resolve quota status
Quota: &Quota{
Free: 2840756224000,
Used: 5059416668,
Total: 2845815640668,
Relative: 0.18,
Definition: "default",
},
DisplayName: u.DisplayName,
Email: u.Mail,
})
h.handleUsers(w, r, u)
return
case "groups":
response.WriteOCSSuccess(w, r, &Groups{})
Expand Down Expand Up @@ -100,3 +103,57 @@ type Users struct {
type Groups struct {
Groups []string `json:"groups" xml:"groups>element"`
}

func (h *Handler) handleUsers(w http.ResponseWriter, r *http.Request, u *userpb.User) {
ctx := r.Context()
sublog := appctx.GetLogger(r.Context())

gc, err := pool.GetGatewayServiceClient(h.gatewayAddr)
if err != nil {
sublog.Error().Err(err).Msg("error getting gateway client")
w.WriteHeader(http.StatusInternalServerError)
return
}

getHomeRes, err := gc.GetHome(ctx, &provider.GetHomeRequest{})
if err != nil {
sublog.Error().Err(err).Msg("error calling GetHome")
w.WriteHeader(http.StatusInternalServerError)
return
}
if getHomeRes.Status.Code != rpc.Code_CODE_OK {
ocdav.HandleErrorStatus(sublog, w, getHomeRes.Status)
return
}

getQuotaRes, err := gc.GetQuota(ctx, &gateway.GetQuotaRequest{
Ref: &provider.Reference{
Spec: &provider.Reference_Path{
Path: getHomeRes.Path,
},
},
})
if err != nil {
sublog.Error().Err(err).Msg("error calling GetQuota")
w.WriteHeader(http.StatusInternalServerError)
return
}

if getQuotaRes.Status.Code != rpc.Code_CODE_OK {
ocdav.HandleErrorStatus(sublog, w, getQuotaRes.Status)
return
}

response.WriteOCSSuccess(w, r, &Users{
// ocs can only return the home storage quota
Quota: &Quota{
Free: int64(getQuotaRes.TotalBytes - getQuotaRes.UsedBytes),
Used: int64(getQuotaRes.UsedBytes),
Total: int64(getQuotaRes.TotalBytes), // -1, -2 have special meaning?
Relative: float32(float64(getQuotaRes.UsedBytes) / float64(getQuotaRes.TotalBytes)),
Definition: "default",
},
DisplayName: u.DisplayName,
Email: u.Mail,
})
}
16 changes: 8 additions & 8 deletions pkg/eosclient/eosbinary/eosbinary.go
Original file line number Diff line number Diff line change
Expand Up @@ -771,19 +771,19 @@ func (c *Client) parseQuota(path, raw string) (*eosclient.QuotaInfo, error) {
if strings.HasPrefix(path, space) {
maxBytesString := m["maxlogicalbytes"]
usedBytesString := m["usedlogicalbytes"]
maxBytes, _ := strconv.ParseInt(maxBytesString, 10, 64)
usedBytes, _ := strconv.ParseInt(usedBytesString, 10, 64)
maxBytes, _ := strconv.ParseUint(maxBytesString, 10, 64)
usedBytes, _ := strconv.ParseUint(usedBytesString, 10, 64)

maxInodesString := m["maxfiles"]
usedInodesString := m["usedfiles"]
maxInodes, _ := strconv.ParseInt(maxInodesString, 10, 64)
usedInodes, _ := strconv.ParseInt(usedInodesString, 10, 64)
maxInodes, _ := strconv.ParseUint(maxInodesString, 10, 64)
usedInodes, _ := strconv.ParseUint(usedInodesString, 10, 64)

qi := &eosclient.QuotaInfo{
AvailableBytes: int(maxBytes),
UsedBytes: int(usedBytes),
AvailableInodes: int(maxInodes),
UsedInodes: int(usedInodes),
AvailableBytes: maxBytes,
UsedBytes: usedBytes,
AvailableInodes: maxInodes,
UsedInodes: usedInodes,
}
return qi, nil
}
Expand Down
5 changes: 3 additions & 2 deletions pkg/eosclient/eosclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ type DeletedEntry struct {
}

// QuotaInfo reports the available bytes and inodes for a particular user.
// eos reports all quota values are unsigned long, see https://github.com/cern-eos/eos/blob/93515df8c0d5a858982853d960bec98f983c1285/mgm/Quota.hh#L135
type QuotaInfo struct {
AvailableBytes, UsedBytes int
AvailableInodes, UsedInodes int
AvailableBytes, UsedBytes uint64
AvailableInodes, UsedInodes uint64
}
19 changes: 17 additions & 2 deletions pkg/storage/fs/ocis/ocis.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"os"
"path/filepath"
"strings"
"syscall"

userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
Expand Down Expand Up @@ -183,8 +184,22 @@ func (fs *ocisfs) Shutdown(ctx context.Context) error {
return nil
}

func (fs *ocisfs) GetQuota(ctx context.Context) (int, int, error) {
return 0, 0, nil
func (fs *ocisfs) GetQuota(ctx context.Context) (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
n, err := fs.lu.NodeFromPath(ctx, "/")
if err != nil {
return 0, 0, err
}
stat := syscall.Statfs_t{}
err = syscall.Statfs(n.lu.toInternalPath(n.ID), &stat)
if err != nil {
return 0, 0, err
}
total := stat.Blocks * uint64(stat.Bsize) // Total data blocks in filesystem
used := stat.Bavail * uint64(stat.Bsize) // Free blocks available to unprivileged user
return total, used, nil
}

// CreateHome creates a new root node that has no parent id
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/fs/owncloud/owncloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -1105,7 +1105,7 @@ func (fs *ocfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *pro
return fs.propagate(ctx, ip)
}

func (fs *ocfs) GetQuota(ctx context.Context) (int, int, error) {
func (fs *ocfs) GetQuota(ctx context.Context) (uint64, uint64, error) {
return 0, 0, nil
}

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 @@ -262,7 +262,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) (int, int, error) {
func (fs *s3FS) GetQuota(ctx context.Context) (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 @@ -51,7 +51,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) (int, int, error)
GetQuota(ctx context.Context) (uint64, 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
2 changes: 1 addition & 1 deletion pkg/storage/utils/eosfs/eosfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ func (fs *eosfs) listShareFolderRoot(ctx context.Context, p string) (finfos []*p
return finfos, nil
}

func (fs *eosfs) GetQuota(ctx context.Context) (int, int, error) {
func (fs *eosfs) GetQuota(ctx context.Context) (uint64, uint64, error) {
u, err := getUser(ctx)
if err != nil {
return 0, 0, errors.Wrap(err, "eos: no user in ctx")
Expand Down
15 changes: 13 additions & 2 deletions pkg/storage/utils/localfs/localfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"path"
"strconv"
"strings"
"syscall"
"time"

userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
Expand Down Expand Up @@ -501,8 +502,18 @@ func (fs *localfs) UpdateGrant(ctx context.Context, ref *provider.Reference, g *
return fs.AddGrant(ctx, ref, g)
}

func (fs *localfs) GetQuota(ctx context.Context) (int, int, error) {
return 0, 0, nil
func (fs *localfs) GetQuota(ctx context.Context) (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.conf.Root, &stat)
if err != nil {
return 0, 0, err
}
total := stat.Blocks * uint64(stat.Bsize) // Total data blocks in filesystem
used := stat.Bavail * uint64(stat.Bsize) // Free blocks available to unprivileged user
return total, used, nil
}

func (fs *localfs) CreateReference(ctx context.Context, path string, targetURI *url.URL) error {
Expand Down

0 comments on commit 99864e4

Please sign in to comment.