vitess.io/vitess@v0.16.2/go/cmd/vtctldclient/command/validate.go (about)

     1  /*
     2  Copyright 2021 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 command
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"github.com/spf13/cobra"
    25  
    26  	"vitess.io/vitess/go/cmd/vtctldclient/cli"
    27  	"vitess.io/vitess/go/vt/topo/topoproto"
    28  
    29  	vtctldatapb "vitess.io/vitess/go/vt/proto/vtctldata"
    30  )
    31  
    32  var (
    33  	// Validate makes a Validate gRPC call to a vtctld.
    34  	Validate = &cobra.Command{
    35  		Use:                   "Validate [--ping-tablets]",
    36  		Short:                 "Validates that all nodes reachable from the global replication graph, as well as all tablets in discoverable cells, are consistent.",
    37  		DisableFlagsInUseLine: true,
    38  		Args:                  cobra.NoArgs,
    39  		RunE:                  commandValidate,
    40  	}
    41  	// ValidateKeyspace makes a ValidateKeyspace gRPC call to a vtctld.
    42  	ValidateKeyspace = &cobra.Command{
    43  		Use:                   "ValidateKeyspace [--ping-tablets] <keyspace>",
    44  		Short:                 "Validates that all nodes reachable from the specified keyspace are consistent.",
    45  		DisableFlagsInUseLine: true,
    46  		Args:                  cobra.ExactArgs(1),
    47  		RunE:                  commandValidateKeyspace,
    48  	}
    49  	// ValidateShard makes a ValidateShard gRPC call to a vtctld.
    50  	ValidateShard = &cobra.Command{
    51  		Use:                   "ValidateShard [--ping-tablets] <keyspace/shard>",
    52  		Short:                 "Validates that all nodes reachable from the specified shard are consistent.",
    53  		DisableFlagsInUseLine: true,
    54  		Args:                  cobra.ExactArgs(1),
    55  		RunE:                  commandValidateShard,
    56  	}
    57  )
    58  
    59  var validateOptions = struct {
    60  	PingTablets bool
    61  }{}
    62  
    63  func commandValidate(cmd *cobra.Command, args []string) error {
    64  	cli.FinishedParsing(cmd)
    65  
    66  	resp, err := client.Validate(commandCtx, &vtctldatapb.ValidateRequest{
    67  		PingTablets: validateOptions.PingTablets,
    68  	})
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	buf := &strings.Builder{}
    74  	if err := consumeValidationResults(resp, buf); err != nil {
    75  		fmt.Printf("Validation results:\n%s", buf.String() /* note: this should have a trailing newline already */)
    76  		return err
    77  	}
    78  
    79  	fmt.Println("Validation complete; no issues found.")
    80  	return nil
    81  }
    82  
    83  var validateKeyspaceOptions = struct {
    84  	PingTablets bool
    85  }{}
    86  
    87  func commandValidateKeyspace(cmd *cobra.Command, args []string) error {
    88  	cli.FinishedParsing(cmd)
    89  
    90  	keyspace := cmd.Flags().Arg(0)
    91  	resp, err := client.ValidateKeyspace(commandCtx, &vtctldatapb.ValidateKeyspaceRequest{
    92  		Keyspace:    keyspace,
    93  		PingTablets: validateKeyspaceOptions.PingTablets,
    94  	})
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	buf := &strings.Builder{}
   100  	if err := consumeKeyspaceValidationResults(keyspace, resp, buf); err != nil {
   101  		fmt.Printf("Validation results:\n%s", buf.String() /* note: this should have a trailing newline already */)
   102  		return err
   103  	}
   104  
   105  	fmt.Printf("Validation of %s complete; no issues found.\n", keyspace)
   106  	return nil
   107  }
   108  
   109  var validateShardOptions = struct {
   110  	PingTablets bool
   111  }{}
   112  
   113  func commandValidateShard(cmd *cobra.Command, args []string) error {
   114  	keyspace, shard, err := topoproto.ParseKeyspaceShard(cmd.Flags().Arg(0))
   115  	if err != nil {
   116  		return fmt.Errorf("could not parse <keyspace/shard> from %s: %w", cmd.Flags().Arg(0), err)
   117  	}
   118  
   119  	cli.FinishedParsing(cmd)
   120  
   121  	resp, err := client.ValidateShard(commandCtx, &vtctldatapb.ValidateShardRequest{
   122  		Keyspace:    keyspace,
   123  		Shard:       shard,
   124  		PingTablets: validateShardOptions.PingTablets,
   125  	})
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	buf := &strings.Builder{}
   131  	if err := consumeShardValidationResults(keyspace, shard, resp, buf); err != nil {
   132  		fmt.Printf("Validation results:\n%s", buf.String() /* note: this should have a trailing newline already */)
   133  		return err
   134  	}
   135  
   136  	fmt.Printf("Validation of %s/%s complete; no issues found.\n", keyspace, shard)
   137  	return nil
   138  }
   139  
   140  func consumeValidationResults(resp *vtctldatapb.ValidateResponse, buf *strings.Builder) error {
   141  	for _, result := range resp.Results {
   142  		fmt.Fprintf(buf, "- %s\n", result)
   143  	}
   144  
   145  	for keyspace, keyspaceResults := range resp.ResultsByKeyspace {
   146  		buf2 := &strings.Builder{}
   147  		if err := consumeKeyspaceValidationResults(keyspace, keyspaceResults, buf2); err != nil {
   148  			fmt.Fprintf(buf, "\nFor %s:\n%s", keyspace, buf2.String() /* note: this should have a trailing newline */)
   149  		}
   150  	}
   151  
   152  	if buf.Len() > 0 {
   153  		return errors.New("some issues were found during validation; see above for details")
   154  	}
   155  
   156  	return nil
   157  }
   158  
   159  func consumeKeyspaceValidationResults(keyspace string, resp *vtctldatapb.ValidateKeyspaceResponse, buf *strings.Builder) error {
   160  	for _, result := range resp.Results {
   161  		fmt.Fprintf(buf, "- %s\n", result)
   162  	}
   163  
   164  	for shard, shardResults := range resp.ResultsByShard {
   165  		if len(shardResults.Results) == 0 {
   166  			continue
   167  		}
   168  
   169  		fmt.Fprintf(buf, "\nFor %s/%s:\n", keyspace, shard)
   170  		_ = consumeShardValidationResults(keyspace, shard, shardResults, buf)
   171  	}
   172  
   173  	if buf.Len() > 0 {
   174  		return fmt.Errorf("keyspace %s had validation issues; see above for details", keyspace)
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  func consumeShardValidationResults(keyspace string, shard string, resp *vtctldatapb.ValidateShardResponse, buf *strings.Builder) error {
   181  	for _, result := range resp.Results {
   182  		fmt.Fprintf(buf, "- %s\n", result)
   183  	}
   184  
   185  	if buf.Len() > 0 {
   186  		return fmt.Errorf("shard %s/%s had validation issues; see above for details", keyspace, shard)
   187  	}
   188  
   189  	return nil
   190  }
   191  
   192  func init() {
   193  	pingTabletsName := "ping-tablets"
   194  	pingTabletsShort := "p"
   195  	pingTabletsDefault := false
   196  	pingTabletsUsage := "Indicates whether all tablets should be pinged during the validation process."
   197  
   198  	Validate.Flags().BoolVarP(&validateOptions.PingTablets, pingTabletsName, pingTabletsShort, pingTabletsDefault, pingTabletsUsage)
   199  	ValidateKeyspace.Flags().BoolVarP(&validateKeyspaceOptions.PingTablets, pingTabletsName, pingTabletsShort, pingTabletsDefault, pingTabletsUsage)
   200  	ValidateShard.Flags().BoolVarP(&validateShardOptions.PingTablets, pingTabletsName, pingTabletsShort, pingTabletsDefault, pingTabletsUsage)
   201  
   202  	Root.AddCommand(Validate)
   203  	Root.AddCommand(ValidateKeyspace)
   204  	Root.AddCommand(ValidateShard)
   205  }