From 9d67227fb42de3b689381e2988258296d450644a Mon Sep 17 00:00:00 2001 From: Pengfei Ni Date: Tue, 31 Dec 2019 11:02:47 +0800 Subject: [PATCH] Addd Azure ARM client with backoff retries --- .../azure/clients/armclient/BUILD | 48 ++ .../clients/armclient/azure_armclient.go | 543 ++++++++++++++++++ .../clients/armclient/azure_armclient_test.go | 253 ++++++++ .../azure/clients/armclient/doc.go | 20 + .../azure/clients/armclient/interface.go | 84 +++ .../clients/armclient/mockarmclient/BUILD | 32 ++ .../clients/armclient/mockarmclient/doc.go | 20 + .../armclient/mockarmclient/interface.go | 329 +++++++++++ 8 files changed, 1329 insertions(+) create mode 100644 staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/BUILD create mode 100644 staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/azure_armclient.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/azure_armclient_test.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/doc.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/interface.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/BUILD create mode 100644 staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/doc.go create mode 100644 staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/interface.go diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/BUILD b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/BUILD new file mode 100644 index 00000000000..6fed3c8cf7f --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/BUILD @@ -0,0 +1,48 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "azure_armclient.go", + "doc.go", + "interface.go", + ], + importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/armclient", + importpath = "k8s.io/legacy-cloud-providers/azure/clients/armclient", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/client-go/pkg/version:go_default_library", + "//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library", + "//vendor/github.com/Azure/go-autorest/autorest:go_default_library", + "//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library", + "//vendor/k8s.io/klog:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["azure_armclient_test.go"], + embed = [":go_default_library"], + deps = [ + "//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library", + "//vendor/github.com/Azure/go-autorest/autorest:go_default_library", + "//vendor/github.com/stretchr/testify/assert:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/azure_armclient.go b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/azure_armclient.go new file mode 100644 index 00000000000..0a422946c9b --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/azure_armclient.go @@ -0,0 +1,543 @@ +// +build !providerless + +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package armclient + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "time" + "unicode" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + + "k8s.io/client-go/pkg/version" + "k8s.io/klog" + "k8s.io/legacy-cloud-providers/azure/retry" +) + +var _ Interface = &Client{} + +// Client implements ARM client Interface. +type Client struct { + client autorest.Client + backoff *retry.Backoff + + baseURI string + apiVersion string + clientRegion string +} + +// New creates a ARM client +func New(authorizer autorest.Authorizer, baseURI, userAgent, apiVersion, clientRegion string, clientBackoff *retry.Backoff) *Client { + restClient := autorest.NewClientWithUserAgent(userAgent) + restClient.PollingDelay = 5 * time.Second + restClient.RetryAttempts = 3 + restClient.RetryDuration = time.Second * 1 + restClient.Authorizer = authorizer + + if userAgent == "" { + restClient.UserAgent = GetUserAgent(restClient) + } + + backoff := clientBackoff + if backoff == nil { + // 1 steps means no retry. + backoff = &retry.Backoff{ + Steps: 1, + } + } + + return &Client{ + client: restClient, + baseURI: baseURI, + backoff: backoff, + apiVersion: apiVersion, + clientRegion: NormalizeAzureRegion(clientRegion), + } +} + +// GetUserAgent gets the autorest client with a user agent that +// includes "kubernetes" and the full kubernetes git version string +// example: +// Azure-SDK-for-Go/7.0.1 arm-network/2016-09-01; kubernetes-cloudprovider/v1.17.0; +func GetUserAgent(client autorest.Client) string { + k8sVersion := version.Get().GitVersion + return fmt.Sprintf("%s; kubernetes-cloudprovider/%s", client.UserAgent, k8sVersion) +} + +// NormalizeAzureRegion returns a normalized Azure region with white spaces removed and converted to lower case +func NormalizeAzureRegion(name string) string { + region := "" + for _, runeValue := range name { + if !unicode.IsSpace(runeValue) { + region += string(runeValue) + } + } + return strings.ToLower(region) +} + +// sendRequest sends a http request to ARM service. +// Although Azure SDK supports retries per https://github.com/azure/azure-sdk-for-go#request-retry-policy, we +// disable it since we want to fully control the retry policies. +func (c *Client) sendRequest(ctx context.Context, request *http.Request) (*http.Response, *retry.Error) { + sendBackoff := *c.backoff + response, err := autorest.SendWithSender( + c.client, + request, + retry.DoExponentialBackoffRetry(&sendBackoff), + ) + return response, retry.GetError(response, err) +} + +// Send sends a http request to ARM service with possible retry to regional ARM endpoint. +func (c *Client) Send(ctx context.Context, request *http.Request) (*http.Response, *retry.Error) { + response, rerr := c.sendRequest(ctx, request) + if rerr != nil { + return response, rerr + } + + if response.StatusCode != http.StatusNotFound || c.clientRegion == "" { + return response, rerr + } + + bodyBytes, _ := ioutil.ReadAll(response.Body) + defer func() { + response.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) + }() + + bodyString := string(bodyBytes) + klog.V(5).Infof("Send.sendRequest original error message: %s", bodyString) + + // Hack: retry the regional ARM endpoint in case of ARM traffic split and arm resource group replication is too slow + var body map[string]interface{} + if e := json.Unmarshal(bodyBytes, &body); e != nil { + klog.V(5).Infof("Send.sendRequest: error in parsing response body string: %s, Skip retrying regional host", e) + return response, rerr + } + + if err, ok := body["error"].(map[string]interface{}); !ok || + err["code"] == nil || + !strings.EqualFold(err["code"].(string), "ResourceGroupNotFound") { + klog.V(5).Infof("Send.sendRequest: response body does not contain ResourceGroupNotFound error code. Skip retrying regional host") + return response, rerr + } + + currentHost := request.URL.Host + if request.Host != "" { + currentHost = request.Host + } + + if strings.HasPrefix(strings.ToLower(currentHost), c.clientRegion) { + klog.V(5).Infof("Send.sendRequest: current host %s is regional host. Skip retrying regional host.", currentHost) + return response, rerr + } + + request.Host = fmt.Sprintf("%s.%s", c.clientRegion, strings.ToLower(currentHost)) + klog.V(5).Infof("Send.sendRegionalRequest on ResourceGroupNotFound error. Retrying regional host: %s", request.Host) + regionalResponse, regionalError := c.sendRequest(ctx, request) + + // only use the result if the regional request actually goes through and returns 2xx status code, for two reasons: + // 1. the retry on regional ARM host approach is a hack. + // 2. the concatted regional uri could be wrong as the rule is not officially declared by ARM. + if regionalResponse == nil || regionalResponse.StatusCode > 299 { + regionalErrStr := "" + if regionalError != nil { + regionalErrStr = regionalError.Error().Error() + } + + klog.V(5).Infof("Send.sendRegionalRequest failed to get response from regional host, error: '%s'. Ignoring the result.", regionalErrStr) + return response, rerr + } + + return regionalResponse, regionalError +} + +// PreparePutRequest prepares put request +func (c *Client) PreparePutRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) { + decorators = append( + []autorest.PrepareDecorator{ + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsPut(), + autorest.WithBaseURL(c.baseURI)}, + decorators...) + return c.prepareRequest(ctx, decorators...) +} + +// PreparePostRequest prepares post request +func (c *Client) PreparePostRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) { + decorators = append( + []autorest.PrepareDecorator{ + autorest.AsContentType("application/json; charset=utf-8"), + autorest.AsPost(), + autorest.WithBaseURL(c.baseURI)}, + decorators...) + return c.prepareRequest(ctx, decorators...) +} + +// PrepareGetRequest prepares get request +func (c *Client) PrepareGetRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) { + decorators = append( + []autorest.PrepareDecorator{ + autorest.AsGet(), + autorest.WithBaseURL(c.baseURI)}, + decorators...) + return c.prepareRequest(ctx, decorators...) +} + +// PrepareDeleteRequest preparse delete request +func (c *Client) PrepareDeleteRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) { + decorators = append( + []autorest.PrepareDecorator{ + autorest.AsDelete(), + autorest.WithBaseURL(c.baseURI)}, + decorators...) + return c.prepareRequest(ctx, decorators...) +} + +// PrepareHeadRequest prepares head request +func (c *Client) PrepareHeadRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) { + decorators = append( + []autorest.PrepareDecorator{ + autorest.AsHead(), + autorest.WithBaseURL(c.baseURI)}, + decorators...) + return c.prepareRequest(ctx, decorators...) +} + +// WaitForAsyncOperationCompletion waits for an operation completion +func (c *Client) WaitForAsyncOperationCompletion(ctx context.Context, future *azure.Future, asyncOperationName string) error { + err := future.WaitForCompletionRef(ctx, c.client) + if err != nil { + klog.V(5).Infof("Received error in WaitForCompletionRef: '%v'", err) + return err + } + + var done bool + done, err = future.DoneWithContext(ctx, c.client) + if err != nil { + klog.V(5).Infof("Received error in DoneWithContext: '%v'", err) + return autorest.NewErrorWithError(err, asyncOperationName, "Result", future.Response(), "Polling failure") + } + if !done { + return azure.NewAsyncOpIncompleteError(asyncOperationName) + } + + return nil +} + +// WaitForAsyncOperationResult waits for an operation result. +func (c *Client) WaitForAsyncOperationResult(ctx context.Context, future *azure.Future, asyncOperationName string) (*http.Response, error) { + err := c.WaitForAsyncOperationCompletion(ctx, future, asyncOperationName) + if err != nil { + klog.V(5).Infof("Received error in WaitForAsyncOperationCompletion: '%v'", err) + return nil, err + } + + sendBackoff := *c.backoff + sender := autorest.DecorateSender( + c.client, + retry.DoExponentialBackoffRetry(&sendBackoff), + ) + return future.GetResult(sender) +} + +// SendAsync send a request and return a future object representing the async result as well as the origin http response +func (c *Client) SendAsync(ctx context.Context, request *http.Request) (*azure.Future, *http.Response, *retry.Error) { + asyncResponse, rerr := c.Send(ctx, request) + if rerr != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "sendAsync.send", request.URL.String(), rerr.Error()) + return nil, nil, rerr + } + + future, err := azure.NewFutureFromResponse(asyncResponse) + if err != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "sendAsync.responed", request.URL.String(), err) + return nil, asyncResponse, retry.GetError(asyncResponse, err) + } + + return &future, asyncResponse, nil +} + +// GetResource get a resource by resource ID +func (c *Client) GetResource(ctx context.Context, resourceID, expand string) (*http.Response, *retry.Error) { + decorators := []autorest.PrepareDecorator{ + autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}), + } + if expand != "" { + queryParameters := map[string]interface{}{ + "$expand": autorest.Encode("query", expand), + } + decorators = append(decorators, autorest.WithQueryParameters(queryParameters)) + } + request, err := c.PrepareGetRequest(ctx, decorators...) + if err != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "get.prepare", resourceID, err) + return nil, retry.NewError(false, err) + } + + return c.Send(ctx, request) +} + +// PutResource puts a resource by resource ID +func (c *Client) PutResource(ctx context.Context, resourceID string, parameters interface{}) (*http.Response, *retry.Error) { + decorators := []autorest.PrepareDecorator{ + autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}), + autorest.WithJSON(parameters), + } + + request, err := c.PreparePutRequest(ctx, decorators...) + if err != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "put.prepare", resourceID, err) + return nil, retry.NewError(false, err) + } + + future, resp, clientErr := c.SendAsync(ctx, request) + defer c.CloseResponse(ctx, resp) + if clientErr != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "put.send", resourceID, clientErr.Error()) + return nil, clientErr + } + + response, err := c.WaitForAsyncOperationResult(ctx, future, "armclient.PutResource") + if err != nil { + if response != nil { + klog.V(5).Infof("Received error in WaitForAsyncOperationResult: '%s', response code %d", err.Error(), response.StatusCode) + } else { + klog.V(5).Infof("Received error in WaitForAsyncOperationResult: '%s', no response", err.Error()) + } + + retriableErr := retry.GetError(response, err) + if !retriableErr.Retriable && + strings.Contains(strings.ToUpper(err.Error()), strings.ToUpper("InternalServerError")) { + klog.V(5).Infof("Received InternalServerError in WaitForAsyncOperationResult: '%s', setting error retriable", err.Error()) + retriableErr.Retriable = true + } + return nil, retriableErr + } + + return response, nil +} + +// PutResourceAsync puts a resource by resource ID in async mode +func (c *Client) PutResourceAsync(ctx context.Context, resourceID string, parameters interface{}) (*azure.Future, *retry.Error) { + decorators := []autorest.PrepareDecorator{ + autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}), + autorest.WithJSON(parameters), + } + + request, err := c.PreparePutRequest(ctx, decorators...) + if err != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "put.prepare", resourceID, err) + return nil, retry.NewError(false, err) + } + + future, resp, rErr := c.SendAsync(ctx, request) + defer c.CloseResponse(ctx, resp) + if rErr != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "put.send", resourceID, err) + return nil, rErr + } + + return future, nil +} + +// PostResource posts a resource by resource ID +func (c *Client) PostResource(ctx context.Context, resourceID, action string, parameters interface{}) (*http.Response, *retry.Error) { + pathParameters := map[string]interface{}{ + "resourceID": resourceID, + "action": action, + } + + decorators := []autorest.PrepareDecorator{ + autorest.WithPathParameters("{resourceID}/{action}", pathParameters), + autorest.WithJSON(parameters), + } + request, err := c.PreparePostRequest(ctx, decorators...) + if err != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "post.prepare", resourceID, err) + return nil, retry.NewError(false, err) + } + + return c.sendRequest(ctx, request) +} + +// DeleteResource deletes a resource by resource ID +func (c *Client) DeleteResource(ctx context.Context, resourceID, ifMatch string) *retry.Error { + future, clientErr := c.DeleteResourceAsync(ctx, resourceID, ifMatch) + if clientErr != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "delete.request", resourceID, clientErr.Error()) + return clientErr + } + + if future == nil { + return nil + } + + if err := c.WaitForAsyncOperationCompletion(ctx, future, "armclient.DeleteResource"); err != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "delete.wait", resourceID, clientErr.Error()) + return retry.NewError(true, err) + } + + return nil +} + +// HeadResource heads a resource by resource ID +func (c *Client) HeadResource(ctx context.Context, resourceID string) (*http.Response, *retry.Error) { + decorators := []autorest.PrepareDecorator{ + autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}), + } + request, err := c.PrepareHeadRequest(ctx, decorators...) + if err != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "head.prepare", resourceID, err) + return nil, retry.NewError(false, err) + } + + return c.sendRequest(ctx, request) +} + +// DeleteResourceAsync delete a resource by resource ID and returns a future representing the async result +func (c *Client) DeleteResourceAsync(ctx context.Context, resourceID, ifMatch string) (*azure.Future, *retry.Error) { + decorators := []autorest.PrepareDecorator{ + autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}), + } + if len(ifMatch) > 0 { + decorators = append(decorators, autorest.WithHeader("If-Match", autorest.String(ifMatch))) + } + + deleteRequest, err := c.PrepareDeleteRequest(ctx, decorators...) + if err != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deleteAsync.prepare", resourceID, err) + return nil, retry.NewError(false, err) + } + + resp, rerr := c.sendRequest(ctx, deleteRequest) + defer c.CloseResponse(ctx, resp) + if rerr != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deleteAsync.send", resourceID, rerr.Error()) + return nil, rerr + } + + err = autorest.Respond( + resp, + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusAccepted, http.StatusNoContent, http.StatusNotFound)) + if err != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deleteAsync.respond", resourceID, err) + return nil, retry.GetError(resp, err) + } + + if resp.StatusCode == http.StatusNotFound { + return nil, nil + } + + future, err := azure.NewFutureFromResponse(resp) + if err != nil { + klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deleteAsync.future", resourceID, err) + return nil, retry.GetError(resp, err) + } + + return &future, nil +} + +// CloseResponse closes a response +func (c *Client) CloseResponse(ctx context.Context, response *http.Response) { + if response != nil && response.Body != nil { + if err := response.Body.Close(); err != nil { + klog.Errorf("Error closing the response body: %v", err) + } + } +} + +func (c *Client) prepareRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) { + decorators = append( + decorators, + withAPIVersion(c.apiVersion)) + preparer := autorest.CreatePreparer(decorators...) + return preparer.Prepare((&http.Request{}).WithContext(ctx)) +} + +func withAPIVersion(apiVersion string) autorest.PrepareDecorator { + const apiVersionKey = "api-version" + return func(p autorest.Preparer) autorest.Preparer { + return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) { + r, err := p.Prepare(r) + if err == nil { + if r.URL == nil { + return r, fmt.Errorf("Error in withAPIVersion: Invoked with a nil URL") + } + + v := r.URL.Query() + if len(v.Get(apiVersionKey)) > 0 { + return r, nil + } + + v.Add(apiVersionKey, apiVersion) + r.URL.RawQuery = v.Encode() + } + return r, err + }) + } +} + +// GetResourceID gets Azure resource ID +func GetResourceID(subscriptionID, resourceGroupName, resourceType, resourceName string) string { + return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s", + autorest.Encode("path", subscriptionID), + autorest.Encode("path", resourceGroupName), + resourceType, + autorest.Encode("path", resourceName)) +} + +// GetChildResourceID gets Azure child resource ID +func GetChildResourceID(subscriptionID, resourceGroupName, resourceType, resourceName, childResourceType, childResourceName string) string { + return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s/%s/%s", + autorest.Encode("path", subscriptionID), + autorest.Encode("path", resourceGroupName), + resourceType, + autorest.Encode("path", resourceName), + childResourceType, + autorest.Encode("path", childResourceName)) +} + +// GetChildResourcesListID gets Azure child resources list ID +func GetChildResourcesListID(subscriptionID, resourceGroupName, resourceType, resourceName, childResourceType string) string { + return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s/%s", + autorest.Encode("path", subscriptionID), + autorest.Encode("path", resourceGroupName), + resourceType, + autorest.Encode("path", resourceName), + childResourceType) +} + +// GetProviderResourceID gets Azure RP resource ID +func GetProviderResourceID(subscriptionID, providerNamespace string) string { + return fmt.Sprintf("/subscriptions/%s/providers/%s", + autorest.Encode("path", subscriptionID), + providerNamespace) +} + +// GetProviderResourcesListID gets Azure RP resources list ID +func GetProviderResourcesListID(subscriptionID string) string { + return fmt.Sprintf("/subscriptions/%s/providers", autorest.Encode("path", subscriptionID)) +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/azure_armclient_test.go b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/azure_armclient_test.go new file mode 100644 index 00000000000..3554e3eeaf4 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/azure_armclient_test.go @@ -0,0 +1,253 @@ +// +build !providerless + +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package armclient + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/Azure/go-autorest/autorest" + "github.com/stretchr/testify/assert" + "k8s.io/legacy-cloud-providers/azure/retry" +) + +func TestSend(t *testing.T) { + count := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if count <= 1 { + http.Error(w, "failed", http.StatusInternalServerError) + count++ + } + })) + + backoff := &retry.Backoff{Steps: 3} + armClient := New(nil, server.URL, "test", "2019-01-01", "eastus", backoff) + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", "testgroup"), + "subscriptionId": autorest.Encode("path", "testid"), + "resourceName": autorest.Encode("path", "testname"), + } + + decorators := []autorest.PrepareDecorator{ + autorest.WithPathParameters( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/vNets/{resourceName}", pathParameters), + } + + ctx := context.Background() + request, err := armClient.PrepareGetRequest(ctx, decorators...) + assert.NoError(t, err) + + response, rerr := armClient.Send(ctx, request) + assert.Nil(t, rerr) + assert.Equal(t, 2, count) + assert.Equal(t, http.StatusOK, response.StatusCode) +} + +func TestSendFailure(t *testing.T) { + count := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "failed", http.StatusInternalServerError) + count++ + })) + + backoff := &retry.Backoff{Steps: 3} + armClient := New(nil, server.URL, "test", "2019-01-01", "eastus", backoff) + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", "testgroup"), + "subscriptionId": autorest.Encode("path", "testid"), + "resourceName": autorest.Encode("path", "testname"), + } + + decorators := []autorest.PrepareDecorator{ + autorest.WithPathParameters( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/vNets/{resourceName}", pathParameters), + } + + ctx := context.Background() + request, err := armClient.PrepareGetRequest(ctx, decorators...) + assert.NoError(t, err) + + response, rerr := armClient.Send(ctx, request) + assert.NotNil(t, rerr) + assert.Equal(t, 3, count) + assert.Equal(t, http.StatusInternalServerError, response.StatusCode) +} + +func TestSendThrottled(t *testing.T) { + count := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(retry.RetryAfterHeaderKey, "30") + http.Error(w, "failed", http.StatusTooManyRequests) + count++ + })) + + backoff := &retry.Backoff{Steps: 3} + armClient := New(nil, server.URL, "test", "2019-01-01", "eastus", backoff) + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", "testgroup"), + "subscriptionId": autorest.Encode("path", "testid"), + "resourceName": autorest.Encode("path", "testname"), + } + decorators := []autorest.PrepareDecorator{ + autorest.WithPathParameters( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/vNets/{resourceName}", pathParameters), + } + + ctx := context.Background() + request, err := armClient.PrepareGetRequest(ctx, decorators...) + assert.NoError(t, err) + + response, rerr := armClient.Send(ctx, request) + assert.NotNil(t, rerr) + assert.Equal(t, 1, count) + assert.Equal(t, http.StatusTooManyRequests, response.StatusCode) +} + +func TestSendAsync(t *testing.T) { + count := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + count++ + http.Error(w, "failed", http.StatusForbidden) + + })) + + backoff := &retry.Backoff{Steps: 1} + armClient := New(nil, server.URL, "test", "2019-01-01", "eastus", backoff) + armClient.client.RetryDuration = time.Millisecond * 1 + + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", "testgroup"), + "subscriptionId": autorest.Encode("path", "testid"), + "resourceName": autorest.Encode("path", "testname"), + } + decorators := []autorest.PrepareDecorator{ + autorest.WithPathParameters( + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/vNets/{resourceName}", pathParameters), + } + + ctx := context.Background() + request, err := armClient.PreparePutRequest(ctx, decorators...) + assert.NoError(t, err) + + future, response, rerr := armClient.SendAsync(ctx, request) + assert.Nil(t, future) + assert.Nil(t, response) + assert.Equal(t, 1, count) + assert.NotNil(t, rerr) + assert.Equal(t, true, rerr.Retriable) +} + +func TestNormalizeAzureRegion(t *testing.T) { + tests := []struct { + region string + expected string + }{ + { + region: "eastus", + expected: "eastus", + }, + { + region: " eastus ", + expected: "eastus", + }, + { + region: " eastus\t", + expected: "eastus", + }, + { + region: " eastus\v", + expected: "eastus", + }, + { + region: " eastus\v\r\f\n", + expected: "eastus", + }, + } + + for i, test := range tests { + real := NormalizeAzureRegion(test.region) + assert.Equal(t, test.expected, real, "test[%d]: NormalizeAzureRegion(%q) != %q", i, test.region, test.expected) + } +} + +func TestPutResource(t *testing.T) { + expectedURI := "/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP?api-version=2019-01-01" + operationURI := "/subscriptions/subscription/providers/Microsoft.Network/locations/eastus/operations/op?api-version=2019-01-01" + handlers := []func(http.ResponseWriter, *http.Request){ + func(rw http.ResponseWriter, req *http.Request) { + assert.Equal(t, "PUT", req.Method) + assert.Equal(t, expectedURI, req.URL.String()) + rw.Header().Set(http.CanonicalHeaderKey("Azure-AsyncOperation"), + fmt.Sprintf("http://%s%s", req.Host, operationURI)) + rw.WriteHeader(http.StatusCreated) + }, + + func(rw http.ResponseWriter, req *http.Request) { + assert.Equal(t, "GET", req.Method) + assert.Equal(t, operationURI, req.URL.String()) + + rw.WriteHeader(http.StatusOK) + rw.Write([]byte(`{"error":{"code":"InternalServerError"},"status":"Failed"}`)) + }, + } + + count := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handlers[count](w, r) + count++ + if count > 1 { + count = 1 + } + })) + + backoff := &retry.Backoff{Steps: 1} + armClient := New(nil, server.URL, "test", "2019-01-01", "eastus", backoff) + armClient.client.RetryDuration = time.Millisecond * 1 + + ctx := context.Background() + response, rerr := armClient.PutResource(ctx, "/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP", nil) + assert.Equal(t, 1, count) + assert.Nil(t, response) + assert.NotNil(t, rerr) + assert.Equal(t, true, rerr.Retriable) +} + +func TestDeleteResourceAsync(t *testing.T) { + count := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + count++ + http.Error(w, "failed", http.StatusForbidden) + })) + + backoff := &retry.Backoff{Steps: 3} + armClient := New(nil, server.URL, "test", "2019-01-01", "eastus", backoff) + armClient.client.RetryDuration = time.Millisecond * 1 + + ctx := context.Background() + resourceID := "/subscriptions/subscription/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/testPIP" + future, rerr := armClient.DeleteResourceAsync(ctx, resourceID, "") + assert.Equal(t, 3, count) + assert.Nil(t, future) + assert.NotNil(t, rerr) + assert.Equal(t, true, rerr.Retriable) +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/doc.go b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/doc.go new file mode 100644 index 00000000000..02ee927c97c --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/doc.go @@ -0,0 +1,20 @@ +// +build !providerless + +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package armclient implements the client for ARM. +package armclient // import "k8s.io/legacy-cloud-providers/azure/clients/armclient" diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/interface.go b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/interface.go new file mode 100644 index 00000000000..7ffeef4a89d --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/interface.go @@ -0,0 +1,84 @@ +// +build !providerless + +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package armclient + +import ( + "context" + "net/http" + + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "k8s.io/legacy-cloud-providers/azure/retry" +) + +// Interface is the client interface for ARM. +// Don't forget to run the following command to generate the mock client: +// mockgen -source=$GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/interface.go -package=mockarmclient Interface > $GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/interface.go +type Interface interface { + // Send sends a http request to ARM service with possible retry to regional ARM endpoint. + Send(ctx context.Context, request *http.Request) (*http.Response, *retry.Error) + + // PreparePutRequest prepares put request + PreparePutRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) + + // PreparePostRequest prepares post request + PreparePostRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) + + // PrepareGetRequest prepares get request + PrepareGetRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) + + // PrepareDeleteRequest preparse delete request + PrepareDeleteRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) + + // PrepareHeadRequest prepares head request + PrepareHeadRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) + + // WaitForAsyncOperationCompletion waits for an operation completion + WaitForAsyncOperationCompletion(ctx context.Context, future *azure.Future, asyncOperationName string) error + + // WaitForAsyncOperationResult waits for an operation result. + WaitForAsyncOperationResult(ctx context.Context, future *azure.Future, asyncOperationName string) (*http.Response, error) + + // SendAsync send a request and return a future object representing the async result as well as the origin http response + SendAsync(ctx context.Context, request *http.Request) (*azure.Future, *http.Response, *retry.Error) + + // PutResource puts a resource by resource ID + PutResource(ctx context.Context, resourceID string, parameters interface{}) (*http.Response, *retry.Error) + + // PutResourceAsync puts a resource by resource ID in async mode + PutResourceAsync(ctx context.Context, resourceID string, parameters interface{}) (*azure.Future, *retry.Error) + + // HeadResource heads a resource by resource ID + HeadResource(ctx context.Context, resourceID string) (*http.Response, *retry.Error) + + // GetResource get a resource by resource ID + GetResource(ctx context.Context, resourceID, expand string) (*http.Response, *retry.Error) + + // PostResource posts a resource by resource ID + PostResource(ctx context.Context, resourceID, action string, parameters interface{}) (*http.Response, *retry.Error) + + // DeleteResource deletes a resource by resource ID + DeleteResource(ctx context.Context, resourceID, ifMatch string) *retry.Error + + // DeleteResourceAsync delete a resource by resource ID and returns a future representing the async result + DeleteResourceAsync(ctx context.Context, resourceID, ifMatch string) (*azure.Future, *retry.Error) + + // CloseResponse closes a response + CloseResponse(ctx context.Context, response *http.Response) +} diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/BUILD b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/BUILD new file mode 100644 index 00000000000..d81422796ae --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/BUILD @@ -0,0 +1,32 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "interface.go", + ], + importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient", + importpath = "k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library", + "//vendor/github.com/Azure/go-autorest/autorest:go_default_library", + "//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library", + "//vendor/github.com/golang/mock/gomock:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/doc.go b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/doc.go new file mode 100644 index 00000000000..109f68e1dd2 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/doc.go @@ -0,0 +1,20 @@ +// +build !providerless + +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package mockarmclient implements the mock client for ARM. +package mockarmclient // import "k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient" diff --git a/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/interface.go b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/interface.go new file mode 100644 index 00000000000..724dcd6be21 --- /dev/null +++ b/staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient/interface.go @@ -0,0 +1,329 @@ +// +build !providerless + +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mockarmclient + +import ( + context "context" + http "net/http" + reflect "reflect" + + autorest "github.com/Azure/go-autorest/autorest" + azure "github.com/Azure/go-autorest/autorest/azure" + gomock "github.com/golang/mock/gomock" + retry "k8s.io/legacy-cloud-providers/azure/retry" +) + +// MockInterface is a mock of Interface interface +type MockInterface struct { + ctrl *gomock.Controller + recorder *MockInterfaceMockRecorder +} + +// MockInterfaceMockRecorder is the mock recorder for MockInterface +type MockInterfaceMockRecorder struct { + mock *MockInterface +} + +// NewMockInterface creates a new mock instance +func NewMockInterface(ctrl *gomock.Controller) *MockInterface { + mock := &MockInterface{ctrl: ctrl} + mock.recorder = &MockInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { + return m.recorder +} + +// Send mocks base method +func (m *MockInterface) Send(ctx context.Context, request *http.Request) (*http.Response, *retry.Error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Send", ctx, request) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(*retry.Error) + return ret0, ret1 +} + +// Send indicates an expected call of Send +func (mr *MockInterfaceMockRecorder) Send(ctx, request interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockInterface)(nil).Send), ctx, request) +} + +// PreparePutRequest mocks base method +func (m *MockInterface) PreparePutRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx} + for _, a := range decorators { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PreparePutRequest", varargs...) + ret0, _ := ret[0].(*http.Request) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PreparePutRequest indicates an expected call of PreparePutRequest +func (mr *MockInterfaceMockRecorder) PreparePutRequest(ctx interface{}, decorators ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx}, decorators...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PreparePutRequest", reflect.TypeOf((*MockInterface)(nil).PreparePutRequest), varargs...) +} + +// PreparePostRequest mocks base method +func (m *MockInterface) PreparePostRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx} + for _, a := range decorators { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PreparePostRequest", varargs...) + ret0, _ := ret[0].(*http.Request) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PreparePostRequest indicates an expected call of PreparePostRequest +func (mr *MockInterfaceMockRecorder) PreparePostRequest(ctx interface{}, decorators ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx}, decorators...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PreparePostRequest", reflect.TypeOf((*MockInterface)(nil).PreparePostRequest), varargs...) +} + +// PrepareGetRequest mocks base method +func (m *MockInterface) PrepareGetRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx} + for _, a := range decorators { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PrepareGetRequest", varargs...) + ret0, _ := ret[0].(*http.Request) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PrepareGetRequest indicates an expected call of PrepareGetRequest +func (mr *MockInterfaceMockRecorder) PrepareGetRequest(ctx interface{}, decorators ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx}, decorators...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareGetRequest", reflect.TypeOf((*MockInterface)(nil).PrepareGetRequest), varargs...) +} + +// PrepareDeleteRequest mocks base method +func (m *MockInterface) PrepareDeleteRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx} + for _, a := range decorators { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PrepareDeleteRequest", varargs...) + ret0, _ := ret[0].(*http.Request) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PrepareDeleteRequest indicates an expected call of PrepareDeleteRequest +func (mr *MockInterfaceMockRecorder) PrepareDeleteRequest(ctx interface{}, decorators ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx}, decorators...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareDeleteRequest", reflect.TypeOf((*MockInterface)(nil).PrepareDeleteRequest), varargs...) +} + +// PrepareHeadRequest mocks base method +func (m *MockInterface) PrepareHeadRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx} + for _, a := range decorators { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PrepareHeadRequest", varargs...) + ret0, _ := ret[0].(*http.Request) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PrepareHeadRequest indicates an expected call of PrepareHeadRequest +func (mr *MockInterfaceMockRecorder) PrepareHeadRequest(ctx interface{}, decorators ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx}, decorators...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareHeadRequest", reflect.TypeOf((*MockInterface)(nil).PrepareHeadRequest), varargs...) +} + +// WaitForAsyncOperationCompletion mocks base method +func (m *MockInterface) WaitForAsyncOperationCompletion(ctx context.Context, future *azure.Future, asyncOperationName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitForAsyncOperationCompletion", ctx, future, asyncOperationName) + ret0, _ := ret[0].(error) + return ret0 +} + +// WaitForAsyncOperationCompletion indicates an expected call of WaitForAsyncOperationCompletion +func (mr *MockInterfaceMockRecorder) WaitForAsyncOperationCompletion(ctx, future, asyncOperationName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForAsyncOperationCompletion", reflect.TypeOf((*MockInterface)(nil).WaitForAsyncOperationCompletion), ctx, future, asyncOperationName) +} + +// WaitForAsyncOperationResult mocks base method +func (m *MockInterface) WaitForAsyncOperationResult(ctx context.Context, future *azure.Future, asyncOperationName string) (*http.Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitForAsyncOperationResult", ctx, future, asyncOperationName) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WaitForAsyncOperationResult indicates an expected call of WaitForAsyncOperationResult +func (mr *MockInterfaceMockRecorder) WaitForAsyncOperationResult(ctx, future, asyncOperationName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForAsyncOperationResult", reflect.TypeOf((*MockInterface)(nil).WaitForAsyncOperationResult), ctx, future, asyncOperationName) +} + +// SendAsync mocks base method +func (m *MockInterface) SendAsync(ctx context.Context, request *http.Request) (*azure.Future, *http.Response, *retry.Error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendAsync", ctx, request) + ret0, _ := ret[0].(*azure.Future) + ret1, _ := ret[1].(*http.Response) + ret2, _ := ret[2].(*retry.Error) + return ret0, ret1, ret2 +} + +// SendAsync indicates an expected call of SendAsync +func (mr *MockInterfaceMockRecorder) SendAsync(ctx, request interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAsync", reflect.TypeOf((*MockInterface)(nil).SendAsync), ctx, request) +} + +// PutResource mocks base method +func (m *MockInterface) PutResource(ctx context.Context, resourceID string, parameters interface{}) (*http.Response, *retry.Error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PutResource", ctx, resourceID, parameters) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(*retry.Error) + return ret0, ret1 +} + +// PutResource indicates an expected call of PutResource +func (mr *MockInterfaceMockRecorder) PutResource(ctx, resourceID, parameters interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutResource", reflect.TypeOf((*MockInterface)(nil).PutResource), ctx, resourceID, parameters) +} + +// PutResourceAsync mocks base method +func (m *MockInterface) PutResourceAsync(ctx context.Context, resourceID string, parameters interface{}) (*azure.Future, *retry.Error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PutResourceAsync", ctx, resourceID, parameters) + ret0, _ := ret[0].(*azure.Future) + ret1, _ := ret[1].(*retry.Error) + return ret0, ret1 +} + +// PutResourceAsync indicates an expected call of PutResourceAsync +func (mr *MockInterfaceMockRecorder) PutResourceAsync(ctx, resourceID, parameters interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutResourceAsync", reflect.TypeOf((*MockInterface)(nil).PutResourceAsync), ctx, resourceID, parameters) +} + +// HeadResource mocks base method +func (m *MockInterface) HeadResource(ctx context.Context, resourceID string) (*http.Response, *retry.Error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HeadResource", ctx, resourceID) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(*retry.Error) + return ret0, ret1 +} + +// HeadResource indicates an expected call of HeadResource +func (mr *MockInterfaceMockRecorder) HeadResource(ctx, resourceID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HeadResource", reflect.TypeOf((*MockInterface)(nil).HeadResource), ctx, resourceID) +} + +// GetResource mocks base method +func (m *MockInterface) GetResource(ctx context.Context, resourceID, expand string) (*http.Response, *retry.Error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetResource", ctx, resourceID, expand) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(*retry.Error) + return ret0, ret1 +} + +// GetResource indicates an expected call of GetResource +func (mr *MockInterfaceMockRecorder) GetResource(ctx, resourceID, expand interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResource", reflect.TypeOf((*MockInterface)(nil).GetResource), ctx, resourceID, expand) +} + +// PostResource mocks base method +func (m *MockInterface) PostResource(ctx context.Context, resourceID, action string, parameters interface{}) (*http.Response, *retry.Error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PostResource", ctx, resourceID, action, parameters) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(*retry.Error) + return ret0, ret1 +} + +// PostResource indicates an expected call of PostResource +func (mr *MockInterfaceMockRecorder) PostResource(ctx, resourceID, action, parameters interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostResource", reflect.TypeOf((*MockInterface)(nil).PostResource), ctx, resourceID, action, parameters) +} + +// DeleteResource mocks base method +func (m *MockInterface) DeleteResource(ctx context.Context, resourceID, ifMatch string) *retry.Error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteResource", ctx, resourceID, ifMatch) + ret0, _ := ret[0].(*retry.Error) + return ret0 +} + +// DeleteResource indicates an expected call of DeleteResource +func (mr *MockInterfaceMockRecorder) DeleteResource(ctx, resourceID, ifMatch interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteResource", reflect.TypeOf((*MockInterface)(nil).DeleteResource), ctx, resourceID, ifMatch) +} + +// DeleteResourceAsync mocks base method +func (m *MockInterface) DeleteResourceAsync(ctx context.Context, resourceID, ifMatch string) (*azure.Future, *retry.Error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteResourceAsync", ctx, resourceID, ifMatch) + ret0, _ := ret[0].(*azure.Future) + ret1, _ := ret[1].(*retry.Error) + return ret0, ret1 +} + +// DeleteResourceAsync indicates an expected call of DeleteResourceAsync +func (mr *MockInterfaceMockRecorder) DeleteResourceAsync(ctx, resourceID, ifMatch interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteResourceAsync", reflect.TypeOf((*MockInterface)(nil).DeleteResourceAsync), ctx, resourceID, ifMatch) +} + +// CloseResponse mocks base method +func (m *MockInterface) CloseResponse(ctx context.Context, response *http.Response) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "CloseResponse", ctx, response) +} + +// CloseResponse indicates an expected call of CloseResponse +func (mr *MockInterfaceMockRecorder) CloseResponse(ctx, response interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseResponse", reflect.TypeOf((*MockInterface)(nil).CloseResponse), ctx, response) +}