diff --git a/changelog/unreleased/update-storage-spaces.md b/changelog/unreleased/update-storage-spaces.md new file mode 100644 index 00000000000..2ae662963a1 --- /dev/null +++ b/changelog/unreleased/update-storage-spaces.md @@ -0,0 +1,5 @@ +Enhancement: Implement the UpdateStorageSpace method + +Added the UpdateStorageSpace method to the decomposedfs. + +https://github.com/cs3org/reva/pull/2162 diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 0560c2278d9..47680c0e882 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -489,9 +489,7 @@ func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStora } func (s *service) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { - return &provider.UpdateStorageSpaceResponse{ - Status: status.NewUnimplemented(ctx, errtypes.NotSupported("UpdateStorageSpace not implemented"), "UpdateStorageSpace not implemented"), - }, nil + return s.storage.UpdateStorageSpace(ctx, req) } func (s *service) DeleteStorageSpace(ctx context.Context, req *provider.DeleteStorageSpaceRequest) (*provider.DeleteStorageSpaceResponse, error) { diff --git a/pkg/storage/fs/nextcloud/nextcloud.go b/pkg/storage/fs/nextcloud/nextcloud.go index e53a793a251..e92b1b41bdd 100644 --- a/pkg/storage/fs/nextcloud/nextcloud.go +++ b/pkg/storage/fs/nextcloud/nextcloud.go @@ -784,3 +784,18 @@ func (nc *StorageDriver) CreateStorageSpace(ctx context.Context, req *provider.C } return &respObj, nil } + +func (nc *StorageDriver) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + bodyStr, _ := json.Marshal(req) + _, respBody, err := nc.do(ctx, Action{"UpdateStorageSpace", string(bodyStr)}) + if err != nil { + return nil, err + } + var respObj provider.UpdateStorageSpaceResponse + fmt.Println(string(respBody)) + err = json.Unmarshal(respBody, &respObj) + if err != nil { + return nil, err + } + return &respObj, nil +} diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index e2bcebdd170..9d19fd1f445 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -2234,6 +2234,10 @@ func (fs *ocfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListSt return nil, errtypes.NotSupported("list storage spaces") } +func (fs *ocfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + return nil, errtypes.NotSupported("update storage space") +} + func (fs *ocfs) propagate(ctx context.Context, leafPath string) error { var root string if fs.c.EnableHome { diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index 4c776c34bf7..aa3b15f446b 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -1930,6 +1930,10 @@ func (fs *owncloudsqlfs) ListStorageSpaces(ctx context.Context, filter []*provid return nil, errtypes.NotSupported("list storage spaces") } +func (fs *owncloudsqlfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + return nil, errtypes.NotSupported("update storage space") +} + func readChecksumIntoResourceChecksum(ctx context.Context, checksums, algo string, ri *provider.ResourceInfo) { re := regexp.MustCompile(strings.ToUpper(algo) + `:(.*)`) matches := re.FindStringSubmatch(checksums) diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index f1522deb115..0e879447cbe 100644 --- a/pkg/storage/fs/s3/s3.go +++ b/pkg/storage/fs/s3/s3.go @@ -680,3 +680,7 @@ func (fs *s3FS) RestoreRecycleItem(ctx context.Context, key, path string, restor func (fs *s3FS) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { return nil, errtypes.NotSupported("list storage spaces") } + +func (fs *s3FS) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + return nil, errtypes.NotSupported("update storage space") +} diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index ff39b55b26d..0e25851776a 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -59,6 +59,7 @@ type FS interface { UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) + UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) } // Registry is the interface that storage registries implement diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index 7bc82296c63..36e8c057f48 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -42,6 +42,11 @@ import ( "github.com/pkg/xattr" ) +const ( + spaceTypeAny = "*" + spaceIDAny = "*" +) + // CreateStorageSpace creates a storage space func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) { // spaces will be located by default in the root of the storage. @@ -153,8 +158,8 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide // we would not need /nodes/root if access always happened via spaceid+relative path var ( - spaceType = "*" - spaceID = "*" + spaceType = spaceTypeAny + spaceID = spaceIDAny ) for i := range filter { @@ -196,104 +201,82 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide appctx.GetLogger(ctx).Error().Err(err).Str("id", filepath.Base(target)).Msg("could not read node, skipping") continue } + + spaceType := filepath.Base(filepath.Dir(matches[i])) + owner, err := n.Owner() if err != nil { appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not read owner, skipping") continue } - // TODO apply more filters - - space := &provider.StorageSpace{ - // FIXME the driver should know its id move setting the spaceid from the storage provider to the drivers - //Id: &provider.StorageSpaceId{OpaqueId: "1284d238-aa92-42ce-bdc4-0b0000009157!" + n.ID}, - Root: &provider.ResourceId{ - // FIXME the driver should know its id move setting the spaceid from the storage provider to the drivers - //StorageId: "1284d238-aa92-42ce-bdc4-0b0000009157", - OpaqueId: n.ID, - }, - Name: n.Name, - SpaceType: filepath.Base(filepath.Dir(matches[i])), - // Mtime is set either as node.tmtime or as fi.mtime below - } - - switch space.SpaceType { - case "share": - if utils.UserEqual(u.Id, owner) { - // do not list shares as spaces for the owner - continue - } - case "project": - sname, err := xattr.Get(n.InternalPath(), xattrs.SpaceNameAttr) - if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not read space name, attribute not found") - continue - } - space.Name = string(sname) - default: - space.Name = "root" + if spaceType == "share" && utils.UserEqual(u.Id, owner) { + // do not list shares as spaces for the owner + continue } - // filter out spaces user cannot access (currently based on stat permission) - p, err := n.ReadUserPermissions(ctx, u) + // TODO apply more filters + space, err := fs.storageSpaceFromNode(ctx, n, matches[i], spaceType) if err != nil { - appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not read permissions, skipping") - continue - } - if !p.Stat { - continue + appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not convert to storage space") } + spaces = append(spaces, space) + } + } - // fill in user object if the current user is the owner - if utils.UserEqual(u.Id, owner) { - space.Owner = u - } else { - space.Owner = &userv1beta1.User{ // FIXME only return a UserID, not a full blown user object - Id: owner, - } - } + return spaces, nil - // we set the space mtime to the root item mtime - // override the stat mtime with a tmtime if it is present - if tmt, err := n.GetTMTime(); err == nil { - un := tmt.UnixNano() - space.Mtime = &types.Timestamp{ - Seconds: uint64(un / 1000000000), - Nanos: uint32(un % 1000000000), - } - } else if fi, err := os.Stat(matches[i]); err == nil { - // fall back to stat mtime - un := fi.ModTime().UnixNano() - space.Mtime = &types.Timestamp{ - Seconds: uint64(un / 1000000000), - Nanos: uint32(un % 1000000000), - } - } +} - // quota - v, err := xattr.Get(matches[i], xattrs.QuotaAttr) - if err == nil { - // make sure we have a proper signed int - // we use the same magic numbers to indicate: - // -1 = uncalculated - // -2 = unknown - // -3 = unlimited - if quota, err := strconv.ParseUint(string(v), 10, 64); err == nil { - space.Quota = &provider.Quota{ - QuotaMaxBytes: quota, - QuotaMaxFiles: math.MaxUint64, // TODO MaxUInt64? = unlimited? why even max files? 0 = unlimited? - } - } else { - appctx.GetLogger(ctx).Debug().Err(err).Str("nodepath", matches[i]).Msg("could not read quota") - } - } +func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + space := req.StorageSpace - spaces = append(spaces, space) + _, spaceID, err := utils.SplitStorageSpaceID(space.Id.OpaqueId) + if err != nil { + return nil, err + } + + matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID)) + if err != nil { + return nil, err + } + + if len(matches) != 1 { + return nil, errors.New("multiple spaces found") + } + + target, err := os.Readlink(matches[0]) + if err != nil { + appctx.GetLogger(ctx).Error().Err(err).Str("match", matches[0]).Msg("could not read link, skipping") + } + + node, err := node.ReadNode(ctx, fs.lu, filepath.Base(target)) + if err != nil { + return nil, err + } + + if space.Name != "" { + if err := node.SetMetadata(xattrs.SpaceNameAttr, space.Name); err != nil { + return nil, err } } - return spaces, nil + if space.Quota != nil { + if err := node.SetMetadata(xattrs.QuotaAttr, strconv.FormatUint(space.Quota.QuotaMaxBytes, 10)); err != nil { + return nil, err + } + } + + spaceType := filepath.Base(filepath.Dir(matches[0])) + updated, err := fs.storageSpaceFromNode(ctx, node, matches[0], spaceType) + if err != nil { + return nil, err + } + return &provider.UpdateStorageSpaceResponse{ + Status: &v1beta11.Status{Code: v1beta11.Code_CODE_OK}, + StorageSpace: updated, + }, nil } // createHiddenSpaceFolder bootstraps a storage space root with a hidden ".space" folder used to store space related @@ -328,3 +311,88 @@ func (fs *Decomposedfs) createStorageSpace(ctx context.Context, spaceType, nodeI return nil } + +func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, node *node.Node, nodePath, spaceType string) (*provider.StorageSpace, error) { + owner, err := node.Owner() + if err != nil { + return nil, err + } + + // TODO apply more filters + + space := &provider.StorageSpace{ + // FIXME the driver should know its id move setting the spaceid from the storage provider to the drivers + //Id: &provider.StorageSpaceId{OpaqueId: "1284d238-aa92-42ce-bdc4-0b0000009157!" + n.ID}, + Root: &provider.ResourceId{ + // FIXME the driver should know its id move setting the spaceid from the storage provider to the drivers + //StorageId: "1284d238-aa92-42ce-bdc4-0b0000009157", + OpaqueId: node.ID, + }, + Name: node.Name, + SpaceType: spaceType, + // Mtime is set either as node.tmtime or as fi.mtime below + } + + switch space.SpaceType { + case "project": + sname, err := xattr.Get(node.InternalPath(), xattrs.SpaceNameAttr) + if err != nil { + return nil, err + } + space.Name = string(sname) + default: + space.Name = "root" + } + + user := ctxpkg.ContextMustGetUser(ctx) + + // filter out spaces user cannot access (currently based on stat permission) + p, err := node.ReadUserPermissions(ctx, user) + if err != nil { + return nil, err + } + if !p.Stat { + return nil, errors.New("user is not allowed to Stat the space") + } + + space.Owner = &userv1beta1.User{ // FIXME only return a UserID, not a full blown user object + Id: owner, + } + + // we set the space mtime to the root item mtime + // override the stat mtime with a tmtime if it is present + if tmt, err := node.GetTMTime(); err == nil { + un := tmt.UnixNano() + space.Mtime = &types.Timestamp{ + Seconds: uint64(un / 1000000000), + Nanos: uint32(un % 1000000000), + } + } else if fi, err := os.Stat(nodePath); err == nil { + // fall back to stat mtime + un := fi.ModTime().UnixNano() + space.Mtime = &types.Timestamp{ + Seconds: uint64(un / 1000000000), + Nanos: uint32(un % 1000000000), + } + } + + // quota + v, err := xattr.Get(nodePath, xattrs.QuotaAttr) + if err == nil { + // make sure we have a proper signed int + // we use the same magic numbers to indicate: + // -1 = uncalculated + // -2 = unknown + // -3 = unlimited + if quota, err := strconv.ParseUint(string(v), 10, 64); err == nil { + space.Quota = &provider.Quota{ + QuotaMaxBytes: quota, + QuotaMaxFiles: math.MaxUint64, // TODO MaxUInt64? = unlimited? why even max files? 0 = unlimited? + } + } else { + return nil, err + } + } + + return space, nil +} diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 909c0edbfb1..c8b84b017ff 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1513,6 +1513,10 @@ func (fs *eosfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListS return nil, errtypes.NotSupported("list storage spaces") } +func (fs *eosfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + return nil, errtypes.NotSupported("update storage space") +} + func (fs *eosfs) convertToRecycleItem(ctx context.Context, eosDeletedItem *eosclient.DeletedEntry) (*provider.RecycleItem, error) { path, err := fs.unwrap(ctx, eosDeletedItem.RestorePath) if err != nil { diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index e35d18601b4..e25ebc66fd4 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -1264,6 +1264,10 @@ func (fs *localfs) ListStorageSpaces(ctx context.Context, filter []*provider.Lis return nil, errtypes.NotSupported("list storage spaces") } +func (fs *localfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) { + return nil, errtypes.NotSupported("update storage space") +} + func (fs *localfs) propagate(ctx context.Context, leafPath string) error { var root string