github.com/m3db/m3@v1.5.0/src/dbnode/persist/fs/migration/migration_test.go (about)

     1  // Copyright (c) 2020 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 migration
    22  
    23  import (
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path"
    28  	"path/filepath"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/m3db/m3/src/dbnode/digest"
    33  	"github.com/m3db/m3/src/dbnode/namespace"
    34  	"github.com/m3db/m3/src/dbnode/persist"
    35  	"github.com/m3db/m3/src/dbnode/persist/fs"
    36  	"github.com/m3db/m3/src/dbnode/persist/fs/msgpack"
    37  	"github.com/m3db/m3/src/dbnode/storage"
    38  	"github.com/m3db/m3/src/dbnode/storage/block"
    39  	"github.com/m3db/m3/src/dbnode/storage/index"
    40  	"github.com/m3db/m3/src/x/checked"
    41  	"github.com/m3db/m3/src/x/ident"
    42  	"github.com/m3db/m3/src/x/instrument"
    43  	xtime "github.com/m3db/m3/src/x/time"
    44  
    45  	"github.com/stretchr/testify/require"
    46  )
    47  
    48  func TestToVersion1_1Run(t *testing.T) {
    49  	dir := createTempDir(t)
    50  	filePathPrefix := filepath.Join(dir, "")
    51  	defer os.RemoveAll(dir)
    52  
    53  	var shard uint32 = 1
    54  	nsID := ident.StringID("foo")
    55  
    56  	// Write unmigrated fileset to disk
    57  	fsOpts := writeUnmigratedData(t, filePathPrefix, nsID, shard)
    58  
    59  	// Read info file of just written fileset
    60  	results := fs.ReadInfoFiles(filePathPrefix, nsID, shard,
    61  		fsOpts.InfoReaderBufferSize(), fsOpts.DecodingOptions(), persist.FileSetFlushType)
    62  	require.Equal(t, 1, len(results))
    63  	infoFileResult := results[0]
    64  	indexFd := openFile(t, fsOpts, nsID, shard, infoFileResult, "index")
    65  	oldBytes, err := ioutil.ReadAll(indexFd)
    66  	require.NoError(t, err)
    67  
    68  	// Configure and run migration
    69  	pm, err := fs.NewPersistManager(
    70  		fsOpts.SetEncodingOptions(msgpack.DefaultLegacyEncodingOptions)) // Set encoder to most up-to-date version
    71  	require.NoError(t, err)
    72  	icm, err := fs.NewIndexClaimsManager(fsOpts)
    73  	require.NoError(t, err)
    74  
    75  	md, err := namespace.NewMetadata(nsID, namespace.NewOptions())
    76  	require.NoError(t, err)
    77  
    78  	plCache, err := index.NewPostingsListCache(1, index.PostingsListCacheOptions{
    79  		InstrumentOptions: instrument.NewOptions(),
    80  	})
    81  	require.NoError(t, err)
    82  	defer plCache.Start()()
    83  
    84  	opts := NewTaskOptions().
    85  		SetNewMergerFn(fs.NewMerger).
    86  		SetPersistManager(pm).
    87  		SetNamespaceMetadata(md).
    88  		SetStorageOptions(storage.DefaultTestOptions().
    89  			SetPersistManager(pm).
    90  			SetIndexClaimsManager(icm).
    91  			SetNamespaceInitializer(namespace.NewStaticInitializer([]namespace.Metadata{md})).
    92  			SetRepairEnabled(false).
    93  			SetIndexOptions(index.NewOptions().
    94  				SetPostingsListCache(plCache)).
    95  			SetBlockLeaseManager(block.NewLeaseManager(nil))).
    96  		SetShard(shard).
    97  		SetInfoFileResult(infoFileResult).
    98  		SetFilesystemOptions(fsOpts)
    99  
   100  	task, err := NewToVersion1_1Task(opts)
   101  	require.NoError(t, err)
   102  
   103  	updatedInfoFile, err := task.Run()
   104  	require.NoError(t, err)
   105  
   106  	// Read new info file and make sure it matches results returned by task
   107  	newInfoFd := openFile(t, fsOpts, nsID, shard, updatedInfoFile, "info")
   108  
   109  	newInfoBytes, err := ioutil.ReadAll(newInfoFd)
   110  	require.NoError(t, err)
   111  
   112  	decoder := msgpack.NewDecoder(nil)
   113  	decoder.Reset(msgpack.NewByteDecoderStream(newInfoBytes))
   114  	info, err := decoder.DecodeIndexInfo()
   115  
   116  	require.Equal(t, updatedInfoFile.Info, info)
   117  
   118  	// Read the index entries of new volume set
   119  	indexFd = openFile(t, fsOpts, nsID, shard, updatedInfoFile, "index")
   120  	newBytes, err := ioutil.ReadAll(indexFd)
   121  	require.NoError(t, err)
   122  
   123  	// Diff bytes of unmigrated vs migrated fileset
   124  	require.NotEqual(t, oldBytes, newBytes)
   125  
   126  	// Corrupt bytes to trip newly added checksum
   127  	newBytes[len(newBytes)-1] = 1 + newBytes[len(newBytes)-1]
   128  	decoder.Reset(msgpack.NewByteDecoderStream(newBytes))
   129  	_, err = decoder.DecodeIndexEntry(nil)
   130  	require.Error(t, err)
   131  	require.Contains(t, err.Error(), "checksum mismatch")
   132  }
   133  
   134  func openFile(
   135  	t *testing.T,
   136  	fsOpts fs.Options,
   137  	nsID ident.ID,
   138  	shard uint32,
   139  	infoFileResult fs.ReadInfoFileResult,
   140  	fileType string,
   141  ) *os.File {
   142  	indexFd, err := os.Open(path.Join(fsOpts.FilePathPrefix(), fmt.Sprintf("data/%s/%d/fileset-%d-%d-%s.db",
   143  		nsID.String(), shard, infoFileResult.Info.BlockStart, infoFileResult.Info.VolumeIndex, fileType)))
   144  	require.NoError(t, err)
   145  	return indexFd
   146  }
   147  
   148  func writeUnmigratedData(
   149  	t *testing.T,
   150  	filePathPrefix string,
   151  	nsID ident.ID,
   152  	shard uint32,
   153  ) fs.Options {
   154  	// Use encoding options that will not generate entry level checksums
   155  	eOpts := msgpack.LegacyEncodingOptions{EncodeLegacyIndexEntryVersion: msgpack.LegacyEncodingIndexEntryVersionV2}
   156  
   157  	// Write data
   158  	fsOpts := fs.NewOptions().
   159  		SetFilePathPrefix(filePathPrefix).
   160  		SetEncodingOptions(eOpts)
   161  	w, err := fs.NewWriter(fsOpts)
   162  	require.NoError(t, err)
   163  
   164  	blockStart := xtime.Now().Truncate(time.Hour)
   165  	writerOpts := fs.DataWriterOpenOptions{
   166  		Identifier: fs.FileSetFileIdentifier{
   167  			Namespace:   nsID,
   168  			Shard:       shard,
   169  			BlockStart:  blockStart,
   170  			VolumeIndex: 0,
   171  		},
   172  		BlockSize: 2 * time.Hour,
   173  	}
   174  	err = w.Open(writerOpts)
   175  	require.NoError(t, err)
   176  
   177  	entry := []byte{1, 2, 3}
   178  
   179  	chkdBytes := checked.NewBytes(entry, nil)
   180  	chkdBytes.IncRef()
   181  	metadata := persist.NewMetadataFromIDAndTags(ident.StringID("foo"),
   182  		ident.Tags{}, persist.MetadataOptions{})
   183  	err = w.Write(metadata, chkdBytes, digest.Checksum(entry))
   184  	require.NoError(t, err)
   185  
   186  	err = w.Close()
   187  	require.NoError(t, err)
   188  
   189  	return fsOpts
   190  }
   191  
   192  func createTempDir(t *testing.T) string {
   193  	dir, err := ioutil.TempDir("", "testdir")
   194  	require.NoError(t, err)
   195  
   196  	return dir
   197  }