github.com/m3db/m3@v1.5.0/src/cmd/tools/verify_data_files/main/main_test.go (about)

     1  // Copyright (c) 2019 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 main
    22  
    23  import (
    24  	"encoding/hex"
    25  	"io/ioutil"
    26  	"os"
    27  	"testing"
    28  	"time"
    29  	"unicode/utf8"
    30  
    31  	"github.com/m3db/m3/src/dbnode/digest"
    32  	"github.com/m3db/m3/src/dbnode/persist"
    33  	"github.com/m3db/m3/src/dbnode/persist/fs"
    34  	"github.com/m3db/m3/src/x/checked"
    35  	"github.com/m3db/m3/src/x/ident"
    36  	"github.com/m3db/m3/src/x/pool"
    37  	xtime "github.com/m3db/m3/src/x/time"
    38  
    39  	"github.com/stretchr/testify/require"
    40  	"go.uber.org/zap"
    41  )
    42  
    43  var (
    44  	testBytesPool pool.CheckedBytesPool
    45  )
    46  
    47  func init() {
    48  	testBytesPool = pool.NewCheckedBytesPool(nil, pool.NewObjectPoolOptions(),
    49  		func(s []pool.Bucket) pool.BytesPool {
    50  			return pool.NewBytesPool(s, pool.NewObjectPoolOptions())
    51  		})
    52  	testBytesPool.Init()
    53  }
    54  
    55  func newInvalidUTF8Bytes(t *testing.T) []byte {
    56  	bytes, err := hex.DecodeString("bf")
    57  	require.NoError(t, err)
    58  	require.False(t, utf8.Valid(bytes))
    59  	return bytes
    60  }
    61  
    62  type testWriter struct {
    63  	t         *testing.T
    64  	fileSetID fs.FileSetFileIdentifier
    65  	writer    fs.DataFileSetWriter
    66  	blockSize time.Duration
    67  	testDir   string
    68  }
    69  
    70  func (w testWriter) Cleanup() {
    71  	require.NoError(w.t, os.RemoveAll(w.testDir))
    72  }
    73  
    74  func newOpenWriter(t *testing.T) testWriter {
    75  	testDir, err := ioutil.TempDir("", "m3db-data")
    76  	require.NoError(t, err)
    77  
    78  	fsOpts := fs.NewOptions().SetFilePathPrefix(testDir)
    79  	writer, err := fs.NewWriter(fsOpts)
    80  	require.NoError(t, err)
    81  
    82  	blockSize := 2 * time.Hour
    83  	start := xtime.Now().Truncate(blockSize).Add(-2 * blockSize)
    84  	fileSetID := fs.FileSetFileIdentifier{
    85  		FileSetContentType: persist.FileSetDataContentType,
    86  		Namespace:          ident.StringID("foo"),
    87  		BlockStart:         start,
    88  		Shard:              42,
    89  		VolumeIndex:        0,
    90  	}
    91  	err = writer.Open(fs.DataWriterOpenOptions{
    92  		FileSetType:        persist.FileSetFlushType,
    93  		FileSetContentType: fileSetID.FileSetContentType,
    94  		Identifier:         fileSetID,
    95  		BlockSize:          blockSize,
    96  	})
    97  	require.NoError(t, err)
    98  
    99  	return testWriter{
   100  		t:         t,
   101  		fileSetID: fileSetID,
   102  		writer:    writer,
   103  		blockSize: blockSize,
   104  		testDir:   testDir,
   105  	}
   106  }
   107  
   108  func TestFixFileSetInvalidID(t *testing.T) {
   109  	testWriter := newOpenWriter(t)
   110  	defer testWriter.Cleanup()
   111  
   112  	fixDir, err := ioutil.TempDir("", "m3db-fix-data")
   113  	require.NoError(t, err)
   114  	defer os.RemoveAll(fixDir)
   115  
   116  	// Write invalid ID.
   117  	invalidBytes := newInvalidUTF8Bytes(t)
   118  	id := ident.BinaryID(checked.NewBytes(invalidBytes, nil))
   119  	data := checked.NewBytes([]byte{1, 2, 3}, nil)
   120  	data.IncRef()
   121  	checksum := digest.Checksum(data.Bytes())
   122  
   123  	writer := testWriter.writer
   124  	metadata := persist.NewMetadataFromIDAndTags(id, ident.Tags{},
   125  		persist.MetadataOptions{})
   126  	err = writer.Write(metadata, data, checksum)
   127  	require.NoError(t, err)
   128  
   129  	// Write valid ID.
   130  	id = ident.StringID("foo")
   131  	metadata = persist.NewMetadataFromIDAndTags(id, ident.Tags{},
   132  		persist.MetadataOptions{})
   133  	err = writer.Write(metadata, data, checksum)
   134  	require.NoError(t, err)
   135  
   136  	// Close volume.
   137  	err = writer.Close()
   138  	require.NoError(t, err)
   139  
   140  	// Fix the volume.
   141  	run(runOptions{
   142  		filePathPrefix: testWriter.testDir,
   143  		fixDir:         fixDir,
   144  		fixInvalidIDs:  true,
   145  		bytesPool:      testBytesPool,
   146  		log:            zap.NewExample(),
   147  	})
   148  
   149  	// Read back the volume from fix dir.
   150  	reader, err := fs.NewReader(nil, fs.NewOptions().SetFilePathPrefix(fixDir))
   151  	require.NoError(t, err)
   152  
   153  	err = reader.Open(fs.DataReaderOpenOptions{
   154  		Identifier:  testWriter.fileSetID,
   155  		FileSetType: persist.FileSetFlushType,
   156  	})
   157  	require.NoError(t, err)
   158  
   159  	require.Equal(t, 1, reader.Entries())
   160  
   161  	readID, _, _, _, err := reader.Read()
   162  	require.NoError(t, err)
   163  
   164  	require.Equal(t, "foo", readID.String())
   165  
   166  	err = reader.Close()
   167  	require.NoError(t, err)
   168  }
   169  
   170  func TestFixFileSetInvalidTags(t *testing.T) {
   171  	testWriter := newOpenWriter(t)
   172  	defer testWriter.Cleanup()
   173  
   174  	fixDir, err := ioutil.TempDir("", "m3db-fix-data")
   175  	require.NoError(t, err)
   176  	defer os.RemoveAll(fixDir)
   177  
   178  	writer := testWriter.writer
   179  
   180  	// Write invalid tags.
   181  	invalidBytes := newInvalidUTF8Bytes(t)
   182  	id := ident.StringID("foo")
   183  	tags := ident.NewTags(ident.Tag{
   184  		Name:  ident.BinaryID(checked.NewBytes(invalidBytes, nil)),
   185  		Value: ident.StringID("bar"),
   186  	}, ident.Tag{
   187  		Name:  ident.StringID("baz"),
   188  		Value: ident.StringID("qux"),
   189  	}, ident.Tag{
   190  		Name:  ident.StringID("qar"),
   191  		Value: ident.StringID("qaz"),
   192  	})
   193  	data := checked.NewBytes([]byte{1, 2, 3}, nil)
   194  	data.IncRef()
   195  	checksum := digest.Checksum(data.Bytes())
   196  
   197  	metadata := persist.NewMetadataFromIDAndTags(id, tags,
   198  		persist.MetadataOptions{})
   199  	err = writer.Write(metadata, data, checksum)
   200  	require.NoError(t, err)
   201  
   202  	// Write valid tags.
   203  	id = ident.StringID("bar")
   204  	tags = ident.NewTags(ident.Tag{
   205  		Name:  ident.StringID("foo"),
   206  		Value: ident.StringID("bar"),
   207  	}, ident.Tag{
   208  		Name:  ident.StringID("baz"),
   209  		Value: ident.StringID("qux"),
   210  	}, ident.Tag{
   211  		Name:  ident.StringID("qar"),
   212  		Value: ident.StringID("qaz"),
   213  	})
   214  	data = checked.NewBytes([]byte{1, 2, 3}, nil)
   215  	data.IncRef()
   216  	checksum = digest.Checksum(data.Bytes())
   217  
   218  	metadata = persist.NewMetadataFromIDAndTags(id, tags,
   219  		persist.MetadataOptions{})
   220  	err = writer.Write(metadata, data, checksum)
   221  	require.NoError(t, err)
   222  
   223  	// Close volume.
   224  	err = writer.Close()
   225  	require.NoError(t, err)
   226  
   227  	// Fix the volume.
   228  	run(runOptions{
   229  		filePathPrefix: testWriter.testDir,
   230  		fixDir:         fixDir,
   231  		fixInvalidTags: true,
   232  		bytesPool:      testBytesPool,
   233  		log:            zap.NewExample(),
   234  	})
   235  
   236  	// Read back the volume from fix dir.
   237  	reader, err := fs.NewReader(nil, fs.NewOptions().SetFilePathPrefix(fixDir))
   238  	require.NoError(t, err)
   239  
   240  	err = reader.Open(fs.DataReaderOpenOptions{
   241  		Identifier:  testWriter.fileSetID,
   242  		FileSetType: persist.FileSetFlushType,
   243  	})
   244  	require.NoError(t, err)
   245  
   246  	require.Equal(t, 1, reader.Entries())
   247  
   248  	readID, readTags, _, _, err := reader.Read()
   249  	require.NoError(t, err)
   250  
   251  	require.Equal(t, "bar", readID.String())
   252  
   253  	tagsMap := make(map[string]string)
   254  	for readTags.Next() {
   255  		tag := readTags.Current()
   256  		tagsMap[tag.Name.String()] = tag.Value.String()
   257  	}
   258  	require.NoError(t, readTags.Err())
   259  	readTags.Close()
   260  
   261  	require.Equal(t, map[string]string{"foo": "bar", "baz": "qux", "qar": "qaz"}, tagsMap)
   262  
   263  	err = reader.Close()
   264  	require.NoError(t, err)
   265  }
   266  
   267  func TestFixFileSetInvalidChecksum(t *testing.T) {
   268  	testWriter := newOpenWriter(t)
   269  	defer testWriter.Cleanup()
   270  
   271  	fixDir, err := ioutil.TempDir("", "m3db-fix-data")
   272  	require.NoError(t, err)
   273  	defer os.RemoveAll(fixDir)
   274  
   275  	writer := testWriter.writer
   276  
   277  	// Write invalid checksum.
   278  	id := ident.StringID("foo")
   279  	tags := ident.NewTags(ident.Tag{
   280  		Name:  ident.StringID("foo"),
   281  		Value: ident.StringID("bar"),
   282  	}, ident.Tag{
   283  		Name:  ident.StringID("baz"),
   284  		Value: ident.StringID("qux"),
   285  	}, ident.Tag{
   286  		Name:  ident.StringID("qar"),
   287  		Value: ident.StringID("qaz"),
   288  	})
   289  	data := checked.NewBytes([]byte{1, 2, 3}, nil)
   290  	data.IncRef()
   291  	checksum := digest.Checksum(data.Bytes()) + 1
   292  
   293  	metadata := persist.NewMetadataFromIDAndTags(id, tags,
   294  		persist.MetadataOptions{})
   295  	err = writer.Write(metadata, data, checksum)
   296  	require.NoError(t, err)
   297  
   298  	// Write valid checksum.
   299  	id = ident.StringID("bar")
   300  	tags = ident.NewTags(ident.Tag{
   301  		Name:  ident.StringID("foo"),
   302  		Value: ident.StringID("bar"),
   303  	}, ident.Tag{
   304  		Name:  ident.StringID("baz"),
   305  		Value: ident.StringID("qux"),
   306  	}, ident.Tag{
   307  		Name:  ident.StringID("qar"),
   308  		Value: ident.StringID("qaz"),
   309  	})
   310  	data = checked.NewBytes([]byte{1, 2, 3}, nil)
   311  	data.IncRef()
   312  	checksum = digest.Checksum(data.Bytes())
   313  
   314  	metadata = persist.NewMetadataFromIDAndTags(id, tags,
   315  		persist.MetadataOptions{})
   316  	err = writer.Write(metadata, data, checksum)
   317  	require.NoError(t, err)
   318  
   319  	// Close volume.
   320  	err = writer.Close()
   321  	require.NoError(t, err)
   322  
   323  	// Fix the volume.
   324  	run(runOptions{
   325  		filePathPrefix:      testWriter.testDir,
   326  		fixDir:              fixDir,
   327  		fixInvalidChecksums: true,
   328  		bytesPool:           testBytesPool,
   329  		log:                 zap.NewExample(),
   330  	})
   331  
   332  	// Read back the volume from fix dir.
   333  	reader, err := fs.NewReader(nil, fs.NewOptions().SetFilePathPrefix(fixDir))
   334  	require.NoError(t, err)
   335  
   336  	err = reader.Open(fs.DataReaderOpenOptions{
   337  		Identifier:  testWriter.fileSetID,
   338  		FileSetType: persist.FileSetFlushType,
   339  	})
   340  	require.NoError(t, err)
   341  
   342  	require.Equal(t, 1, reader.Entries())
   343  
   344  	readID, readTags, _, _, err := reader.Read()
   345  	require.NoError(t, err)
   346  
   347  	require.Equal(t, "bar", readID.String())
   348  
   349  	tagsMap := make(map[string]string)
   350  	for readTags.Next() {
   351  		tag := readTags.Current()
   352  		tagsMap[tag.Name.String()] = tag.Value.String()
   353  	}
   354  	require.NoError(t, readTags.Err())
   355  	readTags.Close()
   356  
   357  	require.Equal(t, map[string]string{"foo": "bar", "baz": "qux", "qar": "qaz"}, tagsMap)
   358  
   359  	err = reader.Close()
   360  	require.NoError(t, err)
   361  }