vitess.io/vitess@v0.16.2/go/test/endtoend/cluster/cluster_util.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 cluster 18 19 import ( 20 "context" 21 "fmt" 22 "os" 23 "path" 24 "reflect" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/buger/jsonparser" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 33 "vitess.io/vitess/go/json2" 34 "vitess.io/vitess/go/mysql" 35 "vitess.io/vitess/go/vt/vtgate/vtgateconn" 36 37 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 38 tmc "vitess.io/vitess/go/vt/vttablet/grpctmclient" 39 ) 40 41 var ( 42 tmClient = tmc.NewClient() 43 dbCredentialFile string 44 InsertTabletTemplateKsID = `insert into %s (id, msg) values (%d, '%s') /* id:%d */` 45 defaultOperationTimeout = 60 * time.Second 46 defeaultRetryDelay = 1 * time.Second 47 ) 48 49 // Restart restarts vttablet and mysql. 50 func (tablet *Vttablet) Restart() error { 51 if tablet.MysqlctlProcess.TabletUID|tablet.MysqlctldProcess.TabletUID == 0 { 52 return fmt.Errorf("no mysql process is running") 53 } 54 55 if tablet.MysqlctlProcess.TabletUID > 0 { 56 tablet.MysqlctlProcess.Stop() 57 tablet.VttabletProcess.TearDown() 58 os.RemoveAll(tablet.VttabletProcess.Directory) 59 60 return tablet.MysqlctlProcess.Start() 61 } 62 63 tablet.MysqlctldProcess.Stop() 64 tablet.VttabletProcess.TearDown() 65 os.RemoveAll(tablet.VttabletProcess.Directory) 66 67 return tablet.MysqlctldProcess.Start() 68 } 69 70 // RestartOnlyTablet restarts vttablet, but not the underlying mysql instance 71 func (tablet *Vttablet) RestartOnlyTablet() error { 72 err := tablet.VttabletProcess.TearDown() 73 if err != nil { 74 return err 75 } 76 77 tablet.VttabletProcess.ServingStatus = "SERVING" 78 79 return tablet.VttabletProcess.Setup() 80 } 81 82 // ValidateTabletRestart restarts the tablet and validate error if there is any. 83 func (tablet *Vttablet) ValidateTabletRestart(t *testing.T) { 84 require.Nilf(t, tablet.Restart(), "tablet restart failed") 85 } 86 87 // GetPrimaryPosition gets the executed replication position of given vttablet 88 func GetPrimaryPosition(t *testing.T, vttablet Vttablet, hostname string) (string, string) { 89 ctx := context.Background() 90 vtablet := getTablet(vttablet.GrpcPort, hostname) 91 pos, err := tmClient.PrimaryPosition(ctx, vtablet) 92 require.Nil(t, err) 93 gtID := strings.SplitAfter(pos, "/")[1] 94 return pos, gtID 95 } 96 97 // VerifyRowsInTabletForTable verifies the total number of rows in a table. 98 // This is used to check that replication has caught up with the changes on primary. 99 func VerifyRowsInTabletForTable(t *testing.T, vttablet *Vttablet, ksName string, expectedRows int, tableName string) { 100 timeout := time.Now().Add(1 * time.Minute) 101 lastNumRowsFound := 0 102 for time.Now().Before(timeout) { 103 // ignoring the error check, if the newly created table is not replicated, then there might be error and we should ignore it 104 // but eventually it will catch up and if not caught up in required time, testcase will fail 105 qr, _ := vttablet.VttabletProcess.QueryTablet("select * from "+tableName, ksName, true) 106 if qr != nil { 107 if len(qr.Rows) == expectedRows { 108 return 109 } 110 lastNumRowsFound = len(qr.Rows) 111 } 112 time.Sleep(300 * time.Millisecond) 113 } 114 require.Equalf(t, expectedRows, lastNumRowsFound, "unexpected number of rows in %s (%s.%s)", vttablet.Alias, ksName, tableName) 115 } 116 117 // VerifyRowsInTablet Verify total number of rows in a tablet 118 func VerifyRowsInTablet(t *testing.T, vttablet *Vttablet, ksName string, expectedRows int) { 119 VerifyRowsInTabletForTable(t, vttablet, ksName, expectedRows, "vt_insert_test") 120 } 121 122 // PanicHandler handles the panic in the testcase. 123 func PanicHandler(t *testing.T) { 124 err := recover() 125 if t == nil { 126 return 127 } 128 require.Nilf(t, err, "panic occured in testcase %v", t.Name()) 129 } 130 131 // ListBackups Lists back preset in shard 132 func (cluster LocalProcessCluster) ListBackups(shardKsName string) ([]string, error) { 133 output, err := cluster.VtctlclientProcess.ExecuteCommandWithOutput("ListBackups", shardKsName) 134 if err != nil { 135 return nil, err 136 } 137 result := strings.Split(output, "\n") 138 var returnResult []string 139 for _, str := range result { 140 if str != "" { 141 returnResult = append(returnResult, str) 142 } 143 } 144 return returnResult, nil 145 } 146 147 // VerifyBackupCount compares the backup count with expected count. 148 func (cluster LocalProcessCluster) VerifyBackupCount(t *testing.T, shardKsName string, expected int) []string { 149 backups, err := cluster.ListBackups(shardKsName) 150 require.Nil(t, err) 151 assert.Equalf(t, expected, len(backups), "invalid number of backups") 152 return backups 153 } 154 155 // RemoveAllBackups removes all the backup corresponds to list backup. 156 func (cluster LocalProcessCluster) RemoveAllBackups(t *testing.T, shardKsName string) { 157 backups, err := cluster.ListBackups(shardKsName) 158 require.Nil(t, err) 159 for _, backup := range backups { 160 cluster.VtctlclientProcess.ExecuteCommand("RemoveBackup", shardKsName, backup) 161 } 162 } 163 164 // ResetTabletDirectory transitions back to tablet state (i.e. mysql process restarts with cleaned directory and tablet is off) 165 func ResetTabletDirectory(tablet Vttablet) error { 166 tablet.VttabletProcess.TearDown() 167 tablet.MysqlctlProcess.Stop() 168 os.RemoveAll(tablet.VttabletProcess.Directory) 169 170 return tablet.MysqlctlProcess.Start() 171 } 172 173 func getTablet(tabletGrpcPort int, hostname string) *topodatapb.Tablet { 174 portMap := make(map[string]int32) 175 portMap["grpc"] = int32(tabletGrpcPort) 176 return &topodatapb.Tablet{Hostname: hostname, PortMap: portMap} 177 } 178 179 func filterResultForWarning(input string) string { 180 lines := strings.Split(input, "\n") 181 var result string 182 for _, line := range lines { 183 if strings.Contains(line, "WARNING: vtctl should only be used for VDiff v1 workflows. Please use VDiff v2 and consider using vtctldclient for all other commands.") { 184 continue 185 } 186 result = result + line + "\n" 187 } 188 return result 189 } 190 191 func filterResultWhenRunsForCoverage(input string) string { 192 if !*isCoverage { 193 return input 194 } 195 lines := strings.Split(input, "\n") 196 var result string 197 for _, line := range lines { 198 if strings.Contains(line, "=== RUN") { 199 continue 200 } 201 if strings.Contains(line, "--- PASS:") || strings.Contains(line, "PASS") { 202 break 203 } 204 result = result + line + "\n" 205 } 206 return result 207 } 208 209 // WaitForReplicationPos will wait for replication position to catch-up 210 func WaitForReplicationPos(t *testing.T, tabletA *Vttablet, tabletB *Vttablet, hostname string, timeout float64) { 211 replicationPosA, _ := GetPrimaryPosition(t, *tabletA, hostname) 212 for { 213 replicationPosB, _ := GetPrimaryPosition(t, *tabletB, hostname) 214 if positionAtLeast(t, tabletA, replicationPosB, replicationPosA) { 215 break 216 } 217 msg := fmt.Sprintf("%s's replication position to catch up to %s's;currently at: %s, waiting to catch up to: %s", tabletB.Alias, tabletA.Alias, replicationPosB, replicationPosA) 218 waitStep(t, msg, timeout, 0.01) 219 } 220 } 221 222 func waitStep(t *testing.T, msg string, timeout float64, sleepTime float64) float64 { 223 timeout = timeout - sleepTime 224 if timeout < 0.0 { 225 t.Errorf("timeout waiting for condition '%s'", msg) 226 } 227 time.Sleep(time.Duration(sleepTime) * time.Second) 228 return timeout 229 } 230 231 func positionAtLeast(t *testing.T, tablet *Vttablet, a string, b string) bool { 232 isAtleast := false 233 val, err := tablet.MysqlctlProcess.ExecuteCommandWithOutput("position", "at_least", a, b) 234 require.NoError(t, err) 235 if strings.Contains(val, "true") { 236 isAtleast = true 237 } 238 return isAtleast 239 } 240 241 // ExecuteQueriesUsingVtgate sends query to vtgate using vtgate session. 242 func ExecuteQueriesUsingVtgate(t *testing.T, session *vtgateconn.VTGateSession, query string) { 243 _, err := session.Execute(context.Background(), query, nil) 244 require.Nil(t, err) 245 } 246 247 // NewConnParams creates ConnParams corresponds to given arguments. 248 func NewConnParams(port int, password, socketPath, keyspace string) mysql.ConnParams { 249 if port != 0 { 250 socketPath = "" 251 } 252 cp := mysql.ConnParams{ 253 Uname: "vt_dba", 254 Port: port, 255 UnixSocket: socketPath, 256 Pass: password, 257 } 258 cp.DbName = keyspace 259 if keyspace != "" && keyspace != "_vt" { 260 cp.DbName = "vt_" + keyspace 261 } 262 263 return cp 264 265 } 266 267 func filterDoubleDashArgs(args []string, version int) (filtered []string) { 268 if version > 13 { 269 return args 270 } 271 272 for _, arg := range args { 273 if arg == "--" { 274 continue 275 } 276 277 filtered = append(filtered, arg) 278 } 279 280 return filtered 281 } 282 283 // WriteDbCredentialToTmp writes JSON formatted db credentials to the 284 // specified tmp directory. 285 func WriteDbCredentialToTmp(tmpDir string) string { 286 data := []byte(`{ 287 "vt_dba": ["VtDbaPass"], 288 "vt_app": ["VtAppPass"], 289 "vt_allprivs": ["VtAllprivsPass"], 290 "vt_repl": ["VtReplPass"], 291 "vt_filtered": ["VtFilteredPass"] 292 }`) 293 dbCredentialFile = path.Join(tmpDir, "db_credentials.json") 294 os.WriteFile(dbCredentialFile, data, 0666) 295 return dbCredentialFile 296 } 297 298 // GetPasswordUpdateSQL returns the SQL for updating the users' passwords 299 // to the static creds used throughout tests. 300 func GetPasswordUpdateSQL(localCluster *LocalProcessCluster) string { 301 pwdChangeCmd := ` 302 # Set real passwords for all users. 303 SET PASSWORD FOR 'root'@'localhost' = 'RootPass'; 304 SET PASSWORD FOR 'vt_dba'@'localhost' = 'VtDbaPass'; 305 SET PASSWORD FOR 'vt_app'@'localhost' = 'VtAppPass'; 306 SET PASSWORD FOR 'vt_allprivs'@'localhost' = 'VtAllprivsPass'; 307 SET PASSWORD FOR 'vt_repl'@'%' = 'VtReplPass'; 308 SET PASSWORD FOR 'vt_filtered'@'localhost' = 'VtFilteredPass'; 309 FLUSH PRIVILEGES; 310 ` 311 return pwdChangeCmd 312 } 313 314 // CheckSrvKeyspace confirms that the cell and keyspace contain the expected 315 // shard mappings. 316 func CheckSrvKeyspace(t *testing.T, cell string, ksname string, expectedPartition map[topodatapb.TabletType][]string, ci LocalProcessCluster) { 317 srvKeyspace := GetSrvKeyspace(t, cell, ksname, ci) 318 319 currentPartition := map[topodatapb.TabletType][]string{} 320 321 for _, partition := range srvKeyspace.Partitions { 322 currentPartition[partition.ServedType] = []string{} 323 for _, shardRef := range partition.ShardReferences { 324 currentPartition[partition.ServedType] = append(currentPartition[partition.ServedType], shardRef.Name) 325 } 326 } 327 328 assert.True(t, reflect.DeepEqual(currentPartition, expectedPartition)) 329 } 330 331 // GetSrvKeyspace returns the SrvKeyspace structure for the cell and keyspace. 332 func GetSrvKeyspace(t *testing.T, cell string, ksname string, ci LocalProcessCluster) *topodatapb.SrvKeyspace { 333 output, err := ci.VtctlclientProcess.ExecuteCommandWithOutput("GetSrvKeyspace", cell, ksname) 334 require.Nil(t, err) 335 var srvKeyspace topodatapb.SrvKeyspace 336 337 err = json2.Unmarshal([]byte(output), &srvKeyspace) 338 require.Nil(t, err) 339 return &srvKeyspace 340 } 341 342 // ExecuteOnTablet executes a query on the specified vttablet. 343 // It should always be called with a primary tablet for a keyspace/shard. 344 func ExecuteOnTablet(t *testing.T, query string, vttablet Vttablet, ks string, expectFail bool) { 345 _, _ = vttablet.VttabletProcess.QueryTablet("begin", ks, true) 346 _, err := vttablet.VttabletProcess.QueryTablet(query, ks, true) 347 if expectFail { 348 require.Error(t, err) 349 } else { 350 require.Nil(t, err) 351 } 352 _, _ = vttablet.VttabletProcess.QueryTablet("commit", ks, true) 353 } 354 355 func WaitForTabletSetup(vtctlClientProcess *VtctlClientProcess, expectedTablets int, expectedStatus []string) error { 356 // wait for both tablet to get into replica state in topo 357 waitUntil := time.Now().Add(10 * time.Second) 358 for time.Now().Before(waitUntil) { 359 result, err := vtctlClientProcess.ExecuteCommandWithOutput("ListAllTablets") 360 if err != nil { 361 return err 362 } 363 364 tabletsFromCMD := strings.Split(result, "\n") 365 tabletCountFromCMD := 0 366 367 for _, line := range tabletsFromCMD { 368 if len(line) > 0 { 369 for _, status := range expectedStatus { 370 if strings.Contains(line, status) { 371 tabletCountFromCMD = tabletCountFromCMD + 1 372 break 373 } 374 } 375 } 376 } 377 378 if tabletCountFromCMD >= expectedTablets { 379 return nil 380 } 381 382 time.Sleep(1 * time.Second) 383 } 384 385 return fmt.Errorf("all %d tablet are not in expected state %s", expectedTablets, expectedStatus) 386 } 387 388 // WaitForHealthyShard waits for the given shard info record in the topo 389 // server to list a tablet (alias and uid) as the primary serving tablet 390 // for the shard. This is done using "vtctldclient GetShard" and parsing 391 // its JSON output. All other watchers should then also see this shard 392 // info status as well. 393 func WaitForHealthyShard(vtctldclient *VtctldClientProcess, keyspace, shard string) error { 394 var ( 395 tmr = time.NewTimer(defaultOperationTimeout) 396 res string 397 err error 398 json []byte 399 cell string 400 uid int64 401 ) 402 for { 403 res, err = vtctldclient.ExecuteCommandWithOutput("GetShard", fmt.Sprintf("%s/%s", keyspace, shard)) 404 if err != nil { 405 return err 406 } 407 json = []byte(res) 408 409 cell, err = jsonparser.GetString(json, "shard", "primary_alias", "cell") 410 if err != nil && err != jsonparser.KeyPathNotFoundError { 411 return err 412 } 413 uid, err = jsonparser.GetInt(json, "shard", "primary_alias", "uid") 414 if err != nil && err != jsonparser.KeyPathNotFoundError { 415 return err 416 } 417 418 if cell != "" && uid > 0 { 419 return nil 420 } 421 422 select { 423 case <-tmr.C: 424 return fmt.Errorf("timed out waiting for the %s/%s shard to become healthy in the topo after %v; last seen status: %s; last seen error: %v", 425 keyspace, shard, defaultOperationTimeout, res, err) 426 default: 427 } 428 429 time.Sleep(defeaultRetryDelay) 430 } 431 }