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 }