client_test.go 50 KB

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