github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/repair/metadata_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 repair
    22  
    23  import (
    24  	"errors"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/dbnode/client"
    29  	"github.com/m3db/m3/src/dbnode/storage/block"
    30  	"github.com/m3db/m3/src/dbnode/topology"
    31  	"github.com/m3db/m3/src/x/ident"
    32  	xtime "github.com/m3db/m3/src/x/time"
    33  
    34  	"github.com/golang/mock/gomock"
    35  	"github.com/stretchr/testify/require"
    36  )
    37  
    38  func testReplicaMetadataSlicePool() ReplicaMetadataSlicePool {
    39  	return NewReplicaMetadataSlicePool(nil, 0)
    40  }
    41  
    42  func testRepairOptions() Options {
    43  	return NewOptions()
    44  }
    45  
    46  func TestReplicaBlockMetadataAdd(t *testing.T) {
    47  	meta1 := block.NewMetadata(
    48  		ident.StringID("some-id"), ident.Tags{}, 0, 1, nil, 0)
    49  	meta2 := block.NewMetadata(
    50  		ident.StringID("some-id"), ident.Tags{}, 0, 2, new(uint32), 0)
    51  
    52  	now := xtime.Now()
    53  	m := NewReplicaBlockMetadata(now, newReplicaMetadataSlice())
    54  	inputs := []block.ReplicaMetadata{
    55  		{Host: topology.NewHost("foo", "addrFoo"), Metadata: meta1},
    56  		{Host: topology.NewHost("bar", "addrBar"), Metadata: meta2},
    57  	}
    58  	for _, input := range inputs {
    59  		m.Add(input)
    60  	}
    61  	require.Equal(t, now, m.Start())
    62  	require.Equal(t, inputs, m.Metadata())
    63  }
    64  
    65  func TestReplicaBlocksMetadataAdd(t *testing.T) {
    66  	now := xtime.Now()
    67  	block := NewReplicaBlockMetadata(now, newReplicaMetadataSlice())
    68  	m := NewReplicaBlocksMetadata()
    69  	m.Add(block)
    70  
    71  	blocks := m.Blocks()
    72  	require.Equal(t, 1, len(blocks))
    73  
    74  	block, exists := blocks[now]
    75  	require.True(t, exists)
    76  	require.Equal(t, now, block.Start())
    77  }
    78  
    79  func TestReplicaBlocksMetadataGetOrAdd(t *testing.T) {
    80  	now := xtime.Now()
    81  	m := NewReplicaBlocksMetadata()
    82  	require.Equal(t, 0, len(m.Blocks()))
    83  
    84  	// Add a block
    85  	b := m.GetOrAdd(now, testReplicaMetadataSlicePool())
    86  	require.Equal(t, now, b.Start())
    87  	blocks := m.Blocks()
    88  	require.Equal(t, 1, len(blocks))
    89  	block, exists := blocks[now]
    90  	require.True(t, exists)
    91  	require.Equal(t, now, block.Start())
    92  
    93  	// Add the same block and check we don't add new blocks
    94  	m.GetOrAdd(now, testReplicaMetadataSlicePool())
    95  	require.Equal(t, 1, len(m.Blocks()))
    96  }
    97  
    98  func TestReplicaSeriesMetadataGetOrAdd(t *testing.T) {
    99  	m := NewReplicaSeriesMetadata()
   100  
   101  	// Add a series
   102  	m.GetOrAdd(ident.StringID("foo"))
   103  	series := m.Series()
   104  	require.Equal(t, 1, series.Len())
   105  	_, exists := series.Get(ident.StringID("foo"))
   106  	require.True(t, exists)
   107  
   108  	// Add the same series and check we don't add new series
   109  	m.GetOrAdd(ident.StringID("foo"))
   110  	require.Equal(t, 1, m.Series().Len())
   111  }
   112  
   113  type testBlock struct {
   114  	id     ident.ID
   115  	ts     xtime.UnixNano
   116  	blocks []block.ReplicaMetadata
   117  }
   118  
   119  func assertEqual(t *testing.T, expected []testBlock, actual ReplicaSeriesMetadata) {
   120  	require.Equal(t, len(expected), int(actual.NumBlocks()))
   121  
   122  	for _, b := range expected {
   123  		series, ok := actual.Series().Get(b.id)
   124  		require.True(t, ok)
   125  		blocks := series.Metadata.Blocks()[b.ts]
   126  		require.Equal(t, b.blocks, blocks.Metadata())
   127  	}
   128  }
   129  
   130  func TestReplicaMetadataComparerAddLocalMetadata(t *testing.T) {
   131  	ctrl := gomock.NewController(t)
   132  	defer ctrl.Finish()
   133  
   134  	origin := topology.NewHost("foo", "addrFoo")
   135  	now := xtime.Now()
   136  	localIter := block.NewMockFilteredBlocksMetadataIter(ctrl)
   137  	inputBlocks := []block.Metadata{
   138  		block.NewMetadata(ident.StringID("foo"), ident.Tags{}, now, int64(0), new(uint32), 0),
   139  		block.NewMetadata(ident.StringID("foo"), ident.Tags{}, now.Add(time.Second), int64(2), new(uint32), 0),
   140  		block.NewMetadata(ident.StringID("bar"), ident.Tags{}, now, int64(4), nil, 0),
   141  	}
   142  
   143  	gomock.InOrder(
   144  		localIter.EXPECT().Next().Return(true),
   145  		localIter.EXPECT().Current().Return(inputBlocks[0].ID, inputBlocks[0]),
   146  		localIter.EXPECT().Next().Return(true),
   147  		localIter.EXPECT().Current().Return(inputBlocks[1].ID, inputBlocks[1]),
   148  		localIter.EXPECT().Next().Return(true),
   149  		localIter.EXPECT().Current().Return(inputBlocks[2].ID, inputBlocks[2]),
   150  		localIter.EXPECT().Next().Return(false),
   151  		localIter.EXPECT().Err().Return(nil),
   152  	)
   153  
   154  	m := NewReplicaMetadataComparer(origin, testRepairOptions()).(replicaMetadataComparer)
   155  	err := m.AddLocalMetadata(localIter)
   156  	require.NoError(t, err)
   157  
   158  	expected := []testBlock{
   159  		{inputBlocks[0].ID, inputBlocks[0].Start, []block.ReplicaMetadata{{Host: origin, Metadata: inputBlocks[0]}}},
   160  		{inputBlocks[1].ID, inputBlocks[1].Start, []block.ReplicaMetadata{{Host: origin, Metadata: inputBlocks[1]}}},
   161  		{inputBlocks[2].ID, inputBlocks[2].Start, []block.ReplicaMetadata{{Host: origin, Metadata: inputBlocks[2]}}},
   162  	}
   163  	assertEqual(t, expected, m.metadata)
   164  }
   165  
   166  func TestReplicaMetadataComparerAddPeerMetadata(t *testing.T) {
   167  	ctrl := gomock.NewController(t)
   168  	defer ctrl.Finish()
   169  
   170  	now := xtime.Now()
   171  	peerIter := client.NewMockPeerBlockMetadataIter(ctrl)
   172  	inputBlocks := []block.ReplicaMetadata{
   173  		{
   174  			Host: topology.NewHost("1", "addr1"),
   175  			Metadata: block.NewMetadata(ident.StringID("foo"), ident.Tags{},
   176  				now, int64(0), new(uint32), 0),
   177  		},
   178  		{
   179  			Host: topology.NewHost("1", "addr1"),
   180  			Metadata: block.NewMetadata(ident.StringID("foo"), ident.Tags{},
   181  				now.Add(time.Second), int64(1), new(uint32), 0),
   182  		},
   183  		{
   184  			Host: topology.NewHost("2", "addr2"),
   185  			Metadata: block.NewMetadata(ident.StringID("foo"), ident.Tags{},
   186  				now, int64(2), nil, 0),
   187  		},
   188  		{
   189  			Host: topology.NewHost("2", "addr2"),
   190  			Metadata: block.NewMetadata(ident.StringID("bar"), ident.Tags{},
   191  				now.Add(time.Second), int64(3), nil, 0),
   192  		},
   193  	}
   194  	expectedErr := errors.New("some error")
   195  
   196  	gomock.InOrder(
   197  		peerIter.EXPECT().Next().Return(true),
   198  		peerIter.EXPECT().Current().Return(inputBlocks[0].Host, inputBlocks[0].Metadata),
   199  		peerIter.EXPECT().Next().Return(true),
   200  		peerIter.EXPECT().Current().Return(inputBlocks[1].Host, inputBlocks[1].Metadata),
   201  		peerIter.EXPECT().Next().Return(true),
   202  		peerIter.EXPECT().Current().Return(inputBlocks[2].Host, inputBlocks[2].Metadata),
   203  		peerIter.EXPECT().Next().Return(true),
   204  		peerIter.EXPECT().Current().Return(inputBlocks[3].Host, inputBlocks[3].Metadata),
   205  		peerIter.EXPECT().Next().Return(false),
   206  		peerIter.EXPECT().Err().Return(expectedErr),
   207  	)
   208  
   209  	m := NewReplicaMetadataComparer(inputBlocks[0].Host, testRepairOptions()).(replicaMetadataComparer)
   210  	require.Equal(t, expectedErr, m.AddPeerMetadata(peerIter))
   211  
   212  	expected := []testBlock{
   213  		{ident.StringID("foo"), inputBlocks[0].Metadata.Start, []block.ReplicaMetadata{
   214  			inputBlocks[0],
   215  			inputBlocks[2],
   216  		}},
   217  		{ident.StringID("foo"), inputBlocks[1].Metadata.Start, []block.ReplicaMetadata{
   218  			inputBlocks[1],
   219  		}},
   220  		{ident.StringID("bar"), inputBlocks[3].Metadata.Start, []block.ReplicaMetadata{
   221  			inputBlocks[3],
   222  		}},
   223  	}
   224  	assertEqual(t, expected, m.metadata)
   225  }
   226  
   227  func TestReplicaMetadataComparerCompare(t *testing.T) {
   228  	var (
   229  		now   = xtime.Now()
   230  		hosts = []topology.Host{topology.NewHost("foo", "foo"), topology.NewHost("bar", "bar")}
   231  	)
   232  
   233  	metadata := NewReplicaSeriesMetadata()
   234  	defer metadata.Close()
   235  
   236  	ten := uint32(10)
   237  	twenty := uint32(20)
   238  	inputs := []block.ReplicaMetadata{
   239  		{
   240  			Host:     hosts[0],
   241  			Metadata: block.NewMetadata(ident.StringID("foo"), ident.Tags{}, now, int64(1), &ten, 0),
   242  		},
   243  		{
   244  			Host:     hosts[1],
   245  			Metadata: block.NewMetadata(ident.StringID("foo"), ident.Tags{}, now, int64(1), &ten, 0),
   246  		},
   247  		{
   248  			Host:     hosts[0],
   249  			Metadata: block.NewMetadata(ident.StringID("bar"), ident.Tags{}, now.Add(time.Second), int64(0), &ten, 0),
   250  		},
   251  		{
   252  			Host:     hosts[1],
   253  			Metadata: block.NewMetadata(ident.StringID("bar"), ident.Tags{}, now.Add(time.Second), int64(1), &ten, 0),
   254  		},
   255  		// hosts[0] has a checksum but hosts[1] doesn't so this block will not be repaired (skipped until the next attempt) at
   256  		// which points hosts[1] will have merged the blocks and an accurate comparison can be made.
   257  		{
   258  			Host:     hosts[0],
   259  			Metadata: block.NewMetadata(ident.StringID("baz"), ident.Tags{}, now.Add(2*time.Second), int64(2), &twenty, 0),
   260  		},
   261  		{
   262  			Host:     hosts[1],
   263  			Metadata: block.NewMetadata(ident.StringID("baz"), ident.Tags{}, now.Add(2*time.Second), int64(2), nil, 0),
   264  		},
   265  		// hosts[0] and hosts[1] both have a checksum, but they differ, so this should trigger a checksum mismatch.
   266  		{
   267  			Host:     hosts[0],
   268  			Metadata: block.NewMetadata(ident.StringID("boz"), ident.Tags{}, now.Add(2*time.Second), int64(2), &twenty, 0),
   269  		},
   270  		{
   271  			Host:     hosts[1],
   272  			Metadata: block.NewMetadata(ident.StringID("boz"), ident.Tags{}, now.Add(2*time.Second), int64(2), &ten, 0),
   273  		},
   274  		// Block only exists for host[1] but host[0] is the origin so should be consider a size/checksum mismatch.
   275  		{
   276  			Host:     hosts[1],
   277  			Metadata: block.NewMetadata(ident.StringID("gah"), ident.Tags{}, now.Add(3*time.Second), int64(1), &ten, 0),
   278  		},
   279  		// Block only exists for host[0] but host[0] is also the origin so should not be considered a size/checksum mismatch
   280  		// since the peer not the origin is missing data.
   281  		{
   282  			Host:     hosts[0],
   283  			Metadata: block.NewMetadata(ident.StringID("grr"), ident.Tags{}, now.Add(3*time.Second), int64(1), &ten, 0),
   284  		},
   285  	}
   286  	for _, input := range inputs {
   287  		metadata.GetOrAdd(input.Metadata.ID).GetOrAdd(input.Metadata.Start, testReplicaMetadataSlicePool()).Add(input)
   288  	}
   289  
   290  	sizeExpected := []testBlock{
   291  		{ident.StringID("bar"), now.Add(time.Second), []block.ReplicaMetadata{
   292  			inputs[2],
   293  			inputs[3],
   294  		}},
   295  		{ident.StringID("gah"), now.Add(3 * time.Second), []block.ReplicaMetadata{
   296  			inputs[8],
   297  		}},
   298  	}
   299  
   300  	checksumExpected := []testBlock{
   301  		{ident.StringID("boz"), now.Add(2 * time.Second), []block.ReplicaMetadata{
   302  			inputs[6],
   303  			inputs[7],
   304  		}},
   305  		{ident.StringID("gah"), now.Add(3 * time.Second), []block.ReplicaMetadata{
   306  			inputs[8],
   307  		}},
   308  	}
   309  
   310  	m := NewReplicaMetadataComparer(hosts[0], testRepairOptions()).(replicaMetadataComparer)
   311  	m.metadata = metadata
   312  
   313  	res := m.Compare()
   314  	require.Equal(t, int64(6), res.NumSeries)
   315  	require.Equal(t, int64(6), res.NumBlocks)
   316  	assertEqual(t, sizeExpected, res.SizeDifferences)
   317  	assertEqual(t, checksumExpected, res.ChecksumDifferences)
   318  }