client_test.go 49 KB

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