From 6bbdea7744d512eeef8958a73c4e6a2aa4fc8a2a Mon Sep 17 00:00:00 2001 From: Robin Cernin Date: Wed, 10 Mar 2021 08:43:52 +1000 Subject: [PATCH] etcdctl: allow move-leader to connect to multiple endpoints with TLS Re-opening closed PR #11775 which was originaly authored by benmoss. The mustClientForCmd function is responsible for parsing environment variables and flags into configuration data. A change was made in #9382 to call Fatal if a flag is provided multiple times. This means that we cannot call the mustClientForCmd function more than once, since it will think that flags parsed the first time are now being redefined and error out. Some people have commented about this in #8380 but I don't think there's an open issue for it. --- etcdctl/ctlv3/command/move_leader_command.go | 4 +- tests/e2e/ctl_v3_move_leader_test.go | 87 ++++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/etcdctl/ctlv3/command/move_leader_command.go b/etcdctl/ctlv3/command/move_leader_command.go index 1aee99b24445..a07e095b5e1a 100644 --- a/etcdctl/ctlv3/command/move_leader_command.go +++ b/etcdctl/ctlv3/command/move_leader_command.go @@ -42,7 +42,8 @@ func transferLeadershipCommandFunc(cmd *cobra.Command, args []string) { ExitWithError(ExitBadArgs, err) } - c := mustClientFromCmd(cmd) + cfg := clientConfigFromCmd(cmd) + c := cfg.mustClient() eps := c.Endpoints() c.Close() @@ -52,7 +53,6 @@ func transferLeadershipCommandFunc(cmd *cobra.Command, args []string) { var leaderCli *clientv3.Client var leaderID uint64 for _, ep := range eps { - cfg := clientConfigFromCmd(cmd) cfg.endpoints = []string{ep} cli := cfg.mustClient() resp, serr := cli.Status(ctx, ep) diff --git a/tests/e2e/ctl_v3_move_leader_test.go b/tests/e2e/ctl_v3_move_leader_test.go index 507ca4c16213..156a1d97cabc 100644 --- a/tests/e2e/ctl_v3_move_leader_test.go +++ b/tests/e2e/ctl_v3_move_leader_test.go @@ -36,6 +36,14 @@ func TestCtlV3MoveLeaderInsecure(t *testing.T) { testCtlV3MoveLeader(t, *newConfigNoTLS()) } +func TestCtlV3MoveLeaderWithEnvSecure(t *testing.T) { + testCtlV3MoveLeaderWithEnv(t, *newConfigTLS()) +} + +func TestCtlV3MoveLeaderWithEnvInsecure(t *testing.T) { + testCtlV3MoveLeaderWithEnv(t, *newConfigNoTLS()) +} + func testCtlV3MoveLeader(t *testing.T, cfg etcdProcessClusterConfig) { defer testutil.AfterTest(t) @@ -117,3 +125,82 @@ func testCtlV3MoveLeader(t *testing.T, cfg etcdProcessClusterConfig) { } } } + +func testCtlV3MoveLeaderWithEnv(t *testing.T, cfg etcdProcessClusterConfig) { + defer testutil.AfterTest(t) + + epc := setupEtcdctlTest(t, &cfg, true) + defer func() { + if errC := epc.Close(); errC != nil { + t.Fatalf("error closing etcd processes (%v)", errC) + } + }() + + var tcfg *tls.Config + if cfg.clientTLS == clientTLS { + tinfo := transport.TLSInfo{ + CertFile: certPath, + KeyFile: privateKeyPath, + TrustedCAFile: caPath, + } + var err error + tcfg, err = tinfo.ClientConfig() + if err != nil { + t.Fatal(err) + } + } + + var leadIdx int + var leaderID uint64 + var transferee uint64 + for i, ep := range epc.EndpointsV3() { + cli, err := clientv3.New(clientv3.Config{ + Endpoints: []string{ep}, + DialTimeout: 3 * time.Second, + TLS: tcfg, + }) + if err != nil { + t.Fatal(err) + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + resp, err := cli.Status(ctx, ep) + if err != nil { + t.Fatalf("failed to get status from endpoint %s: %v", ep, err) + } + cancel() + cli.Close() + + if resp.Header.GetMemberId() == resp.Leader { + leadIdx = i + leaderID = resp.Leader + } else { + transferee = resp.Header.GetMemberId() + } + } + + os.Setenv("ETCDCTL_API", "3") + defer os.Unsetenv("ETCDCTL_API") + cx := ctlCtx{ + t: t, + cfg: *newConfigNoTLS(), + dialTimeout: 7 * time.Second, + epc: epc, + envMap: map[string]struct{}{}, + } + + tests := []struct { + prefixes []string + expect string + }{ + { // request to leader + cx.prefixArgs([]string{cx.epc.EndpointsV3()[leadIdx]}), + fmt.Sprintf("Leadership transferred from %s to %s", types.ID(leaderID), types.ID(transferee)), + }, + } + for i, tc := range tests { + cmdArgs := append(tc.prefixes, "move-leader", types.ID(transferee).String()) + if err := spawnWithExpect(cmdArgs, tc.expect); err != nil { + t.Fatalf("#%d: %v", i, err) + } + } +}