diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7be5c88b843b..4ff4639d5dba 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -26,7 +26,7 @@ jobs: GOPATH: '$(system.defaultWorkingDirectory)/work' sdkPath: '$(GOPATH)/src/github.com/$(build.repository.name)' IGNORE_BREAKING_CHANGES: true - go.list.filter: '| grep -v vendor' + go.list.filter: '| grep -v vendor | grep -v azure-sdk-for-go/sdk' steps: - task: GoTool@0 @@ -57,7 +57,7 @@ jobs: - script: go build -v $(go list ./... $(go.list.filter)) workingDirectory: '$(sdkPath)' displayName: 'Build' - - script: go test $(dirname $(find . -path ./vendor -prune -o -path ./sdk -prune -o -name '*_test.go' -print) | sort -u) + - script: go test $(dirname $(find . -path ./vendor -prune -o -path ./sdk -prune -o -path ./sdk -prune -o -name '*_test.go' -print) | sort -u) workingDirectory: '$(sdkPath)' displayName: 'Run Tests' - script: go run ./tools/apidiff/main.go packages ./services FETCH_HEAD~1 FETCH_HEAD --copyrepo --breakingchanges || $IGNORE_BREAKING_CHANGES diff --git a/eng/pipelines/templates/steps/build-test.yml b/eng/pipelines/templates/steps/build-test.yml index f08eed8d6bf4..536460220177 100644 --- a/eng/pipelines/templates/steps/build-test.yml +++ b/eng/pipelines/templates/steps/build-test.yml @@ -12,43 +12,78 @@ steps: go get github.com/axw/gocov/gocov go get github.com/AlekSi/gocov-xml go get github.com/matm/gocov-html + go get -u github.com/wadey/gocovmerge displayName: "Install Coverage and Junit Dependencies" workingDirectory: '${{parameters.GoWorkspace}}' - pwsh: | - go vet $(go list $(SCOPE)) - displayName: 'Vet' + $modDirs = (./eng/scripts/get_module_dirs.ps1 -serviceDir $(SCOPE)) + foreach ($md in $modDirs) { + pushd $md + Write-Host "##[command]Executing go build -v $md" + go build -v + } + displayName: 'Build' workingDirectory: '${{parameters.GoWorkspace}}' + env: + GO111MODULE: 'on' - pwsh: | - go build -v $(go list $(SCOPE)) - displayName: 'Build' + $modDirs = (./eng/scripts/get_module_dirs.ps1 -serviceDir $(SCOPE)) + foreach ($md in $modDirs) { + pushd $md + Write-Host "##[command]Executing go vet $md" + go vet + } + displayName: 'Vet' workingDirectory: '${{parameters.GoWorkspace}}' + env: + GO111MODULE: 'on' - pwsh: | - go test -run "^Test" -race -v -coverprofile coverage.txt -covermode atomic $(SCOPE) | go-junit-report -set-exit-code > $(Build.SourcesDirectory)/report.xml + $testDirs = (./eng/scripts/get_test_dirs.ps1 -serviceDir $(SCOPE)) + foreach ($td in $testDirs) { + pushd $td + Write-Host "##[command]Executing go test -run "^Test" -race -v -coverprofile coverage.txt -covermode atomic $td | go-junit-report -set-exit-code > report.xml" + go test -run "^Test" -race -v -coverprofile coverage.txt -covermode atomic . | go-junit-report -set-exit-code > report.xml + # if no tests were actually run (e.g. examples) delete the coverage file so it's omitted from the coverage report + if (Select-String -path ./report.xml -pattern '' -simplematch -quiet) { + Write-Host "##[command]Deleting empty coverage file" + rm coverage.txt + } + } displayName: 'Run Tests' workingDirectory: '${{parameters.GoWorkspace}}' + env: + GO111MODULE: 'on' - pwsh: | - gocov convert ./coverage.txt > ./coverage.json - + $coverageFiles = [Collections.Generic.List[String]]@() + Get-Childitem -recurse -path $(SCOPE) -filter coverage.txt | foreach-object { + $covFile = $_.FullName + Write-Host "Adding $covFile to the list of code coverage files" + $coverageFiles.Add($covFile) + } + gocovmerge $coverageFiles > mergedCoverage.txt + gocov convert ./mergedCoverage.txt > ./coverage.json # gocov converts rely on standard input - Get-Content ./coverage.json | gocov-xml > $(Build.SourcesDirectory)/coverage.xml - Get-Content ./coverage.json | gocov-html > $(Build.SourcesDirectory)/coverage.html + Get-Content ./coverage.json | gocov-xml > ./coverage.xml + Get-Content ./coverage.json | gocov-html > ./coverage.html displayName: 'Generate Coverage XML' - workingDirectory: '${{parameters.GoWorkspace}}' + workingDirectory: '${{parameters.GoWorkspace}}sdk' - task: PublishTestResults@2 condition: succeededOrFailed() inputs: testRunner: JUnit - testResultsFiles: '$(Build.SourcesDirectory)/report.xml' + testResultsFiles: '${{parameters.GoWorkspace}}sdk/**/report.xml' testRunTitle: 'Go ${{ parameters.GoVersion }} on ${{ parameters.Image }}' + failTaskOnFailedTests: true - task: PublishCodeCoverageResults@1 condition: succeededOrFailed() inputs: codeCoverageTool: Cobertura - summaryFileLocation: '$(Build.SourcesDirectory)/coverage.xml' - additionalCodeCoverageFiles: '$(Build.SourcesDirectory)/coverage.html' + summaryFileLocation: '${{parameters.GoWorkspace}}sdk/coverage.xml' + additionalCodeCoverageFiles: '${{parameters.GoWorkspace}}sdk/coverage.html' + failIfCoverageEmpty: true diff --git a/eng/pipelines/templates/steps/set-scope.yml b/eng/pipelines/templates/steps/set-scope.yml index 09d3c71fc9e9..02d355fa69cd 100644 --- a/eng/pipelines/templates/steps/set-scope.yml +++ b/eng/pipelines/templates/steps/set-scope.yml @@ -8,7 +8,7 @@ steps: $serviceDir = "${{parameters.ServiceDirectory}}" $scope = (./eng/scripts/scoped_discover.ps1 -serviceDir "$serviceDir") - Write-Host + Write-Host $scope Write-Host "##vso[task.setvariable variable=SCOPE]$scope" displayName: 'Set Target Scope' workingDirectory: "${{ parameters.GoWorkspace }}" diff --git a/eng/scripts/get_module_dirs.ps1 b/eng/scripts/get_module_dirs.ps1 new file mode 100644 index 000000000000..5404ea45a7a7 --- /dev/null +++ b/eng/scripts/get_module_dirs.ps1 @@ -0,0 +1,15 @@ +Param( + [string] $serviceDir +) + +$modDirs = [Collections.Generic.List[String]]@() + +# find each module directory under $serviceDir +Get-Childitem -recurse -path $serviceDir -filter go.mod | foreach-object { + $cdir = $_.Directory + Write-Host "Adding $cdir to list of module paths" + $modDirs.Add($cdir) +} + +# return the list of module directories +return $modDirs diff --git a/eng/scripts/get_test_dirs.ps1 b/eng/scripts/get_test_dirs.ps1 new file mode 100644 index 000000000000..07a46f047c8b --- /dev/null +++ b/eng/scripts/get_test_dirs.ps1 @@ -0,0 +1,17 @@ +Param( + [string] $serviceDir +) + +$testDirs = [Collections.Generic.List[String]]@() + +# find each directory under $serviceDir that contains Go test files +Get-Childitem -recurse -path $serviceDir -filter *_test.go | foreach-object { + $cdir = $_.Directory + if (!$testDirs.Contains($cdir)) { + Write-Host "Adding $cdir to list of test directories" + $testDirs.Add($cdir) + } +} + +# return the list of test directories +return $testDirs diff --git a/eng/scripts/scoped_discover.ps1 b/eng/scripts/scoped_discover.ps1 index 11bccc1a031f..88168575ae7d 100644 --- a/eng/scripts/scoped_discover.ps1 +++ b/eng/scripts/scoped_discover.ps1 @@ -11,4 +11,4 @@ else { $path = Resolve-Path -Path $targetDir -return "$path/..." +return $path diff --git a/sdk/azcore/core.go b/sdk/azcore/core.go deleted file mode 100644 index 364a3bf9f108..000000000000 --- a/sdk/azcore/core.go +++ /dev/null @@ -1,110 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "io" - "net/http" -) - -// Policy represents an extensibility point for the Pipeline that can mutate the specified -// Request and react to the received Response. -type Policy interface { - // Do applies the policy to the specified Request. When implementing a Policy, mutate the - // request before calling req.Do() to move on to the next policy, and respond to the result - // before returning to the caller. - Do(ctx context.Context, req *Request) (*Response, error) -} - -// PolicyFunc is a type that implements the Policy interface. -// Use this type when implementing a stateless policy as a first-class function. -type PolicyFunc func(context.Context, *Request) (*Response, error) - -// Do implements the Policy interface on PolicyFunc. -func (pf PolicyFunc) Do(ctx context.Context, req *Request) (*Response, error) { - return pf(ctx, req) -} - -// Transport represents an HTTP pipeline transport used to send HTTP requests and receive responses. -type Transport interface { - // Do sends the HTTP request and returns the HTTP response or error. - Do(ctx context.Context, req *http.Request) (*http.Response, error) -} - -// TransportFunc is a type that implements the Transport interface. -// Use this type when implementing a stateless transport as a first-class function. -type TransportFunc func(context.Context, *http.Request) (*http.Response, error) - -// Do implements the Transport interface on TransportFunc. -func (tf TransportFunc) Do(ctx context.Context, req *http.Request) (*http.Response, error) { - return tf(ctx, req) -} - -// used to adapt a TransportPolicy to a Policy -type transportPolicy struct { - trans Transport -} - -func (tp transportPolicy) Do(ctx context.Context, req *Request) (*Response, error) { - resp, err := tp.trans.Do(ctx, req.Request) - if err != nil { - return nil, err - } - return &Response{Response: resp}, nil -} - -// Pipeline represents a primitive for sending HTTP requests and receiving responses. -// Its behavior can be extended by specifying policies during construction. -type Pipeline struct { - policies []Policy -} - -// NewPipeline creates a new goroutine-safe Pipeline object from the specified Policies. -// If no transport is provided then the default HTTP transport will be used. -func NewPipeline(transport Transport, policies ...Policy) Pipeline { - if transport == nil { - transport = DefaultHTTPClientTransport() - } - // transport policy must always be the last in the slice - policies = append(policies, newBodyDownloadPolicy(), transportPolicy{trans: transport}) - return Pipeline{ - policies: policies, - } -} - -// Do is called for each and every HTTP request. It passes the Context and request through -// all the Policy objects (which can transform the Request's URL/query parameters/headers) -// and ultimately sends the transformed HTTP request over the network. -func (p Pipeline) Do(ctx context.Context, req *Request) (*Response, error) { - req.policies = p.policies - return req.Next(ctx) -} - -// ReadSeekCloser is the interface that groups the io.ReadCloser and io.Seeker interfaces. -type ReadSeekCloser interface { - io.ReadCloser - io.Seeker -} - -type nopCloser struct { - io.ReadSeeker -} - -func (n nopCloser) Close() error { - return nil -} - -// NopCloser returns a ReadSeekCloser with a no-op close method wrapping the provided io.ReadSeeker. -func NopCloser(rs io.ReadSeeker) ReadSeekCloser { - return nopCloser{rs} -} - -// Retrier provides methods describing if an error should be considered as transient. -type Retrier interface { - // IsNotRetriable returns true for error types that are not retriable. - IsNotRetriable() bool -} diff --git a/sdk/azcore/credential.go b/sdk/azcore/credential.go deleted file mode 100644 index 997872203925..000000000000 --- a/sdk/azcore/credential.go +++ /dev/null @@ -1,52 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "time" -) - -// AuthenticationPolicyOptions contains various options used to create a credential policy. -type AuthenticationPolicyOptions struct { - // Options contains the TokenRequestOptions that includes a scopes field which contains - // the list of OAuth2 authentication scopes used when requesting a token. - // This field is ignored for other forms of authentication (e.g. shared key). - Options TokenRequestOptions -} - -// Credential represents any credential type. -type Credential interface { - // AuthenticationPolicy returns a policy that requests the credential and applies it to the HTTP request. - AuthenticationPolicy(options AuthenticationPolicyOptions) Policy -} - -// credentialFunc is a type that implements the Credential interface. -// Use this type when implementing a stateless credential as a first-class function. -type credentialFunc func(options AuthenticationPolicyOptions) Policy - -// AuthenticationPolicy implements the Credential interface on credentialFunc. -func (cf credentialFunc) AuthenticationPolicy(options AuthenticationPolicyOptions) Policy { - return cf(options) -} - -// TokenCredential represents a credential capable of providing an OAuth token. -type TokenCredential interface { - Credential - // GetToken requests an access token for the specified set of scopes. - GetToken(ctx context.Context, options TokenRequestOptions) (*AccessToken, error) -} - -// AccessToken represents an Azure service bearer access token with expiry information. -type AccessToken struct { - Token string - ExpiresOn time.Time -} - -// TokenRequestOptions contain specific parameter that may be used by credentials types when attempting to get a token -type TokenRequestOptions struct { - Scopes []string -} diff --git a/sdk/azcore/doc.go b/sdk/azcore/doc.go deleted file mode 100644 index 6546445bc796..000000000000 --- a/sdk/azcore/doc.go +++ /dev/null @@ -1,164 +0,0 @@ -// +build go1.13 - -// Copyright 2017 Microsoft Corporation. All rights reserved. -// Use of this source code is governed by an MIT -// license that can be found in the LICENSE file. - -/* -Package azcore implements an HTTP request/response middleware pipeline whose -policy objects mutate an HTTP request's URL, query parameters, and/or headers before -the request is sent over the wire. - -Not all policy objects mutate an HTTP request; some policy objects simply impact the -flow of requests/responses by performing operations such as logging, retry policies, -timeouts, failure injection, and deserialization of response payloads. - -Implementing the Policy Interface - -To implement a policy, define a struct that implements the pipeline.Policy interface's Do method. Your Do -method is called when an HTTP request wants to be sent over the network. Your Do method can perform any -operation(s) it desires. For example, it can log the outgoing request, mutate the URL, headers, and/or query -parameters, inject a failure, etc. Your Do method must then forward the HTTP request to next Policy object -in a linked-list ensuring that the remaining Policy objects perform their work. Ultimately, the last Policy -object sends the HTTP request over the network (by calling the HTTPSender's Do method). - -When an HTTP response comes back, each Policy object in the linked-list gets a chance to process the response -(in reverse order). The Policy object can log the response, retry the operation if due to a transient failure -or timeout, deserialize the response body, etc. Ultimately, the last Policy object returns the HTTP response -to the code that initiated the original HTTP request. - -Here is a template for how to define a pipeline.Policy object: - - type myPolicy struct { - node PolicyNode - // TODO: Add configuration/setting fields here (if desired)... - } - - func (p *myPolicy) Do(ctx context.Context, request pipeline.Request) (pipeline.Response, error) { - // TODO: Mutate/process the HTTP request here... - response, err := p.node.Do(ctx, request) // Forward HTTP request to next Policy & get HTTP response - // TODO: Mutate/process the HTTP response here... - return response, err // Return response/error to previous Policy - } - -Implementing the Factory Interface - -Each Policy struct definition requires a factory struct definition that implements the pipeline.Factory interface's New -method. The New method is called when application code wants to initiate a new HTTP request. Factory's New method is -passed a pipeline.PolicyNode object which contains a reference to the owning pipeline.Pipeline object (discussed later) and -a reference to the next Policy object in the linked list. The New method should create its corresponding Policy object -passing it the PolicyNode and any other configuration/settings fields appropriate for the specific Policy object. - -Here is a template for how to define a pipeline.Policy object: - - // NOTE: Once created & initialized, Factory objects should be goroutine-safe (ex: immutable); - // this allows reuse (efficient use of memory) and makes these objects usable by multiple goroutines concurrently. - type myPolicyFactory struct { - // TODO: Add any configuration/setting fields if desired... - } - - func (f *myPolicyFactory) New(node pipeline.PolicyNode) Policy { - return &myPolicy{node: node} // TODO: Also initialize any configuration/setting fields here (if desired)... - } - -Using your Factory and Policy objects via a Pipeline - -To use the Factory and Policy objects, an application constructs a slice of Factory objects and passes -this slice to the pipeline.NewPipeline function. - - func NewPipeline(factories []pipeline.Factory, sender pipeline.HTTPSender) Pipeline - -This function also requires an object implementing the HTTPSender interface. For simple scenarios, -passing nil for HTTPSender causes a standard Go http.Client object to be created and used to actually -send the HTTP response over the network. For more advanced scenarios, you can pass your own HTTPSender -object in. This allows sharing of http.Client objects or the use of custom-configured http.Client objects -or other objects that can simulate the network requests for testing purposes. - -Now that you have a pipeline.Pipeline object, you can create a pipeline.Request object (which is a simple -wrapper around Go's standard http.Request object) and pass it to Pipeline's Do method along with passing a -context.Context for cancelling the HTTP request (if desired). - - type Pipeline interface { - Do(ctx context.Context, methodFactory pipeline.Factory, request pipeline.Request) (pipeline.Response, error) - } - -Do iterates over the slice of Factory objects and tells each one to create its corresponding -Policy object. After the linked-list of Policy objects have been created, Do calls the first -Policy object passing it the Context & HTTP request parameters. These parameters now flow through -all the Policy objects giving each object a chance to look at and/or mutate the HTTP request. -The last Policy object sends the message over the network. - -When the network operation completes, the HTTP response and error return values pass -back through the same Policy objects in reverse order. Most Policy objects ignore the -response/error but some log the result, retry the operation (depending on the exact -reason the operation failed), or deserialize the response's body. Your own Policy -objects can do whatever they like when processing outgoing requests or incoming responses. - -Note that after an I/O request runs to completion, the Policy objects for that request -are garbage collected. However, Pipeline object (like Factory objects) are goroutine-safe allowing -them to be created once and reused over many I/O operations. This allows for efficient use of -memory and also makes them safely usable by multiple goroutines concurrently. - -Inserting a Method-Specific Factory into the Linked-List of Policy Objects - -While Pipeline and Factory objects can be reused over many different operations, it is -common to have special behavior for a specific operation/method. For example, a method -may need to deserialize the response's body to an instance of a specific data type. -To accommodate this, the Pipeline's Do method takes an additional method-specific -Factory object. The Do method tells this Factory to create a Policy object and -injects this method-specific Policy object into the linked-list of Policy objects. - -When creating a Pipeline object, the slice of Factory objects passed must have 1 -(and only 1) entry marking where the method-specific Factory should be injected. -The Factory marker is obtained by calling the pipeline.MethodFactoryMarker() function: - - func MethodFactoryMarker() pipeline.Factory - -Creating an HTTP Request Object - -The HTTP request object passed to Pipeline's Do method is not Go's http.Request struct. -Instead, it is a pipeline.Request struct which is a simple wrapper around Go's standard -http.Request. You create a pipeline.Request object by calling the pipeline.NewRequest function: - - func NewRequest(method string, url url.URL, options pipeline.RequestOptions) (request pipeline.Request, err error) - -To this function, you must pass a pipeline.RequestOptions that looks like this: - - type RequestOptions struct { - // The readable and seekable stream to be sent to the server as the request's body. - Body io.ReadSeeker - - // The callback method (if not nil) to be invoked to report progress as the stream is uploaded in the HTTP request. - Progress ProgressReceiver - } - -The method and struct ensure that the request's body stream is a read/seekable stream. -A seekable stream is required so that upon retry, the final Policy object can seek -the stream back to the beginning before retrying the network request and re-uploading the -body. In addition, you can associate a ProgressReceiver callback function which will be -invoked periodically to report progress while bytes are being read from the body stream -and sent over the network. - -Processing the HTTP Response - -When an HTTP response comes in from the network, a reference to Go's http.Response struct is -embedded in a struct that implements the pipeline.Response interface: - - type Response interface { - Response() *http.Response - } - -This interface is returned through all the Policy objects. Each Policy object can call the Response -interface's Response method to examine (or mutate) the embedded http.Response object. - -A Policy object can internally define another struct (implementing the pipeline.Response interface) -that embeds an http.Response and adds additional fields and return this structure to other Policy -objects. This allows a Policy object to deserialize the body to some other struct and return the -original http.Response and the additional struct back through the Policy chain. Other Policy objects -can see the Response but cannot see the additional struct with the deserialized body. After all the -Policy objects have returned, the pipeline.Response interface is returned by Pipeline's Do method. -The caller of this method can perform a type assertion attempting to get back to the struct type -really returned by the Policy object. If the type assertion is successful, the caller now has -access to both the http.Response and the deserialized struct object. -*/ -package azcore diff --git a/sdk/azcore/error.go b/sdk/azcore/error.go deleted file mode 100644 index 93d6957e206f..000000000000 --- a/sdk/azcore/error.go +++ /dev/null @@ -1,53 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "errors" - "fmt" -) - -var ( - // ErrNoMorePolicies is returned from Request.Next() if there are no more policies in the pipeline. - ErrNoMorePolicies = errors.New("no more policies") -) - -// TODO: capture frame info for marshal, unmarshal, and parsing errors -// built in frame in xerror? %w -type frameInfo struct { - file string - line int -} - -func (f frameInfo) String() string { - if f.zero() { - return "" - } - return fmt.Sprintf("file: %s, line: %d", f.file, f.line) -} - -func (f frameInfo) zero() bool { - return f.file == "" && f.line == 0 -} - -// RequestError is returned when the service returns an unsuccessful resopnse code (4xx, 5xx). -type RequestError struct { - msg string - resp *Response -} - -func newRequestError(message string, response *Response) error { - return RequestError{msg: message, resp: response} -} - -func (re RequestError) Error() string { - return re.msg -} - -// Response returns the underlying response. -func (re RequestError) Response() *Response { - return re.resp -} diff --git a/sdk/azcore/go.mod b/sdk/azcore/go.mod deleted file mode 100644 index 637312064cb5..000000000000 --- a/sdk/azcore/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/Azure/azure-sdk-for-go/sdk/azcore - -require github.com/Azure/azure-sdk-for-go/sdk/internal v0.1.0 - -go 1.13 diff --git a/sdk/azcore/go.sum b/sdk/azcore/go.sum deleted file mode 100644 index 6306f1577dcd..000000000000 --- a/sdk/azcore/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/Azure/azure-sdk-for-go/sdk/internal v0.1.0 h1:sgOdyT1ZAW3nErwCuvlGrkeP03pTtbRBW5MGCXWGZws= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.1.0/go.mod h1:Q+TCQnSr+clUU0JU+xrHZ3slYCxw17AOFdvWFpQXjAY= diff --git a/sdk/azcore/headers.go b/sdk/azcore/headers.go deleted file mode 100644 index 2b8ea562e2d5..000000000000 --- a/sdk/azcore/headers.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -// Constants ensuring that header names are correctly spelled and consistently cased. -const ( - HeaderAuthorization = "Authorization" - HeaderCacheControl = "Cache-Control" - HeaderContentEncoding = "Content-Encoding" - HeaderContentDisposition = "Content-Disposition" - HeaderContentLanguage = "Content-Language" - HeaderContentLength = "Content-Length" - HeaderContentMD5 = "Content-MD5" - HeaderContentType = "Content-Type" - HeaderDate = "Date" - HeaderIfMatch = "If-Match" - HeaderIfModifiedSince = "If-Modified-Since" - HeaderIfNoneMatch = "If-None-Match" - HeaderIfUnmodifiedSince = "If-Unmodified-Since" - HeaderMetadata = "Metadata" - HeaderRange = "Range" - HeaderRetryAfter = "Retry-After" - HeaderURLEncoded = "application/x-www-form-urlencoded" - HeaderUserAgent = "User-Agent" - HeaderXmsDate = "x-ms-date" - HeaderXmsVersion = "x-ms-version" -) diff --git a/sdk/azcore/log.go b/sdk/azcore/log.go deleted file mode 100644 index 1fe1517b4b65..000000000000 --- a/sdk/azcore/log.go +++ /dev/null @@ -1,93 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -// LogClassification is used to group entries. Each group can be toggled on or off. -type LogClassification string - -const ( - // LogError entries contain detailed error information. - // This includes the error message and stack trace. - LogError LogClassification = "Error" - - // LogRequest entries contain information about HTTP requests. - // This includes information like the URL, query parameters, and headers. - LogRequest LogClassification = "Request" - - // LogResponse entries contain information about HTTP responses. - // This includes information like the HTTP status code, headers, and request URL. - LogResponse LogClassification = "Response" - - // LogRetryPolicy entries contain information specific to the retry policy in use. - LogRetryPolicy LogClassification = "RetryPolicy" - - // LogSlowResponse entries contain information for responses that take longer than the specified threshold. - LogSlowResponse LogClassification = "SlowResponse" -) - -// Listener is the function signature invoked when writing log entries. -// A Listener is required to perform its own synchronization if it's -// expected to be called from multiple Go routines. -type Listener func(LogClassification, string) - -// Logger controls which classifications to log and writing to the underlying log. -type Logger struct { - cls []LogClassification - lst Listener -} - -// SetClassifications is used to control which classifications are written to -// the log. By default all log classifications are written. -func (l *Logger) SetClassifications(cls ...LogClassification) { - l.cls = cls -} - -// SetListener will set the Logger to write to the specified Listener. -func (l *Logger) SetListener(lst Listener) { - l.lst = lst -} - -// Should returns true if the specified log classification should be written to the log. -// By default all log classifications will be logged. Call SetClassification() to limit -// the log classifications for logging. -// If no listener has been set this will return false. -// Calling this method is useful when the message to log is computationally expensive -// and you want to avoid the overhead if its log classification is not enabled. -func (l *Logger) Should(cls LogClassification) bool { - if l.lst == nil { - return false - } - if l.cls == nil || len(l.cls) == 0 { - return true - } - for _, c := range l.cls { - if c == cls { - return true - } - } - return false -} - -// Write invokes the underlying Listener with the specified classification and message. -// If the classification shouldn't be logged or there is no listener then Write does nothing. -func (l *Logger) Write(cls LogClassification, message string) { - if l.lst == nil || !l.Should(cls) { - return - } - l.lst(cls, message) -} - -// for testing purposes -func (l *Logger) resetClassifications() { - l.cls = nil -} - -var log Logger - -// Log returns the process-wide logger. -func Log() *Logger { - return &log -} diff --git a/sdk/azcore/log_test.go b/sdk/azcore/log_test.go deleted file mode 100644 index f9eadbe2c5f8..000000000000 --- a/sdk/azcore/log_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import "testing" - -func TestLoggingDefault(t *testing.T) { - // ensure logging with nil listener doesn't fail - Log().SetListener(nil) - Log().Write(LogError, "this should work just fine") - - log := map[LogClassification]string{} - Log().SetListener(func(cls LogClassification, msg string) { - log[cls] = msg - }) - const req = "this is a request" - Log().Write(LogRequest, req) - const resp = "this is a response" - Log().Write(LogResponse, resp) - if l := len(log); l != 2 { - t.Fatalf("unexpected log entry count: %d", l) - } - if log[LogRequest] != req { - t.Fatalf("unexpected log request: %s", log[LogRequest]) - } - if log[LogResponse] != resp { - t.Fatalf("unexpected log response: %s", log[LogResponse]) - } -} - -func TestLoggingClassification(t *testing.T) { - log := map[LogClassification]string{} - Log().SetListener(func(cls LogClassification, msg string) { - log[cls] = msg - }) - Log().SetClassifications(LogError) - defer Log().resetClassifications() - Log().Write(LogSlowResponse, "this shouldn't be in the log") - if s, ok := log[LogSlowResponse]; ok { - t.Fatalf("unexpected log entry %s", s) - } - const err = "this is an error" - Log().Write(LogError, err) - if log[LogError] != err { - t.Fatalf("unexpected log entry: %s", log[LogError]) - } -} diff --git a/sdk/azcore/policy_anonymous_credential.go b/sdk/azcore/policy_anonymous_credential.go deleted file mode 100644 index 6d5977ccd9f6..000000000000 --- a/sdk/azcore/policy_anonymous_credential.go +++ /dev/null @@ -1,18 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import "context" - -// AnonymousCredential is for use with HTTP(S) requests that read public resource -// or for use with Shared Access Signatures (SAS). -func AnonymousCredential() Credential { - return credentialFunc(func(AuthenticationPolicyOptions) Policy { - return PolicyFunc(func(ctx context.Context, req *Request) (*Response, error) { - return req.Next(ctx) - }) - }) -} diff --git a/sdk/azcore/policy_anonymous_credential_test.go b/sdk/azcore/policy_anonymous_credential_test.go deleted file mode 100644 index d860a843f704..000000000000 --- a/sdk/azcore/policy_anonymous_credential_test.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "net/http" - "reflect" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -func TestAnonymousCredential(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithStatusCode(http.StatusOK)) - pl := NewPipeline(srv, AnonymousCredential().AuthenticationPolicy(AuthenticationPolicyOptions{})) - req := NewRequest(http.MethodGet, srv.URL()) - resp, err := pl.Do(context.Background(), req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !reflect.DeepEqual(req.Header, resp.Request.Header) { - t.Fatal("unexpected modification to request headers") - } -} diff --git a/sdk/azcore/policy_body_download.go b/sdk/azcore/policy_body_download.go deleted file mode 100644 index 22f5a1b9b415..000000000000 --- a/sdk/azcore/policy_body_download.go +++ /dev/null @@ -1,73 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "fmt" - "io" - "io/ioutil" -) - -// newBodyDownloadPolicy creates a policy object that downloads the response's body to a []byte. -func newBodyDownloadPolicy() Policy { - return PolicyFunc(func(ctx context.Context, req *Request) (*Response, error) { - resp, err := req.Next(ctx) - if err != nil { - return resp, err - } - var opValues bodyDownloadPolicyOpValues - if req.OperationValue(&opValues); !opValues.skip && resp.Body != nil { - // Either bodyDownloadPolicyOpValues was not specified (so skip is false) - // or it was specified and skip is false: don't skip downloading the body - b, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - err = fmt.Errorf("body download policy: %w", err) - } - resp.Body = &nopClosingBytesReader{s: b} - } - return resp, err - }) -} - -// bodyDownloadPolicyOpValues is the struct containing the per-operation values -type bodyDownloadPolicyOpValues struct { - skip bool -} - -// nopClosingBytesReader is an io.ReadCloser around a byte slice. -// It also provides direct access to the byte slice. -type nopClosingBytesReader struct { - s []byte - i int64 -} - -// Bytes returns the underlying byte slice. -func (r *nopClosingBytesReader) Bytes() []byte { - return r.s -} - -// Close implements the io.Closer interface. -func (*nopClosingBytesReader) Close() error { - return nil -} - -// Read implements the io.Reader interface. -func (r *nopClosingBytesReader) Read(b []byte) (n int, err error) { - if r.i >= int64(len(r.s)) { - return 0, io.EOF - } - n = copy(b, r.s[r.i:]) - r.i += int64(n) - return -} - -// Set replaces the existing byte slice with the specified byte slice and resets the reader. -func (r *nopClosingBytesReader) Set(b []byte) { - r.s = b - r.i = 0 -} diff --git a/sdk/azcore/policy_body_download_test.go b/sdk/azcore/policy_body_download_test.go deleted file mode 100644 index a46f4dcfd502..000000000000 --- a/sdk/azcore/policy_body_download_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "net/http" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -func TestDownloadBody(t *testing.T) { - const message = "downloaded" - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithBody([]byte(message))) - // download policy is automatically added during pipeline construction - pl := NewPipeline(srv) - resp, err := pl.Do(context.Background(), NewRequest(http.MethodGet, srv.URL())) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if len(resp.payload()) == 0 { - t.Fatal("missing payload") - } - if string(resp.payload()) != message { - t.Fatalf("unexpected response: %s", string(resp.payload())) - } -} - -func TestSkipBodyDownload(t *testing.T) { - const message = "not downloaded" - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithBody([]byte(message))) - // download policy is automatically added during pipeline construction - pl := NewPipeline(srv) - req := NewRequest(http.MethodGet, srv.URL()) - req.SkipBodyDownload() - resp, err := pl.Do(context.Background(), req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if len(resp.payload()) > 0 { - t.Fatalf("unexpected download: %s", string(resp.payload())) - } -} diff --git a/sdk/azcore/policy_logging.go b/sdk/azcore/policy_logging.go deleted file mode 100644 index 5900e64f9c04..000000000000 --- a/sdk/azcore/policy_logging.go +++ /dev/null @@ -1,160 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "bytes" - "context" - "fmt" - "net/http" - "net/url" - "runtime" - "strings" - "time" -) - -// RequestLogOptions configures the retry policy's behavior. -type RequestLogOptions struct { - // LogWarningIfTryOverThreshold logs a warning if a tried operation takes longer than the specified - // duration (-1=no logging; 0=default threshold). - LogWarningIfTryOverThreshold time.Duration -} - -func (o RequestLogOptions) defaults() RequestLogOptions { - if o.LogWarningIfTryOverThreshold == 0 { - // It would be good to relate this to https://azure.microsoft.com/en-us/support/legal/sla/storage/v1_2/ - // But this monitors the time to get the HTTP response; NOT the time to download the response body. - o.LogWarningIfTryOverThreshold = 3 * time.Second // Default to 3 seconds - } - return o -} - -type requestLogPolicy struct { - options RequestLogOptions -} - -// NewRequestLogPolicy creates a RequestLogPolicy object configured using the specified options. -func NewRequestLogPolicy(o RequestLogOptions) Policy { - o = o.defaults() // Force defaults to be calculated - return &requestLogPolicy{options: o} -} - -// logPolicyOpValues is the struct containing the per-operation values -type logPolicyOpValues struct { - try int32 - start time.Time -} - -func (p *requestLogPolicy) Do(ctx context.Context, req *Request) (*Response, error) { - // Get the per-operation values. These are saved in the Message's map so that they persist across each retry calling into this policy object. - var opValues logPolicyOpValues - if req.OperationValue(&opValues); opValues.start.IsZero() { - opValues.start = time.Now() // If this is the 1st try, record this operation's start time - } - opValues.try++ // The first try is #1 (not #0) - req.SetOperationValue(opValues) - - // Log the outgoing request as informational - if Log().Should(LogRequest) { - b := &bytes.Buffer{} - fmt.Fprintf(b, "==> OUTGOING REQUEST (Try=%d)\n", opValues.try) - WriteRequestWithResponse(b, prepareRequestForLogging(req), nil, nil) - Log().Write(LogRequest, b.String()) - } - - // Set the time for this particular retry operation and then Do the operation. - tryStart := time.Now() - response, err := req.Next(ctx) // Make the request - tryEnd := time.Now() - tryDuration := tryEnd.Sub(tryStart) - opDuration := tryEnd.Sub(opValues.start) - - logClass := LogResponse // Default logging information - - // If the response took too long, we'll upgrade to warning. - if p.options.LogWarningIfTryOverThreshold > 0 && tryDuration > p.options.LogWarningIfTryOverThreshold { - // Log a warning if the try duration exceeded the specified threshold - logClass = LogSlowResponse - } - - if err == nil { // We got a response from the service - sc := response.StatusCode - if ((sc >= 400 && sc <= 499) && sc != http.StatusNotFound && sc != http.StatusConflict && sc != http.StatusPreconditionFailed && sc != http.StatusRequestedRangeNotSatisfiable) || (sc >= 500 && sc <= 599) { - logClass = LogError // Promote to Error any 4xx (except those listed is an error) or any 5xx - } else { - // For other status codes, we leave the level as is. - } - } else { // This error did not get an HTTP response from the service; upgrade the severity to Error - logClass = LogError - } - - if Log().Should(logClass) { - // We're going to log this; build the string to log - b := &bytes.Buffer{} - slow := "" - if p.options.LogWarningIfTryOverThreshold > 0 && tryDuration > p.options.LogWarningIfTryOverThreshold { - slow = fmt.Sprintf("[SLOW >%v]", p.options.LogWarningIfTryOverThreshold) - } - fmt.Fprintf(b, "==> REQUEST/RESPONSE (Try=%d/%v%s, OpTime=%v) -- ", opValues.try, tryDuration, slow, opDuration) - if err != nil { // This HTTP request did not get a response from the service - fmt.Fprint(b, "REQUEST ERROR\n") - } else { - if logClass == LogError { - fmt.Fprint(b, "RESPONSE STATUS CODE ERROR\n") - } else { - fmt.Fprint(b, "RESPONSE SUCCESSFULLY RECEIVED\n") - } - } - - WriteRequestWithResponse(b, prepareRequestForLogging(req), response, err) - if logClass == LogError { - b.Write(stack()) // For errors (or lower levels), we append the stack trace (an expensive operation) - } - Log().Write(logClass, b.String()) - } - return response, err -} - -// RedactSigQueryParam redacts the 'sig' query parameter in URL's raw query to protect secret. -func RedactSigQueryParam(rawQuery string) (bool, string) { - rawQuery = strings.ToLower(rawQuery) // lowercase the string so we can look for ?sig= and &sig= - sigFound := strings.Contains(rawQuery, "?sig=") - if !sigFound { - sigFound = strings.Contains(rawQuery, "&sig=") - if !sigFound { - return sigFound, rawQuery // [?|&]sig= not found; return same rawQuery passed in (no memory allocation) - } - } - // [?|&]sig= found, redact its value - values, _ := url.ParseQuery(rawQuery) - for name := range values { - if strings.EqualFold(name, "sig") { - values[name] = []string{"REDACTED"} - } - } - return sigFound, values.Encode() -} - -func prepareRequestForLogging(req *Request) *Request { - request := req - if sigFound, rawQuery := RedactSigQueryParam(request.URL.RawQuery); sigFound { - // Make copy so we don't destroy the query parameters we actually need to send in the request - request = req.copy() - request.URL.RawQuery = rawQuery - } - return request -} - -func stack() []byte { - buf := make([]byte, 1024) - for { - n := runtime.Stack(buf, false) - if n < len(buf) { - return buf[:n] - } - buf = make([]byte, 2*len(buf)) - } -} diff --git a/sdk/azcore/policy_logging_test.go b/sdk/azcore/policy_logging_test.go deleted file mode 100644 index 7afc73d3f59b..000000000000 --- a/sdk/azcore/policy_logging_test.go +++ /dev/null @@ -1,118 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "errors" - "net/http" - "strings" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -func TestPolicyLoggingSuccess(t *testing.T) { - log := map[LogClassification]string{} - Log().SetListener(func(cls LogClassification, s string) { - log[cls] = s - }) - srv, close := mock.NewServer() - defer close() - srv.SetResponse() - pl := NewPipeline(srv, NewRequestLogPolicy(RequestLogOptions{})) - req := NewRequest(http.MethodGet, srv.URL()) - qp := req.URL.Query() - qp.Set("one", "fish") - qp.Set("sig", "redact") - req.URL.RawQuery = qp.Encode() - resp, err := pl.Do(context.Background(), req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - if logReq, ok := log[LogRequest]; ok { - // Request ==> OUTGOING REQUEST (Try=1) - // GET http://127.0.0.1:49475?one=fish&sig=REDACTED - // (no headers) - if !strings.Contains(logReq, "sig=REDACTED") { - t.Fatal("missing redacted sig query param") - } - if !strings.Contains(logReq, "(no headers)") { - t.Fatal("missing (no headers)") - } - } else { - t.Fatal("missing LogRequest") - } - if logResp, ok := log[LogResponse]; ok { - // Response ==> REQUEST/RESPONSE (Try=1/1.0034ms, OpTime=1.0034ms) -- RESPONSE SUCCESSFULLY RECEIVED - // GET http://127.0.0.1:49475?one=fish&sig=REDACTED - // (no headers) - // -------------------------------------------------------------------------------- - // RESPONSE Status: 200 OK - // Content-Length: [0] - // Date: [Fri, 22 Nov 2019 23:48:02 GMT] - if !strings.Contains(logResp, "RESPONSE Status: 200 OK") { - t.Fatal("missing response status") - } - } else { - t.Fatal("missing LogResponse") - } -} - -func TestPolicyLoggingError(t *testing.T) { - log := map[LogClassification]string{} - Log().SetListener(func(cls LogClassification, s string) { - log[cls] = s - }) - srv, close := mock.NewServer() - defer close() - srv.SetError(errors.New("bogus error")) - pl := NewPipeline(srv, NewRequestLogPolicy(RequestLogOptions{})) - req := NewRequest(http.MethodGet, srv.URL()) - req.Header.Add("header", "one") - req.Header.Add("Authorization", "redact") - resp, err := pl.Do(context.Background(), req) - if err == nil { - t.Fatal("unexpected nil error") - } - if resp != nil { - t.Fatal("unexpected respose") - } - if logReq, ok := log[LogRequest]; ok { - // Request ==> OUTGOING REQUEST (Try=1) - // GET http://127.0.0.1:50057 - // Authorization: REDACTED - // Header: [one] - if !strings.Contains(logReq, "Authorization: REDACTED") { - t.Fatal("missing redacted authorization header") - } - } else { - t.Fatal("missing LogRequest") - } - if logError, ok := log[LogError]; ok { - // Error ==> REQUEST/RESPONSE (Try=1/0s, OpTime=0s) -- REQUEST ERROR - // GET http://127.0.0.1:50057 - // Authorization: REDACTED - // Header: [one] - // -------------------------------------------------------------------------------- - // ERROR: - // bogus error - // ...stack track... - if !strings.Contains(logError, "Authorization: REDACTED") { - t.Fatal("missing redacted authorization header") - } - if !strings.Contains(logError, "bogus error") { - t.Fatal("missing error message") - } - } else { - t.Fatal("missing LogError") - } -} - -// TODO: add test for slow response diff --git a/sdk/azcore/policy_retry.go b/sdk/azcore/policy_retry.go deleted file mode 100644 index d632b8191ffa..000000000000 --- a/sdk/azcore/policy_retry.go +++ /dev/null @@ -1,244 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "fmt" - "io" - "math/rand" - "net/http" - "time" -) - -const ( - defaultMaxRetries = 3 -) - -// RetryOptions configures the retry policy's behavior. -type RetryOptions struct { - // MaxRetries specifies the maximum number of attempts a failed operation will be retried - // before producing an error. A value of zero means one try and no retries. - MaxRetries int32 - - // TryTimeout indicates the maximum time allowed for any single try of an HTTP request. - TryTimeout time.Duration - - // RetryDelay specifies the amount of delay to use before retrying an operation. - // The delay increases exponentially with each retry up to a maximum specified by MaxRetryDelay. - // If you specify 0, then you must also specify 0 for MaxRetryDelay. - // If you specify RetryDelay, then you must also specify MaxRetryDelay, and MaxRetryDelay should be - // equal to or greater than RetryDelay. - RetryDelay time.Duration - - // MaxRetryDelay specifies the maximum delay allowed before retrying an operation. - // If you specify 0, then you must also specify 0 for RetryDelay. - MaxRetryDelay time.Duration - - // StatusCodes specifies the HTTP status codes that indicate the operation should be retried. - // If unspecified it will default to the status codes in StatusCodesForRetry. - StatusCodes []int -} - -var ( - // StatusCodesForRetry is the default set of HTTP status code for which the policy will retry. - // Changing its value will affect future created clients that use the default values. - StatusCodesForRetry = []int{ - http.StatusRequestTimeout, // 408 - http.StatusInternalServerError, // 500 - http.StatusBadGateway, // 502 - http.StatusServiceUnavailable, // 503 - http.StatusGatewayTimeout, // 504 - } -) - -// DefaultRetryOptions returns an instance of RetryOptions initialized with default values. -func DefaultRetryOptions() RetryOptions { - return RetryOptions{ - StatusCodes: StatusCodesForRetry, - MaxRetries: defaultMaxRetries, - TryTimeout: 1 * time.Minute, - RetryDelay: 4 * time.Second, - MaxRetryDelay: 120 * time.Second, - } -} - -// used as a context key for adding/retrieving RetryOptions -type ctxWithRetryOptionsKey struct{} - -// WithRetryOptions adds the specified RetryOptions to the parent context. -// Use this to specify custom RetryOptions at the API-call level. -func WithRetryOptions(parent context.Context, options RetryOptions) context.Context { - return context.WithValue(parent, ctxWithRetryOptionsKey{}, options) -} - -func (o RetryOptions) calcDelay(try int32) time.Duration { // try is >=1; never 0 - pow := func(number int64, exponent int32) int64 { // pow is nested helper function - var result int64 = 1 - for n := int32(0); n < exponent; n++ { - result *= number - } - return result - } - - delay := time.Duration(pow(2, try)-1) * o.RetryDelay - - // Introduce some jitter: [0.0, 1.0) / 2 = [0.0, 0.5) + 0.8 = [0.8, 1.3) - delay = time.Duration(delay.Seconds() * (rand.Float64()/2 + 0.8) * float64(time.Second)) // NOTE: We want math/rand; not crypto/rand - if delay > o.MaxRetryDelay { - delay = o.MaxRetryDelay - } - return delay -} - -// NewRetryPolicy creates a policy object configured using the specified options. -// Pass nil to accept the default values; this is the same as passing the result -// from a call to DefaultRetryOptions(). -func NewRetryPolicy(o *RetryOptions) Policy { - if o == nil { - def := DefaultRetryOptions() - o = &def - } - return &retryPolicy{options: *o} -} - -type retryPolicy struct { - options RetryOptions -} - -func (p *retryPolicy) Do(ctx context.Context, req *Request) (resp *Response, err error) { - options := p.options - // check if the retry options have been overridden for this call - if override := ctx.Value(ctxWithRetryOptionsKey{}); override != nil { - options = override.(RetryOptions) - } - // Exponential retry algorithm: ((2 ^ attempt) - 1) * delay * random(0.8, 1.2) - // When to retry: connection failure or temporary/timeout. - if req.Body != nil { - // wrap the body so we control when it's actually closed - rwbody := &retryableRequestBody{body: req.Body.(ReadSeekCloser)} - req.Body = rwbody - req.Request.GetBody = func() (io.ReadCloser, error) { - _, err := rwbody.Seek(0, io.SeekStart) // Seek back to the beginning of the stream - return rwbody, err - } - defer rwbody.realClose() - } - try := int32(1) - shouldLog := Log().Should(LogRetryPolicy) - for { - resp = nil // reset - if shouldLog { - Log().Write(LogRetryPolicy, fmt.Sprintf("\n=====> Try=%d\n", try)) - } - - // For each try, seek to the beginning of the Body stream. We do this even for the 1st try because - // the stream may not be at offset 0 when we first get it and we want the same behavior for the - // 1st try as for additional tries. - err = req.RewindBody() - if err != nil { - return - } - - // Set the per-try time for this particular retry operation and then Do the operation. - tryCtx, tryCancel := context.WithTimeout(ctx, options.TryTimeout) - resp, err = req.Next(tryCtx) // Make the request - if req.bodyDownloadEnabled() || err != nil || resp.Body == nil { - // immediately cancel the per-try timeout if any of the following are true - // 1. auto-download of the response body is enabled - // 2. an error was returned - // 3. there is no response body - // note that we have to check 2 before 3 as if 2 is true then we can't touch resp - tryCancel() - } else { - // wrap the response body in a responseBodyReader. - // closing the responseBodyReader will cancel the timeout. - resp.Body = &responseBodyReader{rb: resp.Body, cancelFunc: tryCancel} - } - if shouldLog { - Log().Write(LogRetryPolicy, fmt.Sprintf("Err=%v, response=%v\n", err, resp)) - } - - if err == nil && !resp.HasStatusCode(options.StatusCodes...) { - // if there is no error and the response code isn't in the list of retry codes then we're done. - return - } else if ctx.Err() != nil { - // don't retry if the parent context has been cancelled or its deadline exceeded - return - } else if retrier, ok := err.(Retrier); ok && retrier.IsNotRetriable() { - // the error says it's not retriable so don't retry - return - } - - // drain before retrying so nothing is leaked - resp.Drain() - - if try == options.MaxRetries+1 { - // max number of tries has been reached, don't sleep again - return - } - - // use the delay from retry-after if available - delay := resp.retryAfter() - if delay <= 0 { - delay = options.calcDelay(try) - } - if shouldLog { - Log().Write(LogRetryPolicy, fmt.Sprintf("Try=%d, Delay=%v\n", try, delay)) - } - select { - case <-time.After(delay): - try++ - case <-ctx.Done(): - err = ctx.Err() - return - } - } -} - -// ********** The following type/methods implement the retryableRequestBody (a ReadSeekCloser) - -// This struct is used when sending a body to the network -type retryableRequestBody struct { - body io.ReadSeeker // Seeking is required to support retries -} - -// Read reads a block of data from an inner stream and reports progress -func (b *retryableRequestBody) Read(p []byte) (n int, err error) { - return b.body.Read(p) -} - -func (b *retryableRequestBody) Seek(offset int64, whence int) (offsetFromStart int64, err error) { - return b.body.Seek(offset, whence) -} - -func (b *retryableRequestBody) Close() error { - // We don't want the underlying transport to close the request body on transient failures so this is a nop. - // The retry policy closes the request body upon success. - return nil -} - -func (b *retryableRequestBody) realClose() error { - if c, ok := b.body.(io.Closer); ok { - return c.Close() - } - return nil -} - -// used when returning the response body to the caller for reading/closing -type responseBodyReader struct { - rb io.ReadCloser - cancelFunc context.CancelFunc -} - -func (r *responseBodyReader) Read(p []byte) (int, error) { - return r.rb.Read(p) -} - -func (r *responseBodyReader) Close() error { - r.cancelFunc() - return r.rb.Close() -} diff --git a/sdk/azcore/policy_retry_test.go b/sdk/azcore/policy_retry_test.go deleted file mode 100644 index 139dfe4598b3..000000000000 --- a/sdk/azcore/policy_retry_test.go +++ /dev/null @@ -1,334 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "errors" - "io" - "net/http" - "strings" - "testing" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -func testRetryOptions() *RetryOptions { - def := DefaultRetryOptions() - def.RetryDelay = 20 * time.Millisecond - return &def -} - -func TestRetryPolicySuccess(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithStatusCode(http.StatusOK)) - pl := NewPipeline(srv, NewRetryPolicy(nil)) - req := NewRequest(http.MethodGet, srv.URL()) - body := newRewindTrackingBody("stuff") - req.SetBody(body) - resp, err := pl.Do(context.Background(), req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - if body.rcount > 0 { - t.Fatalf("unexpected rewind count: %d", body.rcount) - } - if !body.closed { - t.Fatal("request body wasn't closed") - } -} - -func TestRetryPolicyFailOnStatusCode(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithStatusCode(http.StatusInternalServerError)) - pl := NewPipeline(srv, NewRetryPolicy(testRetryOptions())) - req := NewRequest(http.MethodGet, srv.URL()) - body := newRewindTrackingBody("stuff") - req.SetBody(body) - resp, err := pl.Do(context.Background(), req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if resp.StatusCode != http.StatusInternalServerError { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - if r := srv.Requests(); r != defaultMaxRetries+1 { - t.Fatalf("wrong request count, got %d expected %d", r, defaultMaxRetries+1) - } - if body.rcount != defaultMaxRetries { - t.Fatalf("unexpected rewind count: %d", body.rcount) - } - if !body.closed { - t.Fatal("request body wasn't closed") - } -} - -func TestRetryPolicySuccessWithRetry(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.AppendResponse(mock.WithStatusCode(http.StatusRequestTimeout)) - srv.AppendResponse(mock.WithStatusCode(http.StatusInternalServerError)) - srv.AppendResponse() - pl := NewPipeline(srv, NewRetryPolicy(testRetryOptions())) - req := NewRequest(http.MethodGet, srv.URL()) - body := newRewindTrackingBody("stuff") - req.SetBody(body) - resp, err := pl.Do(context.Background(), req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - if r := srv.Requests(); r != 3 { - t.Fatalf("wrong retry count, got %d expected %d", r, 3) - } - if body.rcount != 2 { - t.Fatalf("unexpected rewind count: %d", body.rcount) - } - if !body.closed { - t.Fatal("request body wasn't closed") - } -} - -func TestRetryPolicyFailOnError(t *testing.T) { - srv, close := mock.NewServer() - defer close() - fakeErr := errors.New("bogus error") - srv.SetError(fakeErr) - pl := NewPipeline(srv, NewRetryPolicy(testRetryOptions())) - req := NewRequest(http.MethodPost, srv.URL()) - body := newRewindTrackingBody("stuff") - req.SetBody(body) - resp, err := pl.Do(context.Background(), req) - if !errors.Is(err, fakeErr) { - t.Fatalf("unexpected error: %v", err) - } - if resp != nil { - t.Fatal("unexpected response") - } - if r := srv.Requests(); r != defaultMaxRetries+1 { - t.Fatalf("wrong request count, got %d expected %d", r, defaultMaxRetries+1) - } - if body.rcount != defaultMaxRetries { - t.Fatalf("unexpected rewind count: %d", body.rcount) - } - if !body.closed { - t.Fatal("request body wasn't closed") - } -} - -func TestRetryPolicySuccessWithRetryComplex(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.AppendResponse(mock.WithStatusCode(http.StatusRequestTimeout)) - srv.AppendError(errors.New("bogus error")) - srv.AppendResponse(mock.WithStatusCode(http.StatusInternalServerError)) - srv.AppendResponse(mock.WithStatusCode(http.StatusAccepted)) - pl := NewPipeline(srv, NewRetryPolicy(testRetryOptions())) - req := NewRequest(http.MethodGet, srv.URL()) - body := newRewindTrackingBody("stuff") - req.SetBody(body) - resp, err := pl.Do(context.Background(), req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if resp.StatusCode != http.StatusAccepted { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - if r := srv.Requests(); r != defaultMaxRetries+1 { - t.Fatalf("wrong request count, got %d expected %d", r, defaultMaxRetries+1) - } - if body.rcount != defaultMaxRetries { - t.Fatalf("unexpected rewind count: %d", body.rcount) - } - if !body.closed { - t.Fatal("request body wasn't closed") - } -} - -func TestRetryPolicyRequestTimedOut(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetError(errors.New("bogus error")) - pl := NewPipeline(srv, NewRetryPolicy(nil)) - req := NewRequest(http.MethodPost, srv.URL()) - body := newRewindTrackingBody("stuff") - req.SetBody(body) - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - resp, err := pl.Do(ctx, req) - if !errors.Is(err, context.DeadlineExceeded) { - t.Fatalf("unexpected error: %v", err) - } - if resp != nil { - t.Fatal("unexpected response") - } - if body.rcount > 0 { - t.Fatalf("unexpected rewind count: %d", body.rcount) - } - if !body.closed { - t.Fatal("request body wasn't closed") - } -} - -type fatalError struct { - s string -} - -func (f fatalError) Error() string { - return f.s -} - -func (f fatalError) IsNotRetriable() bool { - return true -} - -func TestRetryPolicyIsNotRetriable(t *testing.T) { - theErr := fatalError{s: "it's dead Jim"} - srv, close := mock.NewServer() - defer close() - srv.AppendResponse(mock.WithStatusCode(http.StatusRequestTimeout)) - srv.AppendError(theErr) - pl := NewPipeline(srv, NewRetryPolicy(testRetryOptions())) - _, err := pl.Do(context.Background(), NewRequest(http.MethodGet, srv.URL())) - if err == nil { - t.Fatal("unexpected nil error") - } - if !errors.Is(err, theErr) { - t.Fatalf("unexpected error type: got %v wanted %v", err, theErr) - } - if r := srv.Requests(); r != 2 { - t.Fatalf("wrong retry count, got %d expected %d", r, 3) - } -} - -func TestWithRetryOptions(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.RepeatResponse(9, mock.WithStatusCode(http.StatusRequestTimeout)) - srv.AppendResponse(mock.WithStatusCode(http.StatusOK)) - defaultOptions := testRetryOptions() - pl := NewPipeline(srv, NewRetryPolicy(defaultOptions)) - customOptions := *defaultOptions - customOptions.MaxRetries = 10 - customOptions.MaxRetryDelay = 200 * time.Millisecond - retryCtx := WithRetryOptions(context.Background(), customOptions) - req := NewRequest(http.MethodGet, srv.URL()) - body := newRewindTrackingBody("stuff") - req.SetBody(body) - resp, err := pl.Do(retryCtx, req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - if body.rcount != int(customOptions.MaxRetries-1) { - t.Fatalf("unexpected rewind count: %d", body.rcount) - } - if !body.closed { - t.Fatal("request body wasn't closed") - } -} - -func TestRetryPolicyFailOnErrorNoDownload(t *testing.T) { - srv, close := mock.NewServer() - defer close() - fakeErr := errors.New("bogus error") - srv.SetError(fakeErr) - pl := NewPipeline(srv, NewRetryPolicy(testRetryOptions())) - req := NewRequest(http.MethodPost, srv.URL()) - req.SkipBodyDownload() - resp, err := pl.Do(context.Background(), req) - if !errors.Is(err, fakeErr) { - t.Fatalf("unexpected error: %v", err) - } - if resp != nil { - t.Fatal("unexpected response") - } - if r := srv.Requests(); r != defaultMaxRetries+1 { - t.Fatalf("wrong request count, got %d expected %d", r, defaultMaxRetries+1) - } -} - -func TestRetryPolicySuccessNoDownload(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte("response body"))) - pl := NewPipeline(srv, NewRetryPolicy(nil)) - req := NewRequest(http.MethodGet, srv.URL()) - req.SkipBodyDownload() - resp, err := pl.Do(context.Background(), req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - resp.Body.Close() -} - -func TestRetryPolicySuccessNoDownloadNoBody(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithStatusCode(http.StatusOK)) - pl := NewPipeline(srv, NewRetryPolicy(nil)) - req := NewRequest(http.MethodGet, srv.URL()) - req.SkipBodyDownload() - resp, err := pl.Do(context.Background(), req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - resp.Body.Close() -} - -// TODO: add test for retry failing to read response body - -// TODO: add test for per-retry timeout failed but e2e succeeded - -func newRewindTrackingBody(s string) *rewindTrackingBody { - // there are two rewinds that happen before rewinding for a retry - // 1. to get the body's size in SetBody() - // 2. the first call to Do() in the retry policy - // to offset this we init rcount with -2 so rcount is only > 0 on a rewind due to a retry - return &rewindTrackingBody{ - body: strings.NewReader(s), - rcount: -2, - } -} - -// used to track the number of times a request body has been rewound -type rewindTrackingBody struct { - body *strings.Reader - closed bool // indicates if the body was closed - rcount int // number of times a rewind happened -} - -func (r *rewindTrackingBody) Close() error { - r.closed = true - return nil -} - -func (r *rewindTrackingBody) Read(b []byte) (int, error) { - return r.body.Read(b) -} - -func (r *rewindTrackingBody) Seek(offset int64, whence int) (int64, error) { - if offset == 0 && whence == io.SeekStart { - r.rcount++ - } - return r.body.Seek(offset, whence) -} diff --git a/sdk/azcore/policy_telemetry.go b/sdk/azcore/policy_telemetry.go deleted file mode 100644 index 10c83fb82f2f..000000000000 --- a/sdk/azcore/policy_telemetry.go +++ /dev/null @@ -1,53 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "bytes" - "context" - "fmt" - "os" - "runtime" -) - -// TelemetryOptions configures the telemetry policy's behavior. -type TelemetryOptions struct { - // Value is a string prepended to each request's User-Agent and sent to the service. - // The service records the user-agent in logs for diagnostics and tracking of client requests. - Value string -} - -type telemetryPolicy struct { - telemetryValue string -} - -// NewTelemetryPolicy creates a telemetry policy object that adds telemetry information to outgoing HTTP requests. -func NewTelemetryPolicy(o TelemetryOptions) Policy { - b := &bytes.Buffer{} - b.WriteString(o.Value) - if b.Len() > 0 { - b.WriteRune(' ') - } - b.WriteString(platformInfo) - return &telemetryPolicy{telemetryValue: b.String()} -} - -func (p telemetryPolicy) Do(ctx context.Context, req *Request) (*Response, error) { - req.Request.Header.Set(HeaderUserAgent, p.telemetryValue) - return req.Next(ctx) -} - -// NOTE: the ONLY function that should write to this variable is this func -var platformInfo = func() string { - operatingSystem := runtime.GOOS // Default OS string - switch operatingSystem { - case "windows": - operatingSystem = os.Getenv("OS") // Get more specific OS information - case "linux": // accept default OS info - case "freebsd": // accept default OS info - } - return fmt.Sprintf("(%s; %s)", runtime.Version(), operatingSystem) -}() diff --git a/sdk/azcore/policy_telemetry_test.go b/sdk/azcore/policy_telemetry_test.go deleted file mode 100644 index 2c5dfe3a7f71..000000000000 --- a/sdk/azcore/policy_telemetry_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "fmt" - "net/http" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -func TestPolicyTelemetryDefault(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse() - pl := NewPipeline(srv, NewTelemetryPolicy(TelemetryOptions{})) - resp, err := pl.Do(context.Background(), NewRequest(http.MethodGet, srv.URL())) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if v := resp.Request.Header.Get(HeaderUserAgent); v != platformInfo { - t.Fatalf("unexpected user agent value: %s", v) - } -} - -func TestPolicyTelemetryWithCustomInfo(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse() - const testValue = "azcore_test" - pl := NewPipeline(srv, NewTelemetryPolicy(TelemetryOptions{Value: testValue})) - resp, err := pl.Do(context.Background(), NewRequest(http.MethodGet, srv.URL())) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if v := resp.Request.Header.Get(HeaderUserAgent); v != fmt.Sprintf("%s %s", testValue, platformInfo) { - t.Fatalf("unexpected user agent value: %s", v) - } -} diff --git a/sdk/azcore/policy_unique_request_id.go b/sdk/azcore/policy_unique_request_id.go deleted file mode 100644 index c5f9a84f2867..000000000000 --- a/sdk/azcore/policy_unique_request_id.go +++ /dev/null @@ -1,26 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - - "github.com/Azure/azure-sdk-for-go/sdk/internal/uuid" -) - -const xMsClientRequestID = "x-ms-client-request-id" - -// NewUniqueRequestIDPolicy creates a policy object that sets the request's x-ms-client-request-id header if it doesn't already exist. -func NewUniqueRequestIDPolicy() Policy { - return PolicyFunc(func(ctx context.Context, req *Request) (*Response, error) { - id := req.Request.Header.Get(xMsClientRequestID) - if id == "" { - // Add a unique request ID if the caller didn't specify one already - req.Request.Header.Set(xMsClientRequestID, uuid.New().String()) - } - return req.Next(ctx) - }) -} diff --git a/sdk/azcore/policy_unique_request_id_test.go b/sdk/azcore/policy_unique_request_id_test.go deleted file mode 100644 index 66c2141063c1..000000000000 --- a/sdk/azcore/policy_unique_request_id_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "net/http" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -func TestUniqueRequestIDPolicy(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse() - pl := NewPipeline(srv, NewUniqueRequestIDPolicy()) - resp, err := pl.Do(context.Background(), NewRequest(http.MethodGet, srv.URL())) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if resp.Request.Header.Get(xMsClientRequestID) == "" { - t.Fatal("missing request ID header") - } -} - -func TestUniqueRequestIDPolicyUserDefined(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse() - pl := NewPipeline(srv, NewUniqueRequestIDPolicy()) - req := NewRequest(http.MethodGet, srv.URL()) - const customID = "my-custom-id" - req.Header.Set(xMsClientRequestID, customID) - resp, err := pl.Do(context.Background(), req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if v := resp.Request.Header.Get(xMsClientRequestID); v != customID { - t.Fatalf("unexpected request ID value: %s", v) - } -} diff --git a/sdk/azcore/progress.go b/sdk/azcore/progress.go deleted file mode 100644 index 8364581b657e..000000000000 --- a/sdk/azcore/progress.go +++ /dev/null @@ -1,78 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import "io" - -// ********** The following is common between the request body AND the response body. - -// ProgressReceiver defines the signature of a callback function invoked as progress is reported. -type ProgressReceiver func(bytesTransferred int64) - -// ********** The following are specific to the request body (a ReadSeekCloser) - -// This struct is used when sending a body to the network -type requestBodyProgress struct { - requestBody ReadSeekCloser // Seeking is required to support retries - pr ProgressReceiver -} - -// NewRequestBodyProgress adds progress reporting to an HTTP request's body stream. -func NewRequestBodyProgress(requestBody ReadSeekCloser, pr ProgressReceiver) ReadSeekCloser { - return &requestBodyProgress{requestBody: requestBody, pr: pr} -} - -// Read reads a block of data from an inner stream and reports progress -func (rbp *requestBodyProgress) Read(p []byte) (n int, err error) { - n, err = rbp.requestBody.Read(p) - if err != nil { - return - } - // Invokes the user's callback method to report progress - position, err := rbp.requestBody.Seek(0, io.SeekCurrent) - if err != nil { - return - } - rbp.pr(position) - return -} - -func (rbp *requestBodyProgress) Seek(offset int64, whence int) (offsetFromStart int64, err error) { - return rbp.requestBody.Seek(offset, whence) -} - -// requestBodyProgress supports Close but the underlying stream may not; if it does, Close will close it. -func (rbp *requestBodyProgress) Close() error { - return rbp.requestBody.Close() -} - -// ********** The following are specific to the response body (a ReadCloser) - -// This struct is used when sending a body to the network -type responseBodyProgress struct { - responseBody io.ReadCloser - pr ProgressReceiver - offset int64 -} - -// NewResponseBodyProgress adds progress reporting to an HTTP response's body stream. -func NewResponseBodyProgress(responseBody io.ReadCloser, pr ProgressReceiver) io.ReadCloser { - return &responseBodyProgress{responseBody: responseBody, pr: pr, offset: 0} -} - -// Read reads a block of data from an inner stream and reports progress -func (rbp *responseBodyProgress) Read(p []byte) (n int, err error) { - n, err = rbp.responseBody.Read(p) - rbp.offset += int64(n) - - // Invokes the user's callback method to report progress - rbp.pr(rbp.offset) - return -} - -func (rbp *responseBodyProgress) Close() error { - return rbp.responseBody.Close() -} diff --git a/sdk/azcore/progress_test.go b/sdk/azcore/progress_test.go deleted file mode 100644 index 61c3830175f0..000000000000 --- a/sdk/azcore/progress_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "bytes" - "context" - "io/ioutil" - "net/http" - "reflect" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -func TestProgressReporting(t *testing.T) { - const contentSize = 4096 - content := make([]byte, contentSize) - for i := 0; i < contentSize; i++ { - content[i] = byte(i % 255) - } - body := bytes.NewReader(content) - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithBody(content)) - pl := NewPipeline(srv, NewTelemetryPolicy(TelemetryOptions{})) - req := NewRequest(http.MethodGet, srv.URL()) - req.SkipBodyDownload() - var bytesSent int64 - reqRpt := NewRequestBodyProgress(NopCloser(body), func(bytesTransferred int64) { - bytesSent = bytesTransferred - }) - req.SetBody(reqRpt) - resp, err := pl.Do(context.Background(), req) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - var bytesReceived int64 - respRpt := NewResponseBodyProgress(resp.Body, func(bytesTransferred int64) { - bytesReceived = bytesTransferred - }) - defer respRpt.Close() - b, err := ioutil.ReadAll(respRpt) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if bytesSent != contentSize { - t.Fatalf("wrong bytes sent: %d", bytesSent) - } - if bytesReceived != contentSize { - t.Fatalf("wrong bytes received: %d", bytesReceived) - } - if !reflect.DeepEqual(content, b) { - t.Fatal("request and response bodies don't match") - } -} diff --git a/sdk/azcore/request.go b/sdk/azcore/request.go deleted file mode 100644 index 55f2e1e90874..000000000000 --- a/sdk/azcore/request.go +++ /dev/null @@ -1,218 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "bytes" - "context" - "encoding/base64" - "encoding/json" - "encoding/xml" - "fmt" - "io" - "net/http" - "net/url" - "reflect" - "strings" -) - -const ( - contentTypeAppJSON = "application/json" - contentTypeAppXML = "application/xml" -) - -// Base64Encoding is usesd to specify which base-64 encoder/decoder to use when -// encoding/decoding a slice of bytes to/from a string. -type Base64Encoding int - -const ( - Base64StdFormat Base64Encoding = 0 - Base64URLFormat Base64Encoding = 1 -) - -// Request is an abstraction over the creation of an HTTP request as it passes through the pipeline. -type Request struct { - *http.Request - policies []Policy - values opValues -} - -type opValues map[reflect.Type]interface{} - -// Set adds/changes a value -func (ov opValues) set(value interface{}) { - ov[reflect.TypeOf(value)] = value -} - -// Get looks for a value set by SetValue first -func (ov opValues) get(value interface{}) bool { - v, ok := ov[reflect.ValueOf(value).Elem().Type()] - if ok { - reflect.ValueOf(value).Elem().Set(reflect.ValueOf(v)) - } - return ok -} - -// NewRequest creates a new Request with the specified input. -func NewRequest(httpMethod string, endpoint url.URL) *Request { - // removeEmptyPort strips the empty port in ":port" to "" - // as mandated by RFC 3986 Section 6.2.3. - // adapted from removeEmptyPort() in net/http.go - if strings.LastIndex(endpoint.Host, ":") > strings.LastIndex(endpoint.Host, "]") { - endpoint.Host = strings.TrimSuffix(endpoint.Host, ":") - } - return &Request{ - Request: &http.Request{ - Method: httpMethod, - URL: &endpoint, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Header: http.Header{}, - Host: endpoint.Host, - }, - } -} - -// Next calls the next policy in the pipeline. -// If there are no more policies, nil and ErrNoMorePolicies are returned. -// This method is intended to be called from pipeline policies. -// To send a request through a pipeline call Pipeline.Do(). -func (req *Request) Next(ctx context.Context) (*Response, error) { - if len(req.policies) == 0 { - return nil, ErrNoMorePolicies - } - nextPolicy := req.policies[0] - nextReq := *req - nextReq.policies = nextReq.policies[1:] - return nextPolicy.Do(ctx, &nextReq) -} - -// MarshalAsByteArray will base-64 encode the byte slice v, then calls SetBody. -// The encoded value is treated as a JSON string. -func (req *Request) MarshalAsByteArray(v []byte, format Base64Encoding) error { - var encode string - switch format { - case Base64StdFormat: - encode = base64.StdEncoding.EncodeToString(v) - case Base64URLFormat: - // use raw encoding so that '=' characters are omitted as they have special meaning in URLs - encode = base64.RawURLEncoding.EncodeToString(v) - default: - return fmt.Errorf("unrecognized byte array format: %d", format) - } - // send as a JSON string - encode = fmt.Sprintf("\"%s\"", encode) - req.Header.Set(HeaderContentType, contentTypeAppJSON) - return req.SetBody(NopCloser(strings.NewReader(encode))) -} - -// MarshalAsJSON calls json.Marshal() to get the JSON encoding of v then calls SetBody. -// If json.Marshal fails a MarshalError is returned. Any error from SetBody is returned. -func (req *Request) MarshalAsJSON(v interface{}) error { - b, err := json.Marshal(v) - if err != nil { - return fmt.Errorf("error marshalling type %s: %w", reflect.TypeOf(v).Name(), err) - } - req.Header.Set(HeaderContentType, contentTypeAppJSON) - return req.SetBody(NopCloser(bytes.NewReader(b))) -} - -// MarshalAsXML calls xml.Marshal() to get the XML encoding of v then calls SetBody. -// If xml.Marshal fails a MarshalError is returned. Any error from SetBody is returned. -func (req *Request) MarshalAsXML(v interface{}) error { - b, err := xml.Marshal(v) - if err != nil { - return fmt.Errorf("error marshalling type %s: %w", reflect.TypeOf(v).Name(), err) - } - req.Header.Set(HeaderContentType, contentTypeAppXML) - return req.SetBody(NopCloser(bytes.NewReader(b))) -} - -// SetOperationValue adds/changes a mutable key/value associated with a single operation. -func (req *Request) SetOperationValue(value interface{}) { - if req.values == nil { - req.values = opValues{} - } - req.values.set(value) -} - -// OperationValue looks for a value set by SetOperationValue(). -func (req *Request) OperationValue(value interface{}) bool { - if req.values == nil { - return false - } - return req.values.get(value) -} - -// SetBody sets the specified ReadSeekCloser as the HTTP request body. -func (req *Request) SetBody(body ReadSeekCloser) error { - // Set the body and content length. - size, err := body.Seek(0, io.SeekEnd) // Seek to the end to get the stream's size - if err != nil { - return err - } - if size == 0 { - body.Close() - return nil - } - _, err = body.Seek(0, io.SeekStart) - if err != nil { - return err - } - req.Request.Body = body - req.Request.ContentLength = size - return nil -} - -// SkipBodyDownload will disable automatic downloading of the response body. -func (req *Request) SkipBodyDownload() { - req.SetOperationValue(bodyDownloadPolicyOpValues{skip: true}) -} - -// returns true if auto-body download policy is enabled -func (req *Request) bodyDownloadEnabled() bool { - var opValues bodyDownloadPolicyOpValues - req.OperationValue(&opValues) - return !opValues.skip -} - -// RewindBody seeks the request's Body stream back to the beginning so it can be resent when retrying an operation. -func (req *Request) RewindBody() error { - if req.Body != nil { - // Reset the stream back to the beginning - _, err := req.Body.(io.Seeker).Seek(0, io.SeekStart) - return err - } - return nil -} - -// Close closes the request body. -func (req *Request) Close() error { - if req.Body == nil { - return nil - } - return req.Body.Close() -} - -func (req *Request) copy() *Request { - clonedURL := *req.URL - // Copy the values and immutable references - return &Request{ - Request: &http.Request{ - Method: req.Method, - URL: &clonedURL, - Proto: req.Proto, - ProtoMajor: req.ProtoMajor, - ProtoMinor: req.ProtoMinor, - Header: req.Header.Clone(), - Host: req.URL.Host, - Body: req.Body, // shallow copy - ContentLength: req.ContentLength, - GetBody: req.GetBody, - }, - } -} diff --git a/sdk/azcore/request_test.go b/sdk/azcore/request_test.go deleted file mode 100644 index 3154541c6b1a..000000000000 --- a/sdk/azcore/request_test.go +++ /dev/null @@ -1,140 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "errors" - "io/ioutil" - "net/http" - "net/url" - "testing" -) - -type testJSON struct { - SomeInt int - SomeString string -} - -type testXML struct { - SomeInt int - SomeString string -} - -func TestRequestMarshalXML(t *testing.T) { - u, err := url.Parse("https://contoso.com") - if err != nil { - panic(err) - } - req := NewRequest(http.MethodPost, *u) - err = req.MarshalAsXML(testXML{SomeInt: 1, SomeString: "s"}) - if err != nil { - t.Fatalf("marshal failure: %v", err) - } - if ct := req.Header.Get(HeaderContentType); ct != contentTypeAppXML { - t.Fatalf("unexpected content type, got %s wanted %s", ct, contentTypeAppXML) - } - if req.Body == nil { - t.Fatal("unexpected nil request body") - } - if req.ContentLength == 0 { - t.Fatal("unexpected zero content length") - } -} - -func TestRequestEmptyPipeline(t *testing.T) { - u, err := url.Parse("https://contoso.com") - if err != nil { - panic(err) - } - req := NewRequest(http.MethodPost, *u) - resp, err := req.Next(context.Background()) - if resp != nil { - t.Fatal("expected nil response") - } - if !errors.Is(err, ErrNoMorePolicies) { - t.Fatalf("expected ErrNoMorePolicies, got %v", err) - } -} - -func TestRequestMarshalJSON(t *testing.T) { - u, err := url.Parse("https://contoso.com") - if err != nil { - panic(err) - } - req := NewRequest(http.MethodPost, *u) - err = req.MarshalAsJSON(testJSON{SomeInt: 1, SomeString: "s"}) - if err != nil { - t.Fatalf("marshal failure: %v", err) - } - if ct := req.Header.Get(HeaderContentType); ct != contentTypeAppJSON { - t.Fatalf("unexpected content type, got %s wanted %s", ct, contentTypeAppJSON) - } - if req.Body == nil { - t.Fatal("unexpected nil request body") - } - if req.ContentLength == 0 { - t.Fatal("unexpected zero content length") - } -} - -func TestRequestMarshalAsByteArrayURLFormat(t *testing.T) { - u, err := url.Parse("https://contoso.com") - if err != nil { - panic(err) - } - req := NewRequest(http.MethodPost, *u) - const payload = "a string that gets encoded with base64url" - err = req.MarshalAsByteArray([]byte(payload), Base64URLFormat) - if err != nil { - t.Fatalf("marshal failure: %v", err) - } - if ct := req.Header.Get(HeaderContentType); ct != contentTypeAppJSON { - t.Fatalf("unexpected content type, got %s wanted %s", ct, contentTypeAppJSON) - } - if req.Body == nil { - t.Fatal("unexpected nil request body") - } - if req.ContentLength == 0 { - t.Fatal("unexpected zero content length") - } - b, err := ioutil.ReadAll(req.Body) - if err != nil { - t.Fatal(err) - } - if string(b) != `"YSBzdHJpbmcgdGhhdCBnZXRzIGVuY29kZWQgd2l0aCBiYXNlNjR1cmw"` { - t.Fatalf("bad body, got %s", string(b)) - } -} - -func TestRequestMarshalAsByteArrayStdFormat(t *testing.T) { - u, err := url.Parse("https://contoso.com") - if err != nil { - panic(err) - } - req := NewRequest(http.MethodPost, *u) - const payload = "a string that gets encoded with base64url" - err = req.MarshalAsByteArray([]byte(payload), Base64StdFormat) - if err != nil { - t.Fatalf("marshal failure: %v", err) - } - if ct := req.Header.Get(HeaderContentType); ct != contentTypeAppJSON { - t.Fatalf("unexpected content type, got %s wanted %s", ct, contentTypeAppJSON) - } - if req.Body == nil { - t.Fatal("unexpected nil request body") - } - if req.ContentLength == 0 { - t.Fatal("unexpected zero content length") - } - b, err := ioutil.ReadAll(req.Body) - if err != nil { - t.Fatal(err) - } - if string(b) != `"YSBzdHJpbmcgdGhhdCBnZXRzIGVuY29kZWQgd2l0aCBiYXNlNjR1cmw="` { - t.Fatalf("bad body, got %s", string(b)) - } -} diff --git a/sdk/azcore/response.go b/sdk/azcore/response.go deleted file mode 100644 index 3f018acd82ce..000000000000 --- a/sdk/azcore/response.go +++ /dev/null @@ -1,195 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "encoding/xml" - "fmt" - "io" - "io/ioutil" - "net/http" - "reflect" - "sort" - "strconv" - "strings" - "time" -) - -// Response represents the response from an HTTP request. -type Response struct { - *http.Response -} - -func (r *Response) payload() []byte { - if r.Body == nil { - return nil - } - // r.Body won't be a nopClosingBytesReader if downloading was skipped - if buf, ok := r.Body.(*nopClosingBytesReader); ok { - return buf.Bytes() - } - return nil -} - -// HasStatusCode returns true if the Response's status code is one of the specified values. -func (r *Response) HasStatusCode(statusCodes ...int) bool { - if r == nil { - return false - } - for _, sc := range statusCodes { - if r.StatusCode == sc { - return true - } - } - return false -} - -// UnmarshalAsByteArray will base-64 decode the received payload and place the result into the value pointed to by v. -func (r *Response) UnmarshalAsByteArray(v **[]byte, format Base64Encoding) error { - if len(r.payload()) == 0 { - return nil - } - payload := string(r.payload()) - if payload[0] == '"' { - // remove surrounding quotes - payload = payload[1 : len(payload)-1] - } - switch format { - case Base64StdFormat: - decoded, err := base64.StdEncoding.DecodeString(payload) - if err == nil { - *v = &decoded - return nil - } - return err - case Base64URLFormat: - // use raw encoding as URL format should not contain any '=' characters - decoded, err := base64.RawURLEncoding.DecodeString(payload) - if err == nil { - *v = &decoded - return nil - } - return err - default: - return fmt.Errorf("unrecognized byte array format: %d", format) - } -} - -// UnmarshalAsJSON calls json.Unmarshal() to unmarshal the received payload into the value pointed to by v. -// If no payload was received a RequestError is returned. If json.Unmarshal fails a UnmarshalError is returned. -func (r *Response) UnmarshalAsJSON(v interface{}) error { - // TODO: verify early exit is correct - if len(r.payload()) == 0 { - return nil - } - r.removeBOM() - err := json.Unmarshal(r.payload(), v) - if err != nil { - err = fmt.Errorf("unmarshalling type %s: %w", reflect.TypeOf(v).Elem().Name(), err) - } - return err -} - -// UnmarshalAsXML calls xml.Unmarshal() to unmarshal the received payload into the value pointed to by v. -// If no payload was received a RequestError is returned. If xml.Unmarshal fails a UnmarshalError is returned. -func (r *Response) UnmarshalAsXML(v interface{}) error { - // TODO: verify early exit is correct - if len(r.payload()) == 0 { - return nil - } - r.removeBOM() - err := xml.Unmarshal(r.payload(), v) - if err != nil { - err = fmt.Errorf("unmarshalling type %s: %w", reflect.TypeOf(v).Elem().Name(), err) - } - return err -} - -// Drain reads the response body to completion then closes it. The bytes read are discarded. -func (r *Response) Drain() { - if r != nil && r.Body != nil { - io.Copy(ioutil.Discard, r.Body) - r.Body.Close() - } -} - -// removeBOM removes any byte-order mark prefix from the payload if present. -func (r *Response) removeBOM() { - // UTF8 - trimmed := bytes.TrimPrefix(r.payload(), []byte("\xef\xbb\xbf")) - if len(trimmed) < len(r.payload()) { - r.Body.(*nopClosingBytesReader).Set(trimmed) - } -} - -// helper to reduce nil Response checks -func (r *Response) retryAfter() time.Duration { - if r == nil { - return 0 - } - return RetryAfter(r.Response) -} - -// RetryAfter returns non-zero if the response contains a Retry-After header value. -func RetryAfter(resp *http.Response) time.Duration { - if resp == nil { - return 0 - } - ra := resp.Header.Get(HeaderRetryAfter) - if ra == "" { - return 0 - } - // retry-after values are expressed in either number of - // seconds or an HTTP-date indicating when to try again - if retryAfter, _ := strconv.Atoi(ra); retryAfter > 0 { - return time.Duration(retryAfter) * time.Second - } else if t, err := time.Parse(time.RFC1123, ra); err == nil { - return t.Sub(time.Now()) - } - return 0 -} - -// WriteRequestWithResponse appends a formatted HTTP request into a Buffer. If request and/or err are -// not nil, then these are also written into the Buffer. -func WriteRequestWithResponse(b *bytes.Buffer, request *Request, response *Response, err error) { - // Write the request into the buffer. - fmt.Fprint(b, " "+request.Method+" "+request.URL.String()+"\n") - writeHeader(b, request.Header) - if response != nil { - fmt.Fprintln(b, " --------------------------------------------------------------------------------") - fmt.Fprint(b, " RESPONSE Status: "+response.Status+"\n") - writeHeader(b, response.Header) - } - if err != nil { - fmt.Fprintln(b, " --------------------------------------------------------------------------------") - fmt.Fprint(b, " ERROR:\n"+err.Error()+"\n") - } -} - -// formatHeaders appends an HTTP request's or response's header into a Buffer. -func writeHeader(b *bytes.Buffer, header http.Header) { - if len(header) == 0 { - b.WriteString(" (no headers)\n") - return - } - keys := make([]string, 0, len(header)) - // Alphabetize the headers - for k := range header { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - // Redact the value of any Authorization header to prevent security information from persisting in logs - value := interface{}("REDACTED") - if !strings.EqualFold(k, "Authorization") { - value = header[k] - } - fmt.Fprintf(b, " %s: %+v\n", k, value) - } -} diff --git a/sdk/azcore/response_test.go b/sdk/azcore/response_test.go deleted file mode 100644 index f4b1324dc971..000000000000 --- a/sdk/azcore/response_test.go +++ /dev/null @@ -1,176 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "net/http" - "testing" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -func TestResponseUnmarshalXML(t *testing.T) { - srv, close := mock.NewServer() - defer close() - // include UTF8 BOM - srv.SetResponse(mock.WithBody([]byte("\xef\xbb\xbf1s"))) - pl := NewPipeline(srv, NewTelemetryPolicy(TelemetryOptions{})) - resp, err := pl.Do(context.Background(), NewRequest(http.MethodGet, srv.URL())) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !resp.HasStatusCode(http.StatusOK) { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - var tx testXML - if err := resp.UnmarshalAsXML(&tx); err != nil { - t.Fatalf("unexpected error unmarshalling: %v", err) - } - if tx.SomeInt != 1 || tx.SomeString != "s" { - t.Fatal("unexpected value") - } -} - -func TestResponseFailureStatusCode(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithStatusCode(http.StatusForbidden)) - pl := NewPipeline(srv, NewTelemetryPolicy(TelemetryOptions{})) - resp, err := pl.Do(context.Background(), NewRequest(http.MethodGet, srv.URL())) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if resp.HasStatusCode(http.StatusOK) { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } -} - -func TestResponseUnmarshalJSON(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithBody([]byte(`{ "someInt": 1, "someString": "s" }`))) - pl := NewPipeline(srv, NewTelemetryPolicy(TelemetryOptions{})) - resp, err := pl.Do(context.Background(), NewRequest(http.MethodGet, srv.URL())) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !resp.HasStatusCode(http.StatusOK) { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - var tx testJSON - if err := resp.UnmarshalAsJSON(&tx); err != nil { - t.Fatalf("unexpected error unmarshalling: %v", err) - } - if tx.SomeInt != 1 || tx.SomeString != "s" { - t.Fatal("unexpected value") - } -} - -func TestResponseUnmarshalJSONNoBody(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithBody([]byte{})) - pl := NewPipeline(srv, NewTelemetryPolicy(TelemetryOptions{})) - resp, err := pl.Do(context.Background(), NewRequest(http.MethodGet, srv.URL())) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !resp.HasStatusCode(http.StatusOK) { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - if err := resp.UnmarshalAsJSON(nil); err != nil { - t.Fatalf("unexpected error unmarshalling: %v", err) - } -} - -func TestResponseUnmarshalXMLNoBody(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithBody([]byte{})) - pl := NewPipeline(srv, NewTelemetryPolicy(TelemetryOptions{})) - resp, err := pl.Do(context.Background(), NewRequest(http.MethodGet, srv.URL())) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !resp.HasStatusCode(http.StatusOK) { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - if err := resp.UnmarshalAsXML(nil); err != nil { - t.Fatalf("unexpected error unmarshalling: %v", err) - } -} - -func TestRetryAfter(t *testing.T) { - raw := &http.Response{ - Header: http.Header{}, - } - resp := Response{raw} - if d := resp.retryAfter(); d > 0 { - t.Fatalf("unexpected retry-after value %d", d) - } - raw.Header.Set(HeaderRetryAfter, "300") - d := resp.retryAfter() - if d <= 0 { - t.Fatal("expected retry-after value from seconds") - } - if d != 300*time.Second { - t.Fatalf("expected 300 seconds, got %d", d/time.Second) - } - atDate := time.Now().Add(600 * time.Second) - raw.Header.Set(HeaderRetryAfter, atDate.Format(time.RFC1123)) - d = resp.retryAfter() - if d <= 0 { - t.Fatal("expected retry-after value from date") - } - // d will not be exactly 600 seconds but it will be close - if d/time.Second != 599 { - t.Fatalf("expected ~600 seconds, got %d", d/time.Second) - } -} - -func TestResponseUnmarshalAsByteArrayURLFormat(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithBody([]byte(`"YSBzdHJpbmcgdGhhdCBnZXRzIGVuY29kZWQgd2l0aCBiYXNlNjR1cmw"`))) - pl := NewPipeline(srv, NewTelemetryPolicy(TelemetryOptions{})) - resp, err := pl.Do(context.Background(), NewRequest(http.MethodGet, srv.URL())) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !resp.HasStatusCode(http.StatusOK) { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - var ba *[]byte - if err := resp.UnmarshalAsByteArray(&ba, Base64URLFormat); err != nil { - t.Fatalf("unexpected error unmarshalling: %v", err) - } - if string(*ba) != "a string that gets encoded with base64url" { - t.Fatalf("bad payload, got %s", string(*ba)) - } -} - -func TestResponseUnmarshalAsByteArrayStdFormat(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.SetResponse(mock.WithBody([]byte(`"YSBzdHJpbmcgdGhhdCBnZXRzIGVuY29kZWQgd2l0aCBiYXNlNjR1cmw="`))) - pl := NewPipeline(srv, NewTelemetryPolicy(TelemetryOptions{})) - resp, err := pl.Do(context.Background(), NewRequest(http.MethodGet, srv.URL())) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !resp.HasStatusCode(http.StatusOK) { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - var ba *[]byte - if err := resp.UnmarshalAsByteArray(&ba, Base64StdFormat); err != nil { - t.Fatalf("unexpected error unmarshalling: %v", err) - } - if string(*ba) != "a string that gets encoded with base64url" { - t.Fatalf("bad payload, got %s", string(*ba)) - } -} diff --git a/sdk/azcore/transport_default_http_client.go b/sdk/azcore/transport_default_http_client.go deleted file mode 100644 index 4a91d0609cd0..000000000000 --- a/sdk/azcore/transport_default_http_client.go +++ /dev/null @@ -1,40 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -import ( - "context" - "crypto/tls" - "net/http" -) - -var defaultHTTPClient *http.Client - -func init() { - defaultTransport := http.DefaultTransport.(*http.Transport) - transport := &http.Transport{ - Proxy: defaultTransport.Proxy, - DialContext: defaultTransport.DialContext, - MaxIdleConns: defaultTransport.MaxIdleConns, - IdleConnTimeout: defaultTransport.IdleConnTimeout, - TLSHandshakeTimeout: defaultTransport.TLSHandshakeTimeout, - ExpectContinueTimeout: defaultTransport.ExpectContinueTimeout, - TLSClientConfig: &tls.Config{ - MinVersion: tls.VersionTLS12, - }, - } - // TODO: in track 1 we created a cookiejar, do we need one here? make it an option? user-specified HTTP client policy? - defaultHTTPClient = &http.Client{ - Transport: transport, - } -} - -// DefaultHTTPClientTransport ... -func DefaultHTTPClientTransport() Transport { - return TransportFunc(func(ctx context.Context, req *http.Request) (*http.Response, error) { - return defaultHTTPClient.Do(req.WithContext(ctx)) - }) -} diff --git a/sdk/azcore/version.go b/sdk/azcore/version.go deleted file mode 100644 index 8d4493c648c8..000000000000 --- a/sdk/azcore/version.go +++ /dev/null @@ -1,14 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package azcore - -const ( - // UserAgent is the string to be used in the user agent string when making requests. - UserAgent = "azcore/" + Version - - // Version is the semantic version (see http://semver.org) of the pipeline package. - Version = "0.1.0" -) diff --git a/sdk/azidentity/go.mod b/sdk/azidentity/go.mod index e4f14583feef..ec84ba11f6ce 100644 --- a/sdk/azidentity/go.mod +++ b/sdk/azidentity/go.mod @@ -3,6 +3,6 @@ module github.com/Azure/azure-sdk-for-go/sdk/azidentity go 1.13 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v0.8.0 - github.com/Azure/azure-sdk-for-go/sdk/internal v0.1.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v0.9.0 + github.com/Azure/azure-sdk-for-go/sdk/internal v0.2.0 ) diff --git a/sdk/azidentity/go.sum b/sdk/azidentity/go.sum index 1ae648017870..00dfdfebed95 100644 --- a/sdk/azidentity/go.sum +++ b/sdk/azidentity/go.sum @@ -1,4 +1,4 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.8.0 h1:ogZVkNX3wS6ekWS3hD9sQCghWIGfw8CQ9JKv0rQQlVw= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.8.0/go.mod h1:UKq2za3CMGx75vfPM9tPSuTBNODR4hX1qAeb+GRoDkc= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.1.0 h1:sgOdyT1ZAW3nErwCuvlGrkeP03pTtbRBW5MGCXWGZws= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.1.0/go.mod h1:Q+TCQnSr+clUU0JU+xrHZ3slYCxw17AOFdvWFpQXjAY= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.9.0 h1:VdhfbVpQ3dkhXYOx/Wj1+utikcZkZSZSmpqmXWwaNJY= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.9.0/go.mod h1:hL9TGc07RkJVzDIBxsYXC/r0M+YiRkvl4z1elXCD+8s= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.2.0 h1:cLpVMIkXC/umSP9DMz9I6FttDWJAsmvhpaB6MlkagGY= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.2.0/go.mod h1:Q+TCQnSr+clUU0JU+xrHZ3slYCxw17AOFdvWFpQXjAY= diff --git a/sdk/internal/atomic/atomic.go b/sdk/internal/atomic/atomic.go deleted file mode 100644 index 781dbeca94af..000000000000 --- a/sdk/internal/atomic/atomic.go +++ /dev/null @@ -1,78 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package atomic - -import ( - "sync/atomic" - "time" -) - -// Int64 is an atomic wrapper around an int64. -type Int64 int64 - -// NewInt64 creates a new Int64. -func NewInt64(i int64) Int64 { - return Int64(i) -} - -// CAS is an atomic compare-and-swap. -func (i *Int64) CAS(old, new int64) bool { - return atomic.CompareAndSwapInt64((*int64)(i), old, new) -} - -// Load atomically loads the value. -func (i *Int64) Load() int64 { - return atomic.LoadInt64((*int64)(i)) -} - -// Store atomically stores the value. -func (i *Int64) Store(v int64) { - atomic.StoreInt64((*int64)(i), v) -} - -// String is an atomic wrapper around a string. -type String struct { - v atomic.Value -} - -// NewString creats a new String. -func NewString(s string) *String { - ss := String{} - ss.v.Store(s) - return &ss -} - -// Load atomically loads the string. -func (s *String) Load() string { - return s.v.Load().(string) -} - -// Store atomically stores the string. -func (s *String) Store(v string) { - s.v.Store(v) -} - -// Time is an atomic wrapper around a time.Time. -type Time struct { - v atomic.Value -} - -// NewTime creates a new Time. -func NewTime(t time.Time) *Time { - tt := Time{} - tt.v.Store(t) - return &tt -} - -// Load atomically loads the time.Time. -func (t *Time) Load() time.Time { - return t.v.Load().(time.Time) -} - -// Store atomically stores the time.Time. -func (t *Time) Store(v time.Time) { - t.v.Store(v) -} diff --git a/sdk/internal/go.mod b/sdk/internal/go.mod deleted file mode 100644 index 1bb024518b8c..000000000000 --- a/sdk/internal/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/Azure/azure-sdk-for-go/sdk/internal - -go 1.13 diff --git a/sdk/internal/mock/mock.go b/sdk/internal/mock/mock.go deleted file mode 100644 index d409860389ca..000000000000 --- a/sdk/internal/mock/mock.go +++ /dev/null @@ -1,207 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package mock - -import ( - "context" - "net/http" - "net/http/httptest" - "net/url" -) - -// Server is a wrapper around an httptest.Server. -// The serving of requests is not safe for concurrent use -// which is ok for right now as each test creates is own -// server and doesn't create additional go routines. -type Server struct { - srv *httptest.Server - - // static is the static response, if this is not nil it's always returned. - static *mockResponse - - // resp is the queue of responses. each response is taken from the front. - resp []mockResponse - - // count tracks the number of requests that have been made. - count int -} - -// NewServer creates a new Server object. -// The returned close func must be called when the server is no longer needed. -func NewServer() (*Server, func()) { - s := Server{} - s.srv = httptest.NewServer(http.HandlerFunc(s.serveHTTP)) - return &s, func() { s.srv.Close() } -} - -// NewTLSServer creates a new Server object. -// The returned close func must be called when the server is no longer needed. -func NewTLSServer() (*Server, func()) { - s := Server{} - s.srv = httptest.NewTLSServer(http.HandlerFunc(s.serveHTTP)) - return &s, func() { s.srv.Close() } -} - -// returns true if the next response is an error response -func (s *Server) isErrorResp() bool { - if s.static == nil && len(s.resp) == 0 { - panic("no more responses") - } - // always favor static response - if s.static != nil && s.static.err != nil { - return true - } - if len(s.resp) == 0 { - return false - } - return s.resp[0].err != nil -} - -// returns the static response or the next response in the queue -func (s *Server) getResponse() mockResponse { - if s.static == nil && len(s.resp) == 0 { - panic("no more responses") - } - // always favor static response - if s.static != nil { - return *s.static - } - // pop off first response and return it - resp := s.resp[0] - s.resp = s.resp[1:] - return resp -} - -// URL returns the endpoint of the test server in URL format. -func (s *Server) URL() url.URL { - u, err := url.Parse(s.srv.URL) - if err != nil { - panic(err) - } - return *u -} - -// Do implements the azcore.Transport interface on Server. -// Calling this when the response queue is empty and no static -// response has been set will cause a panic. -func (s *Server) Do(ctx context.Context, req *http.Request) (*http.Response, error) { - s.count++ - // error responses are returned here - if s.isErrorResp() { - resp := s.getResponse() - return nil, resp.err - } - return s.srv.Client().Do(req.WithContext(ctx)) -} - -func (s *Server) serveHTTP(w http.ResponseWriter, req *http.Request) { - s.getResponse().write(w) -} - -// Requests returns the number of times an HTTP request was made. -func (s *Server) Requests() int { - return s.count -} - -// AppendError appends the error to the end of the response queue. -func (s *Server) AppendError(err error) { - s.resp = append(s.resp, mockResponse{err: err}) -} - -// RepeatError appends the error n number of times to the end of the response queue. -func (s *Server) RepeatError(n int, err error) { - for i := 0; i < n; i++ { - s.AppendError(err) - } -} - -// SetError indicates the same error should always be returned. -// Any responses set via other methods will be ignored. -func (s *Server) SetError(err error) { - s.static = &mockResponse{err: err} -} - -// AppendResponse appends the response to the end of the response queue. -// If no options are provided the default response is an http.StatusOK. -func (s *Server) AppendResponse(opts ...ResponseOption) { - mr := mockResponse{code: http.StatusOK} - for _, o := range opts { - o.apply(&mr) - } - s.resp = append(s.resp, mr) -} - -// RepeatResponse appends the response n number of times to the end of the response queue. -// If no options are provided the default response is an http.StatusOK. -func (s *Server) RepeatResponse(n int, opts ...ResponseOption) { - for i := 0; i < n; i++ { - s.AppendResponse(opts...) - } -} - -// SetResponse indicates the same response should always be returned. -// Any responses set via other methods will be ignored. -// If no options are provided the default response is an http.StatusOK. -func (s *Server) SetResponse(opts ...ResponseOption) { - mr := mockResponse{code: http.StatusOK} - for _, o := range opts { - o.apply(&mr) - } - s.static = &mr -} - -// ResponseOption is an abstraction for configuring a mock HTTP response. -type ResponseOption interface { - apply(mr *mockResponse) -} - -type fnRespOpt func(*mockResponse) - -func (fn fnRespOpt) apply(mr *mockResponse) { - fn(mr) -} - -type mockResponse struct { - code int - body []byte - headers http.Header - err error -} - -func (mr mockResponse) write(w http.ResponseWriter) { - w.WriteHeader(mr.code) - if mr.body != nil { - w.Write(mr.body) - } - if len(mr.headers) > 0 { - for k, v := range mr.headers { - for _, vv := range v { - w.Header().Add(k, vv) - } - } - } -} - -// WithStatusCode sets the HTTP response's status code to the specified value. -func WithStatusCode(c int) ResponseOption { - return fnRespOpt(func(mr *mockResponse) { - mr.code = c - }) -} - -// WithBody sets the HTTP response's body to the specified value. -func WithBody(b []byte) ResponseOption { - return fnRespOpt(func(mr *mockResponse) { - mr.body = b - }) -} - -// WithHeader adds the specified header and value to the HTTP response. -func WithHeader(k, v string) ResponseOption { - return fnRespOpt(func(mr *mockResponse) { - mr.headers.Add(k, v) - }) -} diff --git a/sdk/internal/uuid/uuid.go b/sdk/internal/uuid/uuid.go deleted file mode 100644 index 4b288d81fecd..000000000000 --- a/sdk/internal/uuid/uuid.go +++ /dev/null @@ -1,89 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package uuid - -import ( - "fmt" - "math/rand" - "strconv" - "time" -) - -// The UUID reserved variants. -const ( - reservedNCS byte = 0x80 - reservedRFC4122 byte = 0x40 - reservedMicrosoft byte = 0x20 - reservedFuture byte = 0x00 -) - -func init() { - rand.Seed(time.Now().Unix()) -} - -// A UUID representation compliant with specification in RFC 4122 document. -type UUID [16]byte - -// New returns a new uuid using RFC 4122 algorithm. -func New() UUID { - u := UUID{} - // Set all bits to randomly (or pseudo-randomly) chosen values. - // math/rand.Read() is no-fail so we omit any error checking. - // NOTE: this takes a process-wide lock - rand.Read(u[:]) - u[8] = (u[8] | reservedRFC4122) & 0x7F // u.setVariant(ReservedRFC4122) - - var version byte = 4 - u[6] = (u[6] & 0xF) | (version << 4) // u.setVersion(4) - return u -} - -// String returns an unparsed version of the generated UUID sequence. -func (u UUID) String() string { - return fmt.Sprintf("%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:]) -} - -// Parse parses a string formatted as "003020100-0504-0706-0809-0a0b0c0d0e0f" -// or "{03020100-0504-0706-0809-0a0b0c0d0e0f}" into a UUID. -func Parse(uuidStr string) UUID { - char := func(hexString string) byte { - i, _ := strconv.ParseUint(hexString, 16, 8) - return byte(i) - } - if uuidStr[0] == '{' { - uuidStr = uuidStr[1:] // Skip over the '{' - } - // 03020100 - 05 04 - 07 06 - 08 09 - 0a 0b 0c 0d 0e 0f - // 1 11 1 11 11 1 12 22 2 22 22 22 33 33 33 - // 01234567 8 90 12 3 45 67 8 90 12 3 45 67 89 01 23 45 - uuidVal := UUID{ - char(uuidStr[0:2]), - char(uuidStr[2:4]), - char(uuidStr[4:6]), - char(uuidStr[6:8]), - - char(uuidStr[9:11]), - char(uuidStr[11:13]), - - char(uuidStr[14:16]), - char(uuidStr[16:18]), - - char(uuidStr[19:21]), - char(uuidStr[21:23]), - - char(uuidStr[24:26]), - char(uuidStr[26:28]), - char(uuidStr[28:30]), - char(uuidStr[30:32]), - char(uuidStr[32:34]), - char(uuidStr[34:36]), - } - return uuidVal -} - -func (u UUID) bytes() []byte { - return u[:] -} diff --git a/sdk/to/go.mod b/sdk/to/go.mod deleted file mode 100644 index 1e4a74c22d67..000000000000 --- a/sdk/to/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/Azure/azure-sdk-for-go/sdk/to - -go 1.13 diff --git a/sdk/to/to.go b/sdk/to/to.go deleted file mode 100644 index 2ebb4b23c202..000000000000 --- a/sdk/to/to.go +++ /dev/null @@ -1,36 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package to - -// BoolPtr returns a pointer to the provided bool. -func BoolPtr(b bool) *bool { - return &b -} - -// Float32Ptr returns a pointer to the provided float32. -func Float32Ptr(i float32) *float32 { - return &i -} - -// Float64Ptr returns a pointer to the provided float64. -func Float64Ptr(i float64) *float64 { - return &i -} - -// Int32Ptr returns a pointer to the provided int32. -func Int32Ptr(i int32) *int32 { - return &i -} - -// Int64Ptr returns a pointer to the provided int64. -func Int64Ptr(i int64) *int64 { - return &i -} - -// StringPtr returns a pointer to the provided string. -func StringPtr(s string) *string { - return &s -}