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

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