vitess.io/vitess@v0.16.2/go/test/endtoend/tabletmanager/lock_unlock_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 tabletmanager
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	"vitess.io/vitess/go/test/endtoend/utils"
    27  
    28  	"github.com/stretchr/testify/require"
    29  
    30  	"github.com/stretchr/testify/assert"
    31  
    32  	"vitess.io/vitess/go/mysql"
    33  	"vitess.io/vitess/go/test/endtoend/cluster"
    34  )
    35  
    36  // TestLockAndUnlock tests the lock ability by locking a replica and asserting it does not see changes
    37  func TestLockAndUnlock(t *testing.T) {
    38  	defer cluster.PanicHandler(t)
    39  	ctx := context.Background()
    40  
    41  	conn, err := mysql.Connect(ctx, &primaryTabletParams)
    42  	require.Nil(t, err)
    43  	defer conn.Close()
    44  
    45  	replicaConn, err := mysql.Connect(ctx, &replicaTabletParams)
    46  	require.Nil(t, err)
    47  	defer replicaConn.Close()
    48  
    49  	// first make sure that our writes to the primary make it to the replica
    50  	utils.Exec(t, conn, "delete from t1")
    51  	utils.Exec(t, conn, "insert into t1(id, value) values(1,'a'), (2,'b')")
    52  	checkDataOnReplica(t, replicaConn, `[[VARCHAR("a")] [VARCHAR("b")]]`)
    53  
    54  	// now lock the replica
    55  	err = tmcLockTables(ctx, replicaTablet.GrpcPort)
    56  	require.Nil(t, err)
    57  	// make sure that writing to the primary does not show up on the replica while locked
    58  	utils.Exec(t, conn, "insert into t1(id, value) values(3,'c')")
    59  	checkDataOnReplica(t, replicaConn, `[[VARCHAR("a")] [VARCHAR("b")]]`)
    60  
    61  	// finally, make sure that unlocking the replica leads to the previous write showing up
    62  	err = tmcUnlockTables(ctx, replicaTablet.GrpcPort)
    63  	require.Nil(t, err)
    64  	checkDataOnReplica(t, replicaConn, `[[VARCHAR("a")] [VARCHAR("b")] [VARCHAR("c")]]`)
    65  
    66  	// Unlocking when we do not have a valid lock should lead to an exception being raised
    67  	err = tmcUnlockTables(ctx, replicaTablet.GrpcPort)
    68  	want := "tables were not locked"
    69  	if err == nil || !strings.Contains(err.Error(), want) {
    70  		t.Errorf("Table unlock: %v, must contain %s", err, want)
    71  	}
    72  
    73  	// Clean the table for further testing
    74  	utils.Exec(t, conn, "delete from t1")
    75  }
    76  
    77  // TestStartReplicationUntilAfter tests by writing three rows, noting the gtid after each, and then replaying them one by one
    78  func TestStartReplicationUntilAfter(t *testing.T) {
    79  	defer cluster.PanicHandler(t)
    80  	ctx := context.Background()
    81  
    82  	conn, err := mysql.Connect(ctx, &primaryTabletParams)
    83  	require.Nil(t, err)
    84  	defer conn.Close()
    85  
    86  	replicaConn, err := mysql.Connect(ctx, &replicaTabletParams)
    87  	require.Nil(t, err)
    88  	defer replicaConn.Close()
    89  
    90  	//first we stop replication to the replica, so we can move forward step by step.
    91  	err = tmcStopReplication(ctx, replicaTablet.GrpcPort)
    92  	require.Nil(t, err)
    93  
    94  	utils.Exec(t, conn, "insert into t1(id, value) values(1,'a')")
    95  	pos1, err := tmcPrimaryPosition(ctx, primaryTablet.GrpcPort)
    96  	require.Nil(t, err)
    97  
    98  	utils.Exec(t, conn, "insert into t1(id, value) values(2,'b')")
    99  	pos2, err := tmcPrimaryPosition(ctx, primaryTablet.GrpcPort)
   100  	require.Nil(t, err)
   101  
   102  	utils.Exec(t, conn, "insert into t1(id, value) values(3,'c')")
   103  	pos3, err := tmcPrimaryPosition(ctx, primaryTablet.GrpcPort)
   104  	require.Nil(t, err)
   105  
   106  	// Now, we'll resume stepwise position by position and make sure that we see the expected data
   107  	checkDataOnReplica(t, replicaConn, `[]`)
   108  
   109  	// starts the mysql replication until
   110  	timeout := 10 * time.Second
   111  	err = tmcStartReplicationUntilAfter(ctx, replicaTablet.GrpcPort, pos1, timeout)
   112  	require.Nil(t, err)
   113  	// first row should be visible
   114  	checkDataOnReplica(t, replicaConn, `[[VARCHAR("a")]]`)
   115  
   116  	err = tmcStartReplicationUntilAfter(ctx, replicaTablet.GrpcPort, pos2, timeout)
   117  	require.Nil(t, err)
   118  	checkDataOnReplica(t, replicaConn, `[[VARCHAR("a")] [VARCHAR("b")]]`)
   119  
   120  	err = tmcStartReplicationUntilAfter(ctx, replicaTablet.GrpcPort, pos3, timeout)
   121  	require.Nil(t, err)
   122  	checkDataOnReplica(t, replicaConn, `[[VARCHAR("a")] [VARCHAR("b")] [VARCHAR("c")]]`)
   123  
   124  	// Strat replication to the replica
   125  	err = tmcStartReplication(ctx, replicaTablet.GrpcPort)
   126  	require.Nil(t, err)
   127  	// Clean the table for further testing
   128  	utils.Exec(t, conn, "delete from t1")
   129  }
   130  
   131  // TestLockAndTimeout tests that the lock times out and updates can be seen after timeout
   132  func TestLockAndTimeout(t *testing.T) {
   133  	defer cluster.PanicHandler(t)
   134  	ctx := context.Background()
   135  
   136  	primaryConn, err := mysql.Connect(ctx, &primaryTabletParams)
   137  	require.Nil(t, err)
   138  	defer primaryConn.Close()
   139  
   140  	replicaConn, err := mysql.Connect(ctx, &replicaTabletParams)
   141  	require.Nil(t, err)
   142  	defer replicaConn.Close()
   143  
   144  	// first make sure that our writes to the primary make it to the replica
   145  	utils.Exec(t, primaryConn, "insert into t1(id, value) values(1,'a')")
   146  	checkDataOnReplica(t, replicaConn, `[[VARCHAR("a")]]`)
   147  
   148  	// now lock the replica
   149  	err = tmcLockTables(ctx, replicaTablet.GrpcPort)
   150  	require.Nil(t, err)
   151  
   152  	// make sure that writing to the primary does not show up on the replica while locked
   153  	utils.Exec(t, primaryConn, "insert into t1(id, value) values(2,'b')")
   154  	checkDataOnReplica(t, replicaConn, `[[VARCHAR("a")]]`)
   155  
   156  	// the tests sets the lock timeout to 5 seconds, so sleeping 8 should be safe
   157  	time.Sleep(8 * time.Second)
   158  	checkDataOnReplica(t, replicaConn, `[[VARCHAR("a")] [VARCHAR("b")]]`)
   159  
   160  	// Clean the table for further testing
   161  	utils.Exec(t, primaryConn, "delete from t1")
   162  }
   163  
   164  func checkDataOnReplica(t *testing.T, replicaConn *mysql.Conn, want string) {
   165  	startTime := time.Now()
   166  	for {
   167  		qr := utils.Exec(t, replicaConn, "select value from t1")
   168  		got := fmt.Sprintf("%v", qr.Rows)
   169  
   170  		if time.Since(startTime) > 3*time.Second /* timeout */ {
   171  			assert.Equal(t, want, got)
   172  			break
   173  		}
   174  
   175  		if got == want {
   176  			assert.Equal(t, want, got)
   177  			break
   178  		} else {
   179  			time.Sleep(300 * time.Millisecond /* interval at which to check again */)
   180  		}
   181  	}
   182  }