cluster_test.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. // Copyright 2016 The etcd Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package integration
  15. import (
  16. "context"
  17. "reflect"
  18. "strings"
  19. "testing"
  20. "time"
  21. "go.etcd.io/etcd/integration"
  22. "go.etcd.io/etcd/pkg/testutil"
  23. "go.etcd.io/etcd/pkg/types"
  24. )
  25. func TestMemberList(t *testing.T) {
  26. defer testutil.AfterTest(t)
  27. clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
  28. defer clus.Terminate(t)
  29. capi := clus.RandClient()
  30. resp, err := capi.MemberList(context.Background())
  31. if err != nil {
  32. t.Fatalf("failed to list member %v", err)
  33. }
  34. if len(resp.Members) != 3 {
  35. t.Errorf("number of members = %d, want %d", len(resp.Members), 3)
  36. }
  37. }
  38. func TestMemberAdd(t *testing.T) {
  39. defer testutil.AfterTest(t)
  40. clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
  41. defer clus.Terminate(t)
  42. capi := clus.RandClient()
  43. urls := []string{"http://127.0.0.1:1234"}
  44. resp, err := capi.MemberAdd(context.Background(), urls)
  45. if err != nil {
  46. t.Fatalf("failed to add member %v", err)
  47. }
  48. if !reflect.DeepEqual(resp.Member.PeerURLs, urls) {
  49. t.Errorf("urls = %v, want %v", urls, resp.Member.PeerURLs)
  50. }
  51. }
  52. func TestMemberAddWithExistingURLs(t *testing.T) {
  53. defer testutil.AfterTest(t)
  54. clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
  55. defer clus.Terminate(t)
  56. capi := clus.RandClient()
  57. resp, err := capi.MemberList(context.Background())
  58. if err != nil {
  59. t.Fatalf("failed to list member %v", err)
  60. }
  61. existingURL := resp.Members[0].PeerURLs[0]
  62. _, err = capi.MemberAdd(context.Background(), []string{existingURL})
  63. expectedErrKeywords := "Peer URLs already exists"
  64. if err == nil {
  65. t.Fatalf("expecting add member to fail, got no error")
  66. }
  67. if !strings.Contains(err.Error(), expectedErrKeywords) {
  68. t.Errorf("expecting error to contain %s, got %s", expectedErrKeywords, err.Error())
  69. }
  70. }
  71. func TestMemberRemove(t *testing.T) {
  72. defer testutil.AfterTest(t)
  73. clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
  74. defer clus.Terminate(t)
  75. capi := clus.Client(1)
  76. resp, err := capi.MemberList(context.Background())
  77. if err != nil {
  78. t.Fatalf("failed to list member %v", err)
  79. }
  80. rmvID := resp.Members[0].ID
  81. // indexes in capi member list don't necessarily match cluster member list;
  82. // find member that is not the client to remove
  83. for _, m := range resp.Members {
  84. mURLs, _ := types.NewURLs(m.PeerURLs)
  85. if !reflect.DeepEqual(mURLs, clus.Members[1].ServerConfig.PeerURLs) {
  86. rmvID = m.ID
  87. break
  88. }
  89. }
  90. _, err = capi.MemberRemove(context.Background(), rmvID)
  91. if err != nil {
  92. t.Fatalf("failed to remove member %v", err)
  93. }
  94. resp, err = capi.MemberList(context.Background())
  95. if err != nil {
  96. t.Fatalf("failed to list member %v", err)
  97. }
  98. if len(resp.Members) != 2 {
  99. t.Errorf("number of members = %d, want %d", len(resp.Members), 2)
  100. }
  101. }
  102. func TestMemberUpdate(t *testing.T) {
  103. defer testutil.AfterTest(t)
  104. clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
  105. defer clus.Terminate(t)
  106. capi := clus.RandClient()
  107. resp, err := capi.MemberList(context.Background())
  108. if err != nil {
  109. t.Fatalf("failed to list member %v", err)
  110. }
  111. urls := []string{"http://127.0.0.1:1234"}
  112. _, err = capi.MemberUpdate(context.Background(), resp.Members[0].ID, urls)
  113. if err != nil {
  114. t.Fatalf("failed to update member %v", err)
  115. }
  116. resp, err = capi.MemberList(context.Background())
  117. if err != nil {
  118. t.Fatalf("failed to list member %v", err)
  119. }
  120. if !reflect.DeepEqual(resp.Members[0].PeerURLs, urls) {
  121. t.Errorf("urls = %v, want %v", urls, resp.Members[0].PeerURLs)
  122. }
  123. }
  124. func TestMemberAddUpdateWrongURLs(t *testing.T) {
  125. defer testutil.AfterTest(t)
  126. clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
  127. defer clus.Terminate(t)
  128. capi := clus.RandClient()
  129. tt := [][]string{
  130. // missing protocol scheme
  131. {"://127.0.0.1:2379"},
  132. // unsupported scheme
  133. {"mailto://127.0.0.1:2379"},
  134. // not conform to host:port
  135. {"http://127.0.0.1"},
  136. // contain a path
  137. {"http://127.0.0.1:2379/path"},
  138. // first path segment in URL cannot contain colon
  139. {"127.0.0.1:1234"},
  140. // URL scheme must be http, https, unix, or unixs
  141. {"localhost:1234"},
  142. }
  143. for i := range tt {
  144. _, err := capi.MemberAdd(context.Background(), tt[i])
  145. if err == nil {
  146. t.Errorf("#%d: MemberAdd err = nil, but error", i)
  147. }
  148. _, err = capi.MemberUpdate(context.Background(), 0, tt[i])
  149. if err == nil {
  150. t.Errorf("#%d: MemberUpdate err = nil, but error", i)
  151. }
  152. }
  153. }
  154. func TestMemberAddForLearner(t *testing.T) {
  155. defer testutil.AfterTest(t)
  156. clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
  157. defer clus.Terminate(t)
  158. capi := clus.RandClient()
  159. urls := []string{"http://127.0.0.1:1234"}
  160. resp, err := capi.MemberAddAsLearner(context.Background(), urls)
  161. if err != nil {
  162. t.Fatalf("failed to add member %v", err)
  163. }
  164. if !resp.Member.IsLearner {
  165. t.Errorf("Added a member as learner, got resp.Member.IsLearner = %v", resp.Member.IsLearner)
  166. }
  167. numberOfLearners := 0
  168. for _, m := range resp.Members {
  169. if m.IsLearner {
  170. numberOfLearners++
  171. }
  172. }
  173. if numberOfLearners != 1 {
  174. t.Errorf("Added 1 learner node to cluster, got %d", numberOfLearners)
  175. }
  176. }
  177. func TestMemberPromote(t *testing.T) {
  178. defer testutil.AfterTest(t)
  179. clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
  180. defer clus.Terminate(t)
  181. // member promote request can be sent to any server in cluster,
  182. // the request will be auto-forwarded to leader on server-side.
  183. // This test explicitly includes the server-side forwarding by
  184. // sending the request to follower.
  185. leaderIdx := clus.WaitLeader(t)
  186. followerIdx := (leaderIdx + 1) % 3
  187. capi := clus.Client(followerIdx)
  188. urls := []string{"http://127.0.0.1:1234"}
  189. memberAddResp, err := capi.MemberAddAsLearner(context.Background(), urls)
  190. if err != nil {
  191. t.Fatalf("failed to add member %v", err)
  192. }
  193. if !memberAddResp.Member.IsLearner {
  194. t.Fatalf("Added a member as learner, got resp.Member.IsLearner = %v", memberAddResp.Member.IsLearner)
  195. }
  196. learnerID := memberAddResp.Member.ID
  197. numberOfLearners := 0
  198. for _, m := range memberAddResp.Members {
  199. if m.IsLearner {
  200. numberOfLearners++
  201. }
  202. }
  203. if numberOfLearners != 1 {
  204. t.Fatalf("Added 1 learner node to cluster, got %d", numberOfLearners)
  205. }
  206. // learner is not started yet. Expect learner progress check to fail.
  207. // As the result, member promote request will fail.
  208. _, err = capi.MemberPromote(context.Background(), learnerID)
  209. expectedErrKeywords := "can only promote a learner member which is in sync with leader"
  210. if err == nil {
  211. t.Fatalf("expecting promote not ready learner to fail, got no error")
  212. }
  213. if !strings.Contains(err.Error(), expectedErrKeywords) {
  214. t.Fatalf("expecting error to contain %s, got %s", expectedErrKeywords, err.Error())
  215. }
  216. // create and launch learner member based on the response of V3 Member Add API.
  217. // (the response has information on peer urls of the existing members in cluster)
  218. learnerMember := clus.MustNewMember(t, memberAddResp)
  219. clus.Members = append(clus.Members, learnerMember)
  220. if err := learnerMember.Launch(); err != nil {
  221. t.Fatal(err)
  222. }
  223. // retry until promote succeed or timeout
  224. timeout := time.After(5 * time.Second)
  225. for {
  226. select {
  227. case <-time.After(500 * time.Millisecond):
  228. case <-timeout:
  229. t.Errorf("failed all attempts to promote learner member, last error: %v", err)
  230. break
  231. }
  232. _, err = capi.MemberPromote(context.Background(), learnerID)
  233. // successfully promoted learner
  234. if err == nil {
  235. break
  236. }
  237. // if member promote fails due to learner not ready, retry.
  238. // otherwise fails the test.
  239. if !strings.Contains(err.Error(), expectedErrKeywords) {
  240. t.Fatalf("unexpected error when promoting learner member: %v", err)
  241. }
  242. }
  243. }
  244. // TestMaxLearnerInCluster verifies that the maximum number of learners allowed in a cluster is 1
  245. func TestMaxLearnerInCluster(t *testing.T) {
  246. defer testutil.AfterTest(t)
  247. // 1. start with a cluster with 3 voting member and 0 learner member
  248. clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
  249. defer clus.Terminate(t)
  250. // 2. adding a learner member should succeed
  251. resp1, err := clus.Client(0).MemberAddAsLearner(context.Background(), []string{"http://127.0.0.1:1234"})
  252. if err != nil {
  253. t.Fatalf("failed to add learner member %v", err)
  254. }
  255. numberOfLearners := 0
  256. for _, m := range resp1.Members {
  257. if m.IsLearner {
  258. numberOfLearners++
  259. }
  260. }
  261. if numberOfLearners != 1 {
  262. t.Fatalf("Added 1 learner node to cluster, got %d", numberOfLearners)
  263. }
  264. // 3. cluster has 3 voting member and 1 learner, adding another learner should fail
  265. _, err = clus.Client(0).MemberAddAsLearner(context.Background(), []string{"http://127.0.0.1:2345"})
  266. if err == nil {
  267. t.Fatalf("expect member add to fail, got no error")
  268. }
  269. expectedErrKeywords := "too many learner members in cluster"
  270. if !strings.Contains(err.Error(), expectedErrKeywords) {
  271. t.Fatalf("expecting error to contain %s, got %s", expectedErrKeywords, err.Error())
  272. }
  273. // 4. cluster has 3 voting member and 1 learner, adding a voting member should succeed
  274. _, err = clus.Client(0).MemberAdd(context.Background(), []string{"http://127.0.0.1:3456"})
  275. if err != nil {
  276. t.Errorf("failed to add member %v", err)
  277. }
  278. }