http_test.go 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753
  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 := parseKeyRequest(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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  301. },
  302. },
  303. {
  304. // empty TTL specified
  305. mustNewRequest(t, "foo?ttl="),
  306. etcdserverpb.Request{
  307. ID: 1234,
  308. Method: "GET",
  309. Path: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/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: path.Join(etcdserver.StoreKeysPrefix, "/foo"),
  429. },
  430. },
  431. }
  432. for i, tt := range tests {
  433. got, err := parseKeyRequest(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. writeKeyEvent(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. writeKeyEvent(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{Cluster: &etcdserver.Cluster{}})
  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. clientURLs: []string{"http://localhost:8080", "http://localhost:8081", "http://localhost:8082"},
  603. }
  604. writer := httptest.NewRecorder()
  605. req, err := http.NewRequest("GET", "", nil)
  606. if err != nil {
  607. t.Fatal(err)
  608. }
  609. h := &serverHandler{clusterInfo: cluster}
  610. h.serveMachines(writer, req)
  611. w := "http://localhost:8080, http://localhost:8081, http://localhost:8082"
  612. if g := writer.Body.String(); g != w {
  613. t.Errorf("body = %s, want %s", g, w)
  614. }
  615. if writer.Code != http.StatusOK {
  616. t.Errorf("code = %d, want %d", writer.Code, http.StatusOK)
  617. }
  618. }
  619. type dummyStats struct {
  620. data []byte
  621. }
  622. func (ds *dummyStats) SelfStats() []byte { return ds.data }
  623. func (ds *dummyStats) LeaderStats() []byte { return ds.data }
  624. func (ds *dummyStats) StoreStats() []byte { return ds.data }
  625. func (ds *dummyStats) UpdateRecvApp(_ uint64, _ int64) {}
  626. func TestServeSelfStats(t *testing.T) {
  627. wb := []byte("some statistics")
  628. w := string(wb)
  629. sh := &serverHandler{
  630. stats: &dummyStats{data: wb},
  631. }
  632. rw := httptest.NewRecorder()
  633. sh.serveSelfStats(rw, &http.Request{Method: "GET"})
  634. if rw.Code != http.StatusOK {
  635. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  636. }
  637. wct := "application/json"
  638. if gct := rw.Header().Get("Content-Type"); gct != wct {
  639. t.Errorf("Content-Type = %q, want %q", gct, wct)
  640. }
  641. if g := rw.Body.String(); g != w {
  642. t.Errorf("body = %s, want %s", g, w)
  643. }
  644. }
  645. func TestSelfServeStatsBad(t *testing.T) {
  646. for _, m := range []string{"PUT", "POST", "DELETE"} {
  647. sh := &serverHandler{}
  648. rw := httptest.NewRecorder()
  649. sh.serveSelfStats(
  650. rw,
  651. &http.Request{
  652. Method: m,
  653. },
  654. )
  655. if rw.Code != http.StatusMethodNotAllowed {
  656. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  657. }
  658. }
  659. }
  660. func TestLeaderServeStatsBad(t *testing.T) {
  661. for _, m := range []string{"PUT", "POST", "DELETE"} {
  662. sh := &serverHandler{}
  663. rw := httptest.NewRecorder()
  664. sh.serveLeaderStats(
  665. rw,
  666. &http.Request{
  667. Method: m,
  668. },
  669. )
  670. if rw.Code != http.StatusMethodNotAllowed {
  671. t.Errorf("method %s: code=%d, want %d", m, rw.Code, http.StatusMethodNotAllowed)
  672. }
  673. }
  674. }
  675. func TestServeLeaderStats(t *testing.T) {
  676. wb := []byte("some statistics")
  677. w := string(wb)
  678. sh := &serverHandler{
  679. stats: &dummyStats{data: wb},
  680. }
  681. rw := httptest.NewRecorder()
  682. sh.serveLeaderStats(rw, &http.Request{Method: "GET"})
  683. if rw.Code != http.StatusOK {
  684. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  685. }
  686. wct := "application/json"
  687. if gct := rw.Header().Get("Content-Type"); gct != wct {
  688. t.Errorf("Content-Type = %q, want %q", gct, wct)
  689. }
  690. if g := rw.Body.String(); g != w {
  691. t.Errorf("body = %s, want %s", g, w)
  692. }
  693. }
  694. func TestServeStoreStats(t *testing.T) {
  695. wb := []byte("some statistics")
  696. w := string(wb)
  697. sh := &serverHandler{
  698. stats: &dummyStats{data: wb},
  699. }
  700. rw := httptest.NewRecorder()
  701. sh.serveStoreStats(rw, &http.Request{Method: "GET"})
  702. if rw.Code != http.StatusOK {
  703. t.Errorf("code = %d, want %d", rw.Code, http.StatusOK)
  704. }
  705. wct := "application/json"
  706. if gct := rw.Header().Get("Content-Type"); gct != wct {
  707. t.Errorf("Content-Type = %q, want %q", gct, wct)
  708. }
  709. if g := rw.Body.String(); g != w {
  710. t.Errorf("body = %s, want %s", g, w)
  711. }
  712. }
  713. func TestAllowMethod(t *testing.T) {
  714. tests := []struct {
  715. m string
  716. ms []string
  717. w bool
  718. wh string
  719. }{
  720. // Accepted methods
  721. {
  722. m: "GET",
  723. ms: []string{"GET", "POST", "PUT"},
  724. w: true,
  725. },
  726. {
  727. m: "POST",
  728. ms: []string{"POST"},
  729. w: true,
  730. },
  731. // Made-up methods no good
  732. {
  733. m: "FAKE",
  734. ms: []string{"GET", "POST", "PUT"},
  735. w: false,
  736. wh: "GET,POST,PUT",
  737. },
  738. // Empty methods no good
  739. {
  740. m: "",
  741. ms: []string{"GET", "POST"},
  742. w: false,
  743. wh: "GET,POST",
  744. },
  745. // Empty accepted methods no good
  746. {
  747. m: "GET",
  748. ms: []string{""},
  749. w: false,
  750. wh: "",
  751. },
  752. // No methods accepted
  753. {
  754. m: "GET",
  755. ms: []string{},
  756. w: false,
  757. wh: "",
  758. },
  759. }
  760. for i, tt := range tests {
  761. rw := httptest.NewRecorder()
  762. g := allowMethod(rw, tt.m, tt.ms...)
  763. if g != tt.w {
  764. t.Errorf("#%d: got allowMethod()=%t, want %t", i, g, tt.w)
  765. }
  766. if !tt.w {
  767. if rw.Code != http.StatusMethodNotAllowed {
  768. t.Errorf("#%d: code=%d, want %d", i, rw.Code, http.StatusMethodNotAllowed)
  769. }
  770. gh := rw.Header().Get("Allow")
  771. if gh != tt.wh {
  772. t.Errorf("#%d: Allow header=%q, want %q", i, gh, tt.wh)
  773. }
  774. }
  775. }
  776. }
  777. // errServer implements the etcd.Server interface for testing.
  778. // It returns the given error from any Do/Process/AddMember/RemoveMember calls.
  779. type errServer struct {
  780. err error
  781. }
  782. func (fs *errServer) Do(ctx context.Context, r etcdserverpb.Request) (etcdserver.Response, error) {
  783. return etcdserver.Response{}, fs.err
  784. }
  785. func (fs *errServer) Process(ctx context.Context, m raftpb.Message) error {
  786. return fs.err
  787. }
  788. func (fs *errServer) Start() {}
  789. func (fs *errServer) Stop() {}
  790. func (fs *errServer) AddMember(ctx context.Context, m etcdserver.Member) error {
  791. return fs.err
  792. }
  793. func (fs *errServer) RemoveMember(ctx context.Context, id uint64) error {
  794. return fs.err
  795. }
  796. // errReader implements io.Reader to facilitate a broken request.
  797. type errReader struct{}
  798. func (er *errReader) Read(_ []byte) (int, error) { return 0, errors.New("some error") }
  799. func mustMarshalMsg(t *testing.T, m raftpb.Message) []byte {
  800. json, err := m.Marshal()
  801. if err != nil {
  802. t.Fatalf("error marshalling raft Message: %#v", err)
  803. }
  804. return json
  805. }
  806. func TestServeRaft(t *testing.T) {
  807. testCases := []struct {
  808. method string
  809. body io.Reader
  810. serverErr error
  811. clusterID string
  812. wcode int
  813. }{
  814. {
  815. // bad method
  816. "GET",
  817. bytes.NewReader(
  818. mustMarshalMsg(
  819. t,
  820. raftpb.Message{},
  821. ),
  822. ),
  823. nil,
  824. "0",
  825. http.StatusMethodNotAllowed,
  826. },
  827. {
  828. // bad method
  829. "PUT",
  830. bytes.NewReader(
  831. mustMarshalMsg(
  832. t,
  833. raftpb.Message{},
  834. ),
  835. ),
  836. nil,
  837. "0",
  838. http.StatusMethodNotAllowed,
  839. },
  840. {
  841. // bad method
  842. "DELETE",
  843. bytes.NewReader(
  844. mustMarshalMsg(
  845. t,
  846. raftpb.Message{},
  847. ),
  848. ),
  849. nil,
  850. "0",
  851. http.StatusMethodNotAllowed,
  852. },
  853. {
  854. // bad request body
  855. "POST",
  856. &errReader{},
  857. nil,
  858. "0",
  859. http.StatusBadRequest,
  860. },
  861. {
  862. // bad request protobuf
  863. "POST",
  864. strings.NewReader("malformed garbage"),
  865. nil,
  866. "0",
  867. http.StatusBadRequest,
  868. },
  869. {
  870. // good request, etcdserver.Server internal error
  871. "POST",
  872. bytes.NewReader(
  873. mustMarshalMsg(
  874. t,
  875. raftpb.Message{},
  876. ),
  877. ),
  878. errors.New("some error"),
  879. "0",
  880. http.StatusInternalServerError,
  881. },
  882. {
  883. // good request from removed member
  884. "POST",
  885. bytes.NewReader(
  886. mustMarshalMsg(
  887. t,
  888. raftpb.Message{},
  889. ),
  890. ),
  891. etcdserver.ErrRemoved,
  892. "0",
  893. http.StatusForbidden,
  894. },
  895. {
  896. // good request
  897. "POST",
  898. bytes.NewReader(
  899. mustMarshalMsg(
  900. t,
  901. raftpb.Message{},
  902. ),
  903. ),
  904. nil,
  905. "1",
  906. http.StatusPreconditionFailed,
  907. },
  908. {
  909. // good request
  910. "POST",
  911. bytes.NewReader(
  912. mustMarshalMsg(
  913. t,
  914. raftpb.Message{},
  915. ),
  916. ),
  917. nil,
  918. "0",
  919. http.StatusNoContent,
  920. },
  921. }
  922. for i, tt := range testCases {
  923. req, err := http.NewRequest(tt.method, "foo", tt.body)
  924. if err != nil {
  925. t.Fatalf("#%d: could not create request: %#v", i, err)
  926. }
  927. req.Header.Set("X-Etcd-Cluster-ID", tt.clusterID)
  928. h := &serverHandler{
  929. timeout: time.Hour,
  930. server: &errServer{tt.serverErr},
  931. clusterInfo: &fakeCluster{id: 0},
  932. }
  933. rw := httptest.NewRecorder()
  934. h.serveRaft(rw, req)
  935. if rw.Code != tt.wcode {
  936. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  937. }
  938. }
  939. }
  940. // resServer implements the etcd.Server interface for testing.
  941. // It returns the given responsefrom any Do calls, and nil error
  942. type resServer struct {
  943. res etcdserver.Response
  944. }
  945. func (rs *resServer) Do(_ context.Context, _ etcdserverpb.Request) (etcdserver.Response, error) {
  946. return rs.res, nil
  947. }
  948. func (rs *resServer) Process(_ context.Context, _ raftpb.Message) error { return nil }
  949. func (rs *resServer) Start() {}
  950. func (rs *resServer) Stop() {}
  951. func (rs *resServer) AddMember(_ context.Context, _ etcdserver.Member) error { return nil }
  952. func (rs *resServer) RemoveMember(_ context.Context, _ uint64) error { return nil }
  953. func mustMarshalEvent(t *testing.T, ev *store.Event) string {
  954. b := new(bytes.Buffer)
  955. if err := json.NewEncoder(b).Encode(ev); err != nil {
  956. t.Fatalf("error marshalling event %#v: %v", ev, err)
  957. }
  958. return b.String()
  959. }
  960. func TestBadServeKeys(t *testing.T) {
  961. testBadCases := []struct {
  962. req *http.Request
  963. server etcdserver.Server
  964. wcode int
  965. }{
  966. {
  967. // bad method
  968. &http.Request{
  969. Method: "CONNECT",
  970. },
  971. &resServer{},
  972. http.StatusMethodNotAllowed,
  973. },
  974. {
  975. // bad method
  976. &http.Request{
  977. Method: "TRACE",
  978. },
  979. &resServer{},
  980. http.StatusMethodNotAllowed,
  981. },
  982. {
  983. // parseRequest error
  984. &http.Request{
  985. Body: nil,
  986. Method: "PUT",
  987. },
  988. &resServer{},
  989. http.StatusBadRequest,
  990. },
  991. {
  992. // etcdserver.Server error
  993. mustNewRequest(t, "foo"),
  994. &errServer{
  995. errors.New("blah"),
  996. },
  997. http.StatusInternalServerError,
  998. },
  999. {
  1000. // non-event/watcher response from etcdserver.Server
  1001. mustNewRequest(t, "foo"),
  1002. &resServer{
  1003. etcdserver.Response{},
  1004. },
  1005. http.StatusInternalServerError,
  1006. },
  1007. }
  1008. for i, tt := range testBadCases {
  1009. h := &serverHandler{
  1010. timeout: 0, // context times out immediately
  1011. server: tt.server,
  1012. }
  1013. rw := httptest.NewRecorder()
  1014. h.serveKeys(rw, tt.req)
  1015. if rw.Code != tt.wcode {
  1016. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  1017. }
  1018. }
  1019. }
  1020. func TestServeKeysEvent(t *testing.T) {
  1021. req := mustNewRequest(t, "foo")
  1022. server := &resServer{
  1023. etcdserver.Response{
  1024. Event: &store.Event{
  1025. Action: store.Get,
  1026. Node: &store.NodeExtern{},
  1027. },
  1028. },
  1029. }
  1030. h := &serverHandler{
  1031. timeout: time.Hour,
  1032. server: server,
  1033. timer: &dummyRaftTimer{},
  1034. }
  1035. rw := httptest.NewRecorder()
  1036. h.serveKeys(rw, req)
  1037. wcode := http.StatusOK
  1038. wbody := mustMarshalEvent(
  1039. t,
  1040. &store.Event{
  1041. Action: store.Get,
  1042. Node: &store.NodeExtern{},
  1043. },
  1044. )
  1045. if rw.Code != wcode {
  1046. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1047. }
  1048. g := rw.Body.String()
  1049. if g != wbody {
  1050. t.Errorf("got body=%#v, want %#v", g, wbody)
  1051. }
  1052. }
  1053. func TestServeKeysWatch(t *testing.T) {
  1054. req := mustNewRequest(t, "/foo/bar")
  1055. ec := make(chan *store.Event)
  1056. dw := &dummyWatcher{
  1057. echan: ec,
  1058. }
  1059. server := &resServer{
  1060. etcdserver.Response{
  1061. Watcher: dw,
  1062. },
  1063. }
  1064. h := &serverHandler{
  1065. timeout: time.Hour,
  1066. server: server,
  1067. timer: &dummyRaftTimer{},
  1068. }
  1069. go func() {
  1070. ec <- &store.Event{
  1071. Action: store.Get,
  1072. Node: &store.NodeExtern{},
  1073. }
  1074. }()
  1075. rw := httptest.NewRecorder()
  1076. h.serveKeys(rw, req)
  1077. wcode := http.StatusOK
  1078. wbody := mustMarshalEvent(
  1079. t,
  1080. &store.Event{
  1081. Action: store.Get,
  1082. Node: &store.NodeExtern{},
  1083. },
  1084. )
  1085. if rw.Code != wcode {
  1086. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1087. }
  1088. g := rw.Body.String()
  1089. if g != wbody {
  1090. t.Errorf("got body=%#v, want %#v", g, wbody)
  1091. }
  1092. }
  1093. type recordingCloseNotifier struct {
  1094. *httptest.ResponseRecorder
  1095. cn chan bool
  1096. }
  1097. func (rcn *recordingCloseNotifier) CloseNotify() <-chan bool {
  1098. return rcn.cn
  1099. }
  1100. func TestHandleWatch(t *testing.T) {
  1101. defaultRwRr := func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1102. r := httptest.NewRecorder()
  1103. return r, r
  1104. }
  1105. noopEv := func(chan *store.Event) {}
  1106. tests := []struct {
  1107. getCtx func() context.Context
  1108. getRwRr func() (http.ResponseWriter, *httptest.ResponseRecorder)
  1109. doToChan func(chan *store.Event)
  1110. wbody string
  1111. }{
  1112. {
  1113. // Normal case: one event
  1114. context.Background,
  1115. defaultRwRr,
  1116. func(ch chan *store.Event) {
  1117. ch <- &store.Event{
  1118. Action: store.Get,
  1119. Node: &store.NodeExtern{},
  1120. }
  1121. },
  1122. mustMarshalEvent(
  1123. t,
  1124. &store.Event{
  1125. Action: store.Get,
  1126. Node: &store.NodeExtern{},
  1127. },
  1128. ),
  1129. },
  1130. {
  1131. // Channel is closed, no event
  1132. context.Background,
  1133. defaultRwRr,
  1134. func(ch chan *store.Event) {
  1135. close(ch)
  1136. },
  1137. "",
  1138. },
  1139. {
  1140. // Simulate a timed-out context
  1141. func() context.Context {
  1142. ctx, cancel := context.WithCancel(context.Background())
  1143. cancel()
  1144. return ctx
  1145. },
  1146. defaultRwRr,
  1147. noopEv,
  1148. "",
  1149. },
  1150. {
  1151. // Close-notifying request
  1152. context.Background,
  1153. func() (http.ResponseWriter, *httptest.ResponseRecorder) {
  1154. rw := &recordingCloseNotifier{
  1155. ResponseRecorder: httptest.NewRecorder(),
  1156. cn: make(chan bool, 1),
  1157. }
  1158. rw.cn <- true
  1159. return rw, rw.ResponseRecorder
  1160. },
  1161. noopEv,
  1162. "",
  1163. },
  1164. }
  1165. for i, tt := range tests {
  1166. rw, rr := tt.getRwRr()
  1167. wa := &dummyWatcher{
  1168. echan: make(chan *store.Event, 1),
  1169. sidx: 10,
  1170. }
  1171. tt.doToChan(wa.echan)
  1172. handleKeyWatch(tt.getCtx(), rw, wa, false, dummyRaftTimer{})
  1173. wcode := http.StatusOK
  1174. wct := "application/json"
  1175. wei := "10"
  1176. wri := "100"
  1177. wrt := "5"
  1178. if rr.Code != wcode {
  1179. t.Errorf("#%d: got code=%d, want %d", i, rr.Code, wcode)
  1180. }
  1181. h := rr.Header()
  1182. if ct := h.Get("Content-Type"); ct != wct {
  1183. t.Errorf("#%d: Content-Type=%q, want %q", i, ct, wct)
  1184. }
  1185. if ei := h.Get("X-Etcd-Index"); ei != wei {
  1186. t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, ei, wei)
  1187. }
  1188. if ri := h.Get("X-Raft-Index"); ri != wri {
  1189. t.Errorf("#%d: X-Raft-Index=%q, want %q", i, ri, wri)
  1190. }
  1191. if rt := h.Get("X-Raft-Term"); rt != wrt {
  1192. t.Errorf("#%d: X-Raft-Term=%q, want %q", i, rt, wrt)
  1193. }
  1194. g := rr.Body.String()
  1195. if g != tt.wbody {
  1196. t.Errorf("#%d: got body=%#v, want %#v", i, g, tt.wbody)
  1197. }
  1198. }
  1199. }
  1200. // flushingRecorder provides a channel to allow users to block until the Recorder is Flushed()
  1201. type flushingRecorder struct {
  1202. *httptest.ResponseRecorder
  1203. ch chan struct{}
  1204. }
  1205. func (fr *flushingRecorder) Flush() {
  1206. fr.ResponseRecorder.Flush()
  1207. fr.ch <- struct{}{}
  1208. }
  1209. func TestHandleWatchStreaming(t *testing.T) {
  1210. rw := &flushingRecorder{
  1211. httptest.NewRecorder(),
  1212. make(chan struct{}, 1),
  1213. }
  1214. wa := &dummyWatcher{
  1215. echan: make(chan *store.Event),
  1216. }
  1217. // Launch the streaming handler in the background with a cancellable context
  1218. ctx, cancel := context.WithCancel(context.Background())
  1219. done := make(chan struct{})
  1220. go func() {
  1221. handleKeyWatch(ctx, rw, wa, true, dummyRaftTimer{})
  1222. close(done)
  1223. }()
  1224. // Expect one Flush for the headers etc.
  1225. select {
  1226. case <-rw.ch:
  1227. case <-time.After(time.Second):
  1228. t.Fatalf("timed out waiting for flush")
  1229. }
  1230. // Expect headers but no body
  1231. wcode := http.StatusOK
  1232. wct := "application/json"
  1233. wbody := ""
  1234. if rw.Code != wcode {
  1235. t.Errorf("got code=%d, want %d", rw.Code, wcode)
  1236. }
  1237. h := rw.Header()
  1238. if ct := h.Get("Content-Type"); ct != wct {
  1239. t.Errorf("Content-Type=%q, want %q", ct, wct)
  1240. }
  1241. g := rw.Body.String()
  1242. if g != wbody {
  1243. t.Errorf("got body=%#v, want %#v", g, wbody)
  1244. }
  1245. // Now send the first event
  1246. select {
  1247. case wa.echan <- &store.Event{
  1248. Action: store.Get,
  1249. Node: &store.NodeExtern{},
  1250. }:
  1251. case <-time.After(time.Second):
  1252. t.Fatal("timed out waiting for send")
  1253. }
  1254. // Wait for it to be flushed...
  1255. select {
  1256. case <-rw.ch:
  1257. case <-time.After(time.Second):
  1258. t.Fatalf("timed out waiting for flush")
  1259. }
  1260. // And check the body is as expected
  1261. wbody = mustMarshalEvent(
  1262. t,
  1263. &store.Event{
  1264. Action: store.Get,
  1265. Node: &store.NodeExtern{},
  1266. },
  1267. )
  1268. g = rw.Body.String()
  1269. if g != wbody {
  1270. t.Errorf("got body=%#v, want %#v", g, wbody)
  1271. }
  1272. // Rinse and repeat
  1273. select {
  1274. case wa.echan <- &store.Event{
  1275. Action: store.Get,
  1276. Node: &store.NodeExtern{},
  1277. }:
  1278. case <-time.After(time.Second):
  1279. t.Fatal("timed out waiting for send")
  1280. }
  1281. select {
  1282. case <-rw.ch:
  1283. case <-time.After(time.Second):
  1284. t.Fatalf("timed out waiting for flush")
  1285. }
  1286. // This time, we expect to see both events
  1287. wbody = wbody + wbody
  1288. g = rw.Body.String()
  1289. if g != wbody {
  1290. t.Errorf("got body=%#v, want %#v", g, wbody)
  1291. }
  1292. // Finally, time out the connection and ensure the serving goroutine returns
  1293. cancel()
  1294. select {
  1295. case <-done:
  1296. case <-time.After(time.Second):
  1297. t.Fatalf("timed out waiting for done")
  1298. }
  1299. }
  1300. func TestServeAdminMembersFail(t *testing.T) {
  1301. tests := []struct {
  1302. req *http.Request
  1303. server etcdserver.Server
  1304. wcode int
  1305. }{
  1306. {
  1307. // bad method
  1308. &http.Request{
  1309. Method: "CONNECT",
  1310. },
  1311. &resServer{},
  1312. http.StatusMethodNotAllowed,
  1313. },
  1314. {
  1315. // bad method
  1316. &http.Request{
  1317. Method: "TRACE",
  1318. },
  1319. &resServer{},
  1320. http.StatusMethodNotAllowed,
  1321. },
  1322. {
  1323. // parse body error
  1324. &http.Request{
  1325. URL: mustNewURL(t, adminMembersPrefix),
  1326. Method: "POST",
  1327. Body: ioutil.NopCloser(strings.NewReader("bad json")),
  1328. },
  1329. &resServer{},
  1330. http.StatusBadRequest,
  1331. },
  1332. {
  1333. // bad content type
  1334. &http.Request{
  1335. URL: mustNewURL(t, adminMembersPrefix),
  1336. Method: "POST",
  1337. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  1338. Header: map[string][]string{"Content-Type": []string{"application/bad"}},
  1339. },
  1340. &errServer{},
  1341. http.StatusBadRequest,
  1342. },
  1343. {
  1344. // bad url
  1345. &http.Request{
  1346. URL: mustNewURL(t, adminMembersPrefix),
  1347. Method: "POST",
  1348. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://a"]}`)),
  1349. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  1350. },
  1351. &errServer{},
  1352. http.StatusBadRequest,
  1353. },
  1354. {
  1355. // etcdserver.AddMember error
  1356. &http.Request{
  1357. URL: mustNewURL(t, adminMembersPrefix),
  1358. Method: "POST",
  1359. Body: ioutil.NopCloser(strings.NewReader(`{"PeerURLs": ["http://127.0.0.1:1"]}`)),
  1360. Header: map[string][]string{"Content-Type": []string{"application/json"}},
  1361. },
  1362. &errServer{
  1363. errors.New("blah"),
  1364. },
  1365. http.StatusInternalServerError,
  1366. },
  1367. {
  1368. // etcdserver.RemoveMember error
  1369. &http.Request{
  1370. URL: mustNewURL(t, path.Join(adminMembersPrefix, "1")),
  1371. Method: "DELETE",
  1372. },
  1373. &errServer{
  1374. errors.New("blah"),
  1375. },
  1376. http.StatusInternalServerError,
  1377. },
  1378. {
  1379. // etcdserver.GetMember bad id
  1380. &http.Request{
  1381. URL: mustNewURL(t, path.Join(adminMembersPrefix, "badid")),
  1382. Method: "GET",
  1383. },
  1384. &errServer{},
  1385. http.StatusBadRequest,
  1386. },
  1387. }
  1388. for i, tt := range tests {
  1389. h := &serverHandler{
  1390. server: tt.server,
  1391. clock: clockwork.NewFakeClock(),
  1392. }
  1393. rw := httptest.NewRecorder()
  1394. h.serveAdminMembers(rw, tt.req)
  1395. if rw.Code != tt.wcode {
  1396. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  1397. }
  1398. }
  1399. }
  1400. type action struct {
  1401. name string
  1402. params []interface{}
  1403. }
  1404. type serverRecorder struct {
  1405. actions []action
  1406. }
  1407. func (s *serverRecorder) Do(_ context.Context, r etcdserverpb.Request) (etcdserver.Response, error) {
  1408. s.actions = append(s.actions, action{name: "Do", params: []interface{}{r}})
  1409. return etcdserver.Response{}, nil
  1410. }
  1411. func (s *serverRecorder) Process(_ context.Context, m raftpb.Message) error {
  1412. s.actions = append(s.actions, action{name: "Process", params: []interface{}{m}})
  1413. return nil
  1414. }
  1415. func (s *serverRecorder) Start() {}
  1416. func (s *serverRecorder) Stop() {}
  1417. func (s *serverRecorder) AddMember(_ context.Context, m etcdserver.Member) error {
  1418. s.actions = append(s.actions, action{name: "AddMember", params: []interface{}{m}})
  1419. return nil
  1420. }
  1421. func (s *serverRecorder) RemoveMember(_ context.Context, id uint64) error {
  1422. s.actions = append(s.actions, action{name: "RemoveMember", params: []interface{}{id}})
  1423. return nil
  1424. }
  1425. func TestServeAdminMembersGet(t *testing.T) {
  1426. cluster := &fakeCluster{
  1427. members: []etcdserver.Member{
  1428. {ID: 1, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8080"}}},
  1429. {ID: 2, Attributes: etcdserver.Attributes{ClientURLs: []string{"http://localhost:8081"}}},
  1430. },
  1431. }
  1432. h := &serverHandler{
  1433. server: &serverRecorder{},
  1434. clock: clockwork.NewFakeClock(),
  1435. clusterStore: cluster,
  1436. }
  1437. msb, err := json.Marshal(cluster.members)
  1438. if err != nil {
  1439. t.Fatal(err)
  1440. }
  1441. wms := string(msb) + "\n"
  1442. mb, err := json.Marshal(cluster.members[0])
  1443. if err != nil {
  1444. t.Fatal(err)
  1445. }
  1446. wm := string(mb) + "\n"
  1447. tests := []struct {
  1448. path string
  1449. wcode int
  1450. wct string
  1451. wbody string
  1452. }{
  1453. {adminMembersPrefix, http.StatusOK, "application/json", wms},
  1454. {path.Join(adminMembersPrefix, "1"), http.StatusOK, "application/json", wm},
  1455. {path.Join(adminMembersPrefix, "100"), http.StatusNotFound, "text/plain; charset=utf-8", "member not found\n"},
  1456. }
  1457. for i, tt := range tests {
  1458. req, err := http.NewRequest("GET", mustNewURL(t, tt.path).String(), nil)
  1459. if err != nil {
  1460. t.Fatal(err)
  1461. }
  1462. rw := httptest.NewRecorder()
  1463. h.serveAdminMembers(rw, req)
  1464. if rw.Code != tt.wcode {
  1465. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  1466. }
  1467. if gct := rw.Header().Get("Content-Type"); gct != tt.wct {
  1468. t.Errorf("#%d: content-type = %s, want %s", i, gct, tt.wct)
  1469. }
  1470. if rw.Body.String() != tt.wbody {
  1471. t.Errorf("#%d: body = %s, want %s", i, rw.Body.String(), tt.wbody)
  1472. }
  1473. }
  1474. }
  1475. func TestServeAdminMembersPut(t *testing.T) {
  1476. u := mustNewURL(t, adminMembersPrefix)
  1477. raftAttr := etcdserver.RaftAttributes{PeerURLs: []string{"http://127.0.0.1:1"}}
  1478. b, err := json.Marshal(raftAttr)
  1479. if err != nil {
  1480. t.Fatal(err)
  1481. }
  1482. body := bytes.NewReader(b)
  1483. req, err := http.NewRequest("POST", u.String(), body)
  1484. if err != nil {
  1485. t.Fatal(err)
  1486. }
  1487. req.Header.Set("Content-Type", "application/json")
  1488. s := &serverRecorder{}
  1489. h := &serverHandler{
  1490. server: s,
  1491. clock: clockwork.NewFakeClock(),
  1492. }
  1493. rw := httptest.NewRecorder()
  1494. h.serveAdminMembers(rw, req)
  1495. wcode := http.StatusCreated
  1496. if rw.Code != wcode {
  1497. t.Errorf("code=%d, want %d", rw.Code, wcode)
  1498. }
  1499. wm := etcdserver.Member{
  1500. ID: 3064321551348478165,
  1501. RaftAttributes: raftAttr,
  1502. }
  1503. wb, err := json.Marshal(wm)
  1504. if err != nil {
  1505. t.Fatal(err)
  1506. }
  1507. wct := "application/json"
  1508. if gct := rw.Header().Get("Content-Type"); gct != wct {
  1509. t.Errorf("content-type = %s, want %s", gct, wct)
  1510. }
  1511. g := rw.Body.String()
  1512. w := string(wb) + "\n"
  1513. if g != w {
  1514. t.Errorf("got body=%q, want %q", g, w)
  1515. }
  1516. wactions := []action{{name: "AddMember", params: []interface{}{wm}}}
  1517. if !reflect.DeepEqual(s.actions, wactions) {
  1518. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  1519. }
  1520. }
  1521. func TestServeAdminMembersDelete(t *testing.T) {
  1522. req := &http.Request{
  1523. Method: "DELETE",
  1524. URL: mustNewURL(t, path.Join(adminMembersPrefix, "BEEF")),
  1525. }
  1526. s := &serverRecorder{}
  1527. h := &serverHandler{
  1528. server: s,
  1529. }
  1530. rw := httptest.NewRecorder()
  1531. h.serveAdminMembers(rw, req)
  1532. wcode := http.StatusNoContent
  1533. if rw.Code != wcode {
  1534. t.Errorf("code=%d, want %d", rw.Code, wcode)
  1535. }
  1536. g := rw.Body.String()
  1537. if g != "" {
  1538. t.Errorf("got body=%q, want %q", g, "")
  1539. }
  1540. wactions := []action{{name: "RemoveMember", params: []interface{}{uint64(0xBEEF)}}}
  1541. if !reflect.DeepEqual(s.actions, wactions) {
  1542. t.Errorf("actions = %+v, want %+v", s.actions, wactions)
  1543. }
  1544. }
  1545. func TestTrimEventPrefix(t *testing.T) {
  1546. pre := "/abc"
  1547. tests := []struct {
  1548. ev *store.Event
  1549. wev *store.Event
  1550. }{
  1551. {
  1552. nil,
  1553. nil,
  1554. },
  1555. {
  1556. &store.Event{},
  1557. &store.Event{},
  1558. },
  1559. {
  1560. &store.Event{Node: &store.NodeExtern{Key: "/abc/def"}},
  1561. &store.Event{Node: &store.NodeExtern{Key: "/def"}},
  1562. },
  1563. {
  1564. &store.Event{PrevNode: &store.NodeExtern{Key: "/abc/ghi"}},
  1565. &store.Event{PrevNode: &store.NodeExtern{Key: "/ghi"}},
  1566. },
  1567. {
  1568. &store.Event{
  1569. Node: &store.NodeExtern{Key: "/abc/def"},
  1570. PrevNode: &store.NodeExtern{Key: "/abc/ghi"},
  1571. },
  1572. &store.Event{
  1573. Node: &store.NodeExtern{Key: "/def"},
  1574. PrevNode: &store.NodeExtern{Key: "/ghi"},
  1575. },
  1576. },
  1577. }
  1578. for i, tt := range tests {
  1579. ev := trimEventPrefix(tt.ev, pre)
  1580. if !reflect.DeepEqual(ev, tt.wev) {
  1581. t.Errorf("#%d: event = %+v, want %+v", i, ev, tt.wev)
  1582. }
  1583. }
  1584. }
  1585. func TestTrimNodeExternPrefix(t *testing.T) {
  1586. pre := "/abc"
  1587. tests := []struct {
  1588. n *store.NodeExtern
  1589. wn *store.NodeExtern
  1590. }{
  1591. {
  1592. nil,
  1593. nil,
  1594. },
  1595. {
  1596. &store.NodeExtern{Key: "/abc/def"},
  1597. &store.NodeExtern{Key: "/def"},
  1598. },
  1599. {
  1600. &store.NodeExtern{
  1601. Key: "/abc/def",
  1602. Nodes: []*store.NodeExtern{
  1603. {Key: "/abc/def/1"},
  1604. {Key: "/abc/def/2"},
  1605. },
  1606. },
  1607. &store.NodeExtern{
  1608. Key: "/def",
  1609. Nodes: []*store.NodeExtern{
  1610. {Key: "/def/1"},
  1611. {Key: "/def/2"},
  1612. },
  1613. },
  1614. },
  1615. }
  1616. for i, tt := range tests {
  1617. n := trimNodeExternPrefix(tt.n, pre)
  1618. if !reflect.DeepEqual(n, tt.wn) {
  1619. t.Errorf("#%d: node = %+v, want %+v", i, n, tt.wn)
  1620. }
  1621. }
  1622. }
  1623. type fakeCluster struct {
  1624. id uint64
  1625. clientURLs []string
  1626. }
  1627. func (c *fakeCluster) ID() uint64 { return c.id }
  1628. func (c *fakeCluster) ClientURLs() []string { return c.clientURLs }