v2_http_kv_test.go 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093
  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, err := tc.PutForm(fmt.Sprintf("%s%s", u, tt.relativeURL), tt.value)
  175. if err != nil {
  176. t.Fatalf("#%d: put err = %v, want nil", i, err)
  177. }
  178. if resp.StatusCode != tt.wStatus {
  179. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  180. }
  181. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  182. t.Errorf("#%d: %v", i, err)
  183. }
  184. }
  185. }
  186. func TestV2CAS(t *testing.T) {
  187. cl := NewCluster(t, 1)
  188. cl.Launch(t)
  189. defer cl.Terminate(t)
  190. u := cl.URL(0)
  191. tc := NewTestClient()
  192. tests := []struct {
  193. relativeURL string
  194. value url.Values
  195. wStatus int
  196. w map[string]interface{}
  197. }{
  198. {
  199. "/v2/keys/cas/foo",
  200. url.Values(map[string][]string{"value": {"XXX"}}),
  201. http.StatusCreated,
  202. nil,
  203. },
  204. {
  205. "/v2/keys/cas/foo",
  206. url.Values(map[string][]string{"value": {"YYY"}, "prevIndex": {"4"}}),
  207. http.StatusOK,
  208. map[string]interface{}{
  209. "node": map[string]interface{}{
  210. "value": "YYY",
  211. "modifiedIndex": float64(5),
  212. },
  213. "action": "compareAndSwap",
  214. },
  215. },
  216. {
  217. "/v2/keys/cas/foo",
  218. url.Values(map[string][]string{"value": {"YYY"}, "prevIndex": {"10"}}),
  219. http.StatusPreconditionFailed,
  220. map[string]interface{}{
  221. "errorCode": float64(101),
  222. "message": "Compare failed",
  223. "cause": "[10 != 5]",
  224. "index": float64(5),
  225. },
  226. },
  227. {
  228. "/v2/keys/cas/foo",
  229. url.Values(map[string][]string{"value": {"YYY"}, "prevIndex": {"bad_index"}}),
  230. http.StatusBadRequest,
  231. map[string]interface{}{
  232. "errorCode": float64(203),
  233. "message": "The given index in POST form is not a number",
  234. },
  235. },
  236. {
  237. "/v2/keys/cas/foo",
  238. url.Values(map[string][]string{"value": {"ZZZ"}, "prevValue": {"YYY"}}),
  239. http.StatusOK,
  240. map[string]interface{}{
  241. "node": map[string]interface{}{
  242. "value": "ZZZ",
  243. },
  244. "action": "compareAndSwap",
  245. },
  246. },
  247. {
  248. "/v2/keys/cas/foo",
  249. url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {"bad_value"}}),
  250. http.StatusPreconditionFailed,
  251. map[string]interface{}{
  252. "errorCode": float64(101),
  253. "message": "Compare failed",
  254. "cause": "[bad_value != ZZZ]",
  255. },
  256. },
  257. // prevValue is required
  258. {
  259. "/v2/keys/cas/foo",
  260. url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {""}}),
  261. http.StatusBadRequest,
  262. map[string]interface{}{
  263. "errorCode": float64(201),
  264. },
  265. },
  266. {
  267. "/v2/keys/cas/foo",
  268. url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {"bad_value"}, "prevIndex": {"100"}}),
  269. http.StatusPreconditionFailed,
  270. map[string]interface{}{
  271. "errorCode": float64(101),
  272. "message": "Compare failed",
  273. "cause": "[bad_value != ZZZ] [100 != 6]",
  274. },
  275. },
  276. {
  277. "/v2/keys/cas/foo",
  278. url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {"ZZZ"}, "prevIndex": {"100"}}),
  279. http.StatusPreconditionFailed,
  280. map[string]interface{}{
  281. "errorCode": float64(101),
  282. "message": "Compare failed",
  283. "cause": "[100 != 6]",
  284. },
  285. },
  286. {
  287. "/v2/keys/cas/foo",
  288. url.Values(map[string][]string{"value": {"XXX"}, "prevValue": {"bad_value"}, "prevIndex": {"6"}}),
  289. http.StatusPreconditionFailed,
  290. map[string]interface{}{
  291. "errorCode": float64(101),
  292. "message": "Compare failed",
  293. "cause": "[bad_value != ZZZ]",
  294. },
  295. },
  296. }
  297. for i, tt := range tests {
  298. resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, tt.relativeURL), tt.value)
  299. if err != nil {
  300. t.Fatalf("#%d: put err = %v, want nil", i, err)
  301. }
  302. if resp.StatusCode != tt.wStatus {
  303. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  304. }
  305. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  306. t.Errorf("#%d: %v", i, err)
  307. }
  308. }
  309. }
  310. func TestV2Delete(t *testing.T) {
  311. cl := NewCluster(t, 1)
  312. cl.Launch(t)
  313. defer cl.Terminate(t)
  314. u := cl.URL(0)
  315. tc := NewTestClient()
  316. v := url.Values{}
  317. v.Set("value", "XXX")
  318. r, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo"), v)
  319. if err != nil {
  320. t.Error(err)
  321. }
  322. r.Body.Close()
  323. r, err = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/emptydir?dir=true"), v)
  324. if err != nil {
  325. t.Error(err)
  326. }
  327. r.Body.Close()
  328. r, err = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foodir/bar?dir=true"), v)
  329. if err != nil {
  330. t.Error(err)
  331. }
  332. r.Body.Close()
  333. tests := []struct {
  334. relativeURL string
  335. wStatus int
  336. w map[string]interface{}
  337. }{
  338. {
  339. "/v2/keys/foo",
  340. http.StatusOK,
  341. map[string]interface{}{
  342. "node": map[string]interface{}{
  343. "key": "/foo",
  344. },
  345. "prevNode": map[string]interface{}{
  346. "key": "/foo",
  347. "value": "XXX",
  348. },
  349. "action": "delete",
  350. },
  351. },
  352. {
  353. "/v2/keys/emptydir",
  354. http.StatusForbidden,
  355. map[string]interface{}{
  356. "errorCode": float64(102),
  357. "message": "Not a file",
  358. "cause": "/emptydir",
  359. },
  360. },
  361. {
  362. "/v2/keys/emptydir?dir=true",
  363. http.StatusOK,
  364. nil,
  365. },
  366. {
  367. "/v2/keys/foodir?dir=true",
  368. http.StatusForbidden,
  369. map[string]interface{}{
  370. "errorCode": float64(108),
  371. "message": "Directory not empty",
  372. "cause": "/foodir",
  373. },
  374. },
  375. {
  376. "/v2/keys/foodir?recursive=true",
  377. http.StatusOK,
  378. map[string]interface{}{
  379. "node": map[string]interface{}{
  380. "key": "/foodir",
  381. "dir": true,
  382. },
  383. "prevNode": map[string]interface{}{
  384. "key": "/foodir",
  385. "dir": true,
  386. },
  387. "action": "delete",
  388. },
  389. },
  390. }
  391. for i, tt := range tests {
  392. resp, err := tc.DeleteForm(fmt.Sprintf("%s%s", u, tt.relativeURL), nil)
  393. if err != nil {
  394. t.Fatalf("#%d: delete err = %v, want nil", i, err)
  395. }
  396. if resp.StatusCode != tt.wStatus {
  397. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  398. }
  399. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  400. t.Errorf("#%d: %v", i, err)
  401. }
  402. }
  403. }
  404. func TestV2CAD(t *testing.T) {
  405. cl := NewCluster(t, 1)
  406. cl.Launch(t)
  407. defer cl.Terminate(t)
  408. u := cl.URL(0)
  409. tc := NewTestClient()
  410. v := url.Values{}
  411. v.Set("value", "XXX")
  412. r, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo"), v)
  413. if err != nil {
  414. t.Error(err)
  415. }
  416. r.Body.Close()
  417. r, err = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foovalue"), v)
  418. if err != nil {
  419. t.Error(err)
  420. }
  421. r.Body.Close()
  422. tests := []struct {
  423. relativeURL string
  424. wStatus int
  425. w map[string]interface{}
  426. }{
  427. {
  428. "/v2/keys/foo?prevIndex=100",
  429. http.StatusPreconditionFailed,
  430. map[string]interface{}{
  431. "errorCode": float64(101),
  432. "message": "Compare failed",
  433. "cause": "[100 != 4]",
  434. },
  435. },
  436. {
  437. "/v2/keys/foo?prevIndex=bad_index",
  438. http.StatusBadRequest,
  439. map[string]interface{}{
  440. "errorCode": float64(203),
  441. "message": "The given index in POST form is not a number",
  442. },
  443. },
  444. {
  445. "/v2/keys/foo?prevIndex=4",
  446. http.StatusOK,
  447. map[string]interface{}{
  448. "node": map[string]interface{}{
  449. "key": "/foo",
  450. "modifiedIndex": float64(6),
  451. },
  452. "action": "compareAndDelete",
  453. },
  454. },
  455. {
  456. "/v2/keys/foovalue?prevValue=YYY",
  457. http.StatusPreconditionFailed,
  458. map[string]interface{}{
  459. "errorCode": float64(101),
  460. "message": "Compare failed",
  461. "cause": "[YYY != XXX]",
  462. },
  463. },
  464. {
  465. "/v2/keys/foovalue?prevValue=",
  466. http.StatusBadRequest,
  467. map[string]interface{}{
  468. "errorCode": float64(201),
  469. "cause": `"prevValue" cannot be empty`,
  470. },
  471. },
  472. {
  473. "/v2/keys/foovalue?prevValue=XXX",
  474. http.StatusOK,
  475. map[string]interface{}{
  476. "node": map[string]interface{}{
  477. "key": "/foovalue",
  478. "modifiedIndex": float64(7),
  479. },
  480. "action": "compareAndDelete",
  481. },
  482. },
  483. }
  484. for i, tt := range tests {
  485. resp, err := tc.DeleteForm(fmt.Sprintf("%s%s", u, tt.relativeURL), nil)
  486. if err != nil {
  487. t.Fatalf("#%d: delete err = %v, want nil", i, err)
  488. }
  489. if resp.StatusCode != tt.wStatus {
  490. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  491. }
  492. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  493. t.Errorf("#%d: %v", i, err)
  494. }
  495. }
  496. }
  497. func TestV2Unique(t *testing.T) {
  498. cl := NewCluster(t, 1)
  499. cl.Launch(t)
  500. defer cl.Terminate(t)
  501. u := cl.URL(0)
  502. tc := NewTestClient()
  503. tests := []struct {
  504. relativeURL string
  505. value url.Values
  506. wStatus int
  507. w map[string]interface{}
  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/4",
  516. "value": "XXX",
  517. },
  518. "action": "create",
  519. },
  520. },
  521. {
  522. "/v2/keys/foo",
  523. url.Values(map[string][]string{"value": {"XXX"}}),
  524. http.StatusCreated,
  525. map[string]interface{}{
  526. "node": map[string]interface{}{
  527. "key": "/foo/5",
  528. "value": "XXX",
  529. },
  530. "action": "create",
  531. },
  532. },
  533. {
  534. "/v2/keys/bar",
  535. url.Values(map[string][]string{"value": {"XXX"}}),
  536. http.StatusCreated,
  537. map[string]interface{}{
  538. "node": map[string]interface{}{
  539. "key": "/bar/6",
  540. "value": "XXX",
  541. },
  542. "action": "create",
  543. },
  544. },
  545. }
  546. for i, tt := range tests {
  547. resp, err := tc.PostForm(fmt.Sprintf("%s%s", u, tt.relativeURL), tt.value)
  548. if err != nil {
  549. t.Fatalf("#%d: post err = %v, want nil", i, err)
  550. }
  551. if resp.StatusCode != tt.wStatus {
  552. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  553. }
  554. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  555. t.Errorf("#%d: %v", i, err)
  556. }
  557. }
  558. }
  559. func TestV2Get(t *testing.T) {
  560. cl := NewCluster(t, 1)
  561. cl.Launch(t)
  562. defer cl.Terminate(t)
  563. u := cl.URL(0)
  564. tc := NewTestClient()
  565. v := url.Values{}
  566. v.Set("value", "XXX")
  567. r, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar/zar"), v)
  568. if err != nil {
  569. t.Error(err)
  570. }
  571. r.Body.Close()
  572. tests := []struct {
  573. relativeURL string
  574. wStatus int
  575. w map[string]interface{}
  576. }{
  577. {
  578. "/v2/keys/foo/bar/zar",
  579. http.StatusOK,
  580. map[string]interface{}{
  581. "node": map[string]interface{}{
  582. "key": "/foo/bar/zar",
  583. "value": "XXX",
  584. },
  585. "action": "get",
  586. },
  587. },
  588. {
  589. "/v2/keys/foo",
  590. http.StatusOK,
  591. map[string]interface{}{
  592. "node": map[string]interface{}{
  593. "key": "/foo",
  594. "dir": true,
  595. "nodes": []interface{}{
  596. map[string]interface{}{
  597. "key": "/foo/bar",
  598. "dir": true,
  599. "createdIndex": float64(4),
  600. "modifiedIndex": float64(4),
  601. },
  602. },
  603. },
  604. "action": "get",
  605. },
  606. },
  607. {
  608. "/v2/keys/foo?recursive=true",
  609. http.StatusOK,
  610. map[string]interface{}{
  611. "node": map[string]interface{}{
  612. "key": "/foo",
  613. "dir": true,
  614. "nodes": []interface{}{
  615. map[string]interface{}{
  616. "key": "/foo/bar",
  617. "dir": true,
  618. "createdIndex": float64(4),
  619. "modifiedIndex": float64(4),
  620. "nodes": []interface{}{
  621. map[string]interface{}{
  622. "key": "/foo/bar/zar",
  623. "value": "XXX",
  624. "createdIndex": float64(4),
  625. "modifiedIndex": float64(4),
  626. },
  627. },
  628. },
  629. },
  630. },
  631. "action": "get",
  632. },
  633. },
  634. }
  635. for i, tt := range tests {
  636. resp, err := tc.Get(fmt.Sprintf("%s%s", u, tt.relativeURL))
  637. if err != nil {
  638. t.Fatalf("#%d: get err = %v, want nil", i, err)
  639. }
  640. if resp.StatusCode != tt.wStatus {
  641. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  642. }
  643. if resp.Header.Get("Content-Type") != "application/json" {
  644. t.Errorf("#%d: header = %v, want %v", i, resp.Header.Get("Content-Type"), "application/json")
  645. }
  646. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  647. t.Errorf("#%d: %v", i, err)
  648. }
  649. }
  650. }
  651. func TestV2QuorumGet(t *testing.T) {
  652. cl := NewCluster(t, 1)
  653. cl.Launch(t)
  654. defer cl.Terminate(t)
  655. u := cl.URL(0)
  656. tc := NewTestClient()
  657. v := url.Values{}
  658. v.Set("value", "XXX")
  659. r, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar/zar?quorum=true"), v)
  660. if err != nil {
  661. t.Error(err)
  662. }
  663. r.Body.Close()
  664. tests := []struct {
  665. relativeURL string
  666. wStatus int
  667. w map[string]interface{}
  668. }{
  669. {
  670. "/v2/keys/foo/bar/zar",
  671. http.StatusOK,
  672. map[string]interface{}{
  673. "node": map[string]interface{}{
  674. "key": "/foo/bar/zar",
  675. "value": "XXX",
  676. },
  677. "action": "get",
  678. },
  679. },
  680. {
  681. "/v2/keys/foo",
  682. http.StatusOK,
  683. map[string]interface{}{
  684. "node": map[string]interface{}{
  685. "key": "/foo",
  686. "dir": true,
  687. "nodes": []interface{}{
  688. map[string]interface{}{
  689. "key": "/foo/bar",
  690. "dir": true,
  691. "createdIndex": float64(4),
  692. "modifiedIndex": float64(4),
  693. },
  694. },
  695. },
  696. "action": "get",
  697. },
  698. },
  699. {
  700. "/v2/keys/foo?recursive=true",
  701. http.StatusOK,
  702. map[string]interface{}{
  703. "node": map[string]interface{}{
  704. "key": "/foo",
  705. "dir": true,
  706. "nodes": []interface{}{
  707. map[string]interface{}{
  708. "key": "/foo/bar",
  709. "dir": true,
  710. "createdIndex": float64(4),
  711. "modifiedIndex": float64(4),
  712. "nodes": []interface{}{
  713. map[string]interface{}{
  714. "key": "/foo/bar/zar",
  715. "value": "XXX",
  716. "createdIndex": float64(4),
  717. "modifiedIndex": float64(4),
  718. },
  719. },
  720. },
  721. },
  722. },
  723. "action": "get",
  724. },
  725. },
  726. }
  727. for i, tt := range tests {
  728. resp, err := tc.Get(fmt.Sprintf("%s%s", u, tt.relativeURL))
  729. if err != nil {
  730. t.Fatalf("#%d: get err = %v, want nil", i, err)
  731. }
  732. if resp.StatusCode != tt.wStatus {
  733. t.Errorf("#%d: status = %d, want %d", i, resp.StatusCode, tt.wStatus)
  734. }
  735. if resp.Header.Get("Content-Type") != "application/json" {
  736. t.Errorf("#%d: header = %v, want %v", i, resp.Header.Get("Content-Type"), "application/json")
  737. }
  738. if err := checkBody(tc.ReadBodyJSON(resp), tt.w); err != nil {
  739. t.Errorf("#%d: %v", i, err)
  740. }
  741. }
  742. }
  743. func TestV2Watch(t *testing.T) {
  744. cl := NewCluster(t, 1)
  745. cl.Launch(t)
  746. defer cl.Terminate(t)
  747. u := cl.URL(0)
  748. tc := NewTestClient()
  749. watchResp, err := tc.Get(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar?wait=true"))
  750. if err != nil {
  751. t.Fatalf("watch err = %v, want nil", err)
  752. }
  753. // Set a value.
  754. v := url.Values{}
  755. v.Set("value", "XXX")
  756. resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar"), v)
  757. if err != nil {
  758. t.Fatalf("put err = %v, want nil", err)
  759. }
  760. resp.Body.Close()
  761. body := tc.ReadBodyJSON(watchResp)
  762. w := map[string]interface{}{
  763. "node": map[string]interface{}{
  764. "key": "/foo/bar",
  765. "value": "XXX",
  766. "modifiedIndex": float64(4),
  767. },
  768. "action": "set",
  769. }
  770. if err := checkBody(body, w); err != nil {
  771. t.Error(err)
  772. }
  773. }
  774. func TestV2WatchWithIndex(t *testing.T) {
  775. cl := NewCluster(t, 1)
  776. cl.Launch(t)
  777. defer cl.Terminate(t)
  778. u := cl.URL(0)
  779. tc := NewTestClient()
  780. var body map[string]interface{}
  781. c := make(chan bool, 1)
  782. go func() {
  783. resp, err := tc.Get(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar?wait=true&waitIndex=5"))
  784. if err != nil {
  785. t.Fatalf("watch err = %v, want nil", err)
  786. }
  787. body = tc.ReadBodyJSON(resp)
  788. c <- true
  789. }()
  790. select {
  791. case <-c:
  792. t.Fatal("should not get the watch result")
  793. case <-time.After(time.Millisecond):
  794. }
  795. // Set a value (before given index).
  796. v := url.Values{}
  797. v.Set("value", "XXX")
  798. resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar"), v)
  799. if err != nil {
  800. t.Fatalf("put err = %v, want nil", err)
  801. }
  802. resp.Body.Close()
  803. select {
  804. case <-c:
  805. t.Fatal("should not get the watch result")
  806. case <-time.After(time.Millisecond):
  807. }
  808. // Set a value (before given index).
  809. resp, err = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar"), v)
  810. if err != nil {
  811. t.Fatalf("put err = %v, want nil", err)
  812. }
  813. resp.Body.Close()
  814. select {
  815. case <-c:
  816. case <-time.After(time.Second):
  817. t.Fatal("cannot get watch result")
  818. }
  819. w := map[string]interface{}{
  820. "node": map[string]interface{}{
  821. "key": "/foo/bar",
  822. "value": "XXX",
  823. "modifiedIndex": float64(5),
  824. },
  825. "action": "set",
  826. }
  827. if err := checkBody(body, w); err != nil {
  828. t.Error(err)
  829. }
  830. }
  831. func TestV2WatchKeyInDir(t *testing.T) {
  832. cl := NewCluster(t, 1)
  833. cl.Launch(t)
  834. defer cl.Terminate(t)
  835. u := cl.URL(0)
  836. tc := NewTestClient()
  837. var body map[string]interface{}
  838. c := make(chan bool)
  839. // Create an expiring directory
  840. v := url.Values{}
  841. v.Set("dir", "true")
  842. v.Set("ttl", "1")
  843. resp, err := tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/keyindir"), v)
  844. if err != nil {
  845. t.Fatalf("put err = %v, want nil", err)
  846. }
  847. resp.Body.Close()
  848. // Create a permanent node within the directory
  849. v = url.Values{}
  850. v.Set("value", "XXX")
  851. resp, err = tc.PutForm(fmt.Sprintf("%s%s", u, "/v2/keys/keyindir/bar"), v)
  852. if err != nil {
  853. t.Fatalf("put err = %v, want nil", err)
  854. }
  855. resp.Body.Close()
  856. go func() {
  857. // Expect a notification when watching the node
  858. resp, err := tc.Get(fmt.Sprintf("%s%s", u, "/v2/keys/keyindir/bar?wait=true"))
  859. if err != nil {
  860. t.Fatalf("watch err = %v, want nil", err)
  861. }
  862. body = tc.ReadBodyJSON(resp)
  863. c <- true
  864. }()
  865. select {
  866. case <-c:
  867. // 1s ttl + 0.5s sync delay + 1.5s disk and network delay
  868. // We set that long disk and network delay because travis may be slow
  869. // when do system calls.
  870. case <-time.After(3 * time.Second):
  871. t.Fatal("timed out waiting for watch result")
  872. }
  873. w := map[string]interface{}{
  874. "node": map[string]interface{}{
  875. "key": "/keyindir",
  876. },
  877. "action": "expire",
  878. }
  879. if err := checkBody(body, w); err != nil {
  880. t.Error(err)
  881. }
  882. }
  883. func TestV2Head(t *testing.T) {
  884. cl := NewCluster(t, 1)
  885. cl.Launch(t)
  886. defer cl.Terminate(t)
  887. u := cl.URL(0)
  888. tc := NewTestClient()
  889. v := url.Values{}
  890. v.Set("value", "XXX")
  891. fullURL := fmt.Sprintf("%s%s", u, "/v2/keys/foo/bar")
  892. resp, err := tc.Head(fullURL)
  893. if err != nil {
  894. t.Fatalf("head err = %v, want nil", err)
  895. }
  896. resp.Body.Close()
  897. if resp.StatusCode != http.StatusNotFound {
  898. t.Errorf("status = %d, want %d", resp.StatusCode, http.StatusNotFound)
  899. }
  900. if resp.ContentLength <= 0 {
  901. t.Errorf("ContentLength = %d, want > 0", resp.ContentLength)
  902. }
  903. resp, err = tc.PutForm(fullURL, v)
  904. if err != nil {
  905. t.Fatalf("put err = %v, want nil", err)
  906. }
  907. resp.Body.Close()
  908. resp, err = tc.Head(fullURL)
  909. if err != nil {
  910. t.Fatalf("head err = %v, want nil", err)
  911. }
  912. resp.Body.Close()
  913. if resp.StatusCode != http.StatusOK {
  914. t.Errorf("status = %d, want %d", resp.StatusCode, http.StatusOK)
  915. }
  916. if resp.ContentLength <= 0 {
  917. t.Errorf("ContentLength = %d, want > 0", resp.ContentLength)
  918. }
  919. }
  920. func checkBody(body map[string]interface{}, w map[string]interface{}) error {
  921. if body["node"] != nil {
  922. if w["node"] != nil {
  923. wn := w["node"].(map[string]interface{})
  924. n := body["node"].(map[string]interface{})
  925. for k := range n {
  926. if wn[k] == nil {
  927. delete(n, k)
  928. }
  929. }
  930. body["node"] = n
  931. }
  932. if w["prevNode"] != nil {
  933. wn := w["prevNode"].(map[string]interface{})
  934. n := body["prevNode"].(map[string]interface{})
  935. for k := range n {
  936. if wn[k] == nil {
  937. delete(n, k)
  938. }
  939. }
  940. body["prevNode"] = n
  941. }
  942. }
  943. for k, v := range w {
  944. g := body[k]
  945. if !reflect.DeepEqual(g, v) {
  946. return fmt.Errorf("%v = %+v, want %+v", k, g, v)
  947. }
  948. }
  949. return nil
  950. }
  951. type testHttpClient struct {
  952. *http.Client
  953. }
  954. // Creates a new HTTP client with KeepAlive disabled.
  955. func NewTestClient() *testHttpClient {
  956. tr := &http.Transport{
  957. Dial: (&net.Dialer{Timeout: time.Second}).Dial,
  958. DisableKeepAlives: true,
  959. }
  960. return &testHttpClient{&http.Client{Transport: tr}}
  961. }
  962. // Reads the body from the response and closes it.
  963. func (t *testHttpClient) ReadBody(resp *http.Response) []byte {
  964. if resp == nil {
  965. return []byte{}
  966. }
  967. body, _ := ioutil.ReadAll(resp.Body)
  968. resp.Body.Close()
  969. return body
  970. }
  971. // Reads the body from the response and parses it as JSON.
  972. func (t *testHttpClient) ReadBodyJSON(resp *http.Response) map[string]interface{} {
  973. m := make(map[string]interface{})
  974. b := t.ReadBody(resp)
  975. if err := json.Unmarshal(b, &m); err != nil {
  976. panic(fmt.Sprintf("HTTP body JSON parse error: %v: %s", err, string(b)))
  977. }
  978. return m
  979. }
  980. func (t *testHttpClient) Head(url string) (*http.Response, error) {
  981. return t.send("HEAD", url, "application/json", nil)
  982. }
  983. func (t *testHttpClient) Get(url string) (*http.Response, error) {
  984. return t.send("GET", url, "application/json", nil)
  985. }
  986. func (t *testHttpClient) Post(url string, bodyType string, body io.Reader) (*http.Response, error) {
  987. return t.send("POST", url, bodyType, body)
  988. }
  989. func (t *testHttpClient) PostForm(url string, data url.Values) (*http.Response, error) {
  990. return t.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
  991. }
  992. func (t *testHttpClient) Put(url string, bodyType string, body io.Reader) (*http.Response, error) {
  993. return t.send("PUT", url, bodyType, body)
  994. }
  995. func (t *testHttpClient) PutForm(url string, data url.Values) (*http.Response, error) {
  996. return t.Put(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
  997. }
  998. func (t *testHttpClient) Delete(url string, bodyType string, body io.Reader) (*http.Response, error) {
  999. return t.send("DELETE", url, bodyType, body)
  1000. }
  1001. func (t *testHttpClient) DeleteForm(url string, data url.Values) (*http.Response, error) {
  1002. return t.Delete(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
  1003. }
  1004. func (t *testHttpClient) send(method string, url string, bodyType string, body io.Reader) (*http.Response, error) {
  1005. req, err := http.NewRequest(method, url, body)
  1006. if err != nil {
  1007. return nil, err
  1008. }
  1009. req.Header.Set("Content-Type", bodyType)
  1010. return t.Do(req)
  1011. }