http_test.go 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553
  1. /*
  2. Copyright 2014 CoreOS, Inc.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package etcdhttp
  14. import (
  15. "bytes"
  16. "encoding/json"
  17. "errors"
  18. "io"
  19. "io/ioutil"
  20. "net/http"
  21. "net/http/httptest"
  22. "net/url"
  23. "path"
  24. "reflect"
  25. "strings"
  26. "testing"
  27. "time"
  28. "github.com/coreos/etcd/Godeps/_workspace/src/code.google.com/p/go.net/context"
  29. "github.com/coreos/etcd/Godeps/_workspace/src/github.com/jonboulle/clockwork"
  30. etcdErr "github.com/coreos/etcd/error"
  31. "github.com/coreos/etcd/etcdserver"
  32. "github.com/coreos/etcd/etcdserver/etcdserverpb"
  33. "github.com/coreos/etcd/raft/raftpb"
  34. "github.com/coreos/etcd/store"
  35. )
  36. func boolp(b bool) *bool { return &b }
  37. func mustNewURL(t *testing.T, s string) *url.URL {
  38. u, err := url.Parse(s)
  39. if err != nil {
  40. t.Fatalf("error creating URL from %q: %v", s, err)
  41. }
  42. return u
  43. }
  44. // mustNewRequest takes a path, appends it to the standard keysPrefix, and constructs
  45. // a GET *http.Request referencing the resulting URL
  46. func mustNewRequest(t *testing.T, p string) *http.Request {
  47. return mustNewMethodRequest(t, "GET", p)
  48. }
  49. func mustNewMethodRequest(t *testing.T, m, p string) *http.Request {
  50. return &http.Request{
  51. Method: m,
  52. URL: mustNewURL(t, path.Join(keysPrefix, p)),
  53. }
  54. }
  55. // mustNewForm takes a set of Values and constructs a PUT *http.Request,
  56. // with a URL constructed from appending the given path to the standard keysPrefix
  57. func mustNewForm(t *testing.T, p string, vals url.Values) *http.Request {
  58. u := mustNewURL(t, path.Join(keysPrefix, p))
  59. req, err := http.NewRequest("PUT", u.String(), strings.NewReader(vals.Encode()))
  60. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  61. if err != nil {
  62. t.Fatalf("error creating new request: %v", err)
  63. }
  64. return req
  65. }
  66. func TestBadParseRequest(t *testing.T) {
  67. tests := []struct {
  68. in *http.Request
  69. wcode int
  70. }{
  71. {
  72. // parseForm failure
  73. &http.Request{
  74. Body: nil,
  75. Method: "PUT",
  76. },
  77. etcdErr.EcodeInvalidForm,
  78. },
  79. {
  80. // bad key prefix
  81. &http.Request{
  82. URL: mustNewURL(t, "/badprefix/"),
  83. },
  84. etcdErr.EcodeInvalidForm,
  85. },
  86. // bad values for prevIndex, waitIndex, ttl
  87. {
  88. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"garbage"}}),
  89. etcdErr.EcodeIndexNaN,
  90. },
  91. {
  92. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"1.5"}}),
  93. etcdErr.EcodeIndexNaN,
  94. },
  95. {
  96. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"-1"}}),
  97. etcdErr.EcodeIndexNaN,
  98. },
  99. {
  100. mustNewForm(t, "foo", url.Values{"waitIndex": []string{"garbage"}}),
  101. etcdErr.EcodeIndexNaN,
  102. },
  103. {
  104. mustNewForm(t, "foo", url.Values{"waitIndex": []string{"??"}}),
  105. etcdErr.EcodeIndexNaN,
  106. },
  107. {
  108. mustNewForm(t, "foo", url.Values{"ttl": []string{"-1"}}),
  109. etcdErr.EcodeTTLNaN,
  110. },
  111. // bad values for recursive, sorted, wait, prevExist, dir, stream
  112. {
  113. mustNewForm(t, "foo", url.Values{"recursive": []string{"hahaha"}}),
  114. etcdErr.EcodeInvalidField,
  115. },
  116. {
  117. mustNewForm(t, "foo", url.Values{"recursive": []string{"1234"}}),
  118. etcdErr.EcodeInvalidField,
  119. },
  120. {
  121. mustNewForm(t, "foo", url.Values{"recursive": []string{"?"}}),
  122. etcdErr.EcodeInvalidField,
  123. },
  124. {
  125. mustNewForm(t, "foo", url.Values{"sorted": []string{"?"}}),
  126. etcdErr.EcodeInvalidField,
  127. },
  128. {
  129. mustNewForm(t, "foo", url.Values{"sorted": []string{"x"}}),
  130. etcdErr.EcodeInvalidField,
  131. },
  132. {
  133. mustNewForm(t, "foo", url.Values{"wait": []string{"?!"}}),
  134. etcdErr.EcodeInvalidField,
  135. },
  136. {
  137. mustNewForm(t, "foo", url.Values{"wait": []string{"yes"}}),
  138. etcdErr.EcodeInvalidField,
  139. },
  140. {
  141. mustNewForm(t, "foo", url.Values{"prevExist": []string{"yes"}}),
  142. etcdErr.EcodeInvalidField,
  143. },
  144. {
  145. mustNewForm(t, "foo", url.Values{"prevExist": []string{"#2"}}),
  146. etcdErr.EcodeInvalidField,
  147. },
  148. {
  149. mustNewForm(t, "foo", url.Values{"dir": []string{"no"}}),
  150. etcdErr.EcodeInvalidField,
  151. },
  152. {
  153. mustNewForm(t, "foo", url.Values{"dir": []string{"file"}}),
  154. etcdErr.EcodeInvalidField,
  155. },
  156. {
  157. mustNewForm(t, "foo", url.Values{"stream": []string{"zzz"}}),
  158. etcdErr.EcodeInvalidField,
  159. },
  160. {
  161. mustNewForm(t, "foo", url.Values{"stream": []string{"something"}}),
  162. etcdErr.EcodeInvalidField,
  163. },
  164. // prevValue cannot be empty
  165. {
  166. mustNewForm(t, "foo", url.Values{"prevValue": []string{""}}),
  167. etcdErr.EcodeInvalidField,
  168. },
  169. // wait is only valid with GET requests
  170. {
  171. mustNewMethodRequest(t, "HEAD", "foo?wait=true"),
  172. etcdErr.EcodeInvalidField,
  173. },
  174. // query values are considered
  175. {
  176. mustNewRequest(t, "foo?prevExist=wrong"),
  177. etcdErr.EcodeInvalidField,
  178. },
  179. {
  180. mustNewRequest(t, "foo?ttl=wrong"),
  181. etcdErr.EcodeTTLNaN,
  182. },
  183. // but body takes precedence if both are specified
  184. {
  185. mustNewForm(
  186. t,
  187. "foo?ttl=12",
  188. url.Values{"ttl": []string{"garbage"}},
  189. ),
  190. etcdErr.EcodeTTLNaN,
  191. },
  192. {
  193. mustNewForm(
  194. t,
  195. "foo?prevExist=false",
  196. url.Values{"prevExist": []string{"yes"}},
  197. ),
  198. etcdErr.EcodeInvalidField,
  199. },
  200. }
  201. for i, tt := range tests {
  202. got, err := parseRequest(tt.in, 1234, clockwork.NewFakeClock())
  203. if err == nil {
  204. t.Errorf("#%d: unexpected nil error!", i)
  205. continue
  206. }
  207. ee, ok := err.(*etcdErr.Error)
  208. if !ok {
  209. t.Errorf("#%d: err is not etcd.Error!", i)
  210. continue
  211. }
  212. if ee.ErrorCode != tt.wcode {
  213. t.Errorf("#%d: code=%d, want %v", i, ee.ErrorCode, tt.wcode)
  214. t.Logf("cause: %#v", ee.Cause)
  215. }
  216. if !reflect.DeepEqual(got, etcdserverpb.Request{}) {
  217. t.Errorf("#%d: unexpected non-empty Request: %#v", i, got)
  218. }
  219. }
  220. }
  221. func TestGoodParseRequest(t *testing.T) {
  222. fc := clockwork.NewFakeClock()
  223. fc.Advance(1111)
  224. tests := []struct {
  225. in *http.Request
  226. w etcdserverpb.Request
  227. }{
  228. {
  229. // good prefix, all other values default
  230. mustNewRequest(t, "foo"),
  231. etcdserverpb.Request{
  232. ID: 1234,
  233. Method: "GET",
  234. Path: "/foo",
  235. },
  236. },
  237. {
  238. // value specified
  239. mustNewForm(
  240. t,
  241. "foo",
  242. url.Values{"value": []string{"some_value"}},
  243. ),
  244. etcdserverpb.Request{
  245. ID: 1234,
  246. Method: "PUT",
  247. Val: "some_value",
  248. Path: "/foo",
  249. },
  250. },
  251. {
  252. // prevIndex specified
  253. mustNewForm(
  254. t,
  255. "foo",
  256. url.Values{"prevIndex": []string{"98765"}},
  257. ),
  258. etcdserverpb.Request{
  259. ID: 1234,
  260. Method: "PUT",
  261. PrevIndex: 98765,
  262. Path: "/foo",
  263. },
  264. },
  265. {
  266. // recursive specified
  267. mustNewForm(
  268. t,
  269. "foo",
  270. url.Values{"recursive": []string{"true"}},
  271. ),
  272. etcdserverpb.Request{
  273. ID: 1234,
  274. Method: "PUT",
  275. Recursive: true,
  276. Path: "/foo",
  277. },
  278. },
  279. {
  280. // sorted specified
  281. mustNewForm(
  282. t,
  283. "foo",
  284. url.Values{"sorted": []string{"true"}},
  285. ),
  286. etcdserverpb.Request{
  287. ID: 1234,
  288. Method: "PUT",
  289. Sorted: true,
  290. Path: "/foo",
  291. },
  292. },
  293. {
  294. // wait specified
  295. mustNewRequest(t, "foo?wait=true"),
  296. etcdserverpb.Request{
  297. ID: 1234,
  298. Method: "GET",
  299. Wait: true,
  300. Path: "/foo",
  301. },
  302. },
  303. {
  304. // empty TTL specified
  305. mustNewRequest(t, "foo?ttl="),
  306. etcdserverpb.Request{
  307. ID: 1234,
  308. Method: "GET",
  309. Path: "/foo",
  310. Expiration: 0,
  311. },
  312. },
  313. {
  314. // non-empty TTL specified
  315. mustNewRequest(t, "foo?ttl=5678"),
  316. etcdserverpb.Request{
  317. ID: 1234,
  318. Method: "GET",
  319. Path: "/foo",
  320. Expiration: fc.Now().Add(5678 * time.Second).UnixNano(),
  321. },
  322. },
  323. {
  324. // zero TTL specified
  325. mustNewRequest(t, "foo?ttl=0"),
  326. etcdserverpb.Request{
  327. ID: 1234,
  328. Method: "GET",
  329. Path: "/foo",
  330. Expiration: fc.Now().UnixNano(),
  331. },
  332. },
  333. {
  334. // dir specified
  335. mustNewRequest(t, "foo?dir=true"),
  336. etcdserverpb.Request{
  337. ID: 1234,
  338. Method: "GET",
  339. Dir: true,
  340. Path: "/foo",
  341. },
  342. },
  343. {
  344. // dir specified negatively
  345. mustNewRequest(t, "foo?dir=false"),
  346. etcdserverpb.Request{
  347. ID: 1234,
  348. Method: "GET",
  349. Dir: false,
  350. Path: "/foo",
  351. },
  352. },
  353. {
  354. // prevExist should be non-null if specified
  355. mustNewForm(
  356. t,
  357. "foo",
  358. url.Values{"prevExist": []string{"true"}},
  359. ),
  360. etcdserverpb.Request{
  361. ID: 1234,
  362. Method: "PUT",
  363. PrevExist: boolp(true),
  364. Path: "/foo",
  365. },
  366. },
  367. {
  368. // prevExist should be non-null if specified
  369. mustNewForm(
  370. t,
  371. "foo",
  372. url.Values{"prevExist": []string{"false"}},
  373. ),
  374. etcdserverpb.Request{
  375. ID: 1234,
  376. Method: "PUT",
  377. PrevExist: boolp(false),
  378. Path: "/foo",
  379. },
  380. },
  381. // mix various fields
  382. {
  383. mustNewForm(
  384. t,
  385. "foo",
  386. url.Values{
  387. "value": []string{"some value"},
  388. "prevExist": []string{"true"},
  389. "prevValue": []string{"previous value"},
  390. },
  391. ),
  392. etcdserverpb.Request{
  393. ID: 1234,
  394. Method: "PUT",
  395. PrevExist: boolp(true),
  396. PrevValue: "previous value",
  397. Val: "some value",
  398. Path: "/foo",
  399. },
  400. },
  401. // query parameters should be used if given
  402. {
  403. mustNewForm(
  404. t,
  405. "foo?prevValue=woof",
  406. url.Values{},
  407. ),
  408. etcdserverpb.Request{
  409. ID: 1234,
  410. Method: "PUT",
  411. PrevValue: "woof",
  412. Path: "/foo",
  413. },
  414. },
  415. // but form values should take precedence over query parameters
  416. {
  417. mustNewForm(
  418. t,
  419. "foo?prevValue=woof",
  420. url.Values{
  421. "prevValue": []string{"miaow"},
  422. },
  423. ),
  424. etcdserverpb.Request{
  425. ID: 1234,
  426. Method: "PUT",
  427. PrevValue: "miaow",
  428. Path: "/foo",
  429. },
  430. },
  431. }
  432. for i, tt := range tests {
  433. got, err := parseRequest(tt.in, 1234, fc)
  434. if err != nil {
  435. t.Errorf("#%d: err = %v, want %v", i, err, nil)
  436. }
  437. if !reflect.DeepEqual(got, tt.w) {
  438. t.Errorf("#%d: request=%#v, want %#v", i, got, tt.w)
  439. }
  440. }
  441. }
  442. // eventingWatcher immediately returns a simple event of the given action on its channel
  443. type eventingWatcher struct {
  444. action string
  445. }
  446. func (w *eventingWatcher) EventChan() chan *store.Event {
  447. ch := make(chan *store.Event)
  448. go func() {
  449. ch <- &store.Event{
  450. Action: w.action,
  451. Node: &store.NodeExtern{},
  452. }
  453. }()
  454. return ch
  455. }
  456. func (w *eventingWatcher) Remove() {}
  457. func TestWriteError(t *testing.T) {
  458. // nil error should not panic
  459. rw := httptest.NewRecorder()
  460. writeError(rw, nil)
  461. h := rw.Header()
  462. if len(h) > 0 {
  463. t.Fatalf("unexpected non-empty headers: %#v", h)
  464. }
  465. b := rw.Body.String()
  466. if len(b) > 0 {
  467. t.Fatalf("unexpected non-empty body: %q", b)
  468. }
  469. tests := []struct {
  470. err error
  471. wcode int
  472. wi string
  473. }{
  474. {
  475. etcdErr.NewError(etcdErr.EcodeKeyNotFound, "/foo/bar", 123),
  476. http.StatusNotFound,
  477. "123",
  478. },
  479. {
  480. etcdErr.NewError(etcdErr.EcodeTestFailed, "/foo/bar", 456),
  481. http.StatusPreconditionFailed,
  482. "456",
  483. },
  484. {
  485. err: errors.New("something went wrong"),
  486. wcode: http.StatusInternalServerError,
  487. },
  488. }
  489. for i, tt := range tests {
  490. rw := httptest.NewRecorder()
  491. writeError(rw, tt.err)
  492. if code := rw.Code; code != tt.wcode {
  493. t.Errorf("#%d: code=%d, want %d", i, code, tt.wcode)
  494. }
  495. if idx := rw.Header().Get("X-Etcd-Index"); idx != tt.wi {
  496. t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, idx, tt.wi)
  497. }
  498. }
  499. }
  500. type dummyRaftTimer struct{}
  501. func (drt dummyRaftTimer) Index() uint64 { return uint64(100) }
  502. func (drt dummyRaftTimer) Term() uint64 { return uint64(5) }
  503. func TestWriteEvent(t *testing.T) {
  504. // nil event should not panic
  505. rw := httptest.NewRecorder()
  506. writeEvent(rw, nil, dummyRaftTimer{})
  507. h := rw.Header()
  508. if len(h) > 0 {
  509. t.Fatalf("unexpected non-empty headers: %#v", h)
  510. }
  511. b := rw.Body.String()
  512. if len(b) > 0 {
  513. t.Fatalf("unexpected non-empty body: %q", b)
  514. }
  515. tests := []struct {
  516. ev *store.Event
  517. idx string
  518. // TODO(jonboulle): check body as well as just status code
  519. code int
  520. err error
  521. }{
  522. // standard case, standard 200 response
  523. {
  524. &store.Event{
  525. Action: store.Get,
  526. Node: &store.NodeExtern{},
  527. PrevNode: &store.NodeExtern{},
  528. },
  529. "0",
  530. http.StatusOK,
  531. nil,
  532. },
  533. // check new nodes return StatusCreated
  534. {
  535. &store.Event{
  536. Action: store.Create,
  537. Node: &store.NodeExtern{},
  538. PrevNode: &store.NodeExtern{},
  539. },
  540. "0",
  541. http.StatusCreated,
  542. nil,
  543. },
  544. }
  545. for i, tt := range tests {
  546. rw := httptest.NewRecorder()
  547. writeEvent(rw, tt.ev, dummyRaftTimer{})
  548. if gct := rw.Header().Get("Content-Type"); gct != "application/json" {
  549. t.Errorf("case %d: bad Content-Type: got %q, want application/json", i, gct)
  550. }
  551. if gri := rw.Header().Get("X-Raft-Index"); gri != "100" {
  552. t.Errorf("case %d: bad X-Raft-Index header: got %s, want %s", i, gri, "100")
  553. }
  554. if grt := rw.Header().Get("X-Raft-Term"); grt != "5" {
  555. t.Errorf("case %d: bad X-Raft-Term header: got %s, want %s", i, grt, "5")
  556. }
  557. if gei := rw.Header().Get("X-Etcd-Index"); gei != tt.idx {
  558. t.Errorf("case %d: bad X-Etcd-Index header: got %s, want %s", i, gei, tt.idx)
  559. }
  560. if rw.Code != tt.code {
  561. t.Errorf("case %d: bad response code: got %d, want %v", i, rw.Code, tt.code)
  562. }
  563. }
  564. }
  565. type dummyWatcher struct {
  566. echan chan *store.Event
  567. sidx uint64
  568. }
  569. func (w *dummyWatcher) EventChan() chan *store.Event {
  570. return w.echan
  571. }
  572. func (w *dummyWatcher) StartIndex() uint64 { return w.sidx }
  573. func (w *dummyWatcher) Remove() {}
  574. func TestV2DeprecatedMachinesEndpoint(t *testing.T) {
  575. tests := []struct {
  576. method string
  577. wcode int
  578. }{
  579. {"GET", http.StatusOK},
  580. {"HEAD", http.StatusOK},
  581. {"POST", http.StatusMethodNotAllowed},
  582. }
  583. m := NewClientHandler(&etcdserver.EtcdServer{ClusterStore: &fakeCluster{}})
  584. s := httptest.NewServer(m)
  585. defer s.Close()
  586. for _, tt := range tests {
  587. req, err := http.NewRequest(tt.method, s.URL+deprecatedMachinesPrefix, nil)
  588. if err != nil {
  589. t.Fatal(err)
  590. }
  591. resp, err := http.DefaultClient.Do(req)
  592. if err != nil {
  593. t.Fatal(err)
  594. }
  595. if resp.StatusCode != tt.wcode {
  596. t.Errorf("StatusCode = %d, expected %d", resp.StatusCode, tt.wcode)
  597. }
  598. }
  599. }
  600. func TestServeMachines(t *testing.T) {
  601. cluster := &fakeCluster{
  602. members: []etcdserver.Member{
  603. {ID: 0xBEEF0, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080"}}},
  604. {ID: 0xBEEF1, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8081"}}},
  605. {ID: 0xBEEF2, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8082"}}},
  606. },
  607. }
  608. writer := httptest.NewRecorder()
  609. req, err := http.NewRequest("GET", "", nil)
  610. if err != nil {
  611. t.Fatal(err)
  612. }
  613. h := &serverHandler{clusterStore: cluster}
  614. h.serveMachines(writer, req)
  615. w := "http://localhost:8080, http://localhost:8081, http://localhost:8082"
  616. if g := writer.Body.String(); g != w {
  617. t.Errorf("body = %s, want %s", g, w)
  618. }
  619. if writer.Code != http.StatusOK {
  620. t.Errorf("code = %d, want %d", writer.Code, http.StatusOK)
  621. }
  622. }
  623. type dummyStats struct {
  624. data []byte
  625. }
  626. func (ds *dummyStats) SelfStats() []byte { return ds.data }
  627. func (ds *dummyStats) LeaderStats() []byte { return ds.data }
  628. func (ds *dummyStats) StoreStats() []byte { return ds.data }
  629. func (ds *dummyStats) UpdateRecvApp(_ uint64, _ int64) {}
  630. func TestServeSelfStats(t *testing.T) {
  631. wb := []byte("some statistics")
  632. w := string(wb)
  633. sh := &serverHandler{
  634. stats: &dummyStats{data: wb},
  635. }
  636. rw := httptest.NewRecorder()
  637. sh.serveSelfStats(rw, &http.Request{Method: "GET"})
  638. if rw.Code != http.StatusOK {
  639. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  640. }
  641. wct := "application/json"
  642. if gct := rw.Header().Get("Content-Type"); gct != wct {
  643. t.Errorf("Content-Type = %q, want %q", gct, wct)
  644. }
  645. if g := rw.Body.String(); g != w {
  646. t.Errorf("body = %s, want %s", g, w)
  647. }
  648. }
  649. func TestSelfServeStatsBad(t *testing.T) {
  650. for _, m := range []string{"PUT", "POST", "DELETE"} {
  651. sh := &serverHandler{}
  652. rw := httptest.NewRecorder()
  653. sh.serveSelfStats(
  654. rw,
  655. &http.Request{
  656. Method: m,
  657. },
  658. )
  659. if rw.Code != http.StatusMethodNotAllowed {
  660. t.Errorf("method %s: code=%d, want %d", m, http.StatusMethodNotAllowed)
  661. }
  662. }
  663. }
  664. func TestLeaderServeStatsBad(t *testing.T) {
  665. for _, m := range []string{"PUT", "POST", "DELETE"} {
  666. sh := &serverHandler{}
  667. rw := httptest.NewRecorder()
  668. sh.serveLeaderStats(
  669. rw,
  670. &http.Request{
  671. Method: m,
  672. },
  673. )
  674. if rw.Code != http.StatusMethodNotAllowed {
  675. t.Errorf("method %s: code=%d, want %d", m, http.StatusMethodNotAllowed)
  676. }
  677. }
  678. }
  679. func TestServeLeaderStats(t *testing.T) {
  680. wb := []byte("some statistics")
  681. w := string(wb)
  682. sh := &serverHandler{
  683. stats: &dummyStats{data: wb},
  684. }
  685. rw := httptest.NewRecorder()
  686. sh.serveLeaderStats(rw, &http.Request{Method: "GET"})
  687. if rw.Code != http.StatusOK {
  688. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  689. }
  690. wct := "application/json"
  691. if gct := rw.Header().Get("Content-Type"); gct != wct {
  692. t.Errorf("Content-Type = %q, want %q", gct, wct)
  693. }
  694. if g := rw.Body.String(); g != w {
  695. t.Errorf("body = %s, want %s", g, w)
  696. }
  697. }
  698. func TestServeStoreStats(t *testing.T) {
  699. wb := []byte("some statistics")
  700. w := string(wb)
  701. sh := &serverHandler{
  702. stats: &dummyStats{data: wb},
  703. }
  704. rw := httptest.NewRecorder()
  705. sh.serveStoreStats(rw, &http.Request{Method: "GET"})
  706. if rw.Code != http.StatusOK {
  707. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  708. }
  709. wct := "application/json"
  710. if gct := rw.Header().Get("Content-Type"); gct != wct {
  711. t.Errorf("Content-Type = %q, want %q", gct, wct)
  712. }
  713. if g := rw.Body.String(); g != w {
  714. t.Errorf("body = %s, want %s", g, w)
  715. }
  716. }
  717. func TestAllowMethod(t *testing.T) {
  718. tests := []struct {
  719. m string
  720. ms []string
  721. w bool
  722. wh string
  723. }{
  724. // Accepted methods
  725. {
  726. m: "GET",
  727. ms: []string{"GET", "POST", "PUT"},
  728. w: true,
  729. },
  730. {
  731. m: "POST",
  732. ms: []string{"POST"},
  733. w: true,
  734. },
  735. // Made-up methods no good
  736. {
  737. m: "FAKE",
  738. ms: []string{"GET", "POST", "PUT"},
  739. w: false,
  740. wh: "GET,POST,PUT",
  741. },
  742. // Empty methods no good
  743. {
  744. m: "",
  745. ms: []string{"GET", "POST"},
  746. w: false,
  747. wh: "GET,POST",
  748. },
  749. // Empty accepted methods no good
  750. {
  751. m: "GET",
  752. ms: []string{""},
  753. w: false,
  754. wh: "",
  755. },
  756. // No methods accepted
  757. {
  758. m: "GET",
  759. ms: []string{},
  760. w: false,
  761. wh: "",
  762. },
  763. }
  764. for i, tt := range tests {
  765. rw := httptest.NewRecorder()
  766. g := allowMethod(rw, tt.m, tt.ms...)
  767. if g != tt.w {
  768. t.Errorf("#%d: got allowMethod()=%t, want %t", i, g, tt.w)
  769. }
  770. if !tt.w {
  771. if rw.Code != http.StatusMethodNotAllowed {
  772. t.Errorf("#%d: code=%d, want %d", i, rw.Code, http.StatusMethodNotAllowed)
  773. }
  774. gh := rw.Header().Get("Allow")
  775. if gh != tt.wh {
  776. t.Errorf("#%d: Allow header=%q, want %q", i, gh, tt.wh)
  777. }
  778. }
  779. }
  780. }
  781. // errServer implements the etcd.Server interface for testing.
  782. // It returns the given error from any Do/Process/AddMember/RemoveMember calls.
  783. type errServer struct {
  784. err error
  785. }
  786. func (fs *errServer) Do(ctx context.Context, r etcdserverpb.Request) (etcdserver.Response, error) {
  787. return etcdserver.Response{}, fs.err
  788. }
  789. func (fs *errServer) Process(ctx context.Context, m raftpb.Message) error {
  790. return fs.err
  791. }
  792. func (fs *errServer) Start() {}
  793. func (fs *errServer) Stop() {}
  794. func (fs *errServer) AddMember(ctx context.Context, m etcdserver.Member) error {
  795. return fs.err
  796. }
  797. func (fs *errServer) RemoveMember(ctx context.Context, id uint64) error {
  798. return fs.err
  799. }
  800. // errReader implements io.Reader to facilitate a broken request.
  801. type errReader struct{}
  802. func (er *errReader) Read(_ []byte) (int, error) { return 0, errors.New("some error") }
  803. func mustMarshalMsg(t *testing.T, m raftpb.Message) []byte {
  804. json, err := m.Marshal()
  805. if err != nil {
  806. t.Fatalf("error marshalling raft Message: %#v", err)
  807. }
  808. return json
  809. }
  810. func TestServeRaft(t *testing.T) {
  811. testCases := []struct {
  812. method string
  813. body io.Reader
  814. serverErr error
  815. wcode int
  816. }{
  817. {
  818. // bad method
  819. "GET",
  820. bytes.NewReader(
  821. mustMarshalMsg(
  822. t,
  823. raftpb.Message{},
  824. ),
  825. ),
  826. nil,
  827. http.StatusMethodNotAllowed,
  828. },
  829. {
  830. // bad method
  831. "PUT",
  832. bytes.NewReader(
  833. mustMarshalMsg(
  834. t,
  835. raftpb.Message{},
  836. ),
  837. ),
  838. nil,
  839. http.StatusMethodNotAllowed,
  840. },
  841. {
  842. // bad method
  843. "DELETE",
  844. bytes.NewReader(
  845. mustMarshalMsg(
  846. t,
  847. raftpb.Message{},
  848. ),
  849. ),
  850. nil,
  851. http.StatusMethodNotAllowed,
  852. },
  853. {
  854. // bad request body
  855. "POST",
  856. &errReader{},
  857. nil,
  858. http.StatusBadRequest,
  859. },
  860. {
  861. // bad request protobuf
  862. "POST",
  863. strings.NewReader("malformed garbage"),
  864. nil,
  865. http.StatusBadRequest,
  866. },
  867. {
  868. // good request, etcdserver.Server error
  869. "POST",
  870. bytes.NewReader(
  871. mustMarshalMsg(
  872. t,
  873. raftpb.Message{},
  874. ),
  875. ),
  876. errors.New("some error"),
  877. http.StatusInternalServerError,
  878. },
  879. {
  880. // good request
  881. "POST",
  882. bytes.NewReader(
  883. mustMarshalMsg(
  884. t,
  885. raftpb.Message{},
  886. ),
  887. ),
  888. nil,
  889. http.StatusNoContent,
  890. },
  891. }
  892. for i, tt := range testCases {
  893. req, err := http.NewRequest(tt.method, "foo", tt.body)
  894. if err != nil {
  895. t.Fatalf("#%d: could not create request: %#v", i, err)
  896. }
  897. h := &serverHandler{
  898. timeout: time.Hour,
  899. server: &errServer{tt.serverErr},
  900. }
  901. rw := httptest.NewRecorder()
  902. h.serveRaft(rw, req)
  903. if rw.Code != tt.wcode {
  904. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  905. }
  906. }
  907. }
  908. // resServer implements the etcd.Server interface for testing.
  909. // It returns the given responsefrom any Do calls, and nil error
  910. type resServer struct {
  911. res etcdserver.Response
  912. }
  913. func (rs *resServer) Do(_ context.Context, _ etcdserverpb.Request) (etcdserver.Response, error) {
  914. return rs.res, nil
  915. }
  916. func (rs *resServer) Process(_ context.Context, _ raftpb.Message) error { return nil }
  917. func (rs *resServer) Start() {}
  918. func (rs *resServer) Stop() {}
  919. func (rs *resServer) AddMember(_ context.Context, _ etcdserver.Member) error { return nil }
  920. func (rs *resServer) RemoveMember(_ context.Context, _ uint64) error { return nil }
  921. func mustMarshalEvent(t *testing.T, ev *store.Event) string {
  922. b := new(bytes.Buffer)
  923. if err := json.NewEncoder(b).Encode(ev); err != nil {
  924. t.Fatalf("error marshalling event %#v: %v", ev, err)
  925. }
  926. return b.String()
  927. }
  928. func TestBadServeKeys(t *testing.T) {
  929. testBadCases := []struct {
  930. req *http.Request
  931. server etcdserver.Server
  932. wcode int
  933. }{
  934. {
  935. // bad method
  936. &http.Request{
  937. Method: "CONNECT",
  938. },
  939. &resServer{},
  940. http.StatusMethodNotAllowed,
  941. },
  942. {
  943. // bad method
  944. &http.Request{
  945. Method: "TRACE",
  946. },
  947. &resServer{},
  948. http.StatusMethodNotAllowed,
  949. },
  950. {
  951. // parseRequest error
  952. &http.Request{
  953. Body: nil,
  954. Method: "PUT",
  955. },
  956. &resServer{},
  957. http.StatusBadRequest,
  958. },
  959. {
  960. // etcdserver.Server error
  961. mustNewRequest(t, "foo"),
  962. &errServer{
  963. errors.New("blah"),
  964. },
  965. http.StatusInternalServerError,
  966. },
  967. {
  968. // non-event/watcher response from etcdserver.Server
  969. mustNewRequest(t, "foo"),
  970. &resServer{
  971. etcdserver.Response{},
  972. },
  973. http.StatusInternalServerError,
  974. },
  975. }
  976. for i, tt := range testBadCases {
  977. h := &serverHandler{
  978. timeout: 0, // context times out immediately
  979. server: tt.server,
  980. }
  981. rw := httptest.NewRecorder()
  982. h.serveKeys(rw, tt.req)
  983. if rw.Code != tt.wcode {
  984. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  985. }
  986. }
  987. }
  988. func TestServeKeysEvent(t *testing.T) {
  989. req := mustNewRequest(t, "foo")
  990. server := &resServer{
  991. etcdserver.Response{
  992. Event: &store.Event{
  993. Action: store.Get,
  994. Node: &store.NodeExtern{},
  995. },
  996. },
  997. }
  998. h := &serverHandler{
  999. timeout: time.Hour,
  1000. server: server,
  1001. timer: &dummyRaftTimer{},
  1002. }
  1003. rw := httptest.NewRecorder()
  1004. h.serveKeys(rw, req)
  1005. wcode := http.StatusOK
  1006. wbody := mustMarshalEvent(
  1007. t,
  1008. &store.Event{
  1009. Action: store.Get,
  1010. Node: &store.NodeExtern{},
  1011. },
  1012. )
  1013. if rw.Code != wcode {
  1014. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1015. }
  1016. g := rw.Body.String()
  1017. if g != wbody {
  1018. t.Errorf("got body=%#v, want %#v", g, wbody)
  1019. }
  1020. }
  1021. func TestServeKeysWatch(t *testing.T) {
  1022. req := mustNewRequest(t, "/foo/bar")
  1023. ec := make(chan *store.Event)
  1024. dw := &dummyWatcher{
  1025. echan: ec,
  1026. }
  1027. server := &resServer{
  1028. etcdserver.Response{
  1029. Watcher: dw,
  1030. },
  1031. }
  1032. h := &serverHandler{
  1033. timeout: time.Hour,
  1034. server: server,
  1035. timer: &dummyRaftTimer{},
  1036. }
  1037. go func() {
  1038. ec <- &store.Event{
  1039. Action: store.Get,
  1040. Node: &store.NodeExtern{},
  1041. }
  1042. }()
  1043. rw := httptest.NewRecorder()
  1044. h.serveKeys(rw, req)
  1045. wcode := http.StatusOK
  1046. wbody := mustMarshalEvent(
  1047. t,
  1048. &store.Event{
  1049. Action: store.Get,
  1050. Node: &store.NodeExtern{},
  1051. },
  1052. )
  1053. if rw.Code != wcode {
  1054. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1055. }
  1056. g := rw.Body.String()
  1057. if g != wbody {
  1058. t.Errorf("got body=%#v, want %#v", g, wbody)
  1059. }
  1060. }
  1061. type recordingCloseNotifier struct {
  1062. *httptest.ResponseRecorder
  1063. cn chan bool
  1064. }
  1065. func (rcn *recordingCloseNotifier) CloseNotify() <-chan bool {
  1066. return rcn.cn
  1067. }
  1068. func TestHandleWatch(t *testing.T) {
  1069. defaultRwRr := func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1070. r := httptest.NewRecorder()
  1071. return r, r
  1072. }
  1073. noopEv := func(chan *store.Event) {}
  1074. tests := []struct {
  1075. getCtx func() context.Context
  1076. getRwRr func() (http.ResponseWriter, *httptest.ResponseRecorder)
  1077. doToChan func(chan *store.Event)
  1078. wbody string
  1079. }{
  1080. {
  1081. // Normal case: one event
  1082. context.Background,
  1083. defaultRwRr,
  1084. func(ch chan *store.Event) {
  1085. ch <- &store.Event{
  1086. Action: store.Get,
  1087. Node: &store.NodeExtern{},
  1088. }
  1089. },
  1090. mustMarshalEvent(
  1091. t,
  1092. &store.Event{
  1093. Action: store.Get,
  1094. Node: &store.NodeExtern{},
  1095. },
  1096. ),
  1097. },
  1098. {
  1099. // Channel is closed, no event
  1100. context.Background,
  1101. defaultRwRr,
  1102. func(ch chan *store.Event) {
  1103. close(ch)
  1104. },
  1105. "",
  1106. },
  1107. {
  1108. // Simulate a timed-out context
  1109. func() context.Context {
  1110. ctx, cancel := context.WithCancel(context.Background())
  1111. cancel()
  1112. return ctx
  1113. },
  1114. defaultRwRr,
  1115. noopEv,
  1116. "",
  1117. },
  1118. {
  1119. // Close-notifying request
  1120. context.Background,
  1121. func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1122. rw := &recordingCloseNotifier{
  1123. ResponseRecorder: httptest.NewRecorder(),
  1124. cn: make(chan bool, 1),
  1125. }
  1126. rw.cn <- true
  1127. return rw, rw.ResponseRecorder
  1128. },
  1129. noopEv,
  1130. "",
  1131. },
  1132. }
  1133. for i, tt := range tests {
  1134. rw, rr := tt.getRwRr()
  1135. wa := &dummyWatcher{
  1136. echan: make(chan *store.Event, 1),
  1137. sidx: 10,
  1138. }
  1139. tt.doToChan(wa.echan)
  1140. handleWatch(tt.getCtx(), rw, wa, false, dummyRaftTimer{})
  1141. wcode := http.StatusOK
  1142. wct := "application/json"
  1143. wei := "10"
  1144. wri := "100"
  1145. wrt := "5"
  1146. if rr.Code != wcode {
  1147. t.Errorf("#%d: got code=%d, want %d", rr.Code, wcode)
  1148. }
  1149. h := rr.Header()
  1150. if ct := h.Get("Content-Type"); ct != wct {
  1151. t.Errorf("#%d: Content-Type=%q, want %q", i, ct, wct)
  1152. }
  1153. if ei := h.Get("X-Etcd-Index"); ei != wei {
  1154. t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, ei, wei)
  1155. }
  1156. if ri := h.Get("X-Raft-Index"); ri != wri {
  1157. t.Errorf("#%d: X-Raft-Index=%q, want %q", i, ri, wri)
  1158. }
  1159. if rt := h.Get("X-Raft-Term"); rt != wrt {
  1160. t.Errorf("#%d: X-Raft-Term=%q, want %q", i, rt, wrt)
  1161. }
  1162. g := rr.Body.String()
  1163. if g != tt.wbody {
  1164. t.Errorf("#%d: got body=%#v, want %#v", i, g, tt.wbody)
  1165. }
  1166. }
  1167. }
  1168. // flushingRecorder provides a channel to allow users to block until the Recorder is Flushed()
  1169. type flushingRecorder struct {
  1170. *httptest.ResponseRecorder
  1171. ch chan struct{}
  1172. }
  1173. func (fr *flushingRecorder) Flush() {
  1174. fr.ResponseRecorder.Flush()
  1175. fr.ch <- struct{}{}
  1176. }
  1177. func TestHandleWatchStreaming(t *testing.T) {
  1178. rw := &flushingRecorder{
  1179. httptest.NewRecorder(),
  1180. make(chan struct{}, 1),
  1181. }
  1182. wa := &dummyWatcher{
  1183. echan: make(chan *store.Event),
  1184. }
  1185. // Launch the streaming handler in the background with a cancellable context
  1186. ctx, cancel := context.WithCancel(context.Background())
  1187. done := make(chan struct{})
  1188. go func() {
  1189. handleWatch(ctx, rw, wa, true, dummyRaftTimer{})
  1190. close(done)
  1191. }()
  1192. // Expect one Flush for the headers etc.
  1193. select {
  1194. case <-rw.ch:
  1195. case <-time.After(time.Second):
  1196. t.Fatalf("timed out waiting for flush")
  1197. }
  1198. // Expect headers but no body
  1199. wcode := http.StatusOK
  1200. wct := "application/json"
  1201. wbody := ""
  1202. if rw.Code != wcode {
  1203. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1204. }
  1205. h := rw.Header()
  1206. if ct := h.Get("Content-Type"); ct != wct {
  1207. t.Errorf("Content-Type=%q, want %q", ct, wct)
  1208. }
  1209. g := rw.Body.String()
  1210. if g != wbody {
  1211. t.Errorf("got body=%#v, want %#v", g, wbody)
  1212. }
  1213. // Now send the first event
  1214. select {
  1215. case wa.echan <- &store.Event{
  1216. Action: store.Get,
  1217. Node: &store.NodeExtern{},
  1218. }:
  1219. case <-time.After(time.Second):
  1220. t.Fatal("timed out waiting for send")
  1221. }
  1222. // Wait for it to be flushed...
  1223. select {
  1224. case <-rw.ch:
  1225. case <-time.After(time.Second):
  1226. t.Fatalf("timed out waiting for flush")
  1227. }
  1228. // And check the body is as expected
  1229. wbody = mustMarshalEvent(
  1230. t,
  1231. &store.Event{
  1232. Action: store.Get,
  1233. Node: &store.NodeExtern{},
  1234. },
  1235. )
  1236. g = rw.Body.String()
  1237. if g != wbody {
  1238. t.Errorf("got body=%#v, want %#v", g, wbody)
  1239. }
  1240. // Rinse and repeat
  1241. select {
  1242. case wa.echan <- &store.Event{
  1243. Action: store.Get,
  1244. Node: &store.NodeExtern{},
  1245. }:
  1246. case <-time.After(time.Second):
  1247. t.Fatal("timed out waiting for send")
  1248. }
  1249. select {
  1250. case <-rw.ch:
  1251. case <-time.After(time.Second):
  1252. t.Fatalf("timed out waiting for flush")
  1253. }
  1254. // This time, we expect to see both events
  1255. wbody = wbody + wbody
  1256. g = rw.Body.String()
  1257. if g != wbody {
  1258. t.Errorf("got body=%#v, want %#v", g, wbody)
  1259. }
  1260. // Finally, time out the connection and ensure the serving goroutine returns
  1261. cancel()
  1262. select {
  1263. case <-done:
  1264. case <-time.After(time.Second):
  1265. t.Fatalf("timed out waiting for done")
  1266. }
  1267. }
  1268. func TestServeAdminMembersFail(t *testing.T) {
  1269. tests := []struct {
  1270. req *http.Request
  1271. server etcdserver.Server
  1272. wcode int
  1273. }{
  1274. {
  1275. // bad method
  1276. &http.Request{
  1277. Method: "CONNECT",
  1278. },
  1279. &resServer{},
  1280. http.StatusMethodNotAllowed,
  1281. },
  1282. {
  1283. // bad method
  1284. &http.Request{
  1285. Method: "TRACE",
  1286. },
  1287. &resServer{},
  1288. http.StatusMethodNotAllowed,
  1289. },
  1290. {
  1291. // parse id error
  1292. &http.Request{
  1293. URL: mustNewURL(t, path.Join(adminMembersPrefix, "badID")),
  1294. Method: "PUT",
  1295. },
  1296. &resServer{},
  1297. http.StatusBadRequest,
  1298. },
  1299. {
  1300. // parse body error
  1301. &http.Request{
  1302. URL: mustNewURL(t, path.Join(adminMembersPrefix, "1")),
  1303. Method: "PUT",
  1304. },
  1305. &resServer{},
  1306. http.StatusBadRequest,
  1307. },
  1308. {
  1309. // etcdserver.AddMember error
  1310. &http.Request{
  1311. URL: mustNewURL(t, path.Join(adminMembersPrefix, "1")),
  1312. Method: "PUT",
  1313. Body: ioutil.NopCloser(strings.NewReader("")),
  1314. },
  1315. &errServer{
  1316. errors.New("blah"),
  1317. },
  1318. http.StatusInternalServerError,
  1319. },
  1320. {
  1321. // etcdserver.RemoveMember error
  1322. &http.Request{
  1323. URL: mustNewURL(t, path.Join(adminMembersPrefix, "1")),
  1324. Method: "DELETE",
  1325. },
  1326. &errServer{
  1327. errors.New("blah"),
  1328. },
  1329. http.StatusInternalServerError,
  1330. },
  1331. }
  1332. for i, tt := range tests {
  1333. h := &serverHandler{
  1334. server: tt.server,
  1335. }
  1336. rw := httptest.NewRecorder()
  1337. h.serveAdminMembers(rw, tt.req)
  1338. if rw.Code != tt.wcode {
  1339. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  1340. }
  1341. }
  1342. }
  1343. type action struct {
  1344. name string
  1345. params []interface{}
  1346. }
  1347. type serverRecorder struct {
  1348. actions []action
  1349. }
  1350. func (s *serverRecorder) Do(_ context.Context, r etcdserverpb.Request) (etcdserver.Response, error) {
  1351. s.actions = append(s.actions, action{name: "Do", params: []interface{}{r}})
  1352. return etcdserver.Response{}, nil
  1353. }
  1354. func (s *serverRecorder) Process(_ context.Context, m raftpb.Message) error {
  1355. s.actions = append(s.actions, action{name: "Process", params: []interface{}{m}})
  1356. return nil
  1357. }
  1358. func (s *serverRecorder) Start() {}
  1359. func (s *serverRecorder) Stop() {}
  1360. func (s *serverRecorder) AddMember(_ context.Context, m etcdserver.Member) error {
  1361. s.actions = append(s.actions, action{name: "AddMember", params: []interface{}{m}})
  1362. return nil
  1363. }
  1364. func (s *serverRecorder) RemoveMember(_ context.Context, id uint64) error {
  1365. s.actions = append(s.actions, action{name: "RemoveMember", params: []interface{}{id}})
  1366. return nil
  1367. }
  1368. func TestServeAdminMembersPut(t *testing.T) {
  1369. u := mustNewURL(t, path.Join(adminMembersPrefix, "BEEF"))
  1370. form := url.Values{"PeerURLs": []string{"http://a", "http://b"}}
  1371. body := strings.NewReader(form.Encode())
  1372. req, err := http.NewRequest("PUT", u.String(), body)
  1373. if err != nil {
  1374. t.Fatal(err)
  1375. }
  1376. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  1377. s := &serverRecorder{}
  1378. h := &serverHandler{
  1379. server: s,
  1380. }
  1381. rw := httptest.NewRecorder()
  1382. h.serveAdminMembers(rw, req)
  1383. wcode := http.StatusCreated
  1384. if rw.Code != wcode {
  1385. t.Errorf("code=%d, want %d", rw.Code, wcode)
  1386. }
  1387. g := rw.Body.String()
  1388. if g != "" {
  1389. t.Errorf("got body=%q, want %q", g, "")
  1390. }
  1391. wm := etcdserver.Member{
  1392. ID: 0xBEEF,
  1393. RaftAttributes: etcdserver.RaftAttributes{
  1394. PeerURLs: []string{"http://a", "http://b"},
  1395. },
  1396. }
  1397. wactions := []action{{name: "AddMember", params: []interface{}{wm}}}
  1398. if !reflect.DeepEqual(s.actions, wactions) {
  1399. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  1400. }
  1401. }
  1402. func TestServeAdminMembersDelete(t *testing.T) {
  1403. req := &http.Request{
  1404. Method: "DELETE",
  1405. URL: mustNewURL(t, path.Join(adminMembersPrefix, "BEEF")),
  1406. }
  1407. s := &serverRecorder{}
  1408. h := &serverHandler{
  1409. server: s,
  1410. }
  1411. rw := httptest.NewRecorder()
  1412. h.serveAdminMembers(rw, req)
  1413. wcode := http.StatusNoContent
  1414. if rw.Code != wcode {
  1415. t.Errorf("code=%d, want %d", rw.Code, wcode)
  1416. }
  1417. g := rw.Body.String()
  1418. if g != "" {
  1419. t.Errorf("got body=%q, want %q", g, "")
  1420. }
  1421. wactions := []action{{name: "RemoveMember", params: []interface{}{uint64(0xBEEF)}}}
  1422. if !reflect.DeepEqual(s.actions, wactions) {
  1423. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  1424. }
  1425. }
  1426. type fakeCluster struct {
  1427. members []etcdserver.Member
  1428. }
  1429. func (c *fakeCluster) Add(m etcdserver.Member) { return }
  1430. func (c *fakeCluster) Get() etcdserver.Cluster {
  1431. cl := &etcdserver.Cluster{}
  1432. cl.AddSlice(c.members)
  1433. return *cl
  1434. }
  1435. func (c *fakeCluster) Remove(id uint64) { return }