go.etcd.io/etcd@v3.3.27+incompatible/rafthttp/stream_test.go (about)

     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 rafthttp
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"reflect"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"golang.org/x/time/rate"
    30  
    31  	"github.com/coreos/etcd/etcdserver/stats"
    32  	"github.com/coreos/etcd/pkg/testutil"
    33  	"github.com/coreos/etcd/pkg/types"
    34  	"github.com/coreos/etcd/raft/raftpb"
    35  	"github.com/coreos/etcd/version"
    36  	"github.com/coreos/go-semver/semver"
    37  )
    38  
    39  // TestStreamWriterAttachOutgoingConn tests that outgoingConn can be attached
    40  // to streamWriter. After that, streamWriter can use it to send messages
    41  // continuously, and closes it when stopped.
    42  func TestStreamWriterAttachOutgoingConn(t *testing.T) {
    43  	sw := startStreamWriter(types.ID(1), newPeerStatus(types.ID(1)), &stats.FollowerStats{}, &fakeRaft{})
    44  	// the expected initial state of streamWriter is not working
    45  	if _, ok := sw.writec(); ok {
    46  		t.Errorf("initial working status = %v, want false", ok)
    47  	}
    48  
    49  	// repeat tests to ensure streamWriter can use last attached connection
    50  	var wfc *fakeWriteFlushCloser
    51  	for i := 0; i < 3; i++ {
    52  		prevwfc := wfc
    53  		wfc = newFakeWriteFlushCloser(nil)
    54  		sw.attach(&outgoingConn{t: streamTypeMessage, Writer: wfc, Flusher: wfc, Closer: wfc})
    55  
    56  		// previous attached connection should be closed
    57  		if prevwfc != nil {
    58  			select {
    59  			case <-prevwfc.closed:
    60  			case <-time.After(time.Second):
    61  				t.Errorf("#%d: close of previous connection timed out", i)
    62  			}
    63  		}
    64  
    65  		// if prevwfc != nil, the new msgc is ready since prevwfc has closed
    66  		// if prevwfc == nil, the first connection may be pending, but the first
    67  		// msgc is already available since it's set on calling startStreamwriter
    68  		msgc, _ := sw.writec()
    69  		msgc <- raftpb.Message{}
    70  
    71  		select {
    72  		case <-wfc.writec:
    73  		case <-time.After(time.Second):
    74  			t.Errorf("#%d: failed to write to the underlying connection", i)
    75  		}
    76  		// write chan is still available
    77  		if _, ok := sw.writec(); !ok {
    78  			t.Errorf("#%d: working status = %v, want true", i, ok)
    79  		}
    80  	}
    81  
    82  	sw.stop()
    83  	// write chan is unavailable since the writer is stopped.
    84  	if _, ok := sw.writec(); ok {
    85  		t.Errorf("working status after stop = %v, want false", ok)
    86  	}
    87  	if !wfc.Closed() {
    88  		t.Errorf("failed to close the underlying connection")
    89  	}
    90  }
    91  
    92  // TestStreamWriterAttachBadOutgoingConn tests that streamWriter with bad
    93  // outgoingConn will close the outgoingConn and fall back to non-working status.
    94  func TestStreamWriterAttachBadOutgoingConn(t *testing.T) {
    95  	sw := startStreamWriter(types.ID(1), newPeerStatus(types.ID(1)), &stats.FollowerStats{}, &fakeRaft{})
    96  	defer sw.stop()
    97  	wfc := newFakeWriteFlushCloser(errors.New("blah"))
    98  	sw.attach(&outgoingConn{t: streamTypeMessage, Writer: wfc, Flusher: wfc, Closer: wfc})
    99  
   100  	sw.msgc <- raftpb.Message{}
   101  	select {
   102  	case <-wfc.closed:
   103  	case <-time.After(time.Second):
   104  		t.Errorf("failed to close the underlying connection in time")
   105  	}
   106  	// no longer working
   107  	if _, ok := sw.writec(); ok {
   108  		t.Errorf("working = %v, want false", ok)
   109  	}
   110  }
   111  
   112  func TestStreamReaderDialRequest(t *testing.T) {
   113  	for i, tt := range []streamType{streamTypeMessage, streamTypeMsgAppV2} {
   114  		tr := &roundTripperRecorder{rec: &testutil.RecorderBuffered{}}
   115  		sr := &streamReader{
   116  			peerID: types.ID(2),
   117  			tr:     &Transport{streamRt: tr, ClusterID: types.ID(1), ID: types.ID(1)},
   118  			picker: mustNewURLPicker(t, []string{"http://localhost:2380"}),
   119  			ctx:    context.Background(),
   120  		}
   121  		sr.dial(tt)
   122  
   123  		act, err := tr.rec.Wait(1)
   124  		if err != nil {
   125  			t.Fatal(err)
   126  		}
   127  		req := act[0].Params[0].(*http.Request)
   128  
   129  		wurl := fmt.Sprintf("http://localhost:2380" + tt.endpoint() + "/1")
   130  		if req.URL.String() != wurl {
   131  			t.Errorf("#%d: url = %s, want %s", i, req.URL.String(), wurl)
   132  		}
   133  		if w := "GET"; req.Method != w {
   134  			t.Errorf("#%d: method = %s, want %s", i, req.Method, w)
   135  		}
   136  		if g := req.Header.Get("X-Etcd-Cluster-ID"); g != "1" {
   137  			t.Errorf("#%d: header X-Etcd-Cluster-ID = %s, want 1", i, g)
   138  		}
   139  		if g := req.Header.Get("X-Raft-To"); g != "2" {
   140  			t.Errorf("#%d: header X-Raft-To = %s, want 2", i, g)
   141  		}
   142  	}
   143  }
   144  
   145  // TestStreamReaderDialResult tests the result of the dial func call meets the
   146  // HTTP response received.
   147  func TestStreamReaderDialResult(t *testing.T) {
   148  	tests := []struct {
   149  		code  int
   150  		err   error
   151  		wok   bool
   152  		whalt bool
   153  	}{
   154  		{0, errors.New("blah"), false, false},
   155  		{http.StatusOK, nil, true, false},
   156  		{http.StatusMethodNotAllowed, nil, false, false},
   157  		{http.StatusNotFound, nil, false, false},
   158  		{http.StatusPreconditionFailed, nil, false, false},
   159  		{http.StatusGone, nil, false, true},
   160  	}
   161  	for i, tt := range tests {
   162  		h := http.Header{}
   163  		h.Add("X-Server-Version", version.Version)
   164  		tr := &respRoundTripper{
   165  			code:   tt.code,
   166  			header: h,
   167  			err:    tt.err,
   168  		}
   169  		sr := &streamReader{
   170  			peerID: types.ID(2),
   171  			tr:     &Transport{streamRt: tr, ClusterID: types.ID(1)},
   172  			picker: mustNewURLPicker(t, []string{"http://localhost:2380"}),
   173  			errorc: make(chan error, 1),
   174  			ctx:    context.Background(),
   175  		}
   176  
   177  		_, err := sr.dial(streamTypeMessage)
   178  		if ok := err == nil; ok != tt.wok {
   179  			t.Errorf("#%d: ok = %v, want %v", i, ok, tt.wok)
   180  		}
   181  		if halt := len(sr.errorc) > 0; halt != tt.whalt {
   182  			t.Errorf("#%d: halt = %v, want %v", i, halt, tt.whalt)
   183  		}
   184  	}
   185  }
   186  
   187  // TestStreamReaderStopOnDial tests a stream reader closes the connection on stop.
   188  func TestStreamReaderStopOnDial(t *testing.T) {
   189  	defer testutil.AfterTest(t)
   190  	h := http.Header{}
   191  	h.Add("X-Server-Version", version.Version)
   192  	tr := &respWaitRoundTripper{rrt: &respRoundTripper{code: http.StatusOK, header: h}}
   193  	sr := &streamReader{
   194  		peerID: types.ID(2),
   195  		tr:     &Transport{streamRt: tr, ClusterID: types.ID(1)},
   196  		picker: mustNewURLPicker(t, []string{"http://localhost:2380"}),
   197  		errorc: make(chan error, 1),
   198  		typ:    streamTypeMessage,
   199  		status: newPeerStatus(types.ID(2)),
   200  		rl:     rate.NewLimiter(rate.Every(100*time.Millisecond), 1),
   201  	}
   202  	tr.onResp = func() {
   203  		// stop() waits for the run() goroutine to exit, but that exit
   204  		// needs a response from RoundTrip() first; use goroutine
   205  		go sr.stop()
   206  		// wait so that stop() is blocked on run() exiting
   207  		time.Sleep(10 * time.Millisecond)
   208  		// sr.run() completes dialing then begins decoding while stopped
   209  	}
   210  	sr.start()
   211  	select {
   212  	case <-sr.done:
   213  	case <-time.After(time.Second):
   214  		t.Fatal("streamReader did not stop in time")
   215  	}
   216  }
   217  
   218  type respWaitRoundTripper struct {
   219  	rrt    *respRoundTripper
   220  	onResp func()
   221  }
   222  
   223  func (t *respWaitRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   224  	resp, err := t.rrt.RoundTrip(req)
   225  	resp.Body = newWaitReadCloser()
   226  	t.onResp()
   227  	return resp, err
   228  }
   229  
   230  type waitReadCloser struct{ closec chan struct{} }
   231  
   232  func newWaitReadCloser() *waitReadCloser { return &waitReadCloser{make(chan struct{})} }
   233  func (wrc *waitReadCloser) Read(p []byte) (int, error) {
   234  	<-wrc.closec
   235  	return 0, io.EOF
   236  }
   237  func (wrc *waitReadCloser) Close() error {
   238  	close(wrc.closec)
   239  	return nil
   240  }
   241  
   242  // TestStreamReaderDialDetectUnsupport tests that dial func could find
   243  // out that the stream type is not supported by the remote.
   244  func TestStreamReaderDialDetectUnsupport(t *testing.T) {
   245  	for i, typ := range []streamType{streamTypeMsgAppV2, streamTypeMessage} {
   246  		// the response from etcd 2.0
   247  		tr := &respRoundTripper{
   248  			code:   http.StatusNotFound,
   249  			header: http.Header{},
   250  		}
   251  		sr := &streamReader{
   252  			peerID: types.ID(2),
   253  			tr:     &Transport{streamRt: tr, ClusterID: types.ID(1)},
   254  			picker: mustNewURLPicker(t, []string{"http://localhost:2380"}),
   255  			ctx:    context.Background(),
   256  		}
   257  
   258  		_, err := sr.dial(typ)
   259  		if err != errUnsupportedStreamType {
   260  			t.Errorf("#%d: error = %v, want %v", i, err, errUnsupportedStreamType)
   261  		}
   262  	}
   263  }
   264  
   265  // TestStream tests that streamReader and streamWriter can build stream to
   266  // send messages between each other.
   267  func TestStream(t *testing.T) {
   268  	recvc := make(chan raftpb.Message, streamBufSize)
   269  	propc := make(chan raftpb.Message, streamBufSize)
   270  	msgapp := raftpb.Message{
   271  		Type:    raftpb.MsgApp,
   272  		From:    2,
   273  		To:      1,
   274  		Term:    1,
   275  		LogTerm: 1,
   276  		Index:   3,
   277  		Entries: []raftpb.Entry{{Term: 1, Index: 4}},
   278  	}
   279  
   280  	tests := []struct {
   281  		t  streamType
   282  		m  raftpb.Message
   283  		wc chan raftpb.Message
   284  	}{
   285  		{
   286  			streamTypeMessage,
   287  			raftpb.Message{Type: raftpb.MsgProp, To: 2},
   288  			propc,
   289  		},
   290  		{
   291  			streamTypeMessage,
   292  			msgapp,
   293  			recvc,
   294  		},
   295  		{
   296  			streamTypeMsgAppV2,
   297  			msgapp,
   298  			recvc,
   299  		},
   300  	}
   301  	for i, tt := range tests {
   302  		h := &fakeStreamHandler{t: tt.t}
   303  		srv := httptest.NewServer(h)
   304  		defer srv.Close()
   305  
   306  		sw := startStreamWriter(types.ID(1), newPeerStatus(types.ID(1)), &stats.FollowerStats{}, &fakeRaft{})
   307  		defer sw.stop()
   308  		h.sw = sw
   309  
   310  		picker := mustNewURLPicker(t, []string{srv.URL})
   311  		tr := &Transport{streamRt: &http.Transport{}, ClusterID: types.ID(1)}
   312  
   313  		sr := &streamReader{
   314  			peerID: types.ID(2),
   315  			typ:    tt.t,
   316  			tr:     tr,
   317  			picker: picker,
   318  			status: newPeerStatus(types.ID(2)),
   319  			recvc:  recvc,
   320  			propc:  propc,
   321  			rl:     rate.NewLimiter(rate.Every(100*time.Millisecond), 1),
   322  		}
   323  		sr.start()
   324  
   325  		// wait for stream to work
   326  		var writec chan<- raftpb.Message
   327  		for {
   328  			var ok bool
   329  			if writec, ok = sw.writec(); ok {
   330  				break
   331  			}
   332  			time.Sleep(time.Millisecond)
   333  		}
   334  
   335  		writec <- tt.m
   336  		var m raftpb.Message
   337  		select {
   338  		case m = <-tt.wc:
   339  		case <-time.After(time.Second):
   340  			t.Fatalf("#%d: failed to receive message from the channel", i)
   341  		}
   342  		if !reflect.DeepEqual(m, tt.m) {
   343  			t.Fatalf("#%d: message = %+v, want %+v", i, m, tt.m)
   344  		}
   345  
   346  		sr.stop()
   347  	}
   348  }
   349  
   350  func TestCheckStreamSupport(t *testing.T) {
   351  	tests := []struct {
   352  		v *semver.Version
   353  		t streamType
   354  		w bool
   355  	}{
   356  		// support
   357  		{
   358  			semver.Must(semver.NewVersion("2.1.0")),
   359  			streamTypeMsgAppV2,
   360  			true,
   361  		},
   362  		// ignore patch
   363  		{
   364  			semver.Must(semver.NewVersion("2.1.9")),
   365  			streamTypeMsgAppV2,
   366  			true,
   367  		},
   368  		// ignore prerelease
   369  		{
   370  			semver.Must(semver.NewVersion("2.1.0-alpha")),
   371  			streamTypeMsgAppV2,
   372  			true,
   373  		},
   374  	}
   375  	for i, tt := range tests {
   376  		if g := checkStreamSupport(tt.v, tt.t); g != tt.w {
   377  			t.Errorf("#%d: check = %v, want %v", i, g, tt.w)
   378  		}
   379  	}
   380  }
   381  
   382  type fakeWriteFlushCloser struct {
   383  	mu      sync.Mutex
   384  	err     error
   385  	written int
   386  	closed  chan struct{}
   387  	writec  chan struct{}
   388  }
   389  
   390  func newFakeWriteFlushCloser(err error) *fakeWriteFlushCloser {
   391  	return &fakeWriteFlushCloser{
   392  		err:    err,
   393  		closed: make(chan struct{}),
   394  		writec: make(chan struct{}, 1),
   395  	}
   396  }
   397  
   398  func (wfc *fakeWriteFlushCloser) Write(p []byte) (n int, err error) {
   399  	wfc.mu.Lock()
   400  	defer wfc.mu.Unlock()
   401  	select {
   402  	case wfc.writec <- struct{}{}:
   403  	default:
   404  	}
   405  	wfc.written += len(p)
   406  	return len(p), wfc.err
   407  }
   408  
   409  func (wfc *fakeWriteFlushCloser) Flush() {}
   410  
   411  func (wfc *fakeWriteFlushCloser) Close() error {
   412  	close(wfc.closed)
   413  	return wfc.err
   414  }
   415  
   416  func (wfc *fakeWriteFlushCloser) Written() int {
   417  	wfc.mu.Lock()
   418  	defer wfc.mu.Unlock()
   419  	return wfc.written
   420  }
   421  
   422  func (wfc *fakeWriteFlushCloser) Closed() bool {
   423  	select {
   424  	case <-wfc.closed:
   425  		return true
   426  	default:
   427  		return false
   428  	}
   429  }
   430  
   431  type fakeStreamHandler struct {
   432  	t  streamType
   433  	sw *streamWriter
   434  }
   435  
   436  func (h *fakeStreamHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   437  	w.Header().Add("X-Server-Version", version.Version)
   438  	w.(http.Flusher).Flush()
   439  	c := newCloseNotifier()
   440  	h.sw.attach(&outgoingConn{
   441  		t:       h.t,
   442  		Writer:  w,
   443  		Flusher: w.(http.Flusher),
   444  		Closer:  c,
   445  	})
   446  	<-c.closeNotify()
   447  }