From a039f2efb8ee6349c5d0aec4ed616f26e738605b Mon Sep 17 00:00:00 2001 From: WizardCXY Date: Tue, 2 Apr 2019 12:31:51 +0800 Subject: [PATCH] clientv3, etcdctl: MemberPromote for learner --- clientv3/integration/cluster_test.go | 35 +++++++++++++++++++++++++ etcdctl/ctlv3/command/member_command.go | 35 +++++++++++++++++++++++++ etcdctl/ctlv3/command/printer.go | 1 + etcdctl/ctlv3/command/printer_simple.go | 4 +++ etcdserver/api/v3rpc/rpctypes/error.go | 8 ++++-- 5 files changed, 81 insertions(+), 2 deletions(-) diff --git a/clientv3/integration/cluster_test.go b/clientv3/integration/cluster_test.go index d9a03cbff83..a1fb9d0a085 100644 --- a/clientv3/integration/cluster_test.go +++ b/clientv3/integration/cluster_test.go @@ -202,6 +202,27 @@ func TestMemberAddForLearner(t *testing.T) { if !resp.Member.IsLearner { t.Errorf("Added a member as learner, got resp.Member.IsLearner = %v", resp.Member.IsLearner) } +} + +func TestMemberPromoteForLearner(t *testing.T) { + // TODO test not ready learner promotion. + defer testutil.AfterTest(t) + + clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3}) + defer clus.Terminate(t) + // TODO change the random client to client that talk to leader directly. + capi := clus.RandClient() + + urls := []string{"http://127.0.0.1:1234"} + isLearner := true + resp, err := capi.MemberAddAsLearner(context.Background(), urls) + if err != nil { + t.Fatalf("failed to add member %v", err) + } + + if !resp.Member.IsLearner { + t.Errorf("Added a member as learner, got resp.Member.IsLearner = %v", resp.Member.IsLearner) + } learners, err := clus.GetLearnerMembers() if err != nil { @@ -210,4 +231,18 @@ func TestMemberAddForLearner(t *testing.T) { if len(learners) != 1 { t.Errorf("Added 1 learner node to cluster, got %d", len(learners)) } + _, err = capi.MemberPromote(context.Background(), resp.Member.ID) + + if err != nil { + t.Fatalf("failed to promote member error: %v", err) + } + + learners, err = clus.GetLearnerMembers() + if err != nil { + t.Fatalf("failed to get the number of learners in cluster: %v", err) + } + if len(learners) != 0 { + t.Errorf("learner promoted, expect 0 learner, got %d", len(learners)) + } + } diff --git a/etcdctl/ctlv3/command/member_command.go b/etcdctl/ctlv3/command/member_command.go index 5c4ca2a72b8..8a2c0527830 100644 --- a/etcdctl/ctlv3/command/member_command.go +++ b/etcdctl/ctlv3/command/member_command.go @@ -40,6 +40,7 @@ func NewMemberCommand() *cobra.Command { mc.AddCommand(NewMemberRemoveCommand()) mc.AddCommand(NewMemberUpdateCommand()) mc.AddCommand(NewMemberListCommand()) + mc.AddCommand(NewMemberPromoteCommand()) return mc } @@ -100,6 +101,20 @@ The items in the lists are ID, Status, Name, Peer Addrs, Client Addrs, Is Learne return cc } +// NewMemberPromoteCommand returns the cobra command for "member promote". +func NewMemberPromoteCommand() *cobra.Command { + cc := &cobra.Command{ + Use: "promote ", + Short: "Promotes a non-voting member in the cluster", + Long: `Promotes a non-voting learner member to a voting one in the cluster. +`, + + Run: memberPromoteCommandFunc, + } + + return cc +} + // memberAddCommandFunc executes the "member add" command. func memberAddCommandFunc(cmd *cobra.Command, args []string) { if len(args) < 1 { @@ -238,3 +253,23 @@ func memberListCommandFunc(cmd *cobra.Command, args []string) { display.MemberList(*resp) } + +// memberPromoteCommandFunc executes the "member promote" command. +func memberPromoteCommandFunc(cmd *cobra.Command, args []string) { + if len(args) != 1 { + ExitWithError(ExitBadArgs, fmt.Errorf("member ID is not provided")) + } + + id, err := strconv.ParseUint(args[0], 16, 64) + if err != nil { + ExitWithError(ExitBadArgs, fmt.Errorf("bad member ID arg (%v), expecting ID in Hex", err)) + } + + ctx, cancel := commandCtx(cmd) + resp, err := mustClientFromCmd(cmd).MemberPromote(ctx, id) + cancel() + if err != nil { + ExitWithError(ExitError, err) + } + display.MemberPromote(id, *resp) +} diff --git a/etcdctl/ctlv3/command/printer.go b/etcdctl/ctlv3/command/printer.go index 942668d9e85..5e844f15016 100644 --- a/etcdctl/ctlv3/command/printer.go +++ b/etcdctl/ctlv3/command/printer.go @@ -42,6 +42,7 @@ type printer interface { MemberAdd(v3.MemberAddResponse) MemberRemove(id uint64, r v3.MemberRemoveResponse) MemberUpdate(id uint64, r v3.MemberUpdateResponse) + MemberPromote(id uint64, r v3.MemberPromoteResponse) MemberList(v3.MemberListResponse) EndpointHealth([]epHealth) diff --git a/etcdctl/ctlv3/command/printer_simple.go b/etcdctl/ctlv3/command/printer_simple.go index 3881ed4b275..8223e87da65 100644 --- a/etcdctl/ctlv3/command/printer_simple.go +++ b/etcdctl/ctlv3/command/printer_simple.go @@ -136,6 +136,10 @@ func (s *simplePrinter) MemberUpdate(id uint64, r v3.MemberUpdateResponse) { fmt.Printf("Member %16x updated in cluster %16x\n", id, r.Header.ClusterId) } +func (s *simplePrinter) MemberPromote(id uint64, r v3.MemberPromoteResponse) { + fmt.Printf("Member %16x promoted in cluster %16x\n", id, r.Header.ClusterId) +} + func (s *simplePrinter) MemberList(resp v3.MemberListResponse) { _, rows := makeMemberListTable(resp) for _, row := range rows { diff --git a/etcdserver/api/v3rpc/rpctypes/error.go b/etcdserver/api/v3rpc/rpctypes/error.go index 84683ed3c5a..ebf8504b79a 100644 --- a/etcdserver/api/v3rpc/rpctypes/error.go +++ b/etcdserver/api/v3rpc/rpctypes/error.go @@ -40,8 +40,8 @@ var ( ErrGRPCMemberNotEnoughStarted = status.New(codes.FailedPrecondition, "etcdserver: re-configuration failed due to not enough started members").Err() ErrGRPCMemberBadURLs = status.New(codes.InvalidArgument, "etcdserver: given member URLs are invalid").Err() ErrGRPCMemberNotFound = status.New(codes.NotFound, "etcdserver: member not found").Err() - ErrGRPCMemberNotLearner = status.New(codes.FailedPrecondition, "etcdserver: can only promote a learner member which catches up with peers").Err() - ErrGRPCLearnerNotReady = status.New(codes.FailedPrecondition, "etcdserver: can only promote a learner member").Err() + ErrGRPCMemberNotLearner = status.New(codes.FailedPrecondition, "etcdserver: can only promote a learner member").Err() + ErrGRPCLearnerNotReady = status.New(codes.FailedPrecondition, "etcdserver: can only promote a learner member which catches up with leader").Err() ErrGRPCRequestTooLarge = status.New(codes.InvalidArgument, "etcdserver: request is too large").Err() ErrGRPCRequestTooManyRequests = status.New(codes.ResourceExhausted, "etcdserver: too many requests").Err() @@ -95,6 +95,8 @@ var ( ErrorDesc(ErrGRPCMemberNotEnoughStarted): ErrGRPCMemberNotEnoughStarted, ErrorDesc(ErrGRPCMemberBadURLs): ErrGRPCMemberBadURLs, ErrorDesc(ErrGRPCMemberNotFound): ErrGRPCMemberNotFound, + ErrorDesc(ErrGRPCMemberNotLearner): ErrGRPCMemberNotLearner, + ErrorDesc(ErrGRPCLearnerNotReady): ErrGRPCLearnerNotReady, ErrorDesc(ErrGRPCRequestTooLarge): ErrGRPCRequestTooLarge, ErrorDesc(ErrGRPCRequestTooManyRequests): ErrGRPCRequestTooManyRequests, @@ -149,6 +151,8 @@ var ( ErrMemberNotEnoughStarted = Error(ErrGRPCMemberNotEnoughStarted) ErrMemberBadURLs = Error(ErrGRPCMemberBadURLs) ErrMemberNotFound = Error(ErrGRPCMemberNotFound) + ErrMemberNotLearner = Error(ErrGRPCMemberNotLearner) + ErrMemberLearnerNotReady = Error(ErrGRPCLearnerNotReady) ErrRequestTooLarge = Error(ErrGRPCRequestTooLarge) ErrTooManyRequests = Error(ErrGRPCRequestTooManyRequests)