vitess.io/vitess@v0.16.2/go/vt/vtgate/tabletgateway_flaky_test.go (about)

     1  /*
     2  Copyright 2021 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 vtgate
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/stretchr/testify/require"
    25  
    26  	"vitess.io/vitess/go/sqltypes"
    27  	"vitess.io/vitess/go/vt/discovery"
    28  	querypb "vitess.io/vitess/go/vt/proto/query"
    29  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    30  	"vitess.io/vitess/go/vt/vtgate/buffer"
    31  )
    32  
    33  // TestGatewayBufferingWhenPrimarySwitchesServingState is used to test that the buffering mechanism buffers the queries when a primary goes to a non serving state and
    34  // stops buffering when the primary is healthy again
    35  func TestGatewayBufferingWhenPrimarySwitchesServingState(t *testing.T) {
    36  	bufferImplementation = "keyspace_events"
    37  	buffer.SetBufferingModeInTestingEnv(true)
    38  	defer func() {
    39  		buffer.SetBufferingModeInTestingEnv(false)
    40  		bufferImplementation = "healthcheck"
    41  	}()
    42  
    43  	keyspace := "ks1"
    44  	shard := "-80"
    45  	tabletType := topodatapb.TabletType_PRIMARY
    46  	host := "1.1.1.1"
    47  	port := int32(1001)
    48  	target := &querypb.Target{
    49  		Keyspace:   keyspace,
    50  		Shard:      shard,
    51  		TabletType: tabletType,
    52  	}
    53  
    54  	ts := &fakeTopoServer{}
    55  	// create a new fake health check. We want to check the buffering code which uses Subscribe, so we must also pass a channel
    56  	hc := discovery.NewFakeHealthCheck(make(chan *discovery.TabletHealth))
    57  	// create a new tablet gateway
    58  	tg := NewTabletGateway(context.Background(), hc, ts, "cell")
    59  
    60  	// add a primary tabelt which is serving
    61  	sbc := hc.AddTestTablet("cell", host, port, keyspace, shard, tabletType, true, 10, nil)
    62  
    63  	// add a result to the sandbox connection
    64  	sqlResult1 := &sqltypes.Result{
    65  		Fields: []*querypb.Field{{
    66  			Name: "col1",
    67  			Type: sqltypes.VarChar,
    68  		}},
    69  		RowsAffected: 1,
    70  		Rows: [][]sqltypes.Value{{
    71  			sqltypes.MakeTrusted(sqltypes.VarChar, []byte("bb")),
    72  		}},
    73  	}
    74  	sbc.SetResults([]*sqltypes.Result{sqlResult1})
    75  
    76  	// run a query that we indeed get the result added to the sandbox connection back
    77  	res, err := tg.Execute(context.Background(), target, "query", nil, 0, 0, nil)
    78  	require.NoError(t, err)
    79  	require.Equal(t, res, sqlResult1)
    80  
    81  	// get the primary tablet from the fake health check
    82  	primaryTablet := hc.GetPrimaryTablet()
    83  	require.NotNil(t, primaryTablet)
    84  	hc.Broadcast(primaryTablet)
    85  
    86  	// set the serving type for the primary tablet false and broadcast it so that the buffering code registers this change
    87  	hc.SetServing(primaryTablet, false)
    88  	hc.Broadcast(primaryTablet)
    89  	// add another result to the sandbox connection
    90  	sbc.SetResults([]*sqltypes.Result{sqlResult1})
    91  
    92  	// execute the query in a go routine since it should be buffered, and check that it eventually succeed
    93  	queryChan := make(chan struct{})
    94  	go func() {
    95  		res, err = tg.Execute(context.Background(), target, "query", nil, 0, 0, nil)
    96  		queryChan <- struct{}{}
    97  	}()
    98  
    99  	// set the serving type for the primary tablet true and broadcast it so that the buffering code registers this change
   100  	// this should stop the buffering and the query executed in the go routine should work. This should be done with some delay so
   101  	// that we know that the query was buffered
   102  	time.Sleep(1 * time.Second)
   103  	hc.SetServing(primaryTablet, true)
   104  	hc.Broadcast(primaryTablet)
   105  
   106  	// wait for the query to execute before checking for results
   107  	select {
   108  	case <-queryChan:
   109  		require.NoError(t, err)
   110  		require.Equal(t, res, sqlResult1)
   111  	case <-time.After(15 * time.Second):
   112  		t.Fatalf("timed out waiting for query to execute")
   113  	}
   114  }
   115  
   116  // TestGatewayBufferingWhileReparenting is used to test that the buffering mechanism buffers the queries when a PRS happens
   117  // the healthchecks that happen during a PRS are simulated in this test
   118  func TestGatewayBufferingWhileReparenting(t *testing.T) {
   119  	bufferImplementation = "keyspace_events"
   120  	buffer.SetBufferingModeInTestingEnv(true)
   121  	defer func() {
   122  		buffer.SetBufferingModeInTestingEnv(false)
   123  		bufferImplementation = "healthcheck"
   124  	}()
   125  
   126  	keyspace := "ks1"
   127  	shard := "-80"
   128  	tabletType := topodatapb.TabletType_PRIMARY
   129  	host := "1.1.1.1"
   130  	hostReplica := "1.1.1.2"
   131  	port := int32(1001)
   132  	portReplica := int32(1002)
   133  	target := &querypb.Target{
   134  		Keyspace:   keyspace,
   135  		Shard:      shard,
   136  		TabletType: tabletType,
   137  	}
   138  
   139  	ts := &fakeTopoServer{}
   140  	// create a new fake health check. We want to check the buffering code which uses Subscribe, so we must also pass a channel
   141  	hc := discovery.NewFakeHealthCheck(make(chan *discovery.TabletHealth))
   142  	// create a new tablet gateway
   143  	tg := NewTabletGateway(context.Background(), hc, ts, "cell")
   144  
   145  	// add a primary tabelt which is serving
   146  	sbc := hc.AddTestTablet("cell", host, port, keyspace, shard, tabletType, true, 10, nil)
   147  	// also add a replica which is serving
   148  	sbcReplica := hc.AddTestTablet("cell", hostReplica, portReplica, keyspace, shard, topodatapb.TabletType_REPLICA, true, 0, nil)
   149  
   150  	// add a result to the sandbox connection
   151  	sqlResult1 := &sqltypes.Result{
   152  		Fields: []*querypb.Field{{
   153  			Name: "col1",
   154  			Type: sqltypes.VarChar,
   155  		}},
   156  		RowsAffected: 1,
   157  		Rows: [][]sqltypes.Value{{
   158  			sqltypes.MakeTrusted(sqltypes.VarChar, []byte("bb")),
   159  		}},
   160  	}
   161  	sbc.SetResults([]*sqltypes.Result{sqlResult1})
   162  
   163  	// run a query that we indeed get the result added to the sandbox connection back
   164  	// this also checks that the query reaches the primary tablet and not the replica
   165  	res, err := tg.Execute(context.Background(), target, "query", nil, 0, 0, nil)
   166  	require.NoError(t, err)
   167  	require.Equal(t, res, sqlResult1)
   168  
   169  	// get the primary and replica tablet from the fake health check
   170  	tablets := hc.GetAllTablets()
   171  	var primaryTablet *topodatapb.Tablet
   172  	var replicaTablet *topodatapb.Tablet
   173  
   174  	for _, tablet := range tablets {
   175  		if tablet.Type == topodatapb.TabletType_PRIMARY {
   176  			primaryTablet = tablet
   177  		} else {
   178  			replicaTablet = tablet
   179  		}
   180  	}
   181  	require.NotNil(t, primaryTablet)
   182  	require.NotNil(t, replicaTablet)
   183  
   184  	// broadcast its state initially
   185  	hc.Broadcast(primaryTablet)
   186  	// set the serving type for the primary tablet false and broadcast it so that the buffering code registers this change
   187  	hc.SetServing(primaryTablet, false)
   188  	hc.Broadcast(primaryTablet)
   189  
   190  	// add a result to the sandbox connection of the new primary
   191  	sbcReplica.SetResults([]*sqltypes.Result{sqlResult1})
   192  
   193  	// execute the query in a go routine since it should be buffered, and check that it eventually succeed
   194  	queryChan := make(chan struct{})
   195  	go func() {
   196  		res, err = tg.Execute(context.Background(), target, "query", nil, 0, 0, nil)
   197  		queryChan <- struct{}{}
   198  	}()
   199  
   200  	// set the serving type for the new primary tablet true and broadcast it so that the buffering code registers this change
   201  	// this should stop the buffering and the query executed in the go routine should work. This should be done with some delay so
   202  	// that we know that the query was buffered
   203  	time.Sleep(1 * time.Second)
   204  	// change the tablets types to simulate a PRS.
   205  	hc.SetTabletType(primaryTablet, topodatapb.TabletType_REPLICA)
   206  	hc.Broadcast(primaryTablet)
   207  	hc.SetTabletType(replicaTablet, topodatapb.TabletType_PRIMARY)
   208  	hc.SetServing(replicaTablet, true)
   209  	hc.Broadcast(replicaTablet)
   210  
   211  	// wait for the query to execute before checking for results
   212  	select {
   213  	case <-queryChan:
   214  		require.NoError(t, err)
   215  		require.Equal(t, res, sqlResult1)
   216  	case <-time.After(15 * time.Second):
   217  		t.Fatalf("timed out waiting for query to execute")
   218  	}
   219  }