vitess.io/vitess@v0.16.2/go/test/endtoend/cluster/vtgate_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 "encoding/json" 21 "fmt" 22 "io" 23 "net/http" 24 "os" 25 "os/exec" 26 "path" 27 "reflect" 28 "strconv" 29 "strings" 30 "syscall" 31 "time" 32 33 "vitess.io/vitess/go/vt/log" 34 "vitess.io/vitess/go/vt/mysqlctl" 35 "vitess.io/vitess/go/vt/vtgate/planbuilder" 36 "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" 37 ) 38 39 // VtgateProcess is a generic handle for a running vtgate . 40 // It can be spawned manually 41 type VtgateProcess struct { 42 Name string 43 Binary string 44 CommonArg VtctlProcess 45 LogDir string 46 FileToLogQueries string 47 Port int 48 GrpcPort int 49 MySQLServerPort int 50 MySQLServerSocketPath string 51 Cell string 52 CellsToWatch string 53 TabletTypesToWait string 54 ServiceMap string 55 MySQLAuthServerImpl string 56 Directory string 57 VerifyURL string 58 VSchemaURL string 59 SysVarSetEnabled bool 60 PlannerVersion plancontext.PlannerVersion 61 // Extra Args to be set before starting the vtgate process 62 ExtraArgs []string 63 64 proc *exec.Cmd 65 exit chan error 66 } 67 68 const defaultVtGatePlannerVersion = planbuilder.Gen4CompareV3 69 70 // Setup starts Vtgate process with required arguements 71 func (vtgate *VtgateProcess) Setup() (err error) { 72 args := []string{ 73 "--topo_implementation", vtgate.CommonArg.TopoImplementation, 74 "--topo_global_server_address", vtgate.CommonArg.TopoGlobalAddress, 75 "--topo_global_root", vtgate.CommonArg.TopoGlobalRoot, 76 "--log_dir", vtgate.LogDir, 77 "--log_queries_to_file", vtgate.FileToLogQueries, 78 "--port", fmt.Sprintf("%d", vtgate.Port), 79 "--grpc_port", fmt.Sprintf("%d", vtgate.GrpcPort), 80 "--mysql_server_port", fmt.Sprintf("%d", vtgate.MySQLServerPort), 81 "--mysql_server_socket_path", vtgate.MySQLServerSocketPath, 82 "--cell", vtgate.Cell, 83 "--cells_to_watch", vtgate.CellsToWatch, 84 "--tablet_types_to_wait", vtgate.TabletTypesToWait, 85 "--service_map", vtgate.ServiceMap, 86 "--mysql_auth_server_impl", vtgate.MySQLAuthServerImpl, 87 } 88 // If no explicit mysql_server_version has been specified then we autodetect 89 // the MySQL version that will be used for the test and base the vtgate's 90 // mysql server version on that. 91 msvflag := false 92 for _, f := range vtgate.ExtraArgs { 93 if strings.Contains(f, "mysql_server_version") { 94 msvflag = true 95 break 96 } 97 } 98 if !msvflag { 99 version, err := mysqlctl.GetVersionString() 100 if err != nil { 101 return err 102 } 103 _, vers, err := mysqlctl.ParseVersionString(version) 104 if err != nil { 105 return err 106 } 107 mysqlvers := fmt.Sprintf("%d.%d.%d-vitess", vers.Major, vers.Minor, vers.Patch) 108 args = append(args, "--mysql_server_version", mysqlvers) 109 } 110 if vtgate.PlannerVersion > 0 { 111 args = append(args, "--planner-version", vtgate.PlannerVersion.String()) 112 } 113 if vtgate.SysVarSetEnabled { 114 args = append(args, "--enable_system_settings") 115 } 116 vtgate.proc = exec.Command( 117 vtgate.Binary, 118 args..., 119 ) 120 if *isCoverage { 121 vtgate.proc.Args = append(vtgate.proc.Args, "--test.coverprofile="+getCoveragePath("vtgate.out")) 122 } 123 124 vtgate.proc.Args = append(vtgate.proc.Args, vtgate.ExtraArgs...) 125 126 errFile, _ := os.Create(path.Join(vtgate.LogDir, "vtgate-stderr.txt")) 127 vtgate.proc.Stderr = errFile 128 129 vtgate.proc.Env = append(vtgate.proc.Env, os.Environ()...) 130 131 log.Infof("Running vtgate with command: %v", strings.Join(vtgate.proc.Args, " ")) 132 133 err = vtgate.proc.Start() 134 if err != nil { 135 return 136 } 137 vtgate.exit = make(chan error) 138 go func() { 139 if vtgate.proc != nil { 140 vtgate.exit <- vtgate.proc.Wait() 141 close(vtgate.exit) 142 } 143 }() 144 145 timeout := time.Now().Add(60 * time.Second) 146 for time.Now().Before(timeout) { 147 if vtgate.WaitForStatus() { 148 return nil 149 } 150 select { 151 case err := <-vtgate.exit: 152 return fmt.Errorf("process '%s' exited prematurely (err: %s)", vtgate.Name, err) 153 default: 154 time.Sleep(300 * time.Millisecond) 155 } 156 } 157 158 return fmt.Errorf("process '%s' timed out after 60s (err: %s)", vtgate.Name, <-vtgate.exit) 159 } 160 161 // WaitForStatus function checks if vtgate process is up and running 162 func (vtgate *VtgateProcess) WaitForStatus() bool { 163 resp, err := http.Get(vtgate.VerifyURL) 164 if err != nil { 165 return false 166 } 167 defer resp.Body.Close() 168 169 return resp.StatusCode == 200 170 } 171 172 // GetStatusForTabletOfShard function gets status for a specific tablet of a shard in keyspace 173 // endPointsCount : number of endpoints 174 func (vtgate *VtgateProcess) GetStatusForTabletOfShard(name string, endPointsCount int) bool { 175 resp, err := http.Get(vtgate.VerifyURL) 176 if err != nil { 177 return false 178 } 179 defer resp.Body.Close() 180 181 if resp.StatusCode == 200 { 182 resultMap := make(map[string]any) 183 respByte, _ := io.ReadAll(resp.Body) 184 err := json.Unmarshal(respByte, &resultMap) 185 if err != nil { 186 panic(err) 187 } 188 object := reflect.ValueOf(resultMap["HealthcheckConnections"]) 189 if object.Kind() == reflect.Map { 190 for _, key := range object.MapKeys() { 191 if key.String() == name { 192 value := fmt.Sprintf("%v", object.MapIndex(key)) 193 countStr := strconv.Itoa(endPointsCount) 194 return value == countStr 195 } 196 } 197 } 198 } 199 return false 200 } 201 202 // WaitForStatusOfTabletInShard function waits till status of a tablet in shard is 1 203 // endPointsCount: how many endpoints to wait for 204 func (vtgate *VtgateProcess) WaitForStatusOfTabletInShard(name string, endPointsCount int, timeout time.Duration) error { 205 log.Infof("Waiting for healthy status of %d %s tablets in cell %s", 206 endPointsCount, name, vtgate.Cell) 207 deadline := time.Now().Add(timeout) 208 for time.Now().Before(deadline) { 209 if vtgate.GetStatusForTabletOfShard(name, endPointsCount) { 210 return nil 211 } 212 select { 213 case err := <-vtgate.exit: 214 return fmt.Errorf("process '%s' exited prematurely (err: %s)", vtgate.Name, err) 215 default: 216 time.Sleep(300 * time.Millisecond) 217 } 218 } 219 return fmt.Errorf("wait for %s failed", name) 220 } 221 222 // TearDown shuts down the running vtgate service 223 func (vtgate *VtgateProcess) TearDown() error { 224 if vtgate.proc == nil || vtgate.exit == nil { 225 return nil 226 } 227 // graceful shutdown is not currently working with vtgate, attempting a force-kill to make tests less flaky 228 // Attempt graceful shutdown with SIGTERM first 229 vtgate.proc.Process.Signal(syscall.SIGTERM) 230 231 // We are not checking vtgate's exit code because it sometimes 232 // returns exit code 2, even though vtgate terminates cleanly. 233 select { 234 case <-vtgate.exit: 235 vtgate.proc = nil 236 return nil 237 238 case <-time.After(30 * time.Second): 239 vtgate.proc.Process.Kill() 240 err := <-vtgate.exit 241 vtgate.proc = nil 242 return err 243 } 244 } 245 246 // VtgateProcessInstance returns a Vtgate handle for vtgate process 247 // configured with the given Config. 248 // The process must be manually started by calling setup() 249 func VtgateProcessInstance( 250 port, grpcPort, mySQLServerPort int, 251 cell, cellsToWatch, hostname, tabletTypesToWait string, 252 topoPort int, 253 tmpDirectory string, 254 extraArgs []string, 255 plannerVersion plancontext.PlannerVersion, 256 ) *VtgateProcess { 257 vtctl := VtctlProcessInstance(topoPort, hostname) 258 vtgate := &VtgateProcess{ 259 Name: "vtgate", 260 Binary: "vtgate", 261 FileToLogQueries: path.Join(tmpDirectory, "/vtgate_querylog.txt"), 262 Directory: os.Getenv("VTDATAROOT"), 263 ServiceMap: "grpc-tabletmanager,grpc-throttler,grpc-queryservice,grpc-updatestream,grpc-vtctl,grpc-vtgateservice", 264 LogDir: tmpDirectory, 265 Port: port, 266 GrpcPort: grpcPort, 267 MySQLServerPort: mySQLServerPort, 268 MySQLServerSocketPath: path.Join(tmpDirectory, "mysql.sock"), 269 Cell: cell, 270 CellsToWatch: cellsToWatch, 271 TabletTypesToWait: tabletTypesToWait, 272 CommonArg: *vtctl, 273 MySQLAuthServerImpl: "none", 274 ExtraArgs: extraArgs, 275 PlannerVersion: plannerVersion, 276 } 277 278 vtgate.VerifyURL = fmt.Sprintf("http://%s:%d/debug/vars", hostname, port) 279 vtgate.VSchemaURL = fmt.Sprintf("http://%s:%d/debug/vschema", hostname, port) 280 281 return vtgate 282 } 283 284 // GetVars returns map of vars 285 func (vtgate *VtgateProcess) GetVars() (map[string]any, error) { 286 resultMap := make(map[string]any) 287 resp, err := http.Get(vtgate.VerifyURL) 288 if err != nil { 289 return nil, fmt.Errorf("error getting response from %s", vtgate.VerifyURL) 290 } 291 defer resp.Body.Close() 292 293 if resp.StatusCode == 200 { 294 respByte, _ := io.ReadAll(resp.Body) 295 err := json.Unmarshal(respByte, &resultMap) 296 if err != nil { 297 return nil, fmt.Errorf("not able to parse response body") 298 } 299 return resultMap, nil 300 } 301 return nil, fmt.Errorf("unsuccessful response") 302 } 303 304 // ReadVSchema reads the vschema from the vtgate endpoint for it and returns 305 // a pointer to the interface. To read this vschema, the caller must convert it to a map 306 func (vtgate *VtgateProcess) ReadVSchema() (*interface{}, error) { 307 httpClient := &http.Client{Timeout: 5 * time.Second} 308 resp, err := httpClient.Get(vtgate.VSchemaURL) 309 if err != nil { 310 return nil, err 311 } 312 defer resp.Body.Close() 313 res, err := io.ReadAll(resp.Body) 314 if err != nil { 315 return nil, err 316 } 317 var results interface{} 318 err = json.Unmarshal(res, &results) 319 if err != nil { 320 return nil, err 321 } 322 return &results, nil 323 }