ep_command.go 6.4 KB

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