v2_http_kv_test.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080
  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 etcdserver
  14. import (
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "io"
  19. "io/ioutil"
  20. "net"
  21. "net/http"
  22. "net/url"
  23. "reflect"
  24. "strings"
  25. "testing"
  26. "time"
  27. )
  28. func TestV2Set(t *testing.T) {
  29. cl := testCluster{Size: 1}
  30. cl.Start()
  31. defer cl.Destroy()
  32. u := cl.URL(0)
  33. tc := NewTestClient()
  34. v := url.Values{}
  35. v.Set("value", "bar")
  36. tests := []struct {
  37. relativeURL string
  38. value url.Values
  39. wStatus int
  40. w string
  41. }{
  42. {
  43. "/v2/keys/foo/bar",
  44. v,
  45. http.StatusCreated,
  46. `{"action":"set","node":{"key":"/foo/bar","value":"bar","modifiedIndex":2,"createdIndex":2}}`,
  47. },
  48. {
  49. "/v2/keys/foodir?dir=true",
  50. url.Values{},
  51. http.StatusCreated,
  52. `{"action":"set","node":{"key":"/foodir","dir":true,"modifiedIndex":3,"createdIndex":3}}`,
  53. },
  54. {
  55. "/v2/keys/fooempty",
  56. url.Values(map[string][]string{"value": {""}}),
  57. http.StatusCreated,
  58. `{"action":"set","node":{"key":"/fooempty","value":"","modifiedIndex":4,"createdIndex":4}}`,
  59. },
  60. }
  61. for i, tt := range tests {
  62. resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, tt.relativeURL), tt.value)
  63. if err != nil {
  64. t.Errorf("#%d: err = %v, want nil", i, err)
  65. }
  66. g := string(tc.ReadBody(resp))
  67. if g != tt.w {
  68. t.Errorf("#%d: body = %v, want %v", i, g, tt.w)
  69. }
  70. if resp.StatusCode != tt.wStatus {
  71. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  72. }
  73. }
  74. }
  75. func TestV2CreateUpdate(t *testing.T) {
  76. cl := testCluster{Size: 1}
  77. cl.Start()
  78. defer cl.Destroy()
  79. u := cl.URL(0)
  80. tc := NewTestClient()
  81. tests := []struct {
  82. relativeURL string
  83. value url.Values
  84. wStatus int
  85. w map[string]interface{}
  86. }{
  87. // key with ttl
  88. {
  89. "/v2/keys/ttl/foo",
  90. url.Values(map[string][]string{"value": {"XXX"}, "ttl": {"20"}}),
  91. http.StatusCreated,
  92. map[string]interface{}{
  93. "node": map[string]interface{}{
  94. "value": "XXX",
  95. "ttl": float64(20),
  96. },
  97. },
  98. },
  99. // key with bad ttl
  100. {
  101. "/v2/keys/ttl/foo",
  102. url.Values(map[string][]string{"value": {"XXX"}, "ttl": {"bad_ttl"}}),
  103. http.StatusBadRequest,
  104. map[string]interface{}{
  105. "errorCode": float64(202),
  106. "message": "The given TTL in POST form is not a number",
  107. "cause": "Update",
  108. },
  109. },
  110. // create key
  111. {
  112. "/v2/keys/create/foo",
  113. url.Values(map[string][]string{"value": {"XXX"}, "prevExist": {"false"}}),
  114. http.StatusCreated,
  115. map[string]interface{}{
  116. "node": map[string]interface{}{
  117. "value": "XXX",
  118. },
  119. },
  120. },
  121. // created key failed
  122. {
  123. "/v2/keys/create/foo",
  124. url.Values(map[string][]string{"value": {"XXX"}, "prevExist": {"false"}}),
  125. http.StatusPreconditionFailed,
  126. map[string]interface{}{
  127. "errorCode": float64(105),
  128. "message": "Key already exists",
  129. "cause": "/create/foo",
  130. },
  131. },
  132. // update the newly created key with ttl
  133. {
  134. "/v2/keys/create/foo",
  135. url.Values(map[string][]string{"value": {"YYY"}, "prevExist": {"true"}, "ttl": {"20"}}),
  136. http.StatusOK,
  137. map[string]interface{}{
  138. "node": map[string]interface{}{
  139. "value": "YYY",
  140. "ttl": float64(20),
  141. },
  142. "action": "update",
  143. },
  144. },
  145. // update the ttl to none
  146. {
  147. "/v2/keys/create/foo",
  148. url.Values(map[string][]string{"value": {"ZZZ"}, "prevExist": {"true"}}),
  149. http.StatusOK,
  150. map[string]interface{}{
  151. "node": map[string]interface{}{
  152. "value": "ZZZ",
  153. },
  154. "action": "update",
  155. },
  156. },
  157. // update on a non-existing key
  158. {
  159. "/v2/keys/nonexist",
  160. url.Values(map[string][]string{"value": {"XXX"}, "prevExist": {"true"}}),
  161. http.StatusNotFound,
  162. map[string]interface{}{
  163. "errorCode": float64(100),
  164. "message": "Key not found",
  165. "cause": "/nonexist",
  166. },
  167. },
  168. }
  169. for i, tt := range tests {
  170. resp, _ := tc.PutForm(fmt.Sprintf("%s%s", u, tt.relativeURL), tt.value)
  171. if resp.StatusCode != tt.wStatus {
  172. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  173. }
  174. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  175. t.Errorf("#%d: %v", i, err)
  176. }
  177. }
  178. }
  179. func TestV2CAS(t *testing.T) {
  180. cl := testCluster{Size: 1}
  181. cl.Start()
  182. defer cl.Destroy()
  183. u := cl.URL(0)
  184. tc := NewTestClient()
  185. tests := []struct {
  186. relativeURL string
  187. value url.Values
  188. wStatus int
  189. w map[string]interface{}
  190. }{
  191. {
  192. "/v2/keys/cas/foo",
  193. url.Values(map[string][]string{"value": {"XXX"}}),
  194. http.StatusCreated,
  195. nil,
  196. },
  197. {
  198. "/v2/keys/cas/foo",
  199. url.Values(map[string][]string{"value": {"YYY"}, "prevIndex": {"2"}}),
  200. http.StatusOK,
  201. map[string]interface{}{
  202. "node": map[string]interface{}{
  203. "value": "YYY",
  204. "modifiedIndex": float64(3),
  205. },
  206. "action": "compareAndSwap",
  207. },
  208. },
  209. {
  210. "/v2/keys/cas/foo",
  211. url.Values(map[string][]string{"value": {"YYY"}, "prevIndex": {"10"}}),
  212. http.StatusPreconditionFailed,
  213. map[string]interface{}{
  214. "errorCode": float64(101),
  215. "message": "Compare failed",
  216. "cause": "[10 != 3]",
  217. "index": float64(3),
  218. },
  219. },
  220. {
  221. "/v2/keys/cas/foo",
  222. url.Values(map[string][]string{"value": {"YYY"}, "prevIndex": {"bad_index"}}),
  223. http.StatusBadRequest,
  224. map[string]interface{}{
  225. "errorCode": float64(203),
  226. "message": "The given index in POST form is not a number",
  227. "cause": "CompareAndSwap",
  228. },
  229. },
  230. {
  231. "/v2/keys/cas/foo",
  232. url.Values(map[string][]string{"value": {"ZZZ"}, "prevValue": {"YYY"}}),
  233. http.StatusOK,
  234. map[string]interface{}{
  235. "node": map[string]interface{}{
  236. "value": "ZZZ",
  237. },
  238. "action": "compareAndSwap",
  239. },
  240. },
  241. {
  242. "/v2/keys/cas/foo",
  243. url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {"bad_value"}}),
  244. http.StatusPreconditionFailed,
  245. map[string]interface{}{
  246. "errorCode": float64(101),
  247. "message": "Compare failed",
  248. "cause": "[bad_value != ZZZ]",
  249. },
  250. },
  251. // prevValue is required
  252. {
  253. "/v2/keys/cas/foo",
  254. url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {""}}),
  255. http.StatusBadRequest,
  256. map[string]interface{}{
  257. "errorCode": float64(201),
  258. "message": "PrevValue is Required in POST form",
  259. "cause": "CompareAndSwap",
  260. },
  261. },
  262. {
  263. "/v2/keys/cas/foo",
  264. url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {"bad_value"}, "prevIndex": {"100"}}),
  265. http.StatusPreconditionFailed,
  266. map[string]interface{}{
  267. "errorCode": float64(101),
  268. "message": "Compare failed",
  269. "cause": "[bad_value != ZZZ] [100 != 4]",
  270. },
  271. },
  272. {
  273. "/v2/keys/cas/foo",
  274. url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {"ZZZ"}, "prevIndex": {"100"}}),
  275. http.StatusPreconditionFailed,
  276. map[string]interface{}{
  277. "errorCode": float64(101),
  278. "message": "Compare failed",
  279. "cause": "[100 != 4]",
  280. },
  281. },
  282. {
  283. "/v2/keys/cas/foo",
  284. url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {"bad_value"}, "prevIndex": {"4"}}),
  285. http.StatusPreconditionFailed,
  286. map[string]interface{}{
  287. "errorCode": float64(101),
  288. "message": "Compare failed",
  289. "cause": "[bad_value != ZZZ]",
  290. },
  291. },
  292. }
  293. for i, tt := range tests {
  294. resp, _ := tc.PutForm(fmt.Sprintf("%s%s", u, tt.relativeURL), tt.value)
  295. if resp.StatusCode != tt.wStatus {
  296. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  297. }
  298. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  299. t.Errorf("#%d: %v", i, err)
  300. }
  301. }
  302. }
  303. func TestV2Delete(t *testing.T) {
  304. cl := testCluster{Size: 1}
  305. cl.Start()
  306. defer cl.Destroy()
  307. u := cl.URL(0)
  308. tc := NewTestClient()
  309. v := url.Values{}
  310. v.Set("value", "XXX")
  311. resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo"), v)
  312. if err != nil {
  313. t.Error(err)
  314. }
  315. resp.Body.Close()
  316. resp, err = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/emptydir?dir=true"), v)
  317. if err != nil {
  318. t.Error(err)
  319. }
  320. resp.Body.Close()
  321. resp, err = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foodir/bar?dir=true"), v)
  322. if err != nil {
  323. t.Error(err)
  324. }
  325. resp.Body.Close()
  326. tests := []struct {
  327. relativeURL string
  328. wStatus int
  329. w map[string]interface{}
  330. }{
  331. {
  332. "/v2/keys/foo",
  333. http.StatusOK,
  334. map[string]interface{}{
  335. "node": map[string]interface{}{
  336. "key": "/foo",
  337. },
  338. "prevNode": map[string]interface{}{
  339. "key": "/foo",
  340. "value": "XXX",
  341. },
  342. "action": "delete",
  343. },
  344. },
  345. {
  346. "/v2/keys/emptydir",
  347. http.StatusForbidden,
  348. map[string]interface{}{
  349. "errorCode": float64(102),
  350. "message": "Not a file",
  351. "cause": "/emptydir",
  352. },
  353. },
  354. {
  355. "/v2/keys/emptydir?dir=true",
  356. http.StatusOK,
  357. nil,
  358. },
  359. {
  360. "/v2/keys/foodir?dir=true",
  361. http.StatusForbidden,
  362. map[string]interface{}{
  363. "errorCode": float64(108),
  364. "message": "Directory not empty",
  365. "cause": "/foodir",
  366. },
  367. },
  368. {
  369. "/v2/keys/foodir?recursive=true",
  370. http.StatusOK,
  371. map[string]interface{}{
  372. "node": map[string]interface{}{
  373. "key": "/foodir",
  374. "dir": true,
  375. },
  376. "prevNode": map[string]interface{}{
  377. "key": "/foodir",
  378. "dir": true,
  379. },
  380. "action": "delete",
  381. },
  382. },
  383. }
  384. for i, tt := range tests {
  385. resp, _ := tc.DeleteForm(fmt.Sprintf("%s%s", u, tt.relativeURL), nil)
  386. if resp.StatusCode != tt.wStatus {
  387. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  388. }
  389. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  390. t.Errorf("#%d: %v", i, err)
  391. }
  392. }
  393. }
  394. func TestV2CAD(t *testing.T) {
  395. cl := testCluster{Size: 1}
  396. cl.Start()
  397. defer cl.Destroy()
  398. u := cl.URL(0)
  399. tc := NewTestClient()
  400. v := url.Values{}
  401. v.Set("value", "XXX")
  402. resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo"), v)
  403. if err != nil {
  404. t.Error(err)
  405. }
  406. resp.Body.Close()
  407. resp, err = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foovalue"), v)
  408. if err != nil {
  409. t.Error(err)
  410. }
  411. resp.Body.Close()
  412. tests := []struct {
  413. relativeURL string
  414. wStatus int
  415. w map[string]interface{}
  416. }{
  417. {
  418. "/v2/keys/foo?prevIndex=100",
  419. http.StatusPreconditionFailed,
  420. map[string]interface{}{
  421. "errorCode": float64(101),
  422. "message": "Compare failed",
  423. "cause": "[100 != 2]",
  424. },
  425. },
  426. {
  427. "/v2/keys/foo?prevIndex=bad_index",
  428. http.StatusBadRequest,
  429. map[string]interface{}{
  430. "errorCode": float64(203),
  431. "message": "The given index in POST form is not a number",
  432. "cause": "CompareAndDelete",
  433. },
  434. },
  435. {
  436. "/v2/keys/foo?prevIndex=2",
  437. http.StatusOK,
  438. map[string]interface{}{
  439. "node": map[string]interface{}{
  440. "key": "/foo",
  441. "modifiedIndex": float64(4),
  442. },
  443. "action": "compareAndDelete",
  444. },
  445. },
  446. {
  447. "/v2/keys/foovalue?prevValue=YYY",
  448. http.StatusPreconditionFailed,
  449. map[string]interface{}{
  450. "errorCode": float64(101),
  451. "message": "Compare failed",
  452. "cause": "[YYY != XXX]",
  453. },
  454. },
  455. {
  456. "/v2/keys/foovalue?prevValue=",
  457. http.StatusBadRequest,
  458. map[string]interface{}{
  459. "errorCode": float64(201),
  460. "message": "PrevValue is Required in POST form",
  461. "cause": "CompareAndDelete",
  462. },
  463. },
  464. {
  465. "/v2/keys/foovalue?prevValue=XXX",
  466. http.StatusOK,
  467. map[string]interface{}{
  468. "node": map[string]interface{}{
  469. "key": "/foovalue",
  470. "modifiedIndex": float64(5),
  471. },
  472. "action": "compareAndDelete",
  473. },
  474. },
  475. }
  476. for i, tt := range tests {
  477. resp, _ := tc.DeleteForm(fmt.Sprintf("%s%s", u, tt.relativeURL), nil)
  478. if resp.StatusCode != tt.wStatus {
  479. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  480. }
  481. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  482. t.Errorf("#%d: %v", i, err)
  483. }
  484. }
  485. }
  486. func TestV2Unique(t *testing.T) {
  487. cl := testCluster{Size: 1}
  488. cl.Start()
  489. defer cl.Destroy()
  490. u := cl.URL(0)
  491. tc := NewTestClient()
  492. tests := []struct {
  493. relativeURL string
  494. value url.Values
  495. wStatus int
  496. w map[string]interface{}
  497. }{
  498. {
  499. "/v2/keys/foo",
  500. url.Values(map[string][]string{"value": {"XXX"}}),
  501. http.StatusCreated,
  502. map[string]interface{}{
  503. "node": map[string]interface{}{
  504. "key": "/foo/2",
  505. "value": "XXX",
  506. },
  507. "action": "create",
  508. },
  509. },
  510. {
  511. "/v2/keys/foo",
  512. url.Values(map[string][]string{"value": {"XXX"}}),
  513. http.StatusCreated,
  514. map[string]interface{}{
  515. "node": map[string]interface{}{
  516. "key": "/foo/3",
  517. "value": "XXX",
  518. },
  519. "action": "create",
  520. },
  521. },
  522. {
  523. "/v2/keys/bar",
  524. url.Values(map[string][]string{"value": {"XXX"}}),
  525. http.StatusCreated,
  526. map[string]interface{}{
  527. "node": map[string]interface{}{
  528. "key": "/bar/4",
  529. "value": "XXX",
  530. },
  531. "action": "create",
  532. },
  533. },
  534. }
  535. for i, tt := range tests {
  536. resp, _ := tc.PostForm(fmt.Sprintf("%s%s", u, tt.relativeURL), tt.value)
  537. if resp.StatusCode != tt.wStatus {
  538. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  539. }
  540. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  541. t.Errorf("#%d: %v", i, err)
  542. }
  543. }
  544. }
  545. func TestV2Get(t *testing.T) {
  546. cl := testCluster{Size: 1}
  547. cl.Start()
  548. defer cl.Destroy()
  549. u := cl.URL(0)
  550. tc := NewTestClient()
  551. v := url.Values{}
  552. v.Set("value", "XXX")
  553. resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar/zar"), v)
  554. if err != nil {
  555. t.Error(err)
  556. }
  557. resp.Body.Close()
  558. tests := []struct {
  559. relativeURL string
  560. wStatus int
  561. w map[string]interface{}
  562. }{
  563. {
  564. "/v2/keys/foo/bar/zar",
  565. http.StatusOK,
  566. map[string]interface{}{
  567. "node": map[string]interface{}{
  568. "key": "/foo/bar/zar",
  569. "value": "XXX",
  570. },
  571. "action": "get",
  572. },
  573. },
  574. {
  575. "/v2/keys/foo",
  576. http.StatusOK,
  577. map[string]interface{}{
  578. "node": map[string]interface{}{
  579. "key": "/foo",
  580. "dir": true,
  581. "nodes": []interface{}{
  582. map[string]interface{}{
  583. "key": "/foo/bar",
  584. "dir": true,
  585. "createdIndex": float64(2),
  586. "modifiedIndex": float64(2),
  587. },
  588. },
  589. },
  590. "action": "get",
  591. },
  592. },
  593. {
  594. "/v2/keys/foo?recursive=true",
  595. http.StatusOK,
  596. map[string]interface{}{
  597. "node": map[string]interface{}{
  598. "key": "/foo",
  599. "dir": true,
  600. "nodes": []interface{}{
  601. map[string]interface{}{
  602. "key": "/foo/bar",
  603. "dir": true,
  604. "createdIndex": float64(2),
  605. "modifiedIndex": float64(2),
  606. "nodes": []interface{}{
  607. map[string]interface{}{
  608. "key": "/foo/bar/zar",
  609. "value": "XXX",
  610. "createdIndex": float64(2),
  611. "modifiedIndex": float64(2),
  612. },
  613. },
  614. },
  615. },
  616. },
  617. "action": "get",
  618. },
  619. },
  620. }
  621. for i, tt := range tests {
  622. resp, _ := tc.Get(fmt.Sprintf("%s%s", u, tt.relativeURL))
  623. if resp.StatusCode != tt.wStatus {
  624. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  625. }
  626. if resp.Header.Get("Content-Type") != "application/json" {
  627. t.Errorf("#%d: header = %v, want %v", resp.Header.Get("Content-Type"), "application/json")
  628. }
  629. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  630. t.Errorf("#%d: %v", i, err)
  631. }
  632. }
  633. }
  634. func TestConsistentGet(t *testing.T) {
  635. defer afterTest(t)
  636. noredirect := func(req *http.Request, via []*http.Request) error {
  637. return errors.New("no redirect")
  638. }
  639. c := &testCluster{Size: 3}
  640. c.Start()
  641. defer c.Destroy()
  642. u := fmt.Sprintf("%s%s", c.URL(1), "/v2/keys/foo?consistent=true")
  643. ru := fmt.Sprintf("%s%s", c.URL(0), "/v2/keys/foo?consistent=true")
  644. tc := testHttpClient{&http.Client{CheckRedirect: noredirect}}
  645. resp, _ := tc.Get(u)
  646. if resp.StatusCode != http.StatusTemporaryRedirect {
  647. t.Errorf("status = %d, want %d", resp.StatusCode, http.StatusTemporaryRedirect)
  648. }
  649. location, err := resp.Location()
  650. if err != nil {
  651. t.Errorf("err = %v, want nil", err)
  652. }
  653. if location.String() != ru {
  654. t.Errorf("location = %v, want %v", location.String(), ru)
  655. }
  656. resp.Body.Close()
  657. }
  658. func TestV2QuorumGet(t *testing.T) {
  659. cl := testCluster{Size: 1}
  660. cl.Start()
  661. defer cl.Destroy()
  662. u := cl.URL(0)
  663. tc := NewTestClient()
  664. v := url.Values{}
  665. v.Set("value", "XXX")
  666. resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar/zar?quorum=true"), v)
  667. if err != nil {
  668. t.Error(err)
  669. }
  670. resp.Body.Close()
  671. tests := []struct {
  672. relativeURL string
  673. wStatus int
  674. w map[string]interface{}
  675. }{
  676. {
  677. "/v2/keys/foo/bar/zar",
  678. http.StatusOK,
  679. map[string]interface{}{
  680. "node": map[string]interface{}{
  681. "key": "/foo/bar/zar",
  682. "value": "XXX",
  683. },
  684. "action": "get",
  685. },
  686. },
  687. {
  688. "/v2/keys/foo",
  689. http.StatusOK,
  690. map[string]interface{}{
  691. "node": map[string]interface{}{
  692. "key": "/foo",
  693. "dir": true,
  694. "nodes": []interface{}{
  695. map[string]interface{}{
  696. "key": "/foo/bar",
  697. "dir": true,
  698. "createdIndex": float64(2),
  699. "modifiedIndex": float64(2),
  700. },
  701. },
  702. },
  703. "action": "get",
  704. },
  705. },
  706. {
  707. "/v2/keys/foo?recursive=true",
  708. http.StatusOK,
  709. map[string]interface{}{
  710. "node": map[string]interface{}{
  711. "key": "/foo",
  712. "dir": true,
  713. "nodes": []interface{}{
  714. map[string]interface{}{
  715. "key": "/foo/bar",
  716. "dir": true,
  717. "createdIndex": float64(2),
  718. "modifiedIndex": float64(2),
  719. "nodes": []interface{}{
  720. map[string]interface{}{
  721. "key": "/foo/bar/zar",
  722. "value": "XXX",
  723. "createdIndex": float64(2),
  724. "modifiedIndex": float64(2),
  725. },
  726. },
  727. },
  728. },
  729. },
  730. "action": "get",
  731. },
  732. },
  733. }
  734. for i, tt := range tests {
  735. resp, _ := tc.Get(fmt.Sprintf("%s%s", u, tt.relativeURL))
  736. if resp.StatusCode != tt.wStatus {
  737. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  738. }
  739. if resp.Header.Get("Content-Type") != "application/json" {
  740. t.Errorf("#%d: header = %v, want %v", resp.Header.Get("Content-Type"), "application/json")
  741. }
  742. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  743. t.Errorf("#%d: %v", i, err)
  744. }
  745. }
  746. }
  747. func TestV2Watch(t *testing.T) {
  748. cl := testCluster{Size: 1}
  749. cl.Start()
  750. defer cl.Destroy()
  751. u := cl.URL(0)
  752. tc := NewTestClient()
  753. var watchResp *http.Response
  754. c := make(chan bool)
  755. go func() {
  756. watchResp, _ = tc.Get(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar?wait=true"))
  757. c <- true
  758. }()
  759. // Make sure response didn't fire early.
  760. time.Sleep(1 * time.Millisecond)
  761. // Set a value.
  762. v := url.Values{}
  763. v.Set("value", "XXX")
  764. resp, _ := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar"), v)
  765. resp.Body.Close()
  766. select {
  767. case <-c:
  768. case <-time.After(time.Millisecond):
  769. t.Fatal("cannot get watch result")
  770. }
  771. body := tc.ReadBodyJSON(watchResp)
  772. w := map[string]interface{}{
  773. "node": map[string]interface{}{
  774. "key": "/foo/bar",
  775. "value": "XXX",
  776. "modifiedIndex": float64(2),
  777. },
  778. "action": "set",
  779. }
  780. if err := checkBody(body, w); err != nil {
  781. t.Error(err)
  782. }
  783. }
  784. func TestV2WatchWithIndex(t *testing.T) {
  785. cl := testCluster{Size: 1}
  786. cl.Start()
  787. defer cl.Destroy()
  788. u := cl.URL(0)
  789. tc := NewTestClient()
  790. var body map[string]interface{}
  791. c := make(chan bool, 1)
  792. go func() {
  793. resp, _ := tc.Get(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar?wait=true&waitIndex=3"))
  794. body = tc.ReadBodyJSON(resp)
  795. c <- true
  796. }()
  797. select {
  798. case <-c:
  799. t.Fatal("should not get the watch result")
  800. case <-time.After(time.Millisecond):
  801. }
  802. // Set a value (before given index).
  803. v := url.Values{}
  804. v.Set("value", "XXX")
  805. resp, _ := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar"), v)
  806. resp.Body.Close()
  807. select {
  808. case <-c:
  809. t.Fatal("should not get the watch result")
  810. case <-time.After(time.Millisecond):
  811. }
  812. // Set a value (before given index).
  813. resp, _ = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar"), v)
  814. resp.Body.Close()
  815. select {
  816. case <-c:
  817. case <-time.After(time.Millisecond):
  818. t.Fatal("cannot get watch result")
  819. }
  820. w := map[string]interface{}{
  821. "node": map[string]interface{}{
  822. "key": "/foo/bar",
  823. "value": "XXX",
  824. "modifiedIndex": float64(3),
  825. },
  826. "action": "set",
  827. }
  828. if err := checkBody(body, w); err != nil {
  829. t.Error(err)
  830. }
  831. }
  832. func TestV2WatchKeyInDir(t *testing.T) {
  833. cl := testCluster{Size: 1}
  834. cl.Start()
  835. defer cl.Destroy()
  836. u := cl.URL(0)
  837. tc := NewTestClient()
  838. var body map[string]interface{}
  839. c := make(chan bool)
  840. // Set a value (before given index).
  841. v := url.Values{}
  842. v.Set("dir", "true")
  843. v.Set("ttl", "1")
  844. resp, _ := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/keyindir"), v)
  845. resp.Body.Close()
  846. // Set a value (before given index).
  847. v = url.Values{}
  848. v.Set("value", "XXX")
  849. resp, _ = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/keyindir/bar"), v)
  850. resp.Body.Close()
  851. go func() {
  852. resp, _ := tc.Get(fmt.Sprintf("%s%s", u, "/v2/keys/keyindir/bar?wait=true"))
  853. body = tc.ReadBodyJSON(resp)
  854. c <- true
  855. }()
  856. select {
  857. case <-c:
  858. case <-time.After(time.Millisecond * 1500):
  859. t.Fatal("cannot get watch result")
  860. }
  861. w := map[string]interface{}{
  862. "node": map[string]interface{}{
  863. "key": "/keyindir",
  864. },
  865. "action": "expire",
  866. }
  867. if err := checkBody(body, w); err != nil {
  868. t.Error(err)
  869. }
  870. }
  871. func TestV2Head(t *testing.T) {
  872. cl := testCluster{Size: 1}
  873. cl.Start()
  874. defer cl.Destroy()
  875. u := cl.URL(0)
  876. tc := NewTestClient()
  877. v := url.Values{}
  878. v.Set("value", "XXX")
  879. fullURL := fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar")
  880. resp, _ := tc.Head(fullURL)
  881. resp.Body.Close()
  882. if resp.StatusCode != http.StatusNotFound {
  883. t.Errorf("status = %d, want %d", resp.StatusCode, http.StatusNotFound)
  884. }
  885. if resp.ContentLength != -1 {
  886. t.Errorf("ContentLength = %d, want -1", resp.ContentLength)
  887. }
  888. resp, _ = tc.PutForm(fullURL, v)
  889. resp.Body.Close()
  890. resp, _ = tc.Head(fullURL)
  891. resp.Body.Close()
  892. if resp.StatusCode != http.StatusOK {
  893. t.Errorf("status = %d, want %d", resp.StatusCode, http.StatusOK)
  894. }
  895. if resp.ContentLength != -1 {
  896. t.Errorf("ContentLength = %d, want -1", resp.ContentLength)
  897. }
  898. }
  899. func checkBody(body map[string]interface{}, w map[string]interface{}) error {
  900. if body["node"] != nil {
  901. if w["node"] != nil {
  902. wn := w["node"].(map[string]interface{})
  903. n := body["node"].(map[string]interface{})
  904. for k := range n {
  905. if wn[k] == nil {
  906. delete(n, k)
  907. }
  908. }
  909. body["node"] = n
  910. }
  911. if w["prevNode"] != nil {
  912. wn := w["prevNode"].(map[string]interface{})
  913. n := body["prevNode"].(map[string]interface{})
  914. for k := range n {
  915. if wn[k] == nil {
  916. delete(n, k)
  917. }
  918. }
  919. body["prevNode"] = n
  920. }
  921. }
  922. for k, v := range w {
  923. g := body[k]
  924. if !reflect.DeepEqual(g, v) {
  925. return fmt.Errorf("%v = %+v, want %+v", k, g, v)
  926. }
  927. }
  928. return nil
  929. }
  930. type testHttpClient struct {
  931. *http.Client
  932. }
  933. // Creates a new HTTP client with KeepAlive disabled.
  934. func NewTestClient() *testHttpClient {
  935. tr := &http.Transport{
  936. Dial: (&net.Dialer{Timeout: time.Second}).Dial,
  937. DisableKeepAlives: true,
  938. }
  939. return &testHttpClient{&http.Client{Transport: tr}}
  940. }
  941. // Reads the body from the response and closes it.
  942. func (t *testHttpClient) ReadBody(resp *http.Response) []byte {
  943. if resp == nil {
  944. return []byte{}
  945. }
  946. body, _ := ioutil.ReadAll(resp.Body)
  947. resp.Body.Close()
  948. return body
  949. }
  950. // Reads the body from the response and parses it as JSON.
  951. func (t *testHttpClient) ReadBodyJSON(resp *http.Response) map[string]interface{} {
  952. m := make(map[string]interface{})
  953. b := t.ReadBody(resp)
  954. if err := json.Unmarshal(b, &m); err != nil {
  955. panic(fmt.Sprintf("HTTP body JSON parse error: %v: %s", err, string(b)))
  956. }
  957. return m
  958. }
  959. func (t *testHttpClient) Head(url string) (*http.Response, error) {
  960. return t.send("HEAD", url, "application/json", nil)
  961. }
  962. func (t *testHttpClient) Get(url string) (*http.Response, error) {
  963. return t.send("GET", url, "application/json", nil)
  964. }
  965. func (t *testHttpClient) Post(url string, bodyType string, body io.Reader) (*http.Response, error) {
  966. return t.send("POST", url, bodyType, body)
  967. }
  968. func (t *testHttpClient) PostForm(url string, data url.Values) (*http.Response, error) {
  969. return t.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
  970. }
  971. func (t *testHttpClient) Put(url string, bodyType string, body io.Reader) (*http.Response, error) {
  972. return t.send("PUT", url, bodyType, body)
  973. }
  974. func (t *testHttpClient) PutForm(url string, data url.Values) (*http.Response, error) {
  975. return t.Put(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
  976. }
  977. func (t *testHttpClient) Delete(url string, bodyType string, body io.Reader) (*http.Response, error) {
  978. return t.send("DELETE", url, bodyType, body)
  979. }
  980. func (t *testHttpClient) DeleteForm(url string, data url.Values) (*http.Response, error) {
  981. return t.Delete(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
  982. }
  983. func (t *testHttpClient) send(method string, url string, bodyType string, body io.Reader) (*http.Response, error) {
  984. req, err := http.NewRequest(method, url, body)
  985. if err != nil {
  986. return nil, err
  987. }
  988. req.Header.Set("Content-Type", bodyType)
  989. return t.Do(req)
  990. }