github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/persist/fs/index_read_write_test.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package fs
    22  
    23  import (
    24  	"bufio"
    25  	"bytes"
    26  	"crypto/rand"
    27  	"encoding/json"
    28  	"io"
    29  	"io/ioutil"
    30  	"os"
    31  	"path/filepath"
    32  	"sync"
    33  	"testing"
    34  	"time"
    35  
    36  	"github.com/m3db/m3/src/dbnode/persist"
    37  	idxpersist "github.com/m3db/m3/src/m3ninx/persist"
    38  	"github.com/m3db/m3/src/x/ident"
    39  	xtime "github.com/m3db/m3/src/x/time"
    40  
    41  	"github.com/golang/mock/gomock"
    42  	"github.com/stretchr/testify/assert"
    43  	"github.com/stretchr/testify/require"
    44  )
    45  
    46  func shardsSet(shards ...uint32) map[uint32]struct{} {
    47  	r := make(map[uint32]struct{})
    48  	for _, shard := range shards {
    49  		r[shard] = struct{}{}
    50  	}
    51  	return r
    52  }
    53  
    54  type indexWriteTestSetup struct {
    55  	now            xtime.UnixNano
    56  	rootDir        string
    57  	filePathPrefix string
    58  	blockSize      time.Duration
    59  	blockStart     xtime.UnixNano
    60  	fileSetID      FileSetFileIdentifier
    61  }
    62  
    63  func newIndexWriteTestSetup(t *testing.T) indexWriteTestSetup {
    64  	now := xtime.ToUnixNano(time.Now().UTC())
    65  	dir := createTempDir(t)
    66  	filePathPrefix := filepath.Join(dir, "")
    67  	blockSize := 12 * time.Hour
    68  	blockStart := now.Truncate(blockSize)
    69  	fileSetID := FileSetFileIdentifier{
    70  		FileSetContentType: persist.FileSetIndexContentType,
    71  		Namespace:          ident.StringID("metrics"),
    72  		BlockStart:         blockStart,
    73  	}
    74  	return indexWriteTestSetup{
    75  		now:            now,
    76  		rootDir:        dir,
    77  		filePathPrefix: filePathPrefix,
    78  		blockSize:      blockSize,
    79  		blockStart:     blockStart,
    80  		fileSetID:      fileSetID,
    81  	}
    82  }
    83  
    84  func (s indexWriteTestSetup) cleanup() {
    85  	os.RemoveAll(s.rootDir)
    86  }
    87  
    88  type testIndexReadWriteOptions struct {
    89  	IndexReaderOptions testIndexReaderOptions
    90  }
    91  
    92  func TestIndexSimpleReadWrite(t *testing.T) {
    93  	tests := []struct {
    94  		TestOptions testIndexReadWriteOptions
    95  	}{
    96  		{
    97  			TestOptions: testIndexReadWriteOptions{
    98  				IndexReaderOptions: testIndexReaderOptions{
    99  					AutovalidateIndexSegments: true,
   100  				},
   101  			},
   102  		},
   103  		{
   104  			TestOptions: testIndexReadWriteOptions{
   105  				IndexReaderOptions: testIndexReaderOptions{
   106  					AutovalidateIndexSegments: true,
   107  				},
   108  			},
   109  		},
   110  	}
   111  
   112  	for _, test := range tests {
   113  		test := test
   114  		name, err := json.Marshal(test)
   115  		require.NoError(t, err)
   116  		t.Run(string(name), func(t *testing.T) {
   117  			testIndexSimpleReadWrite(t, test.TestOptions)
   118  		})
   119  	}
   120  }
   121  
   122  func testIndexSimpleReadWrite(t *testing.T, testOpts testIndexReadWriteOptions) {
   123  	ctrl := gomock.NewController(t)
   124  	defer ctrl.Finish()
   125  
   126  	test := newIndexWriteTestSetup(t)
   127  	defer test.cleanup()
   128  
   129  	writer := newTestIndexWriter(t, test.filePathPrefix)
   130  	err := writer.Open(IndexWriterOpenOptions{
   131  		Identifier:  test.fileSetID,
   132  		BlockSize:   test.blockSize,
   133  		FileSetType: persist.FileSetFlushType,
   134  		Shards:      shardsSet(1, 3, 5),
   135  	})
   136  	require.NoError(t, err)
   137  
   138  	testSegments := []testIndexSegment{
   139  		{
   140  			segmentType:  idxpersist.IndexSegmentType("fst"),
   141  			majorVersion: 1,
   142  			minorVersion: 2,
   143  			files: []testIndexSegmentFile{
   144  				{idxpersist.IndexSegmentFileType("first"), randDataFactorOfBuffSize(t, 1.5)},
   145  				{idxpersist.IndexSegmentFileType("second"), randDataFactorOfBuffSize(t, 2.5)},
   146  			},
   147  		},
   148  		{
   149  			segmentType:  idxpersist.IndexSegmentType("trie"),
   150  			majorVersion: 3,
   151  			minorVersion: 4,
   152  			files: []testIndexSegmentFile{
   153  				{idxpersist.IndexSegmentFileType("first"), randDataFactorOfBuffSize(t, 1.5)},
   154  				{idxpersist.IndexSegmentFileType("second"), randDataFactorOfBuffSize(t, 2.5)},
   155  				{idxpersist.IndexSegmentFileType("third"), randDataFactorOfBuffSize(t, 3)},
   156  			},
   157  		},
   158  	}
   159  	writeTestIndexSegments(t, ctrl, writer, testSegments)
   160  
   161  	err = writer.Close()
   162  	require.NoError(t, err)
   163  
   164  	reader := newTestIndexReader(t, test.filePathPrefix,
   165  		testOpts.IndexReaderOptions)
   166  	result, err := reader.Open(IndexReaderOpenOptions{
   167  		Identifier:  test.fileSetID,
   168  		FileSetType: persist.FileSetFlushType,
   169  	})
   170  	require.NoError(t, err)
   171  	require.Equal(t, shardsSet(1, 3, 5), result.Shards)
   172  
   173  	readTestIndexSegments(t, ctrl, reader, testSegments)
   174  
   175  	err = reader.Validate()
   176  	require.NoError(t, err)
   177  
   178  	err = reader.Close()
   179  	require.NoError(t, err)
   180  }
   181  
   182  func newTestIndexWriter(t *testing.T, filePathPrefix string) IndexFileSetWriter {
   183  	writer, err := NewIndexWriter(testDefaultOpts.
   184  		SetFilePathPrefix(filePathPrefix).
   185  		SetWriterBufferSize(testWriterBufferSize))
   186  	require.NoError(t, err)
   187  	return writer
   188  }
   189  
   190  type testIndexReaderOptions struct {
   191  	AutovalidateIndexSegments bool
   192  }
   193  
   194  func newTestIndexReader(
   195  	t *testing.T,
   196  	filePathPrefix string,
   197  	opts testIndexReaderOptions,
   198  ) IndexFileSetReader {
   199  	reader, err := NewIndexReader(testDefaultOpts.
   200  		SetFilePathPrefix(filePathPrefix).
   201  		SetIndexReaderAutovalidateIndexSegments(opts.AutovalidateIndexSegments))
   202  	require.NoError(t, err)
   203  	return reader
   204  }
   205  
   206  func randDataFactorOfBuffSize(t *testing.T, factor float64) []byte {
   207  	length := int(factor * float64(defaultBufferedReaderSize()))
   208  	buffer := bytes.NewBuffer(nil)
   209  	src := io.LimitReader(rand.Reader, int64(length))
   210  	_, err := io.Copy(buffer, src)
   211  	require.NoError(t, err)
   212  	return buffer.Bytes()
   213  }
   214  
   215  type testIndexSegment struct {
   216  	segmentType  idxpersist.IndexSegmentType
   217  	majorVersion int
   218  	minorVersion int
   219  	metadata     []byte
   220  	files        []testIndexSegmentFile
   221  }
   222  
   223  type testIndexSegmentFile struct {
   224  	segmentFileType idxpersist.IndexSegmentFileType
   225  	data            []byte
   226  }
   227  
   228  func writeTestIndexSegments(
   229  	t *testing.T,
   230  	ctrl *gomock.Controller,
   231  	writer IndexFileSetWriter,
   232  	v []testIndexSegment,
   233  ) {
   234  	for _, s := range v {
   235  		fileSet := NewMockIndexSegmentFileSetWriter(ctrl)
   236  		fileSet.EXPECT().SegmentType().Return(s.segmentType).AnyTimes()
   237  		fileSet.EXPECT().MajorVersion().Return(s.majorVersion)
   238  		fileSet.EXPECT().MinorVersion().Return(s.minorVersion)
   239  		fileSet.EXPECT().SegmentMetadata().Return(s.metadata)
   240  
   241  		var files []idxpersist.IndexSegmentFileType
   242  		for _, f := range s.files {
   243  			files = append(files, f.segmentFileType)
   244  		}
   245  		fileSet.EXPECT().Files().Return(files).AnyTimes()
   246  
   247  		for _, f := range s.files {
   248  			f := f
   249  			// Make sure we're actually trying to test writing out file contents
   250  			require.True(t, len(f.data) > 0)
   251  
   252  			fileSet.EXPECT().
   253  				WriteFile(f.segmentFileType, gomock.Any()).
   254  				DoAndReturn(func(_ idxpersist.IndexSegmentFileType, w io.Writer) error {
   255  					_, err := w.Write(f.data)
   256  					return err
   257  				})
   258  		}
   259  
   260  		err := writer.WriteSegmentFileSet(fileSet)
   261  		require.NoError(t, err)
   262  	}
   263  }
   264  
   265  func readTestIndexSegments(
   266  	t *testing.T,
   267  	ctrl *gomock.Controller,
   268  	reader IndexFileSetReader,
   269  	v []testIndexSegment,
   270  ) {
   271  	require.Equal(t, len(v), reader.SegmentFileSets())
   272  
   273  	for _, s := range v {
   274  		result, err := reader.ReadSegmentFileSet()
   275  		require.NoError(t, err)
   276  
   277  		assert.Equal(t, s.segmentType, result.SegmentType())
   278  		assert.Equal(t, s.majorVersion, result.MajorVersion())
   279  		assert.Equal(t, s.minorVersion, result.MinorVersion())
   280  		assert.Equal(t, s.metadata, result.SegmentMetadata())
   281  
   282  		require.Equal(t, len(s.files), len(result.Files()))
   283  
   284  		for i, expected := range s.files {
   285  			actual := result.Files()[i]
   286  
   287  			require.Equal(t, expected.segmentFileType, actual.SegmentFileType())
   288  
   289  			// Assert read data is correct
   290  			actualData, err := ioutil.ReadAll(actual)
   291  			require.NoError(t, err)
   292  			assert.Equal(t, expected.data, actualData)
   293  
   294  			// Assert bytes data (should be mmap'd byte slice) is also correct
   295  			directData, err := actual.Mmap()
   296  			require.NoError(t, err)
   297  			assert.True(t, bytes.Equal(expected.data, directData.Bytes))
   298  
   299  			err = actual.Close()
   300  			require.NoError(t, err)
   301  		}
   302  	}
   303  
   304  	// Ensure last read is io.EOF
   305  	_, err := reader.ReadSegmentFileSet()
   306  	require.Equal(t, io.EOF, err)
   307  }
   308  
   309  var (
   310  	defaultBufferedReaderLock sync.Mutex
   311  	defaultBufferedReader     *bufio.Reader
   312  )
   313  
   314  func defaultBufferedReaderSize() int {
   315  	// Pre go1.10 it wasn't possible to query the size of the buffered reader
   316  	// however in go1.10 it is possible
   317  	defaultBufferedReaderLock.Lock()
   318  	defer defaultBufferedReaderLock.Unlock()
   319  
   320  	if defaultBufferedReader == nil {
   321  		defaultBufferedReader = bufio.NewReader(nil)
   322  	}
   323  	return defaultBufferedReader.Size()
   324  }