client_test.go 48 KB

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