github.com/uber/kraken@v0.1.4/lib/store/base/file_op_test.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     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  package base
    15  
    16  import (
    17  	"io/ioutil"
    18  	"log"
    19  	"os"
    20  	"path/filepath"
    21  	"reflect"
    22  	"runtime"
    23  	"strings"
    24  	"sync"
    25  	"sync/atomic"
    26  	"testing"
    27  
    28  	"github.com/uber/kraken/core"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  // These tests should pass for all FileStore/FileOp implementations
    33  func TestFileOp(t *testing.T) {
    34  	stores := []struct {
    35  		name    string
    36  		fixture func() (storeBundle *fileStoreTestBundle, cleanup func())
    37  	}{
    38  		{"LocalFileStoreDefault", fileStoreDefaultFixture},
    39  		{"LocalFileStoreCAS", fileStoreCASFixture},
    40  		{"LocalFileStoreLRU", func() (storeBundle *fileStoreTestBundle, cleanup func()) {
    41  			return fileStoreLRUFixture(2)
    42  		}},
    43  	}
    44  
    45  	tests := []func(require *require.Assertions, storeBundle *fileStoreTestBundle){
    46  		testCreateFile,
    47  		testCreateFileFail,
    48  		testReloadFileEntry,
    49  		testMoveFile,
    50  		testLinkFileTo,
    51  		testDeleteFile,
    52  		testGetFileReader,
    53  		testGetFileReadWriter,
    54  		testGetOrSetFileMetadataConcurrently,
    55  		testSetFileMetadataAtConcurrently,
    56  		testDeleteFileMetadata,
    57  	}
    58  
    59  	for _, store := range stores {
    60  		t.Run(store.name, func(t *testing.T) {
    61  			for _, test := range tests {
    62  				testName := runtime.FuncForPC(reflect.ValueOf(test).Pointer()).Name()
    63  				t.Run(testName, func(t *testing.T) {
    64  					require := require.New(t)
    65  					s, cleanup := store.fixture()
    66  					defer cleanup()
    67  					test(require, s)
    68  				})
    69  			}
    70  		})
    71  	}
    72  }
    73  
    74  func testCreateFile(require *require.Assertions, storeBundle *fileStoreTestBundle) {
    75  	store := storeBundle.store
    76  
    77  	fn := core.DigestFixture().Hex()
    78  	s1 := storeBundle.state1
    79  	s2 := storeBundle.state2
    80  
    81  	var wg sync.WaitGroup
    82  	var successCount, existsErrorCount uint32
    83  	for i := 0; i < 100; i++ {
    84  		wg.Add(1)
    85  		go func() {
    86  			defer wg.Done()
    87  			// Create empty file.
    88  			if err := store.NewFileOp().AcceptState(s1).CreateFile(fn, s1, 5); err == nil {
    89  				atomic.AddUint32(&successCount, 1)
    90  			} else if os.IsExist(err) {
    91  				atomic.AddUint32(&existsErrorCount, 1)
    92  			}
    93  		}()
    94  	}
    95  	wg.Wait()
    96  
    97  	require.Equal(successCount, uint32(1))
    98  	require.Equal(existsErrorCount, uint32(99))
    99  
   100  	// Verify file exists.
   101  	_, err := os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn)))
   102  	require.NoError(err)
   103  
   104  	// Create file again with different target state, but include state of existing file as an acceptable state.
   105  	err = store.NewFileOp().AcceptState(s1).CreateFile(fn, s2, 5)
   106  	require.Error(err)
   107  	require.True(os.IsExist(err))
   108  	_, err = os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn)))
   109  	require.NoError(err)
   110  }
   111  
   112  func testCreateFileFail(require *require.Assertions, storeBundle *fileStoreTestBundle) {
   113  	store := storeBundle.store
   114  
   115  	fn := core.DigestFixture().Hex()
   116  	s1 := storeBundle.state1
   117  	s2 := storeBundle.state2
   118  	s3 := storeBundle.state3
   119  
   120  	// Create empty file
   121  	err := store.NewFileOp().AcceptState(s1).CreateFile(fn, s1, 5)
   122  	require.NoError(err)
   123  	_, err = os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn)))
   124  	require.NoError(err)
   125  
   126  	// Create file again with different target state
   127  	err = store.NewFileOp().AcceptState(s3).CreateFile(fn, s2, 5)
   128  	require.Error(err)
   129  	require.True(IsFileStateError(err))
   130  	require.True(strings.HasPrefix(err.Error(), "failed to perform"))
   131  	_, err = os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn)))
   132  	require.NoError(err)
   133  }
   134  
   135  func testReloadFileEntry(require *require.Assertions, storeBundle *fileStoreTestBundle) {
   136  	store := storeBundle.store
   137  
   138  	fn := core.DigestFixture().Hex()
   139  	m := getMockMetadataOne()
   140  	m.content = []byte("foo")
   141  	s1 := storeBundle.state1
   142  
   143  	// Create file
   144  	require.NoError(store.NewFileOp().CreateFile(fn, s1, 5))
   145  	_, err := os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn)))
   146  	require.NoError(err)
   147  	_, err = store.NewFileOp().AcceptState(s1).GetFileStat(fn)
   148  	require.NoError(err)
   149  	ok := store.fileMap.Contains(fn)
   150  	require.True(ok)
   151  	_, err = store.NewFileOp().AcceptState(s1).SetFileMetadata(fn, m)
   152  	require.NoError(err)
   153  
   154  	// Recreate store nukes store's in memory map
   155  	storeBundle.recreateStore()
   156  	store = storeBundle.store
   157  	ok = store.fileMap.Contains(fn)
   158  	require.False(ok)
   159  
   160  	// GetFileReader should load file from disk into map, including metadata.
   161  	_, err = store.NewFileOp().AcceptState(s1).GetFileReader(fn)
   162  	require.NoError(err)
   163  	ok = store.fileMap.Contains(fn)
   164  	require.True(ok)
   165  	result := getMockMetadataOne()
   166  	require.NoError(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, result))
   167  	require.Equal(m.content, result.content)
   168  }
   169  
   170  func testMoveFile(require *require.Assertions, storeBundle *fileStoreTestBundle) {
   171  	store := storeBundle.store
   172  
   173  	s1 := storeBundle.state1
   174  	s2 := storeBundle.state2
   175  	s3 := storeBundle.state3
   176  	fn, ok := storeBundle.files[s1]
   177  	if !ok {
   178  		log.Fatal("file not found in state1")
   179  	}
   180  
   181  	// Update content
   182  	readWriterState2, err := store.NewFileOp().AcceptState(s1).GetFileReadWriter(fn)
   183  	require.NoError(err)
   184  	_, err = readWriterState2.Write([]byte{'t', 'e', 's', 't', '\n'})
   185  	require.NoError(err)
   186  	readWriterState2.Close()
   187  	readWriterState2, err = store.NewFileOp().AcceptState(s1).GetFileReadWriter(fn)
   188  	require.NoError(err)
   189  
   190  	// Move from state1 to state2
   191  	err = store.NewFileOp().AcceptState(s1).MoveFile(fn, s2)
   192  	require.NoError(err)
   193  	_, err = os.Stat(filepath.Join(s2.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn)))
   194  	require.NoError(err)
   195  	_, err = os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn)))
   196  	require.True(os.IsNotExist(err))
   197  	_, err = store.NewFileOp().AcceptState(s2).GetFileReader(fn)
   198  	require.NoError(err)
   199  
   200  	// Move from state1 to state3 would fail with state error
   201  	err = store.NewFileOp().AcceptState(s1).MoveFile(fn, s3)
   202  	require.Error(err)
   203  	require.True(IsFileStateError(err))
   204  
   205  	// Create new readWriter at new state
   206  	readWriterState1, err := store.NewFileOp().AcceptState(s2).GetFileReadWriter(fn)
   207  	require.NoError(err)
   208  	// Check content
   209  	dataState1, err := ioutil.ReadAll(readWriterState1)
   210  	require.NoError(err)
   211  	dataState2, err := ioutil.ReadAll(readWriterState2)
   212  	require.NoError(err)
   213  	require.Equal(dataState1, dataState2)
   214  	require.Equal([]byte{'t', 'e', 's', 't', '\n'}, dataState1)
   215  	// Write with old readWriter
   216  	_, err = readWriterState1.WriteAt([]byte{'1'}, 0)
   217  	require.NoError(err)
   218  	// Check content again
   219  	readWriterState1.Seek(0, 0)
   220  	readWriterState2.Seek(0, 0)
   221  	dataState1, err = ioutil.ReadAll(readWriterState1)
   222  	require.NoError(err)
   223  	dataState2, err = ioutil.ReadAll(readWriterState2)
   224  	require.NoError(err)
   225  	require.Equal(dataState1, dataState2)
   226  	require.Equal([]byte{'1', 'e', 's', 't', '\n'}, dataState1)
   227  	// Close on last opened readwriter removes hardlink
   228  	readWriterState2.Close()
   229  	_, err = os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn)))
   230  	require.True(os.IsNotExist(err))
   231  	readWriterState1.Close()
   232  	_, err = os.Stat(filepath.Join(s2.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn)))
   233  	require.NoError(err)
   234  	// Check content again
   235  	readWriterStateMoved, err := store.NewFileOp().AcceptState(s2).GetFileReadWriter(fn)
   236  	require.NoError(err)
   237  	dataMoved, err := ioutil.ReadAll(readWriterStateMoved)
   238  	require.NoError(err)
   239  	require.Equal([]byte{'1', 'e', 's', 't', '\n'}, dataMoved)
   240  	readWriterStateMoved.Close()
   241  
   242  	// Move back to state1
   243  	err = store.NewFileOp().AcceptState(s2).MoveFile(fn, s1)
   244  	require.NoError(err)
   245  	_, err = store.NewFileOp().AcceptState(s1).GetFileReader(fn)
   246  	require.NoError(err)
   247  }
   248  
   249  func testLinkFileTo(require *require.Assertions, storeBundle *fileStoreTestBundle) {
   250  	store := storeBundle.store
   251  
   252  	s1 := storeBundle.state1
   253  	s3 := storeBundle.state3
   254  	fn, ok := storeBundle.files[s1]
   255  	if !ok {
   256  		log.Fatal("file not found in state1")
   257  	}
   258  
   259  	dst := filepath.Join(s3.GetDirectory(), "test_dst")
   260  	require.NoError(store.NewFileOp().AcceptState(s1).LinkFileTo(fn, dst))
   261  	_, err := os.Stat(dst)
   262  	require.NoError(err)
   263  }
   264  
   265  func testDeleteFile(require *require.Assertions, storeBundle *fileStoreTestBundle) {
   266  	store := storeBundle.store
   267  
   268  	s1 := storeBundle.state1
   269  	fn, ok := storeBundle.files[s1]
   270  	if !ok {
   271  		log.Fatal("file not found in state1")
   272  	}
   273  	content := "this a test for read after delete"
   274  
   275  	// Write to file
   276  	rw, err := store.NewFileOp().AcceptState(s1).GetFileReadWriter(fn)
   277  	require.NoError(err)
   278  	rw.Write([]byte(content))
   279  
   280  	// Confirm deletion
   281  	err = store.NewFileOp().AcceptState(s1).DeleteFile(fn)
   282  	require.NoError(err)
   283  	_, err = os.Stat(filepath.Join(s1.GetDirectory(), store.fileEntryFactory.GetRelativePath(fn)))
   284  	require.True(os.IsNotExist(err))
   285  
   286  	// Existing readwriter should still work after deletion
   287  	rw.Seek(0, 0)
   288  	data, err := ioutil.ReadAll(rw)
   289  	require.NoError(err)
   290  	require.Equal(content, string(data))
   291  
   292  	rw.Write([]byte(content))
   293  	rw.Seek(0, 0)
   294  	data, err = ioutil.ReadAll(rw)
   295  	require.NoError(err)
   296  	require.Equal(content+content, string(data))
   297  
   298  	rw.Close()
   299  
   300  	// Get deleted file should fail
   301  	_, err = store.NewFileOp().AcceptState(s1).GetFileReader(fn)
   302  	require.True(os.IsNotExist(err))
   303  }
   304  
   305  func testGetFileReader(require *require.Assertions, storeBundle *fileStoreTestBundle) {
   306  	store := storeBundle.store
   307  
   308  	s1 := storeBundle.state1
   309  	fn, ok := storeBundle.files[s1]
   310  	if !ok {
   311  		log.Fatal("file not found in state1")
   312  	}
   313  
   314  	// Get ReadWriter and modify the file.
   315  	readWriter, err := store.NewFileOp().AcceptState(s1).GetFileReadWriter(fn)
   316  	require.NoError(err)
   317  	defer readWriter.Close()
   318  	_, err = readWriter.Write([]byte{'t', 'e', 's', 't', '\n'})
   319  	require.NoError(err)
   320  
   321  	// Test getFileReader.
   322  	var wg sync.WaitGroup
   323  	for i := 0; i < 100; i++ {
   324  		wg.Add(1)
   325  		go func() {
   326  			defer wg.Done()
   327  			reader, err := store.NewFileOp().AcceptState(s1).GetFileReader(fn)
   328  			require.NoError(err)
   329  
   330  			b := make([]byte, 5)
   331  			_, err = reader.Seek(0, 0)
   332  			require.NoError(err)
   333  			l, err := reader.ReadAt(b, 0)
   334  			require.NoError(err)
   335  			require.Equal(l, 5)
   336  			require.Equal(string(b[:l]), "test\n")
   337  
   338  			err = reader.Close()
   339  			require.NoError(err)
   340  		}()
   341  	}
   342  	wg.Wait()
   343  
   344  	reader, err := store.NewFileOp().AcceptState(s1).GetFileReader(fn)
   345  	require.NoError(err)
   346  	reader.Close()
   347  }
   348  
   349  func testGetFileReadWriter(require *require.Assertions, storeBundle *fileStoreTestBundle) {
   350  	store := storeBundle.store
   351  
   352  	s1 := storeBundle.state1
   353  	fn, ok := storeBundle.files[s1]
   354  	if !ok {
   355  		log.Fatal("file not found in state1")
   356  	}
   357  
   358  	// Get ReadWriter and modify file concurrently.
   359  	var wg sync.WaitGroup
   360  	for i := 0; i < 100; i++ {
   361  		wg.Add(1)
   362  		go func() {
   363  			defer wg.Done()
   364  			readWriter, err := store.NewFileOp().AcceptState(s1).GetFileReadWriter(fn)
   365  			require.NoError(err)
   366  
   367  			_, err = readWriter.Write([]byte{'t', 'e', 's', 't', '\n'})
   368  			require.NoError(err)
   369  
   370  			b := make([]byte, 3)
   371  			_, err = readWriter.Seek(1, 0)
   372  			require.NoError(err)
   373  			l, err := readWriter.Read(b)
   374  			require.NoError(err)
   375  			require.Equal(l, 3)
   376  			require.Equal(string(b[:l]), "est")
   377  			_, err = readWriter.Seek(0, 0)
   378  			require.NoError(err)
   379  
   380  			err = readWriter.Close()
   381  			require.NoError(err)
   382  		}()
   383  	}
   384  	wg.Wait()
   385  
   386  	// Verify content.
   387  	reader, err := store.NewFileOp().AcceptState(s1).GetFileReader(fn)
   388  	require.NoError(err)
   389  
   390  	b := make([]byte, 5)
   391  	_, err = reader.Seek(0, 0)
   392  	require.NoError(err)
   393  	l, err := reader.ReadAt(b, 0)
   394  	require.NoError(err)
   395  	require.Equal(l, 5)
   396  	require.Equal(string(b[:l]), "test\n")
   397  
   398  	err = reader.Close()
   399  	require.NoError(err)
   400  }
   401  
   402  func testGetOrSetFileMetadataConcurrently(require *require.Assertions, storeBundle *fileStoreTestBundle) {
   403  	store := storeBundle.store
   404  
   405  	s1 := storeBundle.state1
   406  	fn, ok := storeBundle.files[s1]
   407  	if !ok {
   408  		log.Fatal("file not found in state1")
   409  	}
   410  
   411  	original := []byte("foo")
   412  
   413  	// Get ReadWriter and modify file concurrently.
   414  	var wg sync.WaitGroup
   415  	for i := 0; i < 50; i++ {
   416  		wg.Add(1)
   417  		go func() {
   418  			defer wg.Done()
   419  
   420  			m := getMockMetadataOne()
   421  			m.content = original
   422  			require.NoError(store.NewFileOp().AcceptState(s1).GetOrSetFileMetadata(fn, m))
   423  			require.Equal(original, m.content)
   424  		}()
   425  	}
   426  	wg.Wait()
   427  
   428  	// Verify content
   429  	m := getMockMetadataOne()
   430  	require.NoError(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, m))
   431  	require.Equal(original, m.content)
   432  }
   433  
   434  func testSetFileMetadataAtConcurrently(require *require.Assertions, storeBundle *fileStoreTestBundle) {
   435  	store := storeBundle.store
   436  
   437  	s1 := storeBundle.state1
   438  	fn, ok := storeBundle.files[s1]
   439  	if !ok {
   440  		log.Fatal("file not found in state1")
   441  	}
   442  
   443  	m := getMockMetadataOne()
   444  	m.content = make([]byte, 50)
   445  	updated, err := store.NewFileOp().AcceptState(s1).SetFileMetadata(fn, m)
   446  	require.True(updated)
   447  	require.NoError(err)
   448  
   449  	// Get ReadWriter and modify file concurrently.
   450  	var wg sync.WaitGroup
   451  	for i := 0; i < 50; i++ {
   452  		wg.Add(1)
   453  		go func(offset int) {
   454  			defer wg.Done()
   455  
   456  			_, err := store.NewFileOp().AcceptState(s1).SetFileMetadataAt(fn, m, []byte("f"), int64(offset))
   457  			// require.True(ok)
   458  			require.NoError(err)
   459  		}(i)
   460  	}
   461  	wg.Wait()
   462  
   463  	// Verify content
   464  	require.NoError(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, m))
   465  	require.Equal(50, len(m.content))
   466  	for i := 0; i < 50; i++ {
   467  		require.Equal(byte('f'), m.content[i])
   468  	}
   469  }
   470  
   471  func testDeleteFileMetadata(require *require.Assertions, storeBundle *fileStoreTestBundle) {
   472  	store := storeBundle.store
   473  
   474  	s1 := storeBundle.state1
   475  	fn, ok := storeBundle.files[s1]
   476  	if !ok {
   477  		log.Fatal("file not found in state1")
   478  	}
   479  
   480  	m := getMockMetadataOne()
   481  
   482  	// DeleteFileMetadata doesn't return error if the file doesn't exist.
   483  	require.NoError(store.NewFileOp().AcceptState(s1).DeleteFileMetadata(fn, m))
   484  
   485  	m.content = make([]byte, 1)
   486  	updated, err := store.NewFileOp().AcceptState(s1).SetFileMetadata(fn, m)
   487  	require.True(updated)
   488  	require.NoError(err)
   489  
   490  	require.NoError(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, m))
   491  	require.NoError(store.NewFileOp().AcceptState(s1).DeleteFileMetadata(fn, m))
   492  	require.Error(store.NewFileOp().AcceptState(s1).GetFileMetadata(fn, m))
   493  }