v2_http_kv_test.go 22 KB

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