client_test.go 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566
  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: 12, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080"}}}
  527. memb2 := etcdserver.Member{ID: 13, 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. wmc := string(`[{"id":"c","name":"","peerURLs":[],"clientURLs":[""]},{"id":"d","name":"","peerURLs":[],"clientURLs":[""]}]`)
  538. tests := []struct {
  539. path string
  540. wcode int
  541. wct string
  542. wbody string
  543. }{
  544. {adminMembersPrefix, http.StatusOK, "application/json", wmc + "\n"},
  545. {adminMembersPrefix + "/", http.StatusOK, "application/json", wmc + "\n"},
  546. {path.Join(adminMembersPrefix, "100"), http.StatusNotFound, "application/json", `{"message":"Not found"}`},
  547. {path.Join(adminMembersPrefix, "foobar"), http.StatusNotFound, "application/json", `{"message":"Not found"}`},
  548. }
  549. for i, tt := range tests {
  550. req, err := http.NewRequest("GET", mustNewURL(t, tt.path).String(), nil)
  551. if err != nil {
  552. t.Fatal(err)
  553. }
  554. rw := httptest.NewRecorder()
  555. h.ServeHTTP(rw, req)
  556. if rw.Code != tt.wcode {
  557. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  558. }
  559. if gct := rw.Header().Get("Content-Type"); gct != tt.wct {
  560. t.Errorf("#%d: content-type = %s, want %s", i, gct, tt.wct)
  561. }
  562. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  563. wcid := strconv.FormatUint(cluster.ID(), 16)
  564. if gcid != wcid {
  565. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  566. }
  567. if rw.Body.String() != tt.wbody {
  568. t.Errorf("#%d: body = %q, want %q", i, rw.Body.String(), tt.wbody)
  569. }
  570. }
  571. }
  572. func TestServeAdminMembersPut(t *testing.T) {
  573. u := mustNewURL(t, adminMembersPrefix)
  574. raftAttr := etcdserver.RaftAttributes{PeerURLs: []string{"http://127.0.0.1:1"}}
  575. b, err := json.Marshal(raftAttr)
  576. if err != nil {
  577. t.Fatal(err)
  578. }
  579. body := bytes.NewReader(b)
  580. req, err := http.NewRequest("POST", u.String(), body)
  581. if err != nil {
  582. t.Fatal(err)
  583. }
  584. req.Header.Set("Content-Type", "application/json")
  585. s := &serverRecorder{}
  586. h := &adminMembersHandler{
  587. server: s,
  588. clock: clockwork.NewFakeClock(),
  589. clusterInfo: &fakeCluster{id: 1},
  590. }
  591. rw := httptest.NewRecorder()
  592. h.ServeHTTP(rw, req)
  593. wcode := http.StatusCreated
  594. if rw.Code != wcode {
  595. t.Errorf("code=%d, want %d", rw.Code, wcode)
  596. }
  597. wm := etcdserver.Member{
  598. ID: 3064321551348478165,
  599. RaftAttributes: raftAttr,
  600. }
  601. wb, err := json.Marshal(wm)
  602. if err != nil {
  603. t.Fatal(err)
  604. }
  605. wct := "application/json"
  606. if gct := rw.Header().Get("Content-Type"); gct != wct {
  607. t.Errorf("content-type = %s, want %s", gct, wct)
  608. }
  609. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  610. wcid := strconv.FormatUint(h.clusterInfo.ID(), 16)
  611. if gcid != wcid {
  612. t.Errorf("cid = %s, want %s", gcid, wcid)
  613. }
  614. g := rw.Body.String()
  615. w := string(wb) + "\n"
  616. if g != w {
  617. t.Errorf("got body=%q, want %q", g, w)
  618. }
  619. wactions := []action{{name: "AddMember", params: []interface{}{wm}}}
  620. if !reflect.DeepEqual(s.actions, wactions) {
  621. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  622. }
  623. }
  624. func TestServeAdminMembersDelete(t *testing.T) {
  625. req := &http.Request{
  626. Method: "DELETE",
  627. URL: mustNewURL(t, path.Join(adminMembersPrefix, "BEEF")),
  628. }
  629. s := &serverRecorder{}
  630. h := &adminMembersHandler{
  631. server: s,
  632. clusterInfo: &fakeCluster{id: 1},
  633. }
  634. rw := httptest.NewRecorder()
  635. h.ServeHTTP(rw, req)
  636. wcode := http.StatusNoContent
  637. if rw.Code != wcode {
  638. t.Errorf("code=%d, want %d", rw.Code, wcode)
  639. }
  640. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  641. wcid := strconv.FormatUint(h.clusterInfo.ID(), 16)
  642. if gcid != wcid {
  643. t.Errorf("cid = %s, want %s", gcid, wcid)
  644. }
  645. g := rw.Body.String()
  646. if g != "" {
  647. t.Errorf("got body=%q, want %q", g, "")
  648. }
  649. wactions := []action{{name: "RemoveMember", params: []interface{}{uint64(0xBEEF)}}}
  650. if !reflect.DeepEqual(s.actions, wactions) {
  651. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  652. }
  653. }
  654. func TestServeAdminMembersFail(t *testing.T) {
  655. tests := []struct {
  656. req *http.Request
  657. server etcdserver.Server
  658. wcode int
  659. }{
  660. {
  661. // bad method
  662. &http.Request{
  663. Method: "CONNECT",
  664. },
  665. &resServer{},
  666. http.StatusMethodNotAllowed,
  667. },
  668. {
  669. // bad method
  670. &http.Request{
  671. Method: "TRACE",
  672. },
  673. &resServer{},
  674. http.StatusMethodNotAllowed,
  675. },
  676. {
  677. // parse body error
  678. &http.Request{
  679. URL: mustNewURL(t, adminMembersPrefix),
  680. Method: "POST",
  681. Body: ioutil.NopCloser(strings.NewReader("bad json")),
  682. },
  683. &resServer{},
  684. http.StatusBadRequest,
  685. },
  686. {
  687. // bad content type
  688. &http.Request{
  689. URL: mustNewURL(t, adminMembersPrefix),
  690. Method: "POST",
  691. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  692. Header: map[string][]string{"Content-Type": []string{"application/bad"}},
  693. },
  694. &errServer{},
  695. http.StatusBadRequest,
  696. },
  697. {
  698. // bad url
  699. &http.Request{
  700. URL: mustNewURL(t, adminMembersPrefix),
  701. Method: "POST",
  702. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://a"]}`)),
  703. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  704. },
  705. &errServer{},
  706. http.StatusBadRequest,
  707. },
  708. {
  709. // etcdserver.AddMember error
  710. &http.Request{
  711. URL: mustNewURL(t, adminMembersPrefix),
  712. Method: "POST",
  713. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  714. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  715. },
  716. &errServer{
  717. errors.New("blah"),
  718. },
  719. http.StatusInternalServerError,
  720. },
  721. {
  722. // etcdserver.RemoveMember error
  723. &http.Request{
  724. URL: mustNewURL(t, path.Join(adminMembersPrefix, "1")),
  725. Method: "DELETE",
  726. },
  727. &errServer{
  728. errors.New("blah"),
  729. },
  730. http.StatusInternalServerError,
  731. },
  732. {
  733. // etcdserver.RemoveMember error
  734. &http.Request{
  735. URL: mustNewURL(t, adminMembersPrefix),
  736. Method: "DELETE",
  737. },
  738. nil,
  739. http.StatusMethodNotAllowed,
  740. },
  741. }
  742. for i, tt := range tests {
  743. h := &adminMembersHandler{
  744. server: tt.server,
  745. clusterInfo: &fakeCluster{id: 1},
  746. clock: clockwork.NewFakeClock(),
  747. }
  748. rw := httptest.NewRecorder()
  749. h.ServeHTTP(rw, tt.req)
  750. if rw.Code != tt.wcode {
  751. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  752. }
  753. if rw.Code != http.StatusMethodNotAllowed {
  754. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  755. wcid := strconv.FormatUint(h.clusterInfo.ID(), 16)
  756. if gcid != wcid {
  757. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  758. }
  759. }
  760. }
  761. }
  762. func TestWriteEvent(t *testing.T) {
  763. // nil event should not panic
  764. rw := httptest.NewRecorder()
  765. writeKeyEvent(rw, nil, dummyRaftTimer{})
  766. h := rw.Header()
  767. if len(h) > 0 {
  768. t.Fatalf("unexpected non-empty headers: %#v", h)
  769. }
  770. b := rw.Body.String()
  771. if len(b) > 0 {
  772. t.Fatalf("unexpected non-empty body: %q", b)
  773. }
  774. tests := []struct {
  775. ev *store.Event
  776. idx string
  777. // TODO(jonboulle): check body as well as just status code
  778. code int
  779. err error
  780. }{
  781. // standard case, standard 200 response
  782. {
  783. &store.Event{
  784. Action: store.Get,
  785. Node: &store.NodeExtern{},
  786. PrevNode: &store.NodeExtern{},
  787. },
  788. "0",
  789. http.StatusOK,
  790. nil,
  791. },
  792. // check new nodes return StatusCreated
  793. {
  794. &store.Event{
  795. Action: store.Create,
  796. Node: &store.NodeExtern{},
  797. PrevNode: &store.NodeExtern{},
  798. },
  799. "0",
  800. http.StatusCreated,
  801. nil,
  802. },
  803. }
  804. for i, tt := range tests {
  805. rw := httptest.NewRecorder()
  806. writeKeyEvent(rw, tt.ev, dummyRaftTimer{})
  807. if gct := rw.Header().Get("Content-Type"); gct != "application/json" {
  808. t.Errorf("case %d: bad Content-Type: got %q, want application/json", i, gct)
  809. }
  810. if gri := rw.Header().Get("X-Raft-Index"); gri != "100" {
  811. t.Errorf("case %d: bad X-Raft-Index header: got %s, want %s", i, gri, "100")
  812. }
  813. if grt := rw.Header().Get("X-Raft-Term"); grt != "5" {
  814. t.Errorf("case %d: bad X-Raft-Term header: got %s, want %s", i, grt, "5")
  815. }
  816. if gei := rw.Header().Get("X-Etcd-Index"); gei != tt.idx {
  817. t.Errorf("case %d: bad X-Etcd-Index header: got %s, want %s", i, gei, tt.idx)
  818. }
  819. if rw.Code != tt.code {
  820. t.Errorf("case %d: bad response code: got %d, want %v", i, rw.Code, tt.code)
  821. }
  822. }
  823. }
  824. func TestV2DeprecatedMachinesEndpoint(t *testing.T) {
  825. tests := []struct {
  826. method string
  827. wcode int
  828. }{
  829. {"GET", http.StatusOK},
  830. {"HEAD", http.StatusOK},
  831. {"POST", http.StatusMethodNotAllowed},
  832. }
  833. m := NewClientHandler(&etcdserver.EtcdServer{Cluster: &etcdserver.Cluster{}})
  834. s := httptest.NewServer(m)
  835. defer s.Close()
  836. for _, tt := range tests {
  837. req, err := http.NewRequest(tt.method, s.URL+deprecatedMachinesPrefix, nil)
  838. if err != nil {
  839. t.Fatal(err)
  840. }
  841. resp, err := http.DefaultClient.Do(req)
  842. if err != nil {
  843. t.Fatal(err)
  844. }
  845. if resp.StatusCode != tt.wcode {
  846. t.Errorf("StatusCode = %d, expected %d", resp.StatusCode, tt.wcode)
  847. }
  848. }
  849. }
  850. func TestServeMachines(t *testing.T) {
  851. cluster := &fakeCluster{
  852. clientURLs: []string{"http://localhost:8080", "http://localhost:8081", "http://localhost:8082"},
  853. }
  854. writer := httptest.NewRecorder()
  855. req, err := http.NewRequest("GET", "", nil)
  856. if err != nil {
  857. t.Fatal(err)
  858. }
  859. h := &deprecatedMachinesHandler{clusterInfo: cluster}
  860. h.ServeHTTP(writer, req)
  861. w := "http://localhost:8080, http://localhost:8081, http://localhost:8082"
  862. if g := writer.Body.String(); g != w {
  863. t.Errorf("body = %s, want %s", g, w)
  864. }
  865. if writer.Code != http.StatusOK {
  866. t.Errorf("code = %d, want %d", writer.Code, http.StatusOK)
  867. }
  868. }
  869. type dummyStats struct {
  870. data []byte
  871. }
  872. func (ds *dummyStats) SelfStats() []byte { return ds.data }
  873. func (ds *dummyStats) LeaderStats() []byte { return ds.data }
  874. func (ds *dummyStats) StoreStats() []byte { return ds.data }
  875. func (ds *dummyStats) UpdateRecvApp(_ uint64, _ int64) {}
  876. func TestServeSelfStats(t *testing.T) {
  877. wb := []byte("some statistics")
  878. w := string(wb)
  879. sh := &statsHandler{
  880. stats: &dummyStats{data: wb},
  881. }
  882. rw := httptest.NewRecorder()
  883. sh.serveSelf(rw, &http.Request{Method: "GET"})
  884. if rw.Code != http.StatusOK {
  885. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  886. }
  887. wct := "application/json"
  888. if gct := rw.Header().Get("Content-Type"); gct != wct {
  889. t.Errorf("Content-Type = %q, want %q", gct, wct)
  890. }
  891. if g := rw.Body.String(); g != w {
  892. t.Errorf("body = %s, want %s", g, w)
  893. }
  894. }
  895. func TestSelfServeStatsBad(t *testing.T) {
  896. for _, m := range []string{"PUT", "POST", "DELETE"} {
  897. sh := &statsHandler{}
  898. rw := httptest.NewRecorder()
  899. sh.serveSelf(
  900. rw,
  901. &http.Request{
  902. Method: m,
  903. },
  904. )
  905. if rw.Code != http.StatusMethodNotAllowed {
  906. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  907. }
  908. }
  909. }
  910. func TestLeaderServeStatsBad(t *testing.T) {
  911. for _, m := range []string{"PUT", "POST", "DELETE"} {
  912. sh := &statsHandler{}
  913. rw := httptest.NewRecorder()
  914. sh.serveLeader(
  915. rw,
  916. &http.Request{
  917. Method: m,
  918. },
  919. )
  920. if rw.Code != http.StatusMethodNotAllowed {
  921. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  922. }
  923. }
  924. }
  925. func TestServeLeaderStats(t *testing.T) {
  926. wb := []byte("some statistics")
  927. w := string(wb)
  928. sh := &statsHandler{
  929. stats: &dummyStats{data: wb},
  930. }
  931. rw := httptest.NewRecorder()
  932. sh.serveLeader(rw, &http.Request{Method: "GET"})
  933. if rw.Code != http.StatusOK {
  934. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  935. }
  936. wct := "application/json"
  937. if gct := rw.Header().Get("Content-Type"); gct != wct {
  938. t.Errorf("Content-Type = %q, want %q", gct, wct)
  939. }
  940. if g := rw.Body.String(); g != w {
  941. t.Errorf("body = %s, want %s", g, w)
  942. }
  943. }
  944. func TestServeStoreStats(t *testing.T) {
  945. wb := []byte("some statistics")
  946. w := string(wb)
  947. sh := &statsHandler{
  948. stats: &dummyStats{data: wb},
  949. }
  950. rw := httptest.NewRecorder()
  951. sh.serveStore(rw, &http.Request{Method: "GET"})
  952. if rw.Code != http.StatusOK {
  953. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  954. }
  955. wct := "application/json"
  956. if gct := rw.Header().Get("Content-Type"); gct != wct {
  957. t.Errorf("Content-Type = %q, want %q", gct, wct)
  958. }
  959. if g := rw.Body.String(); g != w {
  960. t.Errorf("body = %s, want %s", g, w)
  961. }
  962. }
  963. func TestServeVersion(t *testing.T) {
  964. req, err := http.NewRequest("GET", "", nil)
  965. if err != nil {
  966. t.Fatalf("error creating request: %v", err)
  967. }
  968. rw := httptest.NewRecorder()
  969. serveVersion(rw, req)
  970. if rw.Code != http.StatusOK {
  971. t.Errorf("code=%d, want %d", rw.Code, http.StatusOK)
  972. }
  973. w := fmt.Sprintf("etcd %s", version.Version)
  974. if g := rw.Body.String(); g != w {
  975. t.Fatalf("body = %q, want %q", g, w)
  976. }
  977. }
  978. func TestServeVersionFails(t *testing.T) {
  979. for _, m := range []string{
  980. "CONNECT", "TRACE", "PUT", "POST", "HEAD",
  981. } {
  982. req, err := http.NewRequest(m, "", nil)
  983. if err != nil {
  984. t.Fatalf("error creating request: %v", err)
  985. }
  986. rw := httptest.NewRecorder()
  987. serveVersion(rw, req)
  988. if rw.Code != http.StatusMethodNotAllowed {
  989. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  990. }
  991. }
  992. }
  993. func TestBadServeKeys(t *testing.T) {
  994. testBadCases := []struct {
  995. req *http.Request
  996. server etcdserver.Server
  997. wcode int
  998. wbody string
  999. }{
  1000. {
  1001. // bad method
  1002. &http.Request{
  1003. Method: "CONNECT",
  1004. },
  1005. &resServer{},
  1006. http.StatusMethodNotAllowed,
  1007. "Method Not Allowed",
  1008. },
  1009. {
  1010. // bad method
  1011. &http.Request{
  1012. Method: "TRACE",
  1013. },
  1014. &resServer{},
  1015. http.StatusMethodNotAllowed,
  1016. "Method Not Allowed",
  1017. },
  1018. {
  1019. // parseRequest error
  1020. &http.Request{
  1021. Body: nil,
  1022. Method: "PUT",
  1023. },
  1024. &resServer{},
  1025. http.StatusBadRequest,
  1026. `{"errorCode":210,"message":"Invalid POST form","cause":"missing form body","index":0}`,
  1027. },
  1028. {
  1029. // etcdserver.Server error
  1030. mustNewRequest(t, "foo"),
  1031. &errServer{
  1032. errors.New("blah"),
  1033. },
  1034. http.StatusInternalServerError,
  1035. "Internal Server Error",
  1036. },
  1037. {
  1038. // etcdserver.Server etcd error
  1039. mustNewRequest(t, "foo"),
  1040. &errServer{
  1041. etcdErr.NewError(etcdErr.EcodeKeyNotFound, "/1/pant", 0),
  1042. },
  1043. http.StatusNotFound,
  1044. `{"errorCode":100,"message":"Key not found","cause":"/pant","index":0}`,
  1045. },
  1046. {
  1047. // non-event/watcher response from etcdserver.Server
  1048. mustNewRequest(t, "foo"),
  1049. &resServer{
  1050. etcdserver.Response{},
  1051. },
  1052. http.StatusInternalServerError,
  1053. "Internal Server Error",
  1054. },
  1055. }
  1056. for i, tt := range testBadCases {
  1057. h := &keysHandler{
  1058. timeout: 0, // context times out immediately
  1059. server: tt.server,
  1060. clusterInfo: &fakeCluster{id: 1},
  1061. }
  1062. rw := httptest.NewRecorder()
  1063. h.ServeHTTP(rw, tt.req)
  1064. if rw.Code != tt.wcode {
  1065. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  1066. }
  1067. if rw.Code != http.StatusMethodNotAllowed {
  1068. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  1069. wcid := strconv.FormatUint(h.clusterInfo.ID(), 16)
  1070. if gcid != wcid {
  1071. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  1072. }
  1073. }
  1074. if g := strings.TrimSuffix(rw.Body.String(), "\n"); g != tt.wbody {
  1075. t.Errorf("#%d: body = %s, want %s", i, g, tt.wbody)
  1076. }
  1077. }
  1078. }
  1079. func TestServeKeysEvent(t *testing.T) {
  1080. req := mustNewRequest(t, "foo")
  1081. server := &resServer{
  1082. etcdserver.Response{
  1083. Event: &store.Event{
  1084. Action: store.Get,
  1085. Node: &store.NodeExtern{},
  1086. },
  1087. },
  1088. }
  1089. h := &keysHandler{
  1090. timeout: time.Hour,
  1091. server: server,
  1092. clusterInfo: &fakeCluster{id: 1},
  1093. timer: &dummyRaftTimer{},
  1094. }
  1095. rw := httptest.NewRecorder()
  1096. h.ServeHTTP(rw, req)
  1097. wcode := http.StatusOK
  1098. wbody := mustMarshalEvent(
  1099. t,
  1100. &store.Event{
  1101. Action: store.Get,
  1102. Node: &store.NodeExtern{},
  1103. },
  1104. )
  1105. if rw.Code != wcode {
  1106. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1107. }
  1108. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  1109. wcid := strconv.FormatUint(h.clusterInfo.ID(), 16)
  1110. if gcid != wcid {
  1111. t.Errorf("cid = %s, want %s", gcid, wcid)
  1112. }
  1113. g := rw.Body.String()
  1114. if g != wbody {
  1115. t.Errorf("got body=%#v, want %#v", g, wbody)
  1116. }
  1117. }
  1118. func TestServeKeysWatch(t *testing.T) {
  1119. req := mustNewRequest(t, "/foo/bar")
  1120. ec := make(chan *store.Event)
  1121. dw := &dummyWatcher{
  1122. echan: ec,
  1123. }
  1124. server := &resServer{
  1125. etcdserver.Response{
  1126. Watcher: dw,
  1127. },
  1128. }
  1129. h := &keysHandler{
  1130. timeout: time.Hour,
  1131. server: server,
  1132. clusterInfo: &fakeCluster{id: 1},
  1133. timer: &dummyRaftTimer{},
  1134. }
  1135. go func() {
  1136. ec <- &store.Event{
  1137. Action: store.Get,
  1138. Node: &store.NodeExtern{},
  1139. }
  1140. }()
  1141. rw := httptest.NewRecorder()
  1142. h.ServeHTTP(rw, req)
  1143. wcode := http.StatusOK
  1144. wbody := mustMarshalEvent(
  1145. t,
  1146. &store.Event{
  1147. Action: store.Get,
  1148. Node: &store.NodeExtern{},
  1149. },
  1150. )
  1151. if rw.Code != wcode {
  1152. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1153. }
  1154. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  1155. wcid := strconv.FormatUint(h.clusterInfo.ID(), 16)
  1156. if gcid != wcid {
  1157. t.Errorf("cid = %s, want %s", gcid, wcid)
  1158. }
  1159. g := rw.Body.String()
  1160. if g != wbody {
  1161. t.Errorf("got body=%#v, want %#v", g, wbody)
  1162. }
  1163. }
  1164. type recordingCloseNotifier struct {
  1165. *httptest.ResponseRecorder
  1166. cn chan bool
  1167. }
  1168. func (rcn *recordingCloseNotifier) CloseNotify() <-chan bool {
  1169. return rcn.cn
  1170. }
  1171. func TestHandleWatch(t *testing.T) {
  1172. defaultRwRr := func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1173. r := httptest.NewRecorder()
  1174. return r, r
  1175. }
  1176. noopEv := func(chan *store.Event) {}
  1177. tests := []struct {
  1178. getCtx func() context.Context
  1179. getRwRr func() (http.ResponseWriter, *httptest.ResponseRecorder)
  1180. doToChan func(chan *store.Event)
  1181. wbody string
  1182. }{
  1183. {
  1184. // Normal case: one event
  1185. context.Background,
  1186. defaultRwRr,
  1187. func(ch chan *store.Event) {
  1188. ch <- &store.Event{
  1189. Action: store.Get,
  1190. Node: &store.NodeExtern{},
  1191. }
  1192. },
  1193. mustMarshalEvent(
  1194. t,
  1195. &store.Event{
  1196. Action: store.Get,
  1197. Node: &store.NodeExtern{},
  1198. },
  1199. ),
  1200. },
  1201. {
  1202. // Channel is closed, no event
  1203. context.Background,
  1204. defaultRwRr,
  1205. func(ch chan *store.Event) {
  1206. close(ch)
  1207. },
  1208. "",
  1209. },
  1210. {
  1211. // Simulate a timed-out context
  1212. func() context.Context {
  1213. ctx, cancel := context.WithCancel(context.Background())
  1214. cancel()
  1215. return ctx
  1216. },
  1217. defaultRwRr,
  1218. noopEv,
  1219. "",
  1220. },
  1221. {
  1222. // Close-notifying request
  1223. context.Background,
  1224. func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1225. rw := &recordingCloseNotifier{
  1226. ResponseRecorder: httptest.NewRecorder(),
  1227. cn: make(chan bool, 1),
  1228. }
  1229. rw.cn <- true
  1230. return rw, rw.ResponseRecorder
  1231. },
  1232. noopEv,
  1233. "",
  1234. },
  1235. }
  1236. for i, tt := range tests {
  1237. rw, rr := tt.getRwRr()
  1238. wa := &dummyWatcher{
  1239. echan: make(chan *store.Event, 1),
  1240. sidx: 10,
  1241. }
  1242. tt.doToChan(wa.echan)
  1243. handleKeyWatch(tt.getCtx(), rw, wa, false, dummyRaftTimer{})
  1244. wcode := http.StatusOK
  1245. wct := "application/json"
  1246. wei := "10"
  1247. wri := "100"
  1248. wrt := "5"
  1249. if rr.Code != wcode {
  1250. t.Errorf("#%d: got code=%d, want %d", i, rr.Code, wcode)
  1251. }
  1252. h := rr.Header()
  1253. if ct := h.Get("Content-Type"); ct != wct {
  1254. t.Errorf("#%d: Content-Type=%q, want %q", i, ct, wct)
  1255. }
  1256. if ei := h.Get("X-Etcd-Index"); ei != wei {
  1257. t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, ei, wei)
  1258. }
  1259. if ri := h.Get("X-Raft-Index"); ri != wri {
  1260. t.Errorf("#%d: X-Raft-Index=%q, want %q", i, ri, wri)
  1261. }
  1262. if rt := h.Get("X-Raft-Term"); rt != wrt {
  1263. t.Errorf("#%d: X-Raft-Term=%q, want %q", i, rt, wrt)
  1264. }
  1265. g := rr.Body.String()
  1266. if g != tt.wbody {
  1267. t.Errorf("#%d: got body=%#v, want %#v", i, g, tt.wbody)
  1268. }
  1269. }
  1270. }
  1271. func TestHandleWatchStreaming(t *testing.T) {
  1272. rw := &flushingRecorder{
  1273. httptest.NewRecorder(),
  1274. make(chan struct{}, 1),
  1275. }
  1276. wa := &dummyWatcher{
  1277. echan: make(chan *store.Event),
  1278. }
  1279. // Launch the streaming handler in the background with a cancellable context
  1280. ctx, cancel := context.WithCancel(context.Background())
  1281. done := make(chan struct{})
  1282. go func() {
  1283. handleKeyWatch(ctx, rw, wa, true, dummyRaftTimer{})
  1284. close(done)
  1285. }()
  1286. // Expect one Flush for the headers etc.
  1287. select {
  1288. case <-rw.ch:
  1289. case <-time.After(time.Second):
  1290. t.Fatalf("timed out waiting for flush")
  1291. }
  1292. // Expect headers but no body
  1293. wcode := http.StatusOK
  1294. wct := "application/json"
  1295. wbody := ""
  1296. if rw.Code != wcode {
  1297. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1298. }
  1299. h := rw.Header()
  1300. if ct := h.Get("Content-Type"); ct != wct {
  1301. t.Errorf("Content-Type=%q, want %q", ct, wct)
  1302. }
  1303. g := rw.Body.String()
  1304. if g != wbody {
  1305. t.Errorf("got body=%#v, want %#v", g, wbody)
  1306. }
  1307. // Now send the first event
  1308. select {
  1309. case wa.echan <- &store.Event{
  1310. Action: store.Get,
  1311. Node: &store.NodeExtern{},
  1312. }:
  1313. case <-time.After(time.Second):
  1314. t.Fatal("timed out waiting for send")
  1315. }
  1316. // Wait for it to be flushed...
  1317. select {
  1318. case <-rw.ch:
  1319. case <-time.After(time.Second):
  1320. t.Fatalf("timed out waiting for flush")
  1321. }
  1322. // And check the body is as expected
  1323. wbody = mustMarshalEvent(
  1324. t,
  1325. &store.Event{
  1326. Action: store.Get,
  1327. Node: &store.NodeExtern{},
  1328. },
  1329. )
  1330. g = rw.Body.String()
  1331. if g != wbody {
  1332. t.Errorf("got body=%#v, want %#v", g, wbody)
  1333. }
  1334. // Rinse and repeat
  1335. select {
  1336. case wa.echan <- &store.Event{
  1337. Action: store.Get,
  1338. Node: &store.NodeExtern{},
  1339. }:
  1340. case <-time.After(time.Second):
  1341. t.Fatal("timed out waiting for send")
  1342. }
  1343. select {
  1344. case <-rw.ch:
  1345. case <-time.After(time.Second):
  1346. t.Fatalf("timed out waiting for flush")
  1347. }
  1348. // This time, we expect to see both events
  1349. wbody = wbody + wbody
  1350. g = rw.Body.String()
  1351. if g != wbody {
  1352. t.Errorf("got body=%#v, want %#v", g, wbody)
  1353. }
  1354. // Finally, time out the connection and ensure the serving goroutine returns
  1355. cancel()
  1356. select {
  1357. case <-done:
  1358. case <-time.After(time.Second):
  1359. t.Fatalf("timed out waiting for done")
  1360. }
  1361. }
  1362. func TestTrimEventPrefix(t *testing.T) {
  1363. pre := "/abc"
  1364. tests := []struct {
  1365. ev *store.Event
  1366. wev *store.Event
  1367. }{
  1368. {
  1369. nil,
  1370. nil,
  1371. },
  1372. {
  1373. &store.Event{},
  1374. &store.Event{},
  1375. },
  1376. {
  1377. &store.Event{Node: &store.NodeExtern{Key: "/abc/def"}},
  1378. &store.Event{Node: &store.NodeExtern{Key: "/def"}},
  1379. },
  1380. {
  1381. &store.Event{PrevNode: &store.NodeExtern{Key: "/abc/ghi"}},
  1382. &store.Event{PrevNode: &store.NodeExtern{Key: "/ghi"}},
  1383. },
  1384. {
  1385. &store.Event{
  1386. Node: &store.NodeExtern{Key: "/abc/def"},
  1387. PrevNode: &store.NodeExtern{Key: "/abc/ghi"},
  1388. },
  1389. &store.Event{
  1390. Node: &store.NodeExtern{Key: "/def"},
  1391. PrevNode: &store.NodeExtern{Key: "/ghi"},
  1392. },
  1393. },
  1394. }
  1395. for i, tt := range tests {
  1396. ev := trimEventPrefix(tt.ev, pre)
  1397. if !reflect.DeepEqual(ev, tt.wev) {
  1398. t.Errorf("#%d: event = %+v, want %+v", i, ev, tt.wev)
  1399. }
  1400. }
  1401. }
  1402. func TestTrimNodeExternPrefix(t *testing.T) {
  1403. pre := "/abc"
  1404. tests := []struct {
  1405. n *store.NodeExtern
  1406. wn *store.NodeExtern
  1407. }{
  1408. {
  1409. nil,
  1410. nil,
  1411. },
  1412. {
  1413. &store.NodeExtern{Key: "/abc/def"},
  1414. &store.NodeExtern{Key: "/def"},
  1415. },
  1416. {
  1417. &store.NodeExtern{
  1418. Key: "/abc/def",
  1419. Nodes: []*store.NodeExtern{
  1420. {Key: "/abc/def/1"},
  1421. {Key: "/abc/def/2"},
  1422. },
  1423. },
  1424. &store.NodeExtern{
  1425. Key: "/def",
  1426. Nodes: []*store.NodeExtern{
  1427. {Key: "/def/1"},
  1428. {Key: "/def/2"},
  1429. },
  1430. },
  1431. },
  1432. }
  1433. for i, tt := range tests {
  1434. n := trimNodeExternPrefix(tt.n, pre)
  1435. if !reflect.DeepEqual(n, tt.wn) {
  1436. t.Errorf("#%d: node = %+v, want %+v", i, n, tt.wn)
  1437. }
  1438. }
  1439. }
  1440. func TestTrimPrefix(t *testing.T) {
  1441. tests := []struct {
  1442. in string
  1443. prefix string
  1444. w string
  1445. }{
  1446. {"/v2/admin/members", "/v2/admin/members", ""},
  1447. {"/v2/admin/members/", "/v2/admin/members", ""},
  1448. {"/v2/admin/members/foo", "/v2/admin/members", "foo"},
  1449. }
  1450. for i, tt := range tests {
  1451. if g := trimPrefix(tt.in, tt.prefix); g != tt.w {
  1452. t.Errorf("#%d: trimPrefix = %q, want %q", i, g, tt.w)
  1453. }
  1454. }
  1455. }