github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/testing/provider_client_test.go (about)

     1  package testing
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"math"
     8  	"net"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"strings"
    12  	"sync"
    13  	"sync/atomic"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/huaweicloud/golangsdk"
    18  	th "github.com/huaweicloud/golangsdk/testhelper"
    19  	"github.com/huaweicloud/golangsdk/testhelper/client"
    20  )
    21  
    22  func TestAuthenticatedHeaders(t *testing.T) {
    23  	p := &golangsdk.ProviderClient{
    24  		TokenID: "1234",
    25  	}
    26  	expected := map[string]string{"X-Auth-Token": "1234"}
    27  	actual := p.AuthenticatedHeaders()
    28  	th.CheckDeepEquals(t, expected, actual)
    29  }
    30  
    31  func TestUserAgent(t *testing.T) {
    32  	p := &golangsdk.ProviderClient{}
    33  
    34  	p.UserAgent.Prepend("custom-user-agent/2.4.0")
    35  	expected := "custom-user-agent/2.4.0 golangsdk/2.0.0"
    36  	actual := p.UserAgent.Join()
    37  	th.CheckEquals(t, expected, actual)
    38  
    39  	p.UserAgent.Prepend("another-custom-user-agent/0.3.0", "a-third-ua/5.9.0")
    40  	expected = "another-custom-user-agent/0.3.0 a-third-ua/5.9.0 custom-user-agent/2.4.0 golangsdk/2.0.0"
    41  	actual = p.UserAgent.Join()
    42  	th.CheckEquals(t, expected, actual)
    43  
    44  	p.UserAgent = golangsdk.UserAgent{}
    45  	expected = "golangsdk/2.0.0"
    46  	actual = p.UserAgent.Join()
    47  	th.CheckEquals(t, expected, actual)
    48  }
    49  
    50  func TestConcurrentReauth(t *testing.T) {
    51  	var info = struct {
    52  		numreauths int
    53  		mut        *sync.RWMutex
    54  	}{
    55  		0,
    56  		new(sync.RWMutex),
    57  	}
    58  
    59  	numconc := 20
    60  
    61  	prereauthTok := client.TokenID
    62  	postreauthTok := "12345678"
    63  
    64  	p := new(golangsdk.ProviderClient)
    65  	p.UseTokenLock()
    66  	p.SetToken(prereauthTok)
    67  	p.ReauthFunc = func() error {
    68  		time.Sleep(1 * time.Second)
    69  		p.AuthenticatedHeaders()
    70  		info.mut.Lock()
    71  		info.numreauths++
    72  		info.mut.Unlock()
    73  		p.TokenID = postreauthTok
    74  		return nil
    75  	}
    76  
    77  	th.SetupHTTP()
    78  	defer th.TeardownHTTP()
    79  
    80  	th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) {
    81  		if r.Header.Get("X-Auth-Token") != postreauthTok {
    82  			w.WriteHeader(http.StatusUnauthorized)
    83  			return
    84  		}
    85  		info.mut.RLock()
    86  		hasReauthed := info.numreauths != 0
    87  		info.mut.RUnlock()
    88  
    89  		if hasReauthed {
    90  			th.CheckEquals(t, p.Token(), postreauthTok)
    91  		}
    92  
    93  		w.Header().Add("Content-Type", "application/json")
    94  		fmt.Fprintf(w, `{}`)
    95  	})
    96  
    97  	wg := new(sync.WaitGroup)
    98  	reqopts := new(golangsdk.RequestOpts)
    99  	reqopts.KeepResponseBody = true
   100  	reqopts.MoreHeaders = map[string]string{
   101  		"X-Auth-Token": prereauthTok,
   102  	}
   103  
   104  	for i := 0; i < numconc; i++ {
   105  		wg.Add(1)
   106  		go func() {
   107  			defer wg.Done()
   108  			resp, err := p.Request("GET", fmt.Sprintf("%s/route", th.Endpoint()), reqopts)
   109  			th.CheckNoErr(t, err)
   110  			if resp == nil {
   111  				t.Errorf("got a nil response")
   112  				return
   113  			}
   114  			if resp.Body == nil {
   115  				t.Errorf("response body was nil")
   116  				return
   117  			}
   118  			defer resp.Body.Close()
   119  			actual, err := ioutil.ReadAll(resp.Body)
   120  			if err != nil {
   121  				t.Errorf("error reading response body: %s", err)
   122  				return
   123  			}
   124  			th.CheckByteArrayEquals(t, []byte(`{}`), actual)
   125  		}()
   126  	}
   127  
   128  	wg.Wait()
   129  
   130  	th.AssertEquals(t, 1, info.numreauths)
   131  }
   132  
   133  func TestRequestWithContext(t *testing.T) {
   134  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   135  		fmt.Fprintln(w, "OK")
   136  	}))
   137  	defer ts.Close()
   138  
   139  	ctx, cancel := context.WithCancel(context.Background())
   140  	p := &golangsdk.ProviderClient{Context: ctx}
   141  
   142  	res, err := p.Request("GET", ts.URL, &golangsdk.RequestOpts{KeepResponseBody: true})
   143  	th.AssertNoErr(t, err)
   144  	_, err = ioutil.ReadAll(res.Body)
   145  	th.AssertNoErr(t, err)
   146  	err = res.Body.Close()
   147  	th.AssertNoErr(t, err)
   148  
   149  	cancel()
   150  	res, err = p.Request("GET", ts.URL, &golangsdk.RequestOpts{})
   151  	if err == nil {
   152  		t.Fatal("expecting error, got nil")
   153  	}
   154  	if !strings.Contains(err.Error(), ctx.Err().Error()) {
   155  		t.Fatalf("expecting error to contain: %q, got %q", ctx.Err().Error(), err.Error())
   156  	}
   157  }
   158  
   159  func TestRequestConnectionReuse(t *testing.T) {
   160  	ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   161  		fmt.Fprintln(w, "OK")
   162  	}))
   163  
   164  	// an amount of iterations
   165  	var iter = 10000
   166  	// connections tracks an amount of connections made
   167  	var connections int64
   168  
   169  	ts.Config.ConnState = func(_ net.Conn, s http.ConnState) {
   170  		// track an amount of connections
   171  		if s == http.StateNew {
   172  			atomic.AddInt64(&connections, 1)
   173  		}
   174  	}
   175  	ts.Start()
   176  	defer ts.Close()
   177  
   178  	p := &golangsdk.ProviderClient{}
   179  	reqopts := new(golangsdk.RequestOpts)
   180  
   181  	for i := 0; i < iter; i++ {
   182  		_, err := p.Request("GET", ts.URL, reqopts)
   183  		th.AssertNoErr(t, err)
   184  	}
   185  
   186  	th.AssertEquals(t, int64(1), connections)
   187  }
   188  
   189  func TestRequestConnectionClose(t *testing.T) {
   190  	ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   191  		fmt.Fprintln(w, "OK")
   192  	}))
   193  
   194  	// an amount of iterations
   195  	var iter = 10
   196  	// connections tracks an amount of connections made
   197  	var connections int64
   198  
   199  	ts.Config.ConnState = func(_ net.Conn, s http.ConnState) {
   200  		// track an amount of connections
   201  		if s == http.StateNew {
   202  			atomic.AddInt64(&connections, 1)
   203  		}
   204  	}
   205  	ts.Start()
   206  	defer ts.Close()
   207  
   208  	p := &golangsdk.ProviderClient{}
   209  	reqopts := new(golangsdk.RequestOpts)
   210  	reqopts.KeepResponseBody = true
   211  
   212  	for i := 0; i < iter; i++ {
   213  		_, err := p.Request("GET", ts.URL, reqopts)
   214  		th.AssertNoErr(t, err)
   215  	}
   216  
   217  	th.AssertEquals(t, int64(iter), connections)
   218  }
   219  
   220  func retryTest(retryCounter *uint, t *testing.T) golangsdk.RetryFunc {
   221  	return func(ctx context.Context, respErr *golangsdk.ErrUnexpectedResponseCode, e error, retries uint) error {
   222  		seconds := math.Pow(2, float64(retries))
   223  		if seconds > 60 { // won't wait more than 60 seconds
   224  			seconds = 60
   225  		}
   226  
   227  		sleep := time.Duration(seconds) * time.Second
   228  
   229  		if ctx != nil {
   230  			t.Logf("Context sleeping for %d seconds, retry number %d", int(seconds), retries)
   231  			select {
   232  			case <-time.After(sleep):
   233  				t.Log("sleep is over")
   234  			case <-ctx.Done():
   235  				t.Log(ctx.Err())
   236  				return e
   237  			}
   238  		} else {
   239  			t.Logf("Sleeping for %d seconds, retry number %d", int(seconds), retries)
   240  			time.Sleep(sleep)
   241  			t.Log("sleep is over")
   242  		}
   243  
   244  		*retryCounter = *retryCounter + 1
   245  
   246  		return nil
   247  	}
   248  }
   249  
   250  func TestRequestRetry(t *testing.T) {
   251  	var retryCounter uint
   252  
   253  	p := &golangsdk.ProviderClient{}
   254  	p.UseTokenLock()
   255  	p.SetToken(client.TokenID)
   256  	p.MaxBackoffRetries = 3
   257  
   258  	p.RetryBackoffFunc = retryTest(&retryCounter, t)
   259  
   260  	th.SetupHTTP()
   261  	defer th.TeardownHTTP()
   262  
   263  	th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) {
   264  		//always reply 429
   265  		http.Error(w, "retry later", http.StatusTooManyRequests)
   266  	})
   267  
   268  	_, err := p.Request("GET", th.Endpoint()+"/route", &golangsdk.RequestOpts{})
   269  	if err == nil {
   270  		t.Fatal("expecting error, got nil")
   271  	}
   272  	t.Logf("error message: %s", err)
   273  	th.AssertEquals(t, retryCounter, p.MaxBackoffRetries)
   274  }
   275  
   276  func TestRequestRetrySuccess(t *testing.T) {
   277  	var retryCounter uint
   278  
   279  	p := &golangsdk.ProviderClient{}
   280  	p.UseTokenLock()
   281  	p.SetToken(client.TokenID)
   282  	p.MaxBackoffRetries = 3
   283  
   284  	p.RetryBackoffFunc = retryTest(&retryCounter, t)
   285  
   286  	th.SetupHTTP()
   287  	defer th.TeardownHTTP()
   288  
   289  	th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) {
   290  		//always reply 200
   291  		http.Error(w, "retry later", http.StatusOK)
   292  	})
   293  
   294  	_, err := p.Request("GET", th.Endpoint()+"/route", &golangsdk.RequestOpts{})
   295  	if err != nil {
   296  		t.Fatal(err)
   297  	}
   298  	th.AssertEquals(t, retryCounter, uint(0))
   299  }
   300  
   301  func TestRequestRetryContext(t *testing.T) {
   302  	var retryCounter uint
   303  
   304  	ctx, cancel := context.WithCancel(context.Background())
   305  	go func() {
   306  		sleep := 5 * time.Second
   307  		time.Sleep(sleep)
   308  		cancel()
   309  	}()
   310  
   311  	p := &golangsdk.ProviderClient{
   312  		Context: ctx,
   313  	}
   314  	p.UseTokenLock()
   315  	p.SetToken(client.TokenID)
   316  	p.MaxBackoffRetries = 3
   317  
   318  	p.RetryBackoffFunc = retryTest(&retryCounter, t)
   319  
   320  	th.SetupHTTP()
   321  	defer th.TeardownHTTP()
   322  
   323  	th.Mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) {
   324  		//always reply 429
   325  		http.Error(w, "retry later", http.StatusTooManyRequests)
   326  	})
   327  
   328  	_, err := p.Request("GET", th.Endpoint()+"/route", &golangsdk.RequestOpts{})
   329  	if err == nil {
   330  		t.Fatal("expecting error, got nil")
   331  	}
   332  	t.Logf("retryCounter: %d, p.MaxBackoffRetries: %d", retryCounter, p.MaxBackoffRetries)
   333  	th.AssertEquals(t, retryCounter, p.MaxBackoffRetries-1)
   334  }