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