client_test.go 49 KB

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