http_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. package etcdhttp
  2. import (
  3. "errors"
  4. "net/http"
  5. "net/http/httptest"
  6. "net/url"
  7. "path"
  8. "reflect"
  9. "sync"
  10. "testing"
  11. etcdErr "github.com/coreos/etcd/error"
  12. "github.com/coreos/etcd/etcdserver/etcdserverpb"
  13. "github.com/coreos/etcd/store"
  14. "github.com/coreos/etcd/third_party/code.google.com/p/go.net/context"
  15. )
  16. func boolp(b bool) *bool { return &b }
  17. func mustNewURL(t *testing.T, s string) *url.URL {
  18. u, err := url.Parse(s)
  19. if err != nil {
  20. t.Fatalf("error creating URL from %q: %v", s, err)
  21. }
  22. return u
  23. }
  24. // mustNewRequest takes a path, appends it to the standard keysPrefix, and constructs
  25. // an *http.Request referencing the resulting URL
  26. func mustNewRequest(t *testing.T, p string) *http.Request {
  27. return &http.Request{
  28. URL: mustNewURL(t, path.Join(keysPrefix, p)),
  29. }
  30. }
  31. func TestParseBool(t *testing.T) {
  32. got, err := parseBool("")
  33. if got != false {
  34. t.Fatalf("got %t, want %t", got, false)
  35. }
  36. if err != nil {
  37. t.Fatalf("err = %v, want %v", err, nil)
  38. }
  39. }
  40. func TestParseUint64(t *testing.T) {
  41. got, err := parseUint64("")
  42. if got != 0 {
  43. t.Fatalf("got %d, want %d", got, 0)
  44. }
  45. if err != nil {
  46. t.Fatalf("err = %v, want %v", err, nil)
  47. }
  48. }
  49. func TestBadParseRequest(t *testing.T) {
  50. tests := []struct {
  51. in *http.Request
  52. wcode int
  53. }{
  54. {
  55. // parseForm failure
  56. &http.Request{
  57. Body: nil,
  58. Method: "PUT",
  59. },
  60. etcdErr.EcodeInvalidForm,
  61. },
  62. {
  63. // bad key prefix
  64. &http.Request{
  65. URL: mustNewURL(t, "/badprefix/"),
  66. },
  67. etcdErr.EcodeInvalidForm,
  68. },
  69. // bad values for prevIndex, waitIndex, ttl
  70. {
  71. mustNewRequest(t, "?prevIndex=foo"),
  72. etcdErr.EcodeIndexNaN,
  73. },
  74. {
  75. mustNewRequest(t, "?prevIndex=1.5"),
  76. etcdErr.EcodeIndexNaN,
  77. },
  78. {
  79. mustNewRequest(t, "?prevIndex=-1"),
  80. etcdErr.EcodeIndexNaN,
  81. },
  82. {
  83. mustNewRequest(t, "?waitIndex=garbage"),
  84. etcdErr.EcodeIndexNaN,
  85. },
  86. {
  87. mustNewRequest(t, "?waitIndex=??"),
  88. etcdErr.EcodeIndexNaN,
  89. },
  90. {
  91. mustNewRequest(t, "?ttl=-1"),
  92. etcdErr.EcodeTTLNaN,
  93. },
  94. // bad values for recursive, sorted, wait
  95. {
  96. mustNewRequest(t, "?recursive=hahaha"),
  97. etcdErr.EcodeInvalidField,
  98. },
  99. {
  100. mustNewRequest(t, "?recursive=1234"),
  101. etcdErr.EcodeInvalidField,
  102. },
  103. {
  104. mustNewRequest(t, "?recursive=?"),
  105. etcdErr.EcodeInvalidField,
  106. },
  107. {
  108. mustNewRequest(t, "?sorted=hahaha"),
  109. etcdErr.EcodeInvalidField,
  110. },
  111. {
  112. mustNewRequest(t, "?sorted=!!"),
  113. etcdErr.EcodeInvalidField,
  114. },
  115. {
  116. mustNewRequest(t, "?wait=notreally"),
  117. etcdErr.EcodeInvalidField,
  118. },
  119. {
  120. mustNewRequest(t, "?wait=what!"),
  121. etcdErr.EcodeInvalidField,
  122. },
  123. }
  124. for i, tt := range tests {
  125. got, err := parseRequest(tt.in, 1234)
  126. if err == nil {
  127. t.Errorf("#%d: unexpected nil error!", i)
  128. continue
  129. }
  130. ee, ok := err.(*etcdErr.Error)
  131. if !ok {
  132. t.Errorf("#%d: err is not etcd.Error!", i)
  133. continue
  134. }
  135. if ee.ErrorCode != tt.wcode {
  136. t.Errorf("#%d: code=%d, want %v", i, ee.ErrorCode, tt.wcode)
  137. }
  138. if !reflect.DeepEqual(got, etcdserverpb.Request{}) {
  139. t.Errorf("#%d: unexpected non-empty Request: %#v", i, got)
  140. }
  141. }
  142. }
  143. func TestGoodParseRequest(t *testing.T) {
  144. tests := []struct {
  145. in *http.Request
  146. w etcdserverpb.Request
  147. }{
  148. {
  149. // good prefix, all other values default
  150. mustNewRequest(t, "foo"),
  151. etcdserverpb.Request{
  152. Id: 1234,
  153. Path: "/foo",
  154. },
  155. },
  156. {
  157. // value specified
  158. mustNewRequest(t, "foo?value=some_value"),
  159. etcdserverpb.Request{
  160. Id: 1234,
  161. Val: "some_value",
  162. Path: "/foo",
  163. },
  164. },
  165. {
  166. // prevIndex specified
  167. mustNewRequest(t, "foo?prevIndex=98765"),
  168. etcdserverpb.Request{
  169. Id: 1234,
  170. PrevIndex: 98765,
  171. Path: "/foo",
  172. },
  173. },
  174. {
  175. // recursive specified
  176. mustNewRequest(t, "foo?recursive=true"),
  177. etcdserverpb.Request{
  178. Id: 1234,
  179. Recursive: true,
  180. Path: "/foo",
  181. },
  182. },
  183. {
  184. // sorted specified
  185. mustNewRequest(t, "foo?sorted=true"),
  186. etcdserverpb.Request{
  187. Id: 1234,
  188. Sorted: true,
  189. Path: "/foo",
  190. },
  191. },
  192. {
  193. // wait specified
  194. mustNewRequest(t, "foo?wait=true"),
  195. etcdserverpb.Request{
  196. Id: 1234,
  197. Wait: true,
  198. Path: "/foo",
  199. },
  200. },
  201. {
  202. // prevExists should be non-null if specified
  203. mustNewRequest(t, "foo?prevExists=true"),
  204. etcdserverpb.Request{
  205. Id: 1234,
  206. PrevExists: boolp(true),
  207. Path: "/foo",
  208. },
  209. },
  210. {
  211. // prevExists should be non-null if specified
  212. mustNewRequest(t, "foo?prevExists=false"),
  213. etcdserverpb.Request{
  214. Id: 1234,
  215. PrevExists: boolp(false),
  216. Path: "/foo",
  217. },
  218. },
  219. }
  220. for i, tt := range tests {
  221. got, err := parseRequest(tt.in, 1234)
  222. if err != nil {
  223. t.Errorf("#%d: err = %v, want %v", i, err, nil)
  224. }
  225. if !reflect.DeepEqual(got, tt.w) {
  226. t.Errorf("#%d: bad request: got %#v, want %#v", i, got, tt.w)
  227. }
  228. }
  229. }
  230. // eventingWatcher immediately returns a simple event of the given action on its channel
  231. type eventingWatcher struct {
  232. action string
  233. }
  234. func (w *eventingWatcher) EventChan() chan *store.Event {
  235. ch := make(chan *store.Event)
  236. go func() {
  237. ch <- &store.Event{
  238. Action: w.action,
  239. Node: &store.NodeExtern{},
  240. }
  241. }()
  242. return ch
  243. }
  244. func (w *eventingWatcher) Remove() {}
  245. func TestWriteError(t *testing.T) {
  246. // nil error should not panic
  247. rw := httptest.NewRecorder()
  248. writeError(rw, nil)
  249. h := rw.Header()
  250. if len(h) > 0 {
  251. t.Fatalf("unexpected non-empty headers: %#v", h)
  252. }
  253. b := rw.Body.String()
  254. if len(b) > 0 {
  255. t.Fatalf("unexpected non-empty body: %q", b)
  256. }
  257. tests := []struct {
  258. err error
  259. wcode int
  260. wi string
  261. }{
  262. {
  263. etcdErr.NewError(etcdErr.EcodeKeyNotFound, "/foo/bar", 123),
  264. http.StatusNotFound,
  265. "123",
  266. },
  267. {
  268. etcdErr.NewError(etcdErr.EcodeTestFailed, "/foo/bar", 456),
  269. http.StatusPreconditionFailed,
  270. "456",
  271. },
  272. {
  273. err: errors.New("something went wrong"),
  274. wcode: http.StatusInternalServerError,
  275. },
  276. }
  277. for i, tt := range tests {
  278. rw := httptest.NewRecorder()
  279. writeError(rw, tt.err)
  280. if code := rw.Code; code != tt.wcode {
  281. t.Errorf("#%d: code=%d, want %d", i, code, tt.wcode)
  282. }
  283. if idx := rw.Header().Get("X-Etcd-Index"); idx != tt.wi {
  284. t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, idx, tt.wi)
  285. }
  286. }
  287. }
  288. func TestWriteEvent(t *testing.T) {
  289. // nil event should not panic
  290. rw := httptest.NewRecorder()
  291. writeEvent(rw, nil)
  292. h := rw.Header()
  293. if len(h) > 0 {
  294. t.Fatalf("unexpected non-empty headers: %#v", h)
  295. }
  296. b := rw.Body.String()
  297. if len(b) > 0 {
  298. t.Fatalf("unexpected non-empty body: %q", b)
  299. }
  300. tests := []struct {
  301. ev *store.Event
  302. idx string
  303. code int
  304. err error
  305. }{
  306. // standard case, standard 200 response
  307. {
  308. &store.Event{
  309. Action: store.Get,
  310. Node: &store.NodeExtern{},
  311. PrevNode: &store.NodeExtern{},
  312. },
  313. "0",
  314. http.StatusOK,
  315. nil,
  316. },
  317. // check new nodes return StatusCreated
  318. {
  319. &store.Event{
  320. Action: store.Create,
  321. Node: &store.NodeExtern{},
  322. PrevNode: &store.NodeExtern{},
  323. },
  324. "0",
  325. http.StatusCreated,
  326. nil,
  327. },
  328. }
  329. for i, tt := range tests {
  330. rw := httptest.NewRecorder()
  331. writeEvent(rw, tt.ev)
  332. if gct := rw.Header().Get("Content-Type"); gct != "application/json" {
  333. t.Errorf("case %d: bad Content-Type: got %q, want application/json", i, gct)
  334. }
  335. if gei := rw.Header().Get("X-Etcd-Index"); gei != tt.idx {
  336. t.Errorf("case %d: bad X-Etcd-Index header: got %s, want %s", i, gei, tt.idx)
  337. }
  338. if rw.Code != tt.code {
  339. t.Errorf("case %d: bad response code: got %d, want %v", i, rw.Code, tt.code)
  340. }
  341. }
  342. }
  343. type dummyWatcher struct {
  344. echan chan *store.Event
  345. }
  346. func (w *dummyWatcher) EventChan() chan *store.Event {
  347. return w.echan
  348. }
  349. func (w *dummyWatcher) Remove() {}
  350. type dummyResponseWriter struct {
  351. cnchan chan bool
  352. http.ResponseWriter
  353. }
  354. func (rw *dummyResponseWriter) CloseNotify() <-chan bool {
  355. return rw.cnchan
  356. }
  357. func TestWaitForEventChan(t *testing.T) {
  358. ctx := context.Background()
  359. ec := make(chan *store.Event)
  360. dw := &dummyWatcher{
  361. echan: ec,
  362. }
  363. w := httptest.NewRecorder()
  364. var wg sync.WaitGroup
  365. var ev *store.Event
  366. var err error
  367. wg.Add(1)
  368. go func() {
  369. ev, err = waitForEvent(ctx, w, dw)
  370. wg.Done()
  371. }()
  372. ec <- &store.Event{
  373. Action: store.Get,
  374. Node: &store.NodeExtern{
  375. Key: "/foo/bar",
  376. ModifiedIndex: 12345,
  377. },
  378. }
  379. wg.Wait()
  380. want := &store.Event{
  381. Action: store.Get,
  382. Node: &store.NodeExtern{
  383. Key: "/foo/bar",
  384. ModifiedIndex: 12345,
  385. },
  386. }
  387. if !reflect.DeepEqual(ev, want) {
  388. t.Fatalf("bad event: got %#v, want %#v", ev, want)
  389. }
  390. if err != nil {
  391. t.Fatalf("unexpected error: %v", err)
  392. }
  393. }
  394. func TestWaitForEventCloseNotify(t *testing.T) {
  395. ctx := context.Background()
  396. dw := &dummyWatcher{}
  397. cnchan := make(chan bool)
  398. w := &dummyResponseWriter{
  399. cnchan: cnchan,
  400. }
  401. var wg sync.WaitGroup
  402. var ev *store.Event
  403. var err error
  404. wg.Add(1)
  405. go func() {
  406. ev, err = waitForEvent(ctx, w, dw)
  407. wg.Done()
  408. }()
  409. close(cnchan)
  410. wg.Wait()
  411. if ev != nil {
  412. t.Fatalf("non-nil Event returned with CloseNotifier: %v", ev)
  413. }
  414. if err == nil {
  415. t.Fatalf("nil err returned with CloseNotifier!")
  416. }
  417. }
  418. func TestWaitForEventCancelledContext(t *testing.T) {
  419. cctx, cancel := context.WithCancel(context.Background())
  420. dw := &dummyWatcher{}
  421. w := httptest.NewRecorder()
  422. var wg sync.WaitGroup
  423. var ev *store.Event
  424. var err error
  425. wg.Add(1)
  426. go func() {
  427. ev, err = waitForEvent(cctx, w, dw)
  428. wg.Done()
  429. }()
  430. cancel()
  431. wg.Wait()
  432. if ev != nil {
  433. t.Fatalf("non-nil Event returned with cancelled context: %v", ev)
  434. }
  435. if err == nil {
  436. t.Fatalf("nil err returned with cancelled context!")
  437. }
  438. }
  439. func TestV2MachinesEndpoint(t *testing.T) {
  440. tests := []struct {
  441. method string
  442. wcode int
  443. }{
  444. {"GET", http.StatusOK},
  445. {"HEAD", http.StatusOK},
  446. {"POST", http.StatusMethodNotAllowed},
  447. }
  448. h := Handler{Peers: Peers{}}
  449. s := httptest.NewServer(h)
  450. defer s.Close()
  451. for _, tt := range tests {
  452. req, err := http.NewRequest(tt.method, s.URL+machinesPrefix, nil)
  453. if err != nil {
  454. t.Fatal(err)
  455. }
  456. resp, err := http.DefaultClient.Do(req)
  457. if err != nil {
  458. t.Fatal(err)
  459. }
  460. if resp.StatusCode != tt.wcode {
  461. t.Errorf("StatusCode = %d, expected %d", resp.StatusCode, tt.wcode)
  462. }
  463. }
  464. }
  465. func TestServeMachines(t *testing.T) {
  466. peers := Peers{}
  467. peers.Set("0xBEEF0=localhost:8080&0xBEEF1=localhost:8081&0xBEEF2=localhost:8082")
  468. h := Handler{Peers: peers}
  469. writer := httptest.NewRecorder()
  470. req, err := http.NewRequest("GET", "", nil)
  471. if err != nil {
  472. t.Fatal(err)
  473. }
  474. h.serveMachines(writer, req)
  475. w := "http://localhost:8080, http://localhost:8081, http://localhost:8082"
  476. if g := writer.Body.String(); g != w {
  477. t.Errorf("body = %s, want %s", g, w)
  478. }
  479. if writer.Code != http.StatusOK {
  480. t.Errorf("header = %d, want %d", writer.Code, http.StatusOK)
  481. }
  482. }