From 510f9f87694c7ba4dfe2857f281e90f9b1d61680 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..599addca6321 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) + } + } +}