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 }