cluster_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  1. // Copyright 2015 CoreOS, Inc.
  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 etcdserver
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "path"
  19. "reflect"
  20. "testing"
  21. "github.com/coreos/etcd/pkg/mock/mockstore"
  22. "github.com/coreos/etcd/pkg/testutil"
  23. "github.com/coreos/etcd/pkg/types"
  24. "github.com/coreos/etcd/raft/raftpb"
  25. "github.com/coreos/etcd/store"
  26. )
  27. func TestClusterMember(t *testing.T) {
  28. membs := []*Member{
  29. newTestMember(1, nil, "node1", nil),
  30. newTestMember(2, nil, "node2", nil),
  31. }
  32. tests := []struct {
  33. id types.ID
  34. match bool
  35. }{
  36. {1, true},
  37. {2, true},
  38. {3, false},
  39. }
  40. for i, tt := range tests {
  41. c := newTestCluster(membs)
  42. m := c.Member(tt.id)
  43. if g := m != nil; g != tt.match {
  44. t.Errorf("#%d: find member = %v, want %v", i, g, tt.match)
  45. }
  46. if m != nil && m.ID != tt.id {
  47. t.Errorf("#%d: id = %x, want %x", i, m.ID, tt.id)
  48. }
  49. }
  50. }
  51. func TestClusterMemberByName(t *testing.T) {
  52. membs := []*Member{
  53. newTestMember(1, nil, "node1", nil),
  54. newTestMember(2, nil, "node2", nil),
  55. }
  56. tests := []struct {
  57. name string
  58. match bool
  59. }{
  60. {"node1", true},
  61. {"node2", true},
  62. {"node3", false},
  63. }
  64. for i, tt := range tests {
  65. c := newTestCluster(membs)
  66. m := c.MemberByName(tt.name)
  67. if g := m != nil; g != tt.match {
  68. t.Errorf("#%d: find member = %v, want %v", i, g, tt.match)
  69. }
  70. if m != nil && m.Name != tt.name {
  71. t.Errorf("#%d: name = %v, want %v", i, m.Name, tt.name)
  72. }
  73. }
  74. }
  75. func TestClusterMemberIDs(t *testing.T) {
  76. c := newTestCluster([]*Member{
  77. newTestMember(1, nil, "", nil),
  78. newTestMember(4, nil, "", nil),
  79. newTestMember(100, nil, "", nil),
  80. })
  81. w := []types.ID{1, 4, 100}
  82. g := c.MemberIDs()
  83. if !reflect.DeepEqual(w, g) {
  84. t.Errorf("IDs = %+v, want %+v", g, w)
  85. }
  86. }
  87. func TestClusterPeerURLs(t *testing.T) {
  88. tests := []struct {
  89. mems []*Member
  90. wurls []string
  91. }{
  92. // single peer with a single address
  93. {
  94. mems: []*Member{
  95. newTestMember(1, []string{"http://192.0.2.1"}, "", nil),
  96. },
  97. wurls: []string{"http://192.0.2.1"},
  98. },
  99. // single peer with a single address with a port
  100. {
  101. mems: []*Member{
  102. newTestMember(1, []string{"http://192.0.2.1:8001"}, "", nil),
  103. },
  104. wurls: []string{"http://192.0.2.1:8001"},
  105. },
  106. // several members explicitly unsorted
  107. {
  108. mems: []*Member{
  109. newTestMember(2, []string{"http://192.0.2.3", "http://192.0.2.4"}, "", nil),
  110. newTestMember(3, []string{"http://192.0.2.5", "http://192.0.2.6"}, "", nil),
  111. newTestMember(1, []string{"http://192.0.2.1", "http://192.0.2.2"}, "", nil),
  112. },
  113. wurls: []string{"http://192.0.2.1", "http://192.0.2.2", "http://192.0.2.3", "http://192.0.2.4", "http://192.0.2.5", "http://192.0.2.6"},
  114. },
  115. // no members
  116. {
  117. mems: []*Member{},
  118. wurls: []string{},
  119. },
  120. // peer with no peer urls
  121. {
  122. mems: []*Member{
  123. newTestMember(3, []string{}, "", nil),
  124. },
  125. wurls: []string{},
  126. },
  127. }
  128. for i, tt := range tests {
  129. c := newTestCluster(tt.mems)
  130. urls := c.PeerURLs()
  131. if !reflect.DeepEqual(urls, tt.wurls) {
  132. t.Errorf("#%d: PeerURLs = %v, want %v", i, urls, tt.wurls)
  133. }
  134. }
  135. }
  136. func TestClusterClientURLs(t *testing.T) {
  137. tests := []struct {
  138. mems []*Member
  139. wurls []string
  140. }{
  141. // single peer with a single address
  142. {
  143. mems: []*Member{
  144. newTestMember(1, nil, "", []string{"http://192.0.2.1"}),
  145. },
  146. wurls: []string{"http://192.0.2.1"},
  147. },
  148. // single peer with a single address with a port
  149. {
  150. mems: []*Member{
  151. newTestMember(1, nil, "", []string{"http://192.0.2.1:8001"}),
  152. },
  153. wurls: []string{"http://192.0.2.1:8001"},
  154. },
  155. // several members explicitly unsorted
  156. {
  157. mems: []*Member{
  158. newTestMember(2, nil, "", []string{"http://192.0.2.3", "http://192.0.2.4"}),
  159. newTestMember(3, nil, "", []string{"http://192.0.2.5", "http://192.0.2.6"}),
  160. newTestMember(1, nil, "", []string{"http://192.0.2.1", "http://192.0.2.2"}),
  161. },
  162. wurls: []string{"http://192.0.2.1", "http://192.0.2.2", "http://192.0.2.3", "http://192.0.2.4", "http://192.0.2.5", "http://192.0.2.6"},
  163. },
  164. // no members
  165. {
  166. mems: []*Member{},
  167. wurls: []string{},
  168. },
  169. // peer with no client urls
  170. {
  171. mems: []*Member{
  172. newTestMember(3, nil, "", []string{}),
  173. },
  174. wurls: []string{},
  175. },
  176. }
  177. for i, tt := range tests {
  178. c := newTestCluster(tt.mems)
  179. urls := c.ClientURLs()
  180. if !reflect.DeepEqual(urls, tt.wurls) {
  181. t.Errorf("#%d: ClientURLs = %v, want %v", i, urls, tt.wurls)
  182. }
  183. }
  184. }
  185. func TestClusterValidateAndAssignIDsBad(t *testing.T) {
  186. tests := []struct {
  187. clmembs []*Member
  188. membs []*Member
  189. }{
  190. {
  191. // unmatched length
  192. []*Member{
  193. newTestMember(1, []string{"http://127.0.0.1:2379"}, "", nil),
  194. },
  195. []*Member{},
  196. },
  197. {
  198. // unmatched peer urls
  199. []*Member{
  200. newTestMember(1, []string{"http://127.0.0.1:2379"}, "", nil),
  201. },
  202. []*Member{
  203. newTestMember(1, []string{"http://127.0.0.1:4001"}, "", nil),
  204. },
  205. },
  206. {
  207. // unmatched peer urls
  208. []*Member{
  209. newTestMember(1, []string{"http://127.0.0.1:2379"}, "", nil),
  210. newTestMember(2, []string{"http://127.0.0.2:2379"}, "", nil),
  211. },
  212. []*Member{
  213. newTestMember(1, []string{"http://127.0.0.1:2379"}, "", nil),
  214. newTestMember(2, []string{"http://127.0.0.2:4001"}, "", nil),
  215. },
  216. },
  217. }
  218. for i, tt := range tests {
  219. ecl := newTestCluster(tt.clmembs)
  220. lcl := newTestCluster(tt.membs)
  221. if err := ValidateClusterAndAssignIDs(lcl, ecl); err == nil {
  222. t.Errorf("#%d: unexpected update success", i)
  223. }
  224. }
  225. }
  226. func TestClusterValidateAndAssignIDs(t *testing.T) {
  227. tests := []struct {
  228. clmembs []*Member
  229. membs []*Member
  230. wids []types.ID
  231. }{
  232. {
  233. []*Member{
  234. newTestMember(1, []string{"http://127.0.0.1:2379"}, "", nil),
  235. newTestMember(2, []string{"http://127.0.0.2:2379"}, "", nil),
  236. },
  237. []*Member{
  238. newTestMember(3, []string{"http://127.0.0.1:2379"}, "", nil),
  239. newTestMember(4, []string{"http://127.0.0.2:2379"}, "", nil),
  240. },
  241. []types.ID{3, 4},
  242. },
  243. }
  244. for i, tt := range tests {
  245. lcl := newTestCluster(tt.clmembs)
  246. ecl := newTestCluster(tt.membs)
  247. if err := ValidateClusterAndAssignIDs(lcl, ecl); err != nil {
  248. t.Errorf("#%d: unexpect update error: %v", i, err)
  249. }
  250. if !reflect.DeepEqual(lcl.MemberIDs(), tt.wids) {
  251. t.Errorf("#%d: ids = %v, want %v", i, lcl.MemberIDs(), tt.wids)
  252. }
  253. }
  254. }
  255. func TestClusterValidateConfigurationChange(t *testing.T) {
  256. cl := newCluster("")
  257. cl.SetStore(store.New())
  258. for i := 1; i <= 4; i++ {
  259. attr := RaftAttributes{PeerURLs: []string{fmt.Sprintf("http://127.0.0.1:%d", i)}}
  260. cl.AddMember(&Member{ID: types.ID(i), RaftAttributes: attr})
  261. }
  262. cl.RemoveMember(4)
  263. attr := RaftAttributes{PeerURLs: []string{fmt.Sprintf("http://127.0.0.1:%d", 1)}}
  264. ctx, err := json.Marshal(&Member{ID: types.ID(5), RaftAttributes: attr})
  265. if err != nil {
  266. t.Fatal(err)
  267. }
  268. attr = RaftAttributes{PeerURLs: []string{fmt.Sprintf("http://127.0.0.1:%d", 5)}}
  269. ctx5, err := json.Marshal(&Member{ID: types.ID(5), RaftAttributes: attr})
  270. if err != nil {
  271. t.Fatal(err)
  272. }
  273. attr = RaftAttributes{PeerURLs: []string{fmt.Sprintf("http://127.0.0.1:%d", 3)}}
  274. ctx2to3, err := json.Marshal(&Member{ID: types.ID(2), RaftAttributes: attr})
  275. if err != nil {
  276. t.Fatal(err)
  277. }
  278. attr = RaftAttributes{PeerURLs: []string{fmt.Sprintf("http://127.0.0.1:%d", 5)}}
  279. ctx2to5, err := json.Marshal(&Member{ID: types.ID(2), RaftAttributes: attr})
  280. if err != nil {
  281. t.Fatal(err)
  282. }
  283. tests := []struct {
  284. cc raftpb.ConfChange
  285. werr error
  286. }{
  287. {
  288. raftpb.ConfChange{
  289. Type: raftpb.ConfChangeRemoveNode,
  290. NodeID: 3,
  291. },
  292. nil,
  293. },
  294. {
  295. raftpb.ConfChange{
  296. Type: raftpb.ConfChangeAddNode,
  297. NodeID: 4,
  298. },
  299. ErrIDRemoved,
  300. },
  301. {
  302. raftpb.ConfChange{
  303. Type: raftpb.ConfChangeRemoveNode,
  304. NodeID: 4,
  305. },
  306. ErrIDRemoved,
  307. },
  308. {
  309. raftpb.ConfChange{
  310. Type: raftpb.ConfChangeAddNode,
  311. NodeID: 1,
  312. },
  313. ErrIDExists,
  314. },
  315. {
  316. raftpb.ConfChange{
  317. Type: raftpb.ConfChangeAddNode,
  318. NodeID: 5,
  319. Context: ctx,
  320. },
  321. ErrPeerURLexists,
  322. },
  323. {
  324. raftpb.ConfChange{
  325. Type: raftpb.ConfChangeRemoveNode,
  326. NodeID: 5,
  327. },
  328. ErrIDNotFound,
  329. },
  330. {
  331. raftpb.ConfChange{
  332. Type: raftpb.ConfChangeAddNode,
  333. NodeID: 5,
  334. Context: ctx5,
  335. },
  336. nil,
  337. },
  338. {
  339. raftpb.ConfChange{
  340. Type: raftpb.ConfChangeUpdateNode,
  341. NodeID: 5,
  342. Context: ctx,
  343. },
  344. ErrIDNotFound,
  345. },
  346. // try to change the peer url of 2 to the peer url of 3
  347. {
  348. raftpb.ConfChange{
  349. Type: raftpb.ConfChangeUpdateNode,
  350. NodeID: 2,
  351. Context: ctx2to3,
  352. },
  353. ErrPeerURLexists,
  354. },
  355. {
  356. raftpb.ConfChange{
  357. Type: raftpb.ConfChangeUpdateNode,
  358. NodeID: 2,
  359. Context: ctx2to5,
  360. },
  361. nil,
  362. },
  363. }
  364. for i, tt := range tests {
  365. err := cl.ValidateConfigurationChange(tt.cc)
  366. if err != tt.werr {
  367. t.Errorf("#%d: validateConfigurationChange error = %v, want %v", i, err, tt.werr)
  368. }
  369. }
  370. }
  371. func TestClusterGenID(t *testing.T) {
  372. cs := newTestCluster([]*Member{
  373. newTestMember(1, nil, "", nil),
  374. newTestMember(2, nil, "", nil),
  375. })
  376. cs.genID()
  377. if cs.ID() == 0 {
  378. t.Fatalf("cluster.ID = %v, want not 0", cs.ID())
  379. }
  380. previd := cs.ID()
  381. cs.SetStore(mockstore.NewNop())
  382. cs.AddMember(newTestMember(3, nil, "", nil))
  383. cs.genID()
  384. if cs.ID() == previd {
  385. t.Fatalf("cluster.ID = %v, want not %v", cs.ID(), previd)
  386. }
  387. }
  388. func TestNodeToMemberBad(t *testing.T) {
  389. tests := []*store.NodeExtern{
  390. {Key: "/1234", Nodes: []*store.NodeExtern{
  391. {Key: "/1234/strange"},
  392. }},
  393. {Key: "/1234", Nodes: []*store.NodeExtern{
  394. {Key: "/1234/raftAttributes", Value: stringp("garbage")},
  395. }},
  396. {Key: "/1234", Nodes: []*store.NodeExtern{
  397. {Key: "/1234/attributes", Value: stringp(`{"name":"node1","clientURLs":null}`)},
  398. }},
  399. {Key: "/1234", Nodes: []*store.NodeExtern{
  400. {Key: "/1234/raftAttributes", Value: stringp(`{"peerURLs":null}`)},
  401. {Key: "/1234/strange"},
  402. }},
  403. {Key: "/1234", Nodes: []*store.NodeExtern{
  404. {Key: "/1234/raftAttributes", Value: stringp(`{"peerURLs":null}`)},
  405. {Key: "/1234/attributes", Value: stringp("garbage")},
  406. }},
  407. {Key: "/1234", Nodes: []*store.NodeExtern{
  408. {Key: "/1234/raftAttributes", Value: stringp(`{"peerURLs":null}`)},
  409. {Key: "/1234/attributes", Value: stringp(`{"name":"node1","clientURLs":null}`)},
  410. {Key: "/1234/strange"},
  411. }},
  412. }
  413. for i, tt := range tests {
  414. if _, err := nodeToMember(tt); err == nil {
  415. t.Errorf("#%d: unexpected nil error", i)
  416. }
  417. }
  418. }
  419. func TestClusterAddMember(t *testing.T) {
  420. st := mockstore.NewRecorder()
  421. c := newTestCluster(nil)
  422. c.SetStore(st)
  423. c.AddMember(newTestMember(1, nil, "node1", nil))
  424. wactions := []testutil.Action{
  425. {
  426. Name: "Create",
  427. Params: []interface{}{
  428. path.Join(storeMembersPrefix, "1", "raftAttributes"),
  429. false,
  430. `{"peerURLs":null}`,
  431. false,
  432. store.TTLOptionSet{ExpireTime: store.Permanent},
  433. },
  434. },
  435. }
  436. if g := st.Action(); !reflect.DeepEqual(g, wactions) {
  437. t.Errorf("actions = %v, want %v", g, wactions)
  438. }
  439. }
  440. func TestClusterMembers(t *testing.T) {
  441. cls := &cluster{
  442. members: map[types.ID]*Member{
  443. 1: {ID: 1},
  444. 20: {ID: 20},
  445. 100: {ID: 100},
  446. 5: {ID: 5},
  447. 50: {ID: 50},
  448. },
  449. }
  450. w := []*Member{
  451. {ID: 1},
  452. {ID: 5},
  453. {ID: 20},
  454. {ID: 50},
  455. {ID: 100},
  456. }
  457. if g := cls.Members(); !reflect.DeepEqual(g, w) {
  458. t.Fatalf("Members()=%#v, want %#v", g, w)
  459. }
  460. }
  461. func TestClusterRemoveMember(t *testing.T) {
  462. st := mockstore.NewRecorder()
  463. c := newTestCluster(nil)
  464. c.SetStore(st)
  465. c.RemoveMember(1)
  466. wactions := []testutil.Action{
  467. {Name: "Delete", Params: []interface{}{memberStoreKey(1), true, true}},
  468. {Name: "Create", Params: []interface{}{removedMemberStoreKey(1), false, "", false, store.TTLOptionSet{ExpireTime: store.Permanent}}},
  469. }
  470. if !reflect.DeepEqual(st.Action(), wactions) {
  471. t.Errorf("actions = %v, want %v", st.Action(), wactions)
  472. }
  473. }
  474. func TestClusterUpdateAttributes(t *testing.T) {
  475. name := "etcd"
  476. clientURLs := []string{"http://127.0.0.1:4001"}
  477. tests := []struct {
  478. mems []*Member
  479. removed map[types.ID]bool
  480. wmems []*Member
  481. }{
  482. // update attributes of existing member
  483. {
  484. []*Member{
  485. newTestMember(1, nil, "", nil),
  486. },
  487. nil,
  488. []*Member{
  489. newTestMember(1, nil, name, clientURLs),
  490. },
  491. },
  492. // update attributes of removed member
  493. {
  494. nil,
  495. map[types.ID]bool{types.ID(1): true},
  496. nil,
  497. },
  498. }
  499. for i, tt := range tests {
  500. c := newTestCluster(tt.mems)
  501. c.removed = tt.removed
  502. c.UpdateAttributes(types.ID(1), Attributes{Name: name, ClientURLs: clientURLs})
  503. if g := c.Members(); !reflect.DeepEqual(g, tt.wmems) {
  504. t.Errorf("#%d: members = %+v, want %+v", i, g, tt.wmems)
  505. }
  506. }
  507. }
  508. func TestNodeToMember(t *testing.T) {
  509. n := &store.NodeExtern{Key: "/1234", Nodes: []*store.NodeExtern{
  510. {Key: "/1234/attributes", Value: stringp(`{"name":"node1","clientURLs":null}`)},
  511. {Key: "/1234/raftAttributes", Value: stringp(`{"peerURLs":null}`)},
  512. }}
  513. wm := &Member{ID: 0x1234, RaftAttributes: RaftAttributes{}, Attributes: Attributes{Name: "node1"}}
  514. m, err := nodeToMember(n)
  515. if err != nil {
  516. t.Fatalf("unexpected nodeToMember error: %v", err)
  517. }
  518. if !reflect.DeepEqual(m, wm) {
  519. t.Errorf("member = %+v, want %+v", m, wm)
  520. }
  521. }
  522. func newTestCluster(membs []*Member) *cluster {
  523. c := &cluster{members: make(map[types.ID]*Member), removed: make(map[types.ID]bool)}
  524. for _, m := range membs {
  525. c.members[m.ID] = m
  526. }
  527. return c
  528. }
  529. func stringp(s string) *string { return &s }
  530. func TestIsReadyToAddNewMember(t *testing.T) {
  531. tests := []struct {
  532. members []*Member
  533. want bool
  534. }{
  535. {
  536. // 0/3 members ready, should fail
  537. []*Member{
  538. newTestMember(1, nil, "", nil),
  539. newTestMember(2, nil, "", nil),
  540. newTestMember(3, nil, "", nil),
  541. },
  542. false,
  543. },
  544. {
  545. // 1/2 members ready, should fail
  546. []*Member{
  547. newTestMember(1, nil, "1", nil),
  548. newTestMember(2, nil, "", nil),
  549. },
  550. false,
  551. },
  552. {
  553. // 1/3 members ready, should fail
  554. []*Member{
  555. newTestMember(1, nil, "1", nil),
  556. newTestMember(2, nil, "", nil),
  557. newTestMember(3, nil, "", nil),
  558. },
  559. false,
  560. },
  561. {
  562. // 1/1 members ready, should succeed (special case of 1-member cluster for recovery)
  563. []*Member{
  564. newTestMember(1, nil, "1", nil),
  565. },
  566. true,
  567. },
  568. {
  569. // 2/3 members ready, should fail
  570. []*Member{
  571. newTestMember(1, nil, "1", nil),
  572. newTestMember(2, nil, "2", nil),
  573. newTestMember(3, nil, "", nil),
  574. },
  575. false,
  576. },
  577. {
  578. // 3/3 members ready, should be fine to add one member and retain quorum
  579. []*Member{
  580. newTestMember(1, nil, "1", nil),
  581. newTestMember(2, nil, "2", nil),
  582. newTestMember(3, nil, "3", nil),
  583. },
  584. true,
  585. },
  586. {
  587. // 3/4 members ready, should be fine to add one member and retain quorum
  588. []*Member{
  589. newTestMember(1, nil, "1", nil),
  590. newTestMember(2, nil, "2", nil),
  591. newTestMember(3, nil, "3", nil),
  592. newTestMember(4, nil, "", nil),
  593. },
  594. true,
  595. },
  596. {
  597. // empty cluster, it is impossible but should fail
  598. []*Member{},
  599. false,
  600. },
  601. }
  602. for i, tt := range tests {
  603. c := newTestCluster(tt.members)
  604. if got := c.isReadyToAddNewMember(); got != tt.want {
  605. t.Errorf("%d: isReadyToAddNewMember returned %t, want %t", i, got, tt.want)
  606. }
  607. }
  608. }
  609. func TestIsReadyToRemoveMember(t *testing.T) {
  610. tests := []struct {
  611. members []*Member
  612. removeID uint64
  613. want bool
  614. }{
  615. {
  616. // 1/1 members ready, should fail
  617. []*Member{
  618. newTestMember(1, nil, "1", nil),
  619. },
  620. 1,
  621. false,
  622. },
  623. {
  624. // 0/3 members ready, should fail
  625. []*Member{
  626. newTestMember(1, nil, "", nil),
  627. newTestMember(2, nil, "", nil),
  628. newTestMember(3, nil, "", nil),
  629. },
  630. 1,
  631. false,
  632. },
  633. {
  634. // 1/2 members ready, should be fine to remove unstarted member
  635. // (isReadyToRemoveMember() logic should return success, but operation itself would fail)
  636. []*Member{
  637. newTestMember(1, nil, "1", nil),
  638. newTestMember(2, nil, "", nil),
  639. },
  640. 2,
  641. true,
  642. },
  643. {
  644. // 2/3 members ready, should fail
  645. []*Member{
  646. newTestMember(1, nil, "1", nil),
  647. newTestMember(2, nil, "2", nil),
  648. newTestMember(3, nil, "", nil),
  649. },
  650. 2,
  651. false,
  652. },
  653. {
  654. // 3/3 members ready, should be fine to remove one member and retain quorum
  655. []*Member{
  656. newTestMember(1, nil, "1", nil),
  657. newTestMember(2, nil, "2", nil),
  658. newTestMember(3, nil, "3", nil),
  659. },
  660. 3,
  661. true,
  662. },
  663. {
  664. // 3/4 members ready, should be fine to remove one member
  665. []*Member{
  666. newTestMember(1, nil, "1", nil),
  667. newTestMember(2, nil, "2", nil),
  668. newTestMember(3, nil, "3", nil),
  669. newTestMember(4, nil, "", nil),
  670. },
  671. 3,
  672. true,
  673. },
  674. {
  675. // 3/4 members ready, should be fine to remove unstarted member
  676. []*Member{
  677. newTestMember(1, nil, "1", nil),
  678. newTestMember(2, nil, "2", nil),
  679. newTestMember(3, nil, "3", nil),
  680. newTestMember(4, nil, "", nil),
  681. },
  682. 4,
  683. true,
  684. },
  685. }
  686. for i, tt := range tests {
  687. c := newTestCluster(tt.members)
  688. if got := c.isReadyToRemoveMember(tt.removeID); got != tt.want {
  689. t.Errorf("%d: isReadyToAddNewMember returned %t, want %t", i, got, tt.want)
  690. }
  691. }
  692. }