github.com/matrixorigin/matrixone@v0.7.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  	"context"
    19  	"encoding/csv"
    20  	"encoding/json"
    21  	"errors"
    22  	"os"
    23  	"os/exec"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/aws/aws-sdk-go-v2/aws"
    29  	"github.com/aws/aws-sdk-go-v2/config"
    30  	"github.com/aws/aws-sdk-go-v2/service/s3"
    31  	"github.com/stretchr/testify/assert"
    32  )
    33  
    34  type _TestS3Config struct {
    35  	Endpoint  string `json:"s3-test-endpoint"`
    36  	Region    string `json:"s3-test-region"`
    37  	APIKey    string `json:"s3-test-key"`
    38  	APISecret string `json:"s3-test-secret"`
    39  	Bucket    string `json:"s3-test-bucket"`
    40  	RoleARN   string `json:"role-arn"`
    41  }
    42  
    43  func loadS3TestConfig() (config _TestS3Config, err error) {
    44  
    45  	// load from s3.json
    46  	content, err := os.ReadFile("s3.json")
    47  	if err != nil {
    48  		if os.IsNotExist(err) {
    49  			err = nil
    50  		} else {
    51  			return config, err
    52  		}
    53  	}
    54  	if len(content) > 0 {
    55  		err := json.Unmarshal(content, &config)
    56  		if err != nil {
    57  			return config, err
    58  		}
    59  	}
    60  
    61  	// load from env
    62  	loadEnv := func(name string, ptr *string) {
    63  		if *ptr != "" {
    64  			return
    65  		}
    66  		if value := os.Getenv(name); value != "" {
    67  			*ptr = value
    68  		}
    69  	}
    70  	loadEnv("endpoint", &config.Endpoint)
    71  	loadEnv("region", &config.Region)
    72  	loadEnv("apikey", &config.APIKey)
    73  	loadEnv("apisecret", &config.APISecret)
    74  	loadEnv("bucket", &config.Bucket)
    75  
    76  	return
    77  }
    78  
    79  func TestS3FS(t *testing.T) {
    80  	config, err := loadS3TestConfig()
    81  	assert.Nil(t, err)
    82  	if config.Endpoint == "" {
    83  		// no config
    84  		t.Skip()
    85  	}
    86  
    87  	t.Setenv("AWS_REGION", config.Region)
    88  	t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey)
    89  	t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret)
    90  
    91  	t.Run("file service", func(t *testing.T) {
    92  		cacheDir := t.TempDir()
    93  		testFileService(t, func(name string) FileService {
    94  
    95  			fs, err := NewS3FS(
    96  				"",
    97  				name,
    98  				config.Endpoint,
    99  				config.Bucket,
   100  				time.Now().Format("2006-01-02.15:04:05.000000"),
   101  				-1,
   102  				-1,
   103  				cacheDir,
   104  			)
   105  			assert.Nil(t, err)
   106  
   107  			return fs
   108  		})
   109  	})
   110  
   111  	t.Run("list root", func(t *testing.T) {
   112  		cacheDir := t.TempDir()
   113  		fs, err := NewS3FS(
   114  			"",
   115  			"s3",
   116  			config.Endpoint,
   117  			config.Bucket,
   118  			"",
   119  			-1,
   120  			-1,
   121  			cacheDir,
   122  		)
   123  		assert.Nil(t, err)
   124  		ctx := context.Background()
   125  		entries, err := fs.List(ctx, "")
   126  		assert.Nil(t, err)
   127  		assert.True(t, len(entries) > 0)
   128  	})
   129  
   130  	t.Run("caching file service", func(t *testing.T) {
   131  		cacheDir := t.TempDir()
   132  		testCachingFileService(t, func() CachingFileService {
   133  			fs, err := NewS3FS(
   134  				"",
   135  				"s3",
   136  				config.Endpoint,
   137  				config.Bucket,
   138  				time.Now().Format("2006-01-02.15:04:05.000000"),
   139  				128*1024,
   140  				128*1024,
   141  				cacheDir,
   142  			)
   143  			assert.Nil(t, err)
   144  			return fs
   145  		})
   146  	})
   147  
   148  }
   149  
   150  func TestDynamicS3(t *testing.T) {
   151  	config, err := loadS3TestConfig()
   152  	assert.Nil(t, err)
   153  	if config.Endpoint == "" {
   154  		// no config
   155  		t.Skip()
   156  	}
   157  	testFileService(t, func(name string) FileService {
   158  		buf := new(strings.Builder)
   159  		w := csv.NewWriter(buf)
   160  		err := w.Write([]string{
   161  			"s3",
   162  			config.Endpoint,
   163  			config.Region,
   164  			config.Bucket,
   165  			config.APIKey,
   166  			config.APISecret,
   167  			time.Now().Format("2006-01-02.15:04:05.000000"),
   168  			name,
   169  		})
   170  		assert.Nil(t, err)
   171  		w.Flush()
   172  		fs, path, err := GetForETL(nil, JoinPath(
   173  			buf.String(),
   174  			"foo/bar/baz",
   175  		))
   176  		assert.Nil(t, err)
   177  		assert.Equal(t, path, "foo/bar/baz")
   178  		return fs
   179  	})
   180  }
   181  
   182  func TestDynamicS3NoKey(t *testing.T) {
   183  	config, err := loadS3TestConfig()
   184  	assert.Nil(t, err)
   185  	if config.Endpoint == "" {
   186  		// no config
   187  		t.Skip()
   188  	}
   189  	t.Setenv("AWS_REGION", config.Region)
   190  	t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey)
   191  	t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret)
   192  	testFileService(t, func(name string) FileService {
   193  		buf := new(strings.Builder)
   194  		w := csv.NewWriter(buf)
   195  		err := w.Write([]string{
   196  			"s3-no-key",
   197  			config.Endpoint,
   198  			config.Region,
   199  			config.Bucket,
   200  			time.Now().Format("2006-01-02.15:04:05.000000"),
   201  			name,
   202  		})
   203  		assert.Nil(t, err)
   204  		w.Flush()
   205  		fs, path, err := GetForETL(nil, JoinPath(
   206  			buf.String(),
   207  			"foo/bar/baz",
   208  		))
   209  		assert.Nil(t, err)
   210  		assert.Equal(t, path, "foo/bar/baz")
   211  		return fs
   212  	})
   213  }
   214  
   215  func TestDynamicS3Opts(t *testing.T) {
   216  	config, err := loadS3TestConfig()
   217  	assert.Nil(t, err)
   218  	if config.Endpoint == "" {
   219  		// no config
   220  		t.Skip()
   221  	}
   222  	t.Setenv("AWS_REGION", config.Region)
   223  	t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey)
   224  	t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret)
   225  	testFileService(t, func(name string) FileService {
   226  		buf := new(strings.Builder)
   227  		w := csv.NewWriter(buf)
   228  		err := w.Write([]string{
   229  			"s3-opts",
   230  			"endpoint=" + config.Endpoint,
   231  			"region=" + config.Region,
   232  			"bucket=" + config.Bucket,
   233  			"prefix=" + time.Now().Format("2006-01-02.15:04:05.000000"),
   234  			"name=" + name,
   235  		})
   236  		assert.Nil(t, err)
   237  		w.Flush()
   238  		fs, path, err := GetForETL(nil, JoinPath(
   239  			buf.String(),
   240  			"foo/bar/baz",
   241  		))
   242  		assert.Nil(t, err)
   243  		assert.Equal(t, path, "foo/bar/baz")
   244  		return fs
   245  	})
   246  }
   247  
   248  func TestDynamicS3OptsRoleARN(t *testing.T) {
   249  	config, err := loadS3TestConfig()
   250  	assert.Nil(t, err)
   251  	if config.Endpoint == "" {
   252  		// no config
   253  		t.Skip()
   254  	}
   255  	t.Setenv("AWS_REGION", config.Region)
   256  	t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey)
   257  	t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret)
   258  	testFileService(t, func(name string) FileService {
   259  		buf := new(strings.Builder)
   260  		w := csv.NewWriter(buf)
   261  		err := w.Write([]string{
   262  			"s3-opts",
   263  			"endpoint=" + config.Endpoint,
   264  			"bucket=" + config.Bucket,
   265  			"prefix=" + time.Now().Format("2006-01-02.15:04:05.000000"),
   266  			"name=" + name,
   267  			"role-arn=" + config.RoleARN,
   268  		})
   269  		assert.Nil(t, err)
   270  		w.Flush()
   271  		fs, path, err := GetForETL(nil, JoinPath(
   272  			buf.String(),
   273  			"foo/bar/baz",
   274  		))
   275  		if err != nil {
   276  			t.Fatal(err)
   277  		}
   278  		assert.Equal(t, path, "foo/bar/baz")
   279  		return fs
   280  	})
   281  }
   282  
   283  func TestDynamicS3OptsNoRegion(t *testing.T) {
   284  	config, err := loadS3TestConfig()
   285  	assert.Nil(t, err)
   286  	if config.Endpoint == "" {
   287  		// no config
   288  		t.Skip()
   289  	}
   290  	t.Setenv("AWS_REGION", "")
   291  	t.Setenv("AWS_ACCESS_KEY_ID", config.APIKey)
   292  	t.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret)
   293  	testFileService(t, func(name string) FileService {
   294  		buf := new(strings.Builder)
   295  		w := csv.NewWriter(buf)
   296  		err := w.Write([]string{
   297  			"s3-opts",
   298  			"bucket=" + config.Bucket,
   299  			"prefix=" + time.Now().Format("2006-01-02.15:04:05.000000"),
   300  			"name=" + name,
   301  			"role-arn=" + config.RoleARN,
   302  		})
   303  		assert.Nil(t, err)
   304  		w.Flush()
   305  		fs, path, err := GetForETL(nil, JoinPath(
   306  			buf.String(),
   307  			"foo/bar/baz",
   308  		))
   309  		if err != nil {
   310  			t.Fatal(err)
   311  		}
   312  		assert.Equal(t, path, "foo/bar/baz")
   313  		return fs
   314  	})
   315  }
   316  
   317  func TestS3FSMinioServer(t *testing.T) {
   318  
   319  	// find minio executable
   320  	exePath, err := exec.LookPath("minio")
   321  	if errors.Is(err, exec.ErrNotFound) {
   322  		// minio not found in machine
   323  		return
   324  	}
   325  
   326  	// start minio
   327  	ctx, cancel := context.WithCancel(context.Background())
   328  	defer cancel()
   329  	cmd := exec.CommandContext(ctx,
   330  		exePath,
   331  		"server",
   332  		t.TempDir(),
   333  		//"--certs-dir", filepath.Join("testdata", "minio-certs"),
   334  	)
   335  	cmd.Env = append(os.Environ(),
   336  		"MINIO_SITE_NAME=test",
   337  		"MINIO_SITE_REGION=test",
   338  	)
   339  	//cmd.Stderr = os.Stderr
   340  	//cmd.Stdout = os.Stdout
   341  	err = cmd.Start()
   342  	assert.Nil(t, err)
   343  
   344  	// set s3 credentials
   345  	t.Setenv("AWS_REGION", "test")
   346  	t.Setenv("AWS_ACCESS_KEY_ID", "minioadmin")
   347  	t.Setenv("AWS_SECRET_ACCESS_KEY", "minioadmin")
   348  
   349  	endpoint := "http://localhost:9000"
   350  
   351  	// create bucket
   352  	ctx, cancel = context.WithTimeout(ctx, time.Second*59)
   353  	defer cancel()
   354  	cfg, err := config.LoadDefaultConfig(ctx)
   355  	assert.Nil(t, err)
   356  	client := s3.NewFromConfig(cfg,
   357  		s3.WithEndpointResolver(
   358  			s3.EndpointResolverFunc(
   359  				func(
   360  					region string,
   361  					options s3.EndpointResolverOptions,
   362  				) (
   363  					ep aws.Endpoint,
   364  					err error,
   365  				) {
   366  					_ = options
   367  					ep.URL = endpoint
   368  					ep.Source = aws.EndpointSourceCustom
   369  					ep.HostnameImmutable = true
   370  					ep.SigningRegion = region
   371  					return
   372  				},
   373  			),
   374  		),
   375  	)
   376  	_, err = client.CreateBucket(ctx, &s3.CreateBucketInput{
   377  		Bucket: ptrTo("test"),
   378  	})
   379  	assert.Nil(t, err)
   380  
   381  	// run test
   382  	t.Run("file service", func(t *testing.T) {
   383  		cacheDir := t.TempDir()
   384  		testFileService(t, func(name string) FileService {
   385  
   386  			fs, err := NewS3FSOnMinio(
   387  				"",
   388  				name,
   389  				endpoint,
   390  				"test",
   391  				time.Now().Format("2006-01-02.15:04:05.000000"),
   392  				-1,
   393  				-1,
   394  				cacheDir,
   395  			)
   396  			assert.Nil(t, err)
   397  
   398  			return fs
   399  		})
   400  	})
   401  
   402  }
   403  
   404  func BenchmarkS3FS(b *testing.B) {
   405  	config, err := loadS3TestConfig()
   406  	assert.Nil(b, err)
   407  	if config.Endpoint == "" {
   408  		// no config
   409  		b.Skip()
   410  	}
   411  
   412  	b.Setenv("AWS_REGION", config.Region)
   413  	b.Setenv("AWS_ACCESS_KEY_ID", config.APIKey)
   414  	b.Setenv("AWS_SECRET_ACCESS_KEY", config.APISecret)
   415  
   416  	cacheDir := b.TempDir()
   417  
   418  	b.ResetTimer()
   419  
   420  	benchmarkFileService(b, func() FileService {
   421  		fs, err := NewS3FS(
   422  			"",
   423  			"s3",
   424  			config.Endpoint,
   425  			config.Bucket,
   426  			time.Now().Format("2006-01-02.15:04:05.000000"),
   427  			-1,
   428  			-1,
   429  			cacheDir,
   430  		)
   431  		assert.Nil(b, err)
   432  		return fs
   433  	})
   434  }