client_test.go 46 KB

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