vitess.io/vitess@v0.16.2/go/test/endtoend/cluster/mysqlctl_process.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 "html/template" 23 "os" 24 "os/exec" 25 "path" 26 "strconv" 27 "strings" 28 "syscall" 29 "time" 30 31 "vitess.io/vitess/go/mysql" 32 "vitess.io/vitess/go/vt/log" 33 "vitess.io/vitess/go/vt/tlstest" 34 ) 35 36 // MysqlctlProcess is a generic handle for a running mysqlctl command . 37 // It can be spawned manually 38 type MysqlctlProcess struct { 39 Name string 40 Binary string 41 LogDirectory string 42 TabletUID int 43 MySQLPort int 44 InitDBFile string 45 ExtraArgs []string 46 InitMysql bool 47 SecureTransport bool 48 } 49 50 // InitDb executes mysqlctl command to add cell info 51 func (mysqlctl *MysqlctlProcess) InitDb() (err error) { 52 args := []string{"--log_dir", mysqlctl.LogDirectory, 53 "--tablet_uid", fmt.Sprintf("%d", mysqlctl.TabletUID), 54 "--mysql_port", fmt.Sprintf("%d", mysqlctl.MySQLPort), 55 "init", "--", 56 "--init_db_sql_file", mysqlctl.InitDBFile} 57 if *isCoverage { 58 args = append([]string{"--test.coverprofile=" + getCoveragePath("mysql-initdb.out"), "--test.v"}, args...) 59 } 60 tmpProcess := exec.Command( 61 mysqlctl.Binary, 62 args...) 63 return tmpProcess.Run() 64 } 65 66 // Start executes mysqlctl command to start mysql instance 67 func (mysqlctl *MysqlctlProcess) Start() (err error) { 68 tmpProcess, err := mysqlctl.startProcess(true) 69 if err != nil { 70 return err 71 } 72 return tmpProcess.Wait() 73 } 74 75 // StartProvideInit executes mysqlctl command to start mysql instance 76 func (mysqlctl *MysqlctlProcess) StartProvideInit(init bool) (err error) { 77 tmpProcess, err := mysqlctl.startProcess(init) 78 if err != nil { 79 return err 80 } 81 return tmpProcess.Wait() 82 } 83 84 // StartProcess starts the mysqlctl and returns the process reference 85 func (mysqlctl *MysqlctlProcess) StartProcess() (*exec.Cmd, error) { 86 return mysqlctl.startProcess(true) 87 } 88 89 func (mysqlctl *MysqlctlProcess) startProcess(init bool) (*exec.Cmd, error) { 90 tmpProcess := exec.Command( 91 mysqlctl.Binary, 92 "--log_dir", mysqlctl.LogDirectory, 93 "--tablet_uid", fmt.Sprintf("%d", mysqlctl.TabletUID), 94 "--mysql_port", fmt.Sprintf("%d", mysqlctl.MySQLPort), 95 ) 96 if *isCoverage { 97 tmpProcess.Args = append(tmpProcess.Args, []string{"--test.coverprofile=" + getCoveragePath("mysql-start.out")}...) 98 } 99 100 if len(mysqlctl.ExtraArgs) > 0 { 101 tmpProcess.Args = append(tmpProcess.Args, mysqlctl.ExtraArgs...) 102 } 103 if mysqlctl.InitMysql { 104 if mysqlctl.SecureTransport { 105 // Set up EXTRA_MY_CNF for ssl 106 sslPath := path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/ssl_%010d", mysqlctl.TabletUID)) 107 os.MkdirAll(sslPath, 0755) 108 109 // create certificates 110 clientServerKeyPair := tlstest.CreateClientServerCertPairs(sslPath) 111 112 // use the certificate values in template to create cnf file 113 sslPathData := struct { 114 Dir string 115 ServerCert string 116 ServerKey string 117 }{ 118 Dir: sslPath, 119 ServerCert: clientServerKeyPair.ServerCert, 120 ServerKey: clientServerKeyPair.ServerKey, 121 } 122 123 extraMyCNF := path.Join(sslPath, "ssl.cnf") 124 fout, err := os.Create(extraMyCNF) 125 if err != nil { 126 log.Error(err) 127 return nil, err 128 } 129 130 template.Must(template.New(fmt.Sprintf("%010d", mysqlctl.TabletUID)).Parse(` 131 ssl_ca={{.Dir}}/ca-cert.pem 132 ssl_cert={{.ServerCert}} 133 ssl_key={{.ServerKey}} 134 `)).Execute(fout, sslPathData) 135 if err := fout.Close(); err != nil { 136 return nil, err 137 } 138 139 tmpProcess.Env = append(tmpProcess.Env, "EXTRA_MY_CNF="+extraMyCNF) 140 tmpProcess.Env = append(tmpProcess.Env, "VTDATAROOT="+os.Getenv("VTDATAROOT")) 141 } 142 143 if init { 144 tmpProcess.Args = append(tmpProcess.Args, "init", "--", 145 "--init_db_sql_file", mysqlctl.InitDBFile) 146 } 147 } 148 tmpProcess.Args = append(tmpProcess.Args, "start") 149 log.Infof("Starting mysqlctl with command: %v", tmpProcess.Args) 150 return tmpProcess, tmpProcess.Start() 151 } 152 153 // Stop executes mysqlctl command to stop mysql instance and kills the mysql instance if it doesn't shutdown in 30 seconds. 154 func (mysqlctl *MysqlctlProcess) Stop() (err error) { 155 log.Infof("Shutting down MySQL: %d", mysqlctl.TabletUID) 156 defer log.Infof("MySQL shutdown complete: %d", mysqlctl.TabletUID) 157 tmpProcess, err := mysqlctl.StopProcess() 158 if err != nil { 159 return err 160 } 161 // On the CI it was noticed that MySQL shutdown hangs sometimes and 162 // on local investigation it was waiting on SEMI_SYNC acks for an internal command 163 // of Vitess even after closing the socket file. 164 // To prevent this process for hanging for 5 minutes, we will add a 30-second timeout. 165 exit := make(chan error) 166 go func() { 167 exit <- tmpProcess.Wait() 168 }() 169 select { 170 case <-time.After(30 * time.Second): 171 break 172 case err := <-exit: 173 if err == nil { 174 return nil 175 } 176 break 177 } 178 pidFile := path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/vt_%010d/mysql.pid", mysqlctl.TabletUID)) 179 pidBytes, err := os.ReadFile(pidFile) 180 if err != nil { 181 // We can't read the file which means the PID file does not exist 182 // The server must have stopped 183 return nil 184 } 185 pid, err := strconv.Atoi(strings.TrimSpace(string(pidBytes))) 186 if err != nil { 187 return err 188 } 189 return syscall.Kill(pid, syscall.SIGKILL) 190 } 191 192 // StopProcess executes mysqlctl command to stop mysql instance and returns process reference 193 func (mysqlctl *MysqlctlProcess) StopProcess() (*exec.Cmd, error) { 194 tmpProcess := exec.Command( 195 mysqlctl.Binary, 196 "--log_dir", mysqlctl.LogDirectory, 197 "--tablet_uid", fmt.Sprintf("%d", mysqlctl.TabletUID), 198 ) 199 if *isCoverage { 200 tmpProcess.Args = append(tmpProcess.Args, []string{"--test.coverprofile=" + getCoveragePath("mysql-stop.out")}...) 201 } 202 if len(mysqlctl.ExtraArgs) > 0 { 203 tmpProcess.Args = append(tmpProcess.Args, mysqlctl.ExtraArgs...) 204 } 205 tmpProcess.Args = append(tmpProcess.Args, "shutdown") 206 return tmpProcess, tmpProcess.Start() 207 } 208 209 func (mysqlctl *MysqlctlProcess) BasePath() string { 210 return path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/vt_%010d", mysqlctl.TabletUID)) 211 } 212 213 func (mysqlctl *MysqlctlProcess) BinaryLogsPath() string { 214 return path.Join(mysqlctl.BasePath(), "bin-logs") 215 } 216 217 // CleanupFiles clean the mysql files to make sure we can start the same process again 218 func (mysqlctl *MysqlctlProcess) CleanupFiles(tabletUID int) { 219 os.RemoveAll(path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/vt_%010d/data", tabletUID))) 220 os.RemoveAll(path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/vt_%010d/relay-logs", tabletUID))) 221 os.RemoveAll(path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/vt_%010d/tmp", tabletUID))) 222 os.RemoveAll(path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/vt_%010d/bin-logs", tabletUID))) 223 os.RemoveAll(path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/vt_%010d/innodb", tabletUID))) 224 } 225 226 // Connect returns a new connection to the underlying MySQL server 227 func (mysqlctl *MysqlctlProcess) Connect(ctx context.Context, username string) (*mysql.Conn, error) { 228 params := mysql.ConnParams{ 229 Uname: username, 230 UnixSocket: path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/vt_%010d", mysqlctl.TabletUID), "/mysql.sock"), 231 } 232 233 return mysql.Connect(ctx, ¶ms) 234 } 235 236 // MysqlCtlProcessInstanceOptionalInit returns a Mysqlctl handle for mysqlctl process 237 // configured with the given Config. 238 func MysqlCtlProcessInstanceOptionalInit(tabletUID int, mySQLPort int, tmpDirectory string, initMySQL bool) *MysqlctlProcess { 239 mysqlctl := &MysqlctlProcess{ 240 Name: "mysqlctl", 241 Binary: "mysqlctl", 242 LogDirectory: tmpDirectory, 243 InitDBFile: path.Join(os.Getenv("VTROOT"), "/config/init_db.sql"), 244 } 245 mysqlctl.MySQLPort = mySQLPort 246 mysqlctl.TabletUID = tabletUID 247 mysqlctl.InitMysql = initMySQL 248 mysqlctl.SecureTransport = false 249 return mysqlctl 250 } 251 252 // MysqlCtlProcessInstance returns a Mysqlctl handle for mysqlctl process 253 // configured with the given Config. 254 func MysqlCtlProcessInstance(tabletUID int, mySQLPort int, tmpDirectory string) *MysqlctlProcess { 255 return MysqlCtlProcessInstanceOptionalInit(tabletUID, mySQLPort, tmpDirectory, true) 256 } 257 258 // StartMySQL starts mysqlctl process 259 func StartMySQL(ctx context.Context, tablet *Vttablet, username string, tmpDirectory string) error { 260 tablet.MysqlctlProcess = *MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, tmpDirectory) 261 return tablet.MysqlctlProcess.Start() 262 } 263 264 // StartMySQLAndGetConnection create a connection to tablet mysql 265 func StartMySQLAndGetConnection(ctx context.Context, tablet *Vttablet, username string, tmpDirectory string) (*mysql.Conn, error) { 266 tablet.MysqlctlProcess = *MysqlCtlProcessInstance(tablet.TabletUID, tablet.MySQLPort, tmpDirectory) 267 err := tablet.MysqlctlProcess.Start() 268 if err != nil { 269 return nil, err 270 } 271 params := mysql.ConnParams{ 272 Uname: username, 273 UnixSocket: path.Join(os.Getenv("VTDATAROOT"), fmt.Sprintf("/vt_%010d", tablet.TabletUID), "/mysql.sock"), 274 } 275 276 return mysql.Connect(ctx, ¶ms) 277 } 278 279 // ExecuteCommandWithOutput executes any mysqlctl command and returns output 280 func (mysqlctl *MysqlctlProcess) ExecuteCommandWithOutput(args ...string) (result string, err error) { 281 tmpProcess := exec.Command( 282 mysqlctl.Binary, 283 args..., 284 ) 285 log.Info(fmt.Sprintf("Executing mysqlctl with arguments %v", strings.Join(tmpProcess.Args, " "))) 286 resultByte, err := tmpProcess.CombinedOutput() 287 return string(resultByte), err 288 }