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  }