github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/client/aggregate_results_accumulator_consistency_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 client 22 23 import ( 24 "fmt" 25 "testing" 26 27 "github.com/m3db/m3/src/cluster/shard" 28 "github.com/m3db/m3/src/dbnode/generated/thrift/rpc" 29 "github.com/m3db/m3/src/dbnode/topology" 30 tu "github.com/m3db/m3/src/dbnode/topology/testutil" 31 xtime "github.com/m3db/m3/src/x/time" 32 ) 33 34 var ( 35 testStartTime, testEndTime xtime.UnixNano 36 testAggregateSuccessResponse = rpc.AggregateQueryRawResult_{} 37 errTestAggregate = fmt.Errorf("random error") 38 ) 39 40 func TestAggregateResultsAccumulatorAnyResponseShouldTerminateConsistencyLevelOneSimpleTopo(t *testing.T) { 41 // rf=3, 30 shards total; three identical hosts 42 topoMap := tu.MustNewTopologyMap(3, map[string][]shard.Shard{ 43 "testhost0": tu.ShardsRange(0, 29, shard.Available), 44 "testhost1": tu.ShardsRange(0, 29, shard.Available), 45 "testhost2": tu.ShardsRange(0, 29, shard.Available), 46 }) 47 48 // any response should satisfy consistency lvl one 49 for i := 0; i < 3; i++ { 50 testFetchStateWorkflow{ 51 t: t, 52 topoMap: topoMap, 53 level: topology.ReadConsistencyLevelOne, 54 steps: []testFetchStateWorklowStep{ 55 { 56 hostname: fmt.Sprintf("testhost%d", i), 57 aggregateResult: &testAggregateSuccessResponse, 58 expectedDone: true, 59 }, 60 }, 61 }.run() 62 } 63 64 // should terminate only after all failures, and say it failed 65 testFetchStateWorkflow{ 66 t: t, 67 topoMap: topoMap, 68 level: topology.ReadConsistencyLevelOne, 69 steps: []testFetchStateWorklowStep{ 70 { 71 hostname: "testhost0", 72 aggregateErr: errTestAggregate, 73 }, 74 { 75 hostname: "testhost1", 76 aggregateErr: errTestAggregate, 77 }, 78 { 79 hostname: "testhost1", 80 aggregateErr: errTestAggregate, 81 expectedDone: true, 82 expectedErr: true, 83 }, 84 }, 85 }.run() 86 } 87 88 func TestAggregateResultsAccumulatorShardAvailabilityIsEnforced(t *testing.T) { 89 // rf=3, 30 shards total; three identical hosts 90 topoMap := tu.MustNewTopologyMap(3, map[string][]shard.Shard{ 91 "testhost0": tu.ShardsRange(0, 29, shard.Available), 92 "testhost1": tu.ShardsRange(0, 29, shard.Initializing), 93 "testhost2": tu.ShardsRange(0, 29, shard.Available), 94 }) 95 96 // responses from testhost1 should not count towards success 97 // for consistency level 1 98 testFetchStateWorkflow{ 99 t: t, 100 topoMap: topoMap, 101 level: topology.ReadConsistencyLevelOne, 102 steps: []testFetchStateWorklowStep{ 103 { 104 hostname: "testhost1", 105 aggregateResult: &testAggregateSuccessResponse, 106 expectedDone: false, 107 }, 108 }, 109 }.run() 110 111 // for consistency level unstrict majority 112 testFetchStateWorkflow{ 113 t: t, 114 topoMap: topoMap, 115 level: topology.ReadConsistencyLevelUnstrictMajority, 116 steps: []testFetchStateWorklowStep{ 117 { 118 hostname: "testhost1", 119 aggregateResult: &testAggregateSuccessResponse, 120 }, 121 { 122 hostname: "testhost2", 123 aggregateErr: errTestAggregate, 124 }, 125 { 126 hostname: "testhost0", 127 aggregateErr: errTestAggregate, 128 expectedDone: true, 129 expectedErr: true, 130 }, 131 }, 132 }.run() 133 134 // for consistency level majority 135 testFetchStateWorkflow{ 136 t: t, 137 topoMap: topoMap, 138 level: topology.ReadConsistencyLevelMajority, 139 steps: []testFetchStateWorklowStep{ 140 { 141 hostname: "testhost1", 142 aggregateResult: &testAggregateSuccessResponse, 143 }, 144 { 145 hostname: "testhost2", 146 aggregateResult: &testAggregateSuccessResponse, 147 }, 148 { 149 hostname: "testhost0", 150 aggregateErr: errTestAggregate, 151 expectedDone: true, 152 expectedErr: true, 153 }, 154 }, 155 }.run() 156 157 // for consistency level unstrict all 158 testFetchStateWorkflow{ 159 t: t, 160 topoMap: topoMap, 161 level: topology.ReadConsistencyLevelUnstrictAll, 162 steps: []testFetchStateWorklowStep{ 163 { 164 hostname: "testhost1", 165 aggregateResult: &testAggregateSuccessResponse, 166 }, 167 { 168 hostname: "testhost2", 169 aggregateResult: &testAggregateSuccessResponse, 170 }, 171 { 172 hostname: "testhost0", 173 aggregateErr: errTestAggregate, 174 expectedDone: true, 175 expectedErr: false, 176 }, 177 }, 178 }.run() 179 testFetchStateWorkflow{ 180 t: t, 181 topoMap: topoMap, 182 level: topology.ReadConsistencyLevelUnstrictAll, 183 steps: []testFetchStateWorklowStep{ 184 { 185 hostname: "testhost1", 186 aggregateErr: errTestAggregate, 187 }, 188 { 189 hostname: "testhost2", 190 aggregateResult: &testAggregateSuccessResponse, 191 }, 192 { 193 hostname: "testhost0", 194 aggregateErr: errTestAggregate, 195 expectedDone: true, 196 expectedErr: false, 197 }, 198 }, 199 }.run() 200 testFetchStateWorkflow{ 201 t: t, 202 topoMap: topoMap, 203 level: topology.ReadConsistencyLevelUnstrictAll, 204 steps: []testFetchStateWorklowStep{ 205 { 206 hostname: "testhost1", 207 aggregateErr: errTestAggregate, 208 }, 209 { 210 hostname: "testhost2", 211 aggregateErr: errTestAggregate, 212 }, 213 { 214 hostname: "testhost0", 215 aggregateErr: errTestAggregate, 216 expectedDone: true, 217 expectedErr: true, 218 }, 219 }, 220 }.run() 221 222 // for consistency level all 223 testFetchStateWorkflow{ 224 t: t, 225 topoMap: topoMap, 226 level: topology.ReadConsistencyLevelAll, 227 steps: []testFetchStateWorklowStep{ 228 { 229 hostname: "testhost1", 230 aggregateResult: &testAggregateSuccessResponse, 231 }, 232 { 233 hostname: "testhost2", 234 aggregateResult: &testAggregateSuccessResponse, 235 }, 236 { 237 hostname: "testhost0", 238 aggregateResult: &testAggregateSuccessResponse, 239 expectedDone: true, 240 expectedErr: true, 241 }, 242 }, 243 }.run() 244 } 245 246 func TestAggregateResultsAccumulatorAnyResponseShouldTerminateConsistencyLevelOneComplexTopo(t *testing.T) { 247 // rf=3, 30 shards total; 2 identical hosts, one additional host with a subset of all shards 248 topoMap := tu.MustNewTopologyMap(3, map[string][]shard.Shard{ 249 "testhost0": tu.ShardsRange(0, 29, shard.Available), 250 "testhost1": tu.ShardsRange(0, 29, shard.Available), 251 "testhost2": tu.ShardsRange(10, 20, shard.Available), 252 }) 253 254 // a single response from a host with partial shards isn't enough 255 testFetchStateWorkflow{ 256 t: t, 257 topoMap: topoMap, 258 level: topology.ReadConsistencyLevelOne, 259 steps: []testFetchStateWorklowStep{ 260 { 261 hostname: "testhost2", 262 aggregateResult: &testAggregateSuccessResponse, 263 expectedDone: false, 264 }, 265 }, 266 }.run() 267 } 268 269 func TestAggregateResultsAccumulatorConsistencyUnstrictMajority(t *testing.T) { 270 // rf=3, 30 shards total; three identical hosts 271 topoMap := tu.MustNewTopologyMap(3, map[string][]shard.Shard{ 272 "testhost0": tu.ShardsRange(0, 29, shard.Available), 273 "testhost1": tu.ShardsRange(0, 29, shard.Available), 274 "testhost2": tu.ShardsRange(0, 29, shard.Available), 275 }) 276 277 // two success responses should succeed immediately 278 testFetchStateWorkflow{ 279 t: t, 280 topoMap: topoMap, 281 level: topology.ReadConsistencyLevelUnstrictMajority, 282 steps: []testFetchStateWorklowStep{ 283 { 284 hostname: "testhost0", 285 aggregateResult: &testAggregateSuccessResponse, 286 expectedDone: false, 287 }, 288 { 289 hostname: "testhost1", 290 aggregateResult: &testAggregateSuccessResponse, 291 expectedDone: true, 292 }, 293 }, 294 }.run() 295 296 // two failures, and one success response should succeed 297 testFetchStateWorkflow{ 298 t: t, 299 topoMap: topoMap, 300 level: topology.ReadConsistencyLevelUnstrictMajority, 301 steps: []testFetchStateWorklowStep{ 302 { 303 hostname: "testhost0", 304 aggregateErr: errTestAggregate, 305 }, 306 { 307 hostname: "testhost1", 308 aggregateErr: errTestAggregate, 309 }, 310 { 311 hostname: "testhost1", 312 aggregateResult: &testAggregateSuccessResponse, 313 expectedDone: true, 314 }, 315 }, 316 }.run() 317 318 // should terminate only after all failures 319 testFetchStateWorkflow{ 320 t: t, 321 topoMap: topoMap, 322 level: topology.ReadConsistencyLevelUnstrictMajority, 323 steps: []testFetchStateWorklowStep{ 324 { 325 hostname: "testhost0", 326 aggregateErr: errTestAggregate, 327 }, 328 { 329 hostname: "testhost1", 330 aggregateErr: errTestAggregate, 331 }, 332 { 333 hostname: "testhost1", 334 aggregateErr: errTestAggregate, 335 expectedErr: true, 336 expectedDone: true, 337 }, 338 }, 339 }.run() 340 } 341 342 func TestAggregateResultsAccumulatorConsistencyUnstrictMajorityComplexTopo(t *testing.T) { 343 // rf=3, 30 shards total; three identical hosts 344 topoMap := tu.MustNewTopologyMap(3, map[string][]shard.Shard{ 345 "testhost0": tu.ShardsRange(0, 29, shard.Initializing), 346 "testhost1": tu.ShardsRange(0, 29, shard.Available), 347 "testhost2": tu.ShardsRange(0, 29, shard.Available), 348 "testhost3": tu.ShardsRange(0, 29, shard.Leaving), 349 }) 350 351 // one success responses should succeed 352 testFetchStateWorkflow{ 353 t: t, 354 topoMap: topoMap, 355 level: topology.ReadConsistencyLevelUnstrictMajority, 356 steps: []testFetchStateWorklowStep{ 357 { 358 hostname: "testhost0", 359 aggregateResult: &testAggregateSuccessResponse, 360 }, 361 { 362 hostname: "testhost1", 363 aggregateResult: &testAggregateSuccessResponse, 364 }, 365 { 366 hostname: "testhost2", 367 aggregateErr: errTestAggregate, 368 }, 369 { 370 hostname: "testhost3", 371 aggregateResult: &testAggregateSuccessResponse, 372 expectedDone: true, 373 }, 374 }, 375 }.run() 376 } 377 378 func TestAggregateResultsAccumulatorComplextTopoUnstrictMajorityPartialResponses(t *testing.T) { 379 // rf=3, 30 shards total; 2 identical "complete hosts", 2 additional hosts which together comprise a "complete" host. 380 topoMap := tu.MustNewTopologyMap(3, map[string][]shard.Shard{ 381 "testhost0": tu.ShardsRange(0, 29, shard.Available), 382 "testhost1": tu.ShardsRange(0, 29, shard.Available), 383 "testhost2": tu.ShardsRange(15, 29, shard.Available), 384 "testhost3": tu.ShardsRange(0, 14, shard.Available), 385 }) 386 387 // response from testhost2+testhost3 should be sufficient 388 testFetchStateWorkflow{ 389 t: t, 390 topoMap: topoMap, 391 level: topology.ReadConsistencyLevelUnstrictMajority, 392 steps: []testFetchStateWorklowStep{ 393 { 394 hostname: "testhost2", 395 aggregateResult: &testAggregateSuccessResponse, 396 }, 397 { 398 hostname: "testhost3", 399 aggregateResult: &testAggregateSuccessResponse, 400 }, 401 { 402 hostname: "testhost1", 403 aggregateErr: errTestAggregate, 404 }, 405 { 406 hostname: "testhost0", 407 aggregateErr: errTestAggregate, 408 expectedDone: true, 409 }, 410 }, 411 }.run() 412 } 413 414 func TestAggregateResultsAccumulatorComplexIncompleteTopoUnstrictMajorityPartialResponses(t *testing.T) { 415 // rf=3, 30 shards total; 2 identical "complete hosts", 2 additional hosts which do not comprise a complete host. 416 topoMap := tu.MustNewTopologyMap(3, map[string][]shard.Shard{ 417 "testhost0": tu.ShardsRange(0, 29, shard.Available), 418 "testhost1": tu.ShardsRange(0, 29, shard.Available), 419 "testhost2": tu.ShardsRange(15, 27, shard.Available), 420 "testhost3": tu.ShardsRange(0, 14, shard.Available), 421 }) 422 423 // response from testhost2+testhost3 should be in-sufficient, as they're not complete together 424 testFetchStateWorkflow{ 425 t: t, 426 topoMap: topoMap, 427 level: topology.ReadConsistencyLevelUnstrictMajority, 428 steps: []testFetchStateWorklowStep{ 429 { 430 hostname: "testhost2", 431 aggregateResult: &testAggregateSuccessResponse, 432 }, 433 { 434 hostname: "testhost3", 435 aggregateResult: &testAggregateSuccessResponse, 436 }, 437 { 438 hostname: "testhost1", 439 aggregateErr: errTestAggregate, 440 }, 441 { 442 hostname: "testhost0", 443 aggregateErr: errTestAggregate, 444 expectedDone: true, 445 expectedErr: true, 446 }, 447 }, 448 }.run() 449 } 450 451 func TestAggregateResultsAccumulatorReadConsitencyLevelMajority(t *testing.T) { 452 // rf=3, 30 shards total; three identical hosts 453 topoMap := tu.MustNewTopologyMap(3, map[string][]shard.Shard{ 454 "testhost0": tu.ShardsRange(0, 29, shard.Available), 455 "testhost1": tu.ShardsRange(0, 29, shard.Available), 456 "testhost2": tu.ShardsRange(0, 29, shard.Available), 457 }) 458 459 // any single success response should not satisfy consistency majority 460 for i := 0; i < 3; i++ { 461 testFetchStateWorkflow{ 462 t: t, 463 topoMap: topoMap, 464 level: topology.ReadConsistencyLevelMajority, 465 steps: []testFetchStateWorklowStep{ 466 { 467 hostname: fmt.Sprintf("testhost%d", i), 468 aggregateResult: &testAggregateSuccessResponse, 469 expectedDone: false, 470 }, 471 }, 472 }.run() 473 } 474 475 // all responses failing should fail consistency lvl majority 476 testFetchStateWorkflow{ 477 t: t, 478 topoMap: topoMap, 479 level: topology.ReadConsistencyLevelMajority, 480 steps: []testFetchStateWorklowStep{ 481 { 482 hostname: "testhost0", 483 aggregateErr: errTestAggregate, 484 }, 485 { 486 hostname: "testhost1", 487 aggregateErr: errTestAggregate, 488 }, 489 { 490 hostname: "testhost2", 491 aggregateErr: errTestAggregate, 492 expectedDone: true, 493 expectedErr: true, 494 }, 495 }, 496 }.run() 497 498 // any two responses failing should fail regardless of third response 499 testFetchStateWorkflow{ 500 t: t, 501 topoMap: topoMap, 502 level: topology.ReadConsistencyLevelMajority, 503 steps: []testFetchStateWorklowStep{ 504 { 505 hostname: "testhost0", 506 aggregateErr: errTestAggregate, 507 }, 508 { 509 hostname: "testhost1", 510 aggregateResult: &testAggregateSuccessResponse, 511 }, 512 { 513 hostname: "testhost2", 514 aggregateErr: errTestAggregate, 515 expectedDone: true, 516 expectedErr: true, 517 }, 518 }, 519 }.run() 520 }