v2_http_kv_test.go 24 KB

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