123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598 |
- // Copyright 2015 The etcd Authors
- //
- // 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 (
- "context"
- "encoding/json"
- "errors"
- "net/http"
- "net/url"
- "reflect"
- "testing"
- "go.etcd.io/etcd/pkg/types"
- )
- func TestMembersAPIActionList(t *testing.T) {
- ep := url.URL{Scheme: "http", Host: "example.com"}
- act := &membersAPIActionList{}
- wantURL := &url.URL{
- Scheme: "http",
- Host: "example.com",
- Path: "/v2/members",
- }
- got := *act.HTTPRequest(ep)
- err := assertRequest(got, "GET", wantURL, http.Header{}, nil)
- if err != nil {
- t.Error(err.Error())
- }
- }
- func TestMembersAPIActionAdd(t *testing.T) {
- ep := url.URL{Scheme: "http", Host: "example.com"}
- act := &membersAPIActionAdd{
- peerURLs: types.URLs([]url.URL{
- {Scheme: "https", Host: "127.0.0.1:8081"},
- {Scheme: "http", Host: "127.0.0.1:8080"},
- }),
- }
- wantURL := &url.URL{
- Scheme: "http",
- Host: "example.com",
- Path: "/v2/members",
- }
- wantHeader := http.Header{
- "Content-Type": []string{"application/json"},
- }
- wantBody := []byte(`{"peerURLs":["https://127.0.0.1:8081","http://127.0.0.1:8080"]}`)
- got := *act.HTTPRequest(ep)
- err := assertRequest(got, "POST", wantURL, wantHeader, wantBody)
- if err != nil {
- t.Error(err.Error())
- }
- }
- func TestMembersAPIActionUpdate(t *testing.T) {
- ep := url.URL{Scheme: "http", Host: "example.com"}
- act := &membersAPIActionUpdate{
- memberID: "0xabcd",
- peerURLs: types.URLs([]url.URL{
- {Scheme: "https", Host: "127.0.0.1:8081"},
- {Scheme: "http", Host: "127.0.0.1:8080"},
- }),
- }
- wantURL := &url.URL{
- Scheme: "http",
- Host: "example.com",
- Path: "/v2/members/0xabcd",
- }
- wantHeader := http.Header{
- "Content-Type": []string{"application/json"},
- }
- wantBody := []byte(`{"peerURLs":["https://127.0.0.1:8081","http://127.0.0.1:8080"]}`)
- got := *act.HTTPRequest(ep)
- err := assertRequest(got, "PUT", wantURL, wantHeader, wantBody)
- if err != nil {
- t.Error(err.Error())
- }
- }
- func TestMembersAPIActionRemove(t *testing.T) {
- ep := url.URL{Scheme: "http", Host: "example.com"}
- act := &membersAPIActionRemove{memberID: "XXX"}
- wantURL := &url.URL{
- Scheme: "http",
- Host: "example.com",
- Path: "/v2/members/XXX",
- }
- got := *act.HTTPRequest(ep)
- err := assertRequest(got, "DELETE", wantURL, http.Header{}, nil)
- if err != nil {
- t.Error(err.Error())
- }
- }
- func TestMembersAPIActionLeader(t *testing.T) {
- ep := url.URL{Scheme: "http", Host: "example.com"}
- act := &membersAPIActionLeader{}
- wantURL := &url.URL{
- Scheme: "http",
- Host: "example.com",
- Path: "/v2/members/leader",
- }
- got := *act.HTTPRequest(ep)
- err := assertRequest(got, "GET", wantURL, http.Header{}, nil)
- if err != nil {
- t.Error(err.Error())
- }
- }
- func TestAssertStatusCode(t *testing.T) {
- if err := assertStatusCode(404, 400); err == nil {
- t.Errorf("assertStatusCode failed to detect conflict in 400 vs 404")
- }
- if err := assertStatusCode(404, 400, 404); err != nil {
- t.Errorf("assertStatusCode found conflict in (404,400) vs 400: %v", err)
- }
- }
- func TestV2MembersURL(t *testing.T) {
- got := v2MembersURL(url.URL{
- Scheme: "http",
- Host: "foo.example.com:4002",
- Path: "/pants",
- })
- want := &url.URL{
- Scheme: "http",
- Host: "foo.example.com:4002",
- Path: "/pants/v2/members",
- }
- if !reflect.DeepEqual(want, got) {
- 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:2379"], "clientURLs": ["http://127.0.0.1:2379"]}`),
- wantMember: Member{
- PeerURLs: []string{
- "http://127.0.0.1:2379",
- },
- ClientURLs: []string{
- "http://127.0.0.1:2379",
- },
- },
- },
- // multiple peer URLs
- {
- body: []byte(`{"peerURLs": ["http://127.0.0.1:2379", "https://example.com"]}`),
- wantMember: Member{
- PeerURLs: []string{
- "http://127.0.0.1:2379",
- "https://example.com",
- },
- ClientURLs: nil,
- },
- },
- // multiple client URLs
- {
- body: []byte(`{"clientURLs": ["http://127.0.0.1:2379", "https://example.com"]}`),
- wantMember: Member{
- PeerURLs: nil,
- ClientURLs: []string{
- "http://127.0.0.1:2379",
- "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 TestMemberCollectionUnmarshalFail(t *testing.T) {
- mc := &memberCollection{}
- if err := mc.UnmarshalJSON([]byte(`{`)); err == nil {
- t.Errorf("got nil error")
- }
- }
- func TestMemberCollectionUnmarshal(t *testing.T) {
- tests := []struct {
- body []byte
- want memberCollection
- }{
- {
- body: []byte(`{}`),
- want: memberCollection([]Member{}),
- },
- {
- 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 := memberCreateOrUpdateRequest{
- PeerURLs: types.URLs([]url.URL{
- {Scheme: "http", Host: "127.0.0.1:8081"},
- {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)
- }
- }
- func TestHTTPMembersAPIAddSuccess(t *testing.T) {
- wantAction := &membersAPIActionAdd{
- peerURLs: types.URLs([]url.URL{
- {Scheme: "http", Host: "127.0.0.1:7002"},
- }),
- }
- mAPI := &httpMembersAPI{
- client: &actionAssertingHTTPClient{
- t: t,
- act: wantAction,
- resp: http.Response{
- StatusCode: http.StatusCreated,
- },
- body: []byte(`{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"]}`),
- },
- }
- wantResponseMember := &Member{
- ID: "94088180e21eb87b",
- PeerURLs: []string{"http://127.0.0.1:7002"},
- }
- m, err := mAPI.Add(context.Background(), "http://127.0.0.1:7002")
- if err != nil {
- t.Errorf("got non-nil err: %#v", err)
- }
- if !reflect.DeepEqual(wantResponseMember, m) {
- t.Errorf("incorrect Member: want=%#v got=%#v", wantResponseMember, m)
- }
- }
- func TestHTTPMembersAPIAddError(t *testing.T) {
- okPeer := "http://example.com:2379"
- tests := []struct {
- peerURL string
- client httpClient
- // if wantErr == nil, assert that the returned error is non-nil
- // if wantErr != nil, assert that the returned error matches
- wantErr error
- }{
- // malformed peer URL
- {
- peerURL: ":",
- },
- // generic httpClient failure
- {
- peerURL: okPeer,
- client: &staticHTTPClient{err: errors.New("fail!")},
- },
- // unrecognized HTTP status code
- {
- peerURL: okPeer,
- client: &staticHTTPClient{
- resp: http.Response{StatusCode: http.StatusTeapot},
- },
- },
- // unmarshal body into membersError on StatusConflict
- {
- peerURL: okPeer,
- client: &staticHTTPClient{
- resp: http.Response{
- StatusCode: http.StatusConflict,
- },
- body: []byte(`{"message":"fail!"}`),
- },
- wantErr: membersError{Message: "fail!"},
- },
- // fail to unmarshal body on StatusConflict
- {
- peerURL: okPeer,
- client: &staticHTTPClient{
- resp: http.Response{
- StatusCode: http.StatusConflict,
- },
- body: []byte(`{"`),
- },
- },
- // fail to unmarshal body on StatusCreated
- {
- peerURL: okPeer,
- client: &staticHTTPClient{
- resp: http.Response{
- StatusCode: http.StatusCreated,
- },
- body: []byte(`{"id":"XX`),
- },
- },
- }
- for i, tt := range tests {
- mAPI := &httpMembersAPI{client: tt.client}
- m, err := mAPI.Add(context.Background(), tt.peerURL)
- if err == nil {
- t.Errorf("#%d: got nil err", i)
- }
- if tt.wantErr != nil && !reflect.DeepEqual(tt.wantErr, err) {
- t.Errorf("#%d: incorrect error: want=%#v got=%#v", i, tt.wantErr, err)
- }
- if m != nil {
- t.Errorf("#%d: got non-nil Member", i)
- }
- }
- }
- func TestHTTPMembersAPIRemoveSuccess(t *testing.T) {
- wantAction := &membersAPIActionRemove{
- memberID: "94088180e21eb87b",
- }
- mAPI := &httpMembersAPI{
- client: &actionAssertingHTTPClient{
- t: t,
- act: wantAction,
- resp: http.Response{
- StatusCode: http.StatusNoContent,
- },
- },
- }
- if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err != nil {
- t.Errorf("got non-nil err: %#v", err)
- }
- }
- func TestHTTPMembersAPIRemoveFail(t *testing.T) {
- tests := []httpClient{
- // generic error
- &staticHTTPClient{
- err: errors.New("fail!"),
- },
- // unexpected HTTP status code
- &staticHTTPClient{
- resp: http.Response{
- StatusCode: http.StatusInternalServerError,
- },
- },
- }
- for i, tt := range tests {
- mAPI := &httpMembersAPI{client: tt}
- if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err == nil {
- t.Errorf("#%d: got nil err", i)
- }
- }
- }
- func TestHTTPMembersAPIListSuccess(t *testing.T) {
- wantAction := &membersAPIActionList{}
- mAPI := &httpMembersAPI{
- client: &actionAssertingHTTPClient{
- t: t,
- act: wantAction,
- resp: http.Response{
- StatusCode: http.StatusOK,
- },
- body: []byte(`{"members":[{"id":"94088180e21eb87b","name":"node2","peerURLs":["http://127.0.0.1:7002"],"clientURLs":["http://127.0.0.1:4002"]}]}`),
- },
- }
- wantResponseMembers := []Member{
- {
- ID: "94088180e21eb87b",
- Name: "node2",
- PeerURLs: []string{"http://127.0.0.1:7002"},
- ClientURLs: []string{"http://127.0.0.1:4002"},
- },
- }
- m, err := mAPI.List(context.Background())
- if err != nil {
- t.Errorf("got non-nil err: %#v", err)
- }
- if !reflect.DeepEqual(wantResponseMembers, m) {
- t.Errorf("incorrect Members: want=%#v got=%#v", wantResponseMembers, m)
- }
- }
- func TestHTTPMembersAPIListError(t *testing.T) {
- tests := []httpClient{
- // generic httpClient failure
- &staticHTTPClient{err: errors.New("fail!")},
- // unrecognized HTTP status code
- &staticHTTPClient{
- resp: http.Response{StatusCode: http.StatusTeapot},
- },
- // fail to unmarshal body on StatusOK
- &staticHTTPClient{
- resp: http.Response{
- StatusCode: http.StatusOK,
- },
- body: []byte(`[{"id":"XX`),
- },
- }
- for i, tt := range tests {
- mAPI := &httpMembersAPI{client: tt}
- ms, err := mAPI.List(context.Background())
- if err == nil {
- t.Errorf("#%d: got nil err", i)
- }
- if ms != nil {
- t.Errorf("#%d: got non-nil Member slice", i)
- }
- }
- }
- func TestHTTPMembersAPILeaderSuccess(t *testing.T) {
- wantAction := &membersAPIActionLeader{}
- mAPI := &httpMembersAPI{
- client: &actionAssertingHTTPClient{
- t: t,
- act: wantAction,
- resp: http.Response{
- StatusCode: http.StatusOK,
- },
- body: []byte(`{"id":"94088180e21eb87b","name":"node2","peerURLs":["http://127.0.0.1:7002"],"clientURLs":["http://127.0.0.1:4002"]}`),
- },
- }
- wantResponseMember := &Member{
- ID: "94088180e21eb87b",
- Name: "node2",
- PeerURLs: []string{"http://127.0.0.1:7002"},
- ClientURLs: []string{"http://127.0.0.1:4002"},
- }
- m, err := mAPI.Leader(context.Background())
- if err != nil {
- t.Errorf("err = %v, want %v", err, nil)
- }
- if !reflect.DeepEqual(wantResponseMember, m) {
- t.Errorf("incorrect member: member = %v, want %v", wantResponseMember, m)
- }
- }
- func TestHTTPMembersAPILeaderError(t *testing.T) {
- tests := []httpClient{
- // generic httpClient failure
- &staticHTTPClient{err: errors.New("fail!")},
- // unrecognized HTTP status code
- &staticHTTPClient{
- resp: http.Response{StatusCode: http.StatusTeapot},
- },
- // fail to unmarshal body on StatusOK
- &staticHTTPClient{
- resp: http.Response{
- StatusCode: http.StatusOK,
- },
- body: []byte(`[{"id":"XX`),
- },
- }
- for i, tt := range tests {
- mAPI := &httpMembersAPI{client: tt}
- m, err := mAPI.Leader(context.Background())
- if err == nil {
- t.Errorf("#%d: err = nil, want not nil", i)
- }
- if m != nil {
- t.Errorf("member slice = %v, want nil", m)
- }
- }
- }
|