vitess.io/vitess@v0.16.2/go/test/endtoend/cluster/vtorc_process.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 */ 17 18 package cluster 19 20 import ( 21 "encoding/json" 22 "fmt" 23 "io" 24 "net/http" 25 "os" 26 "os/exec" 27 "path" 28 "strings" 29 "syscall" 30 "testing" 31 "time" 32 33 "vitess.io/vitess/go/vt/log" 34 ) 35 36 // VTOrcProcess is a test struct for running 37 // vtorc as a separate process for testing 38 type VTOrcProcess struct { 39 VtctlProcess 40 Port int 41 LogDir string 42 ExtraArgs []string 43 ConfigPath string 44 Config VTOrcConfiguration 45 WebPort int 46 proc *exec.Cmd 47 exit chan error 48 } 49 50 type VTOrcConfiguration struct { 51 Debug bool 52 ListenAddress string 53 MySQLTopologyUser string 54 MySQLTopologyPassword string 55 MySQLReplicaUser string 56 MySQLReplicaPassword string 57 RecoveryPeriodBlockSeconds int 58 PreventCrossDataCenterPrimaryFailover bool `json:",omitempty"` 59 LockShardTimeoutSeconds int `json:",omitempty"` 60 ReplicationLagQuery string `json:",omitempty"` 61 FailPrimaryPromotionOnLagMinutes int `json:",omitempty"` 62 } 63 64 // ToJSONString will marshal this configuration as JSON 65 func (config *VTOrcConfiguration) ToJSONString() string { 66 b, _ := json.MarshalIndent(config, "", "\t") 67 return string(b) 68 } 69 70 func (config *VTOrcConfiguration) AddDefaults(webPort int) { 71 config.Debug = true 72 config.MySQLTopologyUser = "orc_client_user" 73 config.MySQLTopologyPassword = "orc_client_user_password" 74 config.MySQLReplicaUser = "vt_repl" 75 config.MySQLReplicaPassword = "" 76 if config.RecoveryPeriodBlockSeconds == 0 { 77 config.RecoveryPeriodBlockSeconds = 1 78 } 79 config.ListenAddress = fmt.Sprintf(":%d", webPort) 80 } 81 82 // Setup starts orc process with required arguements 83 func (orc *VTOrcProcess) Setup() (err error) { 84 85 // create the configuration file 86 timeNow := time.Now().UnixNano() 87 configFile, _ := os.Create(path.Join(orc.LogDir, fmt.Sprintf("orc-config-%d.json", timeNow))) 88 orc.ConfigPath = configFile.Name() 89 90 // Add the default configurations and print them out 91 orc.Config.AddDefaults(orc.WebPort) 92 log.Errorf("configuration - %v", orc.Config.ToJSONString()) 93 _, err = configFile.WriteString(orc.Config.ToJSONString()) 94 if err != nil { 95 return err 96 } 97 err = configFile.Close() 98 if err != nil { 99 return err 100 } 101 102 /* minimal command line arguments: 103 $ vtorc --topo_implementation etcd2 --topo_global_server_address localhost:2379 --topo_global_root /vitess/global 104 --config config/vtorc/default.json --alsologtostderr 105 */ 106 orc.proc = exec.Command( 107 orc.Binary, 108 "--topo_implementation", orc.TopoImplementation, 109 "--topo_global_server_address", orc.TopoGlobalAddress, 110 "--topo_global_root", orc.TopoGlobalRoot, 111 "--config", orc.ConfigPath, 112 "--port", fmt.Sprintf("%d", orc.Port), 113 // This parameter is overriden from the config file, added here to just verify that we indeed use the config file paramter over the flag 114 "--recovery-period-block-duration", "10h", 115 "--instance-poll-time", "1s", 116 // Faster topo information refresh speeds up the tests. This doesn't add any significant load either 117 "--topo-information-refresh-duration", "3s", 118 "--orc_web_dir", path.Join(os.Getenv("VTROOT"), "web", "vtorc"), 119 ) 120 if *isCoverage { 121 orc.proc.Args = append(orc.proc.Args, "--test.coverprofile="+getCoveragePath("orc.out")) 122 } 123 124 orc.proc.Args = append(orc.proc.Args, orc.ExtraArgs...) 125 orc.proc.Args = append(orc.proc.Args, "--alsologtostderr") 126 127 errFile, _ := os.Create(path.Join(orc.LogDir, fmt.Sprintf("orc-stderr-%d.txt", timeNow))) 128 orc.proc.Stderr = errFile 129 130 orc.proc.Env = append(orc.proc.Env, os.Environ()...) 131 132 log.Infof("Running vtorc with command: %v", strings.Join(orc.proc.Args, " ")) 133 134 err = orc.proc.Start() 135 if err != nil { 136 return 137 } 138 139 orc.exit = make(chan error) 140 go func() { 141 if orc.proc != nil { 142 orc.exit <- orc.proc.Wait() 143 close(orc.exit) 144 } 145 }() 146 147 return nil 148 } 149 150 // TearDown shuts down the running vtorc service 151 func (orc *VTOrcProcess) TearDown() error { 152 if orc.proc == nil || orc.exit == nil { 153 return nil 154 } 155 // Attempt graceful shutdown with SIGTERM first 156 _ = orc.proc.Process.Signal(syscall.SIGTERM) 157 158 select { 159 case <-orc.exit: 160 orc.proc = nil 161 return nil 162 163 case <-time.After(30 * time.Second): 164 _ = orc.proc.Process.Kill() 165 err := <-orc.exit 166 orc.proc = nil 167 return err 168 } 169 } 170 171 // GetVars gets the variables exported on the /debug/vars page of VTOrc 172 func (orc *VTOrcProcess) GetVars() map[string]any { 173 varsURL := fmt.Sprintf("http://localhost:%d/debug/vars", orc.Port) 174 resp, err := http.Get(varsURL) 175 if err != nil { 176 return nil 177 } 178 defer resp.Body.Close() 179 180 if resp.StatusCode == 200 { 181 resultMap := make(map[string]any) 182 respByte, _ := io.ReadAll(resp.Body) 183 err := json.Unmarshal(respByte, &resultMap) 184 if err != nil { 185 return nil 186 } 187 return resultMap 188 } 189 return nil 190 } 191 192 // MakeAPICall makes an API call on the given endpoint of VTOrc 193 func (orc *VTOrcProcess) MakeAPICall(endpoint string) (status int, response string, err error) { 194 url := fmt.Sprintf("http://localhost:%d/%s", orc.Port, endpoint) 195 resp, err := http.Get(url) 196 if err != nil { 197 if resp != nil { 198 status = resp.StatusCode 199 } 200 return status, "", err 201 } 202 defer func() { 203 if resp != nil && resp.Body != nil { 204 resp.Body.Close() 205 } 206 }() 207 208 respByte, _ := io.ReadAll(resp.Body) 209 return resp.StatusCode, string(respByte), err 210 } 211 212 // MakeAPICallRetry is used to make an API call and retries until success 213 func (orc *VTOrcProcess) MakeAPICallRetry(t *testing.T, url string) { 214 t.Helper() 215 timeout := time.After(10 * time.Second) 216 for { 217 select { 218 case <-timeout: 219 t.Fatal("timed out waiting for api to work") 220 return 221 default: 222 status, _, err := orc.MakeAPICall(url) 223 if err == nil && status == 200 { 224 return 225 } 226 time.Sleep(1 * time.Second) 227 } 228 } 229 } 230 231 // DisableGlobalRecoveries stops VTOrc from running any recoveries 232 func (orc *VTOrcProcess) DisableGlobalRecoveries(t *testing.T) { 233 orc.MakeAPICallRetry(t, "/api/disable-global-recoveries") 234 } 235 236 // EnableGlobalRecoveries allows VTOrc to run any recoveries 237 func (orc *VTOrcProcess) EnableGlobalRecoveries(t *testing.T) { 238 orc.MakeAPICallRetry(t, "/api/enable-global-recoveries") 239 }