github.com/leeclow-ops/gophercloud@v1.2.1/provider_client.go (about)

     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 (
    17  	DefaultUserAgent         = "gophercloud/v1.2.0"
    18  	DefaultMaxBackoffRetries = 60
    19  )
    20  
    21  // UserAgent represents a User-Agent header.
    22  type UserAgent struct {
    23  	// prepend is the slice of User-Agent strings to prepend to DefaultUserAgent.
    24  	// All the strings to prepend are accumulated and prepended in the Join method.
    25  	prepend []string
    26  }
    27  
    28  type RetryBackoffFunc func(context.Context, *ErrUnexpectedResponseCode, error, uint) error
    29  
    30  // RetryFunc is a catch-all function for retrying failed API requests.
    31  // If it returns nil, the request will be retried.  If it returns an error,
    32  // the request method will exit with that error.  failCount is the number of
    33  // times the request has failed (starting at 1).
    34  type RetryFunc func(context context.Context, method, url string, options *RequestOpts, err error, failCount uint) error
    35  
    36  // Prepend prepends a user-defined string to the default User-Agent string. Users
    37  // may pass in one or more strings to prepend.
    38  func (ua *UserAgent) Prepend(s ...string) {
    39  	ua.prepend = append(s, ua.prepend...)
    40  }
    41  
    42  // Join concatenates all the user-defined User-Agend strings with the default
    43  // Gophercloud User-Agent string.
    44  func (ua *UserAgent) Join() string {
    45  	uaSlice := append(ua.prepend, DefaultUserAgent)
    46  	return strings.Join(uaSlice, " ")
    47  }
    48  
    49  // ProviderClient stores details that are required to interact with any
    50  // services within a specific provider's API.
    51  //
    52  // Generally, you acquire a ProviderClient by calling the NewClient method in
    53  // the appropriate provider's child package, providing whatever authentication
    54  // credentials are required.
    55  type ProviderClient struct {
    56  	// IdentityBase is the base URL used for a particular provider's identity
    57  	// service - it will be used when issuing authenticatation requests. It
    58  	// should point to the root resource of the identity service, not a specific
    59  	// identity version.
    60  	IdentityBase string
    61  
    62  	// IdentityEndpoint is the identity endpoint. This may be a specific version
    63  	// of the identity service. If this is the case, this endpoint is used rather
    64  	// than querying versions first.
    65  	IdentityEndpoint string
    66  
    67  	// TokenID is the ID of the most recently issued valid token.
    68  	// NOTE: Aside from within a custom ReauthFunc, this field shouldn't be set by an application.
    69  	// To safely read or write this value, call `Token` or `SetToken`, respectively
    70  	TokenID string
    71  
    72  	// EndpointLocator describes how this provider discovers the endpoints for
    73  	// its constituent services.
    74  	EndpointLocator EndpointLocator
    75  
    76  	// HTTPClient allows users to interject arbitrary http, https, or other transit behaviors.
    77  	HTTPClient http.Client
    78  
    79  	// UserAgent represents the User-Agent header in the HTTP request.
    80  	UserAgent UserAgent
    81  
    82  	// ReauthFunc is the function used to re-authenticate the user if the request
    83  	// fails with a 401 HTTP response code. This a needed because there may be multiple
    84  	// authentication functions for different Identity service versions.
    85  	ReauthFunc func() error
    86  
    87  	// Throwaway determines whether if this client is a throw-away client. It's a copy of user's provider client
    88  	// with the token and reauth func zeroed. Such client can be used to perform reauthorization.
    89  	Throwaway bool
    90  
    91  	// Context is the context passed to the HTTP request.
    92  	Context context.Context
    93  
    94  	// Retry backoff func is called when rate limited.
    95  	RetryBackoffFunc RetryBackoffFunc
    96  
    97  	// MaxBackoffRetries set the maximum number of backoffs. When not set, defaults to DefaultMaxBackoffRetries
    98  	MaxBackoffRetries uint
    99  
   100  	// A general failed request handler method - this is always called in the end if a request failed. Leave as nil
   101  	// to abort when an error is encountered.
   102  	RetryFunc RetryFunc
   103  
   104  	// mut is a mutex for the client. It protects read and write access to client attributes such as getting
   105  	// and setting the TokenID.
   106  	mut *sync.RWMutex
   107  
   108  	// reauthmut is a mutex for reauthentication it attempts to ensure that only one reauthentication
   109  	// attempt happens at one time.
   110  	reauthmut *reauthlock
   111  
   112  	authResult AuthResult
   113  }
   114  
   115  // reauthlock represents a set of attributes used to help in the reauthentication process.
   116  type reauthlock struct {
   117  	sync.RWMutex
   118  	ongoing *reauthFuture
   119  }
   120  
   121  // reauthFuture represents future result of the reauthentication process.
   122  // while done channel is not closed, reauthentication is in progress.
   123  // when done channel is closed, err contains the result of reauthentication.
   124  type reauthFuture struct {
   125  	done chan struct{}
   126  	err  error
   127  }
   128  
   129  func newReauthFuture() *reauthFuture {
   130  	return &reauthFuture{
   131  		make(chan struct{}),
   132  		nil,
   133  	}
   134  }
   135  
   136  func (f *reauthFuture) Set(err error) {
   137  	f.err = err
   138  	close(f.done)
   139  }
   140  
   141  func (f *reauthFuture) Get() error {
   142  	<-f.done
   143  	return f.err
   144  }
   145  
   146  // AuthenticatedHeaders returns a map of HTTP headers that are common for all
   147  // authenticated service requests. Blocks if Reauthenticate is in progress.
   148  func (client *ProviderClient) AuthenticatedHeaders() (m map[string]string) {
   149  	if client.IsThrowaway() {
   150  		return
   151  	}
   152  	if client.reauthmut != nil {
   153  		// If a Reauthenticate is in progress, wait for it to complete.
   154  		client.reauthmut.Lock()
   155  		ongoing := client.reauthmut.ongoing
   156  		client.reauthmut.Unlock()
   157  		if ongoing != nil {
   158  			_ = ongoing.Get()
   159  		}
   160  	}
   161  	t := client.Token()
   162  	if t == "" {
   163  		return
   164  	}
   165  	return map[string]string{"X-Auth-Token": t}
   166  }
   167  
   168  // UseTokenLock creates a mutex that is used to allow safe concurrent access to the auth token.
   169  // If the application's ProviderClient is not used concurrently, this doesn't need to be called.
   170  func (client *ProviderClient) UseTokenLock() {
   171  	client.mut = new(sync.RWMutex)
   172  	client.reauthmut = new(reauthlock)
   173  }
   174  
   175  // GetAuthResult returns the result from the request that was used to obtain a
   176  // provider client's Keystone token.
   177  //
   178  // The result is nil when authentication has not yet taken place, when the token
   179  // was set manually with SetToken(), or when a ReauthFunc was used that does not
   180  // record the AuthResult.
   181  func (client *ProviderClient) GetAuthResult() AuthResult {
   182  	if client.mut != nil {
   183  		client.mut.RLock()
   184  		defer client.mut.RUnlock()
   185  	}
   186  	return client.authResult
   187  }
   188  
   189  // Token safely reads the value of the auth token from the ProviderClient. Applications should
   190  // call this method to access the token instead of the TokenID field
   191  func (client *ProviderClient) Token() string {
   192  	if client.mut != nil {
   193  		client.mut.RLock()
   194  		defer client.mut.RUnlock()
   195  	}
   196  	return client.TokenID
   197  }
   198  
   199  // SetToken safely sets the value of the auth token in the ProviderClient. Applications may
   200  // use this method in a custom ReauthFunc.
   201  //
   202  // WARNING: This function is deprecated. Use SetTokenAndAuthResult() instead.
   203  func (client *ProviderClient) SetToken(t string) {
   204  	if client.mut != nil {
   205  		client.mut.Lock()
   206  		defer client.mut.Unlock()
   207  	}
   208  	client.TokenID = t
   209  	client.authResult = nil
   210  }
   211  
   212  // SetTokenAndAuthResult safely sets the value of the auth token in the
   213  // ProviderClient and also records the AuthResult that was returned from the
   214  // token creation request. Applications may call this in a custom ReauthFunc.
   215  func (client *ProviderClient) SetTokenAndAuthResult(r AuthResult) error {
   216  	tokenID := ""
   217  	var err error
   218  	if r != nil {
   219  		tokenID, err = r.ExtractTokenID()
   220  		if err != nil {
   221  			return err
   222  		}
   223  	}
   224  
   225  	if client.mut != nil {
   226  		client.mut.Lock()
   227  		defer client.mut.Unlock()
   228  	}
   229  	client.TokenID = tokenID
   230  	client.authResult = r
   231  	return nil
   232  }
   233  
   234  // CopyTokenFrom safely copies the token from another ProviderClient into the
   235  // this one.
   236  func (client *ProviderClient) CopyTokenFrom(other *ProviderClient) {
   237  	if client.mut != nil {
   238  		client.mut.Lock()
   239  		defer client.mut.Unlock()
   240  	}
   241  	if other.mut != nil && other.mut != client.mut {
   242  		other.mut.RLock()
   243  		defer other.mut.RUnlock()
   244  	}
   245  	client.TokenID = other.TokenID
   246  	client.authResult = other.authResult
   247  }
   248  
   249  // IsThrowaway safely reads the value of the client Throwaway field.
   250  func (client *ProviderClient) IsThrowaway() bool {
   251  	if client.reauthmut != nil {
   252  		client.reauthmut.RLock()
   253  		defer client.reauthmut.RUnlock()
   254  	}
   255  	return client.Throwaway
   256  }
   257  
   258  // SetThrowaway safely sets the value of the client Throwaway field.
   259  func (client *ProviderClient) SetThrowaway(v bool) {
   260  	if client.reauthmut != nil {
   261  		client.reauthmut.Lock()
   262  		defer client.reauthmut.Unlock()
   263  	}
   264  	client.Throwaway = v
   265  }
   266  
   267  // Reauthenticate calls client.ReauthFunc in a thread-safe way. If this is
   268  // called because of a 401 response, the caller may pass the previous token. In
   269  // this case, the reauthentication can be skipped if another thread has already
   270  // reauthenticated in the meantime. If no previous token is known, an empty
   271  // string should be passed instead to force unconditional reauthentication.
   272  func (client *ProviderClient) Reauthenticate(previousToken string) error {
   273  	if client.ReauthFunc == nil {
   274  		return nil
   275  	}
   276  
   277  	if client.reauthmut == nil {
   278  		return client.ReauthFunc()
   279  	}
   280  
   281  	future := newReauthFuture()
   282  
   283  	// Check if a Reauthenticate is in progress, or start one if not.
   284  	client.reauthmut.Lock()
   285  	ongoing := client.reauthmut.ongoing
   286  	if ongoing == nil {
   287  		client.reauthmut.ongoing = future
   288  	}
   289  	client.reauthmut.Unlock()
   290  
   291  	// If Reauthenticate is running elsewhere, wait for its result.
   292  	if ongoing != nil {
   293  		return ongoing.Get()
   294  	}
   295  
   296  	// Perform the actual reauthentication.
   297  	var err error
   298  	if previousToken == "" || client.TokenID == previousToken {
   299  		err = client.ReauthFunc()
   300  	} else {
   301  		err = nil
   302  	}
   303  
   304  	// Mark Reauthenticate as finished.
   305  	client.reauthmut.Lock()
   306  	client.reauthmut.ongoing.Set(err)
   307  	client.reauthmut.ongoing = nil
   308  	client.reauthmut.Unlock()
   309  
   310  	return err
   311  }
   312  
   313  // RequestOpts customizes the behavior of the provider.Request() method.
   314  type RequestOpts struct {
   315  	// JSONBody, if provided, will be encoded as JSON and used as the body of the HTTP request. The
   316  	// content type of the request will default to "application/json" unless overridden by MoreHeaders.
   317  	// It's an error to specify both a JSONBody and a RawBody.
   318  	JSONBody interface{}
   319  	// RawBody contains an io.Reader that will be consumed by the request directly. No content-type
   320  	// will be set unless one is provided explicitly by MoreHeaders.
   321  	RawBody io.Reader
   322  	// JSONResponse, if provided, will be populated with the contents of the response body parsed as
   323  	// JSON.
   324  	JSONResponse interface{}
   325  	// OkCodes contains a list of numeric HTTP status codes that should be interpreted as success. If
   326  	// the response has a different code, an error will be returned.
   327  	OkCodes []int
   328  	// MoreHeaders specifies additional HTTP headers to be provided on the request.
   329  	// MoreHeaders will be overridden by OmitHeaders
   330  	MoreHeaders map[string]string
   331  	// OmitHeaders specifies the HTTP headers which should be omitted.
   332  	// OmitHeaders will override MoreHeaders
   333  	OmitHeaders []string
   334  	// ErrorContext specifies the resource error type to return if an error is encountered.
   335  	// This lets resources override default error messages based on the response status code.
   336  	ErrorContext error
   337  	// KeepResponseBody specifies whether to keep the HTTP response body. Usually used, when the HTTP
   338  	// response body is considered for further use. Valid when JSONResponse is nil.
   339  	KeepResponseBody bool
   340  }
   341  
   342  // requestState contains temporary state for a single ProviderClient.Request() call.
   343  type requestState struct {
   344  	// This flag indicates if we have reauthenticated during this request because of a 401 response.
   345  	// It ensures that we don't reauthenticate multiple times for a single request. If we
   346  	// reauthenticate, but keep getting 401 responses with the fresh token, reauthenticating some more
   347  	// will just get us into an infinite loop.
   348  	hasReauthenticated bool
   349  	// Retry-After backoff counter, increments during each backoff call
   350  	retries uint
   351  }
   352  
   353  var applicationJSON = "application/json"
   354  
   355  // Request performs an HTTP request using the ProviderClient's current HTTPClient. An authentication
   356  // header will automatically be provided.
   357  func (client *ProviderClient) Request(method, url string, options *RequestOpts) (*http.Response, error) {
   358  	return client.doRequest(method, url, options, &requestState{
   359  		hasReauthenticated: false,
   360  	})
   361  }
   362  
   363  func (client *ProviderClient) doRequest(method, url string, options *RequestOpts, state *requestState) (*http.Response, error) {
   364  	var body io.Reader
   365  	var contentType *string
   366  
   367  	// Derive the content body by either encoding an arbitrary object as JSON, or by taking a provided
   368  	// io.ReadSeeker as-is. Default the content-type to application/json.
   369  	if options.JSONBody != nil {
   370  		if options.RawBody != nil {
   371  			return nil, errors.New("please provide only one of JSONBody or RawBody to gophercloud.Request()")
   372  		}
   373  
   374  		rendered, err := json.Marshal(options.JSONBody)
   375  		if err != nil {
   376  			return nil, err
   377  		}
   378  
   379  		body = bytes.NewReader(rendered)
   380  		contentType = &applicationJSON
   381  	}
   382  
   383  	// Return an error, when "KeepResponseBody" is true and "JSONResponse" is not nil
   384  	if options.KeepResponseBody && options.JSONResponse != nil {
   385  		return nil, errors.New("cannot use KeepResponseBody when JSONResponse is not nil")
   386  	}
   387  
   388  	if options.RawBody != nil {
   389  		body = options.RawBody
   390  	}
   391  
   392  	// Construct the http.Request.
   393  	req, err := http.NewRequest(method, url, body)
   394  	if err != nil {
   395  		return nil, err
   396  	}
   397  	if client.Context != nil {
   398  		req = req.WithContext(client.Context)
   399  	}
   400  
   401  	// Populate the request headers.
   402  	// Apply options.MoreHeaders and options.OmitHeaders, to give the caller the chance to
   403  	// modify or omit any header.
   404  	if contentType != nil {
   405  		req.Header.Set("Content-Type", *contentType)
   406  	}
   407  	req.Header.Set("Accept", applicationJSON)
   408  
   409  	// Set the User-Agent header
   410  	req.Header.Set("User-Agent", client.UserAgent.Join())
   411  
   412  	if options.MoreHeaders != nil {
   413  		for k, v := range options.MoreHeaders {
   414  			req.Header.Set(k, v)
   415  			// Set the Host header
   416  			if k == "Host" {
   417  				req.Host = v
   418  			}
   419  		}
   420  	}
   421  
   422  	for _, v := range options.OmitHeaders {
   423  		req.Header.Del(v)
   424  	}
   425  
   426  	// get latest token from client
   427  	for k, v := range client.AuthenticatedHeaders() {
   428  		req.Header.Set(k, v)
   429  	}
   430  
   431  	prereqtok := req.Header.Get("X-Auth-Token")
   432  
   433  	// Issue the request.
   434  	resp, err := client.HTTPClient.Do(req)
   435  	if err != nil {
   436  		if client.RetryFunc != nil {
   437  			var e error
   438  			state.retries = state.retries + 1
   439  			e = client.RetryFunc(client.Context, method, url, options, err, state.retries)
   440  			if e != nil {
   441  				return nil, e
   442  			}
   443  
   444  			return client.doRequest(method, url, options, state)
   445  		}
   446  		return nil, err
   447  	}
   448  
   449  	// Allow default OkCodes if none explicitly set
   450  	okc := options.OkCodes
   451  	if okc == nil {
   452  		okc = defaultOkCodes(method)
   453  	}
   454  
   455  	// Validate the HTTP response status.
   456  	var ok bool
   457  	for _, code := range okc {
   458  		if resp.StatusCode == code {
   459  			ok = true
   460  			break
   461  		}
   462  	}
   463  
   464  	if !ok {
   465  		body, _ := ioutil.ReadAll(resp.Body)
   466  		resp.Body.Close()
   467  		respErr := ErrUnexpectedResponseCode{
   468  			URL:            url,
   469  			Method:         method,
   470  			Expected:       okc,
   471  			Actual:         resp.StatusCode,
   472  			Body:           body,
   473  			ResponseHeader: resp.Header,
   474  		}
   475  
   476  		errType := options.ErrorContext
   477  		switch resp.StatusCode {
   478  		case http.StatusBadRequest:
   479  			err = ErrDefault400{respErr}
   480  			if error400er, ok := errType.(Err400er); ok {
   481  				err = error400er.Error400(respErr)
   482  			}
   483  		case http.StatusUnauthorized:
   484  			if client.ReauthFunc != nil && !state.hasReauthenticated {
   485  				err = client.Reauthenticate(prereqtok)
   486  				if err != nil {
   487  					e := &ErrUnableToReauthenticate{}
   488  					e.ErrOriginal = respErr
   489  					e.ErrReauth = err
   490  					return nil, e
   491  				}
   492  				if options.RawBody != nil {
   493  					if seeker, ok := options.RawBody.(io.Seeker); ok {
   494  						seeker.Seek(0, 0)
   495  					}
   496  				}
   497  				state.hasReauthenticated = true
   498  				resp, err = client.doRequest(method, url, options, state)
   499  				if err != nil {
   500  					switch err.(type) {
   501  					case *ErrUnexpectedResponseCode:
   502  						e := &ErrErrorAfterReauthentication{}
   503  						e.ErrOriginal = err.(*ErrUnexpectedResponseCode)
   504  						return nil, e
   505  					default:
   506  						e := &ErrErrorAfterReauthentication{}
   507  						e.ErrOriginal = err
   508  						return nil, e
   509  					}
   510  				}
   511  				return resp, nil
   512  			}
   513  			err = ErrDefault401{respErr}
   514  			if error401er, ok := errType.(Err401er); ok {
   515  				err = error401er.Error401(respErr)
   516  			}
   517  		case http.StatusForbidden:
   518  			err = ErrDefault403{respErr}
   519  			if error403er, ok := errType.(Err403er); ok {
   520  				err = error403er.Error403(respErr)
   521  			}
   522  		case http.StatusNotFound:
   523  			err = ErrDefault404{respErr}
   524  			if error404er, ok := errType.(Err404er); ok {
   525  				err = error404er.Error404(respErr)
   526  			}
   527  		case http.StatusMethodNotAllowed:
   528  			err = ErrDefault405{respErr}
   529  			if error405er, ok := errType.(Err405er); ok {
   530  				err = error405er.Error405(respErr)
   531  			}
   532  		case http.StatusRequestTimeout:
   533  			err = ErrDefault408{respErr}
   534  			if error408er, ok := errType.(Err408er); ok {
   535  				err = error408er.Error408(respErr)
   536  			}
   537  		case http.StatusConflict:
   538  			err = ErrDefault409{respErr}
   539  			if error409er, ok := errType.(Err409er); ok {
   540  				err = error409er.Error409(respErr)
   541  			}
   542  		case http.StatusTooManyRequests, 498:
   543  			err = ErrDefault429{respErr}
   544  			if error429er, ok := errType.(Err429er); ok {
   545  				err = error429er.Error429(respErr)
   546  			}
   547  
   548  			maxTries := client.MaxBackoffRetries
   549  			if maxTries == 0 {
   550  				maxTries = DefaultMaxBackoffRetries
   551  			}
   552  
   553  			if f := client.RetryBackoffFunc; f != nil && state.retries < maxTries {
   554  				var e error
   555  
   556  				state.retries = state.retries + 1
   557  				e = f(client.Context, &respErr, err, state.retries)
   558  
   559  				if e != nil {
   560  					return resp, e
   561  				}
   562  
   563  				return client.doRequest(method, url, options, state)
   564  			}
   565  		case http.StatusInternalServerError:
   566  			err = ErrDefault500{respErr}
   567  			if error500er, ok := errType.(Err500er); ok {
   568  				err = error500er.Error500(respErr)
   569  			}
   570  		case http.StatusBadGateway:
   571  			err = ErrDefault502{respErr}
   572  			if error502er, ok := errType.(Err502er); ok {
   573  				err = error502er.Error502(respErr)
   574  			}
   575  		case http.StatusServiceUnavailable:
   576  			err = ErrDefault503{respErr}
   577  			if error503er, ok := errType.(Err503er); ok {
   578  				err = error503er.Error503(respErr)
   579  			}
   580  		case http.StatusGatewayTimeout:
   581  			err = ErrDefault504{respErr}
   582  			if error504er, ok := errType.(Err504er); ok {
   583  				err = error504er.Error504(respErr)
   584  			}
   585  		}
   586  
   587  		if err == nil {
   588  			err = respErr
   589  		}
   590  
   591  		if err != nil && client.RetryFunc != nil {
   592  			var e error
   593  			state.retries = state.retries + 1
   594  			e = client.RetryFunc(client.Context, method, url, options, err, state.retries)
   595  			if e != nil {
   596  				return resp, e
   597  			}
   598  
   599  			return client.doRequest(method, url, options, state)
   600  		}
   601  
   602  		return resp, err
   603  	}
   604  
   605  	// Parse the response body as JSON, if requested to do so.
   606  	if options.JSONResponse != nil {
   607  		defer resp.Body.Close()
   608  		// Don't decode JSON when there is no content
   609  		if resp.StatusCode == http.StatusNoContent {
   610  			// read till EOF, otherwise the connection will be closed and cannot be reused
   611  			_, err = io.Copy(ioutil.Discard, resp.Body)
   612  			return resp, err
   613  		}
   614  		if err := json.NewDecoder(resp.Body).Decode(options.JSONResponse); err != nil {
   615  			if client.RetryFunc != nil {
   616  				var e error
   617  				state.retries = state.retries + 1
   618  				e = client.RetryFunc(client.Context, method, url, options, err, state.retries)
   619  				if e != nil {
   620  					return resp, e
   621  				}
   622  
   623  				return client.doRequest(method, url, options, state)
   624  			}
   625  			return nil, err
   626  		}
   627  	}
   628  
   629  	// Close unused body to allow the HTTP connection to be reused
   630  	if !options.KeepResponseBody && options.JSONResponse == nil {
   631  		defer resp.Body.Close()
   632  		// read till EOF, otherwise the connection will be closed and cannot be reused
   633  		if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
   634  			return nil, err
   635  		}
   636  	}
   637  
   638  	return resp, nil
   639  }
   640  
   641  func defaultOkCodes(method string) []int {
   642  	switch method {
   643  	case "GET", "HEAD":
   644  		return []int{200}
   645  	case "POST":
   646  		return []int{201, 202}
   647  	case "PUT":
   648  		return []int{201, 202}
   649  	case "PATCH":
   650  		return []int{200, 202, 204}
   651  	case "DELETE":
   652  		return []int{202, 204}
   653  	}
   654  
   655  	return []int{}
   656  }