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  })