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 }