v2_http_kv_test.go 27 KB

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