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 }