http_test.go 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656
  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, rw.Code, 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, rw.Code, 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. clusterID string
  816. wcode int
  817. }{
  818. {
  819. // bad method
  820. "GET",
  821. bytes.NewReader(
  822. mustMarshalMsg(
  823. t,
  824. raftpb.Message{},
  825. ),
  826. ),
  827. nil,
  828. "0",
  829. http.StatusMethodNotAllowed,
  830. },
  831. {
  832. // bad method
  833. "PUT",
  834. bytes.NewReader(
  835. mustMarshalMsg(
  836. t,
  837. raftpb.Message{},
  838. ),
  839. ),
  840. nil,
  841. "0",
  842. http.StatusMethodNotAllowed,
  843. },
  844. {
  845. // bad method
  846. "DELETE",
  847. bytes.NewReader(
  848. mustMarshalMsg(
  849. t,
  850. raftpb.Message{},
  851. ),
  852. ),
  853. nil,
  854. "0",
  855. http.StatusMethodNotAllowed,
  856. },
  857. {
  858. // bad request body
  859. "POST",
  860. &errReader{},
  861. nil,
  862. "0",
  863. http.StatusBadRequest,
  864. },
  865. {
  866. // bad request protobuf
  867. "POST",
  868. strings.NewReader("malformed garbage"),
  869. nil,
  870. "0",
  871. http.StatusBadRequest,
  872. },
  873. {
  874. // good request, etcdserver.Server error
  875. "POST",
  876. bytes.NewReader(
  877. mustMarshalMsg(
  878. t,
  879. raftpb.Message{},
  880. ),
  881. ),
  882. errors.New("some error"),
  883. "0",
  884. http.StatusInternalServerError,
  885. },
  886. {
  887. // good request
  888. "POST",
  889. bytes.NewReader(
  890. mustMarshalMsg(
  891. t,
  892. raftpb.Message{},
  893. ),
  894. ),
  895. nil,
  896. "1",
  897. http.StatusPreconditionFailed,
  898. },
  899. {
  900. // good request
  901. "POST",
  902. bytes.NewReader(
  903. mustMarshalMsg(
  904. t,
  905. raftpb.Message{},
  906. ),
  907. ),
  908. nil,
  909. "0",
  910. http.StatusNoContent,
  911. },
  912. }
  913. for i, tt := range testCases {
  914. req, err := http.NewRequest(tt.method, "foo", tt.body)
  915. if err != nil {
  916. t.Fatalf("#%d: could not create request: %#v", i, err)
  917. }
  918. req.Header.Set("X-Etcd-Cluster-ID", tt.clusterID)
  919. h := &serverHandler{
  920. timeout: time.Hour,
  921. server: &errServer{tt.serverErr},
  922. clusterStore: &fakeCluster{},
  923. }
  924. rw := httptest.NewRecorder()
  925. h.serveRaft(rw, req)
  926. if rw.Code != tt.wcode {
  927. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  928. }
  929. }
  930. }
  931. // resServer implements the etcd.Server interface for testing.
  932. // It returns the given responsefrom any Do calls, and nil error
  933. type resServer struct {
  934. res etcdserver.Response
  935. }
  936. func (rs *resServer) Do(_ context.Context, _ etcdserverpb.Request) (etcdserver.Response, error) {
  937. return rs.res, nil
  938. }
  939. func (rs *resServer) Process(_ context.Context, _ raftpb.Message) error { return nil }
  940. func (rs *resServer) Start() {}
  941. func (rs *resServer) Stop() {}
  942. func (rs *resServer) AddMember(_ context.Context, _ etcdserver.Member) error { return nil }
  943. func (rs *resServer) RemoveMember(_ context.Context, _ uint64) error { return nil }
  944. func mustMarshalEvent(t *testing.T, ev *store.Event) string {
  945. b := new(bytes.Buffer)
  946. if err := json.NewEncoder(b).Encode(ev); err != nil {
  947. t.Fatalf("error marshalling event %#v: %v", ev, err)
  948. }
  949. return b.String()
  950. }
  951. func TestBadServeKeys(t *testing.T) {
  952. testBadCases := []struct {
  953. req *http.Request
  954. server etcdserver.Server
  955. wcode int
  956. }{
  957. {
  958. // bad method
  959. &http.Request{
  960. Method: "CONNECT",
  961. },
  962. &resServer{},
  963. http.StatusMethodNotAllowed,
  964. },
  965. {
  966. // bad method
  967. &http.Request{
  968. Method: "TRACE",
  969. },
  970. &resServer{},
  971. http.StatusMethodNotAllowed,
  972. },
  973. {
  974. // parseRequest error
  975. &http.Request{
  976. Body: nil,
  977. Method: "PUT",
  978. },
  979. &resServer{},
  980. http.StatusBadRequest,
  981. },
  982. {
  983. // etcdserver.Server error
  984. mustNewRequest(t, "foo"),
  985. &errServer{
  986. errors.New("blah"),
  987. },
  988. http.StatusInternalServerError,
  989. },
  990. {
  991. // non-event/watcher response from etcdserver.Server
  992. mustNewRequest(t, "foo"),
  993. &resServer{
  994. etcdserver.Response{},
  995. },
  996. http.StatusInternalServerError,
  997. },
  998. }
  999. for i, tt := range testBadCases {
  1000. h := &serverHandler{
  1001. timeout: 0, // context times out immediately
  1002. server: tt.server,
  1003. }
  1004. rw := httptest.NewRecorder()
  1005. h.serveKeys(rw, tt.req)
  1006. if rw.Code != tt.wcode {
  1007. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  1008. }
  1009. }
  1010. }
  1011. func TestServeKeysEvent(t *testing.T) {
  1012. req := mustNewRequest(t, "foo")
  1013. server := &resServer{
  1014. etcdserver.Response{
  1015. Event: &store.Event{
  1016. Action: store.Get,
  1017. Node: &store.NodeExtern{},
  1018. },
  1019. },
  1020. }
  1021. h := &serverHandler{
  1022. timeout: time.Hour,
  1023. server: server,
  1024. timer: &dummyRaftTimer{},
  1025. }
  1026. rw := httptest.NewRecorder()
  1027. h.serveKeys(rw, req)
  1028. wcode := http.StatusOK
  1029. wbody := mustMarshalEvent(
  1030. t,
  1031. &store.Event{
  1032. Action: store.Get,
  1033. Node: &store.NodeExtern{},
  1034. },
  1035. )
  1036. if rw.Code != wcode {
  1037. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1038. }
  1039. g := rw.Body.String()
  1040. if g != wbody {
  1041. t.Errorf("got body=%#v, want %#v", g, wbody)
  1042. }
  1043. }
  1044. func TestServeKeysWatch(t *testing.T) {
  1045. req := mustNewRequest(t, "/foo/bar")
  1046. ec := make(chan *store.Event)
  1047. dw := &dummyWatcher{
  1048. echan: ec,
  1049. }
  1050. server := &resServer{
  1051. etcdserver.Response{
  1052. Watcher: dw,
  1053. },
  1054. }
  1055. h := &serverHandler{
  1056. timeout: time.Hour,
  1057. server: server,
  1058. timer: &dummyRaftTimer{},
  1059. }
  1060. go func() {
  1061. ec <- &store.Event{
  1062. Action: store.Get,
  1063. Node: &store.NodeExtern{},
  1064. }
  1065. }()
  1066. rw := httptest.NewRecorder()
  1067. h.serveKeys(rw, req)
  1068. wcode := http.StatusOK
  1069. wbody := mustMarshalEvent(
  1070. t,
  1071. &store.Event{
  1072. Action: store.Get,
  1073. Node: &store.NodeExtern{},
  1074. },
  1075. )
  1076. if rw.Code != wcode {
  1077. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1078. }
  1079. g := rw.Body.String()
  1080. if g != wbody {
  1081. t.Errorf("got body=%#v, want %#v", g, wbody)
  1082. }
  1083. }
  1084. type recordingCloseNotifier struct {
  1085. *httptest.ResponseRecorder
  1086. cn chan bool
  1087. }
  1088. func (rcn *recordingCloseNotifier) CloseNotify() <-chan bool {
  1089. return rcn.cn
  1090. }
  1091. func TestHandleWatch(t *testing.T) {
  1092. defaultRwRr := func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1093. r := httptest.NewRecorder()
  1094. return r, r
  1095. }
  1096. noopEv := func(chan *store.Event) {}
  1097. tests := []struct {
  1098. getCtx func() context.Context
  1099. getRwRr func() (http.ResponseWriter, *httptest.ResponseRecorder)
  1100. doToChan func(chan *store.Event)
  1101. wbody string
  1102. }{
  1103. {
  1104. // Normal case: one event
  1105. context.Background,
  1106. defaultRwRr,
  1107. func(ch chan *store.Event) {
  1108. ch <- &store.Event{
  1109. Action: store.Get,
  1110. Node: &store.NodeExtern{},
  1111. }
  1112. },
  1113. mustMarshalEvent(
  1114. t,
  1115. &store.Event{
  1116. Action: store.Get,
  1117. Node: &store.NodeExtern{},
  1118. },
  1119. ),
  1120. },
  1121. {
  1122. // Channel is closed, no event
  1123. context.Background,
  1124. defaultRwRr,
  1125. func(ch chan *store.Event) {
  1126. close(ch)
  1127. },
  1128. "",
  1129. },
  1130. {
  1131. // Simulate a timed-out context
  1132. func() context.Context {
  1133. ctx, cancel := context.WithCancel(context.Background())
  1134. cancel()
  1135. return ctx
  1136. },
  1137. defaultRwRr,
  1138. noopEv,
  1139. "",
  1140. },
  1141. {
  1142. // Close-notifying request
  1143. context.Background,
  1144. func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1145. rw := &recordingCloseNotifier{
  1146. ResponseRecorder: httptest.NewRecorder(),
  1147. cn: make(chan bool, 1),
  1148. }
  1149. rw.cn <- true
  1150. return rw, rw.ResponseRecorder
  1151. },
  1152. noopEv,
  1153. "",
  1154. },
  1155. }
  1156. for i, tt := range tests {
  1157. rw, rr := tt.getRwRr()
  1158. wa := &dummyWatcher{
  1159. echan: make(chan *store.Event, 1),
  1160. sidx: 10,
  1161. }
  1162. tt.doToChan(wa.echan)
  1163. handleKeyWatch(tt.getCtx(), rw, wa, false, dummyRaftTimer{})
  1164. wcode := http.StatusOK
  1165. wct := "application/json"
  1166. wei := "10"
  1167. wri := "100"
  1168. wrt := "5"
  1169. if rr.Code != wcode {
  1170. t.Errorf("#%d: got code=%d, want %d", i, rr.Code, wcode)
  1171. }
  1172. h := rr.Header()
  1173. if ct := h.Get("Content-Type"); ct != wct {
  1174. t.Errorf("#%d: Content-Type=%q, want %q", i, ct, wct)
  1175. }
  1176. if ei := h.Get("X-Etcd-Index"); ei != wei {
  1177. t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, ei, wei)
  1178. }
  1179. if ri := h.Get("X-Raft-Index"); ri != wri {
  1180. t.Errorf("#%d: X-Raft-Index=%q, want %q", i, ri, wri)
  1181. }
  1182. if rt := h.Get("X-Raft-Term"); rt != wrt {
  1183. t.Errorf("#%d: X-Raft-Term=%q, want %q", i, rt, wrt)
  1184. }
  1185. g := rr.Body.String()
  1186. if g != tt.wbody {
  1187. t.Errorf("#%d: got body=%#v, want %#v", i, g, tt.wbody)
  1188. }
  1189. }
  1190. }
  1191. // flushingRecorder provides a channel to allow users to block until the Recorder is Flushed()
  1192. type flushingRecorder struct {
  1193. *httptest.ResponseRecorder
  1194. ch chan struct{}
  1195. }
  1196. func (fr *flushingRecorder) Flush() {
  1197. fr.ResponseRecorder.Flush()
  1198. fr.ch <- struct{}{}
  1199. }
  1200. func TestHandleWatchStreaming(t *testing.T) {
  1201. rw := &flushingRecorder{
  1202. httptest.NewRecorder(),
  1203. make(chan struct{}, 1),
  1204. }
  1205. wa := &dummyWatcher{
  1206. echan: make(chan *store.Event),
  1207. }
  1208. // Launch the streaming handler in the background with a cancellable context
  1209. ctx, cancel := context.WithCancel(context.Background())
  1210. done := make(chan struct{})
  1211. go func() {
  1212. handleKeyWatch(ctx, rw, wa, true, dummyRaftTimer{})
  1213. close(done)
  1214. }()
  1215. // Expect one Flush for the headers etc.
  1216. select {
  1217. case <-rw.ch:
  1218. case <-time.After(time.Second):
  1219. t.Fatalf("timed out waiting for flush")
  1220. }
  1221. // Expect headers but no body
  1222. wcode := http.StatusOK
  1223. wct := "application/json"
  1224. wbody := ""
  1225. if rw.Code != wcode {
  1226. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1227. }
  1228. h := rw.Header()
  1229. if ct := h.Get("Content-Type"); ct != wct {
  1230. t.Errorf("Content-Type=%q, want %q", ct, wct)
  1231. }
  1232. g := rw.Body.String()
  1233. if g != wbody {
  1234. t.Errorf("got body=%#v, want %#v", g, wbody)
  1235. }
  1236. // Now send the first event
  1237. select {
  1238. case wa.echan <- &store.Event{
  1239. Action: store.Get,
  1240. Node: &store.NodeExtern{},
  1241. }:
  1242. case <-time.After(time.Second):
  1243. t.Fatal("timed out waiting for send")
  1244. }
  1245. // Wait for it to be flushed...
  1246. select {
  1247. case <-rw.ch:
  1248. case <-time.After(time.Second):
  1249. t.Fatalf("timed out waiting for flush")
  1250. }
  1251. // And check the body is as expected
  1252. wbody = mustMarshalEvent(
  1253. t,
  1254. &store.Event{
  1255. Action: store.Get,
  1256. Node: &store.NodeExtern{},
  1257. },
  1258. )
  1259. g = rw.Body.String()
  1260. if g != wbody {
  1261. t.Errorf("got body=%#v, want %#v", g, wbody)
  1262. }
  1263. // Rinse and repeat
  1264. select {
  1265. case wa.echan <- &store.Event{
  1266. Action: store.Get,
  1267. Node: &store.NodeExtern{},
  1268. }:
  1269. case <-time.After(time.Second):
  1270. t.Fatal("timed out waiting for send")
  1271. }
  1272. select {
  1273. case <-rw.ch:
  1274. case <-time.After(time.Second):
  1275. t.Fatalf("timed out waiting for flush")
  1276. }
  1277. // This time, we expect to see both events
  1278. wbody = wbody + wbody
  1279. g = rw.Body.String()
  1280. if g != wbody {
  1281. t.Errorf("got body=%#v, want %#v", g, wbody)
  1282. }
  1283. // Finally, time out the connection and ensure the serving goroutine returns
  1284. cancel()
  1285. select {
  1286. case <-done:
  1287. case <-time.After(time.Second):
  1288. t.Fatalf("timed out waiting for done")
  1289. }
  1290. }
  1291. func TestServeAdminMembersFail(t *testing.T) {
  1292. tests := []struct {
  1293. req *http.Request
  1294. server etcdserver.Server
  1295. wcode int
  1296. }{
  1297. {
  1298. // bad method
  1299. &http.Request{
  1300. Method: "CONNECT",
  1301. },
  1302. &resServer{},
  1303. http.StatusMethodNotAllowed,
  1304. },
  1305. {
  1306. // bad method
  1307. &http.Request{
  1308. Method: "TRACE",
  1309. },
  1310. &resServer{},
  1311. http.StatusMethodNotAllowed,
  1312. },
  1313. {
  1314. // parse id error
  1315. &http.Request{
  1316. URL: mustNewURL(t, path.Join(adminMembersPrefix, "badID")),
  1317. Method: "PUT",
  1318. },
  1319. &resServer{},
  1320. http.StatusBadRequest,
  1321. },
  1322. {
  1323. // parse body error
  1324. &http.Request{
  1325. URL: mustNewURL(t, path.Join(adminMembersPrefix, "1")),
  1326. Method: "PUT",
  1327. },
  1328. &resServer{},
  1329. http.StatusBadRequest,
  1330. },
  1331. {
  1332. // etcdserver.AddMember error
  1333. &http.Request{
  1334. URL: mustNewURL(t, path.Join(adminMembersPrefix, "1")),
  1335. Method: "PUT",
  1336. Body: ioutil.NopCloser(strings.NewReader("")),
  1337. },
  1338. &errServer{
  1339. errors.New("blah"),
  1340. },
  1341. http.StatusInternalServerError,
  1342. },
  1343. {
  1344. // etcdserver.RemoveMember error
  1345. &http.Request{
  1346. URL: mustNewURL(t, path.Join(adminMembersPrefix, "1")),
  1347. Method: "DELETE",
  1348. },
  1349. &errServer{
  1350. errors.New("blah"),
  1351. },
  1352. http.StatusInternalServerError,
  1353. },
  1354. }
  1355. for i, tt := range tests {
  1356. h := &serverHandler{
  1357. server: tt.server,
  1358. }
  1359. rw := httptest.NewRecorder()
  1360. h.serveAdminMembers(rw, tt.req)
  1361. if rw.Code != tt.wcode {
  1362. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  1363. }
  1364. }
  1365. }
  1366. type action struct {
  1367. name string
  1368. params []interface{}
  1369. }
  1370. type serverRecorder struct {
  1371. actions []action
  1372. }
  1373. func (s *serverRecorder) Do(_ context.Context, r etcdserverpb.Request) (etcdserver.Response, error) {
  1374. s.actions = append(s.actions, action{name: "Do", params: []interface{}{r}})
  1375. return etcdserver.Response{}, nil
  1376. }
  1377. func (s *serverRecorder) Process(_ context.Context, m raftpb.Message) error {
  1378. s.actions = append(s.actions, action{name: "Process", params: []interface{}{m}})
  1379. return nil
  1380. }
  1381. func (s *serverRecorder) Start() {}
  1382. func (s *serverRecorder) Stop() {}
  1383. func (s *serverRecorder) AddMember(_ context.Context, m etcdserver.Member) error {
  1384. s.actions = append(s.actions, action{name: "AddMember", params: []interface{}{m}})
  1385. return nil
  1386. }
  1387. func (s *serverRecorder) RemoveMember(_ context.Context, id uint64) error {
  1388. s.actions = append(s.actions, action{name: "RemoveMember", params: []interface{}{id}})
  1389. return nil
  1390. }
  1391. func TestServeAdminMembersPut(t *testing.T) {
  1392. u := mustNewURL(t, path.Join(adminMembersPrefix, "BEEF"))
  1393. form := url.Values{"PeerURLs": []string{"http://a", "http://b"}}
  1394. body := strings.NewReader(form.Encode())
  1395. req, err := http.NewRequest("PUT", u.String(), body)
  1396. if err != nil {
  1397. t.Fatal(err)
  1398. }
  1399. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  1400. s := &serverRecorder{}
  1401. h := &serverHandler{
  1402. server: s,
  1403. }
  1404. rw := httptest.NewRecorder()
  1405. h.serveAdminMembers(rw, req)
  1406. wcode := http.StatusCreated
  1407. if rw.Code != wcode {
  1408. t.Errorf("code=%d, want %d", rw.Code, wcode)
  1409. }
  1410. g := rw.Body.String()
  1411. if g != "" {
  1412. t.Errorf("got body=%q, want %q", g, "")
  1413. }
  1414. wm := etcdserver.Member{
  1415. ID: 0xBEEF,
  1416. RaftAttributes: etcdserver.RaftAttributes{
  1417. PeerURLs: []string{"http://a", "http://b"},
  1418. },
  1419. }
  1420. wactions := []action{{name: "AddMember", params: []interface{}{wm}}}
  1421. if !reflect.DeepEqual(s.actions, wactions) {
  1422. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  1423. }
  1424. }
  1425. func TestServeAdminMembersDelete(t *testing.T) {
  1426. req := &http.Request{
  1427. Method: "DELETE",
  1428. URL: mustNewURL(t, path.Join(adminMembersPrefix, "BEEF")),
  1429. }
  1430. s := &serverRecorder{}
  1431. h := &serverHandler{
  1432. server: s,
  1433. }
  1434. rw := httptest.NewRecorder()
  1435. h.serveAdminMembers(rw, req)
  1436. wcode := http.StatusNoContent
  1437. if rw.Code != wcode {
  1438. t.Errorf("code=%d, want %d", rw.Code, wcode)
  1439. }
  1440. g := rw.Body.String()
  1441. if g != "" {
  1442. t.Errorf("got body=%q, want %q", g, "")
  1443. }
  1444. wactions := []action{{name: "RemoveMember", params: []interface{}{uint64(0xBEEF)}}}
  1445. if !reflect.DeepEqual(s.actions, wactions) {
  1446. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  1447. }
  1448. }
  1449. func TestTrimEventPrefix(t *testing.T) {
  1450. pre := "/abc"
  1451. tests := []struct {
  1452. ev *store.Event
  1453. wev *store.Event
  1454. }{
  1455. {
  1456. nil,
  1457. nil,
  1458. },
  1459. {
  1460. &store.Event{},
  1461. &store.Event{},
  1462. },
  1463. {
  1464. &store.Event{Node: &store.NodeExtern{Key: "/abc/def"}},
  1465. &store.Event{Node: &store.NodeExtern{Key: "/def"}},
  1466. },
  1467. {
  1468. &store.Event{PrevNode: &store.NodeExtern{Key: "/abc/ghi"}},
  1469. &store.Event{PrevNode: &store.NodeExtern{Key: "/ghi"}},
  1470. },
  1471. {
  1472. &store.Event{
  1473. Node: &store.NodeExtern{Key: "/abc/def"},
  1474. PrevNode: &store.NodeExtern{Key: "/abc/ghi"},
  1475. },
  1476. &store.Event{
  1477. Node: &store.NodeExtern{Key: "/def"},
  1478. PrevNode: &store.NodeExtern{Key: "/ghi"},
  1479. },
  1480. },
  1481. }
  1482. for i, tt := range tests {
  1483. ev := trimEventPrefix(tt.ev, pre)
  1484. if !reflect.DeepEqual(ev, tt.wev) {
  1485. t.Errorf("#%d: event = %+v, want %+v", i, ev, tt.wev)
  1486. }
  1487. }
  1488. }
  1489. func TestTrimNodeExternPrefix(t *testing.T) {
  1490. pre := "/abc"
  1491. tests := []struct {
  1492. n *store.NodeExtern
  1493. wn *store.NodeExtern
  1494. }{
  1495. {
  1496. nil,
  1497. nil,
  1498. },
  1499. {
  1500. &store.NodeExtern{Key: "/abc/def"},
  1501. &store.NodeExtern{Key: "/def"},
  1502. },
  1503. {
  1504. &store.NodeExtern{
  1505. Key: "/abc/def",
  1506. Nodes: []*store.NodeExtern{
  1507. {Key: "/abc/def/1"},
  1508. {Key: "/abc/def/2"},
  1509. },
  1510. },
  1511. &store.NodeExtern{
  1512. Key: "/def",
  1513. Nodes: []*store.NodeExtern{
  1514. {Key: "/def/1"},
  1515. {Key: "/def/2"},
  1516. },
  1517. },
  1518. },
  1519. }
  1520. for i, tt := range tests {
  1521. n := trimNodeExternPrefix(tt.n, pre)
  1522. if !reflect.DeepEqual(n, tt.wn) {
  1523. t.Errorf("#%d: node = %+v, want %+v", i, n, tt.wn)
  1524. }
  1525. }
  1526. }
  1527. type fakeCluster struct {
  1528. members []etcdserver.Member
  1529. }
  1530. func (c *fakeCluster) Add(m etcdserver.Member) { return }
  1531. func (c *fakeCluster) Get() etcdserver.Cluster {
  1532. cl := etcdserver.NewCluster()
  1533. cl.AddSlice(c.members)
  1534. return *cl
  1535. }
  1536. func (c *fakeCluster) Remove(id uint64) { return }