vitess.io/vitess@v0.16.2/go/vt/wrangler/wrangler_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/log"
    29  	"vitess.io/vitess/go/vt/logutil"
    30  	"vitess.io/vitess/go/vt/topo"
    31  	"vitess.io/vitess/go/vt/topo/memorytopo"
    32  	"vitess.io/vitess/go/vt/vttablet/queryservice"
    33  	"vitess.io/vitess/go/vt/vttablet/queryservice/fakes"
    34  	"vitess.io/vitess/go/vt/vttablet/tabletconn"
    35  	"vitess.io/vitess/go/vt/vttablet/tabletconntest"
    36  	"vitess.io/vitess/go/vt/vttablet/tmclient"
    37  
    38  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    39  	querypb "vitess.io/vitess/go/vt/proto/query"
    40  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    41  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    42  )
    43  
    44  const (
    45  	testStopPosition          = "MariaDB/5-456-892"
    46  	testSourceGtid            = "MariaDB/5-456-893"
    47  	testTargetPrimaryPosition = "MariaDB/6-456-892"
    48  )
    49  
    50  type testWranglerEnv struct {
    51  	wr         *Wrangler
    52  	workflow   string
    53  	topoServ   *topo.Server
    54  	cell       string
    55  	tabletType topodatapb.TabletType
    56  	tmc        *testWranglerTMClient
    57  	mu         sync.Mutex
    58  }
    59  
    60  //----------------------------------------------
    61  // testWranglerEnv
    62  
    63  func newWranglerTestEnv(t testing.TB, sourceShards, targetShards []string, query string, positions map[string]string, timeUpdated int64) *testWranglerEnv {
    64  	env := &testWranglerEnv{
    65  		workflow:   "wrWorkflow",
    66  		topoServ:   memorytopo.NewServer("zone1"),
    67  		cell:       "zone1",
    68  		tabletType: topodatapb.TabletType_REPLICA,
    69  		tmc:        newTestWranglerTMClient(),
    70  	}
    71  	env.wr = New(logutil.NewConsoleLogger(), env.topoServ, env.tmc)
    72  	env.tmc.tablets = make(map[int]*testWranglerTablet)
    73  
    74  	// Generate a unique dialer name.
    75  	dialerName := fmt.Sprintf("WranglerTest-%s-%d", t.Name(), rand.Intn(1000000000))
    76  	tabletconn.RegisterDialer(dialerName, func(tablet *topodatapb.Tablet, failFast grpcclient.FailFast) (queryservice.QueryService, error) {
    77  		env.mu.Lock()
    78  		defer env.mu.Unlock()
    79  		if qs, ok := env.tmc.tablets[int(tablet.Alias.Uid)]; ok {
    80  			return qs, nil
    81  		}
    82  		// some tests don't require the query service. Earlier we were returning an error for such cases but the tablet picker
    83  		// now logs a warning and spams the logs. Hence we return a fake service instead
    84  		return newFakeTestWranglerTablet(), nil
    85  	})
    86  	tabletconntest.SetProtocol("go.vt.wrangler.wrangler_env_test", dialerName)
    87  
    88  	tabletID := 100
    89  	for _, shard := range sourceShards {
    90  		_ = env.addTablet(tabletID, "source", shard, topodatapb.TabletType_PRIMARY)
    91  		_ = env.addTablet(tabletID+1, "source", shard, topodatapb.TabletType_REPLICA)
    92  		env.tmc.waitpos[tabletID+1] = testStopPosition
    93  
    94  		tabletID += 10
    95  	}
    96  	tabletID = 200
    97  	for _, shard := range targetShards {
    98  		primary := env.addTablet(tabletID, "target", shard, topodatapb.TabletType_PRIMARY)
    99  		_ = env.addTablet(tabletID+1, "target", shard, topodatapb.TabletType_REPLICA)
   100  
   101  		var rows []string
   102  		var posRows []string
   103  		var bls *binlogdatapb.BinlogSource
   104  		for j, sourceShard := range sourceShards {
   105  			bls = &binlogdatapb.BinlogSource{
   106  				Keyspace: "source",
   107  				Shard:    sourceShard,
   108  				Filter: &binlogdatapb.Filter{
   109  					Rules: []*binlogdatapb.Rule{{
   110  						Match:  "t1",
   111  						Filter: query,
   112  					}},
   113  				},
   114  			}
   115  			rows = append(rows, fmt.Sprintf("%d|%v||||0|0|0", j+1, bls))
   116  			position := testStopPosition
   117  			if pos := positions[sourceShard+shard]; pos != "" {
   118  				position = pos
   119  			}
   120  			posRows = append(posRows, fmt.Sprintf("%v|%s", bls, position))
   121  
   122  			env.tmc.setVRResults(
   123  				primary.tablet,
   124  				fmt.Sprintf("update _vt.vreplication set state='Running', stop_pos='%s', message='synchronizing for wrangler test' where id=%d", testSourceGtid, j+1),
   125  				&sqltypes.Result{},
   126  			)
   127  		}
   128  		// migrater buildMigrationTargets
   129  		env.tmc.setVRResults(
   130  			primary.tablet,
   131  			"select id, source, message, cell, tablet_types, workflow_type, workflow_sub_type, defer_secondary_keys from _vt.vreplication where db_name = 'vt_target' and workflow = 'wrWorkflow'",
   132  			sqltypes.MakeTestResult(sqltypes.MakeTestFields(
   133  				"id|source|message|cell|tablet_types|workflow_type|workflow_sub_type|defer_secondary_keys",
   134  				"int64|varchar|varchar|varchar|varchar|int64|int64|int64"),
   135  				rows...,
   136  			),
   137  		)
   138  
   139  		env.tmc.setVRResults(primary.tablet, "update _vt.vreplication set state = 'Stopped', message = 'for wrangler test' where db_name = 'vt_target' and workflow = 'wrWorkflow'", &sqltypes.Result{RowsAffected: 1})
   140  		env.tmc.setVRResults(primary.tablet, "update _vt.vreplication set state = 'Stopped' where db_name = 'vt_target' and workflow = 'wrWorkflow'", &sqltypes.Result{RowsAffected: 1})
   141  		env.tmc.setVRResults(primary.tablet, "delete from _vt.vreplication where message != '' and db_name = 'vt_target' and workflow = 'wrWorkflow'", &sqltypes.Result{RowsAffected: 1})
   142  		env.tmc.setVRResults(primary.tablet, "insert into _vt.vreplication(state, workflow, db_name) values ('Running', 'wk1', 'ks1'), ('Stopped', 'wk1', 'ks1')", &sqltypes.Result{RowsAffected: 2})
   143  
   144  		result := sqltypes.MakeTestResult(sqltypes.MakeTestFields(
   145  			"id|source|pos|stop_pos|max_replication_lag|state|db_name|time_updated|transaction_timestamp|time_heartbeat|time_throttled|component_throttled|message|tags|workflow_type|workflow_sub_type",
   146  			"int64|varchar|varchar|varchar|int64|varchar|varchar|int64|int64|int64|int64|int64|varchar|varchar|varchar|int64|int64"),
   147  			fmt.Sprintf("1|%v|MySQL56/14b68925-696a-11ea-aee7-fec597a91f5e:1-3||0|Running|vt_target|%d|0|%d|0|||||", bls, timeUpdated, timeUpdated),
   148  		)
   149  		env.tmc.setVRResults(primary.tablet, "select id, source, pos, stop_pos, max_replication_lag, state, db_name, time_updated, transaction_timestamp, time_heartbeat, time_throttled, component_throttled, message, tags, workflow_type, workflow_sub_type, defer_secondary_keys from _vt.vreplication where db_name = 'vt_target' and workflow = 'wrWorkflow'", result)
   150  		env.tmc.setVRResults(
   151  			primary.tablet,
   152  			"select source, pos from _vt.vreplication where db_name='vt_target' and workflow='wrWorkflow'",
   153  			sqltypes.MakeTestResult(sqltypes.MakeTestFields(
   154  				"source|pos",
   155  				"varchar|varchar"),
   156  				posRows...,
   157  			),
   158  		)
   159  		result = sqltypes.MakeTestResult(sqltypes.MakeTestFields(
   160  			"workflow",
   161  			"varchar"),
   162  			"wrWorkflow",
   163  		)
   164  		env.tmc.setVRResults(primary.tablet, "select distinct workflow from _vt.vreplication where state != 'Stopped' and db_name = 'vt_target'", result)
   165  
   166  		result = sqltypes.MakeTestResult(sqltypes.MakeTestFields(
   167  			"table|lastpk",
   168  			"varchar|varchar"),
   169  			"t1|pk1",
   170  		)
   171  
   172  		env.tmc.setVRResults(primary.tablet, "select table_name, lastpk from _vt.copy_state where vrepl_id = 1 and id in (select max(id) from _vt.copy_state where vrepl_id = 1 group by vrepl_id, table_name)", result)
   173  
   174  		env.tmc.setVRResults(primary.tablet, "select id, source, pos, stop_pos, max_replication_lag, state, db_name, time_updated, transaction_timestamp, time_heartbeat, time_throttled, component_throttled, message, tags from _vt.vreplication where db_name = 'vt_target' and workflow = 'bad'", &sqltypes.Result{})
   175  
   176  		env.tmc.setVRResults(primary.tablet, "select id, source, pos, stop_pos, max_replication_lag, state, db_name, time_updated, transaction_timestamp, time_heartbeat, time_throttled, component_throttled, message, tags from _vt.vreplication where db_name = 'vt_target' and workflow = 'badwf'", &sqltypes.Result{})
   177  		env.tmc.vrpos[tabletID] = testSourceGtid
   178  		env.tmc.pos[tabletID] = testTargetPrimaryPosition
   179  
   180  		env.tmc.waitpos[tabletID+1] = testTargetPrimaryPosition
   181  
   182  		env.tmc.setVRResults(primary.tablet, "update _vt.vreplication set state='Running', message='', stop_pos='' where db_name='vt_target' and workflow='wrWorkflow'", &sqltypes.Result{})
   183  
   184  		result = sqltypes.MakeTestResult(sqltypes.MakeTestFields(
   185  			"workflow",
   186  			"varchar"),
   187  			"wrWorkflow", "wrWorkflow2",
   188  		)
   189  		env.tmc.setVRResults(primary.tablet, "select distinct workflow from _vt.vreplication where db_name = 'vt_target'", result)
   190  		tabletID += 10
   191  	}
   192  	primary := env.addTablet(300, "target2", "0", topodatapb.TabletType_PRIMARY)
   193  	result := sqltypes.MakeTestResult(sqltypes.MakeTestFields(
   194  		"workflow",
   195  		"varchar"),
   196  		"wrWorkflow", "wrWorkflow2",
   197  	)
   198  	env.tmc.setVRResults(primary.tablet, "select distinct workflow from _vt.vreplication where db_name = 'vt_target2'", result)
   199  	return env
   200  }
   201  
   202  func (env *testWranglerEnv) close() {
   203  	env.mu.Lock()
   204  	defer env.mu.Unlock()
   205  	for _, t := range env.tmc.tablets {
   206  		env.topoServ.DeleteTablet(context.Background(), t.tablet.Alias)
   207  	}
   208  	env.tmc.tablets = nil
   209  }
   210  
   211  func newFakeTestWranglerTablet() *testWranglerTablet {
   212  	id := 999
   213  	tablet := &topodatapb.Tablet{
   214  		Alias: &topodatapb.TabletAlias{
   215  			Cell: "fake",
   216  			Uid:  uint32(id),
   217  		},
   218  		Keyspace: "fake",
   219  		Shard:    "fake",
   220  		KeyRange: &topodatapb.KeyRange{},
   221  		Type:     topodatapb.TabletType_PRIMARY,
   222  		PortMap: map[string]int32{
   223  			"test": int32(id),
   224  		},
   225  	}
   226  	return newTestWranglerTablet(tablet)
   227  }
   228  
   229  func (env *testWranglerEnv) addTablet(id int, keyspace, shard string, tabletType topodatapb.TabletType) *testWranglerTablet {
   230  	env.mu.Lock()
   231  	defer env.mu.Unlock()
   232  	tablet := &topodatapb.Tablet{
   233  		Alias: &topodatapb.TabletAlias{
   234  			Cell: env.cell,
   235  			Uid:  uint32(id),
   236  		},
   237  		Keyspace: keyspace,
   238  		Shard:    shard,
   239  		KeyRange: &topodatapb.KeyRange{},
   240  		Type:     tabletType,
   241  		PortMap: map[string]int32{
   242  			"test": int32(id),
   243  		},
   244  	}
   245  	env.tmc.tablets[id] = newTestWranglerTablet(tablet)
   246  	if err := env.wr.TopoServer().InitTablet(context.Background(), tablet, false /* allowPrimaryOverride */, true /* createShardAndKeyspace */, false /* allowUpdate */); err != nil {
   247  		panic(err)
   248  	}
   249  	if tabletType == topodatapb.TabletType_PRIMARY {
   250  		_, err := env.wr.ts.UpdateShardFields(context.Background(), keyspace, shard, func(si *topo.ShardInfo) error {
   251  			si.PrimaryAlias = tablet.Alias
   252  			return nil
   253  		})
   254  		if err != nil {
   255  			panic(err)
   256  		}
   257  	}
   258  	env.tmc.tablets[id].queryResults = make(map[string]*querypb.QueryResult)
   259  	return env.tmc.tablets[id]
   260  }
   261  
   262  //----------------------------------------------
   263  // testWranglerTablet
   264  
   265  type testWranglerTablet struct {
   266  	queryservice.QueryService
   267  	tablet       *topodatapb.Tablet
   268  	queryResults map[string]*querypb.QueryResult
   269  	gotQueries   []string
   270  }
   271  
   272  func newTestWranglerTablet(tablet *topodatapb.Tablet) *testWranglerTablet {
   273  	return &testWranglerTablet{
   274  		QueryService: fakes.ErrorQueryService,
   275  		tablet:       tablet,
   276  	}
   277  }
   278  
   279  func (tvt *testWranglerTablet) StreamHealth(ctx context.Context, callback func(*querypb.StreamHealthResponse) error) error {
   280  	return callback(&querypb.StreamHealthResponse{
   281  		Serving: true,
   282  		Target: &querypb.Target{
   283  			Keyspace:   tvt.tablet.Keyspace,
   284  			Shard:      tvt.tablet.Shard,
   285  			TabletType: tvt.tablet.Type,
   286  		},
   287  		RealtimeStats: &querypb.RealtimeStats{},
   288  	})
   289  }
   290  
   291  //----------------------------------------------
   292  // testWranglerTMClient
   293  
   294  type testWranglerTMClient struct {
   295  	tmclient.TabletManagerClient
   296  	tablets   map[int]*testWranglerTablet
   297  	schema    *tabletmanagerdatapb.SchemaDefinition
   298  	vrQueries map[int]map[string]*querypb.QueryResult
   299  	waitpos   map[int]string
   300  	vrpos     map[int]string
   301  	pos       map[int]string
   302  }
   303  
   304  func newTestWranglerTMClient() *testWranglerTMClient {
   305  	return &testWranglerTMClient{
   306  		vrQueries: make(map[int]map[string]*querypb.QueryResult),
   307  		waitpos:   make(map[int]string),
   308  		vrpos:     make(map[int]string),
   309  		pos:       make(map[int]string),
   310  	}
   311  }
   312  
   313  func (tmc *testWranglerTMClient) GetSchema(ctx context.Context, tablet *topodatapb.Tablet, request *tabletmanagerdatapb.GetSchemaRequest) (*tabletmanagerdatapb.SchemaDefinition, error) {
   314  	return tmc.schema, nil
   315  }
   316  
   317  func (tmc *testWranglerTMClient) setVRResults(tablet *topodatapb.Tablet, query string, result *sqltypes.Result) {
   318  	queries, ok := tmc.vrQueries[int(tablet.Alias.Uid)]
   319  	if !ok {
   320  		queries = make(map[string]*querypb.QueryResult)
   321  		tmc.vrQueries[int(tablet.Alias.Uid)] = queries
   322  	}
   323  	queries[query] = sqltypes.ResultToProto3(result)
   324  }
   325  
   326  func (tmc *testWranglerTMClient) VReplicationExec(ctx context.Context, tablet *topodatapb.Tablet, query string) (*querypb.QueryResult, error) {
   327  	result, ok := tmc.vrQueries[int(tablet.Alias.Uid)][query]
   328  	if !ok {
   329  		return nil, fmt.Errorf("query %q not found for tablet %d", query, tablet.Alias.Uid)
   330  	}
   331  	return result, nil
   332  }
   333  
   334  func (tmc *testWranglerTMClient) ExecuteFetchAsApp(ctx context.Context, tablet *topodatapb.Tablet, usePool bool, req *tabletmanagerdatapb.ExecuteFetchAsAppRequest) (*querypb.QueryResult, error) {
   335  	t := tmc.tablets[int(tablet.Alias.Uid)]
   336  	t.gotQueries = append(t.gotQueries, string(req.Query))
   337  	result, ok := t.queryResults[string(req.Query)]
   338  	if !ok {
   339  		result = &querypb.QueryResult{}
   340  		log.Errorf("Query: %s, Result :%v\n", string(req.Query), result)
   341  	}
   342  	return result, nil
   343  }