vitess.io/vitess@v0.16.2/go/test/endtoend/tabletmanager/replication_manager/tablet_test.go (about)

     1  /*
     2  Copyright 2022 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  	"flag"
    22  	"fmt"
    23  	"os"
    24  	"testing"
    25  	"time"
    26  
    27  	"vitess.io/vitess/go/vt/sidecardb"
    28  
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"vitess.io/vitess/go/test/endtoend/cluster"
    32  	replicationdatapb "vitess.io/vitess/go/vt/proto/replicationdata"
    33  	tabletpb "vitess.io/vitess/go/vt/proto/topodata"
    34  	tmc "vitess.io/vitess/go/vt/vttablet/grpctmclient"
    35  )
    36  
    37  var (
    38  	clusterInstance *cluster.LocalProcessCluster
    39  	tmClient        *tmc.Client
    40  	primaryTablet   cluster.Vttablet
    41  	replicaTablet   cluster.Vttablet
    42  	hostname        = "localhost"
    43  	keyspaceName    = "ks"
    44  	cell            = "zone1"
    45  	sqlSchema       = `
    46  	create table t1(
    47  		id bigint,
    48  		value varchar(16),
    49  		primary key(id)
    50  	) Engine=InnoDB DEFAULT CHARSET=utf8;
    51  	CREATE VIEW v1 AS SELECT id, value FROM t1;
    52  `
    53  
    54  	vSchema = `
    55  	{
    56      "sharded": false,
    57      "vindexes": {
    58        "hash": {
    59          "type": "hash"
    60        }
    61      },
    62      "tables": {
    63        "t1": {
    64          "column_vindexes": [
    65            {
    66              "column": "id",
    67              "name": "hash"
    68            }
    69          ]
    70        }
    71      }
    72    }`
    73  )
    74  
    75  func TestMain(m *testing.M) {
    76  	defer cluster.PanicHandler(nil)
    77  	flag.Parse()
    78  
    79  	exitCode := func() int {
    80  		clusterInstance = cluster.NewCluster(cell, hostname)
    81  		defer clusterInstance.Teardown()
    82  
    83  		// Start topo server
    84  		err := clusterInstance.StartTopo()
    85  		if err != nil {
    86  			return 1
    87  		}
    88  
    89  		// Start keyspace
    90  		keyspace := &cluster.Keyspace{
    91  			Name:      keyspaceName,
    92  			SchemaSQL: sqlSchema,
    93  			VSchema:   vSchema,
    94  		}
    95  
    96  		if err = clusterInstance.StartUnshardedKeyspace(*keyspace, 1, false); err != nil {
    97  			return 1
    98  		}
    99  
   100  		// Collect table paths and ports
   101  		tablets := clusterInstance.Keyspaces[0].Shards[0].Vttablets
   102  		for _, tablet := range tablets {
   103  			if tablet.Type == "primary" {
   104  				primaryTablet = *tablet
   105  			} else {
   106  				replicaTablet = *tablet
   107  			}
   108  		}
   109  
   110  		// create tablet manager client
   111  		tmClient = tmc.NewClient()
   112  
   113  		return m.Run()
   114  	}()
   115  	os.Exit(exitCode)
   116  }
   117  
   118  func tmcGetReplicationStatus(ctx context.Context, tabletGrpcPort int) (*replicationdatapb.Status, error) {
   119  	vtablet := getTablet(tabletGrpcPort)
   120  	return tmClient.ReplicationStatus(ctx, vtablet)
   121  }
   122  
   123  func getTablet(tabletGrpcPort int) *tabletpb.Tablet {
   124  	portMap := make(map[string]int32)
   125  	portMap["grpc"] = int32(tabletGrpcPort)
   126  	return &tabletpb.Tablet{Hostname: hostname, PortMap: portMap}
   127  }
   128  
   129  // resurrectTablet is used to resurrect the given tablet
   130  func resurrectTablet(t *testing.T, tab cluster.Vttablet) {
   131  	// initialize config again to regenerate the my.cnf file which has the port to use
   132  	_, err := tab.MysqlctlProcess.ExecuteCommandWithOutput("--log_dir", tab.MysqlctlProcess.LogDirectory,
   133  		"--tablet_uid", fmt.Sprintf("%d", tab.MysqlctlProcess.TabletUID),
   134  		"--mysql_port", fmt.Sprintf("%d", tab.MysqlctlProcess.MySQLPort),
   135  		"init_config")
   136  	require.NoError(t, err)
   137  
   138  	tab.MysqlctlProcess.InitMysql = false
   139  	err = tab.MysqlctlProcess.Start()
   140  	require.NoError(t, err)
   141  
   142  	// Start the tablet
   143  	tab.VttabletProcess.ServingStatus = "SERVING"
   144  	err = tab.VttabletProcess.Setup()
   145  	require.NoError(t, err)
   146  }
   147  
   148  // stopTablet stops the tablet
   149  func stopTablet(t *testing.T, tab cluster.Vttablet) {
   150  	err := tab.VttabletProcess.TearDownWithTimeout(30 * time.Second)
   151  	require.NoError(t, err)
   152  	err = tab.MysqlctlProcess.Stop()
   153  	require.NoError(t, err)
   154  }
   155  
   156  func waitForSourcePort(ctx context.Context, t *testing.T, tablet cluster.Vttablet, expectedPort int32) error {
   157  	timeout := time.Now().Add(15 * time.Second)
   158  	for time.Now().Before(timeout) {
   159  		// Check that initially replication is setup correctly on the replica tablet
   160  		replicaStatus, err := tmcGetReplicationStatus(ctx, tablet.GrpcPort)
   161  		if err == nil && replicaStatus.SourcePort == expectedPort {
   162  			return nil
   163  		}
   164  		time.Sleep(300 * time.Millisecond)
   165  	}
   166  	return fmt.Errorf("time out before source port became %v for %v", expectedPort, tablet.Alias)
   167  }
   168  
   169  func getSidecarDBDDLQueryCount(tablet *cluster.VttabletProcess) (int64, error) {
   170  	vars := tablet.GetVars()
   171  	key := sidecardb.StatsKeyQueryCount
   172  	val, ok := vars[key]
   173  	if !ok {
   174  		return 0, fmt.Errorf("%s not found in debug/vars", key)
   175  	}
   176  	return int64(val.(float64)), nil
   177  }
   178  func TestReplicationRepairAfterPrimaryTabletChange(t *testing.T) {
   179  	ctx := context.Background()
   180  	// Check that initially replication is setup correctly on the replica tablet
   181  	err := waitForSourcePort(ctx, t, replicaTablet, int32(primaryTablet.MySQLPort))
   182  	require.NoError(t, err)
   183  
   184  	sidecarDDLCount, err := getSidecarDBDDLQueryCount(primaryTablet.VttabletProcess)
   185  	require.NoError(t, err)
   186  	// sidecar db should create all _vt tables when vttablet started
   187  	require.Greater(t, sidecarDDLCount, int64(0))
   188  
   189  	// Stop the primary tablet
   190  	stopTablet(t, primaryTablet)
   191  	// Change the MySQL port of the primary tablet
   192  	newMysqlPort := clusterInstance.GetAndReservePort()
   193  	primaryTablet.MySQLPort = newMysqlPort
   194  	primaryTablet.MysqlctlProcess.MySQLPort = newMysqlPort
   195  
   196  	// Start the primary tablet again
   197  	resurrectTablet(t, primaryTablet)
   198  
   199  	// Let replication manager repair replication
   200  	err = waitForSourcePort(ctx, t, replicaTablet, int32(newMysqlPort))
   201  	require.NoError(t, err)
   202  
   203  	sidecarDDLCount, err = getSidecarDBDDLQueryCount(primaryTablet.VttabletProcess)
   204  	require.NoError(t, err)
   205  	// sidecardb should find the desired _vt schema and not apply any new creates or upgrades when the tablet comes up again
   206  	require.Equal(t, sidecarDDLCount, int64(0))
   207  }