v2_http_kv_test.go 26 KB

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