client_test.go 38 KB

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