...

Source file src/github.com/Azure/go-autorest/autorest/azure/async.go

Documentation: github.com/Azure/go-autorest/autorest/azure

     1  package azure
     2  
     3  // Copyright 2017 Microsoft Corporation
     4  //
     5  //  Licensed under the Apache License, Version 2.0 (the "License");
     6  //  you may not use this file except in compliance with the License.
     7  //  You may obtain a copy of the License at
     8  //
     9  //      http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  //  Unless required by applicable law or agreed to in writing, software
    12  //  distributed under the License is distributed on an "AS IS" BASIS,
    13  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  //  See the License for the specific language governing permissions and
    15  //  limitations under the License.
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"net/url"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/Azure/go-autorest/autorest"
    29  	"github.com/Azure/go-autorest/logger"
    30  	"github.com/Azure/go-autorest/tracing"
    31  )
    32  
    33  const (
    34  	headerAsyncOperation = "Azure-AsyncOperation"
    35  )
    36  
    37  const (
    38  	operationInProgress string = "InProgress"
    39  	operationCanceled   string = "Canceled"
    40  	operationFailed     string = "Failed"
    41  	operationSucceeded  string = "Succeeded"
    42  )
    43  
    44  var pollingCodes = [...]int{http.StatusNoContent, http.StatusAccepted, http.StatusCreated, http.StatusOK}
    45  
    46  // FutureAPI contains the set of methods on the Future type.
    47  type FutureAPI interface {
    48  	// Response returns the last HTTP response.
    49  	Response() *http.Response
    50  
    51  	// Status returns the last status message of the operation.
    52  	Status() string
    53  
    54  	// PollingMethod returns the method used to monitor the status of the asynchronous operation.
    55  	PollingMethod() PollingMethodType
    56  
    57  	// DoneWithContext queries the service to see if the operation has completed.
    58  	DoneWithContext(context.Context, autorest.Sender) (bool, error)
    59  
    60  	// GetPollingDelay returns a duration the application should wait before checking
    61  	// the status of the asynchronous request and true; this value is returned from
    62  	// the service via the Retry-After response header.  If the header wasn't returned
    63  	// then the function returns the zero-value time.Duration and false.
    64  	GetPollingDelay() (time.Duration, bool)
    65  
    66  	// WaitForCompletionRef will return when one of the following conditions is met: the long
    67  	// running operation has completed, the provided context is cancelled, or the client's
    68  	// polling duration has been exceeded.  It will retry failed polling attempts based on
    69  	// the retry value defined in the client up to the maximum retry attempts.
    70  	// If no deadline is specified in the context then the client.PollingDuration will be
    71  	// used to determine if a default deadline should be used.
    72  	// If PollingDuration is greater than zero the value will be used as the context's timeout.
    73  	// If PollingDuration is zero then no default deadline will be used.
    74  	WaitForCompletionRef(context.Context, autorest.Client) error
    75  
    76  	// MarshalJSON implements the json.Marshaler interface.
    77  	MarshalJSON() ([]byte, error)
    78  
    79  	// MarshalJSON implements the json.Unmarshaler interface.
    80  	UnmarshalJSON([]byte) error
    81  
    82  	// PollingURL returns the URL used for retrieving the status of the long-running operation.
    83  	PollingURL() string
    84  
    85  	// GetResult should be called once polling has completed successfully.
    86  	// It makes the final GET call to retrieve the resultant payload.
    87  	GetResult(autorest.Sender) (*http.Response, error)
    88  }
    89  
    90  var _ FutureAPI = (*Future)(nil)
    91  
    92  // Future provides a mechanism to access the status and results of an asynchronous request.
    93  // Since futures are stateful they should be passed by value to avoid race conditions.
    94  type Future struct {
    95  	pt pollingTracker
    96  }
    97  
    98  // NewFutureFromResponse returns a new Future object initialized
    99  // with the initial response from an asynchronous operation.
   100  func NewFutureFromResponse(resp *http.Response) (Future, error) {
   101  	pt, err := createPollingTracker(resp)
   102  	return Future{pt: pt}, err
   103  }
   104  
   105  // Response returns the last HTTP response.
   106  func (f Future) Response() *http.Response {
   107  	if f.pt == nil {
   108  		return nil
   109  	}
   110  	return f.pt.latestResponse()
   111  }
   112  
   113  // Status returns the last status message of the operation.
   114  func (f Future) Status() string {
   115  	if f.pt == nil {
   116  		return ""
   117  	}
   118  	return f.pt.pollingStatus()
   119  }
   120  
   121  // PollingMethod returns the method used to monitor the status of the asynchronous operation.
   122  func (f Future) PollingMethod() PollingMethodType {
   123  	if f.pt == nil {
   124  		return PollingUnknown
   125  	}
   126  	return f.pt.pollingMethod()
   127  }
   128  
   129  // DoneWithContext queries the service to see if the operation has completed.
   130  func (f *Future) DoneWithContext(ctx context.Context, sender autorest.Sender) (done bool, err error) {
   131  	ctx = tracing.StartSpan(ctx, "github.com/Azure/go-autorest/autorest/azure/async.DoneWithContext")
   132  	defer func() {
   133  		sc := -1
   134  		resp := f.Response()
   135  		if resp != nil {
   136  			sc = resp.StatusCode
   137  		}
   138  		tracing.EndSpan(ctx, sc, err)
   139  	}()
   140  
   141  	if f.pt == nil {
   142  		return false, autorest.NewError("Future", "Done", "future is not initialized")
   143  	}
   144  	if f.pt.hasTerminated() {
   145  		return true, f.pt.pollingError()
   146  	}
   147  	if err := f.pt.pollForStatus(ctx, sender); err != nil {
   148  		return false, err
   149  	}
   150  	if err := f.pt.checkForErrors(); err != nil {
   151  		return f.pt.hasTerminated(), err
   152  	}
   153  	if err := f.pt.updatePollingState(f.pt.provisioningStateApplicable()); err != nil {
   154  		return false, err
   155  	}
   156  	if err := f.pt.initPollingMethod(); err != nil {
   157  		return false, err
   158  	}
   159  	if err := f.pt.updatePollingMethod(); err != nil {
   160  		return false, err
   161  	}
   162  	return f.pt.hasTerminated(), f.pt.pollingError()
   163  }
   164  
   165  // GetPollingDelay returns a duration the application should wait before checking
   166  // the status of the asynchronous request and true; this value is returned from
   167  // the service via the Retry-After response header.  If the header wasn't returned
   168  // then the function returns the zero-value time.Duration and false.
   169  func (f Future) GetPollingDelay() (time.Duration, bool) {
   170  	if f.pt == nil {
   171  		return 0, false
   172  	}
   173  	resp := f.pt.latestResponse()
   174  	if resp == nil {
   175  		return 0, false
   176  	}
   177  
   178  	retry := resp.Header.Get(autorest.HeaderRetryAfter)
   179  	if retry == "" {
   180  		return 0, false
   181  	}
   182  
   183  	d, err := time.ParseDuration(retry + "s")
   184  	if err != nil {
   185  		panic(err)
   186  	}
   187  
   188  	return d, true
   189  }
   190  
   191  // WaitForCompletionRef will return when one of the following conditions is met: the long
   192  // running operation has completed, the provided context is cancelled, or the client's
   193  // polling duration has been exceeded.  It will retry failed polling attempts based on
   194  // the retry value defined in the client up to the maximum retry attempts.
   195  // If no deadline is specified in the context then the client.PollingDuration will be
   196  // used to determine if a default deadline should be used.
   197  // If PollingDuration is greater than zero the value will be used as the context's timeout.
   198  // If PollingDuration is zero then no default deadline will be used.
   199  func (f *Future) WaitForCompletionRef(ctx context.Context, client autorest.Client) (err error) {
   200  	ctx = tracing.StartSpan(ctx, "github.com/Azure/go-autorest/autorest/azure/async.WaitForCompletionRef")
   201  	defer func() {
   202  		sc := -1
   203  		resp := f.Response()
   204  		if resp != nil {
   205  			sc = resp.StatusCode
   206  		}
   207  		tracing.EndSpan(ctx, sc, err)
   208  	}()
   209  	cancelCtx := ctx
   210  	// if the provided context already has a deadline don't override it
   211  	_, hasDeadline := ctx.Deadline()
   212  	if d := client.PollingDuration; !hasDeadline && d != 0 {
   213  		var cancel context.CancelFunc
   214  		cancelCtx, cancel = context.WithTimeout(ctx, d)
   215  		defer cancel()
   216  	}
   217  	// if the initial response has a Retry-After, sleep for the specified amount of time before starting to poll
   218  	if delay, ok := f.GetPollingDelay(); ok {
   219  		logger.Instance.Writeln(logger.LogInfo, "WaitForCompletionRef: initial polling delay")
   220  		if delayElapsed := autorest.DelayForBackoff(delay, 0, cancelCtx.Done()); !delayElapsed {
   221  			err = cancelCtx.Err()
   222  			return
   223  		}
   224  	}
   225  	done, err := f.DoneWithContext(ctx, client)
   226  	for attempts := 0; !done; done, err = f.DoneWithContext(ctx, client) {
   227  		if attempts >= client.RetryAttempts {
   228  			return autorest.NewErrorWithError(err, "Future", "WaitForCompletion", f.pt.latestResponse(), "the number of retries has been exceeded")
   229  		}
   230  		// we want delayAttempt to be zero in the non-error case so
   231  		// that DelayForBackoff doesn't perform exponential back-off
   232  		var delayAttempt int
   233  		var delay time.Duration
   234  		if err == nil {
   235  			// check for Retry-After delay, if not present use the client's polling delay
   236  			var ok bool
   237  			delay, ok = f.GetPollingDelay()
   238  			if !ok {
   239  				logger.Instance.Writeln(logger.LogInfo, "WaitForCompletionRef: Using client polling delay")
   240  				delay = client.PollingDelay
   241  			}
   242  		} else {
   243  			// there was an error polling for status so perform exponential
   244  			// back-off based on the number of attempts using the client's retry
   245  			// duration.  update attempts after delayAttempt to avoid off-by-one.
   246  			logger.Instance.Writef(logger.LogError, "WaitForCompletionRef: %s\n", err)
   247  			delayAttempt = attempts
   248  			delay = client.RetryDuration
   249  			attempts++
   250  		}
   251  		// wait until the delay elapses or the context is cancelled
   252  		delayElapsed := autorest.DelayForBackoff(delay, delayAttempt, cancelCtx.Done())
   253  		if !delayElapsed {
   254  			return autorest.NewErrorWithError(cancelCtx.Err(), "Future", "WaitForCompletion", f.pt.latestResponse(), "context has been cancelled")
   255  		}
   256  	}
   257  	return
   258  }
   259  
   260  // MarshalJSON implements the json.Marshaler interface.
   261  func (f Future) MarshalJSON() ([]byte, error) {
   262  	return json.Marshal(f.pt)
   263  }
   264  
   265  // UnmarshalJSON implements the json.Unmarshaler interface.
   266  func (f *Future) UnmarshalJSON(data []byte) error {
   267  	// unmarshal into JSON object to determine the tracker type
   268  	obj := map[string]interface{}{}
   269  	err := json.Unmarshal(data, &obj)
   270  	if err != nil {
   271  		return err
   272  	}
   273  	if obj["method"] == nil {
   274  		return autorest.NewError("Future", "UnmarshalJSON", "missing 'method' property")
   275  	}
   276  	method := obj["method"].(string)
   277  	switch strings.ToUpper(method) {
   278  	case http.MethodDelete:
   279  		f.pt = &pollingTrackerDelete{}
   280  	case http.MethodPatch:
   281  		f.pt = &pollingTrackerPatch{}
   282  	case http.MethodPost:
   283  		f.pt = &pollingTrackerPost{}
   284  	case http.MethodPut:
   285  		f.pt = &pollingTrackerPut{}
   286  	default:
   287  		return autorest.NewError("Future", "UnmarshalJSON", "unsupoorted method '%s'", method)
   288  	}
   289  	// now unmarshal into the tracker
   290  	return json.Unmarshal(data, &f.pt)
   291  }
   292  
   293  // PollingURL returns the URL used for retrieving the status of the long-running operation.
   294  func (f Future) PollingURL() string {
   295  	if f.pt == nil {
   296  		return ""
   297  	}
   298  	return f.pt.pollingURL()
   299  }
   300  
   301  // GetResult should be called once polling has completed successfully.
   302  // It makes the final GET call to retrieve the resultant payload.
   303  func (f Future) GetResult(sender autorest.Sender) (*http.Response, error) {
   304  	if f.pt.finalGetURL() == "" {
   305  		// we can end up in this situation if the async operation returns a 200
   306  		// with no polling URLs.  in that case return the response which should
   307  		// contain the JSON payload (only do this for successful terminal cases).
   308  		if lr := f.pt.latestResponse(); lr != nil && f.pt.hasSucceeded() {
   309  			return lr, nil
   310  		}
   311  		return nil, autorest.NewError("Future", "GetResult", "missing URL for retrieving result")
   312  	}
   313  	req, err := http.NewRequest(http.MethodGet, f.pt.finalGetURL(), nil)
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  	resp, err := sender.Do(req)
   318  	if err == nil && resp.Body != nil {
   319  		// copy the body and close it so callers don't have to
   320  		defer resp.Body.Close()
   321  		b, err := ioutil.ReadAll(resp.Body)
   322  		if err != nil {
   323  			return resp, err
   324  		}
   325  		resp.Body = ioutil.NopCloser(bytes.NewReader(b))
   326  	}
   327  	return resp, err
   328  }
   329  
   330  type pollingTracker interface {
   331  	// these methods can differ per tracker
   332  
   333  	// checks the response headers and status code to determine the polling mechanism
   334  	updatePollingMethod() error
   335  
   336  	// checks the response for tracker-specific error conditions
   337  	checkForErrors() error
   338  
   339  	// returns true if provisioning state should be checked
   340  	provisioningStateApplicable() bool
   341  
   342  	// methods common to all trackers
   343  
   344  	// initializes a tracker's polling URL and method, called for each iteration.
   345  	// these values can be overridden by each polling tracker as required.
   346  	initPollingMethod() error
   347  
   348  	// initializes the tracker's internal state, call this when the tracker is created
   349  	initializeState() error
   350  
   351  	// makes an HTTP request to check the status of the LRO
   352  	pollForStatus(ctx context.Context, sender autorest.Sender) error
   353  
   354  	// updates internal tracker state, call this after each call to pollForStatus
   355  	updatePollingState(provStateApl bool) error
   356  
   357  	// returns the error response from the service, can be nil
   358  	pollingError() error
   359  
   360  	// returns the polling method being used
   361  	pollingMethod() PollingMethodType
   362  
   363  	// returns the state of the LRO as returned from the service
   364  	pollingStatus() string
   365  
   366  	// returns the URL used for polling status
   367  	pollingURL() string
   368  
   369  	// returns the URL used for the final GET to retrieve the resource
   370  	finalGetURL() string
   371  
   372  	// returns true if the LRO is in a terminal state
   373  	hasTerminated() bool
   374  
   375  	// returns true if the LRO is in a failed terminal state
   376  	hasFailed() bool
   377  
   378  	// returns true if the LRO is in a successful terminal state
   379  	hasSucceeded() bool
   380  
   381  	// returns the cached HTTP response after a call to pollForStatus(), can be nil
   382  	latestResponse() *http.Response
   383  }
   384  
   385  type pollingTrackerBase struct {
   386  	// resp is the last response, either from the submission of the LRO or from polling
   387  	resp *http.Response
   388  
   389  	// method is the HTTP verb, this is needed for deserialization
   390  	Method string `json:"method"`
   391  
   392  	// rawBody is the raw JSON response body
   393  	rawBody map[string]interface{}
   394  
   395  	// denotes if polling is using async-operation or location header
   396  	Pm PollingMethodType `json:"pollingMethod"`
   397  
   398  	// the URL to poll for status
   399  	URI string `json:"pollingURI"`
   400  
   401  	// the state of the LRO as returned from the service
   402  	State string `json:"lroState"`
   403  
   404  	// the URL to GET for the final result
   405  	FinalGetURI string `json:"resultURI"`
   406  
   407  	// used to hold an error object returned from the service
   408  	Err *ServiceError `json:"error,omitempty"`
   409  }
   410  
   411  func (pt *pollingTrackerBase) initializeState() error {
   412  	// determine the initial polling state based on response body and/or HTTP status
   413  	// code.  this is applicable to the initial LRO response, not polling responses!
   414  	pt.Method = pt.resp.Request.Method
   415  	if err := pt.updateRawBody(); err != nil {
   416  		return err
   417  	}
   418  	switch pt.resp.StatusCode {
   419  	case http.StatusOK:
   420  		if ps := pt.getProvisioningState(); ps != nil {
   421  			pt.State = *ps
   422  			if pt.hasFailed() {
   423  				pt.updateErrorFromResponse()
   424  				return pt.pollingError()
   425  			}
   426  		} else {
   427  			pt.State = operationSucceeded
   428  		}
   429  	case http.StatusCreated:
   430  		if ps := pt.getProvisioningState(); ps != nil {
   431  			pt.State = *ps
   432  		} else {
   433  			pt.State = operationInProgress
   434  		}
   435  	case http.StatusAccepted:
   436  		pt.State = operationInProgress
   437  	case http.StatusNoContent:
   438  		pt.State = operationSucceeded
   439  	default:
   440  		pt.State = operationFailed
   441  		pt.updateErrorFromResponse()
   442  		return pt.pollingError()
   443  	}
   444  	return pt.initPollingMethod()
   445  }
   446  
   447  func (pt pollingTrackerBase) getProvisioningState() *string {
   448  	if pt.rawBody != nil && pt.rawBody["properties"] != nil {
   449  		p := pt.rawBody["properties"].(map[string]interface{})
   450  		if ps := p["provisioningState"]; ps != nil {
   451  			s := ps.(string)
   452  			return &s
   453  		}
   454  	}
   455  	return nil
   456  }
   457  
   458  func (pt *pollingTrackerBase) updateRawBody() error {
   459  	pt.rawBody = map[string]interface{}{}
   460  	if pt.resp.ContentLength != 0 {
   461  		defer pt.resp.Body.Close()
   462  		b, err := ioutil.ReadAll(pt.resp.Body)
   463  		if err != nil {
   464  			return autorest.NewErrorWithError(err, "pollingTrackerBase", "updateRawBody", nil, "failed to read response body")
   465  		}
   466  		// put the body back so it's available to other callers
   467  		pt.resp.Body = ioutil.NopCloser(bytes.NewReader(b))
   468  		// observed in 204 responses over HTTP/2.0; the content length is -1 but body is empty
   469  		if len(b) == 0 {
   470  			return nil
   471  		}
   472  		if err = json.Unmarshal(b, &pt.rawBody); err != nil {
   473  			return autorest.NewErrorWithError(err, "pollingTrackerBase", "updateRawBody", nil, "failed to unmarshal response body")
   474  		}
   475  	}
   476  	return nil
   477  }
   478  
   479  func (pt *pollingTrackerBase) pollForStatus(ctx context.Context, sender autorest.Sender) error {
   480  	req, err := http.NewRequest(http.MethodGet, pt.URI, nil)
   481  	if err != nil {
   482  		return autorest.NewErrorWithError(err, "pollingTrackerBase", "pollForStatus", nil, "failed to create HTTP request")
   483  	}
   484  
   485  	req = req.WithContext(ctx)
   486  	preparer := autorest.CreatePreparer(autorest.GetPrepareDecorators(ctx)...)
   487  	req, err = preparer.Prepare(req)
   488  	if err != nil {
   489  		return autorest.NewErrorWithError(err, "pollingTrackerBase", "pollForStatus", nil, "failed preparing HTTP request")
   490  	}
   491  	pt.resp, err = sender.Do(req)
   492  	if err != nil {
   493  		return autorest.NewErrorWithError(err, "pollingTrackerBase", "pollForStatus", nil, "failed to send HTTP request")
   494  	}
   495  	if autorest.ResponseHasStatusCode(pt.resp, pollingCodes[:]...) {
   496  		// reset the service error on success case
   497  		pt.Err = nil
   498  		err = pt.updateRawBody()
   499  	} else {
   500  		// check response body for error content
   501  		pt.updateErrorFromResponse()
   502  		err = pt.pollingError()
   503  	}
   504  	return err
   505  }
   506  
   507  // attempts to unmarshal a ServiceError type from the response body.
   508  // if that fails then make a best attempt at creating something meaningful.
   509  // NOTE: this assumes that the async operation has failed.
   510  func (pt *pollingTrackerBase) updateErrorFromResponse() {
   511  	var err error
   512  	if pt.resp.ContentLength != 0 {
   513  		type respErr struct {
   514  			ServiceError *ServiceError `json:"error"`
   515  		}
   516  		re := respErr{}
   517  		defer pt.resp.Body.Close()
   518  		var b []byte
   519  		if b, err = ioutil.ReadAll(pt.resp.Body); err != nil {
   520  			goto Default
   521  		}
   522  		// put the body back so it's available to other callers
   523  		pt.resp.Body = ioutil.NopCloser(bytes.NewReader(b))
   524  		if len(b) == 0 {
   525  			goto Default
   526  		}
   527  		if err = json.Unmarshal(b, &re); err != nil {
   528  			goto Default
   529  		}
   530  		// unmarshalling the error didn't yield anything, try unwrapped error
   531  		if re.ServiceError == nil {
   532  			err = json.Unmarshal(b, &re.ServiceError)
   533  			if err != nil {
   534  				goto Default
   535  			}
   536  		}
   537  		// the unmarshaller will ensure re.ServiceError is non-nil
   538  		// even if there was no content unmarshalled so check the code.
   539  		if re.ServiceError.Code != "" {
   540  			pt.Err = re.ServiceError
   541  			return
   542  		}
   543  	}
   544  Default:
   545  	se := &ServiceError{
   546  		Code:    pt.pollingStatus(),
   547  		Message: "The async operation failed.",
   548  	}
   549  	if err != nil {
   550  		se.InnerError = make(map[string]interface{})
   551  		se.InnerError["unmarshalError"] = err.Error()
   552  	}
   553  	// stick the response body into the error object in hopes
   554  	// it contains something useful to help diagnose the failure.
   555  	if len(pt.rawBody) > 0 {
   556  		se.AdditionalInfo = []map[string]interface{}{
   557  			pt.rawBody,
   558  		}
   559  	}
   560  	pt.Err = se
   561  }
   562  
   563  func (pt *pollingTrackerBase) updatePollingState(provStateApl bool) error {
   564  	if pt.Pm == PollingAsyncOperation && pt.rawBody["status"] != nil {
   565  		pt.State = pt.rawBody["status"].(string)
   566  	} else {
   567  		if pt.resp.StatusCode == http.StatusAccepted {
   568  			pt.State = operationInProgress
   569  		} else if provStateApl {
   570  			if ps := pt.getProvisioningState(); ps != nil {
   571  				pt.State = *ps
   572  			} else {
   573  				pt.State = operationSucceeded
   574  			}
   575  		} else {
   576  			return autorest.NewError("pollingTrackerBase", "updatePollingState", "the response from the async operation has an invalid status code")
   577  		}
   578  	}
   579  	// if the operation has failed update the error state
   580  	if pt.hasFailed() {
   581  		pt.updateErrorFromResponse()
   582  	}
   583  	return nil
   584  }
   585  
   586  func (pt pollingTrackerBase) pollingError() error {
   587  	if pt.Err == nil {
   588  		return nil
   589  	}
   590  	return pt.Err
   591  }
   592  
   593  func (pt pollingTrackerBase) pollingMethod() PollingMethodType {
   594  	return pt.Pm
   595  }
   596  
   597  func (pt pollingTrackerBase) pollingStatus() string {
   598  	return pt.State
   599  }
   600  
   601  func (pt pollingTrackerBase) pollingURL() string {
   602  	return pt.URI
   603  }
   604  
   605  func (pt pollingTrackerBase) finalGetURL() string {
   606  	return pt.FinalGetURI
   607  }
   608  
   609  func (pt pollingTrackerBase) hasTerminated() bool {
   610  	return strings.EqualFold(pt.State, operationCanceled) || strings.EqualFold(pt.State, operationFailed) || strings.EqualFold(pt.State, operationSucceeded)
   611  }
   612  
   613  func (pt pollingTrackerBase) hasFailed() bool {
   614  	return strings.EqualFold(pt.State, operationCanceled) || strings.EqualFold(pt.State, operationFailed)
   615  }
   616  
   617  func (pt pollingTrackerBase) hasSucceeded() bool {
   618  	return strings.EqualFold(pt.State, operationSucceeded)
   619  }
   620  
   621  func (pt pollingTrackerBase) latestResponse() *http.Response {
   622  	return pt.resp
   623  }
   624  
   625  // error checking common to all trackers
   626  func (pt pollingTrackerBase) baseCheckForErrors() error {
   627  	// for Azure-AsyncOperations the response body cannot be nil or empty
   628  	if pt.Pm == PollingAsyncOperation {
   629  		if pt.resp.Body == nil || pt.resp.ContentLength == 0 {
   630  			return autorest.NewError("pollingTrackerBase", "baseCheckForErrors", "for Azure-AsyncOperation response body cannot be nil")
   631  		}
   632  		if pt.rawBody["status"] == nil {
   633  			return autorest.NewError("pollingTrackerBase", "baseCheckForErrors", "missing status property in Azure-AsyncOperation response body")
   634  		}
   635  	}
   636  	return nil
   637  }
   638  
   639  // default initialization of polling URL/method.  each verb tracker will update this as required.
   640  func (pt *pollingTrackerBase) initPollingMethod() error {
   641  	if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil {
   642  		return err
   643  	} else if ao != "" {
   644  		pt.URI = ao
   645  		pt.Pm = PollingAsyncOperation
   646  		return nil
   647  	}
   648  	if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
   649  		return err
   650  	} else if lh != "" {
   651  		pt.URI = lh
   652  		pt.Pm = PollingLocation
   653  		return nil
   654  	}
   655  	// it's ok if we didn't find a polling header, this will be handled elsewhere
   656  	return nil
   657  }
   658  
   659  // DELETE
   660  
   661  type pollingTrackerDelete struct {
   662  	pollingTrackerBase
   663  }
   664  
   665  func (pt *pollingTrackerDelete) updatePollingMethod() error {
   666  	// for 201 the Location header is required
   667  	if pt.resp.StatusCode == http.StatusCreated {
   668  		if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
   669  			return err
   670  		} else if lh == "" {
   671  			return autorest.NewError("pollingTrackerDelete", "updateHeaders", "missing Location header in 201 response")
   672  		} else {
   673  			pt.URI = lh
   674  		}
   675  		pt.Pm = PollingLocation
   676  		pt.FinalGetURI = pt.URI
   677  	}
   678  	// for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary
   679  	if pt.resp.StatusCode == http.StatusAccepted {
   680  		ao, err := getURLFromAsyncOpHeader(pt.resp)
   681  		if err != nil {
   682  			return err
   683  		} else if ao != "" {
   684  			pt.URI = ao
   685  			pt.Pm = PollingAsyncOperation
   686  		}
   687  		// if the Location header is invalid and we already have a polling URL
   688  		// then we don't care if the Location header URL is malformed.
   689  		if lh, err := getURLFromLocationHeader(pt.resp); err != nil && pt.URI == "" {
   690  			return err
   691  		} else if lh != "" {
   692  			if ao == "" {
   693  				pt.URI = lh
   694  				pt.Pm = PollingLocation
   695  			}
   696  			// when both headers are returned we use the value in the Location header for the final GET
   697  			pt.FinalGetURI = lh
   698  		}
   699  		// make sure a polling URL was found
   700  		if pt.URI == "" {
   701  			return autorest.NewError("pollingTrackerPost", "updateHeaders", "didn't get any suitable polling URLs in 202 response")
   702  		}
   703  	}
   704  	return nil
   705  }
   706  
   707  func (pt pollingTrackerDelete) checkForErrors() error {
   708  	return pt.baseCheckForErrors()
   709  }
   710  
   711  func (pt pollingTrackerDelete) provisioningStateApplicable() bool {
   712  	return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusNoContent
   713  }
   714  
   715  // PATCH
   716  
   717  type pollingTrackerPatch struct {
   718  	pollingTrackerBase
   719  }
   720  
   721  func (pt *pollingTrackerPatch) updatePollingMethod() error {
   722  	// by default we can use the original URL for polling and final GET
   723  	if pt.URI == "" {
   724  		pt.URI = pt.resp.Request.URL.String()
   725  	}
   726  	if pt.FinalGetURI == "" {
   727  		pt.FinalGetURI = pt.resp.Request.URL.String()
   728  	}
   729  	if pt.Pm == PollingUnknown {
   730  		pt.Pm = PollingRequestURI
   731  	}
   732  	// for 201 it's permissible for no headers to be returned
   733  	if pt.resp.StatusCode == http.StatusCreated {
   734  		if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil {
   735  			return err
   736  		} else if ao != "" {
   737  			pt.URI = ao
   738  			pt.Pm = PollingAsyncOperation
   739  		}
   740  	}
   741  	// for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary
   742  	// note the absence of the "final GET" mechanism for PATCH
   743  	if pt.resp.StatusCode == http.StatusAccepted {
   744  		ao, err := getURLFromAsyncOpHeader(pt.resp)
   745  		if err != nil {
   746  			return err
   747  		} else if ao != "" {
   748  			pt.URI = ao
   749  			pt.Pm = PollingAsyncOperation
   750  		}
   751  		if ao == "" {
   752  			if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
   753  				return err
   754  			} else if lh == "" {
   755  				return autorest.NewError("pollingTrackerPatch", "updateHeaders", "didn't get any suitable polling URLs in 202 response")
   756  			} else {
   757  				pt.URI = lh
   758  				pt.Pm = PollingLocation
   759  			}
   760  		}
   761  	}
   762  	return nil
   763  }
   764  
   765  func (pt pollingTrackerPatch) checkForErrors() error {
   766  	return pt.baseCheckForErrors()
   767  }
   768  
   769  func (pt pollingTrackerPatch) provisioningStateApplicable() bool {
   770  	return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusCreated
   771  }
   772  
   773  // POST
   774  
   775  type pollingTrackerPost struct {
   776  	pollingTrackerBase
   777  }
   778  
   779  func (pt *pollingTrackerPost) updatePollingMethod() error {
   780  	// 201 requires Location header
   781  	if pt.resp.StatusCode == http.StatusCreated {
   782  		if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
   783  			return err
   784  		} else if lh == "" {
   785  			return autorest.NewError("pollingTrackerPost", "updateHeaders", "missing Location header in 201 response")
   786  		} else {
   787  			pt.URI = lh
   788  			pt.FinalGetURI = lh
   789  			pt.Pm = PollingLocation
   790  		}
   791  	}
   792  	// for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary
   793  	if pt.resp.StatusCode == http.StatusAccepted {
   794  		ao, err := getURLFromAsyncOpHeader(pt.resp)
   795  		if err != nil {
   796  			return err
   797  		} else if ao != "" {
   798  			pt.URI = ao
   799  			pt.Pm = PollingAsyncOperation
   800  		}
   801  		// if the Location header is invalid and we already have a polling URL
   802  		// then we don't care if the Location header URL is malformed.
   803  		if lh, err := getURLFromLocationHeader(pt.resp); err != nil && pt.URI == "" {
   804  			return err
   805  		} else if lh != "" {
   806  			if ao == "" {
   807  				pt.URI = lh
   808  				pt.Pm = PollingLocation
   809  			}
   810  			// when both headers are returned we use the value in the Location header for the final GET
   811  			pt.FinalGetURI = lh
   812  		}
   813  		// make sure a polling URL was found
   814  		if pt.URI == "" {
   815  			return autorest.NewError("pollingTrackerPost", "updateHeaders", "didn't get any suitable polling URLs in 202 response")
   816  		}
   817  	}
   818  	return nil
   819  }
   820  
   821  func (pt pollingTrackerPost) checkForErrors() error {
   822  	return pt.baseCheckForErrors()
   823  }
   824  
   825  func (pt pollingTrackerPost) provisioningStateApplicable() bool {
   826  	return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusNoContent
   827  }
   828  
   829  // PUT
   830  
   831  type pollingTrackerPut struct {
   832  	pollingTrackerBase
   833  }
   834  
   835  func (pt *pollingTrackerPut) updatePollingMethod() error {
   836  	// by default we can use the original URL for polling and final GET
   837  	if pt.URI == "" {
   838  		pt.URI = pt.resp.Request.URL.String()
   839  	}
   840  	if pt.FinalGetURI == "" {
   841  		pt.FinalGetURI = pt.resp.Request.URL.String()
   842  	}
   843  	if pt.Pm == PollingUnknown {
   844  		pt.Pm = PollingRequestURI
   845  	}
   846  	// for 201 it's permissible for no headers to be returned
   847  	if pt.resp.StatusCode == http.StatusCreated {
   848  		if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil {
   849  			return err
   850  		} else if ao != "" {
   851  			pt.URI = ao
   852  			pt.Pm = PollingAsyncOperation
   853  		}
   854  	}
   855  	// for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary
   856  	if pt.resp.StatusCode == http.StatusAccepted {
   857  		ao, err := getURLFromAsyncOpHeader(pt.resp)
   858  		if err != nil {
   859  			return err
   860  		} else if ao != "" {
   861  			pt.URI = ao
   862  			pt.Pm = PollingAsyncOperation
   863  		}
   864  		// if the Location header is invalid and we already have a polling URL
   865  		// then we don't care if the Location header URL is malformed.
   866  		if lh, err := getURLFromLocationHeader(pt.resp); err != nil && pt.URI == "" {
   867  			return err
   868  		} else if lh != "" {
   869  			if ao == "" {
   870  				pt.URI = lh
   871  				pt.Pm = PollingLocation
   872  			}
   873  		}
   874  		// make sure a polling URL was found
   875  		if pt.URI == "" {
   876  			return autorest.NewError("pollingTrackerPut", "updateHeaders", "didn't get any suitable polling URLs in 202 response")
   877  		}
   878  	}
   879  	return nil
   880  }
   881  
   882  func (pt pollingTrackerPut) checkForErrors() error {
   883  	err := pt.baseCheckForErrors()
   884  	if err != nil {
   885  		return err
   886  	}
   887  	// if there are no LRO headers then the body cannot be empty
   888  	ao, err := getURLFromAsyncOpHeader(pt.resp)
   889  	if err != nil {
   890  		return err
   891  	}
   892  	lh, err := getURLFromLocationHeader(pt.resp)
   893  	if err != nil {
   894  		return err
   895  	}
   896  	if ao == "" && lh == "" && len(pt.rawBody) == 0 {
   897  		return autorest.NewError("pollingTrackerPut", "checkForErrors", "the response did not contain a body")
   898  	}
   899  	return nil
   900  }
   901  
   902  func (pt pollingTrackerPut) provisioningStateApplicable() bool {
   903  	return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusCreated
   904  }
   905  
   906  // creates a polling tracker based on the verb of the original request
   907  func createPollingTracker(resp *http.Response) (pollingTracker, error) {
   908  	var pt pollingTracker
   909  	switch strings.ToUpper(resp.Request.Method) {
   910  	case http.MethodDelete:
   911  		pt = &pollingTrackerDelete{pollingTrackerBase: pollingTrackerBase{resp: resp}}
   912  	case http.MethodPatch:
   913  		pt = &pollingTrackerPatch{pollingTrackerBase: pollingTrackerBase{resp: resp}}
   914  	case http.MethodPost:
   915  		pt = &pollingTrackerPost{pollingTrackerBase: pollingTrackerBase{resp: resp}}
   916  	case http.MethodPut:
   917  		pt = &pollingTrackerPut{pollingTrackerBase: pollingTrackerBase{resp: resp}}
   918  	default:
   919  		return nil, autorest.NewError("azure", "createPollingTracker", "unsupported HTTP method %s", resp.Request.Method)
   920  	}
   921  	if err := pt.initializeState(); err != nil {
   922  		return pt, err
   923  	}
   924  	// this initializes the polling header values, we do this during creation in case the
   925  	// initial response send us invalid values; this way the API call will return a non-nil
   926  	// error (not doing this means the error shows up in Future.Done)
   927  	return pt, pt.updatePollingMethod()
   928  }
   929  
   930  // gets the polling URL from the Azure-AsyncOperation header.
   931  // ensures the URL is well-formed and absolute.
   932  func getURLFromAsyncOpHeader(resp *http.Response) (string, error) {
   933  	s := resp.Header.Get(http.CanonicalHeaderKey(headerAsyncOperation))
   934  	if s == "" {
   935  		return "", nil
   936  	}
   937  	if !isValidURL(s) {
   938  		return "", autorest.NewError("azure", "getURLFromAsyncOpHeader", "invalid polling URL '%s'", s)
   939  	}
   940  	return s, nil
   941  }
   942  
   943  // gets the polling URL from the Location header.
   944  // ensures the URL is well-formed and absolute.
   945  func getURLFromLocationHeader(resp *http.Response) (string, error) {
   946  	s := resp.Header.Get(http.CanonicalHeaderKey(autorest.HeaderLocation))
   947  	if s == "" {
   948  		return "", nil
   949  	}
   950  	if !isValidURL(s) {
   951  		return "", autorest.NewError("azure", "getURLFromLocationHeader", "invalid polling URL '%s'", s)
   952  	}
   953  	return s, nil
   954  }
   955  
   956  // verify that the URL is valid and absolute
   957  func isValidURL(s string) bool {
   958  	u, err := url.Parse(s)
   959  	return err == nil && u.IsAbs()
   960  }
   961  
   962  // PollingMethodType defines a type used for enumerating polling mechanisms.
   963  type PollingMethodType string
   964  
   965  const (
   966  	// PollingAsyncOperation indicates the polling method uses the Azure-AsyncOperation header.
   967  	PollingAsyncOperation PollingMethodType = "AsyncOperation"
   968  
   969  	// PollingLocation indicates the polling method uses the Location header.
   970  	PollingLocation PollingMethodType = "Location"
   971  
   972  	// PollingRequestURI indicates the polling method uses the original request URI.
   973  	PollingRequestURI PollingMethodType = "RequestURI"
   974  
   975  	// PollingUnknown indicates an unknown polling method and is the default value.
   976  	PollingUnknown PollingMethodType = ""
   977  )
   978  
   979  // AsyncOpIncompleteError is the type that's returned from a future that has not completed.
   980  type AsyncOpIncompleteError struct {
   981  	// FutureType is the name of the type composed of a azure.Future.
   982  	FutureType string
   983  }
   984  
   985  // Error returns an error message including the originating type name of the error.
   986  func (e AsyncOpIncompleteError) Error() string {
   987  	return fmt.Sprintf("%s: asynchronous operation has not completed", e.FutureType)
   988  }
   989  
   990  // NewAsyncOpIncompleteError creates a new AsyncOpIncompleteError with the specified parameters.
   991  func NewAsyncOpIncompleteError(futureType string) AsyncOpIncompleteError {
   992  	return AsyncOpIncompleteError{
   993  		FutureType: futureType,
   994  	}
   995  }
   996  

View as plain text