github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/decomposedfs/upload_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 decomposedfs_test 20 21 import ( 22 "bytes" 23 "context" 24 "io" 25 "os" 26 27 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 28 cs3permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" 29 v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 30 provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 31 ruser "github.com/cs3org/reva/v2/pkg/ctx" 32 "github.com/cs3org/reva/v2/pkg/errtypes" 33 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 34 "github.com/cs3org/reva/v2/pkg/storage" 35 "github.com/cs3org/reva/v2/pkg/storage/cache" 36 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs" 37 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/aspects" 38 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" 39 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata" 40 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata/prefixes" 41 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/node" 42 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options" 43 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions" 44 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/permissions/mocks" 45 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/timemanager" 46 "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree" 47 treemocks "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/tree/mocks" 48 "github.com/cs3org/reva/v2/pkg/storagespace" 49 "github.com/cs3org/reva/v2/pkg/store" 50 "github.com/cs3org/reva/v2/tests/helpers" 51 "github.com/rs/zerolog" 52 "github.com/stretchr/testify/mock" 53 "google.golang.org/grpc" 54 55 . "github.com/onsi/ginkgo/v2" 56 . "github.com/onsi/gomega" 57 ) 58 59 var _ = Describe("File uploads", func() { 60 var ( 61 ref *provider.Reference 62 rootRef *provider.Reference 63 fs storage.FS 64 user *userpb.User 65 ctx context.Context 66 67 o *options.Options 68 lu *lookup.Lookup 69 pmock *mocks.PermissionsChecker 70 cs3permissionsclient *mocks.CS3PermissionsClient 71 permissionsSelector pool.Selectable[cs3permissions.PermissionsAPIClient] 72 bs *treemocks.Blobstore 73 ) 74 75 BeforeEach(func() { 76 ref = &provider.Reference{ 77 ResourceId: &provider.ResourceId{ 78 SpaceId: "u-s-e-r-id", 79 }, 80 Path: "/foo", 81 } 82 83 user = &userpb.User{ 84 Id: &userpb.UserId{ 85 Idp: "idp", 86 OpaqueId: "u-s-e-r-id", 87 Type: userpb.UserType_USER_TYPE_PRIMARY, 88 }, 89 Username: "username", 90 } 91 92 rootRef = &provider.Reference{ 93 ResourceId: &provider.ResourceId{ 94 SpaceId: "u-s-e-r-id", 95 OpaqueId: "u-s-e-r-id", 96 }, 97 Path: "/", 98 } 99 100 ctx = ruser.ContextSetUser(context.Background(), user) 101 102 tmpRoot, err := helpers.TempDir("reva-unit-tests-*-root") 103 Expect(err).ToNot(HaveOccurred()) 104 105 o, err = options.New(map[string]interface{}{ 106 "root": tmpRoot, 107 }) 108 Expect(err).ToNot(HaveOccurred()) 109 lu = lookup.New(metadata.NewXattrsBackend(o.Root, cache.Config{}), o, &timemanager.Manager{}) 110 pmock = &mocks.PermissionsChecker{} 111 cs3permissionsclient = &mocks.CS3PermissionsClient{} 112 pool.RemoveSelector("PermissionsSelector" + "any") 113 permissionsSelector = pool.GetSelector[cs3permissions.PermissionsAPIClient]( 114 "PermissionsSelector", 115 "any", 116 func(cc grpc.ClientConnInterface) cs3permissions.PermissionsAPIClient { 117 return cs3permissionsclient 118 }, 119 ) 120 121 bs = &treemocks.Blobstore{} 122 }) 123 124 AfterEach(func() { 125 root := o.Root 126 if root != "" { 127 os.RemoveAll(root) 128 } 129 }) 130 131 BeforeEach(func() { 132 cs3permissionsclient.On("CheckPermission", mock.Anything, mock.Anything, mock.Anything).Return(&cs3permissions.CheckPermissionResponse{ 133 Status: &v1beta11.Status{Code: v1beta11.Code_CODE_OK}, 134 }, nil) 135 pmock.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ResourcePermissions{ 136 Stat: true, 137 AddGrant: true, 138 }, nil).Times(1) 139 var err error 140 tree := tree.New(lu, bs, o, store.Create(), &zerolog.Logger{}) 141 142 aspects := aspects.Aspects{ 143 Lookup: lu, 144 Tree: tree, 145 Permissions: permissions.NewPermissions(pmock, permissionsSelector), 146 Trashbin: &decomposedfs.DecomposedfsTrashbin{}, 147 } 148 fs, err = decomposedfs.New(o, aspects, &zerolog.Logger{}) 149 Expect(err).ToNot(HaveOccurred()) 150 151 resp, err := fs.CreateStorageSpace(ctx, &provider.CreateStorageSpaceRequest{Owner: user, Type: "personal"}) 152 Expect(err).ToNot(HaveOccurred()) 153 Expect(resp.Status.Code).To(Equal(v1beta11.Code_CODE_OK)) 154 resID, err := storagespace.ParseID(resp.StorageSpace.Id.OpaqueId) 155 Expect(err).ToNot(HaveOccurred()) 156 ref.ResourceId = &resID 157 }) 158 159 Context("the user's quota is exceeded", func() { 160 BeforeEach(func() { 161 pmock.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ResourcePermissions{ 162 Stat: true, 163 GetQuota: true, 164 }, nil) 165 }) 166 When("the user wants to initiate a file upload", func() { 167 It("fails", func() { 168 var originalFunc = node.CheckQuota 169 node.CheckQuota = func(ctx context.Context, spaceRoot *node.Node, overwrite bool, oldSize, newSize uint64) (quotaSufficient bool, err error) { 170 return false, errtypes.InsufficientStorage("quota exceeded") 171 } 172 _, err := fs.InitiateUpload(ctx, ref, 10, map[string]string{}) 173 Expect(err).To(MatchError(errtypes.InsufficientStorage("quota exceeded"))) 174 node.CheckQuota = originalFunc 175 }) 176 }) 177 }) 178 179 Context("the user has insufficient permissions", func() { 180 BeforeEach(func() { 181 pmock.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ResourcePermissions{ 182 Stat: true, 183 }, nil) 184 }) 185 186 When("the user wants to initiate a file upload", func() { 187 It("fails", func() { 188 msg := "error: permission denied: u-s-e-r-id!u-s-e-r-id/foo" 189 _, err := fs.InitiateUpload(ctx, ref, 10, map[string]string{}) 190 Expect(err).To(MatchError(msg)) 191 }) 192 }) 193 }) 194 195 Context("with insufficient permissions, home node", func() { 196 JustBeforeEach(func() { 197 var err error 198 // the space name attribute is the stop condition in the lookup 199 h, err := lu.NodeFromResource(ctx, rootRef) 200 Expect(err).ToNot(HaveOccurred()) 201 err = h.SetXattrString(ctx, prefixes.SpaceNameAttr, "username") 202 Expect(err).ToNot(HaveOccurred()) 203 pmock.On("AssemblePermissions", mock.Anything, mock.Anything, mock.Anything).Return(&provider.ResourcePermissions{ 204 Stat: true, 205 }, nil) 206 }) 207 208 When("the user wants to initiate a file upload", func() { 209 It("fails", func() { 210 msg := "error: permission denied: u-s-e-r-id!u-s-e-r-id/foo" 211 _, err := fs.InitiateUpload(ctx, ref, 10, map[string]string{}) 212 Expect(err).To(MatchError(msg)) 213 }) 214 }) 215 }) 216 217 Context("with sufficient permissions", func() { 218 BeforeEach(func() { 219 pmock.On("AssemblePermissions", mock.Anything, mock.Anything). 220 Return(&provider.ResourcePermissions{ 221 Stat: true, 222 GetQuota: true, 223 InitiateFileUpload: true, 224 ListContainer: true, 225 }, nil) 226 }) 227 228 When("the user initiates a non zero byte file upload", func() { 229 It("succeeds", func() { 230 uploadIds, err := fs.InitiateUpload(ctx, ref, 10, map[string]string{}) 231 232 Expect(err).ToNot(HaveOccurred()) 233 Expect(len(uploadIds)).To(Equal(2)) 234 Expect(uploadIds["simple"]).ToNot(BeEmpty()) 235 Expect(uploadIds["tus"]).ToNot(BeEmpty()) 236 237 resources, err := fs.ListFolder(ctx, rootRef, []string{}, []string{}) 238 Expect(err).ToNot(HaveOccurred()) 239 Expect(len(resources)).To(Equal(0)) 240 }) 241 }) 242 243 When("the user initiates a zero byte file upload", func() { 244 It("succeeds", func() { 245 bs.On("Upload", mock.AnythingOfType("*node.Node"), mock.AnythingOfType("string"), mock.Anything). 246 Return(nil) 247 uploadIds, err := fs.InitiateUpload(ctx, ref, 0, map[string]string{}) 248 249 Expect(err).ToNot(HaveOccurred()) 250 Expect(len(uploadIds)).To(Equal(2)) 251 Expect(uploadIds["simple"]).ToNot(BeEmpty()) 252 Expect(uploadIds["tus"]).ToNot(BeEmpty()) 253 254 resources, err := fs.ListFolder(ctx, rootRef, []string{}, []string{}) 255 Expect(err).ToNot(HaveOccurred()) 256 Expect(len(resources)).To(Equal(1)) 257 }) 258 259 It("fails when trying to upload empty data. 0-byte uploads are finished during initialization already", func() { 260 bs.On("Upload", mock.AnythingOfType("*node.Node"), mock.AnythingOfType("string"), mock.Anything). 261 Return(nil) 262 uploadIds, err := fs.InitiateUpload(ctx, ref, 0, map[string]string{}) 263 264 Expect(err).ToNot(HaveOccurred()) 265 Expect(len(uploadIds)).To(Equal(2)) 266 Expect(uploadIds["simple"]).ToNot(BeEmpty()) 267 268 uploadRef := &provider.Reference{Path: "/" + uploadIds["simple"]} 269 270 _, err = fs.Upload(ctx, storage.UploadRequest{ 271 Ref: uploadRef, 272 Body: io.NopCloser(bytes.NewReader([]byte(""))), 273 Length: 0, 274 }, nil) 275 276 Expect(err).To(HaveOccurred()) 277 }) 278 }) 279 280 When("the user uploads a non zero byte file", func() { 281 It("succeeds", func() { 282 var ( 283 fileContent = []byte("0123456789") 284 ) 285 286 uploadIds, err := fs.InitiateUpload(ctx, ref, 10, map[string]string{}) 287 288 Expect(err).ToNot(HaveOccurred()) 289 Expect(len(uploadIds)).To(Equal(2)) 290 Expect(uploadIds["simple"]).ToNot(BeEmpty()) 291 Expect(uploadIds["tus"]).ToNot(BeEmpty()) 292 293 uploadRef := &provider.Reference{Path: "/" + uploadIds["simple"]} 294 295 bs.On("Upload", mock.AnythingOfType("*node.Node"), mock.AnythingOfType("string"), mock.Anything). 296 Return(nil). 297 Run(func(args mock.Arguments) { 298 data, err := os.ReadFile(args.Get(1).(string)) 299 300 Expect(err).ToNot(HaveOccurred()) 301 Expect(data).To(Equal([]byte("0123456789"))) 302 }) 303 304 _, err = fs.Upload(ctx, storage.UploadRequest{ 305 Ref: uploadRef, 306 Body: io.NopCloser(bytes.NewReader(fileContent)), 307 Length: int64(len(fileContent)), 308 }, nil) 309 310 Expect(err).ToNot(HaveOccurred()) 311 bs.AssertCalled(GinkgoT(), "Upload", mock.Anything, mock.Anything, mock.Anything) 312 313 resources, err := fs.ListFolder(ctx, rootRef, []string{}, []string{}) 314 315 Expect(err).ToNot(HaveOccurred()) 316 Expect(len(resources)).To(Equal(1)) 317 Expect(resources[0].Path).To(Equal(ref.Path)) 318 }) 319 }) 320 321 When("the user tries to upload a file without intialising the upload", func() { 322 It("fails", func() { 323 var ( 324 fileContent = []byte("0123456789") 325 ) 326 327 uploadRef := &provider.Reference{Path: "/some-non-existent-upload-reference"} 328 _, err := fs.Upload(ctx, storage.UploadRequest{ 329 Ref: uploadRef, 330 Body: io.NopCloser(bytes.NewReader(fileContent)), 331 Length: int64(len(fileContent)), 332 }, nil) 333 334 Expect(err).To(HaveOccurred()) 335 336 resources, err := fs.ListFolder(ctx, rootRef, []string{}, []string{}) 337 338 Expect(err).ToNot(HaveOccurred()) 339 Expect(len(resources)).To(Equal(0)) 340 }) 341 }) 342 343 }) 344 })