client_test.go 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711
  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 preivously removed ID
  740. &http.Request{
  741. URL: mustNewURL(t, path.Join(membersPrefix, "0")),
  742. Method: "DELETE",
  743. },
  744. &errServer{
  745. etcdserver.ErrIDRemoved,
  746. },
  747. http.StatusGone,
  748. },
  749. {
  750. // etcdserver.RemoveMember error with nonexistent ID
  751. &http.Request{
  752. URL: mustNewURL(t, path.Join(membersPrefix, "0")),
  753. Method: "DELETE",
  754. },
  755. &errServer{
  756. etcdserver.ErrIDNotFound,
  757. },
  758. http.StatusNotFound,
  759. },
  760. {
  761. // etcdserver.RemoveMember error with badly formed ID
  762. &http.Request{
  763. URL: mustNewURL(t, path.Join(membersPrefix, "bad_id")),
  764. Method: "DELETE",
  765. },
  766. nil,
  767. http.StatusBadRequest,
  768. },
  769. {
  770. // etcdserver.RemoveMember with no ID
  771. &http.Request{
  772. URL: mustNewURL(t, membersPrefix),
  773. Method: "DELETE",
  774. },
  775. nil,
  776. http.StatusMethodNotAllowed,
  777. },
  778. }
  779. for i, tt := range tests {
  780. h := &membersHandler{
  781. server: tt.server,
  782. clusterInfo: &fakeCluster{id: 1},
  783. clock: clockwork.NewFakeClock(),
  784. }
  785. rw := httptest.NewRecorder()
  786. h.ServeHTTP(rw, tt.req)
  787. if rw.Code != tt.wcode {
  788. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  789. }
  790. if rw.Code != http.StatusMethodNotAllowed {
  791. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  792. wcid := h.clusterInfo.ID().String()
  793. if gcid != wcid {
  794. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  795. }
  796. }
  797. }
  798. }
  799. func TestWriteEvent(t *testing.T) {
  800. // nil event should not panic
  801. rw := httptest.NewRecorder()
  802. writeKeyEvent(rw, nil, dummyRaftTimer{})
  803. h := rw.Header()
  804. if len(h) > 0 {
  805. t.Fatalf("unexpected non-empty headers: %#v", h)
  806. }
  807. b := rw.Body.String()
  808. if len(b) > 0 {
  809. t.Fatalf("unexpected non-empty body: %q", b)
  810. }
  811. tests := []struct {
  812. ev *store.Event
  813. idx string
  814. // TODO(jonboulle): check body as well as just status code
  815. code int
  816. err error
  817. }{
  818. // standard case, standard 200 response
  819. {
  820. &store.Event{
  821. Action: store.Get,
  822. Node: &store.NodeExtern{},
  823. PrevNode: &store.NodeExtern{},
  824. },
  825. "0",
  826. http.StatusOK,
  827. nil,
  828. },
  829. // check new nodes return StatusCreated
  830. {
  831. &store.Event{
  832. Action: store.Create,
  833. Node: &store.NodeExtern{},
  834. PrevNode: &store.NodeExtern{},
  835. },
  836. "0",
  837. http.StatusCreated,
  838. nil,
  839. },
  840. }
  841. for i, tt := range tests {
  842. rw := httptest.NewRecorder()
  843. writeKeyEvent(rw, tt.ev, dummyRaftTimer{})
  844. if gct := rw.Header().Get("Content-Type"); gct != "application/json" {
  845. t.Errorf("case %d: bad Content-Type: got %q, want application/json", i, gct)
  846. }
  847. if gri := rw.Header().Get("X-Raft-Index"); gri != "100" {
  848. t.Errorf("case %d: bad X-Raft-Index header: got %s, want %s", i, gri, "100")
  849. }
  850. if grt := rw.Header().Get("X-Raft-Term"); grt != "5" {
  851. t.Errorf("case %d: bad X-Raft-Term header: got %s, want %s", i, grt, "5")
  852. }
  853. if gei := rw.Header().Get("X-Etcd-Index"); gei != tt.idx {
  854. t.Errorf("case %d: bad X-Etcd-Index header: got %s, want %s", i, gei, tt.idx)
  855. }
  856. if rw.Code != tt.code {
  857. t.Errorf("case %d: bad response code: got %d, want %v", i, rw.Code, tt.code)
  858. }
  859. }
  860. }
  861. func TestV2DeprecatedMachinesEndpoint(t *testing.T) {
  862. tests := []struct {
  863. method string
  864. wcode int
  865. }{
  866. {"GET", http.StatusOK},
  867. {"HEAD", http.StatusOK},
  868. {"POST", http.StatusMethodNotAllowed},
  869. }
  870. m := NewClientHandler(&etcdserver.EtcdServer{Cluster: &etcdserver.Cluster{}})
  871. s := httptest.NewServer(m)
  872. defer s.Close()
  873. for _, tt := range tests {
  874. req, err := http.NewRequest(tt.method, s.URL+deprecatedMachinesPrefix, nil)
  875. if err != nil {
  876. t.Fatal(err)
  877. }
  878. resp, err := http.DefaultClient.Do(req)
  879. if err != nil {
  880. t.Fatal(err)
  881. }
  882. if resp.StatusCode != tt.wcode {
  883. t.Errorf("StatusCode = %d, expected %d", resp.StatusCode, tt.wcode)
  884. }
  885. }
  886. }
  887. func TestServeMachines(t *testing.T) {
  888. cluster := &fakeCluster{
  889. clientURLs: []string{"http://localhost:8080", "http://localhost:8081", "http://localhost:8082"},
  890. }
  891. writer := httptest.NewRecorder()
  892. req, err := http.NewRequest("GET", "", nil)
  893. if err != nil {
  894. t.Fatal(err)
  895. }
  896. h := &deprecatedMachinesHandler{clusterInfo: cluster}
  897. h.ServeHTTP(writer, req)
  898. w := "http://localhost:8080, http://localhost:8081, http://localhost:8082"
  899. if g := writer.Body.String(); g != w {
  900. t.Errorf("body = %s, want %s", g, w)
  901. }
  902. if writer.Code != http.StatusOK {
  903. t.Errorf("code = %d, want %d", writer.Code, http.StatusOK)
  904. }
  905. }
  906. type dummyStats struct {
  907. data []byte
  908. }
  909. func (ds *dummyStats) SelfStats() []byte { return ds.data }
  910. func (ds *dummyStats) LeaderStats() []byte { return ds.data }
  911. func (ds *dummyStats) StoreStats() []byte { return ds.data }
  912. func (ds *dummyStats) UpdateRecvApp(_ types.ID, _ int64) {}
  913. func TestServeSelfStats(t *testing.T) {
  914. wb := []byte("some statistics")
  915. w := string(wb)
  916. sh := &statsHandler{
  917. stats: &dummyStats{data: wb},
  918. }
  919. rw := httptest.NewRecorder()
  920. sh.serveSelf(rw, &http.Request{Method: "GET"})
  921. if rw.Code != http.StatusOK {
  922. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  923. }
  924. wct := "application/json"
  925. if gct := rw.Header().Get("Content-Type"); gct != wct {
  926. t.Errorf("Content-Type = %q, want %q", gct, wct)
  927. }
  928. if g := rw.Body.String(); g != w {
  929. t.Errorf("body = %s, want %s", g, w)
  930. }
  931. }
  932. func TestSelfServeStatsBad(t *testing.T) {
  933. for _, m := range []string{"PUT", "POST", "DELETE"} {
  934. sh := &statsHandler{}
  935. rw := httptest.NewRecorder()
  936. sh.serveSelf(
  937. rw,
  938. &http.Request{
  939. Method: m,
  940. },
  941. )
  942. if rw.Code != http.StatusMethodNotAllowed {
  943. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  944. }
  945. }
  946. }
  947. func TestLeaderServeStatsBad(t *testing.T) {
  948. for _, m := range []string{"PUT", "POST", "DELETE"} {
  949. sh := &statsHandler{}
  950. rw := httptest.NewRecorder()
  951. sh.serveLeader(
  952. rw,
  953. &http.Request{
  954. Method: m,
  955. },
  956. )
  957. if rw.Code != http.StatusMethodNotAllowed {
  958. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  959. }
  960. }
  961. }
  962. func TestServeLeaderStats(t *testing.T) {
  963. wb := []byte("some statistics")
  964. w := string(wb)
  965. sh := &statsHandler{
  966. stats: &dummyStats{data: wb},
  967. }
  968. rw := httptest.NewRecorder()
  969. sh.serveLeader(rw, &http.Request{Method: "GET"})
  970. if rw.Code != http.StatusOK {
  971. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  972. }
  973. wct := "application/json"
  974. if gct := rw.Header().Get("Content-Type"); gct != wct {
  975. t.Errorf("Content-Type = %q, want %q", gct, wct)
  976. }
  977. if g := rw.Body.String(); g != w {
  978. t.Errorf("body = %s, want %s", g, w)
  979. }
  980. }
  981. func TestServeStoreStats(t *testing.T) {
  982. wb := []byte("some statistics")
  983. w := string(wb)
  984. sh := &statsHandler{
  985. stats: &dummyStats{data: wb},
  986. }
  987. rw := httptest.NewRecorder()
  988. sh.serveStore(rw, &http.Request{Method: "GET"})
  989. if rw.Code != http.StatusOK {
  990. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  991. }
  992. wct := "application/json"
  993. if gct := rw.Header().Get("Content-Type"); gct != wct {
  994. t.Errorf("Content-Type = %q, want %q", gct, wct)
  995. }
  996. if g := rw.Body.String(); g != w {
  997. t.Errorf("body = %s, want %s", g, w)
  998. }
  999. }
  1000. func TestServeVersion(t *testing.T) {
  1001. req, err := http.NewRequest("GET", "", nil)
  1002. if err != nil {
  1003. t.Fatalf("error creating request: %v", err)
  1004. }
  1005. rw := httptest.NewRecorder()
  1006. serveVersion(rw, req)
  1007. if rw.Code != http.StatusOK {
  1008. t.Errorf("code=%d, want %d", rw.Code, http.StatusOK)
  1009. }
  1010. w := fmt.Sprintf("etcd %s", version.Version)
  1011. if g := rw.Body.String(); g != w {
  1012. t.Fatalf("body = %q, want %q", g, w)
  1013. }
  1014. }
  1015. func TestServeVersionFails(t *testing.T) {
  1016. for _, m := range []string{
  1017. "CONNECT", "TRACE", "PUT", "POST", "HEAD",
  1018. } {
  1019. req, err := http.NewRequest(m, "", nil)
  1020. if err != nil {
  1021. t.Fatalf("error creating request: %v", err)
  1022. }
  1023. rw := httptest.NewRecorder()
  1024. serveVersion(rw, req)
  1025. if rw.Code != http.StatusMethodNotAllowed {
  1026. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  1027. }
  1028. }
  1029. }
  1030. func TestBadServeKeys(t *testing.T) {
  1031. testBadCases := []struct {
  1032. req *http.Request
  1033. server etcdserver.Server
  1034. wcode int
  1035. wbody string
  1036. }{
  1037. {
  1038. // bad method
  1039. &http.Request{
  1040. Method: "CONNECT",
  1041. },
  1042. &resServer{},
  1043. http.StatusMethodNotAllowed,
  1044. "Method Not Allowed",
  1045. },
  1046. {
  1047. // bad method
  1048. &http.Request{
  1049. Method: "TRACE",
  1050. },
  1051. &resServer{},
  1052. http.StatusMethodNotAllowed,
  1053. "Method Not Allowed",
  1054. },
  1055. {
  1056. // parseRequest error
  1057. &http.Request{
  1058. Body: nil,
  1059. Method: "PUT",
  1060. },
  1061. &resServer{},
  1062. http.StatusBadRequest,
  1063. `{"errorCode":210,"message":"Invalid POST form","cause":"missing form body","index":0}`,
  1064. },
  1065. {
  1066. // etcdserver.Server error
  1067. mustNewRequest(t, "foo"),
  1068. &errServer{
  1069. errors.New("blah"),
  1070. },
  1071. http.StatusInternalServerError,
  1072. `{"message":"Internal Server Error"}`,
  1073. },
  1074. {
  1075. // etcdserver.Server etcd error
  1076. mustNewRequest(t, "foo"),
  1077. &errServer{
  1078. etcdErr.NewError(etcdErr.EcodeKeyNotFound, "/1/pant", 0),
  1079. },
  1080. http.StatusNotFound,
  1081. `{"errorCode":100,"message":"Key not found","cause":"/pant","index":0}`,
  1082. },
  1083. {
  1084. // non-event/watcher response from etcdserver.Server
  1085. mustNewRequest(t, "foo"),
  1086. &resServer{
  1087. etcdserver.Response{},
  1088. },
  1089. http.StatusInternalServerError,
  1090. `{"message":"Internal Server Error"}`,
  1091. },
  1092. }
  1093. for i, tt := range testBadCases {
  1094. h := &keysHandler{
  1095. timeout: 0, // context times out immediately
  1096. server: tt.server,
  1097. clusterInfo: &fakeCluster{id: 1},
  1098. }
  1099. rw := httptest.NewRecorder()
  1100. h.ServeHTTP(rw, tt.req)
  1101. if rw.Code != tt.wcode {
  1102. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  1103. }
  1104. if rw.Code != http.StatusMethodNotAllowed {
  1105. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  1106. wcid := h.clusterInfo.ID().String()
  1107. if gcid != wcid {
  1108. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  1109. }
  1110. }
  1111. if g := strings.TrimSuffix(rw.Body.String(), "\n"); g != tt.wbody {
  1112. t.Errorf("#%d: body = %s, want %s", i, g, tt.wbody)
  1113. }
  1114. }
  1115. }
  1116. func TestServeKeysGood(t *testing.T) {
  1117. tests := []struct {
  1118. req *http.Request
  1119. wcode int
  1120. }{
  1121. {
  1122. mustNewMethodRequest(t, "HEAD", "foo"),
  1123. http.StatusOK,
  1124. },
  1125. {
  1126. mustNewMethodRequest(t, "GET", "foo"),
  1127. http.StatusOK,
  1128. },
  1129. {
  1130. mustNewForm(t, "foo", url.Values{"value": []string{"bar"}}),
  1131. http.StatusOK,
  1132. },
  1133. {
  1134. mustNewMethodRequest(t, "DELETE", "foo"),
  1135. http.StatusOK,
  1136. },
  1137. {
  1138. mustNewPostForm(t, "foo", url.Values{"value": []string{"bar"}}),
  1139. http.StatusOK,
  1140. },
  1141. }
  1142. server := &resServer{
  1143. etcdserver.Response{
  1144. Event: &store.Event{
  1145. Action: store.Get,
  1146. Node: &store.NodeExtern{},
  1147. },
  1148. },
  1149. }
  1150. for i, tt := range tests {
  1151. h := &keysHandler{
  1152. timeout: time.Hour,
  1153. server: server,
  1154. timer: &dummyRaftTimer{},
  1155. clusterInfo: &fakeCluster{id: 1},
  1156. }
  1157. rw := httptest.NewRecorder()
  1158. h.ServeHTTP(rw, tt.req)
  1159. if rw.Code != tt.wcode {
  1160. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  1161. }
  1162. }
  1163. }
  1164. func TestServeKeysEvent(t *testing.T) {
  1165. req := mustNewRequest(t, "foo")
  1166. server := &resServer{
  1167. etcdserver.Response{
  1168. Event: &store.Event{
  1169. Action: store.Get,
  1170. Node: &store.NodeExtern{},
  1171. },
  1172. },
  1173. }
  1174. h := &keysHandler{
  1175. timeout: time.Hour,
  1176. server: server,
  1177. clusterInfo: &fakeCluster{id: 1},
  1178. timer: &dummyRaftTimer{},
  1179. }
  1180. rw := httptest.NewRecorder()
  1181. h.ServeHTTP(rw, req)
  1182. wcode := http.StatusOK
  1183. wbody := mustMarshalEvent(
  1184. t,
  1185. &store.Event{
  1186. Action: store.Get,
  1187. Node: &store.NodeExtern{},
  1188. },
  1189. )
  1190. if rw.Code != wcode {
  1191. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1192. }
  1193. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  1194. wcid := h.clusterInfo.ID().String()
  1195. if gcid != wcid {
  1196. t.Errorf("cid = %s, want %s", gcid, wcid)
  1197. }
  1198. g := rw.Body.String()
  1199. if g != wbody {
  1200. t.Errorf("got body=%#v, want %#v", g, wbody)
  1201. }
  1202. }
  1203. func TestServeKeysWatch(t *testing.T) {
  1204. req := mustNewRequest(t, "/foo/bar")
  1205. ec := make(chan *store.Event)
  1206. dw := &dummyWatcher{
  1207. echan: ec,
  1208. }
  1209. server := &resServer{
  1210. etcdserver.Response{
  1211. Watcher: dw,
  1212. },
  1213. }
  1214. h := &keysHandler{
  1215. timeout: time.Hour,
  1216. server: server,
  1217. clusterInfo: &fakeCluster{id: 1},
  1218. timer: &dummyRaftTimer{},
  1219. }
  1220. go func() {
  1221. ec <- &store.Event{
  1222. Action: store.Get,
  1223. Node: &store.NodeExtern{},
  1224. }
  1225. }()
  1226. rw := httptest.NewRecorder()
  1227. h.ServeHTTP(rw, req)
  1228. wcode := http.StatusOK
  1229. wbody := mustMarshalEvent(
  1230. t,
  1231. &store.Event{
  1232. Action: store.Get,
  1233. Node: &store.NodeExtern{},
  1234. },
  1235. )
  1236. if rw.Code != wcode {
  1237. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1238. }
  1239. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  1240. wcid := h.clusterInfo.ID().String()
  1241. if gcid != wcid {
  1242. t.Errorf("cid = %s, want %s", gcid, wcid)
  1243. }
  1244. g := rw.Body.String()
  1245. if g != wbody {
  1246. t.Errorf("got body=%#v, want %#v", g, wbody)
  1247. }
  1248. }
  1249. type recordingCloseNotifier struct {
  1250. *httptest.ResponseRecorder
  1251. cn chan bool
  1252. }
  1253. func (rcn *recordingCloseNotifier) CloseNotify() <-chan bool {
  1254. return rcn.cn
  1255. }
  1256. func TestHandleWatch(t *testing.T) {
  1257. defaultRwRr := func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1258. r := httptest.NewRecorder()
  1259. return r, r
  1260. }
  1261. noopEv := func(chan *store.Event) {}
  1262. tests := []struct {
  1263. getCtx func() context.Context
  1264. getRwRr func() (http.ResponseWriter, *httptest.ResponseRecorder)
  1265. doToChan func(chan *store.Event)
  1266. wbody string
  1267. }{
  1268. {
  1269. // Normal case: one event
  1270. context.Background,
  1271. defaultRwRr,
  1272. func(ch chan *store.Event) {
  1273. ch <- &store.Event{
  1274. Action: store.Get,
  1275. Node: &store.NodeExtern{},
  1276. }
  1277. },
  1278. mustMarshalEvent(
  1279. t,
  1280. &store.Event{
  1281. Action: store.Get,
  1282. Node: &store.NodeExtern{},
  1283. },
  1284. ),
  1285. },
  1286. {
  1287. // Channel is closed, no event
  1288. context.Background,
  1289. defaultRwRr,
  1290. func(ch chan *store.Event) {
  1291. close(ch)
  1292. },
  1293. "",
  1294. },
  1295. {
  1296. // Simulate a timed-out context
  1297. func() context.Context {
  1298. ctx, cancel := context.WithCancel(context.Background())
  1299. cancel()
  1300. return ctx
  1301. },
  1302. defaultRwRr,
  1303. noopEv,
  1304. "",
  1305. },
  1306. {
  1307. // Close-notifying request
  1308. context.Background,
  1309. func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1310. rw := &recordingCloseNotifier{
  1311. ResponseRecorder: httptest.NewRecorder(),
  1312. cn: make(chan bool, 1),
  1313. }
  1314. rw.cn <- true
  1315. return rw, rw.ResponseRecorder
  1316. },
  1317. noopEv,
  1318. "",
  1319. },
  1320. }
  1321. for i, tt := range tests {
  1322. rw, rr := tt.getRwRr()
  1323. wa := &dummyWatcher{
  1324. echan: make(chan *store.Event, 1),
  1325. sidx: 10,
  1326. }
  1327. tt.doToChan(wa.echan)
  1328. handleKeyWatch(tt.getCtx(), rw, wa, false, dummyRaftTimer{})
  1329. wcode := http.StatusOK
  1330. wct := "application/json"
  1331. wei := "10"
  1332. wri := "100"
  1333. wrt := "5"
  1334. if rr.Code != wcode {
  1335. t.Errorf("#%d: got code=%d, want %d", i, rr.Code, wcode)
  1336. }
  1337. h := rr.Header()
  1338. if ct := h.Get("Content-Type"); ct != wct {
  1339. t.Errorf("#%d: Content-Type=%q, want %q", i, ct, wct)
  1340. }
  1341. if ei := h.Get("X-Etcd-Index"); ei != wei {
  1342. t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, ei, wei)
  1343. }
  1344. if ri := h.Get("X-Raft-Index"); ri != wri {
  1345. t.Errorf("#%d: X-Raft-Index=%q, want %q", i, ri, wri)
  1346. }
  1347. if rt := h.Get("X-Raft-Term"); rt != wrt {
  1348. t.Errorf("#%d: X-Raft-Term=%q, want %q", i, rt, wrt)
  1349. }
  1350. g := rr.Body.String()
  1351. if g != tt.wbody {
  1352. t.Errorf("#%d: got body=%#v, want %#v", i, g, tt.wbody)
  1353. }
  1354. }
  1355. }
  1356. func TestHandleWatchStreaming(t *testing.T) {
  1357. rw := &flushingRecorder{
  1358. httptest.NewRecorder(),
  1359. make(chan struct{}, 1),
  1360. }
  1361. wa := &dummyWatcher{
  1362. echan: make(chan *store.Event),
  1363. }
  1364. // Launch the streaming handler in the background with a cancellable context
  1365. ctx, cancel := context.WithCancel(context.Background())
  1366. done := make(chan struct{})
  1367. go func() {
  1368. handleKeyWatch(ctx, rw, wa, true, dummyRaftTimer{})
  1369. close(done)
  1370. }()
  1371. // Expect one Flush for the headers etc.
  1372. select {
  1373. case <-rw.ch:
  1374. case <-time.After(time.Second):
  1375. t.Fatalf("timed out waiting for flush")
  1376. }
  1377. // Expect headers but no body
  1378. wcode := http.StatusOK
  1379. wct := "application/json"
  1380. wbody := ""
  1381. if rw.Code != wcode {
  1382. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1383. }
  1384. h := rw.Header()
  1385. if ct := h.Get("Content-Type"); ct != wct {
  1386. t.Errorf("Content-Type=%q, want %q", ct, wct)
  1387. }
  1388. g := rw.Body.String()
  1389. if g != wbody {
  1390. t.Errorf("got body=%#v, want %#v", g, wbody)
  1391. }
  1392. // Now send the first event
  1393. select {
  1394. case wa.echan <- &store.Event{
  1395. Action: store.Get,
  1396. Node: &store.NodeExtern{},
  1397. }:
  1398. case <-time.After(time.Second):
  1399. t.Fatal("timed out waiting for send")
  1400. }
  1401. // Wait for it to be flushed...
  1402. select {
  1403. case <-rw.ch:
  1404. case <-time.After(time.Second):
  1405. t.Fatalf("timed out waiting for flush")
  1406. }
  1407. // And check the body is as expected
  1408. wbody = mustMarshalEvent(
  1409. t,
  1410. &store.Event{
  1411. Action: store.Get,
  1412. Node: &store.NodeExtern{},
  1413. },
  1414. )
  1415. g = rw.Body.String()
  1416. if g != wbody {
  1417. t.Errorf("got body=%#v, want %#v", g, wbody)
  1418. }
  1419. // Rinse and repeat
  1420. select {
  1421. case wa.echan <- &store.Event{
  1422. Action: store.Get,
  1423. Node: &store.NodeExtern{},
  1424. }:
  1425. case <-time.After(time.Second):
  1426. t.Fatal("timed out waiting for send")
  1427. }
  1428. select {
  1429. case <-rw.ch:
  1430. case <-time.After(time.Second):
  1431. t.Fatalf("timed out waiting for flush")
  1432. }
  1433. // This time, we expect to see both events
  1434. wbody = wbody + wbody
  1435. g = rw.Body.String()
  1436. if g != wbody {
  1437. t.Errorf("got body=%#v, want %#v", g, wbody)
  1438. }
  1439. // Finally, time out the connection and ensure the serving goroutine returns
  1440. cancel()
  1441. select {
  1442. case <-done:
  1443. case <-time.After(time.Second):
  1444. t.Fatalf("timed out waiting for done")
  1445. }
  1446. }
  1447. func TestTrimEventPrefix(t *testing.T) {
  1448. pre := "/abc"
  1449. tests := []struct {
  1450. ev *store.Event
  1451. wev *store.Event
  1452. }{
  1453. {
  1454. nil,
  1455. nil,
  1456. },
  1457. {
  1458. &store.Event{},
  1459. &store.Event{},
  1460. },
  1461. {
  1462. &store.Event{Node: &store.NodeExtern{Key: "/abc/def"}},
  1463. &store.Event{Node: &store.NodeExtern{Key: "/def"}},
  1464. },
  1465. {
  1466. &store.Event{PrevNode: &store.NodeExtern{Key: "/abc/ghi"}},
  1467. &store.Event{PrevNode: &store.NodeExtern{Key: "/ghi"}},
  1468. },
  1469. {
  1470. &store.Event{
  1471. Node: &store.NodeExtern{Key: "/abc/def"},
  1472. PrevNode: &store.NodeExtern{Key: "/abc/ghi"},
  1473. },
  1474. &store.Event{
  1475. Node: &store.NodeExtern{Key: "/def"},
  1476. PrevNode: &store.NodeExtern{Key: "/ghi"},
  1477. },
  1478. },
  1479. }
  1480. for i, tt := range tests {
  1481. ev := trimEventPrefix(tt.ev, pre)
  1482. if !reflect.DeepEqual(ev, tt.wev) {
  1483. t.Errorf("#%d: event = %+v, want %+v", i, ev, tt.wev)
  1484. }
  1485. }
  1486. }
  1487. func TestTrimNodeExternPrefix(t *testing.T) {
  1488. pre := "/abc"
  1489. tests := []struct {
  1490. n *store.NodeExtern
  1491. wn *store.NodeExtern
  1492. }{
  1493. {
  1494. nil,
  1495. nil,
  1496. },
  1497. {
  1498. &store.NodeExtern{Key: "/abc/def"},
  1499. &store.NodeExtern{Key: "/def"},
  1500. },
  1501. {
  1502. &store.NodeExtern{
  1503. Key: "/abc/def",
  1504. Nodes: []*store.NodeExtern{
  1505. {Key: "/abc/def/1"},
  1506. {Key: "/abc/def/2"},
  1507. },
  1508. },
  1509. &store.NodeExtern{
  1510. Key: "/def",
  1511. Nodes: []*store.NodeExtern{
  1512. {Key: "/def/1"},
  1513. {Key: "/def/2"},
  1514. },
  1515. },
  1516. },
  1517. }
  1518. for i, tt := range tests {
  1519. n := trimNodeExternPrefix(tt.n, pre)
  1520. if !reflect.DeepEqual(n, tt.wn) {
  1521. t.Errorf("#%d: node = %+v, want %+v", i, n, tt.wn)
  1522. }
  1523. }
  1524. }
  1525. func TestTrimPrefix(t *testing.T) {
  1526. tests := []struct {
  1527. in string
  1528. prefix string
  1529. w string
  1530. }{
  1531. {"/v2/members", "/v2/members", ""},
  1532. {"/v2/members/", "/v2/members", ""},
  1533. {"/v2/members/foo", "/v2/members", "foo"},
  1534. }
  1535. for i, tt := range tests {
  1536. if g := trimPrefix(tt.in, tt.prefix); g != tt.w {
  1537. t.Errorf("#%d: trimPrefix = %q, want %q", i, g, tt.w)
  1538. }
  1539. }
  1540. }
  1541. func TestNewMemberCollection(t *testing.T) {
  1542. fixture := []*etcdserver.Member{
  1543. &etcdserver.Member{
  1544. ID: 12,
  1545. Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"}},
  1546. RaftAttributes: etcdserver.RaftAttributes{PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"}},
  1547. },
  1548. &etcdserver.Member{
  1549. ID: 13,
  1550. Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:9090", "http://localhost:9091"}},
  1551. RaftAttributes: etcdserver.RaftAttributes{PeerURLs: []string{"http://localhost:9092", "http://localhost:9093"}},
  1552. },
  1553. }
  1554. got := newMemberCollection(fixture)
  1555. want := httptypes.MemberCollection([]httptypes.Member{
  1556. httptypes.Member{
  1557. ID: "c",
  1558. ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"},
  1559. PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"},
  1560. },
  1561. httptypes.Member{
  1562. ID: "d",
  1563. ClientURLs: []string{"http://localhost:9090", "http://localhost:9091"},
  1564. PeerURLs: []string{"http://localhost:9092", "http://localhost:9093"},
  1565. },
  1566. })
  1567. if !reflect.DeepEqual(&want, got) {
  1568. t.Fatalf("newMemberCollection failure: want=%#v, got=%#v", &want, got)
  1569. }
  1570. }
  1571. func TestNewMember(t *testing.T) {
  1572. fixture := &etcdserver.Member{
  1573. ID: 12,
  1574. Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"}},
  1575. RaftAttributes: etcdserver.RaftAttributes{PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"}},
  1576. }
  1577. got := newMember(fixture)
  1578. want := httptypes.Member{
  1579. ID: "c",
  1580. ClientURLs: []string{"http://localhost:8080", "http://localhost:8081"},
  1581. PeerURLs: []string{"http://localhost:8082", "http://localhost:8083"},
  1582. }
  1583. if !reflect.DeepEqual(want, got) {
  1584. t.Fatalf("newMember failure: want=%#v, got=%#v", want, got)
  1585. }
  1586. }