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