vitess.io/vitess@v0.16.2/go/vt/wrangler/vdiff_env_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 wrangler 18 19 import ( 20 "context" 21 "fmt" 22 "math/rand" 23 "sync" 24 "testing" 25 26 "vitess.io/vitess/go/sqltypes" 27 "vitess.io/vitess/go/vt/grpcclient" 28 "vitess.io/vitess/go/vt/logutil" 29 "vitess.io/vitess/go/vt/topo" 30 "vitess.io/vitess/go/vt/topo/memorytopo" 31 "vitess.io/vitess/go/vt/vttablet/queryservice" 32 "vitess.io/vitess/go/vt/vttablet/queryservice/fakes" 33 "vitess.io/vitess/go/vt/vttablet/tabletconn" 34 "vitess.io/vitess/go/vt/vttablet/tabletconntest" 35 "vitess.io/vitess/go/vt/vttablet/tmclient" 36 37 binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata" 38 querypb "vitess.io/vitess/go/vt/proto/query" 39 tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" 40 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 41 ) 42 43 const ( 44 // vdiffStopPosition is the default stop position for the target vreplication. 45 // It can be overridden with the positons argument to newTestVDiffEnv. 46 vdiffStopPosition = "MariaDB/5-456-892" 47 // vdiffSourceGtid should be the position reported by the source side VStreamResults. 48 // It's expected to be higher the vdiffStopPosition. 49 vdiffSourceGtid = "MariaDB/5-456-893" 50 // vdiffTargetPrimaryPosition is the primary position of the target after 51 // vreplication has been synchronized. 52 vdiffTargetPrimaryPosition = "MariaDB/6-456-892" 53 ) 54 55 type testVDiffEnv struct { 56 wr *Wrangler 57 workflow string 58 topoServ *topo.Server 59 cell string 60 tabletType topodatapb.TabletType 61 tmc *testVDiffTMClient 62 63 mu sync.Mutex 64 tablets map[int]*testVDiffTablet 65 } 66 67 //---------------------------------------------- 68 // testVDiffEnv 69 70 func newTestVDiffEnv(t testing.TB, sourceShards, targetShards []string, query string, positions map[string]string) *testVDiffEnv { 71 env := &testVDiffEnv{ 72 workflow: "vdiffTest", 73 tablets: make(map[int]*testVDiffTablet), 74 topoServ: memorytopo.NewServer("cell"), 75 cell: "cell", 76 tabletType: topodatapb.TabletType_REPLICA, 77 tmc: newTestVDiffTMClient(), 78 } 79 env.wr = New(logutil.NewConsoleLogger(), env.topoServ, env.tmc) 80 81 // Generate a unique dialer name. 82 dialerName := fmt.Sprintf("VDiffTest-%s-%d", t.Name(), rand.Intn(1000000000)) 83 tabletconn.RegisterDialer(dialerName, func(tablet *topodatapb.Tablet, failFast grpcclient.FailFast) (queryservice.QueryService, error) { 84 env.mu.Lock() 85 defer env.mu.Unlock() 86 if qs, ok := env.tablets[int(tablet.Alias.Uid)]; ok { 87 return qs, nil 88 } 89 return nil, fmt.Errorf("tablet %d not found", tablet.Alias.Uid) 90 }) 91 tabletconntest.SetProtocol("go.vt.wrangler.vdiff_env_test", dialerName) 92 93 tabletID := 100 94 for _, shard := range sourceShards { 95 _ = env.addTablet(tabletID, "source", shard, topodatapb.TabletType_PRIMARY) 96 _ = env.addTablet(tabletID+1, "source", shard, topodatapb.TabletType_REPLICA) 97 env.tmc.waitpos[tabletID+1] = vdiffStopPosition 98 99 tabletID += 10 100 } 101 tabletID = 200 102 for _, shard := range targetShards { 103 primary := env.addTablet(tabletID, "target", shard, topodatapb.TabletType_PRIMARY) 104 _ = env.addTablet(tabletID+1, "target", shard, topodatapb.TabletType_REPLICA) 105 106 var rows []string 107 var posRows []string 108 for j, sourceShard := range sourceShards { 109 bls := &binlogdatapb.BinlogSource{ 110 Keyspace: "source", 111 Shard: sourceShard, 112 Filter: &binlogdatapb.Filter{ 113 Rules: []*binlogdatapb.Rule{{ 114 Match: "t1", 115 Filter: query, 116 }}, 117 }, 118 } 119 rows = append(rows, fmt.Sprintf("%d|%v|||", j+1, bls)) 120 position := vdiffStopPosition 121 if pos := positions[sourceShard+shard]; pos != "" { 122 position = pos 123 } 124 posRows = append(posRows, fmt.Sprintf("%v|%s", bls, position)) 125 126 // vdiff.syncTargets. This actually happens after stopTargets. 127 // But this is one statement per stream. 128 env.tmc.setVRResults( 129 primary.tablet, 130 fmt.Sprintf("update _vt.vreplication set state='Running', stop_pos='%s', message='synchronizing for vdiff' where id=%d", vdiffSourceGtid, j+1), 131 &sqltypes.Result{}, 132 ) 133 } 134 // migrater buildMigrationTargets 135 env.tmc.setVRResults( 136 primary.tablet, 137 "select id, source, message, cell, tablet_types, workflow_type, workflow_sub_type, defer_secondary_keys from _vt.vreplication where workflow='vdiffTest' and db_name='vt_target'", 138 sqltypes.MakeTestResult(sqltypes.MakeTestFields( 139 "id|source|message|cell|tablet_types|workflow_type|workflow_sub_type|defer_secondary_keys", 140 "int64|varchar|varchar|varchar|varchar|int64|int64|int64"), 141 rows..., 142 ), 143 ) 144 145 // vdiff.stopTargets 146 env.tmc.setVRResults(primary.tablet, "update _vt.vreplication set state='Stopped', message='for vdiff' where db_name='vt_target' and workflow='vdiffTest'", &sqltypes.Result{}) 147 env.tmc.setVRResults( 148 primary.tablet, 149 "select source, pos from _vt.vreplication where db_name='vt_target' and workflow='vdiffTest'", 150 sqltypes.MakeTestResult(sqltypes.MakeTestFields( 151 "source|pos", 152 "varchar|varchar"), 153 posRows..., 154 ), 155 ) 156 157 // vdiff.syncTargets (continued) 158 env.tmc.vrpos[tabletID] = vdiffSourceGtid 159 env.tmc.pos[tabletID] = vdiffTargetPrimaryPosition 160 161 // vdiff.startQueryStreams 162 env.tmc.waitpos[tabletID+1] = vdiffTargetPrimaryPosition 163 164 // vdiff.restartTargets 165 env.tmc.setVRResults(primary.tablet, "update _vt.vreplication set state='Running', message='', stop_pos='' where db_name='vt_target' and workflow='vdiffTest'", &sqltypes.Result{}) 166 167 tabletID += 10 168 } 169 return env 170 } 171 172 func (env *testVDiffEnv) close() { 173 env.mu.Lock() 174 defer env.mu.Unlock() 175 for _, t := range env.tablets { 176 env.topoServ.DeleteTablet(context.Background(), t.tablet.Alias) 177 } 178 env.tablets = nil 179 env.topoServ.Close() 180 } 181 182 func (env *testVDiffEnv) addTablet(id int, keyspace, shard string, tabletType topodatapb.TabletType) *testVDiffTablet { 183 env.mu.Lock() 184 defer env.mu.Unlock() 185 tablet := &topodatapb.Tablet{ 186 Alias: &topodatapb.TabletAlias{ 187 Cell: env.cell, 188 Uid: uint32(id), 189 }, 190 Keyspace: keyspace, 191 Shard: shard, 192 KeyRange: &topodatapb.KeyRange{}, 193 Type: tabletType, 194 PortMap: map[string]int32{ 195 "test": int32(id), 196 }, 197 } 198 env.tablets[id] = newTestVDiffTablet(tablet) 199 if err := env.wr.TopoServer().InitTablet(context.Background(), tablet, false /* allowPrimaryOverride */, true /* createShardAndKeyspace */, false /* allowUpdate */); err != nil { 200 panic(err) 201 } 202 if tabletType == topodatapb.TabletType_PRIMARY { 203 _, err := env.wr.ts.UpdateShardFields(context.Background(), keyspace, shard, func(si *topo.ShardInfo) error { 204 si.PrimaryAlias = tablet.Alias 205 return nil 206 }) 207 if err != nil { 208 panic(err) 209 } 210 } 211 return env.tablets[id] 212 } 213 214 //---------------------------------------------- 215 // testVDiffTablet 216 217 type testVDiffTablet struct { 218 queryservice.QueryService 219 tablet *topodatapb.Tablet 220 queries map[string][]*binlogdatapb.VStreamResultsResponse 221 } 222 223 func newTestVDiffTablet(tablet *topodatapb.Tablet) *testVDiffTablet { 224 return &testVDiffTablet{ 225 QueryService: fakes.ErrorQueryService, 226 tablet: tablet, 227 queries: make(map[string][]*binlogdatapb.VStreamResultsResponse), 228 } 229 } 230 231 func (tvt *testVDiffTablet) StreamHealth(ctx context.Context, callback func(*querypb.StreamHealthResponse) error) error { 232 return callback(&querypb.StreamHealthResponse{ 233 Serving: true, 234 Target: &querypb.Target{ 235 Keyspace: tvt.tablet.Keyspace, 236 Shard: tvt.tablet.Shard, 237 TabletType: tvt.tablet.Type, 238 }, 239 RealtimeStats: &querypb.RealtimeStats{}, 240 }) 241 } 242 243 func (tvt *testVDiffTablet) VStreamResults(ctx context.Context, target *querypb.Target, query string, send func(*binlogdatapb.VStreamResultsResponse) error) error { 244 results, ok := tvt.queries[query] 245 if !ok { 246 return fmt.Errorf("query %q not in list", query) 247 } 248 for _, result := range results { 249 if err := send(result); err != nil { 250 return err 251 } 252 } 253 return nil 254 } 255 256 func (tvt *testVDiffTablet) setResults(query string, gtid string, results []*sqltypes.Result) { 257 vrs := []*binlogdatapb.VStreamResultsResponse{{ 258 Fields: results[0].Fields, 259 Gtid: gtid, 260 }} 261 262 for _, result := range results[1:] { 263 vr := &binlogdatapb.VStreamResultsResponse{ 264 Rows: sqltypes.RowsToProto3(result.Rows), 265 } 266 vrs = append(vrs, vr) 267 } 268 tvt.queries[query] = vrs 269 } 270 271 //---------------------------------------------- 272 // testVDiffTMCclient 273 274 type testVDiffTMClient struct { 275 tmclient.TabletManagerClient 276 schema *tabletmanagerdatapb.SchemaDefinition 277 vrQueries map[int]map[string]*querypb.QueryResult 278 waitpos map[int]string 279 vrpos map[int]string 280 pos map[int]string 281 } 282 283 func newTestVDiffTMClient() *testVDiffTMClient { 284 return &testVDiffTMClient{ 285 vrQueries: make(map[int]map[string]*querypb.QueryResult), 286 waitpos: make(map[int]string), 287 vrpos: make(map[int]string), 288 pos: make(map[int]string), 289 } 290 } 291 292 func (tmc *testVDiffTMClient) GetSchema(ctx context.Context, tablet *topodatapb.Tablet, request *tabletmanagerdatapb.GetSchemaRequest) (*tabletmanagerdatapb.SchemaDefinition, error) { 293 return tmc.schema, nil 294 } 295 296 func (tmc *testVDiffTMClient) setVRResults(tablet *topodatapb.Tablet, query string, result *sqltypes.Result) { 297 queries, ok := tmc.vrQueries[int(tablet.Alias.Uid)] 298 if !ok { 299 queries = make(map[string]*querypb.QueryResult) 300 tmc.vrQueries[int(tablet.Alias.Uid)] = queries 301 } 302 queries[query] = sqltypes.ResultToProto3(result) 303 } 304 305 func (tmc *testVDiffTMClient) VReplicationExec(ctx context.Context, tablet *topodatapb.Tablet, query string) (*querypb.QueryResult, error) { 306 result, ok := tmc.vrQueries[int(tablet.Alias.Uid)][query] 307 if !ok { 308 return nil, fmt.Errorf("query %q not found for tablet %d", query, tablet.Alias.Uid) 309 } 310 return result, nil 311 } 312 313 func (tmc *testVDiffTMClient) WaitForPosition(ctx context.Context, tablet *topodatapb.Tablet, pos string) error { 314 select { 315 case <-ctx.Done(): 316 return ctx.Err() 317 default: 318 } 319 if pos != tmc.waitpos[int(tablet.Alias.Uid)] { 320 return fmt.Errorf("waitpos %s not reached for tablet %d", pos, tablet.Alias.Uid) 321 } 322 return nil 323 } 324 325 func (tmc *testVDiffTMClient) VReplicationWaitForPos(ctx context.Context, tablet *topodatapb.Tablet, id int, pos string) error { 326 select { 327 case <-ctx.Done(): 328 return ctx.Err() 329 default: 330 } 331 if pos != tmc.vrpos[int(tablet.Alias.Uid)] { 332 return fmt.Errorf("vrpos %s not reached for tablet %d", pos, tablet.Alias.Uid) 333 } 334 return nil 335 } 336 337 func (tmc *testVDiffTMClient) PrimaryPosition(ctx context.Context, tablet *topodatapb.Tablet) (string, error) { 338 pos, ok := tmc.pos[int(tablet.Alias.Uid)] 339 if !ok { 340 return "", fmt.Errorf("no primary position for %d", tablet.Alias.Uid) 341 } 342 return pos, nil 343 }