peer_test.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. package etcdhttp
  15. import (
  16. "context"
  17. "encoding/json"
  18. "fmt"
  19. "io/ioutil"
  20. "net/http"
  21. "net/http/httptest"
  22. "path"
  23. "sort"
  24. "strings"
  25. "testing"
  26. "go.uber.org/zap"
  27. "github.com/coreos/go-semver/semver"
  28. "go.etcd.io/etcd/etcdserver/api"
  29. "go.etcd.io/etcd/etcdserver/api/membership"
  30. "go.etcd.io/etcd/etcdserver/api/rafthttp"
  31. pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
  32. "go.etcd.io/etcd/pkg/testutil"
  33. "go.etcd.io/etcd/pkg/types"
  34. )
  35. type fakeCluster struct {
  36. id uint64
  37. clientURLs []string
  38. members map[uint64]*membership.Member
  39. }
  40. func (c *fakeCluster) ID() types.ID { return types.ID(c.id) }
  41. func (c *fakeCluster) ClientURLs() []string { return c.clientURLs }
  42. func (c *fakeCluster) Members() []*membership.Member {
  43. var ms membership.MembersByID
  44. for _, m := range c.members {
  45. ms = append(ms, m)
  46. }
  47. sort.Sort(ms)
  48. return []*membership.Member(ms)
  49. }
  50. func (c *fakeCluster) Member(id types.ID) *membership.Member { return c.members[uint64(id)] }
  51. func (c *fakeCluster) Version() *semver.Version { return nil }
  52. type fakeServer struct {
  53. cluster api.Cluster
  54. }
  55. func (s *fakeServer) AddMember(ctx context.Context, memb membership.Member) ([]*membership.Member, error) {
  56. return nil, fmt.Errorf("AddMember not implemented in fakeServer")
  57. }
  58. func (s *fakeServer) RemoveMember(ctx context.Context, id uint64) ([]*membership.Member, error) {
  59. return nil, fmt.Errorf("RemoveMember not implemented in fakeServer")
  60. }
  61. func (s *fakeServer) UpdateMember(ctx context.Context, updateMemb membership.Member) ([]*membership.Member, error) {
  62. return nil, fmt.Errorf("UpdateMember not implemented in fakeServer")
  63. }
  64. func (s *fakeServer) PromoteMember(ctx context.Context, id uint64) ([]*membership.Member, error) {
  65. return nil, fmt.Errorf("PromoteMember not implemented in fakeServer")
  66. }
  67. func (s *fakeServer) ClusterVersion() *semver.Version { return nil }
  68. func (s *fakeServer) Cluster() api.Cluster { return s.cluster }
  69. func (s *fakeServer) Alarms() []*pb.AlarmMember { return nil }
  70. var fakeRaftHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  71. w.Write([]byte("test data"))
  72. })
  73. // TestNewPeerHandlerOnRaftPrefix tests that NewPeerHandler returns a handler that
  74. // handles raft-prefix requests well.
  75. func TestNewPeerHandlerOnRaftPrefix(t *testing.T) {
  76. ph := newPeerHandler(zap.NewExample(), &fakeServer{cluster: &fakeCluster{}}, fakeRaftHandler, nil)
  77. srv := httptest.NewServer(ph)
  78. defer srv.Close()
  79. tests := []string{
  80. rafthttp.RaftPrefix,
  81. rafthttp.RaftPrefix + "/hello",
  82. }
  83. for i, tt := range tests {
  84. resp, err := http.Get(srv.URL + tt)
  85. if err != nil {
  86. t.Fatalf("unexpected http.Get error: %v", err)
  87. }
  88. body, err := ioutil.ReadAll(resp.Body)
  89. if err != nil {
  90. t.Fatalf("unexpected ioutil.ReadAll error: %v", err)
  91. }
  92. if w := "test data"; string(body) != w {
  93. t.Errorf("#%d: body = %s, want %s", i, body, w)
  94. }
  95. }
  96. }
  97. // TestServeMembersFails ensures peerMembersHandler only accepts GET request
  98. func TestServeMembersFails(t *testing.T) {
  99. tests := []struct {
  100. method string
  101. wcode int
  102. }{
  103. {
  104. "POST",
  105. http.StatusMethodNotAllowed,
  106. },
  107. {
  108. "PUT",
  109. http.StatusMethodNotAllowed,
  110. },
  111. {
  112. "DELETE",
  113. http.StatusMethodNotAllowed,
  114. },
  115. {
  116. "BAD",
  117. http.StatusMethodNotAllowed,
  118. },
  119. }
  120. for i, tt := range tests {
  121. rw := httptest.NewRecorder()
  122. h := newPeerMembersHandler(nil, &fakeCluster{})
  123. req, err := http.NewRequest(tt.method, "", nil)
  124. if err != nil {
  125. t.Fatalf("#%d: failed to create http request: %v", i, err)
  126. }
  127. h.ServeHTTP(rw, req)
  128. if rw.Code != tt.wcode {
  129. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  130. }
  131. }
  132. }
  133. func TestServeMembersGet(t *testing.T) {
  134. memb1 := membership.Member{ID: 1, Attributes: membership.Attributes{ClientURLs: []string{"http://localhost:8080"}}}
  135. memb2 := membership.Member{ID: 2, Attributes: membership.Attributes{ClientURLs: []string{"http://localhost:8081"}}}
  136. cluster := &fakeCluster{
  137. id: 1,
  138. members: map[uint64]*membership.Member{1: &memb1, 2: &memb2},
  139. }
  140. h := newPeerMembersHandler(nil, cluster)
  141. msb, err := json.Marshal([]membership.Member{memb1, memb2})
  142. if err != nil {
  143. t.Fatal(err)
  144. }
  145. wms := string(msb) + "\n"
  146. tests := []struct {
  147. path string
  148. wcode int
  149. wct string
  150. wbody string
  151. }{
  152. {peerMembersPath, http.StatusOK, "application/json", wms},
  153. {path.Join(peerMembersPath, "bad"), http.StatusBadRequest, "text/plain; charset=utf-8", "bad path\n"},
  154. }
  155. for i, tt := range tests {
  156. req, err := http.NewRequest("GET", testutil.MustNewURL(t, tt.path).String(), nil)
  157. if err != nil {
  158. t.Fatal(err)
  159. }
  160. rw := httptest.NewRecorder()
  161. h.ServeHTTP(rw, req)
  162. if rw.Code != tt.wcode {
  163. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  164. }
  165. if gct := rw.Header().Get("Content-Type"); gct != tt.wct {
  166. t.Errorf("#%d: content-type = %s, want %s", i, gct, tt.wct)
  167. }
  168. if rw.Body.String() != tt.wbody {
  169. t.Errorf("#%d: body = %s, want %s", i, rw.Body.String(), tt.wbody)
  170. }
  171. gcid := rw.Header().Get("X-Etcd-Cluster-ID")
  172. wcid := cluster.ID().String()
  173. if gcid != wcid {
  174. t.Errorf("#%d: cid = %s, want %s", i, gcid, wcid)
  175. }
  176. }
  177. }
  178. // TestServeMemberPromoteFails ensures peerMemberPromoteHandler only accepts POST request
  179. func TestServeMemberPromoteFails(t *testing.T) {
  180. tests := []struct {
  181. method string
  182. wcode int
  183. }{
  184. {
  185. "GET",
  186. http.StatusMethodNotAllowed,
  187. },
  188. {
  189. "PUT",
  190. http.StatusMethodNotAllowed,
  191. },
  192. {
  193. "DELETE",
  194. http.StatusMethodNotAllowed,
  195. },
  196. {
  197. "BAD",
  198. http.StatusMethodNotAllowed,
  199. },
  200. }
  201. for i, tt := range tests {
  202. rw := httptest.NewRecorder()
  203. h := newPeerMemberPromoteHandler(nil, &fakeServer{cluster: &fakeCluster{}})
  204. req, err := http.NewRequest(tt.method, "", nil)
  205. if err != nil {
  206. t.Fatalf("#%d: failed to create http request: %v", i, err)
  207. }
  208. h.ServeHTTP(rw, req)
  209. if rw.Code != tt.wcode {
  210. t.Errorf("#%d: code=%d, want %d", i, rw.Code, tt.wcode)
  211. }
  212. }
  213. }
  214. // TestNewPeerHandlerOnMembersPromotePrefix verifies the request with members promote prefix is routed correctly
  215. func TestNewPeerHandlerOnMembersPromotePrefix(t *testing.T) {
  216. ph := newPeerHandler(zap.NewExample(), &fakeServer{cluster: &fakeCluster{}}, fakeRaftHandler, nil)
  217. srv := httptest.NewServer(ph)
  218. defer srv.Close()
  219. tests := []struct {
  220. path string
  221. wcode int
  222. checkBody bool
  223. wKeyWords string
  224. }{
  225. {
  226. // does not contain member id in path
  227. peerMemberPromotePrefix,
  228. http.StatusNotFound,
  229. false,
  230. "",
  231. },
  232. {
  233. // try to promote member id = 1
  234. peerMemberPromotePrefix + "1",
  235. http.StatusInternalServerError,
  236. true,
  237. "PromoteMember not implemented in fakeServer",
  238. },
  239. }
  240. for i, tt := range tests {
  241. req, err := http.NewRequest("POST", srv.URL+tt.path, nil)
  242. if err != nil {
  243. t.Fatalf("failed to create request: %v", err)
  244. }
  245. resp, err := http.DefaultClient.Do(req)
  246. if err != nil {
  247. t.Fatalf("failed to get http response: %v", err)
  248. }
  249. body, err := ioutil.ReadAll(resp.Body)
  250. resp.Body.Close()
  251. if err != nil {
  252. t.Fatalf("unexpected ioutil.ReadAll error: %v", err)
  253. }
  254. if resp.StatusCode != tt.wcode {
  255. t.Fatalf("#%d: code = %d, want %d", i, resp.StatusCode, tt.wcode)
  256. }
  257. if tt.checkBody && strings.Contains(string(body), tt.wKeyWords) {
  258. t.Errorf("#%d: body: %s, want body to contain keywords: %s", i, string(body), tt.wKeyWords)
  259. }
  260. }
  261. }