...

Source file src/go.etcd.io/etcd/server/v3/etcdserver/api/etcdhttp/peer_test.go

Documentation: go.etcd.io/etcd/server/v3/etcdserver/api/etcdhttp

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

View as plain text