client_test.go 37 KB

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