vitess.io/vitess@v0.16.2/go/test/endtoend/vtcombo/vttest_sample_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 vtcombo
    18  
    19  import (
    20  	"context"
    21  	"database/sql"
    22  	"encoding/json"
    23  	"flag"
    24  	"fmt"
    25  	"io"
    26  	"net/http"
    27  	"os"
    28  	"os/exec"
    29  	"strconv"
    30  	"strings"
    31  	"testing"
    32  
    33  	"github.com/go-sql-driver/mysql"
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/require"
    36  
    37  	"vitess.io/vitess/go/vt/log"
    38  	"vitess.io/vitess/go/vt/vtgate/vtgateconn"
    39  	"vitess.io/vitess/go/vt/vttest"
    40  
    41  	querypb "vitess.io/vitess/go/vt/proto/query"
    42  	vttestpb "vitess.io/vitess/go/vt/proto/vttest"
    43  )
    44  
    45  var (
    46  	localCluster *vttest.LocalCluster
    47  	grpcAddress  string
    48  	vtctldAddr   string
    49  	mysqlAddress string
    50  	ks1          = "test_keyspace"
    51  	redirected   = "redirected"
    52  	jsonTopo     = `
    53  {
    54  	"keyspaces": [
    55  		{
    56  			"name": "test_keyspace",
    57  			"shards": [{"name": "-80"}, {"name": "80-"}],
    58  			"rdonlyCount": 1,
    59  			"replicaCount": 2
    60  		},
    61  		{
    62  			"name": "redirected",
    63  			"servedFrom": "test_keyspace"
    64  		},
    65  		{
    66  			"name": "routed",
    67  			"shards": [{"name": "0"}]
    68  		}
    69  	],
    70  	"routing_rules": {
    71  		"rules": [{
    72              "from_table": "routed.routed_table",
    73              "to_tables": [
    74                  "routed.test_table"
    75              ]
    76          }]
    77  	}
    78  }`
    79  )
    80  
    81  func TestMain(m *testing.M) {
    82  	flag.Parse()
    83  
    84  	exitcode, err := func() (int, error) {
    85  		var topology vttestpb.VTTestTopology
    86  
    87  		data := vttest.JSONTopoData(&topology)
    88  		err := data.Set(jsonTopo)
    89  		if err != nil {
    90  			return 1, err
    91  		}
    92  
    93  		var cfg vttest.Config
    94  		cfg.Topology = &topology
    95  		cfg.SchemaDir = os.Getenv("VTROOT") + "/test/vttest_schema"
    96  		cfg.DefaultSchemaDir = os.Getenv("VTROOT") + "/test/vttest_schema/default"
    97  		cfg.PersistentMode = true
    98  
    99  		localCluster = &vttest.LocalCluster{
   100  			Config: cfg,
   101  		}
   102  
   103  		err = localCluster.Setup()
   104  		defer localCluster.TearDown()
   105  		if err != nil {
   106  			return 1, err
   107  		}
   108  
   109  		grpcAddress = fmt.Sprintf("localhost:%d", localCluster.Env.PortForProtocol("vtcombo", "grpc"))
   110  		mysqlAddress = fmt.Sprintf("localhost:%d", localCluster.Env.PortForProtocol("vtcombo_mysql_port", ""))
   111  		vtctldAddr = fmt.Sprintf("localhost:%d", localCluster.Env.PortForProtocol("vtcombo", "port"))
   112  
   113  		return m.Run(), nil
   114  	}()
   115  	if err != nil {
   116  		log.Errorf("top level error: %v\n", err)
   117  		os.Exit(1)
   118  	} else {
   119  		os.Exit(exitcode)
   120  	}
   121  }
   122  
   123  func TestStandalone(t *testing.T) {
   124  	// validate debug vars
   125  	resp, err := http.Get(fmt.Sprintf("http://%s/debug/vars", vtctldAddr))
   126  	require.NoError(t, err)
   127  	defer resp.Body.Close()
   128  	require.Equal(t, 200, resp.StatusCode)
   129  	resultMap := make(map[string]any)
   130  	respByte, _ := io.ReadAll(resp.Body)
   131  	err = json.Unmarshal(respByte, &resultMap)
   132  	require.NoError(t, err)
   133  	cmd := resultMap["cmdline"]
   134  	require.NotNil(t, cmd, "cmdline is not available in debug vars")
   135  	tmp, _ := cmd.([]any)
   136  	require.Contains(t, tmp[0], "vtcombo")
   137  
   138  	ctx := context.Background()
   139  	conn, err := vtgateconn.Dial(ctx, grpcAddress)
   140  	require.NoError(t, err)
   141  	defer conn.Close()
   142  
   143  	cfg := mysql.NewConfig()
   144  	cfg.Net = "tcp"
   145  	cfg.Addr = mysqlAddress
   146  	cfg.DBName = "routed@primary"
   147  	db, err := sql.Open("mysql", cfg.FormatDSN())
   148  	require.NoError(t, err)
   149  	defer db.Close()
   150  
   151  	idStart, rowCount := 1000, 500
   152  	insertManyRows(ctx, t, conn, idStart, rowCount)
   153  	assertInsertedRowsExist(ctx, t, conn, idStart, rowCount)
   154  	assertRouting(ctx, t, db)
   155  	assertCanInsertRow(ctx, t, conn)
   156  	assertTabletsPresent(t)
   157  
   158  	err = localCluster.TearDown()
   159  	require.NoError(t, err)
   160  	err = localCluster.Setup()
   161  	require.NoError(t, err)
   162  
   163  	assertInsertedRowsExist(ctx, t, conn, idStart, rowCount)
   164  	assertTabletsPresent(t)
   165  	assertTransactionalityAndRollbackObeyed(ctx, t, conn, idStart)
   166  }
   167  
   168  func assertInsertedRowsExist(ctx context.Context, t *testing.T, conn *vtgateconn.VTGateConn, idStart, rowCount int) {
   169  	cur := conn.Session(ks1+":-80@rdonly", nil)
   170  	bindVariables := map[string]*querypb.BindVariable{
   171  		"id_start": {Type: querypb.Type_UINT64, Value: []byte(strconv.FormatInt(int64(idStart), 10))},
   172  	}
   173  	res, err := cur.Execute(ctx, "select * from test_table where id >= :id_start", bindVariables)
   174  	require.NoError(t, err)
   175  
   176  	assert.Equal(t, rowCount, len(res.Rows))
   177  
   178  	cur = conn.Session(redirected+":-80@replica", nil)
   179  	bindVariables = map[string]*querypb.BindVariable{
   180  		"id_start": {Type: querypb.Type_UINT64, Value: []byte(strconv.FormatInt(int64(idStart), 10))},
   181  	}
   182  	res, err = cur.Execute(ctx, "select * from test_table where id = :id_start", bindVariables)
   183  	require.NoError(t, err)
   184  	require.Equal(t, 1, len(res.Rows))
   185  	assert.Equal(t, "VARCHAR(\"test1000\")", res.Rows[0][1].String())
   186  }
   187  
   188  func assertRouting(ctx context.Context, t *testing.T, db *sql.DB) {
   189  	// insert into test table
   190  	_, err := db.ExecContext(ctx, `insert into test_table (id, msg, keyspace_id) values (?, ?, ?)`, 1, "message", 1)
   191  	require.NoError(t, err)
   192  
   193  	// read from routed table
   194  	row := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM routed_table")
   195  	require.NoError(t, row.Err())
   196  	var count uint64
   197  	require.NoError(t, row.Scan(&count))
   198  	require.NotZero(t, count)
   199  }
   200  
   201  func assertCanInsertRow(ctx context.Context, t *testing.T, conn *vtgateconn.VTGateConn) {
   202  	cur := conn.Session(ks1+":80-@primary", nil)
   203  	_, err := cur.Execute(ctx, "begin", nil)
   204  	require.NoError(t, err)
   205  
   206  	i := 0x810000000000000
   207  	bindVariables := map[string]*querypb.BindVariable{
   208  		"id":          {Type: querypb.Type_UINT64, Value: []byte(strconv.FormatInt(int64(i), 10))},
   209  		"msg":         {Type: querypb.Type_VARCHAR, Value: []byte("test" + strconv.FormatInt(int64(i), 10))},
   210  		"keyspace_id": {Type: querypb.Type_UINT64, Value: []byte(strconv.FormatInt(int64(i), 10))},
   211  	}
   212  	query := "insert into test_table (id, msg, keyspace_id) values (:id, :msg, :keyspace_id)"
   213  	_, err = cur.Execute(ctx, query, bindVariables)
   214  	require.NoError(t, err)
   215  
   216  	_, err = cur.Execute(ctx, "commit", nil)
   217  	require.NoError(t, err)
   218  }
   219  
   220  func insertManyRows(ctx context.Context, t *testing.T, conn *vtgateconn.VTGateConn, idStart, rowCount int) {
   221  	cur := conn.Session(ks1+":-80@primary", nil)
   222  
   223  	query := "insert into test_table (id, msg, keyspace_id) values (:id, :msg, :keyspace_id)"
   224  	_, err := cur.Execute(ctx, "begin", nil)
   225  	require.NoError(t, err)
   226  
   227  	for i := idStart; i < idStart+rowCount; i++ {
   228  		bindVariables := map[string]*querypb.BindVariable{
   229  			"id":          {Type: querypb.Type_UINT64, Value: []byte(strconv.FormatInt(int64(i), 10))},
   230  			"msg":         {Type: querypb.Type_VARCHAR, Value: []byte("test" + strconv.FormatInt(int64(i), 10))},
   231  			"keyspace_id": {Type: querypb.Type_UINT64, Value: []byte(strconv.FormatInt(int64(i), 10))},
   232  		}
   233  		_, err = cur.Execute(ctx, query, bindVariables)
   234  		require.NoError(t, err)
   235  	}
   236  
   237  	_, err = cur.Execute(ctx, "commit", nil)
   238  	require.NoError(t, err)
   239  }
   240  
   241  func assertTabletsPresent(t *testing.T) {
   242  	tmpCmd := exec.Command("vtctlclient", "--vtctl_client_protocol", "grpc", "--server", grpcAddress, "--stderrthreshold", "0", "ListAllTablets", "--", "test")
   243  
   244  	log.Infof("Running vtctlclient with command: %v", tmpCmd.Args)
   245  
   246  	output, err := tmpCmd.CombinedOutput()
   247  	require.NoError(t, err)
   248  
   249  	numPrimary, numReplica, numRdonly, numDash80, num80Dash, numRouted := 0, 0, 0, 0, 0, 0
   250  	lines := strings.Split(string(output), "\n")
   251  
   252  	for _, line := range lines {
   253  		if !strings.HasPrefix(line, "test-") {
   254  			continue
   255  		}
   256  		parts := strings.Split(line, " ")
   257  		if parts[1] == "routed" {
   258  			numRouted++
   259  			continue
   260  		}
   261  
   262  		assert.Equal(t, "test_keyspace", parts[1])
   263  
   264  		switch parts[3] {
   265  		case "primary":
   266  			numPrimary++
   267  		case "replica":
   268  			numReplica++
   269  		case "rdonly":
   270  			numRdonly++
   271  		default:
   272  			t.Logf("invalid tablet type %s", parts[3])
   273  		}
   274  
   275  		switch parts[2] {
   276  		case "-80":
   277  			numDash80++
   278  		case "80-":
   279  			num80Dash++
   280  		default:
   281  			t.Logf("invalid shard %s", parts[2])
   282  		}
   283  
   284  	}
   285  
   286  	assert.Equal(t, 2, numPrimary)
   287  	assert.Equal(t, 2, numReplica)
   288  	assert.Equal(t, 2, numRdonly)
   289  	assert.Equal(t, 3, numDash80)
   290  	assert.Equal(t, 3, num80Dash)
   291  	assert.NotZero(t, numRouted)
   292  }
   293  
   294  func assertTransactionalityAndRollbackObeyed(ctx context.Context, t *testing.T, conn *vtgateconn.VTGateConn, idStart int) {
   295  	cur := conn.Session(ks1+":80-@primary", &querypb.ExecuteOptions{})
   296  
   297  	i := idStart + 1
   298  	msg := "test"
   299  	bindVariables := map[string]*querypb.BindVariable{
   300  		"id":          {Type: querypb.Type_UINT64, Value: []byte(strconv.FormatInt(int64(i), 10))},
   301  		"msg":         {Type: querypb.Type_VARCHAR, Value: []byte(msg)},
   302  		"keyspace_id": {Type: querypb.Type_UINT64, Value: []byte(strconv.FormatInt(int64(i), 10))},
   303  	}
   304  	query := "insert into test_table (id, msg, keyspace_id) values (:id, :msg, :keyspace_id)"
   305  	_, err := cur.Execute(ctx, query, bindVariables)
   306  	require.NoError(t, err)
   307  
   308  	bindVariables = map[string]*querypb.BindVariable{
   309  		"msg": {Type: querypb.Type_VARCHAR, Value: []byte(msg)},
   310  	}
   311  	res, err := cur.Execute(ctx, "select * from test_table where msg = :msg", bindVariables)
   312  	require.NoError(t, err)
   313  	require.Equal(t, 1, len(res.Rows))
   314  
   315  	_, err = cur.Execute(ctx, "begin", nil)
   316  	require.NoError(t, err)
   317  
   318  	msg2 := msg + "2"
   319  	bindVariables = map[string]*querypb.BindVariable{
   320  		"id":  {Type: querypb.Type_UINT64, Value: []byte(strconv.FormatInt(int64(i), 10))},
   321  		"msg": {Type: querypb.Type_VARCHAR, Value: []byte(msg2)},
   322  	}
   323  	query = "update test_table set msg = :msg where id = :id"
   324  	_, err = cur.Execute(ctx, query, bindVariables)
   325  	require.NoError(t, err)
   326  
   327  	_, err = cur.Execute(ctx, "rollback", nil)
   328  	require.NoError(t, err)
   329  
   330  	bindVariables = map[string]*querypb.BindVariable{
   331  		"msg": {Type: querypb.Type_VARCHAR, Value: []byte(msg2)},
   332  	}
   333  	res, err = cur.Execute(ctx, "select * from test_table where msg = :msg", bindVariables)
   334  	require.NoError(t, err)
   335  	require.Equal(t, 0, len(res.Rows))
   336  }