client_test.go 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961
  1. /*
  2. Copyright 2014 CoreOS, Inc.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package etcdhttp
  14. import (
  15. "bytes"
  16. "encoding/json"
  17. "errors"
  18. "fmt"
  19. "io/ioutil"
  20. "net/http"
  21. "net/http/httptest"
  22. "net/url"
  23. "path"
  24. "reflect"
  25. "strings"
  26. "testing"
  27. "time"
  28. "github.com/coreos/etcd/Godeps/_workspace/src/github.com/jonboulle/clockwork"
  29. "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
  30. etcdErr "github.com/coreos/etcd/error"
  31. "github.com/coreos/etcd/etcdserver"
  32. "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
  33. "github.com/coreos/etcd/etcdserver/etcdserverpb"
  34. "github.com/coreos/etcd/pkg/testutil"
  35. "github.com/coreos/etcd/pkg/types"
  36. "github.com/coreos/etcd/raft/raftpb"
  37. "github.com/coreos/etcd/store"
  38. "github.com/coreos/etcd/version"
  39. )
  40. func mustMarshalEvent(t *testing.T, ev *store.Event) string {
  41. b := new(bytes.Buffer)
  42. if err := json.NewEncoder(b).Encode(ev); err != nil {
  43. t.Fatalf("error marshalling event %#v: %v", ev, err)
  44. }
  45. return b.String()
  46. }
  47. // mustNewForm takes a set of Values and constructs a PUT *http.Request,
  48. // with a URL constructed from appending the given path to the standard keysPrefix
  49. func mustNewForm(t *testing.T, p string, vals url.Values) *http.Request {
  50. u := testutil.MustNewURL(t, path.Join(keysPrefix, p))
  51. req, err := http.NewRequest("PUT", u.String(), strings.NewReader(vals.Encode()))
  52. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  53. if err != nil {
  54. t.Fatalf("error creating new request: %v", err)
  55. }
  56. return req
  57. }
  58. // mustNewPostForm takes a set of Values and constructs a POST *http.Request,
  59. // with a URL constructed from appending the given path to the standard keysPrefix
  60. func mustNewPostForm(t *testing.T, p string, vals url.Values) *http.Request {
  61. u := testutil.MustNewURL(t, path.Join(keysPrefix, p))
  62. req, err := http.NewRequest("POST", u.String(), strings.NewReader(vals.Encode()))
  63. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  64. if err != nil {
  65. t.Fatalf("error creating new request: %v", err)
  66. }
  67. return req
  68. }
  69. // mustNewRequest takes a path, appends it to the standard keysPrefix, and constructs
  70. // a GET *http.Request referencing the resulting URL
  71. func mustNewRequest(t *testing.T, p string) *http.Request {
  72. return mustNewMethodRequest(t, "GET", p)
  73. }
  74. func mustNewMethodRequest(t *testing.T, m, p string) *http.Request {
  75. return &http.Request{
  76. Method: m,
  77. URL: testutil.MustNewURL(t, path.Join(keysPrefix, p)),
  78. }
  79. }
  80. type serverRecorder struct {
  81. actions []action
  82. }
  83. func (s *serverRecorder) Start() {}
  84. func (s *serverRecorder) Stop() {}
  85. func (s *serverRecorder) Leader() types.ID { return types.ID(1) }
  86. func (s *serverRecorder) ID() types.ID { return types.ID(1) }
  87. func (s *serverRecorder) Do(_ context.Context, r etcdserverpb.Request) (etcdserver.Response, error) {
  88. s.actions = append(s.actions, action{name: "Do", params: []interface{}{r}})
  89. return etcdserver.Response{}, nil
  90. }
  91. func (s *serverRecorder) Process(_ context.Context, m raftpb.Message) error {
  92. s.actions = append(s.actions, action{name: "Process", params: []interface{}{m}})
  93. return nil
  94. }
  95. func (s *serverRecorder) AddMember(_ context.Context, m etcdserver.Member) error {
  96. s.actions = append(s.actions, action{name: "AddMember", params: []interface{}{m}})
  97. return nil
  98. }
  99. func (s *serverRecorder) RemoveMember(_ context.Context, id uint64) error {
  100. s.actions = append(s.actions, action{name: "RemoveMember", params: []interface{}{id}})
  101. return nil
  102. }
  103. func (s *serverRecorder) UpdateMember(_ context.Context, m etcdserver.Member) error {
  104. s.actions = append(s.actions, action{name: "UpdateMember", params: []interface{}{m}})
  105. return nil
  106. }
  107. type action struct {
  108. name string
  109. params []interface{}
  110. }
  111. // flushingRecorder provides a channel to allow users to block until the Recorder is Flushed()
  112. type flushingRecorder struct {
  113. *httptest.ResponseRecorder
  114. ch chan struct{}
  115. }
  116. func (fr *flushingRecorder) Flush() {
  117. fr.ResponseRecorder.Flush()
  118. fr.ch <- struct{}{}
  119. }
  120. // resServer implements the etcd.Server interface for testing.
  121. // It returns the given responsefrom any Do calls, and nil error
  122. type resServer struct {
  123. res etcdserver.Response
  124. }
  125. func (rs *resServer) Start() {}
  126. func (rs *resServer) Stop() {}
  127. func (rs *resServer) ID() types.ID { return types.ID(1) }
  128. func (rs *resServer) Leader() types.ID { return types.ID(1) }
  129. func (rs *resServer) Do(_ context.Context, _ etcdserverpb.Request) (etcdserver.Response, error) {
  130. return rs.res, nil
  131. }
  132. func (rs *resServer) Process(_ context.Context, _ raftpb.Message) error { return nil }
  133. func (rs *resServer) AddMember(_ context.Context, _ etcdserver.Member) error { return nil }
  134. func (rs *resServer) RemoveMember(_ context.Context, _ uint64) error { return nil }
  135. func (rs *resServer) UpdateMember(_ context.Context, _ etcdserver.Member) error { return nil }
  136. func boolp(b bool) *bool { return &b }
  137. type dummyRaftTimer struct{}
  138. func (drt dummyRaftTimer) Index() uint64 { return uint64(100) }
  139. func (drt dummyRaftTimer) Term() uint64 { return uint64(5) }
  140. type dummyWatcher struct {
  141. echan chan *store.Event
  142. sidx uint64
  143. }
  144. func (w *dummyWatcher) EventChan() chan *store.Event {
  145. return w.echan
  146. }
  147. func (w *dummyWatcher) StartIndex() uint64 { return w.sidx }
  148. func (w *dummyWatcher) Remove() {}
  149. func TestBadParseRequest(t *testing.T) {
  150. tests := []struct {
  151. in *http.Request
  152. wcode int
  153. }{
  154. {
  155. // parseForm failure
  156. &http.Request{
  157. Body: nil,
  158. Method: "PUT",
  159. },
  160. etcdErr.EcodeInvalidForm,
  161. },
  162. {
  163. // bad key prefix
  164. &http.Request{
  165. URL: testutil.MustNewURL(t, "/badprefix/"),
  166. },
  167. etcdErr.EcodeInvalidForm,
  168. },
  169. // bad values for prevIndex, waitIndex, ttl
  170. {
  171. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"garbage"}}),
  172. etcdErr.EcodeIndexNaN,
  173. },
  174. {
  175. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"1.5"}}),
  176. etcdErr.EcodeIndexNaN,
  177. },
  178. {
  179. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"-1"}}),
  180. etcdErr.EcodeIndexNaN,
  181. },
  182. {
  183. mustNewForm(t, "foo", url.Values{"waitIndex": []string{"garbage"}}),
  184. etcdErr.EcodeIndexNaN,
  185. },
  186. {
  187. mustNewForm(t, "foo", url.Values{"waitIndex": []string{"??"}}),
  188. etcdErr.EcodeIndexNaN,
  189. },
  190. {
  191. mustNewForm(t, "foo", url.Values{"ttl": []string{"-1"}}),
  192. etcdErr.EcodeTTLNaN,
  193. },
  194. // bad values for recursive, sorted, wait, prevExist, dir, stream
  195. {
  196. mustNewForm(t, "foo", url.Values{"recursive": []string{"hahaha"}}),
  197. etcdErr.EcodeInvalidField,
  198. },
  199. {
  200. mustNewForm(t, "foo", url.Values{"recursive": []string{"1234"}}),
  201. etcdErr.EcodeInvalidField,
  202. },
  203. {
  204. mustNewForm(t, "foo", url.Values{"recursive": []string{"?"}}),
  205. etcdErr.EcodeInvalidField,
  206. },
  207. {
  208. mustNewForm(t, "foo", url.Values{"sorted": []string{"?"}}),
  209. etcdErr.EcodeInvalidField,
  210. },
  211. {
  212. mustNewForm(t, "foo", url.Values{"sorted": []string{"x"}}),
  213. etcdErr.EcodeInvalidField,
  214. },
  215. {
  216. mustNewForm(t, "foo", url.Values{"wait": []string{"?!"}}),
  217. etcdErr.EcodeInvalidField,
  218. },
  219. {
  220. mustNewForm(t, "foo", url.Values{"wait": []string{"yes"}}),
  221. etcdErr.EcodeInvalidField,
  222. },
  223. {
  224. mustNewForm(t, "foo", url.Values{"prevExist": []string{"yes"}}),
  225. etcdErr.EcodeInvalidField,
  226. },
  227. {
  228. mustNewForm(t, "foo", url.Values{"prevExist": []string{"#2"}}),
  229. etcdErr.EcodeInvalidField,
  230. },
  231. {
  232. mustNewForm(t, "foo", url.Values{"dir": []string{"no"}}),
  233. etcdErr.EcodeInvalidField,
  234. },
  235. {
  236. mustNewForm(t, "foo", url.Values{"dir": []string{"file"}}),
  237. etcdErr.EcodeInvalidField,
  238. },
  239. {
  240. mustNewForm(t, "foo", url.Values{"quorum": []string{"no"}}),
  241. etcdErr.EcodeInvalidField,
  242. },
  243. {
  244. mustNewForm(t, "foo", url.Values{"quorum": []string{"file"}}),
  245. etcdErr.EcodeInvalidField,
  246. },
  247. {
  248. mustNewForm(t, "foo", url.Values{"stream": []string{"zzz"}}),
  249. etcdErr.EcodeInvalidField,
  250. },
  251. {
  252. mustNewForm(t, "foo", url.Values{"stream": []string{"something"}}),
  253. etcdErr.EcodeInvalidField,
  254. },
  255. // prevValue cannot be empty
  256. {
  257. mustNewForm(t, "foo", url.Values{"prevValue": []string{""}}),
  258. etcdErr.EcodePrevValueRequired,
  259. },
  260. // wait is only valid with GET requests
  261. {
  262. mustNewMethodRequest(t, "HEAD", "foo?wait=true"),
  263. etcdErr.EcodeInvalidField,
  264. },
  265. // query values are considered
  266. {
  267. mustNewRequest(t, "foo?prevExist=wrong"),
  268. etcdErr.EcodeInvalidField,
  269. },
  270. {
  271. mustNewRequest(t, "foo?ttl=wrong"),
  272. etcdErr.EcodeTTLNaN,
  273. },
  274. // but body takes precedence if both are specified
  275. {
  276. mustNewForm(
  277. t,
  278. "foo?ttl=12",
  279. url.Values{"ttl": []string{"garbage"}},
  280. ),
  281. etcdErr.EcodeTTLNaN,
  282. },
  283. {
  284. mustNewForm(
  285. t,
  286. "foo?prevExist=false",
  287. url.Values{"prevExist": []string{"yes"}},
  288. ),
  289. etcdErr.EcodeInvalidField,
  290. },
  291. }
  292. for i, tt := range tests {
  293. got, err := parseKeyRequest(tt.in, clockwork.NewFakeClock())
  294. if err == nil {
  295. t.Errorf("#%d: unexpected nil error!", i)
  296. continue
  297. }
  298. ee, ok := err.(*etcdErr.Error)
  299. if !ok {
  300. t.Errorf("#%d: err is not etcd.Error!", i)
  301. continue
  302. }
  303. if ee.ErrorCode != tt.wcode {
  304. t.Errorf("#%d: code=%d, want %v", i, ee.ErrorCode, tt.wcode)
  305. t.Logf("cause: %#v", ee.Cause)
  306. }
  307. if !reflect.DeepEqual(got, etcdserverpb.Request{}) {
  308. t.Errorf("#%d: unexpected non-empty Request: %#v", i, got)
  309. }
  310. }
  311. }
  312. func TestGoodParseRequest(t *testing.T) {
  313. fc := clockwork.NewFakeClock()
  314. fc.Advance(1111)
  315. tests := []struct {
  316. in *http.Request
  317. w etcdserverpb.Request
  318. }{
  319. {
  320. // good prefix, all other values default
  321. mustNewRequest(t, "foo"),
  322. etcdserverpb.Request{
  323. Method: "GET",
  324. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  325. },
  326. },
  327. {
  328. // value specified
  329. mustNewForm(
  330. t,
  331. "foo",
  332. url.Values{"value": []string{"some_value"}},
  333. ),
  334. etcdserverpb.Request{
  335. Method: "PUT",
  336. Val: "some_value",
  337. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  338. },
  339. },
  340. {
  341. // prevIndex specified
  342. mustNewForm(
  343. t,
  344. "foo",
  345. url.Values{"prevIndex": []string{"98765"}},
  346. ),
  347. etcdserverpb.Request{
  348. Method: "PUT",
  349. PrevIndex: 98765,
  350. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  351. },
  352. },
  353. {
  354. // recursive specified
  355. mustNewForm(
  356. t,
  357. "foo",
  358. url.Values{"recursive": []string{"true"}},
  359. ),
  360. etcdserverpb.Request{
  361. Method: "PUT",
  362. Recursive: true,
  363. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  364. },
  365. },
  366. {
  367. // sorted specified
  368. mustNewForm(
  369. t,
  370. "foo",
  371. url.Values{"sorted": []string{"true"}},
  372. ),
  373. etcdserverpb.Request{
  374. Method: "PUT",
  375. Sorted: true,
  376. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  377. },
  378. },
  379. {
  380. // quorum specified
  381. mustNewForm(
  382. t,
  383. "foo",
  384. url.Values{"quorum": []string{"true"}},
  385. ),
  386. etcdserverpb.Request{
  387. Method: "PUT",
  388. Quorum: true,
  389. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  390. },
  391. },
  392. {
  393. // wait specified
  394. mustNewRequest(t, "foo?wait=true"),
  395. etcdserverpb.Request{
  396. Method: "GET",
  397. Wait: true,
  398. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  399. },
  400. },
  401. {
  402. // empty TTL specified
  403. mustNewRequest(t, "foo?ttl="),
  404. etcdserverpb.Request{
  405. Method: "GET",
  406. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  407. Expiration: 0,
  408. },
  409. },
  410. {
  411. // non-empty TTL specified
  412. mustNewRequest(t, "foo?ttl=5678"),
  413. etcdserverpb.Request{
  414. Method: "GET",
  415. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  416. Expiration: fc.Now().Add(5678 * time.Second).UnixNano(),
  417. },
  418. },
  419. {
  420. // zero TTL specified
  421. mustNewRequest(t, "foo?ttl=0"),
  422. etcdserverpb.Request{
  423. Method: "GET",
  424. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  425. Expiration: fc.Now().UnixNano(),
  426. },
  427. },
  428. {
  429. // dir specified
  430. mustNewRequest(t, "foo?dir=true"),
  431. etcdserverpb.Request{
  432. Method: "GET",
  433. Dir: true,
  434. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  435. },
  436. },
  437. {
  438. // dir specified negatively
  439. mustNewRequest(t, "foo?dir=false"),
  440. etcdserverpb.Request{
  441. Method: "GET",
  442. Dir: false,
  443. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  444. },
  445. },
  446. {
  447. // prevExist should be non-null if specified
  448. mustNewForm(
  449. t,
  450. "foo",
  451. url.Values{"prevExist": []string{"true"}},
  452. ),
  453. etcdserverpb.Request{
  454. Method: "PUT",
  455. PrevExist: boolp(true),
  456. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  457. },
  458. },
  459. {
  460. // prevExist should be non-null if specified
  461. mustNewForm(
  462. t,
  463. "foo",
  464. url.Values{"prevExist": []string{"false"}},
  465. ),
  466. etcdserverpb.Request{
  467. Method: "PUT",
  468. PrevExist: boolp(false),
  469. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  470. },
  471. },
  472. // mix various fields
  473. {
  474. mustNewForm(
  475. t,
  476. "foo",
  477. url.Values{
  478. "value": []string{"some value"},
  479. "prevExist": []string{"true"},
  480. "prevValue": []string{"previous value"},
  481. },
  482. ),
  483. etcdserverpb.Request{
  484. Method: "PUT",
  485. PrevExist: boolp(true),
  486. PrevValue: "previous value",
  487. Val: "some value",
  488. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  489. },
  490. },
  491. // query parameters should be used if given
  492. {
  493. mustNewForm(
  494. t,
  495. "foo?prevValue=woof",
  496. url.Values{},
  497. ),
  498. etcdserverpb.Request{
  499. Method: "PUT",
  500. PrevValue: "woof",
  501. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  502. },
  503. },
  504. // but form values should take precedence over query parameters
  505. {
  506. mustNewForm(
  507. t,
  508. "foo?prevValue=woof",
  509. url.Values{
  510. "prevValue": []string{"miaow"},
  511. },
  512. ),
  513. etcdserverpb.Request{
  514. Method: "PUT",
  515. PrevValue: "miaow",
  516. Path: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  517. },
  518. },
  519. }
  520. for i, tt := range tests {
  521. got, err := parseKeyRequest(tt.in, fc)
  522. if err != nil {
  523. t.Errorf("#%d: err = %v, want %v", i, err, nil)
  524. }
  525. if !reflect.DeepEqual(got, tt.w) {
  526. t.Errorf("#%d: request=%#v, want %#v", i, got, tt.w)
  527. }
  528. }
  529. }
  530. func TestServeMembers(t *testing.T) {
  531. memb1 := etcdserver.Member{ID: 12, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080"}}}
  532. memb2 := etcdserver.Member{ID: 13, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8081"}}}
  533. cluster := &fakeCluster{
  534. id: 1,
  535. members: map[uint64]*etcdserver.Member{1: &memb1, 2: &memb2},
  536. }
  537. h := &membersHandler{
  538. server: &serverRecorder{},
  539. clock: clockwork.NewFakeClock(),
  540. clusterInfo: cluster,
  541. }
  542. wmc := string(`{"members":[{"id":"c","name":"","peerURLs":[],"clientURLs":["http://localhost:8080"]},{"id":"d","name":"","peerURLs":[],"clientURLs":["http://localhost:8081"]}]}`)
  543. tests := []struct {
  544. path string
  545. wcode int
  546. wct string
  547. wbody string
  548. }{
  549. {membersPrefix, http.StatusOK, "application/json", wmc + "\n"},
  550. {membersPrefix + "/", http.StatusOK, "application/json", wmc + "\n"},
  551. {path.Join(membersPrefix, "100"), http.StatusNotFound, "application/json", `{"message":"Not found"}`},
  552. {path.Join(membersPrefix, "foobar"), http.StatusNotFound, "application/json", `{"message":"Not found"}`},
  553. }
  554. for i, tt := range tests {
  555. req, err := http.NewRequest("GET", testutil.MustNewURL(t, tt.path).String(), nil)
  556. if err != nil {
  557. t.Fatal(err)
  558. }
  559. rw := httptest.NewRecorder()
  560. h.ServeHTTP(rw, req)
  561. if rw.Code != tt.wcode {
  562. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  563. }
  564. if gct := rw.Header().Get("Content-Type"); gct != tt.wct {
  565. t.Errorf("#%d: content-type = %s, want %s", i, gct, tt.wct)
  566. }
  567. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  568. wcid := cluster.ID().String()
  569. if gcid != wcid {
  570. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  571. }
  572. if rw.Body.String() != tt.wbody {
  573. t.Errorf("#%d: body = %q, want %q", i, rw.Body.String(), tt.wbody)
  574. }
  575. }
  576. }
  577. // TODO: consolidate **ALL** fake server implementations and add no leader test case.
  578. func TestServeLeader(t *testing.T) {
  579. memb1 := etcdserver.Member{ID: 1, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080"}}}
  580. memb2 := etcdserver.Member{ID: 2, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8081"}}}
  581. cluster := &fakeCluster{
  582. id: 1,
  583. members: map[uint64]*etcdserver.Member{1: &memb1, 2: &memb2},
  584. }
  585. h := &membersHandler{
  586. server: &serverRecorder{},
  587. clock: clockwork.NewFakeClock(),
  588. clusterInfo: cluster,
  589. }
  590. wmc := string(`{"id":"1","name":"","peerURLs":[],"clientURLs":["http://localhost:8080"]}`)
  591. tests := []struct {
  592. path string
  593. wcode int
  594. wct string
  595. wbody string
  596. }{
  597. {membersPrefix + "leader", http.StatusOK, "application/json", wmc + "\n"},
  598. // TODO: add no leader case
  599. }
  600. for i, tt := range tests {
  601. req, err := http.NewRequest("GET", testutil.MustNewURL(t, tt.path).String(), nil)
  602. if err != nil {
  603. t.Fatal(err)
  604. }
  605. rw := httptest.NewRecorder()
  606. h.ServeHTTP(rw, req)
  607. if rw.Code != tt.wcode {
  608. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  609. }
  610. if gct := rw.Header().Get("Content-Type"); gct != tt.wct {
  611. t.Errorf("#%d: content-type = %s, want %s", i, gct, tt.wct)
  612. }
  613. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  614. wcid := cluster.ID().String()
  615. if gcid != wcid {
  616. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  617. }
  618. if rw.Body.String() != tt.wbody {
  619. t.Errorf("#%d: body = %q, want %q", i, rw.Body.String(), tt.wbody)
  620. }
  621. }
  622. }
  623. func TestServeMembersCreate(t *testing.T) {
  624. u := testutil.MustNewURL(t, membersPrefix)
  625. b := []byte(`{"peerURLs":["http://127.0.0.1:1"]}`)
  626. req, err := http.NewRequest("POST", u.String(), bytes.NewReader(b))
  627. if err != nil {
  628. t.Fatal(err)
  629. }
  630. req.Header.Set("Content-Type", "application/json")
  631. s := &serverRecorder{}
  632. h := &membersHandler{
  633. server: s,
  634. clock: clockwork.NewFakeClock(),
  635. clusterInfo: &fakeCluster{id: 1},
  636. }
  637. rw := httptest.NewRecorder()
  638. h.ServeHTTP(rw, req)
  639. wcode := http.StatusCreated
  640. if rw.Code != wcode {
  641. t.Errorf("code=%d, want %d", rw.Code, wcode)
  642. }
  643. wct := "application/json"
  644. if gct := rw.Header().Get("Content-Type"); gct != wct {
  645. t.Errorf("content-type = %s, want %s", gct, wct)
  646. }
  647. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  648. wcid := h.clusterInfo.ID().String()
  649. if gcid != wcid {
  650. t.Errorf("cid = %s, want %s", gcid, wcid)
  651. }
  652. wb := `{"id":"2a86a83729b330d5","name":"","peerURLs":["http://127.0.0.1:1"],"clientURLs":[]}` + "\n"
  653. g := rw.Body.String()
  654. if g != wb {
  655. t.Errorf("got body=%q, want %q", g, wb)
  656. }
  657. wm := etcdserver.Member{
  658. ID: 3064321551348478165,
  659. RaftAttributes: etcdserver.RaftAttributes{
  660. PeerURLs: []string{"http://127.0.0.1:1"},
  661. },
  662. }
  663. wactions := []action{{name: "AddMember", params: []interface{}{wm}}}
  664. if !reflect.DeepEqual(s.actions, wactions) {
  665. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  666. }
  667. }
  668. func TestServeMembersDelete(t *testing.T) {
  669. req := &http.Request{
  670. Method: "DELETE",
  671. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "BEEF")),
  672. }
  673. s := &serverRecorder{}
  674. h := &membersHandler{
  675. server: s,
  676. clusterInfo: &fakeCluster{id: 1},
  677. }
  678. rw := httptest.NewRecorder()
  679. h.ServeHTTP(rw, req)
  680. wcode := http.StatusNoContent
  681. if rw.Code != wcode {
  682. t.Errorf("code=%d, want %d", rw.Code, wcode)
  683. }
  684. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  685. wcid := h.clusterInfo.ID().String()
  686. if gcid != wcid {
  687. t.Errorf("cid = %s, want %s", gcid, wcid)
  688. }
  689. g := rw.Body.String()
  690. if g != "" {
  691. t.Errorf("got body=%q, want %q", g, "")
  692. }
  693. wactions := []action{{name: "RemoveMember", params: []interface{}{uint64(0xBEEF)}}}
  694. if !reflect.DeepEqual(s.actions, wactions) {
  695. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  696. }
  697. }
  698. func TestServeMembersUpdate(t *testing.T) {
  699. u := testutil.MustNewURL(t, path.Join(membersPrefix, "1"))
  700. b := []byte(`{"peerURLs":["http://127.0.0.1:1"]}`)
  701. req, err := http.NewRequest("PUT", u.String(), bytes.NewReader(b))
  702. if err != nil {
  703. t.Fatal(err)
  704. }
  705. req.Header.Set("Content-Type", "application/json")
  706. s := &serverRecorder{}
  707. h := &membersHandler{
  708. server: s,
  709. clock: clockwork.NewFakeClock(),
  710. clusterInfo: &fakeCluster{id: 1},
  711. }
  712. rw := httptest.NewRecorder()
  713. h.ServeHTTP(rw, req)
  714. wcode := http.StatusNoContent
  715. if rw.Code != wcode {
  716. t.Errorf("code=%d, want %d", rw.Code, wcode)
  717. }
  718. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  719. wcid := h.clusterInfo.ID().String()
  720. if gcid != wcid {
  721. t.Errorf("cid = %s, want %s", gcid, wcid)
  722. }
  723. wm := etcdserver.Member{
  724. ID: 1,
  725. RaftAttributes: etcdserver.RaftAttributes{
  726. PeerURLs: []string{"http://127.0.0.1:1"},
  727. },
  728. }
  729. wactions := []action{{name: "UpdateMember", params: []interface{}{wm}}}
  730. if !reflect.DeepEqual(s.actions, wactions) {
  731. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  732. }
  733. }
  734. func TestServeMembersFail(t *testing.T) {
  735. tests := []struct {
  736. req *http.Request
  737. server etcdserver.Server
  738. wcode int
  739. }{
  740. {
  741. // bad method
  742. &http.Request{
  743. Method: "CONNECT",
  744. },
  745. &resServer{},
  746. http.StatusMethodNotAllowed,
  747. },
  748. {
  749. // bad method
  750. &http.Request{
  751. Method: "TRACE",
  752. },
  753. &resServer{},
  754. http.StatusMethodNotAllowed,
  755. },
  756. {
  757. // parse body error
  758. &http.Request{
  759. URL: testutil.MustNewURL(t, membersPrefix),
  760. Method: "POST",
  761. Body: ioutil.NopCloser(strings.NewReader("bad json")),
  762. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  763. },
  764. &resServer{},
  765. http.StatusBadRequest,
  766. },
  767. {
  768. // bad content type
  769. &http.Request{
  770. URL: testutil.MustNewURL(t, membersPrefix),
  771. Method: "POST",
  772. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  773. Header: map[string][]string{"Content-Type": []string{"application/bad"}},
  774. },
  775. &errServer{},
  776. http.StatusUnsupportedMediaType,
  777. },
  778. {
  779. // bad url
  780. &http.Request{
  781. URL: testutil.MustNewURL(t, membersPrefix),
  782. Method: "POST",
  783. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://a"]}`)),
  784. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  785. },
  786. &errServer{},
  787. http.StatusBadRequest,
  788. },
  789. {
  790. // etcdserver.AddMember error
  791. &http.Request{
  792. URL: testutil.MustNewURL(t, membersPrefix),
  793. Method: "POST",
  794. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  795. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  796. },
  797. &errServer{
  798. errors.New("Error while adding a member"),
  799. },
  800. http.StatusInternalServerError,
  801. },
  802. {
  803. // etcdserver.AddMember error
  804. &http.Request{
  805. URL: testutil.MustNewURL(t, membersPrefix),
  806. Method: "POST",
  807. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  808. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  809. },
  810. &errServer{
  811. etcdserver.ErrIDExists,
  812. },
  813. http.StatusConflict,
  814. },
  815. {
  816. // etcdserver.AddMember error
  817. &http.Request{
  818. URL: testutil.MustNewURL(t, membersPrefix),
  819. Method: "POST",
  820. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  821. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  822. },
  823. &errServer{
  824. etcdserver.ErrPeerURLexists,
  825. },
  826. http.StatusConflict,
  827. },
  828. {
  829. // etcdserver.RemoveMember error with arbitrary server error
  830. &http.Request{
  831. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "1")),
  832. Method: "DELETE",
  833. },
  834. &errServer{
  835. errors.New("Error while removing member"),
  836. },
  837. http.StatusInternalServerError,
  838. },
  839. {
  840. // etcdserver.RemoveMember error with previously removed ID
  841. &http.Request{
  842. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  843. Method: "DELETE",
  844. },
  845. &errServer{
  846. etcdserver.ErrIDRemoved,
  847. },
  848. http.StatusGone,
  849. },
  850. {
  851. // etcdserver.RemoveMember error with nonexistent ID
  852. &http.Request{
  853. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  854. Method: "DELETE",
  855. },
  856. &errServer{
  857. etcdserver.ErrIDNotFound,
  858. },
  859. http.StatusNotFound,
  860. },
  861. {
  862. // etcdserver.RemoveMember error with badly formed ID
  863. &http.Request{
  864. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "bad_id")),
  865. Method: "DELETE",
  866. },
  867. nil,
  868. http.StatusNotFound,
  869. },
  870. {
  871. // etcdserver.RemoveMember with no ID
  872. &http.Request{
  873. URL: testutil.MustNewURL(t, membersPrefix),
  874. Method: "DELETE",
  875. },
  876. nil,
  877. http.StatusMethodNotAllowed,
  878. },
  879. {
  880. // parse body error
  881. &http.Request{
  882. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  883. Method: "PUT",
  884. Body: ioutil.NopCloser(strings.NewReader("bad json")),
  885. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  886. },
  887. &resServer{},
  888. http.StatusBadRequest,
  889. },
  890. {
  891. // bad content type
  892. &http.Request{
  893. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  894. Method: "PUT",
  895. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  896. Header: map[string][]string{"Content-Type": []string{"application/bad"}},
  897. },
  898. &errServer{},
  899. http.StatusUnsupportedMediaType,
  900. },
  901. {
  902. // bad url
  903. &http.Request{
  904. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  905. Method: "PUT",
  906. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://a"]}`)),
  907. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  908. },
  909. &errServer{},
  910. http.StatusBadRequest,
  911. },
  912. {
  913. // etcdserver.UpdateMember error
  914. &http.Request{
  915. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  916. Method: "PUT",
  917. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  918. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  919. },
  920. &errServer{
  921. errors.New("blah"),
  922. },
  923. http.StatusInternalServerError,
  924. },
  925. {
  926. // etcdserver.UpdateMember error
  927. &http.Request{
  928. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  929. Method: "PUT",
  930. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  931. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  932. },
  933. &errServer{
  934. etcdserver.ErrPeerURLexists,
  935. },
  936. http.StatusConflict,
  937. },
  938. {
  939. // etcdserver.UpdateMember error
  940. &http.Request{
  941. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "0")),
  942. Method: "PUT",
  943. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  944. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  945. },
  946. &errServer{
  947. etcdserver.ErrIDNotFound,
  948. },
  949. http.StatusNotFound,
  950. },
  951. {
  952. // etcdserver.UpdateMember error with badly formed ID
  953. &http.Request{
  954. URL: testutil.MustNewURL(t, path.Join(membersPrefix, "bad_id")),
  955. Method: "PUT",
  956. },
  957. nil,
  958. http.StatusNotFound,
  959. },
  960. {
  961. // etcdserver.UpdateMember with no ID
  962. &http.Request{
  963. URL: testutil.MustNewURL(t, membersPrefix),
  964. Method: "PUT",
  965. },
  966. nil,
  967. http.StatusMethodNotAllowed,
  968. },
  969. }
  970. for i, tt := range tests {
  971. h := &membersHandler{
  972. server: tt.server,
  973. clusterInfo: &fakeCluster{id: 1},
  974. clock: clockwork.NewFakeClock(),
  975. }
  976. rw := httptest.NewRecorder()
  977. h.ServeHTTP(rw, tt.req)
  978. if rw.Code != tt.wcode {
  979. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  980. }
  981. if rw.Code != http.StatusMethodNotAllowed {
  982. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  983. wcid := h.clusterInfo.ID().String()
  984. if gcid != wcid {
  985. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  986. }
  987. }
  988. }
  989. }
  990. func TestWriteEvent(t *testing.T) {
  991. // nil event should not panic
  992. rw := httptest.NewRecorder()
  993. writeKeyEvent(rw, nil, dummyRaftTimer{})
  994. h := rw.Header()
  995. if len(h) > 0 {
  996. t.Fatalf("unexpected non-empty headers: %#v", h)
  997. }
  998. b := rw.Body.String()
  999. if len(b) > 0 {
  1000. t.Fatalf("unexpected non-empty body: %q", b)
  1001. }
  1002. tests := []struct {
  1003. ev *store.Event
  1004. idx string
  1005. // TODO(jonboulle): check body as well as just status code
  1006. code int
  1007. err error
  1008. }{
  1009. // standard case, standard 200 response
  1010. {
  1011. &store.Event{
  1012. Action: store.Get,
  1013. Node: &store.NodeExtern{},
  1014. PrevNode: &store.NodeExtern{},
  1015. },
  1016. "0",
  1017. http.StatusOK,
  1018. nil,
  1019. },
  1020. // check new nodes return StatusCreated
  1021. {
  1022. &store.Event{
  1023. Action: store.Create,
  1024. Node: &store.NodeExtern{},
  1025. PrevNode: &store.NodeExtern{},
  1026. },
  1027. "0",
  1028. http.StatusCreated,
  1029. nil,
  1030. },
  1031. }
  1032. for i, tt := range tests {
  1033. rw := httptest.NewRecorder()
  1034. writeKeyEvent(rw, tt.ev, dummyRaftTimer{})
  1035. if gct := rw.Header().Get("Content-Type"); gct != "application/json" {
  1036. t.Errorf("case %d: bad Content-Type: got %q, want application/json", i, gct)
  1037. }
  1038. if gri := rw.Header().Get("X-Raft-Index"); gri != "100" {
  1039. t.Errorf("case %d: bad X-Raft-Index header: got %s, want %s", i, gri, "100")
  1040. }
  1041. if grt := rw.Header().Get("X-Raft-Term"); grt != "5" {
  1042. t.Errorf("case %d: bad X-Raft-Term header: got %s, want %s", i, grt, "5")
  1043. }
  1044. if gei := rw.Header().Get("X-Etcd-Index"); gei != tt.idx {
  1045. t.Errorf("case %d: bad X-Etcd-Index header: got %s, want %s", i, gei, tt.idx)
  1046. }
  1047. if rw.Code != tt.code {
  1048. t.Errorf("case %d: bad response code: got %d, want %v", i, rw.Code, tt.code)
  1049. }
  1050. }
  1051. }
  1052. func TestV2DeprecatedMachinesEndpoint(t *testing.T) {
  1053. tests := []struct {
  1054. method string
  1055. wcode int
  1056. }{
  1057. {"GET", http.StatusOK},
  1058. {"HEAD", http.StatusOK},
  1059. {"POST", http.StatusMethodNotAllowed},
  1060. }
  1061. m := NewClientHandler(&etcdserver.EtcdServer{Cluster: &etcdserver.Cluster{}})
  1062. s := httptest.NewServer(m)
  1063. defer s.Close()
  1064. for _, tt := range tests {
  1065. req, err := http.NewRequest(tt.method, s.URL+deprecatedMachinesPrefix, nil)
  1066. if err != nil {
  1067. t.Fatal(err)
  1068. }
  1069. resp, err := http.DefaultClient.Do(req)
  1070. if err != nil {
  1071. t.Fatal(err)
  1072. }
  1073. if resp.StatusCode != tt.wcode {
  1074. t.Errorf("StatusCode = %d, expected %d", resp.StatusCode, tt.wcode)
  1075. }
  1076. }
  1077. }
  1078. func TestServeMachines(t *testing.T) {
  1079. cluster := &fakeCluster{
  1080. clientURLs: []string{"http://localhost:8080", "http://localhost:8081", "http://localhost:8082"},
  1081. }
  1082. writer := httptest.NewRecorder()
  1083. req, err := http.NewRequest("GET", "", nil)
  1084. if err != nil {
  1085. t.Fatal(err)
  1086. }
  1087. h := &deprecatedMachinesHandler{clusterInfo: cluster}
  1088. h.ServeHTTP(writer, req)
  1089. w := "http://localhost:8080, http://localhost:8081, http://localhost:8082"
  1090. if g := writer.Body.String(); g != w {
  1091. t.Errorf("body = %s, want %s", g, w)
  1092. }
  1093. if writer.Code != http.StatusOK {
  1094. t.Errorf("code = %d, want %d", writer.Code, http.StatusOK)
  1095. }
  1096. }
  1097. func TestGetID(t *testing.T) {
  1098. tests := []struct {
  1099. path string
  1100. wok bool
  1101. wid types.ID
  1102. wcode int
  1103. }{
  1104. {
  1105. "123",
  1106. true, 0x123, http.StatusOK,
  1107. },
  1108. {
  1109. "bad_id",
  1110. false, 0, http.StatusNotFound,
  1111. },
  1112. {
  1113. "",
  1114. false, 0, http.StatusMethodNotAllowed,
  1115. },
  1116. }
  1117. for i, tt := range tests {
  1118. w := httptest.NewRecorder()
  1119. id, ok := getID(tt.path, w)
  1120. if id != tt.wid {
  1121. t.Errorf("#%d: id = %d, want %d", i, id, tt.wid)
  1122. }
  1123. if ok != tt.wok {
  1124. t.Errorf("#%d: ok = %t, want %t", i, ok, tt.wok)
  1125. }
  1126. if w.Code != tt.wcode {
  1127. t.Errorf("#%d code = %d, want %d", i, w.Code, tt.wcode)
  1128. }
  1129. }
  1130. }
  1131. type dummyStats struct {
  1132. data []byte
  1133. }
  1134. func (ds *dummyStats) SelfStats() []byte { return ds.data }
  1135. func (ds *dummyStats) LeaderStats() []byte { return ds.data }
  1136. func (ds *dummyStats) StoreStats() []byte { return ds.data }
  1137. func (ds *dummyStats) UpdateRecvApp(_ types.ID, _ int64) {}
  1138. func TestServeSelfStats(t *testing.T) {
  1139. wb := []byte("some statistics")
  1140. w := string(wb)
  1141. sh := &statsHandler{
  1142. stats: &dummyStats{data: wb},
  1143. }
  1144. rw := httptest.NewRecorder()
  1145. sh.serveSelf(rw, &http.Request{Method: "GET"})
  1146. if rw.Code != http.StatusOK {
  1147. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  1148. }
  1149. wct := "application/json"
  1150. if gct := rw.Header().Get("Content-Type"); gct != wct {
  1151. t.Errorf("Content-Type = %q, want %q", gct, wct)
  1152. }
  1153. if g := rw.Body.String(); g != w {
  1154. t.Errorf("body = %s, want %s", g, w)
  1155. }
  1156. }
  1157. func TestSelfServeStatsBad(t *testing.T) {
  1158. for _, m := range []string{"PUT", "POST", "DELETE"} {
  1159. sh := &statsHandler{}
  1160. rw := httptest.NewRecorder()
  1161. sh.serveSelf(
  1162. rw,
  1163. &http.Request{
  1164. Method: m,
  1165. },
  1166. )
  1167. if rw.Code != http.StatusMethodNotAllowed {
  1168. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  1169. }
  1170. }
  1171. }
  1172. func TestLeaderServeStatsBad(t *testing.T) {
  1173. for _, m := range []string{"PUT", "POST", "DELETE"} {
  1174. sh := &statsHandler{}
  1175. rw := httptest.NewRecorder()
  1176. sh.serveLeader(
  1177. rw,
  1178. &http.Request{
  1179. Method: m,
  1180. },
  1181. )
  1182. if rw.Code != http.StatusMethodNotAllowed {
  1183. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  1184. }
  1185. }
  1186. }
  1187. func TestServeLeaderStats(t *testing.T) {
  1188. wb := []byte("some statistics")
  1189. w := string(wb)
  1190. sh := &statsHandler{
  1191. stats: &dummyStats{data: wb},
  1192. }
  1193. rw := httptest.NewRecorder()
  1194. sh.serveLeader(rw, &http.Request{Method: "GET"})
  1195. if rw.Code != http.StatusOK {
  1196. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  1197. }
  1198. wct := "application/json"
  1199. if gct := rw.Header().Get("Content-Type"); gct != wct {
  1200. t.Errorf("Content-Type = %q, want %q", gct, wct)
  1201. }
  1202. if g := rw.Body.String(); g != w {
  1203. t.Errorf("body = %s, want %s", g, w)
  1204. }
  1205. }
  1206. func TestServeStoreStats(t *testing.T) {
  1207. wb := []byte("some statistics")
  1208. w := string(wb)
  1209. sh := &statsHandler{
  1210. stats: &dummyStats{data: wb},
  1211. }
  1212. rw := httptest.NewRecorder()
  1213. sh.serveStore(rw, &http.Request{Method: "GET"})
  1214. if rw.Code != http.StatusOK {
  1215. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  1216. }
  1217. wct := "application/json"
  1218. if gct := rw.Header().Get("Content-Type"); gct != wct {
  1219. t.Errorf("Content-Type = %q, want %q", gct, wct)
  1220. }
  1221. if g := rw.Body.String(); g != w {
  1222. t.Errorf("body = %s, want %s", g, w)
  1223. }
  1224. }
  1225. func TestServeVersion(t *testing.T) {
  1226. req, err := http.NewRequest("GET", "", nil)
  1227. if err != nil {
  1228. t.Fatalf("error creating request: %v", err)
  1229. }
  1230. rw := httptest.NewRecorder()
  1231. serveVersion(rw, req)
  1232. if rw.Code != http.StatusOK {
  1233. t.Errorf("code=%d, want %d", rw.Code, http.StatusOK)
  1234. }
  1235. w := fmt.Sprintf("etcd %s", version.Version)
  1236. if g := rw.Body.String(); g != w {
  1237. t.Fatalf("body = %q, want %q", g, w)
  1238. }
  1239. }
  1240. func TestServeVersionFails(t *testing.T) {
  1241. for _, m := range []string{
  1242. "CONNECT", "TRACE", "PUT", "POST", "HEAD",
  1243. } {
  1244. req, err := http.NewRequest(m, "", nil)
  1245. if err != nil {
  1246. t.Fatalf("error creating request: %v", err)
  1247. }
  1248. rw := httptest.NewRecorder()
  1249. serveVersion(rw, req)
  1250. if rw.Code != http.StatusMethodNotAllowed {
  1251. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  1252. }
  1253. }
  1254. }
  1255. func TestBadServeKeys(t *testing.T) {
  1256. testBadCases := []struct {
  1257. req *http.Request
  1258. server etcdserver.Server
  1259. wcode int
  1260. wbody string
  1261. }{
  1262. {
  1263. // bad method
  1264. &http.Request{
  1265. Method: "CONNECT",
  1266. },
  1267. &resServer{},
  1268. http.StatusMethodNotAllowed,
  1269. "Method Not Allowed",
  1270. },
  1271. {
  1272. // bad method
  1273. &http.Request{
  1274. Method: "TRACE",
  1275. },
  1276. &resServer{},
  1277. http.StatusMethodNotAllowed,
  1278. "Method Not Allowed",
  1279. },
  1280. {
  1281. // parseRequest error
  1282. &http.Request{
  1283. Body: nil,
  1284. Method: "PUT",
  1285. },
  1286. &resServer{},
  1287. http.StatusBadRequest,
  1288. `{"errorCode":210,"message":"Invalid POST form","cause":"missing form body","index":0}`,
  1289. },
  1290. {
  1291. // etcdserver.Server error
  1292. mustNewRequest(t, "foo"),
  1293. &errServer{
  1294. errors.New("Internal Server Error"),
  1295. },
  1296. http.StatusInternalServerError,
  1297. `{"message":"Internal Server Error"}`,
  1298. },
  1299. {
  1300. // etcdserver.Server etcd error
  1301. mustNewRequest(t, "foo"),
  1302. &errServer{
  1303. etcdErr.NewError(etcdErr.EcodeKeyNotFound, "/1/pant", 0),
  1304. },
  1305. http.StatusNotFound,
  1306. `{"errorCode":100,"message":"Key not found","cause":"/pant","index":0}`,
  1307. },
  1308. {
  1309. // non-event/watcher response from etcdserver.Server
  1310. mustNewRequest(t, "foo"),
  1311. &resServer{
  1312. etcdserver.Response{},
  1313. },
  1314. http.StatusInternalServerError,
  1315. `{"message":"Internal Server Error"}`,
  1316. },
  1317. }
  1318. for i, tt := range testBadCases {
  1319. h := &keysHandler{
  1320. timeout: 0, // context times out immediately
  1321. server: tt.server,
  1322. clusterInfo: &fakeCluster{id: 1},
  1323. }
  1324. rw := httptest.NewRecorder()
  1325. h.ServeHTTP(rw, tt.req)
  1326. if rw.Code != tt.wcode {
  1327. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  1328. }
  1329. if rw.Code != http.StatusMethodNotAllowed {
  1330. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  1331. wcid := h.clusterInfo.ID().String()
  1332. if gcid != wcid {
  1333. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  1334. }
  1335. }
  1336. if g := strings.TrimSuffix(rw.Body.String(), "\n"); g != tt.wbody {
  1337. t.Errorf("#%d: body = %s, want %s", i, g, tt.wbody)
  1338. }
  1339. }
  1340. }
  1341. func TestServeKeysGood(t *testing.T) {
  1342. tests := []struct {
  1343. req *http.Request
  1344. wcode int
  1345. }{
  1346. {
  1347. mustNewMethodRequest(t, "HEAD", "foo"),
  1348. http.StatusOK,
  1349. },
  1350. {
  1351. mustNewMethodRequest(t, "GET", "foo"),
  1352. http.StatusOK,
  1353. },
  1354. {
  1355. mustNewForm(t, "foo", url.Values{"value": []string{"bar"}}),
  1356. http.StatusOK,
  1357. },
  1358. {
  1359. mustNewMethodRequest(t, "DELETE", "foo"),
  1360. http.StatusOK,
  1361. },
  1362. {
  1363. mustNewPostForm(t, "foo", url.Values{"value": []string{"bar"}}),
  1364. http.StatusOK,
  1365. },
  1366. }
  1367. server := &resServer{
  1368. etcdserver.Response{
  1369. Event: &store.Event{
  1370. Action: store.Get,
  1371. Node: &store.NodeExtern{},
  1372. },
  1373. },
  1374. }
  1375. for i, tt := range tests {
  1376. h := &keysHandler{
  1377. timeout: time.Hour,
  1378. server: server,
  1379. timer: &dummyRaftTimer{},
  1380. clusterInfo: &fakeCluster{id: 1},
  1381. }
  1382. rw := httptest.NewRecorder()
  1383. h.ServeHTTP(rw, tt.req)
  1384. if rw.Code != tt.wcode {
  1385. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  1386. }
  1387. }
  1388. }
  1389. func TestServeKeysEvent(t *testing.T) {
  1390. req := mustNewRequest(t, "foo")
  1391. server := &resServer{
  1392. etcdserver.Response{
  1393. Event: &store.Event{
  1394. Action: store.Get,
  1395. Node: &store.NodeExtern{},
  1396. },
  1397. },
  1398. }
  1399. h := &keysHandler{
  1400. timeout: time.Hour,
  1401. server: server,
  1402. clusterInfo: &fakeCluster{id: 1},
  1403. timer: &dummyRaftTimer{},
  1404. }
  1405. rw := httptest.NewRecorder()
  1406. h.ServeHTTP(rw, req)
  1407. wcode := http.StatusOK
  1408. wbody := mustMarshalEvent(
  1409. t,
  1410. &store.Event{
  1411. Action: store.Get,
  1412. Node: &store.NodeExtern{},
  1413. },
  1414. )
  1415. if rw.Code != wcode {
  1416. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1417. }
  1418. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  1419. wcid := h.clusterInfo.ID().String()
  1420. if gcid != wcid {
  1421. t.Errorf("cid = %s, want %s", gcid, wcid)
  1422. }
  1423. g := rw.Body.String()
  1424. if g != wbody {
  1425. t.Errorf("got body=%#v, want %#v", g, wbody)
  1426. }
  1427. }
  1428. func TestServeKeysWatch(t *testing.T) {
  1429. req := mustNewRequest(t, "/foo/bar")
  1430. ec := make(chan *store.Event)
  1431. dw := &dummyWatcher{
  1432. echan: ec,
  1433. }
  1434. server := &resServer{
  1435. etcdserver.Response{
  1436. Watcher: dw,
  1437. },
  1438. }
  1439. h := &keysHandler{
  1440. timeout: time.Hour,
  1441. server: server,
  1442. clusterInfo: &fakeCluster{id: 1},
  1443. timer: &dummyRaftTimer{},
  1444. }
  1445. go func() {
  1446. ec <- &store.Event{
  1447. Action: store.Get,
  1448. Node: &store.NodeExtern{},
  1449. }
  1450. }()
  1451. rw := httptest.NewRecorder()
  1452. h.ServeHTTP(rw, req)
  1453. wcode := http.StatusOK
  1454. wbody := mustMarshalEvent(
  1455. t,
  1456. &store.Event{
  1457. Action: store.Get,
  1458. Node: &store.NodeExtern{},
  1459. },
  1460. )
  1461. if rw.Code != wcode {
  1462. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1463. }
  1464. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  1465. wcid := h.clusterInfo.ID().String()
  1466. if gcid != wcid {
  1467. t.Errorf("cid = %s, want %s", gcid, wcid)
  1468. }
  1469. g := rw.Body.String()
  1470. if g != wbody {
  1471. t.Errorf("got body=%#v, want %#v", g, wbody)
  1472. }
  1473. }
  1474. type recordingCloseNotifier struct {
  1475. *httptest.ResponseRecorder
  1476. cn chan bool
  1477. }
  1478. func (rcn *recordingCloseNotifier) CloseNotify() <-chan bool {
  1479. return rcn.cn
  1480. }
  1481. func TestHandleWatch(t *testing.T) {
  1482. defaultRwRr := func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1483. r := httptest.NewRecorder()
  1484. return r, r
  1485. }
  1486. noopEv := func(chan *store.Event) {}
  1487. tests := []struct {
  1488. getCtx func() context.Context
  1489. getRwRr func() (http.ResponseWriter, *httptest.ResponseRecorder)
  1490. doToChan func(chan *store.Event)
  1491. wbody string
  1492. }{
  1493. {
  1494. // Normal case: one event
  1495. context.Background,
  1496. defaultRwRr,
  1497. func(ch chan *store.Event) {
  1498. ch <- &store.Event{
  1499. Action: store.Get,
  1500. Node: &store.NodeExtern{},
  1501. }
  1502. },
  1503. mustMarshalEvent(
  1504. t,
  1505. &store.Event{
  1506. Action: store.Get,
  1507. Node: &store.NodeExtern{},
  1508. },
  1509. ),
  1510. },
  1511. {
  1512. // Channel is closed, no event
  1513. context.Background,
  1514. defaultRwRr,
  1515. func(ch chan *store.Event) {
  1516. close(ch)
  1517. },
  1518. "",
  1519. },
  1520. {
  1521. // Simulate a timed-out context
  1522. func() context.Context {
  1523. ctx, cancel := context.WithCancel(context.Background())
  1524. cancel()
  1525. return ctx
  1526. },
  1527. defaultRwRr,
  1528. noopEv,
  1529. "",
  1530. },
  1531. {
  1532. // Close-notifying request
  1533. context.Background,
  1534. func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1535. rw := &recordingCloseNotifier{
  1536. ResponseRecorder: httptest.NewRecorder(),
  1537. cn: make(chan bool, 1),
  1538. }
  1539. rw.cn <- true
  1540. return rw, rw.ResponseRecorder
  1541. },
  1542. noopEv,
  1543. "",
  1544. },
  1545. }
  1546. for i, tt := range tests {
  1547. rw, rr := tt.getRwRr()
  1548. wa := &dummyWatcher{
  1549. echan: make(chan *store.Event, 1),
  1550. sidx: 10,
  1551. }
  1552. tt.doToChan(wa.echan)
  1553. handleKeyWatch(tt.getCtx(), rw, wa, false, dummyRaftTimer{})
  1554. wcode := http.StatusOK
  1555. wct := "application/json"
  1556. wei := "10"
  1557. wri := "100"
  1558. wrt := "5"
  1559. if rr.Code != wcode {
  1560. t.Errorf("#%d: got code=%d, want %d", i, rr.Code, wcode)
  1561. }
  1562. h := rr.Header()
  1563. if ct := h.Get("Content-Type"); ct != wct {
  1564. t.Errorf("#%d: Content-Type=%q, want %q", i, ct, wct)
  1565. }
  1566. if ei := h.Get("X-Etcd-Index"); ei != wei {
  1567. t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, ei, wei)
  1568. }
  1569. if ri := h.Get("X-Raft-Index"); ri != wri {
  1570. t.Errorf("#%d: X-Raft-Index=%q, want %q", i, ri, wri)
  1571. }
  1572. if rt := h.Get("X-Raft-Term"); rt != wrt {
  1573. t.Errorf("#%d: X-Raft-Term=%q, want %q", i, rt, wrt)
  1574. }
  1575. g := rr.Body.String()
  1576. if g != tt.wbody {
  1577. t.Errorf("#%d: got body=%#v, want %#v", i, g, tt.wbody)
  1578. }
  1579. }
  1580. }
  1581. func TestHandleWatchStreaming(t *testing.T) {
  1582. rw := &flushingRecorder{
  1583. httptest.NewRecorder(),
  1584. make(chan struct{}, 1),
  1585. }
  1586. wa := &dummyWatcher{
  1587. echan: make(chan *store.Event),
  1588. }
  1589. // Launch the streaming handler in the background with a cancellable context
  1590. ctx, cancel := context.WithCancel(context.Background())
  1591. done := make(chan struct{})
  1592. go func() {
  1593. handleKeyWatch(ctx, rw, wa, true, dummyRaftTimer{})
  1594. close(done)
  1595. }()
  1596. // Expect one Flush for the headers etc.
  1597. select {
  1598. case <-rw.ch:
  1599. case <-time.After(time.Second):
  1600. t.Fatalf("timed out waiting for flush")
  1601. }
  1602. // Expect headers but no body
  1603. wcode := http.StatusOK
  1604. wct := "application/json"
  1605. wbody := ""
  1606. if rw.Code != wcode {
  1607. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1608. }
  1609. h := rw.Header()
  1610. if ct := h.Get("Content-Type"); ct != wct {
  1611. t.Errorf("Content-Type=%q, want %q", ct, wct)
  1612. }
  1613. g := rw.Body.String()
  1614. if g != wbody {
  1615. t.Errorf("got body=%#v, want %#v", g, wbody)
  1616. }
  1617. // Now send the first event
  1618. select {
  1619. case wa.echan <- &store.Event{
  1620. Action: store.Get,
  1621. Node: &store.NodeExtern{},
  1622. }:
  1623. case <-time.After(time.Second):
  1624. t.Fatal("timed out waiting for send")
  1625. }
  1626. // Wait for it to be flushed...
  1627. select {
  1628. case <-rw.ch:
  1629. case <-time.After(time.Second):
  1630. t.Fatalf("timed out waiting for flush")
  1631. }
  1632. // And check the body is as expected
  1633. wbody = mustMarshalEvent(
  1634. t,
  1635. &store.Event{
  1636. Action: store.Get,
  1637. Node: &store.NodeExtern{},
  1638. },
  1639. )
  1640. g = rw.Body.String()
  1641. if g != wbody {
  1642. t.Errorf("got body=%#v, want %#v", g, wbody)
  1643. }
  1644. // Rinse and repeat
  1645. select {
  1646. case wa.echan <- &store.Event{
  1647. Action: store.Get,
  1648. Node: &store.NodeExtern{},
  1649. }:
  1650. case <-time.After(time.Second):
  1651. t.Fatal("timed out waiting for send")
  1652. }
  1653. select {
  1654. case <-rw.ch:
  1655. case <-time.After(time.Second):
  1656. t.Fatalf("timed out waiting for flush")
  1657. }
  1658. // This time, we expect to see both events
  1659. wbody = wbody + wbody
  1660. g = rw.Body.String()
  1661. if g != wbody {
  1662. t.Errorf("got body=%#v, want %#v", g, wbody)
  1663. }
  1664. // Finally, time out the connection and ensure the serving goroutine returns
  1665. cancel()
  1666. select {
  1667. case <-done:
  1668. case <-time.After(time.Second):
  1669. t.Fatalf("timed out waiting for done")
  1670. }
  1671. }
  1672. func TestTrimEventPrefix(t *testing.T) {
  1673. pre := "/abc"
  1674. tests := []struct {
  1675. ev *store.Event
  1676. wev *store.Event
  1677. }{
  1678. {
  1679. nil,
  1680. nil,
  1681. },
  1682. {
  1683. &store.Event{},
  1684. &store.Event{},
  1685. },
  1686. {
  1687. &store.Event{Node: &store.NodeExtern{Key: "/abc/def"}},
  1688. &store.Event{Node: &store.NodeExtern{Key: "/def"}},
  1689. },
  1690. {
  1691. &store.Event{PrevNode: &store.NodeExtern{Key: "/abc/ghi"}},
  1692. &store.Event{PrevNode: &store.NodeExtern{Key: "/ghi"}},
  1693. },
  1694. {
  1695. &store.Event{
  1696. Node: &store.NodeExtern{Key: "/abc/def"},
  1697. PrevNode: &store.NodeExtern{Key: "/abc/ghi"},
  1698. },
  1699. &store.Event{
  1700. Node: &store.NodeExtern{Key: "/def"},
  1701. PrevNode: &store.NodeExtern{Key: "/ghi"},
  1702. },
  1703. },
  1704. }
  1705. for i, tt := range tests {
  1706. ev := trimEventPrefix(tt.ev, pre)
  1707. if !reflect.DeepEqual(ev, tt.wev) {
  1708. t.Errorf("#%d: event = %+v, want %+v", i, ev, tt.wev)
  1709. }
  1710. }
  1711. }
  1712. func TestTrimNodeExternPrefix(t *testing.T) {
  1713. pre := "/abc"
  1714. tests := []struct {
  1715. n *store.NodeExtern
  1716. wn *store.NodeExtern
  1717. }{
  1718. {
  1719. nil,
  1720. nil,
  1721. },
  1722. {
  1723. &store.NodeExtern{Key: "/abc/def"},
  1724. &store.NodeExtern{Key: "/def"},
  1725. },
  1726. {
  1727. &store.NodeExtern{
  1728. Key: "/abc/def",
  1729. Nodes: []*store.NodeExtern{
  1730. {Key: "/abc/def/1"},
  1731. {Key: "/abc/def/2"},
  1732. },
  1733. },
  1734. &store.NodeExtern{
  1735. Key: "/def",
  1736. Nodes: []*store.NodeExtern{
  1737. {Key: "/def/1"},
  1738. {Key: "/def/2"},
  1739. },
  1740. },
  1741. },
  1742. }
  1743. for i, tt := range tests {
  1744. n := trimNodeExternPrefix(tt.n, pre)
  1745. if !reflect.DeepEqual(n, tt.wn) {
  1746. t.Errorf("#%d: node = %+v, want %+v", i, n, tt.wn)
  1747. }
  1748. }
  1749. }
  1750. func TestTrimPrefix(t *testing.T) {
  1751. tests := []struct {
  1752. in string
  1753. prefix string
  1754. w string
  1755. }{
  1756. {"/v2/members", "/v2/members", ""},
  1757. {"/v2/members/", "/v2/members", ""},
  1758. {"/v2/members/foo", "/v2/members", "foo"},
  1759. }
  1760. for i, tt := range tests {
  1761. if g := trimPrefix(tt.in, tt.prefix); g != tt.w {
  1762. t.Errorf("#%d: trimPrefix = %q, want %q", i, g, tt.w)
  1763. }
  1764. }
  1765. }
  1766. func TestNewMemberCollection(t *testing.T) {
  1767. fixture := []*etcdserver.Member{
  1768. &etcdserver.Member{
  1769. ID: 12,
  1770. Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"}},
  1771. RaftAttributes: etcdserver.RaftAttributes{PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"}},
  1772. },
  1773. &etcdserver.Member{
  1774. ID: 13,
  1775. Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:9090", "http://localhost:9091"}},
  1776. RaftAttributes: etcdserver.RaftAttributes{PeerURLs: []string{"http://localhost:9092", "http://localhost:9093"}},
  1777. },
  1778. }
  1779. got := newMemberCollection(fixture)
  1780. want := httptypes.MemberCollection([]httptypes.Member{
  1781. httptypes.Member{
  1782. ID: "c",
  1783. ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"},
  1784. PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"},
  1785. },
  1786. httptypes.Member{
  1787. ID: "d",
  1788. ClientURLs: []string{"http://localhost:9090", "http://localhost:9091"},
  1789. PeerURLs: []string{"http://localhost:9092", "http://localhost:9093"},
  1790. },
  1791. })
  1792. if !reflect.DeepEqual(&want, got) {
  1793. t.Fatalf("newMemberCollection failure: want=%#v, got=%#v", &want, got)
  1794. }
  1795. }
  1796. func TestNewMember(t *testing.T) {
  1797. fixture := &etcdserver.Member{
  1798. ID: 12,
  1799. Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"}},
  1800. RaftAttributes: etcdserver.RaftAttributes{PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"}},
  1801. }
  1802. got := newMember(fixture)
  1803. want := httptypes.Member{
  1804. ID: "c",
  1805. ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"},
  1806. PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"},
  1807. }
  1808. if !reflect.DeepEqual(want, got) {
  1809. t.Fatalf("newMember failure: want=%#v, got=%#v", want, got)
  1810. }
  1811. }