commit
73215447c1
|
@ -53,21 +53,6 @@ type httpClient struct {
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHTTPClient(tr *http.Transport, ep string, to time.Duration) (*httpClient, error) {
|
|
||||||
u, err := url.Parse(ep)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &httpClient{
|
|
||||||
transport: tr,
|
|
||||||
endpoint: *u,
|
|
||||||
timeout: to,
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpClient) doWithTimeout(act httpAction) (*http.Response, []byte, error) {
|
func (c *httpClient) doWithTimeout(act httpAction) (*http.Response, []byte, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
|
@ -49,13 +49,21 @@ func NewDiscoveryKeysAPI(tr *http.Transport, ep string, to time.Duration) (KeysA
|
||||||
return newHTTPKeysAPIWithPrefix(tr, ep, to, "")
|
return newHTTPKeysAPIWithPrefix(tr, ep, to, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHTTPKeysAPIWithPrefix(tr *http.Transport, ep string, to time.Duration, prefix string) (*HTTPKeysAPI, error) {
|
func newHTTPKeysAPIWithPrefix(tr *http.Transport, ep string, to time.Duration, prefix string) (*httpKeysAPI, error) {
|
||||||
c, err := newHTTPClient(tr, ep, to)
|
u, err := url.Parse(ep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
kAPI := HTTPKeysAPI{
|
u.Path = path.Join(u.Path, prefix)
|
||||||
|
|
||||||
|
c := &httpClient{
|
||||||
|
transport: tr,
|
||||||
|
endpoint: *u,
|
||||||
|
timeout: to,
|
||||||
|
}
|
||||||
|
|
||||||
|
kAPI := httpKeysAPI{
|
||||||
client: c,
|
client: c,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,11 +100,11 @@ func (n *Node) String() string {
|
||||||
return fmt.Sprintf("{Key: %s, CreatedIndex: %d, ModifiedIndex: %d}", n.Key, n.CreatedIndex, n.ModifiedIndex)
|
return fmt.Sprintf("{Key: %s, CreatedIndex: %d, ModifiedIndex: %d}", n.Key, n.CreatedIndex, n.ModifiedIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPKeysAPI struct {
|
type httpKeysAPI struct {
|
||||||
client *httpClient
|
client *httpClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *HTTPKeysAPI) Create(key, val string, ttl time.Duration) (*Response, error) {
|
func (k *httpKeysAPI) Create(key, val string, ttl time.Duration) (*Response, error) {
|
||||||
create := &createAction{
|
create := &createAction{
|
||||||
Key: key,
|
Key: key,
|
||||||
Value: val,
|
Value: val,
|
||||||
|
@ -114,7 +122,7 @@ func (k *HTTPKeysAPI) Create(key, val string, ttl time.Duration) (*Response, err
|
||||||
return unmarshalHTTPResponse(httpresp.StatusCode, body)
|
return unmarshalHTTPResponse(httpresp.StatusCode, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *HTTPKeysAPI) Get(key string) (*Response, error) {
|
func (k *httpKeysAPI) Get(key string) (*Response, error) {
|
||||||
get := &getAction{
|
get := &getAction{
|
||||||
Key: key,
|
Key: key,
|
||||||
Recursive: false,
|
Recursive: false,
|
||||||
|
@ -128,7 +136,7 @@ func (k *HTTPKeysAPI) Get(key string) (*Response, error) {
|
||||||
return unmarshalHTTPResponse(httpresp.StatusCode, body)
|
return unmarshalHTTPResponse(httpresp.StatusCode, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *HTTPKeysAPI) Watch(key string, idx uint64) Watcher {
|
func (k *httpKeysAPI) Watch(key string, idx uint64) Watcher {
|
||||||
return &httpWatcher{
|
return &httpWatcher{
|
||||||
client: k.client,
|
client: k.client,
|
||||||
nextWait: waitAction{
|
nextWait: waitAction{
|
||||||
|
@ -139,7 +147,7 @@ func (k *HTTPKeysAPI) Watch(key string, idx uint64) Watcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *HTTPKeysAPI) RecursiveWatch(key string, idx uint64) Watcher {
|
func (k *httpKeysAPI) RecursiveWatch(key string, idx uint64) Watcher {
|
||||||
return &httpWatcher{
|
return &httpWatcher{
|
||||||
client: k.client,
|
client: k.client,
|
||||||
nextWait: waitAction{
|
nextWait: waitAction{
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 CoreOS, Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultV2MembersPrefix = "/v2/admin/members"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewMembersAPI(tr *http.Transport, ep string, to time.Duration) (MembersAPI, error) {
|
||||||
|
u, err := url.Parse(ep)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u.Path = path.Join(u.Path, DefaultV2MembersPrefix)
|
||||||
|
|
||||||
|
c := &httpClient{
|
||||||
|
transport: tr,
|
||||||
|
endpoint: *u,
|
||||||
|
timeout: to,
|
||||||
|
}
|
||||||
|
|
||||||
|
mAPI := httpMembersAPI{
|
||||||
|
client: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mAPI, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MembersAPI interface {
|
||||||
|
List() ([]Member, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Member struct {
|
||||||
|
ID uint64
|
||||||
|
Name string
|
||||||
|
PeerURLs []url.URL
|
||||||
|
ClientURLs []url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Member) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
rm := struct {
|
||||||
|
ID uint64
|
||||||
|
Name string
|
||||||
|
PeerURLs []string
|
||||||
|
ClientURLs []string
|
||||||
|
}{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &rm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parseURLs := func(strs []string) ([]url.URL, error) {
|
||||||
|
urls := make([]url.URL, len(strs))
|
||||||
|
for i, s := range strs {
|
||||||
|
u, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
urls[i] = *u
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.PeerURLs, err = parseURLs(rm.PeerURLs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.ClientURLs, err = parseURLs(rm.ClientURLs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m.ID = rm.ID
|
||||||
|
m.Name = rm.Name
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type membersCollection struct {
|
||||||
|
Members []Member
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpMembersAPI struct {
|
||||||
|
client *httpClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *httpMembersAPI) List() ([]Member, error) {
|
||||||
|
httpresp, body, err := m.client.doWithTimeout(&membersAPIActionList{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mResponse := httpMembersAPIResponse{
|
||||||
|
code: httpresp.StatusCode,
|
||||||
|
body: body,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mResponse.err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var mCollection membersCollection
|
||||||
|
if err = mResponse.unmarshalBody(&mCollection); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mCollection.Members, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpMembersAPIResponse struct {
|
||||||
|
code int
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *httpMembersAPIResponse) err() (err error) {
|
||||||
|
if r.code != http.StatusOK {
|
||||||
|
err = fmt.Errorf("unrecognized status code %d", r.code)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *httpMembersAPIResponse) unmarshalBody(dst interface{}) (err error) {
|
||||||
|
return json.Unmarshal(r.body, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
type membersAPIActionList struct{}
|
||||||
|
|
||||||
|
func (l *membersAPIActionList) httpRequest(ep url.URL) *http.Request {
|
||||||
|
req, _ := http.NewRequest("GET", ep.String(), nil)
|
||||||
|
return req
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 CoreOS, Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMembersAPIListAction(t *testing.T) {
|
||||||
|
ep := url.URL{Scheme: "http", Host: "example.com/v2/admin/members"}
|
||||||
|
wantURL := &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "example.com",
|
||||||
|
Path: "/v2/admin/members",
|
||||||
|
}
|
||||||
|
|
||||||
|
act := &membersAPIActionList{}
|
||||||
|
got := *act.httpRequest(ep)
|
||||||
|
err := assertResponse(got, wantURL, http.Header{}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMembersAPIUnmarshalMember(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
body []byte
|
||||||
|
wantMember Member
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
// no URLs, just check ID & Name
|
||||||
|
{
|
||||||
|
body: []byte(`{"id": 1, "name": "dungarees"}`),
|
||||||
|
wantMember: Member{ID: 1, Name: "dungarees", PeerURLs: []url.URL{}, ClientURLs: []url.URL{}},
|
||||||
|
},
|
||||||
|
|
||||||
|
// both client and peer URLs
|
||||||
|
{
|
||||||
|
body: []byte(`{"peerURLs": ["http://127.0.0.1:4001"], "clientURLs": ["http://127.0.0.1:4001"]}`),
|
||||||
|
wantMember: Member{
|
||||||
|
PeerURLs: []url.URL{
|
||||||
|
{Scheme: "http", Host: "127.0.0.1:4001"},
|
||||||
|
},
|
||||||
|
ClientURLs: []url.URL{
|
||||||
|
{Scheme: "http", Host: "127.0.0.1:4001"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// multiple peer URLs
|
||||||
|
{
|
||||||
|
body: []byte(`{"peerURLs": ["http://127.0.0.1:4001", "https://example.com"]}`),
|
||||||
|
wantMember: Member{
|
||||||
|
PeerURLs: []url.URL{
|
||||||
|
{Scheme: "http", Host: "127.0.0.1:4001"},
|
||||||
|
{Scheme: "https", Host: "example.com"},
|
||||||
|
},
|
||||||
|
ClientURLs: []url.URL{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// multiple client URLs
|
||||||
|
{
|
||||||
|
body: []byte(`{"clientURLs": ["http://127.0.0.1:4001", "https://example.com"]}`),
|
||||||
|
wantMember: Member{
|
||||||
|
PeerURLs: []url.URL{},
|
||||||
|
ClientURLs: []url.URL{
|
||||||
|
{Scheme: "http", Host: "127.0.0.1:4001"},
|
||||||
|
{Scheme: "https", Host: "example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// invalid JSON
|
||||||
|
{
|
||||||
|
body: []byte(`{"peerU`),
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// valid JSON, invalid URL
|
||||||
|
{
|
||||||
|
body: []byte(`{"peerURLs": [":"]}`),
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
got := Member{}
|
||||||
|
err := json.Unmarshal(tt.body, &got)
|
||||||
|
if tt.wantError != (err != nil) {
|
||||||
|
t.Errorf("#%d: want error %t, got %v", i, tt.wantError, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tt.wantMember, got) {
|
||||||
|
t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.wantMember, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMembersAPIUnmarshalMembers(t *testing.T) {
|
||||||
|
body := []byte(`{"members":[{"id":176869799018424574,"peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":297577273835923749,"peerURLs":["http://127.0.0.1:2380","http://127.0.0.1:7001"],"name":"node1","clientURLs":["http://127.0.0.1:2379","http://127.0.0.1:4001"]},{"id":10666918107976480891,"peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`)
|
||||||
|
|
||||||
|
want := membersCollection{
|
||||||
|
Members: []Member{
|
||||||
|
{
|
||||||
|
ID: 176869799018424574,
|
||||||
|
Name: "node3",
|
||||||
|
PeerURLs: []url.URL{
|
||||||
|
{Scheme: "http", Host: "127.0.0.1:7003"},
|
||||||
|
},
|
||||||
|
ClientURLs: []url.URL{
|
||||||
|
{Scheme: "http", Host: "127.0.0.1:4003"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 297577273835923749,
|
||||||
|
Name: "node1",
|
||||||
|
PeerURLs: []url.URL{
|
||||||
|
{Scheme: "http", Host: "127.0.0.1:2380"},
|
||||||
|
{Scheme: "http", Host: "127.0.0.1:7001"},
|
||||||
|
},
|
||||||
|
ClientURLs: []url.URL{
|
||||||
|
{Scheme: "http", Host: "127.0.0.1:2379"},
|
||||||
|
{Scheme: "http", Host: "127.0.0.1:4001"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 10666918107976480891,
|
||||||
|
Name: "node2",
|
||||||
|
PeerURLs: []url.URL{
|
||||||
|
{Scheme: "http", Host: "127.0.0.1:7002"},
|
||||||
|
},
|
||||||
|
ClientURLs: []url.URL{
|
||||||
|
{Scheme: "http", Host: "127.0.0.1:4002"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := membersCollection{}
|
||||||
|
err := json.Unmarshal(body, &got)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(want, got) {
|
||||||
|
t.Errorf("Incorrect output: want=%#v, got=%#v", want, got)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue