github.com/m3db/m3@v1.5.0/src/dbnode/integration/disk_cleanup_index_corrupted_test.go (about)

     1  // +build integration
     2  
     3  // Copyright (c) 2021 Uber Technologies, Inc.
     4  //
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  //
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  //
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  package integration
    24  
    25  import (
    26  	"os"
    27  	"reflect"
    28  	"sort"
    29  	"strings"
    30  	"testing"
    31  	"time"
    32  
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  
    36  	"github.com/m3db/m3/src/dbnode/namespace"
    37  	"github.com/m3db/m3/src/dbnode/persist/fs"
    38  	"github.com/m3db/m3/src/dbnode/retention"
    39  	xclock "github.com/m3db/m3/src/x/clock"
    40  	xtime "github.com/m3db/m3/src/x/time"
    41  )
    42  
    43  func TestDiskCleanupIndexCorrupted(t *testing.T) {
    44  	if testing.Short() {
    45  		t.SkipNow() // Just skip if we're doing a short run
    46  	}
    47  
    48  	var (
    49  		rOpts        = retention.NewOptions().SetRetentionPeriod(48 * time.Hour)
    50  		nsBlockSize  = time.Hour
    51  		idxBlockSize = 2 * time.Hour
    52  
    53  		nsROpts = rOpts.SetBlockSize(nsBlockSize)
    54  		idxOpts = namespace.NewIndexOptions().SetBlockSize(idxBlockSize).SetEnabled(true)
    55  		nsOpts  = namespace.NewOptions().
    56  			SetCleanupEnabled(true).
    57  			SetRetentionOptions(nsROpts).
    58  			SetIndexOptions(idxOpts)
    59  	)
    60  
    61  	ns, err := namespace.NewMetadata(testNamespaces[0], nsOpts)
    62  	require.NoError(t, err)
    63  
    64  	opts := NewTestOptions(t).
    65  		SetNamespaces([]namespace.Metadata{ns})
    66  	setup, err := NewTestSetup(t, opts, nil)
    67  	require.NoError(t, err)
    68  	defer setup.Close()
    69  
    70  	filePathPrefix := setup.StorageOpts().CommitLogOptions().FilesystemOptions().FilePathPrefix()
    71  
    72  	// Now create some fileset files
    73  	var (
    74  		filesetsIdentifiers = make([]fs.FileSetFileIdentifier, 0)
    75  		numVolumes          = 4
    76  
    77  		now         = setup.NowFn()().Truncate(idxBlockSize)
    78  		blockStarts = []xtime.UnixNano{
    79  			now.Add(-3 * idxBlockSize),
    80  			now.Add(-2 * idxBlockSize),
    81  			now.Add(-1 * idxBlockSize),
    82  		}
    83  	)
    84  	for _, blockStart := range blockStarts {
    85  		for idx := 0; idx < numVolumes; idx++ {
    86  			filesetsIdentifiers = append(filesetsIdentifiers, fs.FileSetFileIdentifier{
    87  				Namespace:   ns.ID(),
    88  				BlockStart:  blockStart,
    89  				VolumeIndex: idx,
    90  			})
    91  		}
    92  	}
    93  	writeIndexFileSetFiles(t, setup.StorageOpts(), ns, filesetsIdentifiers)
    94  
    95  	filesets := fs.ReadIndexInfoFiles(fs.ReadIndexInfoFilesOptions{
    96  		FilePathPrefix:   filePathPrefix,
    97  		Namespace:        ns.ID(),
    98  		ReaderBufferSize: setup.FilesystemOpts().InfoReaderBufferSize(),
    99  	})
   100  	require.Len(t, filesets, len(blockStarts)*numVolumes)
   101  
   102  	filesThatShouldBeKept := make([]string, 0)
   103  	keep := func(files []string) {
   104  		filesThatShouldBeKept = append(filesThatShouldBeKept, files...)
   105  	}
   106  	// Corrupt some filesets.
   107  	forBlockStart(blockStarts[0], filesets, func(filesets []fs.ReadIndexInfoFileResult) {
   108  		keep(missingDigest(t, filesets[0])) // most recent volume index for volume type
   109  		corruptedInfo(t, filesets[1])
   110  		missingInfo(t, filesets[2])
   111  		keep(corruptedInfo(t, filesets[3])) // corrupted info files are kept if it's the most recent volume index
   112  	})
   113  
   114  	forBlockStart(blockStarts[1], filesets, func(filesets []fs.ReadIndexInfoFileResult) {
   115  		keep(filesets[0].AbsoluteFilePaths)
   116  		missingDigest(t, filesets[1])
   117  		missingDigest(t, filesets[2])
   118  		keep(missingDigest(t, filesets[3])) // most recent volume index for volume type
   119  	})
   120  
   121  	forBlockStart(blockStarts[2], filesets, func(filesets []fs.ReadIndexInfoFileResult) {
   122  		missingInfo(t, filesets[0])
   123  		corruptedInfo(t, filesets[1])
   124  		missingDigest(t, filesets[2])
   125  		keep(filesets[3].AbsoluteFilePaths) // most recent volume index for volume type
   126  	})
   127  	sort.Strings(filesThatShouldBeKept)
   128  
   129  	// Start the server
   130  	log := setup.StorageOpts().InstrumentOptions().Logger()
   131  	require.NoError(t, setup.StartServer())
   132  	log.Debug("server is now up")
   133  
   134  	// Stop the server
   135  	defer func() {
   136  		require.NoError(t, setup.StopServer())
   137  		log.Debug("server is now down")
   138  	}()
   139  
   140  	// Check if corrupted files have been deleted
   141  	waitTimeout := 30 * time.Second
   142  	deleted := xclock.WaitUntil(func() bool {
   143  		files, err := fs.IndexFileSetsBefore(filePathPrefix, ns.ID(), now.Add(time.Minute))
   144  		require.NoError(t, err)
   145  		sort.Strings(files)
   146  		return reflect.DeepEqual(files, filesThatShouldBeKept)
   147  	}, waitTimeout)
   148  	if !assert.True(t, deleted) {
   149  		files, err := fs.IndexFileSetsBefore(filePathPrefix, ns.ID(), now.Add(time.Minute))
   150  		require.NoError(t, err)
   151  		sort.Strings(files)
   152  		require.Equal(t, filesThatShouldBeKept, files)
   153  	}
   154  }
   155  
   156  func forBlockStart(
   157  	blockStart xtime.UnixNano,
   158  	filesets []fs.ReadIndexInfoFileResult,
   159  	fn func([]fs.ReadIndexInfoFileResult),
   160  ) {
   161  	res := make([]fs.ReadIndexInfoFileResult, 0)
   162  	for _, f := range filesets {
   163  		if f.ID.BlockStart.Equal(blockStart) {
   164  			res = append(res, f)
   165  		}
   166  	}
   167  	sort.Slice(res, func(i, j int) bool {
   168  		return res[i].ID.VolumeIndex < res[j].ID.VolumeIndex
   169  	})
   170  	fn(res)
   171  }
   172  
   173  func corruptedInfo(t *testing.T, fileset fs.ReadIndexInfoFileResult) []string {
   174  	for _, f := range fileset.AbsoluteFilePaths {
   175  		if strings.Contains(f, "info.db") {
   176  			require.NoError(t, os.Truncate(f, 0))
   177  			return fileset.AbsoluteFilePaths
   178  		}
   179  	}
   180  	require.Fail(t, "could not find info file")
   181  	return nil
   182  }
   183  
   184  func missingInfo(t *testing.T, fileset fs.ReadIndexInfoFileResult) []string { //nolint:unparam
   185  	for i, f := range fileset.AbsoluteFilePaths {
   186  		if strings.Contains(f, "info.db") {
   187  			require.NoError(t, os.Remove(f))
   188  			res := make([]string, 0)
   189  			res = append(res, fileset.AbsoluteFilePaths[:i]...)
   190  			res = append(res, fileset.AbsoluteFilePaths[i+1:]...)
   191  			return res
   192  		}
   193  	}
   194  	require.Fail(t, "could not find info file")
   195  	return nil
   196  }
   197  
   198  func missingDigest(t *testing.T, fileset fs.ReadIndexInfoFileResult) []string {
   199  	for i, f := range fileset.AbsoluteFilePaths {
   200  		if strings.Contains(f, "digest.db") {
   201  			require.NoError(t, os.Remove(f))
   202  			res := make([]string, 0)
   203  			res = append(res, fileset.AbsoluteFilePaths[:i]...)
   204  			res = append(res, fileset.AbsoluteFilePaths[i+1:]...)
   205  			return res
   206  		}
   207  	}
   208  	require.Fail(t, "could not find digest file")
   209  	return nil
   210  }