ソースを参照

etcdctl/ctlv3: add 'move-leader' command

Signed-off-by: Gyu-Ho Lee <gyuhox@gmail.com>
Gyu-Ho Lee 8 年 前
コミット
6e9b776fce

+ 23 - 0
etcdctl/README.md

@@ -805,6 +805,29 @@ Prints a line of JSON encoding the database hash, revision, total keys, and size
 +----------+----------+------------+------------+
 ```
 
+### MOVE-LEADER \<hexadecimal-transferee-id\>
+
+MOVE-LEADER transfers leadership from the leader to another member in the cluster.
+
+#### Example
+
+```bash
+# to choose transferee
+transferee_id=$(./etcdctl \
+  --endpoints localhost:12379,localhost:22379,localhost:32379 \
+  endpoint status | grep -m 1 "false" | awk -F', ' '{print $2}')
+echo ${transferee_id}
+# c89feb932daef420
+
+# endpoints should include leader node
+./etcdctl --endpoints ${transferee_ep} move-leader ${transferee_id}
+# Error:  no leader endpoint given at [localhost:22379 localhost:32379]
+
+# request to leader with target node ID
+./etcdctl --endpoints ${leader_ep} move-leader ${transferee_id}
+# Leadership transferred from 45ddc0e800e20b93 to c89feb932daef420
+```
+
 ## Concurrency commands
 
 ### LOCK \<lockname\> [command arg1 arg2 ...]

+ 87 - 0
etcdctl/ctlv3/command/move_leader_command.go

@@ -0,0 +1,87 @@
+// Copyright 2017 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package command
+
+import (
+	"fmt"
+	"strconv"
+	"time"
+
+	"github.com/coreos/etcd/clientv3"
+	"github.com/spf13/cobra"
+)
+
+// NewMoveLeaderCommand returns the cobra command for "move-leader".
+func NewMoveLeaderCommand() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "move-leader <transferee-member-id>",
+		Short: "Transfers leadership to another etcd cluster member.",
+		Run:   transferLeadershipCommandFunc,
+	}
+	return cmd
+}
+
+// transferLeadershipCommandFunc executes the "compaction" command.
+func transferLeadershipCommandFunc(cmd *cobra.Command, args []string) {
+	if len(args) != 1 {
+		ExitWithError(ExitBadArgs, fmt.Errorf("move-leader command needs 1 argument"))
+	}
+	target, err := strconv.ParseUint(args[0], 16, 64)
+	if err != nil {
+		ExitWithError(ExitBadArgs, err)
+	}
+
+	c := mustClientFromCmd(cmd)
+	eps := c.Endpoints()
+	c.Close()
+
+	ctx, cancel := commandCtx(cmd)
+
+	// find current leader
+	var leaderCli *clientv3.Client
+	var leaderID uint64
+	for _, ep := range eps {
+		cli, err := clientv3.New(clientv3.Config{
+			Endpoints:   []string{ep},
+			DialTimeout: 3 * time.Second,
+		})
+		if err != nil {
+			ExitWithError(ExitError, err)
+		}
+		resp, err := cli.Status(ctx, ep)
+		if err != nil {
+			ExitWithError(ExitError, err)
+		}
+
+		if resp.Header.GetMemberId() == resp.Leader {
+			leaderCli = cli
+			leaderID = resp.Leader
+			break
+		}
+		cli.Close()
+	}
+	if leaderCli == nil {
+		ExitWithError(ExitBadArgs, fmt.Errorf("no leader endpoint given at %v", eps))
+	}
+
+	var resp *clientv3.MoveLeaderResponse
+	resp, err = leaderCli.MoveLeader(ctx, target)
+	cancel()
+	if err != nil {
+		ExitWithError(ExitError, err)
+	}
+
+	display.MoveLeader(leaderID, target, *resp)
+}

+ 6 - 1
etcdctl/ctlv3/command/printer.go

@@ -43,6 +43,7 @@ type printer interface {
 	MemberList(v3.MemberListResponse)
 
 	EndpointStatus([]epStatus)
+	MoveLeader(leader, target uint64, r v3.MoveLeaderResponse)
 
 	Alarm(v3.AlarmResponse)
 	DBStatus(dbstatus)
@@ -104,7 +105,9 @@ func (p *printerRPC) MemberUpdate(id uint64, r v3.MemberUpdateResponse) {
 }
 func (p *printerRPC) MemberList(r v3.MemberListResponse) { p.p((*pb.MemberListResponse)(&r)) }
 func (p *printerRPC) Alarm(r v3.AlarmResponse)           { p.p((*pb.AlarmResponse)(&r)) }
-
+func (p *printerRPC) MoveLeader(leader, target uint64, r v3.MoveLeaderResponse) {
+	p.p((*pb.MoveLeaderResponse)(&r))
+}
 func (p *printerRPC) RoleAdd(_ string, r v3.AuthRoleAddResponse) { p.p((*pb.AuthRoleAddResponse)(&r)) }
 func (p *printerRPC) RoleGet(_ string, r v3.AuthRoleGetResponse) { p.p((*pb.AuthRoleGetResponse)(&r)) }
 func (p *printerRPC) RoleDelete(_ string, r v3.AuthRoleDeleteResponse) {
@@ -145,6 +148,8 @@ func newPrinterUnsupported(n string) printer {
 func (p *printerUnsupported) EndpointStatus([]epStatus) { p.p(nil) }
 func (p *printerUnsupported) DBStatus(dbstatus)         { p.p(nil) }
 
+func (p *printerUnsupported) MoveLeader(leader, target uint64, r v3.MoveLeaderResponse) { p.p(nil) }
+
 func makeMemberListTable(r v3.MemberListResponse) (hdr []string, rows [][]string) {
 	hdr = []string{"ID", "Status", "Name", "Peer Addrs", "Client Addrs"}
 	for _, m := range r.Members {

+ 5 - 0
etcdctl/ctlv3/command/printer_simple.go

@@ -20,6 +20,7 @@ import (
 
 	v3 "github.com/coreos/etcd/clientv3"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
+	"github.com/coreos/etcd/pkg/types"
 )
 
 type simplePrinter struct {
@@ -142,6 +143,10 @@ func (s *simplePrinter) DBStatus(ds dbstatus) {
 	}
 }
 
+func (s *simplePrinter) MoveLeader(leader, target uint64, r v3.MoveLeaderResponse) {
+	fmt.Printf("Leadership transferred from %s to %s\n", types.ID(leader), types.ID(target))
+}
+
 func (s *simplePrinter) RoleAdd(role string, r v3.AuthRoleAddResponse) {
 	fmt.Printf("Role %s created\n", role)
 }

+ 1 - 0
etcdctl/ctlv3/ctl.go

@@ -69,6 +69,7 @@ func init() {
 		command.NewAlarmCommand(),
 		command.NewDefragCommand(),
 		command.NewEndpointCommand(),
+		command.NewMoveLeaderCommand(),
 		command.NewWatchCommand(),
 		command.NewVersionCommand(),
 		command.NewLeaseCommand(),