vitess.io/vitess@v0.16.2/go/vt/vtgate/endtoend/vstream_test.go (about) 1 /* 2 Copyright 2019 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 endtoend 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "sort" 24 "sync" 25 "testing" 26 27 vtgatepb "vitess.io/vitess/go/vt/proto/vtgate" 28 29 "github.com/stretchr/testify/require" 30 "google.golang.org/protobuf/proto" 31 32 "vitess.io/vitess/go/mysql" 33 "vitess.io/vitess/go/sqltypes" 34 "vitess.io/vitess/go/vt/log" 35 binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" 36 "vitess.io/vitess/go/vt/proto/query" 37 querypb "vitess.io/vitess/go/vt/proto/query" 38 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 39 "vitess.io/vitess/go/vt/vtgate/vtgateconn" 40 ) 41 42 func initialize(ctx context.Context, t *testing.T) (*vtgateconn.VTGateConn, *mysql.Conn, *mysql.Conn, func()) { 43 gconn, err := vtgateconn.Dial(ctx, grpcAddress) 44 if err != nil { 45 t.Fatal(err) 46 } 47 conn, err := mysql.Connect(ctx, &vtParams) 48 if err != nil { 49 t.Fatal(err) 50 } 51 mconn, err := mysql.Connect(ctx, &mysqlParams) 52 if err != nil { 53 t.Fatal(err) 54 } 55 close := func() { 56 gconn.Close() 57 conn.Close() 58 mconn.Close() 59 } 60 return gconn, conn, mconn, close 61 } 62 func TestVStream(t *testing.T) { 63 ctx, cancel := context.WithCancel(context.Background()) 64 defer cancel() 65 66 gconn, conn, mconn, closeConnections := initialize(ctx, t) 67 defer closeConnections() 68 69 mpos, err := mconn.PrimaryPosition() 70 if err != nil { 71 t.Fatal(err) 72 } 73 vgtid := &binlogdatapb.VGtid{ 74 ShardGtids: []*binlogdatapb.ShardGtid{{ 75 Keyspace: "ks", 76 Shard: "-80", 77 Gtid: fmt.Sprintf("%s/%s", mpos.GTIDSet.Flavor(), mpos), 78 }}, 79 } 80 filter := &binlogdatapb.Filter{ 81 Rules: []*binlogdatapb.Rule{{ 82 Match: "/.*/", 83 }}, 84 } 85 flags := &vtgatepb.VStreamFlags{} 86 reader, err := gconn.VStream(ctx, topodatapb.TabletType_PRIMARY, vgtid, filter, flags) 87 if err != nil { 88 t.Fatal(err) 89 } 90 91 _, err = conn.ExecuteFetch("insert into vstream_test(id,val) values(1,1), (4,4)", 1, false) 92 if err != nil { 93 t.Fatal(err) 94 } 95 // We expect two events because the insert goes to two shards (-80 and 80-), 96 // and both of them are in the same mysql server. 97 // The row that goes to 80- will have events. 98 // The other will be an empty transaction. 99 // In a real world scenario where every mysql instance hosts only one 100 // keyspace/shard, we should expect only a single event. 101 // The events could come in any order as the scatter insert runs in parallel. 102 emptyEventSkipped := false 103 for i := 0; i < 2; i++ { 104 events, err := reader.Recv() 105 if err != nil { 106 t.Fatal(err) 107 } 108 fmt.Printf("events: %v\n", events) 109 // An empty transaction has three events: begin, gtid and commit. 110 if len(events) == 3 && !emptyEventSkipped { 111 emptyEventSkipped = true 112 continue 113 } 114 if len(events) != 5 { 115 t.Errorf("Unexpected event length: %v", events) 116 continue 117 } 118 wantFields := &binlogdatapb.FieldEvent{ 119 TableName: "ks.vstream_test", 120 Keyspace: "ks", 121 Shard: "-80", 122 Fields: []*querypb.Field{{ 123 Name: "id", 124 Type: querypb.Type_INT64, 125 }, { 126 Name: "val", 127 Type: querypb.Type_INT64, 128 }}, 129 } 130 131 gotFields := events[1].FieldEvent 132 filteredFields := &binlogdatapb.FieldEvent{ 133 TableName: gotFields.TableName, 134 Keyspace: gotFields.Keyspace, 135 Shard: gotFields.Shard, 136 Fields: []*querypb.Field{}, 137 } 138 for _, field := range gotFields.Fields { 139 filteredFields.Fields = append(filteredFields.Fields, &querypb.Field{ 140 Name: field.Name, 141 Type: field.Type, 142 }) 143 } 144 if !proto.Equal(filteredFields, wantFields) { 145 t.Errorf("FieldEvent:\n%v, want\n%v", filteredFields, wantFields) 146 } 147 wantRows := &binlogdatapb.RowEvent{ 148 TableName: "ks.vstream_test", 149 Keyspace: "ks", 150 Shard: "-80", 151 RowChanges: []*binlogdatapb.RowChange{{ 152 After: &query.Row{ 153 Lengths: []int64{1, 1}, 154 Values: []byte("11"), 155 }, 156 }}, 157 } 158 gotRows := events[2].RowEvent 159 if !proto.Equal(gotRows, wantRows) { 160 t.Errorf("RowEvent:\n%v, want\n%v", gotRows, wantRows) 161 } 162 } 163 cancel() 164 } 165 166 func TestVStreamCopyBasic(t *testing.T) { 167 ctx, cancel := context.WithCancel(context.Background()) 168 defer cancel() 169 gconn, conn, mconn, closeConnections := initialize(ctx, t) 170 defer closeConnections() 171 172 _, err := conn.ExecuteFetch("insert into t1_copy_basic(id1,id2) values(1,1), (2,2), (3,3), (4,4), (5,5), (6,6), (7,7), (8,8)", 1, false) 173 if err != nil { 174 t.Fatal(err) 175 } 176 177 lastPK := sqltypes.Result{ 178 Fields: []*query.Field{{Name: "id1", Type: query.Type_INT32}}, 179 Rows: [][]sqltypes.Value{{sqltypes.NewInt32(4)}}, 180 } 181 qr := sqltypes.ResultToProto3(&lastPK) 182 tablePKs := []*binlogdatapb.TableLastPK{{ 183 TableName: "t1_copy_basic", 184 Lastpk: qr, 185 }} 186 var shardGtids []*binlogdatapb.ShardGtid 187 var vgtid = &binlogdatapb.VGtid{} 188 shardGtids = append(shardGtids, &binlogdatapb.ShardGtid{ 189 Keyspace: "ks", 190 Shard: "-80", 191 Gtid: "", 192 TablePKs: tablePKs, 193 }) 194 shardGtids = append(shardGtids, &binlogdatapb.ShardGtid{ 195 Keyspace: "ks", 196 Shard: "80-", 197 Gtid: "", 198 TablePKs: tablePKs, 199 }) 200 vgtid.ShardGtids = shardGtids 201 filter := &binlogdatapb.Filter{ 202 Rules: []*binlogdatapb.Rule{{ 203 Match: "t1_copy_basic", 204 Filter: "select * from t1_copy_basic", 205 }}, 206 } 207 flags := &vtgatepb.VStreamFlags{} 208 reader, err := gconn.VStream(ctx, topodatapb.TabletType_PRIMARY, vgtid, filter, flags) 209 _, _ = conn, mconn 210 if err != nil { 211 t.Fatal(err) 212 } 213 numExpectedEvents := 2 /* num shards */ *(7 /* begin/field/vgtid:pos/2 rowevents avg/vgitd: lastpk/commit) */ +3 /* begin/vgtid/commit for completed table */ +1 /* copy operation completed */) + 1 /* fully copy operation completed */ 214 expectedCompletedEvents := []string{ 215 `type:COPY_COMPLETED keyspace:"ks" shard:"-80"`, 216 `type:COPY_COMPLETED keyspace:"ks" shard:"80-"`, 217 `type:COPY_COMPLETED`, 218 } 219 require.NotNil(t, reader) 220 var evs []*binlogdatapb.VEvent 221 var completedEvs []*binlogdatapb.VEvent 222 for { 223 e, err := reader.Recv() 224 switch err { 225 case nil: 226 evs = append(evs, e...) 227 228 for _, ev := range e { 229 if ev.Type == binlogdatapb.VEventType_COPY_COMPLETED { 230 completedEvs = append(completedEvs, ev) 231 } 232 } 233 234 printEvents(evs) // for debugging ci failures 235 236 if len(evs) == numExpectedEvents { 237 // The arrival order of COPY_COMPLETED events with keyspace/shard is not constant. 238 // On the other hand, the last event should always be a fully COPY_COMPLETED event. 239 // That's why the sort.Slice doesn't have to handle the last element in completedEvs. 240 sort.Slice(completedEvs[:len(completedEvs)-1], func(i, j int) bool { 241 return completedEvs[i].GetShard() < completedEvs[j].GetShard() 242 }) 243 for i, ev := range completedEvs { 244 require.Regexp(t, expectedCompletedEvents[i], ev.String()) 245 } 246 t.Logf("TestVStreamCopyBasic was successful") 247 return 248 } else if numExpectedEvents < len(evs) { 249 t.Fatalf("len(events)=%v are not expected\n", len(evs)) 250 } 251 case io.EOF: 252 log.Infof("stream ended\n") 253 cancel() 254 default: 255 log.Errorf("Returned err %v", err) 256 t.Fatalf("remote error: %v\n", err) 257 } 258 } 259 } 260 261 func TestVStreamCopyResume(t *testing.T) { 262 ctx, cancel := context.WithCancel(context.Background()) 263 defer cancel() 264 gconn, conn, mconn, closeConnections := initialize(ctx, t) 265 defer closeConnections() 266 267 _, err := conn.ExecuteFetch("insert into t1_copy_resume(id1,id2) values(1,1), (2,2), (3,3), (4,4), (5,5), (6,6), (7,7), (8,8)", 1, false) 268 if err != nil { 269 t.Fatal(err) 270 } 271 272 // Any subsequent GTIDs will be part of the stream 273 mpos, err := mconn.PrimaryPosition() 274 require.NoError(t, err) 275 276 // lastPK is id1=4, meaning we should only copy rows for id1 IN(5,6,7,8,9) 277 lastPK := sqltypes.Result{ 278 Fields: []*query.Field{{Name: "id1", Type: query.Type_INT64}}, 279 Rows: [][]sqltypes.Value{{sqltypes.NewInt64(4)}}, 280 } 281 tableLastPK := []*binlogdatapb.TableLastPK{{ 282 TableName: "t1_copy_resume", 283 Lastpk: sqltypes.ResultToProto3(&lastPK), 284 }} 285 286 catchupQueries := []string{ 287 "insert into t1_copy_resume(id1,id2) values(9,9)", // this row will show up twice: once in catchup and copy 288 "update t1_copy_resume set id2 = 10 where id1 = 1", 289 "insert into t1(id1, id2) values(100,100)", 290 "delete from t1_copy_resume where id1 = 1", 291 "update t1_copy_resume set id2 = 90 where id1 = 9", 292 } 293 for _, query := range catchupQueries { 294 _, err = conn.ExecuteFetch(query, 1, false) 295 require.NoError(t, err) 296 } 297 298 var shardGtids []*binlogdatapb.ShardGtid 299 var vgtid = &binlogdatapb.VGtid{} 300 shardGtids = append(shardGtids, &binlogdatapb.ShardGtid{ 301 Keyspace: "ks", 302 Shard: "-80", 303 Gtid: fmt.Sprintf("%s/%s", mpos.GTIDSet.Flavor(), mpos), 304 TablePKs: tableLastPK, 305 }) 306 shardGtids = append(shardGtids, &binlogdatapb.ShardGtid{ 307 Keyspace: "ks", 308 Shard: "80-", 309 Gtid: fmt.Sprintf("%s/%s", mpos.GTIDSet.Flavor(), mpos), 310 TablePKs: tableLastPK, 311 }) 312 vgtid.ShardGtids = shardGtids 313 filter := &binlogdatapb.Filter{ 314 Rules: []*binlogdatapb.Rule{{ 315 Match: "t1_copy_resume", 316 Filter: "select * from t1_copy_resume", 317 }}, 318 } 319 flags := &vtgatepb.VStreamFlags{} 320 reader, err := gconn.VStream(ctx, topodatapb.TabletType_PRIMARY, vgtid, filter, flags) 321 if err != nil { 322 t.Fatal(err) 323 } 324 require.NotNil(t, reader) 325 326 expectedRowCopyEvents := 5 // id1 and id2 IN(5,6,7,8,9) 327 expectedCatchupEvents := len(catchupQueries) - 1 // insert into t1 should never reach 328 rowCopyEvents, replCatchupEvents := 0, 0 329 expectedEvents := []string{ 330 `type:ROW timestamp:[0-9]+ row_event:{table_name:"ks.t1_copy_resume" row_changes:{before:{lengths:1 lengths:1 values:"11"} after:{lengths:1 lengths:2 values:"110"}} keyspace:"ks" shard:"-80"} current_time:[0-9]+ keyspace:"ks" shard:"-80"`, 331 `type:ROW timestamp:[0-9]+ row_event:{table_name:"ks.t1_copy_resume" row_changes:{before:{lengths:1 lengths:2 values:"110"}} keyspace:"ks" shard:"-80"} current_time:[0-9]+ keyspace:"ks" shard:"-80"`, 332 `type:ROW row_event:{table_name:"ks.t1_copy_resume" row_changes:{after:{lengths:1 lengths:1 values:"55"}} keyspace:"ks" shard:"-80"} keyspace:"ks" shard:"-80"`, 333 `type:ROW row_event:{table_name:"ks.t1_copy_resume" row_changes:{after:{lengths:1 lengths:1 values:"66"}} keyspace:"ks" shard:"80-"} keyspace:"ks" shard:"80-"`, 334 `type:ROW row_event:{table_name:"ks.t1_copy_resume" row_changes:{after:{lengths:1 lengths:1 values:"77"}} keyspace:"ks" shard:"80-"} keyspace:"ks" shard:"80-"`, 335 `type:ROW row_event:{table_name:"ks.t1_copy_resume" row_changes:{after:{lengths:1 lengths:1 values:"88"}} keyspace:"ks" shard:"80-"} keyspace:"ks" shard:"80-"`, 336 `type:ROW timestamp:[0-9]+ row_event:{table_name:"ks.t1_copy_resume" row_changes:{after:{lengths:1 lengths:1 values:"99"}} keyspace:"ks" shard:"-80"} current_time:[0-9]+ keyspace:"ks" shard:"-80"`, 337 `type:ROW row_event:{table_name:"ks.t1_copy_resume" row_changes:{after:{lengths:1 lengths:2 values:"990"}} keyspace:"ks" shard:"-80"} keyspace:"ks" shard:"-80"`, 338 `type:ROW timestamp:[0-9]+ row_event:{table_name:"ks.t1_copy_resume" row_changes:{before:{lengths:1 lengths:1 values:"99"} after:{lengths:1 lengths:2 values:"990"}} keyspace:"ks" shard:"-80"} current_time:[0-9]+ keyspace:"ks" shard:"-80"`, 339 } 340 var evs []*binlogdatapb.VEvent 341 for { 342 e, err := reader.Recv() 343 switch err { 344 case nil: 345 for _, ev := range e { 346 if ev.Type == binlogdatapb.VEventType_ROW { 347 evs = append(evs, ev) 348 if ev.Timestamp == 0 { 349 rowCopyEvents++ 350 } else { 351 replCatchupEvents++ 352 } 353 printEvents(evs) // for debugging ci failures 354 } 355 } 356 if expectedCatchupEvents == replCatchupEvents && expectedRowCopyEvents == rowCopyEvents { 357 sort.Sort(VEventSorter(evs)) 358 for i, ev := range evs { 359 require.Regexp(t, expectedEvents[i], ev.String()) 360 } 361 t.Logf("TestVStreamCopyResume was successful") 362 return 363 } 364 case io.EOF: 365 log.Infof("stream ended\n") 366 cancel() 367 default: 368 log.Errorf("Returned err %v", err) 369 t.Fatalf("remote error: %v\n", err) 370 } 371 } 372 } 373 374 func TestVStreamCurrent(t *testing.T) { 375 ctx, cancel := context.WithCancel(context.Background()) 376 defer cancel() 377 gconn, conn, mconn, closeConnections := initialize(ctx, t) 378 defer closeConnections() 379 380 var shardGtids []*binlogdatapb.ShardGtid 381 var vgtid = &binlogdatapb.VGtid{} 382 shardGtids = append(shardGtids, &binlogdatapb.ShardGtid{ 383 Keyspace: "ks", 384 Shard: "-80", 385 Gtid: "current", 386 }) 387 shardGtids = append(shardGtids, &binlogdatapb.ShardGtid{ 388 Keyspace: "ks", 389 Shard: "80-", 390 Gtid: "current", 391 }) 392 vgtid.ShardGtids = shardGtids 393 filter := &binlogdatapb.Filter{ 394 Rules: []*binlogdatapb.Rule{{ 395 Match: "t1", 396 Filter: "select * from t1", 397 }}, 398 } 399 flags := &vtgatepb.VStreamFlags{} 400 reader, err := gconn.VStream(ctx, topodatapb.TabletType_PRIMARY, vgtid, filter, flags) 401 _, _ = conn, mconn 402 if err != nil { 403 t.Fatal(err) 404 } 405 numExpectedEvents := 4 // vgtid+other per shard for "current" 406 require.NotNil(t, reader) 407 var evs []*binlogdatapb.VEvent 408 for { 409 e, err := reader.Recv() 410 switch err { 411 case nil: 412 evs = append(evs, e...) 413 printEvents(evs) // for debugging ci failures 414 if len(evs) == numExpectedEvents { 415 t.Logf("TestVStreamCurrent was successful") 416 return 417 } 418 case io.EOF: 419 log.Infof("stream ended\n") 420 cancel() 421 default: 422 log.Errorf("Returned err %v", err) 423 t.Fatalf("remote error: %v\n", err) 424 } 425 } 426 } 427 428 func TestVStreamSharded(t *testing.T) { 429 ctx, cancel := context.WithCancel(context.Background()) 430 defer cancel() 431 gconn, conn, mconn, closeConnections := initialize(ctx, t) 432 defer closeConnections() 433 434 var shardGtids []*binlogdatapb.ShardGtid 435 var vgtid = &binlogdatapb.VGtid{} 436 shardGtids = append(shardGtids, &binlogdatapb.ShardGtid{ 437 Keyspace: "ks", 438 Shard: "-80", 439 Gtid: "", 440 }) 441 shardGtids = append(shardGtids, &binlogdatapb.ShardGtid{ 442 Keyspace: "ks", 443 Shard: "80-", 444 Gtid: "", 445 }) 446 vgtid.ShardGtids = shardGtids 447 filter := &binlogdatapb.Filter{ 448 Rules: []*binlogdatapb.Rule{{ 449 Match: "t1_sharded", 450 Filter: "select * from t1_sharded", 451 }}, 452 } 453 _, err := conn.ExecuteFetch("insert into t1_sharded(id1,id2) values(1,1), (4,4)", 1, false) 454 if err != nil { 455 t.Fatal(err) 456 } 457 flags := &vtgatepb.VStreamFlags{} 458 reader, err := gconn.VStream(ctx, topodatapb.TabletType_PRIMARY, vgtid, filter, flags) 459 _, _ = conn, mconn 460 if err != nil { 461 t.Fatal(err) 462 } 463 numExpectedEvents := 4 464 require.NotNil(t, reader) 465 var evs []*binlogdatapb.VEvent 466 467 type expectedEvent struct { 468 ev string 469 received bool 470 } 471 expectedEvents := []*expectedEvent{ 472 {`type:FIELD field_event:{table_name:"ks.t1_sharded" fields:{name:"id1" type:INT64 table:"t1_sharded" org_table:"t1_sharded" database:"vt_ks_-80" org_name:"id1" column_length:20 charset:63 flags:53251} fields:{name:"id2" type:INT64 table:"t1_sharded" org_table:"t1_sharded" database:"vt_ks_-80" org_name:"id2" column_length:20 charset:63 flags:32768} keyspace:"ks" shard:"-80"}`, false}, 473 {`type:ROW row_event:{table_name:"ks.t1_sharded" row_changes:{after:{lengths:1 lengths:1 values:"11"}} keyspace:"ks" shard:"-80"}`, false}, 474 {`type:FIELD field_event:{table_name:"ks.t1_sharded" fields:{name:"id1" type:INT64 table:"t1_sharded" org_table:"t1_sharded" database:"vt_ks_80-" org_name:"id1" column_length:20 charset:63 flags:53251} fields:{name:"id2" type:INT64 table:"t1_sharded" org_table:"t1_sharded" database:"vt_ks_80-" org_name:"id2" column_length:20 charset:63 flags:32768} keyspace:"ks" shard:"80-"}`, false}, 475 {`type:ROW row_event:{table_name:"ks.t1_sharded" row_changes:{after:{lengths:1 lengths:1 values:"44"}} keyspace:"ks" shard:"80-"}`, false}, 476 } 477 for { 478 events, err := reader.Recv() 479 switch err { 480 case nil: 481 for _, event := range events { 482 // check for Keyspace/Shard values first and then reset them so that we don't have to add them to each expected event proto string 483 require.Equal(t, "ks", event.Keyspace, "event has an incorrect keyspace attribute: %s", event.Keyspace) 484 require.True(t, event.Shard == "-80" || event.Shard == "80-", "event has an incorrect shard attribute: %s", event.Shard) 485 event.Keyspace = "" 486 event.Shard = "" 487 switch event.Type { 488 case binlogdatapb.VEventType_ROW, binlogdatapb.VEventType_FIELD: 489 evs = append(evs, event) 490 default: 491 } 492 } 493 printEvents(evs) // for debugging ci failures 494 if len(evs) == numExpectedEvents { 495 // events from the two shards -80 and 80- can come in any order, hence this logic 496 for _, ev := range evs { 497 s := fmt.Sprintf("%v", ev) 498 for _, expectedEv := range expectedEvents { 499 if expectedEv.ev == s { 500 expectedEv.received = true 501 break 502 } 503 } 504 } 505 for _, expectedEv := range expectedEvents { 506 require.Truef(t, expectedEv.received, "event %s not received", expectedEv.ev) 507 } 508 509 t.Logf("TestVStreamCurrent was successful") 510 return 511 } 512 case io.EOF: 513 log.Infof("stream ended\n") 514 cancel() 515 default: 516 log.Errorf("Returned err %v", err) 517 t.Fatalf("remote error: %v\n", err) 518 } 519 } 520 521 } 522 523 var printMu sync.Mutex 524 525 func printEvents(evs []*binlogdatapb.VEvent) { 526 printMu.Lock() 527 defer printMu.Unlock() 528 if len(evs) == 0 { 529 return 530 } 531 s := "\n===START===" + "\n" 532 for i, ev := range evs { 533 s += fmt.Sprintf("Event %d; %v\n", i, ev) 534 } 535 s += "===END===" + "\n" 536 log.Infof("%s", s) 537 } 538 539 // Sort the VEvents by the first row change's after value bytes primarily, with 540 // secondary ordering by timestamp (ASC). Note that row copy events do not have 541 // a timestamp and the value will be 0. 542 type VEventSorter []*binlogdatapb.VEvent 543 544 func (v VEventSorter) Len() int { 545 return len(v) 546 } 547 func (v VEventSorter) Swap(i, j int) { 548 v[i], v[j] = v[j], v[i] 549 } 550 func (v VEventSorter) Less(i, j int) bool { 551 valsI := v[i].GetRowEvent().RowChanges[0].After 552 if valsI == nil { 553 valsI = v[i].GetRowEvent().RowChanges[0].Before 554 } 555 valsJ := v[j].GetRowEvent().RowChanges[0].After 556 if valsJ == nil { 557 valsJ = v[j].GetRowEvent().RowChanges[0].Before 558 } 559 valI := string(valsI.Values) 560 valJ := string(valsJ.Values) 561 if valI == valJ { 562 return v[i].Timestamp < v[j].Timestamp 563 } 564 return valI < valJ 565 }