diff --git a/client/error.go b/client/error.go new file mode 100644 index 000000000..72c7edd0d --- /dev/null +++ b/client/error.go @@ -0,0 +1,24 @@ +// Copyright 2015 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 + +type HTTPError struct { + Message string `json:"message"` + Code int `json:"-"` +} + +func (e HTTPError) Error() string { + return e.Message +} diff --git a/client/members.go b/client/members.go index 1e04919fb..6dc2fe622 100644 --- a/client/members.go +++ b/client/members.go @@ -23,7 +23,7 @@ import ( "path" "github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context" - "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes" + "github.com/coreos/etcd/pkg/types" ) @@ -31,7 +31,50 @@ var ( defaultV2MembersPrefix = "/v2/members" ) -type Member httptypes.Member +type Member struct { + ID string `json:"id"` + Name string `json:"name"` + PeerURLs []string `json:"peerURLs"` + ClientURLs []string `json:"clientURLs"` +} + +type memberCollection []Member + +func (c *memberCollection) UnmarshalJSON(data []byte) error { + d := struct { + Members []Member + }{} + + if err := json.Unmarshal(data, &d); err != nil { + return err + } + + if d.Members == nil { + *c = make([]Member, 0) + return nil + } + + *c = d.Members + return nil +} + +type memberCreateRequest struct { + PeerURLs types.URLs +} + +func (m *memberCreateRequest) MarshalJSON() ([]byte, error) { + s := struct { + PeerURLs []string `json:"peerURLs"` + }{ + PeerURLs: make([]string, len(m.PeerURLs)), + } + + for i, u := range m.PeerURLs { + s.PeerURLs[i] = u.String() + } + + return json.Marshal(&s) +} // NewMembersAPI constructs a new MembersAPI that uses HTTP to // interact with etcd's membership API. @@ -67,17 +110,12 @@ func (m *httpMembersAPI) List(ctx context.Context) ([]Member, error) { return nil, err } - var mCollection httptypes.MemberCollection + var mCollection memberCollection if err := json.Unmarshal(body, &mCollection); err != nil { return nil, err } - ms := make([]Member, len(mCollection)) - for i, m := range mCollection { - m := Member(m) - ms[i] = m - } - return ms, nil + return []Member(mCollection), nil } func (m *httpMembersAPI) Add(ctx context.Context, peerURL string) (*Member, error) { @@ -97,7 +135,7 @@ func (m *httpMembersAPI) Add(ctx context.Context, peerURL string) (*Member, erro } if resp.StatusCode != http.StatusCreated { - var httperr httptypes.HTTPError + var httperr HTTPError if err := json.Unmarshal(body, &httperr); err != nil { return nil, err } @@ -147,7 +185,7 @@ type membersAPIActionAdd struct { func (a *membersAPIActionAdd) HTTPRequest(ep url.URL) *http.Request { u := v2MembersURL(ep) - m := httptypes.MemberCreateRequest{PeerURLs: a.peerURLs} + m := memberCreateRequest{PeerURLs: a.peerURLs} b, _ := json.Marshal(&m) req, _ := http.NewRequest("POST", u.String(), bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") diff --git a/client/members_test.go b/client/members_test.go index d479ba30f..d953c43f7 100644 --- a/client/members_test.go +++ b/client/members_test.go @@ -15,6 +15,7 @@ package client import ( + "encoding/json" "net/http" "net/url" "reflect" @@ -109,3 +110,156 @@ func TestV2MembersURL(t *testing.T) { t.Fatalf("v2MembersURL got %#v, want %#v", got, want) } } + +func TestMemberUnmarshal(t *testing.T) { + tests := []struct { + body []byte + wantMember Member + wantError bool + }{ + // no URLs, just check ID & Name + { + body: []byte(`{"id": "c", "name": "dungarees"}`), + wantMember: Member{ID: "c", Name: "dungarees", PeerURLs: nil, ClientURLs: nil}, + }, + + // both client and peer URLs + { + body: []byte(`{"peerURLs": ["http://127.0.0.1:4001"], "clientURLs": ["http://127.0.0.1:4001"]}`), + wantMember: Member{ + PeerURLs: []string{ + "http://127.0.0.1:4001", + }, + ClientURLs: []string{ + "http://127.0.0.1:4001", + }, + }, + }, + + // multiple peer URLs + { + body: []byte(`{"peerURLs": ["http://127.0.0.1:4001", "https://example.com"]}`), + wantMember: Member{ + PeerURLs: []string{ + "http://127.0.0.1:4001", + "https://example.com", + }, + ClientURLs: nil, + }, + }, + + // multiple client URLs + { + body: []byte(`{"clientURLs": ["http://127.0.0.1:4001", "https://example.com"]}`), + wantMember: Member{ + PeerURLs: nil, + ClientURLs: []string{ + "http://127.0.0.1:4001", + "https://example.com", + }, + }, + }, + + // invalid JSON + { + body: []byte(`{"peerU`), + 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 TestMemberCollectionUnmarshal(t *testing.T) { + tests := []struct { + body []byte + want memberCollection + }{ + { + body: []byte(`{"members":[]}`), + want: memberCollection([]Member{}), + }, + { + body: []byte(`{"members":[{"id":"2745e2525fce8fe","peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":"42134f434382925","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":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`), + want: memberCollection( + []Member{ + { + ID: "2745e2525fce8fe", + Name: "node3", + PeerURLs: []string{ + "http://127.0.0.1:7003", + }, + ClientURLs: []string{ + "http://127.0.0.1:4003", + }, + }, + { + ID: "42134f434382925", + Name: "node1", + PeerURLs: []string{ + "http://127.0.0.1:2380", + "http://127.0.0.1:7001", + }, + ClientURLs: []string{ + "http://127.0.0.1:2379", + "http://127.0.0.1:4001", + }, + }, + { + ID: "94088180e21eb87b", + Name: "node2", + PeerURLs: []string{ + "http://127.0.0.1:7002", + }, + ClientURLs: []string{ + "http://127.0.0.1:4002", + }, + }, + }, + ), + }, + } + + for i, tt := range tests { + var got memberCollection + err := json.Unmarshal(tt.body, &got) + if err != nil { + t.Errorf("#%d: unexpected error: %v", i, err) + continue + } + + if !reflect.DeepEqual(tt.want, got) { + t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.want, got) + } + } +} + +func TestMemberCreateRequestMarshal(t *testing.T) { + req := memberCreateRequest{ + PeerURLs: types.URLs([]url.URL{ + url.URL{Scheme: "http", Host: "127.0.0.1:8081"}, + url.URL{Scheme: "https", Host: "127.0.0.1:8080"}, + }), + } + want := []byte(`{"peerURLs":["http://127.0.0.1:8081","https://127.0.0.1:8080"]}`) + + got, err := json.Marshal(&req) + if err != nil { + t.Fatalf("Marshal returned unexpected err=%v", err) + } + + if !reflect.DeepEqual(want, got) { + t.Fatalf("Failed to marshal memberCreateRequest: want=%s, got=%s", want, got) + } +} diff --git a/etcdserver/etcdhttp/httptypes/member.go b/etcdserver/etcdhttp/httptypes/member.go index 4f713c76c..30ecbb539 100644 --- a/etcdserver/etcdhttp/httptypes/member.go +++ b/etcdserver/etcdhttp/httptypes/member.go @@ -35,20 +35,6 @@ type MemberUpdateRequest struct { MemberCreateRequest } -func (m *MemberCreateRequest) MarshalJSON() ([]byte, error) { - s := struct { - PeerURLs []string `json:"peerURLs"` - }{ - PeerURLs: make([]string, len(m.PeerURLs)), - } - - for i, u := range m.PeerURLs { - s.PeerURLs[i] = u.String() - } - - return json.Marshal(&s) -} - func (m *MemberCreateRequest) UnmarshalJSON(data []byte) error { s := struct { PeerURLs []string `json:"peerURLs"` @@ -79,21 +65,3 @@ func (c *MemberCollection) MarshalJSON() ([]byte, error) { return json.Marshal(d) } - -func (c *MemberCollection) UnmarshalJSON(data []byte) error { - d := struct { - Members []Member - }{} - - if err := json.Unmarshal(data, &d); err != nil { - return err - } - - if d.Members == nil { - *c = make([]Member, 0) - return nil - } - - *c = d.Members - return nil -} diff --git a/etcdserver/etcdhttp/httptypes/member_test.go b/etcdserver/etcdhttp/httptypes/member_test.go index a922a638b..e0b29d883 100644 --- a/etcdserver/etcdhttp/httptypes/member_test.go +++ b/etcdserver/etcdhttp/httptypes/member_test.go @@ -93,70 +93,6 @@ func TestMemberUnmarshal(t *testing.T) { } } -func TestMemberCollectionUnmarshal(t *testing.T) { - tests := []struct { - body []byte - want MemberCollection - }{ - { - body: []byte(`{"members":[]}`), - want: MemberCollection([]Member{}), - }, - { - body: []byte(`{"members":[{"id":"2745e2525fce8fe","peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":"42134f434382925","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":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`), - want: MemberCollection( - []Member{ - { - ID: "2745e2525fce8fe", - Name: "node3", - PeerURLs: []string{ - "http://127.0.0.1:7003", - }, - ClientURLs: []string{ - "http://127.0.0.1:4003", - }, - }, - { - ID: "42134f434382925", - Name: "node1", - PeerURLs: []string{ - "http://127.0.0.1:2380", - "http://127.0.0.1:7001", - }, - ClientURLs: []string{ - "http://127.0.0.1:2379", - "http://127.0.0.1:4001", - }, - }, - { - ID: "94088180e21eb87b", - Name: "node2", - PeerURLs: []string{ - "http://127.0.0.1:7002", - }, - ClientURLs: []string{ - "http://127.0.0.1:4002", - }, - }, - }, - ), - }, - } - - for i, tt := range tests { - var got MemberCollection - err := json.Unmarshal(tt.body, &got) - if err != nil { - t.Errorf("#%d: unexpected error: %v", i, err) - continue - } - - if !reflect.DeepEqual(tt.want, got) { - t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.want, got) - } - } -} - func TestMemberCreateRequestUnmarshal(t *testing.T) { body := []byte(`{"peerURLs": ["http://127.0.0.1:8081", "https://127.0.0.1:8080"]}`) want := MemberCreateRequest{ @@ -197,22 +133,3 @@ func TestMemberCreateRequestUnmarshalFail(t *testing.T) { } } } - -func TestMemberCreateRequestMarshal(t *testing.T) { - req := MemberCreateRequest{ - PeerURLs: types.URLs([]url.URL{ - url.URL{Scheme: "http", Host: "127.0.0.1:8081"}, - url.URL{Scheme: "https", Host: "127.0.0.1:8080"}, - }), - } - want := []byte(`{"peerURLs":["http://127.0.0.1:8081","https://127.0.0.1:8080"]}`) - - got, err := json.Marshal(&req) - if err != nil { - t.Fatalf("Marshal returned unexpected err=%v", err) - } - - if !reflect.DeepEqual(want, got) { - t.Fatalf("Failed to marshal MemberCreateRequest: want=%s, got=%s", want, got) - } -}