async.go raw

   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"
  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 := io.ReadAll(resp.Body)
 322  		if err != nil {
 323  			return resp, err
 324  		}
 325  		resp.Body = io.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 := io.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 = io.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 = io.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 = io.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