client_test.go 40 KB

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