client_test.go 47 KB

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