github.com/lyft/flytestdlib@v0.3.12-0.20210213045714-8cdd111ecda1/storage/stow_store_test.go (about)

     1  package storage
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/url"
    10  	"os"
    11  	"path/filepath"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/graymeta/stow/google"
    16  	"github.com/graymeta/stow/local"
    17  	"github.com/pkg/errors"
    18  
    19  	"github.com/graymeta/stow"
    20  	"github.com/stretchr/testify/assert"
    21  
    22  	"github.com/lyft/flytestdlib/config"
    23  	"github.com/lyft/flytestdlib/contextutils"
    24  	"github.com/lyft/flytestdlib/internal/utils"
    25  	"github.com/lyft/flytestdlib/promutils"
    26  	"github.com/lyft/flytestdlib/promutils/labeled"
    27  )
    28  
    29  type mockStowLoc struct {
    30  	stow.Location
    31  	ContainerCb func(id string) (stow.Container, error)
    32  }
    33  
    34  func (m mockStowLoc) Container(id string) (stow.Container, error) {
    35  	return m.ContainerCb(id)
    36  }
    37  
    38  type mockStowContainer struct {
    39  	id    string
    40  	items map[string]mockStowItem
    41  }
    42  
    43  func (m mockStowContainer) ID() string {
    44  	return m.id
    45  }
    46  
    47  func (m mockStowContainer) Name() string {
    48  	return m.id
    49  }
    50  
    51  func (m mockStowContainer) Item(id string) (stow.Item, error) {
    52  	if item, found := m.items[id]; found {
    53  		return item, nil
    54  	}
    55  
    56  	return nil, stow.ErrNotFound
    57  }
    58  
    59  func (mockStowContainer) Items(prefix, cursor string, count int) ([]stow.Item, string, error) {
    60  	return []stow.Item{}, "", nil
    61  }
    62  
    63  func (mockStowContainer) RemoveItem(id string) error {
    64  	return nil
    65  }
    66  
    67  func (m *mockStowContainer) Put(name string, r io.Reader, size int64, metadata map[string]interface{}) (stow.Item, error) {
    68  	item := mockStowItem{url: name, size: size}
    69  	m.items[name] = item
    70  	return item, nil
    71  }
    72  
    73  func newMockStowContainer(id string) *mockStowContainer {
    74  	return &mockStowContainer{
    75  		id:    id,
    76  		items: map[string]mockStowItem{},
    77  	}
    78  }
    79  
    80  type mockStowItem struct {
    81  	url  string
    82  	size int64
    83  }
    84  
    85  func (m mockStowItem) ID() string {
    86  	return m.url
    87  }
    88  
    89  func (m mockStowItem) Name() string {
    90  	return m.url
    91  }
    92  
    93  func (m mockStowItem) URL() *url.URL {
    94  	u, err := url.Parse(m.url)
    95  	if err != nil {
    96  		panic(err)
    97  	}
    98  
    99  	return u
   100  }
   101  
   102  func (m mockStowItem) Size() (int64, error) {
   103  	return m.size, nil
   104  }
   105  
   106  func (mockStowItem) Open() (io.ReadCloser, error) {
   107  	return ioutil.NopCloser(bytes.NewReader([]byte{})), nil
   108  }
   109  
   110  func (mockStowItem) ETag() (string, error) {
   111  	return "", nil
   112  }
   113  
   114  func (mockStowItem) LastMod() (time.Time, error) {
   115  	return time.Now(), nil
   116  }
   117  
   118  func (mockStowItem) Metadata() (map[string]interface{}, error) {
   119  	return map[string]interface{}{}, nil
   120  }
   121  
   122  func TestStowStore_ReadRaw(t *testing.T) {
   123  	labeled.SetMetricKeys(contextutils.ProjectKey, contextutils.DomainKey, contextutils.WorkflowIDKey, contextutils.TaskIDKey)
   124  
   125  	const container = "container"
   126  	t.Run("Happy Path", func(t *testing.T) {
   127  		testScope := promutils.NewTestScope()
   128  		fn := fQNFn["s3"]
   129  		s, err := NewStowRawStore(fn(container), &mockStowLoc{
   130  			ContainerCb: func(id string) (stow.Container, error) {
   131  				if id == container {
   132  					return newMockStowContainer(container), nil
   133  				}
   134  				return nil, fmt.Errorf("container is not supported")
   135  			},
   136  		}, false, testScope)
   137  		assert.NoError(t, err)
   138  		err = s.WriteRaw(context.TODO(), DataReference("s3://container/path"), 0, Options{}, bytes.NewReader([]byte{}))
   139  		assert.NoError(t, err)
   140  		metadata, err := s.Head(context.TODO(), DataReference("s3://container/path"))
   141  		assert.NoError(t, err)
   142  		assert.True(t, metadata.Exists())
   143  		raw, err := s.ReadRaw(context.TODO(), DataReference("s3://container/path"))
   144  		assert.NoError(t, err)
   145  		rawBytes, err := ioutil.ReadAll(raw)
   146  		assert.NoError(t, err)
   147  		assert.Equal(t, 0, len(rawBytes))
   148  		assert.Equal(t, DataReference("s3://container"), s.GetBaseContainerFQN(context.TODO()))
   149  	})
   150  
   151  	t.Run("Exceeds limit", func(t *testing.T) {
   152  		testScope := promutils.NewTestScope()
   153  		fn := fQNFn["s3"]
   154  		s, err := NewStowRawStore(fn(container), &mockStowLoc{
   155  			ContainerCb: func(id string) (stow.Container, error) {
   156  				if id == container {
   157  					return newMockStowContainer(container), nil
   158  				}
   159  				return nil, fmt.Errorf("container is not supported")
   160  			},
   161  		}, false, testScope)
   162  		assert.NoError(t, err)
   163  		err = s.WriteRaw(context.TODO(), DataReference("s3://container/path"), 3*MiB, Options{}, bytes.NewReader([]byte{}))
   164  		assert.NoError(t, err)
   165  		metadata, err := s.Head(context.TODO(), DataReference("s3://container/path"))
   166  		assert.NoError(t, err)
   167  		assert.True(t, metadata.Exists())
   168  		_, err = s.ReadRaw(context.TODO(), DataReference("s3://container/path"))
   169  		assert.Error(t, err)
   170  		assert.True(t, IsExceedsLimit(err))
   171  		assert.NotNil(t, errors.Cause(err))
   172  	})
   173  
   174  	t.Run("Happy Path multi-container enabled", func(t *testing.T) {
   175  		testScope := promutils.NewTestScope()
   176  		fn := fQNFn["s3"]
   177  		s, err := NewStowRawStore(fn(container), &mockStowLoc{
   178  			ContainerCb: func(id string) (stow.Container, error) {
   179  				if id == container {
   180  					return newMockStowContainer(container), nil
   181  				} else if id == "bad-container" {
   182  					return newMockStowContainer("bad-container"), nil
   183  				}
   184  				return nil, fmt.Errorf("container is not supported")
   185  			},
   186  		}, true, testScope)
   187  		assert.NoError(t, err)
   188  		err = s.WriteRaw(context.TODO(), "s3://bad-container/path", 0, Options{}, bytes.NewReader([]byte{}))
   189  		assert.NoError(t, err)
   190  		metadata, err := s.Head(context.TODO(), "s3://bad-container/path")
   191  		if assert.NoError(t, err) {
   192  			assert.True(t, metadata.Exists())
   193  		}
   194  		raw, err := s.ReadRaw(context.TODO(), "s3://bad-container/path")
   195  		assert.NoError(t, err)
   196  		rawBytes, err := ioutil.ReadAll(raw)
   197  		assert.NoError(t, err)
   198  		assert.Equal(t, 0, len(rawBytes))
   199  		assert.Equal(t, DataReference("s3://container"), s.GetBaseContainerFQN(context.TODO()))
   200  	})
   201  
   202  	t.Run("Happy Path multi-container bad", func(t *testing.T) {
   203  		testScope := promutils.NewTestScope()
   204  		fn := fQNFn["s3"]
   205  		s, err := NewStowRawStore(fn(container), &mockStowLoc{
   206  			ContainerCb: func(id string) (stow.Container, error) {
   207  				if id == container {
   208  					return newMockStowContainer(container), nil
   209  				}
   210  				return nil, fmt.Errorf("container is not supported")
   211  			},
   212  		}, true, testScope)
   213  		assert.NoError(t, err)
   214  		err = s.WriteRaw(context.TODO(), "s3://bad-container/path", 0, Options{}, bytes.NewReader([]byte{}))
   215  		assert.Error(t, err)
   216  		_, err = s.Head(context.TODO(), "s3://bad-container/path")
   217  		assert.Error(t, err)
   218  		_, err = s.ReadRaw(context.TODO(), "s3://bad-container/path")
   219  		assert.Error(t, err)
   220  	})
   221  }
   222  
   223  func TestNewLocalStore(t *testing.T) {
   224  	labeled.SetMetricKeys(contextutils.ProjectKey, contextutils.DomainKey, contextutils.WorkflowIDKey, contextutils.TaskIDKey)
   225  	t.Run("Valid config", func(t *testing.T) {
   226  		testScope := promutils.NewTestScope()
   227  		store, err := newStowRawStore(&Config{
   228  			Stow: &StowConfig{
   229  				Kind: local.Kind,
   230  				Config: map[string]string{
   231  					local.ConfigKeyPath: "./",
   232  				},
   233  			},
   234  			InitContainer: "testdata",
   235  		}, testScope.NewSubScope("x"))
   236  
   237  		assert.NoError(t, err)
   238  		assert.NotNil(t, store)
   239  
   240  		// Stow local store expects the full path after the container portion (looks like a bug to me)
   241  		rc, err := store.ReadRaw(context.TODO(), DataReference("file://testdata/config.yaml"))
   242  		assert.NoError(t, err)
   243  		if assert.NotNil(t, rc) {
   244  			assert.NoError(t, rc.Close())
   245  		}
   246  	})
   247  
   248  	t.Run("Invalid config", func(t *testing.T) {
   249  		testScope := promutils.NewTestScope()
   250  		_, err := newStowRawStore(&Config{}, testScope)
   251  		assert.Error(t, err)
   252  	})
   253  
   254  	t.Run("Initialize container", func(t *testing.T) {
   255  		testScope := promutils.NewTestScope()
   256  		tmpDir, err := ioutil.TempDir("", "stdlib_local")
   257  		assert.NoError(t, err)
   258  
   259  		stats, err := os.Stat(tmpDir)
   260  		assert.NoError(t, err)
   261  		assert.NotNil(t, stats)
   262  
   263  		store, err := newStowRawStore(&Config{
   264  			Stow: &StowConfig{
   265  				Kind: local.Kind,
   266  				Config: map[string]string{
   267  					local.ConfigKeyPath: tmpDir,
   268  				},
   269  			},
   270  			InitContainer: "tmp",
   271  		}, testScope.NewSubScope("y"))
   272  
   273  		assert.NoError(t, err)
   274  		assert.NotNil(t, store)
   275  
   276  		stats, err = os.Stat(filepath.Join(tmpDir, "tmp"))
   277  		assert.NoError(t, err)
   278  		if assert.NotNil(t, stats) {
   279  			assert.True(t, stats.IsDir())
   280  		}
   281  	})
   282  
   283  	t.Run("missing init container", func(t *testing.T) {
   284  		testScope := promutils.NewTestScope()
   285  		tmpDir, err := ioutil.TempDir("", "stdlib_local")
   286  		assert.NoError(t, err)
   287  
   288  		stats, err := os.Stat(tmpDir)
   289  		assert.NoError(t, err)
   290  		assert.NotNil(t, stats)
   291  
   292  		store, err := newStowRawStore(&Config{
   293  			Stow: &StowConfig{
   294  				Kind: local.Kind,
   295  				Config: map[string]string{
   296  					local.ConfigKeyPath: tmpDir,
   297  				},
   298  			},
   299  		}, testScope.NewSubScope("y"))
   300  
   301  		assert.Error(t, err)
   302  		assert.Nil(t, store)
   303  	})
   304  
   305  	t.Run("multi-container enabled", func(t *testing.T) {
   306  		testScope := promutils.NewTestScope()
   307  		tmpDir, err := ioutil.TempDir("", "stdlib_local")
   308  		assert.NoError(t, err)
   309  
   310  		stats, err := os.Stat(tmpDir)
   311  		assert.NoError(t, err)
   312  		assert.NotNil(t, stats)
   313  
   314  		store, err := newStowRawStore(&Config{
   315  			Stow: &StowConfig{
   316  				Kind: local.Kind,
   317  				Config: map[string]string{
   318  					local.ConfigKeyPath: tmpDir,
   319  				},
   320  			},
   321  			InitContainer:         "tmp",
   322  			MultiContainerEnabled: true,
   323  		}, testScope.NewSubScope("y"))
   324  
   325  		assert.NoError(t, err)
   326  		assert.NotNil(t, store)
   327  
   328  		stats, err = os.Stat(filepath.Join(tmpDir, "tmp"))
   329  		assert.NoError(t, err)
   330  		if assert.NotNil(t, stats) {
   331  			assert.True(t, stats.IsDir())
   332  		}
   333  	})
   334  }
   335  
   336  func Test_newStowRawStore(t *testing.T) {
   337  	type args struct {
   338  		cfg          *Config
   339  		metricsScope promutils.Scope
   340  	}
   341  	tests := []struct {
   342  		name    string
   343  		args    args
   344  		wantErr bool
   345  	}{
   346  		{"fail", args{&Config{}, promutils.NewTestScope()}, true},
   347  		{"google", args{&Config{
   348  			InitContainer: "flyte",
   349  			Stow: &StowConfig{
   350  				Kind: google.Kind,
   351  				Config: map[string]string{
   352  					google.ConfigProjectId: "x",
   353  					google.ConfigScopes:    "y",
   354  				},
   355  			},
   356  		}, promutils.NewTestScope()}, true},
   357  		{"minio", args{&Config{
   358  			Type:          TypeMinio,
   359  			InitContainer: "some-container",
   360  			Connection: ConnectionConfig{
   361  				Endpoint: config.URL{URL: utils.MustParseURL("http://minio:9000")},
   362  			},
   363  		}, promutils.NewTestScope()}, true},
   364  	}
   365  	for _, tt := range tests {
   366  		t.Run(tt.name, func(t *testing.T) {
   367  			got, err := newStowRawStore(tt.args.cfg, tt.args.metricsScope)
   368  			if tt.wantErr {
   369  				assert.Error(t, err, "newStowRawStore() error = %v, wantErr %v", err, tt.wantErr)
   370  				return
   371  			}
   372  			assert.NotNil(t, got, "Expected rawstore, found nil!")
   373  		})
   374  	}
   375  }