diff --git a/client/keys.go b/client/keys.go index 60fff6231..7d7daeb34 100644 --- a/client/keys.go +++ b/client/keys.go @@ -55,8 +55,6 @@ func newHTTPKeysAPIWithPrefix(tr *http.Transport, ep string, to time.Duration, p return nil, err } - u.Path = path.Join(u.Path, prefix) - c := &httpClient{ transport: tr, endpoint: *u, @@ -65,6 +63,7 @@ func newHTTPKeysAPIWithPrefix(tr *http.Transport, ep string, to time.Duration, p kAPI := httpKeysAPI{ client: c, + prefix: prefix, } return &kAPI, nil @@ -102,12 +101,14 @@ func (n *Node) String() string { type httpKeysAPI struct { client *httpClient + prefix string } func (k *httpKeysAPI) Create(key, val string, ttl time.Duration) (*Response, error) { create := &createAction{ - Key: key, - Value: val, + Prefix: k.prefix, + Key: key, + Value: val, } if ttl >= 0 { uttl := uint64(ttl.Seconds()) @@ -124,6 +125,7 @@ func (k *httpKeysAPI) Create(key, val string, ttl time.Duration) (*Response, err func (k *httpKeysAPI) Get(key string) (*Response, error) { get := &getAction{ + Prefix: k.prefix, Key: key, Recursive: false, } @@ -140,6 +142,7 @@ func (k *httpKeysAPI) Watch(key string, idx uint64) Watcher { return &httpWatcher{ client: k.client, nextWait: waitAction{ + Prefix: k.prefix, Key: key, WaitIndex: idx, Recursive: false, @@ -151,6 +154,7 @@ func (k *httpKeysAPI) RecursiveWatch(key string, idx uint64) Watcher { return &httpWatcher{ client: k.client, nextWait: waitAction{ + Prefix: k.prefix, Key: key, WaitIndex: idx, Recursive: true, @@ -179,21 +183,24 @@ func (hw *httpWatcher) Next() (*Response, error) { return resp, nil } -// v2KeysURL forms a URL representing the location of a key. The provided -// endpoint must be the root of the etcd keys API. For example, a valid -// endpoint probably has the path "/v2/keys". -func v2KeysURL(ep url.URL, key string) *url.URL { - ep.Path = path.Join(ep.Path, key) +// v2KeysURL forms a URL representing the location of a key. +// The endpoint argument represents the base URL of an etcd +// server. The prefix is the path needed to route from the +// provided endpoint's path to the root of the keys API +// (typically "/v2/keys"). +func v2KeysURL(ep url.URL, prefix, key string) *url.URL { + ep.Path = path.Join(ep.Path, prefix, key) return &ep } type getAction struct { + Prefix string Key string Recursive bool } func (g *getAction) httpRequest(ep url.URL) *http.Request { - u := v2KeysURL(ep, g.Key) + u := v2KeysURL(ep, g.Prefix, g.Key) params := u.Query() params.Set("recursive", strconv.FormatBool(g.Recursive)) @@ -204,13 +211,14 @@ func (g *getAction) httpRequest(ep url.URL) *http.Request { } type waitAction struct { + Prefix string Key string WaitIndex uint64 Recursive bool } func (w *waitAction) httpRequest(ep url.URL) *http.Request { - u := v2KeysURL(ep, w.Key) + u := v2KeysURL(ep, w.Prefix, w.Key) params := u.Query() params.Set("wait", "true") @@ -223,13 +231,14 @@ func (w *waitAction) httpRequest(ep url.URL) *http.Request { } type createAction struct { - Key string - Value string - TTL *uint64 + Prefix string + Key string + Value string + TTL *uint64 } func (c *createAction) httpRequest(ep url.URL) *http.Request { - u := v2KeysURL(ep, c.Key) + u := v2KeysURL(ep, c.Prefix, c.Key) params := u.Query() params.Set("prevExist", "false") diff --git a/client/keys_test.go b/client/keys_test.go index d2428c72d..ad7b4b1c0 100644 --- a/client/keys_test.go +++ b/client/keys_test.go @@ -29,12 +29,14 @@ import ( func TestV2KeysURLHelper(t *testing.T) { tests := []struct { endpoint url.URL + prefix string key string want url.URL }{ // key is empty, no problem { endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"}, + prefix: "", key: "", want: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"}, }, @@ -42,6 +44,7 @@ func TestV2KeysURLHelper(t *testing.T) { // key is joined to path { endpoint: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys"}, + prefix: "", key: "/foo/bar", want: url.URL{Scheme: "http", Host: "example.com", Path: "/v2/keys/foo/bar"}, }, @@ -49,6 +52,7 @@ func TestV2KeysURLHelper(t *testing.T) { // key is joined to path when path is empty { endpoint: url.URL{Scheme: "http", Host: "example.com", Path: ""}, + prefix: "", key: "/foo/bar", want: url.URL{Scheme: "http", Host: "example.com", Path: "/foo/bar"}, }, @@ -56,6 +60,7 @@ func TestV2KeysURLHelper(t *testing.T) { // Host field carries through with port { endpoint: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"}, + prefix: "", key: "", want: url.URL{Scheme: "http", Host: "example.com:8080", Path: "/v2/keys"}, }, @@ -63,13 +68,21 @@ func TestV2KeysURLHelper(t *testing.T) { // Scheme carries through { endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"}, + prefix: "", key: "", want: url.URL{Scheme: "https", Host: "example.com", Path: "/v2/keys"}, }, + // Prefix is applied + { + endpoint: url.URL{Scheme: "https", Host: "example.com", Path: "/foo"}, + prefix: "/bar", + key: "/baz", + want: url.URL{Scheme: "https", Host: "example.com", Path: "/foo/bar/baz"}, + }, } for i, tt := range tests { - got := v2KeysURL(tt.endpoint, tt.key) + got := v2KeysURL(tt.endpoint, tt.prefix, tt.key) if tt.want != *got { t.Errorf("#%d: want=%#v, got=%#v", i, tt.want, *got) } diff --git a/client/members.go b/client/members.go index d178b9a76..61f9a9cde 100644 --- a/client/members.go +++ b/client/members.go @@ -39,8 +39,6 @@ func NewMembersAPI(tr *http.Transport, ep string, to time.Duration) (MembersAPI, return nil, err } - u.Path = path.Join(u.Path, DefaultV2MembersPrefix) - c := &httpClient{ transport: tr, endpoint: *u, @@ -65,7 +63,8 @@ type httpMembersAPI struct { } func (m *httpMembersAPI) List() ([]httptypes.Member, error) { - code, body, err := m.client.doWithTimeout(&membersAPIActionList{}) + req := &membersAPIActionList{} + code, body, err := m.client.doWithTimeout(req) if err != nil { return nil, err } @@ -119,6 +118,7 @@ func (m *httpMembersAPI) Remove(memberID string) error { type membersAPIActionList struct{} func (l *membersAPIActionList) httpRequest(ep url.URL) *http.Request { + ep.Path = path.Join(ep.Path, DefaultV2MembersPrefix) req, _ := http.NewRequest("GET", ep.String(), nil) return req } @@ -128,7 +128,7 @@ type membersAPIActionRemove struct { } func (d *membersAPIActionRemove) httpRequest(ep url.URL) *http.Request { - ep.Path = path.Join(ep.Path, d.memberID) + ep.Path = path.Join(ep.Path, DefaultV2MembersPrefix, d.memberID) req, _ := http.NewRequest("DELETE", ep.String(), nil) return req } @@ -138,6 +138,7 @@ type membersAPIActionAdd struct { } func (a *membersAPIActionAdd) httpRequest(ep url.URL) *http.Request { + ep.Path = path.Join(ep.Path, DefaultV2MembersPrefix) m := httptypes.MemberCreateRequest{PeerURLs: a.peerURLs} b, _ := json.Marshal(&m) req, _ := http.NewRequest("POST", ep.String(), bytes.NewReader(b)) diff --git a/client/members_test.go b/client/members_test.go index 7d693a79f..a8975c0cc 100644 --- a/client/members_test.go +++ b/client/members_test.go @@ -25,7 +25,7 @@ import ( ) func TestMembersAPIActionList(t *testing.T) { - ep := url.URL{Scheme: "http", Host: "example.com/v2/members"} + ep := url.URL{Scheme: "http", Host: "example.com"} act := &membersAPIActionList{} wantURL := &url.URL{ @@ -42,7 +42,7 @@ func TestMembersAPIActionList(t *testing.T) { } func TestMembersAPIActionAdd(t *testing.T) { - ep := url.URL{Scheme: "http", Host: "example.com/v2/admin/members"} + ep := url.URL{Scheme: "http", Host: "example.com"} act := &membersAPIActionAdd{ peerURLs: types.URLs([]url.URL{ url.URL{Scheme: "https", Host: "127.0.0.1:8081"}, @@ -53,7 +53,7 @@ func TestMembersAPIActionAdd(t *testing.T) { wantURL := &url.URL{ Scheme: "http", Host: "example.com", - Path: "/v2/admin/members", + Path: "/v2/members", } wantHeader := http.Header{ "Content-Type": []string{"application/json"}, @@ -68,7 +68,7 @@ func TestMembersAPIActionAdd(t *testing.T) { } func TestMembersAPIActionRemove(t *testing.T) { - ep := url.URL{Scheme: "http", Host: "example.com/v2/members"} + ep := url.URL{Scheme: "http", Host: "example.com"} act := &membersAPIActionRemove{memberID: "XXX"} wantURL := &url.URL{