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

Restoring Spaces #2458

Merged
merged 15 commits into from
Feb 4, 2022
Merged
7 changes: 7 additions & 0 deletions changelog/unreleased/restore-spaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Enhancement: Restore spaces that were previously deleted

After the first step of the two step delete process an admin can decide to restore
the space instead of deleting it. This will undo the deletion and
all files and shares are accessible again

https://github.com/cs3org/reva/pull/2457
11 changes: 7 additions & 4 deletions internal/grpc/services/gateway/storageprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,11 @@ func (s *svc) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorag
}, nil
}

id := res.StorageSpace.Root
s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), id)
s.cache.RemoveListStorageProviders(id)
if res.Status.Code == rpc.Code_CODE_OK {
micbar marked this conversation as resolved.
Show resolved Hide resolved
id := res.StorageSpace.Root
s.cache.RemoveStat(ctxpkg.ContextMustGetUser(ctx), id)
s.cache.RemoveListStorageProviders(id)
}
return res, nil
}

Expand Down Expand Up @@ -1089,8 +1091,9 @@ func (s *svc) findSpaces(ctx context.Context, ref *provider.Reference) ([]*regis
}

listReq := &registry.ListStorageProvidersRequest{
Opaque: &typesv1beta1.Opaque{},
Opaque: &typesv1beta1.Opaque{Map: make(map[string]*typesv1beta1.OpaqueEntry)},
}

sdk.EncodeOpaqueMap(listReq.Opaque, filters)

return s.findProvider(ctx, listReq)
Expand Down
137 changes: 102 additions & 35 deletions pkg/storage/utils/decomposedfs/spaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"path/filepath"
"strconv"
"strings"
"time"

userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
permissionsv1beta1 "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
Expand Down Expand Up @@ -217,7 +218,8 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide

matches := []string{}
for _, spaceType := range spaceTypes {
m, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceType, nodeID))
path := filepath.Join(fs.o.Root, "spaces", spaceType, nodeID)
m, err := filepath.Glob(path)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -317,10 +319,50 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide

// UpdateStorageSpace updates a storage space
func (fs *Decomposedfs) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) {
space := req.StorageSpace
var restore bool
if req.Opaque != nil {
_, restore = req.Opaque.Map["restore"]
}

space := req.StorageSpace
_, spaceID, _ := utils.SplitStorageSpaceID(space.Id.OpaqueId)

if restore {
matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID))
if err != nil {
return nil, err
}

if len(matches) != 1 {
return &provider.UpdateStorageSpaceResponse{
Status: &v1beta11.Status{
Code: v1beta11.Code_CODE_NOT_FOUND,
Message: fmt.Sprintf("restoring space failed: found %d matching spaces", len(matches)),
},
}, nil

}

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")
}

n, err := node.ReadNode(ctx, fs.lu, filepath.Base(target))
if err != nil {
return nil, err
}

newnode := *n
newnode.Name = strings.Split(n.Name, node.TrashIDDelimiter)[0]
newnode.Exists = false

err = fs.tp.Move(ctx, n, &newnode)
if err != nil {
return nil, err
}
}

matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID))
if err != nil {
return nil, err
Expand Down Expand Up @@ -377,8 +419,29 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De
_, purge = opaque.Map["purge"]
}

spaceID := req.Id.OpaqueId

matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID))
if err != nil {
return err
}

if len(matches) != 1 {
return fmt.Errorf("delete space failed: found %d matching spaces", len(matches))
}

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")
}

n, err := node.ReadNode(ctx, fs.lu, filepath.Base(target))
if err != nil {
return err
}

if purge {
if !strings.Contains(req.Id.OpaqueId, node.TrashIDDelimiter) {
if !strings.Contains(n.Name, node.TrashIDDelimiter) {
return errtypes.NewErrtypeFromStatus(status.NewInvalidArg(ctx, "can't purge enabled space"))
}
ip := fs.lu.InternalPath(req.Id.OpaqueId)
Expand All @@ -400,31 +463,27 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De
return fmt.Errorf("delete space failed: found %d matching spaces", len(matches))
}

return os.RemoveAll(matches[0])
}

spaceID := req.Id.OpaqueId

matches, err := filepath.Glob(filepath.Join(fs.o.Root, "spaces", spaceTypeAny, spaceID))
if err != nil {
return err
}
if err := os.RemoveAll(matches[0]); err != nil {
return err
}

if len(matches) != 1 {
return fmt.Errorf("delete space failed: found %d matching spaces", len(matches))
}
matches, err = filepath.Glob(filepath.Join(fs.o.Root, "nodes", "root", req.Id.OpaqueId+node.TrashIDDelimiter+"*"))
if err != nil {
return err
}

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")
}
if len(matches) != 1 {
return fmt.Errorf("delete root node failed: found %d matching root nodes", len(matches))
}

n, err := node.ReadNode(ctx, fs.lu, filepath.Base(target))
if err != nil {
return err
return os.RemoveAll(matches[0])
}

err = fs.tp.Delete(ctx, n)
// don't delete - just rename
dn := *n
deletionTime := time.Now().UTC().Format(time.RFC3339Nano)
dn.Name = n.Name + node.TrashIDDelimiter + deletionTime
dn.Exists = false
err = fs.tp.Move(ctx, n, &dn)
if err != nil {
return err
}
Expand All @@ -434,15 +493,9 @@ func (fs *Decomposedfs) DeleteStorageSpace(ctx context.Context, req *provider.De
return err
}

trashPathMatches, err := filepath.Glob(n.InternalPath() + node.TrashIDDelimiter + "*")
if err != nil {
return err
}
if len(trashPathMatches) != 1 {
return fmt.Errorf("delete space failed: found %d matching trashed spaces", len(trashPathMatches))
}
trashPath := trashPathMatches[0]
return os.Symlink(trashPath, filepath.Join(filepath.Dir(matches[0]), filepath.Base(trashPath)))
trashPath := dn.InternalPath()
np := filepath.Join(filepath.Dir(matches[0]), filepath.Base(trashPath))
return os.Symlink(trashPath, np)
}

// createHiddenSpaceFolder bootstraps a storage space root with a hidden ".space" folder used to store space related
Expand Down Expand Up @@ -487,8 +540,15 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node,
if err != nil || !ok {
return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to Stat the space %s", user.Username, n.SpaceRoot.ID))
}
if n.SpaceRoot != nil && strings.Contains(n.SpaceRoot.ID, node.TrashIDDelimiter) {
return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.SpaceRoot.ID))

if strings.Contains(n.Name, node.TrashIDDelimiter) {
ok, err := node.NewPermissions(fs.lu).HasPermission(ctx, n, func(p *provider.ResourcePermissions) bool {
// TODO: Which permission do I need to see the space?
return p.AddGrant
micbar marked this conversation as resolved.
Show resolved Hide resolved
})
if err != nil || !ok {
return nil, errtypes.PermissionDenied(fmt.Sprintf("user %s is not allowed to list deleted spaces %s", user.Username, n.SpaceRoot.ID))
}
}
}

Expand Down Expand Up @@ -563,6 +623,13 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, n *node.Node,
// Mtime is set either as node.tmtime or as fi.mtime below
}

if strings.Contains(n.Name, node.TrashIDDelimiter) {
space.Opaque.Map["trashed"] = &types.OpaqueEntry{
Decoder: "plain",
Value: []byte("trashed"),
}
}

space.Owner = &userv1beta1.User{ // FIXME only return a UserID, not a full blown user object
Id: owner,
}
Expand Down