client_test.go 36 KB

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