client_test.go 39 KB

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