v2_http_kv_test.go 22 KB

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