vitess.io/vitess@v0.16.2/go/vt/vtctl/reparentutil/replication_test.go (about) 1 /* 2 Copyright 2021 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 "os" 22 "testing" 23 "time" 24 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 28 "k8s.io/apimachinery/pkg/util/sets" 29 30 _flag "vitess.io/vitess/go/internal/flag" 31 "vitess.io/vitess/go/mysql" 32 "vitess.io/vitess/go/vt/logutil" 33 "vitess.io/vitess/go/vt/topo" 34 "vitess.io/vitess/go/vt/topo/topoproto" 35 "vitess.io/vitess/go/vt/topotools/events" 36 "vitess.io/vitess/go/vt/vterrors" 37 "vitess.io/vitess/go/vt/vttablet/tmclient" 38 39 replicationdatapb "vitess.io/vitess/go/vt/proto/replicationdata" 40 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 41 ) 42 43 func TestMain(m *testing.M) { 44 _flag.ParseFlagsForTest() 45 os.Exit(m.Run()) 46 } 47 48 func TestFindValidEmergencyReparentCandidates(t *testing.T) { 49 t.Parallel() 50 51 tests := []struct { 52 name string 53 statusMap map[string]*replicationdatapb.StopReplicationStatus 54 primaryStatusMap map[string]*replicationdatapb.PrimaryStatus 55 // Note: for these tests, it's simpler to compare keys than actual 56 // mysql.Postion structs, which are just thin wrappers around the 57 // mysql.GTIDSet interface. If a tablet alias makes it into the map, we 58 // know it was chosen by the method, and that either 59 // mysql.DecodePosition was successful (in the primary case) or 60 // status.FindErrantGTIDs was successful (in the replica case). If the 61 // former is not true, then the function should return an error. If the 62 // latter is not true, then the tablet alias will not be in the map. The 63 // point is, the combination of (1) whether the test should error and 64 // (2) the set of keys we expect in the map is enough to fully assert on 65 // the correctness of the behavior of this functional unit. 66 expected []string 67 shouldErr bool 68 }{ 69 { 70 name: "success", 71 statusMap: map[string]*replicationdatapb.StopReplicationStatus{ 72 "r1": { 73 After: &replicationdatapb.Status{ 74 SourceUuid: "3E11FA47-71CA-11E1-9E33-C80AA9429562", 75 RelayLogPosition: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", 76 }, 77 }, 78 "r2": { 79 After: &replicationdatapb.Status{ 80 SourceUuid: "3E11FA47-71CA-11E1-9E33-C80AA9429562", 81 RelayLogPosition: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", 82 }, 83 }, 84 }, 85 primaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{ 86 "p1": { 87 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", 88 }, 89 }, 90 expected: []string{"r1", "r2", "p1"}, 91 shouldErr: false, 92 }, { 93 name: "success for single tablet", 94 statusMap: map[string]*replicationdatapb.StopReplicationStatus{ 95 "r1": { 96 After: &replicationdatapb.Status{ 97 SourceUuid: "3E11FA47-71CA-11E1-9E33-C80AA9429562", 98 RelayLogPosition: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5,AAAAAAAA-71CA-11E1-9E33-C80AA9429562:1", 99 }, 100 }, 101 }, 102 primaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{}, 103 expected: []string{"r1"}, 104 shouldErr: false, 105 }, 106 { 107 name: "mixed replication modes", 108 statusMap: map[string]*replicationdatapb.StopReplicationStatus{ 109 "r1": { 110 After: &replicationdatapb.Status{ 111 SourceUuid: "3E11FA47-71CA-11E1-9E33-C80AA9429562", 112 RelayLogPosition: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", 113 }, 114 }, 115 "r2": { 116 After: &replicationdatapb.Status{ 117 SourceUuid: "3E11FA47-71CA-11E1-9E33-C80AA9429562", 118 RelayLogPosition: "FilePos/mysql-bin.0001:10", 119 }, 120 }, 121 }, 122 expected: nil, 123 shouldErr: true, 124 }, 125 { 126 name: "tablet without relay log position", 127 statusMap: map[string]*replicationdatapb.StopReplicationStatus{ 128 "r1": { 129 After: &replicationdatapb.Status{ 130 SourceUuid: "3E11FA47-71CA-11E1-9E33-C80AA9429562", 131 RelayLogPosition: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", 132 }, 133 }, 134 "r2": { 135 After: &replicationdatapb.Status{ 136 RelayLogPosition: "", 137 }, 138 }, 139 }, 140 expected: nil, 141 shouldErr: true, 142 }, 143 { 144 name: "non-GTID-based", 145 statusMap: map[string]*replicationdatapb.StopReplicationStatus{ 146 "r1": { 147 After: &replicationdatapb.Status{ 148 SourceUuid: "3E11FA47-71CA-11E1-9E33-C80AA9429562", 149 RelayLogPosition: "FilePos/mysql-bin.0001:100", 150 }, 151 }, 152 "r2": { 153 After: &replicationdatapb.Status{ 154 SourceUuid: "3E11FA47-71CA-11E1-9E33-C80AA9429562", 155 RelayLogPosition: "FilePos/mysql-bin.0001:10", 156 }, 157 }, 158 }, 159 expected: []string{"r1", "r2"}, 160 shouldErr: false, 161 }, 162 { 163 name: "tablet with errant GTIDs is excluded", 164 statusMap: map[string]*replicationdatapb.StopReplicationStatus{ 165 "r1": { 166 After: &replicationdatapb.Status{ 167 SourceUuid: "3E11FA47-71CA-11E1-9E33-C80AA9429562", 168 RelayLogPosition: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", 169 }, 170 }, 171 "errant": { 172 After: &replicationdatapb.Status{ 173 SourceUuid: "3E11FA47-71CA-11E1-9E33-C80AA9429562", 174 RelayLogPosition: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5,AAAAAAAA-71CA-11E1-9E33-C80AA9429562:1", 175 }, 176 }, 177 }, 178 primaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{ 179 "p1": { 180 Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", 181 }, 182 }, 183 expected: []string{"r1", "p1"}, 184 shouldErr: false, 185 }, 186 { 187 name: "bad primary position fails the call", 188 statusMap: map[string]*replicationdatapb.StopReplicationStatus{ 189 "r1": { 190 After: &replicationdatapb.Status{ 191 SourceUuid: "3E11FA47-71CA-11E1-9E33-C80AA9429562", 192 RelayLogPosition: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5", 193 }, 194 }, 195 }, 196 primaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{ 197 "p1": { 198 Position: "InvalidFlavor/1234", 199 }, 200 }, 201 expected: nil, 202 shouldErr: true, 203 }, 204 } 205 206 for _, tt := range tests { 207 tt := tt 208 209 t.Run(tt.name, func(t *testing.T) { 210 t.Parallel() 211 212 actual, err := FindValidEmergencyReparentCandidates(tt.statusMap, tt.primaryStatusMap) 213 if tt.shouldErr { 214 assert.Error(t, err) 215 return 216 } 217 218 assert.NoError(t, err) 219 220 keys := make([]string, 0, len(actual)) 221 for key := range actual { 222 keys = append(keys, key) 223 } 224 assert.ElementsMatch(t, tt.expected, keys) 225 }) 226 } 227 } 228 229 // stopReplicationAndBuildStatusMapsTestTMClient implements 230 // tmclient.TabletManagerClient to facilitate testing of 231 // stopReplicationAndBuildStatusMaps. 232 type stopReplicationAndBuildStatusMapsTestTMClient struct { 233 tmclient.TabletManagerClient 234 235 demotePrimaryResults map[string]*struct { 236 PrimaryStatus *replicationdatapb.PrimaryStatus 237 Err error 238 } 239 demotePrimaryDelays map[string]time.Duration 240 241 stopReplicationAndGetStatusResults map[string]*struct { 242 StopStatus *replicationdatapb.StopReplicationStatus 243 Err error 244 } 245 stopReplicationAndGetStatusDelays map[string]time.Duration 246 } 247 248 func (fake *stopReplicationAndBuildStatusMapsTestTMClient) DemotePrimary(ctx context.Context, tablet *topodatapb.Tablet) (*replicationdatapb.PrimaryStatus, error) { 249 if tablet.Alias == nil { 250 return nil, assert.AnError 251 } 252 253 key := topoproto.TabletAliasString(tablet.Alias) 254 255 if delay, ok := fake.demotePrimaryDelays[key]; ok { 256 select { 257 case <-time.After(delay): 258 case <-ctx.Done(): 259 return nil, ctx.Err() 260 } 261 } 262 263 if result, ok := fake.demotePrimaryResults[key]; ok { 264 return result.PrimaryStatus, result.Err 265 } 266 267 return nil, assert.AnError 268 } 269 270 func (fake *stopReplicationAndBuildStatusMapsTestTMClient) StopReplicationAndGetStatus(ctx context.Context, tablet *topodatapb.Tablet, mode replicationdatapb.StopReplicationMode) (*replicationdatapb.StopReplicationStatus, error) { 271 if tablet.Alias == nil { 272 return nil, assert.AnError 273 } 274 275 key := topoproto.TabletAliasString(tablet.Alias) 276 277 if delay, ok := fake.stopReplicationAndGetStatusDelays[key]; ok { 278 select { 279 case <-time.After(delay): 280 case <-ctx.Done(): 281 return nil, ctx.Err() 282 } 283 } 284 285 if result, ok := fake.stopReplicationAndGetStatusResults[key]; ok { 286 return result.StopStatus, result.Err 287 } 288 289 return nil, assert.AnError 290 } 291 292 func Test_stopReplicationAndBuildStatusMaps(t *testing.T) { 293 ctx := context.Background() 294 logger := logutil.NewMemoryLogger() 295 tests := []struct { 296 name string 297 durability string 298 tmc *stopReplicationAndBuildStatusMapsTestTMClient 299 tabletMap map[string]*topo.TabletInfo 300 stopReplicasTimeout time.Duration 301 ignoredTablets sets.Set[string] 302 tabletToWaitFor *topodatapb.TabletAlias 303 expectedStatusMap map[string]*replicationdatapb.StopReplicationStatus 304 expectedPrimaryStatusMap map[string]*replicationdatapb.PrimaryStatus 305 expectedTabletsReachable []*topodatapb.Tablet 306 shouldErr bool 307 }{ 308 { 309 name: "success", 310 durability: "none", 311 tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ 312 stopReplicationAndGetStatusResults: map[string]*struct { 313 StopStatus *replicationdatapb.StopReplicationStatus 314 Err error 315 }{ 316 "zone1-0000000100": { 317 StopStatus: &replicationdatapb.StopReplicationStatus{ 318 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 319 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"}, 320 }, 321 }, 322 "zone1-0000000101": { 323 StopStatus: &replicationdatapb.StopReplicationStatus{ 324 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 325 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 326 }, 327 }, 328 }, 329 }, 330 tabletMap: map[string]*topo.TabletInfo{ 331 "zone1-0000000100": { 332 Tablet: &topodatapb.Tablet{ 333 Type: topodatapb.TabletType_REPLICA, 334 Alias: &topodatapb.TabletAlias{ 335 Cell: "zone1", 336 Uid: 100, 337 }, 338 }, 339 }, 340 "zone1-0000000101": { 341 Tablet: &topodatapb.Tablet{ 342 Type: topodatapb.TabletType_REPLICA, 343 Alias: &topodatapb.TabletAlias{ 344 Cell: "zone1", 345 Uid: 101, 346 }, 347 }, 348 }, 349 }, 350 ignoredTablets: sets.New[string](), 351 expectedStatusMap: map[string]*replicationdatapb.StopReplicationStatus{ 352 "zone1-0000000100": { 353 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 354 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"}, 355 }, 356 "zone1-0000000101": { 357 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 358 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 359 }, 360 }, 361 expectedPrimaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{}, 362 expectedTabletsReachable: []*topodatapb.Tablet{{ 363 Type: topodatapb.TabletType_REPLICA, 364 Alias: &topodatapb.TabletAlias{ 365 Cell: "zone1", 366 Uid: 100, 367 }, 368 }, { 369 Type: topodatapb.TabletType_REPLICA, 370 Alias: &topodatapb.TabletAlias{ 371 Cell: "zone1", 372 Uid: 101, 373 }, 374 }}, 375 shouldErr: false, 376 }, 377 { 378 name: "success - 2 rdonly failures", 379 durability: "none", 380 tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ 381 stopReplicationAndGetStatusResults: map[string]*struct { 382 StopStatus *replicationdatapb.StopReplicationStatus 383 Err error 384 }{ 385 "zone1-0000000100": { 386 StopStatus: &replicationdatapb.StopReplicationStatus{ 387 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 388 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"}, 389 }, 390 }, 391 "zone1-0000000101": { 392 StopStatus: &replicationdatapb.StopReplicationStatus{ 393 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 394 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 395 }, 396 }, 397 "zone1-0000000102": { 398 Err: assert.AnError, 399 }, 400 "zone1-0000000103": { 401 Err: assert.AnError, 402 }, 403 }, 404 }, 405 tabletMap: map[string]*topo.TabletInfo{ 406 "zone1-0000000100": { 407 Tablet: &topodatapb.Tablet{ 408 Type: topodatapb.TabletType_REPLICA, 409 Alias: &topodatapb.TabletAlias{ 410 Cell: "zone1", 411 Uid: 100, 412 }, 413 }, 414 }, 415 "zone1-0000000101": { 416 Tablet: &topodatapb.Tablet{ 417 Type: topodatapb.TabletType_REPLICA, 418 Alias: &topodatapb.TabletAlias{ 419 Cell: "zone1", 420 Uid: 101, 421 }, 422 }, 423 }, 424 "zone1-0000000102": { 425 Tablet: &topodatapb.Tablet{ 426 Type: topodatapb.TabletType_RDONLY, 427 Alias: &topodatapb.TabletAlias{ 428 Cell: "zone1", 429 Uid: 102, 430 }, 431 }, 432 }, 433 "zone1-0000000103": { 434 Tablet: &topodatapb.Tablet{ 435 Type: topodatapb.TabletType_RDONLY, 436 Alias: &topodatapb.TabletAlias{ 437 Cell: "zone1", 438 Uid: 103, 439 }, 440 }, 441 }, 442 }, 443 ignoredTablets: sets.New[string](), 444 expectedStatusMap: map[string]*replicationdatapb.StopReplicationStatus{ 445 "zone1-0000000100": { 446 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 447 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"}, 448 }, 449 "zone1-0000000101": { 450 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 451 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 452 }, 453 }, 454 expectedPrimaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{}, 455 expectedTabletsReachable: []*topodatapb.Tablet{{ 456 Type: topodatapb.TabletType_REPLICA, 457 Alias: &topodatapb.TabletAlias{ 458 Cell: "zone1", 459 Uid: 100, 460 }, 461 }, { 462 Type: topodatapb.TabletType_REPLICA, 463 Alias: &topodatapb.TabletAlias{ 464 Cell: "zone1", 465 Uid: 101, 466 }, 467 }}, 468 shouldErr: false, 469 }, 470 { 471 name: "success - 1 rdonly and 1 replica failures", 472 durability: "semi_sync", 473 tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ 474 stopReplicationAndGetStatusResults: map[string]*struct { 475 StopStatus *replicationdatapb.StopReplicationStatus 476 Err error 477 }{ 478 "zone1-0000000100": { 479 StopStatus: &replicationdatapb.StopReplicationStatus{ 480 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 481 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"}, 482 }, 483 }, 484 "zone1-0000000101": { 485 StopStatus: &replicationdatapb.StopReplicationStatus{ 486 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 487 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 488 }, 489 }, 490 "zone1-0000000102": { 491 Err: assert.AnError, 492 }, 493 "zone1-0000000103": { 494 Err: assert.AnError, 495 }, 496 }, 497 }, 498 tabletMap: map[string]*topo.TabletInfo{ 499 "zone1-0000000100": { 500 Tablet: &topodatapb.Tablet{ 501 Type: topodatapb.TabletType_REPLICA, 502 Alias: &topodatapb.TabletAlias{ 503 Cell: "zone1", 504 Uid: 100, 505 }, 506 }, 507 }, 508 "zone1-0000000101": { 509 Tablet: &topodatapb.Tablet{ 510 Type: topodatapb.TabletType_REPLICA, 511 Alias: &topodatapb.TabletAlias{ 512 Cell: "zone1", 513 Uid: 101, 514 }, 515 }, 516 }, 517 "zone1-0000000102": { 518 Tablet: &topodatapb.Tablet{ 519 Type: topodatapb.TabletType_REPLICA, 520 Alias: &topodatapb.TabletAlias{ 521 Cell: "zone1", 522 Uid: 102, 523 }, 524 }, 525 }, 526 "zone1-0000000103": { 527 Tablet: &topodatapb.Tablet{ 528 Type: topodatapb.TabletType_RDONLY, 529 Alias: &topodatapb.TabletAlias{ 530 Cell: "zone1", 531 Uid: 103, 532 }, 533 }, 534 }, 535 }, 536 ignoredTablets: sets.New[string](), 537 expectedStatusMap: map[string]*replicationdatapb.StopReplicationStatus{ 538 "zone1-0000000100": { 539 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 540 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"}, 541 }, 542 "zone1-0000000101": { 543 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 544 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 545 }, 546 }, 547 expectedPrimaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{}, 548 expectedTabletsReachable: []*topodatapb.Tablet{{ 549 Type: topodatapb.TabletType_REPLICA, 550 Alias: &topodatapb.TabletAlias{ 551 Cell: "zone1", 552 Uid: 100, 553 }, 554 }, { 555 Type: topodatapb.TabletType_REPLICA, 556 Alias: &topodatapb.TabletAlias{ 557 Cell: "zone1", 558 Uid: 101, 559 }, 560 }}, 561 shouldErr: false, 562 }, 563 { 564 name: "ignore tablets", 565 durability: "none", 566 tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ 567 stopReplicationAndGetStatusResults: map[string]*struct { 568 StopStatus *replicationdatapb.StopReplicationStatus 569 Err error 570 }{ 571 "zone1-0000000100": { 572 StopStatus: &replicationdatapb.StopReplicationStatus{ 573 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 574 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"}, 575 }, 576 }, 577 "zone1-0000000101": { 578 StopStatus: &replicationdatapb.StopReplicationStatus{ 579 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 580 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 581 }, 582 }, 583 }, 584 }, 585 tabletMap: map[string]*topo.TabletInfo{ 586 "zone1-0000000100": { 587 Tablet: &topodatapb.Tablet{ 588 Type: topodatapb.TabletType_REPLICA, 589 Alias: &topodatapb.TabletAlias{ 590 Cell: "zone1", 591 Uid: 100, 592 }, 593 }, 594 }, 595 "zone1-0000000101": { 596 Tablet: &topodatapb.Tablet{ 597 Type: topodatapb.TabletType_REPLICA, 598 Alias: &topodatapb.TabletAlias{ 599 Cell: "zone1", 600 Uid: 101, 601 }, 602 }, 603 }, 604 }, 605 ignoredTablets: sets.New[string]("zone1-0000000100"), 606 expectedStatusMap: map[string]*replicationdatapb.StopReplicationStatus{ 607 "zone1-0000000101": { 608 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 609 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 610 }, 611 }, 612 expectedTabletsReachable: []*topodatapb.Tablet{{ 613 Type: topodatapb.TabletType_REPLICA, 614 Alias: &topodatapb.TabletAlias{ 615 Cell: "zone1", 616 Uid: 101, 617 }, 618 }}, 619 expectedPrimaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{}, 620 shouldErr: false, 621 }, 622 { 623 name: "have PRIMARY tablet and can demote", 624 durability: "none", 625 tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ 626 demotePrimaryResults: map[string]*struct { 627 PrimaryStatus *replicationdatapb.PrimaryStatus 628 Err error 629 }{ 630 "zone1-0000000100": { 631 PrimaryStatus: &replicationdatapb.PrimaryStatus{ 632 Position: "primary-position-100", 633 }, 634 }, 635 }, 636 stopReplicationAndGetStatusResults: map[string]*struct { 637 StopStatus *replicationdatapb.StopReplicationStatus 638 Err error 639 }{ 640 "zone1-0000000100": { 641 // In the tabletManager implementation of StopReplicationAndGetStatus 642 // we wrap the error and then send it via GRPC. This should still work as expected. 643 Err: vterrors.ToGRPC(vterrors.Wrap(mysql.ErrNotReplica, "before status failed")), 644 }, 645 "zone1-0000000101": { 646 StopStatus: &replicationdatapb.StopReplicationStatus{ 647 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 648 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 649 }, 650 }, 651 }, 652 }, 653 tabletMap: map[string]*topo.TabletInfo{ 654 "zone1-0000000100": { 655 Tablet: &topodatapb.Tablet{ 656 Type: topodatapb.TabletType_PRIMARY, 657 Alias: &topodatapb.TabletAlias{ 658 Cell: "zone1", 659 Uid: 100, 660 }, 661 }, 662 }, 663 "zone1-0000000101": { 664 Tablet: &topodatapb.Tablet{ 665 Type: topodatapb.TabletType_REPLICA, 666 Alias: &topodatapb.TabletAlias{ 667 Cell: "zone1", 668 Uid: 101, 669 }, 670 }, 671 }, 672 }, 673 ignoredTablets: sets.New[string](), 674 expectedStatusMap: map[string]*replicationdatapb.StopReplicationStatus{ 675 "zone1-0000000101": { 676 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 677 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 678 }, 679 }, 680 expectedPrimaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{ 681 "zone1-0000000100": { 682 Position: "primary-position-100", 683 }, 684 }, 685 expectedTabletsReachable: []*topodatapb.Tablet{{ 686 Type: topodatapb.TabletType_REPLICA, 687 Alias: &topodatapb.TabletAlias{ 688 Cell: "zone1", 689 Uid: 100, 690 }, 691 }, { 692 Type: topodatapb.TabletType_REPLICA, 693 Alias: &topodatapb.TabletAlias{ 694 Cell: "zone1", 695 Uid: 101, 696 }, 697 }}, 698 shouldErr: false, 699 }, 700 { 701 name: "one tablet is PRIMARY and cannot demote", 702 durability: "none", 703 tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ 704 demotePrimaryResults: map[string]*struct { 705 PrimaryStatus *replicationdatapb.PrimaryStatus 706 Err error 707 }{ 708 "zone1-0000000100": { 709 Err: assert.AnError, 710 }, 711 }, 712 stopReplicationAndGetStatusResults: map[string]*struct { 713 StopStatus *replicationdatapb.StopReplicationStatus 714 Err error 715 }{ 716 "zone1-0000000100": { 717 Err: mysql.ErrNotReplica, 718 }, 719 "zone1-0000000101": { 720 StopStatus: &replicationdatapb.StopReplicationStatus{ 721 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 722 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 723 }, 724 }, 725 }, 726 }, 727 tabletMap: map[string]*topo.TabletInfo{ 728 "zone1-0000000100": { 729 Tablet: &topodatapb.Tablet{ 730 Type: topodatapb.TabletType_PRIMARY, 731 Alias: &topodatapb.TabletAlias{ 732 Cell: "zone1", 733 Uid: 100, 734 }, 735 }, 736 }, 737 "zone1-0000000101": { 738 Tablet: &topodatapb.Tablet{ 739 Type: topodatapb.TabletType_REPLICA, 740 Alias: &topodatapb.TabletAlias{ 741 Cell: "zone1", 742 Uid: 101, 743 }, 744 }, 745 }, 746 }, 747 ignoredTablets: sets.New[string](), 748 expectedStatusMap: map[string]*replicationdatapb.StopReplicationStatus{ 749 "zone1-0000000101": { 750 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 751 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 752 }, 753 }, 754 expectedPrimaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{}, // zone1-0000000100 fails to demote, so does not appear 755 expectedTabletsReachable: []*topodatapb.Tablet{{ 756 Type: topodatapb.TabletType_REPLICA, 757 Alias: &topodatapb.TabletAlias{ 758 Cell: "zone1", 759 Uid: 101, 760 }, 761 }}, 762 shouldErr: false, 763 }, 764 { 765 name: "multiple tablets are PRIMARY and cannot demote", 766 durability: "none", 767 tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ 768 demotePrimaryResults: map[string]*struct { 769 PrimaryStatus *replicationdatapb.PrimaryStatus 770 Err error 771 }{ 772 "zone1-0000000100": { 773 Err: assert.AnError, 774 }, 775 "zone1-0000000101": { 776 Err: assert.AnError, 777 }, 778 }, 779 stopReplicationAndGetStatusResults: map[string]*struct { 780 StopStatus *replicationdatapb.StopReplicationStatus 781 Err error 782 }{ 783 "zone1-0000000100": { 784 Err: mysql.ErrNotReplica, 785 }, 786 "zone1-0000000101": { 787 Err: mysql.ErrNotReplica, 788 }, 789 }, 790 }, 791 tabletMap: map[string]*topo.TabletInfo{ 792 "zone1-0000000100": { 793 Tablet: &topodatapb.Tablet{ 794 Type: topodatapb.TabletType_PRIMARY, 795 Alias: &topodatapb.TabletAlias{ 796 Cell: "zone1", 797 Uid: 100, 798 }, 799 }, 800 }, 801 "zone1-0000000101": { 802 Tablet: &topodatapb.Tablet{ 803 Type: topodatapb.TabletType_PRIMARY, 804 Alias: &topodatapb.TabletAlias{ 805 Cell: "zone1", 806 Uid: 101, 807 }, 808 }, 809 }, 810 }, 811 ignoredTablets: sets.New[string](), 812 expectedStatusMap: nil, 813 expectedPrimaryStatusMap: nil, 814 expectedTabletsReachable: nil, 815 shouldErr: true, // we get multiple errors, so we fail 816 }, 817 { 818 name: "stopReplicasTimeout exceeded", 819 durability: "none", 820 tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ 821 stopReplicationAndGetStatusDelays: map[string]time.Duration{ 822 "zone1-0000000100": time.Minute, // zone1-0000000100 will timeout and not be included 823 }, 824 stopReplicationAndGetStatusResults: map[string]*struct { 825 StopStatus *replicationdatapb.StopReplicationStatus 826 Err error 827 }{ 828 "zone1-0000000100": { 829 StopStatus: &replicationdatapb.StopReplicationStatus{ 830 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 831 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"}, 832 }, 833 }, 834 "zone1-0000000101": { 835 StopStatus: &replicationdatapb.StopReplicationStatus{ 836 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 837 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 838 }, 839 }, 840 }, 841 }, 842 tabletMap: map[string]*topo.TabletInfo{ 843 "zone1-0000000100": { 844 Tablet: &topodatapb.Tablet{ 845 Type: topodatapb.TabletType_REPLICA, 846 Alias: &topodatapb.TabletAlias{ 847 Cell: "zone1", 848 Uid: 100, 849 }, 850 }, 851 }, 852 "zone1-0000000101": { 853 Tablet: &topodatapb.Tablet{ 854 Type: topodatapb.TabletType_REPLICA, 855 Alias: &topodatapb.TabletAlias{ 856 Cell: "zone1", 857 Uid: 101, 858 }, 859 }, 860 }, 861 }, 862 stopReplicasTimeout: time.Millisecond * 5, 863 ignoredTablets: sets.New[string](), 864 expectedStatusMap: map[string]*replicationdatapb.StopReplicationStatus{ 865 "zone1-0000000101": { 866 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 867 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 868 }, 869 }, 870 expectedTabletsReachable: []*topodatapb.Tablet{{ 871 Type: topodatapb.TabletType_REPLICA, 872 Alias: &topodatapb.TabletAlias{ 873 Cell: "zone1", 874 Uid: 101, 875 }, 876 }}, 877 expectedPrimaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{}, 878 shouldErr: false, 879 }, 880 { 881 name: "one tablet fails to StopReplication", 882 durability: "none", 883 tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ 884 stopReplicationAndGetStatusResults: map[string]*struct { 885 StopStatus *replicationdatapb.StopReplicationStatus 886 Err error 887 }{ 888 "zone1-0000000100": { 889 Err: assert.AnError, // not being mysql.ErrNotReplica will not cause us to call DemotePrimary 890 }, 891 "zone1-0000000101": { 892 StopStatus: &replicationdatapb.StopReplicationStatus{ 893 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 894 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 895 }, 896 }, 897 }, 898 }, 899 tabletMap: map[string]*topo.TabletInfo{ 900 "zone1-0000000100": { 901 Tablet: &topodatapb.Tablet{ 902 Type: topodatapb.TabletType_REPLICA, 903 Alias: &topodatapb.TabletAlias{ 904 Cell: "zone1", 905 Uid: 100, 906 }, 907 }, 908 }, 909 "zone1-0000000101": { 910 Tablet: &topodatapb.Tablet{ 911 Type: topodatapb.TabletType_REPLICA, 912 Alias: &topodatapb.TabletAlias{ 913 Cell: "zone1", 914 Uid: 101, 915 }, 916 }, 917 }, 918 }, 919 ignoredTablets: sets.New[string](), 920 expectedStatusMap: map[string]*replicationdatapb.StopReplicationStatus{ 921 "zone1-0000000101": { 922 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 923 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 924 }, 925 }, 926 expectedPrimaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{}, 927 expectedTabletsReachable: []*topodatapb.Tablet{{ 928 Type: topodatapb.TabletType_REPLICA, 929 Alias: &topodatapb.TabletAlias{ 930 Cell: "zone1", 931 Uid: 101, 932 }, 933 }}, 934 shouldErr: false, 935 }, 936 { 937 name: "multiple tablets fail StopReplication", 938 durability: "none", 939 tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ 940 stopReplicationAndGetStatusResults: map[string]*struct { 941 StopStatus *replicationdatapb.StopReplicationStatus 942 Err error 943 }{ 944 "zone1-0000000100": { 945 Err: assert.AnError, 946 }, 947 "zone1-0000000101": { 948 Err: assert.AnError, 949 }, 950 }, 951 }, 952 tabletMap: map[string]*topo.TabletInfo{ 953 "zone1-0000000100": { 954 Tablet: &topodatapb.Tablet{ 955 Type: topodatapb.TabletType_REPLICA, 956 Alias: &topodatapb.TabletAlias{ 957 Cell: "zone1", 958 Uid: 100, 959 }, 960 }, 961 }, 962 "zone1-0000000101": { 963 Tablet: &topodatapb.Tablet{ 964 Type: topodatapb.TabletType_REPLICA, 965 Alias: &topodatapb.TabletAlias{ 966 Cell: "zone1", 967 Uid: 101, 968 }, 969 }, 970 }, 971 }, 972 ignoredTablets: sets.New[string](), 973 expectedStatusMap: nil, 974 expectedPrimaryStatusMap: nil, 975 expectedTabletsReachable: nil, 976 shouldErr: true, 977 }, { 978 name: "1 tablets fail StopReplication and 1 has replication stopped", 979 durability: "none", 980 tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ 981 stopReplicationAndGetStatusResults: map[string]*struct { 982 StopStatus *replicationdatapb.StopReplicationStatus 983 Err error 984 }{ 985 "zone1-0000000100": { 986 Err: assert.AnError, 987 }, 988 "zone1-0000000101": { 989 StopStatus: &replicationdatapb.StopReplicationStatus{ 990 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5"}, 991 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 992 }, 993 }, 994 }, 995 }, 996 tabletMap: map[string]*topo.TabletInfo{ 997 "zone1-0000000100": { 998 Tablet: &topodatapb.Tablet{ 999 Type: topodatapb.TabletType_REPLICA, 1000 Alias: &topodatapb.TabletAlias{ 1001 Cell: "zone1", 1002 Uid: 100, 1003 }, 1004 }, 1005 }, 1006 "zone1-0000000101": { 1007 Tablet: &topodatapb.Tablet{ 1008 Type: topodatapb.TabletType_REPLICA, 1009 Alias: &topodatapb.TabletAlias{ 1010 Cell: "zone1", 1011 Uid: 101, 1012 }, 1013 }, 1014 }, 1015 }, 1016 ignoredTablets: sets.New[string](), 1017 expectedStatusMap: nil, 1018 expectedPrimaryStatusMap: nil, 1019 expectedTabletsReachable: nil, 1020 shouldErr: true, 1021 }, 1022 { 1023 name: "slow tablet is the new primary requested", 1024 durability: "none", 1025 tmc: &stopReplicationAndBuildStatusMapsTestTMClient{ 1026 stopReplicationAndGetStatusDelays: map[string]time.Duration{ 1027 "zone1-0000000102": 1 * time.Second, // zone1-0000000102 is slow to respond but has to be included since it is the requested primary 1028 }, 1029 stopReplicationAndGetStatusResults: map[string]*struct { 1030 StopStatus *replicationdatapb.StopReplicationStatus 1031 Err error 1032 }{ 1033 "zone1-0000000100": { 1034 StopStatus: &replicationdatapb.StopReplicationStatus{ 1035 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 1036 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"}, 1037 }, 1038 }, 1039 "zone1-0000000101": { 1040 StopStatus: &replicationdatapb.StopReplicationStatus{ 1041 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 1042 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 1043 }, 1044 }, 1045 "zone1-0000000102": { 1046 StopStatus: &replicationdatapb.StopReplicationStatus{ 1047 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429102:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 1048 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429102:1-9"}, 1049 }, 1050 }, 1051 }, 1052 }, 1053 tabletMap: map[string]*topo.TabletInfo{ 1054 "zone1-0000000100": { 1055 Tablet: &topodatapb.Tablet{ 1056 Type: topodatapb.TabletType_REPLICA, 1057 Alias: &topodatapb.TabletAlias{ 1058 Cell: "zone1", 1059 Uid: 100, 1060 }, 1061 }, 1062 }, 1063 "zone1-0000000101": { 1064 Tablet: &topodatapb.Tablet{ 1065 Type: topodatapb.TabletType_REPLICA, 1066 Alias: &topodatapb.TabletAlias{ 1067 Cell: "zone1", 1068 Uid: 101, 1069 }, 1070 }, 1071 }, 1072 "zone1-0000000102": { 1073 Tablet: &topodatapb.Tablet{ 1074 Type: topodatapb.TabletType_REPLICA, 1075 Alias: &topodatapb.TabletAlias{ 1076 Cell: "zone1", 1077 Uid: 102, 1078 }, 1079 }, 1080 }, 1081 }, 1082 tabletToWaitFor: &topodatapb.TabletAlias{ 1083 Cell: "zone1", 1084 Uid: 102, 1085 }, 1086 ignoredTablets: sets.New[string](), 1087 expectedStatusMap: map[string]*replicationdatapb.StopReplicationStatus{ 1088 "zone1-0000000100": { 1089 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 1090 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429100:1-9"}, 1091 }, 1092 "zone1-0000000101": { 1093 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 1094 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429101:1-9"}, 1095 }, 1096 "zone1-0000000102": { 1097 Before: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429102:1-5", IoState: int32(mysql.ReplicationStateRunning), SqlState: int32(mysql.ReplicationStateRunning)}, 1098 After: &replicationdatapb.Status{Position: "MySQL56/3E11FA47-71CA-11E1-9E33-C80AA9429102:1-9"}, 1099 }, 1100 }, 1101 expectedTabletsReachable: []*topodatapb.Tablet{{ 1102 Type: topodatapb.TabletType_REPLICA, 1103 Alias: &topodatapb.TabletAlias{ 1104 Cell: "zone1", 1105 Uid: 100, 1106 }, 1107 }, { 1108 Type: topodatapb.TabletType_REPLICA, 1109 Alias: &topodatapb.TabletAlias{ 1110 Cell: "zone1", 1111 Uid: 101, 1112 }, 1113 }, { 1114 Type: topodatapb.TabletType_REPLICA, 1115 Alias: &topodatapb.TabletAlias{ 1116 Cell: "zone1", 1117 Uid: 102, 1118 }, 1119 }}, 1120 stopReplicasTimeout: time.Minute, 1121 expectedPrimaryStatusMap: map[string]*replicationdatapb.PrimaryStatus{}, 1122 shouldErr: false, 1123 }, 1124 } 1125 1126 for _, tt := range tests { 1127 tt := tt 1128 1129 t.Run(tt.name, func(t *testing.T) { 1130 durability, err := GetDurabilityPolicy(tt.durability) 1131 require.NoError(t, err) 1132 res, err := stopReplicationAndBuildStatusMaps(ctx, tt.tmc, &events.Reparent{}, tt.tabletMap, tt.stopReplicasTimeout, tt.ignoredTablets, tt.tabletToWaitFor, durability, logger) 1133 if tt.shouldErr { 1134 assert.Error(t, err) 1135 return 1136 } 1137 1138 assert.NoError(t, err) 1139 assert.Equal(t, tt.expectedStatusMap, res.statusMap, "StopReplicationStatus mismatch") 1140 assert.Equal(t, tt.expectedPrimaryStatusMap, res.primaryStatusMap, "PrimaryStatusMap mismatch") 1141 require.Equal(t, len(tt.expectedTabletsReachable), len(res.reachableTablets), "TabletsReached length mismatch") 1142 for idx, tablet := range res.reachableTablets { 1143 assert.True(t, topoproto.IsTabletInList(tablet, tt.expectedTabletsReachable), "TabletsReached[%d] not found - %s", idx, topoproto.TabletAliasString(tablet.Alias)) 1144 } 1145 }) 1146 } 1147 } 1148 1149 func TestReplicaWasRunning(t *testing.T) { 1150 t.Parallel() 1151 1152 tests := []struct { 1153 name string 1154 in *replicationdatapb.StopReplicationStatus 1155 expected bool 1156 shouldErr bool 1157 }{ 1158 { 1159 name: "io thread running", 1160 in: &replicationdatapb.StopReplicationStatus{ 1161 Before: &replicationdatapb.Status{ 1162 IoState: int32(mysql.ReplicationStateRunning), 1163 SqlState: int32(mysql.ReplicationStateStopped), 1164 }, 1165 }, 1166 expected: true, 1167 shouldErr: false, 1168 }, 1169 { 1170 name: "sql thread running", 1171 in: &replicationdatapb.StopReplicationStatus{ 1172 Before: &replicationdatapb.Status{ 1173 IoState: int32(mysql.ReplicationStateStopped), 1174 SqlState: int32(mysql.ReplicationStateRunning), 1175 }, 1176 }, 1177 expected: true, 1178 shouldErr: false, 1179 }, 1180 { 1181 name: "io and sql threads running", 1182 in: &replicationdatapb.StopReplicationStatus{ 1183 Before: &replicationdatapb.Status{ 1184 IoState: int32(mysql.ReplicationStateRunning), 1185 SqlState: int32(mysql.ReplicationStateRunning), 1186 }, 1187 }, 1188 expected: true, 1189 shouldErr: false, 1190 }, 1191 { 1192 name: "no replication threads running", 1193 in: &replicationdatapb.StopReplicationStatus{ 1194 Before: &replicationdatapb.Status{ 1195 IoState: int32(mysql.ReplicationStateStopped), 1196 SqlState: int32(mysql.ReplicationStateStopped), 1197 }, 1198 }, 1199 expected: false, 1200 shouldErr: false, 1201 }, 1202 { 1203 name: "passing nil pointer results in an error", 1204 in: nil, 1205 expected: false, 1206 shouldErr: true, 1207 }, 1208 { 1209 name: "status.Before is nil results in an error", 1210 in: &replicationdatapb.StopReplicationStatus{ 1211 Before: nil, 1212 }, 1213 expected: false, 1214 shouldErr: true, 1215 }, 1216 } 1217 1218 for _, tt := range tests { 1219 tt := tt 1220 1221 t.Run(tt.name, func(t *testing.T) { 1222 t.Parallel() 1223 1224 actual, err := ReplicaWasRunning(tt.in) 1225 if tt.shouldErr { 1226 assert.Error(t, err) 1227 1228 return 1229 } 1230 1231 assert.NoError(t, err) 1232 assert.Equal(t, tt.expected, actual) 1233 }) 1234 } 1235 } 1236 1237 func TestSQLThreadWasRunning(t *testing.T) { 1238 t.Parallel() 1239 1240 tests := []struct { 1241 name string 1242 in *replicationdatapb.StopReplicationStatus 1243 expected bool 1244 shouldErr bool 1245 }{ 1246 { 1247 name: "io thread running", 1248 in: &replicationdatapb.StopReplicationStatus{ 1249 Before: &replicationdatapb.Status{ 1250 IoState: int32(mysql.ReplicationStateRunning), 1251 SqlState: int32(mysql.ReplicationStateStopped), 1252 }, 1253 }, 1254 expected: false, 1255 shouldErr: false, 1256 }, 1257 { 1258 name: "sql thread running", 1259 in: &replicationdatapb.StopReplicationStatus{ 1260 Before: &replicationdatapb.Status{ 1261 IoState: int32(mysql.ReplicationStateStopped), 1262 SqlState: int32(mysql.ReplicationStateRunning), 1263 }, 1264 }, 1265 expected: true, 1266 shouldErr: false, 1267 }, 1268 { 1269 name: "io and sql threads running", 1270 in: &replicationdatapb.StopReplicationStatus{ 1271 Before: &replicationdatapb.Status{ 1272 IoState: int32(mysql.ReplicationStateRunning), 1273 SqlState: int32(mysql.ReplicationStateRunning), 1274 }, 1275 }, 1276 expected: true, 1277 shouldErr: false, 1278 }, 1279 { 1280 name: "no replication threads running", 1281 in: &replicationdatapb.StopReplicationStatus{ 1282 Before: &replicationdatapb.Status{ 1283 IoState: int32(mysql.ReplicationStateStopped), 1284 SqlState: int32(mysql.ReplicationStateStopped), 1285 }, 1286 }, 1287 expected: false, 1288 shouldErr: false, 1289 }, 1290 { 1291 name: "passing nil pointer results in an error", 1292 in: nil, 1293 expected: false, 1294 shouldErr: true, 1295 }, 1296 { 1297 name: "status.Before is nil results in an error", 1298 in: &replicationdatapb.StopReplicationStatus{ 1299 Before: nil, 1300 }, 1301 expected: false, 1302 shouldErr: true, 1303 }, 1304 } 1305 1306 for _, tt := range tests { 1307 tt := tt 1308 1309 t.Run(tt.name, func(t *testing.T) { 1310 t.Parallel() 1311 1312 actual, err := SQLThreadWasRunning(tt.in) 1313 if tt.shouldErr { 1314 assert.Error(t, err) 1315 1316 return 1317 } 1318 1319 assert.NoError(t, err) 1320 assert.Equal(t, tt.expected, actual) 1321 }) 1322 } 1323 } 1324 1325 // waitForRelayLogsToApplyTestTMClient implements just the WaitForPosition 1326 // method of the tmclient.TabletManagerClient interface for 1327 // TestWaitForRelayLogsToApply, with the necessary trackers to facilitate 1328 // testing that unit. 1329 type waitForRelayLogsToApplyTestTMClient struct { 1330 tmclient.TabletManagerClient 1331 calledPositions []string 1332 shouldErr bool 1333 } 1334 1335 func (fake *waitForRelayLogsToApplyTestTMClient) WaitForPosition(_ context.Context, _ *topodatapb.Tablet, position string) error { 1336 if fake.shouldErr { 1337 return assert.AnError 1338 } 1339 1340 fake.calledPositions = append(fake.calledPositions, position) 1341 return nil 1342 } 1343 1344 func TestWaitForRelayLogsToApply(t *testing.T) { 1345 t.Parallel() 1346 1347 ctx := context.Background() 1348 tests := []struct { 1349 name string 1350 client *waitForRelayLogsToApplyTestTMClient 1351 status *replicationdatapb.StopReplicationStatus 1352 expectedCalledPositions []string 1353 shouldErr bool 1354 }{ 1355 { 1356 name: "using relay log position", 1357 client: &waitForRelayLogsToApplyTestTMClient{}, 1358 status: &replicationdatapb.StopReplicationStatus{ 1359 After: &replicationdatapb.Status{ 1360 RelayLogPosition: "relay-pos", 1361 }, 1362 }, 1363 expectedCalledPositions: []string{"relay-pos"}, 1364 shouldErr: false, 1365 }, 1366 { 1367 name: "using file relay log position", 1368 client: &waitForRelayLogsToApplyTestTMClient{}, 1369 status: &replicationdatapb.StopReplicationStatus{ 1370 After: &replicationdatapb.Status{ 1371 RelayLogSourceBinlogEquivalentPosition: "file-relay-pos", 1372 }, 1373 }, 1374 expectedCalledPositions: []string{"file-relay-pos"}, 1375 shouldErr: false, 1376 }, 1377 { 1378 name: "when both are set, relay log position takes precedence over file relay log position", 1379 client: &waitForRelayLogsToApplyTestTMClient{}, 1380 status: &replicationdatapb.StopReplicationStatus{ 1381 After: &replicationdatapb.Status{ 1382 RelayLogPosition: "relay-pos", 1383 FilePosition: "file-relay-pos", 1384 }, 1385 }, 1386 expectedCalledPositions: []string{"relay-pos"}, 1387 shouldErr: false, 1388 }, 1389 { 1390 name: "error waiting for position", 1391 client: &waitForRelayLogsToApplyTestTMClient{ 1392 shouldErr: true, 1393 }, 1394 status: &replicationdatapb.StopReplicationStatus{ 1395 After: &replicationdatapb.Status{ 1396 RelayLogPosition: "relay-pos", 1397 }, 1398 }, 1399 expectedCalledPositions: nil, 1400 shouldErr: true, 1401 }, 1402 } 1403 1404 for _, tt := range tests { 1405 tt := tt 1406 1407 t.Run(tt.name, func(t *testing.T) { 1408 t.Parallel() 1409 1410 err := WaitForRelayLogsToApply(ctx, tt.client, &topo.TabletInfo{}, tt.status) 1411 defer assert.Equal(t, tt.expectedCalledPositions, tt.client.calledPositions) 1412 if tt.shouldErr { 1413 assert.Error(t, err) 1414 return 1415 } 1416 1417 assert.NoError(t, err) 1418 }) 1419 } 1420 }