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  }