Update go dependencies

This commit is contained in:
Manuel de Brito Fontes 2017-11-12 14:14:23 -03:00
parent a858c549d9
commit f3bde94d68
643 changed files with 14296 additions and 19354 deletions

View file

@ -6,7 +6,6 @@ go:
- 1.9
- 1.8
- 1.7
- 1.6
install:
- go get -u github.com/golang/lint/golint

View file

@ -1,5 +1,38 @@
# CHANGELOG
## v9.4.0
### New Features
- Added WaitForCompletion() to Future as a default polling implementation.
### Bug Fixes
- Method Future.Done() shouldn't update polling status for unexpected HTTP status codes.
## v9.3.1
### Bug Fixes
- DoRetryForStatusCodes will retry if sender.Do returns a non-nil error.
## v9.3.0
### New Features
- Added PollingMethod() to Future so callers know what kind of polling mechanism is used.
- Added azure.ChangeToGet() which transforms an http.Request into a GET (to be used with LROs).
## v9.2.0
### New Features
- Added support for custom Azure Stack endpoints.
- Added type azure.Future used to track the status of long-running operations.
### Bug Fixes
- Preserve the original error in DoRetryWithRegistration when registration fails.
## v9.1.1

View file

@ -87,6 +87,9 @@ const (
// ResponseHasStatusCode returns true if the status code in the HTTP Response is in the passed set
// and false otherwise.
func ResponseHasStatusCode(resp *http.Response, codes ...int) bool {
if resp == nil {
return false
}
return containsInt(codes, resp.StatusCode)
}

View file

@ -16,6 +16,8 @@ package azure
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
@ -37,6 +39,152 @@ const (
operationSucceeded string = "Succeeded"
)
var pollingCodes = [...]int{http.StatusAccepted, http.StatusCreated, http.StatusOK}
// Future provides a mechanism to access the status and results of an asynchronous request.
// Since futures are stateful they should be passed by value to avoid race conditions.
type Future struct {
req *http.Request
resp *http.Response
ps pollingState
}
// NewFuture returns a new Future object initialized with the specified request.
func NewFuture(req *http.Request) Future {
return Future{req: req}
}
// Response returns the last HTTP response or nil if there isn't one.
func (f Future) Response() *http.Response {
return f.resp
}
// Status returns the last status message of the operation.
func (f Future) Status() string {
if f.ps.State == "" {
return "Unknown"
}
return f.ps.State
}
// PollingMethod returns the method used to monitor the status of the asynchronous operation.
func (f Future) PollingMethod() PollingMethodType {
return f.ps.PollingMethod
}
// Done queries the service to see if the operation has completed.
func (f *Future) Done(sender autorest.Sender) (bool, error) {
// exit early if this future has terminated
if f.ps.hasTerminated() {
return true, f.errorInfo()
}
resp, err := sender.Do(f.req)
f.resp = resp
if err != nil || !autorest.ResponseHasStatusCode(resp, pollingCodes[:]...) {
return false, err
}
err = updatePollingState(resp, &f.ps)
if err != nil {
return false, err
}
if f.ps.hasTerminated() {
return true, f.errorInfo()
}
f.req, err = newPollingRequest(f.ps)
return false, err
}
// GetPollingDelay returns a duration the application should wait before checking
// the status of the asynchronous request and true; this value is returned from
// the service via the Retry-After response header. If the header wasn't returned
// then the function returns the zero-value time.Duration and false.
func (f Future) GetPollingDelay() (time.Duration, bool) {
if f.resp == nil {
return 0, false
}
retry := f.resp.Header.Get(autorest.HeaderRetryAfter)
if retry == "" {
return 0, false
}
d, err := time.ParseDuration(retry + "s")
if err != nil {
panic(err)
}
return d, true
}
// WaitForCompletion will return when one of the following conditions is met: the long
// running operation has completed, the provided context is cancelled, or the client's
// polling duration has been exceeded. It will retry failed polling attempts based on
// the retry value defined in the client up to the maximum retry attempts.
func (f Future) WaitForCompletion(ctx context.Context, client autorest.Client) error {
ctx, cancel := context.WithTimeout(ctx, client.PollingDuration)
defer cancel()
done, err := f.Done(client)
for attempts := 0; !done; done, err = f.Done(client) {
if attempts >= client.RetryAttempts {
return autorest.NewErrorWithError(err, "azure", "WaitForCompletion", f.resp, "the number of retries has been exceeded")
}
// we want delayAttempt to be zero in the non-error case so
// that DelayForBackoff doesn't perform exponential back-off
var delayAttempt int
var delay time.Duration
if err == nil {
// check for Retry-After delay, if not present use the client's polling delay
var ok bool
delay, ok = f.GetPollingDelay()
if !ok {
delay = client.PollingDelay
}
} else {
// there was an error polling for status so perform exponential
// back-off based on the number of attempts using the client's retry
// duration. update attempts after delayAttempt to avoid off-by-one.
delayAttempt = attempts
delay = client.RetryDuration
attempts++
}
// wait until the delay elapses or the context is cancelled
delayElapsed := autorest.DelayForBackoff(delay, delayAttempt, ctx.Done())
if !delayElapsed {
return autorest.NewErrorWithError(ctx.Err(), "azure", "WaitForCompletion", f.resp, "context has been cancelled")
}
}
return err
}
// if the operation failed the polling state will contain
// error information and implements the error interface
func (f *Future) errorInfo() error {
if !f.ps.hasSucceeded() {
return f.ps
}
return nil
}
// MarshalJSON implements the json.Marshaler interface.
func (f Future) MarshalJSON() ([]byte, error) {
return json.Marshal(&f.ps)
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (f *Future) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, &f.ps)
if err != nil {
return err
}
f.req, err = newPollingRequest(f.ps)
return err
}
// DoPollForAsynchronous returns a SendDecorator that polls if the http.Response is for an Azure
// long-running operation. It will delay between requests for the duration specified in the
// RetryAfter header or, if the header is absent, the passed delay. Polling may be canceled by
@ -48,8 +196,7 @@ func DoPollForAsynchronous(delay time.Duration) autorest.SendDecorator {
if err != nil {
return resp, err
}
pollingCodes := []int{http.StatusAccepted, http.StatusCreated, http.StatusOK}
if !autorest.ResponseHasStatusCode(resp, pollingCodes...) {
if !autorest.ResponseHasStatusCode(resp, pollingCodes[:]...) {
return resp, nil
}
@ -66,10 +213,11 @@ func DoPollForAsynchronous(delay time.Duration) autorest.SendDecorator {
break
}
r, err = newPollingRequest(resp, ps)
r, err = newPollingRequest(ps)
if err != nil {
return resp, err
}
r.Cancel = resp.Request.Cancel
delay = autorest.GetRetryAfter(resp, delay)
resp, err = autorest.SendWithSender(s, r,
@ -160,36 +308,42 @@ func (ps provisioningStatus) hasProvisioningError() bool {
return ps.ProvisioningError != ServiceError{}
}
type pollingResponseFormat string
// PollingMethodType defines a type used for enumerating polling mechanisms.
type PollingMethodType string
const (
usesOperationResponse pollingResponseFormat = "OperationResponse"
usesProvisioningStatus pollingResponseFormat = "ProvisioningStatus"
formatIsUnknown pollingResponseFormat = ""
// PollingAsyncOperation indicates the polling method uses the Azure-AsyncOperation header.
PollingAsyncOperation PollingMethodType = "AsyncOperation"
// PollingLocation indicates the polling method uses the Location header.
PollingLocation PollingMethodType = "Location"
// PollingUnknown indicates an unknown polling method and is the default value.
PollingUnknown PollingMethodType = ""
)
type pollingState struct {
responseFormat pollingResponseFormat
uri string
state string
code string
message string
PollingMethod PollingMethodType `json:"pollingMethod"`
URI string `json:"uri"`
State string `json:"state"`
Code string `json:"code"`
Message string `json:"message"`
}
func (ps pollingState) hasSucceeded() bool {
return hasSucceeded(ps.state)
return hasSucceeded(ps.State)
}
func (ps pollingState) hasTerminated() bool {
return hasTerminated(ps.state)
return hasTerminated(ps.State)
}
func (ps pollingState) hasFailed() bool {
return hasFailed(ps.state)
return hasFailed(ps.State)
}
func (ps pollingState) Error() string {
return fmt.Sprintf("Long running operation terminated with status '%s': Code=%q Message=%q", ps.state, ps.code, ps.message)
return fmt.Sprintf("Long running operation terminated with status '%s': Code=%q Message=%q", ps.State, ps.Code, ps.Message)
}
// updatePollingState maps the operation status -- retrieved from either a provisioningState
@ -204,7 +358,7 @@ func updatePollingState(resp *http.Response, ps *pollingState) error {
// -- The first response will always be a provisioningStatus response; only the polling requests,
// depending on the header returned, may be something otherwise.
var pt provisioningTracker
if ps.responseFormat == usesOperationResponse {
if ps.PollingMethod == PollingAsyncOperation {
pt = &operationResource{}
} else {
pt = &provisioningStatus{}
@ -212,30 +366,30 @@ func updatePollingState(resp *http.Response, ps *pollingState) error {
// If this is the first request (that is, the polling response shape is unknown), determine how
// to poll and what to expect
if ps.responseFormat == formatIsUnknown {
if ps.PollingMethod == PollingUnknown {
req := resp.Request
if req == nil {
return autorest.NewError("azure", "updatePollingState", "Azure Polling Error - Original HTTP request is missing")
}
// Prefer the Azure-AsyncOperation header
ps.uri = getAsyncOperation(resp)
if ps.uri != "" {
ps.responseFormat = usesOperationResponse
ps.URI = getAsyncOperation(resp)
if ps.URI != "" {
ps.PollingMethod = PollingAsyncOperation
} else {
ps.responseFormat = usesProvisioningStatus
ps.PollingMethod = PollingLocation
}
// Else, use the Location header
if ps.uri == "" {
ps.uri = autorest.GetLocation(resp)
if ps.URI == "" {
ps.URI = autorest.GetLocation(resp)
}
// Lastly, requests against an existing resource, use the last request URI
if ps.uri == "" {
if ps.URI == "" {
m := strings.ToUpper(req.Method)
if m == http.MethodPatch || m == http.MethodPut || m == http.MethodGet {
ps.uri = req.URL.String()
ps.URI = req.URL.String()
}
}
}
@ -256,23 +410,23 @@ func updatePollingState(resp *http.Response, ps *pollingState) error {
// -- Unknown states are per-service inprogress states
// -- Otherwise, infer state from HTTP status code
if pt.hasTerminated() {
ps.state = pt.state()
ps.State = pt.state()
} else if pt.state() != "" {
ps.state = operationInProgress
ps.State = operationInProgress
} else {
switch resp.StatusCode {
case http.StatusAccepted:
ps.state = operationInProgress
ps.State = operationInProgress
case http.StatusNoContent, http.StatusCreated, http.StatusOK:
ps.state = operationSucceeded
ps.State = operationSucceeded
default:
ps.state = operationFailed
ps.State = operationFailed
}
}
if ps.state == operationInProgress && ps.uri == "" {
if ps.State == operationInProgress && ps.URI == "" {
return autorest.NewError("azure", "updatePollingState", "Azure Polling Error - Unable to obtain polling URI for %s %s", resp.Request.Method, resp.Request.URL)
}
@ -281,35 +435,30 @@ func updatePollingState(resp *http.Response, ps *pollingState) error {
// -- Response
// -- Otherwise, Unknown
if ps.hasFailed() {
if ps.responseFormat == usesOperationResponse {
if ps.PollingMethod == PollingAsyncOperation {
or := pt.(*operationResource)
ps.code = or.OperationError.Code
ps.message = or.OperationError.Message
ps.Code = or.OperationError.Code
ps.Message = or.OperationError.Message
} else {
p := pt.(*provisioningStatus)
if p.hasProvisioningError() {
ps.code = p.ProvisioningError.Code
ps.message = p.ProvisioningError.Message
ps.Code = p.ProvisioningError.Code
ps.Message = p.ProvisioningError.Message
} else {
ps.code = "Unknown"
ps.message = "None"
ps.Code = "Unknown"
ps.Message = "None"
}
}
}
return nil
}
func newPollingRequest(resp *http.Response, ps pollingState) (*http.Request, error) {
req := resp.Request
if req == nil {
return nil, autorest.NewError("azure", "newPollingRequest", "Azure Polling Error - Original HTTP request is missing")
}
reqPoll, err := autorest.Prepare(&http.Request{Cancel: req.Cancel},
func newPollingRequest(ps pollingState) (*http.Request, error) {
reqPoll, err := autorest.Prepare(&http.Request{},
autorest.AsGet(),
autorest.WithBaseURL(ps.uri))
autorest.WithBaseURL(ps.URI))
if err != nil {
return nil, autorest.NewErrorWithError(err, "azure", "newPollingRequest", nil, "Failure creating poll request to %s", ps.uri)
return nil, autorest.NewErrorWithError(err, "azure", "newPollingRequest", nil, "Failure creating poll request to %s", ps.URI)
}
return reqPoll, nil

View file

@ -15,6 +15,9 @@ package azure
// limitations under the License.
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
@ -145,27 +148,27 @@ func TestProvisioningStatus_HasTerminatedReturnsFalseForUnknownStates(t *testing
}
func TestPollingState_HasSucceededReturnsFalseIfNotSuccess(t *testing.T) {
if (pollingState{state: "not a success string"}).hasSucceeded() {
if (pollingState{State: "not a success string"}).hasSucceeded() {
t.Fatalf("azure: pollingState#hasSucceeded failed to return false for a canceled operation")
}
}
func TestPollingState_HasSucceededReturnsTrueIfSuccessful(t *testing.T) {
if !(pollingState{state: operationSucceeded}).hasSucceeded() {
if !(pollingState{State: operationSucceeded}).hasSucceeded() {
t.Fatalf("azure: pollingState#hasSucceeded failed to return true for a successful operation")
}
}
func TestPollingState_HasTerminatedReturnsTrueForKnownStates(t *testing.T) {
for _, state := range []string{operationSucceeded, operationCanceled, operationFailed} {
if !(pollingState{state: state}).hasTerminated() {
if !(pollingState{State: state}).hasTerminated() {
t.Fatalf("azure: pollingState#hasTerminated failed to return true for the '%s' state", state)
}
}
}
func TestPollingState_HasTerminatedReturnsFalseForUnknownStates(t *testing.T) {
if (pollingState{state: "not a known state"}).hasTerminated() {
if (pollingState{State: "not a known state"}).hasTerminated() {
t.Fatalf("azure: pollingState#hasTerminated returned true for a non-terminal operation")
}
}
@ -182,7 +185,7 @@ func TestUpdatePollingState_ReturnsTerminatedForKnownProvisioningStates(t *testi
for _, state := range []string{operationSucceeded, operationCanceled, operationFailed} {
resp := mocks.NewResponseWithContent(fmt.Sprintf(pollingStateFormat, state))
resp.StatusCode = 42
ps := &pollingState{responseFormat: usesProvisioningStatus}
ps := &pollingState{PollingMethod: PollingLocation}
updatePollingState(resp, ps)
if !ps.hasTerminated() {
t.Fatalf("azure: updatePollingState failed to return a terminating pollingState for the '%s' state", state)
@ -193,7 +196,7 @@ func TestUpdatePollingState_ReturnsTerminatedForKnownProvisioningStates(t *testi
func TestUpdatePollingState_ReturnsSuccessForSuccessfulProvisioningState(t *testing.T) {
resp := mocks.NewResponseWithContent(fmt.Sprintf(pollingStateFormat, operationSucceeded))
resp.StatusCode = 42
ps := &pollingState{responseFormat: usesProvisioningStatus}
ps := &pollingState{PollingMethod: PollingLocation}
updatePollingState(resp, ps)
if !ps.hasSucceeded() {
t.Fatalf("azure: updatePollingState failed to return a successful pollingState for the '%s' state", operationSucceeded)
@ -204,7 +207,7 @@ func TestUpdatePollingState_ReturnsInProgressForAllOtherProvisioningStates(t *te
s := "not a recognized state"
resp := mocks.NewResponseWithContent(fmt.Sprintf(pollingStateFormat, s))
resp.StatusCode = 42
ps := &pollingState{responseFormat: usesProvisioningStatus}
ps := &pollingState{PollingMethod: PollingLocation}
updatePollingState(resp, ps)
if ps.hasTerminated() {
t.Fatalf("azure: updatePollingState returned terminated for unknown state '%s'", s)
@ -215,7 +218,7 @@ func TestUpdatePollingState_ReturnsSuccessWhenProvisioningStateFieldIsAbsentForS
for _, sc := range []int{http.StatusOK, http.StatusCreated, http.StatusNoContent} {
resp := mocks.NewResponseWithContent(pollingStateEmpty)
resp.StatusCode = sc
ps := &pollingState{responseFormat: usesProvisioningStatus}
ps := &pollingState{PollingMethod: PollingLocation}
updatePollingState(resp, ps)
if !ps.hasSucceeded() {
t.Fatalf("azure: updatePollingState failed to return success when the provisionState field is absent for Status Code %d", sc)
@ -226,7 +229,7 @@ func TestUpdatePollingState_ReturnsSuccessWhenProvisioningStateFieldIsAbsentForS
func TestUpdatePollingState_ReturnsInProgressWhenProvisioningStateFieldIsAbsentForAccepted(t *testing.T) {
resp := mocks.NewResponseWithContent(pollingStateEmpty)
resp.StatusCode = http.StatusAccepted
ps := &pollingState{responseFormat: usesProvisioningStatus}
ps := &pollingState{PollingMethod: PollingLocation}
updatePollingState(resp, ps)
if ps.hasTerminated() {
t.Fatalf("azure: updatePollingState returned terminated when the provisionState field is absent for Status Code Accepted")
@ -236,7 +239,7 @@ func TestUpdatePollingState_ReturnsInProgressWhenProvisioningStateFieldIsAbsentF
func TestUpdatePollingState_ReturnsFailedWhenProvisioningStateFieldIsAbsentForUnknownStatusCodes(t *testing.T) {
resp := mocks.NewResponseWithContent(pollingStateEmpty)
resp.StatusCode = 42
ps := &pollingState{responseFormat: usesProvisioningStatus}
ps := &pollingState{PollingMethod: PollingLocation}
updatePollingState(resp, ps)
if !ps.hasTerminated() || ps.hasSucceeded() {
t.Fatalf("azure: updatePollingState did not return failed when the provisionState field is absent for an unknown Status Code")
@ -247,7 +250,7 @@ func TestUpdatePollingState_ReturnsTerminatedForKnownOperationResourceStates(t *
for _, state := range []string{operationSucceeded, operationCanceled, operationFailed} {
resp := mocks.NewResponseWithContent(fmt.Sprintf(operationResourceFormat, state))
resp.StatusCode = 42
ps := &pollingState{responseFormat: usesOperationResponse}
ps := &pollingState{PollingMethod: PollingAsyncOperation}
updatePollingState(resp, ps)
if !ps.hasTerminated() {
t.Fatalf("azure: updatePollingState failed to return a terminating pollingState for the '%s' state", state)
@ -258,7 +261,7 @@ func TestUpdatePollingState_ReturnsTerminatedForKnownOperationResourceStates(t *
func TestUpdatePollingState_ReturnsSuccessForSuccessfulOperationResourceState(t *testing.T) {
resp := mocks.NewResponseWithContent(fmt.Sprintf(operationResourceFormat, operationSucceeded))
resp.StatusCode = 42
ps := &pollingState{responseFormat: usesOperationResponse}
ps := &pollingState{PollingMethod: PollingAsyncOperation}
updatePollingState(resp, ps)
if !ps.hasSucceeded() {
t.Fatalf("azure: updatePollingState failed to return a successful pollingState for the '%s' state", operationSucceeded)
@ -269,7 +272,7 @@ func TestUpdatePollingState_ReturnsInProgressForAllOtherOperationResourceStates(
s := "not a recognized state"
resp := mocks.NewResponseWithContent(fmt.Sprintf(operationResourceFormat, s))
resp.StatusCode = 42
ps := &pollingState{responseFormat: usesOperationResponse}
ps := &pollingState{PollingMethod: PollingAsyncOperation}
updatePollingState(resp, ps)
if ps.hasTerminated() {
t.Fatalf("azure: updatePollingState returned terminated for unknown state '%s'", s)
@ -280,7 +283,7 @@ func TestUpdatePollingState_CopiesTheResponseBody(t *testing.T) {
s := fmt.Sprintf(pollingStateFormat, operationSucceeded)
resp := mocks.NewResponseWithContent(s)
resp.StatusCode = 42
ps := &pollingState{responseFormat: usesOperationResponse}
ps := &pollingState{PollingMethod: PollingAsyncOperation}
updatePollingState(resp, ps)
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
@ -294,7 +297,7 @@ func TestUpdatePollingState_CopiesTheResponseBody(t *testing.T) {
func TestUpdatePollingState_ClosesTheOriginalResponseBody(t *testing.T) {
resp := mocks.NewResponse()
b := resp.Body.(*mocks.Body)
ps := &pollingState{responseFormat: usesProvisioningStatus}
ps := &pollingState{PollingMethod: PollingLocation}
updatePollingState(resp, ps)
if b.IsOpen() {
t.Fatal("azure: updatePollingState failed to close the original http.Response Body")
@ -312,23 +315,23 @@ func TestUpdatePollingState_FailsWhenResponseLacksRequest(t *testing.T) {
}
}
func TestUpdatePollingState_SetsTheResponseFormatWhenUsingTheAzureAsyncOperationHeader(t *testing.T) {
func TestUpdatePollingState_SetsThePollingMethodWhenUsingTheAzureAsyncOperationHeader(t *testing.T) {
ps := pollingState{}
updatePollingState(newAsynchronousResponse(), &ps)
if ps.responseFormat != usesOperationResponse {
if ps.PollingMethod != PollingAsyncOperation {
t.Fatal("azure: updatePollingState failed to set the correct response format when using the Azure-AsyncOperation header")
}
}
func TestUpdatePollingState_SetsTheResponseFormatWhenUsingTheAzureAsyncOperationHeaderIsMissing(t *testing.T) {
func TestUpdatePollingState_SetsThePollingMethodWhenUsingTheAzureAsyncOperationHeaderIsMissing(t *testing.T) {
resp := newAsynchronousResponse()
resp.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
ps := pollingState{}
updatePollingState(resp, &ps)
if ps.responseFormat != usesProvisioningStatus {
if ps.PollingMethod != PollingLocation {
t.Fatal("azure: updatePollingState failed to set the correct response format when the Azure-AsyncOperation header is absent")
}
}
@ -337,10 +340,10 @@ func TestUpdatePollingState_DoesNotChangeAnExistingReponseFormat(t *testing.T) {
resp := newAsynchronousResponse()
resp.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
ps := pollingState{responseFormat: usesOperationResponse}
ps := pollingState{PollingMethod: PollingAsyncOperation}
updatePollingState(resp, &ps)
if ps.responseFormat != usesOperationResponse {
if ps.PollingMethod != PollingAsyncOperation {
t.Fatal("azure: updatePollingState failed to leave an existing response format setting")
}
}
@ -351,7 +354,7 @@ func TestUpdatePollingState_PrefersTheAzureAsyncOperationHeader(t *testing.T) {
ps := pollingState{}
updatePollingState(resp, &ps)
if ps.uri != mocks.TestAzureAsyncURL {
if ps.URI != mocks.TestAzureAsyncURL {
t.Fatal("azure: updatePollingState failed to prefer the Azure-AsyncOperation header")
}
}
@ -363,7 +366,7 @@ func TestUpdatePollingState_PrefersLocationWhenTheAzureAsyncOperationHeaderMissi
ps := pollingState{}
updatePollingState(resp, &ps)
if ps.uri != mocks.TestLocationURL {
if ps.URI != mocks.TestLocationURL {
t.Fatal("azure: updatePollingState failed to prefer the Location header when the Azure-AsyncOperation header is missing")
}
}
@ -377,7 +380,7 @@ func TestUpdatePollingState_UsesTheObjectLocationIfAsyncHeadersAreMissing(t *tes
ps := pollingState{}
updatePollingState(resp, &ps)
if ps.uri != mocks.TestURL {
if ps.URI != mocks.TestURL {
t.Fatal("azure: updatePollingState failed to use the Object URL when the asynchronous headers are missing")
}
}
@ -392,7 +395,7 @@ func TestUpdatePollingState_RecognizesLowerCaseHTTPVerbs(t *testing.T) {
ps := pollingState{}
updatePollingState(resp, &ps)
if ps.uri != mocks.TestURL {
if ps.URI != mocks.TestURL {
t.Fatalf("azure: updatePollingState failed to recognize the lower-case HTTP verb '%s'", m)
}
}
@ -412,32 +415,22 @@ func TestUpdatePollingState_ReturnsAnErrorIfAsyncHeadersAreMissingForANewOrDelet
}
}
func TestNewPollingRequest_FailsWhenResponseLacksRequest(t *testing.T) {
resp := newAsynchronousResponse()
resp.Request = nil
_, err := newPollingRequest(resp, pollingState{})
if err == nil {
t.Fatal("azure: newPollingRequest failed to return an error when the http.Response lacked the original http.Request")
}
}
func TestNewPollingRequest_ReturnsAnErrorWhenPrepareFails(t *testing.T) {
_, err := newPollingRequest(newAsynchronousResponse(), pollingState{responseFormat: usesOperationResponse, uri: mocks.TestBadURL})
_, err := newPollingRequest(pollingState{PollingMethod: PollingAsyncOperation, URI: mocks.TestBadURL})
if err == nil {
t.Fatal("azure: newPollingRequest failed to return an error when Prepare fails")
}
}
func TestNewPollingRequest_DoesNotReturnARequestWhenPrepareFails(t *testing.T) {
req, _ := newPollingRequest(newAsynchronousResponse(), pollingState{responseFormat: usesOperationResponse, uri: mocks.TestBadURL})
req, _ := newPollingRequest(pollingState{PollingMethod: PollingAsyncOperation, URI: mocks.TestBadURL})
if req != nil {
t.Fatal("azure: newPollingRequest returned an http.Request when Prepare failed")
}
}
func TestNewPollingRequest_ReturnsAGetRequest(t *testing.T) {
req, _ := newPollingRequest(newAsynchronousResponse(), pollingState{responseFormat: usesOperationResponse, uri: mocks.TestAzureAsyncURL})
req, _ := newPollingRequest(pollingState{PollingMethod: PollingAsyncOperation, URI: mocks.TestAzureAsyncURL})
if req.Method != "GET" {
t.Fatalf("azure: newPollingRequest did not create an HTTP GET request -- actual method %v", req.Method)
}
@ -643,7 +636,7 @@ func TestDoPollForAsynchronous_PollsForStatusAccepted(t *testing.T) {
r, _ := autorest.SendWithSender(client, mocks.NewRequest(),
DoPollForAsynchronous(time.Millisecond))
if client.Attempts() < 4 {
if client.Attempts() < client.NumResponses() {
t.Fatalf("azure: DoPollForAsynchronous stopped polling before receiving a terminated OperationResource")
}
@ -666,7 +659,7 @@ func TestDoPollForAsynchronous_PollsForStatusCreated(t *testing.T) {
r, _ := autorest.SendWithSender(client, mocks.NewRequest(),
DoPollForAsynchronous(time.Millisecond))
if client.Attempts() < 4 {
if client.Attempts() < client.NumResponses() {
t.Fatalf("azure: DoPollForAsynchronous stopped polling before receiving a terminated OperationResource")
}
@ -690,7 +683,7 @@ func TestDoPollForAsynchronous_PollsUntilProvisioningStatusTerminates(t *testing
r, _ := autorest.SendWithSender(client, mocks.NewRequest(),
DoPollForAsynchronous(time.Millisecond))
if client.Attempts() < 4 {
if client.Attempts() < client.NumResponses() {
t.Fatalf("azure: DoPollForAsynchronous stopped polling before receiving a terminated OperationResource")
}
@ -714,7 +707,7 @@ func TestDoPollForAsynchronous_PollsUntilProvisioningStatusSucceeds(t *testing.T
r, _ := autorest.SendWithSender(client, mocks.NewRequest(),
DoPollForAsynchronous(time.Millisecond))
if client.Attempts() < 4 {
if client.Attempts() < client.NumResponses() {
t.Fatalf("azure: DoPollForAsynchronous stopped polling before receiving a terminated OperationResource")
}
@ -735,7 +728,7 @@ func TestDoPollForAsynchronous_PollsUntilOperationResourceHasTerminated(t *testi
r, _ := autorest.SendWithSender(client, mocks.NewRequest(),
DoPollForAsynchronous(time.Millisecond))
if client.Attempts() < 4 {
if client.Attempts() < client.NumResponses() {
t.Fatalf("azure: DoPollForAsynchronous stopped polling before receiving a terminated OperationResource")
}
@ -756,7 +749,7 @@ func TestDoPollForAsynchronous_PollsUntilOperationResourceHasSucceeded(t *testin
r, _ := autorest.SendWithSender(client, mocks.NewRequest(),
DoPollForAsynchronous(time.Millisecond))
if client.Attempts() < 4 {
if client.Attempts() < client.NumResponses() {
t.Fatalf("azure: DoPollForAsynchronous stopped polling before receiving a terminated OperationResource")
}
@ -885,7 +878,7 @@ func TestDoPollForAsynchronous_ReturnsErrorForLastErrorResponse(t *testing.T) {
r1.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
r2 := newProvisioningStatusResponse("busy")
r2.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
r3 := newAsynchronousResponseWithError()
r3 := newAsynchronousResponseWithError("400 Bad Request", http.StatusBadRequest)
r3.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
client := mocks.NewSender()
@ -932,7 +925,7 @@ func TestDoPollForAsynchronous_ReturnsOperationResourceErrorForFailedOperations(
func TestDoPollForAsynchronous_ReturnsErrorForFirstPutRequest(t *testing.T) {
// Return 400 bad response with error code and message in first put
r1 := newAsynchronousResponseWithError()
r1 := newAsynchronousResponseWithError("400 Bad Request", http.StatusBadRequest)
client := mocks.NewSender()
client.AppendResponse(r1)
@ -1024,6 +1017,210 @@ func TestDoPollForAsynchronous_StopsPollingIfItReceivesAnInvalidOperationResourc
autorest.ByClosing())
}
func TestFuture_PollsUntilProvisioningStatusSucceeds(t *testing.T) {
r1 := newAsynchronousResponse()
r1.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
r2 := newProvisioningStatusResponse("busy")
r2.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
r3 := newProvisioningStatusResponse(operationSucceeded)
r3.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
client := mocks.NewSender()
client.AppendResponse(r1)
client.AppendAndRepeatResponse(r2, 2)
client.AppendResponse(r3)
future := NewFuture(mocks.NewRequest())
for done, err := future.Done(client); !done; done, err = future.Done(client) {
if future.PollingMethod() != PollingLocation {
t.Fatalf("azure: wrong future polling method")
}
if err != nil {
t.Fatalf("azure: TestFuture polling Done failed")
}
delay, ok := future.GetPollingDelay()
if !ok {
t.Fatalf("expected Retry-After value")
}
time.Sleep(delay)
}
if client.Attempts() < client.NumResponses() {
t.Fatalf("azure: TestFuture stopped polling before receiving a terminated OperationResource")
}
autorest.Respond(future.Response(),
autorest.ByClosing())
}
func TestFuture_Marshalling(t *testing.T) {
client := mocks.NewSender()
client.AppendResponse(newAsynchronousResponse())
future := NewFuture(mocks.NewRequest())
done, err := future.Done(client)
if err != nil {
t.Fatalf("azure: TestFuture marshalling Done failed")
}
if done {
t.Fatalf("azure: TestFuture marshalling shouldn't be done")
}
if future.PollingMethod() != PollingAsyncOperation {
t.Fatalf("azure: wrong future polling method")
}
data, err := json.Marshal(future)
if err != nil {
t.Fatalf("azure: TestFuture failed to marshal")
}
var future2 Future
err = json.Unmarshal(data, &future2)
if err != nil {
t.Fatalf("azure: TestFuture failed to unmarshal")
}
if future.ps.Code != future2.ps.Code {
t.Fatalf("azure: TestFuture marshalling codes don't match")
}
if future.ps.Message != future2.ps.Message {
t.Fatalf("azure: TestFuture marshalling messages don't match")
}
if future.ps.PollingMethod != future2.ps.PollingMethod {
t.Fatalf("azure: TestFuture marshalling response formats don't match")
}
if future.ps.State != future2.ps.State {
t.Fatalf("azure: TestFuture marshalling states don't match")
}
if future.ps.URI != future2.ps.URI {
t.Fatalf("azure: TestFuture marshalling URIs don't match")
}
}
func TestFuture_WaitForCompletion(t *testing.T) {
r1 := newAsynchronousResponse()
r1.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
r2 := newProvisioningStatusResponse("busy")
r2.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
r3 := newAsynchronousResponseWithError("Internal server error", http.StatusInternalServerError)
r3.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
r3.Header.Del(http.CanonicalHeaderKey("Retry-After"))
r4 := newProvisioningStatusResponse(operationSucceeded)
r4.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
sender := mocks.NewSender()
sender.AppendResponse(r1)
sender.AppendError(errors.New("transient network failure"))
sender.AppendAndRepeatResponse(r2, 2)
sender.AppendResponse(r3)
sender.AppendResponse(r4)
future := NewFuture(mocks.NewRequest())
client := autorest.Client{
PollingDelay: 1 * time.Second,
PollingDuration: autorest.DefaultPollingDuration,
RetryAttempts: autorest.DefaultRetryAttempts,
RetryDuration: 1 * time.Second,
Sender: sender,
}
err := future.WaitForCompletion(context.Background(), client)
if err != nil {
t.Fatalf("azure: WaitForCompletion returned non-nil error")
}
if sender.Attempts() < sender.NumResponses() {
t.Fatalf("azure: TestFuture stopped polling before receiving a terminated OperationResource")
}
autorest.Respond(future.Response(),
autorest.ByClosing())
}
func TestFuture_WaitForCompletionTimedOut(t *testing.T) {
r1 := newAsynchronousResponse()
r1.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
r2 := newProvisioningStatusResponse("busy")
r2.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
sender := mocks.NewSender()
sender.AppendResponse(r1)
sender.AppendAndRepeatResponseWithDelay(r2, 1*time.Second, 5)
future := NewFuture(mocks.NewRequest())
client := autorest.Client{
PollingDelay: autorest.DefaultPollingDelay,
PollingDuration: 2 * time.Second,
RetryAttempts: autorest.DefaultRetryAttempts,
RetryDuration: 1 * time.Second,
Sender: sender,
}
err := future.WaitForCompletion(context.Background(), client)
if err == nil {
t.Fatalf("azure: WaitForCompletion returned nil error, should have timed out")
}
}
func TestFuture_WaitForCompletionRetriesExceeded(t *testing.T) {
r1 := newAsynchronousResponse()
r1.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
sender := mocks.NewSender()
sender.AppendResponse(r1)
sender.AppendAndRepeatError(errors.New("transient network failure"), autorest.DefaultRetryAttempts+1)
future := NewFuture(mocks.NewRequest())
client := autorest.Client{
PollingDelay: autorest.DefaultPollingDelay,
PollingDuration: autorest.DefaultPollingDuration,
RetryAttempts: autorest.DefaultRetryAttempts,
RetryDuration: 100 * time.Millisecond,
Sender: sender,
}
err := future.WaitForCompletion(context.Background(), client)
if err == nil {
t.Fatalf("azure: WaitForCompletion returned nil error, should have errored out")
}
}
func TestFuture_WaitForCompletionCancelled(t *testing.T) {
r1 := newAsynchronousResponse()
r1.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
r2 := newProvisioningStatusResponse("busy")
r2.Header.Del(http.CanonicalHeaderKey(headerAsyncOperation))
sender := mocks.NewSender()
sender.AppendResponse(r1)
sender.AppendAndRepeatResponseWithDelay(r2, 1*time.Second, 5)
future := NewFuture(mocks.NewRequest())
client := autorest.Client{
PollingDelay: autorest.DefaultPollingDelay,
PollingDuration: autorest.DefaultPollingDuration,
RetryAttempts: autorest.DefaultRetryAttempts,
RetryDuration: autorest.DefaultRetryDuration,
Sender: sender,
}
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(2 * time.Second)
cancel()
}()
err := future.WaitForCompletion(ctx, client)
if err == nil {
t.Fatalf("azure: WaitForCompletion returned nil error, should have been cancelled")
}
}
const (
operationResourceIllegal = `
This is not JSON and should fail...badly.
@ -1099,8 +1296,8 @@ func newAsynchronousResponse() *http.Response {
return r
}
func newAsynchronousResponseWithError() *http.Response {
r := mocks.NewResponseWithStatus("400 Bad Request", http.StatusBadRequest)
func newAsynchronousResponseWithError(response string, status int) *http.Response {
r := mocks.NewResponseWithStatus(response, status)
mocks.SetRetryHeader(r, retryDelay)
r.Request = mocks.NewRequestForURL(mocks.TestURL)
r.Body = mocks.NewBody(errorResponse)

View file

@ -15,10 +15,17 @@ package azure
// limitations under the License.
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
)
// EnvironmentFilepathName captures the name of the environment variable containing the path to the file
// to be used while populating the Azure Environment.
const EnvironmentFilepathName = "AZURE_ENVIRONMENT_FILEPATH"
var environments = map[string]Environment{
"AZURECHINACLOUD": ChinaCloud,
"AZUREGERMANCLOUD": GermanCloud,
@ -133,12 +140,37 @@ var (
}
)
// EnvironmentFromName returns an Environment based on the common name specified
// EnvironmentFromName returns an Environment based on the common name specified.
func EnvironmentFromName(name string) (Environment, error) {
// IMPORTANT
// As per @radhikagupta5:
// This is technical debt, fundamentally here because Kubernetes is not currently accepting
// contributions to the providers. Once that is an option, the provider should be updated to
// directly call `EnvironmentFromFile`. Until then, we rely on dispatching Azure Stack environment creation
// from this method based on the name that is provided to us.
if strings.EqualFold(name, "AZURESTACKCLOUD") {
return EnvironmentFromFile(os.Getenv(EnvironmentFilepathName))
}
name = strings.ToUpper(name)
env, ok := environments[name]
if !ok {
return env, fmt.Errorf("autorest/azure: There is no cloud environment matching the name %q", name)
}
return env, nil
}
// EnvironmentFromFile loads an Environment from a configuration file available on disk.
// This function is particularly useful in the Hybrid Cloud model, where one must define their own
// endpoints.
func EnvironmentFromFile(location string) (unmarshaled Environment, err error) {
fileContents, err := ioutil.ReadFile(location)
if err != nil {
return
}
err = json.Unmarshal(fileContents, &unmarshaled)
return
}

View file

@ -17,9 +17,63 @@ package azure
import (
"encoding/json"
"os"
"path"
"path/filepath"
"runtime"
"testing"
)
// This correlates to the expected contents of ./testdata/test_environment_1.json
var testEnvironment1 = Environment{
Name: "--unit-test--",
ManagementPortalURL: "--management-portal-url",
PublishSettingsURL: "--publish-settings-url--",
ServiceManagementEndpoint: "--service-management-endpoint--",
ResourceManagerEndpoint: "--resource-management-endpoint--",
ActiveDirectoryEndpoint: "--active-directory-endpoint--",
GalleryEndpoint: "--gallery-endpoint--",
KeyVaultEndpoint: "--key-vault--endpoint--",
GraphEndpoint: "--graph-endpoint--",
StorageEndpointSuffix: "--storage-endpoint-suffix--",
SQLDatabaseDNSSuffix: "--sql-database-dns-suffix--",
TrafficManagerDNSSuffix: "--traffic-manager-dns-suffix--",
KeyVaultDNSSuffix: "--key-vault-dns-suffix--",
ServiceBusEndpointSuffix: "--service-bus-endpoint-suffix--",
ServiceManagementVMDNSSuffix: "--asm-vm-dns-suffix--",
ResourceManagerVMDNSSuffix: "--arm-vm-dns-suffix--",
ContainerRegistryDNSSuffix: "--container-registry-dns-suffix--",
}
func TestEnvironment_EnvironmentFromFile(t *testing.T) {
got, err := EnvironmentFromFile(filepath.Join("testdata", "test_environment_1.json"))
if err != nil {
t.Error(err)
}
if got != testEnvironment1 {
t.Logf("got: %v want: %v", got, testEnvironment1)
t.Fail()
}
}
func TestEnvironment_EnvironmentFromName_Stack(t *testing.T) {
_, currentFile, _, _ := runtime.Caller(0)
prevEnvFilepathValue := os.Getenv(EnvironmentFilepathName)
os.Setenv(EnvironmentFilepathName, filepath.Join(path.Dir(currentFile), "testdata", "test_environment_1.json"))
defer os.Setenv(EnvironmentFilepathName, prevEnvFilepathValue)
got, err := EnvironmentFromName("AZURESTACKCLOUD")
if err != nil {
t.Error(err)
}
if got != testEnvironment1 {
t.Logf("got: %v want: %v", got, testEnvironment1)
t.Fail()
}
}
func TestEnvironmentFromName(t *testing.T) {
name := "azurechinacloud"
if env, _ := EnvironmentFromName(name); env != ChinaCloud {

View file

@ -55,15 +55,16 @@ func DoRetryWithRegistration(client autorest.Client) autorest.SendDecorator {
if err != nil {
return resp, err
}
err = re
if re.ServiceError != nil && re.ServiceError.Code == "MissingSubscriptionRegistration" {
err = register(client, r, re)
if err != nil {
return resp, fmt.Errorf("failed auto registering Resource Provider: %s", err)
regErr := register(client, r, re)
if regErr != nil {
return resp, fmt.Errorf("failed auto registering Resource Provider: %s. Original error: %s", regErr, err)
}
}
}
return resp, errors.New("failed request and resource provider registration")
return resp, fmt.Errorf("failed request: %s", err)
})
}
}

View file

@ -35,6 +35,9 @@ const (
// DefaultRetryAttempts is number of attempts for retry status codes (5xx).
DefaultRetryAttempts = 3
// DefaultRetryDuration is the duration to wait between retries.
DefaultRetryDuration = 30 * time.Second
)
var (
@ -172,7 +175,7 @@ func NewClientWithUserAgent(ua string) Client {
PollingDelay: DefaultPollingDelay,
PollingDuration: DefaultPollingDuration,
RetryAttempts: DefaultRetryAttempts,
RetryDuration: 30 * time.Second,
RetryDuration: DefaultRetryDuration,
UserAgent: defaultUserAgent,
}
c.Sender = c.sender()

View file

@ -221,7 +221,8 @@ func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) Se
return resp, err
}
resp, err = s.Do(rr.Request())
if err != nil || !ResponseHasStatusCode(resp, codes...) {
// we want to retry if err is not nil (e.g. transient network failure)
if err == nil && !ResponseHasStatusCode(resp, codes...) {
return resp, err
}
delayed := DelayWithRetryAfter(resp, r.Cancel)
@ -237,6 +238,9 @@ func DoRetryForStatusCodes(attempts int, backoff time.Duration, codes ...int) Se
// DelayWithRetryAfter invokes time.After for the duration specified in the "Retry-After" header in
// responses with status code 429
func DelayWithRetryAfter(resp *http.Response, cancel <-chan struct{}) bool {
if resp == nil {
return false
}
retryAfter, _ := strconv.Atoi(resp.Header.Get("Retry-After"))
if resp.StatusCode == http.StatusTooManyRequests && retryAfter > 0 {
select {

View file

@ -20,6 +20,7 @@ import (
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"reflect"
"sort"
@ -190,3 +191,14 @@ func createQuery(v url.Values) string {
}
return buf.String()
}
// ChangeToGet turns the specified http.Request into a GET (it assumes it wasn't).
// This is mainly useful for long-running operations that use the Azure-AsyncOperation
// header, so we change the initial PUT into a GET to retrieve the final result.
func ChangeToGet(req *http.Request) *http.Request {
req.Method = "GET"
req.Body = nil
req.ContentLength = 0
req.Header.Del("Content-Length")
return req
}