client_test.go 50 KB

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