vitess.io/vitess@v0.16.2/go/test/endtoend/tabletmanager/throttler_custom_config/throttler_test.go (about)

     1  /*
     2  Copyright 2020 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  package throttler
    17  
    18  import (
    19  	"context"
    20  	"flag"
    21  	"fmt"
    22  	"net/http"
    23  	"os"
    24  	"sync"
    25  	"testing"
    26  	"time"
    27  
    28  	"vitess.io/vitess/go/mysql"
    29  	"vitess.io/vitess/go/sqltypes"
    30  	"vitess.io/vitess/go/vt/vttablet/tabletserver/throttle/base"
    31  
    32  	"vitess.io/vitess/go/test/endtoend/cluster"
    33  
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/require"
    36  )
    37  
    38  var (
    39  	clusterInstance *cluster.LocalProcessCluster
    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;
    51  `
    52  
    53  	vSchema = `
    54  	{
    55      "sharded": true,
    56      "vindexes": {
    57        "hash": {
    58          "type": "hash"
    59        }
    60      },
    61      "tables": {
    62        "t1": {
    63          "column_vindexes": [
    64            {
    65              "column": "id",
    66              "name": "hash"
    67            }
    68          ]
    69        }
    70      }
    71  	}`
    72  
    73  	httpClient       = base.SetupHTTPClient(time.Second)
    74  	checkAPIPath     = "throttler/check"
    75  	checkSelfAPIPath = "throttler/check-self"
    76  	vtParams         mysql.ConnParams
    77  )
    78  
    79  const (
    80  	testThreshold   = 5
    81  	applyConfigWait = 15 * time.Second // time after which we're sure the throttler has refreshed config and tablets
    82  )
    83  
    84  func TestMain(m *testing.M) {
    85  	defer cluster.PanicHandler(nil)
    86  	flag.Parse()
    87  
    88  	exitCode := func() int {
    89  		clusterInstance = cluster.NewCluster(cell, hostname)
    90  		defer clusterInstance.Teardown()
    91  
    92  		// Start topo server
    93  		err := clusterInstance.StartTopo()
    94  		if err != nil {
    95  			return 1
    96  		}
    97  
    98  		// Set extra tablet args for lock timeout
    99  		clusterInstance.VtTabletExtraArgs = []string{
   100  			"--lock_tables_timeout", "5s",
   101  			"--watch_replication_stream",
   102  			"--enable_replication_reporter",
   103  			"--enable-lag-throttler",
   104  			"--throttle_metrics_query", "show global status like 'threads_running'",
   105  			"--throttle_metrics_threshold", fmt.Sprintf("%d", testThreshold),
   106  			"--throttle_check_as_check_self",
   107  			"--heartbeat_enable",
   108  			"--heartbeat_interval", "250ms",
   109  		}
   110  
   111  		// Start keyspace
   112  		keyspace := &cluster.Keyspace{
   113  			Name:      keyspaceName,
   114  			SchemaSQL: sqlSchema,
   115  			VSchema:   vSchema,
   116  		}
   117  
   118  		if err = clusterInstance.StartUnshardedKeyspace(*keyspace, 0, false); err != nil {
   119  			return 1
   120  		}
   121  
   122  		// Collect table paths and ports
   123  		tablets := clusterInstance.Keyspaces[0].Shards[0].Vttablets
   124  		for _, tablet := range tablets {
   125  			if tablet.Type == "primary" {
   126  				primaryTablet = tablet
   127  			} else if tablet.Type != "rdonly" {
   128  				replicaTablet = tablet
   129  			}
   130  		}
   131  
   132  		vtgateInstance := clusterInstance.NewVtgateInstance()
   133  		// Start vtgate
   134  		if err := vtgateInstance.Setup(); err != nil {
   135  			return 1
   136  		}
   137  		// ensure it is torn down during cluster TearDown
   138  		clusterInstance.VtgateProcess = *vtgateInstance
   139  		vtParams = mysql.ConnParams{
   140  			Host: clusterInstance.Hostname,
   141  			Port: clusterInstance.VtgateMySQLPort,
   142  		}
   143  
   144  		return m.Run()
   145  	}()
   146  	os.Exit(exitCode)
   147  }
   148  
   149  func throttleCheck(tablet *cluster.Vttablet) (*http.Response, error) {
   150  	resp, err := httpClient.Get(fmt.Sprintf("http://localhost:%d/%s", tablet.HTTPPort, checkAPIPath))
   151  	return resp, err
   152  }
   153  
   154  func throttleCheckSelf(tablet *cluster.Vttablet) (*http.Response, error) {
   155  	return httpClient.Head(fmt.Sprintf("http://localhost:%d/%s", tablet.HTTPPort, checkSelfAPIPath))
   156  }
   157  
   158  func TestThrottlerThresholdOK(t *testing.T) {
   159  	defer cluster.PanicHandler(t)
   160  
   161  	t.Run("immediately", func(t *testing.T) {
   162  		resp, err := throttleCheck(primaryTablet)
   163  		require.NoError(t, err)
   164  		defer resp.Body.Close()
   165  		assert.Equal(t, http.StatusOK, resp.StatusCode)
   166  	})
   167  	t.Run("after long wait", func(t *testing.T) {
   168  		time.Sleep(applyConfigWait)
   169  		resp, err := throttleCheck(primaryTablet)
   170  		require.NoError(t, err)
   171  		defer resp.Body.Close()
   172  		assert.Equal(t, http.StatusOK, resp.StatusCode)
   173  	})
   174  }
   175  
   176  func TestThreadsRunning(t *testing.T) {
   177  	defer cluster.PanicHandler(t)
   178  
   179  	sleepDuration := 10 * time.Second
   180  	var wg sync.WaitGroup
   181  	for i := 0; i < testThreshold; i++ {
   182  		// generate different Sleep() calls, all at minimum sleepDuration
   183  		wg.Add(1)
   184  		go func(i int) {
   185  			defer wg.Done()
   186  			vtgateExec(t, fmt.Sprintf("select sleep(%d)", int(sleepDuration.Seconds())+i), "")
   187  		}(i)
   188  	}
   189  	t.Run("exceeds threshold", func(t *testing.T) {
   190  		time.Sleep(sleepDuration / 2)
   191  		// by this time we will have testThreshold+1 threads_running, and we should hit the threshold
   192  		// {"StatusCode":429,"Value":2,"Threshold":2,"Message":"Threshold exceeded"}
   193  		{
   194  			resp, err := throttleCheck(primaryTablet)
   195  			require.NoError(t, err)
   196  			defer resp.Body.Close()
   197  			assert.Equal(t, http.StatusTooManyRequests, resp.StatusCode)
   198  		}
   199  		{
   200  			resp, err := throttleCheckSelf(primaryTablet)
   201  			require.NoError(t, err)
   202  			defer resp.Body.Close()
   203  			assert.Equal(t, http.StatusTooManyRequests, resp.StatusCode)
   204  		}
   205  	})
   206  	t.Run("wait for queries to terminate", func(t *testing.T) {
   207  		wg.Wait()
   208  	})
   209  	t.Run("restored below threshold", func(t *testing.T) {
   210  		{
   211  			resp, err := throttleCheck(primaryTablet)
   212  			require.NoError(t, err)
   213  			defer resp.Body.Close()
   214  			assert.Equal(t, http.StatusOK, resp.StatusCode)
   215  		}
   216  		{
   217  			resp, err := throttleCheckSelf(primaryTablet)
   218  			require.NoError(t, err)
   219  			defer resp.Body.Close()
   220  			assert.Equal(t, http.StatusOK, resp.StatusCode)
   221  		}
   222  	})
   223  }
   224  
   225  func vtgateExec(t *testing.T, query string, expectError string) *sqltypes.Result {
   226  	t.Helper()
   227  
   228  	ctx := context.Background()
   229  	conn, err := mysql.Connect(ctx, &vtParams)
   230  	require.NoError(t, err)
   231  	defer conn.Close()
   232  
   233  	qr, err := conn.ExecuteFetch(query, 1000, true)
   234  	if expectError == "" {
   235  		require.NoError(t, err)
   236  	} else {
   237  		require.Error(t, err, "error should not be nil")
   238  		assert.Contains(t, err.Error(), expectError, "Unexpected error")
   239  	}
   240  	return qr
   241  }