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  }