vitess.io/vitess@v0.16.2/go/vt/vttest/vtprocess.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 vttest contains helpers to set up Vitess for testing. 18 package vttest 19 20 import ( 21 "fmt" 22 "io" 23 "net/http" 24 "os" 25 "os/exec" 26 "strings" 27 "syscall" 28 "time" 29 30 "google.golang.org/protobuf/encoding/prototext" 31 32 "vitess.io/vitess/go/vt/log" 33 "vitess.io/vitess/go/vt/servenv" 34 ) 35 36 // HealthChecker is a callback that impements a service-specific health check 37 // It must return true if the service at the given `addr` is reachable, false 38 // otherwerise. 39 type HealthChecker func(addr string) bool 40 41 // VtProcess is a generic handle for a running Vitess process. 42 // It can be spawned manually or through one of the available 43 // helper methods. 44 type VtProcess struct { 45 Name string 46 Directory string 47 LogDirectory string 48 Binary string 49 ExtraArgs []string 50 Env []string 51 Port int 52 PortGrpc int 53 HealthCheck HealthChecker 54 55 proc *exec.Cmd 56 exit chan error 57 } 58 59 // getVars returns the JSON contents of the `/debug/vars` endpoint 60 // of this Vitess-based process. If an error is returned, it probably 61 // means that the Vitess service has not started successfully. 62 func getVars(addr string) ([]byte, error) { 63 url := fmt.Sprintf("http://%s/debug/vars", addr) 64 resp, err := http.Get(url) 65 if err != nil { 66 return nil, err 67 } 68 defer resp.Body.Close() 69 return io.ReadAll(resp.Body) 70 } 71 72 // defaultHealthCheck checks the health of the Vitess process using getVars. 73 // It is used when VtProcess.HealthCheck is nil. 74 func defaultHealthCheck(addr string) bool { 75 _, err := getVars(addr) 76 return err == nil 77 } 78 79 // IsHealthy returns whether the monitored Vitess process has started 80 // successfully. 81 func (vtp *VtProcess) IsHealthy() bool { 82 healthCheck := vtp.HealthCheck 83 if healthCheck == nil { 84 healthCheck = defaultHealthCheck 85 } 86 return healthCheck(vtp.Address()) 87 } 88 89 // Address returns the main address for this Vitess process. 90 // This is usually the main HTTP endpoint for the service. 91 func (vtp *VtProcess) Address() string { 92 return fmt.Sprintf("localhost:%d", vtp.Port) 93 } 94 95 // WaitTerminate attempts to gracefully shutdown the Vitess process by sending 96 // a SIGTERM, then wait for up to 10s for it to exit. If the process hasn't 97 // exited cleanly after 10s, a SIGKILL is forced and the corresponding exit 98 // error is returned to the user 99 func (vtp *VtProcess) WaitTerminate() error { 100 if vtp.proc == nil || vtp.exit == nil { 101 return nil 102 } 103 104 // Attempt graceful shutdown with SIGTERM first 105 vtp.proc.Process.Signal(syscall.SIGTERM) 106 107 select { 108 case err := <-vtp.exit: 109 vtp.proc = nil 110 return err 111 112 case <-time.After(10 * time.Second): 113 vtp.proc.Process.Kill() 114 vtp.proc = nil 115 return <-vtp.exit 116 } 117 } 118 119 // WaitStart spawns this Vitess process and waits for it to be up 120 // and running. The process is considered "up" when it starts serving 121 // its debug HTTP endpoint -- this means the process was successfully 122 // started. 123 // If the process is not healthy after 60s, this method will timeout and 124 // return an error. 125 func (vtp *VtProcess) WaitStart() (err error) { 126 vtp.proc = exec.Command( 127 vtp.Binary, 128 "--port", fmt.Sprintf("%d", vtp.Port), 129 "--log_dir", vtp.LogDirectory, 130 "--alsologtostderr", 131 ) 132 133 if vtp.PortGrpc != 0 { 134 vtp.proc.Args = append(vtp.proc.Args, "--grpc_port") 135 vtp.proc.Args = append(vtp.proc.Args, fmt.Sprintf("%d", vtp.PortGrpc)) 136 } 137 138 vtp.proc.Args = append(vtp.proc.Args, vtp.ExtraArgs...) 139 vtp.proc.Env = append(vtp.proc.Env, os.Environ()...) 140 vtp.proc.Env = append(vtp.proc.Env, vtp.Env...) 141 142 vtp.proc.Stderr = os.Stderr 143 vtp.proc.Stdout = os.Stdout 144 145 log.Infof("%v %v", strings.Join(vtp.proc.Args, " ")) 146 err = vtp.proc.Start() 147 if err != nil { 148 return 149 } 150 151 vtp.exit = make(chan error) 152 go func() { 153 vtp.exit <- vtp.proc.Wait() 154 }() 155 156 timeout := time.Now().Add(60 * time.Second) 157 for time.Now().Before(timeout) { 158 if vtp.IsHealthy() { 159 return nil 160 } 161 162 select { 163 case err := <-vtp.exit: 164 return fmt.Errorf("process '%s' exited prematurely (err: %s)", vtp.Name, err) 165 default: 166 time.Sleep(300 * time.Millisecond) 167 } 168 } 169 170 vtp.proc.Process.Kill() 171 return fmt.Errorf("process '%s' timed out after 60s (err: %s)", vtp.Name, <-vtp.exit) 172 } 173 174 const ( 175 // DefaultCharset is the default charset used by MySQL instances 176 DefaultCharset = "utf8mb4" 177 ) 178 179 // QueryServerArgs are the default arguments passed to all Vitess query servers 180 var QueryServerArgs = []string{ 181 "--queryserver-config-pool-size", "4", 182 "--queryserver-config-query-timeout", "300", 183 "--queryserver-config-schema-reload-time", "60", 184 "--queryserver-config-stream-pool-size", "4", 185 "--queryserver-config-transaction-cap", "4", 186 "--queryserver-config-transaction-timeout", "300", 187 "--queryserver-config-txpool-timeout", "300", 188 } 189 190 // VtcomboProcess returns a VtProcess handle for a local `vtcombo` service, 191 // configured with the given Config. 192 // The process must be manually started by calling WaitStart() 193 func VtcomboProcess(environment Environment, args *Config, mysql MySQLManager) (*VtProcess, error) { 194 vt := &VtProcess{ 195 Name: "vtcombo", 196 Directory: environment.Directory(), 197 LogDirectory: environment.LogDirectory(), 198 Binary: environment.BinaryPath("vtcombo"), 199 Port: environment.PortForProtocol("vtcombo", ""), 200 PortGrpc: environment.PortForProtocol("vtcombo", "grpc"), 201 HealthCheck: environment.ProcessHealthCheck("vtcombo"), 202 Env: environment.EnvVars(), 203 } 204 205 user, pass := mysql.Auth() 206 socket := mysql.UnixSocket() 207 charset := args.Charset 208 if charset == "" { 209 charset = DefaultCharset 210 } 211 protoTopo, _ := prototext.Marshal(args.Topology) 212 vt.ExtraArgs = append(vt.ExtraArgs, []string{ 213 "--db_charset", charset, 214 "--db_app_user", user, 215 "--db_app_password", pass, 216 "--db_dba_user", user, 217 "--db_dba_password", pass, 218 "--proto_topo", string(protoTopo), 219 "--mycnf_server_id", "1", 220 "--mycnf_socket_file", socket, 221 "--normalize_queries", 222 "--enable_query_plan_field_caching=false", 223 "--dbddl_plugin", "vttest", 224 "--foreign_key_mode", args.ForeignKeyMode, 225 "--planner-version", args.PlannerVersion, 226 fmt.Sprintf("--enable_online_ddl=%t", args.EnableOnlineDDL), 227 fmt.Sprintf("--enable_direct_ddl=%t", args.EnableDirectDDL), 228 fmt.Sprintf("--enable_system_settings=%t", args.EnableSystemSettings), 229 }...) 230 231 // If topo tablet refresh interval is not defined then we will give it value of 10s. Please note 232 // that the default value is 1 minute, but we are keeping it low to make vttestserver perform faster. 233 // Less value might result in high pressure on topo but for testing purpose that should not be a concern. 234 if args.VtgateTabletRefreshInterval <= 0 { 235 vt.ExtraArgs = append(vt.ExtraArgs, fmt.Sprintf("--tablet_refresh_interval=%v", 10*time.Second)) 236 } else { 237 vt.ExtraArgs = append(vt.ExtraArgs, fmt.Sprintf("--tablet_refresh_interval=%v", args.VtgateTabletRefreshInterval)) 238 } 239 240 vt.ExtraArgs = append(vt.ExtraArgs, QueryServerArgs...) 241 vt.ExtraArgs = append(vt.ExtraArgs, environment.VtcomboArguments()...) 242 243 if args.SchemaDir != "" { 244 vt.ExtraArgs = append(vt.ExtraArgs, []string{"--schema_dir", args.SchemaDir}...) 245 } 246 if args.TransactionMode != "" { 247 vt.ExtraArgs = append(vt.ExtraArgs, []string{"--transaction_mode", args.TransactionMode}...) 248 } 249 if args.TransactionTimeout != 0 { 250 vt.ExtraArgs = append(vt.ExtraArgs, "--queryserver-config-transaction-timeout", fmt.Sprintf("%f", args.TransactionTimeout)) 251 } 252 if args.TabletHostName != "" { 253 vt.ExtraArgs = append(vt.ExtraArgs, []string{"--tablet_hostname", args.TabletHostName}...) 254 } 255 if servenv.GRPCAuth() == "mtls" { 256 vt.ExtraArgs = append(vt.ExtraArgs, []string{"--grpc_auth_mode", servenv.GRPCAuth(), "--grpc_key", servenv.GRPCKey(), "--grpc_cert", servenv.GRPCCert(), "--grpc_ca", servenv.GRPCCertificateAuthority(), "--grpc_auth_mtls_allowed_substrings", servenv.ClientCertSubstrings()}...) 257 } 258 if args.VSchemaDDLAuthorizedUsers != "" { 259 vt.ExtraArgs = append(vt.ExtraArgs, []string{"--vschema_ddl_authorized_users", args.VSchemaDDLAuthorizedUsers}...) 260 } 261 vt.ExtraArgs = append(vt.ExtraArgs, "--mysql_server_version", servenv.MySQLServerVersion()) 262 if socket != "" { 263 vt.ExtraArgs = append(vt.ExtraArgs, []string{ 264 "--db_socket", socket, 265 }...) 266 } else { 267 hostname, p := mysql.Address() 268 port := fmt.Sprintf("%d", p) 269 270 vt.ExtraArgs = append(vt.ExtraArgs, []string{ 271 "--db_host", hostname, 272 "--db_port", port, 273 }...) 274 } 275 276 vtcomboMysqlPort := environment.PortForProtocol("vtcombo_mysql_port", "") 277 vtcomboMysqlBindAddress := "localhost" 278 if args.MySQLBindHost != "" { 279 vtcomboMysqlBindAddress = args.MySQLBindHost 280 } 281 282 vt.ExtraArgs = append(vt.ExtraArgs, []string{ 283 "--mysql_auth_server_impl", "none", 284 "--mysql_server_port", fmt.Sprintf("%d", vtcomboMysqlPort), 285 "--mysql_server_bind_address", vtcomboMysqlBindAddress, 286 }...) 287 288 if args.ExternalTopoImplementation != "" { 289 vt.ExtraArgs = append(vt.ExtraArgs, []string{ 290 "--external_topo_server", 291 "--topo_implementation", args.ExternalTopoImplementation, 292 "--topo_global_server_address", args.ExternalTopoGlobalServerAddress, 293 "--topo_global_root", args.ExternalTopoGlobalRoot, 294 }...) 295 } 296 297 return vt, nil 298 }