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  }