github.com/djmaze/goofys@v0.24.2/internal/backend_gcs_test.go (about) 1 package internal 2 3 import ( 4 "github.com/djmaze/goofys/api/common" 5 6 "bytes" 7 "context" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "path" 13 "sort" 14 "syscall" 15 16 "github.com/jacobsa/fuse" 17 "golang.org/x/oauth2/google" 18 "golang.org/x/sync/errgroup" 19 . "gopkg.in/check.v1" 20 ) 21 22 type GCSTestSpec struct { 23 objCount int // number of non-prefix blobs uploaded to the bucket 24 prefixCount int // number of prefix blobs uploaded to the bucket 25 existingObjKey string // test setup will add a blob with this name 26 existingObjContent string 27 existingObjLen uint64 28 defaultObjLen uint64 // blobs created in the setup will be of this length 29 nonPrefixObjects []string 30 prefixObjects []string 31 prefixes []string // these are the prefixes of the prefixObjects 32 metadata map[string]*string // default Metadata for all blobs 33 contentType *string // default Content-Type for all blobs 34 // will be reset in SetUpTest, optionally modified on each test, and cleaned up in TearDownTest 35 additionalBlobs []string 36 } 37 38 type GCSBackendTest struct { 39 gcsBackend *GCSBackend 40 bucketName string 41 testSpec GCSTestSpec 42 chunkSize int 43 } 44 45 var _ = Suite(&GCSBackendTest{}) 46 47 func (s *GCSBackendTest) getGCSTestConfig() (*common.GCSConfig, error) { 48 config := common.NewGCSConfig() 49 config.ChunkSize = s.chunkSize 50 return config, nil 51 } 52 53 func (s *GCSBackendTest) getGCSTestBackend(gcsBucket string) (*GCSBackend, error) { 54 config, err := s.getGCSTestConfig() 55 if err != nil { 56 return nil, err 57 } 58 spec, err := ParseBucketSpec(gcsBucket) 59 if err != nil { 60 return nil, err 61 } 62 gcsBackend, err := NewGCS(spec.Bucket, config) 63 64 return gcsBackend, err 65 } 66 67 func (s *GCSBackendTest) SetUpSuite(c *C) { 68 if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") == "" { 69 c.Skip("Skipping because GOOGLE_APPLICATION_CREDENTIALS variable is unset.") 70 } 71 72 bktName := "goofys-test-" + RandStringBytesMaskImprSrc(16) 73 s.bucketName = bktName 74 s.testSpec = GCSTestSpec{ 75 objCount: 10, 76 prefixCount: 5, 77 defaultObjLen: 16, 78 metadata: map[string]*string{ 79 "foo": PString("bar"), 80 "bar": PString("baz"), 81 }, 82 contentType: PString("text/plain"), 83 } 84 s.chunkSize = 1 * 1024 * 1024 85 s.gcsBackend, _ = s.getGCSTestBackend(fmt.Sprintf("gs://%s", bktName)) 86 87 _, err := s.gcsBackend.MakeBucket(&MakeBucketInput{}) 88 c.Assert(err, IsNil) 89 90 errGroup, _ := errgroup.WithContext(context.Background()) 91 objLen := s.testSpec.defaultObjLen 92 93 // create object without prefix 94 // files is stored as 00_blob, 01_blob, ... 09_blob (10 items) 95 for i := 0; i < s.testSpec.objCount; i++ { 96 objKey := fmt.Sprintf("%02d_blob", i) 97 objContent := RandStringBytesMaskImprSrc(int(objLen)) 98 99 errGroup.Go(func() error { 100 _, err := s.gcsBackend.PutBlob(&PutBlobInput{ 101 Key: objKey, 102 Body: bytes.NewReader([]byte(objContent)), 103 Metadata: s.testSpec.metadata, 104 ContentType: s.testSpec.contentType, 105 }) 106 c.Assert(err, IsNil) 107 108 // verify object attr after a put blob 109 objAttr, err := s.gcsBackend.HeadBlob(&HeadBlobInput{Key: objKey}) 110 c.Assert(err, IsNil) 111 c.Assert(NilStr(objAttr.Key), Equals, objKey) 112 c.Assert(objAttr.Size, Equals, uint64(int(objLen))) 113 c.Assert(NilStr(objAttr.ContentType), Equals, NilStr(s.testSpec.contentType)) 114 c.Assert(objAttr.Metadata, DeepEquals, s.testSpec.metadata) 115 116 return err 117 }) 118 119 // set the existing object to be the first object 120 if s.testSpec.existingObjKey == "" { 121 s.testSpec.existingObjKey = objKey 122 s.testSpec.existingObjContent = objContent 123 s.testSpec.existingObjLen = objLen 124 } 125 s.testSpec.nonPrefixObjects = append(s.testSpec.nonPrefixObjects, objKey) 126 } 127 128 // create object with prefix 129 // file is stored as 00_prefix/blob, 01_prefix/blob, ..., 04_prefix/blob (5 items) 130 for i := 0; i < s.testSpec.prefixCount; i++ { 131 objKey := fmt.Sprintf("%02d_prefix/blob", i) 132 objContent := RandStringBytesMaskImprSrc(int(objLen)) 133 errGroup.Go(func() error { 134 _, err := s.gcsBackend.PutBlob(&PutBlobInput{ 135 Key: objKey, 136 Body: bytes.NewReader([]byte(objContent)), 137 Metadata: s.testSpec.metadata, 138 ContentType: s.testSpec.contentType, 139 }) 140 c.Assert(err, IsNil) 141 142 objAttr, err := s.gcsBackend.HeadBlob(&HeadBlobInput{Key: objKey}) 143 c.Assert(err, IsNil) 144 c.Assert(NilStr(objAttr.Key), Equals, objKey) 145 c.Assert(objAttr.Size, Equals, uint64(int(objLen))) 146 c.Assert(NilStr(objAttr.ContentType), Equals, NilStr(s.testSpec.contentType)) 147 c.Assert(objAttr.Metadata, DeepEquals, s.testSpec.metadata) 148 return err 149 }) 150 151 s.testSpec.prefixObjects = append(s.testSpec.prefixObjects, objKey) 152 dir, _ := path.Split(objKey) 153 s.testSpec.prefixes = append(s.testSpec.prefixes, dir) 154 } 155 156 err = errGroup.Wait() 157 c.Assert(err, IsNil) 158 } 159 160 func (s *GCSBackendTest) TearDownSuite(c *C) { 161 if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") == "" { 162 c.Skip("Skipping because GOOGLE_APPLICATION_CREDENTIALS variable is unset.") 163 } 164 out, err := s.gcsBackend.ListBlobs(&ListBlobsInput{}) 165 c.Assert(err, IsNil) 166 167 var items []string 168 for _, item := range out.Items { 169 items = append(items, NilStr(item.Key)) 170 } 171 172 _, err = s.gcsBackend.DeleteBlobs(&DeleteBlobsInput{Items: items}) 173 c.Assert(err, IsNil) 174 175 _, err = s.gcsBackend.RemoveBucket(&RemoveBucketInput{}) 176 c.Assert(err, IsNil) 177 } 178 179 func (s *GCSBackendTest) SetUpTest(c *C) { 180 s.testSpec.additionalBlobs = []string{} 181 } 182 183 func (s *GCSBackendTest) TearDownTest(c *C) { 184 _, err := s.gcsBackend.DeleteBlobs(&DeleteBlobsInput{s.testSpec.additionalBlobs}) 185 // successful deletion should be: nil error or not found 186 c.Assert(err == nil || err == syscall.ENOENT, Equals, true) 187 } 188 189 func (s *GCSBackendTest) TestGCSBackend_Init_Authenticated(c *C) { 190 // No error when accessing existing private bucket. 191 err := s.gcsBackend.Init(s.bucketName) 192 c.Assert(err, IsNil) 193 194 // Not Found error when accessing nonexistent bucket. 195 randBktName := RandStringBytesMaskImprSrc(16) 196 gcsBackend, err := s.getGCSTestBackend(randBktName) 197 c.Assert(err, IsNil) 198 err = gcsBackend.Init(RandStringBytesMaskImprSrc(16)) 199 c.Assert(err, ErrorMatches, fmt.Sprintf("bucket %s does not exist", randBktName)) 200 201 // No error when accessing public bucket. 202 gcsBackend, err = s.getGCSTestBackend("gcp-public-data-nexrad-l2") 203 c.Assert(err, IsNil) 204 err = gcsBackend.Init(RandStringBytesMaskImprSrc(16)) 205 c.Assert(err, IsNil) 206 } 207 208 func (s *GCSBackendTest) TestGCSBackend_Init_Unauthenticated(c *C) { 209 defaultCredentials := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") 210 defer func() { 211 os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", defaultCredentials) 212 }() 213 os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", "") 214 215 credentials, err := google.FindDefaultCredentials(context.Background()) 216 if credentials != nil { 217 c.Skip("Skipping this test because credentials still exist in the environment.") 218 } 219 220 // Access error on existing private bucket. 221 gcsBackend, err := s.getGCSTestBackend(s.bucketName) 222 c.Assert(err, IsNil) 223 err = gcsBackend.Init(RandStringBytesMaskImprSrc(15)) 224 c.Assert(err, Equals, syscall.EACCES) 225 226 // Not Found error when accessing nonexistent bucket. 227 randBktName := RandStringBytesMaskImprSrc(16) 228 gcsBackend, err = s.getGCSTestBackend(randBktName) 229 c.Assert(err, IsNil) 230 err = gcsBackend.Init(RandStringBytesMaskImprSrc(16)) 231 c.Assert(err, ErrorMatches, fmt.Sprintf("bucket %s does not exist", randBktName)) 232 233 // No error when accessing public bucket. 234 gcsBackend, err = s.getGCSTestBackend("gcp-public-data-nexrad-l2") 235 c.Assert(err, IsNil) 236 err = gcsBackend.Init(RandStringBytesMaskImprSrc(16)) 237 c.Assert(err, IsNil) 238 } 239 240 func (s *GCSBackendTest) TestGCSBackend_Init_Authenticated_ReadOnlyAccess(c *C) { 241 // READONLY_GOOGLE_APPLICATION_CREDENTIALS is a credential that is authorized to read-only access to the bucket 242 if os.Getenv("READONLY_GOOGLE_APPLICATION_CREDENTIALS") == "" { 243 c.Skip("Skipping because READONLY_GOOGLE_APPLICATION_CREDENTIALS is unset.") 244 } 245 246 defaultCredentials := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") 247 defer func() { 248 os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", defaultCredentials) 249 }() 250 os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", os.Getenv("READONLY_GOOGLE_APPLICATION_CREDENTIALS")) 251 252 // No error when accessing existing private bucket. 253 gcsBackend, err := s.getGCSTestBackend(s.bucketName) 254 c.Assert(err, IsNil) 255 err = gcsBackend.Init(s.bucketName) 256 c.Assert(err, IsNil) 257 258 // Access error when trying to modify object. 259 _, err = gcsBackend.DeleteBlob(&DeleteBlobInput{Key: s.testSpec.existingObjKey}) 260 c.Assert(err, Equals, syscall.EACCES) 261 262 // Not Found error when accessing nonexistent bucket. 263 randBktName := RandStringBytesMaskImprSrc(16) 264 gcsBackend, err = s.getGCSTestBackend(randBktName) 265 c.Assert(err, IsNil) 266 err = gcsBackend.Init(RandStringBytesMaskImprSrc(16)) 267 c.Assert(err, ErrorMatches, fmt.Sprintf("bucket %s does not exist", randBktName)) 268 269 // No error when accessing public bucket. 270 gcsBackend, err = s.getGCSTestBackend("gcp-public-data-nexrad-l2") 271 c.Assert(err, IsNil) 272 err = gcsBackend.Init(RandStringBytesMaskImprSrc(16)) 273 c.Assert(err, IsNil) 274 } 275 276 func (s *GCSBackendTest) TestGCSBackend_HeadBlob_NotExist(c *C) { 277 objKey := RandStringBytesMaskImprSrc(16) 278 _, err := s.gcsBackend.HeadBlob(&HeadBlobInput{Key: objKey}) 279 c.Assert(err, Equals, fuse.ENOENT) 280 } 281 282 func (s *GCSBackendTest) TestGCSBackend_GetBlob_NotExist(c *C) { 283 objKey := RandStringBytesMaskImprSrc(16) 284 _, err := s.gcsBackend.GetBlob(&GetBlobInput{Key: objKey}) 285 c.Assert(err, Equals, fuse.ENOENT) 286 } 287 288 func (s *GCSBackendTest) TestGCSBackend_GetFullBlob(c *C) { 289 fullBlob, err := s.gcsBackend.GetBlob(&GetBlobInput{Key: s.testSpec.existingObjKey}) 290 c.Assert(err, IsNil) 291 defer fullBlob.Body.Close() 292 293 actualContent, err := ioutil.ReadAll(fullBlob.Body) 294 c.Assert(err, IsNil) 295 c.Assert(string(actualContent), Equals, s.testSpec.existingObjContent) 296 } 297 298 func (s *GCSBackendTest) TestGCSBackend_GetPartialBlob(c *C) { 299 var startOffset uint64 = 4 300 var count uint64 = 8 301 partialBlob, err := s.gcsBackend.GetBlob(&GetBlobInput{ 302 Key: s.testSpec.existingObjKey, 303 Start: startOffset, 304 Count: count, 305 }) 306 c.Assert(err, IsNil) 307 defer partialBlob.Body.Close() 308 309 actualContent, err := ioutil.ReadAll(partialBlob.Body) 310 c.Assert(err, IsNil) 311 c.Assert(string(actualContent), Equals, s.testSpec.existingObjContent[startOffset:startOffset+count]) 312 } 313 314 func (s *GCSBackendTest) TestGCSBackend_PutBlob_NilBody(c *C) { 315 objKey := RandStringBytesMaskImprSrc(int(s.testSpec.defaultObjLen)) 316 s.addToAdditionalBlobs(objKey) 317 318 _, err := s.gcsBackend.PutBlob(&PutBlobInput{Key: objKey, Body: nil}) 319 c.Assert(err, IsNil) 320 } 321 322 func (s *GCSBackendTest) addToAdditionalBlobs(objKey string) { 323 s.testSpec.additionalBlobs = append(s.testSpec.additionalBlobs, objKey) 324 } 325 326 func (s *GCSBackendTest) TestGCSBackend_GetGzipEncodedBlob(c *C) { 327 objKey := "gzipObj" 328 s.addToAdditionalBlobs(objKey) 329 originalContent := RandStringBytesMaskImprSrc(int(s.testSpec.defaultObjLen)) 330 331 // Goofys cannot upload a file with content-encoding: gzip. So we will use GCS sdk directly to create such blob 332 s.writeGzipEncodedFile(c, objKey, originalContent) 333 334 gzipBlob, err := s.gcsBackend.GetBlob(&GetBlobInput{Key: objKey}) 335 c.Assert(err, IsNil) 336 defer gzipBlob.Body.Close() 337 338 actualContent, err := ioutil.ReadAll(gzipBlob.Body) 339 c.Assert(string(actualContent), Equals, originalContent) 340 } 341 342 func (s *GCSBackendTest) writeGzipEncodedFile(c *C, objKey string, content string) { 343 writer := s.gcsBackend.bucket.Object(objKey).NewWriter(context.Background()) 344 writer.ContentEncoding = "gzip" 345 _, err := writer.Write([]byte(content)) 346 defer writer.Close() 347 c.Assert(err, IsNil) 348 } 349 350 func (s *GCSBackendTest) TestGCSBackend_ListBlobs_NoPrefixNoDelim(c *C) { 351 // list all objects in bucket as items, no prefix because delim is unset 352 listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{}) 353 c.Assert(err, IsNil) 354 355 actualOutputs := s.extractAllNamesFromListOutputs(listOutputs) 356 s.checkListOutputs(c, actualOutputs, s.testSpec.nonPrefixObjects, s.testSpec.prefixObjects) 357 c.Assert(listOutputs.NextContinuationToken, IsNil) 358 c.Assert(listOutputs.IsTruncated, Equals, false) 359 } 360 361 func (s *GCSBackendTest) TestGCSBackend_ListBlobs_NoPrefixWithDelim(c *C) { 362 // list all objects but separates items and prefixes 363 listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{Delimiter: PString("/")}) 364 c.Assert(err, IsNil) 365 // should contain non prefix objects: 00_blob, ..., 09_blob and prefixes: 00_prefix/, ..., 04_prefix/ 366 actualOutputs := s.extractAllNamesFromListOutputs(listOutputs) 367 s.checkListOutputs(c, actualOutputs, s.testSpec.nonPrefixObjects, s.testSpec.prefixes) 368 c.Assert(listOutputs.NextContinuationToken, IsNil) 369 c.Assert(listOutputs.IsTruncated, Equals, false) 370 } 371 372 func (s *GCSBackendTest) TestGCSBackend_ListBlobs_WithPrefixNoDelim(c *C) { 373 // list all objects that have a matching prefix as items 374 listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{Prefix: PString("00")}) 375 c.Assert(err, IsNil) 376 // should contain all items under prefix: 00_blob & 00_prefix/blob 377 actualOutputs := s.extractAllNamesFromListOutputs(listOutputs) 378 s.checkListOutputs(c, actualOutputs, s.testSpec.nonPrefixObjects[:1], s.testSpec.prefixObjects[:1]) 379 c.Assert(listOutputs.NextContinuationToken, IsNil) 380 c.Assert(listOutputs.IsTruncated, Equals, false) 381 } 382 383 func (s *GCSBackendTest) TestGCSBackend_ListBlobs_WithPrefixWithDelim(c *C) { 384 // lists objects that have a matching prefix and separates non prefix & prefixes 385 listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{ 386 Prefix: PString("00"), 387 Delimiter: PString("/"), 388 }) 389 c.Assert(err, IsNil) 390 // should contain 00_blob and 00_prefix/ 391 actualOutputs := s.extractAllNamesFromListOutputs(listOutputs) 392 s.checkListOutputs(c, actualOutputs, s.testSpec.nonPrefixObjects[:1], s.testSpec.prefixes[:1]) 393 c.Assert(listOutputs.NextContinuationToken, IsNil) 394 c.Assert(listOutputs.IsTruncated, Equals, false) 395 } 396 397 func (s *GCSBackendTest) TestGCSBackend_ListBlobs_StartAfter(c *C) { 398 cutoffKey := "04_prefix/blob" 399 // list results all come right on or after StartAfter in lexicographical order 400 listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{ 401 Delimiter: PString("/"), 402 StartAfter: PString(cutoffKey), 403 }) 404 c.Assert(err, IsNil) 405 406 actualOutputs := s.extractAllNamesFromListOutputs(listOutputs) 407 // contain elements from 05_blob and 04_prefix/ 408 s.checkListOutputs(c, actualOutputs, s.testSpec.nonPrefixObjects[5:], 409 s.testSpec.prefixes[len(s.testSpec.prefixes)-1:]) 410 411 // assert all items are larger or equal to StartAfter prefix (because we used delimiter) in lexicographical order 412 cutOffPrefix, _ := path.Split(cutoffKey) 413 for _, item := range actualOutputs { 414 c.Assert(item >= cutOffPrefix, Equals, true) 415 } 416 } 417 418 func (s *GCSBackendTest) TestGCSBackend_ListBlobs_MaxKeysPaginate(c *C) { 419 totalKeys := s.testSpec.objCount + s.testSpec.prefixCount 420 maxKeys := 1 421 422 var nextContToken *string 423 var actualOutputs []string 424 425 // iterate over pages of maxKeys 426 for i := 0; i < totalKeys-1; i++ { 427 listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{ 428 Delimiter: PString("/"), 429 MaxKeys: PUInt32(uint32(maxKeys)), 430 ContinuationToken: nextContToken, 431 }) 432 c.Assert(err, IsNil) 433 c.Assert(len(listOutputs.Items)+len(listOutputs.Prefixes), Equals, maxKeys) 434 c.Assert(listOutputs.NextContinuationToken, NotNil) 435 nextContToken = listOutputs.NextContinuationToken 436 actualOutputs = append(actualOutputs, s.extractAllNamesFromListOutputs(listOutputs)...) 437 } 438 // remaining object in the last page 439 listOutputs, err := s.gcsBackend.ListBlobs(&ListBlobsInput{ 440 Delimiter: PString("/"), 441 MaxKeys: PUInt32(uint32(maxKeys)), 442 ContinuationToken: nextContToken, 443 }) 444 actualOutputs = append(actualOutputs, s.extractAllNamesFromListOutputs(listOutputs)...) 445 c.Assert(err, IsNil) 446 c.Assert(len(listOutputs.Items)+len(listOutputs.Prefixes) <= maxKeys, Equals, true) 447 c.Assert(listOutputs.NextContinuationToken, IsNil) 448 s.checkListOutputs(c, actualOutputs, s.testSpec.nonPrefixObjects, s.testSpec.prefixes) 449 } 450 451 func (s *GCSBackendTest) extractAllNamesFromListOutputs(listOutputs *ListBlobsOutput) []string { 452 var actualOutputs []string 453 for _, item := range listOutputs.Items { 454 actualOutputs = append(actualOutputs, NilStr(item.Key)) 455 } 456 for _, prefix := range listOutputs.Prefixes { 457 actualOutputs = append(actualOutputs, NilStr(prefix.Prefix)) 458 } 459 return actualOutputs 460 } 461 462 // This check the actual outputs against list of expected outputs 463 func (s *GCSBackendTest) checkListOutputs(c *C, actualOutputs []string, expectedOutputsArgs ...[]string) { 464 var expectedResults []string 465 for _, expectedOutputs := range expectedOutputsArgs { 466 expectedResults = append(expectedResults, expectedOutputs...) 467 } 468 sort.Strings(expectedResults) 469 sort.Strings(actualOutputs) 470 471 c.Assert(actualOutputs, DeepEquals, expectedResults) 472 } 473 474 func (s *GCSBackendTest) TestGCSBackend_CopyBlob_PreserveMetadata(c *C) { 475 srcKey := s.testSpec.existingObjKey 476 destKey := RandStringBytesMaskImprSrc(16) 477 s.addToAdditionalBlobs(destKey) 478 479 srcAttr, err := s.gcsBackend.HeadBlob(&HeadBlobInput{Key: srcKey}) 480 c.Assert(err, IsNil) 481 482 _, err = s.gcsBackend.CopyBlob(&CopyBlobInput{ 483 Source: srcKey, 484 Destination: destKey, 485 }) 486 c.Assert(err, IsNil) 487 488 destAttr, err := s.gcsBackend.HeadBlob(&HeadBlobInput{Key: destKey}) 489 c.Assert(err, IsNil) 490 c.Assert(NilStr(destAttr.ContentType), Equals, NilStr(srcAttr.ContentType)) 491 c.Assert(destAttr.Metadata, DeepEquals, srcAttr.Metadata) 492 c.Assert(srcAttr.ETag, Not(Equals), destAttr.ETag) 493 494 destOut, err := s.gcsBackend.GetBlob(&GetBlobInput{Key: srcKey}) 495 c.Assert(err, IsNil) 496 defer destOut.Body.Close() 497 destContent, err := ioutil.ReadAll(destOut.Body) 498 c.Assert(string(destContent), Equals, s.testSpec.existingObjContent) 499 } 500 501 func (s *GCSBackendTest) TestGCSBackend_MultipartUpload_BeginAddCommit(c *C) { 502 objKey := RandStringBytesMaskImprSrc(16) 503 s.addToAdditionalBlobs(objKey) 504 505 commitInput, err := s.gcsBackend.MultipartBlobBegin(&MultipartBlobBeginInput{ 506 Key: objKey, 507 }) 508 c.Assert(err, IsNil) 509 c.Assert(commitInput.NumParts, Equals, uint32(0)) 510 511 // We will have numFullChunks+1 chunks. That last chunk is of size lastChunkSize 512 var numFullChunks = 2 513 lastChunkSize := s.chunkSize - 1 514 fileSize := uint64((numFullChunks * s.chunkSize) + lastChunkSize) 515 516 // generate data to simulate MPU behavior 517 buf := new(bytes.Buffer) 518 data := io.LimitReader(&SeqReader{}, int64(fileSize)) 519 _, err = buf.ReadFrom(data) 520 c.Assert(err, IsNil) 521 reader := bytes.NewReader(buf.Bytes()) 522 523 // add the full chunks 524 for i := 0; i < numFullChunks; i++ { 525 sectionReader := io.NewSectionReader(reader, int64(i*s.chunkSize), int64(s.chunkSize)) 526 addOut, err := s.gcsBackend.MultipartBlobAdd(&MultipartBlobAddInput{ 527 Commit: commitInput, 528 PartNumber: uint32(i + 1), 529 Body: sectionReader, 530 Size: uint64(s.chunkSize), 531 }) 532 c.Assert(err, IsNil) 533 c.Assert(addOut, NotNil) 534 } 535 536 // add the remaining chunk 537 sectionReader := io.NewSectionReader(reader, int64(numFullChunks*s.chunkSize), int64(lastChunkSize)) 538 addOut, err := s.gcsBackend.MultipartBlobAdd(&MultipartBlobAddInput{ 539 Commit: commitInput, 540 PartNumber: uint32(numFullChunks + 1), 541 Body: sectionReader, 542 Size: uint64(lastChunkSize), 543 }) 544 c.Assert(addOut, NotNil) 545 c.Assert(err, IsNil) 546 547 commitOut, err := s.gcsBackend.MultipartBlobCommit(commitInput) 548 c.Assert(err, IsNil) 549 c.Assert(commitOut.ETag, NotNil) 550 551 _, err = s.gcsBackend.HeadBlob(&HeadBlobInput{Key: objKey}) 552 c.Assert(err, IsNil) 553 554 // assert uploaded content is correct 555 blobOutput, err := s.gcsBackend.GetBlob(&GetBlobInput{Key: objKey}) 556 c.Assert(err, IsNil) 557 defer blobOutput.Body.Close() 558 559 _, err = reader.Seek(0, io.SeekStart) // seek to the beginning of the file 560 c.Assert(err, IsNil) 561 _, err = CompareReader(reader, blobOutput.Body, 0) // assert content 562 c.Assert(err, IsNil) 563 } 564 565 func (s *GCSBackendTest) TestGCSBackend_MultipartUpload_Abort(c *C) { 566 src := RandStringBytesMaskImprSrc(16) 567 commitInput, err := s.gcsBackend.MultipartBlobBegin(&MultipartBlobBeginInput{Key: src}) 568 c.Assert(err, IsNil) 569 570 addOut, err := s.gcsBackend.MultipartBlobAdd(&MultipartBlobAddInput{ 571 Commit: commitInput, 572 PartNumber: 1, 573 Body: bytes.NewReader([]byte(src)), 574 Size: uint64(len(src)), 575 Last: false, 576 }) 577 c.Assert(err, IsNil) 578 c.Assert(addOut, NotNil) 579 580 _, err = s.gcsBackend.MultipartBlobAbort(commitInput) 581 c.Assert(err, IsNil) 582 583 _, err = s.gcsBackend.HeadBlob(&HeadBlobInput{Key: src}) 584 c.Assert(err, Equals, fuse.ENOENT) 585 }