go.etcd.io/etcd@v3.3.27+incompatible/etcdctl/ctlv3/command/ep_command.go (about)

     1  // Copyright 2015 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package command
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"sync"
    21  	"time"
    22  
    23  	v3 "github.com/coreos/etcd/clientv3"
    24  	"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
    25  	"github.com/coreos/etcd/pkg/flags"
    26  
    27  	"github.com/spf13/cobra"
    28  )
    29  
    30  var epClusterEndpoints bool
    31  var epHashKVRev int64
    32  
    33  // NewEndpointCommand returns the cobra command for "endpoint".
    34  func NewEndpointCommand() *cobra.Command {
    35  	ec := &cobra.Command{
    36  		Use:   "endpoint <subcommand>",
    37  		Short: "Endpoint related commands",
    38  	}
    39  
    40  	ec.PersistentFlags().BoolVar(&epClusterEndpoints, "cluster", false, "use all endpoints from the cluster member list")
    41  	ec.AddCommand(newEpHealthCommand())
    42  	ec.AddCommand(newEpStatusCommand())
    43  	ec.AddCommand(newEpHashKVCommand())
    44  
    45  	return ec
    46  }
    47  
    48  func newEpHealthCommand() *cobra.Command {
    49  	cmd := &cobra.Command{
    50  		Use:   "health",
    51  		Short: "Checks the healthiness of endpoints specified in `--endpoints` flag",
    52  		Run:   epHealthCommandFunc,
    53  	}
    54  
    55  	return cmd
    56  }
    57  
    58  func newEpStatusCommand() *cobra.Command {
    59  	return &cobra.Command{
    60  		Use:   "status",
    61  		Short: "Prints out the status of endpoints specified in `--endpoints` flag",
    62  		Long: `When --write-out is set to simple, this command prints out comma-separated status lists for each endpoint.
    63  The items in the lists are endpoint, ID, version, db size, is leader, raft term, raft index.
    64  `,
    65  		Run: epStatusCommandFunc,
    66  	}
    67  }
    68  
    69  func newEpHashKVCommand() *cobra.Command {
    70  	hc := &cobra.Command{
    71  		Use:   "hashkv",
    72  		Short: "Prints the KV history hash for each endpoint in --endpoints",
    73  		Run:   epHashKVCommandFunc,
    74  	}
    75  	hc.PersistentFlags().Int64Var(&epHashKVRev, "rev", 0, "maximum revision to hash (default: all revisions)")
    76  	return hc
    77  }
    78  
    79  type epHealth struct {
    80  	Ep     string `json:"endpoint"`
    81  	Health bool   `json:"health"`
    82  	Took   string `json:"took"`
    83  	Error  string `json:"error,omitempty"`
    84  }
    85  
    86  // epHealthCommandFunc executes the "endpoint-health" command.
    87  func epHealthCommandFunc(cmd *cobra.Command, args []string) {
    88  	flags.SetPflagsFromEnv("ETCDCTL", cmd.InheritedFlags())
    89  	initDisplayFromCmd(cmd)
    90  
    91  	sec := secureCfgFromCmd(cmd)
    92  	dt := dialTimeoutFromCmd(cmd)
    93  	ka := keepAliveTimeFromCmd(cmd)
    94  	kat := keepAliveTimeoutFromCmd(cmd)
    95  	auth := authCfgFromCmd(cmd)
    96  	cfgs := []*v3.Config{}
    97  	for _, ep := range endpointsFromCluster(cmd) {
    98  		cfg, err := newClientCfg([]string{ep}, dt, ka, kat, sec, auth)
    99  		if err != nil {
   100  			ExitWithError(ExitBadArgs, err)
   101  		}
   102  		cfgs = append(cfgs, cfg)
   103  	}
   104  
   105  	var wg sync.WaitGroup
   106  	hch := make(chan epHealth, len(cfgs))
   107  	for _, cfg := range cfgs {
   108  		wg.Add(1)
   109  		go func(cfg *v3.Config) {
   110  			defer wg.Done()
   111  			ep := cfg.Endpoints[0]
   112  			cli, err := v3.New(*cfg)
   113  			if err != nil {
   114  				hch <- epHealth{Ep: ep, Health: false, Error: err.Error()}
   115  				return
   116  			}
   117  			st := time.Now()
   118  			// get a random key. As long as we can get the response without an error, the
   119  			// endpoint is health.
   120  			ctx, cancel := commandCtx(cmd)
   121  			_, err = cli.Get(ctx, "health")
   122  			cancel()
   123  			eh := epHealth{Ep: ep, Health: false, Took: time.Since(st).String()}
   124  			// permission denied is OK since proposal goes through consensus to get it
   125  			if err == nil || err == rpctypes.ErrPermissionDenied {
   126  				eh.Health = true
   127  			} else {
   128  				eh.Error = err.Error()
   129  			}
   130  			hch <- eh
   131  		}(cfg)
   132  	}
   133  
   134  	wg.Wait()
   135  	close(hch)
   136  
   137  	errs := false
   138  	healthList := []epHealth{}
   139  	for h := range hch {
   140  		healthList = append(healthList, h)
   141  		if h.Error != "" {
   142  			errs = true
   143  		}
   144  	}
   145  	display.EndpointHealth(healthList)
   146  	if errs {
   147  		ExitWithError(ExitError, fmt.Errorf("unhealthy cluster"))
   148  	}
   149  }
   150  
   151  type epStatus struct {
   152  	Ep   string             `json:"Endpoint"`
   153  	Resp *v3.StatusResponse `json:"Status"`
   154  }
   155  
   156  func epStatusCommandFunc(cmd *cobra.Command, args []string) {
   157  	c := mustClientFromCmd(cmd)
   158  
   159  	statusList := []epStatus{}
   160  	var err error
   161  	for _, ep := range endpointsFromCluster(cmd) {
   162  		ctx, cancel := commandCtx(cmd)
   163  		resp, serr := c.Status(ctx, ep)
   164  		cancel()
   165  		if serr != nil {
   166  			err = serr
   167  			fmt.Fprintf(os.Stderr, "Failed to get the status of endpoint %s (%v)\n", ep, serr)
   168  			continue
   169  		}
   170  		statusList = append(statusList, epStatus{Ep: ep, Resp: resp})
   171  	}
   172  
   173  	display.EndpointStatus(statusList)
   174  
   175  	if err != nil {
   176  		os.Exit(ExitError)
   177  	}
   178  }
   179  
   180  type epHashKV struct {
   181  	Ep   string             `json:"Endpoint"`
   182  	Resp *v3.HashKVResponse `json:"HashKV"`
   183  }
   184  
   185  func epHashKVCommandFunc(cmd *cobra.Command, args []string) {
   186  	c := mustClientFromCmd(cmd)
   187  
   188  	hashList := []epHashKV{}
   189  	var err error
   190  	for _, ep := range endpointsFromCluster(cmd) {
   191  		ctx, cancel := commandCtx(cmd)
   192  		resp, serr := c.HashKV(ctx, ep, epHashKVRev)
   193  		cancel()
   194  		if serr != nil {
   195  			err = serr
   196  			fmt.Fprintf(os.Stderr, "Failed to get the hash of endpoint %s (%v)\n", ep, serr)
   197  			continue
   198  		}
   199  		hashList = append(hashList, epHashKV{Ep: ep, Resp: resp})
   200  	}
   201  
   202  	display.EndpointHashKV(hashList)
   203  
   204  	if err != nil {
   205  		ExitWithError(ExitError, err)
   206  	}
   207  }
   208  
   209  func endpointsFromCluster(cmd *cobra.Command) []string {
   210  	if !epClusterEndpoints {
   211  		endpoints, err := cmd.Flags().GetStringSlice("endpoints")
   212  		if err != nil {
   213  			ExitWithError(ExitError, err)
   214  		}
   215  		return endpoints
   216  	}
   217  
   218  	sec := secureCfgFromCmd(cmd)
   219  	dt := dialTimeoutFromCmd(cmd)
   220  	ka := keepAliveTimeFromCmd(cmd)
   221  	kat := keepAliveTimeoutFromCmd(cmd)
   222  	eps, err := endpointsFromCmd(cmd)
   223  	if err != nil {
   224  		ExitWithError(ExitError, err)
   225  	}
   226  	// exclude auth for not asking needless password (MemberList() doesn't need authentication)
   227  
   228  	cfg, err := newClientCfg(eps, dt, ka, kat, sec, nil)
   229  	if err != nil {
   230  		ExitWithError(ExitError, err)
   231  	}
   232  	c, err := v3.New(*cfg)
   233  	if err != nil {
   234  		ExitWithError(ExitError, err)
   235  	}
   236  
   237  	ctx, cancel := commandCtx(cmd)
   238  	defer func() {
   239  		c.Close()
   240  		cancel()
   241  	}()
   242  	membs, err := c.MemberList(ctx)
   243  	if err != nil {
   244  		err = fmt.Errorf("failed to fetch endpoints from etcd cluster member list: %v", err)
   245  		ExitWithError(ExitError, err)
   246  	}
   247  
   248  	ret := []string{}
   249  	for _, m := range membs.Members {
   250  		ret = append(ret, m.ClientURLs...)
   251  	}
   252  	return ret
   253  }