diff --git a/tests/common/member_test.go b/tests/common/member_test.go index f1b6be3aef03..bed3c07321f5 100644 --- a/tests/common/member_test.go +++ b/tests/common/member_test.go @@ -16,11 +16,16 @@ package common import ( "context" - "go.etcd.io/etcd/tests/v3/framework/testutils" "testing" "time" + + clientv3 "go.etcd.io/etcd/client/v3" + "go.etcd.io/etcd/tests/v3/framework/testutils" ) +// 240.0.0.0 is reserved for future use according to https://en.wikipedia.org/wiki/Reserved_IP_addresses +var blackHolePeerUrl = "http://240.0.0.0:65535" + func TestMemberList(t *testing.T) { testRunner.BeforeTest(t) @@ -33,6 +38,7 @@ func TestMemberList(t *testing.T) { cc := clus.Client() testutils.ExecuteUntil(ctx, t, func() { + clus.WaitLeader(t) resp, err := cc.MemberList() if err != nil { t.Fatalf("could not get member list, err: %s", err) @@ -51,3 +57,56 @@ func TestMemberList(t *testing.T) { }) } } + +func TestMemberAdd(t *testing.T) { + testRunner.BeforeTest(t) + + tcs := []struct { + name string + learner bool + }{ + { + name: "NotLearner", + learner: false, + }, + { + name: "Learner", + learner: true, + }, + } + + for _, tc := range tcs { + for _, nc := range clusterTestCases { + t.Run(tc.name+"/"+nc.name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + clus := testRunner.NewCluster(ctx, t, nc.config) + defer clus.Close() + cc := clus.Client() + + testutils.ExecuteUntil(ctx, t, func() { + clus.WaitLeader(t) + var addResp *clientv3.MemberAddResponse + var err error + if tc.learner { + addResp, err = cc.MemberAddAsLearner("newmember", []string{blackHolePeerUrl}) + } else { + addResp, err = cc.MemberAdd("newmember", []string{blackHolePeerUrl}) + } + if err != nil { + t.Fatalf("MemberAdd failed, err: %s", err) + } + if addResp.Member == nil { + t.Fatalf("MemberAdd failed, expected: member != nil, got: member == nil") + } + if addResp.Member.ID == 0 { + t.Fatalf("MemberAdd failed, expected: ID != 0, got: ID == 0") + } + if len(addResp.Member.PeerURLs) == 0 { + t.Fatalf("MemberAdd failed, expected: non-empty PeerURLs, got: empty PeerURLs") + } + }) + }) + } + } +} diff --git a/tests/e2e/ctl_v3_member_test.go b/tests/e2e/ctl_v3_member_test.go index a80642398dd3..b6b44acd0a4c 100644 --- a/tests/e2e/ctl_v3_member_test.go +++ b/tests/e2e/ctl_v3_member_test.go @@ -50,19 +50,7 @@ func TestCtlV3MemberRemoveClientAutoTLS(t *testing.T) { func TestCtlV3MemberRemovePeerTLS(t *testing.T) { testCtl(t, memberRemoveTest, withQuorum(), withNoStrictReconfig(), withCfg(*e2e.NewConfigPeerTLS())) } -func TestCtlV3MemberAdd(t *testing.T) { testCtl(t, memberAddTest) } -func TestCtlV3MemberAddNoTLS(t *testing.T) { testCtl(t, memberAddTest, withCfg(*e2e.NewConfigNoTLS())) } -func TestCtlV3MemberAddClientTLS(t *testing.T) { - testCtl(t, memberAddTest, withCfg(*e2e.NewConfigClientTLS())) -} -func TestCtlV3MemberAddClientAutoTLS(t *testing.T) { - testCtl(t, memberAddTest, withCfg(*e2e.NewConfigClientAutoTLS())) -} -func TestCtlV3MemberAddPeerTLS(t *testing.T) { - testCtl(t, memberAddTest, withCfg(*e2e.NewConfigPeerTLS())) -} -func TestCtlV3MemberAddForLearner(t *testing.T) { testCtl(t, memberAddForLearnerTest) } -func TestCtlV3MemberUpdate(t *testing.T) { testCtl(t, memberUpdateTest) } +func TestCtlV3MemberUpdate(t *testing.T) { testCtl(t, memberUpdateTest) } func TestCtlV3MemberUpdateNoTLS(t *testing.T) { testCtl(t, memberUpdateTest, withCfg(*e2e.NewConfigNoTLS())) } @@ -178,18 +166,6 @@ func ctlV3MemberRemove(cx ctlCtx, ep, memberID, clusterID string) error { return e2e.SpawnWithExpectWithEnv(cmdArgs, cx.envMap, fmt.Sprintf("%s removed from cluster %s", memberID, clusterID)) } -func memberAddTest(cx ctlCtx) { - if err := ctlV3MemberAdd(cx, fmt.Sprintf("http://localhost:%d", e2e.EtcdProcessBasePort+11), false); err != nil { - cx.t.Fatal(err) - } -} - -func memberAddForLearnerTest(cx ctlCtx) { - if err := ctlV3MemberAdd(cx, fmt.Sprintf("http://localhost:%d", e2e.EtcdProcessBasePort+11), true); err != nil { - cx.t.Fatal(err) - } -} - func ctlV3MemberAdd(cx ctlCtx, peerURL string, isLearner bool) error { cmdArgs := append(cx.PrefixArgs(), "member", "add", "newmember", fmt.Sprintf("--peer-urls=%s", peerURL)) if isLearner { diff --git a/tests/framework/e2e/etcdctl.go b/tests/framework/e2e/etcdctl.go index f97cc27e2c14..0b9dd8e99a80 100644 --- a/tests/framework/e2e/etcdctl.go +++ b/tests/framework/e2e/etcdctl.go @@ -235,6 +235,12 @@ func (ctl *EtcdctlV3) MemberList() (*clientv3.MemberListResponse, error) { return &resp, err } +func (ctl *EtcdctlV3) MemberAdd(name string, peerAddrs []string) (*clientv3.MemberAddResponse, error) { + var resp clientv3.MemberAddResponse + err := ctl.spawnJsonCmd(&resp, "member", "add", name, "--peer-urls", strings.Join(peerAddrs, ",")) + return &resp, err +} + func (ctl *EtcdctlV3) MemberAddAsLearner(name string, peerAddrs []string) (*clientv3.MemberAddResponse, error) { var resp clientv3.MemberAddResponse err := ctl.spawnJsonCmd(&resp, "member", "add", name, "--learner", "--peer-urls", strings.Join(peerAddrs, ",")) diff --git a/tests/framework/integration.go b/tests/framework/integration.go index 631b7263e696..9da6aa9418a4 100644 --- a/tests/framework/integration.go +++ b/tests/framework/integration.go @@ -376,3 +376,11 @@ func getOps(ss []string) ([]clientv3.Op, error) { func (c integrationClient) MemberList() (*clientv3.MemberListResponse, error) { return c.Client.MemberList(c.ctx) } + +func (c integrationClient) MemberAdd(_ string, peerAddrs []string) (*clientv3.MemberAddResponse, error) { + return c.Client.MemberAdd(c.ctx, peerAddrs) +} + +func (c integrationClient) MemberAddAsLearner(_ string, peerAddrs []string) (*clientv3.MemberAddResponse, error) { + return c.Client.MemberAddAsLearner(c.ctx, peerAddrs) +} diff --git a/tests/framework/interface.go b/tests/framework/interface.go index 39b2552b36b6..5fda510e076c 100644 --- a/tests/framework/interface.go +++ b/tests/framework/interface.go @@ -73,4 +73,6 @@ type Client interface { Txn(compares, ifSucess, ifFail []string, o config.TxnOptions) (*clientv3.TxnResponse, error) MemberList() (*clientv3.MemberListResponse, error) + MemberAdd(name string, peerAddrs []string) (*clientv3.MemberAddResponse, error) + MemberAddAsLearner(name string, peerAddrs []string) (*clientv3.MemberAddResponse, error) }