client_test.go 46 KB

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