vitess.io/vitess@v0.16.2/go/vt/wrangler/materializer_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  	"os"
    23  	"regexp"
    24  	"strconv"
    25  	"strings"
    26  	"sync"
    27  	"testing"
    28  
    29  	"vitess.io/vitess/go/sqltypes"
    30  	"vitess.io/vitess/go/vt/logutil"
    31  	"vitess.io/vitess/go/vt/mysqlctl/tmutils"
    32  	"vitess.io/vitess/go/vt/sqlparser"
    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  	_flag "vitess.io/vitess/go/internal/flag"
    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  	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
    42  )
    43  
    44  type testMaterializerEnv struct {
    45  	wr       *Wrangler
    46  	ms       *vtctldatapb.MaterializeSettings
    47  	sources  []string
    48  	targets  []string
    49  	tablets  map[int]*topodatapb.Tablet
    50  	topoServ *topo.Server
    51  	cell     string
    52  	tmc      *testMaterializerTMClient
    53  }
    54  
    55  //----------------------------------------------
    56  // testMaterializerEnv
    57  
    58  func TestMain(m *testing.M) {
    59  	_flag.ParseFlagsForTest()
    60  	os.Exit(m.Run())
    61  }
    62  
    63  func newTestMaterializerEnv(t *testing.T, ms *vtctldatapb.MaterializeSettings, sources, targets []string) *testMaterializerEnv {
    64  	t.Helper()
    65  	env := &testMaterializerEnv{
    66  		ms:       ms,
    67  		sources:  sources,
    68  		targets:  targets,
    69  		tablets:  make(map[int]*topodatapb.Tablet),
    70  		topoServ: memorytopo.NewServer("cell"),
    71  		cell:     "cell",
    72  		tmc:      newTestMaterializerTMClient(),
    73  	}
    74  	env.wr = New(logutil.NewConsoleLogger(), env.topoServ, env.tmc)
    75  	tabletID := 100
    76  	for _, shard := range sources {
    77  		_ = env.addTablet(tabletID, env.ms.SourceKeyspace, shard, topodatapb.TabletType_PRIMARY)
    78  		tabletID += 10
    79  	}
    80  	if ms.SourceKeyspace != ms.TargetKeyspace {
    81  		tabletID = 200
    82  		for _, shard := range targets {
    83  			_ = env.addTablet(tabletID, env.ms.TargetKeyspace, shard, topodatapb.TabletType_PRIMARY)
    84  			tabletID += 10
    85  		}
    86  	}
    87  
    88  	for _, ts := range ms.TableSettings {
    89  		tableName := ts.TargetTable
    90  		table, err := sqlparser.TableFromStatement(ts.SourceExpression)
    91  		if err == nil {
    92  			tableName = table.Name.String()
    93  		}
    94  		env.tmc.schema[ms.SourceKeyspace+"."+tableName] = &tabletmanagerdatapb.SchemaDefinition{
    95  			TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{
    96  				Name:   tableName,
    97  				Schema: fmt.Sprintf("%s_schema", tableName),
    98  			}},
    99  		}
   100  		env.tmc.schema[ms.TargetKeyspace+"."+ts.TargetTable] = &tabletmanagerdatapb.SchemaDefinition{
   101  			TableDefinitions: []*tabletmanagerdatapb.TableDefinition{{
   102  				Name:   ts.TargetTable,
   103  				Schema: fmt.Sprintf("%s_schema", ts.TargetTable),
   104  			}},
   105  		}
   106  	}
   107  	if ms.Workflow != "" {
   108  		env.expectValidation()
   109  	}
   110  	return env
   111  }
   112  
   113  func (env *testMaterializerEnv) expectValidation() {
   114  	for _, tablet := range env.tablets {
   115  		tabletID := int(tablet.Alias.Uid)
   116  		if tabletID < 200 {
   117  			continue
   118  		}
   119  		// wr.validateNewWorkflow
   120  		env.tmc.expectVRQuery(tabletID, fmt.Sprintf("select 1 from _vt.vreplication where db_name='vt_%s' and workflow='%s'", env.ms.TargetKeyspace, env.ms.Workflow), &sqltypes.Result{})
   121  	}
   122  }
   123  
   124  func (env *testMaterializerEnv) close() {
   125  	for _, t := range env.tablets {
   126  		env.deleteTablet(t)
   127  	}
   128  }
   129  
   130  func (env *testMaterializerEnv) addTablet(id int, keyspace, shard string, tabletType topodatapb.TabletType) *topodatapb.Tablet {
   131  	tablet := &topodatapb.Tablet{
   132  		Alias: &topodatapb.TabletAlias{
   133  			Cell: env.cell,
   134  			Uid:  uint32(id),
   135  		},
   136  		Keyspace: keyspace,
   137  		Shard:    shard,
   138  		KeyRange: &topodatapb.KeyRange{},
   139  		Type:     tabletType,
   140  		PortMap: map[string]int32{
   141  			"test": int32(id),
   142  		},
   143  	}
   144  	env.tablets[id] = tablet
   145  	if err := env.wr.TopoServer().InitTablet(context.Background(), tablet, false /* allowPrimaryOverride */, true /* createShardAndKeyspace */, false /* allowUpdate */); err != nil {
   146  		panic(err)
   147  	}
   148  	if tabletType == topodatapb.TabletType_PRIMARY {
   149  		_, err := env.wr.ts.UpdateShardFields(context.Background(), keyspace, shard, func(si *topo.ShardInfo) error {
   150  			si.PrimaryAlias = tablet.Alias
   151  			return nil
   152  		})
   153  		if err != nil {
   154  			panic(err)
   155  		}
   156  	}
   157  	return tablet
   158  }
   159  
   160  func (env *testMaterializerEnv) deleteTablet(tablet *topodatapb.Tablet) {
   161  	env.topoServ.DeleteTablet(context.Background(), tablet.Alias)
   162  	delete(env.tablets, int(tablet.Alias.Uid))
   163  }
   164  
   165  //----------------------------------------------
   166  // testMaterializerTMClient
   167  
   168  type testMaterializerTMClient struct {
   169  	tmclient.TabletManagerClient
   170  	schema map[string]*tabletmanagerdatapb.SchemaDefinition
   171  
   172  	mu              sync.Mutex
   173  	vrQueries       map[int][]*queryResult
   174  	getSchemaCounts map[string]int
   175  	muSchemaCount   sync.Mutex
   176  }
   177  
   178  func newTestMaterializerTMClient() *testMaterializerTMClient {
   179  	return &testMaterializerTMClient{
   180  		schema:          make(map[string]*tabletmanagerdatapb.SchemaDefinition),
   181  		vrQueries:       make(map[int][]*queryResult),
   182  		getSchemaCounts: make(map[string]int),
   183  	}
   184  }
   185  
   186  func (tmc *testMaterializerTMClient) schemaRequested(uid uint32) {
   187  	tmc.muSchemaCount.Lock()
   188  	defer tmc.muSchemaCount.Unlock()
   189  	key := strconv.Itoa(int(uid))
   190  	n, ok := tmc.getSchemaCounts[key]
   191  	if !ok {
   192  		tmc.getSchemaCounts[key] = 1
   193  	} else {
   194  		tmc.getSchemaCounts[key] = n + 1
   195  	}
   196  }
   197  
   198  func (tmc *testMaterializerTMClient) getSchemaRequestCount(uid uint32) int {
   199  	tmc.muSchemaCount.Lock()
   200  	defer tmc.muSchemaCount.Unlock()
   201  	key := strconv.Itoa(int(uid))
   202  	return tmc.getSchemaCounts[key]
   203  }
   204  
   205  func (tmc *testMaterializerTMClient) GetSchema(ctx context.Context, tablet *topodatapb.Tablet, request *tabletmanagerdatapb.GetSchemaRequest) (*tabletmanagerdatapb.SchemaDefinition, error) {
   206  	tmc.schemaRequested(tablet.Alias.Uid)
   207  	schemaDefn := &tabletmanagerdatapb.SchemaDefinition{}
   208  	for _, table := range request.Tables {
   209  		// TODO: Add generalized regexps if needed for test purposes.
   210  		if table == "/.*/" {
   211  			// Special case of all tables in keyspace.
   212  			for key, tableDefn := range tmc.schema {
   213  				if strings.HasPrefix(key, tablet.Keyspace+".") {
   214  					schemaDefn.TableDefinitions = append(schemaDefn.TableDefinitions, tableDefn.TableDefinitions...)
   215  				}
   216  			}
   217  			break
   218  		}
   219  
   220  		key := tablet.Keyspace + "." + table
   221  		tableDefn := tmc.schema[key]
   222  		if tableDefn == nil {
   223  			continue
   224  		}
   225  		schemaDefn.TableDefinitions = append(schemaDefn.TableDefinitions, tableDefn.TableDefinitions...)
   226  	}
   227  	return schemaDefn, nil
   228  }
   229  
   230  func (tmc *testMaterializerTMClient) expectVRQuery(tabletID int, query string, result *sqltypes.Result) {
   231  	tmc.mu.Lock()
   232  	defer tmc.mu.Unlock()
   233  
   234  	tmc.vrQueries[tabletID] = append(tmc.vrQueries[tabletID], &queryResult{
   235  		query:  query,
   236  		result: sqltypes.ResultToProto3(result),
   237  	})
   238  }
   239  
   240  func (tmc *testMaterializerTMClient) VReplicationExec(ctx context.Context, tablet *topodatapb.Tablet, query string) (*querypb.QueryResult, error) {
   241  	tmc.mu.Lock()
   242  	defer tmc.mu.Unlock()
   243  
   244  	qrs := tmc.vrQueries[int(tablet.Alias.Uid)]
   245  	if len(qrs) == 0 {
   246  		return nil, fmt.Errorf("tablet %v does not expect any more queries: %s", tablet, query)
   247  	}
   248  	matched := false
   249  	if qrs[0].query[0] == '/' {
   250  		matched = regexp.MustCompile(qrs[0].query[1:]).MatchString(query)
   251  	} else {
   252  		matched = query == qrs[0].query
   253  	}
   254  	if !matched {
   255  		return nil, fmt.Errorf("tablet %v:\nunexpected query\n%s\nwant:\n%s", tablet, query, qrs[0].query)
   256  	}
   257  	tmc.vrQueries[int(tablet.Alias.Uid)] = qrs[1:]
   258  	return qrs[0].result, nil
   259  }
   260  
   261  func (tmc *testMaterializerTMClient) ExecuteFetchAsDba(ctx context.Context, tablet *topodatapb.Tablet, usePool bool, req *tabletmanagerdatapb.ExecuteFetchAsDbaRequest) (*querypb.QueryResult, error) {
   262  	// Reuse VReplicationExec
   263  	return tmc.VReplicationExec(ctx, tablet, string(req.Query))
   264  }
   265  
   266  func (tmc *testMaterializerTMClient) verifyQueries(t *testing.T) {
   267  	t.Helper()
   268  
   269  	tmc.mu.Lock()
   270  	defer tmc.mu.Unlock()
   271  
   272  	for tabletID, qrs := range tmc.vrQueries {
   273  		if len(qrs) != 0 {
   274  			var list []string
   275  			for _, qr := range qrs {
   276  				list = append(list, qr.query)
   277  			}
   278  			t.Errorf("tablet %v: found queries that were expected but never got executed by the test: %v", tabletID, list)
   279  		}
   280  	}
   281  }
   282  
   283  // Note: ONLY breaks up change.SQL into individual statements and executes it. Does NOT fully implement ApplySchema.
   284  func (tmc *testMaterializerTMClient) ApplySchema(ctx context.Context, tablet *topodatapb.Tablet, change *tmutils.SchemaChange) (*tabletmanagerdatapb.SchemaChangeResult, error) {
   285  	stmts := strings.Split(change.SQL, ";")
   286  
   287  	for _, stmt := range stmts {
   288  		_, err := tmc.ExecuteFetchAsDba(ctx, tablet, false, &tabletmanagerdatapb.ExecuteFetchAsDbaRequest{
   289  			Query:        []byte(stmt),
   290  			MaxRows:      0,
   291  			ReloadSchema: true,
   292  		})
   293  		if err != nil {
   294  			return nil, err
   295  		}
   296  	}
   297  
   298  	return nil, nil
   299  }