cluster_test.go 16 KB

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