github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/tree/tree_test.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 tree_test 20 21 import ( 22 "os" 23 "path" 24 "path/filepath" 25 26 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 27 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" 28 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" 29 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" 30 helpers "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/testhelpers" 31 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree" 32 "github.com/google/uuid" 33 "github.com/stretchr/testify/mock" 34 35 . "github.com/onsi/ginkgo/v2" 36 . "github.com/onsi/gomega" 37 ) 38 39 var _ = Describe("Tree", func() { 40 var ( 41 env *helpers.TestEnv 42 43 t *tree.Tree 44 ) 45 46 JustBeforeEach(func() { 47 var err error 48 env, err = helpers.NewTestEnv(nil) 49 Expect(err).ToNot(HaveOccurred()) 50 t = env.Tree 51 }) 52 53 AfterEach(func() { 54 if env != nil { 55 env.Cleanup() 56 } 57 }) 58 59 Context("with an existingfile", func() { 60 var ( 61 n *node.Node 62 originalPath = "dir1/file1" 63 ) 64 65 JustBeforeEach(func() { 66 var err error 67 n, err = env.Lookup.NodeFromResource(env.Ctx, &provider.Reference{ 68 ResourceId: env.SpaceRootRes, 69 Path: originalPath, 70 }) 71 Expect(err).ToNot(HaveOccurred()) 72 }) 73 74 Describe("Delete", func() { 75 Context("when the file was locked", func() { 76 JustBeforeEach(func() { 77 _, err := os.Stat(n.InternalPath()) 78 Expect(err).ToNot(HaveOccurred()) 79 80 lock := &provider.Lock{ 81 Type: provider.LockType_LOCK_TYPE_EXCL, 82 User: env.Owner.Id, 83 LockId: uuid.New().String(), 84 } 85 Expect(n.SetLock(env.Ctx, lock)).To(Succeed()) 86 Expect(t.Delete(env.Ctx, n)).To(Succeed()) 87 88 _, err = os.Stat(n.InternalPath()) 89 Expect(err).To(HaveOccurred()) 90 }) 91 92 It("also removes the lock file", func() { 93 _, err := os.Stat(n.LockFilePath()) 94 Expect(err).To(HaveOccurred()) 95 }) 96 }) 97 98 Context("when the file was not locked", func() { 99 JustBeforeEach(func() { 100 _, err := os.Stat(n.InternalPath()) 101 Expect(err).ToNot(HaveOccurred()) 102 103 Expect(t.Delete(env.Ctx, n)).To(Succeed()) 104 105 _, err = os.Stat(n.InternalPath()) 106 Expect(err).To(HaveOccurred()) 107 }) 108 109 It("moves the file to the trash", func() { 110 trashPath := path.Join(env.Root, "spaces", lookup.Pathify(n.SpaceRoot.ID, 1, 2), "trash", lookup.Pathify(n.ID, 4, 2)) 111 _, err := os.Stat(trashPath) 112 Expect(err).ToNot(HaveOccurred()) 113 }) 114 115 It("removes the file from its original location", func() { 116 _, err := os.Stat(n.InternalPath()) 117 Expect(err).To(HaveOccurred()) 118 }) 119 120 It("sets the trash origin xattr", func() { 121 trashPath := path.Join(env.Root, "spaces", lookup.Pathify(n.SpaceRoot.ID, 1, 2), "trash", lookup.Pathify(n.ID, 4, 2)) 122 resolveTrashPath, err := filepath.EvalSymlinks(trashPath) 123 Expect(err).ToNot(HaveOccurred()) 124 125 attr, err := env.Lookup.MetadataBackend().Get(env.Ctx, resolveTrashPath, prefixes.TrashOriginAttr) 126 Expect(err).ToNot(HaveOccurred()) 127 Expect(string(attr)).To(Equal("/dir1/file1")) 128 }) 129 130 It("does not delete the blob from the blobstore", func() { 131 env.Blobstore.AssertNotCalled(GinkgoT(), "Delete", mock.AnythingOfType("*node.Node")) 132 }) 133 }) 134 }) 135 136 Context("that was deleted", func() { 137 var ( 138 trashPath string 139 ) 140 141 JustBeforeEach(func() { 142 env.Blobstore.On("Delete", mock.AnythingOfType("*node.Node")).Return(nil) 143 trashPath = path.Join(env.Root, "spaces", lookup.Pathify(n.SpaceRoot.ID, 1, 2), "trash", lookup.Pathify(n.ID, 4, 2)) 144 Expect(t.Delete(env.Ctx, n)).To(Succeed()) 145 }) 146 147 Describe("PurgeRecycleItemFunc", func() { 148 JustBeforeEach(func() { 149 _, err := os.Stat(trashPath) 150 Expect(err).ToNot(HaveOccurred()) 151 152 _, purgeFunc, err := t.PurgeRecycleItemFunc(env.Ctx, n.SpaceRoot.ID, n.ID, "") 153 Expect(err).ToNot(HaveOccurred()) 154 Expect(purgeFunc()).To(Succeed()) 155 }) 156 157 It("removes the file from the trash", func() { 158 _, err := os.Stat(trashPath) 159 Expect(err).To(HaveOccurred()) 160 }) 161 162 It("deletes the blob from the blobstore", func() { 163 env.Blobstore.AssertCalled(GinkgoT(), "Delete", mock.AnythingOfType("*node.Node")) 164 }) 165 }) 166 167 Describe("RestoreRecycleItemFunc", func() { 168 JustBeforeEach(func() { 169 _, err := os.Stat(trashPath) 170 Expect(err).ToNot(HaveOccurred()) 171 _, err = os.Stat(n.InternalPath()) 172 Expect(err).To(HaveOccurred()) 173 }) 174 175 It("restores the file to its original location if the targetPath is empty", func() { 176 _, _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.SpaceRoot.ID, n.ID, "", nil) 177 Expect(err).ToNot(HaveOccurred()) 178 179 Expect(restoreFunc()).To(Succeed()) 180 181 originalNode, err := env.Lookup.NodeFromResource(env.Ctx, &provider.Reference{ 182 ResourceId: env.SpaceRootRes, 183 Path: originalPath, 184 }) 185 Expect(err).ToNot(HaveOccurred()) 186 Expect(originalNode.Exists).To(BeTrue()) 187 }) 188 189 It("restores files to different locations", func() { 190 ref := &provider.Reference{ 191 ResourceId: env.SpaceRootRes, 192 Path: "dir1/newLocation", 193 } 194 dest, err := env.Lookup.NodeFromResource(env.Ctx, ref) 195 Expect(err).ToNot(HaveOccurred()) 196 197 _, _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.SpaceRoot.ID, n.ID, "", dest) 198 Expect(err).ToNot(HaveOccurred()) 199 200 Expect(restoreFunc()).To(Succeed()) 201 202 newNode, err := env.Lookup.NodeFromResource(env.Ctx, ref) 203 Expect(err).ToNot(HaveOccurred()) 204 Expect(newNode.Exists).To(BeTrue()) 205 206 ref.Path = originalPath 207 originalNode, err := env.Lookup.NodeFromResource(env.Ctx, ref) 208 Expect(err).ToNot(HaveOccurred()) 209 Expect(originalNode.Exists).To(BeFalse()) 210 }) 211 212 It("removes the file from the trash", func() { 213 _, _, restoreFunc, err := t.RestoreRecycleItemFunc(env.Ctx, n.SpaceRoot.ID, n.ID, "", nil) 214 Expect(err).ToNot(HaveOccurred()) 215 216 Expect(restoreFunc()).To(Succeed()) 217 218 _, err = os.Stat(trashPath) 219 Expect(err).To(HaveOccurred()) 220 }) 221 }) 222 }) 223 }) 224 225 Context("with an empty directory", func() { 226 var ( 227 n *node.Node 228 ) 229 230 JustBeforeEach(func() { 231 var err error 232 n, err = env.Lookup.NodeFromResource(env.Ctx, &provider.Reference{ 233 ResourceId: env.SpaceRootRes, 234 Path: "emptydir", 235 }) 236 Expect(err).ToNot(HaveOccurred()) 237 }) 238 239 Describe("TouchFile", func() { 240 It("creates a file inside", func() { 241 ref := &provider.Reference{ 242 ResourceId: env.SpaceRootRes, 243 Path: "emptydir/newFile", 244 } 245 fileToBeCreated, err := env.Lookup.NodeFromResource(env.Ctx, ref) 246 Expect(err).ToNot(HaveOccurred()) 247 Expect(fileToBeCreated.Exists).To(BeFalse()) 248 249 err = t.TouchFile(env.Ctx, fileToBeCreated, false, "") 250 Expect(err).ToNot(HaveOccurred()) 251 252 existingFile, err := env.Lookup.NodeFromResource(env.Ctx, ref) 253 Expect(err).ToNot(HaveOccurred()) 254 Expect(existingFile.Exists).To(BeTrue()) 255 }) 256 }) 257 258 Context("that was deleted", func() { 259 var ( 260 trashPath string 261 ) 262 263 JustBeforeEach(func() { 264 trashPath = path.Join(env.Root, "spaces", lookup.Pathify(n.SpaceRoot.ID, 1, 2), "trash", lookup.Pathify(n.ID, 4, 2)) 265 Expect(t.Delete(env.Ctx, n)).To(Succeed()) 266 267 env.Blobstore.On("Delete", mock.Anything).Return(nil) 268 }) 269 270 Describe("PurgeRecycleItemFunc", func() { 271 JustBeforeEach(func() { 272 _, err := os.Stat(trashPath) 273 Expect(err).ToNot(HaveOccurred()) 274 275 _, purgeFunc, err := t.PurgeRecycleItemFunc(env.Ctx, n.SpaceRoot.ID, n.ID, "") 276 Expect(err).ToNot(HaveOccurred()) 277 Expect(purgeFunc()).To(Succeed()) 278 }) 279 280 It("removes the file from the trash", func() { 281 _, err := os.Stat(trashPath) 282 Expect(err).To(HaveOccurred()) 283 }) 284 }) 285 }) 286 }) 287 288 Describe("Propagate", func() { 289 var dir *node.Node 290 291 JustBeforeEach(func() { 292 env.Permissions.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ResourcePermissions{ 293 CreateContainer: true, 294 Stat: true, 295 }, nil) 296 297 // Create test dir 298 var err error 299 dir, err = env.CreateTestDir("testdir", &provider.Reference{ResourceId: env.SpaceRootRes}) 300 Expect(err).ToNot(HaveOccurred()) 301 }) 302 303 Describe("with TreeTimeAccounting enabled", func() { 304 It("sets the tmtime of the parent", func() { 305 file, err := env.CreateTestFile("file1", "", dir.ID, dir.SpaceID, 1) 306 Expect(err).ToNot(HaveOccurred()) 307 308 perms := node.OwnerPermissions() 309 riBefore, err := dir.AsResourceInfo(env.Ctx, perms, []string{}, []string{}, false) 310 Expect(err).ToNot(HaveOccurred()) 311 312 err = env.Tree.Propagate(env.Ctx, file, 0) 313 Expect(err).ToNot(HaveOccurred()) 314 315 dir, err := env.Lookup.NodeFromID(env.Ctx, &provider.ResourceId{ 316 StorageId: dir.SpaceID, 317 SpaceId: dir.SpaceID, 318 OpaqueId: dir.ID, 319 }) 320 Expect(err).ToNot(HaveOccurred()) 321 riAfter, err := dir.AsResourceInfo(env.Ctx, perms, []string{}, []string{}, false) 322 Expect(err).ToNot(HaveOccurred()) 323 Expect(riAfter.Etag).ToNot(Equal(riBefore.Etag)) 324 }) 325 }) 326 327 Describe("with TreeSizeAccounting enabled", func() { 328 It("calculates the size", func() { 329 _, err := env.CreateTestFile("file1", "", dir.ID, dir.SpaceID, 1) 330 Expect(err).ToNot(HaveOccurred()) 331 332 dir, err := env.Lookup.NodeFromID(env.Ctx, &provider.ResourceId{ 333 StorageId: dir.SpaceID, 334 SpaceId: dir.SpaceID, 335 OpaqueId: dir.ID, 336 }) 337 Expect(err).ToNot(HaveOccurred()) 338 size, err := dir.GetTreeSize(env.Ctx) 339 Expect(err).ToNot(HaveOccurred()) 340 Expect(size).To(Equal(uint64(1))) 341 }) 342 343 It("considers all files", func() { 344 _, err := env.CreateTestFile("file1", "", dir.ID, dir.SpaceID, 1) 345 Expect(err).ToNot(HaveOccurred()) 346 _, err = env.CreateTestFile("file2", "", dir.ID, dir.SpaceID, 100) 347 Expect(err).ToNot(HaveOccurred()) 348 349 dir, err := env.Lookup.NodeFromID(env.Ctx, &provider.ResourceId{ 350 StorageId: dir.SpaceID, 351 SpaceId: dir.SpaceID, 352 OpaqueId: dir.ID, 353 }) 354 Expect(err).ToNot(HaveOccurred()) 355 size, err := dir.GetTreeSize(env.Ctx) 356 Expect(err).ToNot(HaveOccurred()) 357 Expect(size).To(Equal(uint64(101))) 358 }) 359 360 It("adds the size of child directories", func() { 361 subdir, err := env.CreateTestDir("testdir/200bytes", &provider.Reference{ResourceId: env.SpaceRootRes}) 362 Expect(err).ToNot(HaveOccurred()) 363 err = subdir.SetTreeSize(env.Ctx, uint64(200)) 364 Expect(err).ToNot(HaveOccurred()) 365 err = env.Tree.Propagate(env.Ctx, subdir, 200) 366 Expect(err).ToNot(HaveOccurred()) 367 368 _, err = env.CreateTestFile("file1", "", dir.ID, dir.SpaceID, 1) 369 Expect(err).ToNot(HaveOccurred()) 370 371 dir, err := env.Lookup.NodeFromID(env.Ctx, &provider.ResourceId{ 372 StorageId: dir.SpaceID, 373 SpaceId: dir.SpaceID, 374 OpaqueId: dir.ID, 375 }) 376 Expect(err).ToNot(HaveOccurred()) 377 size, err := dir.GetTreeSize(env.Ctx) 378 Expect(err).ToNot(HaveOccurred()) 379 Expect(size).To(Equal(uint64(201))) 380 }) 381 382 It("stops at nodes with no propagation flag", func() { 383 subdir, err := env.CreateTestDir("testdir/200bytes", &provider.Reference{ResourceId: env.SpaceRootRes}) 384 Expect(err).ToNot(HaveOccurred()) 385 err = subdir.SetTreeSize(env.Ctx, uint64(200)) 386 Expect(err).ToNot(HaveOccurred()) 387 err = env.Tree.Propagate(env.Ctx, subdir, 200) 388 Expect(err).ToNot(HaveOccurred()) 389 390 dir, err := env.Lookup.NodeFromID(env.Ctx, &provider.ResourceId{ 391 StorageId: dir.SpaceID, 392 SpaceId: dir.SpaceID, 393 OpaqueId: dir.ID, 394 }) 395 Expect(err).ToNot(HaveOccurred()) 396 size, err := dir.GetTreeSize(env.Ctx) 397 Expect(size).To(Equal(uint64(200))) 398 Expect(err).ToNot(HaveOccurred()) 399 400 stopdir, err := env.CreateTestDir("testdir/stophere", &provider.Reference{ResourceId: env.SpaceRootRes}) 401 Expect(err).ToNot(HaveOccurred()) 402 err = stopdir.SetXattrString(env.Ctx, prefixes.PropagationAttr, "0") 403 Expect(err).ToNot(HaveOccurred()) 404 otherdir, err := env.CreateTestDir("testdir/stophere/lotsofbytes", &provider.Reference{ResourceId: env.SpaceRootRes}) 405 Expect(err).ToNot(HaveOccurred()) 406 err = otherdir.SetTreeSize(env.Ctx, uint64(100000)) 407 Expect(err).ToNot(HaveOccurred()) 408 err = env.Tree.Propagate(env.Ctx, otherdir, 100000) 409 Expect(err).ToNot(HaveOccurred()) 410 411 dir, err = env.Lookup.NodeFromID(env.Ctx, &provider.ResourceId{ 412 StorageId: dir.SpaceID, 413 SpaceId: dir.SpaceID, 414 OpaqueId: dir.ID, 415 }) 416 Expect(err).ToNot(HaveOccurred()) 417 size, err = dir.GetTreeSize(env.Ctx) 418 Expect(err).ToNot(HaveOccurred()) 419 Expect(size).To(Equal(uint64(200))) 420 }) 421 }) 422 }) 423 424 DescribeTable("ReadSpaceAndNodeFromIndexLink", 425 func(link string, expectSpace string, expectedNode string, shouldErr bool) { 426 space, node, err := tree.ReadSpaceAndNodeFromIndexLink(link) 427 if shouldErr { 428 Expect(err).To(HaveOccurred()) 429 } else { 430 Expect(err).ToNot(HaveOccurred()) 431 } 432 Expect(space).To(Equal(expectSpace)) 433 Expect(node).To(Equal(expectedNode)) 434 }, 435 436 Entry("invalid number of slashes", "../../../spaces/sp_ace-id/nodes/sh/or/tn/od/eid", "", "", true), 437 Entry("does not contain spaces", "../../../spac_s/sp/ace-id/nodes/sh/or/tn/od/eid", "", "", true), 438 Entry("does not contain nodes", "../../../spaces/sp/ace-id/nod_s/sh/or/tn/od/eid", "", "", true), 439 Entry("does not start with ..", "_./../../spaces/sp/ace-id/nodes/sh/or/tn/od/eid", "", "", true), 440 Entry("does not start with ../..", "../_./../spaces/sp/ace-id/nodes/sh/or/tn/od/eid", "", "", true), 441 Entry("does not start with ../../..", "../_./../spaces/sp/ace-id/nodes/sh/or/tn/od/eid", "", "", true), 442 Entry("invalid", "../../../spaces/space-id/nodes/sh/or/tn/od/eid", "", "", true), 443 Entry("uuid", "../../../spaces/4c/510ada-c86b-4815-8820-42cdf82c3d51/nodes/4c/51/0a/da/-c86b-4815-8820-42cdf82c3d51", "4c510ada-c86b-4815-8820-42cdf82c3d51", "4c510ada-c86b-4815-8820-42cdf82c3d51", false), 444 Entry("uuid", "../../../spaces/4c/510ada-c86b-4815-8820-42cdf82c3d51/nodes/4c/51/0a/da/-c86b-4815-8820-42cdf82c3d51.T.2022-02-24T12:35:18.196484592Z", "4c510ada-c86b-4815-8820-42cdf82c3d51", "4c510ada-c86b-4815-8820-42cdf82c3d51.T.2022-02-24T12:35:18.196484592Z", false), 445 Entry("short", "../../../spaces/sp/ace-id/nodes/sh/or/tn/od/eid", "space-id", "shortnodeid", false), 446 ) 447 })