vitess.io/vitess@v0.16.2/go/test/endtoend/cluster/vtctlclient_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  	"fmt"
    21  	"os/exec"
    22  	"strings"
    23  	"time"
    24  
    25  	"vitess.io/vitess/go/vt/vterrors"
    26  
    27  	"vitess.io/vitess/go/vt/log"
    28  )
    29  
    30  // VtctlClientProcess is a generic handle for a running vtctlclient command .
    31  // It can be spawned manually
    32  type VtctlClientProcess struct {
    33  	Name                    string
    34  	Binary                  string
    35  	Server                  string
    36  	TempDirectory           string
    37  	ZoneName                string
    38  	VtctlClientMajorVersion int
    39  }
    40  
    41  // VtctlClientParams encapsulated params to provide if non-default
    42  type VtctlClientParams struct {
    43  	DDLStrategy      string
    44  	MigrationContext string
    45  	SkipPreflight    bool
    46  	UUIDList         string
    47  	CallerID         string
    48  }
    49  
    50  // InitShardPrimary executes vtctlclient command to make specified tablet the primary for the shard.
    51  func (vtctlclient *VtctlClientProcess) InitShardPrimary(Keyspace string, Shard string, Cell string, TabletUID int) (err error) {
    52  	output, err := vtctlclient.ExecuteCommandWithOutput(
    53  		"InitShardPrimary", "--",
    54  		"--force", "--wait_replicas_timeout", "31s",
    55  		fmt.Sprintf("%s/%s", Keyspace, Shard),
    56  		fmt.Sprintf("%s-%d", Cell, TabletUID))
    57  	if err != nil {
    58  		log.Errorf("error in InitShardPrimary output %s, err %s", output, err.Error())
    59  	}
    60  	return err
    61  }
    62  
    63  // InitializeShard executes vtctlclient command to make specified tablet the primary for the shard.
    64  func (vtctlclient *VtctlClientProcess) InitializeShard(Keyspace string, Shard string, Cell string, TabletUID int) (err error) {
    65  	output, err := vtctlclient.ExecuteCommandWithOutput(
    66  		"PlannedReparentShard", "--",
    67  		"--keyspace_shard", fmt.Sprintf("%s/%s", Keyspace, Shard),
    68  		"--wait_replicas_timeout", "31s",
    69  		"--new_primary", fmt.Sprintf("%s-%d", Cell, TabletUID))
    70  	if err != nil {
    71  		log.Errorf("error in PlannedReparentShard output %s, err %s", output, err.Error())
    72  	}
    73  	return err
    74  }
    75  
    76  // ApplySchemaWithOutput applies SQL schema to the keyspace
    77  func (vtctlclient *VtctlClientProcess) ApplySchemaWithOutput(Keyspace string, SQL string, params VtctlClientParams) (result string, err error) {
    78  	args := []string{
    79  		"ApplySchema", "--",
    80  		"--sql", SQL,
    81  	}
    82  	if params.MigrationContext != "" {
    83  		args = append(args, "--migration_context", params.MigrationContext)
    84  	}
    85  	if params.DDLStrategy != "" {
    86  		args = append(args, "--ddl_strategy", params.DDLStrategy)
    87  	}
    88  	if params.UUIDList != "" {
    89  		args = append(args, "--uuid_list", params.UUIDList)
    90  	}
    91  	if params.SkipPreflight {
    92  		args = append(args, "--skip_preflight")
    93  	}
    94  
    95  	if params.CallerID != "" {
    96  		args = append(args, "--caller_id", params.CallerID)
    97  	}
    98  	args = append(args, Keyspace)
    99  	return vtctlclient.ExecuteCommandWithOutput(args...)
   100  }
   101  
   102  // ApplySchema applies SQL schema to the keyspace
   103  func (vtctlclient *VtctlClientProcess) ApplySchema(Keyspace string, SQL string) error {
   104  	message, err := vtctlclient.ApplySchemaWithOutput(Keyspace, SQL, VtctlClientParams{DDLStrategy: "direct -allow-zero-in-date", SkipPreflight: true})
   105  
   106  	return vterrors.Wrap(err, message)
   107  }
   108  
   109  // ApplyVSchema applies vitess schema (JSON format) to the keyspace
   110  func (vtctlclient *VtctlClientProcess) ApplyVSchema(Keyspace string, JSON string) (err error) {
   111  	return vtctlclient.ExecuteCommand(
   112  		"ApplyVSchema", "--",
   113  		"--vschema", JSON,
   114  		Keyspace,
   115  	)
   116  }
   117  
   118  // ApplyRoutingRules does it
   119  func (vtctlclient *VtctlClientProcess) ApplyRoutingRules(JSON string) (err error) {
   120  	return vtctlclient.ExecuteCommand("ApplyRoutingRules", "--", "--rules", JSON)
   121  }
   122  
   123  // ApplyRoutingRules does it
   124  func (vtctlclient *VtctlClientProcess) ApplyShardRoutingRules(JSON string) (err error) {
   125  	return vtctlclient.ExecuteCommand("ApplyShardRoutingRules", "--", "--rules", JSON)
   126  }
   127  
   128  // OnlineDDLShowRecent responds with recent schema migration list
   129  func (vtctlclient *VtctlClientProcess) OnlineDDLShowRecent(Keyspace string) (result string, err error) {
   130  	return vtctlclient.ExecuteCommandWithOutput(
   131  		"OnlineDDL",
   132  		Keyspace,
   133  		"show",
   134  		"recent",
   135  	)
   136  }
   137  
   138  // OnlineDDLCancelMigration cancels a given migration uuid
   139  func (vtctlclient *VtctlClientProcess) OnlineDDLCancelMigration(Keyspace, uuid string) (result string, err error) {
   140  	return vtctlclient.ExecuteCommandWithOutput(
   141  		"OnlineDDL",
   142  		Keyspace,
   143  		"cancel",
   144  		uuid,
   145  	)
   146  }
   147  
   148  // OnlineDDLCancelAllMigrations cancels all migrations for a keyspace
   149  func (vtctlclient *VtctlClientProcess) OnlineDDLCancelAllMigrations(Keyspace string) (result string, err error) {
   150  	return vtctlclient.ExecuteCommandWithOutput(
   151  		"OnlineDDL",
   152  		Keyspace,
   153  		"cancel-all",
   154  	)
   155  }
   156  
   157  // OnlineDDLRetryMigration retries a given migration uuid
   158  func (vtctlclient *VtctlClientProcess) OnlineDDLRetryMigration(Keyspace, uuid string) (result string, err error) {
   159  	return vtctlclient.ExecuteCommandWithOutput(
   160  		"OnlineDDL",
   161  		Keyspace,
   162  		"retry",
   163  		uuid,
   164  	)
   165  }
   166  
   167  // OnlineDDLRevertMigration reverts a given migration uuid
   168  func (vtctlclient *VtctlClientProcess) OnlineDDLRevertMigration(Keyspace, uuid string) (result string, err error) {
   169  	return vtctlclient.ExecuteCommandWithOutput(
   170  		"OnlineDDL",
   171  		Keyspace,
   172  		"revert",
   173  		uuid,
   174  	)
   175  }
   176  
   177  // VExec runs a VExec query
   178  func (vtctlclient *VtctlClientProcess) VExec(Keyspace, workflow, query string) (result string, err error) {
   179  	return vtctlclient.ExecuteCommandWithOutput(
   180  		"VExec",
   181  		fmt.Sprintf("%s.%s", Keyspace, workflow),
   182  		query,
   183  	)
   184  }
   185  
   186  // ExecuteCommand executes any vtctlclient command
   187  func (vtctlclient *VtctlClientProcess) ExecuteCommand(args ...string) (err error) {
   188  	output, err := vtctlclient.ExecuteCommandWithOutput(args...)
   189  	if output != "" {
   190  		if err != nil {
   191  			log.Errorf("Output:\n%v", output)
   192  		}
   193  	}
   194  	return err
   195  }
   196  
   197  // ExecuteCommandWithOutput executes any vtctlclient command and returns output
   198  func (vtctlclient *VtctlClientProcess) ExecuteCommandWithOutput(args ...string) (string, error) {
   199  	var resultByte []byte
   200  	var resultStr string
   201  	var err error
   202  	retries := 10
   203  	retryDelay := 1 * time.Second
   204  	pArgs := []string{"--server", vtctlclient.Server}
   205  	if *isCoverage {
   206  		pArgs = append(pArgs, "--test.coverprofile="+getCoveragePath("vtctlclient-"+args[0]+".out"), "--test.v")
   207  	}
   208  	pArgs = append(pArgs, args...)
   209  	for i := 1; i <= retries; i++ {
   210  		tmpProcess := exec.Command(
   211  			vtctlclient.Binary,
   212  			filterDoubleDashArgs(pArgs, vtctlclient.VtctlClientMajorVersion)...,
   213  		)
   214  		log.Infof("Executing vtctlclient with command: %v (attempt %d of %d)", strings.Join(tmpProcess.Args, " "), i, retries)
   215  		resultByte, err = tmpProcess.CombinedOutput()
   216  		resultStr = string(resultByte)
   217  		if err == nil || !shouldRetry(resultStr) {
   218  			break
   219  		}
   220  		time.Sleep(retryDelay)
   221  	}
   222  	return filterResultWhenRunsForCoverage(resultStr), err
   223  }
   224  
   225  // VtctlClientProcessInstance returns a VtctlProcess handle for vtctlclient process
   226  // configured with the given Config.
   227  func VtctlClientProcessInstance(hostname string, grpcPort int, tmpDirectory string) *VtctlClientProcess {
   228  	version, err := GetMajorVersion("vtctl") // `vtctlclient` does not have a --version flag, so we assume both vtctl/vtctlclient have the same version
   229  	if err != nil {
   230  		log.Warningf("failed to get major vtctlclient version; interop with CLI changes for VEP-4 may not work: %s", err)
   231  	}
   232  
   233  	vtctlclient := &VtctlClientProcess{
   234  		Name:                    "vtctlclient",
   235  		Binary:                  "vtctlclient",
   236  		Server:                  fmt.Sprintf("%s:%d", hostname, grpcPort),
   237  		TempDirectory:           tmpDirectory,
   238  		VtctlClientMajorVersion: version,
   239  	}
   240  	return vtctlclient
   241  }
   242  
   243  // InitTablet initializes a tablet
   244  func (vtctlclient *VtctlClientProcess) InitTablet(tablet *Vttablet, cell string, keyspaceName string, hostname string, shardName string) error {
   245  	tabletType := "replica"
   246  	if tablet.Type == "rdonly" {
   247  		tabletType = "rdonly"
   248  	}
   249  	args := []string{"InitTablet", "--", "--hostname", hostname,
   250  		"--port", fmt.Sprintf("%d", tablet.HTTPPort), "--allow_update", "--parent",
   251  		"--keyspace", keyspaceName,
   252  		"--shard", shardName}
   253  	if tablet.MySQLPort > 0 {
   254  		args = append(args, "--mysql_port", fmt.Sprintf("%d", tablet.MySQLPort))
   255  	}
   256  	if tablet.GrpcPort > 0 {
   257  		args = append(args, "--grpc_port", fmt.Sprintf("%d", tablet.GrpcPort))
   258  	}
   259  	args = append(args, fmt.Sprintf("%s-%010d", cell, tablet.TabletUID), tabletType)
   260  	return vtctlclient.ExecuteCommand(args...)
   261  }
   262  
   263  // shouldRetry tells us if the command should be retried based on the results/output -- meaning that it
   264  // is likely an ephemeral or recoverable issue that is likely to succeed when retried.
   265  func shouldRetry(cmdResults string) bool {
   266  	return strings.Contains(cmdResults, "Deadlock found when trying to get lock; try restarting transaction")
   267  }