vitess.io/vitess@v0.16.2/go/test/endtoend/vtgate/transaction/tx_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 transaction
    18  
    19  import (
    20  	"context"
    21  	_ "embed"
    22  	"flag"
    23  	"fmt"
    24  	"os"
    25  	"testing"
    26  
    27  	"vitess.io/vitess/go/test/endtoend/utils"
    28  
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"vitess.io/vitess/go/mysql"
    32  	"vitess.io/vitess/go/test/endtoend/cluster"
    33  )
    34  
    35  var (
    36  	clusterInstance *cluster.LocalProcessCluster
    37  	vtParams        mysql.ConnParams
    38  	keyspaceName    = "ks"
    39  	cell            = "zone1"
    40  	hostname        = "localhost"
    41  
    42  	//go:embed schema.sql
    43  	SchemaSQL string
    44  
    45  	//go:embed vschema.json
    46  	VSchema string
    47  )
    48  
    49  func TestMain(m *testing.M) {
    50  	defer cluster.PanicHandler(nil)
    51  	flag.Parse()
    52  
    53  	exitcode, err := func() (int, error) {
    54  		clusterInstance = cluster.NewCluster(cell, hostname)
    55  		defer clusterInstance.Teardown()
    56  
    57  		// Reserve vtGate port in order to pass it to vtTablet
    58  		clusterInstance.VtgateGrpcPort = clusterInstance.GetAndReservePort()
    59  		// Set extra tablet args for twopc
    60  		clusterInstance.VtTabletExtraArgs = []string{
    61  			"--twopc_enable",
    62  			"--twopc_coordinator_address", fmt.Sprintf("localhost:%d", clusterInstance.VtgateGrpcPort),
    63  			"--twopc_abandon_age", "3600",
    64  		}
    65  
    66  		// Start topo server
    67  		if err := clusterInstance.StartTopo(); err != nil {
    68  			return 1, err
    69  		}
    70  
    71  		// Start keyspace
    72  		keyspace := &cluster.Keyspace{
    73  			Name:      keyspaceName,
    74  			SchemaSQL: SchemaSQL,
    75  			VSchema:   VSchema,
    76  		}
    77  		if err := clusterInstance.StartKeyspace(*keyspace, []string{"-80", "80-"}, 1, false); err != nil {
    78  			return 1, err
    79  		}
    80  
    81  		// Starting Vtgate in default MULTI transaction mode
    82  		if err := clusterInstance.StartVtgate(); err != nil {
    83  			return 1, err
    84  		}
    85  		vtParams = clusterInstance.GetVTParams(keyspaceName)
    86  
    87  		return m.Run(), nil
    88  	}()
    89  	if err != nil {
    90  		fmt.Printf("%v\n", err)
    91  		os.Exit(1)
    92  	} else {
    93  		os.Exit(exitcode)
    94  	}
    95  }
    96  
    97  // TestTransactionModes tests transactions using twopc mode
    98  func TestTransactionModes(t *testing.T) {
    99  	defer cluster.PanicHandler(t)
   100  
   101  	ctx := context.Background()
   102  	conn, err := mysql.Connect(ctx, &vtParams)
   103  	require.NoError(t, err)
   104  	defer conn.Close()
   105  
   106  	// set transaction mode to SINGLE.
   107  	utils.Exec(t, conn, "set transaction_mode = 'single'")
   108  
   109  	// Insert targeted to multiple tables should fail as Transaction mode is SINGLE
   110  	utils.Exec(t, conn, "begin")
   111  	utils.Exec(t, conn, "insert into twopc_user(user_id, name) values(1,'john')")
   112  	_, err = conn.ExecuteFetch("insert into twopc_user(user_id, name) values(6,'vick')", 1000, false)
   113  	want := "multi-db transaction attempted"
   114  	require.Error(t, err)
   115  	require.Contains(t, err.Error(), want)
   116  	utils.Exec(t, conn, "rollback")
   117  
   118  	// set transaction mode to TWOPC.
   119  	utils.Exec(t, conn, "set transaction_mode = 'twopc'")
   120  
   121  	// Insert targeted to multiple db should PASS with TWOPC trx mode
   122  	utils.Exec(t, conn, "begin")
   123  	utils.Exec(t, conn, "insert into twopc_user(user_id, name) values(3,'mark')")
   124  	utils.Exec(t, conn, "insert into twopc_user(user_id, name) values(4,'doug')")
   125  	utils.Exec(t, conn, "insert into twopc_lookup(name, id) values('Tim',7)")
   126  	utils.Exec(t, conn, "commit")
   127  
   128  	// Verify the values are present
   129  	utils.AssertMatches(t, conn, "select user_id from twopc_user where name='mark'", `[[INT64(3)]]`)
   130  	utils.AssertMatches(t, conn, "select name from twopc_lookup where id=3", `[[VARCHAR("mark")]]`)
   131  
   132  	// DELETE from multiple tables using TWOPC transaction mode
   133  	utils.Exec(t, conn, "begin")
   134  	utils.Exec(t, conn, "delete from twopc_user where user_id = 3")
   135  	utils.Exec(t, conn, "delete from twopc_lookup where id = 3")
   136  	utils.Exec(t, conn, "commit")
   137  
   138  	// VERIFY that values are deleted
   139  	utils.AssertMatches(t, conn, "select user_id from twopc_user where user_id=3", `[]`)
   140  	utils.AssertMatches(t, conn, "select name from twopc_lookup where id=3", `[]`)
   141  }
   142  
   143  // TestTransactionIsolation tests transaction isolation level.
   144  func TestTransactionIsolation(t *testing.T) {
   145  	defer cluster.PanicHandler(t)
   146  	ctx := context.Background()
   147  
   148  	conn, err := mysql.Connect(ctx, &vtParams)
   149  	require.NoError(t, err)
   150  	defer conn.Close()
   151  
   152  	// inserting some data.
   153  	utils.Exec(t, conn, "insert into test(id, msg) values (1,'v1'), (2, 'v2')")
   154  	defer utils.Exec(t, conn, "delete from test")
   155  
   156  	conn1, err := mysql.Connect(ctx, &vtParams)
   157  	require.NoError(t, err)
   158  	defer conn1.Close()
   159  
   160  	conn2, err := mysql.Connect(ctx, &vtParams)
   161  	require.NoError(t, err)
   162  	defer conn2.Close()
   163  
   164  	// on connection 1 change the isolation level to read-committed.
   165  	// start a transaction and read the data for id = 1.
   166  	utils.Exec(t, conn1, "set transaction isolation level read committed")
   167  	utils.Exec(t, conn1, "begin")
   168  	utils.AssertMatches(t, conn1, "select id, msg from test where id = 1", `[[INT64(1) VARCHAR("v1")]]`)
   169  
   170  	// change the value of msg for id = 1 on connection 2.
   171  	utils.Exec(t, conn2, "update test set msg = 'foo' where id = 1")
   172  
   173  	// new value should be reflected on connection 1 within the open transaction.
   174  	utils.AssertMatches(t, conn1, "select id, msg from test where id = 1", `[[INT64(1) VARCHAR("foo")]]`)
   175  	utils.Exec(t, conn1, "rollback")
   176  }
   177  
   178  func TestTransactionAccessModes(t *testing.T) {
   179  	closer := start(t)
   180  	defer closer()
   181  
   182  	ctx := context.Background()
   183  
   184  	conn, err := mysql.Connect(ctx, &vtParams)
   185  	require.NoError(t, err)
   186  	defer conn.Close()
   187  
   188  	// start a transaction with read-only characteristic.
   189  	utils.Exec(t, conn, "start transaction read only")
   190  	_, err = utils.ExecAllowError(t, conn, "insert into test(id, msg) values (42,'foo')")
   191  	require.Error(t, err)
   192  	require.Contains(t, err.Error(), "Cannot execute statement in a READ ONLY transaction")
   193  	utils.Exec(t, conn, "rollback")
   194  
   195  	// trying autocommit, this should pass as transaction characteristics are limited to single transaction.
   196  	utils.Exec(t, conn, "insert into test(id, msg) values (42,'foo')")
   197  
   198  	// target replica
   199  	utils.Exec(t, conn, "use `ks@replica`")
   200  	// start a transaction with read-only characteristic.
   201  	utils.Exec(t, conn, "start transaction read only")
   202  	utils.Exec(t, conn, "select * from test")
   203  
   204  	// start a transaction with read-write characteristic. This should fail
   205  	utils.Exec(t, conn, "start transaction read write")
   206  	_, err = utils.ExecAllowError(t, conn, "select connection_id()")
   207  	require.Error(t, err)
   208  	require.Contains(t, err.Error(), "cannot start read write transaction on a read only tablet")
   209  	utils.Exec(t, conn, "rollback")
   210  }
   211  
   212  // TestTransactionIsolationInTx tests transaction isolation level inside transaction
   213  // and setting isolation level to different values.
   214  func TestTransactionIsolationInTx(t *testing.T) {
   215  	ctx := context.Background()
   216  
   217  	conn, err := mysql.Connect(ctx, &vtParams)
   218  	require.NoError(t, err)
   219  	defer conn.Close()
   220  
   221  	utils.Exec(t, conn, "set transaction isolation level read committed")
   222  	utils.Exec(t, conn, "begin")
   223  	utils.AssertMatches(t, conn, "select @@transaction_isolation", `[[VARCHAR("READ-COMMITTED")]]`)
   224  	utils.Exec(t, conn, "commit")
   225  
   226  	utils.Exec(t, conn, "set transaction isolation level serializable")
   227  	utils.Exec(t, conn, "begin")
   228  	utils.AssertMatches(t, conn, "select @@transaction_isolation", `[[VARCHAR("SERIALIZABLE")]]`)
   229  	utils.Exec(t, conn, "commit")
   230  
   231  	utils.Exec(t, conn, "set transaction isolation level read committed")
   232  	utils.Exec(t, conn, "begin")
   233  	utils.AssertMatches(t, conn, "select @@transaction_isolation", `[[VARCHAR("READ-COMMITTED")]]`)
   234  	utils.Exec(t, conn, "commit")
   235  }
   236  
   237  func start(t *testing.T) func() {
   238  	deleteAll := func() {
   239  		conn, err := mysql.Connect(context.Background(), &vtParams)
   240  		require.NoError(t, err)
   241  		tables := []string{"test", "twopc_user"}
   242  		for _, table := range tables {
   243  			_, _ = utils.ExecAllowError(t, conn, "delete from "+table)
   244  		}
   245  		conn.Close()
   246  	}
   247  
   248  	deleteAll()
   249  
   250  	return func() {
   251  		deleteAll()
   252  		cluster.PanicHandler(t)
   253  	}
   254  }