github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/testhelpers/helpers.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package helpers
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  
    27  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    28  	"github.com/cs3org/reva/v2/pkg/storage/fs/posix/timemanager"
    29  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/aspects"
    30  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup"
    31  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata"
    32  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes"
    33  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions"
    34  	"github.com/cs3org/reva/v2/pkg/storagespace"
    35  	"github.com/cs3org/reva/v2/pkg/store"
    36  	"github.com/google/uuid"
    37  	"github.com/rs/zerolog"
    38  	"github.com/stretchr/testify/mock"
    39  	"google.golang.org/grpc"
    40  
    41  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    42  	cs3permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
    43  	v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    44  	providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    45  	ruser "github.com/cs3org/reva/v2/pkg/ctx"
    46  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs"
    47  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node"
    48  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options"
    49  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions/mocks"
    50  	"github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree"
    51  	treemocks "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/mocks"
    52  	"github.com/cs3org/reva/v2/tests/helpers"
    53  )
    54  
    55  // TestEnv represents a test environment for unit tests
    56  type TestEnv struct {
    57  	Root                 string
    58  	Fs                   *decomposedfs.Decomposedfs
    59  	Tree                 *tree.Tree
    60  	Permissions          *mocks.PermissionsChecker
    61  	Blobstore            *treemocks.Blobstore
    62  	Owner                *userpb.User
    63  	DeleteAllSpacesUser  *userpb.User
    64  	DeleteHomeSpacesUser *userpb.User
    65  	Users                []*userpb.User
    66  	Lookup               *lookup.Lookup
    67  	Ctx                  context.Context
    68  	SpaceRootRes         *providerv1beta1.ResourceId
    69  	PermissionsClient    *mocks.CS3PermissionsClient
    70  	Options              *options.Options
    71  }
    72  
    73  // Constant UUIDs for the space users
    74  const (
    75  	OwnerID                = "25b69780-5f39-43be-a7ac-a9b9e9fe4230"
    76  	DeleteAllSpacesUserID  = "39885dbc-68c0-47c0-a873-9d5e5646dceb"
    77  	DeleteHomeSpacesUserID = "ca8c6bf1-36a7-4d10-87a5-a2806566f983"
    78  	User0ID                = "824385ae-8fc6-4896-8eb2-d1d171290bd0"
    79  	User1ID                = "693b0d96-80a2-4016-b53d-425ce4f66114"
    80  )
    81  
    82  // NewTestEnv prepares a test environment on disk
    83  // The storage contains some directories and a file:
    84  //
    85  //	/dir1/
    86  //	/dir1/file1
    87  //	/dir1/subdir1/
    88  //
    89  // The default config can be overridden by providing the strings to override
    90  // via map as a parameter
    91  func NewTestEnv(config map[string]interface{}) (*TestEnv, error) {
    92  	tmpRoot, err := helpers.TempDir("reva-unit-tests-*-root")
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	defaultConfig := map[string]interface{}{
    97  		"root":                tmpRoot,
    98  		"treetime_accounting": true,
    99  		"treesize_accounting": true,
   100  		"share_folder":        "/Shares",
   101  	}
   102  	// make it possible to override single config values
   103  	for k, v := range config {
   104  		defaultConfig[k] = v
   105  	}
   106  
   107  	o, err := options.New(defaultConfig)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	owner := &userpb.User{
   113  		Id: &userpb.UserId{
   114  			Idp:      "idp",
   115  			OpaqueId: OwnerID,
   116  			Type:     userpb.UserType_USER_TYPE_PRIMARY,
   117  		},
   118  		Username: "username",
   119  	}
   120  	deleteHomeSpacesUser := &userpb.User{
   121  		Id: &userpb.UserId{
   122  			Idp:      "idp",
   123  			OpaqueId: DeleteHomeSpacesUserID,
   124  			Type:     userpb.UserType_USER_TYPE_PRIMARY,
   125  		},
   126  		Username: "username",
   127  	}
   128  	deleteAllSpacesUser := &userpb.User{
   129  		Id: &userpb.UserId{
   130  			Idp:      "idp",
   131  			OpaqueId: DeleteAllSpacesUserID,
   132  			Type:     userpb.UserType_USER_TYPE_PRIMARY,
   133  		},
   134  		Username: "username",
   135  	}
   136  	users := []*userpb.User{
   137  		{
   138  			Id: &userpb.UserId{
   139  				Idp:      "idp",
   140  				OpaqueId: User0ID,
   141  				Type:     userpb.UserType_USER_TYPE_PRIMARY,
   142  			},
   143  		},
   144  		{
   145  			Id: &userpb.UserId{
   146  				Idp:      "idp",
   147  				OpaqueId: User1ID,
   148  				Type:     userpb.UserType_USER_TYPE_PRIMARY,
   149  			},
   150  		},
   151  	}
   152  	var lu *lookup.Lookup
   153  	switch o.MetadataBackend {
   154  	case "xattrs":
   155  		lu = lookup.New(metadata.NewXattrsBackend(o.Root, o.FileMetadataCache), o, &timemanager.Manager{})
   156  	case "messagepack":
   157  		lu = lookup.New(metadata.NewMessagePackBackend(o.Root, o.FileMetadataCache), o, &timemanager.Manager{})
   158  	default:
   159  		return nil, fmt.Errorf("unknown metadata backend %s", o.MetadataBackend)
   160  	}
   161  
   162  	pmock := &mocks.PermissionsChecker{}
   163  
   164  	cs3permissionsclient := &mocks.CS3PermissionsClient{}
   165  	pool.RemoveSelector("PermissionsSelector" + "any")
   166  	permissionsSelector := pool.GetSelector[cs3permissions.PermissionsAPIClient](
   167  		"PermissionsSelector",
   168  		"any",
   169  		func(cc grpc.ClientConnInterface) cs3permissions.PermissionsAPIClient {
   170  			return cs3permissionsclient
   171  		},
   172  	)
   173  
   174  	log := &zerolog.Logger{}
   175  	bs := &treemocks.Blobstore{}
   176  	tree := tree.New(lu, bs, o, store.Create(), log)
   177  	aspects := aspects.Aspects{
   178  		Lookup:      lu,
   179  		Tree:        tree,
   180  		Permissions: permissions.NewPermissions(pmock, permissionsSelector),
   181  		Trashbin:    &decomposedfs.DecomposedfsTrashbin{},
   182  	}
   183  	fs, err := decomposedfs.New(o, aspects, log)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	ctx := ruser.ContextSetUser(context.Background(), owner)
   188  
   189  	tmpFs, _ := fs.(*decomposedfs.Decomposedfs)
   190  
   191  	env := &TestEnv{
   192  		Root:                 tmpRoot,
   193  		Fs:                   tmpFs,
   194  		Tree:                 tree,
   195  		Lookup:               lu,
   196  		Permissions:          pmock,
   197  		Blobstore:            bs,
   198  		Owner:                owner,
   199  		DeleteAllSpacesUser:  deleteAllSpacesUser,
   200  		DeleteHomeSpacesUser: deleteHomeSpacesUser,
   201  		Users:                users,
   202  		Ctx:                  ctx,
   203  		PermissionsClient:    cs3permissionsclient,
   204  		Options:              o,
   205  	}
   206  
   207  	env.SpaceRootRes, err = env.CreateTestStorageSpace("personal", nil)
   208  	return env, err
   209  }
   210  
   211  // Cleanup removes all files from disk
   212  func (t *TestEnv) Cleanup() {
   213  	os.RemoveAll(t.Root)
   214  }
   215  
   216  // CreateTestDir create a directory and returns a corresponding Node
   217  func (t *TestEnv) CreateTestDir(name string, parentRef *providerv1beta1.Reference) (*node.Node, error) {
   218  	ref := parentRef
   219  	ref.Path = name
   220  
   221  	err := t.Fs.CreateDir(t.Ctx, ref)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	ref.Path = name
   227  	n, err := t.Lookup.NodeFromResource(t.Ctx, ref)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	return n, nil
   233  }
   234  
   235  // CreateTestFile creates a new file and its metadata and returns a corresponding Node
   236  func (t *TestEnv) CreateTestFile(name, blobID, parentID, spaceID string, blobSize int64) (*node.Node, error) {
   237  	// Create n in dir1
   238  	n := node.New(
   239  		spaceID,
   240  		uuid.New().String(),
   241  		parentID,
   242  		name,
   243  		blobSize,
   244  		blobID,
   245  		providerv1beta1.ResourceType_RESOURCE_TYPE_FILE,
   246  		nil,
   247  		t.Lookup,
   248  	)
   249  	nodePath := n.InternalPath()
   250  	if err := os.MkdirAll(filepath.Dir(nodePath), 0700); err != nil {
   251  		return nil, err
   252  	}
   253  	_, err := os.OpenFile(nodePath, os.O_CREATE, 0700)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  	err = n.SetXattrs(n.NodeMetadata(t.Ctx), true)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  	// Link in parent
   262  	childNameLink := filepath.Join(n.ParentPath(), n.Name)
   263  	err = os.Symlink("../../../../../"+lookup.Pathify(n.ID, 4, 2), childNameLink)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  	if err := n.FindStorageSpaceRoot(t.Ctx); err != nil {
   268  		return nil, err
   269  	}
   270  
   271  	return n, t.Tree.Propagate(t.Ctx, n, blobSize)
   272  
   273  }
   274  
   275  // CreateTestStorageSpace will create a storage space with some directories and files
   276  // It returns the ResourceId of the space
   277  //
   278  // /dir1/
   279  // /dir1/file1
   280  // /dir1/subdir1
   281  func (t *TestEnv) CreateTestStorageSpace(typ string, quota *providerv1beta1.Quota) (*providerv1beta1.ResourceId, error) {
   282  	t.PermissionsClient.On("CheckPermission", mock.Anything, mock.Anything, mock.Anything).Times(1).Return(&cs3permissions.CheckPermissionResponse{
   283  		Status: &v1beta11.Status{Code: v1beta11.Code_CODE_OK},
   284  	}, nil)
   285  	// Permissions required for setup below
   286  	t.Permissions.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&providerv1beta1.ResourcePermissions{
   287  		Stat:     true,
   288  		AddGrant: true,
   289  	}, nil).Times(1) //
   290  
   291  	var owner *userpb.User
   292  	if typ == "personal" {
   293  		owner = t.Owner
   294  	}
   295  	space, err := t.Fs.CreateStorageSpace(t.Ctx, &providerv1beta1.CreateStorageSpaceRequest{
   296  		Owner: owner,
   297  		Type:  typ,
   298  		Quota: quota,
   299  	})
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  
   304  	ref := buildRef(space.StorageSpace.Id.OpaqueId, "")
   305  
   306  	// the space name attribute is the stop condition in the lookup
   307  	sid, err := storagespace.ParseID(space.StorageSpace.Id.OpaqueId)
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  	h, err := node.ReadNode(t.Ctx, t.Lookup, sid.SpaceId, sid.OpaqueId, false, nil, false)
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  	if err = h.SetXattr(t.Ctx, prefixes.SpaceNameAttr, []byte("username")); err != nil {
   316  		return nil, err
   317  	}
   318  
   319  	// Create dir1
   320  	t.Permissions.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&providerv1beta1.ResourcePermissions{
   321  		Stat:            true,
   322  		CreateContainer: true,
   323  	}, nil).Times(1) // Permissions required for setup below
   324  	dir1, err := t.CreateTestDir("./dir1", ref)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  
   329  	// Create file1 in dir1
   330  	_, err = t.CreateTestFile("file1", "file1-blobid", dir1.ID, dir1.SpaceID, 1234)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	// Create subdir1 in dir1
   336  	t.Permissions.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&providerv1beta1.ResourcePermissions{
   337  		Stat:            true,
   338  		CreateContainer: true,
   339  	}, nil).Times(1) // Permissions required for setup below
   340  	ref.Path = "./dir1/subdir1"
   341  	err = t.Fs.CreateDir(t.Ctx, ref)
   342  	if err != nil {
   343  		return nil, err
   344  	}
   345  
   346  	_, err = dir1.Child(t.Ctx, "subdir1, ref")
   347  	if err != nil {
   348  		return nil, err
   349  	}
   350  
   351  	// Create emptydir
   352  	t.Permissions.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&providerv1beta1.ResourcePermissions{
   353  		Stat:            true,
   354  		CreateContainer: true,
   355  	}, nil).Times(1) // Permissions required for setup below
   356  	ref.Path = "/emptydir"
   357  	err = t.Fs.CreateDir(t.Ctx, ref)
   358  	if err != nil {
   359  		return nil, err
   360  	}
   361  
   362  	return ref.ResourceId, nil
   363  }
   364  
   365  // shortcut to get a ref
   366  func buildRef(id, path string) *providerv1beta1.Reference {
   367  	res, err := storagespace.ParseID(id)
   368  	if err != nil {
   369  		return nil
   370  	}
   371  	return &providerv1beta1.Reference{
   372  		ResourceId: &providerv1beta1.ResourceId{
   373  			StorageId: res.StorageId,
   374  			SpaceId:   res.SpaceId,
   375  			OpaqueId:  res.OpaqueId,
   376  		},
   377  		Path: path,
   378  	}
   379  }