go.etcd.io/etcd@v3.3.27+incompatible/client/integration/client_test.go (about) 1 // Copyright 2016 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 integration 16 17 import ( 18 "context" 19 "fmt" 20 "net/http" 21 "net/http/httptest" 22 "os" 23 "strings" 24 "sync/atomic" 25 "testing" 26 27 "github.com/coreos/etcd/client" 28 "github.com/coreos/etcd/integration" 29 "github.com/coreos/etcd/pkg/testutil" 30 ) 31 32 // TestV2NoRetryEOF tests destructive api calls won't retry on a disconnection. 33 func TestV2NoRetryEOF(t *testing.T) { 34 defer testutil.AfterTest(t) 35 // generate an EOF response; specify address so appears first in sorted ep list 36 lEOF := integration.NewListenerWithAddr(t, fmt.Sprintf("127.0.0.1:%05d", os.Getpid())) 37 defer lEOF.Close() 38 tries := uint32(0) 39 go func() { 40 for { 41 conn, err := lEOF.Accept() 42 if err != nil { 43 return 44 } 45 atomic.AddUint32(&tries, 1) 46 conn.Close() 47 } 48 }() 49 eofURL := integration.UrlScheme + "://" + lEOF.Addr().String() 50 cli := integration.MustNewHTTPClient(t, []string{eofURL, eofURL}, nil) 51 kapi := client.NewKeysAPI(cli) 52 for i, f := range noRetryList(kapi) { 53 startTries := atomic.LoadUint32(&tries) 54 if err := f(); err == nil { 55 t.Errorf("#%d: expected EOF error, got nil", i) 56 } 57 endTries := atomic.LoadUint32(&tries) 58 if startTries+1 != endTries { 59 t.Errorf("#%d: expected 1 try, got %d", i, endTries-startTries) 60 } 61 } 62 } 63 64 // TestV2NoRetryNoLeader tests destructive api calls won't retry if given an error code. 65 func TestV2NoRetryNoLeader(t *testing.T) { 66 defer testutil.AfterTest(t) 67 lHttp := integration.NewListenerWithAddr(t, fmt.Sprintf("127.0.0.1:%05d", os.Getpid())) 68 eh := &errHandler{errCode: http.StatusServiceUnavailable} 69 srv := httptest.NewUnstartedServer(eh) 70 defer lHttp.Close() 71 defer srv.Close() 72 srv.Listener = lHttp 73 go srv.Start() 74 lHttpURL := integration.UrlScheme + "://" + lHttp.Addr().String() 75 76 cli := integration.MustNewHTTPClient(t, []string{lHttpURL, lHttpURL}, nil) 77 kapi := client.NewKeysAPI(cli) 78 // test error code 79 for i, f := range noRetryList(kapi) { 80 reqs := eh.reqs 81 if err := f(); err == nil || !strings.Contains(err.Error(), "no leader") { 82 t.Errorf("#%d: expected \"no leader\", got %v", i, err) 83 } 84 if eh.reqs != reqs+1 { 85 t.Errorf("#%d: expected 1 request, got %d", i, eh.reqs-reqs) 86 } 87 } 88 } 89 90 // TestV2RetryRefuse tests destructive api calls will retry if a connection is refused. 91 func TestV2RetryRefuse(t *testing.T) { 92 defer testutil.AfterTest(t) 93 cl := integration.NewCluster(t, 1) 94 cl.Launch(t) 95 defer cl.Terminate(t) 96 // test connection refused; expect no error failover 97 cli := integration.MustNewHTTPClient(t, []string{integration.UrlScheme + "://refuseconn:123", cl.URL(0)}, nil) 98 kapi := client.NewKeysAPI(cli) 99 if _, err := kapi.Set(context.Background(), "/delkey", "def", nil); err != nil { 100 t.Fatal(err) 101 } 102 for i, f := range noRetryList(kapi) { 103 if err := f(); err != nil { 104 t.Errorf("#%d: unexpected retry failure (%v)", i, err) 105 } 106 } 107 } 108 109 type errHandler struct { 110 errCode int 111 reqs int 112 } 113 114 func (eh *errHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 115 req.Body.Close() 116 eh.reqs++ 117 w.WriteHeader(eh.errCode) 118 } 119 120 func noRetryList(kapi client.KeysAPI) []func() error { 121 return []func() error{ 122 func() error { 123 opts := &client.SetOptions{PrevExist: client.PrevNoExist} 124 _, err := kapi.Set(context.Background(), "/setkey", "bar", opts) 125 return err 126 }, 127 func() error { 128 _, err := kapi.Delete(context.Background(), "/delkey", nil) 129 return err 130 }, 131 } 132 }