vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/vstreamer/testenv/testenv.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 testenv supplies test functions for testing vstreamer.
    18  package testenv
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"os"
    24  	"regexp"
    25  	"strconv"
    26  	"strings"
    27  
    28  	"vitess.io/vitess/go/json2"
    29  	"vitess.io/vitess/go/vt/dbconfigs"
    30  	"vitess.io/vitess/go/vt/mysqlctl"
    31  	"vitess.io/vitess/go/vt/srvtopo"
    32  	"vitess.io/vitess/go/vt/topo"
    33  	"vitess.io/vitess/go/vt/topo/memorytopo"
    34  	"vitess.io/vitess/go/vt/vttablet/tabletserver/schema"
    35  	"vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv"
    36  	"vitess.io/vitess/go/vt/vttest"
    37  
    38  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    39  	vschemapb "vitess.io/vitess/go/vt/proto/vschema"
    40  	vttestpb "vitess.io/vitess/go/vt/proto/vttest"
    41  )
    42  
    43  // Env contains all the env vars for a test against a mysql instance.
    44  type Env struct {
    45  	cluster *vttest.LocalCluster
    46  
    47  	KeyspaceName string
    48  	ShardName    string
    49  	Cells        []string
    50  
    51  	TabletEnv    tabletenv.Env
    52  	TopoServ     *topo.Server
    53  	SrvTopo      srvtopo.Server
    54  	Dbcfgs       *dbconfigs.DBConfigs
    55  	Mysqld       *mysqlctl.Mysqld
    56  	SchemaEngine *schema.Engine
    57  	Flavor       string
    58  	// MySQL and Percona are considered equivalent here and both called mysql
    59  	DBType         string
    60  	DBMajorVersion int
    61  	DBMinorVersion int
    62  	DBPatchVersion int
    63  }
    64  
    65  // Init initializes an Env.
    66  func Init() (*Env, error) {
    67  	te := &Env{
    68  		KeyspaceName: "vttest",
    69  		ShardName:    "0",
    70  		Cells:        []string{"cell1"},
    71  	}
    72  
    73  	ctx := context.Background()
    74  	te.TopoServ = memorytopo.NewServer(te.Cells...)
    75  	if err := te.TopoServ.CreateKeyspace(ctx, te.KeyspaceName, &topodatapb.Keyspace{}); err != nil {
    76  		return nil, err
    77  	}
    78  	if err := te.TopoServ.CreateShard(ctx, te.KeyspaceName, te.ShardName); err != nil {
    79  		panic(err)
    80  	}
    81  	te.SrvTopo = srvtopo.NewResilientServer(te.TopoServ, "TestTopo")
    82  
    83  	cfg := vttest.Config{
    84  		Topology: &vttestpb.VTTestTopology{
    85  			Keyspaces: []*vttestpb.Keyspace{
    86  				{
    87  					Name: te.KeyspaceName,
    88  					Shards: []*vttestpb.Shard{
    89  						{
    90  							Name:           "0",
    91  							DbNameOverride: "vttest",
    92  						},
    93  					},
    94  				},
    95  			},
    96  		},
    97  		OnlyMySQL: true,
    98  		Charset:   "utf8mb4_general_ci",
    99  	}
   100  	te.cluster = &vttest.LocalCluster{
   101  		Config: cfg,
   102  	}
   103  	if err := te.cluster.Setup(); err != nil {
   104  		os.RemoveAll(te.cluster.Config.SchemaDir)
   105  		return nil, fmt.Errorf("could not launch mysql: %v", err)
   106  	}
   107  	te.Dbcfgs = dbconfigs.NewTestDBConfigs(te.cluster.MySQLConnParams(), te.cluster.MySQLAppDebugConnParams(), te.cluster.DbName())
   108  	config := tabletenv.NewDefaultConfig()
   109  	config.DB = te.Dbcfgs
   110  	te.TabletEnv = tabletenv.NewEnv(config, "VStreamerTest")
   111  	te.Mysqld = mysqlctl.NewMysqld(te.Dbcfgs)
   112  	pos, _ := te.Mysqld.PrimaryPosition()
   113  	te.Flavor = pos.GTIDSet.Flavor()
   114  	if strings.HasPrefix(strings.ToLower(te.Flavor), string(mysqlctl.FlavorMariaDB)) {
   115  		te.DBType = string(mysqlctl.FlavorMariaDB)
   116  	} else {
   117  		// MySQL and Percona are equivalent for the tests
   118  		te.DBType = string(mysqlctl.FlavorMySQL)
   119  	}
   120  	dbVersionStr := te.Mysqld.GetVersionString()
   121  	dbVersionStrParts := strings.Split(dbVersionStr, ".")
   122  	var err error
   123  	te.DBMajorVersion, err = strconv.Atoi(dbVersionStrParts[0])
   124  	if err != nil {
   125  		return nil, fmt.Errorf("could not parse database major version from '%s': %v", dbVersionStr, err)
   126  	}
   127  	te.DBMinorVersion, err = strconv.Atoi(dbVersionStrParts[1])
   128  	if err != nil {
   129  		return nil, fmt.Errorf("could not parse database minor version from '%s': %v", dbVersionStr, err)
   130  	}
   131  	te.DBPatchVersion, err = strconv.Atoi(dbVersionStrParts[2])
   132  	if err != nil {
   133  		return nil, fmt.Errorf("could not parse database patch version from '%s': %v", dbVersionStr, err)
   134  	}
   135  
   136  	te.SchemaEngine = schema.NewEngine(te.TabletEnv)
   137  	te.SchemaEngine.InitDBConfig(te.Dbcfgs.DbaWithDB())
   138  	if err := te.SchemaEngine.Open(); err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	// The first vschema should not be empty. Leads to Node not found error.
   143  	// TODO(sougou): need to fix the bug.
   144  	if err := te.SetVSchema(`{"sharded": true}`); err != nil {
   145  		te.Close()
   146  		return nil, err
   147  	}
   148  
   149  	return te, nil
   150  }
   151  
   152  // Close tears down TestEnv.
   153  func (te *Env) Close() {
   154  	te.SchemaEngine.Close()
   155  	te.Mysqld.Close()
   156  	te.cluster.TearDown()
   157  	os.RemoveAll(te.cluster.Config.SchemaDir)
   158  }
   159  
   160  // SetVSchema sets the vschema for the test keyspace.
   161  func (te *Env) SetVSchema(vs string) error {
   162  	ctx := context.Background()
   163  	var kspb vschemapb.Keyspace
   164  	if err := json2.Unmarshal([]byte(vs), &kspb); err != nil {
   165  		return err
   166  	}
   167  	if err := te.TopoServ.SaveVSchema(ctx, te.KeyspaceName, &kspb); err != nil {
   168  		return err
   169  	}
   170  	te.SchemaEngine.Reload(ctx)
   171  	return te.TopoServ.RebuildSrvVSchema(ctx, te.Cells)
   172  }
   173  
   174  // In MySQL 8.0 and later information_schema no longer contains the display width for integer types and
   175  // as of 8.0.19 for year types as this was an unnecessary headache because it can only serve to confuse
   176  // if the display width is less than the type width (8.0 no longer supports the 2 digit YEAR). So if the
   177  // test is running against MySQL 8.0 or later then you should use this function to replace e.g.
   178  // `int([0-9]*)` with `int` in the expected results string that we define in the test.
   179  func (te *Env) RemoveAnyDeprecatedDisplayWidths(orig string) string {
   180  	if te.DBType != string(mysqlctl.FlavorMySQL) || te.DBMajorVersion < 8 {
   181  		return orig
   182  	}
   183  	var adjusted string
   184  	baseIntType := "int"
   185  	intRE := regexp.MustCompile(`(?i)int\(([0-9]*)?\)`)
   186  	adjusted = intRE.ReplaceAllString(orig, baseIntType)
   187  	if (te.DBMajorVersion > 8 || te.DBMinorVersion > 0) || te.DBPatchVersion >= 19 {
   188  		baseYearType := "year"
   189  		yearRE := regexp.MustCompile(`(?i)year\(([0-9]*)?\)`)
   190  		adjusted = yearRE.ReplaceAllString(adjusted, baseYearType)
   191  	}
   192  	return adjusted
   193  }