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 }