client_test.go 49 KB

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