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 }