vitess.io/vitess@v0.16.2/go/vt/vtctl/reparentutil/util_test.go (about) 1 /* 2 Copyright 20201 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package reparentutil 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 "time" 24 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 28 "vitess.io/vitess/go/mysql" 29 "vitess.io/vitess/go/test/utils" 30 "vitess.io/vitess/go/vt/logutil" 31 "vitess.io/vitess/go/vt/topo" 32 "vitess.io/vitess/go/vt/vtctl/grpcvtctldserver/testutil" 33 "vitess.io/vitess/go/vt/vtctl/reparentutil/promotionrule" 34 "vitess.io/vitess/go/vt/vterrors" 35 "vitess.io/vitess/go/vt/vttablet/tmclient" 36 37 replicationdatapb "vitess.io/vitess/go/vt/proto/replicationdata" 38 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 39 "vitess.io/vitess/go/vt/proto/vttime" 40 "vitess.io/vitess/go/vt/topo/topoproto" 41 ) 42 43 type chooseNewPrimaryTestTMClient struct { 44 tmclient.TabletManagerClient 45 replicationStatuses map[string]*replicationdatapb.Status 46 } 47 48 func (fake *chooseNewPrimaryTestTMClient) ReplicationStatus(ctx context.Context, tablet *topodatapb.Tablet) (*replicationdatapb.Status, error) { 49 if fake.replicationStatuses == nil { 50 return nil, assert.AnError 51 } 52 53 key := topoproto.TabletAliasString(tablet.Alias) 54 55 if status, ok := fake.replicationStatuses[key]; ok { 56 return status, nil 57 } 58 59 return nil, assert.AnError 60 } 61 62 func TestChooseNewPrimary(t *testing.T) { 63 t.Parallel() 64 65 ctx := context.Background() 66 logger := logutil.NewMemoryLogger() 67 tests := []struct { 68 name string 69 tmc *chooseNewPrimaryTestTMClient 70 shardInfo *topo.ShardInfo 71 tabletMap map[string]*topo.TabletInfo 72 avoidPrimaryAlias *topodatapb.TabletAlias 73 expected *topodatapb.TabletAlias 74 shouldErr bool 75 }{ 76 { 77 name: "found a replica", 78 tmc: &chooseNewPrimaryTestTMClient{ 79 // zone1-101 is behind zone1-102 80 replicationStatuses: map[string]*replicationdatapb.Status{ 81 "zone1-0000000101": { 82 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1", 83 }, 84 "zone1-0000000102": { 85 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", 86 }, 87 }, 88 }, 89 shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{ 90 PrimaryAlias: &topodatapb.TabletAlias{ 91 Cell: "zone1", 92 Uid: 100, 93 }, 94 }, nil), 95 tabletMap: map[string]*topo.TabletInfo{ 96 "primary": { 97 Tablet: &topodatapb.Tablet{ 98 Alias: &topodatapb.TabletAlias{ 99 Cell: "zone1", 100 Uid: 100, 101 }, 102 Type: topodatapb.TabletType_PRIMARY, 103 }, 104 }, 105 "replica1": { 106 Tablet: &topodatapb.Tablet{ 107 Alias: &topodatapb.TabletAlias{ 108 Cell: "zone1", 109 Uid: 101, 110 }, 111 Type: topodatapb.TabletType_REPLICA, 112 }, 113 }, 114 "replica2": { 115 Tablet: &topodatapb.Tablet{ 116 Alias: &topodatapb.TabletAlias{ 117 Cell: "zone1", 118 Uid: 102, 119 }, 120 Type: topodatapb.TabletType_REPLICA, 121 }, 122 }, 123 }, 124 avoidPrimaryAlias: &topodatapb.TabletAlias{ 125 Cell: "zone1", 126 Uid: 0, 127 }, 128 expected: &topodatapb.TabletAlias{ 129 Cell: "zone1", 130 Uid: 102, 131 }, 132 shouldErr: false, 133 }, 134 { 135 name: "found a replica - more advanced relay log position", 136 tmc: &chooseNewPrimaryTestTMClient{ 137 // zone1-101 is behind zone1-102 138 // since the relay log position for zone1-102 is more advanced 139 replicationStatuses: map[string]*replicationdatapb.Status{ 140 "zone1-0000000101": { 141 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-2", 142 }, 143 "zone1-0000000102": { 144 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1", 145 RelayLogPosition: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", 146 }, 147 }, 148 }, 149 shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{ 150 PrimaryAlias: &topodatapb.TabletAlias{ 151 Cell: "zone1", 152 Uid: 100, 153 }, 154 }, nil), 155 tabletMap: map[string]*topo.TabletInfo{ 156 "primary": { 157 Tablet: &topodatapb.Tablet{ 158 Alias: &topodatapb.TabletAlias{ 159 Cell: "zone1", 160 Uid: 100, 161 }, 162 Type: topodatapb.TabletType_PRIMARY, 163 }, 164 }, 165 "replica1": { 166 Tablet: &topodatapb.Tablet{ 167 Alias: &topodatapb.TabletAlias{ 168 Cell: "zone1", 169 Uid: 101, 170 }, 171 Type: topodatapb.TabletType_REPLICA, 172 }, 173 }, 174 "replica2": { 175 Tablet: &topodatapb.Tablet{ 176 Alias: &topodatapb.TabletAlias{ 177 Cell: "zone1", 178 Uid: 102, 179 }, 180 Type: topodatapb.TabletType_REPLICA, 181 }, 182 }, 183 }, 184 avoidPrimaryAlias: &topodatapb.TabletAlias{ 185 Cell: "zone1", 186 Uid: 0, 187 }, 188 expected: &topodatapb.TabletAlias{ 189 Cell: "zone1", 190 Uid: 102, 191 }, 192 shouldErr: false, 193 }, 194 { 195 name: "no active primary in shard", 196 tmc: &chooseNewPrimaryTestTMClient{ 197 replicationStatuses: map[string]*replicationdatapb.Status{ 198 "zone1-0000000101": { 199 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1", 200 }, 201 }, 202 }, 203 shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{}, nil), 204 tabletMap: map[string]*topo.TabletInfo{ 205 "primary": { 206 Tablet: &topodatapb.Tablet{ 207 Alias: &topodatapb.TabletAlias{ 208 Cell: "zone1", 209 Uid: 100, 210 }, 211 Type: topodatapb.TabletType_PRIMARY, 212 }, 213 }, 214 "replica1": { 215 Tablet: &topodatapb.Tablet{ 216 Alias: &topodatapb.TabletAlias{ 217 Cell: "zone1", 218 Uid: 101, 219 }, 220 Type: topodatapb.TabletType_REPLICA, 221 }, 222 }, 223 }, 224 avoidPrimaryAlias: &topodatapb.TabletAlias{ 225 Cell: "zone1", 226 Uid: 0, 227 }, 228 expected: &topodatapb.TabletAlias{ 229 Cell: "zone1", 230 Uid: 101, 231 }, 232 shouldErr: false, 233 }, 234 { 235 name: "avoid primary alias is nil", 236 tmc: &chooseNewPrimaryTestTMClient{ 237 replicationStatuses: map[string]*replicationdatapb.Status{ 238 "zone1-0000000101": { 239 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1", 240 }, 241 }, 242 }, 243 shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{ 244 PrimaryAlias: &topodatapb.TabletAlias{ 245 Cell: "zone1", 246 Uid: 100, 247 }, 248 }, nil), 249 tabletMap: map[string]*topo.TabletInfo{ 250 "primary": { 251 Tablet: &topodatapb.Tablet{ 252 Alias: &topodatapb.TabletAlias{ 253 Cell: "zone1", 254 Uid: 100, 255 }, 256 Type: topodatapb.TabletType_PRIMARY, 257 }, 258 }, 259 "replica1": { 260 Tablet: &topodatapb.Tablet{ 261 Alias: &topodatapb.TabletAlias{ 262 Cell: "zone1", 263 Uid: 101, 264 }, 265 Type: topodatapb.TabletType_REPLICA, 266 }, 267 }, 268 }, 269 avoidPrimaryAlias: nil, 270 expected: &topodatapb.TabletAlias{ 271 Cell: "zone1", 272 Uid: 101, 273 }, 274 shouldErr: false, 275 }, { 276 name: "avoid primary alias and shard primary are nil", 277 tmc: &chooseNewPrimaryTestTMClient{ 278 replicationStatuses: map[string]*replicationdatapb.Status{ 279 "zone1-0000000100": { 280 Position: "", 281 }, 282 "zone1-0000000101": { 283 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1", 284 }, 285 }, 286 }, 287 shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{}, nil), 288 tabletMap: map[string]*topo.TabletInfo{ 289 "replica1": { 290 Tablet: &topodatapb.Tablet{ 291 Alias: &topodatapb.TabletAlias{ 292 Cell: "zone1", 293 Uid: 100, 294 }, 295 Type: topodatapb.TabletType_REPLICA, 296 }, 297 }, 298 "replica2": { 299 Tablet: &topodatapb.Tablet{ 300 Alias: &topodatapb.TabletAlias{ 301 Cell: "zone1", 302 Uid: 101, 303 }, 304 Type: topodatapb.TabletType_REPLICA, 305 }, 306 }, 307 }, 308 avoidPrimaryAlias: nil, 309 expected: &topodatapb.TabletAlias{ 310 Cell: "zone1", 311 Uid: 101, 312 }, 313 shouldErr: false, 314 }, 315 { 316 name: "no replicas in primary cell", 317 tmc: &chooseNewPrimaryTestTMClient{ 318 // zone1-101 is behind zone1-102 319 replicationStatuses: map[string]*replicationdatapb.Status{ 320 "zone1-0000000101": { 321 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1", 322 }, 323 "zone1-0000000102": { 324 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", 325 }, 326 }, 327 }, 328 shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{ 329 PrimaryAlias: &topodatapb.TabletAlias{ 330 Cell: "zone2", 331 Uid: 200, 332 }, 333 }, nil), 334 tabletMap: map[string]*topo.TabletInfo{ 335 "primary": { 336 Tablet: &topodatapb.Tablet{ 337 Alias: &topodatapb.TabletAlias{ 338 Cell: "zone2", 339 Uid: 200, 340 }, 341 Type: topodatapb.TabletType_PRIMARY, 342 }, 343 }, 344 "replica1": { 345 Tablet: &topodatapb.Tablet{ 346 Alias: &topodatapb.TabletAlias{ 347 Cell: "zone1", 348 Uid: 101, 349 }, 350 Type: topodatapb.TabletType_REPLICA, 351 }, 352 }, 353 "replica2": { 354 Tablet: &topodatapb.Tablet{ 355 Alias: &topodatapb.TabletAlias{ 356 Cell: "zone1", 357 Uid: 102, 358 }, 359 Type: topodatapb.TabletType_REPLICA, 360 }, 361 }, 362 }, 363 avoidPrimaryAlias: &topodatapb.TabletAlias{ 364 Cell: "zone1", 365 Uid: 0, 366 }, 367 expected: nil, 368 shouldErr: false, 369 }, 370 { 371 name: "only available tablet is AvoidPrimary", 372 tmc: &chooseNewPrimaryTestTMClient{ 373 // zone1-101 is behind zone1-102 374 replicationStatuses: map[string]*replicationdatapb.Status{ 375 "zone1-0000000101": { 376 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1", 377 }, 378 "zone1-0000000102": { 379 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", 380 }, 381 }, 382 }, 383 shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{ 384 PrimaryAlias: &topodatapb.TabletAlias{ 385 Cell: "zone1", 386 Uid: 100, 387 }, 388 }, nil), 389 tabletMap: map[string]*topo.TabletInfo{ 390 "avoid-primary": { 391 Tablet: &topodatapb.Tablet{ 392 Alias: &topodatapb.TabletAlias{ 393 Cell: "zone1", 394 Uid: 101, 395 }, 396 Type: topodatapb.TabletType_REPLICA, 397 }, 398 }, 399 }, 400 avoidPrimaryAlias: &topodatapb.TabletAlias{ 401 Cell: "zone1", 402 Uid: 101, 403 }, 404 expected: nil, 405 shouldErr: false, 406 }, 407 { 408 name: "no replicas in shard", 409 tmc: &chooseNewPrimaryTestTMClient{}, 410 shardInfo: topo.NewShardInfo("testkeyspace", "-", &topodatapb.Shard{ 411 PrimaryAlias: &topodatapb.TabletAlias{ 412 Cell: "zone1", 413 Uid: 100, 414 }, 415 }, nil), 416 tabletMap: map[string]*topo.TabletInfo{ 417 "primary": { 418 Tablet: &topodatapb.Tablet{ 419 Alias: &topodatapb.TabletAlias{ 420 Cell: "zone1", 421 Uid: 100, 422 }, 423 Type: topodatapb.TabletType_PRIMARY, 424 }, 425 }, 426 }, 427 avoidPrimaryAlias: &topodatapb.TabletAlias{ 428 Cell: "zone1", 429 Uid: 0, 430 }, 431 expected: nil, 432 shouldErr: false, 433 }, 434 } 435 436 durability, err := GetDurabilityPolicy("none") 437 require.NoError(t, err) 438 for _, tt := range tests { 439 tt := tt 440 441 t.Run(tt.name, func(t *testing.T) { 442 t.Parallel() 443 444 actual, err := ChooseNewPrimary(ctx, tt.tmc, tt.shardInfo, tt.tabletMap, tt.avoidPrimaryAlias, time.Millisecond*50, durability, logger) 445 if tt.shouldErr { 446 assert.Error(t, err) 447 return 448 } 449 450 assert.NoError(t, err) 451 utils.MustMatch(t, tt.expected, actual) 452 }) 453 } 454 } 455 456 func TestFindPositionForTablet(t *testing.T) { 457 t.Parallel() 458 459 ctx := context.Background() 460 logger := logutil.NewMemoryLogger() 461 tests := []struct { 462 name string 463 tmc *testutil.TabletManagerClient 464 tablet *topodatapb.Tablet 465 expectedPosition string 466 expectedErr string 467 }{ 468 { 469 name: "executed gtid set", 470 tmc: &testutil.TabletManagerClient{ 471 ReplicationStatusResults: map[string]struct { 472 Position *replicationdatapb.Status 473 Error error 474 }{ 475 "zone1-0000000100": { 476 Position: &replicationdatapb.Status{ 477 Position: "MySQL56/3e11fa47-71ca-11e1-9e33-c80aa9429562:1-5", 478 }, 479 }, 480 }, 481 }, 482 tablet: &topodatapb.Tablet{ 483 Alias: &topodatapb.TabletAlias{ 484 Cell: "zone1", 485 Uid: 100, 486 }, 487 }, 488 expectedPosition: "MySQL56/3e11fa47-71ca-11e1-9e33-c80aa9429562:1-5", 489 }, { 490 name: "no replication status", 491 tmc: &testutil.TabletManagerClient{ 492 ReplicationStatusResults: map[string]struct { 493 Position *replicationdatapb.Status 494 Error error 495 }{ 496 "zone1-0000000100": { 497 Error: vterrors.ToGRPC(vterrors.Wrap(mysql.ErrNotReplica, "before status failed")), 498 }, 499 }, 500 }, 501 tablet: &topodatapb.Tablet{ 502 Alias: &topodatapb.TabletAlias{ 503 Cell: "zone1", 504 Uid: 100, 505 }, 506 }, 507 expectedPosition: "", 508 }, { 509 name: "relay log", 510 tmc: &testutil.TabletManagerClient{ 511 ReplicationStatusResults: map[string]struct { 512 Position *replicationdatapb.Status 513 Error error 514 }{ 515 "zone1-0000000100": { 516 Position: &replicationdatapb.Status{ 517 Position: "unused", 518 RelayLogPosition: "MySQL56/3e11fa47-71ca-11e1-9e33-c80aa9429562:1-5", 519 }, 520 }, 521 }, 522 }, 523 tablet: &topodatapb.Tablet{ 524 Alias: &topodatapb.TabletAlias{ 525 Cell: "zone1", 526 Uid: 100, 527 }, 528 }, 529 expectedPosition: "MySQL56/3e11fa47-71ca-11e1-9e33-c80aa9429562:1-5", 530 }, { 531 name: "error in parsing position", 532 tmc: &testutil.TabletManagerClient{ 533 ReplicationStatusResults: map[string]struct { 534 Position *replicationdatapb.Status 535 Error error 536 }{ 537 "zone1-0000000100": { 538 Position: &replicationdatapb.Status{ 539 Position: "unused", 540 }, 541 }, 542 }, 543 }, 544 tablet: &topodatapb.Tablet{ 545 Alias: &topodatapb.TabletAlias{ 546 Cell: "zone1", 547 Uid: 100, 548 }, 549 }, 550 expectedErr: `parse error: unknown GTIDSet flavor ""`, 551 }, 552 } 553 554 for _, test := range tests { 555 t.Run(test.name, func(t *testing.T) { 556 pos, err := findPositionForTablet(ctx, test.tablet, logger, test.tmc, 10*time.Second) 557 if test.expectedErr != "" { 558 require.EqualError(t, err, test.expectedErr) 559 return 560 } 561 require.NoError(t, err) 562 posString := mysql.EncodePosition(pos) 563 require.Equal(t, test.expectedPosition, posString) 564 }) 565 } 566 } 567 568 func TestFindCurrentPrimary(t *testing.T) { 569 t.Parallel() 570 571 // The exact values of the tablet aliases don't matter to this function, but 572 // we need them to be non-nil, so we'll just make one and reuse it. 573 alias := &topodatapb.TabletAlias{ 574 Cell: "zone1", 575 Uid: 100, 576 } 577 logger := logutil.NewMemoryLogger() 578 tests := []struct { 579 name string 580 in map[string]*topo.TabletInfo 581 expected *topo.TabletInfo 582 }{ 583 { 584 name: "single current primary", 585 in: map[string]*topo.TabletInfo{ 586 "primary": { 587 Tablet: &topodatapb.Tablet{ 588 Alias: alias, 589 Type: topodatapb.TabletType_PRIMARY, 590 PrimaryTermStartTime: &vttime.Time{ 591 Seconds: 100, 592 }, 593 Hostname: "primary-tablet", 594 }, 595 }, 596 "replica": { 597 Tablet: &topodatapb.Tablet{ 598 Alias: alias, 599 Type: topodatapb.TabletType_REPLICA, 600 Hostname: "replica-tablet", 601 }, 602 }, 603 "rdonly": { 604 Tablet: &topodatapb.Tablet{ 605 Alias: alias, 606 Type: topodatapb.TabletType_RDONLY, 607 Hostname: "rdonly-tablet", 608 }, 609 }, 610 }, 611 expected: &topo.TabletInfo{ 612 Tablet: &topodatapb.Tablet{ 613 Alias: alias, 614 Type: topodatapb.TabletType_PRIMARY, 615 PrimaryTermStartTime: &vttime.Time{ 616 Seconds: 100, 617 }, 618 Hostname: "primary-tablet", 619 }, 620 }, 621 }, 622 { 623 name: "no primaries", 624 in: map[string]*topo.TabletInfo{ 625 "replica1": { 626 Tablet: &topodatapb.Tablet{ 627 Alias: alias, 628 Type: topodatapb.TabletType_REPLICA, 629 Hostname: "replica-tablet-1", 630 }, 631 }, 632 "replica2": { 633 Tablet: &topodatapb.Tablet{ 634 Alias: alias, 635 Type: topodatapb.TabletType_REPLICA, 636 Hostname: "replica-tablet-2", 637 }, 638 }, 639 "rdonly": { 640 Tablet: &topodatapb.Tablet{ 641 Alias: alias, 642 Type: topodatapb.TabletType_RDONLY, 643 Hostname: "rdonly-tablet", 644 }, 645 }, 646 }, 647 expected: nil, 648 }, 649 { 650 name: "multiple primaries with one true primary", 651 in: map[string]*topo.TabletInfo{ 652 "stale-primary": { 653 Tablet: &topodatapb.Tablet{ 654 Alias: alias, 655 Type: topodatapb.TabletType_PRIMARY, 656 PrimaryTermStartTime: &vttime.Time{ 657 Seconds: 100, 658 }, 659 Hostname: "stale-primary-tablet", 660 }, 661 }, 662 "true-primary": { 663 Tablet: &topodatapb.Tablet{ 664 Alias: alias, 665 Type: topodatapb.TabletType_PRIMARY, 666 PrimaryTermStartTime: &vttime.Time{ 667 Seconds: 1000, 668 }, 669 Hostname: "true-primary-tablet", 670 }, 671 }, 672 "rdonly": { 673 Tablet: &topodatapb.Tablet{ 674 Alias: alias, 675 Type: topodatapb.TabletType_RDONLY, 676 Hostname: "rdonly-tablet", 677 }, 678 }, 679 }, 680 expected: &topo.TabletInfo{ 681 Tablet: &topodatapb.Tablet{ 682 Alias: alias, 683 Type: topodatapb.TabletType_PRIMARY, 684 PrimaryTermStartTime: &vttime.Time{ 685 Seconds: 1000, 686 }, 687 Hostname: "true-primary-tablet", 688 }, 689 }, 690 }, 691 { 692 name: "multiple primaries with same term start", 693 in: map[string]*topo.TabletInfo{ 694 "primary1": { 695 Tablet: &topodatapb.Tablet{ 696 Alias: alias, 697 Type: topodatapb.TabletType_PRIMARY, 698 PrimaryTermStartTime: &vttime.Time{ 699 Seconds: 100, 700 }, 701 Hostname: "primary-tablet-1", 702 }, 703 }, 704 "primary2": { 705 Tablet: &topodatapb.Tablet{ 706 Alias: alias, 707 Type: topodatapb.TabletType_PRIMARY, 708 PrimaryTermStartTime: &vttime.Time{ 709 Seconds: 100, 710 }, 711 Hostname: "primary-tablet-2", 712 }, 713 }, 714 "rdonly": { 715 Tablet: &topodatapb.Tablet{ 716 Alias: alias, 717 Type: topodatapb.TabletType_RDONLY, 718 Hostname: "rdonly-tablet", 719 }, 720 }, 721 }, 722 expected: nil, 723 }, 724 } 725 726 for _, tt := range tests { 727 tt := tt 728 729 t.Run(tt.name, func(t *testing.T) { 730 t.Parallel() 731 732 actual := FindCurrentPrimary(tt.in, logger) 733 assert.Equal(t, tt.expected, actual) 734 }) 735 } 736 } 737 738 func TestGetValidCandidatesAndPositionsAsList(t *testing.T) { 739 sid1 := mysql.SID{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 740 mysqlGTID1 := mysql.Mysql56GTID{ 741 Server: sid1, 742 Sequence: 9, 743 } 744 mysqlGTID2 := mysql.Mysql56GTID{ 745 Server: sid1, 746 Sequence: 10, 747 } 748 mysqlGTID3 := mysql.Mysql56GTID{ 749 Server: sid1, 750 Sequence: 11, 751 } 752 753 positionMostAdvanced := mysql.Position{GTIDSet: mysql.Mysql56GTIDSet{}} 754 positionMostAdvanced.GTIDSet = positionMostAdvanced.GTIDSet.AddGTID(mysqlGTID1) 755 positionMostAdvanced.GTIDSet = positionMostAdvanced.GTIDSet.AddGTID(mysqlGTID2) 756 positionMostAdvanced.GTIDSet = positionMostAdvanced.GTIDSet.AddGTID(mysqlGTID3) 757 758 positionIntermediate1 := mysql.Position{GTIDSet: mysql.Mysql56GTIDSet{}} 759 positionIntermediate1.GTIDSet = positionIntermediate1.GTIDSet.AddGTID(mysqlGTID1) 760 761 positionIntermediate2 := mysql.Position{GTIDSet: mysql.Mysql56GTIDSet{}} 762 positionIntermediate2.GTIDSet = positionIntermediate2.GTIDSet.AddGTID(mysqlGTID1) 763 positionIntermediate2.GTIDSet = positionIntermediate2.GTIDSet.AddGTID(mysqlGTID2) 764 765 tests := []struct { 766 name string 767 validCandidates map[string]mysql.Position 768 tabletMap map[string]*topo.TabletInfo 769 tabletRes []*topodatapb.Tablet 770 }{ 771 { 772 name: "test conversion", 773 validCandidates: map[string]mysql.Position{ 774 "zone1-0000000100": positionMostAdvanced, 775 "zone1-0000000101": positionIntermediate1, 776 "zone1-0000000102": positionIntermediate2, 777 }, 778 tabletMap: map[string]*topo.TabletInfo{ 779 "zone1-0000000100": { 780 Tablet: &topodatapb.Tablet{ 781 Alias: &topodatapb.TabletAlias{ 782 Cell: "zone1", 783 Uid: 100, 784 }, 785 Hostname: "primary-elect", 786 }, 787 }, 788 "zone1-0000000101": { 789 Tablet: &topodatapb.Tablet{ 790 Alias: &topodatapb.TabletAlias{ 791 Cell: "zone1", 792 Uid: 101, 793 }, 794 }, 795 }, 796 "zone1-0000000102": { 797 Tablet: &topodatapb.Tablet{ 798 Alias: &topodatapb.TabletAlias{ 799 Cell: "zone1", 800 Uid: 102, 801 }, 802 Hostname: "requires force start", 803 }, 804 }, 805 "zone1-0000000404": { 806 Tablet: &topodatapb.Tablet{ 807 Alias: &topodatapb.TabletAlias{ 808 Cell: "zone1", 809 Uid: 404, 810 }, 811 Hostname: "ignored tablet", 812 }, 813 }, 814 }, 815 tabletRes: []*topodatapb.Tablet{ 816 { 817 Alias: &topodatapb.TabletAlias{ 818 Cell: "zone1", 819 Uid: 100, 820 }, 821 Hostname: "primary-elect", 822 }, { 823 Alias: &topodatapb.TabletAlias{ 824 Cell: "zone1", 825 Uid: 101, 826 }, 827 }, { 828 Alias: &topodatapb.TabletAlias{ 829 Cell: "zone1", 830 Uid: 102, 831 }, 832 Hostname: "requires force start", 833 }, 834 }, 835 }, 836 } 837 838 for _, test := range tests { 839 t.Run(test.name, func(t *testing.T) { 840 tabletRes, posRes, err := getValidCandidatesAndPositionsAsList(test.validCandidates, test.tabletMap) 841 assert.NoError(t, err) 842 assert.ElementsMatch(t, test.tabletRes, tabletRes) 843 assert.Equal(t, len(tabletRes), len(posRes)) 844 for i, tablet := range tabletRes { 845 assert.Equal(t, test.validCandidates[topoproto.TabletAliasString(tablet.Alias)], posRes[i]) 846 } 847 }) 848 } 849 } 850 851 func TestWaitForCatchUp(t *testing.T) { 852 tests := []struct { 853 name string 854 tmc tmclient.TabletManagerClient 855 source *topodatapb.Tablet 856 newPrimary *topodatapb.Tablet 857 err string 858 }{ 859 { 860 name: "success", 861 tmc: &testutil.TabletManagerClient{ 862 PrimaryPositionResults: map[string]struct { 863 Position string 864 Error error 865 }{ 866 "zone1-0000000100": { 867 Position: "abc", 868 Error: nil, 869 }, 870 }, 871 WaitForPositionResults: map[string]map[string]error{ 872 "zone1-0000000101": { 873 "abc": nil, 874 }, 875 }, 876 }, 877 source: &topodatapb.Tablet{ 878 Alias: &topodatapb.TabletAlias{ 879 Cell: "zone1", 880 Uid: 100, 881 }, 882 }, 883 newPrimary: &topodatapb.Tablet{ 884 Alias: &topodatapb.TabletAlias{ 885 Cell: "zone1", 886 Uid: 101, 887 }, 888 }, 889 }, { 890 name: "error in primary position", 891 tmc: &testutil.TabletManagerClient{ 892 PrimaryPositionResults: map[string]struct { 893 Position string 894 Error error 895 }{ 896 "zone1-0000000100": { 897 Position: "abc", 898 Error: fmt.Errorf("found error in primary position"), 899 }, 900 }, 901 WaitForPositionResults: map[string]map[string]error{ 902 "zone1-0000000101": { 903 "abc": nil, 904 }, 905 }, 906 }, 907 source: &topodatapb.Tablet{ 908 Alias: &topodatapb.TabletAlias{ 909 Cell: "zone1", 910 Uid: 100, 911 }, 912 }, 913 newPrimary: &topodatapb.Tablet{ 914 Alias: &topodatapb.TabletAlias{ 915 Cell: "zone1", 916 Uid: 101, 917 }, 918 }, 919 err: "found error in primary position", 920 }, { 921 name: "error in waiting for position", 922 tmc: &testutil.TabletManagerClient{ 923 PrimaryPositionResults: map[string]struct { 924 Position string 925 Error error 926 }{ 927 "zone1-0000000100": { 928 Position: "abc", 929 Error: nil, 930 }, 931 }, 932 WaitForPositionResults: map[string]map[string]error{ 933 "zone1-0000000101": { 934 "abc": fmt.Errorf("found error in waiting for position"), 935 }, 936 }, 937 }, 938 source: &topodatapb.Tablet{ 939 Alias: &topodatapb.TabletAlias{ 940 Cell: "zone1", 941 Uid: 100, 942 }, 943 }, 944 newPrimary: &topodatapb.Tablet{ 945 Alias: &topodatapb.TabletAlias{ 946 Cell: "zone1", 947 Uid: 101, 948 }, 949 }, 950 err: "found error in waiting for position", 951 }, 952 } 953 954 for _, test := range tests { 955 t.Run(test.name, func(t *testing.T) { 956 ctx := context.Background() 957 logger := logutil.NewMemoryLogger() 958 err := waitForCatchUp(ctx, test.tmc, logger, test.newPrimary, test.source, 2*time.Second) 959 if test.err != "" { 960 assert.EqualError(t, err, test.err) 961 } else { 962 assert.NoError(t, err) 963 } 964 }) 965 } 966 } 967 968 func TestRestrictValidCandidates(t *testing.T) { 969 tests := []struct { 970 name string 971 validCandidates map[string]mysql.Position 972 tabletMap map[string]*topo.TabletInfo 973 result map[string]mysql.Position 974 }{ 975 { 976 name: "remove invalid tablets", 977 validCandidates: map[string]mysql.Position{ 978 "zone1-0000000100": {}, 979 "zone1-0000000101": {}, 980 "zone1-0000000102": {}, 981 "zone1-0000000103": {}, 982 "zone1-0000000104": {}, 983 "zone1-0000000105": {}, 984 }, 985 tabletMap: map[string]*topo.TabletInfo{ 986 "zone1-0000000100": { 987 Tablet: &topodatapb.Tablet{ 988 Alias: &topodatapb.TabletAlias{ 989 Cell: "zone1", 990 Uid: 100, 991 }, 992 Type: topodatapb.TabletType_PRIMARY, 993 }, 994 }, 995 "zone1-0000000101": { 996 Tablet: &topodatapb.Tablet{ 997 Alias: &topodatapb.TabletAlias{ 998 Cell: "zone1", 999 Uid: 101, 1000 }, 1001 Type: topodatapb.TabletType_RDONLY, 1002 }, 1003 }, 1004 "zone1-0000000102": { 1005 Tablet: &topodatapb.Tablet{ 1006 Alias: &topodatapb.TabletAlias{ 1007 Cell: "zone1", 1008 Uid: 102, 1009 }, 1010 Type: topodatapb.TabletType_RESTORE, 1011 }, 1012 }, 1013 "zone1-0000000103": { 1014 Tablet: &topodatapb.Tablet{ 1015 Alias: &topodatapb.TabletAlias{ 1016 Cell: "zone1", 1017 Uid: 103, 1018 }, 1019 Type: topodatapb.TabletType_DRAINED, 1020 }, 1021 }, 1022 "zone1-0000000104": { 1023 Tablet: &topodatapb.Tablet{ 1024 Alias: &topodatapb.TabletAlias{ 1025 Cell: "zone1", 1026 Uid: 104, 1027 }, 1028 Type: topodatapb.TabletType_SPARE, 1029 }, 1030 }, 1031 "zone1-0000000105": { 1032 Tablet: &topodatapb.Tablet{ 1033 Alias: &topodatapb.TabletAlias{ 1034 Cell: "zone1", 1035 Uid: 103, 1036 }, 1037 Type: topodatapb.TabletType_BACKUP, 1038 }, 1039 }, 1040 }, 1041 result: map[string]mysql.Position{ 1042 "zone1-0000000100": {}, 1043 "zone1-0000000101": {}, 1044 "zone1-0000000104": {}, 1045 }, 1046 }, 1047 } 1048 1049 for _, test := range tests { 1050 t.Run(test.name, func(t *testing.T) { 1051 res, err := restrictValidCandidates(test.validCandidates, test.tabletMap) 1052 assert.NoError(t, err) 1053 assert.Equal(t, res, test.result) 1054 }) 1055 } 1056 } 1057 1058 func Test_findCandidate(t *testing.T) { 1059 tests := []struct { 1060 name string 1061 intermediateSource *topodatapb.Tablet 1062 possibleCandidates []*topodatapb.Tablet 1063 candidate *topodatapb.Tablet 1064 }{ 1065 { 1066 name: "empty possible candidates list", 1067 candidate: nil, 1068 }, { 1069 name: "intermediate source in possible candidates list", 1070 intermediateSource: &topodatapb.Tablet{ 1071 Alias: &topodatapb.TabletAlias{ 1072 Cell: "zone1", 1073 Uid: 103, 1074 }, 1075 }, 1076 possibleCandidates: []*topodatapb.Tablet{ 1077 { 1078 Alias: &topodatapb.TabletAlias{ 1079 Cell: "zone1", 1080 Uid: 101, 1081 }, 1082 }, { 1083 Alias: &topodatapb.TabletAlias{ 1084 Cell: "zone1", 1085 Uid: 103, 1086 }, 1087 }, { 1088 Alias: &topodatapb.TabletAlias{ 1089 Cell: "zone1", 1090 Uid: 102, 1091 }, 1092 }, 1093 }, 1094 candidate: &topodatapb.Tablet{ 1095 Alias: &topodatapb.TabletAlias{ 1096 Cell: "zone1", 1097 Uid: 103, 1098 }, 1099 }, 1100 }, { 1101 name: "intermediate source not in possible candidates list", 1102 intermediateSource: &topodatapb.Tablet{ 1103 Alias: &topodatapb.TabletAlias{ 1104 Cell: "zone1", 1105 Uid: 103, 1106 }, 1107 }, 1108 possibleCandidates: []*topodatapb.Tablet{ 1109 { 1110 Alias: &topodatapb.TabletAlias{ 1111 Cell: "zone1", 1112 Uid: 101, 1113 }, 1114 }, { 1115 Alias: &topodatapb.TabletAlias{ 1116 Cell: "zone1", 1117 Uid: 104, 1118 }, 1119 }, { 1120 Alias: &topodatapb.TabletAlias{ 1121 Cell: "zone1", 1122 Uid: 102, 1123 }, 1124 }, 1125 }, 1126 candidate: &topodatapb.Tablet{ 1127 Alias: &topodatapb.TabletAlias{ 1128 Cell: "zone1", 1129 Uid: 101, 1130 }, 1131 }, 1132 }, 1133 } 1134 for _, tt := range tests { 1135 t.Run(tt.name, func(t *testing.T) { 1136 res := findCandidate(tt.intermediateSource, tt.possibleCandidates) 1137 if tt.candidate == nil { 1138 require.Nil(t, res) 1139 } else { 1140 require.NotNil(t, res) 1141 require.Equal(t, topoproto.TabletAliasString(tt.candidate.Alias), topoproto.TabletAliasString(res.Alias)) 1142 } 1143 }) 1144 } 1145 } 1146 1147 func Test_getTabletsWithPromotionRules(t *testing.T) { 1148 var ( 1149 primaryTablet = &topodatapb.Tablet{ 1150 Alias: &topodatapb.TabletAlias{ 1151 Cell: "zone-1", 1152 Uid: 1, 1153 }, 1154 Type: topodatapb.TabletType_PRIMARY, 1155 } 1156 replicaTablet = &topodatapb.Tablet{ 1157 Alias: &topodatapb.TabletAlias{ 1158 Cell: "zone-1", 1159 Uid: 2, 1160 }, 1161 Type: topodatapb.TabletType_REPLICA, 1162 } 1163 rdonlyTablet = &topodatapb.Tablet{ 1164 Alias: &topodatapb.TabletAlias{ 1165 Cell: "zone-1", 1166 Uid: 3, 1167 }, 1168 Type: topodatapb.TabletType_RDONLY, 1169 } 1170 replicaCrossCellTablet = &topodatapb.Tablet{ 1171 Alias: &topodatapb.TabletAlias{ 1172 Cell: "zone-2", 1173 Uid: 2, 1174 }, 1175 Type: topodatapb.TabletType_REPLICA, 1176 } 1177 rdonlyCrossCellTablet = &topodatapb.Tablet{ 1178 Alias: &topodatapb.TabletAlias{ 1179 Cell: "zone-2", 1180 Uid: 3, 1181 }, 1182 Type: topodatapb.TabletType_RDONLY, 1183 } 1184 ) 1185 allTablets := []*topodatapb.Tablet{primaryTablet, replicaTablet, rdonlyTablet, replicaCrossCellTablet, rdonlyCrossCellTablet} 1186 tests := []struct { 1187 name string 1188 tablets []*topodatapb.Tablet 1189 rule promotionrule.CandidatePromotionRule 1190 filteredTablets []*topodatapb.Tablet 1191 }{ 1192 { 1193 name: "filter candidates with Neutral promotion rule", 1194 tablets: allTablets, 1195 rule: promotionrule.Neutral, 1196 filteredTablets: []*topodatapb.Tablet{primaryTablet, replicaTablet, replicaCrossCellTablet}, 1197 }, 1198 { 1199 name: "filter candidates with MustNot promotion rule", 1200 tablets: allTablets, 1201 rule: promotionrule.MustNot, 1202 filteredTablets: []*topodatapb.Tablet{rdonlyTablet, rdonlyCrossCellTablet}, 1203 }, 1204 { 1205 name: "filter candidates with Must promotion rule", 1206 tablets: allTablets, 1207 rule: promotionrule.Must, 1208 filteredTablets: nil, 1209 }, 1210 } 1211 durability, _ := GetDurabilityPolicy("none") 1212 for _, tt := range tests { 1213 t.Run(tt.name, func(t *testing.T) { 1214 res := getTabletsWithPromotionRules(durability, tt.tablets, tt.rule) 1215 require.EqualValues(t, tt.filteredTablets, res) 1216 }) 1217 } 1218 }