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  }