v2_http_kv_test.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  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. "net"
  21. "net/http"
  22. "net/url"
  23. "reflect"
  24. "strings"
  25. "testing"
  26. "time"
  27. "github.com/coreos/etcd/pkg/testutil"
  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 := &http.Transport{
  970. Dial: (&net.Dialer{Timeout: time.Second}).Dial,
  971. DisableKeepAlives: true,
  972. }
  973. return &testHttpClient{&http.Client{Transport: tr}}
  974. }
  975. // Reads the body from the response and closes it.
  976. func (t *testHttpClient) ReadBody(resp *http.Response) []byte {
  977. if resp == nil {
  978. return []byte{}
  979. }
  980. body, _ := ioutil.ReadAll(resp.Body)
  981. resp.Body.Close()
  982. return body
  983. }
  984. // Reads the body from the response and parses it as JSON.
  985. func (t *testHttpClient) ReadBodyJSON(resp *http.Response) map[string]interface{} {
  986. m := make(map[string]interface{})
  987. b := t.ReadBody(resp)
  988. if err := json.Unmarshal(b, &m); err != nil {
  989. panic(fmt.Sprintf("HTTP body JSON parse error: %v: %s", err, string(b)))
  990. }
  991. return m
  992. }
  993. func (t *testHttpClient) Head(url string) (*http.Response, error) {
  994. return t.send("HEAD", url, "application/json", nil)
  995. }
  996. func (t *testHttpClient) Get(url string) (*http.Response, error) {
  997. return t.send("GET", url, "application/json", nil)
  998. }
  999. func (t *testHttpClient) Post(url string, bodyType string, body io.Reader) (*http.Response, error) {
  1000. return t.send("POST", url, bodyType, body)
  1001. }
  1002. func (t *testHttpClient) PostForm(url string, data url.Values) (*http.Response, error) {
  1003. return t.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
  1004. }
  1005. func (t *testHttpClient) Put(url string, bodyType string, body io.Reader) (*http.Response, error) {
  1006. return t.send("PUT", url, bodyType, body)
  1007. }
  1008. func (t *testHttpClient) PutForm(url string, data url.Values) (*http.Response, error) {
  1009. return t.Put(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
  1010. }
  1011. func (t *testHttpClient) Delete(url string, bodyType string, body io.Reader) (*http.Response, error) {
  1012. return t.send("DELETE", url, bodyType, body)
  1013. }
  1014. func (t *testHttpClient) DeleteForm(url string, data url.Values) (*http.Response, error) {
  1015. return t.Delete(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
  1016. }
  1017. func (t *testHttpClient) send(method string, url string, bodyType string, body io.Reader) (*http.Response, error) {
  1018. req, err := http.NewRequest(method, url, body)
  1019. if err != nil {
  1020. return nil, err
  1021. }
  1022. req.Header.Set("Content-Type", bodyType)
  1023. return t.Do(req)
  1024. }