client_test.go 50 KB

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