http_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. package etcdhttp
  2. import (
  3. "errors"
  4. "net/http"
  5. "net/http/httptest"
  6. "net/url"
  7. "path"
  8. "reflect"
  9. "strings"
  10. "sync"
  11. "testing"
  12. etcdErr "github.com/coreos/etcd/error"
  13. "github.com/coreos/etcd/etcdserver/etcdserverpb"
  14. "github.com/coreos/etcd/store"
  15. "github.com/coreos/etcd/third_party/code.google.com/p/go.net/context"
  16. )
  17. func boolp(b bool) *bool { return &b }
  18. func mustNewURL(t *testing.T, s string) *url.URL {
  19. u, err := url.Parse(s)
  20. if err != nil {
  21. t.Fatalf("error creating URL from %q: %v", s, err)
  22. }
  23. return u
  24. }
  25. // mustNewRequest takes a path, appends it to the standard keysPrefix, and constructs
  26. // an *http.Request referencing the resulting URL
  27. func mustNewRequest(t *testing.T, p string) *http.Request {
  28. return &http.Request{
  29. URL: mustNewURL(t, path.Join(keysPrefix, p)),
  30. }
  31. }
  32. // mustNewForm takes a set of Values and constructs a PUT *http.Request,
  33. // with a URL constructed from appending the given path to the standard keysPrefix
  34. func mustNewForm(t *testing.T, p string, vals url.Values) *http.Request {
  35. u := mustNewURL(t, path.Join(keysPrefix, p))
  36. req, err := http.NewRequest("PUT", u.String(), strings.NewReader(vals.Encode()))
  37. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  38. if err != nil {
  39. t.Fatalf("error creating new request: %v", err)
  40. }
  41. return req
  42. }
  43. func TestBadParseRequest(t *testing.T) {
  44. tests := []struct {
  45. in *http.Request
  46. wcode int
  47. }{
  48. {
  49. // parseForm failure
  50. &http.Request{
  51. Body: nil,
  52. Method: "PUT",
  53. },
  54. etcdErr.EcodeInvalidForm,
  55. },
  56. {
  57. // bad key prefix
  58. &http.Request{
  59. URL: mustNewURL(t, "/badprefix/"),
  60. },
  61. etcdErr.EcodeInvalidForm,
  62. },
  63. // bad values for prevIndex, waitIndex, ttl
  64. {
  65. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"garbage"}}),
  66. etcdErr.EcodeIndexNaN,
  67. },
  68. {
  69. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"1.5"}}),
  70. etcdErr.EcodeIndexNaN,
  71. },
  72. {
  73. mustNewForm(t, "foo", url.Values{"prevIndex": []string{"-1"}}),
  74. etcdErr.EcodeIndexNaN,
  75. },
  76. {
  77. mustNewForm(t, "foo", url.Values{"waitIndex": []string{"garbage"}}),
  78. etcdErr.EcodeIndexNaN,
  79. },
  80. {
  81. mustNewForm(t, "foo", url.Values{"waitIndex": []string{"??"}}),
  82. etcdErr.EcodeIndexNaN,
  83. },
  84. {
  85. mustNewForm(t, "foo", url.Values{"ttl": []string{"-1"}}),
  86. etcdErr.EcodeTTLNaN,
  87. },
  88. // bad values for recursive, sorted, wait, prevExists
  89. {
  90. mustNewForm(t, "foo", url.Values{"recursive": []string{"hahaha"}}),
  91. etcdErr.EcodeInvalidField,
  92. },
  93. {
  94. mustNewForm(t, "foo", url.Values{"recursive": []string{"1234"}}),
  95. etcdErr.EcodeInvalidField,
  96. },
  97. {
  98. mustNewForm(t, "foo", url.Values{"recursive": []string{"?"}}),
  99. etcdErr.EcodeInvalidField,
  100. },
  101. {
  102. mustNewForm(t, "foo", url.Values{"sorted": []string{"?"}}),
  103. etcdErr.EcodeInvalidField,
  104. },
  105. {
  106. mustNewForm(t, "foo", url.Values{"sorted": []string{"x"}}),
  107. etcdErr.EcodeInvalidField,
  108. },
  109. {
  110. mustNewForm(t, "foo", url.Values{"wait": []string{"?!"}}),
  111. etcdErr.EcodeInvalidField,
  112. },
  113. {
  114. mustNewForm(t, "foo", url.Values{"wait": []string{"yes"}}),
  115. etcdErr.EcodeInvalidField,
  116. },
  117. {
  118. mustNewForm(t, "foo", url.Values{"prevExists": []string{"yes"}}),
  119. etcdErr.EcodeInvalidField,
  120. },
  121. {
  122. mustNewForm(t, "foo", url.Values{"prevExists": []string{"#2"}}),
  123. etcdErr.EcodeInvalidField,
  124. },
  125. // query values are considered
  126. {
  127. mustNewRequest(t, "foo?prevExists=wrong"),
  128. etcdErr.EcodeInvalidField,
  129. },
  130. {
  131. mustNewRequest(t, "foo?ttl=wrong"),
  132. etcdErr.EcodeTTLNaN,
  133. },
  134. // but body takes precedence if both are specified
  135. {
  136. mustNewForm(
  137. t,
  138. "foo?ttl=12",
  139. url.Values{"ttl": []string{"garbage"}},
  140. ),
  141. etcdErr.EcodeTTLNaN,
  142. },
  143. {
  144. mustNewForm(
  145. t,
  146. "foo?prevExists=false",
  147. url.Values{"prevExists": []string{"yes"}},
  148. ),
  149. etcdErr.EcodeInvalidField,
  150. },
  151. }
  152. for i, tt := range tests {
  153. got, err := parseRequest(tt.in, 1234)
  154. if err == nil {
  155. t.Errorf("#%d: unexpected nil error!", i)
  156. continue
  157. }
  158. ee, ok := err.(*etcdErr.Error)
  159. if !ok {
  160. t.Errorf("#%d: err is not etcd.Error!", i)
  161. continue
  162. }
  163. if ee.ErrorCode != tt.wcode {
  164. t.Errorf("#%d: code=%d, want %v", i, ee.ErrorCode, tt.wcode)
  165. t.Logf("cause: %#v", ee.Cause)
  166. }
  167. if !reflect.DeepEqual(got, etcdserverpb.Request{}) {
  168. t.Errorf("#%d: unexpected non-empty Request: %#v", i, got)
  169. }
  170. }
  171. }
  172. func TestGoodParseRequest(t *testing.T) {
  173. tests := []struct {
  174. in *http.Request
  175. w etcdserverpb.Request
  176. }{
  177. {
  178. // good prefix, all other values default
  179. mustNewRequest(t, "foo"),
  180. etcdserverpb.Request{
  181. Id: 1234,
  182. Path: "/foo",
  183. },
  184. },
  185. {
  186. // value specified
  187. mustNewForm(
  188. t,
  189. "foo",
  190. url.Values{"value": []string{"some_value"}},
  191. ),
  192. etcdserverpb.Request{
  193. Id: 1234,
  194. Method: "PUT",
  195. Val: "some_value",
  196. Path: "/foo",
  197. },
  198. },
  199. {
  200. // prevIndex specified
  201. mustNewForm(
  202. t,
  203. "foo",
  204. url.Values{"prevIndex": []string{"98765"}},
  205. ),
  206. etcdserverpb.Request{
  207. Id: 1234,
  208. Method: "PUT",
  209. PrevIndex: 98765,
  210. Path: "/foo",
  211. },
  212. },
  213. {
  214. // recursive specified
  215. mustNewForm(
  216. t,
  217. "foo",
  218. url.Values{"recursive": []string{"true"}},
  219. ),
  220. etcdserverpb.Request{
  221. Id: 1234,
  222. Method: "PUT",
  223. Recursive: true,
  224. Path: "/foo",
  225. },
  226. },
  227. {
  228. // sorted specified
  229. mustNewForm(
  230. t,
  231. "foo",
  232. url.Values{"sorted": []string{"true"}},
  233. ),
  234. etcdserverpb.Request{
  235. Id: 1234,
  236. Method: "PUT",
  237. Sorted: true,
  238. Path: "/foo",
  239. },
  240. },
  241. {
  242. // wait specified
  243. mustNewForm(
  244. t,
  245. "foo",
  246. url.Values{"wait": []string{"true"}},
  247. ),
  248. etcdserverpb.Request{
  249. Id: 1234,
  250. Method: "PUT",
  251. Wait: true,
  252. Path: "/foo",
  253. },
  254. },
  255. {
  256. // prevExists should be non-null if specified
  257. mustNewForm(
  258. t,
  259. "foo",
  260. url.Values{"prevExists": []string{"true"}},
  261. ),
  262. etcdserverpb.Request{
  263. Id: 1234,
  264. Method: "PUT",
  265. PrevExists: boolp(true),
  266. Path: "/foo",
  267. },
  268. },
  269. {
  270. // prevExists should be non-null if specified
  271. mustNewForm(
  272. t,
  273. "foo",
  274. url.Values{"prevExists": []string{"false"}},
  275. ),
  276. etcdserverpb.Request{
  277. Id: 1234,
  278. Method: "PUT",
  279. PrevExists: boolp(false),
  280. Path: "/foo",
  281. },
  282. },
  283. // mix various fields
  284. {
  285. mustNewForm(
  286. t,
  287. "foo",
  288. url.Values{
  289. "value": []string{"some value"},
  290. "prevExists": []string{"true"},
  291. "prevValue": []string{"previous value"},
  292. },
  293. ),
  294. etcdserverpb.Request{
  295. Id: 1234,
  296. Method: "PUT",
  297. PrevExists: boolp(true),
  298. PrevValue: "previous value",
  299. Val: "some value",
  300. Path: "/foo",
  301. },
  302. },
  303. // query parameters should be used if given
  304. {
  305. mustNewForm(
  306. t,
  307. "foo?prevValue=woof",
  308. url.Values{},
  309. ),
  310. etcdserverpb.Request{
  311. Id: 1234,
  312. Method: "PUT",
  313. PrevValue: "woof",
  314. Path: "/foo",
  315. },
  316. },
  317. // but form values should take precedence over query parameters
  318. {
  319. mustNewForm(
  320. t,
  321. "foo?prevValue=woof",
  322. url.Values{
  323. "prevValue": []string{"miaow"},
  324. },
  325. ),
  326. etcdserverpb.Request{
  327. Id: 1234,
  328. Method: "PUT",
  329. PrevValue: "miaow",
  330. Path: "/foo",
  331. },
  332. },
  333. }
  334. for i, tt := range tests {
  335. got, err := parseRequest(tt.in, 1234)
  336. if err != nil {
  337. t.Errorf("#%d: err = %v, want %v", i, err, nil)
  338. }
  339. if !reflect.DeepEqual(got, tt.w) {
  340. t.Errorf("#%d: request=%#v, want %#v", i, got, tt.w)
  341. }
  342. }
  343. }
  344. // eventingWatcher immediately returns a simple event of the given action on its channel
  345. type eventingWatcher struct {
  346. action string
  347. }
  348. func (w *eventingWatcher) EventChan() chan *store.Event {
  349. ch := make(chan *store.Event)
  350. go func() {
  351. ch <- &store.Event{
  352. Action: w.action,
  353. Node: &store.NodeExtern{},
  354. }
  355. }()
  356. return ch
  357. }
  358. func (w *eventingWatcher) Remove() {}
  359. func TestWriteError(t *testing.T) {
  360. // nil error should not panic
  361. rw := httptest.NewRecorder()
  362. writeError(rw, nil)
  363. h := rw.Header()
  364. if len(h) > 0 {
  365. t.Fatalf("unexpected non-empty headers: %#v", h)
  366. }
  367. b := rw.Body.String()
  368. if len(b) > 0 {
  369. t.Fatalf("unexpected non-empty body: %q", b)
  370. }
  371. tests := []struct {
  372. err error
  373. wcode int
  374. wi string
  375. }{
  376. {
  377. etcdErr.NewError(etcdErr.EcodeKeyNotFound, "/foo/bar", 123),
  378. http.StatusNotFound,
  379. "123",
  380. },
  381. {
  382. etcdErr.NewError(etcdErr.EcodeTestFailed, "/foo/bar", 456),
  383. http.StatusPreconditionFailed,
  384. "456",
  385. },
  386. {
  387. err: errors.New("something went wrong"),
  388. wcode: http.StatusInternalServerError,
  389. },
  390. }
  391. for i, tt := range tests {
  392. rw := httptest.NewRecorder()
  393. writeError(rw, tt.err)
  394. if code := rw.Code; code != tt.wcode {
  395. t.Errorf("#%d: code=%d, want %d", i, code, tt.wcode)
  396. }
  397. if idx := rw.Header().Get("X-Etcd-Index"); idx != tt.wi {
  398. t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, idx, tt.wi)
  399. }
  400. }
  401. }
  402. func TestWriteEvent(t *testing.T) {
  403. // nil event should not panic
  404. rw := httptest.NewRecorder()
  405. writeEvent(rw, nil)
  406. h := rw.Header()
  407. if len(h) > 0 {
  408. t.Fatalf("unexpected non-empty headers: %#v", h)
  409. }
  410. b := rw.Body.String()
  411. if len(b) > 0 {
  412. t.Fatalf("unexpected non-empty body: %q", b)
  413. }
  414. tests := []struct {
  415. ev *store.Event
  416. idx string
  417. code int
  418. err error
  419. }{
  420. // standard case, standard 200 response
  421. {
  422. &store.Event{
  423. Action: store.Get,
  424. Node: &store.NodeExtern{},
  425. PrevNode: &store.NodeExtern{},
  426. },
  427. "0",
  428. http.StatusOK,
  429. nil,
  430. },
  431. // check new nodes return StatusCreated
  432. {
  433. &store.Event{
  434. Action: store.Create,
  435. Node: &store.NodeExtern{},
  436. PrevNode: &store.NodeExtern{},
  437. },
  438. "0",
  439. http.StatusCreated,
  440. nil,
  441. },
  442. }
  443. for i, tt := range tests {
  444. rw := httptest.NewRecorder()
  445. writeEvent(rw, tt.ev)
  446. if gct := rw.Header().Get("Content-Type"); gct != "application/json" {
  447. t.Errorf("case %d: bad Content-Type: got %q, want application/json", i, gct)
  448. }
  449. if gei := rw.Header().Get("X-Etcd-Index"); gei != tt.idx {
  450. t.Errorf("case %d: bad X-Etcd-Index header: got %s, want %s", i, gei, tt.idx)
  451. }
  452. if rw.Code != tt.code {
  453. t.Errorf("case %d: bad response code: got %d, want %v", i, rw.Code, tt.code)
  454. }
  455. }
  456. }
  457. type dummyWatcher struct {
  458. echan chan *store.Event
  459. }
  460. func (w *dummyWatcher) EventChan() chan *store.Event {
  461. return w.echan
  462. }
  463. func (w *dummyWatcher) Remove() {}
  464. type dummyResponseWriter struct {
  465. cnchan chan bool
  466. http.ResponseWriter
  467. }
  468. func (rw *dummyResponseWriter) CloseNotify() <-chan bool {
  469. return rw.cnchan
  470. }
  471. func TestWaitForEventChan(t *testing.T) {
  472. ctx := context.Background()
  473. ec := make(chan *store.Event)
  474. dw := &dummyWatcher{
  475. echan: ec,
  476. }
  477. w := httptest.NewRecorder()
  478. var wg sync.WaitGroup
  479. var ev *store.Event
  480. var err error
  481. wg.Add(1)
  482. go func() {
  483. ev, err = waitForEvent(ctx, w, dw)
  484. wg.Done()
  485. }()
  486. ec <- &store.Event{
  487. Action: store.Get,
  488. Node: &store.NodeExtern{
  489. Key: "/foo/bar",
  490. ModifiedIndex: 12345,
  491. },
  492. }
  493. wg.Wait()
  494. want := &store.Event{
  495. Action: store.Get,
  496. Node: &store.NodeExtern{
  497. Key: "/foo/bar",
  498. ModifiedIndex: 12345,
  499. },
  500. }
  501. if !reflect.DeepEqual(ev, want) {
  502. t.Fatalf("bad event: got %#v, want %#v", ev, want)
  503. }
  504. if err != nil {
  505. t.Fatalf("unexpected error: %v", err)
  506. }
  507. }
  508. func TestWaitForEventCloseNotify(t *testing.T) {
  509. ctx := context.Background()
  510. dw := &dummyWatcher{}
  511. cnchan := make(chan bool)
  512. w := &dummyResponseWriter{
  513. cnchan: cnchan,
  514. }
  515. var wg sync.WaitGroup
  516. var ev *store.Event
  517. var err error
  518. wg.Add(1)
  519. go func() {
  520. ev, err = waitForEvent(ctx, w, dw)
  521. wg.Done()
  522. }()
  523. close(cnchan)
  524. wg.Wait()
  525. if ev != nil {
  526. t.Fatalf("non-nil Event returned with CloseNotifier: %v", ev)
  527. }
  528. if err == nil {
  529. t.Fatalf("nil err returned with CloseNotifier!")
  530. }
  531. }
  532. func TestWaitForEventCancelledContext(t *testing.T) {
  533. cctx, cancel := context.WithCancel(context.Background())
  534. dw := &dummyWatcher{}
  535. w := httptest.NewRecorder()
  536. var wg sync.WaitGroup
  537. var ev *store.Event
  538. var err error
  539. wg.Add(1)
  540. go func() {
  541. ev, err = waitForEvent(cctx, w, dw)
  542. wg.Done()
  543. }()
  544. cancel()
  545. wg.Wait()
  546. if ev != nil {
  547. t.Fatalf("non-nil Event returned with cancelled context: %v", ev)
  548. }
  549. if err == nil {
  550. t.Fatalf("nil err returned with cancelled context!")
  551. }
  552. }
  553. func TestV2MachinesEndpoint(t *testing.T) {
  554. tests := []struct {
  555. method string
  556. wcode int
  557. }{
  558. {"GET", http.StatusOK},
  559. {"HEAD", http.StatusOK},
  560. {"POST", http.StatusMethodNotAllowed},
  561. }
  562. h := Handler{Peers: Peers{}}
  563. s := httptest.NewServer(h)
  564. defer s.Close()
  565. for _, tt := range tests {
  566. req, err := http.NewRequest(tt.method, s.URL+machinesPrefix, nil)
  567. if err != nil {
  568. t.Fatal(err)
  569. }
  570. resp, err := http.DefaultClient.Do(req)
  571. if err != nil {
  572. t.Fatal(err)
  573. }
  574. if resp.StatusCode != tt.wcode {
  575. t.Errorf("StatusCode = %d, expected %d", resp.StatusCode, tt.wcode)
  576. }
  577. }
  578. }
  579. func TestServeMachines(t *testing.T) {
  580. peers := Peers{}
  581. peers.Set("0xBEEF0=localhost:8080&0xBEEF1=localhost:8081&0xBEEF2=localhost:8082")
  582. h := Handler{Peers: peers}
  583. writer := httptest.NewRecorder()
  584. req, err := http.NewRequest("GET", "", nil)
  585. if err != nil {
  586. t.Fatal(err)
  587. }
  588. h.serveMachines(writer, req)
  589. w := "http://localhost:8080, http://localhost:8081, http://localhost:8082"
  590. if g := writer.Body.String(); g != w {
  591. t.Errorf("body = %s, want %s", g, w)
  592. }
  593. if writer.Code != http.StatusOK {
  594. t.Errorf("header = %d, want %d", writer.Code, http.StatusOK)
  595. }
  596. }
  597. func TestPeersEndpoints(t *testing.T) {
  598. tests := []struct {
  599. peers Peers
  600. endpoints []string
  601. }{
  602. // single peer with a single address
  603. {
  604. peers: Peers(map[int64][]string{
  605. 1: []string{"192.0.2.1"},
  606. }),
  607. endpoints: []string{"http://192.0.2.1"},
  608. },
  609. // single peer with a single address with a port
  610. {
  611. peers: Peers(map[int64][]string{
  612. 1: []string{"192.0.2.1:8001"},
  613. }),
  614. endpoints: []string{"http://192.0.2.1:8001"},
  615. },
  616. // several peers explicitly unsorted
  617. {
  618. peers: Peers(map[int64][]string{
  619. 2: []string{"192.0.2.3", "192.0.2.4"},
  620. 3: []string{"192.0.2.5", "192.0.2.6"},
  621. 1: []string{"192.0.2.1", "192.0.2.2"},
  622. }),
  623. endpoints: []string{"http://192.0.2.1", "http://192.0.2.2", "http://192.0.2.3", "http://192.0.2.4", "http://192.0.2.5", "http://192.0.2.6"},
  624. },
  625. // no peers
  626. {
  627. peers: Peers(map[int64][]string{}),
  628. endpoints: []string{},
  629. },
  630. // peer with no endpoints
  631. {
  632. peers: Peers(map[int64][]string{
  633. 3: []string{},
  634. }),
  635. endpoints: []string{},
  636. },
  637. }
  638. for i, tt := range tests {
  639. endpoints := tt.peers.Endpoints()
  640. if !reflect.DeepEqual(tt.endpoints, endpoints) {
  641. t.Errorf("#%d: peers.Endpoints() incorrect: want=%#v got=%#v", i, tt.endpoints, endpoints)
  642. }
  643. }
  644. }