github.com/matrixorigin/matrixone@v1.2.0/pkg/fileservice/s3_fs_test.go (about) 1 // Copyright 2022 Matrix Origin 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 package fileservice 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/csv" 21 "encoding/json" 22 "encoding/xml" 23 "errors" 24 "fmt" 25 "net/http/httptrace" 26 "os" 27 "os/exec" 28 "strings" 29 "sync/atomic" 30 "testing" 31 "time" 32 33 "github.com/aws/aws-sdk-go-v2/aws" 34 "github.com/aws/aws-sdk-go-v2/config" 35 "github.com/aws/aws-sdk-go-v2/service/s3" 36 "github.com/matrixorigin/matrixone/pkg/common/moerr" 37 "github.com/matrixorigin/matrixone/pkg/logutil" 38 "github.com/matrixorigin/matrixone/pkg/perfcounter" 39 "github.com/matrixorigin/matrixone/pkg/util/toml" 40 "github.com/stretchr/testify/assert" 41 "go.uber.org/zap" 42 ) 43 44 type _TestS3Config struct { 45 Endpoint string `json:"s3-test-endpoint"` 46 Region string `json:"s3-test-region"` 47 APIKey string `json:"s3-test-key"` 48 APISecret string `json:"s3-test-secret"` 49 Bucket string `json:"s3-test-bucket"` 50 RoleARN string `json:"role-arn"` 51 } 52 53 func loadS3TestConfig() (config _TestS3Config, err error) { 54 55 // load from s3.json 56 content, err := os.ReadFile("s3.json") 57 if err != nil { 58 if os.IsNotExist(err) { 59 err = nil 60 } else { 61 return config, err 62 } 63 } 64 if len(content) > 0 { 65 err := json.Unmarshal(content, &config) 66 if err != nil { 67 return config, err 68 } 69 } 70 71 // load from env 72 loadEnv := func(name string, ptr *string) { 73 if *ptr != "" { 74 return 75 } 76 if value := os.Getenv(name); value != "" { 77 *ptr = value 78 } 79 } 80 loadEnv("endpoint", &config.Endpoint) 81 loadEnv("region", &config.Region) 82 loadEnv("apikey", &config.APIKey) 83 loadEnv("apisecret", &config.APISecret) 84 loadEnv("bucket", &config.Bucket) 85 86 return 87 } 88 89 func TestS3FS( 90 t *testing.T, 91 ) { 92 t.Run("default policy", func(t *testing.T) { 93 testS3FS(t, 0) 94 }) 95 t.Run("skip full file preloads", func(t *testing.T) { 96 testS3FS(t, SkipFullFilePreloads) 97 }) 98 } 99 100 func testS3FS( 101 t *testing.T, 102 policy Policy, 103 ) { 104 config, err := loadS3TestConfig() 105 assert.Nil(t, err) 106 if config.Endpoint == "" { 107 // no config 108 t.Skip() 109 } 110 111 t.Setenv("AWS_REGION", config.Region) 112 t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey) 113 t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret) 114 115 t.Run("file service", func(t *testing.T) { 116 testFileService(t, policy, func(name string) FileService { 117 ctx := context.Background() 118 fs, err := NewS3FS( 119 ctx, 120 ObjectStorageArguments{ 121 Name: name, 122 Endpoint: config.Endpoint, 123 Bucket: config.Bucket, 124 KeyPrefix: time.Now().Format("2006-01-02.15:04:05.000000"), 125 RoleARN: config.RoleARN, 126 }, 127 DisabledCacheConfig, 128 nil, 129 true, 130 false, 131 ) 132 assert.Nil(t, err) 133 134 // to test continuation 135 switch storage := fs.storage.(type) { 136 case *AwsSDKv2: 137 storage.listMaxKeys = 5 138 case *AwsSDKv1: 139 storage.listMaxKeys = 5 140 } 141 142 return fs 143 }) 144 }) 145 146 t.Run("list root", func(t *testing.T) { 147 ctx := context.Background() 148 fs, err := NewS3FS( 149 ctx, 150 ObjectStorageArguments{ 151 Name: "s3", 152 Endpoint: config.Endpoint, 153 Bucket: config.Bucket, 154 RoleARN: config.RoleARN, 155 }, 156 DisabledCacheConfig, 157 nil, 158 true, 159 false, 160 ) 161 assert.Nil(t, err) 162 var counterSet, counterSet2 perfcounter.CounterSet 163 ctx = perfcounter.WithCounterSet(ctx, &counterSet) 164 ctx = perfcounter.WithCounterSet(ctx, &counterSet2) 165 entries, err := fs.List(ctx, "") 166 assert.Nil(t, err) 167 assert.True(t, len(entries) > 0) 168 assert.True(t, counterSet.FileService.S3.List.Load() > 0) 169 assert.True(t, counterSet2.FileService.S3.List.Load() > 0) 170 }) 171 172 t.Run("mem caching file service", func(t *testing.T) { 173 testCachingFileService(t, func() CachingFileService { 174 ctx := context.Background() 175 fs, err := NewS3FS( 176 ctx, 177 ObjectStorageArguments{ 178 Name: "s3", 179 Endpoint: config.Endpoint, 180 Bucket: config.Bucket, 181 KeyPrefix: time.Now().Format("2006-01-02.15:04:05.000000"), 182 RoleARN: config.RoleARN, 183 }, 184 CacheConfig{ 185 MemoryCapacity: ptrTo[toml.ByteSize](128 * 1024), 186 }, 187 nil, 188 false, 189 false, 190 ) 191 assert.Nil(t, err) 192 return fs 193 }) 194 }) 195 196 t.Run("disk caching file service", func(t *testing.T) { 197 testCachingFileService(t, func() CachingFileService { 198 ctx := context.Background() 199 fs, err := NewS3FS( 200 ctx, 201 ObjectStorageArguments{ 202 Name: "s3", 203 Endpoint: config.Endpoint, 204 Bucket: config.Bucket, 205 KeyPrefix: time.Now().Format("2006-01-02.15:04:05.000000"), 206 RoleARN: config.RoleARN, 207 }, 208 CacheConfig{ 209 MemoryCapacity: ptrTo[toml.ByteSize](1), 210 DiskCapacity: ptrTo[toml.ByteSize](128 * 1024), 211 DiskPath: ptrTo(t.TempDir()), 212 }, 213 nil, 214 false, 215 false, 216 ) 217 assert.Nil(t, err) 218 return fs 219 }) 220 }) 221 222 } 223 224 func TestDynamicS3(t *testing.T) { 225 ctx := context.Background() 226 config, err := loadS3TestConfig() 227 assert.Nil(t, err) 228 if config.Endpoint == "" { 229 // no config 230 t.Skip() 231 } 232 testFileService(t, 0, func(name string) FileService { 233 buf := new(strings.Builder) 234 w := csv.NewWriter(buf) 235 err := w.Write([]string{ 236 "s3", 237 config.Endpoint, 238 config.Region, 239 config.Bucket, 240 config.APIKey, 241 config.APISecret, 242 time.Now().Format("2006-01-02.15:04:05.000000"), 243 name, 244 }) 245 assert.Nil(t, err) 246 w.Flush() 247 fs, path, err := GetForETL(ctx, nil, JoinPath( 248 buf.String(), 249 "foo/bar/baz", 250 )) 251 assert.Nil(t, err) 252 assert.Equal(t, path, "foo/bar/baz") 253 return fs 254 }) 255 } 256 257 func TestDynamicS3NoKey(t *testing.T) { 258 ctx := context.Background() 259 config, err := loadS3TestConfig() 260 assert.Nil(t, err) 261 if config.Endpoint == "" { 262 // no config 263 t.Skip() 264 } 265 t.Setenv("AWS_REGION", config.Region) 266 t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey) 267 t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret) 268 testFileService(t, 0, func(name string) FileService { 269 buf := new(strings.Builder) 270 w := csv.NewWriter(buf) 271 err := w.Write([]string{ 272 "s3-no-key", 273 config.Endpoint, 274 config.Region, 275 config.Bucket, 276 time.Now().Format("2006-01-02.15:04:05.000000"), 277 name, 278 }) 279 assert.Nil(t, err) 280 w.Flush() 281 fs, path, err := GetForETL(ctx, nil, JoinPath( 282 buf.String(), 283 "foo/bar/baz", 284 )) 285 assert.Nil(t, err) 286 assert.Equal(t, path, "foo/bar/baz") 287 return fs 288 }) 289 } 290 291 func TestDynamicS3Opts(t *testing.T) { 292 ctx := context.Background() 293 config, err := loadS3TestConfig() 294 assert.Nil(t, err) 295 if config.Endpoint == "" { 296 // no config 297 t.Skip() 298 } 299 t.Setenv("AWS_REGION", config.Region) 300 t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey) 301 t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret) 302 testFileService(t, 0, func(name string) FileService { 303 buf := new(strings.Builder) 304 w := csv.NewWriter(buf) 305 err := w.Write([]string{ 306 "s3-opts", 307 "endpoint=" + config.Endpoint, 308 "region=" + config.Region, 309 "bucket=" + config.Bucket, 310 "prefix=" + time.Now().Format("2006-01-02.15:04:05.000000"), 311 "name=" + name, 312 }) 313 assert.Nil(t, err) 314 w.Flush() 315 fs, path, err := GetForETL(ctx, nil, JoinPath( 316 buf.String(), 317 "foo/bar/baz", 318 )) 319 assert.Nil(t, err) 320 assert.Equal(t, path, "foo/bar/baz") 321 return fs 322 }) 323 } 324 325 func TestDynamicS3OptsRoleARN(t *testing.T) { 326 ctx := context.Background() 327 config, err := loadS3TestConfig() 328 assert.Nil(t, err) 329 if config.Endpoint == "" { 330 // no config 331 t.Skip() 332 } 333 t.Setenv("AWS_REGION", config.Region) 334 t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey) 335 t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret) 336 testFileService(t, 0, func(name string) FileService { 337 buf := new(strings.Builder) 338 w := csv.NewWriter(buf) 339 err := w.Write([]string{ 340 "s3-opts", 341 "endpoint=" + config.Endpoint, 342 "bucket=" + config.Bucket, 343 "prefix=" + time.Now().Format("2006-01-02.15:04:05.000000"), 344 "name=" + name, 345 "role-arn=" + config.RoleARN, 346 }) 347 assert.Nil(t, err) 348 w.Flush() 349 fs, path, err := GetForETL(ctx, nil, JoinPath( 350 buf.String(), 351 "foo/bar/baz", 352 )) 353 if err != nil { 354 t.Fatal(err) 355 } 356 assert.Equal(t, path, "foo/bar/baz") 357 return fs 358 }) 359 } 360 361 func TestDynamicS3OptsNoRegion(t *testing.T) { 362 ctx := context.Background() 363 config, err := loadS3TestConfig() 364 assert.Nil(t, err) 365 if config.Endpoint == "" { 366 // no config 367 t.Skip() 368 } 369 t.Setenv("AWS_REGION", "") 370 t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey) 371 t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret) 372 testFileService(t, 0, func(name string) FileService { 373 buf := new(strings.Builder) 374 w := csv.NewWriter(buf) 375 err := w.Write([]string{ 376 "s3-opts", 377 "bucket=" + config.Bucket, 378 "prefix=" + time.Now().Format("2006-01-02.15:04:05.000000"), 379 "name=" + name, 380 "role-arn=" + config.RoleARN, 381 }) 382 assert.Nil(t, err) 383 w.Flush() 384 fs, path, err := GetForETL(ctx, nil, JoinPath( 385 buf.String(), 386 "foo/bar/baz", 387 )) 388 if err != nil { 389 t.Fatal(err) 390 } 391 assert.Equal(t, path, "foo/bar/baz") 392 return fs 393 }) 394 } 395 396 func TestS3FSMinioServer(t *testing.T) { 397 398 // find minio executable 399 exePath, err := exec.LookPath("minio") 400 if errors.Is(err, exec.ErrNotFound) { 401 // minio not found in machine 402 return 403 } 404 405 // start minio 406 ctx, cancel := context.WithCancel(context.Background()) 407 defer cancel() 408 cmd := exec.CommandContext(ctx, 409 exePath, 410 "server", 411 t.TempDir(), 412 //"--certs-dir", filepath.Join("testdata", "minio-certs"), 413 ) 414 cmd.Env = append(os.Environ(), 415 "MINIO_SITE_NAME=test", 416 "MINIO_SITE_REGION=test", 417 ) 418 //cmd.Stderr = os.Stderr 419 //cmd.Stdout = os.Stdout 420 err = cmd.Start() 421 assert.Nil(t, err) 422 423 // set s3 credentials 424 t.Setenv("AWS_REGION", "test") 425 t.Setenv("AWS_ACCESS_KEY_ID", "minioadmin") 426 t.Setenv("AWS_SECRET_ACCESS_KEY", "minioadmin") 427 428 endpoint := "http://localhost:9000" 429 430 // create bucket 431 ctx, cancel = context.WithTimeout(ctx, time.Second*59) 432 defer cancel() 433 cfg, err := config.LoadDefaultConfig(ctx) 434 assert.Nil(t, err) 435 client := s3.NewFromConfig(cfg, 436 s3.WithEndpointResolver( 437 s3.EndpointResolverFunc( 438 func( 439 region string, 440 options s3.EndpointResolverOptions, 441 ) ( 442 ep aws.Endpoint, 443 err error, 444 ) { 445 _ = options 446 ep.URL = endpoint 447 ep.Source = aws.EndpointSourceCustom 448 ep.HostnameImmutable = true 449 ep.SigningRegion = region 450 return 451 }, 452 ), 453 ), 454 ) 455 _, err = client.CreateBucket(ctx, &s3.CreateBucketInput{ 456 Bucket: ptrTo("test"), 457 }) 458 assert.Nil(t, err) 459 460 // run test 461 t.Run("file service", func(t *testing.T) { 462 cacheDir := t.TempDir() 463 testFileService(t, 0, func(name string) FileService { 464 ctx := context.Background() 465 fs, err := NewS3FS( 466 ctx, 467 ObjectStorageArguments{ 468 Name: name, 469 Endpoint: endpoint, 470 Bucket: "test", 471 KeyPrefix: time.Now().Format("2006-01-02.15:04:05.000000"), 472 IsMinio: true, 473 }, 474 CacheConfig{ 475 DiskPath: ptrTo(cacheDir), 476 }, 477 nil, 478 true, 479 false, 480 ) 481 assert.Nil(t, err) 482 return fs 483 }) 484 }) 485 486 } 487 488 func BenchmarkS3FS(b *testing.B) { 489 config, err := loadS3TestConfig() 490 assert.Nil(b, err) 491 if config.Endpoint == "" { 492 // no config 493 b.Skip() 494 } 495 496 b.Setenv("AWS_REGION", config.Region) 497 b.Setenv("AWS_ACCESS_KEY_ID", config.APIKey) 498 b.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret) 499 500 cacheDir := b.TempDir() 501 502 b.ResetTimer() 503 504 ctx := context.Background() 505 benchmarkFileService(ctx, b, func() FileService { 506 fs, err := NewS3FS( 507 ctx, 508 ObjectStorageArguments{ 509 Name: "s3", 510 Endpoint: config.Endpoint, 511 Bucket: config.Bucket, 512 KeyPrefix: time.Now().Format("2006-01-02.15:04:05.000000"), 513 RoleARN: config.RoleARN, 514 }, 515 CacheConfig{ 516 DiskPath: ptrTo(cacheDir), 517 }, 518 nil, 519 true, 520 false, 521 ) 522 assert.Nil(b, err) 523 return fs 524 }) 525 } 526 527 func TestS3FSWithSubPath(t *testing.T) { 528 config, err := loadS3TestConfig() 529 assert.Nil(t, err) 530 if config.Endpoint == "" { 531 // no config 532 t.Skip() 533 } 534 535 t.Setenv("AWS_REGION", config.Region) 536 t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey) 537 t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret) 538 539 testFileService(t, 0, func(name string) FileService { 540 ctx := context.Background() 541 fs, err := NewS3FS( 542 ctx, 543 ObjectStorageArguments{ 544 Name: name, 545 Endpoint: config.Endpoint, 546 Bucket: config.Bucket, 547 KeyPrefix: time.Now().Format("2006-01-02.15:04:05.000000"), 548 RoleARN: config.RoleARN, 549 }, 550 DisabledCacheConfig, 551 nil, 552 true, 553 false, 554 ) 555 assert.Nil(t, err) 556 return SubPath(fs, "foo/") 557 }) 558 559 } 560 561 func BenchmarkS3ConcurrentRead(b *testing.B) { 562 config, err := loadS3TestConfig() 563 if err != nil { 564 b.Fatal(err) 565 } 566 if config.Endpoint == "" { 567 // no config 568 b.Skip() 569 } 570 b.Setenv("AWS_REGION", config.Region) 571 b.Setenv("AWS_ACCESS_KEY_ID", config.APIKey) 572 b.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret) 573 574 var numRead atomic.Int64 575 var numGotConn, numReuse, numConnect atomic.Int64 576 var numTLSHandshake atomic.Int64 577 ctx := context.Background() 578 trace := &httptrace.ClientTrace{ 579 580 GetConn: func(hostPort string) { 581 //fmt.Printf("get conn: %s\n", hostPort) 582 }, 583 584 GotConn: func(info httptrace.GotConnInfo) { 585 numGotConn.Add(1) 586 if info.Reused { 587 numReuse.Add(1) 588 } 589 //fmt.Printf("got conn: %+v\n", info) 590 }, 591 592 PutIdleConn: func(err error) { 593 //if err != nil { 594 // fmt.Printf("put idle conn failed: %v\n", err) 595 //} 596 }, 597 598 ConnectStart: func(network, addr string) { 599 numConnect.Add(1) 600 //fmt.Printf("connect %v %v\n", network, addr) 601 }, 602 603 TLSHandshakeStart: func() { 604 numTLSHandshake.Add(1) 605 }, 606 } 607 608 ctx = httptrace.WithClientTrace(ctx, trace) 609 defer func() { 610 fmt.Printf("read %v, got %v conns, reuse %v, connect %v, tls handshake %v\n", 611 numRead.Load(), 612 numGotConn.Load(), 613 numReuse.Load(), 614 numConnect.Load(), 615 numTLSHandshake.Load(), 616 ) 617 }() 618 619 fs, err := NewS3FS( 620 ctx, 621 ObjectStorageArguments{ 622 Name: "bench", 623 Endpoint: config.Endpoint, 624 Bucket: config.Bucket, 625 KeyPrefix: time.Now().Format("2006-01-02.15:04:05.000000"), 626 RoleARN: config.RoleARN, 627 }, 628 DisabledCacheConfig, 629 nil, 630 true, 631 false, 632 ) 633 if err != nil { 634 b.Fatal(err) 635 } 636 if fs == nil { 637 b.Fatal(err) 638 } 639 640 vector := IOVector{ 641 FilePath: "foo", 642 Entries: []IOEntry{ 643 { 644 Size: 3, 645 Data: []byte("foo"), 646 }, 647 }, 648 } 649 err = fs.Write(ctx, vector) 650 if err != nil { 651 b.Fatal(err) 652 } 653 654 b.ResetTimer() 655 656 b.RunParallel(func(pb *testing.PB) { 657 sem := make(chan struct{}, 128) 658 for pb.Next() { 659 sem <- struct{}{} 660 go func() { 661 defer func() { 662 <-sem 663 }() 664 err := fs.Read(ctx, &IOVector{ 665 FilePath: "foo", 666 Entries: []IOEntry{ 667 { 668 Size: 3, 669 }, 670 }, 671 }) 672 if err != nil { 673 panic(err) 674 } 675 numRead.Add(1) 676 }() 677 } 678 for i := 0; i < cap(sem); i++ { 679 sem <- struct{}{} 680 } 681 }) 682 683 } 684 685 func TestSequentialS3Read(t *testing.T) { 686 config, err := loadS3TestConfig() 687 if err != nil { 688 t.Fatal(err) 689 } 690 if config.Endpoint == "" { 691 // no config 692 t.Skip() 693 } 694 t.Setenv("AWS_REGION", config.Region) 695 t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey) 696 t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret) 697 698 var numRead atomic.Int64 699 var numGotConn, numReuse, numConnect atomic.Int64 700 var numTLSHandshake atomic.Int64 701 ctx := context.Background() 702 trace := &httptrace.ClientTrace{ 703 704 GetConn: func(hostPort string) { 705 fmt.Printf("get conn: %s\n", hostPort) 706 }, 707 708 GotConn: func(info httptrace.GotConnInfo) { 709 numGotConn.Add(1) 710 if info.Reused { 711 numReuse.Add(1) 712 } else { 713 fmt.Printf("got conn not reuse: %+v\n", info) 714 } 715 }, 716 717 PutIdleConn: func(err error) { 718 if err != nil { 719 fmt.Printf("put idle conn failed: %v\n", err) 720 } 721 }, 722 723 ConnectDone: func(network string, addr string, err error) { 724 numConnect.Add(1) 725 fmt.Printf("connect done: %v %v\n", network, addr) 726 if err != nil { 727 fmt.Printf("connect error: %v\n", err) 728 } 729 }, 730 731 TLSHandshakeStart: func() { 732 numTLSHandshake.Add(1) 733 }, 734 } 735 736 ctx = httptrace.WithClientTrace(ctx, trace) 737 defer func() { 738 fmt.Printf("read %v, got %v conns, reuse %v, connect %v, tls handshake %v\n", 739 numRead.Load(), 740 numGotConn.Load(), 741 numReuse.Load(), 742 numConnect.Load(), 743 numTLSHandshake.Load(), 744 ) 745 }() 746 747 fs, err := NewS3FS( 748 ctx, 749 ObjectStorageArguments{ 750 Name: "bench", 751 Endpoint: config.Endpoint, 752 Bucket: config.Bucket, 753 KeyPrefix: time.Now().Format("2006-01-02.15:04:05.000000"), 754 RoleARN: config.RoleARN, 755 }, 756 DisabledCacheConfig, 757 nil, 758 true, 759 false, 760 ) 761 if err != nil { 762 t.Fatal(err) 763 } 764 if fs == nil { 765 t.Fatal(err) 766 } 767 768 vector := IOVector{ 769 FilePath: "foo", 770 Entries: []IOEntry{ 771 { 772 Size: 3, 773 Data: []byte("foo"), 774 }, 775 }, 776 } 777 err = fs.Write(ctx, vector) 778 if err != nil { 779 t.Fatal(err) 780 } 781 782 for i := 0; i < 128; i++ { 783 err := fs.Read(ctx, &IOVector{ 784 FilePath: "foo", 785 Entries: []IOEntry{ 786 { 787 Size: 3, 788 }, 789 }, 790 }) 791 if err != nil { 792 t.Fatal(err) 793 } 794 numRead.Add(1) 795 } 796 797 } 798 799 func TestS3RestoreFromCache(t *testing.T) { 800 t.Skip("no longer valid since we delete cache files when calling Delete") 801 ctx := context.Background() 802 803 config, err := loadS3TestConfig() 804 assert.Nil(t, err) 805 if config.Endpoint == "" { 806 // no config 807 t.Skip() 808 } 809 810 t.Setenv("AWS_REGION", config.Region) 811 t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey) 812 t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret) 813 814 cacheDir := t.TempDir() 815 fs, err := NewS3FS( 816 ctx, 817 ObjectStorageArguments{ 818 Name: "s3", 819 Endpoint: config.Endpoint, 820 Bucket: config.Bucket, 821 KeyPrefix: time.Now().Format("2006-01-02.15:04:05.000000"), 822 RoleARN: config.RoleARN, 823 }, 824 CacheConfig{ 825 DiskPath: ptrTo(cacheDir), 826 }, 827 nil, 828 false, 829 false, 830 ) 831 assert.Nil(t, err) 832 833 // write file 834 err = fs.Write(ctx, IOVector{ 835 FilePath: "foo/bar", 836 Entries: []IOEntry{ 837 { 838 Size: 3, 839 Data: []byte("foo"), 840 }, 841 }, 842 }) 843 assert.Nil(t, err) 844 845 // write file without full file cache 846 err = fs.Write(ctx, IOVector{ 847 FilePath: "quux", 848 Entries: []IOEntry{ 849 { 850 Size: 3, 851 Data: []byte("foo"), 852 }, 853 }, 854 Policy: SkipFullFilePreloads, 855 }) 856 assert.Nil(t, err) 857 err = fs.Read(ctx, &IOVector{ 858 FilePath: "quux", 859 Entries: []IOEntry{ 860 { 861 Size: 3, 862 }, 863 }, 864 }) 865 assert.Nil(t, err) 866 867 err = fs.Delete(ctx, "foo/bar") 868 assert.Nil(t, err) 869 870 logutil.Info("cache dir", zap.Any("dir", cacheDir)) 871 872 counterSet := new(perfcounter.CounterSet) 873 ctx = perfcounter.WithCounterSet(ctx, counterSet) 874 fs.restoreFromDiskCache(ctx) 875 876 if n := counterSet.FileService.S3.Put.Load(); n != 1 { 877 t.Fatalf("got %v", n) 878 } 879 880 vec := &IOVector{ 881 FilePath: "foo/bar", 882 Entries: []IOEntry{ 883 { 884 Size: -1, 885 }, 886 }, 887 } 888 err = fs.Read(ctx, vec) 889 assert.Nil(t, err) 890 assert.Equal(t, []byte("foo"), vec.Entries[0].Data) 891 892 } 893 894 func TestS3PrefetchFile(t *testing.T) { 895 ctx := context.Background() 896 var pcSet perfcounter.CounterSet 897 ctx = perfcounter.WithCounterSet(ctx, &pcSet) 898 899 config, err := loadS3TestConfig() 900 assert.Nil(t, err) 901 if config.Endpoint == "" { 902 // no config 903 t.Skip() 904 } 905 906 t.Setenv("AWS_REGION", config.Region) 907 t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey) 908 t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret) 909 910 cacheDir := t.TempDir() 911 fs, err := NewS3FS( 912 ctx, 913 ObjectStorageArguments{ 914 Name: "s3", 915 Endpoint: config.Endpoint, 916 Bucket: config.Bucket, 917 KeyPrefix: time.Now().Format("2006-01-02.15:04:05.000000"), 918 RoleARN: config.RoleARN, 919 }, 920 CacheConfig{ 921 DiskPath: ptrTo(cacheDir), 922 }, 923 nil, 924 false, 925 false, 926 ) 927 assert.Nil(t, err) 928 929 data := bytes.Repeat([]byte("abcd"), 2<<20) 930 931 // write file 932 err = fs.Write(ctx, IOVector{ 933 FilePath: "foo/bar", 934 Entries: []IOEntry{ 935 { 936 Size: int64(len(data)), 937 Data: data, 938 }, 939 }, 940 Policy: SkipDiskCache | SkipMemoryCache, 941 }) 942 assert.Nil(t, err) 943 assert.Equal(t, int64(0), pcSet.FileService.Cache.Disk.WriteFile.Load()) 944 945 // preload 946 err = fs.PrefetchFile(ctx, "foo/bar") 947 assert.Nil(t, err) 948 assert.Equal(t, int64(1), pcSet.FileService.Cache.Disk.WriteFile.Load()) 949 err = fs.PrefetchFile(ctx, "foo/bar") 950 assert.Nil(t, err) 951 assert.Equal(t, int64(1), pcSet.FileService.Cache.Disk.WriteFile.Load()) 952 953 // read 954 lastHit := int64(0) 955 for i := 1; i < len(data); i += len(data) / 1000 { 956 vec := &IOVector{ 957 FilePath: "foo/bar", 958 Entries: []IOEntry{ 959 { 960 Size: int64(i), 961 }, 962 }, 963 } 964 err = fs.Read(ctx, vec) 965 assert.Nil(t, err) 966 assert.Equal(t, data[:i], vec.Entries[0].Data) 967 assert.Equal(t, lastHit+1, pcSet.FileService.Cache.Disk.Hit.Load()) 968 lastHit++ 969 } 970 971 } 972 973 type S3CredentialTestCase struct { 974 Skip bool 975 ObjectStorageArguments 976 } 977 978 var s3CredentialTestCases = func() []S3CredentialTestCase { 979 content, err := os.ReadFile("s3_fs_test_new.xml") 980 if os.IsNotExist(err) { 981 return nil 982 } 983 if err != nil { 984 panic(err) 985 } 986 var spec struct { 987 XMLName xml.Name `xml:"Spec"` 988 Cases []S3CredentialTestCase `xml:"Case"` 989 } 990 err = xml.Unmarshal(content, &spec) 991 if err != nil { 992 panic(err) 993 } 994 return spec.Cases 995 }() 996 997 func TestNewS3FSFromSpec(t *testing.T) { 998 if len(s3CredentialTestCases) == 0 { 999 t.Skip("no case") 1000 } 1001 1002 for _, kase := range s3CredentialTestCases { 1003 if kase.Skip { 1004 continue 1005 } 1006 1007 t.Run(kase.Name, func(t *testing.T) { 1008 1009 ctx := context.Background() 1010 fs, err := NewS3FS( 1011 ctx, 1012 kase.ObjectStorageArguments, 1013 DisabledCacheConfig, 1014 nil, 1015 true, 1016 false, 1017 ) 1018 assert.Nil(t, err) 1019 _ = fs 1020 1021 }) 1022 1023 t.Run(kase.Name+" bad bucket", func(t *testing.T) { 1024 1025 args := kase.ObjectStorageArguments 1026 args.Bucket = args.Bucket + "foobarbaz" 1027 ctx := context.Background() 1028 _, err := NewS3FS( 1029 ctx, 1030 args, 1031 DisabledCacheConfig, 1032 nil, 1033 true, 1034 false, 1035 ) 1036 if err == nil { 1037 t.Fatal("should fail") 1038 } 1039 1040 }) 1041 } 1042 1043 } 1044 1045 func TestNewS3NoDefaultCredential(t *testing.T) { 1046 ctx := context.Background() 1047 _, err := NewS3FS( 1048 ctx, 1049 ObjectStorageArguments{ 1050 Endpoint: "aliyuncs.com", 1051 }, 1052 DisabledCacheConfig, 1053 nil, 1054 true, 1055 true, 1056 ) 1057 assert.True(t, moerr.IsMoErrCode(err, moerr.ErrInvalidInput)) 1058 assert.True(t, strings.Contains(err.Error(), "no valid credentials")) 1059 }