github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/test/object_test.go (about) 1 // Package integration_test. 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package integration_test 6 7 import ( 8 "bytes" 9 "encoding/hex" 10 "fmt" 11 "io" 12 "math/rand" 13 "net/http" 14 "net/url" 15 "os" 16 "reflect" 17 "strconv" 18 "strings" 19 "testing" 20 "time" 21 22 "github.com/NVIDIA/aistore/api" 23 "github.com/NVIDIA/aistore/api/apc" 24 "github.com/NVIDIA/aistore/cmn" 25 "github.com/NVIDIA/aistore/cmn/cos" 26 "github.com/NVIDIA/aistore/core/meta" 27 "github.com/NVIDIA/aistore/core/mock" 28 "github.com/NVIDIA/aistore/tools" 29 "github.com/NVIDIA/aistore/tools/docker" 30 "github.com/NVIDIA/aistore/tools/readers" 31 "github.com/NVIDIA/aistore/tools/tassert" 32 "github.com/NVIDIA/aistore/tools/tlog" 33 "github.com/NVIDIA/aistore/tools/trand" 34 "github.com/NVIDIA/aistore/xact" 35 ) 36 37 const ( 38 httpBucketURL = "http://storage.googleapis.com/minikube/" 39 httpObjectName = "minikube-0.6.iso.sha256" 40 httpObjectURL = httpBucketURL + httpObjectName 41 httpAnotherObjectName = "minikube-0.7.iso.sha256" 42 httpAnotherObjectURL = httpBucketURL + httpAnotherObjectName 43 httpObjectOutput = "ff0f444f4a01f0ec7925e6bb0cb05e84156cff9cc8de6d03102d8b3df35693e2" 44 httpAnotherObjectOutput = "aadc8b6f5720d5a493a36e1f07f71bffb588780c76498d68cd761793d2ca344e" 45 ) 46 47 const ( 48 getOP = "GET" 49 putOP = "PUT" 50 ) 51 52 func TestObjectInvalidName(t *testing.T) { 53 var ( 54 proxyURL = tools.RandomProxyURL(t) 55 baseParams = tools.BaseAPIParams() 56 bck = cmn.Bck{ 57 Name: trand.String(10), 58 Provider: apc.AIS, 59 } 60 ) 61 62 tests := []struct { 63 op string 64 objName string 65 }{ 66 {op: putOP, objName: "."}, 67 {op: putOP, objName: ".."}, 68 {op: putOP, objName: "../smth.txt"}, 69 {op: putOP, objName: "../."}, 70 {op: putOP, objName: "../.."}, 71 {op: putOP, objName: "///"}, 72 {op: putOP, objName: ""}, 73 {op: putOP, objName: "1\x00"}, 74 {op: putOP, objName: "\n"}, 75 76 {op: getOP, objName: ""}, 77 {op: getOP, objName: ".."}, 78 {op: getOP, objName: "///"}, 79 {op: getOP, objName: "...../log/aisnode.INFO"}, 80 {op: getOP, objName: "/...../log/aisnode.INFO"}, 81 {op: getOP, objName: "/\\../\\../\\../\\../log/aisnode.INFO"}, 82 {op: getOP, objName: "\\../\\../\\../\\../log/aisnode.INFO"}, 83 {op: getOP, objName: "/../../../../log/aisnode.INFO"}, 84 {op: getOP, objName: "/././../../../../log/aisnode.INFO"}, 85 } 86 87 tools.CreateBucket(t, proxyURL, bck, nil, true /*cleanup*/) 88 89 for _, test := range tests { 90 t.Run(test.op, func(t *testing.T) { 91 switch test.op { 92 case putOP: 93 reader, err := readers.NewRand(cos.KiB, cos.ChecksumNone) 94 tassert.CheckFatal(t, err) 95 _, err = api.PutObject(&api.PutArgs{ 96 BaseParams: baseParams, 97 Bck: bck, 98 ObjName: test.objName, 99 Reader: reader, 100 }) 101 tassert.Errorf(t, err != nil, "expected error to occur (object name: %q)", test.objName) 102 case getOP: 103 _, err := api.GetObjectWithValidation(baseParams, bck, test.objName, nil) 104 tassert.Errorf(t, err != nil, "expected error to occur (object name: %q)", test.objName) 105 default: 106 panic(test.op) 107 } 108 }) 109 } 110 } 111 112 func TestRemoteBucketObject(t *testing.T) { 113 var ( 114 baseParams = tools.BaseAPIParams() 115 bck = cliBck 116 ) 117 118 tools.CheckSkip(t, &tools.SkipTestArgs{Long: true, RemoteBck: true, Bck: bck}) 119 120 tests := []struct { 121 ty string 122 exists bool 123 }{ 124 {putOP, false}, 125 {putOP, true}, 126 {getOP, false}, 127 {getOP, true}, 128 } 129 130 for _, test := range tests { 131 t.Run(fmt.Sprintf("%s:%v", test.ty, test.exists), func(t *testing.T) { 132 object := trand.String(10) 133 if !test.exists { 134 bck.Name = trand.String(10) 135 } else { 136 bck.Name = cliBck.Name 137 } 138 139 reader, err := readers.NewRand(cos.KiB, cos.ChecksumNone) 140 tassert.CheckFatal(t, err) 141 142 defer api.DeleteObject(baseParams, bck, object) 143 144 switch test.ty { 145 case putOP: 146 var oah api.ObjAttrs 147 oah, err = api.PutObject(&api.PutArgs{ 148 BaseParams: baseParams, 149 Bck: bck, 150 ObjName: object, 151 Reader: reader, 152 }) 153 if err == nil { 154 oa := oah.Attrs() 155 tlog.Logf("PUT(%s) attrs %s\n", bck.Cname(object), oa.String()) 156 } 157 case getOP: 158 if test.exists { 159 _, err = api.PutObject(&api.PutArgs{ 160 BaseParams: baseParams, 161 Bck: bck, 162 ObjName: object, 163 Reader: reader, 164 }) 165 tassert.CheckFatal(t, err) 166 } 167 168 _, err = api.GetObjectWithValidation(baseParams, bck, object, nil) 169 default: 170 t.Fail() 171 } 172 173 if !test.exists { 174 if err == nil { 175 t.Errorf("expected error when doing %s on non existing %q bucket", test.ty, bck) 176 } 177 } else if err != nil { 178 t.Errorf("expected no error when executing %s on existing %q bucket(err = %v)", 179 test.ty, bck, err) 180 } 181 }) 182 } 183 } 184 185 func TestHttpProviderObjectGet(t *testing.T) { 186 var ( 187 proxyURL = tools.RandomProxyURL() 188 baseParams = tools.BaseAPIParams(proxyURL) 189 hbo, _ = cmn.NewHTTPObjPath(httpObjectURL) 190 w = bytes.NewBuffer(nil) 191 getArgs = api.GetArgs{Writer: w} 192 ) 193 t.Cleanup(func() { 194 tools.DestroyBucket(t, proxyURL, hbo.Bck) 195 }) 196 197 // get using the HTTP API 198 getArgs.Query = make(url.Values, 1) 199 getArgs.Query.Set(apc.QparamOrigURL, httpObjectURL) 200 _, err := api.GetObject(baseParams, hbo.Bck, httpObjectName, &getArgs) 201 tassert.CheckFatal(t, err) 202 tassert.Fatalf(t, strings.TrimSpace(w.String()) == httpObjectOutput, "bad content (expected:%s got:%s)", 203 httpObjectOutput, w.String()) 204 205 // get another object using /v1/objects/bucket-name/object-name endpoint 206 w.Reset() 207 getArgs.Query = make(url.Values, 1) 208 getArgs.Query.Set(apc.QparamOrigURL, httpAnotherObjectURL) 209 _, err = api.GetObject(baseParams, hbo.Bck, httpAnotherObjectName, &getArgs) 210 tassert.CheckFatal(t, err) 211 tassert.Fatalf(t, strings.TrimSpace(w.String()) == httpAnotherObjectOutput, "bad content (expected:%s got:%s)", 212 httpAnotherObjectOutput, w.String()) 213 214 // list object should contain both the objects 215 reslist, err := api.ListObjects(baseParams, hbo.Bck, &apc.LsoMsg{}, api.ListArgs{}) 216 tassert.CheckFatal(t, err) 217 tassert.Errorf(t, len(reslist.Entries) == 2, "should have exactly 2 entries in bucket") 218 219 matchCount := 0 220 for _, en := range reslist.Entries { 221 if en.Name == httpAnotherObjectName || en.Name == httpObjectName { 222 matchCount++ 223 } 224 } 225 tassert.Errorf(t, matchCount == 2, "objects %s and %s should be present in %s", 226 httpObjectName, httpAnotherObjectName, hbo.Bck) 227 } 228 229 func TestAppendObject(t *testing.T) { 230 for _, cksumType := range cos.SupportedChecksums() { 231 t.Run(cksumType, func(t *testing.T) { 232 var ( 233 proxyURL = tools.RandomProxyURL(t) 234 baseParams = tools.BaseAPIParams(proxyURL) 235 bck = cmn.Bck{ 236 Name: trand.String(10), 237 Provider: apc.AIS, 238 } 239 objName = "test/obj1" 240 241 objHead = "1111111111" 242 objBody = "222222222222222" 243 objTail = "333333333" 244 content = objHead + objBody + objTail 245 objSize = len(content) 246 ) 247 tools.CreateBucket(t, proxyURL, bck, 248 &cmn.BpropsToSet{Cksum: &cmn.CksumConfToSet{Type: apc.Ptr(cksumType)}}, 249 true, /*cleanup*/ 250 ) 251 252 var ( 253 err error 254 handle string 255 cksum = cos.NewCksumHash(cksumType) 256 ) 257 for _, body := range []string{objHead, objBody, objTail} { 258 args := api.AppendArgs{ 259 BaseParams: baseParams, 260 Bck: bck, 261 Object: objName, 262 Handle: handle, 263 Reader: cos.NewByteHandle([]byte(body)), 264 } 265 handle, err = api.AppendObject(&args) 266 tassert.CheckFatal(t, err) 267 268 _, err = cksum.H.Write([]byte(body)) 269 tassert.CheckFatal(t, err) 270 } 271 272 // Flush object with cksum to make it persistent in the bucket. 273 cksum.Finalize() 274 err = api.FlushObject(&api.FlushArgs{ 275 BaseParams: baseParams, 276 Bck: bck, 277 Object: objName, 278 Handle: handle, 279 Cksum: cksum.Clone(), 280 }) 281 tassert.CheckFatal(t, err) 282 283 // Read the object from the bucket. 284 writer := bytes.NewBuffer(nil) 285 getArgs := api.GetArgs{Writer: writer} 286 oah, err := api.GetObjectWithValidation(baseParams, bck, objName, &getArgs) 287 if !cksum.IsEmpty() { 288 tassert.CheckFatal(t, err) 289 } 290 tassert.Errorf( 291 t, writer.String() == content, 292 "invalid object content: [%d](%s), expected: [%d](%s)", 293 oah.Size(), writer.String(), objSize, content, 294 ) 295 }) 296 time.Sleep(3 * time.Second) 297 } 298 } 299 300 func TestSameBucketName(t *testing.T) { 301 var ( 302 proxyURL = tools.RandomProxyURL(t) 303 baseParams = tools.BaseAPIParams(proxyURL) 304 bckLocal = cmn.Bck{ 305 Name: cliBck.Name, 306 Provider: apc.AIS, 307 } 308 bckRemote = cliBck 309 fileName1 = "mytestobj1.txt" 310 fileName2 = "mytestobj2.txt" 311 objRange = "mytestobj{1..2}.txt" 312 dataLocal = []byte("I'm from ais:// bucket") 313 dataRemote = []byte("I'm from the cloud!") 314 files = []string{fileName1, fileName2} 315 ) 316 317 tools.CheckSkip(t, &tools.SkipTestArgs{RemoteBck: true, Bck: bckRemote}) 318 319 putArgsLocal := api.PutArgs{ 320 BaseParams: baseParams, 321 Bck: bckLocal, 322 ObjName: fileName1, 323 Reader: readers.NewBytes(dataLocal), 324 } 325 326 putArgsRemote := api.PutArgs{ 327 BaseParams: baseParams, 328 Bck: bckRemote, 329 ObjName: fileName1, 330 Reader: readers.NewBytes(dataRemote), 331 } 332 333 // PUT/GET/DEL Without ais bucket 334 tlog.Logf("Validating responses for non-existent ais bucket...\n") 335 _, err := api.PutObject(&putArgsLocal) 336 if err == nil { 337 t.Fatalf("ais bucket %s does not exist: Expected an error.", bckLocal.String()) 338 } 339 340 // PUT -> remote 341 _, err = api.PutObject(&putArgsRemote) 342 tassert.CheckFatal(t, err) 343 putArgsRemote.ObjName = fileName2 344 _, err = api.PutObject(&putArgsRemote) 345 tassert.CheckFatal(t, err) 346 putArgsRemote.ObjName = fileName1 347 348 _, err = api.GetObject(baseParams, bckLocal, fileName1, nil) 349 if err == nil { 350 t.Fatalf("ais bucket %s does not exist: Expected an error.", bckLocal.String()) 351 } 352 353 err = api.DeleteObject(baseParams, bckLocal, fileName1) 354 if err == nil { 355 t.Fatalf("ais bucket %s does not exist: Expected an error.", bckLocal.String()) 356 } 357 358 tlog.Logf("PrefetchList num=%d\n", len(files)) 359 { 360 var msg apc.PrefetchMsg 361 msg.ObjNames = files 362 prefetchListID, err := api.Prefetch(baseParams, bckRemote, msg) 363 tassert.CheckFatal(t, err) 364 args := xact.ArgsMsg{ID: prefetchListID, Kind: apc.ActPrefetchObjects, Timeout: tools.RebalanceTimeout} 365 _, err = api.WaitForXactionIC(baseParams, &args) 366 tassert.CheckFatal(t, err) 367 } 368 369 tlog.Logf("PrefetchRange %s\n", objRange) 370 { 371 var msg apc.PrefetchMsg 372 msg.Template = objRange 373 prefetchRangeID, err := api.Prefetch(baseParams, bckRemote, msg) 374 tassert.CheckFatal(t, err) 375 args := xact.ArgsMsg{ID: prefetchRangeID, Kind: apc.ActPrefetchObjects, Timeout: tools.RebalanceTimeout} 376 _, err = api.WaitForXactionIC(baseParams, &args) 377 tassert.CheckFatal(t, err) 378 } 379 380 // delete one obj from remote, and check evictions (below) 381 err = api.DeleteObject(baseParams, bckRemote, fileName1) 382 tassert.CheckFatal(t, err) 383 384 tlog.Logf("EvictList %v\n", files) 385 evictListID, err := api.EvictMultiObj(baseParams, bckRemote, files, "" /*template*/) 386 tassert.CheckFatal(t, err) 387 args := xact.ArgsMsg{ID: evictListID, Kind: apc.ActEvictObjects, Timeout: tools.RebalanceTimeout} 388 status, err := api.WaitForXactionIC(baseParams, &args) 389 tassert.CheckFatal(t, err) 390 tassert.Errorf(t, status.ErrMsg != "", "expecting errors when not finding listed objects") 391 392 tlog.Logf("EvictRange\n") 393 evictRangeID, err := api.EvictMultiObj(baseParams, bckRemote, nil /*lst objnames*/, objRange) 394 tassert.CheckFatal(t, err) 395 args = xact.ArgsMsg{ID: evictRangeID, Kind: apc.ActEvictObjects, Timeout: tools.RebalanceTimeout} 396 _, err = api.WaitForXactionIC(baseParams, &args) 397 tassert.CheckFatal(t, err) 398 399 tools.CreateBucket(t, proxyURL, bckLocal, nil, true /*cleanup*/) 400 401 // PUT 402 tlog.Logf("PUT %s and %s -> both buckets...\n", fileName1, fileName2) 403 _, err = api.PutObject(&putArgsLocal) 404 tassert.CheckFatal(t, err) 405 putArgsLocal.ObjName = fileName2 406 _, err = api.PutObject(&putArgsLocal) 407 tassert.CheckFatal(t, err) 408 409 _, err = api.PutObject(&putArgsRemote) 410 tassert.CheckFatal(t, err) 411 putArgsRemote.ObjName = fileName2 412 _, err = api.PutObject(&putArgsRemote) 413 tassert.CheckFatal(t, err) 414 415 // Check that ais bucket has 2 objects 416 tlog.Logf("Validating that ais bucket contains %s and %s ...\n", fileName1, fileName2) 417 _, err = api.HeadObject(baseParams, bckLocal, fileName1, apc.FltPresent, false /*silent*/) 418 tassert.CheckFatal(t, err) 419 _, err = api.HeadObject(baseParams, bckLocal, fileName2, apc.FltPresent, false /*silent*/) 420 tassert.CheckFatal(t, err) 421 422 // Prefetch/Evict should work 423 { 424 var msg apc.PrefetchMsg 425 msg.ObjNames = files 426 prefetchListID, err := api.Prefetch(baseParams, bckRemote, msg) 427 tassert.CheckFatal(t, err) 428 args = xact.ArgsMsg{ID: prefetchListID, Kind: apc.ActPrefetchObjects, Timeout: tools.RebalanceTimeout} 429 _, err = api.WaitForXactionIC(baseParams, &args) 430 tassert.CheckFatal(t, err) 431 } 432 433 evictListID, err = api.EvictMultiObj(baseParams, bckRemote, files, "" /*template*/) 434 tassert.CheckFatal(t, err) 435 args = xact.ArgsMsg{ID: evictListID, Kind: apc.ActEvictObjects, Timeout: tools.RebalanceTimeout} 436 _, err = api.WaitForXactionIC(baseParams, &args) 437 tassert.CheckFatal(t, err) 438 439 // Delete from cloud bucket 440 tlog.Logf("Deleting %s and %s from cloud bucket ...\n", fileName1, fileName2) 441 deleteID, err := api.DeleteMultiObj(baseParams, bckRemote, files, "" /*template*/) 442 tassert.CheckFatal(t, err) 443 args = xact.ArgsMsg{ID: deleteID, Kind: apc.ActDeleteObjects, Timeout: tools.RebalanceTimeout} 444 _, err = api.WaitForXactionIC(baseParams, &args) 445 tassert.CheckFatal(t, err) 446 447 // Delete from ais bucket 448 tlog.Logf("Deleting %s and %s from ais bucket ...\n", fileName1, fileName2) 449 deleteID, err = api.DeleteMultiObj(baseParams, bckLocal, files, "" /*template*/) 450 tassert.CheckFatal(t, err) 451 args = xact.ArgsMsg{ID: deleteID, Kind: apc.ActDeleteObjects, Timeout: tools.RebalanceTimeout} 452 _, err = api.WaitForXactionIC(baseParams, &args) 453 tassert.CheckFatal(t, err) 454 455 _, err = api.HeadObject(baseParams, bckLocal, fileName1, apc.FltPresent, false /*silent*/) 456 if err == nil || !strings.Contains(err.Error(), strconv.Itoa(http.StatusNotFound)) { 457 t.Errorf("Local file %s not deleted", fileName1) 458 } 459 _, err = api.HeadObject(baseParams, bckLocal, fileName2, apc.FltPresent, false /*silent*/) 460 if err == nil || !strings.Contains(err.Error(), strconv.Itoa(http.StatusNotFound)) { 461 t.Errorf("Local file %s not deleted", fileName2) 462 } 463 464 _, err = api.HeadObject(baseParams, bckRemote, fileName1, apc.FltExists, false /*silent*/) 465 if err == nil { 466 t.Errorf("remote file %s not deleted", fileName1) 467 } 468 _, err = api.HeadObject(baseParams, bckRemote, fileName2, apc.FltExists, false /*silent*/) 469 if err == nil { 470 t.Errorf("remote file %s not deleted", fileName2) 471 } 472 } 473 474 func Test_SameAISAndRemoteBucketName(t *testing.T) { 475 var ( 476 defLocalProps cmn.BpropsToSet 477 defRemoteProps cmn.BpropsToSet 478 479 bckLocal = cmn.Bck{ 480 Name: cliBck.Name, 481 Provider: apc.AIS, 482 } 483 bckRemote = cliBck 484 proxyURL = tools.RandomProxyURL(t) 485 baseParams = tools.BaseAPIParams(proxyURL) 486 fileName = "mytestobj1.txt" 487 dataLocal = []byte("im local") 488 dataRemote = []byte("I'm from the cloud!") 489 msg = &apc.LsoMsg{Props: "size,status", Prefix: "my"} 490 found = false 491 ) 492 493 tools.CheckSkip(t, &tools.SkipTestArgs{RemoteBck: true, Bck: bckRemote}) 494 495 tools.CreateBucket(t, proxyURL, bckLocal, nil, true /*cleanup*/) 496 497 bucketPropsLocal := &cmn.BpropsToSet{ 498 Cksum: &cmn.CksumConfToSet{ 499 Type: apc.Ptr(cos.ChecksumNone), 500 }, 501 } 502 bucketPropsRemote := &cmn.BpropsToSet{} 503 504 // Put 505 tlog.Logf("PUT %s => %s\n", fileName, bckLocal) 506 putArgs := api.PutArgs{ 507 BaseParams: baseParams, 508 Bck: bckLocal, 509 ObjName: fileName, 510 Reader: readers.NewBytes(dataLocal), 511 } 512 _, err := api.PutObject(&putArgs) 513 tassert.CheckFatal(t, err) 514 515 resLocal, err := api.ListObjects(baseParams, bckLocal, msg, api.ListArgs{}) 516 tassert.CheckFatal(t, err) 517 518 tlog.Logf("PUT %s => %s\n", fileName, bckRemote) 519 putArgs = api.PutArgs{ 520 BaseParams: baseParams, 521 Bck: bckRemote, 522 ObjName: fileName, 523 Reader: readers.NewBytes(dataRemote), 524 } 525 _, err = api.PutObject(&putArgs) 526 tassert.CheckFatal(t, err) 527 528 resRemote, err := api.ListObjects(baseParams, bckRemote, msg, api.ListArgs{}) 529 tassert.CheckFatal(t, err) 530 531 if len(resLocal.Entries) != 1 { 532 t.Fatalf("Expected number of files in ais bucket (%s) does not match: expected %v, got %v", 533 bckRemote.String(), 1, len(resLocal.Entries)) 534 } 535 536 for _, en := range resRemote.Entries { 537 if en.Name == fileName { 538 found = true 539 break 540 } 541 } 542 543 if !found { 544 t.Fatalf("File (%s) not found in cloud bucket (%s)", fileName, bckRemote.String()) 545 } 546 547 // Get 548 oahLocal, err := api.GetObject(baseParams, bckLocal, fileName, nil) 549 tassert.CheckFatal(t, err) 550 lenLocal := oahLocal.Size() 551 oahRemote, err := api.GetObject(baseParams, bckRemote, fileName, nil) 552 tassert.CheckFatal(t, err) 553 lenRemote := oahRemote.Size() 554 555 if lenLocal == lenRemote { 556 t.Errorf("Local file and cloud file have same size, expected: local (%v) cloud (%v) got: local (%v) cloud (%v)", 557 len(dataLocal), len(dataRemote), lenLocal, lenRemote) 558 } 559 560 // Delete 561 err = api.DeleteObject(baseParams, bckRemote, fileName) 562 tassert.CheckFatal(t, err) 563 564 oahLocal, err = api.GetObject(baseParams, bckLocal, fileName, nil) 565 tassert.CheckFatal(t, err) 566 lenLocal = oahLocal.Size() 567 568 // Check that local object still exists 569 if lenLocal != int64(len(dataLocal)) { 570 t.Errorf("Local file %s deleted", fileName) 571 } 572 573 // Check that cloud object is deleted 574 _, err = api.HeadObject(baseParams, bckRemote, fileName, apc.FltExistsOutside, false /*silent*/) 575 if !strings.Contains(err.Error(), strconv.Itoa(http.StatusNotFound)) { 576 t.Errorf("Remote file %s not deleted", fileName) 577 } 578 579 // Set Props Object 580 _, err = api.SetBucketProps(baseParams, bckLocal, bucketPropsLocal) 581 tassert.CheckFatal(t, err) 582 583 _, err = api.SetBucketProps(baseParams, bckRemote, bucketPropsRemote) 584 tassert.CheckFatal(t, err) 585 586 // Validate ais bucket props are set 587 localProps, err := api.HeadBucket(baseParams, bckLocal, true /* don't add */) 588 tassert.CheckFatal(t, err) 589 validateBucketProps(t, bucketPropsLocal, localProps) 590 591 // Validate cloud bucket props are set 592 cloudProps, err := api.HeadBucket(baseParams, bckRemote, true /* don't add */) 593 tassert.CheckFatal(t, err) 594 validateBucketProps(t, bucketPropsRemote, cloudProps) 595 596 // Reset ais bucket props and validate they are reset 597 _, err = api.ResetBucketProps(baseParams, bckLocal) 598 tassert.CheckFatal(t, err) 599 localProps, err = api.HeadBucket(baseParams, bckLocal, true /* don't add */) 600 tassert.CheckFatal(t, err) 601 validateBucketProps(t, &defLocalProps, localProps) 602 603 // Check if cloud bucket props remain the same 604 cloudProps, err = api.HeadBucket(baseParams, bckRemote, true /* don't add */) 605 tassert.CheckFatal(t, err) 606 validateBucketProps(t, bucketPropsRemote, cloudProps) 607 608 // Reset cloud bucket props 609 _, err = api.ResetBucketProps(baseParams, bckRemote) 610 tassert.CheckFatal(t, err) 611 cloudProps, err = api.HeadBucket(baseParams, bckRemote, true /* don't add */) 612 tassert.CheckFatal(t, err) 613 validateBucketProps(t, &defRemoteProps, cloudProps) 614 615 // Check if ais bucket props remain the same 616 localProps, err = api.HeadBucket(baseParams, bckLocal, true /* don't add */) 617 tassert.CheckFatal(t, err) 618 validateBucketProps(t, &defLocalProps, localProps) 619 } 620 621 func Test_coldgetmd5(t *testing.T) { 622 var ( 623 m = ioContext{ 624 t: t, 625 bck: cliBck, 626 num: 5, 627 fileSize: largeFileSize, 628 prefix: "md5/obj-", 629 } 630 totalSize = int64(uint64(m.num) * m.fileSize) 631 proxyURL = tools.RandomProxyURL(t) 632 propsToSet *cmn.BpropsToSet 633 ) 634 635 tools.CheckSkip(t, &tools.SkipTestArgs{RemoteBck: true, Bck: m.bck}) 636 637 m.initAndSaveState(true /*cleanup*/) 638 639 baseParams := tools.BaseAPIParams(proxyURL) 640 p, err := api.HeadBucket(baseParams, m.bck, false /* don't add */) 641 tassert.CheckFatal(t, err) 642 643 t.Cleanup(func() { 644 propsToSet = &cmn.BpropsToSet{ 645 Cksum: &cmn.CksumConfToSet{ 646 ValidateColdGet: apc.Ptr(p.Cksum.ValidateColdGet), 647 }, 648 } 649 _, err = api.SetBucketProps(baseParams, m.bck, propsToSet) 650 tassert.CheckError(t, err) 651 m.del() 652 }) 653 654 m.remotePuts(true /*evict*/) 655 656 // Disable Cold Get Validation 657 if p.Cksum.ValidateColdGet { 658 propsToSet = &cmn.BpropsToSet{ 659 Cksum: &cmn.CksumConfToSet{ 660 ValidateColdGet: apc.Ptr(false), 661 }, 662 } 663 _, err = api.SetBucketProps(baseParams, m.bck, propsToSet) 664 tassert.CheckFatal(t, err) 665 } 666 667 start := time.Now() 668 m.gets(nil /*api.GetArgs*/, false /*withValidation*/) 669 tlog.Logf("GET %s without MD5 validation: %v\n", cos.ToSizeIEC(totalSize, 0), time.Since(start)) 670 671 m.evict() 672 673 // Enable cold get validation. 674 propsToSet = &cmn.BpropsToSet{ 675 Cksum: &cmn.CksumConfToSet{ 676 ValidateColdGet: apc.Ptr(true), 677 }, 678 } 679 _, err = api.SetBucketProps(baseParams, m.bck, propsToSet) 680 tassert.CheckFatal(t, err) 681 682 start = time.Now() 683 m.gets(nil /*api.GetArgs*/, true /*withValidation*/) 684 tlog.Logf("GET %s with MD5 validation: %v\n", cos.ToSizeIEC(totalSize, 0), time.Since(start)) 685 } 686 687 func TestHeadBucket(t *testing.T) { 688 var ( 689 proxyURL = tools.RandomProxyURL(t) 690 baseParams = tools.BaseAPIParams(proxyURL) 691 bck = cmn.Bck{ 692 Name: testBucketName, 693 Provider: apc.AIS, 694 } 695 ) 696 697 tools.CreateBucket(t, proxyURL, bck, nil, true /*cleanup*/) 698 699 bckPropsToSet := &cmn.BpropsToSet{ 700 Cksum: &cmn.CksumConfToSet{ 701 ValidateWarmGet: apc.Ptr(true), 702 }, 703 LRU: &cmn.LRUConfToSet{ 704 Enabled: apc.Ptr(true), 705 }, 706 } 707 _, err := api.SetBucketProps(baseParams, bck, bckPropsToSet) 708 tassert.CheckFatal(t, err) 709 710 p, err := api.HeadBucket(baseParams, bck, false /* don't add */) 711 tassert.CheckFatal(t, err) 712 713 validateBucketProps(t, bckPropsToSet, p) 714 } 715 716 func TestHeadRemoteBucket(t *testing.T) { 717 var ( 718 proxyURL = tools.RandomProxyURL(t) 719 baseParams = tools.BaseAPIParams(proxyURL) 720 bck = cliBck 721 ) 722 723 tools.CheckSkip(t, &tools.SkipTestArgs{RemoteBck: true, Bck: bck}) 724 725 bckPropsToSet := &cmn.BpropsToSet{ 726 Cksum: &cmn.CksumConfToSet{ 727 ValidateWarmGet: apc.Ptr(true), 728 ValidateColdGet: apc.Ptr(true), 729 }, 730 LRU: &cmn.LRUConfToSet{ 731 Enabled: apc.Ptr(true), 732 }, 733 } 734 _, err := api.SetBucketProps(baseParams, bck, bckPropsToSet) 735 tassert.CheckFatal(t, err) 736 defer resetBucketProps(t, proxyURL, bck) 737 738 p, err := api.HeadBucket(baseParams, bck, true /* don't add */) 739 tassert.CheckFatal(t, err) 740 validateBucketProps(t, bckPropsToSet, p) 741 } 742 743 func TestHeadNonexistentBucket(t *testing.T) { 744 var ( 745 proxyURL = tools.RandomProxyURL(t) 746 baseParams = tools.BaseAPIParams(proxyURL) 747 ) 748 749 bucket, err := tools.GenerateNonexistentBucketName("head", baseParams) 750 tassert.CheckFatal(t, err) 751 752 bck := cmn.Bck{ 753 Name: bucket, 754 Provider: apc.AIS, 755 } 756 757 _, err = api.HeadBucket(baseParams, bck, true /* don't add */) 758 if err == nil { 759 t.Fatalf("Expected an error, but go no errors.") 760 } 761 762 status := api.HTTPStatus(err) 763 if status != http.StatusNotFound { 764 t.Errorf("Expected status %d, got %d", http.StatusNotFound, status) 765 } 766 } 767 768 // 1. PUT file 769 // 2. Change contents of the file or change XXHash 770 // 3. GET file. 771 // NOTE: The following test can only work when running on a local setup 772 // (targets are co-located with where this test is running from, because 773 // it searches a local oldFileIfo system). 774 func TestChecksumValidateOnWarmGetForRemoteBucket(t *testing.T) { 775 var ( 776 m = ioContext{ 777 t: t, 778 bck: cliBck, 779 num: 3, 780 fileSize: cos.KiB, 781 prefix: "cksum/obj-", 782 } 783 784 proxyURL = tools.RandomProxyURL(t) 785 baseParams = tools.BaseAPIParams(proxyURL) 786 ) 787 788 m.init(true /*cleanup*/) 789 790 tools.CheckSkip(t, &tools.SkipTestArgs{RemoteBck: true, Bck: m.bck}) 791 792 if docker.IsRunning() { 793 t.Skipf("test %q requires xattrs to be set, doesn't work with docker", t.Name()) 794 } 795 796 p, err := api.HeadBucket(baseParams, m.bck, false /* don't add */) 797 tassert.CheckFatal(t, err) 798 799 _ = mock.NewTarget(mock.NewBaseBownerMock( 800 meta.NewBck( 801 m.bck.Name, m.bck.Provider, cmn.NsGlobal, 802 &cmn.Bprops{Cksum: cmn.CksumConf{Type: cos.ChecksumXXHash}, Extra: p.Extra, BID: 0xa73b9f11}, 803 ), 804 )) 805 806 initMountpaths(t, proxyURL) 807 808 m.puts() 809 810 t.Cleanup(func() { 811 propsToSet := &cmn.BpropsToSet{ 812 Cksum: &cmn.CksumConfToSet{ 813 Type: apc.Ptr(p.Cksum.Type), 814 ValidateWarmGet: apc.Ptr(p.Cksum.ValidateWarmGet), 815 }, 816 } 817 _, err = api.SetBucketProps(baseParams, m.bck, propsToSet) 818 tassert.CheckError(t, err) 819 m.del() 820 }) 821 822 objName := m.objNames[0] 823 _, err = api.GetObjectWithValidation(baseParams, m.bck, objName, nil) 824 tassert.CheckError(t, err) 825 826 if !p.Cksum.ValidateWarmGet { 827 propsToSet := &cmn.BpropsToSet{ 828 Cksum: &cmn.CksumConfToSet{ 829 ValidateWarmGet: apc.Ptr(true), 830 }, 831 } 832 _, err = api.SetBucketProps(baseParams, m.bck, propsToSet) 833 tassert.CheckFatal(t, err) 834 } 835 836 fqn := findObjOnDisk(m.bck, objName) 837 tools.CheckPathExists(t, fqn, false /*dir*/) 838 oldFileInfo, _ := os.Stat(fqn) 839 840 // Test when the contents of the file are changed 841 tlog.Logf("Changing contents of the file [%s]: %s\n", objName, fqn) 842 err = os.WriteFile(fqn, []byte("Contents of this file have been changed."), cos.PermRWR) 843 tassert.CheckFatal(t, err) 844 validateGETUponFileChangeForChecksumValidation(t, proxyURL, objName, fqn, oldFileInfo) 845 846 // Test when the xxHash of the file is changed 847 objName = m.objNames[1] 848 fqn = findObjOnDisk(m.bck, objName) 849 tools.CheckPathExists(t, fqn, false /*dir*/) 850 oldFileInfo, _ = os.Stat(fqn) 851 852 tlog.Logf("Changing file xattr[%s]: %s\n", objName, fqn) 853 err = tools.SetXattrCksum(fqn, m.bck, cos.NewCksum(cos.ChecksumXXHash, "01234")) 854 tassert.CheckError(t, err) 855 validateGETUponFileChangeForChecksumValidation(t, proxyURL, objName, fqn, oldFileInfo) 856 857 // Test for no checksum algo 858 objName = m.objNames[2] 859 fqn = findObjOnDisk(m.bck, objName) 860 861 if p.Cksum.Type != cos.ChecksumNone { 862 propsToSet := &cmn.BpropsToSet{ 863 Cksum: &cmn.CksumConfToSet{ 864 Type: apc.Ptr(cos.ChecksumNone), 865 }, 866 } 867 _, err = api.SetBucketProps(baseParams, m.bck, propsToSet) 868 tassert.CheckFatal(t, err) 869 } 870 871 tlog.Logf("Changing file xattr[%s]: %s\n", objName, fqn) 872 err = tools.SetXattrCksum(fqn, m.bck, cos.NewCksum(cos.ChecksumXXHash, "01234abcde")) 873 tassert.CheckError(t, err) 874 875 _, err = api.GetObject(baseParams, m.bck, objName, nil) 876 tassert.Errorf(t, err == nil, "A GET on an object when checksum algo is none should pass. Error: %v", err) 877 } 878 879 func TestEvictRemoteBucket(t *testing.T) { 880 t.Run("Cloud/KeepMD", func(t *testing.T) { testEvictRemoteBucket(t, cliBck, true) }) 881 t.Run("Cloud/DeleteMD", func(t *testing.T) { testEvictRemoteBucket(t, cliBck, false) }) 882 t.Run("RemoteAIS", testEvictRemoteAISBucket) 883 } 884 885 func testEvictRemoteAISBucket(t *testing.T) { 886 tools.CheckSkip(t, &tools.SkipTestArgs{RequiresRemoteCluster: true}) 887 bck := cmn.Bck{ 888 Name: trand.String(10), 889 Provider: apc.AIS, 890 Ns: cmn.Ns{ 891 UUID: tools.RemoteCluster.UUID, 892 }, 893 } 894 tools.CreateBucket(t, proxyURL, bck, nil, true /*cleanup*/) 895 testEvictRemoteBucket(t, bck, false) 896 } 897 898 func testEvictRemoteBucket(t *testing.T, bck cmn.Bck, keepMD bool) { 899 var ( 900 m = ioContext{ 901 t: t, 902 bck: bck, 903 num: 10, 904 fileSize: largeFileSize, 905 prefix: "evict/obj-", 906 } 907 proxyURL = tools.RandomProxyURL(t) 908 baseParams = tools.BaseAPIParams(proxyURL) 909 ) 910 911 tools.CheckSkip(t, &tools.SkipTestArgs{RemoteBck: true, Bck: m.bck}) 912 m.initAndSaveState(true /*cleanup*/) 913 914 t.Cleanup(func() { 915 m.del() 916 resetBucketProps(t, proxyURL, m.bck) 917 }) 918 919 m.remotePuts(false /*evict*/) 920 921 // Test property, mirror is disabled for cloud bucket that hasn't been accessed, 922 // even if system config says otherwise 923 _, err := api.SetBucketProps(baseParams, m.bck, &cmn.BpropsToSet{ 924 Mirror: &cmn.MirrorConfToSet{Enabled: apc.Ptr(true)}, 925 }) 926 tassert.CheckFatal(t, err) 927 928 bProps, err := api.HeadBucket(baseParams, m.bck, true /* don't add */) 929 tassert.CheckFatal(t, err) 930 tassert.Fatalf(t, bProps.Mirror.Enabled, "test property hasn't changed") 931 932 // Wait for async mirroring to finish 933 flt := xact.ArgsMsg{Kind: apc.ActMakeNCopies, Bck: m.bck} 934 api.WaitForXactionIdle(baseParams, &flt) 935 time.Sleep(time.Second) 936 937 err = api.EvictRemoteBucket(baseParams, m.bck, keepMD) 938 tassert.CheckFatal(t, err) 939 940 for _, objName := range m.objNames { 941 exists := tools.CheckObjIsPresent(proxyURL, m.bck, objName) 942 tassert.Errorf(t, !exists, "object remains cached: %s", objName) 943 } 944 bProps, err = api.HeadBucket(baseParams, m.bck, true /* don't add */) 945 tassert.CheckFatal(t, err) 946 if keepMD { 947 tassert.Fatalf(t, bProps.Mirror.Enabled, "test property was reset") 948 } else { 949 tassert.Fatalf(t, !bProps.Mirror.Enabled, "test property not reset") 950 } 951 } 952 953 func validateGETUponFileChangeForChecksumValidation(t *testing.T, proxyURL, objName, fqn string, 954 oldFileInfo os.FileInfo) { 955 // Do a GET to see to check if a cold get was executed by comparing old and new size 956 var ( 957 baseParams = tools.BaseAPIParams(proxyURL) 958 bck = cliBck 959 ) 960 961 _, err := api.GetObjectWithValidation(baseParams, bck, objName, nil) 962 tassert.CheckError(t, err) 963 964 tools.CheckPathExists(t, fqn, false /*dir*/) 965 newFileInfo, _ := os.Stat(fqn) 966 if newFileInfo.Size() != oldFileInfo.Size() { 967 t.Errorf("Expected size: %d, Actual Size: %d", oldFileInfo.Size(), newFileInfo.Size()) 968 } 969 } 970 971 // 1. PUT file 972 // 2. Change contents of the file or change XXHash 973 // 3. GET file (first GET should fail with Internal Server Error and the 974 // second should fail with not found). 975 // 976 // Note: The following test can only work when running on a local setup 977 // (targets are co-located with where this test is running from, because 978 // it searches a local file system) 979 func TestChecksumValidateOnWarmGetForBucket(t *testing.T) { 980 var ( 981 m = ioContext{ 982 t: t, 983 bck: cmn.Bck{ 984 Provider: apc.AIS, 985 Name: trand.String(15), 986 Props: &cmn.Bprops{BID: 2}, 987 }, 988 num: 3, 989 fileSize: cos.KiB, 990 } 991 992 proxyURL = tools.RandomProxyURL(t) 993 baseParams = tools.BaseAPIParams(proxyURL) 994 _ = mock.NewTarget(mock.NewBaseBownerMock( 995 meta.NewBck( 996 m.bck.Name, apc.AIS, cmn.NsGlobal, 997 &cmn.Bprops{Cksum: cmn.CksumConf{Type: cos.ChecksumXXHash}, BID: 1}, 998 ), 999 meta.CloneBck(&m.bck), 1000 )) 1001 cksumConf = m.bck.DefaultProps(initialClusterConfig).Cksum 1002 ) 1003 1004 m.init(true /*cleanup*/) 1005 1006 if docker.IsRunning() { 1007 t.Skipf("test %q requires write access to xattrs, doesn't work with docker", t.Name()) 1008 } 1009 1010 initMountpaths(t, proxyURL) 1011 1012 tools.CreateBucket(t, proxyURL, m.bck, nil, true /*cleanup*/) 1013 1014 m.puts() 1015 1016 if !cksumConf.ValidateWarmGet { 1017 propsToSet := &cmn.BpropsToSet{ 1018 Cksum: &cmn.CksumConfToSet{ 1019 ValidateWarmGet: apc.Ptr(true), 1020 }, 1021 } 1022 _, err := api.SetBucketProps(baseParams, m.bck, propsToSet) 1023 tassert.CheckFatal(t, err) 1024 } 1025 1026 // Test changing the file content. 1027 objName := m.objNames[0] 1028 fqn := findObjOnDisk(m.bck, objName) 1029 tlog.Logf("Changing contents of the file [%s]: %s\n", objName, fqn) 1030 err := os.WriteFile(fqn, []byte("Contents of this file have been changed."), cos.PermRWR) 1031 tassert.CheckFatal(t, err) 1032 executeTwoGETsForChecksumValidation(proxyURL, m.bck, objName, t) 1033 1034 // Test changing the file xattr. 1035 objName = m.objNames[1] 1036 fqn = findObjOnDisk(m.bck, objName) 1037 tlog.Logf("Changing file xattr[%s]: %s\n", objName, fqn) 1038 err = tools.SetXattrCksum(fqn, m.bck, cos.NewCksum(cos.ChecksumXXHash, "01234abcde")) 1039 tassert.CheckError(t, err) 1040 executeTwoGETsForChecksumValidation(proxyURL, m.bck, objName, t) 1041 1042 // Test for none checksum algorithm. 1043 objName = m.objNames[2] 1044 fqn = findObjOnDisk(m.bck, objName) 1045 1046 if cksumConf.Type != cos.ChecksumNone { 1047 propsToSet := &cmn.BpropsToSet{ 1048 Cksum: &cmn.CksumConfToSet{ 1049 Type: apc.Ptr(cos.ChecksumNone), 1050 }, 1051 } 1052 _, err = api.SetBucketProps(baseParams, m.bck, propsToSet) 1053 tassert.CheckFatal(t, err) 1054 } 1055 1056 tlog.Logf("Changing file xattr[%s]: %s\n", objName, fqn) 1057 err = tools.SetXattrCksum(fqn, m.bck, cos.NewCksum(cos.ChecksumXXHash, "01234abcde")) 1058 tassert.CheckError(t, err) 1059 _, err = api.GetObject(baseParams, m.bck, objName, nil) 1060 tassert.CheckError(t, err) 1061 } 1062 1063 func executeTwoGETsForChecksumValidation(proxyURL string, bck cmn.Bck, objName string, t *testing.T) { 1064 baseParams := tools.BaseAPIParams(proxyURL) 1065 _, err := api.GetObjectWithValidation(baseParams, bck, objName, nil) 1066 if err == nil { 1067 t.Error("Error is nil, expected internal server error on a GET for an object") 1068 } else if !strings.Contains(err.Error(), "ErrBadCksum") { 1069 t.Errorf("Expected bad checksum error on a GET for a corrupted object, got [%v]", err) 1070 } 1071 // Execute another GET to make sure that the object is deleted 1072 _, err = api.GetObjectWithValidation(baseParams, bck, objName, nil) 1073 if err == nil { 1074 t.Error("Error is nil, expected not found on a second GET for a corrupted object") 1075 } else if !strings.Contains(err.Error(), "ErrNotFound") { 1076 t.Errorf("Expected Not Found on a second GET for a corrupted object, got [%v]", err) 1077 } 1078 } 1079 1080 // TODO: validate range checksums 1081 func TestRangeRead(t *testing.T) { 1082 initMountpaths(t, tools.RandomProxyURL(t)) // to run findObjOnDisk() and validate range 1083 1084 runProviderTests(t, func(t *testing.T, bck *meta.Bck) { 1085 var ( 1086 m = ioContext{ 1087 t: t, 1088 bck: bck.Clone(), 1089 num: 5, 1090 fileSize: 5271, 1091 fixedSize: true, 1092 } 1093 proxyURL = tools.RandomProxyURL(t) 1094 baseParams = tools.BaseAPIParams(proxyURL) 1095 cksumProps = bck.CksumConf() 1096 ) 1097 1098 m.init(true /*cleanup*/) 1099 m.puts() 1100 if m.bck.IsRemote() { 1101 defer m.del() 1102 } 1103 objName := m.objNames[0] 1104 1105 defer func() { 1106 tlog.Logln("Cleaning up...") 1107 propsToSet := &cmn.BpropsToSet{ 1108 Cksum: &cmn.CksumConfToSet{ 1109 EnableReadRange: apc.Ptr(cksumProps.EnableReadRange), 1110 }, 1111 } 1112 _, err := api.SetBucketProps(baseParams, m.bck, propsToSet) 1113 tassert.CheckError(t, err) 1114 }() 1115 1116 tlog.Logln("Valid range with object checksum...") 1117 // Validate that entire object checksum is being returned 1118 if cksumProps.EnableReadRange { 1119 propsToSet := &cmn.BpropsToSet{ 1120 Cksum: &cmn.CksumConfToSet{ 1121 EnableReadRange: apc.Ptr(false), 1122 }, 1123 } 1124 _, err := api.SetBucketProps(baseParams, m.bck, propsToSet) 1125 tassert.CheckFatal(t, err) 1126 } 1127 testValidCases(t, proxyURL, bck.Clone(), cksumProps.Type, m.fileSize, objName, true) 1128 1129 // Validate only that range checksum is being returned 1130 tlog.Logln("Valid range with range checksum...") 1131 if !cksumProps.EnableReadRange { 1132 propsToSet := &cmn.BpropsToSet{ 1133 Cksum: &cmn.CksumConfToSet{ 1134 EnableReadRange: apc.Ptr(true), 1135 }, 1136 } 1137 _, err := api.SetBucketProps(baseParams, bck.Clone(), propsToSet) 1138 tassert.CheckFatal(t, err) 1139 } 1140 testValidCases(t, proxyURL, m.bck, cksumProps.Type, m.fileSize, objName, false) 1141 1142 tlog.Logln("Valid range query...") 1143 verifyValidRangesQuery(t, proxyURL, m.bck, objName, "bytes=-1", 1) 1144 verifyValidRangesQuery(t, proxyURL, m.bck, objName, "bytes=0-", int64(m.fileSize)) 1145 verifyValidRangesQuery(t, proxyURL, m.bck, objName, "bytes=10-", int64(m.fileSize-10)) 1146 verifyValidRangesQuery(t, proxyURL, m.bck, objName, fmt.Sprintf("bytes=-%d", m.fileSize), int64(m.fileSize)) 1147 verifyValidRangesQuery(t, proxyURL, m.bck, objName, fmt.Sprintf("bytes=-%d", m.fileSize+2), int64(m.fileSize)) 1148 1149 tlog.Logln("======================================================================") 1150 tlog.Logln("Invalid range query:") 1151 verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "potatoes=0-1") 1152 verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes") 1153 verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, fmt.Sprintf("bytes=%d-", m.fileSize+1)) 1154 verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, fmt.Sprintf("bytes=%d-%d", m.fileSize+1, m.fileSize+2)) 1155 verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=0--1") 1156 verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=1-0") 1157 verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=-1-0") 1158 verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=-1-2") 1159 verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=10--1") 1160 verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=1-2,4-6") 1161 verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=--1") 1162 }) 1163 } 1164 1165 // TODO: validate range checksum if enabled 1166 func testValidCases(t *testing.T, proxyURL string, bck cmn.Bck, cksumType string, fileSize uint64, objName string, 1167 checkEntireObjCksum bool) { 1168 // Range-Read the entire file in 500 increments 1169 byteRange := int64(500) 1170 iterations := int64(fileSize) / byteRange 1171 for i := int64(0); i < iterations; i += byteRange { 1172 verifyValidRanges(t, proxyURL, bck, cksumType, objName, i, byteRange, byteRange, checkEntireObjCksum) 1173 } 1174 1175 verifyValidRanges(t, proxyURL, bck, cksumType, objName, byteRange*iterations, byteRange, 1176 int64(fileSize)%byteRange, checkEntireObjCksum) 1177 } 1178 1179 func verifyValidRanges(t *testing.T, proxyURL string, bck cmn.Bck, cksumType, objName string, 1180 offset, length, expectedLength int64, checkEntireObjCksum bool) { 1181 var ( 1182 w = bytes.NewBuffer(nil) 1183 rng = cmn.MakeRangeHdr(offset, length) 1184 hdr = http.Header{cos.HdrRange: []string{rng}} 1185 baseParams = tools.BaseAPIParams(proxyURL) 1186 fqn = findObjOnDisk(bck, objName) 1187 args = api.GetArgs{Writer: w, Header: hdr} 1188 ) 1189 1190 oah, err := api.GetObjectWithValidation(baseParams, bck, objName, &args) 1191 if err != nil { 1192 if !checkEntireObjCksum { 1193 t.Errorf("Failed to GET %s: %v", bck.Cname(objName), err) 1194 } else { 1195 if ckErr, ok := err.(*cmn.ErrInvalidCksum); ok { 1196 file, err := os.Open(fqn) 1197 if err != nil { 1198 t.Fatalf("Unable to open file: %s. Error: %v", fqn, err) 1199 } 1200 defer file.Close() 1201 _, cksum, err := cos.CopyAndChecksum(io.Discard, file, nil, cksumType) 1202 if err != nil { 1203 t.Errorf("Unable to compute cksum of file: %s. Error: %s", fqn, err) 1204 } 1205 if cksum.Value() != ckErr.Expected() { 1206 t.Errorf("Expected entire object checksum [%s], checksum returned in response [%s]", 1207 ckErr.Expected(), cksum) 1208 } 1209 } else { 1210 t.Errorf("Unexpected error returned [%v].", err) 1211 } 1212 } 1213 } else if oah.Size() != expectedLength { 1214 t.Errorf("number of bytes received (%d) is different from expected (%d)", oah.Size(), expectedLength) 1215 } 1216 1217 file, err := os.Open(fqn) 1218 if err != nil { 1219 t.Fatalf("Unable to open file: %s. Error: %v", fqn, err) 1220 } 1221 defer file.Close() 1222 outputBytes := w.Bytes() 1223 sectionReader := io.NewSectionReader(file, offset, length) 1224 expectedBytesBuffer := bytes.NewBuffer(nil) 1225 _, err = expectedBytesBuffer.ReadFrom(sectionReader) 1226 if err != nil { 1227 t.Errorf("Unable to read the file %s, from offset: %d and length: %d. Error: %v", fqn, offset, length, err) 1228 } 1229 expectedBytes := expectedBytesBuffer.Bytes() 1230 if len(outputBytes) != len(expectedBytes) { 1231 t.Errorf("Bytes length mismatch. Expected bytes: [%d]. Actual bytes: [%d]", len(expectedBytes), len(outputBytes)) 1232 } 1233 if int64(len(outputBytes)) != expectedLength { 1234 t.Errorf("Returned bytes don't match expected length. Expected length: [%d]. Output length: [%d]", 1235 expectedLength, len(outputBytes)) 1236 } 1237 for i := range len(expectedBytes) { 1238 if expectedBytes[i] != outputBytes[i] { 1239 t.Errorf("Byte mismatch. Expected: %v, Actual: %v", string(expectedBytes), string(outputBytes)) 1240 } 1241 } 1242 } 1243 1244 func verifyValidRangesQuery(t *testing.T, proxyURL string, bck cmn.Bck, objName, rangeQuery string, expectedLength int64) { 1245 var ( 1246 baseParams = tools.BaseAPIParams(proxyURL) 1247 hdr = http.Header{cos.HdrRange: {rangeQuery}} 1248 args = api.GetArgs{Header: hdr} 1249 ) 1250 oah, err := api.GetObject(baseParams, bck, objName, &args) 1251 tassert.CheckFatal(t, err) 1252 1253 tlog.Logf("rangeQuery=%s, n=%d\n", rangeQuery, oah.Size()) // DEBUG 1254 1255 // check size 1256 tassert.Errorf(t, oah.Size() == expectedLength, "expected range length %d, got %d", 1257 expectedLength, oah.Size()) 1258 1259 // check read range response headers 1260 respHeader := oah.RespHeader() 1261 acceptRanges := respHeader.Get(cos.HdrAcceptRanges) 1262 tassert.Errorf(t, acceptRanges == "bytes", "%q header is not set correctly: %s", 1263 cos.HdrAcceptRanges, acceptRanges) 1264 contentRange := respHeader.Get(cos.HdrContentRange) 1265 tassert.Errorf(t, contentRange != "", "%q header should be set", cos.HdrContentRange) 1266 } 1267 1268 func verifyInvalidRangesQuery(t *testing.T, proxyURL string, bck cmn.Bck, objName, rangeQuery string) { 1269 var ( 1270 baseParams = tools.BaseAPIParams(proxyURL) 1271 hdr = http.Header{cos.HdrRange: {rangeQuery}} 1272 args = api.GetArgs{Header: hdr} 1273 ) 1274 _, err := api.GetObjectWithValidation(baseParams, bck, objName, &args) 1275 tassert.Errorf(t, err != nil, "must fail for %q combination", rangeQuery) 1276 } 1277 1278 func Test_checksum(t *testing.T) { 1279 var ( 1280 m = ioContext{ 1281 t: t, 1282 bck: cliBck, 1283 num: 5, 1284 fileSize: largeFileSize, 1285 } 1286 totalSize = int64(uint64(m.num) * m.fileSize) 1287 proxyURL = tools.RandomProxyURL(t) 1288 baseParams = tools.BaseAPIParams(proxyURL) 1289 ) 1290 1291 tools.CheckSkip(t, &tools.SkipTestArgs{Long: true, RemoteBck: true, Bck: m.bck}) 1292 1293 p, err := api.HeadBucket(baseParams, m.bck, false /* don't add */) 1294 tassert.CheckFatal(t, err) 1295 1296 t.Cleanup(func() { 1297 propsToSet := &cmn.BpropsToSet{ 1298 Cksum: &cmn.CksumConfToSet{ 1299 Type: apc.Ptr(p.Cksum.Type), 1300 ValidateColdGet: apc.Ptr(p.Cksum.ValidateColdGet), 1301 }, 1302 } 1303 _, err = api.SetBucketProps(baseParams, m.bck, propsToSet) 1304 tassert.CheckError(t, err) 1305 m.del() 1306 }) 1307 1308 m.remotePuts(true /*evict*/) 1309 1310 // Disable checkum. 1311 if p.Cksum.Type != cos.ChecksumNone { 1312 propsToSet := &cmn.BpropsToSet{ 1313 Cksum: &cmn.CksumConfToSet{ 1314 Type: apc.Ptr(cos.ChecksumNone), 1315 }, 1316 } 1317 _, err = api.SetBucketProps(baseParams, m.bck, propsToSet) 1318 tassert.CheckFatal(t, err) 1319 } 1320 1321 // Disable cold get validation. 1322 if p.Cksum.ValidateColdGet { 1323 propsToSet := &cmn.BpropsToSet{ 1324 Cksum: &cmn.CksumConfToSet{ 1325 ValidateColdGet: apc.Ptr(false), 1326 }, 1327 } 1328 _, err = api.SetBucketProps(baseParams, m.bck, propsToSet) 1329 tassert.CheckFatal(t, err) 1330 } 1331 1332 start := time.Now() 1333 m.gets(nil /*api.GetArgs*/, false /*withValidate*/) 1334 tlog.Logf("GET %s without any checksum validation: %v\n", cos.ToSizeIEC(totalSize, 0), time.Since(start)) 1335 1336 m.evict() 1337 1338 propsToSet := &cmn.BpropsToSet{ 1339 Cksum: &cmn.CksumConfToSet{ 1340 Type: apc.Ptr(cos.ChecksumXXHash), 1341 ValidateColdGet: apc.Ptr(true), 1342 }, 1343 } 1344 _, err = api.SetBucketProps(baseParams, m.bck, propsToSet) 1345 tassert.CheckFatal(t, err) 1346 1347 start = time.Now() 1348 m.gets(nil /*api.GetArgs*/, true /*withValidate*/) 1349 tlog.Logf("GET %s and validate checksum: %v\n", cos.ToSizeIEC(totalSize, 0), time.Since(start)) 1350 } 1351 1352 func validateBucketProps(t *testing.T, expected *cmn.BpropsToSet, actual *cmn.Bprops) { 1353 // Apply changes on props that we have received. If after applying anything 1354 // has changed it means that the props were not applied. 1355 tmpProps := actual.Clone() 1356 tmpProps.Apply(expected) 1357 if !reflect.DeepEqual(tmpProps, actual) { 1358 t.Errorf("bucket props are not equal, expected: %+v, got: %+v", tmpProps, actual) 1359 } 1360 } 1361 1362 func resetBucketProps(t *testing.T, proxyURL string, bck cmn.Bck) { 1363 baseParams := tools.BaseAPIParams(proxyURL) 1364 _, err := api.ResetBucketProps(baseParams, bck) 1365 tassert.CheckError(t, err) 1366 } 1367 1368 func corruptSingleBitInFile(t *testing.T, bck cmn.Bck, objName string) { 1369 var ( 1370 fqn = findObjOnDisk(bck, objName) 1371 fi, err = os.Stat(fqn) 1372 b = []byte{0} 1373 ) 1374 tassert.CheckFatal(t, err) 1375 off := rand.Int63n(fi.Size()) 1376 file, err := os.OpenFile(fqn, os.O_RDWR, cos.PermRWR) 1377 tassert.CheckFatal(t, err) 1378 _, err = file.Seek(off, 0) 1379 tassert.CheckFatal(t, err) 1380 _, err = file.Read(b) 1381 tassert.CheckFatal(t, err) 1382 bit := rand.Intn(8) 1383 b[0] ^= 1 << bit 1384 _, err = file.Seek(off, 0) 1385 tassert.CheckFatal(t, err) 1386 _, err = file.Write(b) 1387 tassert.CheckFatal(t, err) 1388 file.Close() 1389 } 1390 1391 func TestPutObjectWithChecksum(t *testing.T) { 1392 var ( 1393 proxyURL = tools.RandomProxyURL(t) 1394 baseParams = tools.BaseAPIParams(proxyURL) 1395 bck = cliBck 1396 basefileName = "mytestobj.txt" 1397 objData = []byte("I am object data") 1398 badCksumVal = "badchecksum" 1399 ) 1400 1401 if bck.IsAIS() { 1402 tools.CreateBucket(t, proxyURL, bck, nil, true /*cleanup*/) 1403 } else { 1404 t.Cleanup(func() { 1405 m := ioContext{ 1406 t: t, 1407 bck: bck, 1408 } 1409 m.del(-1 /*delete all*/, 0 /* lsmsg.Flags */) 1410 }) 1411 } 1412 1413 bprops, err := api.HeadBucket(baseParams, bck, false /* don't add */) 1414 tassert.CheckFatal(t, err) 1415 1416 // Enable Cold Get Validation 1417 if !bprops.Cksum.ValidateColdGet { 1418 propsToSet := &cmn.BpropsToSet{ 1419 Cksum: &cmn.CksumConfToSet{ 1420 ValidateColdGet: apc.Ptr(true), 1421 }, 1422 } 1423 _, err = api.SetBucketProps(baseParams, bck, propsToSet) 1424 tassert.CheckFatal(t, err) 1425 1426 t.Cleanup(func() { 1427 propsToSet.Cksum.ValidateColdGet = apc.Ptr(false) 1428 _, err = api.SetBucketProps(baseParams, bck, propsToSet) 1429 tassert.CheckError(t, err) 1430 }) 1431 } 1432 1433 putArgs := api.PutArgs{ 1434 BaseParams: baseParams, 1435 Bck: bck, 1436 Reader: readers.NewBytes(objData), 1437 } 1438 for _, cksumType := range cos.SupportedChecksums() { 1439 if cksumType == cos.ChecksumNone { 1440 continue 1441 } 1442 fileName := basefileName + cksumType 1443 hasher := cos.NewCksumHash(cksumType) 1444 hasher.H.Write(objData) 1445 cksumValue := hex.EncodeToString(hasher.H.Sum(nil)) 1446 putArgs.Cksum = cos.NewCksum(cksumType, badCksumVal) 1447 putArgs.ObjName = fileName 1448 1449 _, err := api.PutObject(&putArgs) 1450 if err == nil { 1451 t.Errorf("Bad checksum provided by the user, Expected an error") 1452 } 1453 1454 _, err = api.HeadObject(baseParams, bck, fileName, apc.FltExists, false /*silent*/) 1455 if err == nil || !strings.Contains(err.Error(), strconv.Itoa(http.StatusNotFound)) { 1456 t.Errorf("Object %s exists despite bad checksum", fileName) 1457 } 1458 putArgs.Cksum = cos.NewCksum(cksumType, cksumValue) 1459 oah, err := api.PutObject(&putArgs) 1460 if err != nil { 1461 t.Errorf("Correct checksum provided, Err encountered %v", err) 1462 } 1463 op, err := api.HeadObject(baseParams, bck, fileName, apc.FltPresent, false /*silent*/) 1464 if err != nil { 1465 t.Errorf("Object %s does not exist despite correct checksum", fileName) 1466 } 1467 attrs1 := oah.Attrs() 1468 attrs2 := op.ObjAttrs 1469 tassert.Errorf(t, attrs1.Equal(&attrs2), "PUT(obj) attrs %s != %s HEAD\n", attrs1.String(), attrs2.String()) 1470 } 1471 } 1472 1473 func TestOperationsWithRanges(t *testing.T) { 1474 const ( 1475 objCnt = 50 // NOTE: must by a multiple of 10 1476 objSize = cos.KiB 1477 ) 1478 proxyURL := tools.RandomProxyURL(t) 1479 1480 runProviderTests(t, func(t *testing.T, bck *meta.Bck) { 1481 for _, evict := range []bool{false, true} { 1482 t.Run(fmt.Sprintf("evict=%t", evict), func(t *testing.T) { 1483 if evict && bck.IsAIS() { 1484 t.Skip("operation `evict` is not applicable to AIS buckets") 1485 } 1486 1487 var ( 1488 objList = make([]string, 0, objCnt) 1489 cksumType = bck.Props.Cksum.Type 1490 ) 1491 1492 for i := range objCnt / 2 { 1493 objList = append(objList, 1494 fmt.Sprintf("test/a-%04d", i), 1495 fmt.Sprintf("test/b-%04d", i), 1496 ) 1497 } 1498 for _, objName := range objList { 1499 r, _ := readers.NewRand(objSize, cksumType) 1500 _, err := api.PutObject(&api.PutArgs{ 1501 BaseParams: baseParams, 1502 Bck: bck.Clone(), 1503 ObjName: objName, 1504 Reader: r, 1505 Size: objSize, 1506 }) 1507 tassert.CheckFatal(t, err) 1508 } 1509 1510 tests := []struct { 1511 // Title to print out while testing. 1512 name string 1513 // A range of objects. 1514 rangeStr string 1515 // Total number of objects expected to be deleted/evicted. 1516 delta int 1517 }{ 1518 { 1519 "Trying to delete/evict objects with invalid prefix", 1520 "file/a-{0..10}", 1521 0, 1522 }, 1523 { 1524 "Trying to delete/evict objects out of range", 1525 "test/a-" + fmt.Sprintf("{%d..%d}", objCnt+10, objCnt+110), 1526 0, 1527 }, 1528 { 1529 fmt.Sprintf("Deleting/Evicting %d objects with prefix 'a-'", objCnt/10), 1530 "test/a-" + fmt.Sprintf("{%04d..%04d}", (objCnt-objCnt/5)/2, objCnt/2), 1531 objCnt / 10, 1532 }, 1533 { 1534 fmt.Sprintf("Deleting/Evicting %d objects (short range)", objCnt/5), 1535 "test/b-" + fmt.Sprintf("{%04d..%04d}", 1, objCnt/5), 1536 objCnt / 5, 1537 }, 1538 { 1539 "Deleting/Evicting objects with empty range", 1540 "test/b-", 1541 objCnt/2 - objCnt/5, 1542 }, 1543 } 1544 1545 var ( 1546 totalFiles = objCnt 1547 baseParams = tools.BaseAPIParams(proxyURL) 1548 waitTimeout = 10 * time.Second 1549 b = bck.Clone() 1550 ) 1551 1552 if bck.IsRemote() { 1553 waitTimeout = 40 * time.Second 1554 } 1555 1556 for idx, test := range tests { 1557 tlog.Logf("%d. %s; range: [%s]\n", idx+1, test.name, test.rangeStr) 1558 1559 var ( 1560 err error 1561 xid string 1562 kind string 1563 msg = &apc.LsoMsg{Prefix: "test/"} 1564 ) 1565 if evict { 1566 xid, err = api.EvictMultiObj(baseParams, b, nil /*lst objnames*/, test.rangeStr) 1567 msg.Flags = apc.LsObjCached 1568 kind = apc.ActEvictObjects 1569 } else { 1570 xid, err = api.DeleteMultiObj(baseParams, b, nil /*lst objnames*/, test.rangeStr) 1571 kind = apc.ActDeleteObjects 1572 } 1573 if err != nil { 1574 t.Error(err) 1575 continue 1576 } 1577 1578 args := xact.ArgsMsg{ID: xid, Kind: kind, Timeout: waitTimeout} 1579 _, err = api.WaitForXactionIC(baseParams, &args) 1580 tassert.CheckFatal(t, err) 1581 1582 totalFiles -= test.delta 1583 objList, err := api.ListObjects(baseParams, b, msg, api.ListArgs{}) 1584 if err != nil { 1585 t.Error(err) 1586 continue 1587 } 1588 if len(objList.Entries) != totalFiles { 1589 t.Errorf("Incorrect number of remaining objects: %d, should be %d", 1590 len(objList.Entries), totalFiles) 1591 continue 1592 } 1593 1594 tlog.Logf(" %d objects have been deleted/evicted\n", test.delta) 1595 } 1596 1597 msg := &apc.LsoMsg{Prefix: "test/"} 1598 lst, err := api.ListObjects(baseParams, b, msg, api.ListArgs{}) 1599 tassert.CheckFatal(t, err) 1600 1601 tlog.Logf("Cleaning up remaining objects...\n") 1602 for _, obj := range lst.Entries { 1603 err := tools.Del(proxyURL, b, obj.Name, nil, nil, true /*silent*/) 1604 tassert.CheckError(t, err) 1605 } 1606 }) 1607 } 1608 }) 1609 }