member_command.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. // Copyright 2016 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. "errors"
  17. "fmt"
  18. "strconv"
  19. "strings"
  20. "github.com/spf13/cobra"
  21. "go.etcd.io/etcd/clientv3"
  22. )
  23. var (
  24. memberPeerURLs string
  25. isLearner bool
  26. )
  27. // NewMemberCommand returns the cobra command for "member".
  28. func NewMemberCommand() *cobra.Command {
  29. mc := &cobra.Command{
  30. Use: "member <subcommand>",
  31. Short: "Membership related commands",
  32. }
  33. mc.AddCommand(NewMemberAddCommand())
  34. mc.AddCommand(NewMemberRemoveCommand())
  35. mc.AddCommand(NewMemberUpdateCommand())
  36. mc.AddCommand(NewMemberListCommand())
  37. mc.AddCommand(NewMemberPromoteCommand())
  38. return mc
  39. }
  40. // NewMemberAddCommand returns the cobra command for "member add".
  41. func NewMemberAddCommand() *cobra.Command {
  42. cc := &cobra.Command{
  43. Use: "add <memberName> [options]",
  44. Short: "Adds a member into the cluster",
  45. Run: memberAddCommandFunc,
  46. }
  47. cc.Flags().StringVar(&memberPeerURLs, "peer-urls", "", "comma separated peer URLs for the new member.")
  48. cc.Flags().BoolVar(&isLearner, "learner", false, "indicates if the new member is raft learner")
  49. return cc
  50. }
  51. // NewMemberRemoveCommand returns the cobra command for "member remove".
  52. func NewMemberRemoveCommand() *cobra.Command {
  53. cc := &cobra.Command{
  54. Use: "remove <memberID>",
  55. Short: "Removes a member from the cluster",
  56. Run: memberRemoveCommandFunc,
  57. }
  58. return cc
  59. }
  60. // NewMemberUpdateCommand returns the cobra command for "member update".
  61. func NewMemberUpdateCommand() *cobra.Command {
  62. cc := &cobra.Command{
  63. Use: "update <memberID> [options]",
  64. Short: "Updates a member in the cluster",
  65. Run: memberUpdateCommandFunc,
  66. }
  67. cc.Flags().StringVar(&memberPeerURLs, "peer-urls", "", "comma separated peer URLs for the updated member.")
  68. return cc
  69. }
  70. // NewMemberListCommand returns the cobra command for "member list".
  71. func NewMemberListCommand() *cobra.Command {
  72. cc := &cobra.Command{
  73. Use: "list",
  74. Short: "Lists all members in the cluster",
  75. Long: `When --write-out is set to simple, this command prints out comma-separated member lists for each endpoint.
  76. The items in the lists are ID, Status, Name, Peer Addrs, Client Addrs, Is Learner.
  77. `,
  78. Run: memberListCommandFunc,
  79. }
  80. return cc
  81. }
  82. // NewMemberPromoteCommand returns the cobra command for "member promote".
  83. func NewMemberPromoteCommand() *cobra.Command {
  84. cc := &cobra.Command{
  85. Use: "promote <memberID>",
  86. Short: "Promotes a non-voting member in the cluster",
  87. Long: `Promotes a non-voting learner member to a voting one in the cluster.
  88. `,
  89. Run: memberPromoteCommandFunc,
  90. }
  91. return cc
  92. }
  93. // memberAddCommandFunc executes the "member add" command.
  94. func memberAddCommandFunc(cmd *cobra.Command, args []string) {
  95. if len(args) < 1 {
  96. ExitWithError(ExitBadArgs, errors.New("member name not provided"))
  97. }
  98. if len(args) > 1 {
  99. ev := "too many arguments"
  100. for _, s := range args {
  101. if strings.HasPrefix(strings.ToLower(s), "http") {
  102. ev += fmt.Sprintf(`, did you mean --peer-urls=%s`, s)
  103. }
  104. }
  105. ExitWithError(ExitBadArgs, errors.New(ev))
  106. }
  107. newMemberName := args[0]
  108. if len(memberPeerURLs) == 0 {
  109. ExitWithError(ExitBadArgs, errors.New("member peer urls not provided"))
  110. }
  111. urls := strings.Split(memberPeerURLs, ",")
  112. ctx, cancel := commandCtx(cmd)
  113. cli := mustClientFromCmd(cmd)
  114. var (
  115. resp *clientv3.MemberAddResponse
  116. err error
  117. )
  118. if isLearner {
  119. resp, err = cli.MemberAddAsLearner(ctx, urls)
  120. } else {
  121. resp, err = cli.MemberAdd(ctx, urls)
  122. }
  123. cancel()
  124. if err != nil {
  125. ExitWithError(ExitError, err)
  126. }
  127. newID := resp.Member.ID
  128. display.MemberAdd(*resp)
  129. if _, ok := (display).(*simplePrinter); ok {
  130. ctx, cancel = commandCtx(cmd)
  131. listResp, err := cli.MemberList(ctx)
  132. // make sure the member who served member list request has the latest member list.
  133. syncedMemberSet := make(map[uint64]struct{})
  134. syncedMemberSet[resp.Header.MemberId] = struct{}{} // the member who served member add is guaranteed to have the latest member list.
  135. for {
  136. if err != nil {
  137. ExitWithError(ExitError, err)
  138. }
  139. if _, ok := syncedMemberSet[listResp.Header.MemberId]; ok {
  140. break
  141. }
  142. // quorum get to sync cluster list
  143. gresp, gerr := cli.Get(ctx, "_")
  144. if gerr != nil {
  145. ExitWithError(ExitError, err)
  146. }
  147. syncedMemberSet[gresp.Header.MemberId] = struct{}{}
  148. listResp, err = cli.MemberList(ctx)
  149. }
  150. cancel()
  151. conf := []string{}
  152. for _, memb := range listResp.Members {
  153. for _, u := range memb.PeerURLs {
  154. n := memb.Name
  155. if memb.ID == newID {
  156. n = newMemberName
  157. }
  158. conf = append(conf, fmt.Sprintf("%s=%s", n, u))
  159. }
  160. }
  161. fmt.Print("\n")
  162. fmt.Printf("ETCD_NAME=%q\n", newMemberName)
  163. fmt.Printf("ETCD_INITIAL_CLUSTER=%q\n", strings.Join(conf, ","))
  164. fmt.Printf("ETCD_INITIAL_ADVERTISE_PEER_URLS=%q\n", memberPeerURLs)
  165. fmt.Printf("ETCD_INITIAL_CLUSTER_STATE=\"existing\"\n")
  166. }
  167. }
  168. // memberRemoveCommandFunc executes the "member remove" command.
  169. func memberRemoveCommandFunc(cmd *cobra.Command, args []string) {
  170. if len(args) != 1 {
  171. ExitWithError(ExitBadArgs, fmt.Errorf("member ID is not provided"))
  172. }
  173. id, err := strconv.ParseUint(args[0], 16, 64)
  174. if err != nil {
  175. ExitWithError(ExitBadArgs, fmt.Errorf("bad member ID arg (%v), expecting ID in Hex", err))
  176. }
  177. ctx, cancel := commandCtx(cmd)
  178. resp, err := mustClientFromCmd(cmd).MemberRemove(ctx, id)
  179. cancel()
  180. if err != nil {
  181. ExitWithError(ExitError, err)
  182. }
  183. display.MemberRemove(id, *resp)
  184. }
  185. // memberUpdateCommandFunc executes the "member update" command.
  186. func memberUpdateCommandFunc(cmd *cobra.Command, args []string) {
  187. if len(args) != 1 {
  188. ExitWithError(ExitBadArgs, fmt.Errorf("member ID is not provided"))
  189. }
  190. id, err := strconv.ParseUint(args[0], 16, 64)
  191. if err != nil {
  192. ExitWithError(ExitBadArgs, fmt.Errorf("bad member ID arg (%v), expecting ID in Hex", err))
  193. }
  194. if len(memberPeerURLs) == 0 {
  195. ExitWithError(ExitBadArgs, fmt.Errorf("member peer urls not provided"))
  196. }
  197. urls := strings.Split(memberPeerURLs, ",")
  198. ctx, cancel := commandCtx(cmd)
  199. resp, err := mustClientFromCmd(cmd).MemberUpdate(ctx, id, urls)
  200. cancel()
  201. if err != nil {
  202. ExitWithError(ExitError, err)
  203. }
  204. display.MemberUpdate(id, *resp)
  205. }
  206. // memberListCommandFunc executes the "member list" command.
  207. func memberListCommandFunc(cmd *cobra.Command, args []string) {
  208. ctx, cancel := commandCtx(cmd)
  209. resp, err := mustClientFromCmd(cmd).MemberList(ctx)
  210. cancel()
  211. if err != nil {
  212. ExitWithError(ExitError, err)
  213. }
  214. display.MemberList(*resp)
  215. }
  216. // memberPromoteCommandFunc executes the "member promote" command.
  217. func memberPromoteCommandFunc(cmd *cobra.Command, args []string) {
  218. if len(args) != 1 {
  219. ExitWithError(ExitBadArgs, fmt.Errorf("member ID is not provided"))
  220. }
  221. id, err := strconv.ParseUint(args[0], 16, 64)
  222. if err != nil {
  223. ExitWithError(ExitBadArgs, fmt.Errorf("bad member ID arg (%v), expecting ID in Hex", err))
  224. }
  225. ctx, cancel := commandCtx(cmd)
  226. resp, err := mustClientFromCmd(cmd).MemberPromote(ctx, id)
  227. cancel()
  228. if err != nil {
  229. ExitWithError(ExitError, err)
  230. }
  231. display.MemberPromote(id, *resp)
  232. }