|
@@ -109,25 +109,6 @@ func newFakeTransport() *fakeTransport {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (t *fakeTransport) RoundTrip(*http.Request) (*http.Response, error) {
|
|
|
- select {
|
|
|
- case resp := <-t.respchan:
|
|
|
- return resp, nil
|
|
|
- case err := <-t.errchan:
|
|
|
- return nil, err
|
|
|
- case <-t.startCancel:
|
|
|
- select {
|
|
|
- // this simulates that the request is finished before cancel effects
|
|
|
- case resp := <-t.respchan:
|
|
|
- return resp, nil
|
|
|
- // wait on finishCancel to simulate taking some amount of
|
|
|
- // time while calling CancelRequest
|
|
|
- case <-t.finishCancel:
|
|
|
- return nil, errors.New("cancelled")
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
func (t *fakeTransport) CancelRequest(*http.Request) {
|
|
|
t.startCancel <- struct{}{}
|
|
|
}
|
|
@@ -257,8 +238,8 @@ func TestSimpleHTTPClientDoCancelContextResponseBodyClosedWithBlockingBody(t *te
|
|
|
}()
|
|
|
|
|
|
_, _, err := c.Do(ctx, &fakeAction{})
|
|
|
- if err == nil {
|
|
|
- t.Fatalf("expected non-nil error, got nil")
|
|
|
+ if err != context.Canceled {
|
|
|
+ t.Fatalf("expected %+v, got %+v", context.Canceled, err)
|
|
|
}
|
|
|
|
|
|
if !body.closed {
|
|
@@ -297,6 +278,27 @@ func TestSimpleHTTPClientDoCancelContextWaitForRoundTrip(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func TestSimpleHTTPClientDoHeaderTimeout(t *testing.T) {
|
|
|
+ tr := newFakeTransport()
|
|
|
+ tr.finishCancel <- struct{}{}
|
|
|
+ c := &simpleHTTPClient{transport: tr, headerTimeout: time.Millisecond}
|
|
|
+
|
|
|
+ errc := make(chan error)
|
|
|
+ go func() {
|
|
|
+ _, _, err := c.Do(context.Background(), &fakeAction{})
|
|
|
+ errc <- err
|
|
|
+ }()
|
|
|
+
|
|
|
+ select {
|
|
|
+ case err := <-errc:
|
|
|
+ if err == nil {
|
|
|
+ t.Fatalf("expected non-nil error, got nil")
|
|
|
+ }
|
|
|
+ case <-time.After(time.Second):
|
|
|
+ t.Fatalf("unexpected timeout when waitting for the test to finish")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func TestHTTPClusterClientDo(t *testing.T) {
|
|
|
fakeErr := errors.New("fake!")
|
|
|
fakeURL := url.URL{}
|
|
@@ -312,8 +314,8 @@ func TestHTTPClusterClientDo(t *testing.T) {
|
|
|
endpoints: []url.URL{fakeURL, fakeURL},
|
|
|
clientFactory: newStaticHTTPClientFactory(
|
|
|
[]staticHTTPResponse{
|
|
|
- staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
|
|
|
- staticHTTPResponse{err: fakeErr},
|
|
|
+ {resp: http.Response{StatusCode: http.StatusTeapot}},
|
|
|
+ {err: fakeErr},
|
|
|
},
|
|
|
),
|
|
|
rand: rand.New(rand.NewSource(0)),
|
|
@@ -327,8 +329,8 @@ func TestHTTPClusterClientDo(t *testing.T) {
|
|
|
endpoints: []url.URL{fakeURL, fakeURL},
|
|
|
clientFactory: newStaticHTTPClientFactory(
|
|
|
[]staticHTTPResponse{
|
|
|
- staticHTTPResponse{err: fakeErr},
|
|
|
- staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
|
|
|
+ {err: fakeErr},
|
|
|
+ {resp: http.Response{StatusCode: http.StatusTeapot}},
|
|
|
},
|
|
|
),
|
|
|
rand: rand.New(rand.NewSource(0)),
|
|
@@ -337,41 +339,26 @@ func TestHTTPClusterClientDo(t *testing.T) {
|
|
|
wantPinned: 1,
|
|
|
},
|
|
|
|
|
|
- // context.DeadlineExceeded short-circuits Do
|
|
|
- {
|
|
|
- client: &httpClusterClient{
|
|
|
- endpoints: []url.URL{fakeURL, fakeURL},
|
|
|
- clientFactory: newStaticHTTPClientFactory(
|
|
|
- []staticHTTPResponse{
|
|
|
- staticHTTPResponse{err: context.DeadlineExceeded},
|
|
|
- staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
|
|
|
- },
|
|
|
- ),
|
|
|
- rand: rand.New(rand.NewSource(0)),
|
|
|
- },
|
|
|
- wantErr: &ClusterError{Errors: []error{context.DeadlineExceeded}},
|
|
|
- },
|
|
|
-
|
|
|
// context.Canceled short-circuits Do
|
|
|
{
|
|
|
client: &httpClusterClient{
|
|
|
endpoints: []url.URL{fakeURL, fakeURL},
|
|
|
clientFactory: newStaticHTTPClientFactory(
|
|
|
[]staticHTTPResponse{
|
|
|
- staticHTTPResponse{err: context.Canceled},
|
|
|
- staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
|
|
|
+ {err: context.Canceled},
|
|
|
+ {resp: http.Response{StatusCode: http.StatusTeapot}},
|
|
|
},
|
|
|
),
|
|
|
rand: rand.New(rand.NewSource(0)),
|
|
|
},
|
|
|
- wantErr: &ClusterError{Errors: []error{context.Canceled}},
|
|
|
+ wantErr: context.Canceled,
|
|
|
},
|
|
|
|
|
|
// return err if there are no endpoints
|
|
|
{
|
|
|
client: &httpClusterClient{
|
|
|
endpoints: []url.URL{},
|
|
|
- clientFactory: newHTTPClientFactory(nil, nil),
|
|
|
+ clientFactory: newHTTPClientFactory(nil, nil, 0),
|
|
|
rand: rand.New(rand.NewSource(0)),
|
|
|
},
|
|
|
wantErr: ErrNoEndpoints,
|
|
@@ -383,8 +370,8 @@ func TestHTTPClusterClientDo(t *testing.T) {
|
|
|
endpoints: []url.URL{fakeURL, fakeURL},
|
|
|
clientFactory: newStaticHTTPClientFactory(
|
|
|
[]staticHTTPResponse{
|
|
|
- staticHTTPResponse{err: fakeErr},
|
|
|
- staticHTTPResponse{err: fakeErr},
|
|
|
+ {err: fakeErr},
|
|
|
+ {err: fakeErr},
|
|
|
},
|
|
|
),
|
|
|
rand: rand.New(rand.NewSource(0)),
|
|
@@ -398,8 +385,8 @@ func TestHTTPClusterClientDo(t *testing.T) {
|
|
|
endpoints: []url.URL{fakeURL, fakeURL},
|
|
|
clientFactory: newStaticHTTPClientFactory(
|
|
|
[]staticHTTPResponse{
|
|
|
- staticHTTPResponse{resp: http.Response{StatusCode: http.StatusBadGateway}},
|
|
|
- staticHTTPResponse{resp: http.Response{StatusCode: http.StatusTeapot}},
|
|
|
+ {resp: http.Response{StatusCode: http.StatusBadGateway}},
|
|
|
+ {resp: http.Response{StatusCode: http.StatusTeapot}},
|
|
|
},
|
|
|
),
|
|
|
rand: rand.New(rand.NewSource(0)),
|
|
@@ -434,6 +421,33 @@ func TestHTTPClusterClientDo(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func TestHTTPClusterClientDoDeadlineExceedContext(t *testing.T) {
|
|
|
+ fakeURL := url.URL{}
|
|
|
+ tr := newFakeTransport()
|
|
|
+ tr.finishCancel <- struct{}{}
|
|
|
+ c := &httpClusterClient{
|
|
|
+ clientFactory: newHTTPClientFactory(tr, DefaultCheckRedirect, 0),
|
|
|
+ endpoints: []url.URL{fakeURL},
|
|
|
+ }
|
|
|
+
|
|
|
+ errc := make(chan error)
|
|
|
+ go func() {
|
|
|
+ ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
|
|
|
+ defer cancel()
|
|
|
+ _, _, err := c.Do(ctx, &fakeAction{})
|
|
|
+ errc <- err
|
|
|
+ }()
|
|
|
+
|
|
|
+ select {
|
|
|
+ case err := <-errc:
|
|
|
+ if err != context.DeadlineExceeded {
|
|
|
+ t.Errorf("err = %+v, want %+v", err, context.DeadlineExceeded)
|
|
|
+ }
|
|
|
+ case <-time.After(time.Second):
|
|
|
+ t.Fatalf("unexpected timeout when waitting for request to deadline exceed")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func TestRedirectedHTTPAction(t *testing.T) {
|
|
|
act := &redirectedHTTPAction{
|
|
|
action: &staticHTTPAction{
|
|
@@ -480,7 +494,7 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
|
|
|
checkRedirect: func(int) error { return ErrTooManyRedirects },
|
|
|
client: &multiStaticHTTPClient{
|
|
|
responses: []staticHTTPResponse{
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
err: errors.New("fail!"),
|
|
|
},
|
|
|
},
|
|
@@ -493,7 +507,7 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
|
|
|
checkRedirect: func(int) error { return ErrTooManyRedirects },
|
|
|
client: &multiStaticHTTPClient{
|
|
|
responses: []staticHTTPResponse{
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
resp: http.Response{
|
|
|
StatusCode: http.StatusTeapot,
|
|
|
},
|
|
@@ -513,13 +527,13 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
|
|
|
},
|
|
|
client: &multiStaticHTTPClient{
|
|
|
responses: []staticHTTPResponse{
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
resp: http.Response{
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
|
|
Header: http.Header{"Location": []string{"http://example.com"}},
|
|
|
},
|
|
|
},
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
resp: http.Response{
|
|
|
StatusCode: http.StatusTeapot,
|
|
|
},
|
|
@@ -539,19 +553,19 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
|
|
|
},
|
|
|
client: &multiStaticHTTPClient{
|
|
|
responses: []staticHTTPResponse{
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
resp: http.Response{
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
|
|
Header: http.Header{"Location": []string{"http://example.com"}},
|
|
|
},
|
|
|
},
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
resp: http.Response{
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
|
|
Header: http.Header{"Location": []string{"http://example.com"}},
|
|
|
},
|
|
|
},
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
resp: http.Response{
|
|
|
StatusCode: http.StatusTeapot,
|
|
|
},
|
|
@@ -571,19 +585,19 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
|
|
|
},
|
|
|
client: &multiStaticHTTPClient{
|
|
|
responses: []staticHTTPResponse{
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
resp: http.Response{
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
|
|
Header: http.Header{"Location": []string{"http://example.com"}},
|
|
|
},
|
|
|
},
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
resp: http.Response{
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
|
|
Header: http.Header{"Location": []string{"http://example.com"}},
|
|
|
},
|
|
|
},
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
resp: http.Response{
|
|
|
StatusCode: http.StatusTeapot,
|
|
|
},
|
|
@@ -598,7 +612,7 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
|
|
|
checkRedirect: func(int) error { return ErrTooManyRedirects },
|
|
|
client: &multiStaticHTTPClient{
|
|
|
responses: []staticHTTPResponse{
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
resp: http.Response{
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
|
|
},
|
|
@@ -613,7 +627,7 @@ func TestRedirectFollowingHTTPClient(t *testing.T) {
|
|
|
checkRedirect: func(int) error { return ErrTooManyRedirects },
|
|
|
client: &multiStaticHTTPClient{
|
|
|
responses: []staticHTTPResponse{
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
resp: http.Response{
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
|
|
Header: http.Header{"Location": []string{":"}},
|
|
@@ -681,7 +695,7 @@ func TestDefaultCheckRedirect(t *testing.T) {
|
|
|
|
|
|
func TestHTTPClusterClientSync(t *testing.T) {
|
|
|
cf := newStaticHTTPClientFactory([]staticHTTPResponse{
|
|
|
- staticHTTPResponse{
|
|
|
+ {
|
|
|
resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}},
|
|
|
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"]}]}`),
|
|
|
},
|
|
@@ -728,7 +742,7 @@ func TestHTTPClusterClientSync(t *testing.T) {
|
|
|
|
|
|
func TestHTTPClusterClientSyncFail(t *testing.T) {
|
|
|
cf := newStaticHTTPClientFactory([]staticHTTPResponse{
|
|
|
- staticHTTPResponse{err: errors.New("fail!")},
|
|
|
+ {err: errors.New("fail!")},
|
|
|
})
|
|
|
|
|
|
hc := &httpClusterClient{
|
|
@@ -757,13 +771,98 @@ func TestHTTPClusterClientSyncFail(t *testing.T) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func TestHTTPClusterClientAutoSyncCancelContext(t *testing.T) {
|
|
|
+ cf := newStaticHTTPClientFactory([]staticHTTPResponse{
|
|
|
+ {
|
|
|
+ resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}},
|
|
|
+ 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"]}]}`),
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ hc := &httpClusterClient{
|
|
|
+ clientFactory: cf,
|
|
|
+ rand: rand.New(rand.NewSource(0)),
|
|
|
+ }
|
|
|
+ err := hc.reset([]string{"http://127.0.0.1:2379"})
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("unexpected error during setup: %#v", err)
|
|
|
+ }
|
|
|
+ ctx, cancel := context.WithCancel(context.Background())
|
|
|
+ cancel()
|
|
|
+
|
|
|
+ err = hc.AutoSync(ctx, time.Hour)
|
|
|
+ if err != context.Canceled {
|
|
|
+ t.Fatalf("incorrect error value: want=%v got=%v", context.Canceled, err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestHTTPClusterClientAutoSyncFail(t *testing.T) {
|
|
|
+ cf := newStaticHTTPClientFactory([]staticHTTPResponse{
|
|
|
+ {err: errors.New("fail!")},
|
|
|
+ })
|
|
|
+
|
|
|
+ hc := &httpClusterClient{
|
|
|
+ clientFactory: cf,
|
|
|
+ rand: rand.New(rand.NewSource(0)),
|
|
|
+ }
|
|
|
+ err := hc.reset([]string{"http://127.0.0.1:2379"})
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("unexpected error during setup: %#v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ err = hc.AutoSync(context.Background(), time.Hour)
|
|
|
+ if err.Error() != ErrClusterUnavailable.Error() {
|
|
|
+ t.Fatalf("incorrect error value: want=%v got=%v", ErrClusterUnavailable, err)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// TestHTTPClusterClientSyncPinEndpoint tests that Sync() pins the endpoint when
|
|
|
+// it gets the exactly same member list as before.
|
|
|
+func TestHTTPClusterClientSyncPinEndpoint(t *testing.T) {
|
|
|
+ cf := newStaticHTTPClientFactory([]staticHTTPResponse{
|
|
|
+ {
|
|
|
+ resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}},
|
|
|
+ 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"]}]}`),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}},
|
|
|
+ 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"]}]}`),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}},
|
|
|
+ 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"]}]}`),
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ hc := &httpClusterClient{
|
|
|
+ clientFactory: cf,
|
|
|
+ rand: rand.New(rand.NewSource(0)),
|
|
|
+ }
|
|
|
+ err := hc.reset([]string{"http://127.0.0.1:4003", "http://127.0.0.1:2379", "http://127.0.0.1:4001", "http://127.0.0.1:4002"})
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("unexpected error during setup: %#v", err)
|
|
|
+ }
|
|
|
+ pinnedEndpoint := hc.endpoints[hc.pinned]
|
|
|
+
|
|
|
+ for i := 0; i < 3; i++ {
|
|
|
+ err = hc.Sync(context.Background())
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("#%d: unexpected error during Sync: %#v", i, err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if g := hc.endpoints[hc.pinned]; g != pinnedEndpoint {
|
|
|
+ t.Errorf("#%d: pinned endpoint = %s, want %s", i, g, pinnedEndpoint)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func TestHTTPClusterClientResetFail(t *testing.T) {
|
|
|
tests := [][]string{
|
|
|
// need at least one endpoint
|
|
|
- []string{},
|
|
|
+ {},
|
|
|
|
|
|
// urls must be valid
|
|
|
- []string{":"},
|
|
|
+ {":"},
|
|
|
}
|
|
|
|
|
|
for i, tt := range tests {
|