vitess.io/vitess@v0.16.2/go/vt/wrangler/resharder_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  	"regexp"
    23  	"runtime/debug"
    24  	"strings"
    25  	"sync"
    26  	"testing"
    27  
    28  	"github.com/stretchr/testify/require"
    29  
    30  	"vitess.io/vitess/go/sqltypes"
    31  	"vitess.io/vitess/go/vt/key"
    32  	"vitess.io/vitess/go/vt/logutil"
    33  	"vitess.io/vitess/go/vt/topo"
    34  	"vitess.io/vitess/go/vt/topo/memorytopo"
    35  	"vitess.io/vitess/go/vt/vttablet/tmclient"
    36  
    37  	querypb "vitess.io/vitess/go/vt/proto/query"
    38  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    39  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    40  )
    41  
    42  type testResharderEnv struct {
    43  	wr       *Wrangler
    44  	keyspace string
    45  	workflow string
    46  	sources  []string
    47  	targets  []string
    48  	tablets  map[int]*topodatapb.Tablet
    49  	topoServ *topo.Server
    50  	cell     string
    51  	tmc      *testResharderTMClient
    52  }
    53  
    54  var (
    55  	testMode = "" // "debug"
    56  )
    57  
    58  //----------------------------------------------
    59  // testResharderEnv
    60  
    61  func getPartition(t *testing.T, shards []string) *topodatapb.SrvKeyspace_KeyspacePartition {
    62  	partition := &topodatapb.SrvKeyspace_KeyspacePartition{
    63  		ServedType:      topodatapb.TabletType_PRIMARY,
    64  		ShardReferences: []*topodatapb.ShardReference{},
    65  	}
    66  	for _, shard := range shards {
    67  		keyRange, err := key.ParseShardingSpec(shard)
    68  		require.NoError(t, err)
    69  		require.Equal(t, 1, len(keyRange))
    70  		partition.ShardReferences = append(partition.ShardReferences, &topodatapb.ShardReference{
    71  			Name:     shard,
    72  			KeyRange: keyRange[0],
    73  		})
    74  	}
    75  	return partition
    76  }
    77  func initTopo(t *testing.T, topo *topo.Server, keyspace string, sources, targets, cells []string) {
    78  	ctx := context.Background()
    79  	srvKeyspace := &topodatapb.SrvKeyspace{
    80  		Partitions: []*topodatapb.SrvKeyspace_KeyspacePartition{},
    81  	}
    82  	srvKeyspace.Partitions = append(srvKeyspace.Partitions, getPartition(t, sources))
    83  	srvKeyspace.Partitions = append(srvKeyspace.Partitions, getPartition(t, targets))
    84  	for _, cell := range cells {
    85  		topo.UpdateSrvKeyspace(ctx, cell, keyspace, srvKeyspace)
    86  	}
    87  	topo.ValidateSrvKeyspace(ctx, keyspace, strings.Join(cells, ","))
    88  }
    89  
    90  func newTestResharderEnv(t *testing.T, sources, targets []string) *testResharderEnv {
    91  	env := &testResharderEnv{
    92  		keyspace: "ks",
    93  		workflow: "resharderTest",
    94  		sources:  sources,
    95  		targets:  targets,
    96  		tablets:  make(map[int]*topodatapb.Tablet),
    97  		topoServ: memorytopo.NewServer("cell"),
    98  		cell:     "cell",
    99  		tmc:      newTestResharderTMClient(),
   100  	}
   101  	env.wr = New(logutil.NewConsoleLogger(), env.topoServ, env.tmc)
   102  	initTopo(t, env.topoServ, "ks", sources, targets, []string{"cell"})
   103  	tabletID := 100
   104  	for _, shard := range sources {
   105  		_ = env.addTablet(tabletID, env.keyspace, shard, topodatapb.TabletType_PRIMARY)
   106  		tabletID += 10
   107  	}
   108  	tabletID = 200
   109  	for _, shard := range targets {
   110  		_ = env.addTablet(tabletID, env.keyspace, shard, topodatapb.TabletType_PRIMARY)
   111  		tabletID += 10
   112  	}
   113  	return env
   114  }
   115  
   116  func (env *testResharderEnv) expectValidation() {
   117  	for _, tablet := range env.tablets {
   118  		tabletID := int(tablet.Alias.Uid)
   119  		// wr.validateNewWorkflow
   120  		env.tmc.expectVRQuery(tabletID, fmt.Sprintf("select 1 from _vt.vreplication where db_name='vt_%s' and workflow='%s'", env.keyspace, env.workflow), &sqltypes.Result{})
   121  		env.tmc.expectVRQuery(tabletID, rsSelectFrozenQuery, &sqltypes.Result{})
   122  
   123  		if tabletID >= 200 {
   124  			// validateTargets
   125  			env.tmc.expectVRQuery(tabletID, fmt.Sprintf("select 1 from _vt.vreplication where db_name='vt_%s'", env.keyspace), &sqltypes.Result{})
   126  		}
   127  	}
   128  }
   129  
   130  func (env *testResharderEnv) expectNoRefStream() {
   131  	for _, tablet := range env.tablets {
   132  		tabletID := int(tablet.Alias.Uid)
   133  		if tabletID < 200 {
   134  			// readRefStreams
   135  			env.tmc.expectVRQuery(tabletID, fmt.Sprintf("select workflow, source, cell, tablet_types from _vt.vreplication where db_name='vt_%s' and message != 'FROZEN'", env.keyspace), &sqltypes.Result{})
   136  		}
   137  	}
   138  }
   139  
   140  func (env *testResharderEnv) close() {
   141  	for _, t := range env.tablets {
   142  		env.deleteTablet(t)
   143  	}
   144  }
   145  
   146  func (env *testResharderEnv) addTablet(id int, keyspace, shard string, tabletType topodatapb.TabletType) *topodatapb.Tablet {
   147  	tablet := &topodatapb.Tablet{
   148  		Alias: &topodatapb.TabletAlias{
   149  			Cell: env.cell,
   150  			Uid:  uint32(id),
   151  		},
   152  		Keyspace: keyspace,
   153  		Shard:    shard,
   154  		KeyRange: &topodatapb.KeyRange{},
   155  		Type:     tabletType,
   156  		PortMap: map[string]int32{
   157  			"test": int32(id),
   158  		},
   159  	}
   160  	env.tablets[id] = tablet
   161  	if err := env.wr.TopoServer().InitTablet(context.Background(), tablet, false /* allowPrimaryOverride */, true /* createShardAndKeyspace */, false /* allowUpdate */); err != nil {
   162  		panic(err)
   163  	}
   164  	if tabletType == topodatapb.TabletType_PRIMARY {
   165  		_, err := env.wr.ts.UpdateShardFields(context.Background(), keyspace, shard, func(si *topo.ShardInfo) error {
   166  			si.PrimaryAlias = tablet.Alias
   167  			return nil
   168  		})
   169  		if err != nil {
   170  			panic(err)
   171  		}
   172  	}
   173  	return tablet
   174  }
   175  
   176  func (env *testResharderEnv) deleteTablet(tablet *topodatapb.Tablet) {
   177  	env.topoServ.DeleteTablet(context.Background(), tablet.Alias)
   178  	delete(env.tablets, int(tablet.Alias.Uid))
   179  }
   180  
   181  //----------------------------------------------
   182  // testResharderTMClient
   183  
   184  type testResharderTMClient struct {
   185  	tmclient.TabletManagerClient
   186  	schema *tabletmanagerdatapb.SchemaDefinition
   187  
   188  	mu        sync.Mutex
   189  	vrQueries map[int][]*queryResult
   190  }
   191  
   192  type queryResult struct {
   193  	query  string
   194  	result *querypb.QueryResult
   195  }
   196  
   197  func newTestResharderTMClient() *testResharderTMClient {
   198  	return &testResharderTMClient{
   199  		vrQueries: make(map[int][]*queryResult),
   200  	}
   201  }
   202  
   203  func (tmc *testResharderTMClient) GetSchema(ctx context.Context, tablet *topodatapb.Tablet, request *tabletmanagerdatapb.GetSchemaRequest) (*tabletmanagerdatapb.SchemaDefinition, error) {
   204  	return tmc.schema, nil
   205  }
   206  
   207  func (tmc *testResharderTMClient) expectVRQuery(tabletID int, query string, result *sqltypes.Result) {
   208  	tmc.mu.Lock()
   209  	defer tmc.mu.Unlock()
   210  
   211  	tmc.vrQueries[tabletID] = append(tmc.vrQueries[tabletID], &queryResult{
   212  		query:  query,
   213  		result: sqltypes.ResultToProto3(result),
   214  	})
   215  }
   216  
   217  func (tmc *testResharderTMClient) VReplicationExec(ctx context.Context, tablet *topodatapb.Tablet, query string) (*querypb.QueryResult, error) {
   218  	tmc.mu.Lock()
   219  	defer tmc.mu.Unlock()
   220  	if testMode == "debug" {
   221  		fmt.Printf("Got: %d:%s\n", tablet.Alias.Uid, query)
   222  	}
   223  	qrs := tmc.vrQueries[int(tablet.Alias.Uid)]
   224  	if len(qrs) == 0 {
   225  		if testMode == "debug" {
   226  			fmt.Printf("Want: %d:%s, Stack:\n%v\n", tablet.Alias.Uid, query, debug.Stack())
   227  		}
   228  		return nil, fmt.Errorf("tablet %v does not expect any more queries: %s", tablet, query)
   229  	}
   230  	matched := false
   231  	if qrs[0].query[0] == '/' {
   232  		matched = regexp.MustCompile(qrs[0].query[1:]).MatchString(query)
   233  	} else {
   234  		matched = query == qrs[0].query
   235  	}
   236  	if !matched {
   237  		return nil, fmt.Errorf("tablet %v: unexpected query %s, want: %s", tablet, query, qrs[0].query)
   238  	}
   239  	tmc.vrQueries[int(tablet.Alias.Uid)] = qrs[1:]
   240  	return qrs[0].result, nil
   241  }
   242  
   243  func (tmc *testResharderTMClient) ExecuteFetchAsDba(ctx context.Context, tablet *topodatapb.Tablet, usePool bool, req *tabletmanagerdatapb.ExecuteFetchAsDbaRequest) (*querypb.QueryResult, error) {
   244  	// Reuse VReplicationExec
   245  	return tmc.VReplicationExec(ctx, tablet, string(req.Query))
   246  }
   247  
   248  func (tmc *testResharderTMClient) verifyQueries(t *testing.T) {
   249  	t.Helper()
   250  
   251  	tmc.mu.Lock()
   252  	defer tmc.mu.Unlock()
   253  
   254  	for tabletID, qrs := range tmc.vrQueries {
   255  		if len(qrs) != 0 {
   256  			var list []string
   257  			for _, qr := range qrs {
   258  				list = append(list, qr.query)
   259  			}
   260  			t.Errorf("tablet %v: following queries were not run during the test: \n%v", tabletID, strings.Join(list, "\n"))
   261  		}
   262  	}
   263  }