diff --git a/etcdctl/README.md b/etcdctl/README.md index 84ad1dce2..948617d1a 100644 --- a/etcdctl/README.md +++ b/etcdctl/README.md @@ -805,6 +805,29 @@ Prints a line of JSON encoding the database hash, revision, total keys, and size +----------+----------+------------+------------+ ``` +### MOVE-LEADER \ + +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 \ [command arg1 arg2 ...] diff --git a/etcdctl/ctlv3/command/move_leader_command.go b/etcdctl/ctlv3/command/move_leader_command.go new file mode 100644 index 000000000..3bd44255e --- /dev/null +++ b/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 ", + 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) +} diff --git a/etcdctl/ctlv3/command/printer.go b/etcdctl/ctlv3/command/printer.go index ad29e75cd..b84dcda74 100644 --- a/etcdctl/ctlv3/command/printer.go +++ b/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 { diff --git a/etcdctl/ctlv3/command/printer_simple.go b/etcdctl/ctlv3/command/printer_simple.go index baa79e23d..00dda47f7 100644 --- a/etcdctl/ctlv3/command/printer_simple.go +++ b/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) } diff --git a/etcdctl/ctlv3/ctl.go b/etcdctl/ctlv3/ctl.go index 92e715d97..2ca2b4096 100644 --- a/etcdctl/ctlv3/ctl.go +++ b/etcdctl/ctlv3/ctl.go @@ -69,6 +69,7 @@ func init() { command.NewAlarmCommand(), command.NewDefragCommand(), command.NewEndpointCommand(), + command.NewMoveLeaderCommand(), command.NewWatchCommand(), command.NewVersionCommand(), command.NewLeaseCommand(),