client_test.go 39 KB

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