Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / github.com / gophercloud / gophercloud / provider_client.go
1 package gophercloud
2
3 import (
4         "bytes"
5         "context"
6         "encoding/json"
7         "errors"
8         "io"
9         "io/ioutil"
10         "net/http"
11         "strings"
12         "sync"
13 )
14
15 // DefaultUserAgent is the default User-Agent string set in the request header.
16 const DefaultUserAgent = "gophercloud/2.0.0"
17
18 // UserAgent represents a User-Agent header.
19 type UserAgent struct {
20         // prepend is the slice of User-Agent strings to prepend to DefaultUserAgent.
21         // All the strings to prepend are accumulated and prepended in the Join method.
22         prepend []string
23 }
24
25 // Prepend prepends a user-defined string to the default User-Agent string. Users
26 // may pass in one or more strings to prepend.
27 func (ua *UserAgent) Prepend(s ...string) {
28         ua.prepend = append(s, ua.prepend...)
29 }
30
31 // Join concatenates all the user-defined User-Agend strings with the default
32 // Gophercloud User-Agent string.
33 func (ua *UserAgent) Join() string {
34         uaSlice := append(ua.prepend, DefaultUserAgent)
35         return strings.Join(uaSlice, " ")
36 }
37
38 // ProviderClient stores details that are required to interact with any
39 // services within a specific provider's API.
40 //
41 // Generally, you acquire a ProviderClient by calling the NewClient method in
42 // the appropriate provider's child package, providing whatever authentication
43 // credentials are required.
44 type ProviderClient struct {
45         // IdentityBase is the base URL used for a particular provider's identity
46         // service - it will be used when issuing authenticatation requests. It
47         // should point to the root resource of the identity service, not a specific
48         // identity version.
49         IdentityBase string
50
51         // IdentityEndpoint is the identity endpoint. This may be a specific version
52         // of the identity service. If this is the case, this endpoint is used rather
53         // than querying versions first.
54         IdentityEndpoint string
55
56         // TokenID is the ID of the most recently issued valid token.
57         // NOTE: Aside from within a custom ReauthFunc, this field shouldn't be set by an application.
58         // To safely read or write this value, call `Token` or `SetToken`, respectively
59         TokenID string
60
61         // EndpointLocator describes how this provider discovers the endpoints for
62         // its constituent services.
63         EndpointLocator EndpointLocator
64
65         // HTTPClient allows users to interject arbitrary http, https, or other transit behaviors.
66         HTTPClient http.Client
67
68         // UserAgent represents the User-Agent header in the HTTP request.
69         UserAgent UserAgent
70
71         // ReauthFunc is the function used to re-authenticate the user if the request
72         // fails with a 401 HTTP response code. This a needed because there may be multiple
73         // authentication functions for different Identity service versions.
74         ReauthFunc func() error
75
76         // Throwaway determines whether if this client is a throw-away client. It's a copy of user's provider client
77         // with the token and reauth func zeroed. Such client can be used to perform reauthorization.
78         Throwaway bool
79
80         // Context is the context passed to the HTTP request.
81         Context context.Context
82
83         // mut is a mutex for the client. It protects read and write access to client attributes such as getting
84         // and setting the TokenID.
85         mut *sync.RWMutex
86
87         // reauthmut is a mutex for reauthentication it attempts to ensure that only one reauthentication
88         // attempt happens at one time.
89         reauthmut *reauthlock
90
91         authResult AuthResult
92 }
93
94 // reauthlock represents a set of attributes used to help in the reauthentication process.
95 type reauthlock struct {
96         sync.RWMutex
97         reauthing    bool
98         reauthingErr error
99         done         *sync.Cond
100 }
101
102 // AuthenticatedHeaders returns a map of HTTP headers that are common for all
103 // authenticated service requests. Blocks if Reauthenticate is in progress.
104 func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) {
105         if client.IsThrowaway() {
106                 return
107         }
108         if client.reauthmut != nil {
109                 client.reauthmut.Lock()
110                 for client.reauthmut.reauthing {
111                         client.reauthmut.done.Wait()
112                 }
113                 client.reauthmut.Unlock()
114         }
115         t := client.Token()
116         if t == "" {
117                 return
118         }
119         return map[string]string{"X-Auth-Token": t}
120 }
121
122 // UseTokenLock creates a mutex that is used to allow safe concurrent access to the auth token.
123 // If the application's ProviderClient is not used concurrently, this doesn't need to be called.
124 func (client *ProviderClient) UseTokenLock() {
125         client.mut = new(sync.RWMutex)
126         client.reauthmut = new(reauthlock)
127 }
128
129 // GetAuthResult returns the result from the request that was used to obtain a
130 // provider client's Keystone token.
131 //
132 // The result is nil when authentication has not yet taken place, when the token
133 // was set manually with SetToken(), or when a ReauthFunc was used that does not
134 // record the AuthResult.
135 func (client *ProviderClient) GetAuthResult() AuthResult {
136         if client.mut != nil {
137                 client.mut.RLock()
138                 defer client.mut.RUnlock()
139         }
140         return client.authResult
141 }
142
143 // Token safely reads the value of the auth token from the ProviderClient. Applications should
144 // call this method to access the token instead of the TokenID field
145 func (client *ProviderClient) Token() string {
146         if client.mut != nil {
147                 client.mut.RLock()
148                 defer client.mut.RUnlock()
149         }
150         return client.TokenID
151 }
152
153 // SetToken safely sets the value of the auth token in the ProviderClient. Applications may
154 // use this method in a custom ReauthFunc.
155 //
156 // WARNING: This function is deprecated. Use SetTokenAndAuthResult() instead.
157 func (client *ProviderClient) SetToken(t string) {
158         if client.mut != nil {
159                 client.mut.Lock()
160                 defer client.mut.Unlock()
161         }
162         client.TokenID = t
163         client.authResult = nil
164 }
165
166 // SetTokenAndAuthResult safely sets the value of the auth token in the
167 // ProviderClient and also records the AuthResult that was returned from the
168 // token creation request. Applications may call this in a custom ReauthFunc.
169 func (client *ProviderClient) SetTokenAndAuthResult(r AuthResult) error {
170         tokenID := ""
171         var err error
172         if r != nil {
173                 tokenID, err = r.ExtractTokenID()
174                 if err != nil {
175                         return err
176                 }
177         }
178
179         if client.mut != nil {
180                 client.mut.Lock()
181                 defer client.mut.Unlock()
182         }
183         client.TokenID = tokenID
184         client.authResult = r
185         return nil
186 }
187
188 // CopyTokenFrom safely copies the token from another ProviderClient into the
189 // this one.
190 func (client *ProviderClient) CopyTokenFrom(other *ProviderClient) {
191         if client.mut != nil {
192                 client.mut.Lock()
193                 defer client.mut.Unlock()
194         }
195         if other.mut != nil && other.mut != client.mut {
196                 other.mut.RLock()
197                 defer other.mut.RUnlock()
198         }
199         client.TokenID = other.TokenID
200         client.authResult = other.authResult
201 }
202
203 // IsThrowaway safely reads the value of the client Throwaway field.
204 func (client *ProviderClient) IsThrowaway() bool {
205         if client.reauthmut != nil {
206                 client.reauthmut.RLock()
207                 defer client.reauthmut.RUnlock()
208         }
209         return client.Throwaway
210 }
211
212 // SetThrowaway safely sets the value of the client Throwaway field.
213 func (client *ProviderClient) SetThrowaway(v bool) {
214         if client.reauthmut != nil {
215                 client.reauthmut.Lock()
216                 defer client.reauthmut.Unlock()
217         }
218         client.Throwaway = v
219 }
220
221 // Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is
222 // called because of a 401 response, the caller may pass the previous token. In
223 // this case, the reauthentication can be skipped if another thread has already
224 // reauthenticated in the meantime. If no previous token is known, an empty
225 // string should be passed instead to force unconditional reauthentication.
226 func (client *ProviderClient) Reauthenticate(previousToken string) (err error) {
227         if client.ReauthFunc == nil {
228                 return nil
229         }
230
231         if client.reauthmut == nil {
232                 return client.ReauthFunc()
233         }
234
235         client.reauthmut.Lock()
236         if client.reauthmut.reauthing {
237                 for !client.reauthmut.reauthing {
238                         client.reauthmut.done.Wait()
239                 }
240                 err = client.reauthmut.reauthingErr
241                 client.reauthmut.Unlock()
242                 return err
243         }
244         client.reauthmut.Unlock()
245
246         client.reauthmut.Lock()
247         client.reauthmut.reauthing = true
248         client.reauthmut.done = sync.NewCond(client.reauthmut)
249         client.reauthmut.reauthingErr = nil
250         client.reauthmut.Unlock()
251
252         if previousToken == "" || client.TokenID == previousToken {
253                 err = client.ReauthFunc()
254         }
255
256         client.reauthmut.Lock()
257         client.reauthmut.reauthing = false
258         client.reauthmut.reauthingErr = err
259         client.reauthmut.done.Broadcast()
260         client.reauthmut.Unlock()
261         return
262 }
263
264 // RequestOpts customizes the behavior of the provider.Request() method.
265 type RequestOpts struct {
266         // JSONBody, if provided, will be encoded as JSON and used as the body of the HTTP request. The
267         // content type of the request will default to "application/json" unless overridden by MoreHeaders.
268         // It's an error to specify both a JSONBody and a RawBody.
269         JSONBody interface{}
270         // RawBody contains an io.Reader that will be consumed by the request directly. No content-type
271         // will be set unless one is provided explicitly by MoreHeaders.
272         RawBody io.Reader
273         // JSONResponse, if provided, will be populated with the contents of the response body parsed as
274         // JSON.
275         JSONResponse interface{}
276         // OkCodes contains a list of numeric HTTP status codes that should be interpreted as success. If
277         // the response has a different code, an error will be returned.
278         OkCodes []int
279         // MoreHeaders specifies additional HTTP headers to be provide on the request. If a header is
280         // provided with a blank value (""), that header will be *omitted* instead: use this to suppress
281         // the default Accept header or an inferred Content-Type, for example.
282         MoreHeaders map[string]string
283         // ErrorContext specifies the resource error type to return if an error is encountered.
284         // This lets resources override default error messages based on the response status code.
285         ErrorContext error
286 }
287
288 var applicationJSON = "application/json"
289
290 // Request performs an HTTP request using the ProviderClient's current HTTPClient. An authentication
291 // header will automatically be provided.
292 func (client *ProviderClient) Request(method, url string, options *RequestOpts) (*http.Response, error) {
293         var body io.Reader
294         var contentType *string
295
296         // Derive the content body by either encoding an arbitrary object as JSON, or by taking a provided
297         // io.ReadSeeker as-is. Default the content-type to application/json.
298         if options.JSONBody != nil {
299                 if options.RawBody != nil {
300                         return nil, errors.New("please provide only one of JSONBody or RawBody to gophercloud.Request()")
301                 }
302
303                 rendered, err := json.Marshal(options.JSONBody)
304                 if err != nil {
305                         return nil, err
306                 }
307
308                 body = bytes.NewReader(rendered)
309                 contentType = &applicationJSON
310         }
311
312         if options.RawBody != nil {
313                 body = options.RawBody
314         }
315
316         // Construct the http.Request.
317         req, err := http.NewRequest(method, url, body)
318         if err != nil {
319                 return nil, err
320         }
321         if client.Context != nil {
322                 req = req.WithContext(client.Context)
323         }
324
325         // Populate the request headers. Apply options.MoreHeaders last, to give the caller the chance to
326         // modify or omit any header.
327         if contentType != nil {
328                 req.Header.Set("Content-Type", *contentType)
329         }
330         req.Header.Set("Accept", applicationJSON)
331
332         // Set the User-Agent header
333         req.Header.Set("User-Agent", client.UserAgent.Join())
334
335         if options.MoreHeaders != nil {
336                 for k, v := range options.MoreHeaders {
337                         if v != "" {
338                                 req.Header.Set(k, v)
339                         } else {
340                                 req.Header.Del(k)
341                         }
342                 }
343         }
344
345         // get latest token from client
346         for k, v := range client.AuthenticatedHeaders() {
347                 req.Header.Set(k, v)
348         }
349
350         // Set connection parameter to close the connection immediately when we've got the response
351         req.Close = true
352
353         prereqtok := req.Header.Get("X-Auth-Token")
354
355         // Issue the request.
356         resp, err := client.HTTPClient.Do(req)
357         if err != nil {
358                 return nil, err
359         }
360
361         // Allow default OkCodes if none explicitly set
362         okc := options.OkCodes
363         if okc == nil {
364                 okc = defaultOkCodes(method)
365         }
366
367         // Validate the HTTP response status.
368         var ok bool
369         for _, code := range okc {
370                 if resp.StatusCode == code {
371                         ok = true
372                         break
373                 }
374         }
375
376         if !ok {
377                 body, _ := ioutil.ReadAll(resp.Body)
378                 resp.Body.Close()
379                 respErr := ErrUnexpectedResponseCode{
380                         URL:      url,
381                         Method:   method,
382                         Expected: options.OkCodes,
383                         Actual:   resp.StatusCode,
384                         Body:     body,
385                 }
386
387                 errType := options.ErrorContext
388                 switch resp.StatusCode {
389                 case http.StatusBadRequest:
390                         err = ErrDefault400{respErr}
391                         if error400er, ok := errType.(Err400er); ok {
392                                 err = error400er.Error400(respErr)
393                         }
394                 case http.StatusUnauthorized:
395                         if client.ReauthFunc != nil {
396                                 err = client.Reauthenticate(prereqtok)
397                                 if err != nil {
398                                         e := &ErrUnableToReauthenticate{}
399                                         e.ErrOriginal = respErr
400                                         return nil, e
401                                 }
402                                 if options.RawBody != nil {
403                                         if seeker, ok := options.RawBody.(io.Seeker); ok {
404                                                 seeker.Seek(0, 0)
405                                         }
406                                 }
407                                 resp, err = client.Request(method, url, options)
408                                 if err != nil {
409                                         switch err.(type) {
410                                         case *ErrUnexpectedResponseCode:
411                                                 e := &ErrErrorAfterReauthentication{}
412                                                 e.ErrOriginal = err.(*ErrUnexpectedResponseCode)
413                                                 return nil, e
414                                         default:
415                                                 e := &ErrErrorAfterReauthentication{}
416                                                 e.ErrOriginal = err
417                                                 return nil, e
418                                         }
419                                 }
420                                 return resp, nil
421                         }
422                         err = ErrDefault401{respErr}
423                         if error401er, ok := errType.(Err401er); ok {
424                                 err = error401er.Error401(respErr)
425                         }
426                 case http.StatusForbidden:
427                         err = ErrDefault403{respErr}
428                         if error403er, ok := errType.(Err403er); ok {
429                                 err = error403er.Error403(respErr)
430                         }
431                 case http.StatusNotFound:
432                         err = ErrDefault404{respErr}
433                         if error404er, ok := errType.(Err404er); ok {
434                                 err = error404er.Error404(respErr)
435                         }
436                 case http.StatusMethodNotAllowed:
437                         err = ErrDefault405{respErr}
438                         if error405er, ok := errType.(Err405er); ok {
439                                 err = error405er.Error405(respErr)
440                         }
441                 case http.StatusRequestTimeout:
442                         err = ErrDefault408{respErr}
443                         if error408er, ok := errType.(Err408er); ok {
444                                 err = error408er.Error408(respErr)
445                         }
446                 case http.StatusConflict:
447                         err = ErrDefault409{respErr}
448                         if error409er, ok := errType.(Err409er); ok {
449                                 err = error409er.Error409(respErr)
450                         }
451                 case 429:
452                         err = ErrDefault429{respErr}
453                         if error429er, ok := errType.(Err429er); ok {
454                                 err = error429er.Error429(respErr)
455                         }
456                 case http.StatusInternalServerError:
457                         err = ErrDefault500{respErr}
458                         if error500er, ok := errType.(Err500er); ok {
459                                 err = error500er.Error500(respErr)
460                         }
461                 case http.StatusServiceUnavailable:
462                         err = ErrDefault503{respErr}
463                         if error503er, ok := errType.(Err503er); ok {
464                                 err = error503er.Error503(respErr)
465                         }
466                 }
467
468                 if err == nil {
469                         err = respErr
470                 }
471
472                 return resp, err
473         }
474
475         // Parse the response body as JSON, if requested to do so.
476         if options.JSONResponse != nil {
477                 defer resp.Body.Close()
478                 if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil {
479                         return nil, err
480                 }
481         }
482
483         return resp, nil
484 }
485
486 func defaultOkCodes(method string) []int {
487         switch {
488         case method == "GET":
489                 return []int{200}
490         case method == "POST":
491                 return []int{201, 202}
492         case method == "PUT":
493                 return []int{201, 202}
494         case method == "PATCH":
495                 return []int{200, 202, 204}
496         case method == "DELETE":
497                 return []int{202, 204}
498         }
499
500         return []int{}
501 }