Merge pull request #4689 from gandhipr/prachigandhi-Fix-invalidMetadataUrl-add-getSubscriptionIdFromInstanceMetadata

FixBug-invalidMetadataUrl-add-getSubscriptionIdFromInstanceMetadata
This commit is contained in:
Kubernetes Prow Robot 2022-03-02 16:14:46 -08:00 committed by GitHub
commit c22e6f1a54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 24265 additions and 4 deletions

View File

@ -23,6 +23,7 @@ import (
"io"
"io/ioutil"
"os"
"strconv"
"strings"
"time"
@ -31,6 +32,7 @@ import (
"github.com/Azure/go-autorest/autorest/azure"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
providerazure "sigs.k8s.io/cloud-provider-azure/pkg/provider"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
@ -38,7 +40,7 @@ const (
// The path of deployment parameters for standard vm.
deploymentParametersPath = "/var/lib/azure/azuredeploy.parameters.json"
metadataURL = "http://169.254.169.254/metadata/instance"
imdsServerURL = "http://169.254.169.254"
// backoff
backoffRetriesDefault = 6
@ -146,7 +148,6 @@ func BuildAzureConfig(configReader io.Reader) (*Config, error) {
cfg.Cloud = os.Getenv("ARM_CLOUD")
cfg.Location = os.Getenv("LOCATION")
cfg.ResourceGroup = os.Getenv("ARM_RESOURCE_GROUP")
cfg.SubscriptionID = os.Getenv("ARM_SUBSCRIPTION_ID")
cfg.TenantID = os.Getenv("ARM_TENANT_ID")
cfg.AADClientID = os.Getenv("ARM_CLIENT_ID")
cfg.AADClientSecret = os.Getenv("ARM_CLIENT_SECRET")
@ -157,6 +158,12 @@ func BuildAzureConfig(configReader io.Reader) (*Config, error) {
cfg.ClusterName = os.Getenv("AZURE_CLUSTER_NAME")
cfg.NodeResourceGroup = os.Getenv("AZURE_NODE_RESOURCE_GROUP")
subscriptionID, err := getSubscriptionIdFromInstanceMetadata()
if err != nil {
return nil, err
}
cfg.SubscriptionID = subscriptionID
useManagedIdentityExtensionFromEnv := os.Getenv("ARM_USE_MANAGED_IDENTITY_EXTENSION")
if len(useManagedIdentityExtensionFromEnv) > 0 {
cfg.UseManagedIdentityExtension, err = strconv.ParseBool(useManagedIdentityExtensionFromEnv)
@ -473,3 +480,22 @@ func (cfg *Config) validate() error {
return nil
}
// getSubscriptionId reads the Subscription ID from the instance metadata.
func getSubscriptionIdFromInstanceMetadata() (string, error) {
subscriptionID, present := os.LookupEnv("ARM_SUBSCRIPTION_ID")
if !present {
metadataService, err := providerazure.NewInstanceMetadataService(imdsServerURL)
if err != nil {
return "", err
}
metadata, err := metadataService.GetMetadata(0)
if err != nil {
return "", err
}
return metadata.Compute.SubscriptionID, nil
}
return subscriptionID, nil
}

View File

@ -0,0 +1,9 @@
# Change History
## Additive Changes
### New Funcs
1. PrivateZoneProperties.MarshalJSON() ([]byte, error)
1. ProxyResource.MarshalJSON() ([]byte, error)
1. Resource.MarshalJSON() ([]byte, error)

View File

@ -0,0 +1,11 @@
{
"commit": "3c764635e7d442b3e74caf593029fcd440b3ef82",
"readme": "/_/azure-rest-api-specs/specification/privatedns/resource-manager/readme.md",
"tag": "package-2018-09",
"use": "@microsoft.azure/autorest.go@2.1.183",
"repository_url": "https://github.com/Azure/azure-rest-api-specs.git",
"autorest_command": "autorest --use=@microsoft.azure/autorest.go@2.1.183 --tag=package-2018-09 --go-sdk-folder=/_/azure-sdk-for-go --go --verbose --use-onever --version=V2 --go.license-header=MICROSOFT_MIT_NO_VERSION /_/azure-rest-api-specs/specification/privatedns/resource-manager/readme.md",
"additional_properties": {
"additional_options": "--go --verbose --use-onever --version=V2 --go.license-header=MICROSOFT_MIT_NO_VERSION"
}
}

View File

@ -0,0 +1,41 @@
// Package privatedns implements the Azure ARM Privatedns service API version 2018-09-01.
//
// The Private DNS Management Client.
package privatedns
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
import (
"github.com/Azure/go-autorest/autorest"
)
const (
// DefaultBaseURI is the default URI used for the service Privatedns
DefaultBaseURI = "https://management.azure.com"
)
// BaseClient is the base client for Privatedns.
type BaseClient struct {
autorest.Client
BaseURI string
SubscriptionID string
}
// New creates an instance of the BaseClient client.
func New(subscriptionID string) BaseClient {
return NewWithBaseURI(DefaultBaseURI, subscriptionID)
}
// NewWithBaseURI creates an instance of the BaseClient client using a custom endpoint. Use this when interacting with
// an Azure cloud that uses a non-standard base URI (sovereign clouds, Azure stack).
func NewWithBaseURI(baseURI string, subscriptionID string) BaseClient {
return BaseClient{
Client: autorest.NewClientWithUserAgent(UserAgent()),
BaseURI: baseURI,
SubscriptionID: subscriptionID,
}
}

View File

@ -0,0 +1,72 @@
package privatedns
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
// ProvisioningState enumerates the values for provisioning state.
type ProvisioningState string
const (
// Canceled ...
Canceled ProvisioningState = "Canceled"
// Creating ...
Creating ProvisioningState = "Creating"
// Deleting ...
Deleting ProvisioningState = "Deleting"
// Failed ...
Failed ProvisioningState = "Failed"
// Succeeded ...
Succeeded ProvisioningState = "Succeeded"
// Updating ...
Updating ProvisioningState = "Updating"
)
// PossibleProvisioningStateValues returns an array of possible values for the ProvisioningState const type.
func PossibleProvisioningStateValues() []ProvisioningState {
return []ProvisioningState{Canceled, Creating, Deleting, Failed, Succeeded, Updating}
}
// RecordType enumerates the values for record type.
type RecordType string
const (
// A ...
A RecordType = "A"
// AAAA ...
AAAA RecordType = "AAAA"
// CNAME ...
CNAME RecordType = "CNAME"
// MX ...
MX RecordType = "MX"
// PTR ...
PTR RecordType = "PTR"
// SOA ...
SOA RecordType = "SOA"
// SRV ...
SRV RecordType = "SRV"
// TXT ...
TXT RecordType = "TXT"
)
// PossibleRecordTypeValues returns an array of possible values for the RecordType const type.
func PossibleRecordTypeValues() []RecordType {
return []RecordType{A, AAAA, CNAME, MX, PTR, SOA, SRV, TXT}
}
// VirtualNetworkLinkState enumerates the values for virtual network link state.
type VirtualNetworkLinkState string
const (
// Completed ...
Completed VirtualNetworkLinkState = "Completed"
// InProgress ...
InProgress VirtualNetworkLinkState = "InProgress"
)
// PossibleVirtualNetworkLinkStateValues returns an array of possible values for the VirtualNetworkLinkState const type.
func PossibleVirtualNetworkLinkStateValues() []VirtualNetworkLinkState {
return []VirtualNetworkLinkState{Completed, InProgress}
}

View File

@ -0,0 +1,611 @@
package privatedns
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
import (
"context"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/tracing"
"net/http"
)
// PrivateZonesClient is the the Private DNS Management Client.
type PrivateZonesClient struct {
BaseClient
}
// NewPrivateZonesClient creates an instance of the PrivateZonesClient client.
func NewPrivateZonesClient(subscriptionID string) PrivateZonesClient {
return NewPrivateZonesClientWithBaseURI(DefaultBaseURI, subscriptionID)
}
// NewPrivateZonesClientWithBaseURI creates an instance of the PrivateZonesClient client using a custom endpoint. Use
// this when interacting with an Azure cloud that uses a non-standard base URI (sovereign clouds, Azure stack).
func NewPrivateZonesClientWithBaseURI(baseURI string, subscriptionID string) PrivateZonesClient {
return PrivateZonesClient{NewWithBaseURI(baseURI, subscriptionID)}
}
// CreateOrUpdate creates or updates a Private DNS zone. Does not modify Links to virtual networks or DNS records
// within the zone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// parameters - parameters supplied to the CreateOrUpdate operation.
// ifMatch - the ETag of the Private DNS zone. Omit this value to always overwrite the current zone. Specify
// the last-seen ETag value to prevent accidentally overwriting any concurrent changes.
// ifNoneMatch - set to '*' to allow a new Private DNS zone to be created, but to prevent updating an existing
// zone. Other values will be ignored.
func (client PrivateZonesClient) CreateOrUpdate(ctx context.Context, resourceGroupName string, privateZoneName string, parameters PrivateZone, ifMatch string, ifNoneMatch string) (result PrivateZonesCreateOrUpdateFuture, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/PrivateZonesClient.CreateOrUpdate")
defer func() {
sc := -1
if result.FutureAPI != nil && result.FutureAPI.Response() != nil {
sc = result.FutureAPI.Response().StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
req, err := client.CreateOrUpdatePreparer(ctx, resourceGroupName, privateZoneName, parameters, ifMatch, ifNoneMatch)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "CreateOrUpdate", nil, "Failure preparing request")
return
}
result, err = client.CreateOrUpdateSender(req)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "CreateOrUpdate", nil, "Failure sending request")
return
}
return
}
// CreateOrUpdatePreparer prepares the CreateOrUpdate request.
func (client PrivateZonesClient) CreateOrUpdatePreparer(ctx context.Context, resourceGroupName string, privateZoneName string, parameters PrivateZone, ifMatch string, ifNoneMatch string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsContentType("application/json; charset=utf-8"),
autorest.AsPut(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}", pathParameters),
autorest.WithJSON(parameters),
autorest.WithQueryParameters(queryParameters))
if len(ifMatch) > 0 {
preparer = autorest.DecoratePreparer(preparer,
autorest.WithHeader("If-Match", autorest.String(ifMatch)))
}
if len(ifNoneMatch) > 0 {
preparer = autorest.DecoratePreparer(preparer,
autorest.WithHeader("If-None-Match", autorest.String(ifNoneMatch)))
}
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// CreateOrUpdateSender sends the CreateOrUpdate request. The method will close the
// http.Response Body if it receives an error.
func (client PrivateZonesClient) CreateOrUpdateSender(req *http.Request) (future PrivateZonesCreateOrUpdateFuture, err error) {
var resp *http.Response
resp, err = client.Send(req, azure.DoRetryWithRegistration(client.Client))
if err != nil {
return
}
var azf azure.Future
azf, err = azure.NewFutureFromResponse(resp)
future.FutureAPI = &azf
future.Result = future.result
return
}
// CreateOrUpdateResponder handles the response to the CreateOrUpdate request. The method always
// closes the http.Response Body.
func (client PrivateZonesClient) CreateOrUpdateResponder(resp *http.Response) (result PrivateZone, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated, http.StatusAccepted),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}
// Delete deletes a Private DNS zone. WARNING: All DNS records in the zone will also be deleted. This operation cannot
// be undone. Private DNS zone cannot be deleted unless all virtual network links to it are removed.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// ifMatch - the ETag of the Private DNS zone. Omit this value to always delete the current zone. Specify the
// last-seen ETag value to prevent accidentally deleting any concurrent changes.
func (client PrivateZonesClient) Delete(ctx context.Context, resourceGroupName string, privateZoneName string, ifMatch string) (result PrivateZonesDeleteFuture, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/PrivateZonesClient.Delete")
defer func() {
sc := -1
if result.FutureAPI != nil && result.FutureAPI.Response() != nil {
sc = result.FutureAPI.Response().StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
req, err := client.DeletePreparer(ctx, resourceGroupName, privateZoneName, ifMatch)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "Delete", nil, "Failure preparing request")
return
}
result, err = client.DeleteSender(req)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "Delete", nil, "Failure sending request")
return
}
return
}
// DeletePreparer prepares the Delete request.
func (client PrivateZonesClient) DeletePreparer(ctx context.Context, resourceGroupName string, privateZoneName string, ifMatch string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsDelete(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}", pathParameters),
autorest.WithQueryParameters(queryParameters))
if len(ifMatch) > 0 {
preparer = autorest.DecoratePreparer(preparer,
autorest.WithHeader("If-Match", autorest.String(ifMatch)))
}
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// DeleteSender sends the Delete request. The method will close the
// http.Response Body if it receives an error.
func (client PrivateZonesClient) DeleteSender(req *http.Request) (future PrivateZonesDeleteFuture, err error) {
var resp *http.Response
resp, err = client.Send(req, azure.DoRetryWithRegistration(client.Client))
if err != nil {
return
}
var azf azure.Future
azf, err = azure.NewFutureFromResponse(resp)
future.FutureAPI = &azf
future.Result = future.result
return
}
// DeleteResponder handles the response to the Delete request. The method always
// closes the http.Response Body.
func (client PrivateZonesClient) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusAccepted, http.StatusNoContent),
autorest.ByClosing())
result.Response = resp
return
}
// Get gets a Private DNS zone. Retrieves the zone properties, but not the virtual networks links or the record sets
// within the zone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
func (client PrivateZonesClient) Get(ctx context.Context, resourceGroupName string, privateZoneName string) (result PrivateZone, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/PrivateZonesClient.Get")
defer func() {
sc := -1
if result.Response.Response != nil {
sc = result.Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
req, err := client.GetPreparer(ctx, resourceGroupName, privateZoneName)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "Get", nil, "Failure preparing request")
return
}
resp, err := client.GetSender(req)
if err != nil {
result.Response = autorest.Response{Response: resp}
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "Get", resp, "Failure sending request")
return
}
result, err = client.GetResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "Get", resp, "Failure responding to request")
return
}
return
}
// GetPreparer prepares the Get request.
func (client PrivateZonesClient) GetPreparer(ctx context.Context, resourceGroupName string, privateZoneName string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsGet(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}", pathParameters),
autorest.WithQueryParameters(queryParameters))
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// GetSender sends the Get request. The method will close the
// http.Response Body if it receives an error.
func (client PrivateZonesClient) GetSender(req *http.Request) (*http.Response, error) {
return client.Send(req, azure.DoRetryWithRegistration(client.Client))
}
// GetResponder handles the response to the Get request. The method always
// closes the http.Response Body.
func (client PrivateZonesClient) GetResponder(resp *http.Response) (result PrivateZone, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}
// List lists the Private DNS zones in all resource groups in a subscription.
// Parameters:
// top - the maximum number of Private DNS zones to return. If not specified, returns up to 100 zones.
func (client PrivateZonesClient) List(ctx context.Context, top *int32) (result PrivateZoneListResultPage, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/PrivateZonesClient.List")
defer func() {
sc := -1
if result.pzlr.Response.Response != nil {
sc = result.pzlr.Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
result.fn = client.listNextResults
req, err := client.ListPreparer(ctx, top)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "List", nil, "Failure preparing request")
return
}
resp, err := client.ListSender(req)
if err != nil {
result.pzlr.Response = autorest.Response{Response: resp}
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "List", resp, "Failure sending request")
return
}
result.pzlr, err = client.ListResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "List", resp, "Failure responding to request")
return
}
if result.pzlr.hasNextLink() && result.pzlr.IsEmpty() {
err = result.NextWithContext(ctx)
return
}
return
}
// ListPreparer prepares the List request.
func (client PrivateZonesClient) ListPreparer(ctx context.Context, top *int32) (*http.Request, error) {
pathParameters := map[string]interface{}{
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
if top != nil {
queryParameters["$top"] = autorest.Encode("query", *top)
}
preparer := autorest.CreatePreparer(
autorest.AsGet(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/Microsoft.Network/privateDnsZones", pathParameters),
autorest.WithQueryParameters(queryParameters))
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// ListSender sends the List request. The method will close the
// http.Response Body if it receives an error.
func (client PrivateZonesClient) ListSender(req *http.Request) (*http.Response, error) {
return client.Send(req, azure.DoRetryWithRegistration(client.Client))
}
// ListResponder handles the response to the List request. The method always
// closes the http.Response Body.
func (client PrivateZonesClient) ListResponder(resp *http.Response) (result PrivateZoneListResult, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}
// listNextResults retrieves the next set of results, if any.
func (client PrivateZonesClient) listNextResults(ctx context.Context, lastResults PrivateZoneListResult) (result PrivateZoneListResult, err error) {
req, err := lastResults.privateZoneListResultPreparer(ctx)
if err != nil {
return result, autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "listNextResults", nil, "Failure preparing next results request")
}
if req == nil {
return
}
resp, err := client.ListSender(req)
if err != nil {
result.Response = autorest.Response{Response: resp}
return result, autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "listNextResults", resp, "Failure sending next results request")
}
result, err = client.ListResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "listNextResults", resp, "Failure responding to next results request")
}
return
}
// ListComplete enumerates all values, automatically crossing page boundaries as required.
func (client PrivateZonesClient) ListComplete(ctx context.Context, top *int32) (result PrivateZoneListResultIterator, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/PrivateZonesClient.List")
defer func() {
sc := -1
if result.Response().Response.Response != nil {
sc = result.page.Response().Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
result.page, err = client.List(ctx, top)
return
}
// ListByResourceGroup lists the Private DNS zones within a resource group.
// Parameters:
// resourceGroupName - the name of the resource group.
// top - the maximum number of record sets to return. If not specified, returns up to 100 record sets.
func (client PrivateZonesClient) ListByResourceGroup(ctx context.Context, resourceGroupName string, top *int32) (result PrivateZoneListResultPage, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/PrivateZonesClient.ListByResourceGroup")
defer func() {
sc := -1
if result.pzlr.Response.Response != nil {
sc = result.pzlr.Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
result.fn = client.listByResourceGroupNextResults
req, err := client.ListByResourceGroupPreparer(ctx, resourceGroupName, top)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "ListByResourceGroup", nil, "Failure preparing request")
return
}
resp, err := client.ListByResourceGroupSender(req)
if err != nil {
result.pzlr.Response = autorest.Response{Response: resp}
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "ListByResourceGroup", resp, "Failure sending request")
return
}
result.pzlr, err = client.ListByResourceGroupResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "ListByResourceGroup", resp, "Failure responding to request")
return
}
if result.pzlr.hasNextLink() && result.pzlr.IsEmpty() {
err = result.NextWithContext(ctx)
return
}
return
}
// ListByResourceGroupPreparer prepares the ListByResourceGroup request.
func (client PrivateZonesClient) ListByResourceGroupPreparer(ctx context.Context, resourceGroupName string, top *int32) (*http.Request, error) {
pathParameters := map[string]interface{}{
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
if top != nil {
queryParameters["$top"] = autorest.Encode("query", *top)
}
preparer := autorest.CreatePreparer(
autorest.AsGet(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones", pathParameters),
autorest.WithQueryParameters(queryParameters))
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// ListByResourceGroupSender sends the ListByResourceGroup request. The method will close the
// http.Response Body if it receives an error.
func (client PrivateZonesClient) ListByResourceGroupSender(req *http.Request) (*http.Response, error) {
return client.Send(req, azure.DoRetryWithRegistration(client.Client))
}
// ListByResourceGroupResponder handles the response to the ListByResourceGroup request. The method always
// closes the http.Response Body.
func (client PrivateZonesClient) ListByResourceGroupResponder(resp *http.Response) (result PrivateZoneListResult, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}
// listByResourceGroupNextResults retrieves the next set of results, if any.
func (client PrivateZonesClient) listByResourceGroupNextResults(ctx context.Context, lastResults PrivateZoneListResult) (result PrivateZoneListResult, err error) {
req, err := lastResults.privateZoneListResultPreparer(ctx)
if err != nil {
return result, autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "listByResourceGroupNextResults", nil, "Failure preparing next results request")
}
if req == nil {
return
}
resp, err := client.ListByResourceGroupSender(req)
if err != nil {
result.Response = autorest.Response{Response: resp}
return result, autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "listByResourceGroupNextResults", resp, "Failure sending next results request")
}
result, err = client.ListByResourceGroupResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "listByResourceGroupNextResults", resp, "Failure responding to next results request")
}
return
}
// ListByResourceGroupComplete enumerates all values, automatically crossing page boundaries as required.
func (client PrivateZonesClient) ListByResourceGroupComplete(ctx context.Context, resourceGroupName string, top *int32) (result PrivateZoneListResultIterator, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/PrivateZonesClient.ListByResourceGroup")
defer func() {
sc := -1
if result.Response().Response.Response != nil {
sc = result.page.Response().Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
result.page, err = client.ListByResourceGroup(ctx, resourceGroupName, top)
return
}
// Update updates a Private DNS zone. Does not modify virtual network links or DNS records within the zone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// parameters - parameters supplied to the Update operation.
// ifMatch - the ETag of the Private DNS zone. Omit this value to always overwrite the current zone. Specify
// the last-seen ETag value to prevent accidentally overwriting any concurrent changes.
func (client PrivateZonesClient) Update(ctx context.Context, resourceGroupName string, privateZoneName string, parameters PrivateZone, ifMatch string) (result PrivateZonesUpdateFuture, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/PrivateZonesClient.Update")
defer func() {
sc := -1
if result.FutureAPI != nil && result.FutureAPI.Response() != nil {
sc = result.FutureAPI.Response().StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
req, err := client.UpdatePreparer(ctx, resourceGroupName, privateZoneName, parameters, ifMatch)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "Update", nil, "Failure preparing request")
return
}
result, err = client.UpdateSender(req)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.PrivateZonesClient", "Update", nil, "Failure sending request")
return
}
return
}
// UpdatePreparer prepares the Update request.
func (client PrivateZonesClient) UpdatePreparer(ctx context.Context, resourceGroupName string, privateZoneName string, parameters PrivateZone, ifMatch string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsContentType("application/json; charset=utf-8"),
autorest.AsPatch(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}", pathParameters),
autorest.WithJSON(parameters),
autorest.WithQueryParameters(queryParameters))
if len(ifMatch) > 0 {
preparer = autorest.DecoratePreparer(preparer,
autorest.WithHeader("If-Match", autorest.String(ifMatch)))
}
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// UpdateSender sends the Update request. The method will close the
// http.Response Body if it receives an error.
func (client PrivateZonesClient) UpdateSender(req *http.Request) (future PrivateZonesUpdateFuture, err error) {
var resp *http.Response
resp, err = client.Send(req, azure.DoRetryWithRegistration(client.Client))
if err != nil {
return
}
var azf azure.Future
azf, err = azure.NewFutureFromResponse(resp)
future.FutureAPI = &azf
future.Result = future.result
return
}
// UpdateResponder handles the response to the Update request. The method always
// closes the http.Response Body.
func (client PrivateZonesClient) UpdateResponder(resp *http.Response) (result PrivateZone, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusAccepted),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}

View File

@ -0,0 +1,640 @@
package privatedns
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
import (
"context"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/tracing"
"net/http"
)
// RecordSetsClient is the the Private DNS Management Client.
type RecordSetsClient struct {
BaseClient
}
// NewRecordSetsClient creates an instance of the RecordSetsClient client.
func NewRecordSetsClient(subscriptionID string) RecordSetsClient {
return NewRecordSetsClientWithBaseURI(DefaultBaseURI, subscriptionID)
}
// NewRecordSetsClientWithBaseURI creates an instance of the RecordSetsClient client using a custom endpoint. Use this
// when interacting with an Azure cloud that uses a non-standard base URI (sovereign clouds, Azure stack).
func NewRecordSetsClientWithBaseURI(baseURI string, subscriptionID string) RecordSetsClient {
return RecordSetsClient{NewWithBaseURI(baseURI, subscriptionID)}
}
// CreateOrUpdate creates or updates a record set within a Private DNS zone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// recordType - the type of DNS record in this record set. Record sets of type SOA can be updated but not
// created (they are created when the Private DNS zone is created).
// relativeRecordSetName - the name of the record set, relative to the name of the zone.
// parameters - parameters supplied to the CreateOrUpdate operation.
// ifMatch - the ETag of the record set. Omit this value to always overwrite the current record set. Specify
// the last-seen ETag value to prevent accidentally overwriting any concurrent changes.
// ifNoneMatch - set to '*' to allow a new record set to be created, but to prevent updating an existing record
// set. Other values will be ignored.
func (client RecordSetsClient) CreateOrUpdate(ctx context.Context, resourceGroupName string, privateZoneName string, recordType RecordType, relativeRecordSetName string, parameters RecordSet, ifMatch string, ifNoneMatch string) (result RecordSet, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.CreateOrUpdate")
defer func() {
sc := -1
if result.Response.Response != nil {
sc = result.Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
req, err := client.CreateOrUpdatePreparer(ctx, resourceGroupName, privateZoneName, recordType, relativeRecordSetName, parameters, ifMatch, ifNoneMatch)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "CreateOrUpdate", nil, "Failure preparing request")
return
}
resp, err := client.CreateOrUpdateSender(req)
if err != nil {
result.Response = autorest.Response{Response: resp}
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "CreateOrUpdate", resp, "Failure sending request")
return
}
result, err = client.CreateOrUpdateResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "CreateOrUpdate", resp, "Failure responding to request")
return
}
return
}
// CreateOrUpdatePreparer prepares the CreateOrUpdate request.
func (client RecordSetsClient) CreateOrUpdatePreparer(ctx context.Context, resourceGroupName string, privateZoneName string, recordType RecordType, relativeRecordSetName string, parameters RecordSet, ifMatch string, ifNoneMatch string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"recordType": autorest.Encode("path", recordType),
"relativeRecordSetName": relativeRecordSetName,
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsContentType("application/json; charset=utf-8"),
autorest.AsPut(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}/{recordType}/{relativeRecordSetName}", pathParameters),
autorest.WithJSON(parameters),
autorest.WithQueryParameters(queryParameters))
if len(ifMatch) > 0 {
preparer = autorest.DecoratePreparer(preparer,
autorest.WithHeader("If-Match", autorest.String(ifMatch)))
}
if len(ifNoneMatch) > 0 {
preparer = autorest.DecoratePreparer(preparer,
autorest.WithHeader("If-None-Match", autorest.String(ifNoneMatch)))
}
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// CreateOrUpdateSender sends the CreateOrUpdate request. The method will close the
// http.Response Body if it receives an error.
func (client RecordSetsClient) CreateOrUpdateSender(req *http.Request) (*http.Response, error) {
return client.Send(req, azure.DoRetryWithRegistration(client.Client))
}
// CreateOrUpdateResponder handles the response to the CreateOrUpdate request. The method always
// closes the http.Response Body.
func (client RecordSetsClient) CreateOrUpdateResponder(resp *http.Response) (result RecordSet, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}
// Delete deletes a record set from a Private DNS zone. This operation cannot be undone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// recordType - the type of DNS record in this record set. Record sets of type SOA cannot be deleted (they are
// deleted when the Private DNS zone is deleted).
// relativeRecordSetName - the name of the record set, relative to the name of the zone.
// ifMatch - the ETag of the record set. Omit this value to always delete the current record set. Specify the
// last-seen ETag value to prevent accidentally deleting any concurrent changes.
func (client RecordSetsClient) Delete(ctx context.Context, resourceGroupName string, privateZoneName string, recordType RecordType, relativeRecordSetName string, ifMatch string) (result autorest.Response, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.Delete")
defer func() {
sc := -1
if result.Response != nil {
sc = result.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
req, err := client.DeletePreparer(ctx, resourceGroupName, privateZoneName, recordType, relativeRecordSetName, ifMatch)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "Delete", nil, "Failure preparing request")
return
}
resp, err := client.DeleteSender(req)
if err != nil {
result.Response = resp
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "Delete", resp, "Failure sending request")
return
}
result, err = client.DeleteResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "Delete", resp, "Failure responding to request")
return
}
return
}
// DeletePreparer prepares the Delete request.
func (client RecordSetsClient) DeletePreparer(ctx context.Context, resourceGroupName string, privateZoneName string, recordType RecordType, relativeRecordSetName string, ifMatch string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"recordType": autorest.Encode("path", recordType),
"relativeRecordSetName": relativeRecordSetName,
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsDelete(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}/{recordType}/{relativeRecordSetName}", pathParameters),
autorest.WithQueryParameters(queryParameters))
if len(ifMatch) > 0 {
preparer = autorest.DecoratePreparer(preparer,
autorest.WithHeader("If-Match", autorest.String(ifMatch)))
}
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// DeleteSender sends the Delete request. The method will close the
// http.Response Body if it receives an error.
func (client RecordSetsClient) DeleteSender(req *http.Request) (*http.Response, error) {
return client.Send(req, azure.DoRetryWithRegistration(client.Client))
}
// DeleteResponder handles the response to the Delete request. The method always
// closes the http.Response Body.
func (client RecordSetsClient) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusNoContent),
autorest.ByClosing())
result.Response = resp
return
}
// Get gets a record set.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// recordType - the type of DNS record in this record set.
// relativeRecordSetName - the name of the record set, relative to the name of the zone.
func (client RecordSetsClient) Get(ctx context.Context, resourceGroupName string, privateZoneName string, recordType RecordType, relativeRecordSetName string) (result RecordSet, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.Get")
defer func() {
sc := -1
if result.Response.Response != nil {
sc = result.Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
req, err := client.GetPreparer(ctx, resourceGroupName, privateZoneName, recordType, relativeRecordSetName)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "Get", nil, "Failure preparing request")
return
}
resp, err := client.GetSender(req)
if err != nil {
result.Response = autorest.Response{Response: resp}
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "Get", resp, "Failure sending request")
return
}
result, err = client.GetResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "Get", resp, "Failure responding to request")
return
}
return
}
// GetPreparer prepares the Get request.
func (client RecordSetsClient) GetPreparer(ctx context.Context, resourceGroupName string, privateZoneName string, recordType RecordType, relativeRecordSetName string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"recordType": autorest.Encode("path", recordType),
"relativeRecordSetName": relativeRecordSetName,
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsGet(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}/{recordType}/{relativeRecordSetName}", pathParameters),
autorest.WithQueryParameters(queryParameters))
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// GetSender sends the Get request. The method will close the
// http.Response Body if it receives an error.
func (client RecordSetsClient) GetSender(req *http.Request) (*http.Response, error) {
return client.Send(req, azure.DoRetryWithRegistration(client.Client))
}
// GetResponder handles the response to the Get request. The method always
// closes the http.Response Body.
func (client RecordSetsClient) GetResponder(resp *http.Response) (result RecordSet, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}
// List lists all record sets in a Private DNS zone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// top - the maximum number of record sets to return. If not specified, returns up to 100 record sets.
// recordsetnamesuffix - the suffix label of the record set name to be used to filter the record set
// enumeration. If this parameter is specified, the returned enumeration will only contain records that end
// with ".<recordsetnamesuffix>".
func (client RecordSetsClient) List(ctx context.Context, resourceGroupName string, privateZoneName string, top *int32, recordsetnamesuffix string) (result RecordSetListResultPage, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.List")
defer func() {
sc := -1
if result.rslr.Response.Response != nil {
sc = result.rslr.Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
result.fn = client.listNextResults
req, err := client.ListPreparer(ctx, resourceGroupName, privateZoneName, top, recordsetnamesuffix)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "List", nil, "Failure preparing request")
return
}
resp, err := client.ListSender(req)
if err != nil {
result.rslr.Response = autorest.Response{Response: resp}
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "List", resp, "Failure sending request")
return
}
result.rslr, err = client.ListResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "List", resp, "Failure responding to request")
return
}
if result.rslr.hasNextLink() && result.rslr.IsEmpty() {
err = result.NextWithContext(ctx)
return
}
return
}
// ListPreparer prepares the List request.
func (client RecordSetsClient) ListPreparer(ctx context.Context, resourceGroupName string, privateZoneName string, top *int32, recordsetnamesuffix string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
if top != nil {
queryParameters["$top"] = autorest.Encode("query", *top)
}
if len(recordsetnamesuffix) > 0 {
queryParameters["$recordsetnamesuffix"] = autorest.Encode("query", recordsetnamesuffix)
}
preparer := autorest.CreatePreparer(
autorest.AsGet(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}/ALL", pathParameters),
autorest.WithQueryParameters(queryParameters))
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// ListSender sends the List request. The method will close the
// http.Response Body if it receives an error.
func (client RecordSetsClient) ListSender(req *http.Request) (*http.Response, error) {
return client.Send(req, azure.DoRetryWithRegistration(client.Client))
}
// ListResponder handles the response to the List request. The method always
// closes the http.Response Body.
func (client RecordSetsClient) ListResponder(resp *http.Response) (result RecordSetListResult, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}
// listNextResults retrieves the next set of results, if any.
func (client RecordSetsClient) listNextResults(ctx context.Context, lastResults RecordSetListResult) (result RecordSetListResult, err error) {
req, err := lastResults.recordSetListResultPreparer(ctx)
if err != nil {
return result, autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "listNextResults", nil, "Failure preparing next results request")
}
if req == nil {
return
}
resp, err := client.ListSender(req)
if err != nil {
result.Response = autorest.Response{Response: resp}
return result, autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "listNextResults", resp, "Failure sending next results request")
}
result, err = client.ListResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "listNextResults", resp, "Failure responding to next results request")
}
return
}
// ListComplete enumerates all values, automatically crossing page boundaries as required.
func (client RecordSetsClient) ListComplete(ctx context.Context, resourceGroupName string, privateZoneName string, top *int32, recordsetnamesuffix string) (result RecordSetListResultIterator, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.List")
defer func() {
sc := -1
if result.Response().Response.Response != nil {
sc = result.page.Response().Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
result.page, err = client.List(ctx, resourceGroupName, privateZoneName, top, recordsetnamesuffix)
return
}
// ListByType lists the record sets of a specified type in a Private DNS zone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// recordType - the type of record sets to enumerate.
// top - the maximum number of record sets to return. If not specified, returns up to 100 record sets.
// recordsetnamesuffix - the suffix label of the record set name to be used to filter the record set
// enumeration. If this parameter is specified, the returned enumeration will only contain records that end
// with ".<recordsetnamesuffix>".
func (client RecordSetsClient) ListByType(ctx context.Context, resourceGroupName string, privateZoneName string, recordType RecordType, top *int32, recordsetnamesuffix string) (result RecordSetListResultPage, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.ListByType")
defer func() {
sc := -1
if result.rslr.Response.Response != nil {
sc = result.rslr.Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
result.fn = client.listByTypeNextResults
req, err := client.ListByTypePreparer(ctx, resourceGroupName, privateZoneName, recordType, top, recordsetnamesuffix)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "ListByType", nil, "Failure preparing request")
return
}
resp, err := client.ListByTypeSender(req)
if err != nil {
result.rslr.Response = autorest.Response{Response: resp}
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "ListByType", resp, "Failure sending request")
return
}
result.rslr, err = client.ListByTypeResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "ListByType", resp, "Failure responding to request")
return
}
if result.rslr.hasNextLink() && result.rslr.IsEmpty() {
err = result.NextWithContext(ctx)
return
}
return
}
// ListByTypePreparer prepares the ListByType request.
func (client RecordSetsClient) ListByTypePreparer(ctx context.Context, resourceGroupName string, privateZoneName string, recordType RecordType, top *int32, recordsetnamesuffix string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"recordType": autorest.Encode("path", recordType),
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
if top != nil {
queryParameters["$top"] = autorest.Encode("query", *top)
}
if len(recordsetnamesuffix) > 0 {
queryParameters["$recordsetnamesuffix"] = autorest.Encode("query", recordsetnamesuffix)
}
preparer := autorest.CreatePreparer(
autorest.AsGet(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}/{recordType}", pathParameters),
autorest.WithQueryParameters(queryParameters))
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// ListByTypeSender sends the ListByType request. The method will close the
// http.Response Body if it receives an error.
func (client RecordSetsClient) ListByTypeSender(req *http.Request) (*http.Response, error) {
return client.Send(req, azure.DoRetryWithRegistration(client.Client))
}
// ListByTypeResponder handles the response to the ListByType request. The method always
// closes the http.Response Body.
func (client RecordSetsClient) ListByTypeResponder(resp *http.Response) (result RecordSetListResult, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}
// listByTypeNextResults retrieves the next set of results, if any.
func (client RecordSetsClient) listByTypeNextResults(ctx context.Context, lastResults RecordSetListResult) (result RecordSetListResult, err error) {
req, err := lastResults.recordSetListResultPreparer(ctx)
if err != nil {
return result, autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "listByTypeNextResults", nil, "Failure preparing next results request")
}
if req == nil {
return
}
resp, err := client.ListByTypeSender(req)
if err != nil {
result.Response = autorest.Response{Response: resp}
return result, autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "listByTypeNextResults", resp, "Failure sending next results request")
}
result, err = client.ListByTypeResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "listByTypeNextResults", resp, "Failure responding to next results request")
}
return
}
// ListByTypeComplete enumerates all values, automatically crossing page boundaries as required.
func (client RecordSetsClient) ListByTypeComplete(ctx context.Context, resourceGroupName string, privateZoneName string, recordType RecordType, top *int32, recordsetnamesuffix string) (result RecordSetListResultIterator, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.ListByType")
defer func() {
sc := -1
if result.Response().Response.Response != nil {
sc = result.page.Response().Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
result.page, err = client.ListByType(ctx, resourceGroupName, privateZoneName, recordType, top, recordsetnamesuffix)
return
}
// Update updates a record set within a Private DNS zone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// recordType - the type of DNS record in this record set.
// relativeRecordSetName - the name of the record set, relative to the name of the zone.
// parameters - parameters supplied to the Update operation.
// ifMatch - the ETag of the record set. Omit this value to always overwrite the current record set. Specify
// the last-seen ETag value to prevent accidentally overwriting concurrent changes.
func (client RecordSetsClient) Update(ctx context.Context, resourceGroupName string, privateZoneName string, recordType RecordType, relativeRecordSetName string, parameters RecordSet, ifMatch string) (result RecordSet, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/RecordSetsClient.Update")
defer func() {
sc := -1
if result.Response.Response != nil {
sc = result.Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
req, err := client.UpdatePreparer(ctx, resourceGroupName, privateZoneName, recordType, relativeRecordSetName, parameters, ifMatch)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "Update", nil, "Failure preparing request")
return
}
resp, err := client.UpdateSender(req)
if err != nil {
result.Response = autorest.Response{Response: resp}
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "Update", resp, "Failure sending request")
return
}
result, err = client.UpdateResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.RecordSetsClient", "Update", resp, "Failure responding to request")
return
}
return
}
// UpdatePreparer prepares the Update request.
func (client RecordSetsClient) UpdatePreparer(ctx context.Context, resourceGroupName string, privateZoneName string, recordType RecordType, relativeRecordSetName string, parameters RecordSet, ifMatch string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"recordType": autorest.Encode("path", recordType),
"relativeRecordSetName": relativeRecordSetName,
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsContentType("application/json; charset=utf-8"),
autorest.AsPatch(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}/{recordType}/{relativeRecordSetName}", pathParameters),
autorest.WithJSON(parameters),
autorest.WithQueryParameters(queryParameters))
if len(ifMatch) > 0 {
preparer = autorest.DecoratePreparer(preparer,
autorest.WithHeader("If-Match", autorest.String(ifMatch)))
}
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// UpdateSender sends the Update request. The method will close the
// http.Response Body if it receives an error.
func (client RecordSetsClient) UpdateSender(req *http.Request) (*http.Response, error) {
return client.Send(req, azure.DoRetryWithRegistration(client.Client))
}
// UpdateResponder handles the response to the Update request. The method always
// closes the http.Response Body.
func (client RecordSetsClient) UpdateResponder(resp *http.Response) (result RecordSet, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}

View File

@ -0,0 +1,19 @@
package privatedns
import "github.com/Azure/azure-sdk-for-go/version"
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
// UserAgent returns the UserAgent string to use when sending http.Requests.
func UserAgent() string {
return "Azure-SDK-For-Go/" + Version() + " privatedns/2018-09-01"
}
// Version returns the semantic version (see http://semver.org) of the client.
func Version() string {
return version.Number
}

View File

@ -0,0 +1,506 @@
package privatedns
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
//
// Code generated by Microsoft (R) AutoRest Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
import (
"context"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/tracing"
"net/http"
)
// VirtualNetworkLinksClient is the the Private DNS Management Client.
type VirtualNetworkLinksClient struct {
BaseClient
}
// NewVirtualNetworkLinksClient creates an instance of the VirtualNetworkLinksClient client.
func NewVirtualNetworkLinksClient(subscriptionID string) VirtualNetworkLinksClient {
return NewVirtualNetworkLinksClientWithBaseURI(DefaultBaseURI, subscriptionID)
}
// NewVirtualNetworkLinksClientWithBaseURI creates an instance of the VirtualNetworkLinksClient client using a custom
// endpoint. Use this when interacting with an Azure cloud that uses a non-standard base URI (sovereign clouds, Azure
// stack).
func NewVirtualNetworkLinksClientWithBaseURI(baseURI string, subscriptionID string) VirtualNetworkLinksClient {
return VirtualNetworkLinksClient{NewWithBaseURI(baseURI, subscriptionID)}
}
// CreateOrUpdate creates or updates a virtual network link to the specified Private DNS zone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// virtualNetworkLinkName - the name of the virtual network link.
// parameters - parameters supplied to the CreateOrUpdate operation.
// ifMatch - the ETag of the virtual network link to the Private DNS zone. Omit this value to always overwrite
// the current virtual network link. Specify the last-seen ETag value to prevent accidentally overwriting any
// concurrent changes.
// ifNoneMatch - set to '*' to allow a new virtual network link to the Private DNS zone to be created, but to
// prevent updating an existing link. Other values will be ignored.
func (client VirtualNetworkLinksClient) CreateOrUpdate(ctx context.Context, resourceGroupName string, privateZoneName string, virtualNetworkLinkName string, parameters VirtualNetworkLink, ifMatch string, ifNoneMatch string) (result VirtualNetworkLinksCreateOrUpdateFuture, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/VirtualNetworkLinksClient.CreateOrUpdate")
defer func() {
sc := -1
if result.FutureAPI != nil && result.FutureAPI.Response() != nil {
sc = result.FutureAPI.Response().StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
req, err := client.CreateOrUpdatePreparer(ctx, resourceGroupName, privateZoneName, virtualNetworkLinkName, parameters, ifMatch, ifNoneMatch)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "CreateOrUpdate", nil, "Failure preparing request")
return
}
result, err = client.CreateOrUpdateSender(req)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "CreateOrUpdate", nil, "Failure sending request")
return
}
return
}
// CreateOrUpdatePreparer prepares the CreateOrUpdate request.
func (client VirtualNetworkLinksClient) CreateOrUpdatePreparer(ctx context.Context, resourceGroupName string, privateZoneName string, virtualNetworkLinkName string, parameters VirtualNetworkLink, ifMatch string, ifNoneMatch string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
"virtualNetworkLinkName": autorest.Encode("path", virtualNetworkLinkName),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsContentType("application/json; charset=utf-8"),
autorest.AsPut(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}/virtualNetworkLinks/{virtualNetworkLinkName}", pathParameters),
autorest.WithJSON(parameters),
autorest.WithQueryParameters(queryParameters))
if len(ifMatch) > 0 {
preparer = autorest.DecoratePreparer(preparer,
autorest.WithHeader("If-Match", autorest.String(ifMatch)))
}
if len(ifNoneMatch) > 0 {
preparer = autorest.DecoratePreparer(preparer,
autorest.WithHeader("If-None-Match", autorest.String(ifNoneMatch)))
}
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// CreateOrUpdateSender sends the CreateOrUpdate request. The method will close the
// http.Response Body if it receives an error.
func (client VirtualNetworkLinksClient) CreateOrUpdateSender(req *http.Request) (future VirtualNetworkLinksCreateOrUpdateFuture, err error) {
var resp *http.Response
resp, err = client.Send(req, azure.DoRetryWithRegistration(client.Client))
if err != nil {
return
}
var azf azure.Future
azf, err = azure.NewFutureFromResponse(resp)
future.FutureAPI = &azf
future.Result = future.result
return
}
// CreateOrUpdateResponder handles the response to the CreateOrUpdate request. The method always
// closes the http.Response Body.
func (client VirtualNetworkLinksClient) CreateOrUpdateResponder(resp *http.Response) (result VirtualNetworkLink, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated, http.StatusAccepted),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}
// Delete deletes a virtual network link to the specified Private DNS zone. WARNING: In case of a registration virtual
// network, all auto-registered DNS records in the zone for the virtual network will also be deleted. This operation
// cannot be undone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// virtualNetworkLinkName - the name of the virtual network link.
// ifMatch - the ETag of the virtual network link to the Private DNS zone. Omit this value to always delete the
// current zone. Specify the last-seen ETag value to prevent accidentally deleting any concurrent changes.
func (client VirtualNetworkLinksClient) Delete(ctx context.Context, resourceGroupName string, privateZoneName string, virtualNetworkLinkName string, ifMatch string) (result VirtualNetworkLinksDeleteFuture, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/VirtualNetworkLinksClient.Delete")
defer func() {
sc := -1
if result.FutureAPI != nil && result.FutureAPI.Response() != nil {
sc = result.FutureAPI.Response().StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
req, err := client.DeletePreparer(ctx, resourceGroupName, privateZoneName, virtualNetworkLinkName, ifMatch)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "Delete", nil, "Failure preparing request")
return
}
result, err = client.DeleteSender(req)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "Delete", nil, "Failure sending request")
return
}
return
}
// DeletePreparer prepares the Delete request.
func (client VirtualNetworkLinksClient) DeletePreparer(ctx context.Context, resourceGroupName string, privateZoneName string, virtualNetworkLinkName string, ifMatch string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
"virtualNetworkLinkName": autorest.Encode("path", virtualNetworkLinkName),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsDelete(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}/virtualNetworkLinks/{virtualNetworkLinkName}", pathParameters),
autorest.WithQueryParameters(queryParameters))
if len(ifMatch) > 0 {
preparer = autorest.DecoratePreparer(preparer,
autorest.WithHeader("If-Match", autorest.String(ifMatch)))
}
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// DeleteSender sends the Delete request. The method will close the
// http.Response Body if it receives an error.
func (client VirtualNetworkLinksClient) DeleteSender(req *http.Request) (future VirtualNetworkLinksDeleteFuture, err error) {
var resp *http.Response
resp, err = client.Send(req, azure.DoRetryWithRegistration(client.Client))
if err != nil {
return
}
var azf azure.Future
azf, err = azure.NewFutureFromResponse(resp)
future.FutureAPI = &azf
future.Result = future.result
return
}
// DeleteResponder handles the response to the Delete request. The method always
// closes the http.Response Body.
func (client VirtualNetworkLinksClient) DeleteResponder(resp *http.Response) (result autorest.Response, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusAccepted, http.StatusNoContent),
autorest.ByClosing())
result.Response = resp
return
}
// Get gets a virtual network link to the specified Private DNS zone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// virtualNetworkLinkName - the name of the virtual network link.
func (client VirtualNetworkLinksClient) Get(ctx context.Context, resourceGroupName string, privateZoneName string, virtualNetworkLinkName string) (result VirtualNetworkLink, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/VirtualNetworkLinksClient.Get")
defer func() {
sc := -1
if result.Response.Response != nil {
sc = result.Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
req, err := client.GetPreparer(ctx, resourceGroupName, privateZoneName, virtualNetworkLinkName)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "Get", nil, "Failure preparing request")
return
}
resp, err := client.GetSender(req)
if err != nil {
result.Response = autorest.Response{Response: resp}
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "Get", resp, "Failure sending request")
return
}
result, err = client.GetResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "Get", resp, "Failure responding to request")
return
}
return
}
// GetPreparer prepares the Get request.
func (client VirtualNetworkLinksClient) GetPreparer(ctx context.Context, resourceGroupName string, privateZoneName string, virtualNetworkLinkName string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
"virtualNetworkLinkName": autorest.Encode("path", virtualNetworkLinkName),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsGet(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}/virtualNetworkLinks/{virtualNetworkLinkName}", pathParameters),
autorest.WithQueryParameters(queryParameters))
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// GetSender sends the Get request. The method will close the
// http.Response Body if it receives an error.
func (client VirtualNetworkLinksClient) GetSender(req *http.Request) (*http.Response, error) {
return client.Send(req, azure.DoRetryWithRegistration(client.Client))
}
// GetResponder handles the response to the Get request. The method always
// closes the http.Response Body.
func (client VirtualNetworkLinksClient) GetResponder(resp *http.Response) (result VirtualNetworkLink, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}
// List lists the virtual network links to the specified Private DNS zone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// top - the maximum number of virtual network links to return. If not specified, returns up to 100 virtual
// network links.
func (client VirtualNetworkLinksClient) List(ctx context.Context, resourceGroupName string, privateZoneName string, top *int32) (result VirtualNetworkLinkListResultPage, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/VirtualNetworkLinksClient.List")
defer func() {
sc := -1
if result.vnllr.Response.Response != nil {
sc = result.vnllr.Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
result.fn = client.listNextResults
req, err := client.ListPreparer(ctx, resourceGroupName, privateZoneName, top)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "List", nil, "Failure preparing request")
return
}
resp, err := client.ListSender(req)
if err != nil {
result.vnllr.Response = autorest.Response{Response: resp}
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "List", resp, "Failure sending request")
return
}
result.vnllr, err = client.ListResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "List", resp, "Failure responding to request")
return
}
if result.vnllr.hasNextLink() && result.vnllr.IsEmpty() {
err = result.NextWithContext(ctx)
return
}
return
}
// ListPreparer prepares the List request.
func (client VirtualNetworkLinksClient) ListPreparer(ctx context.Context, resourceGroupName string, privateZoneName string, top *int32) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
if top != nil {
queryParameters["$top"] = autorest.Encode("query", *top)
}
preparer := autorest.CreatePreparer(
autorest.AsGet(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}/virtualNetworkLinks", pathParameters),
autorest.WithQueryParameters(queryParameters))
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// ListSender sends the List request. The method will close the
// http.Response Body if it receives an error.
func (client VirtualNetworkLinksClient) ListSender(req *http.Request) (*http.Response, error) {
return client.Send(req, azure.DoRetryWithRegistration(client.Client))
}
// ListResponder handles the response to the List request. The method always
// closes the http.Response Body.
func (client VirtualNetworkLinksClient) ListResponder(resp *http.Response) (result VirtualNetworkLinkListResult, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}
// listNextResults retrieves the next set of results, if any.
func (client VirtualNetworkLinksClient) listNextResults(ctx context.Context, lastResults VirtualNetworkLinkListResult) (result VirtualNetworkLinkListResult, err error) {
req, err := lastResults.virtualNetworkLinkListResultPreparer(ctx)
if err != nil {
return result, autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "listNextResults", nil, "Failure preparing next results request")
}
if req == nil {
return
}
resp, err := client.ListSender(req)
if err != nil {
result.Response = autorest.Response{Response: resp}
return result, autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "listNextResults", resp, "Failure sending next results request")
}
result, err = client.ListResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "listNextResults", resp, "Failure responding to next results request")
}
return
}
// ListComplete enumerates all values, automatically crossing page boundaries as required.
func (client VirtualNetworkLinksClient) ListComplete(ctx context.Context, resourceGroupName string, privateZoneName string, top *int32) (result VirtualNetworkLinkListResultIterator, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/VirtualNetworkLinksClient.List")
defer func() {
sc := -1
if result.Response().Response.Response != nil {
sc = result.page.Response().Response.Response.StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
result.page, err = client.List(ctx, resourceGroupName, privateZoneName, top)
return
}
// Update updates a virtual network link to the specified Private DNS zone.
// Parameters:
// resourceGroupName - the name of the resource group.
// privateZoneName - the name of the Private DNS zone (without a terminating dot).
// virtualNetworkLinkName - the name of the virtual network link.
// parameters - parameters supplied to the Update operation.
// ifMatch - the ETag of the virtual network link to the Private DNS zone. Omit this value to always overwrite
// the current virtual network link. Specify the last-seen ETag value to prevent accidentally overwriting any
// concurrent changes.
func (client VirtualNetworkLinksClient) Update(ctx context.Context, resourceGroupName string, privateZoneName string, virtualNetworkLinkName string, parameters VirtualNetworkLink, ifMatch string) (result VirtualNetworkLinksUpdateFuture, err error) {
if tracing.IsEnabled() {
ctx = tracing.StartSpan(ctx, fqdn+"/VirtualNetworkLinksClient.Update")
defer func() {
sc := -1
if result.FutureAPI != nil && result.FutureAPI.Response() != nil {
sc = result.FutureAPI.Response().StatusCode
}
tracing.EndSpan(ctx, sc, err)
}()
}
req, err := client.UpdatePreparer(ctx, resourceGroupName, privateZoneName, virtualNetworkLinkName, parameters, ifMatch)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "Update", nil, "Failure preparing request")
return
}
result, err = client.UpdateSender(req)
if err != nil {
err = autorest.NewErrorWithError(err, "privatedns.VirtualNetworkLinksClient", "Update", nil, "Failure sending request")
return
}
return
}
// UpdatePreparer prepares the Update request.
func (client VirtualNetworkLinksClient) UpdatePreparer(ctx context.Context, resourceGroupName string, privateZoneName string, virtualNetworkLinkName string, parameters VirtualNetworkLink, ifMatch string) (*http.Request, error) {
pathParameters := map[string]interface{}{
"privateZoneName": autorest.Encode("path", privateZoneName),
"resourceGroupName": autorest.Encode("path", resourceGroupName),
"subscriptionId": autorest.Encode("path", client.SubscriptionID),
"virtualNetworkLinkName": autorest.Encode("path", virtualNetworkLinkName),
}
const APIVersion = "2018-09-01"
queryParameters := map[string]interface{}{
"api-version": APIVersion,
}
preparer := autorest.CreatePreparer(
autorest.AsContentType("application/json; charset=utf-8"),
autorest.AsPatch(),
autorest.WithBaseURL(client.BaseURI),
autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/privateDnsZones/{privateZoneName}/virtualNetworkLinks/{virtualNetworkLinkName}", pathParameters),
autorest.WithJSON(parameters),
autorest.WithQueryParameters(queryParameters))
if len(ifMatch) > 0 {
preparer = autorest.DecoratePreparer(preparer,
autorest.WithHeader("If-Match", autorest.String(ifMatch)))
}
return preparer.Prepare((&http.Request{}).WithContext(ctx))
}
// UpdateSender sends the Update request. The method will close the
// http.Response Body if it receives an error.
func (client VirtualNetworkLinksClient) UpdateSender(req *http.Request) (future VirtualNetworkLinksUpdateFuture, err error) {
var resp *http.Response
resp, err = client.Send(req, azure.DoRetryWithRegistration(client.Client))
if err != nil {
return
}
var azf azure.Future
azf, err = azure.NewFutureFromResponse(resp)
future.FutureAPI = &azf
future.Result = future.result
return
}
// UpdateResponder handles the response to the Update request. The method always
// closes the http.Response Body.
func (client VirtualNetworkLinksClient) UpdateResponder(resp *http.Response) (result VirtualNetworkLink, err error) {
err = autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusAccepted),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return
}

View File

@ -9,6 +9,7 @@ github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2019-05-01/con
github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2020-04-01/containerservice
github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network
github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network
github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns
github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2017-05-10/resources
github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage
github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-02-01/storage
@ -173,8 +174,6 @@ github.com/containerd/containerd/api/types
github.com/containerd/containerd/api/types/task
# github.com/containerd/ttrpc v1.0.2
github.com/containerd/ttrpc
# github.com/containernetworking/cni v0.8.1
## explicit
# github.com/coreos/go-semver v0.3.0
github.com/coreos/go-semver/semver
# github.com/coreos/go-systemd/v22 v22.3.2
@ -1803,23 +1802,50 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client
sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client
# sigs.k8s.io/cloud-provider-azure v1.23.2
## explicit
sigs.k8s.io/cloud-provider-azure/pkg/auth
sigs.k8s.io/cloud-provider-azure/pkg/azureclients
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/containerserviceclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/containerserviceclient/mockcontainerserviceclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/deploymentclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/diskclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/diskclient/mockdiskclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/fileclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/interfaceclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/interfaceclient/mockinterfaceclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/mockloadbalancerclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privatednsclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privatednszonegroupclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/privateendpointclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient/mockpublicipclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routeclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routeclient/mockrouteclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routetableclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routetableclient/mockroutetableclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/securitygroupclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/securitygroupclient/mocksecuritygroupclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/snapshotclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/snapshotclient/mocksnapshotclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/storageaccountclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/storageaccountclient/mockstorageaccountclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/subnetclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/subnetclient/mocksubnetclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/virtualnetworklinksclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmasclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmclient/mockvmclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmsizeclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssclient/mockvmssclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssvmclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssvmclient/mockvmssvmclient
sigs.k8s.io/cloud-provider-azure/pkg/azureclients/zoneclient
sigs.k8s.io/cloud-provider-azure/pkg/cache
sigs.k8s.io/cloud-provider-azure/pkg/consts
sigs.k8s.io/cloud-provider-azure/pkg/metrics
sigs.k8s.io/cloud-provider-azure/pkg/provider
sigs.k8s.io/cloud-provider-azure/pkg/retry
sigs.k8s.io/cloud-provider-azure/pkg/version
# sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2

View File

@ -0,0 +1,290 @@
/*
Copyright 2020 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 auth
import (
"crypto/rsa"
"crypto/x509"
"fmt"
"io/ioutil"
"strings"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure"
"golang.org/x/crypto/pkcs12"
"k8s.io/klog/v2"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
var (
// ErrorNoAuth indicates that no credentials are provided.
ErrorNoAuth = fmt.Errorf("no credentials provided for Azure cloud provider")
)
// AzureAuthConfig holds auth related part of cloud config
type AzureAuthConfig struct {
// The cloud environment identifier. Takes values from https://github.com/Azure/go-autorest/blob/ec5f4903f77ed9927ac95b19ab8e44ada64c1356/autorest/azure/environments.go#L13
Cloud string `json:"cloud,omitempty" yaml:"cloud,omitempty"`
// The AAD Tenant ID for the Subscription that the cluster is deployed in
TenantID string `json:"tenantId,omitempty" yaml:"tenantId,omitempty"`
// The ClientID for an AAD application with RBAC access to talk to Azure RM APIs
AADClientID string `json:"aadClientId,omitempty" yaml:"aadClientId,omitempty"`
// The ClientSecret for an AAD application with RBAC access to talk to Azure RM APIs
AADClientSecret string `json:"aadClientSecret,omitempty" yaml:"aadClientSecret,omitempty" datapolicy:"token"`
// The path of a client certificate for an AAD application with RBAC access to talk to Azure RM APIs
AADClientCertPath string `json:"aadClientCertPath,omitempty" yaml:"aadClientCertPath,omitempty"`
// The password of the client certificate for an AAD application with RBAC access to talk to Azure RM APIs
AADClientCertPassword string `json:"aadClientCertPassword,omitempty" yaml:"aadClientCertPassword,omitempty" datapolicy:"password"`
// Use managed service identity for the virtual machine to access Azure ARM APIs
UseManagedIdentityExtension bool `json:"useManagedIdentityExtension,omitempty" yaml:"useManagedIdentityExtension,omitempty"`
// UserAssignedIdentityID contains the Client ID of the user assigned MSI which is assigned to the underlying VMs. If empty the user assigned identity is not used.
// More details of the user assigned identity can be found at: https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview
// For the user assigned identity specified here to be used, the UseManagedIdentityExtension has to be set to true.
UserAssignedIdentityID string `json:"userAssignedIdentityID,omitempty" yaml:"userAssignedIdentityID,omitempty"`
// The ID of the Azure Subscription that the cluster is deployed in
SubscriptionID string `json:"subscriptionId,omitempty" yaml:"subscriptionId,omitempty"`
// IdentitySystem indicates the identity provider. Relevant only to hybrid clouds (Azure Stack).
// Allowed values are 'azure_ad' (default), 'adfs'.
IdentitySystem string `json:"identitySystem,omitempty" yaml:"identitySystem,omitempty"`
// ResourceManagerEndpoint is the cloud's resource manager endpoint. If set, cloud provider queries this endpoint
// in order to generate an autorest.Environment instance instead of using one of the pre-defined Environments.
ResourceManagerEndpoint string `json:"resourceManagerEndpoint,omitempty" yaml:"resourceManagerEndpoint,omitempty"`
// The AAD Tenant ID for the Subscription that the network resources are deployed in
NetworkResourceTenantID string `json:"networkResourceTenantID,omitempty" yaml:"networkResourceTenantID,omitempty"`
// The ID of the Azure Subscription that the network resources are deployed in
NetworkResourceSubscriptionID string `json:"networkResourceSubscriptionID,omitempty" yaml:"networkResourceSubscriptionID,omitempty"`
}
// GetServicePrincipalToken creates a new service principal token based on the configuration.
//
// By default, the cluster and its network resources are deployed in the same AAD Tenant and Subscription,
// and all azure clients use this method to fetch Service Principal Token.
//
// If NetworkResourceTenantID and NetworkResourceSubscriptionID are specified to have different values than TenantID and SubscriptionID, network resources are deployed in different AAD Tenant and Subscription than those for the cluster,
// than only azure clients except VM/VMSS and network resource ones use this method to fetch Token.
// For tokens for VM/VMSS and network resource ones, please check GetMultiTenantServicePrincipalToken and GetNetworkResourceServicePrincipalToken.
func GetServicePrincipalToken(config *AzureAuthConfig, env *azure.Environment, resource string) (*adal.ServicePrincipalToken, error) {
var tenantID string
if strings.EqualFold(config.IdentitySystem, consts.ADFSIdentitySystem) {
tenantID = consts.ADFSIdentitySystem
} else {
tenantID = config.TenantID
}
if resource == "" {
resource = env.ServiceManagementEndpoint
}
if config.UseManagedIdentityExtension {
klog.V(2).Infoln("azure: using managed identity extension to retrieve access token")
msiEndpoint, err := adal.GetMSIVMEndpoint()
if err != nil {
return nil, fmt.Errorf("error getting the managed service identity endpoint: %w", err)
}
if len(config.UserAssignedIdentityID) > 0 {
klog.V(4).Info("azure: using User Assigned MSI ID to retrieve access token")
return adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint,
resource,
config.UserAssignedIdentityID)
}
klog.V(4).Info("azure: using System Assigned MSI to retrieve access token")
return adal.NewServicePrincipalTokenFromMSI(
msiEndpoint,
resource)
}
oauthConfig, err := adal.NewOAuthConfigWithAPIVersion(env.ActiveDirectoryEndpoint, tenantID, nil)
if err != nil {
return nil, fmt.Errorf("error creating the OAuth config: %w", err)
}
if len(config.AADClientSecret) > 0 {
klog.V(2).Infoln("azure: using client_id+client_secret to retrieve access token")
return adal.NewServicePrincipalToken(
*oauthConfig,
config.AADClientID,
config.AADClientSecret,
resource)
}
if len(config.AADClientCertPath) > 0 && len(config.AADClientCertPassword) > 0 {
klog.V(2).Infoln("azure: using jwt client_assertion (client_cert+client_private_key) to retrieve access token")
certData, err := ioutil.ReadFile(config.AADClientCertPath)
if err != nil {
return nil, fmt.Errorf("reading the client certificate from file %s: %w", config.AADClientCertPath, err)
}
certificate, privateKey, err := decodePkcs12(certData, config.AADClientCertPassword)
if err != nil {
return nil, fmt.Errorf("decoding the client certificate: %w", err)
}
return adal.NewServicePrincipalTokenFromCertificate(
*oauthConfig,
config.AADClientID,
certificate,
privateKey,
resource)
}
return nil, ErrorNoAuth
}
// GetMultiTenantServicePrincipalToken is used when (and only when) NetworkResourceTenantID and NetworkResourceSubscriptionID are specified to have different values than TenantID and SubscriptionID.
//
// In that scenario, network resources are deployed in different AAD Tenant and Subscription than those for the cluster,
// and this method creates a new multi-tenant service principal token based on the configuration.
//
// PrimaryToken of the returned multi-tenant token is for the AAD Tenant specified by TenantID, and AuxiliaryToken of the returned multi-tenant token is for the AAD Tenant specified by NetworkResourceTenantID.
//
// Azure VM/VMSS clients use this multi-tenant token, in order to operate those VM/VMSS in AAD Tenant specified by TenantID, and meanwhile in their payload they are referencing network resources (e.g. Load Balancer, Network Security Group, etc.) in AAD Tenant specified by NetworkResourceTenantID.
func GetMultiTenantServicePrincipalToken(config *AzureAuthConfig, env *azure.Environment) (*adal.MultiTenantServicePrincipalToken, error) {
err := config.checkConfigWhenNetworkResourceInDifferentTenant()
if err != nil {
return nil, fmt.Errorf("got error getting multi-tenant service principal token: %w", err)
}
multiTenantOAuthConfig, err := adal.NewMultiTenantOAuthConfig(
env.ActiveDirectoryEndpoint, config.TenantID, []string{config.NetworkResourceTenantID}, adal.OAuthOptions{})
if err != nil {
return nil, fmt.Errorf("creating the multi-tenant OAuth config: %w", err)
}
if len(config.AADClientSecret) > 0 {
klog.V(2).Infoln("azure: using client_id+client_secret to retrieve multi-tenant access token")
return adal.NewMultiTenantServicePrincipalToken(
multiTenantOAuthConfig,
config.AADClientID,
config.AADClientSecret,
env.ServiceManagementEndpoint)
}
if len(config.AADClientCertPath) > 0 && len(config.AADClientCertPassword) > 0 {
return nil, fmt.Errorf("AAD Application client certificate authentication is not supported in getting multi-tenant service principal token")
}
return nil, ErrorNoAuth
}
// GetNetworkResourceServicePrincipalToken is used when (and only when) NetworkResourceTenantID and NetworkResourceSubscriptionID are specified to have different values than TenantID and SubscriptionID.
//
// In that scenario, network resources are deployed in different AAD Tenant and Subscription than those for the cluster,
// and this method creates a new service principal token for network resources tenant based on the configuration.
//
// Azure network resource (Load Balancer, Public IP, Route Table, Network Security Group and their sub level resources) clients use this multi-tenant token, in order to operate resources in AAD Tenant specified by NetworkResourceTenantID.
func GetNetworkResourceServicePrincipalToken(config *AzureAuthConfig, env *azure.Environment) (*adal.ServicePrincipalToken, error) {
err := config.checkConfigWhenNetworkResourceInDifferentTenant()
if err != nil {
return nil, fmt.Errorf("got error(%w) in getting network resources service principal token", err)
}
oauthConfig, err := adal.NewOAuthConfigWithAPIVersion(env.ActiveDirectoryEndpoint, config.NetworkResourceTenantID, nil)
if err != nil {
return nil, fmt.Errorf("creating the OAuth config for network resources tenant: %w", err)
}
if len(config.AADClientSecret) > 0 {
klog.V(2).Infoln("azure: using client_id+client_secret to retrieve access token for network resources tenant")
return adal.NewServicePrincipalToken(
*oauthConfig,
config.AADClientID,
config.AADClientSecret,
env.ServiceManagementEndpoint)
}
if len(config.AADClientCertPath) > 0 && len(config.AADClientCertPassword) > 0 {
return nil, fmt.Errorf("AAD Application client certificate authentication is not supported in getting network resources service principal token")
}
return nil, ErrorNoAuth
}
// ParseAzureEnvironment returns the azure environment.
// If 'resourceManagerEndpoint' is set, the environment is computed by querying the cloud's resource manager endpoint.
// Otherwise, a pre-defined Environment is looked up by name.
func ParseAzureEnvironment(cloudName, resourceManagerEndpoint, identitySystem string) (*azure.Environment, error) {
var env azure.Environment
var err error
if resourceManagerEndpoint != "" {
klog.V(4).Infof("Loading environment from resource manager endpoint: %s", resourceManagerEndpoint)
nameOverride := azure.OverrideProperty{Key: azure.EnvironmentName, Value: cloudName}
env, err = azure.EnvironmentFromURL(resourceManagerEndpoint, nameOverride)
if err == nil {
azureStackOverrides(&env, resourceManagerEndpoint, identitySystem)
}
} else if cloudName == "" {
klog.V(4).Info("Using public cloud environment")
env = azure.PublicCloud
} else {
klog.V(4).Infof("Using %s environment", cloudName)
env, err = azure.EnvironmentFromName(cloudName)
}
return &env, err
}
// UsesNetworkResourceInDifferentTenantOrSubscription determines whether the AzureAuthConfig indicates to use network resources in different AAD Tenant and Subscription than those for the cluster
// Return true when one of NetworkResourceTenantID and NetworkResourceSubscriptionID are specified
// and equal to one defined in global configs
func (config *AzureAuthConfig) UsesNetworkResourceInDifferentTenantOrSubscription() bool {
return (len(config.NetworkResourceTenantID) > 0 && !strings.EqualFold(config.NetworkResourceTenantID, config.TenantID)) ||
(len(config.NetworkResourceSubscriptionID) > 0 && !strings.EqualFold(config.NetworkResourceSubscriptionID, config.SubscriptionID))
}
// decodePkcs12 decodes a PKCS#12 client certificate by extracting the public certificate and
// the private RSA key
func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {
privateKey, certificate, err := pkcs12.Decode(pkcs, password)
if err != nil {
return nil, nil, fmt.Errorf("decoding the PKCS#12 client certificate: %w", err)
}
rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey)
if !isRsaKey {
return nil, nil, fmt.Errorf("PKCS#12 certificate must contain a RSA private key")
}
return certificate, rsaPrivateKey, nil
}
// azureStackOverrides ensures that the Environment matches what AKSe currently generates for Azure Stack
func azureStackOverrides(env *azure.Environment, resourceManagerEndpoint, identitySystem string) {
env.ManagementPortalURL = strings.Replace(resourceManagerEndpoint, "https://management.", "https://portal.", -1)
env.ServiceManagementEndpoint = env.TokenAudience
env.ResourceManagerVMDNSSuffix = strings.Replace(resourceManagerEndpoint, "https://management.", "cloudapp.", -1)
env.ResourceManagerVMDNSSuffix = strings.TrimSuffix(env.ResourceManagerVMDNSSuffix, "/")
if strings.EqualFold(identitySystem, consts.ADFSIdentitySystem) {
env.ActiveDirectoryEndpoint = strings.TrimSuffix(env.ActiveDirectoryEndpoint, "/")
env.ActiveDirectoryEndpoint = strings.TrimSuffix(env.ActiveDirectoryEndpoint, "adfs")
}
}
// checkConfigWhenNetworkResourceInDifferentTenant checks configuration for the scenario of using network resource in different tenant
func (config *AzureAuthConfig) checkConfigWhenNetworkResourceInDifferentTenant() error {
if !config.UsesNetworkResourceInDifferentTenantOrSubscription() {
return fmt.Errorf("NetworkResourceTenantID and NetworkResourceSubscriptionID must be configured")
}
if strings.EqualFold(config.IdentitySystem, consts.ADFSIdentitySystem) {
return fmt.Errorf("ADFS identity system is not supported")
}
if config.UseManagedIdentityExtension {
return fmt.Errorf("managed identity is not supported")
}
return nil
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 auth provides a general library to authorize Azure ARM clients.
package auth // import "sigs.k8s.io/cloud-provider-azure/pkg/auth"

View File

@ -0,0 +1,459 @@
/*
Copyright 2020 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 deploymentclient
import (
"context"
"fmt"
"net/http"
"time"
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2017-05-10/resources"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
var _ Interface = &Client{}
// Client implements ContainerService client Interface.
type Client struct {
armClient armclient.Interface
subscriptionID string
// Rate limiting configures.
rateLimiterReader flowcontrol.RateLimiter
rateLimiterWriter flowcontrol.RateLimiter
// ARM throttling configures.
RetryAfterReader time.Time
RetryAfterWriter time.Time
}
// New creates a new ContainerServiceClient client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := config.Authorizer
armClient := armclient.New(authorizer, *config, baseURI, APIVersion)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
if azclients.RateLimitEnabled(config.RateLimitConfig) {
klog.V(2).Infof("Azure DeploymentClient (read ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPS,
config.RateLimitConfig.CloudProviderRateLimitBucket)
klog.V(2).Infof("Azure DeploymentClient (write ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPSWrite,
config.RateLimitConfig.CloudProviderRateLimitBucketWrite)
}
client := &Client{
armClient: armClient,
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
subscriptionID: config.SubscriptionID,
}
return client
}
// Get gets a deployment
func (c *Client) Get(ctx context.Context, resourceGroupName string, deploymentName string) (resources.DeploymentExtended, *retry.Error) {
mc := metrics.NewMetricContext("deployments", "get", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return resources.DeploymentExtended{}, retry.GetRateLimitError(false, "GetDeployment")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("GetDeployment", "client throttled", c.RetryAfterReader)
return resources.DeploymentExtended{}, rerr
}
result, rerr := c.getDeployment(ctx, resourceGroupName, deploymentName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// getDeployment gets a deployment.
func (c *Client) getDeployment(ctx context.Context, resourceGroupName string, deploymentName string) (resources.DeploymentExtended, *retry.Error) {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Resources/deployments",
deploymentName,
)
result := resources.DeploymentExtended{}
response, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deployment.get.request", resourceID, rerr.Error())
return result, rerr
}
err := autorest.Respond(
response,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deployment.get.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}
// List gets a list of deployments in the resource group.
func (c *Client) List(ctx context.Context, resourceGroupName string) ([]resources.DeploymentExtended, *retry.Error) {
mc := metrics.NewMetricContext("deployments", "list", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return nil, retry.GetRateLimitError(false, "ListDeployment")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("ListDeployment", "client throttled", c.RetryAfterReader)
return nil, rerr
}
result, rerr := c.listDeployment(ctx, resourceGroupName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// listDeployment gets a list of deployments in the resource group.
func (c *Client) listDeployment(ctx context.Context, resourceGroupName string) ([]resources.DeploymentExtended, *retry.Error) {
resourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Resources/deployments",
autorest.Encode("path", c.subscriptionID),
autorest.Encode("path", resourceGroupName))
result := make([]resources.DeploymentExtended, 0)
page := &DeploymentResultPage{}
page.fn = c.listNextResults
resp, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deployment.list.request", resourceID, rerr.Error())
return result, rerr
}
var err error
page.dplr, err = c.listResponder(resp)
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deployment.list.respond", resourceID, err)
return result, retry.GetError(resp, err)
}
for {
result = append(result, page.Values()...)
// Abort the loop when there's no nextLink in the response.
if to.String(page.Response().NextLink) == "" {
break
}
if err = page.NextWithContext(ctx); err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deployment.list.next", resourceID, err)
return result, retry.GetError(page.Response().Response.Response, err)
}
}
return result, nil
}
func (c *Client) listResponder(resp *http.Response) (result resources.DeploymentListResult, err error) {
err = autorest.Respond(
resp,
autorest.ByIgnoring(),
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return
}
// deploymentListResultPreparer prepares a request to retrieve the next set of results.
// It returns nil if no more results exist.
func (c *Client) deploymentListResultPreparer(ctx context.Context, dplr resources.DeploymentListResult) (*http.Request, error) {
if dplr.NextLink == nil || len(to.String(dplr.NextLink)) < 1 {
return nil, nil
}
decorators := []autorest.PrepareDecorator{
autorest.WithBaseURL(to.String(dplr.NextLink)),
}
return c.armClient.PrepareGetRequest(ctx, decorators...)
}
// listNextResults retrieves the next set of results, if any.
func (c *Client) listNextResults(ctx context.Context, lastResults resources.DeploymentListResult) (result resources.DeploymentListResult, err error) {
req, err := c.deploymentListResultPreparer(ctx, lastResults)
if err != nil {
return result, autorest.NewErrorWithError(err, "deploymentclient", "listNextResults", nil, "Failure preparing next results request")
}
if req == nil {
return
}
resp, rerr := c.armClient.Send(ctx, req)
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
result.Response = autorest.Response{Response: resp}
return result, autorest.NewErrorWithError(rerr.Error(), "deploymentclient", "listNextResults", resp, "Failure sending next results request")
}
result, err = c.listResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "deploymentclient", "listNextResults", resp, "Failure responding to next results request")
}
return
}
// DeploymentResultPage contains a page of deployments values.
type DeploymentResultPage struct {
fn func(context.Context, resources.DeploymentListResult) (resources.DeploymentListResult, error)
dplr resources.DeploymentListResult
}
// NextWithContext advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
func (page *DeploymentResultPage) NextWithContext(ctx context.Context) (err error) {
next, err := page.fn(ctx, page.dplr)
if err != nil {
return err
}
page.dplr = next
return nil
}
// Next advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
// Deprecated: Use NextWithContext() instead.
func (page *DeploymentResultPage) Next() error {
return page.NextWithContext(context.Background())
}
// NotDone returns true if the page enumeration should be started or is not yet complete.
func (page DeploymentResultPage) NotDone() bool {
return !page.dplr.IsEmpty()
}
// Response returns the raw server response from the last page request.
func (page DeploymentResultPage) Response() resources.DeploymentListResult {
return page.dplr
}
// Values returns the slice of values for the current page or nil if there are no values.
func (page DeploymentResultPage) Values() []resources.DeploymentExtended {
if page.dplr.IsEmpty() {
return nil
}
return *page.dplr.Value
}
// CreateOrUpdate creates or updates a deployment.
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, deploymentName string, parameters resources.Deployment, etag string) *retry.Error {
mc := metrics.NewMetricContext("deployments", "create_or_update", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "CreateOrUpdateDeployment")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("CreateOrUpdateDeployment", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.createOrUpdateDeployment(ctx, resourceGroupName, deploymentName, parameters, etag)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
func (c *Client) createOrUpdateDeployment(ctx context.Context, resourceGroupName string, deploymentName string, parameters resources.Deployment, etag string) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Resources/deployments",
deploymentName,
)
decorators := []autorest.PrepareDecorator{
autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}),
autorest.WithJSON(parameters),
}
if etag != "" {
decorators = append(decorators, autorest.WithHeader("If-Match", autorest.String(etag)))
}
response, rerr := c.armClient.PutResourceWithDecorators(ctx, resourceID, parameters, decorators)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deployment.put.request", resourceID, rerr.Error())
return rerr
}
if response != nil && response.StatusCode != http.StatusNoContent {
_, rerr = c.createOrUpdateResponder(response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deployment.put.respond", resourceID, rerr.Error())
return rerr
}
}
return nil
}
func (c *Client) createOrUpdateResponder(resp *http.Response) (*resources.DeploymentExtended, *retry.Error) {
result := &resources.DeploymentExtended{}
err := autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return result, retry.GetError(resp, err)
}
// Delete deletes a deployment by name.
func (c *Client) Delete(ctx context.Context, resourceGroupName string, deploymentName string) *retry.Error {
mc := metrics.NewMetricContext("deployments", "delete", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "DeleteDeployment")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("DeleteDeployment", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.deleteDeployment(ctx, resourceGroupName, deploymentName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// deleteDeployment deletes a deployment by name.
func (c *Client) deleteDeployment(ctx context.Context, resourceGroupName string, deploymentName string) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Resources/deployments",
deploymentName,
)
return c.armClient.DeleteResource(ctx, resourceID, "")
}
// ExportTemplate exports the template used for specified deployment
func (c *Client) ExportTemplate(ctx context.Context, resourceGroupName string, deploymentName string) (result resources.DeploymentExportResult, rerr *retry.Error) {
mc := metrics.NewMetricContext("deployments", "export_template", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return resources.DeploymentExportResult{}, retry.GetRateLimitError(true, "ExportTemplateDeployment")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("CreateOrUpdateDeployment", "client throttled", c.RetryAfterWriter)
return resources.DeploymentExportResult{}, rerr
}
resourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Resources/deployments/%s/exportTemplate",
autorest.Encode("path", c.subscriptionID),
autorest.Encode("path", resourceGroupName),
autorest.Encode("path", deploymentName))
response, rerr := c.armClient.PostResource(ctx, resourceID, "exportTemplate", struct{}{}, map[string]interface{}{})
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deployment.exportTemplate.request", resourceID, rerr.Error())
return
}
err := autorest.Respond(
response,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "deployment.exportTemplate.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, rerr
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 deploymentclient implements the client for azure deployments.
package deploymentclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/deploymentclient"

View File

@ -0,0 +1,40 @@
/*
Copyright 2020 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 deploymentclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2017-05-10/resources"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
const (
// APIVersion is the API version for resources.
APIVersion = "2017-05-10"
)
// Interface is the client interface for Deployments.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
Get(ctx context.Context, resourceGroupName string, deploymentName string) (resources.DeploymentExtended, *retry.Error)
List(ctx context.Context, resourceGroupName string) ([]resources.DeploymentExtended, *retry.Error)
ExportTemplate(ctx context.Context, resourceGroupName string, deploymentName string) (result resources.DeploymentExportResult, rerr *retry.Error)
CreateOrUpdate(ctx context.Context, resourceGroupName string, managedClusterName string, parameters resources.Deployment, etag string) *retry.Error
Delete(ctx context.Context, resourceGroupName string, deploymentName string) *retry.Error
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 mockdiskclient implements the mock client for Disks.
package mockdiskclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/diskclient/mockdiskclient"

View File

@ -0,0 +1,126 @@
// /*
// Copyright 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.
// */
//
// Code generated by MockGen. DO NOT EDIT.
// Source: /go/src/sigs.k8s.io/cloud-provider-azure/pkg/azureclients/diskclient/interface.go
// Package mockdiskclient is a generated GoMock package.
package mockdiskclient
import (
context "context"
reflect "reflect"
compute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
gomock "github.com/golang/mock/gomock"
retry "sigs.k8s.io/cloud-provider-azure/pkg/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
}
// Get mocks base method.
func (m *MockInterface) Get(ctx context.Context, resourceGroupName, diskName string) (compute.Disk, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", ctx, resourceGroupName, diskName)
ret0, _ := ret[0].(compute.Disk)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockInterfaceMockRecorder) Get(ctx, resourceGroupName, diskName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), ctx, resourceGroupName, diskName)
}
// CreateOrUpdate mocks base method.
func (m *MockInterface) CreateOrUpdate(ctx context.Context, resourceGroupName, diskName string, diskParameter compute.Disk) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroupName, diskName, diskParameter)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// CreateOrUpdate indicates an expected call of CreateOrUpdate.
func (mr *MockInterfaceMockRecorder) CreateOrUpdate(ctx, resourceGroupName, diskName, diskParameter interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockInterface)(nil).CreateOrUpdate), ctx, resourceGroupName, diskName, diskParameter)
}
// Update mocks base method.
func (m *MockInterface) Update(ctx context.Context, resourceGroupName, diskName string, diskParameter compute.DiskUpdate) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, resourceGroupName, diskName, diskParameter)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockInterfaceMockRecorder) Update(ctx, resourceGroupName, diskName, diskParameter interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockInterface)(nil).Update), ctx, resourceGroupName, diskName, diskParameter)
}
// Delete mocks base method.
func (m *MockInterface) Delete(ctx context.Context, resourceGroupName, diskName string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, resourceGroupName, diskName)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockInterfaceMockRecorder) Delete(ctx, resourceGroupName, diskName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockInterface)(nil).Delete), ctx, resourceGroupName, diskName)
}
// ListByResourceGroup mocks base method.
func (m *MockInterface) ListByResourceGroup(ctx context.Context, resourceGroupName string) ([]compute.Disk, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListByResourceGroup", ctx, resourceGroupName)
ret0, _ := ret[0].([]compute.Disk)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// ListByResourceGroup indicates an expected call of ListByResourceGroup.
func (mr *MockInterfaceMockRecorder) ListByResourceGroup(ctx, resourceGroupName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByResourceGroup", reflect.TypeOf((*MockInterface)(nil).ListByResourceGroup), ctx, resourceGroupName)
}

View File

@ -0,0 +1,173 @@
/*
Copyright 2020 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 fileclient
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-02-01/storage"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
// Client implements the azure file client interface
type Client struct {
fileSharesClient storage.FileSharesClient
fileServicesClient storage.FileServicesClient
subscriptionID string
}
// ShareOptions contains the fields which are used to create file share.
type ShareOptions struct {
Name string
Protocol storage.EnabledProtocols
RequestGiB int
// supported values: ""(by default), "TransactionOptimized", "Cool", "Hot", "Premium"
AccessTier string
}
// New creates a azure file client
func New(config *azclients.ClientConfig) *Client {
fileSharesClient := storage.NewFileSharesClientWithBaseURI(config.ResourceManagerEndpoint, config.SubscriptionID)
fileSharesClient.Authorizer = config.Authorizer
fileServicesClient := storage.NewFileServicesClientWithBaseURI(config.ResourceManagerEndpoint, config.SubscriptionID)
fileServicesClient.Authorizer = config.Authorizer
return &Client{
fileSharesClient: fileSharesClient,
fileServicesClient: fileServicesClient,
subscriptionID: config.SubscriptionID,
}
}
// CreateFileShare creates a file share
func (c *Client) CreateFileShare(resourceGroupName, accountName string, shareOptions *ShareOptions) error {
mc := metrics.NewMetricContext("file_shares", "create", resourceGroupName, c.subscriptionID, "")
if shareOptions == nil {
return fmt.Errorf("share options is nil")
}
quota := int32(shareOptions.RequestGiB)
fileShareProperties := &storage.FileShareProperties{
ShareQuota: &quota,
}
if shareOptions.Protocol == storage.EnabledProtocolsNFS {
fileShareProperties.EnabledProtocols = shareOptions.Protocol
}
if shareOptions.AccessTier != "" {
fileShareProperties.AccessTier = storage.ShareAccessTier(shareOptions.AccessTier)
}
fileShare := storage.FileShare{
Name: &shareOptions.Name,
FileShareProperties: fileShareProperties,
}
_, err := c.fileSharesClient.Create(context.Background(), resourceGroupName, accountName, shareOptions.Name, fileShare, "")
var rerr *retry.Error
if err != nil {
rerr = &retry.Error{
RawError: err,
}
}
mc.Observe(rerr)
return err
}
// DeleteFileShare deletes a file share
func (c *Client) DeleteFileShare(resourceGroupName, accountName, name string) error {
mc := metrics.NewMetricContext("file_shares", "delete", resourceGroupName, c.subscriptionID, "")
_, err := c.fileSharesClient.Delete(context.Background(), resourceGroupName, accountName, name, "")
var rerr *retry.Error
if err != nil {
rerr = &retry.Error{
RawError: err,
}
}
mc.Observe(rerr)
return err
}
// ResizeFileShare resizes a file share
func (c *Client) ResizeFileShare(resourceGroupName, accountName, name string, sizeGiB int) error {
mc := metrics.NewMetricContext("file_shares", "resize", resourceGroupName, c.subscriptionID, "")
var rerr *retry.Error
quota := int32(sizeGiB)
share, err := c.fileSharesClient.Get(context.Background(), resourceGroupName, accountName, name, storage.GetShareExpandStats, "")
if err != nil {
rerr = &retry.Error{
RawError: err,
}
mc.Observe(rerr)
return fmt.Errorf("failed to get file share (%s): %w", name, err)
}
if *share.FileShareProperties.ShareQuota >= quota {
klog.Warningf("file share size(%dGi) is already greater or equal than requested size(%dGi), accountName: %s, shareName: %s",
share.FileShareProperties.ShareQuota, sizeGiB, accountName, name)
return nil
}
share.FileShareProperties.ShareQuota = &quota
_, err = c.fileSharesClient.Update(context.Background(), resourceGroupName, accountName, name, share)
if err != nil {
rerr = &retry.Error{
RawError: err,
}
mc.Observe(rerr)
return fmt.Errorf("failed to update quota on file share(%s), err: %w", name, err)
}
mc.Observe(rerr)
klog.V(4).Infof("resize file share completed, resourceGroupName(%s), accountName: %s, shareName: %s, sizeGiB: %d", resourceGroupName, accountName, name, sizeGiB)
return nil
}
// GetFileShare gets a file share
func (c *Client) GetFileShare(resourceGroupName, accountName, name string) (storage.FileShare, error) {
mc := metrics.NewMetricContext("file_shares", "get", resourceGroupName, c.subscriptionID, "")
result, err := c.fileSharesClient.Get(context.Background(), resourceGroupName, accountName, name, storage.GetShareExpandStats, "")
var rerr *retry.Error
if err != nil {
rerr = &retry.Error{
RawError: err,
}
}
mc.Observe(rerr)
return result, err
}
// GetServiceProperties get service properties
func (c *Client) GetServiceProperties(resourceGroupName, accountName string) (storage.FileServiceProperties, error) {
return c.fileServicesClient.GetServiceProperties(context.Background(), resourceGroupName, accountName)
}
// SetServiceProperties set service properties
func (c *Client) SetServiceProperties(resourceGroupName, accountName string, parameters storage.FileServiceProperties) (storage.FileServiceProperties, error) {
return c.fileServicesClient.SetServiceProperties(context.Background(), resourceGroupName, accountName, parameters)
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 fileclient implements the client for azure file.
package fileclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/fileclient"

View File

@ -0,0 +1,32 @@
/*
Copyright 2020 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 fileclient
import (
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-02-01/storage"
)
// Interface is the client interface for creating file shares, interface for test injection.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
CreateFileShare(resourceGroupName, accountName string, shareOptions *ShareOptions) error
DeleteFileShare(resourceGroupName, accountName, name string) error
ResizeFileShare(resourceGroupName, accountName, name string, sizeGiB int) error
GetFileShare(resourceGroupName, accountName, name string) (storage.FileShare, error)
GetServiceProperties(resourceGroupName, accountName string) (storage.FileServiceProperties, error)
SetServiceProperties(resourceGroupName, accountName string, parameters storage.FileServiceProperties) (storage.FileServiceProperties, error)
}

View File

@ -0,0 +1,503 @@
/*
Copyright 2020 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 loadbalancerclient
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
var _ Interface = &Client{}
// Client implements LoadBalancer client Interface.
type Client struct {
armClient armclient.Interface
subscriptionID string
cloudName string
// Rate limiting configures.
rateLimiterReader flowcontrol.RateLimiter
rateLimiterWriter flowcontrol.RateLimiter
// ARM throttling configures.
RetryAfterReader time.Time
RetryAfterWriter time.Time
}
// New creates a new LoadBalancer client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := config.Authorizer
apiVersion := APIVersion
if strings.EqualFold(config.CloudName, AzureStackCloudName) && !config.DisableAzureStackCloud {
apiVersion = AzureStackCloudAPIVersion
}
armClient := armclient.New(authorizer, *config, baseURI, apiVersion)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
if azclients.RateLimitEnabled(config.RateLimitConfig) {
klog.V(2).Infof("Azure LoadBalancersClient (read ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPS,
config.RateLimitConfig.CloudProviderRateLimitBucket)
klog.V(2).Infof("Azure LoadBalancersClient (write ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPSWrite,
config.RateLimitConfig.CloudProviderRateLimitBucketWrite)
}
client := &Client{
armClient: armClient,
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
subscriptionID: config.SubscriptionID,
cloudName: config.CloudName,
}
return client
}
// Get gets a LoadBalancer.
func (c *Client) Get(ctx context.Context, resourceGroupName string, loadBalancerName string, expand string) (network.LoadBalancer, *retry.Error) {
mc := metrics.NewMetricContext("load_balancers", "get", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return network.LoadBalancer{}, retry.GetRateLimitError(false, "LBGet")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("LBGet", "client throttled", c.RetryAfterReader)
return network.LoadBalancer{}, rerr
}
result, rerr := c.getLB(ctx, resourceGroupName, loadBalancerName, expand)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// getLB gets a LoadBalancer.
func (c *Client) getLB(ctx context.Context, resourceGroupName string, loadBalancerName string, expand string) (network.LoadBalancer, *retry.Error) {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/loadBalancers",
loadBalancerName,
)
result := network.LoadBalancer{}
response, rerr := c.armClient.GetResource(ctx, resourceID, expand)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "loadbalancer.get.request", resourceID, rerr.Error())
return result, rerr
}
err := autorest.Respond(
response,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "loadbalancer.get.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}
// List gets a list of LoadBalancer in the resource group.
func (c *Client) List(ctx context.Context, resourceGroupName string) ([]network.LoadBalancer, *retry.Error) {
mc := metrics.NewMetricContext("load_balancers", "list", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return nil, retry.GetRateLimitError(false, "LBList")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("LBList", "client throttled", c.RetryAfterReader)
return nil, rerr
}
result, rerr := c.listLB(ctx, resourceGroupName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// listLB gets a list of LoadBalancers in the resource group.
func (c *Client) listLB(ctx context.Context, resourceGroupName string) ([]network.LoadBalancer, *retry.Error) {
resourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers",
autorest.Encode("path", c.subscriptionID),
autorest.Encode("path", resourceGroupName))
result := make([]network.LoadBalancer, 0)
page := &LoadBalancerListResultPage{}
page.fn = c.listNextResults
resp, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "loadbalancer.list.request", resourceID, rerr.Error())
return result, rerr
}
var err error
page.lblr, err = c.listResponder(resp)
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "loadbalancer.list.respond", resourceID, err)
return result, retry.GetError(resp, err)
}
for {
result = append(result, page.Values()...)
// Abort the loop when there's no nextLink in the response.
if to.String(page.Response().NextLink) == "" {
break
}
if err = page.NextWithContext(ctx); err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "loadbalancer.list.next", resourceID, err)
return result, retry.GetError(page.Response().Response.Response, err)
}
}
return result, nil
}
// CreateOrUpdate creates or updates a LoadBalancer.
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, loadBalancerName string, parameters network.LoadBalancer, etag string) *retry.Error {
mc := metrics.NewMetricContext("load_balancers", "create_or_update", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "LBCreateOrUpdate")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("LBCreateOrUpdate", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.createOrUpdateLB(ctx, resourceGroupName, loadBalancerName, parameters, etag)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// createOrUpdateLB creates or updates a LoadBalancer.
func (c *Client) createOrUpdateLB(ctx context.Context, resourceGroupName string, loadBalancerName string, parameters network.LoadBalancer, etag string) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/loadBalancers",
loadBalancerName,
)
decorators := []autorest.PrepareDecorator{
autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}),
autorest.WithJSON(parameters),
}
if etag != "" {
decorators = append(decorators, autorest.WithHeader("If-Match", autorest.String(etag)))
}
response, rerr := c.armClient.PutResourceWithDecorators(ctx, resourceID, parameters, decorators)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "loadbalancer.put.request", resourceID, rerr.Error())
return rerr
}
if response != nil && response.StatusCode != http.StatusNoContent {
_, rerr = c.createOrUpdateResponder(response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "loadbalancer.put.respond", resourceID, rerr.Error())
return rerr
}
}
return nil
}
func (c *Client) createOrUpdateResponder(resp *http.Response) (*network.LoadBalancer, *retry.Error) {
result := &network.LoadBalancer{}
err := autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return result, retry.GetError(resp, err)
}
// Delete deletes a LoadBalancer by name.
func (c *Client) Delete(ctx context.Context, resourceGroupName string, loadBalancerName string) *retry.Error {
mc := metrics.NewMetricContext("load_balancers", "delete", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "LBDelete")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("LBDelete", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.deleteLB(ctx, resourceGroupName, loadBalancerName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// deleteLB deletes a LoadBalancer by name.
func (c *Client) deleteLB(ctx context.Context, resourceGroupName string, loadBalancerName string) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/loadBalancers",
loadBalancerName,
)
return c.armClient.DeleteResource(ctx, resourceID, "")
}
func (c *Client) listResponder(resp *http.Response) (result network.LoadBalancerListResult, err error) {
err = autorest.Respond(
resp,
autorest.ByIgnoring(),
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return
}
// loadBalancerListResultPreparer prepares a request to retrieve the next set of results.
// It returns nil if no more results exist.
func (c *Client) loadBalancerListResultPreparer(ctx context.Context, lblr network.LoadBalancerListResult) (*http.Request, error) {
if lblr.NextLink == nil || len(to.String(lblr.NextLink)) < 1 {
return nil, nil
}
decorators := []autorest.PrepareDecorator{
autorest.WithBaseURL(to.String(lblr.NextLink)),
}
return c.armClient.PrepareGetRequest(ctx, decorators...)
}
// listNextResults retrieves the next set of results, if any.
func (c *Client) listNextResults(ctx context.Context, lastResults network.LoadBalancerListResult) (result network.LoadBalancerListResult, err error) {
req, err := c.loadBalancerListResultPreparer(ctx, lastResults)
if err != nil {
return result, autorest.NewErrorWithError(err, "loadbalancerclient", "listNextResults", nil, "Failure preparing next results request")
}
if req == nil {
return
}
resp, rerr := c.armClient.Send(ctx, req)
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
result.Response = autorest.Response{Response: resp}
return result, autorest.NewErrorWithError(rerr.Error(), "loadbalancerclient", "listNextResults", resp, "Failure sending next results request")
}
result, err = c.listResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "loadbalancerclient", "listNextResults", resp, "Failure responding to next results request")
}
return
}
// LoadBalancerListResultPage contains a page of LoadBalancer values.
type LoadBalancerListResultPage struct {
fn func(context.Context, network.LoadBalancerListResult) (network.LoadBalancerListResult, error)
lblr network.LoadBalancerListResult
}
// NextWithContext advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
func (page *LoadBalancerListResultPage) NextWithContext(ctx context.Context) (err error) {
next, err := page.fn(ctx, page.lblr)
if err != nil {
return err
}
page.lblr = next
return nil
}
// Next advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
// Deprecated: Use NextWithContext() instead.
func (page *LoadBalancerListResultPage) Next() error {
return page.NextWithContext(context.Background())
}
// NotDone returns true if the page enumeration should be started or is not yet complete.
func (page LoadBalancerListResultPage) NotDone() bool {
return !page.lblr.IsEmpty()
}
// Response returns the raw server response from the last page request.
func (page LoadBalancerListResultPage) Response() network.LoadBalancerListResult {
return page.lblr
}
// Values returns the slice of values for the current page or nil if there are no values.
func (page LoadBalancerListResultPage) Values() []network.LoadBalancer {
if page.lblr.IsEmpty() {
return nil
}
return *page.lblr.Value
}
// CreateOrUpdateBackendPools creates or updates a LoadBalancer backend pool.
func (c *Client) CreateOrUpdateBackendPools(ctx context.Context, resourceGroupName string, loadBalancerName string, backendPoolName string, parameters network.BackendAddressPool, etag string) *retry.Error {
mc := metrics.NewMetricContext("load_balancers", "create_or_update_backend_pools", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "LBCreateOrUpdateBackendPools")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("LBCreateOrUpdateBackendPools", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.createOrUpdateLBBackendPool(ctx, resourceGroupName, loadBalancerName, backendPoolName, parameters, etag)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// createOrUpdateLBBackendPool creates or updates a LoadBalancer.
func (c *Client) createOrUpdateLBBackendPool(ctx context.Context, resourceGroupName string, loadBalancerName string, backendPoolName string, parameters network.BackendAddressPool, etag string) *retry.Error {
resourceID := armclient.GetChildResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/loadBalancers",
loadBalancerName,
"backendAddressPools",
backendPoolName,
)
decorators := []autorest.PrepareDecorator{
autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}),
autorest.WithJSON(parameters),
}
if etag != "" {
decorators = append(decorators, autorest.WithHeader("If-Match", autorest.String(etag)))
}
response, rerr := c.armClient.PutResourceWithDecorators(ctx, resourceID, parameters, decorators)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "loadbalancerbackendpool.put.request", resourceID, rerr.Error())
return rerr
}
if response != nil && response.StatusCode != http.StatusNoContent {
_, rerr = c.createOrUpdateBackendPoolResponder(response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "loadbalancerbackendpool.put.respond", resourceID, rerr.Error())
return rerr
}
}
return nil
}
func (c *Client) createOrUpdateBackendPoolResponder(resp *http.Response) (*network.BackendAddressPool, *retry.Error) {
result := &network.BackendAddressPool{}
err := autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return result, retry.GetError(resp, err)
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 loadbalancerclient implements the client for LoadBalancer.
package loadbalancerclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient"

View File

@ -0,0 +1,53 @@
/*
Copyright 2020 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 loadbalancerclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2020-08-01"
// AzureStackCloudAPIVersion is the API version for Azure Stack
AzureStackCloudAPIVersion = "2018-11-01"
// AzureStackCloudName is the cloud name of Azure Stack
AzureStackCloudName = "AZURESTACKCLOUD"
)
// Interface is the client interface for LoadBalancer.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
// Get gets a LoadBalancer.
Get(ctx context.Context, resourceGroupName string, loadBalancerName string, expand string) (result network.LoadBalancer, rerr *retry.Error)
// List gets a list of LoadBalancer in the resource group.
List(ctx context.Context, resourceGroupName string) (result []network.LoadBalancer, rerr *retry.Error)
// CreateOrUpdate creates or updates a LoadBalancer.
CreateOrUpdate(ctx context.Context, resourceGroupName string, loadBalancerName string, parameters network.LoadBalancer, etag string) *retry.Error
// CreateOrUpdateBackendPools creates or updates loadbalancer's backend address pool.
CreateOrUpdateBackendPools(ctx context.Context, resourceGroupName string, loadBalancerName string, backendPoolName string, parameters network.BackendAddressPool, etag string) *retry.Error
// Delete deletes a LoadBalancer by name.
Delete(ctx context.Context, resourceGroupName string, loadBalancerName string) *retry.Error
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 mockloadbalancerclient implements the mock client for LoadBalancer.
package mockloadbalancerclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/mockloadbalancerclient"

View File

@ -0,0 +1,126 @@
// /*
// Copyright 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.
// */
//
// Code generated by MockGen. DO NOT EDIT.
// Source: /go/src/sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/interface.go
// Package mockloadbalancerclient is a generated GoMock package.
package mockloadbalancerclient
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
gomock "github.com/golang/mock/gomock"
retry "sigs.k8s.io/cloud-provider-azure/pkg/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
}
// Get mocks base method.
func (m *MockInterface) Get(ctx context.Context, resourceGroupName, loadBalancerName, expand string) (network.LoadBalancer, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", ctx, resourceGroupName, loadBalancerName, expand)
ret0, _ := ret[0].(network.LoadBalancer)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockInterfaceMockRecorder) Get(ctx, resourceGroupName, loadBalancerName, expand interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), ctx, resourceGroupName, loadBalancerName, expand)
}
// List mocks base method.
func (m *MockInterface) List(ctx context.Context, resourceGroupName string) ([]network.LoadBalancer, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, resourceGroupName)
ret0, _ := ret[0].([]network.LoadBalancer)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// List indicates an expected call of List.
func (mr *MockInterfaceMockRecorder) List(ctx, resourceGroupName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInterface)(nil).List), ctx, resourceGroupName)
}
// CreateOrUpdate mocks base method.
func (m *MockInterface) CreateOrUpdate(ctx context.Context, resourceGroupName, loadBalancerName string, parameters network.LoadBalancer, etag string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroupName, loadBalancerName, parameters, etag)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// CreateOrUpdate indicates an expected call of CreateOrUpdate.
func (mr *MockInterfaceMockRecorder) CreateOrUpdate(ctx, resourceGroupName, loadBalancerName, parameters, etag interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockInterface)(nil).CreateOrUpdate), ctx, resourceGroupName, loadBalancerName, parameters, etag)
}
// CreateOrUpdateBackendPools mocks base method.
func (m *MockInterface) CreateOrUpdateBackendPools(ctx context.Context, resourceGroupName, loadBalancerName, backendPoolName string, parameters network.BackendAddressPool, etag string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdateBackendPools", ctx, resourceGroupName, loadBalancerName, backendPoolName, parameters, etag)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// CreateOrUpdateBackendPools indicates an expected call of CreateOrUpdateBackendPools.
func (mr *MockInterfaceMockRecorder) CreateOrUpdateBackendPools(ctx, resourceGroupName, loadBalancerName, backendPoolName, parameters, etag interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdateBackendPools", reflect.TypeOf((*MockInterface)(nil).CreateOrUpdateBackendPools), ctx, resourceGroupName, loadBalancerName, backendPoolName, parameters, etag)
}
// Delete mocks base method.
func (m *MockInterface) Delete(ctx context.Context, resourceGroupName, loadBalancerName string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, resourceGroupName, loadBalancerName)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockInterfaceMockRecorder) Delete(ctx, resourceGroupName, loadBalancerName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockInterface)(nil).Delete), ctx, resourceGroupName, loadBalancerName)
}

View File

@ -0,0 +1,65 @@
/*
Copyright 2021 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 privatednsclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
)
var _ Interface = &Client{}
// Client implements privatednsclient Interface.
type Client struct {
privateDNSClient privatedns.PrivateZonesClient
}
// New creates a new privatedns client.
func New(config *azclients.ClientConfig) *Client {
privateDNSClient := privatedns.NewPrivateZonesClientWithBaseURI(config.ResourceManagerEndpoint, config.SubscriptionID)
privateDNSClient.Authorizer = config.Authorizer
client := &Client{
privateDNSClient: privateDNSClient,
}
return client
}
// CreateOrUpdate creates or updates a private dns zone
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, privateZoneName string, parameters privatedns.PrivateZone, waitForCompletion bool) error {
createOrUpdateFuture, err := c.privateDNSClient.CreateOrUpdate(ctx, resourceGroupName, privateZoneName, parameters, "", "*")
if err != nil {
klog.V(5).Infof("Received error for %s, resourceGroup: %s, error: %s", "privatedns.put.request", resourceGroupName, err)
return err
}
if waitForCompletion {
err := createOrUpdateFuture.WaitForCompletionRef(ctx, c.privateDNSClient.Client)
if err != nil {
klog.V(5).Infof("Received error while waiting for completion for %s, resourceGroup: %s, error: %s", "privatedns.put.request", resourceGroupName, err)
return err
}
}
return nil
}
func (c *Client) Get(ctx context.Context, resourceGroupName string, privateZoneName string) (result privatedns.PrivateZone, err error) {
return c.privateDNSClient.Get(ctx, resourceGroupName, privateZoneName)
}

View File

@ -0,0 +1,34 @@
/*
Copyright 2021 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 privatednsclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns"
)
// Interface is the client interface for Private DNS Zones
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
//Get gets the PrivateDNSZone
Get(ctx context.Context, resourceGroupName string, privateZoneName string) (result privatedns.PrivateZone, err error)
// CreateOrUpdate creates or updates a private dns zone.
CreateOrUpdate(ctx context.Context, resourceGroupName string, privateZoneName string, parameters privatedns.PrivateZone, waitForCompletion bool) error
}

View File

@ -0,0 +1,64 @@
/*
Copyright 2021 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 privatednszonegroupclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
)
var _ Interface = &Client{}
// Client implements privatednszonegroupclient client Interface.
type Client struct {
privateDNSZoneGroupClient network.PrivateDNSZoneGroupsClient
}
// New creates a new private dns zone group client.
func New(config *azclients.ClientConfig) *Client {
privateDNSZoneGroupClient := network.NewPrivateDNSZoneGroupsClientWithBaseURI(config.ResourceManagerEndpoint, config.SubscriptionID)
privateDNSZoneGroupClient.Authorizer = config.Authorizer
client := &Client{
privateDNSZoneGroupClient: privateDNSZoneGroupClient,
}
return client
}
// CreateOrUpdate creates or updates a private dns zone group
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, privateEndpointName string, privateDNSZoneGroupName string, parameters network.PrivateDNSZoneGroup, waitForCompletion bool) error {
createOrUpdateFuture, err := c.privateDNSZoneGroupClient.CreateOrUpdate(ctx, resourceGroupName, privateEndpointName, privateDNSZoneGroupName, parameters)
if err != nil {
klog.V(5).Infof("Received error for %s, resourceGroup: %s, privateEndpointName: %s, error: %s", "privatednszonegroup.put.request", resourceGroupName, privateEndpointName, err)
return err
}
if waitForCompletion {
err = createOrUpdateFuture.WaitForCompletionRef(ctx, c.privateDNSZoneGroupClient.Client)
if err != nil {
klog.V(5).Infof("Received error while waiting for completion for %s, resourceGroup: %s, privateEndpointName: %s, error: %s", "privatednszonegroup.put.request", resourceGroupName, privateEndpointName, err)
return err
}
}
return nil
}
// Get gets the private dns zone group
func (c *Client) Get(ctx context.Context, resourceGroupName string, privateEndpointName string, privateDNSZoneGroupName string) (result network.PrivateDNSZoneGroup, err error) {
return c.privateDNSZoneGroupClient.Get(ctx, resourceGroupName, privateEndpointName, privateDNSZoneGroupName)
}

View File

@ -0,0 +1,34 @@
/*
Copyright 2021 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 privatednszonegroupclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
)
// Interface is the client interface for Private DNS Zone Group.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
// Get gets the private dns zone group
Get(ctx context.Context, resourceGroupName string, privateEndpointName string, privateDNSZoneGroupName string) (result network.PrivateDNSZoneGroup, err error)
// CreateOrUpdate creates or updates a private dns zone group endpoint.
CreateOrUpdate(ctx context.Context, resourceGroupName string, privateEndpointName string, privateDNSZoneGroupName string, parameters network.PrivateDNSZoneGroup, waitForCompletion bool) error
}

View File

@ -0,0 +1,66 @@
/*
Copyright 2021 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 privateendpointclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
)
var _ Interface = &Client{}
// Client implements privateendpointclient Interface.
type Client struct {
privateEndpointClient network.PrivateEndpointsClient
}
// New creates a new private endpoint client.
func New(config *azclients.ClientConfig) *Client {
privateEndpointClient := network.NewPrivateEndpointsClientWithBaseURI(config.ResourceManagerEndpoint, config.SubscriptionID)
privateEndpointClient.Authorizer = config.Authorizer
client := &Client{
privateEndpointClient: privateEndpointClient,
}
return client
}
// CreateOrUpdate creates or updates a private endpoint.
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, endpointName string, privateEndpoint network.PrivateEndpoint, waitForCompletion bool) error {
createOrUpdateFuture, err := c.privateEndpointClient.CreateOrUpdate(ctx, resourceGroupName, endpointName, privateEndpoint)
if err != nil {
klog.V(5).Infof("Received error for %s, resourceGroup: %s, error: %s", "privateendpoint.put.request", resourceGroupName, err)
return err
}
if waitForCompletion {
err = createOrUpdateFuture.WaitForCompletionRef(ctx, c.privateEndpointClient.Client)
if err != nil {
klog.V(5).Infof("Received error while waiting for completion for %s, resourceGroup: %s, error: %s", "privateendpoint.put.request", resourceGroupName, err)
return err
}
}
return nil
}
// Get gets the private endpoint
func (c *Client) Get(ctx context.Context, resourceGroupName string, privateEndpointName string, expand string) (result network.PrivateEndpoint, err error) {
return c.privateEndpointClient.Get(ctx, resourceGroupName, privateEndpointName, expand)
}

View File

@ -0,0 +1,34 @@
/*
Copyright 2021 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 privateendpointclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
)
// Interface is the client interface for Private Endpoints.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
// Get gets the private endpoint
Get(ctx context.Context, resourceGroupName string, privateEndpointName string, expand string) (result network.PrivateEndpoint, err error)
// CreateOrUpdate creates or updates a private endpoint.
CreateOrUpdate(ctx context.Context, resourceGroupName string, endpointName string, privateEndpoint network.PrivateEndpoint, waitForCompletion bool) error
}

View File

@ -0,0 +1,563 @@
/*
Copyright 2020 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 publicipclient
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
var _ Interface = &Client{}
// Client implements PublicIPAddress client Interface.
type Client struct {
armClient armclient.Interface
subscriptionID string
cloudName string
disableAzureStackCloud bool
// Rate limiting configures.
rateLimiterReader flowcontrol.RateLimiter
rateLimiterWriter flowcontrol.RateLimiter
// ARM throttling configures.
RetryAfterReader time.Time
RetryAfterWriter time.Time
}
// New creates a new PublicIPAddress client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := config.Authorizer
apiVersion := APIVersion
if strings.EqualFold(config.CloudName, AzureStackCloudName) && !config.DisableAzureStackCloud {
apiVersion = AzureStackCloudAPIVersion
}
armClient := armclient.New(authorizer, *config, baseURI, apiVersion)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
if azclients.RateLimitEnabled(config.RateLimitConfig) {
klog.V(2).Infof("Azure PublicIPAddressesClient (read ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPS,
config.RateLimitConfig.CloudProviderRateLimitBucket)
klog.V(2).Infof("Azure PublicIPAddressesClient (write ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPSWrite,
config.RateLimitConfig.CloudProviderRateLimitBucketWrite)
}
client := &Client{
armClient: armClient,
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
subscriptionID: config.SubscriptionID,
cloudName: config.CloudName,
disableAzureStackCloud: config.DisableAzureStackCloud,
}
return client
}
// Get gets a PublicIPAddress.
func (c *Client) Get(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (network.PublicIPAddress, *retry.Error) {
mc := metrics.NewMetricContext("public_ip_addresses", "get", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return network.PublicIPAddress{}, retry.GetRateLimitError(false, "PublicIPGet")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("PublicIPGet", "client throttled", c.RetryAfterReader)
return network.PublicIPAddress{}, rerr
}
result, rerr := c.getPublicIPAddress(ctx, resourceGroupName, publicIPAddressName, expand)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// getPublicIPAddress gets a PublicIPAddress.
func (c *Client) getPublicIPAddress(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (network.PublicIPAddress, *retry.Error) {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/publicIPAddresses",
publicIPAddressName,
)
result := network.PublicIPAddress{}
response, rerr := c.armClient.GetResource(ctx, resourceID, expand)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.get.request", resourceID, rerr.Error())
return result, rerr
}
err := autorest.Respond(
response,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.get.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}
// GetVirtualMachineScaleSetPublicIPAddress gets a PublicIPAddress for VMSS VM.
func (c *Client) GetVirtualMachineScaleSetPublicIPAddress(ctx context.Context, resourceGroupName string, virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string, IPConfigurationName string, publicIPAddressName string, expand string) (network.PublicIPAddress, *retry.Error) {
mc := metrics.NewMetricContext("vmss_public_ip_addresses", "get", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return network.PublicIPAddress{}, retry.GetRateLimitError(false, "VMSSPublicIPGet")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("VMSSPublicIPGet", "client throttled", c.RetryAfterReader)
return network.PublicIPAddress{}, rerr
}
result, rerr := c.getVMSSPublicIPAddress(ctx, resourceGroupName, virtualMachineScaleSetName, virtualmachineIndex, networkInterfaceName, IPConfigurationName, publicIPAddressName, expand)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// getVMSSPublicIPAddress gets a PublicIPAddress for VMSS VM.
func (c *Client) getVMSSPublicIPAddress(ctx context.Context, resourceGroupName string, virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string, IPConfigurationName string, publicIPAddressName string, expand string) (network.PublicIPAddress, *retry.Error) {
resourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachineScaleSets/%s/virtualMachines/%s/networkInterfaces/%s/ipconfigurations/%s/publicipaddresses/%s",
autorest.Encode("path", c.subscriptionID),
autorest.Encode("path", resourceGroupName),
autorest.Encode("path", virtualMachineScaleSetName),
autorest.Encode("path", virtualmachineIndex),
autorest.Encode("path", networkInterfaceName),
autorest.Encode("path", IPConfigurationName),
autorest.Encode("path", publicIPAddressName),
)
result := network.PublicIPAddress{}
computeAPIVersion := ComputeAPIVersion
if strings.EqualFold(c.cloudName, AzureStackCloudName) && !c.disableAzureStackCloud {
computeAPIVersion = AzureStackComputeAPIVersion
}
queryParameters := map[string]interface{}{
"api-version": computeAPIVersion,
}
if len(expand) > 0 {
queryParameters["$expand"] = autorest.Encode("query", expand)
}
decorators := []autorest.PrepareDecorator{
autorest.WithQueryParameters(queryParameters),
}
response, rerr := c.armClient.GetResourceWithDecorators(ctx, resourceID, decorators)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vmsspublicip.get.request", resourceID, rerr.Error())
return result, rerr
}
err := autorest.Respond(
response,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vmsspublicip.get.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}
// List gets a list of PublicIPAddress in the resource group.
func (c *Client) List(ctx context.Context, resourceGroupName string) ([]network.PublicIPAddress, *retry.Error) {
mc := metrics.NewMetricContext("public_ip_addresses", "list", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return nil, retry.GetRateLimitError(false, "PublicIPList")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("PublicIPList", "client throttled", c.RetryAfterReader)
return nil, rerr
}
result, rerr := c.listPublicIPAddress(ctx, resourceGroupName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// listPublicIPAddress gets a list of PublicIPAddress in the resource group.
func (c *Client) listPublicIPAddress(ctx context.Context, resourceGroupName string) ([]network.PublicIPAddress, *retry.Error) {
resourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/publicIPAddresses",
autorest.Encode("path", c.subscriptionID),
autorest.Encode("path", resourceGroupName))
result := make([]network.PublicIPAddress, 0)
page := &PublicIPAddressListResultPage{}
page.fn = c.listNextResults
resp, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.list.request", resourceID, rerr.Error())
return result, rerr
}
var err error
page.pialr, err = c.listResponder(resp)
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.list.respond", resourceID, err)
return result, retry.GetError(resp, err)
}
for {
result = append(result, page.Values()...)
// Abort the loop when there's no nextLink in the response.
if to.String(page.Response().NextLink) == "" {
break
}
if err = page.NextWithContext(ctx); err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.list.next", resourceID, err)
return result, retry.GetError(page.Response().Response.Response, err)
}
}
return result, nil
}
// CreateOrUpdate creates or updates a PublicIPAddress.
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, publicIPAddressName string, parameters network.PublicIPAddress) *retry.Error {
mc := metrics.NewMetricContext("public_ip_addresses", "create_or_update", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "PublicIPCreateOrUpdate")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("PublicIPCreateOrUpdate", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.createOrUpdatePublicIP(ctx, resourceGroupName, publicIPAddressName, parameters)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// createOrUpdatePublicIP creates or updates a PublicIPAddress.
func (c *Client) createOrUpdatePublicIP(ctx context.Context, resourceGroupName string, publicIPAddressName string, parameters network.PublicIPAddress) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/publicIPAddresses",
publicIPAddressName,
)
response, rerr := c.armClient.PutResource(ctx, resourceID, parameters)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.put.request", resourceID, rerr.Error())
return rerr
}
if response != nil && response.StatusCode != http.StatusNoContent {
_, rerr = c.createOrUpdateResponder(response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.put.respond", resourceID, rerr.Error())
return rerr
}
}
return nil
}
func (c *Client) createOrUpdateResponder(resp *http.Response) (*network.PublicIPAddress, *retry.Error) {
result := &network.PublicIPAddress{}
err := autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return result, retry.GetError(resp, err)
}
// Delete deletes a PublicIPAddress by name.
func (c *Client) Delete(ctx context.Context, resourceGroupName string, publicIPAddressName string) *retry.Error {
mc := metrics.NewMetricContext("public_ip_addresses", "delete", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "PublicIPDelete")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("PublicIPDelete", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.deletePublicIP(ctx, resourceGroupName, publicIPAddressName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// deletePublicIP deletes a PublicIPAddress by name.
func (c *Client) deletePublicIP(ctx context.Context, resourceGroupName string, publicIPAddressName string) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/publicIPAddresses",
publicIPAddressName,
)
return c.armClient.DeleteResource(ctx, resourceID, "")
}
func (c *Client) listResponder(resp *http.Response) (result network.PublicIPAddressListResult, err error) {
err = autorest.Respond(
resp,
autorest.ByIgnoring(),
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return
}
// publicIPAddressListResultPreparer prepares a request to retrieve the next set of results.
// It returns nil if no more results exist.
func (c *Client) publicIPAddressListResultPreparer(ctx context.Context, lr network.PublicIPAddressListResult) (*http.Request, error) {
if lr.NextLink == nil || len(to.String(lr.NextLink)) < 1 {
return nil, nil
}
decorators := []autorest.PrepareDecorator{
autorest.WithBaseURL(to.String(lr.NextLink)),
}
return c.armClient.PrepareGetRequest(ctx, decorators...)
}
// listNextResults retrieves the next set of results, if any.
func (c *Client) listNextResults(ctx context.Context, lastResults network.PublicIPAddressListResult) (result network.PublicIPAddressListResult, err error) {
req, err := c.publicIPAddressListResultPreparer(ctx, lastResults)
if err != nil {
return result, autorest.NewErrorWithError(err, "publicipclient", "listNextResults", nil, "Failure preparing next results request")
}
if req == nil {
return
}
resp, rerr := c.armClient.Send(ctx, req)
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
result.Response = autorest.Response{Response: resp}
return result, autorest.NewErrorWithError(rerr.Error(), "publicipclient", "listNextResults", resp, "Failure sending next results request")
}
result, err = c.listResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "publicipclient", "listNextResults", resp, "Failure responding to next results request")
}
return
}
// PublicIPAddressListResultPage contains a page of PublicIPAddress values.
type PublicIPAddressListResultPage struct {
fn func(context.Context, network.PublicIPAddressListResult) (network.PublicIPAddressListResult, error)
pialr network.PublicIPAddressListResult
}
// NextWithContext advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
func (page *PublicIPAddressListResultPage) NextWithContext(ctx context.Context) (err error) {
next, err := page.fn(ctx, page.pialr)
if err != nil {
return err
}
page.pialr = next
return nil
}
// Next advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
// Deprecated: Use NextWithContext() instead.
func (page *PublicIPAddressListResultPage) Next() error {
return page.NextWithContext(context.Background())
}
// NotDone returns true if the page enumeration should be started or is not yet complete.
func (page PublicIPAddressListResultPage) NotDone() bool {
return !page.pialr.IsEmpty()
}
// Response returns the raw server response from the last page request.
func (page PublicIPAddressListResultPage) Response() network.PublicIPAddressListResult {
return page.pialr
}
// Values returns the slice of values for the current page or nil if there are no values.
func (page PublicIPAddressListResultPage) Values() []network.PublicIPAddress {
if page.pialr.IsEmpty() {
return nil
}
return *page.pialr.Value
}
// ListAll gets all of PublicIPAddress in the subscription.
func (c *Client) ListAll(ctx context.Context) ([]network.PublicIPAddress, *retry.Error) {
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
return nil, retry.GetRateLimitError(false, "PublicIPListAll")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
rerr := retry.GetThrottlingError("PublicIPListAll", "client throttled", c.RetryAfterReader)
return nil, rerr
}
result, rerr := c.listAllPublicIPAddress(ctx)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// listAllPublicIPAddress gets all of PublicIPAddress in the subscription.
func (c *Client) listAllPublicIPAddress(ctx context.Context) ([]network.PublicIPAddress, *retry.Error) {
resourceID := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Network/publicIPAddresses",
autorest.Encode("path", c.subscriptionID))
result := make([]network.PublicIPAddress, 0)
page := &PublicIPAddressListResultPage{}
page.fn = c.listNextResults
resp, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.listall.request", resourceID, rerr.Error())
return result, rerr
}
var err error
page.pialr, err = c.listResponder(resp)
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.listall.respond", resourceID, err)
return result, retry.GetError(resp, err)
}
for {
result = append(result, page.Values()...)
// Abort the loop when there's no nextLink in the response.
if to.String(page.Response().NextLink) == "" {
break
}
if err = page.NextWithContext(ctx); err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.listall.next", resourceID, err)
return result, retry.GetError(page.Response().Response.Response, err)
}
}
return result, nil
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 publicipclient implements the client for PublicIPAddress.
package publicipclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient"

View File

@ -0,0 +1,62 @@
/*
Copyright 2020 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 publicipclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2020-08-01"
// AzureStackCloudAPIVersion is the API version for Azure Stack
AzureStackCloudAPIVersion = "2018-11-01"
// ComputeAPIVersion is the API version for compute. It is required to get VMSS public IP.
ComputeAPIVersion = "2017-03-30"
// AzureStackComputeAPIVersion is the API version for compute for Azure Stack. It is required to get VMSS network interface.
AzureStackComputeAPIVersion = "2018-11-01"
// AzureStackCloudName is the cloud name of Azure Stack
AzureStackCloudName = "AZURESTACKCLOUD"
)
// Interface is the client interface for PublicIPAddress.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
// Get gets a PublicIPAddress.
Get(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (result network.PublicIPAddress, rerr *retry.Error)
// GetVirtualMachineScaleSetPublicIPAddress gets a PublicIPAddress for VMSS VM.
GetVirtualMachineScaleSetPublicIPAddress(ctx context.Context, resourceGroupName string, virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string, IPConfigurationName string, publicIPAddressName string, expand string) (result network.PublicIPAddress, rerr *retry.Error)
// List gets a list of PublicIPAddress in the resource group.
List(ctx context.Context, resourceGroupName string) (result []network.PublicIPAddress, rerr *retry.Error)
// ListAll gets all of PublicIPAddress in the subscription.
ListAll(ctx context.Context) (result []network.PublicIPAddress, rerr *retry.Error)
// CreateOrUpdate creates or updates a PublicIPAddress.
CreateOrUpdate(ctx context.Context, resourceGroupName string, publicIPAddressName string, parameters network.PublicIPAddress) *retry.Error
// Delete deletes a PublicIPAddress by name.
Delete(ctx context.Context, resourceGroupName string, publicIPAddressName string) *retry.Error
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 mockpublicipclient implements the mock client for PublicIPAddress.
package mockpublicipclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient/mockpublicipclient"

View File

@ -0,0 +1,142 @@
// /*
// Copyright 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.
// */
//
// Code generated by MockGen. DO NOT EDIT.
// Source: /go/src/sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient/interface.go
// Package mockpublicipclient is a generated GoMock package.
package mockpublicipclient
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
gomock "github.com/golang/mock/gomock"
retry "sigs.k8s.io/cloud-provider-azure/pkg/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
}
// Get mocks base method.
func (m *MockInterface) Get(ctx context.Context, resourceGroupName, publicIPAddressName, expand string) (network.PublicIPAddress, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", ctx, resourceGroupName, publicIPAddressName, expand)
ret0, _ := ret[0].(network.PublicIPAddress)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockInterfaceMockRecorder) Get(ctx, resourceGroupName, publicIPAddressName, expand interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), ctx, resourceGroupName, publicIPAddressName, expand)
}
// GetVirtualMachineScaleSetPublicIPAddress mocks base method.
func (m *MockInterface) GetVirtualMachineScaleSetPublicIPAddress(ctx context.Context, resourceGroupName, virtualMachineScaleSetName, virtualmachineIndex, networkInterfaceName, IPConfigurationName, publicIPAddressName, expand string) (network.PublicIPAddress, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetVirtualMachineScaleSetPublicIPAddress", ctx, resourceGroupName, virtualMachineScaleSetName, virtualmachineIndex, networkInterfaceName, IPConfigurationName, publicIPAddressName, expand)
ret0, _ := ret[0].(network.PublicIPAddress)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// GetVirtualMachineScaleSetPublicIPAddress indicates an expected call of GetVirtualMachineScaleSetPublicIPAddress.
func (mr *MockInterfaceMockRecorder) GetVirtualMachineScaleSetPublicIPAddress(ctx, resourceGroupName, virtualMachineScaleSetName, virtualmachineIndex, networkInterfaceName, IPConfigurationName, publicIPAddressName, expand interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVirtualMachineScaleSetPublicIPAddress", reflect.TypeOf((*MockInterface)(nil).GetVirtualMachineScaleSetPublicIPAddress), ctx, resourceGroupName, virtualMachineScaleSetName, virtualmachineIndex, networkInterfaceName, IPConfigurationName, publicIPAddressName, expand)
}
// List mocks base method.
func (m *MockInterface) List(ctx context.Context, resourceGroupName string) ([]network.PublicIPAddress, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, resourceGroupName)
ret0, _ := ret[0].([]network.PublicIPAddress)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// List indicates an expected call of List.
func (mr *MockInterfaceMockRecorder) List(ctx, resourceGroupName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInterface)(nil).List), ctx, resourceGroupName)
}
// ListAll mocks base method.
func (m *MockInterface) ListAll(ctx context.Context) ([]network.PublicIPAddress, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListAll", ctx)
ret0, _ := ret[0].([]network.PublicIPAddress)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// ListAll indicates an expected call of ListAll.
func (mr *MockInterfaceMockRecorder) ListAll(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAll", reflect.TypeOf((*MockInterface)(nil).ListAll), ctx)
}
// CreateOrUpdate mocks base method.
func (m *MockInterface) CreateOrUpdate(ctx context.Context, resourceGroupName, publicIPAddressName string, parameters network.PublicIPAddress) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroupName, publicIPAddressName, parameters)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// CreateOrUpdate indicates an expected call of CreateOrUpdate.
func (mr *MockInterfaceMockRecorder) CreateOrUpdate(ctx, resourceGroupName, publicIPAddressName, parameters interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockInterface)(nil).CreateOrUpdate), ctx, resourceGroupName, publicIPAddressName, parameters)
}
// Delete mocks base method.
func (m *MockInterface) Delete(ctx context.Context, resourceGroupName, publicIPAddressName string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, resourceGroupName, publicIPAddressName)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockInterfaceMockRecorder) Delete(ctx, resourceGroupName, publicIPAddressName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockInterface)(nil).Delete), ctx, resourceGroupName, publicIPAddressName)
}

View File

@ -0,0 +1,206 @@
/*
Copyright 2020 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 routeclient
import (
"context"
"net/http"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
var _ Interface = &Client{}
// Client implements Route client Interface.
type Client struct {
armClient armclient.Interface
subscriptionID string
cloudName string
// Rate limiting configures.
rateLimiterReader flowcontrol.RateLimiter
rateLimiterWriter flowcontrol.RateLimiter
// ARM throttling configures.
RetryAfterReader time.Time
RetryAfterWriter time.Time
}
// New creates a new Route client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := config.Authorizer
apiVersion := APIVersion
if strings.EqualFold(config.CloudName, AzureStackCloudName) && !config.DisableAzureStackCloud {
apiVersion = AzureStackCloudAPIVersion
}
armClient := armclient.New(authorizer, *config, baseURI, apiVersion)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
if azclients.RateLimitEnabled(config.RateLimitConfig) {
klog.V(2).Infof("Azure RoutesClient (read ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPS,
config.RateLimitConfig.CloudProviderRateLimitBucket)
klog.V(2).Infof("Azure RoutesClient (write ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPSWrite,
config.RateLimitConfig.CloudProviderRateLimitBucketWrite)
}
client := &Client{
armClient: armClient,
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
subscriptionID: config.SubscriptionID,
cloudName: config.CloudName,
}
return client
}
// CreateOrUpdate creates or updates a Route.
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, routeTableName string, routeName string, routeParameters network.Route, etag string) *retry.Error {
mc := metrics.NewMetricContext("routes", "create_or_update", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "RouteCreateOrUpdate")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("RouteCreateOrUpdate", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.createOrUpdateRoute(ctx, resourceGroupName, routeTableName, routeName, routeParameters, etag)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// createOrUpdateRoute creates or updates a Route.
func (c *Client) createOrUpdateRoute(ctx context.Context, resourceGroupName string, routeTableName string, routeName string, routeParameters network.Route, etag string) *retry.Error {
resourceID := armclient.GetChildResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/routeTables",
routeTableName,
"routes",
routeName,
)
decorators := []autorest.PrepareDecorator{
autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}),
autorest.WithJSON(routeParameters),
}
if etag != "" {
decorators = append(decorators, autorest.WithHeader("If-Match", autorest.String(etag)))
}
response, rerr := c.armClient.PutResourceWithDecorators(ctx, resourceID, routeParameters, decorators)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "route.put.request", resourceID, rerr.Error())
return rerr
}
if response != nil && response.StatusCode != http.StatusNoContent {
_, rerr = c.createOrUpdateResponder(response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "route.put.respond", resourceID, rerr.Error())
return rerr
}
}
return nil
}
func (c *Client) createOrUpdateResponder(resp *http.Response) (*network.Route, *retry.Error) {
result := &network.Route{}
err := autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return result, retry.GetError(resp, err)
}
// Delete deletes a Route by name.
func (c *Client) Delete(ctx context.Context, resourceGroupName string, routeTableName string, routeName string) *retry.Error {
mc := metrics.NewMetricContext("routes", "delete", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "RouteDelete")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("RouteDelete", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.deleteRoute(ctx, resourceGroupName, routeTableName, routeName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// deleteRoute deletes a Route by name.
func (c *Client) deleteRoute(ctx context.Context, resourceGroupName string, routeTableName string, routeName string) *retry.Error {
resourceID := armclient.GetChildResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/routeTables",
routeTableName,
"routes",
routeName,
)
return c.armClient.DeleteResource(ctx, resourceID, "")
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 routeclient implements the client for Route.
package routeclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routeclient"

View File

@ -0,0 +1,45 @@
/*
Copyright 2020 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 routeclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2020-08-01"
// AzureStackCloudAPIVersion is the API version for Azure Stack
AzureStackCloudAPIVersion = "2018-11-01"
// AzureStackCloudName is the cloud name of Azure Stack
AzureStackCloudName = "AZURESTACKCLOUD"
)
// Interface is the client interface for Route.
// Don't forget to run the following command to generate the mock client:
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
// CreateOrUpdate creates or updates a Route.
CreateOrUpdate(ctx context.Context, resourceGroupName string, routeTableName string, routeName string, routeParameters network.Route, etag string) *retry.Error
// Delete deletes a Route by name.
Delete(ctx context.Context, resourceGroupName string, routeTableName string, routeName string) *retry.Error
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 mockrouteclient implements the mock client for Route.
package mockrouteclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routeclient/mockrouteclient"

View File

@ -0,0 +1,82 @@
// /*
// Copyright 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.
// */
//
// Code generated by MockGen. DO NOT EDIT.
// Source: /go/src/sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routeclient/interface.go
// Package mockrouteclient is a generated GoMock package.
package mockrouteclient
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
gomock "github.com/golang/mock/gomock"
retry "sigs.k8s.io/cloud-provider-azure/pkg/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
}
// CreateOrUpdate mocks base method.
func (m *MockInterface) CreateOrUpdate(ctx context.Context, resourceGroupName, routeTableName, routeName string, routeParameters network.Route, etag string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroupName, routeTableName, routeName, routeParameters, etag)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// CreateOrUpdate indicates an expected call of CreateOrUpdate.
func (mr *MockInterfaceMockRecorder) CreateOrUpdate(ctx, resourceGroupName, routeTableName, routeName, routeParameters, etag interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockInterface)(nil).CreateOrUpdate), ctx, resourceGroupName, routeTableName, routeName, routeParameters, etag)
}
// Delete mocks base method.
func (m *MockInterface) Delete(ctx context.Context, resourceGroupName, routeTableName, routeName string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, resourceGroupName, routeTableName, routeName)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockInterfaceMockRecorder) Delete(ctx, resourceGroupName, routeTableName, routeName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockInterface)(nil).Delete), ctx, resourceGroupName, routeTableName, routeName)
}

View File

@ -0,0 +1,220 @@
/*
Copyright 2020 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 routetableclient
import (
"context"
"net/http"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
var _ Interface = &Client{}
// Client implements RouteTable client Interface.
type Client struct {
armClient armclient.Interface
subscriptionID string
cloudName string
// Rate limiting configures.
rateLimiterReader flowcontrol.RateLimiter
rateLimiterWriter flowcontrol.RateLimiter
// ARM throttling configures.
RetryAfterReader time.Time
RetryAfterWriter time.Time
}
// New creates a new RouteTable client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := config.Authorizer
apiVersion := APIVersion
if strings.EqualFold(config.CloudName, AzureStackCloudName) && !config.DisableAzureStackCloud {
apiVersion = AzureStackCloudAPIVersion
}
armClient := armclient.New(authorizer, *config, baseURI, apiVersion)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
if azclients.RateLimitEnabled(config.RateLimitConfig) {
klog.V(2).Infof("Azure RouteTablesClient (read ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPS,
config.RateLimitConfig.CloudProviderRateLimitBucket)
klog.V(2).Infof("Azure RouteTablesClient (write ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPSWrite,
config.RateLimitConfig.CloudProviderRateLimitBucketWrite)
}
client := &Client{
armClient: armClient,
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
subscriptionID: config.SubscriptionID,
cloudName: config.CloudName,
}
return client
}
// Get gets a RouteTable.
func (c *Client) Get(ctx context.Context, resourceGroupName string, routeTableName string, expand string) (network.RouteTable, *retry.Error) {
mc := metrics.NewMetricContext("route_tables", "get", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return network.RouteTable{}, retry.GetRateLimitError(false, "RouteTableGet")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("RouteTableGet", "client throttled", c.RetryAfterReader)
return network.RouteTable{}, rerr
}
result, rerr := c.getRouteTable(ctx, resourceGroupName, routeTableName, expand)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// getRouteTable gets a RouteTable.
func (c *Client) getRouteTable(ctx context.Context, resourceGroupName string, routeTableName string, expand string) (network.RouteTable, *retry.Error) {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/routeTables",
routeTableName,
)
result := network.RouteTable{}
response, rerr := c.armClient.GetResource(ctx, resourceID, expand)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "routetable.get.request", resourceID, rerr.Error())
return result, rerr
}
err := autorest.Respond(
response,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "routetable.get.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}
// CreateOrUpdate creates or updates a RouteTable.
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, routeTableName string, parameters network.RouteTable, etag string) *retry.Error {
mc := metrics.NewMetricContext("route_tables", "create_or_update", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "RouteTableCreateOrUpdate")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("RouteTableCreateOrUpdate", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.createOrUpdateRouteTable(ctx, resourceGroupName, routeTableName, parameters, etag)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// createOrUpdateRouteTable creates or updates a RouteTable.
func (c *Client) createOrUpdateRouteTable(ctx context.Context, resourceGroupName string, routeTableName string, parameters network.RouteTable, etag string) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/routeTables",
routeTableName,
)
decorators := []autorest.PrepareDecorator{
autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}),
autorest.WithJSON(parameters),
}
if etag != "" {
decorators = append(decorators, autorest.WithHeader("If-Match", autorest.String(etag)))
}
response, rerr := c.armClient.PutResourceWithDecorators(ctx, resourceID, parameters, decorators)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "routetable.put.request", resourceID, rerr.Error())
return rerr
}
if response != nil && response.StatusCode != http.StatusNoContent {
_, rerr = c.createOrUpdateResponder(response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "routetable.put.respond", resourceID, rerr.Error())
return rerr
}
}
return nil
}
func (c *Client) createOrUpdateResponder(resp *http.Response) (*network.RouteTable, *retry.Error) {
result := &network.RouteTable{}
err := autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return result, retry.GetError(resp, err)
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 routetableclient implements the client for RouteTable.
package routetableclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routetableclient"

View File

@ -0,0 +1,44 @@
/*
Copyright 2020 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 routetableclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2020-08-01"
// AzureStackCloudAPIVersion is the API version for Azure Stack
AzureStackCloudAPIVersion = "2018-11-01"
// AzureStackCloudName is the cloud name of Azure Stack
AzureStackCloudName = "AZURESTACKCLOUD"
)
// Interface is the client interface for RouteTable.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
// Get gets a RouteTable.
Get(ctx context.Context, resourceGroupName string, routeTableName string, expand string) (result network.RouteTable, rerr *retry.Error)
// CreateOrUpdate creates or updates a RouteTable.
CreateOrUpdate(ctx context.Context, resourceGroupName string, routeTableName string, parameters network.RouteTable, etag string) *retry.Error
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 mockroutetableclient implements the mock client for RouteTable.
package mockroutetableclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routetableclient/mockroutetableclient"

View File

@ -0,0 +1,83 @@
// /*
// Copyright 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.
// */
//
// Code generated by MockGen. DO NOT EDIT.
// Source: /go/src/sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routetableclient/interface.go
// Package mockroutetableclient is a generated GoMock package.
package mockroutetableclient
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
gomock "github.com/golang/mock/gomock"
retry "sigs.k8s.io/cloud-provider-azure/pkg/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
}
// Get mocks base method.
func (m *MockInterface) Get(ctx context.Context, resourceGroupName, routeTableName, expand string) (network.RouteTable, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", ctx, resourceGroupName, routeTableName, expand)
ret0, _ := ret[0].(network.RouteTable)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockInterfaceMockRecorder) Get(ctx, resourceGroupName, routeTableName, expand interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), ctx, resourceGroupName, routeTableName, expand)
}
// CreateOrUpdate mocks base method.
func (m *MockInterface) CreateOrUpdate(ctx context.Context, resourceGroupName, routeTableName string, parameters network.RouteTable, etag string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroupName, routeTableName, parameters, etag)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// CreateOrUpdate indicates an expected call of CreateOrUpdate.
func (mr *MockInterfaceMockRecorder) CreateOrUpdate(ctx, resourceGroupName, routeTableName, parameters, etag interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockInterface)(nil).CreateOrUpdate), ctx, resourceGroupName, routeTableName, parameters, etag)
}

View File

@ -0,0 +1,426 @@
/*
Copyright 2020 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 securitygroupclient
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
var _ Interface = &Client{}
// Client implements SecurityGroup client Interface.
type Client struct {
armClient armclient.Interface
subscriptionID string
cloudName string
// Rate limiting configures.
rateLimiterReader flowcontrol.RateLimiter
rateLimiterWriter flowcontrol.RateLimiter
// ARM throttling configures.
RetryAfterReader time.Time
RetryAfterWriter time.Time
}
// New creates a new SecurityGroup client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := config.Authorizer
apiVersion := APIVersion
if strings.EqualFold(config.CloudName, AzureStackCloudName) && !config.DisableAzureStackCloud {
apiVersion = AzureStackCloudAPIVersion
}
armClient := armclient.New(authorizer, *config, baseURI, apiVersion)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
if azclients.RateLimitEnabled(config.RateLimitConfig) {
klog.V(2).Infof("Azure SecurityGroupsClient (read ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPS,
config.RateLimitConfig.CloudProviderRateLimitBucket)
klog.V(2).Infof("Azure SecurityGroupsClient (write ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPSWrite,
config.RateLimitConfig.CloudProviderRateLimitBucketWrite)
}
client := &Client{
armClient: armClient,
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
subscriptionID: config.SubscriptionID,
cloudName: config.CloudName,
}
return client
}
// Get gets a SecurityGroup.
func (c *Client) Get(ctx context.Context, resourceGroupName string, networkSecurityGroupName string, expand string) (network.SecurityGroup, *retry.Error) {
mc := metrics.NewMetricContext("security_groups", "get", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return network.SecurityGroup{}, retry.GetRateLimitError(false, "NSGGet")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("NSGGet", "client throttled", c.RetryAfterReader)
return network.SecurityGroup{}, rerr
}
result, rerr := c.getSecurityGroup(ctx, resourceGroupName, networkSecurityGroupName, expand)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// getSecurityGroup gets a SecurityGroup.
func (c *Client) getSecurityGroup(ctx context.Context, resourceGroupName string, networkSecurityGroupName string, expand string) (network.SecurityGroup, *retry.Error) {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/networkSecurityGroups",
networkSecurityGroupName,
)
result := network.SecurityGroup{}
response, rerr := c.armClient.GetResource(ctx, resourceID, expand)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "securitygroup.get.request", resourceID, rerr.Error())
return result, rerr
}
err := autorest.Respond(
response,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "securitygroup.get.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}
// List gets a list of SecurityGroups in the resource group.
func (c *Client) List(ctx context.Context, resourceGroupName string) ([]network.SecurityGroup, *retry.Error) {
mc := metrics.NewMetricContext("security_groups", "list", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return nil, retry.GetRateLimitError(false, "NSGList")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("NSGList", "client throttled", c.RetryAfterReader)
return nil, rerr
}
result, rerr := c.listSecurityGroup(ctx, resourceGroupName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// listSecurityGroup gets a list of SecurityGroups in the resource group.
func (c *Client) listSecurityGroup(ctx context.Context, resourceGroupName string) ([]network.SecurityGroup, *retry.Error) {
resourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkSecurityGroups",
autorest.Encode("path", c.subscriptionID),
autorest.Encode("path", resourceGroupName))
result := make([]network.SecurityGroup, 0)
page := &SecurityGroupListResultPage{}
page.fn = c.listNextResults
resp, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "securitygroup.list.request", resourceID, rerr.Error())
return result, rerr
}
var err error
page.sglr, err = c.listResponder(resp)
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "securitygroup.list.respond", resourceID, err)
return result, retry.GetError(resp, err)
}
for {
result = append(result, page.Values()...)
// Abort the loop when there's no nextLink in the response.
if to.String(page.Response().NextLink) == "" {
break
}
if err = page.NextWithContext(ctx); err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "securitygroup.list.next", resourceID, err)
return result, retry.GetError(page.Response().Response.Response, err)
}
}
return result, nil
}
// CreateOrUpdate creates or updates a SecurityGroup.
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, networkSecurityGroupName string, parameters network.SecurityGroup, etag string) *retry.Error {
mc := metrics.NewMetricContext("security_groups", "create_or_update", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "NSGCreateOrUpdate")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("NSGCreateOrUpdate", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.createOrUpdateNSG(ctx, resourceGroupName, networkSecurityGroupName, parameters, etag)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// createOrUpdateNSG creates or updates a SecurityGroup.
func (c *Client) createOrUpdateNSG(ctx context.Context, resourceGroupName string, networkSecurityGroupName string, parameters network.SecurityGroup, etag string) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/networkSecurityGroups",
networkSecurityGroupName,
)
decorators := []autorest.PrepareDecorator{
autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}),
autorest.WithJSON(parameters),
}
if etag != "" {
decorators = append(decorators, autorest.WithHeader("If-Match", autorest.String(etag)))
}
response, rerr := c.armClient.PutResourceWithDecorators(ctx, resourceID, parameters, decorators)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "securityGroup.put.request", resourceID, rerr.Error())
return rerr
}
if response != nil && response.StatusCode != http.StatusNoContent {
_, rerr = c.createOrUpdateResponder(response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "securityGroup.put.respond", resourceID, rerr.Error())
return rerr
}
}
return nil
}
func (c *Client) createOrUpdateResponder(resp *http.Response) (*network.SecurityGroup, *retry.Error) {
result := &network.SecurityGroup{}
err := autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return result, retry.GetError(resp, err)
}
// Delete deletes a SecurityGroup by name.
func (c *Client) Delete(ctx context.Context, resourceGroupName string, networkSecurityGroupName string) *retry.Error {
mc := metrics.NewMetricContext("security_groups", "delete", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "NSGDelete")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("NSGDelete", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.deleteNSG(ctx, resourceGroupName, networkSecurityGroupName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// deleteNSG deletes a SecurityGroup by name.
func (c *Client) deleteNSG(ctx context.Context, resourceGroupName string, networkSecurityGroupName string) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/networkSecurityGroups",
networkSecurityGroupName,
)
return c.armClient.DeleteResource(ctx, resourceID, "")
}
func (c *Client) listResponder(resp *http.Response) (result network.SecurityGroupListResult, err error) {
err = autorest.Respond(
resp,
autorest.ByIgnoring(),
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return
}
// securityGroupListResultPreparer prepares a request to retrieve the next set of results.
// It returns nil if no more results exist.
func (c *Client) securityGroupListResultPreparer(ctx context.Context, sglr network.SecurityGroupListResult) (*http.Request, error) {
if sglr.NextLink == nil || len(to.String(sglr.NextLink)) < 1 {
return nil, nil
}
decorators := []autorest.PrepareDecorator{
autorest.WithBaseURL(to.String(sglr.NextLink)),
}
return c.armClient.PrepareGetRequest(ctx, decorators...)
}
// listNextResults retrieves the next set of results, if any.
func (c *Client) listNextResults(ctx context.Context, lastResults network.SecurityGroupListResult) (result network.SecurityGroupListResult, err error) {
req, err := c.securityGroupListResultPreparer(ctx, lastResults)
if err != nil {
return result, autorest.NewErrorWithError(err, "securitygroupclient", "listNextResults", nil, "Failure preparing next results request")
}
if req == nil {
return
}
resp, rerr := c.armClient.Send(ctx, req)
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
result.Response = autorest.Response{Response: resp}
return result, autorest.NewErrorWithError(rerr.Error(), "securitygroupclient", "listNextResults", resp, "Failure sending next results request")
}
result, err = c.listResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "securitygroupclient", "listNextResults", resp, "Failure responding to next results request")
}
return
}
// SecurityGroupListResultPage contains a page of SecurityGroup values.
type SecurityGroupListResultPage struct {
fn func(context.Context, network.SecurityGroupListResult) (network.SecurityGroupListResult, error)
sglr network.SecurityGroupListResult
}
// NextWithContext advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
func (page *SecurityGroupListResultPage) NextWithContext(ctx context.Context) (err error) {
next, err := page.fn(ctx, page.sglr)
if err != nil {
return err
}
page.sglr = next
return nil
}
// Next advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
// Deprecated: Use NextWithContext() instead.
func (page *SecurityGroupListResultPage) Next() error {
return page.NextWithContext(context.Background())
}
// NotDone returns true if the page enumeration should be started or is not yet complete.
func (page SecurityGroupListResultPage) NotDone() bool {
return !page.sglr.IsEmpty()
}
// Response returns the raw server response from the last page request.
func (page SecurityGroupListResultPage) Response() network.SecurityGroupListResult {
return page.sglr
}
// Values returns the slice of values for the current page or nil if there are no values.
func (page SecurityGroupListResultPage) Values() []network.SecurityGroup {
if page.sglr.IsEmpty() {
return nil
}
return *page.sglr.Value
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 securitygroupclient implements the client for SecurityGroups.
package securitygroupclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/securitygroupclient"

View File

@ -0,0 +1,50 @@
/*
Copyright 2020 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 securitygroupclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2020-08-01"
// AzureStackCloudAPIVersion is the API version for Azure Stack
AzureStackCloudAPIVersion = "2018-11-01"
// AzureStackCloudName is the cloud name of Azure Stack
AzureStackCloudName = "AZURESTACKCLOUD"
)
// Interface is the client interface for SecurityGroups.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
// Get gets a SecurityGroup.
Get(ctx context.Context, resourceGroupName string, networkSecurityGroupName string, expand string) (result network.SecurityGroup, rerr *retry.Error)
// List gets a list of SecurityGroup in the resource group.
List(ctx context.Context, resourceGroupName string) (result []network.SecurityGroup, rerr *retry.Error)
// CreateOrUpdate creates or updates a SecurityGroup.
CreateOrUpdate(ctx context.Context, resourceGroupName string, networkSecurityGroupName string, parameters network.SecurityGroup, etag string) *retry.Error
// Delete deletes a SecurityGroup by name.
Delete(ctx context.Context, resourceGroupName string, networkSecurityGroupName string) *retry.Error
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 mocksecuritygroupclient implements the mock client for SecurityGroups.
package mocksecuritygroupclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/securitygroupclient/mocksecuritygroupclient"

View File

@ -0,0 +1,112 @@
// /*
// Copyright 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.
// */
//
// Code generated by MockGen. DO NOT EDIT.
// Source: /go/src/sigs.k8s.io/cloud-provider-azure/pkg/azureclients/securitygroupclient/interface.go
// Package mocksecuritygroupclient is a generated GoMock package.
package mocksecuritygroupclient
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
gomock "github.com/golang/mock/gomock"
retry "sigs.k8s.io/cloud-provider-azure/pkg/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
}
// Get mocks base method.
func (m *MockInterface) Get(ctx context.Context, resourceGroupName, networkSecurityGroupName, expand string) (network.SecurityGroup, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", ctx, resourceGroupName, networkSecurityGroupName, expand)
ret0, _ := ret[0].(network.SecurityGroup)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockInterfaceMockRecorder) Get(ctx, resourceGroupName, networkSecurityGroupName, expand interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), ctx, resourceGroupName, networkSecurityGroupName, expand)
}
// List mocks base method.
func (m *MockInterface) List(ctx context.Context, resourceGroupName string) ([]network.SecurityGroup, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, resourceGroupName)
ret0, _ := ret[0].([]network.SecurityGroup)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// List indicates an expected call of List.
func (mr *MockInterfaceMockRecorder) List(ctx, resourceGroupName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInterface)(nil).List), ctx, resourceGroupName)
}
// CreateOrUpdate mocks base method.
func (m *MockInterface) CreateOrUpdate(ctx context.Context, resourceGroupName, networkSecurityGroupName string, parameters network.SecurityGroup, etag string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroupName, networkSecurityGroupName, parameters, etag)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// CreateOrUpdate indicates an expected call of CreateOrUpdate.
func (mr *MockInterfaceMockRecorder) CreateOrUpdate(ctx, resourceGroupName, networkSecurityGroupName, parameters, etag interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockInterface)(nil).CreateOrUpdate), ctx, resourceGroupName, networkSecurityGroupName, parameters, etag)
}
// Delete mocks base method.
func (m *MockInterface) Delete(ctx context.Context, resourceGroupName, networkSecurityGroupName string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, resourceGroupName, networkSecurityGroupName)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockInterfaceMockRecorder) Delete(ctx, resourceGroupName, networkSecurityGroupName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockInterface)(nil).Delete), ctx, resourceGroupName, networkSecurityGroupName)
}

View File

@ -0,0 +1,419 @@
/*
Copyright 2020 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 snapshotclient
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
var _ Interface = &Client{}
// Client implements Snapshot client Interface.
type Client struct {
armClient armclient.Interface
subscriptionID string
cloudName string
// Rate limiting configures.
rateLimiterReader flowcontrol.RateLimiter
rateLimiterWriter flowcontrol.RateLimiter
// ARM throttling configures.
RetryAfterReader time.Time
RetryAfterWriter time.Time
}
// New creates a new Snapshot client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := config.Authorizer
apiVersion := APIVersion
if strings.EqualFold(config.CloudName, AzureStackCloudName) && !config.DisableAzureStackCloud {
apiVersion = AzureStackCloudAPIVersion
}
armClient := armclient.New(authorizer, *config, baseURI, apiVersion)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
if azclients.RateLimitEnabled(config.RateLimitConfig) {
klog.V(2).Infof("Azure SnapshotClient (read ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPS,
config.RateLimitConfig.CloudProviderRateLimitBucket)
klog.V(2).Infof("Azure SnapshotClient (write ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPSWrite,
config.RateLimitConfig.CloudProviderRateLimitBucketWrite)
}
client := &Client{
armClient: armClient,
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
subscriptionID: config.SubscriptionID,
cloudName: config.CloudName,
}
return client
}
// Get gets a Snapshot.
func (c *Client) Get(ctx context.Context, resourceGroupName string, snapshotName string) (compute.Snapshot, *retry.Error) {
mc := metrics.NewMetricContext("snapshot", "get", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return compute.Snapshot{}, retry.GetRateLimitError(false, "SnapshotGet")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("SnapshotGet", "client throttled", c.RetryAfterReader)
return compute.Snapshot{}, rerr
}
result, rerr := c.getSnapshot(ctx, resourceGroupName, snapshotName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// getSnapshot gets a Snapshot.
func (c *Client) getSnapshot(ctx context.Context, resourceGroupName string, snapshotName string) (compute.Snapshot, *retry.Error) {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Compute/snapshots",
snapshotName,
)
result := compute.Snapshot{}
response, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "snapshot.get.request", resourceID, rerr.Error())
return result, rerr
}
err := autorest.Respond(
response,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "snapshot.get.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}
// Delete deletes a Snapshot by name.
func (c *Client) Delete(ctx context.Context, resourceGroupName string, snapshotName string) *retry.Error {
mc := metrics.NewMetricContext("snapshot", "delete", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "SnapshotDelete")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("SnapshotDelete", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.deleteSnapshot(ctx, resourceGroupName, snapshotName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// deleteSnapshot deletes a PublicIPAddress by name.
func (c *Client) deleteSnapshot(ctx context.Context, resourceGroupName string, snapshotName string) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Compute/snapshots",
snapshotName,
)
return c.armClient.DeleteResource(ctx, resourceID, "")
}
// CreateOrUpdate creates or updates a Snapshot.
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, snapshotName string, snapshot compute.Snapshot) *retry.Error {
mc := metrics.NewMetricContext("snapshot", "create_or_update", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "SnapshotCreateOrUpdate")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("SnapshotCreateOrUpdate", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.createOrUpdateSnapshot(ctx, resourceGroupName, snapshotName, snapshot)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// createOrUpdateSnapshot creates or updates a Snapshot.
func (c *Client) createOrUpdateSnapshot(ctx context.Context, resourceGroupName string, snapshotName string, snapshot compute.Snapshot) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Compute/snapshots",
snapshotName,
)
response, rerr := c.armClient.PutResource(ctx, resourceID, snapshot)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "snapshot.put.request", resourceID, rerr.Error())
return rerr
}
if response != nil && response.StatusCode != http.StatusNoContent {
_, rerr = c.createOrUpdateResponder(response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "snapshot.put.respond", resourceID, rerr.Error())
return rerr
}
}
return nil
}
func (c *Client) createOrUpdateResponder(resp *http.Response) (*compute.Snapshot, *retry.Error) {
result := &compute.Snapshot{}
err := autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return result, retry.GetError(resp, err)
}
// ListByResourceGroup get a list snapshots by resourceGroup.
func (c *Client) ListByResourceGroup(ctx context.Context, resourceGroupName string) ([]compute.Snapshot, *retry.Error) {
mc := metrics.NewMetricContext("snapshot", "list_by_resource_group", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return nil, retry.GetRateLimitError(false, "SnapshotListByResourceGroup")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("SnapshotListByResourceGroup", "client throttled", c.RetryAfterReader)
return nil, rerr
}
result, rerr := c.listSnapshotsByResourceGroup(ctx, resourceGroupName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// listSnapshotsByResourceGroup gets a list of snapshots in the resource group.
func (c *Client) listSnapshotsByResourceGroup(ctx context.Context, resourceGroupName string) ([]compute.Snapshot, *retry.Error) {
resourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/snapshots",
autorest.Encode("path", c.subscriptionID),
autorest.Encode("path", resourceGroupName))
result := make([]compute.Snapshot, 0)
page := &SnapshotListPage{}
page.fn = c.listNextResults
resp, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "snapshot.list.request", resourceID, rerr.Error())
return result, rerr
}
var err error
page.sl, err = c.listResponder(resp)
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "snapshot.list.respond", resourceID, err)
return result, retry.GetError(resp, err)
}
for {
result = append(result, page.Values()...)
// Abort the loop when there's no nextLink in the response.
if to.String(page.Response().NextLink) == "" {
break
}
if err = page.NextWithContext(ctx); err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "snapshot.list.next", resourceID, err)
return result, retry.GetError(page.Response().Response.Response, err)
}
}
return result, nil
}
func (c *Client) listResponder(resp *http.Response) (result compute.SnapshotList, err error) {
err = autorest.Respond(
resp,
autorest.ByIgnoring(),
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return
}
// SnapshotListResultPreparer prepares a request to retrieve the next set of results.
// It returns nil if no more results exist.
func (c *Client) SnapshotListResultPreparer(ctx context.Context, lr compute.SnapshotList) (*http.Request, error) {
if lr.NextLink == nil || len(to.String(lr.NextLink)) < 1 {
return nil, nil
}
decorators := []autorest.PrepareDecorator{
autorest.WithBaseURL(to.String(lr.NextLink)),
}
return c.armClient.PrepareGetRequest(ctx, decorators...)
}
// listNextResults retrieves the next set of results, if any.
func (c *Client) listNextResults(ctx context.Context, lastResults compute.SnapshotList) (result compute.SnapshotList, err error) {
req, err := c.SnapshotListResultPreparer(ctx, lastResults)
if err != nil {
return result, autorest.NewErrorWithError(err, "snapshotclient", "listNextResults", nil, "Failure preparing next results request")
}
if req == nil {
return
}
resp, rerr := c.armClient.Send(ctx, req)
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
result.Response = autorest.Response{Response: resp}
return result, autorest.NewErrorWithError(rerr.Error(), "snapshotclient", "listNextResults", resp, "Failure sending next results request")
}
result, err = c.listResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "snapshotclient", "listNextResults", resp, "Failure responding to next results request")
}
return
}
// SnapshotListPage contains a page of Snapshot values.
type SnapshotListPage struct {
fn func(context.Context, compute.SnapshotList) (compute.SnapshotList, error)
sl compute.SnapshotList
}
// NextWithContext advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
func (page *SnapshotListPage) NextWithContext(ctx context.Context) (err error) {
next, err := page.fn(ctx, page.sl)
if err != nil {
return err
}
page.sl = next
return nil
}
// Next advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
// Deprecated: Use NextWithContext() instead.
func (page *SnapshotListPage) Next() error {
return page.NextWithContext(context.Background())
}
// NotDone returns true if the page enumeration should be started or is not yet complete.
func (page SnapshotListPage) NotDone() bool {
return !page.sl.IsEmpty()
}
// Response returns the raw server response from the last page request.
func (page SnapshotListPage) Response() compute.SnapshotList {
return page.sl
}
// Values returns the slice of values for the current page or nil if there are no values.
func (page SnapshotListPage) Values() []compute.Snapshot {
if page.sl.IsEmpty() {
return nil
}
return *page.sl.Value
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 snapshotclient implements the client for Snapshots.
package snapshotclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/snapshotclient"

View File

@ -0,0 +1,50 @@
/*
Copyright 2020 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 snapshotclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
const (
// APIVersion is the API version for compute.
APIVersion = "2020-12-01"
// AzureStackCloudAPIVersion is the API version for Azure Stack
AzureStackCloudAPIVersion = "2019-03-01"
// AzureStackCloudName is the cloud name of Azure Stack
AzureStackCloudName = "AZURESTACKCLOUD"
)
// Interface is the client interface for Snapshots.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
// Get gets a Snapshot.
Get(ctx context.Context, resourceGroupName string, snapshotName string) (compute.Snapshot, *retry.Error)
// Delete deletes a Snapshot by name.
Delete(ctx context.Context, resourceGroupName string, snapshotName string) *retry.Error
// ListByResourceGroup get a list snapshots by resourceGroup.
ListByResourceGroup(ctx context.Context, resourceGroupName string) ([]compute.Snapshot, *retry.Error)
// CreateOrUpdate creates or updates a Snapshot.
CreateOrUpdate(ctx context.Context, resourceGroupName string, snapshotName string, snapshot compute.Snapshot) *retry.Error
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 mocksnapshotclient implements the mock client for Snapshots.
package mocksnapshotclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/snapshotclient/mocksnapshotclient"

View File

@ -0,0 +1,112 @@
// /*
// Copyright 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.
// */
//
// Code generated by MockGen. DO NOT EDIT.
// Source: /go/src/sigs.k8s.io/cloud-provider-azure/pkg/azureclients/snapshotclient/interface.go
// Package mocksnapshotclient is a generated GoMock package.
package mocksnapshotclient
import (
context "context"
reflect "reflect"
compute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
gomock "github.com/golang/mock/gomock"
retry "sigs.k8s.io/cloud-provider-azure/pkg/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
}
// Get mocks base method.
func (m *MockInterface) Get(ctx context.Context, resourceGroupName, snapshotName string) (compute.Snapshot, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", ctx, resourceGroupName, snapshotName)
ret0, _ := ret[0].(compute.Snapshot)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockInterfaceMockRecorder) Get(ctx, resourceGroupName, snapshotName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), ctx, resourceGroupName, snapshotName)
}
// Delete mocks base method.
func (m *MockInterface) Delete(ctx context.Context, resourceGroupName, snapshotName string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, resourceGroupName, snapshotName)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockInterfaceMockRecorder) Delete(ctx, resourceGroupName, snapshotName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockInterface)(nil).Delete), ctx, resourceGroupName, snapshotName)
}
// ListByResourceGroup mocks base method.
func (m *MockInterface) ListByResourceGroup(ctx context.Context, resourceGroupName string) ([]compute.Snapshot, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListByResourceGroup", ctx, resourceGroupName)
ret0, _ := ret[0].([]compute.Snapshot)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// ListByResourceGroup indicates an expected call of ListByResourceGroup.
func (mr *MockInterfaceMockRecorder) ListByResourceGroup(ctx, resourceGroupName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListByResourceGroup", reflect.TypeOf((*MockInterface)(nil).ListByResourceGroup), ctx, resourceGroupName)
}
// CreateOrUpdate mocks base method.
func (m *MockInterface) CreateOrUpdate(ctx context.Context, resourceGroupName, snapshotName string, snapshot compute.Snapshot) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroupName, snapshotName, snapshot)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// CreateOrUpdate indicates an expected call of CreateOrUpdate.
func (mr *MockInterfaceMockRecorder) CreateOrUpdate(ctx, resourceGroupName, snapshotName, snapshot interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockInterface)(nil).CreateOrUpdate), ctx, resourceGroupName, snapshotName, snapshot)
}

View File

@ -0,0 +1,426 @@
/*
Copyright 2020 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 subnetclient
import (
"context"
"net/http"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
var _ Interface = &Client{}
// Client implements Subnet client Interface.
type Client struct {
armClient armclient.Interface
subscriptionID string
cloudName string
// Rate limiting configures.
rateLimiterReader flowcontrol.RateLimiter
rateLimiterWriter flowcontrol.RateLimiter
// ARM throttling configures.
RetryAfterReader time.Time
RetryAfterWriter time.Time
}
// New creates a new Subnet client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := config.Authorizer
apiVersion := APIVersion
if strings.EqualFold(config.CloudName, AzureStackCloudName) && !config.DisableAzureStackCloud {
apiVersion = AzureStackCloudAPIVersion
}
armClient := armclient.New(authorizer, *config, baseURI, apiVersion)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
if azclients.RateLimitEnabled(config.RateLimitConfig) {
klog.V(2).Infof("Azure SubnetsClient (read ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPS,
config.RateLimitConfig.CloudProviderRateLimitBucket)
klog.V(2).Infof("Azure SubnetsClient (write ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPSWrite,
config.RateLimitConfig.CloudProviderRateLimitBucketWrite)
}
client := &Client{
armClient: armClient,
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
subscriptionID: config.SubscriptionID,
cloudName: config.CloudName,
}
return client
}
// Get gets a Subnet.
func (c *Client) Get(ctx context.Context, resourceGroupName string, virtualNetworkName string, subnetName string, expand string) (network.Subnet, *retry.Error) {
mc := metrics.NewMetricContext("subnets", "get", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return network.Subnet{}, retry.GetRateLimitError(false, "SubnetGet")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("SubnetGet", "client throttled", c.RetryAfterReader)
return network.Subnet{}, rerr
}
result, rerr := c.getSubnet(ctx, resourceGroupName, virtualNetworkName, subnetName, expand)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// getSubnet gets a Subnet.
func (c *Client) getSubnet(ctx context.Context, resourceGroupName string, virtualNetworkName string, subnetName string, expand string) (network.Subnet, *retry.Error) {
resourceID := armclient.GetChildResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/virtualNetworks",
virtualNetworkName,
"subnets",
subnetName,
)
result := network.Subnet{}
response, rerr := c.armClient.GetResource(ctx, resourceID, expand)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "subnet.get.request", resourceID, rerr.Error())
return result, rerr
}
err := autorest.Respond(
response,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "subnet.get.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}
// List gets a list of Subnets in the VNet.
func (c *Client) List(ctx context.Context, resourceGroupName string, virtualNetworkName string) ([]network.Subnet, *retry.Error) {
mc := metrics.NewMetricContext("subnets", "list", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return nil, retry.GetRateLimitError(false, "SubnetList")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("SubnetList", "client throttled", c.RetryAfterReader)
return nil, rerr
}
result, rerr := c.listSubnet(ctx, resourceGroupName, virtualNetworkName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// listSubnet gets a list of Subnets in the VNet.
func (c *Client) listSubnet(ctx context.Context, resourceGroupName string, virtualNetworkName string) ([]network.Subnet, *retry.Error) {
resourceID := armclient.GetChildResourcesListID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/virtualNetworks",
virtualNetworkName,
"subnets")
result := make([]network.Subnet, 0)
page := &SubnetListResultPage{}
page.fn = c.listNextResults
resp, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "subnet.list.request", resourceID, rerr.Error())
return result, rerr
}
var err error
page.slr, err = c.listResponder(resp)
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "subnet.list.respond", resourceID, err)
return result, retry.GetError(resp, err)
}
for {
result = append(result, page.Values()...)
// Abort the loop when there's no nextLink in the response.
if to.String(page.Response().NextLink) == "" {
break
}
if err = page.NextWithContext(ctx); err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "subnet.list.next", resourceID, err)
return result, retry.GetError(page.Response().Response.Response, err)
}
}
return result, nil
}
// CreateOrUpdate creates or updates a Subnet.
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, virtualNetworkName string, subnetName string, subnetParameters network.Subnet) *retry.Error {
mc := metrics.NewMetricContext("subnets", "create_or_update", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "SubnetCreateOrUpdate")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("SubnetCreateOrUpdate", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.createOrUpdateSubnet(ctx, resourceGroupName, virtualNetworkName, subnetName, subnetParameters)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// createOrUpdateSubnet creates or updates a Subnet.
func (c *Client) createOrUpdateSubnet(ctx context.Context, resourceGroupName string, virtualNetworkName string, subnetName string, subnetParameters network.Subnet) *retry.Error {
resourceID := armclient.GetChildResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/virtualNetworks",
virtualNetworkName,
"subnets",
subnetName)
response, rerr := c.armClient.PutResource(ctx, resourceID, subnetParameters)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "subnet.put.request", resourceID, rerr.Error())
return rerr
}
if response != nil && response.StatusCode != http.StatusNoContent {
_, rerr = c.createOrUpdateResponder(response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "subnet.put.respond", resourceID, rerr.Error())
return rerr
}
}
return nil
}
func (c *Client) createOrUpdateResponder(resp *http.Response) (*network.Subnet, *retry.Error) {
result := &network.Subnet{}
err := autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return result, retry.GetError(resp, err)
}
// Delete deletes a Subnet by name.
func (c *Client) Delete(ctx context.Context, resourceGroupName string, virtualNetworkName string, subnetName string) *retry.Error {
mc := metrics.NewMetricContext("subnets", "delete", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "SubnetDelete")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("SubnetDelete", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.deleteSubnet(ctx, resourceGroupName, virtualNetworkName, subnetName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterWriter = rerr.RetryAfter
}
return rerr
}
return nil
}
// deleteSubnet deletes a PublicIPAddress by name.
func (c *Client) deleteSubnet(ctx context.Context, resourceGroupName string, virtualNetworkName string, subnetName string) *retry.Error {
resourceID := armclient.GetChildResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/virtualNetworks",
virtualNetworkName,
"subnets",
subnetName)
return c.armClient.DeleteResource(ctx, resourceID, "")
}
func (c *Client) listResponder(resp *http.Response) (result network.SubnetListResult, err error) {
err = autorest.Respond(
resp,
autorest.ByIgnoring(),
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return
}
// subnetListResultPreparer prepares a request to retrieve the next set of results.
// It returns nil if no more results exist.
func (c *Client) subnetListResultPreparer(ctx context.Context, lblr network.SubnetListResult) (*http.Request, error) {
if lblr.NextLink == nil || len(to.String(lblr.NextLink)) < 1 {
return nil, nil
}
decorators := []autorest.PrepareDecorator{
autorest.WithBaseURL(to.String(lblr.NextLink)),
}
return c.armClient.PrepareGetRequest(ctx, decorators...)
}
// listNextResults retrieves the next set of results, if any.
func (c *Client) listNextResults(ctx context.Context, lastResults network.SubnetListResult) (result network.SubnetListResult, err error) {
req, err := c.subnetListResultPreparer(ctx, lastResults)
if err != nil {
return result, autorest.NewErrorWithError(err, "subnetclient", "listNextResults", nil, "Failure preparing next results request")
}
if req == nil {
return
}
resp, rerr := c.armClient.Send(ctx, req)
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
result.Response = autorest.Response{Response: resp}
return result, autorest.NewErrorWithError(rerr.Error(), "subnetclient", "listNextResults", resp, "Failure sending next results request")
}
result, err = c.listResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "subnetclient", "listNextResults", resp, "Failure responding to next results request")
}
return
}
// SubnetListResultPage contains a page of Subnet values.
type SubnetListResultPage struct {
fn func(context.Context, network.SubnetListResult) (network.SubnetListResult, error)
slr network.SubnetListResult
}
// NextWithContext advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
func (page *SubnetListResultPage) NextWithContext(ctx context.Context) (err error) {
next, err := page.fn(ctx, page.slr)
if err != nil {
return err
}
page.slr = next
return nil
}
// Next advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
// Deprecated: Use NextWithContext() instead.
func (page *SubnetListResultPage) Next() error {
return page.NextWithContext(context.Background())
}
// NotDone returns true if the page enumeration should be started or is not yet complete.
func (page SubnetListResultPage) NotDone() bool {
return !page.slr.IsEmpty()
}
// Response returns the raw server response from the last page request.
func (page SubnetListResultPage) Response() network.SubnetListResult {
return page.slr
}
// Values returns the slice of values for the current page or nil if there are no values.
func (page SubnetListResultPage) Values() []network.Subnet {
if page.slr.IsEmpty() {
return nil
}
return *page.slr.Value
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 subnetclient implements the client for Subnet.
package subnetclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/subnetclient"

View File

@ -0,0 +1,50 @@
/*
Copyright 2020 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 subnetclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2021-02-01"
// AzureStackCloudAPIVersion is the API version for Azure Stack
AzureStackCloudAPIVersion = "2018-11-01"
// AzureStackCloudName is the cloud name of Azure Stack
AzureStackCloudName = "AZURESTACKCLOUD"
)
// Interface is the client interface for Subnet.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
// Get gets a Subnet.
Get(ctx context.Context, resourceGroupName string, virtualNetworkName string, subnetName string, expand string) (result network.Subnet, rerr *retry.Error)
// List gets a list of Subnet in the VNet.
List(ctx context.Context, resourceGroupName string, virtualNetworkName string) (result []network.Subnet, rerr *retry.Error)
// CreateOrUpdate creates or updates a Subnet.
CreateOrUpdate(ctx context.Context, resourceGroupName string, virtualNetworkName string, subnetName string, subnetParameters network.Subnet) *retry.Error
// Delete deletes a Subnet by name.
Delete(ctx context.Context, resourceGroupName string, virtualNetworkName string, subnetName string) *retry.Error
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 mocksubnetclient implements the mock client for Subnet.
package mocksubnetclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/subnetclient/mocksubnetclient"

View File

@ -0,0 +1,112 @@
// /*
// Copyright 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.
// */
//
// Code generated by MockGen. DO NOT EDIT.
// Source: /go/src/sigs.k8s.io/cloud-provider-azure/pkg/azureclients/subnetclient/interface.go
// Package mocksubnetclient is a generated GoMock package.
package mocksubnetclient
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
gomock "github.com/golang/mock/gomock"
retry "sigs.k8s.io/cloud-provider-azure/pkg/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
}
// Get mocks base method.
func (m *MockInterface) Get(ctx context.Context, resourceGroupName, virtualNetworkName, subnetName, expand string) (network.Subnet, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", ctx, resourceGroupName, virtualNetworkName, subnetName, expand)
ret0, _ := ret[0].(network.Subnet)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockInterfaceMockRecorder) Get(ctx, resourceGroupName, virtualNetworkName, subnetName, expand interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), ctx, resourceGroupName, virtualNetworkName, subnetName, expand)
}
// List mocks base method.
func (m *MockInterface) List(ctx context.Context, resourceGroupName, virtualNetworkName string) ([]network.Subnet, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, resourceGroupName, virtualNetworkName)
ret0, _ := ret[0].([]network.Subnet)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// List indicates an expected call of List.
func (mr *MockInterfaceMockRecorder) List(ctx, resourceGroupName, virtualNetworkName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockInterface)(nil).List), ctx, resourceGroupName, virtualNetworkName)
}
// CreateOrUpdate mocks base method.
func (m *MockInterface) CreateOrUpdate(ctx context.Context, resourceGroupName, virtualNetworkName, subnetName string, subnetParameters network.Subnet) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroupName, virtualNetworkName, subnetName, subnetParameters)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// CreateOrUpdate indicates an expected call of CreateOrUpdate.
func (mr *MockInterfaceMockRecorder) CreateOrUpdate(ctx, resourceGroupName, virtualNetworkName, subnetName, subnetParameters interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockInterface)(nil).CreateOrUpdate), ctx, resourceGroupName, virtualNetworkName, subnetName, subnetParameters)
}
// Delete mocks base method.
func (m *MockInterface) Delete(ctx context.Context, resourceGroupName, virtualNetworkName, subnetName string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, resourceGroupName, virtualNetworkName, subnetName)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockInterfaceMockRecorder) Delete(ctx, resourceGroupName, virtualNetworkName, subnetName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockInterface)(nil).Delete), ctx, resourceGroupName, virtualNetworkName, subnetName)
}

View File

@ -0,0 +1,65 @@
/*
Copyright 2021 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 virtualnetworklinksclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
)
var _ Interface = &Client{}
// Client implements virtualnetworklinksclient Interface.
type Client struct {
virtualNetworkLinksClient privatedns.VirtualNetworkLinksClient
}
// New creates a new virtualnetworklinks client.
func New(config *azclients.ClientConfig) *Client {
virtualNetworkLinksClient := privatedns.NewVirtualNetworkLinksClient(config.SubscriptionID)
virtualNetworkLinksClient.Authorizer = config.Authorizer
client := &Client{
virtualNetworkLinksClient: virtualNetworkLinksClient,
}
return client
}
// CreateOrUpdate creates or updates a virtual network link
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, privateZoneName string, virtualNetworkLinkName string, parameters privatedns.VirtualNetworkLink, waitForCompletion bool) error {
createOrUpdateFuture, err := c.virtualNetworkLinksClient.CreateOrUpdate(ctx, resourceGroupName, privateZoneName, virtualNetworkLinkName, parameters, "", "*")
if err != nil {
klog.V(5).Infof("Received error for %s, resourceGroup: %s, privateZoneName: %s, error: %s", "virtualnetworklinks.put.request", resourceGroupName, privateZoneName, err)
return err
}
if waitForCompletion {
err := createOrUpdateFuture.WaitForCompletionRef(ctx, c.virtualNetworkLinksClient.Client)
if err != nil {
klog.V(5).Infof("Received error while waiting for completion for %s, resourceGroup: %s, privateZoneName: %s, error: %s", "virtualnetworklinks.put.request", resourceGroupName, privateZoneName, err)
return err
}
}
return nil
}
// Get gets a virtual network link
func (c *Client) Get(ctx context.Context, resourceGroupName string, privateZoneName string, virtualNetworkLinkName string) (result privatedns.VirtualNetworkLink, err error) {
return c.virtualNetworkLinksClient.Get(ctx, resourceGroupName, privateZoneName, virtualNetworkLinkName)
}

View File

@ -0,0 +1,34 @@
/*
Copyright 2021 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 virtualnetworklinksclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns"
)
// Interface is the client interface for Virtual Network Link.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
// Get gets a virtual network link
Get(ctx context.Context, resourceGroupName string, privateZoneName string, virtualNetworkLinkName string) (result privatedns.VirtualNetworkLink, err error)
// CreateOrUpdate creates or updates a private dns zone.
CreateOrUpdate(ctx context.Context, resourceGroupName string, privateZoneName string, virtualNetworkLinkName string, parameters privatedns.VirtualNetworkLink, waitForCompletion bool) error
}

View File

@ -0,0 +1,308 @@
/*
Copyright 2021 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 vmasclient
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
//var _ Interface = &Client{}
// Client implements VMAS client Interface.
type Client struct {
armClient armclient.Interface
subscriptionID string
cloudName string
// Rate limiting configures.
rateLimiterReader flowcontrol.RateLimiter
rateLimiterWriter flowcontrol.RateLimiter
// ARM throttling configures.
RetryAfterReader time.Time
RetryAfterWriter time.Time
}
// New creates a new VMAS client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := config.Authorizer
apiVersion := APIVersion
if strings.EqualFold(config.CloudName, AzureStackCloudName) && !config.DisableAzureStackCloud {
apiVersion = AzureStackCloudAPIVersion
}
armClient := armclient.New(authorizer, *config, baseURI, apiVersion)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
if azclients.RateLimitEnabled(config.RateLimitConfig) {
klog.V(2).Infof("Azure AvailabilitySetsClient (read ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPS,
config.RateLimitConfig.CloudProviderRateLimitBucket)
klog.V(2).Infof("Azure AvailabilitySetsClient (write ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPSWrite,
config.RateLimitConfig.CloudProviderRateLimitBucketWrite)
}
client := &Client{
armClient: armClient,
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
subscriptionID: config.SubscriptionID,
cloudName: config.CloudName,
}
return client
}
// Get gets a AvailabilitySet.
func (c *Client) Get(ctx context.Context, resourceGroupName string, vmasName string) (compute.AvailabilitySet, *retry.Error) {
mc := metrics.NewMetricContext("vmas", "get", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return compute.AvailabilitySet{}, retry.GetRateLimitError(false, "VMASGet")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("VMASGet", "client throttled", c.RetryAfterReader)
return compute.AvailabilitySet{}, rerr
}
result, rerr := c.getVMAS(ctx, resourceGroupName, vmasName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// getVMAS gets a AvailabilitySet.
func (c *Client) getVMAS(ctx context.Context, resourceGroupName string, vmasName string) (compute.AvailabilitySet, *retry.Error) {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Compute/availabilitySets",
vmasName,
)
result := compute.AvailabilitySet{}
response, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vmas.get.request", resourceID, rerr.Error())
return result, rerr
}
err := autorest.Respond(
response,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vmas.get.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}
// List gets a list of AvailabilitySets in the resource group.
func (c *Client) List(ctx context.Context, resourceGroupName string) ([]compute.AvailabilitySet, *retry.Error) {
mc := metrics.NewMetricContext("vmas", "list", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return nil, retry.GetRateLimitError(false, "VMASList")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("VMASList", "client throttled", c.RetryAfterReader)
return nil, rerr
}
result, rerr := c.listVMAS(ctx, resourceGroupName)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// listVMAS gets a list of AvailabilitySets in the resource group.
func (c *Client) listVMAS(ctx context.Context, resourceGroupName string) ([]compute.AvailabilitySet, *retry.Error) {
resourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/availabilitySets",
autorest.Encode("path", c.subscriptionID),
autorest.Encode("path", resourceGroupName))
result := make([]compute.AvailabilitySet, 0)
page := &AvailabilitySetListResultPage{}
page.fn = c.listNextResults
resp, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vmas.list.request", resourceID, rerr.Error())
return result, rerr
}
var err error
page.vmaslr, err = c.listResponder(resp)
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vmas.list.respond", resourceID, err)
return result, retry.GetError(resp, err)
}
for {
result = append(result, page.Values()...)
// Abort the loop when there's no nextLink in the response.
if to.String(page.Response().NextLink) == "" {
break
}
if err = page.NextWithContext(ctx); err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vmas.list.next", resourceID, err)
return result, retry.GetError(page.Response().Response.Response, err)
}
}
return result, nil
}
func (c *Client) listResponder(resp *http.Response) (result compute.AvailabilitySetListResult, err error) {
err = autorest.Respond(
resp,
autorest.ByIgnoring(),
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
result.Response = autorest.Response{Response: resp}
return
}
// availabilitySetListResultPreparer prepares a request to retrieve the next set of results.
// It returns nil if no more results exist.
func (c *Client) availabilitySetListResultPreparer(ctx context.Context, vmaslr compute.AvailabilitySetListResult) (*http.Request, error) {
if vmaslr.NextLink == nil || len(to.String(vmaslr.NextLink)) < 1 {
return nil, nil
}
decorators := []autorest.PrepareDecorator{
autorest.WithBaseURL(to.String(vmaslr.NextLink)),
}
return c.armClient.PrepareGetRequest(ctx, decorators...)
}
// listNextResults retrieves the next set of results, if any.
func (c *Client) listNextResults(ctx context.Context, lastResults compute.AvailabilitySetListResult) (result compute.AvailabilitySetListResult, err error) {
req, err := c.availabilitySetListResultPreparer(ctx, lastResults)
if err != nil {
return result, autorest.NewErrorWithError(err, "vmasclient", "listNextResults", nil, "Failure preparing next results request")
}
if req == nil {
return
}
resp, rerr := c.armClient.Send(ctx, req)
defer c.armClient.CloseResponse(ctx, resp)
if rerr != nil {
result.Response = autorest.Response{Response: resp}
return result, autorest.NewErrorWithError(rerr.Error(), "vmasclient", "listNextResults", resp, "Failure sending next results request")
}
result, err = c.listResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "vmasclient", "listNextResults", resp, "Failure responding to next results request")
}
return
}
// AvailabilitySetListResultPage contains a page of AvailabilitySet values.
type AvailabilitySetListResultPage struct {
fn func(context.Context, compute.AvailabilitySetListResult) (compute.AvailabilitySetListResult, error)
vmaslr compute.AvailabilitySetListResult
}
// NextWithContext advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
func (page *AvailabilitySetListResultPage) NextWithContext(ctx context.Context) (err error) {
next, err := page.fn(ctx, page.vmaslr)
if err != nil {
return err
}
page.vmaslr = next
return nil
}
// Next advances to the next page of values. If there was an error making
// the request the page does not advance and the error is returned.
// Deprecated: Use NextWithContext() instead.
func (page *AvailabilitySetListResultPage) Next() error {
return page.NextWithContext(context.Background())
}
// NotDone returns true if the page enumeration should be started or is not yet complete.
func (page AvailabilitySetListResultPage) NotDone() bool {
return !page.vmaslr.IsEmpty()
}
// Response returns the raw server response from the last page request.
func (page AvailabilitySetListResultPage) Response() compute.AvailabilitySetListResult {
return page.vmaslr
}
// Values returns the slice of values for the current page or nil if there are no values.
func (page AvailabilitySetListResultPage) Values() []compute.AvailabilitySet {
if page.vmaslr.IsEmpty() {
return nil
}
return *page.vmaslr.Value
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2021 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 vmasclient implements the client for VMAS.
package vmasclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmasclient"

View File

@ -0,0 +1,43 @@
/*
Copyright 2021 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 vmasclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
const (
// APIVersion is the API version for VMAS.
APIVersion = "2020-12-01"
// AzureStackCloudAPIVersion is the API version for Azure Stack
AzureStackCloudAPIVersion = "2019-07-01"
// AzureStackCloudName is the cloud name of Azure Stack
AzureStackCloudName = "AZURESTACKCLOUD"
)
// Interface is the client interface for AvailabilitySet.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
// Get gets a VirtualMachineScaleSet.
Get(ctx context.Context, resourceGroupName string, VMScaleSetName string) (result compute.AvailabilitySet, rerr *retry.Error)
// List gets a list of VirtualMachineScaleSets in the resource group.
List(ctx context.Context, resourceGroupName string) (result []compute.AvailabilitySet, rerr *retry.Error)
}

View File

@ -0,0 +1,144 @@
/*
Copyright 2020 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 vmsizeclient
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
var _ Interface = &Client{}
// Client implements VirtualMachineSize client Interface.
type Client struct {
armClient armclient.Interface
subscriptionID string
cloudName string
// Rate limiting configures.
rateLimiterReader flowcontrol.RateLimiter
rateLimiterWriter flowcontrol.RateLimiter
// ARM throttling configures.
RetryAfterReader time.Time
RetryAfterWriter time.Time
}
// New creates a new VirtualMachineSize client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := config.Authorizer
apiVersion := APIVersion
if strings.EqualFold(config.CloudName, AzureStackCloudName) && !config.DisableAzureStackCloud {
apiVersion = AzureStackCloudAPIVersion
}
armClient := armclient.New(authorizer, *config, baseURI, apiVersion)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
if azclients.RateLimitEnabled(config.RateLimitConfig) {
klog.V(2).Infof("Azure VirtualMachineSizesClient (read ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPS,
config.RateLimitConfig.CloudProviderRateLimitBucket)
klog.V(2).Infof("Azure VirtualMachineSizesClient (write ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPSWrite,
config.RateLimitConfig.CloudProviderRateLimitBucketWrite)
}
client := &Client{
armClient: armClient,
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
subscriptionID: config.SubscriptionID,
cloudName: config.CloudName,
}
return client
}
// List gets compute.VirtualMachineSizeListResult.
func (c *Client) List(ctx context.Context, location string) (compute.VirtualMachineSizeListResult, *retry.Error) {
mc := metrics.NewMetricContext("vmsizes", "list", "", c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return compute.VirtualMachineSizeListResult{}, retry.GetRateLimitError(false, "VMSizesList")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("VMSizesList", "client throttled", c.RetryAfterReader)
return compute.VirtualMachineSizeListResult{}, rerr
}
result, rerr := c.listVirtualMachineSizes(ctx, location)
mc.Observe(rerr)
if rerr != nil {
if rerr.IsThrottled() {
// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
c.RetryAfterReader = rerr.RetryAfter
}
return result, rerr
}
return result, nil
}
// listVirtualMachineSizes gets compute.VirtualMachineSizeListResult.
func (c *Client) listVirtualMachineSizes(ctx context.Context, location string) (compute.VirtualMachineSizeListResult, *retry.Error) {
resourceID := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/vmSizes",
autorest.Encode("path", c.subscriptionID),
autorest.Encode("path", location),
)
result := compute.VirtualMachineSizeListResult{}
response, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vmsize.list.request", resourceID, rerr.Error())
return result, rerr
}
err := autorest.Respond(
response,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vmsize.list.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 vmsizeclient implements the client for VirtualMachineSizes.
package vmsizeclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmsizeclient"

View File

@ -0,0 +1,40 @@
/*
Copyright 2020 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 vmsizeclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
const (
// APIVersion is the API version for compute.
APIVersion = "2020-12-01"
// AzureStackCloudAPIVersion is the API version for Azure Stack
AzureStackCloudAPIVersion = "2017-12-01"
// AzureStackCloudName is the cloud name of Azure Stack
AzureStackCloudName = "AZURESTACKCLOUD"
)
// Interface is the client interface for VirtualMachineSizes.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
// List gets compute.VirtualMachineSizeListResult.
List(ctx context.Context, location string) (result compute.VirtualMachineSizeListResult, rerr *retry.Error)
}

View File

@ -0,0 +1,134 @@
/*
Copyright 2021 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 zoneclient
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"k8s.io/klog/v2"
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/armclient"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
var _ Interface = &Client{}
type resourceTypeMetadata struct {
ResourceType string `json:"resourceType"`
ZoneMappings []zoneMappings `json:"zoneMappings"`
}
type zoneMappings struct {
Location string `json:"location"`
Zones []string `json:"zones"`
}
type providerListDataProperty struct {
ID string `json:"id"`
ResourceTypes []resourceTypeMetadata `json:"resourceTypes"`
}
type providerListData struct {
ProviderListDataProperties []providerListDataProperty `json:"value"`
}
// Client implements zone client Interface.
type Client struct {
armClient armclient.Interface
subscriptionID string
cloudName string
}
// New creates a new zone client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := config.Authorizer
apiVersion := APIVersion
if strings.EqualFold(config.CloudName, AzureStackCloudName) && !config.DisableAzureStackCloud {
apiVersion = AzureStackCloudAPIVersion
}
armClient := armclient.New(authorizer, *config, baseURI, apiVersion)
client := &Client{
armClient: armClient,
subscriptionID: config.SubscriptionID,
cloudName: config.CloudName,
}
return client
}
// GetZones gets the region-zone map for the subscription specified
func (c *Client) GetZones(ctx context.Context, subscriptionID string) (map[string][]string, *retry.Error) {
result, rerr := c.getZones(ctx, subscriptionID)
if rerr != nil {
return result, rerr
}
return result, nil
}
// getZones gets the region-zone map for the subscription specified
func (c *Client) getZones(ctx context.Context, subscriptionID string) (map[string][]string, *retry.Error) {
resourceID := armclient.GetProviderResourcesListID(subscriptionID)
response, rerr := c.armClient.GetResource(ctx, resourceID, "")
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "zone.get.request", resourceID, rerr.Error())
return nil, rerr
}
result := providerListData{}
err := autorest.Respond(
response,
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result))
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "zone.get.respond", resourceID, err)
return nil, retry.GetError(response, err)
}
regionZoneMap := make(map[string][]string)
expectedID := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute", subscriptionID)
if len(result.ProviderListDataProperties) != 0 {
for _, property := range result.ProviderListDataProperties {
if strings.EqualFold(property.ID, expectedID) {
for _, resourceType := range property.ResourceTypes {
if strings.EqualFold(resourceType.ResourceType, "virtualMachines") {
if len(resourceType.ZoneMappings) != 0 {
for _, zoneMapping := range resourceType.ZoneMappings {
location := strings.ToLower(strings.ReplaceAll(zoneMapping.Location, " ", ""))
regionZoneMap[location] = zoneMapping.Zones
}
}
}
}
}
}
}
return regionZoneMap, nil
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2021 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 zoneclient implements the client for ARM.
package zoneclient // import "sigs.k8s.io/cloud-provider-azure/pkg/azureclients/zoneclient"

View File

@ -0,0 +1,38 @@
/*
Copyright 2021 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 zoneclient
import (
"context"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
const (
// APIVersion is the API version for provider list api.
APIVersion = "2020-06-01"
// AzureStackCloudAPIVersion is the API version for Azure Stack
AzureStackCloudAPIVersion = "2019-07-01"
// AzureStackCloudName is the cloud name of Azure Stack
AzureStackCloudName = "AZURESTACKCLOUD"
)
// Interface is the client interface for ARM.
// Don't forget to run "hack/update-mock-clients.sh" command to generate the mock client.
type Interface interface {
GetZones(ctx context.Context, subscriptionID string) (map[string][]string, *retry.Error)
}

View File

@ -0,0 +1,175 @@
/*
Copyright 2020 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 cache
import (
"fmt"
"sync"
"time"
"k8s.io/client-go/tools/cache"
)
// AzureCacheReadType defines the read type for cache data
type AzureCacheReadType int
const (
// CacheReadTypeDefault returns data from cache if cache entry not expired
// if cache entry expired, then it will refetch the data using getter
// save the entry in cache and then return
CacheReadTypeDefault AzureCacheReadType = iota
// CacheReadTypeUnsafe returns data from cache even if the cache entry is
// active/expired. If entry doesn't exist in cache, then data is fetched
// using getter, saved in cache and returned
CacheReadTypeUnsafe
// CacheReadTypeForceRefresh force refreshes the cache even if the cache entry
// is not expired
CacheReadTypeForceRefresh
)
// GetFunc defines a getter function for timedCache.
type GetFunc func(key string) (interface{}, error)
// AzureCacheEntry is the internal structure stores inside TTLStore.
type AzureCacheEntry struct {
Key string
Data interface{}
// The lock to ensure not updating same entry simultaneously.
Lock sync.Mutex
// time when entry was fetched and created
CreatedOn time.Time
}
// cacheKeyFunc defines the key function required in TTLStore.
func cacheKeyFunc(obj interface{}) (string, error) {
return obj.(*AzureCacheEntry).Key, nil
}
// TimedCache is a cache with TTL.
type TimedCache struct {
Store cache.Store
Lock sync.Mutex
Getter GetFunc
TTL time.Duration
}
// NewTimedcache creates a new TimedCache.
func NewTimedcache(ttl time.Duration, getter GetFunc) (*TimedCache, error) {
if getter == nil {
return nil, fmt.Errorf("getter is not provided")
}
return &TimedCache{
Getter: getter,
// switch to using NewStore instead of NewTTLStore so that we can
// reuse entries for calls that are fine with reading expired/stalled data.
// with NewTTLStore, entries are not returned if they have already expired.
Store: cache.NewStore(cacheKeyFunc),
TTL: ttl,
}, nil
}
// getInternal returns AzureCacheEntry by key. If the key is not cached yet,
// it returns a AzureCacheEntry with nil data.
func (t *TimedCache) getInternal(key string) (*AzureCacheEntry, error) {
entry, exists, err := t.Store.GetByKey(key)
if err != nil {
return nil, err
}
// if entry exists, return the entry
if exists {
return entry.(*AzureCacheEntry), nil
}
// lock here to ensure if entry doesn't exist, we add a new entry
// avoiding overwrites
t.Lock.Lock()
defer t.Lock.Unlock()
// Another goroutine might have written the same key.
entry, exists, err = t.Store.GetByKey(key)
if err != nil {
return nil, err
}
if exists {
return entry.(*AzureCacheEntry), nil
}
// Still not found, add new entry with nil data.
// Note the data will be filled later by getter.
newEntry := &AzureCacheEntry{
Key: key,
Data: nil,
}
_ = t.Store.Add(newEntry)
return newEntry, nil
}
// Get returns the requested item by key.
func (t *TimedCache) Get(key string, crt AzureCacheReadType) (interface{}, error) {
entry, err := t.getInternal(key)
if err != nil {
return nil, err
}
entry.Lock.Lock()
defer entry.Lock.Unlock()
// entry exists and if cache is not force refreshed
if entry.Data != nil && crt != CacheReadTypeForceRefresh {
// allow unsafe read, so return data even if expired
if crt == CacheReadTypeUnsafe {
return entry.Data, nil
}
// if cached data is not expired, return cached data
if crt == CacheReadTypeDefault && time.Since(entry.CreatedOn) < t.TTL {
return entry.Data, nil
}
}
// Data is not cached yet, cache data is expired or requested force refresh
// cache it by getter. entry is locked before getting to ensure concurrent
// gets don't result in multiple ARM calls.
data, err := t.Getter(key)
if err != nil {
return nil, err
}
// set the data in cache and also set the last update time
// to now as the data was recently fetched
entry.Data = data
entry.CreatedOn = time.Now().UTC()
return entry.Data, nil
}
// Delete removes an item from the cache.
func (t *TimedCache) Delete(key string) error {
return t.Store.Delete(&AzureCacheEntry{
Key: key,
})
}
// Set sets the data cache for the key.
// It is only used for testing.
func (t *TimedCache) Set(key string, data interface{}) {
_ = t.Store.Add(&AzureCacheEntry{
Key: key,
Data: data,
CreatedOn: time.Now().UTC(),
})
}

View File

@ -0,0 +1,18 @@
/*
Copyright 2020 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 cache is an implementation of Azure caches.
package cache // import "sigs.k8s.io/cloud-provider-azure/pkg/cache"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,543 @@
/*
Copyright 2020 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 provider
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"
"strings"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/go-autorest/autorest/to"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
cloudprovider "k8s.io/cloud-provider"
"k8s.io/klog/v2"
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
var (
pipErrorMessageRE = regexp.MustCompile(`(?:.*)/subscriptions/(?:.*)/resourceGroups/(.*)/providers/Microsoft.Network/publicIPAddresses/([^\s]+)(?:.*)`)
)
// RequestBackoff if backoff is disabled in cloud provider it
// returns a new Backoff object steps = 1
// This is to make sure that the requested command executes
// at least once
func (az *Cloud) RequestBackoff() (resourceRequestBackoff wait.Backoff) {
if az.CloudProviderBackoff {
return az.ResourceRequestBackoff
}
resourceRequestBackoff = wait.Backoff{
Steps: 1,
}
return resourceRequestBackoff
}
// Event creates a event for the specified object.
func (az *Cloud) Event(obj runtime.Object, eventType, reason, message string) {
if obj != nil && reason != "" {
az.eventRecorder.Event(obj, eventType, reason, message)
}
}
// GetVirtualMachineWithRetry invokes az.getVirtualMachine with exponential backoff retry
func (az *Cloud) GetVirtualMachineWithRetry(name types.NodeName, crt azcache.AzureCacheReadType) (compute.VirtualMachine, error) {
var machine compute.VirtualMachine
var retryErr error
err := wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, error) {
machine, retryErr = az.getVirtualMachine(name, crt)
if errors.Is(retryErr, cloudprovider.InstanceNotFound) {
return true, cloudprovider.InstanceNotFound
}
if retryErr != nil {
klog.Errorf("GetVirtualMachineWithRetry(%s): backoff failure, will retry, err=%v", name, retryErr)
return false, nil
}
klog.V(2).Infof("GetVirtualMachineWithRetry(%s): backoff success", name)
return true, nil
})
if errors.Is(err, wait.ErrWaitTimeout) {
err = retryErr
}
return machine, err
}
// ListVirtualMachines invokes az.VirtualMachinesClient.List with exponential backoff retry
func (az *Cloud) ListVirtualMachines(resourceGroup string) ([]compute.VirtualMachine, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
allNodes, rerr := az.VirtualMachinesClient.List(ctx, resourceGroup)
if rerr != nil {
klog.Errorf("VirtualMachinesClient.List(%v) failure with err=%v", resourceGroup, rerr)
return nil, rerr.Error()
}
klog.V(2).Infof("VirtualMachinesClient.List(%v) success", resourceGroup)
return allNodes, nil
}
// getPrivateIPsForMachine is wrapper for optional backoff getting private ips
// list of a node by name
func (az *Cloud) getPrivateIPsForMachine(nodeName types.NodeName) ([]string, error) {
return az.getPrivateIPsForMachineWithRetry(nodeName)
}
func (az *Cloud) getPrivateIPsForMachineWithRetry(nodeName types.NodeName) ([]string, error) {
var privateIPs []string
err := wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, error) {
var retryErr error
privateIPs, retryErr = az.VMSet.GetPrivateIPsByNodeName(string(nodeName))
if retryErr != nil {
// won't retry since the instance doesn't exist on Azure.
if errors.Is(retryErr, cloudprovider.InstanceNotFound) {
return true, retryErr
}
klog.Errorf("GetPrivateIPsByNodeName(%s): backoff failure, will retry,err=%v", nodeName, retryErr)
return false, nil
}
klog.V(3).Infof("GetPrivateIPsByNodeName(%s): backoff success", nodeName)
return true, nil
})
return privateIPs, err
}
func (az *Cloud) getIPForMachine(nodeName types.NodeName) (string, string, error) {
return az.GetIPForMachineWithRetry(nodeName)
}
// GetIPForMachineWithRetry invokes az.getIPForMachine with exponential backoff retry
func (az *Cloud) GetIPForMachineWithRetry(name types.NodeName) (string, string, error) {
var ip, publicIP string
err := wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, error) {
var retryErr error
ip, publicIP, retryErr = az.VMSet.GetIPByNodeName(string(name))
if retryErr != nil {
klog.Errorf("GetIPForMachineWithRetry(%s): backoff failure, will retry,err=%v", name, retryErr)
return false, nil
}
klog.V(3).Infof("GetIPForMachineWithRetry(%s): backoff success", name)
return true, nil
})
return ip, publicIP, err
}
// CreateOrUpdateSecurityGroup invokes az.SecurityGroupsClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) CreateOrUpdateSecurityGroup(sg network.SecurityGroup) error {
ctx, cancel := getContextWithCancel()
defer cancel()
rerr := az.SecurityGroupsClient.CreateOrUpdate(ctx, az.SecurityGroupResourceGroup, *sg.Name, sg, to.String(sg.Etag))
klog.V(10).Infof("SecurityGroupsClient.CreateOrUpdate(%s): end", *sg.Name)
if rerr == nil {
// Invalidate the cache right after updating
_ = az.nsgCache.Delete(*sg.Name)
return nil
}
nsgJSON, _ := json.Marshal(sg)
klog.Warningf("CreateOrUpdateSecurityGroup(%s) failed: %v, NSG request: %s", to.String(sg.Name), rerr.Error(), string(nsgJSON))
// Invalidate the cache because ETAG precondition mismatch.
if rerr.HTTPStatusCode == http.StatusPreconditionFailed {
klog.V(3).Infof("SecurityGroup cache for %s is cleanup because of http.StatusPreconditionFailed", *sg.Name)
_ = az.nsgCache.Delete(*sg.Name)
}
// Invalidate the cache because another new operation has canceled the current request.
if strings.Contains(strings.ToLower(rerr.Error().Error()), consts.OperationCanceledErrorMessage) {
klog.V(3).Infof("SecurityGroup cache for %s is cleanup because CreateOrUpdateSecurityGroup is canceled by another operation", *sg.Name)
_ = az.nsgCache.Delete(*sg.Name)
}
return rerr.Error()
}
func cleanupSubnetInFrontendIPConfigurations(lb *network.LoadBalancer) network.LoadBalancer {
if lb.LoadBalancerPropertiesFormat == nil || lb.FrontendIPConfigurations == nil {
return *lb
}
frontendIPConfigurations := *lb.FrontendIPConfigurations
for i := range frontendIPConfigurations {
config := frontendIPConfigurations[i]
if config.FrontendIPConfigurationPropertiesFormat != nil &&
config.Subnet != nil &&
config.Subnet.ID != nil {
subnet := network.Subnet{
ID: config.Subnet.ID,
}
if config.Subnet.Name != nil {
subnet.Name = config.FrontendIPConfigurationPropertiesFormat.Subnet.Name
}
config.FrontendIPConfigurationPropertiesFormat.Subnet = &subnet
frontendIPConfigurations[i] = config
continue
}
}
lb.FrontendIPConfigurations = &frontendIPConfigurations
return *lb
}
// CreateOrUpdateLB invokes az.LoadBalancerClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) CreateOrUpdateLB(service *v1.Service, lb network.LoadBalancer) error {
ctx, cancel := getContextWithCancel()
defer cancel()
lb = cleanupSubnetInFrontendIPConfigurations(&lb)
rgName := az.getLoadBalancerResourceGroup()
rerr := az.LoadBalancerClient.CreateOrUpdate(ctx, rgName, to.String(lb.Name), lb, to.String(lb.Etag))
klog.V(10).Infof("LoadBalancerClient.CreateOrUpdate(%s): end", *lb.Name)
if rerr == nil {
// Invalidate the cache right after updating
_ = az.lbCache.Delete(*lb.Name)
return nil
}
lbJSON, _ := json.Marshal(lb)
klog.Warningf("LoadBalancerClient.CreateOrUpdate(%s) failed: %v, LoadBalancer request: %s", to.String(lb.Name), rerr.Error(), string(lbJSON))
// Invalidate the cache because ETAG precondition mismatch.
if rerr.HTTPStatusCode == http.StatusPreconditionFailed {
klog.V(3).Infof("LoadBalancer cache for %s is cleanup because of http.StatusPreconditionFailed", to.String(lb.Name))
_ = az.lbCache.Delete(*lb.Name)
}
retryErrorMessage := rerr.Error().Error()
// Invalidate the cache because another new operation has canceled the current request.
if strings.Contains(strings.ToLower(retryErrorMessage), consts.OperationCanceledErrorMessage) {
klog.V(3).Infof("LoadBalancer cache for %s is cleanup because CreateOrUpdate is canceled by another operation", to.String(lb.Name))
_ = az.lbCache.Delete(*lb.Name)
}
// The LB update may fail because the referenced PIP is not in the Succeeded provisioning state
if strings.Contains(strings.ToLower(retryErrorMessage), strings.ToLower(consts.ReferencedResourceNotProvisionedMessageCode)) {
matches := pipErrorMessageRE.FindStringSubmatch(retryErrorMessage)
if len(matches) != 3 {
klog.Errorf("Failed to parse the retry error message %s", retryErrorMessage)
return rerr.Error()
}
pipRG, pipName := matches[1], matches[2]
klog.V(3).Infof("The public IP %s referenced by load balancer %s is not in Succeeded provisioning state, will try to update it", pipName, to.String(lb.Name))
pip, _, err := az.getPublicIPAddress(pipRG, pipName)
if err != nil {
klog.Errorf("Failed to get the public IP %s in resource group %s: %v", pipName, pipRG, err)
return rerr.Error()
}
// Perform a dummy update to fix the provisioning state
err = az.CreateOrUpdatePIP(service, pipRG, pip)
if err != nil {
klog.Errorf("Failed to update the public IP %s in resource group %s: %v", pipName, pipRG, err)
return rerr.Error()
}
// Invalidate the LB cache, return the error, and the controller manager
// would retry the LB update in the next reconcile loop
_ = az.lbCache.Delete(*lb.Name)
}
return rerr.Error()
}
func (az *Cloud) CreateOrUpdateLBBackendPool(lbName string, backendPool network.BackendAddressPool) error {
ctx, cancel := getContextWithCancel()
defer cancel()
klog.V(4).Infof("CreateOrUpdateLBBackendPool: updating backend pool %s in LB %s", to.String(backendPool.Name), lbName)
rerr := az.LoadBalancerClient.CreateOrUpdateBackendPools(ctx, az.getLoadBalancerResourceGroup(), lbName, to.String(backendPool.Name), backendPool, to.String(backendPool.Etag))
if rerr == nil {
// Invalidate the cache right after updating
_ = az.lbCache.Delete(lbName)
return nil
}
// Invalidate the cache because ETAG precondition mismatch.
if rerr.HTTPStatusCode == http.StatusPreconditionFailed {
klog.V(3).Infof("LoadBalancer cache for %s is cleanup because of http.StatusPreconditionFailed", lbName)
_ = az.lbCache.Delete(lbName)
}
retryErrorMessage := rerr.Error().Error()
// Invalidate the cache because another new operation has canceled the current request.
if strings.Contains(strings.ToLower(retryErrorMessage), consts.OperationCanceledErrorMessage) {
klog.V(3).Infof("LoadBalancer cache for %s is cleanup because CreateOrUpdate is canceled by another operation", lbName)
_ = az.lbCache.Delete(lbName)
}
return rerr.Error()
}
// ListManagedLBs invokes az.LoadBalancerClient.List and filter out
// those that are not managed by cloud provider azure or not associated to a managed VMSet.
func (az *Cloud) ListManagedLBs(service *v1.Service, nodes []*v1.Node, clusterName string) ([]network.LoadBalancer, error) {
allLBs, err := az.ListLB(service)
if err != nil {
return nil, err
}
if allLBs == nil {
klog.Warningf("ListManagedLBs: no LBs found")
return nil, nil
}
// return early if wantLb=false
if nodes == nil {
return allLBs, nil
}
agentPoolLBs := make([]network.LoadBalancer, 0)
agentPoolVMSetNames, err := az.VMSet.GetAgentPoolVMSetNames(nodes)
if err != nil {
return nil, fmt.Errorf("ListManagedLBs: failed to get agent pool vmSet names: %w", err)
}
agentPoolVMSetNamesSet := sets.NewString()
if agentPoolVMSetNames != nil && len(*agentPoolVMSetNames) > 0 {
for _, vmSetName := range *agentPoolVMSetNames {
klog.V(5).Infof("ListManagedLBs: found agent pool vmSet name %s", vmSetName)
agentPoolVMSetNamesSet.Insert(strings.ToLower(vmSetName))
}
}
for _, lb := range allLBs {
vmSetNameFromLBName := az.mapLoadBalancerNameToVMSet(to.String(lb.Name), clusterName)
if strings.EqualFold(strings.TrimSuffix(to.String(lb.Name), consts.InternalLoadBalancerNameSuffix), clusterName) ||
agentPoolVMSetNamesSet.Has(strings.ToLower(vmSetNameFromLBName)) {
agentPoolLBs = append(agentPoolLBs, lb)
klog.V(4).Infof("ListManagedLBs: found agent pool LB %s", to.String(lb.Name))
}
}
return agentPoolLBs, nil
}
// ListLB invokes az.LoadBalancerClient.List with exponential backoff retry
func (az *Cloud) ListLB(service *v1.Service) ([]network.LoadBalancer, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
rgName := az.getLoadBalancerResourceGroup()
allLBs, rerr := az.LoadBalancerClient.List(ctx, rgName)
if rerr != nil {
if rerr.IsNotFound() {
return nil, nil
}
az.Event(service, v1.EventTypeWarning, "ListLoadBalancers", rerr.Error().Error())
klog.Errorf("LoadBalancerClient.List(%v) failure with err=%v", rgName, rerr)
return nil, rerr.Error()
}
klog.V(2).Infof("LoadBalancerClient.List(%v) success", rgName)
return allLBs, nil
}
// ListPIP list the PIP resources in the given resource group
func (az *Cloud) ListPIP(service *v1.Service, pipResourceGroup string) ([]network.PublicIPAddress, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
allPIPs, rerr := az.PublicIPAddressesClient.List(ctx, pipResourceGroup)
if rerr != nil {
if rerr.IsNotFound() {
return nil, nil
}
az.Event(service, v1.EventTypeWarning, "ListPublicIPs", rerr.Error().Error())
klog.Errorf("PublicIPAddressesClient.List(%v) failure with err=%v", pipResourceGroup, rerr)
return nil, rerr.Error()
}
klog.V(2).Infof("PublicIPAddressesClient.List(%v) success", pipResourceGroup)
return allPIPs, nil
}
// CreateOrUpdatePIP invokes az.PublicIPAddressesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) CreateOrUpdatePIP(service *v1.Service, pipResourceGroup string, pip network.PublicIPAddress) error {
ctx, cancel := getContextWithCancel()
defer cancel()
rerr := az.PublicIPAddressesClient.CreateOrUpdate(ctx, pipResourceGroup, to.String(pip.Name), pip)
klog.V(10).Infof("PublicIPAddressesClient.CreateOrUpdate(%s, %s): end", pipResourceGroup, to.String(pip.Name))
if rerr != nil {
pipJSON, _ := json.Marshal(pip)
klog.Warningf("PublicIPAddressesClient.CreateOrUpdate(%s, %s) failed: %s, PublicIP request: %s", pipResourceGroup, to.String(pip.Name), rerr.Error().Error(), string(pipJSON))
az.Event(service, v1.EventTypeWarning, "CreateOrUpdatePublicIPAddress", rerr.Error().Error())
return rerr.Error()
}
return nil
}
// CreateOrUpdateInterface invokes az.PublicIPAddressesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) CreateOrUpdateInterface(service *v1.Service, nic network.Interface) error {
ctx, cancel := getContextWithCancel()
defer cancel()
rerr := az.InterfacesClient.CreateOrUpdate(ctx, az.ResourceGroup, *nic.Name, nic)
klog.V(10).Infof("InterfacesClient.CreateOrUpdate(%s): end", *nic.Name)
if rerr != nil {
klog.Errorf("InterfacesClient.CreateOrUpdate(%s) failed: %s", *nic.Name, rerr.Error().Error())
az.Event(service, v1.EventTypeWarning, "CreateOrUpdateInterface", rerr.Error().Error())
return rerr.Error()
}
return nil
}
// DeletePublicIP invokes az.PublicIPAddressesClient.Delete with exponential backoff retry
func (az *Cloud) DeletePublicIP(service *v1.Service, pipResourceGroup string, pipName string) error {
ctx, cancel := getContextWithCancel()
defer cancel()
rerr := az.PublicIPAddressesClient.Delete(ctx, pipResourceGroup, pipName)
if rerr != nil {
klog.Errorf("PublicIPAddressesClient.Delete(%s) failed: %s", pipName, rerr.Error().Error())
az.Event(service, v1.EventTypeWarning, "DeletePublicIPAddress", rerr.Error().Error())
if strings.Contains(rerr.Error().Error(), consts.CannotDeletePublicIPErrorMessageCode) {
klog.Warningf("DeletePublicIP for public IP %s failed with error %v, this is because other resources are referencing the public IP. The deletion of the service will continue.", pipName, rerr.Error())
return nil
}
return rerr.Error()
}
return nil
}
// DeleteLB invokes az.LoadBalancerClient.Delete with exponential backoff retry
func (az *Cloud) DeleteLB(service *v1.Service, lbName string) *retry.Error {
ctx, cancel := getContextWithCancel()
defer cancel()
rgName := az.getLoadBalancerResourceGroup()
rerr := az.LoadBalancerClient.Delete(ctx, rgName, lbName)
if rerr == nil {
// Invalidate the cache right after updating
_ = az.lbCache.Delete(lbName)
return nil
}
klog.Errorf("LoadBalancerClient.Delete(%s) failed: %s", lbName, rerr.Error().Error())
az.Event(service, v1.EventTypeWarning, "DeleteLoadBalancer", rerr.Error().Error())
return rerr
}
// CreateOrUpdateRouteTable invokes az.RouteTablesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) CreateOrUpdateRouteTable(routeTable network.RouteTable) error {
ctx, cancel := getContextWithCancel()
defer cancel()
rerr := az.RouteTablesClient.CreateOrUpdate(ctx, az.RouteTableResourceGroup, az.RouteTableName, routeTable, to.String(routeTable.Etag))
if rerr == nil {
// Invalidate the cache right after updating
_ = az.rtCache.Delete(*routeTable.Name)
return nil
}
rtJSON, _ := json.Marshal(routeTable)
klog.Warningf("RouteTablesClient.CreateOrUpdate(%s) failed: %v, RouteTable request: %s", to.String(routeTable.Name), rerr.Error(), string(rtJSON))
// Invalidate the cache because etag mismatch.
if rerr.HTTPStatusCode == http.StatusPreconditionFailed {
klog.V(3).Infof("Route table cache for %s is cleanup because of http.StatusPreconditionFailed", *routeTable.Name)
_ = az.rtCache.Delete(*routeTable.Name)
}
// Invalidate the cache because another new operation has canceled the current request.
if strings.Contains(strings.ToLower(rerr.Error().Error()), consts.OperationCanceledErrorMessage) {
klog.V(3).Infof("Route table cache for %s is cleanup because CreateOrUpdateRouteTable is canceled by another operation", *routeTable.Name)
_ = az.rtCache.Delete(*routeTable.Name)
}
klog.Errorf("RouteTablesClient.CreateOrUpdate(%s) failed: %v", az.RouteTableName, rerr.Error())
return rerr.Error()
}
// CreateOrUpdateRoute invokes az.RoutesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) CreateOrUpdateRoute(route network.Route) error {
ctx, cancel := getContextWithCancel()
defer cancel()
rerr := az.RoutesClient.CreateOrUpdate(ctx, az.RouteTableResourceGroup, az.RouteTableName, *route.Name, route, to.String(route.Etag))
klog.V(10).Infof("RoutesClient.CreateOrUpdate(%s): end", *route.Name)
if rerr == nil {
_ = az.rtCache.Delete(az.RouteTableName)
return nil
}
if rerr.HTTPStatusCode == http.StatusPreconditionFailed {
klog.V(3).Infof("Route cache for %s is cleanup because of http.StatusPreconditionFailed", *route.Name)
_ = az.rtCache.Delete(az.RouteTableName)
}
// Invalidate the cache because another new operation has canceled the current request.
if strings.Contains(strings.ToLower(rerr.Error().Error()), consts.OperationCanceledErrorMessage) {
klog.V(3).Infof("Route cache for %s is cleanup because CreateOrUpdateRouteTable is canceled by another operation", *route.Name)
_ = az.rtCache.Delete(az.RouteTableName)
}
return rerr.Error()
}
// DeleteRouteWithName invokes az.RoutesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) DeleteRouteWithName(routeName string) error {
ctx, cancel := getContextWithCancel()
defer cancel()
rerr := az.RoutesClient.Delete(ctx, az.RouteTableResourceGroup, az.RouteTableName, routeName)
klog.V(10).Infof("RoutesClient.Delete(%s,%s): end", az.RouteTableName, routeName)
if rerr == nil {
return nil
}
klog.Errorf("RoutesClient.Delete(%s, %s) failed: %v", az.RouteTableName, routeName, rerr.Error())
return rerr.Error()
}
// CreateOrUpdateVMSS invokes az.VirtualMachineScaleSetsClient.Update().
func (az *Cloud) CreateOrUpdateVMSS(resourceGroupName string, VMScaleSetName string, parameters compute.VirtualMachineScaleSet) *retry.Error {
ctx, cancel := getContextWithCancel()
defer cancel()
// When vmss is being deleted, CreateOrUpdate API would report "the vmss is being deleted" error.
// Since it is being deleted, we shouldn't send more CreateOrUpdate requests for it.
klog.V(3).Infof("CreateOrUpdateVMSS: verify the status of the vmss being created or updated")
vmss, rerr := az.VirtualMachineScaleSetsClient.Get(ctx, resourceGroupName, VMScaleSetName)
if rerr != nil {
klog.Errorf("CreateOrUpdateVMSS: error getting vmss(%s): %v", VMScaleSetName, rerr)
return rerr
}
if vmss.ProvisioningState != nil && strings.EqualFold(*vmss.ProvisioningState, consts.VirtualMachineScaleSetsDeallocating) {
klog.V(3).Infof("CreateOrUpdateVMSS: found vmss %s being deleted, skipping", VMScaleSetName)
return nil
}
rerr = az.VirtualMachineScaleSetsClient.CreateOrUpdate(ctx, resourceGroupName, VMScaleSetName, parameters)
klog.V(10).Infof("UpdateVmssVMWithRetry: VirtualMachineScaleSetsClient.CreateOrUpdate(%s): end", VMScaleSetName)
if rerr != nil {
klog.Errorf("CreateOrUpdateVMSS: error CreateOrUpdate vmss(%s): %v", VMScaleSetName, rerr)
return rerr
}
return nil
}

View File

@ -0,0 +1,90 @@
/*
Copyright 2020 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 provider
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
"sigs.k8s.io/yaml"
)
// The config type for Azure cloud provider secret. Supported values are:
// * file : The values are read from local cloud-config file.
// * secret : The values from secret would override all configures from local cloud-config file.
// * merge : The values from secret would override only configurations that are explicitly set in the secret. This is the default value.
type cloudConfigType string
const (
cloudConfigTypeFile cloudConfigType = "file"
cloudConfigTypeSecret cloudConfigType = "secret"
cloudConfigTypeMerge cloudConfigType = "merge"
)
// InitializeCloudFromSecret initializes Azure cloud provider from Kubernetes secret.
func (az *Cloud) InitializeCloudFromSecret() error {
config, err := az.GetConfigFromSecret()
if err != nil {
klog.Errorf("Failed to get cloud-config from secret: %v", err)
return fmt.Errorf("InitializeCloudFromSecret: failed to get cloud config from secret %s/%s: %w", az.SecretNamespace, az.SecretName, err)
}
if config == nil {
// Skip re-initialization if the config is not override.
return nil
}
if err := az.InitializeCloudFromConfig(config, true, true); err != nil {
klog.Errorf("Failed to initialize Azure cloud provider: %v", err)
return fmt.Errorf("InitializeCloudFromSecret: failed to initialize Azure cloud provider: %w", err)
}
return nil
}
func (az *Cloud) GetConfigFromSecret() (*Config, error) {
// Read config from file and no override, return nil.
if az.Config.CloudConfigType == cloudConfigTypeFile {
return nil, nil
}
secret, err := az.KubeClient.CoreV1().Secrets(az.SecretNamespace).Get(context.TODO(), az.SecretName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get secret %s/%s: %w", az.SecretNamespace, az.SecretName, err)
}
cloudConfigData, ok := secret.Data[az.CloudConfigKey]
if !ok {
return nil, fmt.Errorf("cloud-config is not set in the secret (%s/%s)", az.SecretNamespace, az.SecretName)
}
config := Config{}
if az.Config.CloudConfigType == "" || az.Config.CloudConfigType == cloudConfigTypeMerge {
// Merge cloud config, set default value to existing config.
config = az.Config
}
err = yaml.Unmarshal(cloudConfigData, &config)
if err != nil {
return nil, fmt.Errorf("failed to parse Azure cloud-config: %w", err)
}
return &config, nil
}

View File

@ -0,0 +1,657 @@
/*
Copyright 2020 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 provider
import (
"context"
"errors"
"fmt"
"net/http"
"path"
"regexp"
"strings"
"sync"
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"k8s.io/apimachinery/pkg/types"
kwait "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/flowcontrol"
cloudprovider "k8s.io/cloud-provider"
volerr "k8s.io/cloud-provider/volume/errors"
"k8s.io/klog/v2"
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
const (
// Disk Caching is not supported for disks 4 TiB and larger
// https://docs.microsoft.com/en-us/azure/virtual-machines/premium-storage-performance#disk-caching
diskCachingLimit = 4096 // GiB
maxLUN = 64 // max number of LUNs per VM
errStatusCode400 = "statuscode=400"
errInvalidParameter = `code="invalidparameter"`
errTargetInstanceIds = `target="instanceids"`
sourceSnapshot = "snapshot"
sourceVolume = "volume"
attachDiskMapKeySuffix = "attachdiskmap"
detachDiskMapKeySuffix = "detachdiskmap"
// WriteAcceleratorEnabled support for Azure Write Accelerator on Azure Disks
// https://docs.microsoft.com/azure/virtual-machines/windows/how-to-enable-write-accelerator
WriteAcceleratorEnabled = "writeacceleratorenabled"
// see https://docs.microsoft.com/en-us/rest/api/compute/disks/createorupdate#create-a-managed-disk-by-copying-a-snapshot.
diskSnapshotPath = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/snapshots/%s"
// see https://docs.microsoft.com/en-us/rest/api/compute/disks/createorupdate#create-a-managed-disk-from-an-existing-managed-disk-in-the-same-or-different-subscription.
managedDiskPath = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/disks/%s"
)
var defaultBackOff = kwait.Backoff{
Steps: 20,
Duration: 2 * time.Second,
Factor: 1.5,
Jitter: 0.0,
}
var (
managedDiskPathRE = regexp.MustCompile(`.*/subscriptions/(?:.*)/resourceGroups/(?:.*)/providers/Microsoft.Compute/disks/(.+)`)
diskSnapshotPathRE = regexp.MustCompile(`.*/subscriptions/(?:.*)/resourceGroups/(?:.*)/providers/Microsoft.Compute/snapshots/(.+)`)
)
type controllerCommon struct {
subscriptionID string
location string
extendedLocation *ExtendedLocation
storageEndpointSuffix string
resourceGroup string
diskStateMap sync.Map // <diskURI, attaching/detaching state>
lockMap *lockMap
cloud *Cloud
// disk queue that is waiting for attach or detach on specific node
// <nodeName, map<diskURI, *AttachDiskOptions/DetachDiskOptions>>
attachDiskMap sync.Map
detachDiskMap sync.Map
// attach/detach disk rate limiter
diskOpRateLimiter flowcontrol.RateLimiter
}
// AttachDiskOptions attach disk options
type AttachDiskOptions struct {
cachingMode compute.CachingTypes
diskName string
diskEncryptionSetID string
writeAcceleratorEnabled bool
lun int32
}
// ExtendedLocation contains additional info about the location of resources.
type ExtendedLocation struct {
// Name - The name of the extended location.
Name string `json:"name,omitempty"`
// Type - The type of the extended location.
Type string `json:"type,omitempty"`
}
// getNodeVMSet gets the VMSet interface based on config.VMType and the real virtual machine type.
func (c *controllerCommon) getNodeVMSet(nodeName types.NodeName, crt azcache.AzureCacheReadType) (VMSet, error) {
// 1. vmType is standard, return cloud.VMSet directly.
if c.cloud.VMType == consts.VMTypeStandard {
return c.cloud.VMSet, nil
}
// 2. vmType is Virtual Machine Scale Set (vmss), convert vmSet to ScaleSet.
ss, ok := c.cloud.VMSet.(*ScaleSet)
if !ok {
return nil, fmt.Errorf("error of converting vmSet (%q) to ScaleSet with vmType %q", c.cloud.VMSet, c.cloud.VMType)
}
// 3. If the node is managed by availability set, then return ss.availabilitySet.
managedByAS, err := ss.isNodeManagedByAvailabilitySet(mapNodeNameToVMName(nodeName), crt)
if err != nil {
return nil, err
}
if managedByAS {
// vm is managed by availability set.
return ss.availabilitySet, nil
}
// 4. Node is managed by vmss
return ss, nil
}
// AttachDisk attaches a disk to vm
// parameter async indicates whether allow multiple batch disk attach on one node in parallel
// return (lun, error)
func (c *controllerCommon) AttachDisk(ctx context.Context, async bool, diskName, diskURI string, nodeName types.NodeName,
cachingMode compute.CachingTypes, disk *compute.Disk) (int32, error) {
diskEncryptionSetID := ""
writeAcceleratorEnabled := false
// there is possibility that disk is nil when GetDisk is throttled
// don't check disk state when GetDisk is throttled
if disk != nil {
if disk.ManagedBy != nil && (disk.MaxShares == nil || *disk.MaxShares <= 1) {
vmset, err := c.getNodeVMSet(nodeName, azcache.CacheReadTypeUnsafe)
if err != nil {
return -1, err
}
attachedNode, err := vmset.GetNodeNameByProviderID(*disk.ManagedBy)
if err != nil {
return -1, err
}
if strings.EqualFold(string(nodeName), string(attachedNode)) {
klog.Warningf("volume %q is actually attached to current node %q, invalidate vm cache and return error", diskURI, nodeName)
// update VM(invalidate vm cache)
if errUpdate := c.UpdateVM(nodeName); errUpdate != nil {
return -1, errUpdate
}
lun, _, err := c.GetDiskLun(diskName, diskURI, nodeName)
return lun, err
}
attachErr := fmt.Sprintf(
"disk(%s) already attached to node(%s), could not be attached to node(%s)",
diskURI, *disk.ManagedBy, nodeName)
klog.V(2).Infof("found dangling volume %s attached to node %s, could not be attached to node(%s)", diskURI, attachedNode, nodeName)
return -1, volerr.NewDanglingError(attachErr, attachedNode, "")
}
if disk.DiskProperties != nil {
if disk.DiskProperties.DiskSizeGB != nil && *disk.DiskProperties.DiskSizeGB >= diskCachingLimit && cachingMode != compute.CachingTypesNone {
// Disk Caching is not supported for disks 4 TiB and larger
// https://docs.microsoft.com/en-us/azure/virtual-machines/premium-storage-performance#disk-caching
cachingMode = compute.CachingTypesNone
klog.Warningf("size of disk(%s) is %dGB which is bigger than limit(%dGB), set cacheMode as None",
diskURI, *disk.DiskProperties.DiskSizeGB, diskCachingLimit)
}
if disk.DiskProperties.Encryption != nil &&
disk.DiskProperties.Encryption.DiskEncryptionSetID != nil {
diskEncryptionSetID = *disk.DiskProperties.Encryption.DiskEncryptionSetID
}
if disk.DiskProperties.DiskState != compute.Unattached && (disk.MaxShares == nil || *disk.MaxShares <= 1) {
return -1, fmt.Errorf("state of disk(%s) is %s, not in expected %s state", diskURI, disk.DiskProperties.DiskState, compute.Unattached)
}
}
if v, ok := disk.Tags[WriteAcceleratorEnabled]; ok {
if v != nil && strings.EqualFold(*v, "true") {
writeAcceleratorEnabled = true
}
}
}
options := AttachDiskOptions{
lun: -1,
diskName: diskName,
cachingMode: cachingMode,
diskEncryptionSetID: diskEncryptionSetID,
writeAcceleratorEnabled: writeAcceleratorEnabled,
}
node := strings.ToLower(string(nodeName))
diskuri := strings.ToLower(diskURI)
if err := c.insertAttachDiskRequest(diskuri, node, &options); err != nil {
return -1, err
}
c.lockMap.LockEntry(node)
unlock := false
defer func() {
if !unlock {
c.lockMap.UnlockEntry(node)
}
}()
diskMap, err := c.cleanAttachDiskRequests(node)
if err != nil {
return -1, err
}
lun, err := c.SetDiskLun(nodeName, diskuri, diskMap)
if err != nil {
return -1, err
}
klog.V(2).Infof("Trying to attach volume %q lun %d to node %q, diskMap: %s", diskURI, lun, nodeName, diskMap)
if len(diskMap) == 0 {
return lun, nil
}
vmset, err := c.getNodeVMSet(nodeName, azcache.CacheReadTypeUnsafe)
if err != nil {
return -1, err
}
c.diskStateMap.Store(disk, "attaching")
defer c.diskStateMap.Delete(disk)
future, err := vmset.AttachDisk(nodeName, diskMap)
if err != nil {
return -1, err
}
if async && c.diskOpRateLimiter.TryAccept() {
// unlock and wait for attach disk complete
unlock = true
c.lockMap.UnlockEntry(node)
} else {
klog.Warningf("azureDisk - switch to batch operation due to rate limited(async: %t), QPS: %f", async, c.diskOpRateLimiter.QPS())
}
resourceGroup, err := getResourceGroupFromDiskURI(diskURI)
if err != nil {
return -1, err
}
return lun, vmset.WaitForUpdateResult(ctx, future, resourceGroup, "attach_disk")
}
func (c *controllerCommon) insertAttachDiskRequest(diskURI, nodeName string, options *AttachDiskOptions) error {
var diskMap map[string]*AttachDiskOptions
attachDiskMapKey := nodeName + attachDiskMapKeySuffix
c.lockMap.LockEntry(attachDiskMapKey)
defer c.lockMap.UnlockEntry(attachDiskMapKey)
v, ok := c.attachDiskMap.Load(nodeName)
if ok {
if diskMap, ok = v.(map[string]*AttachDiskOptions); !ok {
return fmt.Errorf("convert attachDiskMap failure on node(%s)", nodeName)
}
} else {
diskMap = make(map[string]*AttachDiskOptions)
c.attachDiskMap.Store(nodeName, diskMap)
}
// insert attach disk request to queue
_, ok = diskMap[diskURI]
if ok {
klog.V(2).Infof("azureDisk - duplicated attach disk(%s) request on node(%s)", diskURI, nodeName)
} else {
diskMap[diskURI] = options
}
return nil
}
// clean up attach disk requests
// return original attach disk requests
func (c *controllerCommon) cleanAttachDiskRequests(nodeName string) (map[string]*AttachDiskOptions, error) {
var diskMap map[string]*AttachDiskOptions
attachDiskMapKey := nodeName + attachDiskMapKeySuffix
c.lockMap.LockEntry(attachDiskMapKey)
defer c.lockMap.UnlockEntry(attachDiskMapKey)
v, ok := c.attachDiskMap.Load(nodeName)
if !ok {
return diskMap, nil
}
if diskMap, ok = v.(map[string]*AttachDiskOptions); !ok {
return diskMap, fmt.Errorf("convert attachDiskMap failure on node(%s)", nodeName)
}
c.attachDiskMap.Store(nodeName, make(map[string]*AttachDiskOptions))
return diskMap, nil
}
// DetachDisk detaches a disk from VM
func (c *controllerCommon) DetachDisk(ctx context.Context, diskName, diskURI string, nodeName types.NodeName) error {
if _, err := c.cloud.InstanceID(context.TODO(), nodeName); err != nil {
if errors.Is(err, cloudprovider.InstanceNotFound) {
// if host doesn't exist, no need to detach
klog.Warningf("azureDisk - failed to get azure instance id(%q), DetachDisk(%s) will assume disk is already detached",
nodeName, diskURI)
return nil
}
klog.Warningf("failed to get azure instance id (%v)", err)
return fmt.Errorf("failed to get azure instance id for node %q: %w", nodeName, err)
}
vmset, err := c.getNodeVMSet(nodeName, azcache.CacheReadTypeUnsafe)
if err != nil {
return err
}
node := strings.ToLower(string(nodeName))
disk := strings.ToLower(diskURI)
if err := c.insertDetachDiskRequest(diskName, disk, node); err != nil {
return err
}
c.lockMap.LockEntry(node)
defer c.lockMap.UnlockEntry(node)
diskMap, err := c.cleanDetachDiskRequests(node)
if err != nil {
return err
}
klog.V(2).Infof("Trying to detach volume %q from node %q, diskMap: %s", diskURI, nodeName, diskMap)
if len(diskMap) > 0 {
c.diskStateMap.Store(disk, "detaching")
defer c.diskStateMap.Delete(disk)
if err = vmset.DetachDisk(nodeName, diskMap); err != nil {
if isInstanceNotFoundError(err) {
// if host doesn't exist, no need to detach
klog.Warningf("azureDisk - got InstanceNotFoundError(%v), DetachDisk(%s) will assume disk is already detached",
err, diskURI)
return nil
}
}
} else {
lun, _, errGetLun := c.GetDiskLun(diskName, diskURI, nodeName)
if errGetLun == nil || !strings.Contains(errGetLun.Error(), consts.CannotFindDiskLUN) {
return fmt.Errorf("disk(%s) is still attatched to node(%s) on lun(%d), error: %v", diskURI, nodeName, lun, errGetLun)
}
}
if err != nil {
klog.Errorf("azureDisk - detach disk(%s, %s) failed, err: %v", diskName, diskURI, err)
return err
}
klog.V(2).Infof("azureDisk - detach disk(%s, %s) succeeded", diskName, diskURI)
return nil
}
// UpdateVM updates a vm
func (c *controllerCommon) UpdateVM(nodeName types.NodeName) error {
vmset, err := c.getNodeVMSet(nodeName, azcache.CacheReadTypeUnsafe)
if err != nil {
return err
}
node := strings.ToLower(string(nodeName))
c.lockMap.LockEntry(node)
defer c.lockMap.UnlockEntry(node)
return vmset.UpdateVM(nodeName)
}
func (c *controllerCommon) insertDetachDiskRequest(diskName, diskURI, nodeName string) error {
var diskMap map[string]string
detachDiskMapKey := nodeName + detachDiskMapKeySuffix
c.lockMap.LockEntry(detachDiskMapKey)
defer c.lockMap.UnlockEntry(detachDiskMapKey)
v, ok := c.detachDiskMap.Load(nodeName)
if ok {
if diskMap, ok = v.(map[string]string); !ok {
return fmt.Errorf("convert detachDiskMap failure on node(%s)", nodeName)
}
} else {
diskMap = make(map[string]string)
c.detachDiskMap.Store(nodeName, diskMap)
}
// insert detach disk request to queue
_, ok = diskMap[diskURI]
if ok {
klog.V(2).Infof("azureDisk - duplicated detach disk(%s) request on node(%s)", diskURI, nodeName)
} else {
diskMap[diskURI] = diskName
}
return nil
}
// clean up detach disk requests
// return original detach disk requests
func (c *controllerCommon) cleanDetachDiskRequests(nodeName string) (map[string]string, error) {
var diskMap map[string]string
detachDiskMapKey := nodeName + detachDiskMapKeySuffix
c.lockMap.LockEntry(detachDiskMapKey)
defer c.lockMap.UnlockEntry(detachDiskMapKey)
v, ok := c.detachDiskMap.Load(nodeName)
if !ok {
return diskMap, nil
}
if diskMap, ok = v.(map[string]string); !ok {
return diskMap, fmt.Errorf("convert detachDiskMap failure on node(%s)", nodeName)
}
// clean up original requests in disk map
c.detachDiskMap.Store(nodeName, make(map[string]string))
return diskMap, nil
}
// getNodeDataDisks invokes vmSet interfaces to get data disks for the node.
func (c *controllerCommon) getNodeDataDisks(nodeName types.NodeName, crt azcache.AzureCacheReadType) ([]compute.DataDisk, *string, error) {
vmset, err := c.getNodeVMSet(nodeName, crt)
if err != nil {
return nil, nil, err
}
return vmset.GetDataDisks(nodeName, crt)
}
// GetDiskLun finds the lun on the host that the vhd is attached to, given a vhd's diskName and diskURI.
func (c *controllerCommon) GetDiskLun(diskName, diskURI string, nodeName types.NodeName) (int32, *string, error) {
// getNodeDataDisks need to fetch the cached data/fresh data if cache expired here
// to ensure we get LUN based on latest entry.
disks, provisioningState, err := c.getNodeDataDisks(nodeName, azcache.CacheReadTypeDefault)
if err != nil {
klog.Errorf("error of getting data disks for node %q: %v", nodeName, err)
return -1, provisioningState, err
}
for _, disk := range disks {
if disk.Lun != nil && (disk.Name != nil && diskName != "" && strings.EqualFold(*disk.Name, diskName)) ||
(disk.Vhd != nil && disk.Vhd.URI != nil && diskURI != "" && strings.EqualFold(*disk.Vhd.URI, diskURI)) ||
(disk.ManagedDisk != nil && strings.EqualFold(*disk.ManagedDisk.ID, diskURI)) {
if disk.ToBeDetached != nil && *disk.ToBeDetached {
klog.Warningf("azureDisk - find disk(ToBeDetached): lun %d name %q uri %q", *disk.Lun, diskName, diskURI)
} else {
// found the disk
klog.V(2).Infof("azureDisk - find disk: lun %d name %q uri %q", *disk.Lun, diskName, diskURI)
return *disk.Lun, provisioningState, nil
}
}
}
return -1, provisioningState, fmt.Errorf("%s for disk %s", consts.CannotFindDiskLUN, diskName)
}
// SetDiskLun find unused luns and allocate lun for every disk in diskMap.
// Return lun of diskURI, -1 if all luns are used.
func (c *controllerCommon) SetDiskLun(nodeName types.NodeName, diskURI string, diskMap map[string]*AttachDiskOptions) (int32, error) {
disks, _, err := c.getNodeDataDisks(nodeName, azcache.CacheReadTypeDefault)
if err != nil {
klog.Errorf("error of getting data disks for node %q: %v", nodeName, err)
return -1, err
}
lun := int32(-1)
_, isDiskInMap := diskMap[diskURI]
used := make([]bool, maxLUN)
for _, disk := range disks {
if disk.Lun != nil {
used[*disk.Lun] = true
if !isDiskInMap {
// find lun of diskURI since diskURI is not in diskMap
if disk.ManagedDisk != nil && strings.EqualFold(*disk.ManagedDisk.ID, diskURI) {
lun = *disk.Lun
}
}
}
}
if !isDiskInMap && lun < 0 {
return -1, fmt.Errorf("could not find disk(%s) in current disk list(len: %d) nor in diskMap(%v)", diskURI, len(disks), diskMap)
}
if len(diskMap) == 0 {
// attach disk request is empty, return directly
return lun, nil
}
// allocate lun for every disk in diskMap
var diskLuns []int32
count := 0
for k, v := range used {
if !v {
diskLuns = append(diskLuns, int32(k))
count++
if count >= len(diskMap) {
break
}
}
}
if len(diskLuns) != len(diskMap) {
return -1, fmt.Errorf("could not find enough disk luns(current: %d) for diskMap(%v, len=%d), diskURI(%s)",
len(diskLuns), diskMap, len(diskMap), diskURI)
}
count = 0
for uri, opt := range diskMap {
if opt == nil {
return -1, fmt.Errorf("unexpected nil pointer in diskMap(%v), diskURI(%s)", diskMap, diskURI)
}
if strings.EqualFold(uri, diskURI) {
lun = diskLuns[count]
}
opt.lun = diskLuns[count]
count++
}
if lun < 0 {
return lun, fmt.Errorf("could not find lun of diskURI(%s), diskMap(%v)", diskURI, diskMap)
}
return lun, nil
}
// DisksAreAttached checks if a list of volumes are attached to the node with the specified NodeName.
func (c *controllerCommon) DisksAreAttached(diskNames []string, nodeName types.NodeName) (map[string]bool, error) {
attached := make(map[string]bool)
for _, diskName := range diskNames {
attached[diskName] = false
}
// doing stalled read for getNodeDataDisks to ensure we don't call ARM
// for every reconcile call. The cache is invalidated after Attach/Detach
// disk. So the new entry will be fetched and cached the first time reconcile
// loop runs after the Attach/Disk OP which will reflect the latest model.
disks, _, err := c.getNodeDataDisks(nodeName, azcache.CacheReadTypeUnsafe)
if err != nil {
if errors.Is(err, cloudprovider.InstanceNotFound) {
// if host doesn't exist, no need to detach
klog.Warningf("azureDisk - Cannot find node %q, DisksAreAttached will assume disks %v are not attached to it.",
nodeName, diskNames)
return attached, nil
}
return attached, err
}
for _, disk := range disks {
for _, diskName := range diskNames {
if disk.Name != nil && diskName != "" && strings.EqualFold(*disk.Name, diskName) {
attached[diskName] = true
}
}
}
return attached, nil
}
func filterDetachingDisks(unfilteredDisks []compute.DataDisk) []compute.DataDisk {
filteredDisks := []compute.DataDisk{}
for _, disk := range unfilteredDisks {
if disk.ToBeDetached != nil && *disk.ToBeDetached {
if disk.Name != nil {
klog.V(2).Infof("Filtering disk: %s with ToBeDetached flag set.", *disk.Name)
}
} else {
filteredDisks = append(filteredDisks, disk)
}
}
return filteredDisks
}
func (c *controllerCommon) filterNonExistingDisks(ctx context.Context, unfilteredDisks []compute.DataDisk) []compute.DataDisk {
filteredDisks := []compute.DataDisk{}
for _, disk := range unfilteredDisks {
filter := false
if disk.ManagedDisk != nil && disk.ManagedDisk.ID != nil {
diskURI := *disk.ManagedDisk.ID
exist, err := c.cloud.checkDiskExists(ctx, diskURI)
if err != nil {
klog.Errorf("checkDiskExists(%s) failed with error: %v", diskURI, err)
} else {
// only filter disk when checkDiskExists returns <false, nil>
filter = !exist
if filter {
klog.Errorf("disk(%s) does not exist, removed from data disk list", diskURI)
}
}
}
if !filter {
filteredDisks = append(filteredDisks, disk)
}
}
return filteredDisks
}
func (c *controllerCommon) checkDiskExists(ctx context.Context, diskURI string) (bool, error) {
diskName := path.Base(diskURI)
resourceGroup, err := getResourceGroupFromDiskURI(diskURI)
if err != nil {
return false, err
}
if _, rerr := c.cloud.DisksClient.Get(ctx, resourceGroup, diskName); rerr != nil {
if rerr.HTTPStatusCode == http.StatusNotFound {
return false, nil
}
return false, rerr.Error()
}
return true, nil
}
func getValidCreationData(subscriptionID, resourceGroup, sourceResourceID, sourceType string) (compute.CreationData, error) {
if sourceResourceID == "" {
return compute.CreationData{
CreateOption: compute.Empty,
}, nil
}
switch sourceType {
case sourceSnapshot:
if match := diskSnapshotPathRE.FindString(sourceResourceID); match == "" {
sourceResourceID = fmt.Sprintf(diskSnapshotPath, subscriptionID, resourceGroup, sourceResourceID)
}
case sourceVolume:
if match := managedDiskPathRE.FindString(sourceResourceID); match == "" {
sourceResourceID = fmt.Sprintf(managedDiskPath, subscriptionID, resourceGroup, sourceResourceID)
}
default:
return compute.CreationData{
CreateOption: compute.Empty,
}, nil
}
splits := strings.Split(sourceResourceID, "/")
if len(splits) > 9 {
if sourceType == sourceSnapshot {
return compute.CreationData{}, fmt.Errorf("sourceResourceID(%s) is invalid, correct format: %s", sourceResourceID, diskSnapshotPathRE)
}
return compute.CreationData{}, fmt.Errorf("sourceResourceID(%s) is invalid, correct format: %s", sourceResourceID, managedDiskPathRE)
}
return compute.CreationData{
CreateOption: compute.Copy,
SourceResourceID: &sourceResourceID,
}, nil
}
func isInstanceNotFoundError(err error) bool {
errMsg := strings.ToLower(err.Error())
if strings.Contains(errMsg, strings.ToLower(consts.VmssVMNotActiveErrorMessage)) {
return true
}
return strings.Contains(errMsg, errStatusCode400) && strings.Contains(errMsg, errInvalidParameter) && strings.Contains(errMsg, errTargetInstanceIds)
}

View File

@ -0,0 +1,250 @@
/*
Copyright 2020 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 provider
import (
"context"
"net/http"
"strings"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
// AttachDisk attaches a disk to vm
func (as *availabilitySet) AttachDisk(nodeName types.NodeName, diskMap map[string]*AttachDiskOptions) (*azure.Future, error) {
vm, err := as.getVirtualMachine(nodeName, azcache.CacheReadTypeDefault)
if err != nil {
return nil, err
}
vmName := mapNodeNameToVMName(nodeName)
nodeResourceGroup, err := as.GetNodeResourceGroup(vmName)
if err != nil {
return nil, err
}
disks := make([]compute.DataDisk, len(*vm.StorageProfile.DataDisks))
copy(disks, *vm.StorageProfile.DataDisks)
for k, v := range diskMap {
diskURI := k
opt := v
attached := false
for _, disk := range *vm.StorageProfile.DataDisks {
if disk.ManagedDisk != nil && strings.EqualFold(*disk.ManagedDisk.ID, diskURI) {
attached = true
break
}
}
if attached {
klog.V(2).Infof("azureDisk - disk(%s) already attached to node(%s)", diskURI, nodeName)
continue
}
managedDisk := &compute.ManagedDiskParameters{ID: &diskURI}
if opt.diskEncryptionSetID == "" {
if vm.StorageProfile.OsDisk != nil &&
vm.StorageProfile.OsDisk.ManagedDisk != nil &&
vm.StorageProfile.OsDisk.ManagedDisk.DiskEncryptionSet != nil &&
vm.StorageProfile.OsDisk.ManagedDisk.DiskEncryptionSet.ID != nil {
// set diskEncryptionSet as value of os disk by default
opt.diskEncryptionSetID = *vm.StorageProfile.OsDisk.ManagedDisk.DiskEncryptionSet.ID
}
}
if opt.diskEncryptionSetID != "" {
managedDisk.DiskEncryptionSet = &compute.DiskEncryptionSetParameters{ID: &opt.diskEncryptionSetID}
}
disks = append(disks,
compute.DataDisk{
Name: &opt.diskName,
Lun: &opt.lun,
Caching: opt.cachingMode,
CreateOption: "attach",
ManagedDisk: managedDisk,
WriteAcceleratorEnabled: to.BoolPtr(opt.writeAcceleratorEnabled),
})
}
newVM := compute.VirtualMachineUpdate{
VirtualMachineProperties: &compute.VirtualMachineProperties{
StorageProfile: &compute.StorageProfile{
DataDisks: &disks,
},
},
}
klog.V(2).Infof("azureDisk - update(%s): vm(%s) - attach disk list(%s)", nodeResourceGroup, vmName, diskMap)
ctx, cancel := getContextWithCancel()
defer cancel()
// Invalidate the cache right after updating
defer func() {
_ = as.cloud.vmCache.Delete(vmName)
}()
future, rerr := as.VirtualMachinesClient.UpdateAsync(ctx, nodeResourceGroup, vmName, newVM, "attach_disk")
if rerr != nil {
klog.Errorf("azureDisk - attach disk list(%s) on rg(%s) vm(%s) failed, err: %v", diskMap, nodeResourceGroup, vmName, rerr)
if rerr.HTTPStatusCode == http.StatusNotFound {
klog.Errorf("azureDisk - begin to filterNonExistingDisks(%v) on rg(%s) vm(%s)", diskMap, nodeResourceGroup, vmName)
disks := as.filterNonExistingDisks(ctx, *newVM.VirtualMachineProperties.StorageProfile.DataDisks)
newVM.VirtualMachineProperties.StorageProfile.DataDisks = &disks
future, rerr = as.VirtualMachinesClient.UpdateAsync(ctx, nodeResourceGroup, vmName, newVM, "attach_disk")
}
}
klog.V(2).Infof("azureDisk - update(%s): vm(%s) - attach disk list(%s) returned with %v", nodeResourceGroup, vmName, diskMap, rerr)
if rerr != nil {
return future, rerr.Error()
}
return future, nil
}
// WaitForUpdateResult waits for the response of the update request
func (as *availabilitySet) WaitForUpdateResult(ctx context.Context, future *azure.Future, resourceGroupName, source string) error {
if rerr := as.VirtualMachinesClient.WaitForUpdateResult(ctx, future, resourceGroupName, source); rerr != nil {
return rerr.Error()
}
return nil
}
// DetachDisk detaches a disk from VM
func (as *availabilitySet) DetachDisk(nodeName types.NodeName, diskMap map[string]string) error {
vm, err := as.getVirtualMachine(nodeName, azcache.CacheReadTypeDefault)
if err != nil {
// if host doesn't exist, no need to detach
klog.Warningf("azureDisk - cannot find node %s, skip detaching disk list(%s)", nodeName, diskMap)
return nil
}
vmName := mapNodeNameToVMName(nodeName)
nodeResourceGroup, err := as.GetNodeResourceGroup(vmName)
if err != nil {
return err
}
disks := make([]compute.DataDisk, len(*vm.StorageProfile.DataDisks))
copy(disks, *vm.StorageProfile.DataDisks)
bFoundDisk := false
for i, disk := range disks {
for diskURI, diskName := range diskMap {
if disk.Lun != nil && (disk.Name != nil && diskName != "" && strings.EqualFold(*disk.Name, diskName)) ||
(disk.Vhd != nil && disk.Vhd.URI != nil && diskURI != "" && strings.EqualFold(*disk.Vhd.URI, diskURI)) ||
(disk.ManagedDisk != nil && diskURI != "" && strings.EqualFold(*disk.ManagedDisk.ID, diskURI)) {
// found the disk
klog.V(2).Infof("azureDisk - detach disk: name %q uri %q", diskName, diskURI)
disks[i].ToBeDetached = to.BoolPtr(true)
bFoundDisk = true
}
}
}
if !bFoundDisk {
// only log here, next action is to update VM status with original meta data
klog.Errorf("detach azure disk on node(%s): disk list(%s) not found", nodeName, diskMap)
} else {
if strings.EqualFold(as.cloud.Environment.Name, consts.AzureStackCloudName) && !as.Config.DisableAzureStackCloud {
// Azure stack does not support ToBeDetached flag, use original way to detach disk
newDisks := []compute.DataDisk{}
for _, disk := range disks {
if !to.Bool(disk.ToBeDetached) {
newDisks = append(newDisks, disk)
}
}
disks = newDisks
}
}
newVM := compute.VirtualMachineUpdate{
VirtualMachineProperties: &compute.VirtualMachineProperties{
StorageProfile: &compute.StorageProfile{
DataDisks: &disks,
},
},
}
klog.V(2).Infof("azureDisk - update(%s): vm(%s) - detach disk list(%s)", nodeResourceGroup, vmName, nodeName, diskMap)
ctx, cancel := getContextWithCancel()
defer cancel()
// Invalidate the cache right after updating
defer func() {
_ = as.cloud.vmCache.Delete(vmName)
}()
rerr := as.VirtualMachinesClient.Update(ctx, nodeResourceGroup, vmName, newVM, "detach_disk")
if rerr != nil {
klog.Errorf("azureDisk - detach disk list(%s) on rg(%s) vm(%s) failed, err: %v", diskMap, nodeResourceGroup, vmName, rerr)
if rerr.HTTPStatusCode == http.StatusNotFound {
klog.Errorf("azureDisk - begin to filterNonExistingDisks(%v) on rg(%s) vm(%s)", diskMap, nodeResourceGroup, vmName)
disks := as.filterNonExistingDisks(ctx, *vm.StorageProfile.DataDisks)
newVM.VirtualMachineProperties.StorageProfile.DataDisks = &disks
rerr = as.VirtualMachinesClient.Update(ctx, nodeResourceGroup, vmName, newVM, "detach_disk")
}
}
klog.V(2).Infof("azureDisk - update(%s): vm(%s) - detach disk list(%s) returned with %v", nodeResourceGroup, vmName, diskMap, rerr)
if rerr != nil {
return rerr.Error()
}
return nil
}
// UpdateVM updates a vm
func (as *availabilitySet) UpdateVM(nodeName types.NodeName) error {
vmName := mapNodeNameToVMName(nodeName)
nodeResourceGroup, err := as.GetNodeResourceGroup(vmName)
if err != nil {
return err
}
klog.V(2).Infof("azureDisk - update(%s): vm(%s)", nodeResourceGroup, vmName)
ctx, cancel := getContextWithCancel()
defer cancel()
// Invalidate the cache right after updating
defer func() {
_ = as.cloud.vmCache.Delete(vmName)
}()
rerr := as.VirtualMachinesClient.Update(ctx, nodeResourceGroup, vmName, compute.VirtualMachineUpdate{}, "update_vm")
klog.V(2).Infof("azureDisk - update(%s): vm(%s) - returned with %v", nodeResourceGroup, vmName, rerr)
if rerr != nil {
return rerr.Error()
}
return nil
}
// GetDataDisks gets a list of data disks attached to the node.
func (as *availabilitySet) GetDataDisks(nodeName types.NodeName, crt azcache.AzureCacheReadType) ([]compute.DataDisk, *string, error) {
vm, err := as.getVirtualMachine(nodeName, crt)
if err != nil {
return nil, nil, err
}
if vm.StorageProfile.DataDisks == nil {
return nil, nil, nil
}
return *vm.StorageProfile.DataDisks, vm.ProvisioningState, nil
}

View File

@ -0,0 +1,262 @@
/*
Copyright 2020 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 provider
import (
"context"
"net/http"
"strings"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
// AttachDisk attaches a disk to vm
func (ss *ScaleSet) AttachDisk(nodeName types.NodeName, diskMap map[string]*AttachDiskOptions) (*azure.Future, error) {
vmName := mapNodeNameToVMName(nodeName)
ssName, instanceID, vm, err := ss.getVmssVM(vmName, azcache.CacheReadTypeDefault)
if err != nil {
return nil, err
}
nodeResourceGroup, err := ss.GetNodeResourceGroup(vmName)
if err != nil {
return nil, err
}
disks := []compute.DataDisk{}
if vm.StorageProfile != nil && vm.StorageProfile.DataDisks != nil {
disks = make([]compute.DataDisk, len(*vm.StorageProfile.DataDisks))
copy(disks, *vm.StorageProfile.DataDisks)
}
for k, v := range diskMap {
diskURI := k
opt := v
attached := false
for _, disk := range *vm.StorageProfile.DataDisks {
if disk.ManagedDisk != nil && strings.EqualFold(*disk.ManagedDisk.ID, diskURI) {
attached = true
break
}
}
if attached {
klog.V(2).Infof("azureDisk - disk(%s) already attached to node(%s)", diskURI, nodeName)
continue
}
managedDisk := &compute.ManagedDiskParameters{ID: &diskURI}
if opt.diskEncryptionSetID == "" {
if vm.StorageProfile.OsDisk != nil &&
vm.StorageProfile.OsDisk.ManagedDisk != nil &&
vm.StorageProfile.OsDisk.ManagedDisk.DiskEncryptionSet != nil &&
vm.StorageProfile.OsDisk.ManagedDisk.DiskEncryptionSet.ID != nil {
// set diskEncryptionSet as value of os disk by default
opt.diskEncryptionSetID = *vm.StorageProfile.OsDisk.ManagedDisk.DiskEncryptionSet.ID
}
}
if opt.diskEncryptionSetID != "" {
managedDisk.DiskEncryptionSet = &compute.DiskEncryptionSetParameters{ID: &opt.diskEncryptionSetID}
}
disks = append(disks,
compute.DataDisk{
Name: &opt.diskName,
Lun: &opt.lun,
Caching: opt.cachingMode,
CreateOption: "attach",
ManagedDisk: managedDisk,
WriteAcceleratorEnabled: to.BoolPtr(opt.writeAcceleratorEnabled),
})
}
newVM := compute.VirtualMachineScaleSetVM{
VirtualMachineScaleSetVMProperties: &compute.VirtualMachineScaleSetVMProperties{
StorageProfile: &compute.StorageProfile{
DataDisks: &disks,
},
},
}
ctx, cancel := getContextWithCancel()
defer cancel()
// Invalidate the cache right after updating
defer func() {
_ = ss.deleteCacheForNode(vmName)
}()
klog.V(2).Infof("azureDisk - update(%s): vm(%s) - attach disk list(%s)", nodeResourceGroup, nodeName, diskMap)
future, rerr := ss.VirtualMachineScaleSetVMsClient.UpdateAsync(ctx, nodeResourceGroup, ssName, instanceID, newVM, "attach_disk")
if rerr != nil {
klog.Errorf("azureDisk - attach disk list(%s) on rg(%s) vm(%s) failed, err: %v", diskMap, nodeResourceGroup, nodeName, rerr)
if rerr.HTTPStatusCode == http.StatusNotFound {
klog.Errorf("azureDisk - begin to filterNonExistingDisks(%v) on rg(%s) vm(%s)", diskMap, nodeResourceGroup, nodeName)
disks := ss.filterNonExistingDisks(ctx, *newVM.VirtualMachineScaleSetVMProperties.StorageProfile.DataDisks)
newVM.VirtualMachineScaleSetVMProperties.StorageProfile.DataDisks = &disks
future, rerr = ss.VirtualMachineScaleSetVMsClient.UpdateAsync(ctx, nodeResourceGroup, ssName, instanceID, newVM, "attach_disk")
}
}
klog.V(2).Infof("azureDisk - update(%s): vm(%s) - attach disk list(%s, %s) returned with %v", nodeResourceGroup, nodeName, diskMap, rerr)
if rerr != nil {
return future, rerr.Error()
}
return future, nil
}
// WaitForUpdateResult waits for the response of the update request
func (ss *ScaleSet) WaitForUpdateResult(ctx context.Context, future *azure.Future, resourceGroupName, source string) error {
if rerr := ss.VirtualMachineScaleSetVMsClient.WaitForUpdateResult(ctx, future, resourceGroupName, source); rerr != nil {
return rerr.Error()
}
return nil
}
// DetachDisk detaches a disk from VM
func (ss *ScaleSet) DetachDisk(nodeName types.NodeName, diskMap map[string]string) error {
vmName := mapNodeNameToVMName(nodeName)
ssName, instanceID, vm, err := ss.getVmssVM(vmName, azcache.CacheReadTypeDefault)
if err != nil {
return err
}
nodeResourceGroup, err := ss.GetNodeResourceGroup(vmName)
if err != nil {
return err
}
disks := []compute.DataDisk{}
if vm.StorageProfile != nil && vm.StorageProfile.DataDisks != nil {
disks = make([]compute.DataDisk, len(*vm.StorageProfile.DataDisks))
copy(disks, *vm.StorageProfile.DataDisks)
}
bFoundDisk := false
for i, disk := range disks {
for diskURI, diskName := range diskMap {
if disk.Lun != nil && (disk.Name != nil && diskName != "" && strings.EqualFold(*disk.Name, diskName)) ||
(disk.Vhd != nil && disk.Vhd.URI != nil && diskURI != "" && strings.EqualFold(*disk.Vhd.URI, diskURI)) ||
(disk.ManagedDisk != nil && diskURI != "" && strings.EqualFold(*disk.ManagedDisk.ID, diskURI)) {
// found the disk
klog.V(2).Infof("azureDisk - detach disk: name %q uri %q", diskName, diskURI)
disks[i].ToBeDetached = to.BoolPtr(true)
bFoundDisk = true
}
}
}
if !bFoundDisk {
// only log here, next action is to update VM status with original meta data
klog.Errorf("detach azure disk on node(%s): disk list(%s) not found", nodeName, diskMap)
} else {
if strings.EqualFold(ss.cloud.Environment.Name, consts.AzureStackCloudName) && !ss.Config.DisableAzureStackCloud {
// Azure stack does not support ToBeDetached flag, use original way to detach disk
newDisks := []compute.DataDisk{}
for _, disk := range disks {
if !to.Bool(disk.ToBeDetached) {
newDisks = append(newDisks, disk)
}
}
disks = newDisks
}
}
newVM := compute.VirtualMachineScaleSetVM{
VirtualMachineScaleSetVMProperties: &compute.VirtualMachineScaleSetVMProperties{
StorageProfile: &compute.StorageProfile{
DataDisks: &disks,
},
},
}
ctx, cancel := getContextWithCancel()
defer cancel()
// Invalidate the cache right after updating
defer func() {
_ = ss.deleteCacheForNode(vmName)
}()
klog.V(2).Infof("azureDisk - update(%s): vm(%s) - detach disk list(%s)", nodeResourceGroup, nodeName, diskMap)
rerr := ss.VirtualMachineScaleSetVMsClient.Update(ctx, nodeResourceGroup, ssName, instanceID, newVM, "detach_disk")
if rerr != nil {
klog.Errorf("azureDisk - detach disk list(%s) on rg(%s) vm(%s) failed, err: %v", diskMap, nodeResourceGroup, nodeName, rerr)
if rerr.HTTPStatusCode == http.StatusNotFound {
klog.Errorf("azureDisk - begin to filterNonExistingDisks(%v) on rg(%s) vm(%s)", diskMap, nodeResourceGroup, nodeName)
disks := ss.filterNonExistingDisks(ctx, *newVM.VirtualMachineScaleSetVMProperties.StorageProfile.DataDisks)
newVM.VirtualMachineScaleSetVMProperties.StorageProfile.DataDisks = &disks
rerr = ss.VirtualMachineScaleSetVMsClient.Update(ctx, nodeResourceGroup, ssName, instanceID, newVM, "detach_disk")
}
}
klog.V(2).Infof("azureDisk - update(%s): vm(%s) - detach disk(%v) returned with %v", nodeResourceGroup, nodeName, diskMap, rerr)
if rerr != nil {
return rerr.Error()
}
return nil
}
// UpdateVM updates a vm
func (ss *ScaleSet) UpdateVM(nodeName types.NodeName) error {
vmName := mapNodeNameToVMName(nodeName)
ssName, instanceID, _, err := ss.getVmssVM(vmName, azcache.CacheReadTypeDefault)
if err != nil {
return err
}
nodeResourceGroup, err := ss.GetNodeResourceGroup(vmName)
if err != nil {
return err
}
ctx, cancel := getContextWithCancel()
defer cancel()
// Invalidate the cache right after updating
defer func() {
_ = ss.deleteCacheForNode(vmName)
}()
klog.V(2).Infof("azureDisk - update(%s): vm(%s)", nodeResourceGroup, nodeName)
rerr := ss.VirtualMachineScaleSetVMsClient.Update(ctx, nodeResourceGroup, ssName, instanceID, compute.VirtualMachineScaleSetVM{}, "update_vmss_instance")
klog.V(2).Infof("azureDisk - update(%s): vm(%s) - returned with %v", nodeResourceGroup, nodeName, rerr)
if rerr != nil {
return rerr.Error()
}
return nil
}
// GetDataDisks gets a list of data disks attached to the node.
func (ss *ScaleSet) GetDataDisks(nodeName types.NodeName, crt azcache.AzureCacheReadType) ([]compute.DataDisk, *string, error) {
_, _, vm, err := ss.getVmssVM(string(nodeName), crt)
if err != nil {
return nil, nil, err
}
if vm.StorageProfile == nil || vm.StorageProfile.DataDisks == nil {
return nil, nil, nil
}
return *vm.StorageProfile.DataDisks, vm.ProvisioningState, nil
}

View File

@ -0,0 +1,130 @@
/*
Copyright 2020 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 provider
import (
"fmt"
"github.com/golang/mock/gomock"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/cloud-provider-azure/pkg/auth"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/diskclient/mockdiskclient"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/interfaceclient/mockinterfaceclient"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/loadbalancerclient/mockloadbalancerclient"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/publicipclient/mockpublicipclient"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routeclient/mockrouteclient"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/routetableclient/mockroutetableclient"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/securitygroupclient/mocksecuritygroupclient"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/snapshotclient/mocksnapshotclient"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/subnetclient/mocksubnetclient"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmclient/mockvmclient"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssclient/mockvmssclient"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/vmssvmclient/mockvmssvmclient"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
var (
errPreconditionFailedEtagMismatch = fmt.Errorf("PreconditionFailedEtagMismatch")
)
// NewTestScaleSet creates a fake ScaleSet for unit test
func NewTestScaleSet(ctrl *gomock.Controller) (*ScaleSet, error) {
return newTestScaleSetWithState(ctrl)
}
func newTestScaleSetWithState(ctrl *gomock.Controller) (*ScaleSet, error) {
cloud := GetTestCloud(ctrl)
ss, err := newScaleSet(cloud)
if err != nil {
return nil, err
}
return ss.(*ScaleSet), nil
}
// GetTestCloud returns a fake azure cloud for unit tests in Azure related CSI drivers
func GetTestCloud(ctrl *gomock.Controller) (az *Cloud) {
az = &Cloud{
Config: Config{
AzureAuthConfig: auth.AzureAuthConfig{
TenantID: "tenant",
SubscriptionID: "subscription",
},
ResourceGroup: "rg",
VnetResourceGroup: "rg",
RouteTableResourceGroup: "rg",
SecurityGroupResourceGroup: "rg",
Location: "westus",
VnetName: "vnet",
SubnetName: "subnet",
SecurityGroupName: "nsg",
RouteTableName: "rt",
PrimaryAvailabilitySetName: "as",
PrimaryScaleSetName: "vmss",
MaximumLoadBalancerRuleCount: 250,
VMType: consts.VMTypeStandard,
LoadBalancerBackendPoolConfigurationType: consts.LoadBalancerBackendPoolConfigurationTypeNodeIPConfiguration,
},
nodeZones: map[string]sets.String{},
nodeInformerSynced: func() bool { return true },
nodeResourceGroups: map[string]string{},
unmanagedNodes: sets.NewString(),
excludeLoadBalancerNodes: sets.NewString(),
nodePrivateIPs: map[string]sets.String{},
routeCIDRs: map[string]string{},
eventRecorder: &record.FakeRecorder{},
}
az.DisksClient = mockdiskclient.NewMockInterface(ctrl)
az.SnapshotsClient = mocksnapshotclient.NewMockInterface(ctrl)
az.InterfacesClient = mockinterfaceclient.NewMockInterface(ctrl)
az.LoadBalancerClient = mockloadbalancerclient.NewMockInterface(ctrl)
az.PublicIPAddressesClient = mockpublicipclient.NewMockInterface(ctrl)
az.RoutesClient = mockrouteclient.NewMockInterface(ctrl)
az.RouteTablesClient = mockroutetableclient.NewMockInterface(ctrl)
az.SecurityGroupsClient = mocksecuritygroupclient.NewMockInterface(ctrl)
az.SubnetsClient = mocksubnetclient.NewMockInterface(ctrl)
az.VirtualMachineScaleSetsClient = mockvmssclient.NewMockInterface(ctrl)
az.VirtualMachineScaleSetVMsClient = mockvmssvmclient.NewMockInterface(ctrl)
az.VirtualMachinesClient = mockvmclient.NewMockInterface(ctrl)
az.VMSet, _ = newAvailabilitySet(az)
az.vmCache, _ = az.newVMCache()
az.lbCache, _ = az.newLBCache()
az.nsgCache, _ = az.newNSGCache()
az.rtCache, _ = az.newRouteTableCache()
az.LoadBalancerBackendPool = NewMockBackendPool(ctrl)
_ = initDiskControllers(az)
az.regionZonesMap = map[string][]string{az.Location: {"1", "2", "3"}}
return az
}
// GetTestCloudWithExtendedLocation returns a fake azure cloud for unit tests in Azure related CSI drivers with extended location.
func GetTestCloudWithExtendedLocation(ctrl *gomock.Controller) (az *Cloud) {
az = GetTestCloud(ctrl)
az.Config.ExtendedLocationName = "microsoftlosangeles1"
az.Config.ExtendedLocationType = "EdgeZone"
az.controllerCommon.extendedLocation = &ExtendedLocation{
Name: az.Config.ExtendedLocationName,
Type: az.Config.ExtendedLocationType,
}
return az
}

View File

@ -0,0 +1,40 @@
/*
Copyright 2020 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 provider
import (
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-02-01/storage"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/fileclient"
)
// create file share
func (az *Cloud) createFileShare(resourceGroupName, accountName string, shareOptions *fileclient.ShareOptions) error {
return az.FileClient.CreateFileShare(resourceGroupName, accountName, shareOptions)
}
func (az *Cloud) deleteFileShare(resourceGroupName, accountName, name string) error {
return az.FileClient.DeleteFileShare(resourceGroupName, accountName, name)
}
func (az *Cloud) resizeFileShare(resourceGroupName, accountName, name string, sizeGiB int) error {
return az.FileClient.ResizeFileShare(resourceGroupName, accountName, name, sizeGiB)
}
func (az *Cloud) getFileShare(resourceGroupName, accountName, name string) (storage.FileShare, error) {
return az.FileClient.GetFileShare(resourceGroupName, accountName, name)
}

View File

@ -0,0 +1,257 @@
/*
Copyright 2020 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 provider
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"k8s.io/klog/v2"
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
// NetworkMetadata contains metadata about an instance's network
type NetworkMetadata struct {
Interface []NetworkInterface `json:"interface"`
}
// NetworkInterface represents an instances network interface.
type NetworkInterface struct {
IPV4 NetworkData `json:"ipv4"`
IPV6 NetworkData `json:"ipv6"`
MAC string `json:"macAddress"`
}
// NetworkData contains IP information for a network.
type NetworkData struct {
IPAddress []IPAddress `json:"ipAddress"`
Subnet []Subnet `json:"subnet"`
}
// IPAddress represents IP address information.
type IPAddress struct {
PrivateIP string `json:"privateIpAddress"`
PublicIP string `json:"publicIpAddress"`
}
// Subnet represents subnet information.
type Subnet struct {
Address string `json:"address"`
Prefix string `json:"prefix"`
}
// ComputeMetadata represents compute information
type ComputeMetadata struct {
Environment string `json:"azEnvironment,omitempty"`
SKU string `json:"sku,omitempty"`
Name string `json:"name,omitempty"`
Zone string `json:"zone,omitempty"`
VMSize string `json:"vmSize,omitempty"`
OSType string `json:"osType,omitempty"`
Location string `json:"location,omitempty"`
FaultDomain string `json:"platformFaultDomain,omitempty"`
UpdateDomain string `json:"platformUpdateDomain,omitempty"`
ResourceGroup string `json:"resourceGroupName,omitempty"`
VMScaleSetName string `json:"vmScaleSetName,omitempty"`
SubscriptionID string `json:"subscriptionId,omitempty"`
}
// InstanceMetadata represents instance information.
type InstanceMetadata struct {
Compute *ComputeMetadata `json:"compute,omitempty"`
Network *NetworkMetadata `json:"network,omitempty"`
}
// PublicIPMetadata represents the public IP metadata.
type PublicIPMetadata struct {
FrontendIPAddress string `json:"frontendIpAddress,omitempty"`
PrivateIPAddress string `json:"privateIpAddress,omitempty"`
}
// LoadbalancerProfile represents load balancer profile in IMDS.
type LoadbalancerProfile struct {
PublicIPAddresses []PublicIPMetadata `json:"publicIpAddresses,omitempty"`
}
// LoadBalancerMetadata represents load balancer metadata.
type LoadBalancerMetadata struct {
LoadBalancer *LoadbalancerProfile `json:"loadbalancer,omitempty"`
}
// InstanceMetadataService knows how to query the Azure instance metadata server.
type InstanceMetadataService struct {
imdsServer string
imsCache *azcache.TimedCache
}
// NewInstanceMetadataService creates an instance of the InstanceMetadataService accessor object.
func NewInstanceMetadataService(imdsServer string) (*InstanceMetadataService, error) {
ims := &InstanceMetadataService{
imdsServer: imdsServer,
}
imsCache, err := azcache.NewTimedcache(consts.MetadataCacheTTL, ims.getMetadata)
if err != nil {
return nil, err
}
ims.imsCache = imsCache
return ims, nil
}
func (ims *InstanceMetadataService) getMetadata(key string) (interface{}, error) {
instanceMetadata, err := ims.getInstanceMetadata(key)
if err != nil {
return nil, err
}
if instanceMetadata.Network != nil && len(instanceMetadata.Network.Interface) > 0 {
netInterface := instanceMetadata.Network.Interface[0]
if (len(netInterface.IPV4.IPAddress) > 0 && len(netInterface.IPV4.IPAddress[0].PublicIP) > 0) ||
(len(netInterface.IPV6.IPAddress) > 0 && len(netInterface.IPV6.IPAddress[0].PublicIP) > 0) {
// Return if public IP address has already part of instance metadata.
return instanceMetadata, nil
}
loadBalancerMetadata, err := ims.getLoadBalancerMetadata()
if err != nil || loadBalancerMetadata == nil || loadBalancerMetadata.LoadBalancer == nil {
// Log a warning since loadbalancer metadata may not be available when the VM
// is not in standard LoadBalancer backend address pool.
klog.V(4).Infof("Warning: failed to get loadbalancer metadata: %v", err)
return instanceMetadata, nil
}
publicIPs := loadBalancerMetadata.LoadBalancer.PublicIPAddresses
if len(netInterface.IPV4.IPAddress) > 0 && len(netInterface.IPV4.IPAddress[0].PrivateIP) > 0 {
for _, pip := range publicIPs {
if pip.PrivateIPAddress == netInterface.IPV4.IPAddress[0].PrivateIP {
netInterface.IPV4.IPAddress[0].PublicIP = pip.FrontendIPAddress
break
}
}
}
if len(netInterface.IPV6.IPAddress) > 0 && len(netInterface.IPV6.IPAddress[0].PrivateIP) > 0 {
for _, pip := range publicIPs {
if pip.PrivateIPAddress == netInterface.IPV6.IPAddress[0].PrivateIP {
netInterface.IPV6.IPAddress[0].PublicIP = pip.FrontendIPAddress
break
}
}
}
}
return instanceMetadata, nil
}
func (ims *InstanceMetadataService) getInstanceMetadata(key string) (*InstanceMetadata, error) {
req, err := http.NewRequest("GET", ims.imdsServer+consts.ImdsInstanceURI, nil)
if err != nil {
return nil, err
}
req.Header.Add("Metadata", "True")
req.Header.Add("User-Agent", "golang/kubernetes-cloud-provider")
q := req.URL.Query()
q.Add("format", "json")
q.Add("api-version", consts.ImdsInstanceAPIVersion)
req.URL.RawQuery = q.Encode()
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failure of getting instance metadata with response %q", resp.Status)
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
obj := InstanceMetadata{}
err = json.Unmarshal(data, &obj)
if err != nil {
return nil, err
}
return &obj, nil
}
func (ims *InstanceMetadataService) getLoadBalancerMetadata() (*LoadBalancerMetadata, error) {
req, err := http.NewRequest("GET", ims.imdsServer+consts.ImdsLoadBalancerURI, nil)
if err != nil {
return nil, err
}
req.Header.Add("Metadata", "True")
req.Header.Add("User-Agent", "golang/kubernetes-cloud-provider")
q := req.URL.Query()
q.Add("format", "json")
q.Add("api-version", consts.ImdsLoadBalancerAPIVersion)
req.URL.RawQuery = q.Encode()
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failure of getting loadbalancer metadata with response %q", resp.Status)
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
obj := LoadBalancerMetadata{}
err = json.Unmarshal(data, &obj)
if err != nil {
return nil, err
}
return &obj, nil
}
// GetMetadata gets instance metadata from cache.
// crt determines if we can get data from stalled cache/need fresh if cache expired.
func (ims *InstanceMetadataService) GetMetadata(crt azcache.AzureCacheReadType) (*InstanceMetadata, error) {
cache, err := ims.imsCache.Get(consts.MetadataCacheKey, crt)
if err != nil {
return nil, err
}
// Cache shouldn't be nil, but added a check in case something is wrong.
if cache == nil {
return nil, fmt.Errorf("failure of getting instance metadata")
}
if metadata, ok := cache.(*InstanceMetadata); ok {
return metadata, nil
}
return nil, fmt.Errorf("failure of getting instance metadata")
}

View File

@ -0,0 +1,542 @@
/*
Copyright 2020 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 provider
import (
"context"
"errors"
"fmt"
"os"
"strings"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
cloudprovider "k8s.io/cloud-provider"
"k8s.io/klog/v2"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
const (
vmPowerStatePrefix = "PowerState/"
vmPowerStateStopped = "stopped"
vmPowerStateDeallocated = "deallocated"
vmPowerStateDeallocating = "deallocating"
// nodeNameEnvironmentName is the environment variable name for getting node name.
// It is only used for out-of-tree cloud provider.
nodeNameEnvironmentName = "NODE_NAME"
)
var (
errNodeNotInitialized = fmt.Errorf("providerID is empty, the node is not initialized yet")
)
func (az *Cloud) addressGetter(nodeName types.NodeName) ([]v1.NodeAddress, error) {
ip, publicIP, err := az.getIPForMachine(nodeName)
if err != nil {
klog.V(2).Infof("NodeAddresses(%s) abort backoff: %v", nodeName, err)
return nil, err
}
addresses := []v1.NodeAddress{
{Type: v1.NodeInternalIP, Address: ip},
{Type: v1.NodeHostName, Address: string(nodeName)},
}
if len(publicIP) > 0 {
addresses = append(addresses, v1.NodeAddress{
Type: v1.NodeExternalIP,
Address: publicIP,
})
}
return addresses, nil
}
// NodeAddresses returns the addresses of the specified instance.
func (az *Cloud) NodeAddresses(ctx context.Context, name types.NodeName) ([]v1.NodeAddress, error) {
// Returns nil for unmanaged nodes because azure cloud provider couldn't fetch information for them.
unmanaged, err := az.IsNodeUnmanaged(string(name))
if err != nil {
return nil, err
}
if unmanaged {
klog.V(4).Infof("NodeAddresses: omitting unmanaged node %q", name)
return nil, nil
}
if az.UseInstanceMetadata {
metadata, err := az.Metadata.GetMetadata(azcache.CacheReadTypeDefault)
if err != nil {
return nil, err
}
if metadata.Compute == nil || metadata.Network == nil {
return nil, fmt.Errorf("failure of getting instance metadata")
}
isLocalInstance, err := az.isCurrentInstance(name, metadata.Compute.Name)
if err != nil {
return nil, err
}
// Not local instance, get addresses from Azure ARM API.
if !isLocalInstance {
if az.VMSet != nil {
return az.addressGetter(name)
}
// vmSet == nil indicates credentials are not provided.
return nil, fmt.Errorf("no credentials provided for Azure cloud provider")
}
return az.getLocalInstanceNodeAddresses(metadata.Network.Interface, string(name))
}
return az.addressGetter(name)
}
func (az *Cloud) getLocalInstanceNodeAddresses(netInterfaces []NetworkInterface, nodeName string) ([]v1.NodeAddress, error) {
if len(netInterfaces) == 0 {
return nil, fmt.Errorf("no interface is found for the instance")
}
// Use ip address got from instance metadata.
netInterface := netInterfaces[0]
addresses := []v1.NodeAddress{
{Type: v1.NodeHostName, Address: nodeName},
}
if len(netInterface.IPV4.IPAddress) > 0 && len(netInterface.IPV4.IPAddress[0].PrivateIP) > 0 {
address := netInterface.IPV4.IPAddress[0]
addresses = append(addresses, v1.NodeAddress{
Type: v1.NodeInternalIP,
Address: address.PrivateIP,
})
if len(address.PublicIP) > 0 {
addresses = append(addresses, v1.NodeAddress{
Type: v1.NodeExternalIP,
Address: address.PublicIP,
})
}
}
if len(netInterface.IPV6.IPAddress) > 0 && len(netInterface.IPV6.IPAddress[0].PrivateIP) > 0 {
address := netInterface.IPV6.IPAddress[0]
addresses = append(addresses, v1.NodeAddress{
Type: v1.NodeInternalIP,
Address: address.PrivateIP,
})
if len(address.PublicIP) > 0 {
addresses = append(addresses, v1.NodeAddress{
Type: v1.NodeExternalIP,
Address: address.PublicIP,
})
}
}
if len(addresses) == 1 {
// No IP addresses is got from instance metadata service, clean up cache and report errors.
_ = az.Metadata.imsCache.Delete(consts.MetadataCacheKey)
return nil, fmt.Errorf("get empty IP addresses from instance metadata service")
}
return addresses, nil
}
// NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID
// This method will not be called from the node that is requesting this ID. i.e. metadata service
// and other local methods cannot be used here
func (az *Cloud) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) {
if providerID == "" {
return nil, errNodeNotInitialized
}
// Returns nil for unmanaged nodes because azure cloud provider couldn't fetch information for them.
if az.IsNodeUnmanagedByProviderID(providerID) {
klog.V(4).Infof("NodeAddressesByProviderID: omitting unmanaged node %q", providerID)
return nil, nil
}
if az.VMSet == nil {
// vmSet == nil indicates credentials are not provided.
return nil, fmt.Errorf("no credentials provided for Azure cloud provider")
}
name, err := az.VMSet.GetNodeNameByProviderID(providerID)
if err != nil {
return nil, err
}
return az.NodeAddresses(ctx, name)
}
// InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running.
// If false is returned with no error, the instance will be immediately deleted by the cloud controller manager.
func (az *Cloud) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) {
if providerID == "" {
return false, errNodeNotInitialized
}
// Returns true for unmanaged nodes because azure cloud provider always assumes them exists.
if az.IsNodeUnmanagedByProviderID(providerID) {
klog.V(4).Infof("InstanceExistsByProviderID: assuming unmanaged node %q exists", providerID)
return true, nil
}
if az.VMSet == nil {
// vmSet == nil indicates credentials are not provided.
return false, fmt.Errorf("no credentials provided for Azure cloud provider")
}
name, err := az.VMSet.GetNodeNameByProviderID(providerID)
if err != nil {
if errors.Is(err, cloudprovider.InstanceNotFound) {
return false, nil
}
return false, err
}
_, err = az.InstanceID(ctx, name)
if err != nil {
if errors.Is(err, cloudprovider.InstanceNotFound) {
return false, nil
}
return false, err
}
return true, nil
}
// InstanceExists returns true if the instance for the given node exists according to the cloud provider.
// Use the node.name or node.spec.providerID field to find the node in the cloud provider.
func (az *Cloud) InstanceExists(ctx context.Context, node *v1.Node) (bool, error) {
if node == nil {
return false, nil
}
providerID := node.Spec.ProviderID
if providerID == "" {
var err error
providerID, err = cloudprovider.GetInstanceProviderID(ctx, az, types.NodeName(node.Name))
if err != nil {
klog.Errorf("InstanceExists: failed to get the provider ID by node name %s: %v", node.Name, err)
return false, err
}
}
return az.InstanceExistsByProviderID(ctx, providerID)
}
// InstanceShutdownByProviderID returns true if the instance is in safe state to detach volumes
func (az *Cloud) InstanceShutdownByProviderID(ctx context.Context, providerID string) (bool, error) {
if providerID == "" {
return false, nil
}
if az.VMSet == nil {
// vmSet == nil indicates credentials are not provided.
return false, fmt.Errorf("no credentials provided for Azure cloud provider")
}
nodeName, err := az.VMSet.GetNodeNameByProviderID(providerID)
if err != nil {
// Returns false, so the controller manager will continue to check InstanceExistsByProviderID().
if errors.Is(err, cloudprovider.InstanceNotFound) {
return false, nil
}
return false, err
}
powerStatus, err := az.VMSet.GetPowerStatusByNodeName(string(nodeName))
if err != nil {
// Returns false, so the controller manager will continue to check InstanceExistsByProviderID().
if errors.Is(err, cloudprovider.InstanceNotFound) {
return false, nil
}
return false, err
}
klog.V(3).Infof("InstanceShutdownByProviderID gets power status %q for node %q", powerStatus, nodeName)
provisioningState, err := az.VMSet.GetProvisioningStateByNodeName(string(nodeName))
if err != nil {
// Returns false, so the controller manager will continue to check InstanceExistsByProviderID().
if errors.Is(err, cloudprovider.InstanceNotFound) {
return false, nil
}
return false, err
}
klog.V(3).Infof("InstanceShutdownByProviderID gets provisioning state %q for node %q", provisioningState, nodeName)
status := strings.ToLower(powerStatus)
provisioningSucceeded := strings.EqualFold(strings.ToLower(provisioningState), strings.ToLower(string(compute.ProvisioningStateSucceeded)))
return provisioningSucceeded && (status == vmPowerStateStopped || status == vmPowerStateDeallocated || status == vmPowerStateDeallocating), nil
}
// InstanceShutdown returns true if the instance is shutdown according to the cloud provider.
// Use the node.name or node.spec.providerID field to find the node in the cloud provider.
func (az *Cloud) InstanceShutdown(ctx context.Context, node *v1.Node) (bool, error) {
if node == nil {
return false, nil
}
providerID := node.Spec.ProviderID
if providerID == "" {
var err error
providerID, err = cloudprovider.GetInstanceProviderID(ctx, az, types.NodeName(node.Name))
if err != nil {
// Returns false, so the controller manager will continue to check InstanceExistsByProviderID().
if strings.Contains(err.Error(), cloudprovider.InstanceNotFound.Error()) {
return false, nil
}
klog.Errorf("InstanceShutdown: failed to get the provider ID by node name %s: %v", node.Name, err)
return false, err
}
}
return az.InstanceShutdownByProviderID(ctx, providerID)
}
func (az *Cloud) isCurrentInstance(name types.NodeName, metadataVMName string) (bool, error) {
var err error
nodeName := mapNodeNameToVMName(name)
// VMSS vmName is not same with hostname, use hostname instead.
if az.VMType == consts.VMTypeVMSS {
metadataVMName, err = os.Hostname()
if err != nil {
return false, err
}
// Use name from env variable "NODE_NAME" if it is set.
nodeNameEnv := os.Getenv(nodeNameEnvironmentName)
if nodeNameEnv != "" {
metadataVMName = nodeNameEnv
}
}
metadataVMName = strings.ToLower(metadataVMName)
return metadataVMName == nodeName, nil
}
// InstanceID returns the cloud provider ID of the specified instance.
// Note that if the instance does not exist or is no longer running, we must return ("", cloudprovider.InstanceNotFound)
func (az *Cloud) InstanceID(ctx context.Context, name types.NodeName) (string, error) {
nodeName := mapNodeNameToVMName(name)
unmanaged, err := az.IsNodeUnmanaged(nodeName)
if err != nil {
return "", err
}
if unmanaged {
// InstanceID is same with nodeName for unmanaged nodes.
klog.V(4).Infof("InstanceID: getting ID %q for unmanaged node %q", name, name)
return nodeName, nil
}
if az.UseInstanceMetadata {
metadata, err := az.Metadata.GetMetadata(azcache.CacheReadTypeDefault)
if err != nil {
return "", err
}
if metadata.Compute == nil {
return "", fmt.Errorf("failure of getting instance metadata")
}
isLocalInstance, err := az.isCurrentInstance(name, metadata.Compute.Name)
if err != nil {
return "", err
}
// Not local instance, get instanceID from Azure ARM API.
if !isLocalInstance {
if az.VMSet != nil {
return az.VMSet.GetInstanceIDByNodeName(nodeName)
}
// vmSet == nil indicates credentials are not provided.
return "", fmt.Errorf("no credentials provided for Azure cloud provider")
}
return az.getLocalInstanceProviderID(metadata, nodeName)
}
return az.VMSet.GetInstanceIDByNodeName(nodeName)
}
func (az *Cloud) getLocalInstanceProviderID(metadata *InstanceMetadata, nodeName string) (string, error) {
// Get resource group name and subscription ID.
resourceGroup := strings.ToLower(metadata.Compute.ResourceGroup)
subscriptionID := strings.ToLower(metadata.Compute.SubscriptionID)
// Compose instanceID based on nodeName for standard instance.
if metadata.Compute.VMScaleSetName == "" {
return az.getStandardMachineID(subscriptionID, resourceGroup, nodeName), nil
}
// Get scale set name and instanceID from vmName for vmss.
ssName, instanceID, err := extractVmssVMName(metadata.Compute.Name)
if err != nil {
if errors.Is(err, ErrorNotVmssInstance) {
// Compose machineID for standard Node.
return az.getStandardMachineID(subscriptionID, resourceGroup, nodeName), nil
}
return "", err
}
// Compose instanceID based on ssName and instanceID for vmss instance.
return az.getVmssMachineID(subscriptionID, resourceGroup, ssName, instanceID), nil
}
// InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID
// This method will not be called from the node that is requesting this ID. i.e. metadata service
// and other local methods cannot be used here
func (az *Cloud) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
if providerID == "" {
return "", errNodeNotInitialized
}
// Returns "" for unmanaged nodes because azure cloud provider couldn't fetch information for them.
if az.IsNodeUnmanagedByProviderID(providerID) {
klog.V(4).Infof("InstanceTypeByProviderID: omitting unmanaged node %q", providerID)
return "", nil
}
if az.VMSet == nil {
// vmSet == nil indicates credentials are not provided.
return "", fmt.Errorf("no credentials provided for Azure cloud provider")
}
name, err := az.VMSet.GetNodeNameByProviderID(providerID)
if err != nil {
return "", err
}
return az.InstanceType(ctx, name)
}
// InstanceType returns the type of the specified instance.
// Note that if the instance does not exist or is no longer running, we must return ("", cloudprovider.InstanceNotFound)
// (Implementer Note): This is used by kubelet. Kubelet will label the node. Real log from kubelet:
// Adding node label from cloud provider: beta.kubernetes.io/instance-type=[value]
func (az *Cloud) InstanceType(ctx context.Context, name types.NodeName) (string, error) {
// Returns "" for unmanaged nodes because azure cloud provider couldn't fetch information for them.
unmanaged, err := az.IsNodeUnmanaged(string(name))
if err != nil {
return "", err
}
if unmanaged {
klog.V(4).Infof("InstanceType: omitting unmanaged node %q", name)
return "", nil
}
if az.UseInstanceMetadata {
metadata, err := az.Metadata.GetMetadata(azcache.CacheReadTypeDefault)
if err != nil {
return "", err
}
if metadata.Compute == nil {
return "", fmt.Errorf("failure of getting instance metadata")
}
isLocalInstance, err := az.isCurrentInstance(name, metadata.Compute.Name)
if err != nil {
return "", err
}
if !isLocalInstance {
if az.VMSet != nil {
return az.VMSet.GetInstanceTypeByNodeName(string(name))
}
// vmSet == nil indicates credentials are not provided.
return "", fmt.Errorf("no credentials provided for Azure cloud provider")
}
if metadata.Compute.VMSize != "" {
return metadata.Compute.VMSize, nil
}
}
if az.VMSet == nil {
// vmSet == nil indicates credentials are not provided.
return "", fmt.Errorf("no credentials provided for Azure cloud provider")
}
return az.VMSet.GetInstanceTypeByNodeName(string(name))
}
// AddSSHKeyToAllInstances adds an SSH public key as a legal identity for all instances
// expected format for the key is standard ssh-keygen format: <protocol> <blob>
func (az *Cloud) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error {
return cloudprovider.NotImplemented
}
// CurrentNodeName returns the name of the node we are currently running on.
// On Azure this is the hostname, so we just return the hostname.
func (az *Cloud) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) {
return types.NodeName(hostname), nil
}
// InstanceMetadata returns the instance's metadata. The values returned in InstanceMetadata are
// translated into specific fields in the Node object on registration.
// Use the node.name or node.spec.providerID field to find the node in the cloud provider.
func (az *Cloud) InstanceMetadata(ctx context.Context, node *v1.Node) (*cloudprovider.InstanceMetadata, error) {
if node == nil {
return &cloudprovider.InstanceMetadata{}, nil
}
meta := cloudprovider.InstanceMetadata{}
if node.Spec.ProviderID != "" {
meta.ProviderID = node.Spec.ProviderID
} else {
providerID, err := cloudprovider.GetInstanceProviderID(ctx, az, types.NodeName(node.Name))
if err != nil {
klog.Errorf("InstanceMetadata: failed to get the provider ID by node name %s: %v", node.Name, err)
return nil, err
}
meta.ProviderID = providerID
}
instanceType, err := az.InstanceType(ctx, types.NodeName(node.Name))
if err != nil {
klog.Errorf("InstanceMetadata: failed to get the instance type of %s: %v", node.Name, err)
return &cloudprovider.InstanceMetadata{}, err
}
meta.InstanceType = instanceType
nodeAddresses, err := az.NodeAddresses(ctx, types.NodeName(node.Name))
if err != nil {
klog.Errorf("InstanceMetadata: failed to get the node address of %s: %v", node.Name, err)
return &cloudprovider.InstanceMetadata{}, err
}
meta.NodeAddresses = nodeAddresses
zone, err := az.GetZoneByNodeName(ctx, types.NodeName(node.Name))
if err != nil {
klog.Errorf("InstanceMetadata: failed to get the node zone of %s: %v", node.Name, err)
return &cloudprovider.InstanceMetadata{}, err
}
meta.Zone = zone.FailureDomain
meta.Region = zone.Region
return &meta, nil
}
// mapNodeNameToVMName maps a k8s NodeName to an Azure VM Name
// This is a simple string cast.
func mapNodeNameToVMName(nodeName types.NodeName) string {
return string(nodeName)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,445 @@
/*
Copyright 2021 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 provider
//go:generate sh -c "mockgen -destination=$GOPATH/src/sigs.k8s.io/cloud-provider-azure/pkg/provider/azure_mock_loadbalancer_backendpool.go -source=$GOPATH/src/sigs.k8s.io/cloud-provider-azure/pkg/provider/azure_loadbalancer_backendpool.go -package=provider BackendPool"
import (
"errors"
"fmt"
"strings"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/go-autorest/autorest/to"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
cloudprovider "k8s.io/cloud-provider"
"k8s.io/klog/v2"
utilnet "k8s.io/utils/net"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
type BackendPool interface {
// EnsureHostsInPool ensures the nodes join the backend pool of the load balancer
EnsureHostsInPool(service *v1.Service, nodes []*v1.Node, backendPoolID, vmSetName, clusterName, lbName string, backendPool network.BackendAddressPool) error
// CleanupVMSetFromBackendPoolByCondition removes nodes of the unwanted vmSet from the lb backend pool.
// This is needed in two scenarios:
// 1. When migrating from single SLB to multiple SLBs, the existing
// SLB's backend pool contains nodes from different agent pools, while we only want the
// nodes from the primary agent pool to join the backend pool.
// 2. When migrating from dedicated SLB to shared SLB (or vice versa), we should move the vmSet from
// one SLB to another one.
CleanupVMSetFromBackendPoolByCondition(slb *network.LoadBalancer, service *v1.Service, nodes []*v1.Node, clusterName string, shouldRemoveVMSetFromSLB func(string) bool) (*network.LoadBalancer, error)
// ReconcileBackendPools creates the inbound backend pool if it is not existed, and removes nodes that are supposed to be
// excluded from the load balancers.
ReconcileBackendPools(clusterName string, service *v1.Service, lb *network.LoadBalancer) (bool, bool, error)
}
type backendPoolTypeNodeIPConfig struct {
*Cloud
}
func newBackendPoolTypeNodeIPConfig(c *Cloud) BackendPool {
return &backendPoolTypeNodeIPConfig{c}
}
func (bc *backendPoolTypeNodeIPConfig) EnsureHostsInPool(service *v1.Service, nodes []*v1.Node, backendPoolID, vmSetName, clusterName, lbName string, backendPool network.BackendAddressPool) error {
return bc.VMSet.EnsureHostsInPool(service, nodes, backendPoolID, vmSetName)
}
func (bc *backendPoolTypeNodeIPConfig) CleanupVMSetFromBackendPoolByCondition(slb *network.LoadBalancer, service *v1.Service, nodes []*v1.Node, clusterName string, shouldRemoveVMSetFromSLB func(string) bool) (*network.LoadBalancer, error) {
lbBackendPoolName := getBackendPoolName(clusterName, service)
lbResourceGroup := bc.getLoadBalancerResourceGroup()
lbBackendPoolID := bc.getBackendPoolID(to.String(slb.Name), lbResourceGroup, lbBackendPoolName)
newBackendPools := make([]network.BackendAddressPool, 0)
if slb.LoadBalancerPropertiesFormat != nil && slb.BackendAddressPools != nil {
newBackendPools = *slb.BackendAddressPools
}
vmSetNameToBackendIPConfigurationsToBeDeleted := make(map[string][]network.InterfaceIPConfiguration)
for j, bp := range newBackendPools {
if strings.EqualFold(to.String(bp.Name), lbBackendPoolName) {
klog.V(2).Infof("bc.CleanupVMSetFromBackendPoolByCondition: checking the backend pool %s from standard load balancer %s", to.String(bp.Name), to.String(slb.Name))
if bp.BackendAddressPoolPropertiesFormat != nil && bp.BackendIPConfigurations != nil {
for i := len(*bp.BackendIPConfigurations) - 1; i >= 0; i-- {
ipConf := (*bp.BackendIPConfigurations)[i]
ipConfigID := to.String(ipConf.ID)
_, vmSetName, err := bc.VMSet.GetNodeNameByIPConfigurationID(ipConfigID)
if err != nil && !errors.Is(err, cloudprovider.InstanceNotFound) {
return nil, err
}
if shouldRemoveVMSetFromSLB(vmSetName) {
klog.V(2).Infof("bc.CleanupVMSetFromBackendPoolByCondition: found unwanted vmSet %s, decouple it from the LB", vmSetName)
// construct a backendPool that only contains the IP config of the node to be deleted
interfaceIPConfigToBeDeleted := network.InterfaceIPConfiguration{
ID: to.StringPtr(ipConfigID),
}
vmSetNameToBackendIPConfigurationsToBeDeleted[vmSetName] = append(vmSetNameToBackendIPConfigurationsToBeDeleted[vmSetName], interfaceIPConfigToBeDeleted)
*bp.BackendIPConfigurations = append((*bp.BackendIPConfigurations)[:i], (*bp.BackendIPConfigurations)[i+1:]...)
}
}
}
newBackendPools[j] = bp
break
}
}
for vmSetName := range vmSetNameToBackendIPConfigurationsToBeDeleted {
backendIPConfigurationsToBeDeleted := vmSetNameToBackendIPConfigurationsToBeDeleted[vmSetName]
backendpoolToBeDeleted := &[]network.BackendAddressPool{
{
ID: to.StringPtr(lbBackendPoolID),
BackendAddressPoolPropertiesFormat: &network.BackendAddressPoolPropertiesFormat{
BackendIPConfigurations: &backendIPConfigurationsToBeDeleted,
},
},
}
// decouple the backendPool from the node
err := bc.VMSet.EnsureBackendPoolDeleted(service, lbBackendPoolID, vmSetName, backendpoolToBeDeleted, true)
if err != nil {
return nil, err
}
slb.BackendAddressPools = &newBackendPools
// Proactively disable the etag to prevent etag mismatch error when putting lb later.
// This could happen because when we remove the hosts from the lb, the nrp
// would put the lb to remove the backend references as well.
slb.Etag = nil
}
return slb, nil
}
func (bc *backendPoolTypeNodeIPConfig) ReconcileBackendPools(clusterName string, service *v1.Service, lb *network.LoadBalancer) (bool, bool, error) {
var newBackendPools []network.BackendAddressPool
var err error
if lb.BackendAddressPools != nil {
newBackendPools = *lb.BackendAddressPools
}
foundBackendPool := false
wantLb := true
changed := false
lbName := *lb.Name
serviceName := getServiceName(service)
lbBackendPoolName := getBackendPoolName(clusterName, service)
lbBackendPoolID := bc.getBackendPoolID(lbName, bc.getLoadBalancerResourceGroup(), lbBackendPoolName)
vmSetName := bc.mapLoadBalancerNameToVMSet(lbName, clusterName)
for _, bp := range newBackendPools {
if strings.EqualFold(*bp.Name, lbBackendPoolName) {
klog.V(10).Infof("bc.ReconcileBackendPools for service (%s)(%t): lb backendpool - found wanted backendpool. not adding anything", serviceName, wantLb)
foundBackendPool = true
var backendIPConfigurationsToBeDeleted []network.InterfaceIPConfiguration
if bp.BackendAddressPoolPropertiesFormat != nil && bp.BackendIPConfigurations != nil {
for _, ipConf := range *bp.BackendIPConfigurations {
ipConfID := to.String(ipConf.ID)
nodeName, _, err := bc.VMSet.GetNodeNameByIPConfigurationID(ipConfID)
if err != nil && !errors.Is(err, cloudprovider.InstanceNotFound) {
return false, false, err
}
// If a node is not supposed to be included in the LB, it
// would not be in the `nodes` slice. We need to check the nodes that
// have been added to the LB's backendpool, find the unwanted ones and
// delete them from the pool.
shouldExcludeLoadBalancer, err := bc.ShouldNodeExcludedFromLoadBalancer(nodeName)
if err != nil {
klog.Errorf("bc.ReconcileBackendPools: ShouldNodeExcludedFromLoadBalancer(%s) failed with error: %v", nodeName, err)
return false, false, err
}
if shouldExcludeLoadBalancer {
klog.V(2).Infof("bc.ReconcileBackendPools for service (%s)(%t): lb backendpool - found unwanted node %s, decouple it from the LB %s", serviceName, wantLb, nodeName, lbName)
// construct a backendPool that only contains the IP config of the node to be deleted
backendIPConfigurationsToBeDeleted = append(backendIPConfigurationsToBeDeleted, network.InterfaceIPConfiguration{ID: to.StringPtr(ipConfID)})
}
}
}
if len(backendIPConfigurationsToBeDeleted) > 0 {
backendpoolToBeDeleted := &[]network.BackendAddressPool{
{
ID: to.StringPtr(lbBackendPoolID),
BackendAddressPoolPropertiesFormat: &network.BackendAddressPoolPropertiesFormat{
BackendIPConfigurations: &backendIPConfigurationsToBeDeleted,
},
},
}
// decouple the backendPool from the node
err = bc.VMSet.EnsureBackendPoolDeleted(service, lbBackendPoolID, vmSetName, backendpoolToBeDeleted, false)
if err != nil {
return false, false, err
}
}
break
} else {
klog.V(10).Infof("bc.ReconcileBackendPools for service (%s)(%t): lb backendpool - found unmanaged backendpool %s", serviceName, wantLb, *bp.Name)
}
}
isBackendPoolPreConfigured := bc.isBackendPoolPreConfigured(service)
if !foundBackendPool {
isBackendPoolPreConfigured = newBackendPool(lb, isBackendPoolPreConfigured, bc.PreConfiguredBackendPoolLoadBalancerTypes, getServiceName(service), getBackendPoolName(clusterName, service))
changed = true
}
return isBackendPoolPreConfigured, changed, err
}
type backendPoolTypeNodeIP struct {
*Cloud
}
func newBackendPoolTypeNodeIP(c *Cloud) BackendPool {
return &backendPoolTypeNodeIP{c}
}
func (bi *backendPoolTypeNodeIP) EnsureHostsInPool(service *v1.Service, nodes []*v1.Node, backendPoolID, vmSetName, clusterName, lbName string, backendPool network.BackendAddressPool) error {
vnetResourceGroup := bi.ResourceGroup
if len(bi.VnetResourceGroup) > 0 {
vnetResourceGroup = bi.VnetResourceGroup
}
vnetID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s", bi.SubscriptionID, vnetResourceGroup, bi.VnetName)
changed := false
lbBackendPoolName := getBackendPoolName(clusterName, service)
if strings.EqualFold(to.String(backendPool.Name), lbBackendPoolName) &&
backendPool.BackendAddressPoolPropertiesFormat != nil {
if backendPool.LoadBalancerBackendAddresses == nil {
lbBackendPoolAddresses := make([]network.LoadBalancerBackendAddress, 0)
backendPool.LoadBalancerBackendAddresses = &lbBackendPoolAddresses
}
existingIPs := sets.NewString()
for _, loadBalancerBackendAddress := range *backendPool.LoadBalancerBackendAddresses {
if loadBalancerBackendAddress.LoadBalancerBackendAddressPropertiesFormat != nil &&
loadBalancerBackendAddress.IPAddress != nil {
klog.V(4).Infof("bi.EnsureHostsInPool: found existing IP %s in the backend pool %s", to.String(loadBalancerBackendAddress.IPAddress), lbBackendPoolName)
existingIPs.Insert(to.String(loadBalancerBackendAddress.IPAddress))
}
}
for _, node := range nodes {
if isControlPlaneNode(node) {
klog.V(4).Infof("bi.EnsureHostsInPool: skipping control plane node %s", node.Name)
continue
}
var err error
shouldSkip := false
useSingleSLB := strings.EqualFold(bi.LoadBalancerSku, consts.LoadBalancerSkuStandard) && !bi.EnableMultipleStandardLoadBalancers
if !useSingleSLB {
vmSetName, err = bi.VMSet.GetNodeVMSetName(node)
if err != nil {
klog.Errorf("bi.EnsureHostsInPool: failed to get vmSet name by node name: %s", err.Error())
return err
}
if !strings.EqualFold(vmSetName, bi.mapLoadBalancerNameToVMSet(lbName, clusterName)) {
shouldSkip = true
lbNamePrefix := strings.TrimSuffix(lbName, consts.InternalLoadBalancerNameSuffix)
if strings.EqualFold(lbNamePrefix, clusterName) &&
strings.EqualFold(bi.LoadBalancerSku, consts.LoadBalancerSkuStandard) &&
bi.getVMSetNamesSharingPrimarySLB().Has(vmSetName) {
shouldSkip = false
}
}
}
if shouldSkip {
klog.V(4).Infof("bi.EnsureHostsInPool: skipping attaching node %s to lb %s, because the vmSet of the node is %s", node.Name, lbName, vmSetName)
continue
}
privateIP := getNodePrivateIPAddress(service, node)
if !existingIPs.Has(privateIP) {
name := node.Name
if utilnet.IsIPv6String(privateIP) {
name = fmt.Sprintf("%s-ipv6", name)
}
klog.V(4).Infof("bi.EnsureHostsInPool: adding %s with ip address %s", name, privateIP)
*backendPool.LoadBalancerBackendAddresses = append(*backendPool.LoadBalancerBackendAddresses, network.LoadBalancerBackendAddress{
Name: to.StringPtr(name),
LoadBalancerBackendAddressPropertiesFormat: &network.LoadBalancerBackendAddressPropertiesFormat{
IPAddress: to.StringPtr(privateIP),
VirtualNetwork: &network.SubResource{ID: to.StringPtr(vnetID)},
},
})
changed = true
}
}
}
if changed {
klog.V(2).Infof("bi.EnsureHostsInPool: updating backend pool %s of load balancer %s", lbBackendPoolName, lbName)
if err := bi.CreateOrUpdateLBBackendPool(lbName, backendPool); err != nil {
return fmt.Errorf("bi.EnsureHostsInPool: failed to update backend pool %s: %w", lbBackendPoolName, err)
}
}
return nil
}
func (bi *backendPoolTypeNodeIP) CleanupVMSetFromBackendPoolByCondition(slb *network.LoadBalancer, service *v1.Service, nodes []*v1.Node, clusterName string, shouldRemoveVMSetFromSLB func(string) bool) (*network.LoadBalancer, error) {
lbBackendPoolName := getBackendPoolName(clusterName, service)
newBackendPools := make([]network.BackendAddressPool, 0)
if slb.LoadBalancerPropertiesFormat != nil && slb.BackendAddressPools != nil {
newBackendPools = *slb.BackendAddressPools
}
var updatedPrivateIPs bool
for j, bp := range newBackendPools {
if strings.EqualFold(to.String(bp.Name), lbBackendPoolName) {
klog.V(2).Infof("bi.CleanupVMSetFromBackendPoolByCondition: checking the backend pool %s from standard load balancer %s", to.String(bp.Name), to.String(slb.Name))
vmIPsToBeDeleted := sets.NewString()
for _, node := range nodes {
vmSetName, err := bi.VMSet.GetNodeVMSetName(node)
if err != nil {
return nil, err
}
if shouldRemoveVMSetFromSLB(vmSetName) {
privateIP := getNodePrivateIPAddress(service, node)
klog.V(4).Infof("bi.CleanupVMSetFromBackendPoolByCondition: removing ip %s from the backend pool %s", privateIP, lbBackendPoolName)
vmIPsToBeDeleted.Insert(privateIP)
}
}
if bp.BackendAddressPoolPropertiesFormat != nil && bp.LoadBalancerBackendAddresses != nil {
for i := len(*bp.LoadBalancerBackendAddresses) - 1; i >= 0; i-- {
if (*bp.LoadBalancerBackendAddresses)[i].LoadBalancerBackendAddressPropertiesFormat != nil &&
vmIPsToBeDeleted.Has(to.String((*bp.LoadBalancerBackendAddresses)[i].IPAddress)) {
*bp.LoadBalancerBackendAddresses = append((*bp.LoadBalancerBackendAddresses)[:i], (*bp.LoadBalancerBackendAddresses)[i+1:]...)
updatedPrivateIPs = true
}
}
}
newBackendPools[j] = bp
break
}
}
if updatedPrivateIPs {
klog.V(2).Infof("bi.CleanupVMSetFromBackendPoolByCondition: updating lb %s since there are private IP updates", to.String(slb.Name))
slb.BackendAddressPools = &newBackendPools
for _, backendAddressPool := range *slb.BackendAddressPools {
if strings.EqualFold(lbBackendPoolName, to.String(backendAddressPool.Name)) {
if err := bi.CreateOrUpdateLBBackendPool(to.String(slb.Name), backendAddressPool); err != nil {
return nil, fmt.Errorf("bi.CleanupVMSetFromBackendPoolByCondition: failed to create or update backend pool %s: %w", lbBackendPoolName, err)
}
}
}
}
return slb, nil
}
func (bi *backendPoolTypeNodeIP) ReconcileBackendPools(clusterName string, service *v1.Service, lb *network.LoadBalancer) (bool, bool, error) {
var newBackendPools []network.BackendAddressPool
var err error
if lb.BackendAddressPools != nil {
newBackendPools = *lb.BackendAddressPools
}
foundBackendPool := false
wantLb := true
changed := false
lbName := *lb.Name
serviceName := getServiceName(service)
lbBackendPoolName := getBackendPoolName(clusterName, service)
for i, bp := range newBackendPools {
if strings.EqualFold(*bp.Name, lbBackendPoolName) {
klog.V(10).Infof("bi.ReconcileBackendPools for service (%s)(%t): lb backendpool - found wanted backendpool. not adding anything", serviceName, wantLb)
foundBackendPool = true
var nodeIPAddressesToBeDeleted []string
for nodeName := range bi.excludeLoadBalancerNodes {
for ip := range bi.nodePrivateIPs[nodeName] {
klog.V(2).Infof("bi.ReconcileBackendPools for service (%s)(%t): lb backendpool - found unwanted node private IP %s, decouple it from the LB %s", serviceName, wantLb, ip, lbName)
nodeIPAddressesToBeDeleted = append(nodeIPAddressesToBeDeleted, ip)
}
}
if len(nodeIPAddressesToBeDeleted) > 0 {
updated := removeNodeIPAddressesFromBackendPool(bp, nodeIPAddressesToBeDeleted)
if updated {
(*lb.BackendAddressPools)[i] = bp
if err := bi.CreateOrUpdateLBBackendPool(lbName, bp); err != nil {
return false, false, fmt.Errorf("bi.ReconcileBackendPools for service (%s)(%t): lb backendpool - failed to update backend pool %s for load balancer %s: %w", serviceName, wantLb, lbBackendPoolName, lbName, err)
}
}
}
break
} else {
klog.V(10).Infof("bi.ReconcileBackendPools for service (%s)(%t): lb backendpool - found unmanaged backendpool %s", serviceName, wantLb, *bp.Name)
}
}
isBackendPoolPreConfigured := bi.isBackendPoolPreConfigured(service)
if !foundBackendPool {
isBackendPoolPreConfigured = newBackendPool(lb, isBackendPoolPreConfigured, bi.PreConfiguredBackendPoolLoadBalancerTypes, getServiceName(service), getBackendPoolName(clusterName, service))
changed = true
}
return isBackendPoolPreConfigured, changed, err
}
func newBackendPool(lb *network.LoadBalancer, isBackendPoolPreConfigured bool, preConfiguredBackendPoolLoadBalancerTypes, serviceName, lbBackendPoolName string) bool {
if isBackendPoolPreConfigured {
klog.V(2).Infof("newBackendPool for service (%s)(true): lb backendpool - PreConfiguredBackendPoolLoadBalancerTypes %s has been set but can not find corresponding backend pool, ignoring it",
serviceName,
preConfiguredBackendPoolLoadBalancerTypes)
isBackendPoolPreConfigured = false
}
if lb.BackendAddressPools == nil {
lb.BackendAddressPools = &[]network.BackendAddressPool{}
}
*lb.BackendAddressPools = append(*lb.BackendAddressPools, network.BackendAddressPool{
Name: to.StringPtr(lbBackendPoolName),
BackendAddressPoolPropertiesFormat: &network.BackendAddressPoolPropertiesFormat{},
})
return isBackendPoolPreConfigured
}
func removeNodeIPAddressesFromBackendPool(backendPool network.BackendAddressPool, nodeIPAddresses []string) bool {
changed := false
nodeIPsSet := sets.NewString(nodeIPAddresses...)
if backendPool.BackendAddressPoolPropertiesFormat != nil &&
backendPool.LoadBalancerBackendAddresses != nil {
for i := len(*backendPool.LoadBalancerBackendAddresses) - 1; i >= 0; i-- {
if (*backendPool.LoadBalancerBackendAddresses)[i].LoadBalancerBackendAddressPropertiesFormat != nil {
ipAddress := to.String((*backendPool.LoadBalancerBackendAddresses)[i].IPAddress)
if nodeIPsSet.Has(ipAddress) {
klog.V(4).Infof("removeNodeIPAddressFromBackendPool: removing %s from the backend pool %s", ipAddress, to.String(backendPool.Name))
*backendPool.LoadBalancerBackendAddresses = append((*backendPool.LoadBalancerBackendAddresses)[:i], (*backendPool.LoadBalancerBackendAddresses)[i+1:]...)
changed = true
}
}
}
}
return changed
}

View File

@ -0,0 +1,431 @@
/*
Copyright 2020 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 provider
import (
"context"
"fmt"
"net/http"
"path"
"strconv"
"strings"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"github.com/Azure/go-autorest/autorest/to"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
kwait "k8s.io/apimachinery/pkg/util/wait"
cloudvolume "k8s.io/cloud-provider/volume"
volumehelpers "k8s.io/cloud-provider/volume/helpers"
"k8s.io/klog/v2"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
//ManagedDiskController : managed disk controller struct
type ManagedDiskController struct {
common *controllerCommon
}
// ManagedDiskOptions specifies the options of managed disks.
type ManagedDiskOptions struct {
// The SKU of storage account.
StorageAccountType compute.DiskStorageAccountTypes
// The name of the disk.
DiskName string
// The name of PVC.
PVCName string
// The name of resource group.
ResourceGroup string
// The AvailabilityZone to create the disk.
AvailabilityZone string
// The tags of the disk.
Tags map[string]string
// IOPS Caps for UltraSSD disk
DiskIOPSReadWrite string
// Throughput Cap (MBps) for UltraSSD disk
DiskMBpsReadWrite string
// if SourceResourceID is not empty, then it's a disk copy operation(for snapshot)
SourceResourceID string
// The type of source
SourceType string
// ResourceId of the disk encryption set to use for enabling encryption at rest.
DiskEncryptionSetID string
// The size in GB.
SizeGB int
// The maximum number of VMs that can attach to the disk at the same time. Value greater than one indicates a disk that can be mounted on multiple VMs at the same time.
MaxShares int32
// Logical sector size in bytes for Ultra disks
LogicalSectorSize int32
// SkipGetDiskOperation indicates whether skip GetDisk operation(mainly due to throttling)
SkipGetDiskOperation bool
// NetworkAccessPolicy - Possible values include: 'AllowAll', 'AllowPrivate', 'DenyAll'
NetworkAccessPolicy compute.NetworkAccessPolicy
// DiskAccessID - ARM id of the DiskAccess resource for using private endpoints on disks.
DiskAccessID *string
// BurstingEnabled - Set to true to enable bursting beyond the provisioned performance target of the disk.
BurstingEnabled *bool
}
//CreateManagedDisk : create managed disk
func (c *ManagedDiskController) CreateManagedDisk(options *ManagedDiskOptions) (string, error) {
var err error
klog.V(4).Infof("azureDisk - creating new managed Name:%s StorageAccountType:%s Size:%v", options.DiskName, options.StorageAccountType, options.SizeGB)
var createZones []string
if len(options.AvailabilityZone) > 0 {
requestedZone := c.common.cloud.GetZoneID(options.AvailabilityZone)
if requestedZone != "" {
createZones = append(createZones, requestedZone)
}
}
// insert original tags to newTags
newTags := make(map[string]*string)
azureDDTag := "kubernetes-azure-dd"
newTags[consts.CreatedByTag] = &azureDDTag
if options.Tags != nil {
for k, v := range options.Tags {
// Azure won't allow / (forward slash) in tags
newKey := strings.Replace(k, "/", "-", -1)
newValue := strings.Replace(v, "/", "-", -1)
newTags[newKey] = &newValue
}
}
diskSizeGB := int32(options.SizeGB)
diskSku := options.StorageAccountType
creationData, err := getValidCreationData(c.common.subscriptionID, options.ResourceGroup, options.SourceResourceID, options.SourceType)
if err != nil {
return "", err
}
diskProperties := compute.DiskProperties{
DiskSizeGB: &diskSizeGB,
CreationData: &creationData,
BurstingEnabled: options.BurstingEnabled,
}
if options.NetworkAccessPolicy != "" {
diskProperties.NetworkAccessPolicy = options.NetworkAccessPolicy
if options.NetworkAccessPolicy == compute.AllowPrivate {
if options.DiskAccessID == nil {
return "", fmt.Errorf("DiskAccessID should not be empty when NetworkAccessPolicy is AllowPrivate")
}
diskProperties.DiskAccessID = options.DiskAccessID
} else {
if options.DiskAccessID != nil {
return "", fmt.Errorf("DiskAccessID(%s) must be empty when NetworkAccessPolicy(%s) is not AllowPrivate", *options.DiskAccessID, options.NetworkAccessPolicy)
}
}
}
if diskSku == compute.UltraSSDLRS {
diskIOPSReadWrite := int64(consts.DefaultDiskIOPSReadWrite)
if options.DiskIOPSReadWrite != "" {
v, err := strconv.Atoi(options.DiskIOPSReadWrite)
if err != nil {
return "", fmt.Errorf("AzureDisk - failed to parse DiskIOPSReadWrite: %w", err)
}
diskIOPSReadWrite = int64(v)
}
diskProperties.DiskIOPSReadWrite = to.Int64Ptr(diskIOPSReadWrite)
diskMBpsReadWrite := int64(consts.DefaultDiskMBpsReadWrite)
if options.DiskMBpsReadWrite != "" {
v, err := strconv.Atoi(options.DiskMBpsReadWrite)
if err != nil {
return "", fmt.Errorf("AzureDisk - failed to parse DiskMBpsReadWrite: %w", err)
}
diskMBpsReadWrite = int64(v)
}
diskProperties.DiskMBpsReadWrite = to.Int64Ptr(diskMBpsReadWrite)
if options.LogicalSectorSize != 0 {
klog.V(2).Infof("AzureDisk - requested LogicalSectorSize: %v", options.LogicalSectorSize)
diskProperties.CreationData.LogicalSectorSize = to.Int32Ptr(options.LogicalSectorSize)
}
} else {
if options.DiskIOPSReadWrite != "" {
return "", fmt.Errorf("AzureDisk - DiskIOPSReadWrite parameter is only applicable in UltraSSD_LRS disk type")
}
if options.DiskMBpsReadWrite != "" {
return "", fmt.Errorf("AzureDisk - DiskMBpsReadWrite parameter is only applicable in UltraSSD_LRS disk type")
}
if options.LogicalSectorSize != 0 {
return "", fmt.Errorf("AzureDisk - LogicalSectorSize parameter is only applicable in UltraSSD_LRS disk type")
}
}
if options.DiskEncryptionSetID != "" {
if strings.Index(strings.ToLower(options.DiskEncryptionSetID), "/subscriptions/") != 0 {
return "", fmt.Errorf("AzureDisk - format of DiskEncryptionSetID(%s) is incorrect, correct format: %s", options.DiskEncryptionSetID, consts.DiskEncryptionSetIDFormat)
}
diskProperties.Encryption = &compute.Encryption{
DiskEncryptionSetID: &options.DiskEncryptionSetID,
Type: compute.EncryptionTypeEncryptionAtRestWithCustomerKey,
}
}
if options.MaxShares > 1 {
diskProperties.MaxShares = &options.MaxShares
}
model := compute.Disk{
Location: &c.common.location,
Tags: newTags,
Sku: &compute.DiskSku{
Name: diskSku,
},
DiskProperties: &diskProperties,
}
if el := c.common.extendedLocation; el != nil {
model.ExtendedLocation = &compute.ExtendedLocation{
Name: to.StringPtr(el.Name),
Type: compute.ExtendedLocationTypes(el.Type),
}
}
if len(createZones) > 0 {
model.Zones = &createZones
}
if options.ResourceGroup == "" {
options.ResourceGroup = c.common.resourceGroup
}
cloud := c.common.cloud
ctx, cancel := getContextWithCancel()
defer cancel()
rerr := cloud.DisksClient.CreateOrUpdate(ctx, options.ResourceGroup, options.DiskName, model)
if rerr != nil {
return "", rerr.Error()
}
diskID := fmt.Sprintf(managedDiskPath, cloud.subscriptionID, options.ResourceGroup, options.DiskName)
if options.SkipGetDiskOperation {
klog.Warningf("azureDisk - GetDisk(%s, StorageAccountType:%s) is throttled, unable to confirm provisioningState in poll process", options.DiskName, options.StorageAccountType)
} else {
err = kwait.ExponentialBackoff(defaultBackOff, func() (bool, error) {
provisionState, id, err := c.GetDisk(options.ResourceGroup, options.DiskName)
if err == nil {
if id != "" {
diskID = id
}
} else {
// We are waiting for provisioningState==Succeeded
// We don't want to hand-off managed disks to k8s while they are
//still being provisioned, this is to avoid some race conditions
return false, err
}
if strings.ToLower(provisionState) == "succeeded" {
return true, nil
}
return false, nil
})
if err != nil {
klog.Warningf("azureDisk - created new MD Name:%s StorageAccountType:%s Size:%v but was unable to confirm provisioningState in poll process", options.DiskName, options.StorageAccountType, options.SizeGB)
}
}
klog.V(2).Infof("azureDisk - created new MD Name:%s StorageAccountType:%s Size:%v", options.DiskName, options.StorageAccountType, options.SizeGB)
return diskID, nil
}
//DeleteManagedDisk : delete managed disk
func (c *ManagedDiskController) DeleteManagedDisk(ctx context.Context, diskURI string) error {
resourceGroup, err := getResourceGroupFromDiskURI(diskURI)
if err != nil {
return err
}
if _, ok := c.common.diskStateMap.Load(strings.ToLower(diskURI)); ok {
return fmt.Errorf("failed to delete disk(%s) since it's in attaching or detaching state", diskURI)
}
diskName := path.Base(diskURI)
disk, rerr := c.common.cloud.DisksClient.Get(ctx, resourceGroup, diskName)
if rerr != nil {
if rerr.HTTPStatusCode == http.StatusNotFound {
klog.V(2).Infof("azureDisk - disk(%s) is already deleted", diskURI)
return nil
}
// ignore GetDisk throttling
if !rerr.IsThrottled() && !strings.Contains(rerr.RawError.Error(), consts.RateLimited) {
return rerr.Error()
}
}
if disk.ManagedBy != nil {
return fmt.Errorf("disk(%s) already attached to node(%s), could not be deleted", diskURI, *disk.ManagedBy)
}
if rerr := c.common.cloud.DisksClient.Delete(ctx, resourceGroup, diskName); rerr != nil {
return rerr.Error()
}
// We don't need poll here, k8s will immediately stop referencing the disk
// the disk will be eventually deleted - cleanly - by ARM
klog.V(2).Infof("azureDisk - deleted a managed disk: %s", diskURI)
return nil
}
// GetDisk return: disk provisionState, diskID, error
func (c *ManagedDiskController) GetDisk(resourceGroup, diskName string) (string, string, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
result, rerr := c.common.cloud.DisksClient.Get(ctx, resourceGroup, diskName)
if rerr != nil {
return "", "", rerr.Error()
}
if result.DiskProperties != nil && (*result.DiskProperties).ProvisioningState != nil {
return *(*result.DiskProperties).ProvisioningState, *result.ID, nil
}
return "", "", nil
}
// ResizeDisk Expand the disk to new size
func (c *ManagedDiskController) ResizeDisk(diskURI string, oldSize resource.Quantity, newSize resource.Quantity, supportOnlineResize bool) (resource.Quantity, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
diskName := path.Base(diskURI)
resourceGroup, err := getResourceGroupFromDiskURI(diskURI)
if err != nil {
return oldSize, err
}
result, rerr := c.common.cloud.DisksClient.Get(ctx, resourceGroup, diskName)
if rerr != nil {
return oldSize, rerr.Error()
}
if result.DiskProperties == nil || result.DiskProperties.DiskSizeGB == nil {
return oldSize, fmt.Errorf("DiskProperties of disk(%s) is nil", diskName)
}
// Azure resizes in chunks of GiB (not GB)
requestGiB, err := volumehelpers.RoundUpToGiBInt32(newSize)
if err != nil {
return oldSize, err
}
newSizeQuant := resource.MustParse(fmt.Sprintf("%dGi", requestGiB))
klog.V(2).Infof("azureDisk - begin to resize disk(%s) with new size(%d), old size(%v)", diskName, requestGiB, oldSize)
// If disk already of greater or equal size than requested we return
if *result.DiskProperties.DiskSizeGB >= requestGiB {
return newSizeQuant, nil
}
if !supportOnlineResize && result.DiskProperties.DiskState != compute.Unattached {
return oldSize, fmt.Errorf("azureDisk - disk resize is only supported on Unattached disk, current disk state: %s, already attached to %s", result.DiskProperties.DiskState, to.String(result.ManagedBy))
}
diskParameter := compute.DiskUpdate{
DiskUpdateProperties: &compute.DiskUpdateProperties{
DiskSizeGB: &requestGiB,
},
}
ctx, cancel = getContextWithCancel()
defer cancel()
if rerr := c.common.cloud.DisksClient.Update(ctx, resourceGroup, diskName, diskParameter); rerr != nil {
return oldSize, rerr.Error()
}
klog.V(2).Infof("azureDisk - resize disk(%s) with new size(%d) completed", diskName, requestGiB)
return newSizeQuant, nil
}
// get resource group name from a managed disk URI, e.g. return {group-name} according to
// /subscriptions/{sub-id}/resourcegroups/{group-name}/providers/microsoft.compute/disks/{disk-id}
// according to https://docs.microsoft.com/en-us/rest/api/compute/disks/get
func getResourceGroupFromDiskURI(diskURI string) (string, error) {
fields := strings.Split(diskURI, "/")
if len(fields) != 9 || strings.ToLower(fields[3]) != "resourcegroups" {
return "", fmt.Errorf("invalid disk URI: %s", diskURI)
}
return fields[4], nil
}
// GetLabelsForVolume implements PVLabeler.GetLabelsForVolume
func (c *Cloud) GetLabelsForVolume(ctx context.Context, pv *v1.PersistentVolume) (map[string]string, error) {
// Ignore if not AzureDisk.
if pv.Spec.AzureDisk == nil {
return nil, nil
}
// Ignore any volumes that are being provisioned
if pv.Spec.AzureDisk.DiskName == cloudvolume.ProvisionedVolumeName {
return nil, nil
}
return c.GetAzureDiskLabels(pv.Spec.AzureDisk.DataDiskURI)
}
// GetAzureDiskLabels gets availability zone labels for Azuredisk.
func (c *Cloud) GetAzureDiskLabels(diskURI string) (map[string]string, error) {
// Get disk's resource group.
diskName := path.Base(diskURI)
resourceGroup, err := getResourceGroupFromDiskURI(diskURI)
if err != nil {
klog.Errorf("Failed to get resource group for AzureDisk %q: %v", diskName, err)
return nil, err
}
labels := map[string]string{
consts.LabelFailureDomainBetaRegion: c.Location,
}
// no azure credential is set, return nil
if c.DisksClient == nil {
return labels, nil
}
// Get information of the disk.
ctx, cancel := getContextWithCancel()
defer cancel()
disk, rerr := c.DisksClient.Get(ctx, resourceGroup, diskName)
if rerr != nil {
klog.Errorf("Failed to get information for AzureDisk %q: %v", diskName, rerr)
return nil, rerr.Error()
}
// Check whether availability zone is specified.
if disk.Zones == nil || len(*disk.Zones) == 0 {
klog.V(4).Infof("Azure disk %q is not zoned", diskName)
return labels, nil
}
zones := *disk.Zones
zoneID, err := strconv.Atoi(zones[0])
if err != nil {
return nil, fmt.Errorf("failed to parse zone %v for AzureDisk %v: %w", zones, diskName, err)
}
zone := c.makeZone(c.Location, zoneID)
klog.V(4).Infof("Got zone %q for Azure disk %q", zone, diskName)
labels[consts.LabelFailureDomainBetaZone] = zone
return labels, nil
}

View File

@ -0,0 +1,94 @@
/*
Copyright 2021 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 provider
import (
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
gomock "github.com/golang/mock/gomock"
v1 "k8s.io/api/core/v1"
)
// MockBackendPool is a mock of BackendPool interface
type MockBackendPool struct {
ctrl *gomock.Controller
recorder *MockBackendPoolMockRecorder
}
// MockBackendPoolMockRecorder is the mock recorder for MockBackendPool
type MockBackendPoolMockRecorder struct {
mock *MockBackendPool
}
// NewMockBackendPool creates a new mock instance
func NewMockBackendPool(ctrl *gomock.Controller) *MockBackendPool {
mock := &MockBackendPool{ctrl: ctrl}
mock.recorder = &MockBackendPoolMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockBackendPool) EXPECT() *MockBackendPoolMockRecorder {
return m.recorder
}
// EnsureHostsInPool mocks base method
func (m *MockBackendPool) EnsureHostsInPool(service *v1.Service, nodes []*v1.Node, backendPoolID, vmSetName, clusterName, lbName string, backendPool network.BackendAddressPool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EnsureHostsInPool", service, nodes, backendPoolID, vmSetName, clusterName, lbName, backendPool)
ret0, _ := ret[0].(error)
return ret0
}
// EnsureHostsInPool indicates an expected call of EnsureHostsInPool
func (mr *MockBackendPoolMockRecorder) EnsureHostsInPool(service, nodes, backendPoolID, vmSetName, clusterName, lbName, backendPool interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureHostsInPool", reflect.TypeOf((*MockBackendPool)(nil).EnsureHostsInPool), service, nodes, backendPoolID, vmSetName, clusterName, lbName, backendPool)
}
// CleanupVMSetFromBackendPoolByCondition mocks base method
func (m *MockBackendPool) CleanupVMSetFromBackendPoolByCondition(slb *network.LoadBalancer, service *v1.Service, nodes []*v1.Node, clusterName string, shouldRemoveVMSetFromSLB func(string) bool) (*network.LoadBalancer, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CleanupVMSetFromBackendPoolByCondition", slb, service, nodes, clusterName, shouldRemoveVMSetFromSLB)
ret0, _ := ret[0].(*network.LoadBalancer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CleanupVMSetFromBackendPoolByCondition indicates an expected call of CleanupVMSetFromBackendPoolByCondition
func (mr *MockBackendPoolMockRecorder) CleanupVMSetFromBackendPoolByCondition(slb, service, nodes, clusterName, shouldRemoveVMSetFromSLB interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanupVMSetFromBackendPoolByCondition", reflect.TypeOf((*MockBackendPool)(nil).CleanupVMSetFromBackendPoolByCondition), slb, service, nodes, clusterName, shouldRemoveVMSetFromSLB)
}
// ReconcileBackendPools mocks base method
func (m *MockBackendPool) ReconcileBackendPools(clusterName string, service *v1.Service, lb *network.LoadBalancer) (bool, bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReconcileBackendPools", clusterName, service, lb)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(bool)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ReconcileBackendPools indicates an expected call of ReconcileBackendPools
func (mr *MockBackendPoolMockRecorder) ReconcileBackendPools(clusterName, service, lb interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReconcileBackendPools", reflect.TypeOf((*MockBackendPool)(nil).ReconcileBackendPools), clusterName, service, lb)
}

View File

@ -0,0 +1,414 @@
/*
Copyright 2020 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 provider
import (
context "context"
reflect "reflect"
compute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
azure "github.com/Azure/go-autorest/autorest/azure"
gomock "github.com/golang/mock/gomock"
v1 "k8s.io/api/core/v1"
types "k8s.io/apimachinery/pkg/types"
cloud_provider "k8s.io/cloud-provider"
cache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
)
// MockVMSet is a mock of VMSet interface
type MockVMSet struct {
ctrl *gomock.Controller
recorder *MockVMSetMockRecorder
}
// MockVMSetMockRecorder is the mock recorder for MockVMSet
type MockVMSetMockRecorder struct {
mock *MockVMSet
}
// NewMockVMSet creates a new mock instance
func NewMockVMSet(ctrl *gomock.Controller) *MockVMSet {
mock := &MockVMSet{ctrl: ctrl}
mock.recorder = &MockVMSetMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockVMSet) EXPECT() *MockVMSetMockRecorder {
return m.recorder
}
// GetInstanceIDByNodeName mocks base method
func (m *MockVMSet) GetInstanceIDByNodeName(name string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetInstanceIDByNodeName", name)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetInstanceIDByNodeName indicates an expected call of GetInstanceIDByNodeName
func (mr *MockVMSetMockRecorder) GetInstanceIDByNodeName(name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceIDByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetInstanceIDByNodeName), name)
}
// GetInstanceTypeByNodeName mocks base method
func (m *MockVMSet) GetInstanceTypeByNodeName(name string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetInstanceTypeByNodeName", name)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetInstanceTypeByNodeName indicates an expected call of GetInstanceTypeByNodeName
func (mr *MockVMSetMockRecorder) GetInstanceTypeByNodeName(name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceTypeByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetInstanceTypeByNodeName), name)
}
// GetIPByNodeName mocks base method
func (m *MockVMSet) GetIPByNodeName(name string) (string, string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIPByNodeName", name)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(string)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// GetIPByNodeName indicates an expected call of GetIPByNodeName
func (mr *MockVMSetMockRecorder) GetIPByNodeName(name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIPByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetIPByNodeName), name)
}
// GetPrimaryInterface mocks base method
func (m *MockVMSet) GetPrimaryInterface(nodeName string) (network.Interface, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPrimaryInterface", nodeName)
ret0, _ := ret[0].(network.Interface)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPrimaryInterface indicates an expected call of GetPrimaryInterface
func (mr *MockVMSetMockRecorder) GetPrimaryInterface(nodeName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrimaryInterface", reflect.TypeOf((*MockVMSet)(nil).GetPrimaryInterface), nodeName)
}
// GetNodeNameByProviderID mocks base method
func (m *MockVMSet) GetNodeNameByProviderID(providerID string) (types.NodeName, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetNodeNameByProviderID", providerID)
ret0, _ := ret[0].(types.NodeName)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetNodeNameByProviderID indicates an expected call of GetNodeNameByProviderID
func (mr *MockVMSetMockRecorder) GetNodeNameByProviderID(providerID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeNameByProviderID", reflect.TypeOf((*MockVMSet)(nil).GetNodeNameByProviderID), providerID)
}
// GetZoneByNodeName mocks base method
func (m *MockVMSet) GetZoneByNodeName(name string) (cloud_provider.Zone, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetZoneByNodeName", name)
ret0, _ := ret[0].(cloud_provider.Zone)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetZoneByNodeName indicates an expected call of GetZoneByNodeName
func (mr *MockVMSetMockRecorder) GetZoneByNodeName(name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetZoneByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetZoneByNodeName), name)
}
// GetPrimaryVMSetName mocks base method
func (m *MockVMSet) GetPrimaryVMSetName() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPrimaryVMSetName")
ret0, _ := ret[0].(string)
return ret0
}
// GetPrimaryVMSetName indicates an expected call of GetPrimaryVMSetName
func (mr *MockVMSetMockRecorder) GetPrimaryVMSetName() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrimaryVMSetName", reflect.TypeOf((*MockVMSet)(nil).GetPrimaryVMSetName))
}
// GetVMSetNames mocks base method
func (m *MockVMSet) GetVMSetNames(service *v1.Service, nodes []*v1.Node) (*[]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetVMSetNames", service, nodes)
ret0, _ := ret[0].(*[]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetVMSetNames indicates an expected call of GetVMSetNames
func (mr *MockVMSetMockRecorder) GetVMSetNames(service, nodes interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVMSetNames", reflect.TypeOf((*MockVMSet)(nil).GetVMSetNames), service, nodes)
}
// GetNodeVMSetName mocks base method
func (m *MockVMSet) GetNodeVMSetName(node *v1.Node) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetNodeVMSetName", node)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetNodeVMSetName indicates an expected call of GetNodeVMSetName
func (mr *MockVMSetMockRecorder) GetNodeVMSetName(node interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeVMSetName", reflect.TypeOf((*MockVMSet)(nil).GetNodeVMSetName), node)
}
// EnsureHostsInPool mocks base method
func (m *MockVMSet) EnsureHostsInPool(service *v1.Service, nodes []*v1.Node, backendPoolID, vmSetName string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EnsureHostsInPool", service, nodes, backendPoolID, vmSetName)
ret0, _ := ret[0].(error)
return ret0
}
// EnsureHostsInPool indicates an expected call of EnsureHostsInPool
func (mr *MockVMSetMockRecorder) EnsureHostsInPool(service, nodes, backendPoolID, vmSetName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureHostsInPool", reflect.TypeOf((*MockVMSet)(nil).EnsureHostsInPool), service, nodes, backendPoolID, vmSetName)
}
// EnsureHostInPool mocks base method
func (m *MockVMSet) EnsureHostInPool(service *v1.Service, nodeName types.NodeName, backendPoolID, vmSetName string) (string, string, string, *compute.VirtualMachineScaleSetVM, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EnsureHostInPool", service, nodeName, backendPoolID, vmSetName)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(string)
ret2, _ := ret[2].(string)
ret3, _ := ret[3].(*compute.VirtualMachineScaleSetVM)
ret4, _ := ret[4].(error)
return ret0, ret1, ret2, ret3, ret4
}
// EnsureHostInPool indicates an expected call of EnsureHostInPool
func (mr *MockVMSetMockRecorder) EnsureHostInPool(service, nodeName, backendPoolID, vmSetName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureHostInPool", reflect.TypeOf((*MockVMSet)(nil).EnsureHostInPool), service, nodeName, backendPoolID, vmSetName)
}
// EnsureBackendPoolDeleted mocks base method
func (m *MockVMSet) EnsureBackendPoolDeleted(service *v1.Service, backendPoolID, vmSetName string, backendAddressPools *[]network.BackendAddressPool, deleteFromVMSet bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EnsureBackendPoolDeleted", service, backendPoolID, vmSetName, backendAddressPools, deleteFromVMSet)
ret0, _ := ret[0].(error)
return ret0
}
// EnsureBackendPoolDeleted indicates an expected call of EnsureBackendPoolDeleted
func (mr *MockVMSetMockRecorder) EnsureBackendPoolDeleted(service, backendPoolID, vmSetName, backendAddressPools, deleteFromVMSet interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureBackendPoolDeleted", reflect.TypeOf((*MockVMSet)(nil).EnsureBackendPoolDeleted), service, backendPoolID, vmSetName, backendAddressPools, deleteFromVMSet)
}
// EnsureBackendPoolDeletedFromVMSets mocks base method
func (m *MockVMSet) EnsureBackendPoolDeletedFromVMSets(vmSetNamesMap map[string]bool, backendPoolID string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EnsureBackendPoolDeletedFromVMSets", vmSetNamesMap, backendPoolID)
ret0, _ := ret[0].(error)
return ret0
}
// EnsureBackendPoolDeletedFromVMSets indicates an expected call of EnsureBackendPoolDeletedFromVMSets
func (mr *MockVMSetMockRecorder) EnsureBackendPoolDeletedFromVMSets(vmSetNamesMap, backendPoolID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureBackendPoolDeletedFromVMSets", reflect.TypeOf((*MockVMSet)(nil).EnsureBackendPoolDeletedFromVMSets), vmSetNamesMap, backendPoolID)
}
// AttachDisk mocks base method
func (m *MockVMSet) AttachDisk(nodeName types.NodeName, diskMap map[string]*AttachDiskOptions) (*azure.Future, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AttachDisk", nodeName, diskMap)
ret0, _ := ret[0].(*azure.Future)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AttachDisk indicates an expected call of AttachDisk
func (mr *MockVMSetMockRecorder) AttachDisk(nodeName, diskMap interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AttachDisk", reflect.TypeOf((*MockVMSet)(nil).AttachDisk), nodeName, diskMap)
}
// DetachDisk mocks base method
func (m *MockVMSet) DetachDisk(nodeName types.NodeName, diskMap map[string]string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DetachDisk", nodeName, diskMap)
ret0, _ := ret[0].(error)
return ret0
}
// DetachDisk indicates an expected call of DetachDisk
func (mr *MockVMSetMockRecorder) DetachDisk(nodeName, diskMap interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetachDisk", reflect.TypeOf((*MockVMSet)(nil).DetachDisk), nodeName, diskMap)
}
// WaitForUpdateResult mocks base method
func (m *MockVMSet) WaitForUpdateResult(ctx context.Context, future *azure.Future, resourceGroupName, source string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "WaitForUpdateResult", ctx, future, resourceGroupName, source)
ret0, _ := ret[0].(error)
return ret0
}
// WaitForUpdateResult indicates an expected call of WaitForUpdateResult
func (mr *MockVMSetMockRecorder) WaitForUpdateResult(ctx, future, resourceGroupName, source interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForUpdateResult", reflect.TypeOf((*MockVMSet)(nil).WaitForUpdateResult), ctx, future, resourceGroupName, source)
}
// GetDataDisks mocks base method
func (m *MockVMSet) GetDataDisks(nodeName types.NodeName, crt cache.AzureCacheReadType) ([]compute.DataDisk, *string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetDataDisks", nodeName, crt)
ret0, _ := ret[0].([]compute.DataDisk)
ret1, _ := ret[1].(*string)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// GetDataDisks indicates an expected call of GetDataDisks
func (mr *MockVMSetMockRecorder) GetDataDisks(nodeName, crt interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDataDisks", reflect.TypeOf((*MockVMSet)(nil).GetDataDisks), nodeName, crt)
}
// UpdateVM mocks base method
func (m *MockVMSet) UpdateVM(nodeName types.NodeName) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateVM", nodeName)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateVM indicates an expected call of UpdateVM
func (mr *MockVMSetMockRecorder) UpdateVM(nodeName interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateVM", reflect.TypeOf((*MockVMSet)(nil).UpdateVM), nodeName)
}
// GetPowerStatusByNodeName mocks base method
func (m *MockVMSet) GetPowerStatusByNodeName(name string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPowerStatusByNodeName", name)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPowerStatusByNodeName indicates an expected call of GetPowerStatusByNodeName
func (mr *MockVMSetMockRecorder) GetPowerStatusByNodeName(name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPowerStatusByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetPowerStatusByNodeName), name)
}
// GetProvisioningStateByNodeName mocks base method
func (m *MockVMSet) GetProvisioningStateByNodeName(name string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetProvisioningStateByNodeName", name)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetProvisioningStateByNodeName indicates an expected call of GetProvisioningStateByNodeName
func (mr *MockVMSetMockRecorder) GetProvisioningStateByNodeName(name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvisioningStateByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetProvisioningStateByNodeName), name)
}
// GetPrivateIPsByNodeName mocks base method
func (m *MockVMSet) GetPrivateIPsByNodeName(name string) ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPrivateIPsByNodeName", name)
ret0, _ := ret[0].([]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPrivateIPsByNodeName indicates an expected call of GetPrivateIPsByNodeName
func (mr *MockVMSetMockRecorder) GetPrivateIPsByNodeName(name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrivateIPsByNodeName", reflect.TypeOf((*MockVMSet)(nil).GetPrivateIPsByNodeName), name)
}
// GetNodeNameByIPConfigurationID mocks base method
func (m *MockVMSet) GetNodeNameByIPConfigurationID(ipConfigurationID string) (string, string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetNodeNameByIPConfigurationID", ipConfigurationID)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(string)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// GetNodeNameByIPConfigurationID indicates an expected call of GetNodeNameByIPConfigurationID
func (mr *MockVMSetMockRecorder) GetNodeNameByIPConfigurationID(ipConfigurationID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeNameByIPConfigurationID", reflect.TypeOf((*MockVMSet)(nil).GetNodeNameByIPConfigurationID), ipConfigurationID)
}
// GetNodeCIDRMasksByProviderID mocks base method
func (m *MockVMSet) GetNodeCIDRMasksByProviderID(providerID string) (int, int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetNodeCIDRMasksByProviderID", providerID)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(int)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// GetNodeCIDRMasksByProviderID indicates an expected call of GetNodeCIDRMasksByProviderID
func (mr *MockVMSetMockRecorder) GetNodeCIDRMasksByProviderID(providerID interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeCIDRMasksByProviderID", reflect.TypeOf((*MockVMSet)(nil).GetNodeCIDRMasksByProviderID), providerID)
}
// GetAgentPoolVMSetNames mocks base method
func (m *MockVMSet) GetAgentPoolVMSetNames(nodes []*v1.Node) (*[]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAgentPoolVMSetNames", nodes)
ret0, _ := ret[0].(*[]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAgentPoolVMSetNames indicates an expected call of GetAgentPoolVMSetNames
func (mr *MockVMSetMockRecorder) GetAgentPoolVMSetNames(nodes interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAgentPoolVMSetNames", reflect.TypeOf((*MockVMSet)(nil).GetAgentPoolVMSetNames), nodes)
}

View File

@ -0,0 +1,123 @@
/*
Copyright 2020 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 provider
import (
azclients "sigs.k8s.io/cloud-provider-azure/pkg/azureclients"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
const (
defaultAtachDetachDiskQPS = 6.0
defaultAtachDetachDiskBucket = 10
)
// CloudProviderRateLimitConfig indicates the rate limit config for each clients.
type CloudProviderRateLimitConfig struct {
// The default rate limit config options.
azclients.RateLimitConfig
// Rate limit config for each clients. Values would override default settings above.
RouteRateLimit *azclients.RateLimitConfig `json:"routeRateLimit,omitempty" yaml:"routeRateLimit,omitempty"`
SubnetsRateLimit *azclients.RateLimitConfig `json:"subnetsRateLimit,omitempty" yaml:"subnetsRateLimit,omitempty"`
InterfaceRateLimit *azclients.RateLimitConfig `json:"interfaceRateLimit,omitempty" yaml:"interfaceRateLimit,omitempty"`
RouteTableRateLimit *azclients.RateLimitConfig `json:"routeTableRateLimit,omitempty" yaml:"routeTableRateLimit,omitempty"`
LoadBalancerRateLimit *azclients.RateLimitConfig `json:"loadBalancerRateLimit,omitempty" yaml:"loadBalancerRateLimit,omitempty"`
PublicIPAddressRateLimit *azclients.RateLimitConfig `json:"publicIPAddressRateLimit,omitempty" yaml:"publicIPAddressRateLimit,omitempty"`
SecurityGroupRateLimit *azclients.RateLimitConfig `json:"securityGroupRateLimit,omitempty" yaml:"securityGroupRateLimit,omitempty"`
VirtualMachineRateLimit *azclients.RateLimitConfig `json:"virtualMachineRateLimit,omitempty" yaml:"virtualMachineRateLimit,omitempty"`
StorageAccountRateLimit *azclients.RateLimitConfig `json:"storageAccountRateLimit,omitempty" yaml:"storageAccountRateLimit,omitempty"`
DiskRateLimit *azclients.RateLimitConfig `json:"diskRateLimit,omitempty" yaml:"diskRateLimit,omitempty"`
SnapshotRateLimit *azclients.RateLimitConfig `json:"snapshotRateLimit,omitempty" yaml:"snapshotRateLimit,omitempty"`
VirtualMachineScaleSetRateLimit *azclients.RateLimitConfig `json:"virtualMachineScaleSetRateLimit,omitempty" yaml:"virtualMachineScaleSetRateLimit,omitempty"`
VirtualMachineSizeRateLimit *azclients.RateLimitConfig `json:"virtualMachineSizesRateLimit,omitempty" yaml:"virtualMachineSizesRateLimit,omitempty"`
AvailabilitySetRateLimit *azclients.RateLimitConfig `json:"availabilitySetRateLimit,omitempty" yaml:"availabilitySetRateLimit,omitempty"`
AttachDetachDiskRateLimit *azclients.RateLimitConfig `json:"attachDetachDiskRateLimit,omitempty" yaml:"attachDetachDiskRateLimit,omitempty"`
}
// InitializeCloudProviderRateLimitConfig initializes rate limit configs.
func InitializeCloudProviderRateLimitConfig(config *CloudProviderRateLimitConfig) {
if config == nil {
return
}
// Assign read rate limit defaults if no configuration was passed in.
if config.CloudProviderRateLimitQPS == 0 {
config.CloudProviderRateLimitQPS = consts.RateLimitQPSDefault
}
if config.CloudProviderRateLimitBucket == 0 {
config.CloudProviderRateLimitBucket = consts.RateLimitBucketDefault
}
// Assign write rate limit defaults if no configuration was passed in.
if config.CloudProviderRateLimitQPSWrite == 0 {
config.CloudProviderRateLimitQPSWrite = config.CloudProviderRateLimitQPS
}
if config.CloudProviderRateLimitBucketWrite == 0 {
config.CloudProviderRateLimitBucketWrite = config.CloudProviderRateLimitBucket
}
config.RouteRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.RouteRateLimit)
config.SubnetsRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.SubnetsRateLimit)
config.InterfaceRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.InterfaceRateLimit)
config.RouteTableRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.RouteTableRateLimit)
config.LoadBalancerRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.LoadBalancerRateLimit)
config.PublicIPAddressRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.PublicIPAddressRateLimit)
config.SecurityGroupRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.SecurityGroupRateLimit)
config.VirtualMachineRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.VirtualMachineRateLimit)
config.StorageAccountRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.StorageAccountRateLimit)
config.DiskRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.DiskRateLimit)
config.SnapshotRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.SnapshotRateLimit)
config.VirtualMachineScaleSetRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.VirtualMachineScaleSetRateLimit)
config.VirtualMachineSizeRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.VirtualMachineSizeRateLimit)
config.AvailabilitySetRateLimit = overrideDefaultRateLimitConfig(&config.RateLimitConfig, config.AvailabilitySetRateLimit)
atachDetachDiskRateLimitConfig := azclients.RateLimitConfig{
CloudProviderRateLimit: true,
CloudProviderRateLimitQPSWrite: defaultAtachDetachDiskQPS,
CloudProviderRateLimitBucketWrite: defaultAtachDetachDiskBucket,
}
config.AttachDetachDiskRateLimit = overrideDefaultRateLimitConfig(&atachDetachDiskRateLimitConfig, config.AttachDetachDiskRateLimit)
}
// overrideDefaultRateLimitConfig overrides the default CloudProviderRateLimitConfig.
func overrideDefaultRateLimitConfig(defaults, config *azclients.RateLimitConfig) *azclients.RateLimitConfig {
// If config not set, apply defaults.
if config == nil {
return defaults
}
// Remain disabled if it's set explicitly.
if !config.CloudProviderRateLimit {
return &azclients.RateLimitConfig{CloudProviderRateLimit: false}
}
// Apply default values.
if config.CloudProviderRateLimitQPS == 0 {
config.CloudProviderRateLimitQPS = defaults.CloudProviderRateLimitQPS
}
if config.CloudProviderRateLimitBucket == 0 {
config.CloudProviderRateLimitBucket = defaults.CloudProviderRateLimitBucket
}
if config.CloudProviderRateLimitQPSWrite == 0 {
config.CloudProviderRateLimitQPSWrite = defaults.CloudProviderRateLimitQPSWrite
}
if config.CloudProviderRateLimitBucketWrite == 0 {
config.CloudProviderRateLimitBucketWrite = defaults.CloudProviderRateLimitBucketWrite
}
return config
}

View File

@ -0,0 +1,578 @@
/*
Copyright 2020 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 provider
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
cloudprovider "k8s.io/cloud-provider"
"k8s.io/klog/v2"
utilnet "k8s.io/utils/net"
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
"sigs.k8s.io/cloud-provider-azure/pkg/metrics"
)
var (
// routeUpdateInterval defines the route reconciling interval.
routeUpdateInterval = 30 * time.Second
)
// routeOperation defines the allowed operations for route updating.
type routeOperation string
// copied to minimize the number of cross reference
// and exceptions in publishing and allowed imports.
const (
// Route operations.
routeOperationAdd routeOperation = "add"
routeOperationDelete routeOperation = "delete"
routeTableOperationUpdateTags routeOperation = "updateRouteTableTags"
)
// delayedRouteOperation defines a delayed route operation which is used in delayedRouteUpdater.
type delayedRouteOperation struct {
route network.Route
routeTableTags map[string]*string
operation routeOperation
result chan error
}
// wait waits for the operation completion and returns the result.
func (op *delayedRouteOperation) wait() error {
return <-op.result
}
// delayedRouteUpdater defines a delayed route updater, which batches all the
// route updating operations within "interval" period.
// Example usage:
// op, err := updater.addRouteOperation(routeOperationAdd, route)
// err = op.wait()
type delayedRouteUpdater struct {
az *Cloud
interval time.Duration
lock sync.Mutex
routesToUpdate []*delayedRouteOperation
}
// newDelayedRouteUpdater creates a new delayedRouteUpdater.
func newDelayedRouteUpdater(az *Cloud, interval time.Duration) *delayedRouteUpdater {
return &delayedRouteUpdater{
az: az,
interval: interval,
routesToUpdate: make([]*delayedRouteOperation, 0),
}
}
// run starts the updater reconciling loop.
func (d *delayedRouteUpdater) run() {
err := wait.PollImmediateInfinite(d.interval, func() (bool, error) {
d.updateRoutes()
return false, nil
})
if err != nil { // this should never happen, if it does, panic
panic(err)
}
}
// updateRoutes invokes route table client to update all routes.
func (d *delayedRouteUpdater) updateRoutes() {
d.lock.Lock()
defer d.lock.Unlock()
// No need to do any updating.
if len(d.routesToUpdate) == 0 {
klog.V(4).Info("updateRoutes: nothing to update, returning")
return
}
var err error
defer func() {
// Notify all the goroutines.
for _, rt := range d.routesToUpdate {
rt.result <- err
}
// Clear all the jobs.
d.routesToUpdate = make([]*delayedRouteOperation, 0)
}()
var (
routeTable network.RouteTable
existsRouteTable bool
)
routeTable, existsRouteTable, err = d.az.getRouteTable(azcache.CacheReadTypeDefault)
if err != nil {
klog.Errorf("getRouteTable() failed with error: %v", err)
return
}
// create route table if it doesn't exists yet.
if !existsRouteTable {
err = d.az.createRouteTable()
if err != nil {
klog.Errorf("createRouteTable() failed with error: %v", err)
return
}
routeTable, _, err = d.az.getRouteTable(azcache.CacheReadTypeDefault)
if err != nil {
klog.Errorf("getRouteTable() failed with error: %v", err)
return
}
}
// reconcile routes.
dirty, onlyUpdateTags := false, true
routes := []network.Route{}
if routeTable.RouteTablePropertiesFormat != nil && routeTable.RouteTablePropertiesFormat.Routes != nil {
routes = *routeTable.Routes
}
routes, dirty = d.cleanupOutdatedRoutes(routes)
if dirty {
onlyUpdateTags = false
}
for _, rt := range d.routesToUpdate {
if rt.operation == routeTableOperationUpdateTags {
routeTable.Tags = rt.routeTableTags
dirty = true
continue
}
routeMatch := false
onlyUpdateTags = false
for i, existingRoute := range routes {
if strings.EqualFold(to.String(existingRoute.Name), to.String(rt.route.Name)) {
// delete the name-matched routes here (missing routes would be added later if the operation is add).
routes = append(routes[:i], routes[i+1:]...)
if existingRoute.RoutePropertiesFormat != nil &&
rt.route.RoutePropertiesFormat != nil &&
strings.EqualFold(to.String(existingRoute.AddressPrefix), to.String(rt.route.AddressPrefix)) &&
strings.EqualFold(to.String(existingRoute.NextHopIPAddress), to.String(rt.route.NextHopIPAddress)) {
routeMatch = true
}
if rt.operation == routeOperationDelete {
dirty = true
}
break
}
}
// Add missing routes if the operation is add.
if rt.operation == routeOperationAdd {
routes = append(routes, rt.route)
if !routeMatch {
dirty = true
}
continue
}
}
if dirty {
if !onlyUpdateTags {
klog.V(2).Infof("updateRoutes: updating routes")
routeTable.Routes = &routes
}
err = d.az.CreateOrUpdateRouteTable(routeTable)
if err != nil {
klog.Errorf("CreateOrUpdateRouteTable() failed with error: %v", err)
return
}
// wait a while for route updates to take effect.
time.Sleep(time.Duration(d.az.Config.RouteUpdateWaitingInSeconds) * time.Second)
}
}
// cleanupOutdatedRoutes deletes all non-dualstack routes when dualstack is enabled,
// and deletes all dualstack routes when dualstack is not enabled.
func (d *delayedRouteUpdater) cleanupOutdatedRoutes(existingRoutes []network.Route) (routes []network.Route, changed bool) {
for i := len(existingRoutes) - 1; i >= 0; i-- {
existingRouteName := to.String(existingRoutes[i].Name)
split := strings.Split(existingRouteName, consts.RouteNameSeparator)
klog.V(4).Infof("cleanupOutdatedRoutes: checking route %s", existingRouteName)
// filter out unmanaged routes
deleteRoute := false
if d.az.nodeNames.Has(split[0]) {
if d.az.ipv6DualStackEnabled && len(split) == 1 {
klog.V(2).Infof("cleanupOutdatedRoutes: deleting outdated non-dualstack route %s", existingRouteName)
deleteRoute = true
} else if !d.az.ipv6DualStackEnabled && len(split) == 2 {
klog.V(2).Infof("cleanupOutdatedRoutes: deleting outdated dualstack route %s", existingRouteName)
deleteRoute = true
}
if deleteRoute {
existingRoutes = append(existingRoutes[:i], existingRoutes[i+1:]...)
changed = true
}
}
}
return existingRoutes, changed
}
// addRouteOperation adds the routeOperation to delayedRouteUpdater and returns a delayedRouteOperation.
func (d *delayedRouteUpdater) addRouteOperation(operation routeOperation, route network.Route) (*delayedRouteOperation, error) {
d.lock.Lock()
defer d.lock.Unlock()
op := &delayedRouteOperation{
route: route,
operation: operation,
result: make(chan error),
}
d.routesToUpdate = append(d.routesToUpdate, op)
return op, nil
}
// addUpdateRouteTableTagsOperation adds a update route table tags operation to delayedRouteUpdater and returns a delayedRouteOperation.
func (d *delayedRouteUpdater) addUpdateRouteTableTagsOperation(operation routeOperation, tags map[string]*string) (*delayedRouteOperation, error) {
d.lock.Lock()
defer d.lock.Unlock()
op := &delayedRouteOperation{
routeTableTags: tags,
operation: operation,
result: make(chan error),
}
d.routesToUpdate = append(d.routesToUpdate, op)
return op, nil
}
// ListRoutes lists all managed routes that belong to the specified clusterName
func (az *Cloud) ListRoutes(ctx context.Context, clusterName string) ([]*cloudprovider.Route, error) {
klog.V(10).Infof("ListRoutes: START clusterName=%q", clusterName)
routeTable, existsRouteTable, err := az.getRouteTable(azcache.CacheReadTypeDefault)
routes, err := processRoutes(az.ipv6DualStackEnabled, routeTable, existsRouteTable, err)
if err != nil {
return nil, err
}
// Compose routes for unmanaged routes so that node controller won't retry creating routes for them.
unmanagedNodes, err := az.GetUnmanagedNodes()
if err != nil {
return nil, err
}
az.routeCIDRsLock.Lock()
defer az.routeCIDRsLock.Unlock()
for _, nodeName := range unmanagedNodes.List() {
if cidr, ok := az.routeCIDRs[nodeName]; ok {
routes = append(routes, &cloudprovider.Route{
Name: nodeName,
TargetNode: MapRouteNameToNodeName(az.ipv6DualStackEnabled, nodeName),
DestinationCIDR: cidr,
})
}
}
// ensure the route table is tagged as configured
tags, changed := az.ensureRouteTableTagged(&routeTable)
if changed {
klog.V(2).Infof("ListRoutes: updating tags on route table %s", to.String(routeTable.Name))
op, err := az.routeUpdater.addUpdateRouteTableTagsOperation(routeTableOperationUpdateTags, tags)
if err != nil {
klog.Errorf("ListRoutes: failed to add route table operation with error: %v", err)
return nil, err
}
// Wait for operation complete.
err = op.wait()
if err != nil {
klog.Errorf("ListRoutes: failed to update route table tags with error: %v", err)
return nil, err
}
}
return routes, nil
}
// Injectable for testing
func processRoutes(ipv6DualStackEnabled bool, routeTable network.RouteTable, exists bool, err error) ([]*cloudprovider.Route, error) {
if err != nil {
return nil, err
}
if !exists {
return []*cloudprovider.Route{}, nil
}
var kubeRoutes []*cloudprovider.Route
if routeTable.RouteTablePropertiesFormat != nil && routeTable.Routes != nil {
kubeRoutes = make([]*cloudprovider.Route, len(*routeTable.Routes))
for i, route := range *routeTable.Routes {
instance := MapRouteNameToNodeName(ipv6DualStackEnabled, *route.Name)
cidr := *route.AddressPrefix
klog.V(10).Infof("ListRoutes: * instance=%q, cidr=%q", instance, cidr)
kubeRoutes[i] = &cloudprovider.Route{
Name: *route.Name,
TargetNode: instance,
DestinationCIDR: cidr,
}
}
}
klog.V(10).Info("ListRoutes: FINISH")
return kubeRoutes, nil
}
func (az *Cloud) createRouteTable() error {
routeTable := network.RouteTable{
Name: to.StringPtr(az.RouteTableName),
Location: to.StringPtr(az.Location),
RouteTablePropertiesFormat: &network.RouteTablePropertiesFormat{},
}
klog.V(3).Infof("createRouteTableIfNotExists: creating routetable. routeTableName=%q", az.RouteTableName)
err := az.CreateOrUpdateRouteTable(routeTable)
if err != nil {
return err
}
// Invalidate the cache right after updating
_ = az.rtCache.Delete(az.RouteTableName)
return nil
}
// CreateRoute creates the described managed route
// route.Name will be ignored, although the cloud-provider may use nameHint
// to create a more user-meaningful name.
func (az *Cloud) CreateRoute(ctx context.Context, clusterName string, nameHint string, kubeRoute *cloudprovider.Route) error {
mc := metrics.NewMetricContext("routes", "create_route", az.ResourceGroup, az.SubscriptionID, "")
isOperationSucceeded := false
defer func() {
mc.ObserveOperationWithResult(isOperationSucceeded)
}()
// Returns for unmanaged nodes because azure cloud provider couldn't fetch information for them.
var targetIP string
nodeName := string(kubeRoute.TargetNode)
unmanaged, err := az.IsNodeUnmanaged(nodeName)
if err != nil {
return err
}
if unmanaged {
if az.ipv6DualStackEnabled {
//TODO (khenidak) add support for unmanaged nodes when the feature reaches beta
return fmt.Errorf("unmanaged nodes are not supported in dual stack mode")
}
klog.V(2).Infof("CreateRoute: omitting unmanaged node %q", kubeRoute.TargetNode)
az.routeCIDRsLock.Lock()
defer az.routeCIDRsLock.Unlock()
az.routeCIDRs[nodeName] = kubeRoute.DestinationCIDR
return nil
}
CIDRv6 := utilnet.IsIPv6CIDRString(kubeRoute.DestinationCIDR)
// if single stack IPv4 then get the IP for the primary ip config
// single stack IPv6 is supported on dual stack host. So the IPv6 IP is secondary IP for both single stack IPv6 and dual stack
// Get all private IPs for the machine and find the first one that matches the IPv6 family
if !az.ipv6DualStackEnabled && !CIDRv6 {
targetIP, _, err = az.getIPForMachine(kubeRoute.TargetNode)
if err != nil {
return err
}
} else {
// for dual stack and single stack IPv6 we need to select
// a private ip that matches family of the cidr
klog.V(4).Infof("CreateRoute: create route instance=%q cidr=%q is in dual stack mode", kubeRoute.TargetNode, kubeRoute.DestinationCIDR)
nodePrivateIPs, err := az.getPrivateIPsForMachine(kubeRoute.TargetNode)
if nil != err {
klog.V(3).Infof("CreateRoute: create route: failed(GetPrivateIPsByNodeName) instance=%q cidr=%q with error=%v", kubeRoute.TargetNode, kubeRoute.DestinationCIDR, err)
return err
}
targetIP, err = findFirstIPByFamily(nodePrivateIPs, CIDRv6)
if nil != err {
klog.V(3).Infof("CreateRoute: create route: failed(findFirstIpByFamily) instance=%q cidr=%q with error=%v", kubeRoute.TargetNode, kubeRoute.DestinationCIDR, err)
return err
}
}
routeName := mapNodeNameToRouteName(az.ipv6DualStackEnabled, kubeRoute.TargetNode, kubeRoute.DestinationCIDR)
route := network.Route{
Name: to.StringPtr(routeName),
RoutePropertiesFormat: &network.RoutePropertiesFormat{
AddressPrefix: to.StringPtr(kubeRoute.DestinationCIDR),
NextHopType: network.RouteNextHopTypeVirtualAppliance,
NextHopIPAddress: to.StringPtr(targetIP),
},
}
klog.V(2).Infof("CreateRoute: creating route for clusterName=%q instance=%q cidr=%q", clusterName, kubeRoute.TargetNode, kubeRoute.DestinationCIDR)
op, err := az.routeUpdater.addRouteOperation(routeOperationAdd, route)
if err != nil {
klog.Errorf("CreateRoute failed for node %q with error: %v", kubeRoute.TargetNode, err)
return err
}
// Wait for operation complete.
err = op.wait()
if err != nil {
klog.Errorf("CreateRoute failed for node %q with error: %v", kubeRoute.TargetNode, err)
return err
}
klog.V(2).Infof("CreateRoute: route created. clusterName=%q instance=%q cidr=%q", clusterName, kubeRoute.TargetNode, kubeRoute.DestinationCIDR)
isOperationSucceeded = true
return nil
}
// DeleteRoute deletes the specified managed route
// Route should be as returned by ListRoutes
func (az *Cloud) DeleteRoute(ctx context.Context, clusterName string, kubeRoute *cloudprovider.Route) error {
mc := metrics.NewMetricContext("routes", "delete_route", az.ResourceGroup, az.SubscriptionID, "")
isOperationSucceeded := false
defer func() {
mc.ObserveOperationWithResult(isOperationSucceeded)
}()
// Returns for unmanaged nodes because azure cloud provider couldn't fetch information for them.
nodeName := string(kubeRoute.TargetNode)
unmanaged, err := az.IsNodeUnmanaged(nodeName)
if err != nil {
return err
}
if unmanaged {
klog.V(2).Infof("DeleteRoute: omitting unmanaged node %q", kubeRoute.TargetNode)
az.routeCIDRsLock.Lock()
defer az.routeCIDRsLock.Unlock()
delete(az.routeCIDRs, nodeName)
return nil
}
routeName := mapNodeNameToRouteName(az.ipv6DualStackEnabled, kubeRoute.TargetNode, kubeRoute.DestinationCIDR)
klog.V(2).Infof("DeleteRoute: deleting route. clusterName=%q instance=%q cidr=%q routeName=%q", clusterName, kubeRoute.TargetNode, kubeRoute.DestinationCIDR, routeName)
route := network.Route{
Name: to.StringPtr(routeName),
RoutePropertiesFormat: &network.RoutePropertiesFormat{},
}
op, err := az.routeUpdater.addRouteOperation(routeOperationDelete, route)
if err != nil {
klog.Errorf("DeleteRoute failed for node %q with error: %v", kubeRoute.TargetNode, err)
return err
}
// Wait for operation complete.
err = op.wait()
if err != nil {
klog.Errorf("DeleteRoute failed for node %q with error: %v", kubeRoute.TargetNode, err)
return err
}
// Remove outdated ipv4 routes as well
if az.ipv6DualStackEnabled {
routeNameWithoutIPV6Suffix := strings.Split(routeName, consts.RouteNameSeparator)[0]
klog.V(2).Infof("DeleteRoute: deleting route. clusterName=%q instance=%q cidr=%q routeName=%q", clusterName, kubeRoute.TargetNode, kubeRoute.DestinationCIDR, routeNameWithoutIPV6Suffix)
route := network.Route{
Name: to.StringPtr(routeNameWithoutIPV6Suffix),
RoutePropertiesFormat: &network.RoutePropertiesFormat{},
}
op, err := az.routeUpdater.addRouteOperation(routeOperationDelete, route)
if err != nil {
klog.Errorf("DeleteRoute failed for node %q with error: %v", kubeRoute.TargetNode, err)
return err
}
// Wait for operation complete.
err = op.wait()
if err != nil {
klog.Errorf("DeleteRoute failed for node %q with error: %v", kubeRoute.TargetNode, err)
return err
}
}
klog.V(2).Infof("DeleteRoute: route deleted. clusterName=%q instance=%q cidr=%q", clusterName, kubeRoute.TargetNode, kubeRoute.DestinationCIDR)
isOperationSucceeded = true
return nil
}
// This must be kept in sync with MapRouteNameToNodeName.
// These two functions enable stashing the instance name in the route
// and then retrieving it later when listing. This is needed because
// Azure does not let you put tags/descriptions on the Route itself.
func mapNodeNameToRouteName(ipv6DualStackEnabled bool, nodeName types.NodeName, cidr string) string {
if !ipv6DualStackEnabled {
return string(nodeName)
}
return fmt.Sprintf(consts.RouteNameFmt, nodeName, cidrtoRfc1035(cidr))
}
// MapRouteNameToNodeName is used with mapNodeNameToRouteName.
// See comment on mapNodeNameToRouteName for detailed usage.
func MapRouteNameToNodeName(ipv6DualStackEnabled bool, routeName string) types.NodeName {
if !ipv6DualStackEnabled {
return types.NodeName(routeName)
}
parts := strings.Split(routeName, consts.RouteNameSeparator)
nodeName := parts[0]
return types.NodeName(nodeName)
}
// given a list of ips, return the first one
// that matches the family requested
// error if no match, or failure to parse
// any of the ips
func findFirstIPByFamily(ips []string, v6 bool) (string, error) {
for _, ip := range ips {
bIPv6 := utilnet.IsIPv6String(ip)
if v6 == bIPv6 {
return ip, nil
}
}
return "", fmt.Errorf("no match found matching the ipfamily requested")
}
//strips : . /
func cidrtoRfc1035(cidr string) string {
cidr = strings.ReplaceAll(cidr, ":", "")
cidr = strings.ReplaceAll(cidr, ".", "")
cidr = strings.ReplaceAll(cidr, "/", "")
return cidr
}
// ensureRouteTableTagged ensures the route table is tagged as configured
func (az *Cloud) ensureRouteTableTagged(rt *network.RouteTable) (map[string]*string, bool) {
if az.Tags == "" && (az.TagsMap == nil || len(az.TagsMap) == 0) {
return nil, false
}
tags := parseTags(az.Tags, az.TagsMap)
if rt.Tags == nil {
rt.Tags = make(map[string]*string)
}
tags, changed := az.reconcileTags(rt.Tags, tags)
rt.Tags = tags
return rt.Tags, changed
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
/*
Copyright 2020 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 provider
import (
"context"
"fmt"
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-02-01/storage"
"k8s.io/klog/v2"
"sigs.k8s.io/cloud-provider-azure/pkg/azureclients/fileclient"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
// CreateFileShare creates a file share, using a matching storage account type, account kind, etc.
// storage account will be created if specified account is not found
func (az *Cloud) CreateFileShare(ctx context.Context, accountOptions *AccountOptions, shareOptions *fileclient.ShareOptions) (string, string, error) {
if accountOptions == nil {
return "", "", fmt.Errorf("account options is nil")
}
if shareOptions == nil {
return "", "", fmt.Errorf("share options is nil")
}
if accountOptions.ResourceGroup == "" {
accountOptions.ResourceGroup = az.resourceGroup
}
accountOptions.EnableHTTPSTrafficOnly = true
if shareOptions.Protocol == storage.EnabledProtocolsNFS {
accountOptions.EnableHTTPSTrafficOnly = false
}
accountName, accountKey, err := az.EnsureStorageAccount(ctx, accountOptions, consts.FileShareAccountNamePrefix)
if err != nil {
return "", "", fmt.Errorf("could not get storage key for storage account %s: %w", accountOptions.Name, err)
}
if err := az.createFileShare(accountOptions.ResourceGroup, accountName, shareOptions); err != nil {
return "", "", fmt.Errorf("failed to create share %s in account %s: %w", shareOptions.Name, accountName, err)
}
klog.V(4).Infof("created share %s in account %s", shareOptions.Name, accountOptions.Name)
return accountName, accountKey, nil
}
// DeleteFileShare deletes a file share using storage account name and key
func (az *Cloud) DeleteFileShare(resourceGroup, accountName, shareName string) error {
if err := az.deleteFileShare(resourceGroup, accountName, shareName); err != nil {
return err
}
klog.V(4).Infof("share %s deleted", shareName)
return nil
}
// ResizeFileShare resizes a file share
func (az *Cloud) ResizeFileShare(resourceGroup, accountName, name string, sizeGiB int) error {
return az.resizeFileShare(resourceGroup, accountName, name, sizeGiB)
}
// GetFileShare gets a file share
func (az *Cloud) GetFileShare(resourceGroupName, accountName, name string) (storage.FileShare, error) {
return az.getFileShare(resourceGroupName, accountName, name)
}

View File

@ -0,0 +1,504 @@
/*
Copyright 2020 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 provider
import (
"context"
"fmt"
"strings"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns"
"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-02-01/storage"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/klog/v2"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
// SkipMatchingTag skip account matching tag
const SkipMatchingTag = "skip-matching"
const LocationGlobal = "global"
const GroupIDFile = "file"
const PrivateDNSZoneName = "privatelink.file.core.windows.net"
// AccountOptions contains the fields which are used to create storage account.
type AccountOptions struct {
Name, Type, Kind, ResourceGroup, Location string
EnableHTTPSTrafficOnly bool
// indicate whether create new account when Name is empty or when account does not exists
CreateAccount bool
EnableLargeFileShare bool
CreatePrivateEndpoint bool
DisableFileServiceDeleteRetentionPolicy bool
IsHnsEnabled *bool
EnableNfsV3 *bool
AllowBlobPublicAccess *bool
Tags map[string]string
VirtualNetworkResourceIDs []string
}
type accountWithLocation struct {
Name, StorageType, Location string
}
// getStorageAccounts get matching storage accounts
func (az *Cloud) getStorageAccounts(ctx context.Context, accountOptions *AccountOptions) ([]accountWithLocation, error) {
if az.StorageAccountClient == nil {
return nil, fmt.Errorf("StorageAccountClient is nil")
}
result, rerr := az.StorageAccountClient.ListByResourceGroup(ctx, accountOptions.ResourceGroup)
if rerr != nil {
return nil, rerr.Error()
}
accounts := []accountWithLocation{}
for _, acct := range result {
if acct.Name != nil && acct.Location != nil && acct.Sku != nil {
if !(isStorageTypeEqual(acct, accountOptions) &&
isAccountKindEqual(acct, accountOptions) &&
isLocationEqual(acct, accountOptions) &&
AreVNetRulesEqual(acct, accountOptions) &&
isLargeFileSharesPropertyEqual(acct, accountOptions) &&
isTaggedWithSkip(acct) &&
isHnsPropertyEqual(acct, accountOptions) &&
isEnableNfsV3PropertyEqual(acct, accountOptions) &&
isPrivateEndpointAsExpected(acct, accountOptions)) {
continue
}
accounts = append(accounts, accountWithLocation{Name: *acct.Name, StorageType: string((*acct.Sku).Name), Location: *acct.Location})
}
}
return accounts, nil
}
// GetStorageAccesskey gets the storage account access key
func (az *Cloud) GetStorageAccesskey(ctx context.Context, account, resourceGroup string) (string, error) {
if az.StorageAccountClient == nil {
return "", fmt.Errorf("StorageAccountClient is nil")
}
result, rerr := az.StorageAccountClient.ListKeys(ctx, resourceGroup, account)
if rerr != nil {
return "", rerr.Error()
}
if result.Keys == nil {
return "", fmt.Errorf("empty keys")
}
for _, k := range *result.Keys {
if k.Value != nil && *k.Value != "" {
v := *k.Value
if ind := strings.LastIndex(v, " "); ind >= 0 {
v = v[(ind + 1):]
}
return v, nil
}
}
return "", fmt.Errorf("no valid keys")
}
// EnsureStorageAccount search storage account, create one storage account(with genAccountNamePrefix) if not found, return accountName, accountKey
func (az *Cloud) EnsureStorageAccount(ctx context.Context, accountOptions *AccountOptions, genAccountNamePrefix string) (string, string, error) {
if accountOptions == nil {
return "", "", fmt.Errorf("account options is nil")
}
accountName := accountOptions.Name
accountType := accountOptions.Type
accountKind := accountOptions.Kind
resourceGroup := accountOptions.ResourceGroup
location := accountOptions.Location
enableHTTPSTrafficOnly := accountOptions.EnableHTTPSTrafficOnly
var createNewAccount bool
if len(accountName) == 0 {
createNewAccount = true
if !accountOptions.CreateAccount {
// find a storage account that matches accountType
accounts, err := az.getStorageAccounts(ctx, accountOptions)
if err != nil {
return "", "", fmt.Errorf("could not list storage accounts for account type %s: %w", accountType, err)
}
if len(accounts) > 0 {
accountName = accounts[0].Name
createNewAccount = false
klog.V(4).Infof("found a matching account %s type %s location %s", accounts[0].Name, accounts[0].StorageType, accounts[0].Location)
}
}
if len(accountName) == 0 {
accountName = generateStorageAccountName(genAccountNamePrefix)
}
} else {
createNewAccount = false
if accountOptions.CreateAccount {
// check whether account exists
if _, err := az.GetStorageAccesskey(ctx, accountName, resourceGroup); err != nil {
klog.V(2).Infof("get storage key for storage account %s returned with %v", accountName, err)
createNewAccount = true
}
}
}
vnetResourceGroup := az.ResourceGroup
if len(az.VnetResourceGroup) > 0 {
vnetResourceGroup = az.VnetResourceGroup
}
if accountOptions.CreatePrivateEndpoint {
// Create DNS zone first, this could make sure driver has write permission on vnetResourceGroup
if err := az.createPrivateDNSZone(ctx, vnetResourceGroup); err != nil {
return "", "", fmt.Errorf("Failed to create private DNS zone(%s) in resourceGroup(%s), error: %v", PrivateDNSZoneName, vnetResourceGroup, err)
}
}
if createNewAccount {
// set network rules for storage account
var networkRuleSet *storage.NetworkRuleSet
virtualNetworkRules := []storage.VirtualNetworkRule{}
for i, subnetID := range accountOptions.VirtualNetworkResourceIDs {
vnetRule := storage.VirtualNetworkRule{
VirtualNetworkResourceID: &accountOptions.VirtualNetworkResourceIDs[i],
Action: storage.ActionAllow,
}
virtualNetworkRules = append(virtualNetworkRules, vnetRule)
klog.V(4).Infof("subnetID(%s) has been set", subnetID)
}
if len(virtualNetworkRules) > 0 {
networkRuleSet = &storage.NetworkRuleSet{
VirtualNetworkRules: &virtualNetworkRules,
DefaultAction: storage.DefaultActionDeny,
}
}
if accountOptions.CreatePrivateEndpoint {
networkRuleSet = &storage.NetworkRuleSet{
DefaultAction: storage.DefaultActionDeny,
}
}
if location == "" {
location = az.Location
}
if accountType == "" {
accountType = consts.DefaultStorageAccountType
}
// use StorageV2 by default per https://docs.microsoft.com/en-us/azure/storage/common/storage-account-options
kind := consts.DefaultStorageAccountKind
if accountKind != "" {
kind = storage.Kind(accountKind)
}
if len(accountOptions.Tags) == 0 {
accountOptions.Tags = make(map[string]string)
}
accountOptions.Tags[consts.CreatedByTag] = "azure"
tags := convertMapToMapPointer(accountOptions.Tags)
klog.V(2).Infof("azure - no matching account found, begin to create a new account %s in resource group %s, location: %s, accountType: %s, accountKind: %s, tags: %+v",
accountName, resourceGroup, location, accountType, kind, accountOptions.Tags)
cp := storage.AccountCreateParameters{
Sku: &storage.Sku{Name: storage.SkuName(accountType)},
Kind: kind,
AccountPropertiesCreateParameters: &storage.AccountPropertiesCreateParameters{
EnableHTTPSTrafficOnly: &enableHTTPSTrafficOnly,
NetworkRuleSet: networkRuleSet,
IsHnsEnabled: accountOptions.IsHnsEnabled,
EnableNfsV3: accountOptions.EnableNfsV3,
MinimumTLSVersion: storage.MinimumTLSVersionTLS12,
},
Tags: tags,
Location: &location}
if accountOptions.EnableLargeFileShare {
klog.V(2).Infof("Enabling LargeFileShare for storage account(%s)", accountName)
cp.AccountPropertiesCreateParameters.LargeFileSharesState = storage.LargeFileSharesStateEnabled
}
if accountOptions.AllowBlobPublicAccess != nil {
klog.V(2).Infof("set AllowBlobPublicAccess(%v) for storage account(%s)", *accountOptions.AllowBlobPublicAccess, accountName)
cp.AccountPropertiesCreateParameters.AllowBlobPublicAccess = accountOptions.AllowBlobPublicAccess
}
if az.StorageAccountClient == nil {
return "", "", fmt.Errorf("StorageAccountClient is nil")
}
if rerr := az.StorageAccountClient.Create(ctx, resourceGroup, accountName, cp); rerr != nil {
return "", "", fmt.Errorf("failed to create storage account %s, error: %v", accountName, rerr)
}
if accountOptions.DisableFileServiceDeleteRetentionPolicy {
klog.V(2).Infof("disable DisableFileServiceDeleteRetentionPolicy on account(%s), resource group(%s)", accountName, resourceGroup)
prop, err := az.FileClient.GetServiceProperties(resourceGroup, accountName)
if err != nil {
return "", "", err
}
if prop.FileServicePropertiesProperties == nil {
return "", "", fmt.Errorf("FileServicePropertiesProperties of account(%s), resource group(%s) is nil", accountName, resourceGroup)
}
prop.FileServicePropertiesProperties.ShareDeleteRetentionPolicy = &storage.DeleteRetentionPolicy{Enabled: to.BoolPtr(false)}
if _, err := az.FileClient.SetServiceProperties(resourceGroup, accountName, prop); err != nil {
return "", "", err
}
}
if accountOptions.CreatePrivateEndpoint {
// Get properties of the storageAccount
storageAccount, err := az.StorageAccountClient.GetProperties(ctx, resourceGroup, accountName)
if err != nil {
return "", "", fmt.Errorf("Failed to get the properties of storage account(%s), resourceGroup(%s), error: %v", accountName, resourceGroup, err)
}
// Create private endpoint
privateEndpointName := accountName + "-pvtendpoint"
if err := az.createPrivateEndpoint(ctx, accountName, storageAccount.ID, privateEndpointName, vnetResourceGroup); err != nil {
return "", "", fmt.Errorf("Failed to create private endpoint for storage account(%s), resourceGroup(%s), error: %v", accountName, vnetResourceGroup, err)
}
// Create virtual link to the zone private DNS zone
vNetLinkName := accountName + "-vnetlink"
if err := az.createVNetLink(ctx, vNetLinkName, vnetResourceGroup); err != nil {
return "", "", fmt.Errorf("Failed to create virtual link for vnet(%s) and DNS Zone(%s) in resourceGroup(%s), error: %v", az.VnetName, PrivateDNSZoneName, vnetResourceGroup, err)
}
// Create dns zone group
dnsZoneGroupName := accountName + "-dnszonegroup"
if err := az.createPrivateDNSZoneGroup(ctx, dnsZoneGroupName, privateEndpointName, vnetResourceGroup); err != nil {
return "", "", fmt.Errorf("Failed to create private DNS zone group - privateEndpoint(%s), vNetName(%s), resourceGroup(%s), error: %v", privateEndpointName, az.VnetName, vnetResourceGroup, err)
}
}
}
// find the access key with this account
accountKey, err := az.GetStorageAccesskey(ctx, accountName, resourceGroup)
if err != nil {
return "", "", fmt.Errorf("could not get storage key for storage account %s: %w", accountName, err)
}
return accountName, accountKey, nil
}
func (az *Cloud) createPrivateEndpoint(ctx context.Context, accountName string, accountID *string, privateEndpointName, vnetResourceGroup string) error {
klog.V(2).Infof("Creating private endpoint(%s) for account (%s)", privateEndpointName, accountName)
subnet, _, err := az.getSubnet(az.VnetName, az.SubnetName)
if err != nil {
return err
}
// Disable the private endpoint network policies before creating private endpoint
subnet.SubnetPropertiesFormat.PrivateEndpointNetworkPolicies = network.VirtualNetworkPrivateEndpointNetworkPoliciesDisabled
if rerr := az.SubnetsClient.CreateOrUpdate(ctx, vnetResourceGroup, az.VnetName, az.SubnetName, subnet); rerr != nil {
return rerr.Error()
}
//Create private endpoint
privateLinkServiceConnectionName := accountName + "-pvtsvcconn"
privateLinkServiceConnection := network.PrivateLinkServiceConnection{
Name: &privateLinkServiceConnectionName,
PrivateLinkServiceConnectionProperties: &network.PrivateLinkServiceConnectionProperties{
GroupIds: &[]string{GroupIDFile},
PrivateLinkServiceID: accountID,
},
}
privateLinkServiceConnections := []network.PrivateLinkServiceConnection{privateLinkServiceConnection}
privateEndpoint := network.PrivateEndpoint{
Location: &az.Location,
PrivateEndpointProperties: &network.PrivateEndpointProperties{Subnet: &subnet, PrivateLinkServiceConnections: &privateLinkServiceConnections},
}
return az.privateendpointclient.CreateOrUpdate(ctx, vnetResourceGroup, privateEndpointName, privateEndpoint, true)
}
func (az *Cloud) createPrivateDNSZone(ctx context.Context, vnetResourceGroup string) error {
klog.V(2).Infof("Creating private dns zone(%s) in resourceGroup (%s)", PrivateDNSZoneName, vnetResourceGroup)
location := LocationGlobal
privateDNSZone := privatedns.PrivateZone{Location: &location}
if err := az.privatednsclient.CreateOrUpdate(ctx, vnetResourceGroup, PrivateDNSZoneName, privateDNSZone, true); err != nil {
if strings.Contains(err.Error(), "exists already") {
klog.V(2).Infof("private dns zone(%s) in resourceGroup (%s) already exists", PrivateDNSZoneName, vnetResourceGroup)
return nil
}
return err
}
return nil
}
func (az *Cloud) createVNetLink(ctx context.Context, vNetLinkName, vnetResourceGroup string) error {
klog.V(2).Infof("Creating virtual link for vnet(%s) and DNS Zone(%s) in resourceGroup(%s)", vNetLinkName, PrivateDNSZoneName, vnetResourceGroup)
location := LocationGlobal
vnetID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/virtualNetworks/%s", az.SubscriptionID, vnetResourceGroup, az.VnetName)
parameters := privatedns.VirtualNetworkLink{
Location: &location,
VirtualNetworkLinkProperties: &privatedns.VirtualNetworkLinkProperties{
VirtualNetwork: &privatedns.SubResource{ID: &vnetID},
RegistrationEnabled: to.BoolPtr(true)},
}
return az.virtualNetworkLinksClient.CreateOrUpdate(ctx, vnetResourceGroup, PrivateDNSZoneName, vNetLinkName, parameters, false)
}
func (az *Cloud) createPrivateDNSZoneGroup(ctx context.Context, dnsZoneGroupName, privateEndpointName, vnetResourceGroup string) error {
klog.V(2).Infof("Creating private DNS zone group(%s) with privateEndpoint(%s), vNetName(%s), resourceGroup(%s)", dnsZoneGroupName, privateEndpointName, az.VnetName, vnetResourceGroup)
privateDNSZoneID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/privateDnsZones/%s", az.SubscriptionID, vnetResourceGroup, PrivateDNSZoneName)
dnsZoneName := PrivateDNSZoneName
privateDNSZoneConfig := network.PrivateDNSZoneConfig{
Name: &dnsZoneName,
PrivateDNSZonePropertiesFormat: &network.PrivateDNSZonePropertiesFormat{
PrivateDNSZoneID: &privateDNSZoneID},
}
privateDNSZoneConfigs := []network.PrivateDNSZoneConfig{privateDNSZoneConfig}
privateDNSZoneGroup := network.PrivateDNSZoneGroup{
PrivateDNSZoneGroupPropertiesFormat: &network.PrivateDNSZoneGroupPropertiesFormat{
PrivateDNSZoneConfigs: &privateDNSZoneConfigs,
},
}
return az.privatednszonegroupclient.CreateOrUpdate(ctx, vnetResourceGroup, privateEndpointName, dnsZoneGroupName, privateDNSZoneGroup, false)
}
// AddStorageAccountTags add tags to storage account
func (az *Cloud) AddStorageAccountTags(resourceGroup, account string, tags map[string]*string) *retry.Error {
if az.StorageAccountClient == nil {
return retry.NewError(false, fmt.Errorf("StorageAccountClient is nil"))
}
ctx, cancel := getContextWithCancel()
defer cancel()
result, rerr := az.StorageAccountClient.GetProperties(ctx, resourceGroup, account)
if rerr != nil {
return rerr
}
newTags := result.Tags
if newTags == nil {
newTags = make(map[string]*string)
}
// merge two tag map
for k, v := range tags {
newTags[k] = v
}
updateParams := storage.AccountUpdateParameters{Tags: newTags}
return az.StorageAccountClient.Update(ctx, resourceGroup, account, updateParams)
}
// RemoveStorageAccountTag remove tag from storage account
func (az *Cloud) RemoveStorageAccountTag(resourceGroup, account, key string) *retry.Error {
if az.StorageAccountClient == nil {
return retry.NewError(false, fmt.Errorf("StorageAccountClient is nil"))
}
ctx, cancel := getContextWithCancel()
defer cancel()
result, rerr := az.StorageAccountClient.GetProperties(ctx, resourceGroup, account)
if rerr != nil {
return rerr
}
if len(result.Tags) == 0 {
return nil
}
originalLen := len(result.Tags)
delete(result.Tags, key)
if originalLen != len(result.Tags) {
updateParams := storage.AccountUpdateParameters{Tags: result.Tags}
return az.StorageAccountClient.Update(ctx, resourceGroup, account, updateParams)
}
return nil
}
func isStorageTypeEqual(account storage.Account, accountOptions *AccountOptions) bool {
if accountOptions.Type != "" && !strings.EqualFold(accountOptions.Type, string((*account.Sku).Name)) {
return false
}
return true
}
func isAccountKindEqual(account storage.Account, accountOptions *AccountOptions) bool {
if accountOptions.Kind != "" && !strings.EqualFold(accountOptions.Kind, string(account.Kind)) {
return false
}
return true
}
func isLocationEqual(account storage.Account, accountOptions *AccountOptions) bool {
if accountOptions.Location != "" && !strings.EqualFold(accountOptions.Location, *account.Location) {
return false
}
return true
}
func AreVNetRulesEqual(account storage.Account, accountOptions *AccountOptions) bool {
if len(accountOptions.VirtualNetworkResourceIDs) > 0 {
if account.AccountProperties == nil || account.AccountProperties.NetworkRuleSet == nil ||
account.AccountProperties.NetworkRuleSet.VirtualNetworkRules == nil {
return false
}
found := false
for _, subnetID := range accountOptions.VirtualNetworkResourceIDs {
for _, rule := range *account.AccountProperties.NetworkRuleSet.VirtualNetworkRules {
if strings.EqualFold(to.String(rule.VirtualNetworkResourceID), subnetID) && rule.Action == storage.ActionAllow {
found = true
break
}
}
}
if !found {
return false
}
}
return true
}
func isLargeFileSharesPropertyEqual(account storage.Account, accountOptions *AccountOptions) bool {
if account.Sku.Tier != storage.SkuTier(compute.PremiumLRS) && accountOptions.EnableLargeFileShare && (len(account.LargeFileSharesState) == 0 || account.LargeFileSharesState == storage.LargeFileSharesStateDisabled) {
return false
}
return true
}
func isTaggedWithSkip(account storage.Account) bool {
if account.Tags != nil {
// skip account with SkipMatchingTag tag
if _, ok := account.Tags[SkipMatchingTag]; ok {
klog.V(2).Infof("found %s tag for account %s, skip matching", SkipMatchingTag, *account.Name)
return false
}
}
return true
}
func isHnsPropertyEqual(account storage.Account, accountOptions *AccountOptions) bool {
return to.Bool(account.IsHnsEnabled) == to.Bool(accountOptions.IsHnsEnabled)
}
func isEnableNfsV3PropertyEqual(account storage.Account, accountOptions *AccountOptions) bool {
return to.Bool(account.EnableNfsV3) == to.Bool(accountOptions.EnableNfsV3)
}
func isPrivateEndpointAsExpected(account storage.Account, accountOptions *AccountOptions) bool {
if accountOptions.CreatePrivateEndpoint && account.PrivateEndpointConnections != nil && len(*account.PrivateEndpointConnections) > 0 {
return true
}
if !accountOptions.CreatePrivateEndpoint && (account.PrivateEndpointConnections == nil || len(*account.PrivateEndpointConnections) == 0) {
return true
}
return false
}

View File

@ -0,0 +1,262 @@
/*
Copyright 2020 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 provider
import (
"context"
"fmt"
"net"
"strings"
"sync"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/go-autorest/autorest/to"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog/v2"
utilnet "k8s.io/utils/net"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
var strToExtendedLocationType = map[string]network.ExtendedLocationTypes{
"edgezone": network.ExtendedLocationTypesEdgeZone,
}
// lockMap used to lock on entries
type lockMap struct {
sync.Mutex
mutexMap map[string]*sync.Mutex
}
// NewLockMap returns a new lock map
func newLockMap() *lockMap {
return &lockMap{
mutexMap: make(map[string]*sync.Mutex),
}
}
// LockEntry acquires a lock associated with the specific entry
func (lm *lockMap) LockEntry(entry string) {
lm.Lock()
// check if entry does not exists, then add entry
if _, exists := lm.mutexMap[entry]; !exists {
lm.addEntry(entry)
}
lm.Unlock()
lm.lockEntry(entry)
}
// UnlockEntry release the lock associated with the specific entry
func (lm *lockMap) UnlockEntry(entry string) {
lm.Lock()
defer lm.Unlock()
if _, exists := lm.mutexMap[entry]; !exists {
return
}
lm.unlockEntry(entry)
}
func (lm *lockMap) addEntry(entry string) {
lm.mutexMap[entry] = &sync.Mutex{}
}
func (lm *lockMap) lockEntry(entry string) {
lm.mutexMap[entry].Lock()
}
func (lm *lockMap) unlockEntry(entry string) {
lm.mutexMap[entry].Unlock()
}
func getContextWithCancel() (context.Context, context.CancelFunc) {
return context.WithCancel(context.Background())
}
func convertMapToMapPointer(origin map[string]string) map[string]*string {
newly := make(map[string]*string)
for k, v := range origin {
value := v
newly[k] = &value
}
return newly
}
func parseTags(tags string, tagsMap map[string]string) map[string]*string {
formatted := make(map[string]*string)
if tags != "" {
kvs := strings.Split(tags, consts.TagsDelimiter)
for _, kv := range kvs {
res := strings.Split(kv, consts.TagKeyValueDelimiter)
if len(res) != 2 {
klog.Warningf("parseTags: error when parsing key-value pair %s, would ignore this one", kv)
continue
}
k, v := strings.TrimSpace(res[0]), strings.TrimSpace(res[1])
if k == "" {
klog.Warning("parseTags: empty key, ignoring this key-value pair")
continue
}
formatted[k] = to.StringPtr(v)
}
}
if len(tagsMap) > 0 {
for key, value := range tagsMap {
key, value := strings.TrimSpace(key), strings.TrimSpace(value)
if key == "" {
klog.Warningf("parseTags: empty key, ignoring this key-value pair")
continue
}
if found, k := findKeyInMapCaseInsensitive(formatted, key); found && k != key {
klog.V(4).Infof("parseTags: found identical keys: %s from tags and %s from tagsMap (case-insensitive), %s will replace %s", k, key, key, k)
delete(formatted, k)
}
formatted[key] = to.StringPtr(value)
}
}
return formatted
}
func findKeyInMapCaseInsensitive(targetMap map[string]*string, key string) (bool, string) {
for k := range targetMap {
if strings.EqualFold(k, key) {
return true, k
}
}
return false, ""
}
func (az *Cloud) reconcileTags(currentTagsOnResource, newTags map[string]*string) (reconciledTags map[string]*string, changed bool) {
var systemTags []string
systemTagsMap := make(map[string]*string)
if az.SystemTags != "" {
systemTags = strings.Split(az.SystemTags, consts.TagsDelimiter)
for i := 0; i < len(systemTags); i++ {
systemTags[i] = strings.TrimSpace(systemTags[i])
}
for _, systemTag := range systemTags {
systemTagsMap[systemTag] = to.StringPtr("")
}
}
// if the systemTags is not set, just add/update new currentTagsOnResource and not delete old currentTagsOnResource
for k, v := range newTags {
found, key := findKeyInMapCaseInsensitive(currentTagsOnResource, k)
if !found {
currentTagsOnResource[k] = v
changed = true
} else if !strings.EqualFold(to.String(v), to.String(currentTagsOnResource[key])) {
currentTagsOnResource[key] = v
changed = true
}
}
// if the systemTags is set, delete the old currentTagsOnResource
if len(systemTagsMap) > 0 {
for k := range currentTagsOnResource {
if _, ok := newTags[k]; !ok {
if found, _ := findKeyInMapCaseInsensitive(systemTagsMap, k); !found {
delete(currentTagsOnResource, k)
changed = true
}
}
}
}
return currentTagsOnResource, changed
}
func (az *Cloud) getVMSetNamesSharingPrimarySLB() sets.String {
vmSetNames := make([]string, 0)
if az.NodePoolsWithoutDedicatedSLB != "" {
vmSetNames = strings.Split(az.Config.NodePoolsWithoutDedicatedSLB, consts.VMSetNamesSharingPrimarySLBDelimiter)
for i := 0; i < len(vmSetNames); i++ {
vmSetNames[i] = strings.ToLower(strings.TrimSpace(vmSetNames[i]))
}
}
return sets.NewString(vmSetNames...)
}
func getExtendedLocationTypeFromString(extendedLocationType string) network.ExtendedLocationTypes {
extendedLocationType = strings.ToLower(extendedLocationType)
if val, ok := strToExtendedLocationType[extendedLocationType]; ok {
return val
}
return network.ExtendedLocationTypesEdgeZone
}
func getServiceAdditionalPublicIPs(service *v1.Service) ([]string, error) {
if service == nil {
return nil, nil
}
result := []string{}
if val, ok := service.Annotations[consts.ServiceAnnotationAdditionalPublicIPs]; ok {
pips := strings.Split(strings.TrimSpace(val), ",")
for _, pip := range pips {
ip := strings.TrimSpace(pip)
if ip == "" {
continue // skip empty string
}
if net.ParseIP(ip) == nil {
return nil, fmt.Errorf("%s is not a valid IP address", ip)
}
result = append(result, ip)
}
}
return result, nil
}
func getNodePrivateIPAddress(service *v1.Service, node *v1.Node) string {
isIPV6SVC := utilnet.IsIPv6String(service.Spec.ClusterIP)
for _, nodeAddress := range node.Status.Addresses {
if strings.EqualFold(string(nodeAddress.Type), string(v1.NodeInternalIP)) &&
utilnet.IsIPv6String(nodeAddress.Address) == isIPV6SVC {
klog.V(4).Infof("getNodePrivateIPAddress: node %s, ip %s", node.Name, nodeAddress.Address)
return nodeAddress.Address
}
}
klog.Warningf("getNodePrivateIPAddress: empty ip found for node %s", node.Name)
return ""
}
func getNodePrivateIPAddresses(node *v1.Node) []string {
addresses := make([]string, 0)
for _, nodeAddress := range node.Status.Addresses {
if strings.EqualFold(string(nodeAddress.Type), string(v1.NodeInternalIP)) {
addresses = append(addresses, nodeAddress.Address)
}
}
return addresses
}

View File

@ -0,0 +1,108 @@
/*
Copyright 2020 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 provider
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/go-autorest/autorest/azure"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
cloudprovider "k8s.io/cloud-provider"
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
)
//go:generate sh -c "mockgen -destination=$GOPATH/src/sigs.k8s.io/cloud-provider-azure/pkg/provider/azure_mock_vmsets.go -source=$GOPATH/src/sigs.k8s.io/cloud-provider-azure/pkg/provider/azure_vmsets.go -package=provider VMSet"
// VMSet defines functions all vmsets (including scale set and availability
// set) should be implemented.
// Don't forget to run the following command to generate the mock client:
// mockgen -destination=$GOPATH/src/sigs.k8s.io/cloud-provider-azure/pkg/provider/azure_mock_vmsets.go -source=$GOPATH/src/sigs.k8s.io/cloud-provider-azure/pkg/provider/azure_vmsets.go -package=provider VMSet
type VMSet interface {
// GetInstanceIDByNodeName gets the cloud provider ID by node name.
// It must return ("", cloudprovider.InstanceNotFound) if the instance does
// not exist or is no longer running.
GetInstanceIDByNodeName(name string) (string, error)
// GetInstanceTypeByNodeName gets the instance type by node name.
GetInstanceTypeByNodeName(name string) (string, error)
// GetIPByNodeName gets machine private IP and public IP by node name.
GetIPByNodeName(name string) (string, string, error)
// GetPrimaryInterface gets machine primary network interface by node name.
GetPrimaryInterface(nodeName string) (network.Interface, error)
// GetNodeNameByProviderID gets the node name by provider ID.
GetNodeNameByProviderID(providerID string) (types.NodeName, error)
// GetZoneByNodeName gets cloudprovider.Zone by node name.
GetZoneByNodeName(name string) (cloudprovider.Zone, error)
// GetPrimaryVMSetName returns the VM set name depending on the configured vmType.
// It returns config.PrimaryScaleSetName for vmss and config.PrimaryAvailabilitySetName for standard vmType.
GetPrimaryVMSetName() string
// GetVMSetNames selects all possible availability sets or scale sets
// (depending vmType configured) for service load balancer, if the service has
// no loadbalancer mode annotation returns the primary VMSet. If service annotation
// for loadbalancer exists then return the eligible VMSet.
GetVMSetNames(service *v1.Service, nodes []*v1.Node) (availabilitySetNames *[]string, err error)
// GetNodeVMSetName returns the availability set or vmss name by the node name.
// It will return empty string when using standalone vms.
GetNodeVMSetName(node *v1.Node) (string, error)
// EnsureHostsInPool ensures the given Node's primary IP configurations are
// participating in the specified LoadBalancer Backend Pool.
EnsureHostsInPool(service *v1.Service, nodes []*v1.Node, backendPoolID string, vmSetName string) error
// EnsureHostInPool ensures the given VM's Primary NIC's Primary IP Configuration is
// participating in the specified LoadBalancer Backend Pool.
EnsureHostInPool(service *v1.Service, nodeName types.NodeName, backendPoolID string, vmSetName string) (string, string, string, *compute.VirtualMachineScaleSetVM, error)
// EnsureBackendPoolDeleted ensures the loadBalancer backendAddressPools deleted from the specified nodes.
EnsureBackendPoolDeleted(service *v1.Service, backendPoolID, vmSetName string, backendAddressPools *[]network.BackendAddressPool, deleteFromVMSet bool) error
//EnsureBackendPoolDeletedFromVMSets ensures the loadBalancer backendAddressPools deleted from the specified VMSS/VMAS
EnsureBackendPoolDeletedFromVMSets(vmSetNamesMap map[string]bool, backendPoolID string) error
// AttachDisk attaches a disk to vm
AttachDisk(nodeName types.NodeName, diskMap map[string]*AttachDiskOptions) (*azure.Future, error)
// DetachDisk detaches a disk from vm
DetachDisk(nodeName types.NodeName, diskMap map[string]string) error
// WaitForUpdateResult waits for the response of the update request
WaitForUpdateResult(ctx context.Context, future *azure.Future, resourceGroupName, source string) error
// GetDataDisks gets a list of data disks attached to the node.
GetDataDisks(nodeName types.NodeName, crt azcache.AzureCacheReadType) ([]compute.DataDisk, *string, error)
// UpdateVM updates a vm
UpdateVM(nodeName types.NodeName) error
// GetPowerStatusByNodeName returns the powerState for the specified node.
GetPowerStatusByNodeName(name string) (string, error)
// GetProvisioningStateByNodeName returns the provisioningState for the specified node.
GetProvisioningStateByNodeName(name string) (string, error)
// GetPrivateIPsByNodeName returns a slice of all private ips assigned to node (ipv6 and ipv4)
GetPrivateIPsByNodeName(name string) ([]string, error)
// GetNodeNameByIPConfigurationID gets the nodeName and vmSetName by IP configuration ID.
GetNodeNameByIPConfigurationID(ipConfigurationID string) (string, string, error)
// GetNodeCIDRMasksByProviderID returns the node CIDR subnet mask by provider ID.
GetNodeCIDRMasksByProviderID(providerID string) (int, int, error)
// GetAgentPoolVMSetNames returns all vmSet names according to the nodes
GetAgentPoolVMSetNames(nodes []*v1.Node) (*[]string, error)
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,341 @@
/*
Copyright 2020 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 provider
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog/v2"
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
)
type vmssVirtualMachinesEntry struct {
resourceGroup string
vmssName string
instanceID string
virtualMachine *compute.VirtualMachineScaleSetVM
lastUpdate time.Time
}
type vmssEntry struct {
vmss *compute.VirtualMachineScaleSet
resourceGroup string
lastUpdate time.Time
}
type availabilitySetNodeEntry struct {
vmNames sets.String
nodeNames sets.String
vms []compute.VirtualMachine
}
func (ss *ScaleSet) newVMSSCache() (*azcache.TimedCache, error) {
getter := func(key string) (interface{}, error) {
localCache := &sync.Map{} // [vmasName]*vmssEntry
allResourceGroups, err := ss.GetResourceGroups()
if err != nil {
return nil, err
}
for _, resourceGroup := range allResourceGroups.List() {
allScaleSets, rerr := ss.VirtualMachineScaleSetsClient.List(context.Background(), resourceGroup)
if rerr != nil {
klog.Errorf("VirtualMachineScaleSetsClient.List failed: %v", rerr)
return nil, rerr.Error()
}
for i := range allScaleSets {
scaleSet := allScaleSets[i]
if scaleSet.Name == nil || *scaleSet.Name == "" {
klog.Warning("failed to get the name of VMSS")
continue
}
localCache.Store(*scaleSet.Name, &vmssEntry{
vmss: &scaleSet,
resourceGroup: resourceGroup,
lastUpdate: time.Now().UTC(),
})
}
}
return localCache, nil
}
if ss.Config.VmssCacheTTLInSeconds == 0 {
ss.Config.VmssCacheTTLInSeconds = consts.VMSSCacheTTLDefaultInSeconds
}
return azcache.NewTimedcache(time.Duration(ss.Config.VmssCacheTTLInSeconds)*time.Second, getter)
}
func extractVmssVMName(name string) (string, string, error) {
split := strings.SplitAfter(name, consts.VMSSNameSeparator)
if len(split) < 2 {
klog.V(3).Infof("Failed to extract vmssVMName %q", name)
return "", "", ErrorNotVmssInstance
}
ssName := strings.Join(split[0:len(split)-1], "")
// removing the trailing `vmssNameSeparator` since we used SplitAfter
ssName = ssName[:len(ssName)-1]
instanceID := split[len(split)-1]
return ssName, instanceID, nil
}
// getVMSSVMCache returns an *azcache.TimedCache and cache key for a VMSS (creating that cache if new).
func (ss *ScaleSet) getVMSSVMCache(resourceGroup, vmssName string) (string, *azcache.TimedCache, error) {
cacheKey := strings.ToLower(fmt.Sprintf("%s/%s", resourceGroup, vmssName))
if entry, ok := ss.vmssVMCache.Load(cacheKey); ok {
cache := entry.(*azcache.TimedCache)
return cacheKey, cache, nil
}
cache, err := ss.newVMSSVirtualMachinesCache(resourceGroup, vmssName, cacheKey)
if err != nil {
return "", nil, err
}
ss.vmssVMCache.Store(cacheKey, cache)
return cacheKey, cache, nil
}
// gcVMSSVMCache delete stale VMSS VMs caches from deleted VMSSes.
func (ss *ScaleSet) gcVMSSVMCache() error {
cached, err := ss.vmssCache.Get(consts.VMSSKey, azcache.CacheReadTypeUnsafe)
if err != nil {
return err
}
vmsses := cached.(*sync.Map)
removed := map[string]bool{}
ss.vmssVMCache.Range(func(key, value interface{}) bool {
cacheKey := key.(string)
vlistIdx := cacheKey[strings.LastIndex(cacheKey, "/")+1:]
if _, ok := vmsses.Load(vlistIdx); !ok {
removed[cacheKey] = true
}
return true
})
for key := range removed {
ss.vmssVMCache.Delete(key)
}
return nil
}
// newVMSSVirtualMachinesCache instantiates a new VMs cache for VMs belonging to the provided VMSS.
func (ss *ScaleSet) newVMSSVirtualMachinesCache(resourceGroupName, vmssName, cacheKey string) (*azcache.TimedCache, error) {
vmssVirtualMachinesCacheTTL := time.Duration(ss.Config.VmssVirtualMachinesCacheTTLInSeconds) * time.Second
getter := func(key string) (interface{}, error) {
localCache := &sync.Map{} // [nodeName]*vmssVirtualMachinesEntry
oldCache := make(map[string]vmssVirtualMachinesEntry)
if vmssCache, ok := ss.vmssVMCache.Load(cacheKey); ok {
// get old cache before refreshing the cache
cache := vmssCache.(*azcache.TimedCache)
entry, exists, err := cache.Store.GetByKey(cacheKey)
if err != nil {
return nil, err
}
if exists {
cached := entry.(*azcache.AzureCacheEntry).Data
if cached != nil {
virtualMachines := cached.(*sync.Map)
virtualMachines.Range(func(key, value interface{}) bool {
oldCache[key.(string)] = *value.(*vmssVirtualMachinesEntry)
return true
})
}
}
}
vms, err := ss.listScaleSetVMs(vmssName, resourceGroupName)
if err != nil {
return nil, err
}
for i := range vms {
vm := vms[i]
if vm.OsProfile == nil || vm.OsProfile.ComputerName == nil {
klog.Warningf("failed to get computerName for vmssVM (%q)", vmssName)
continue
}
computerName := strings.ToLower(*vm.OsProfile.ComputerName)
if vm.NetworkProfile == nil || vm.NetworkProfile.NetworkInterfaces == nil {
klog.Warningf("skip caching vmssVM %s since its network profile hasn't initialized yet (probably still under creating)", computerName)
continue
}
vmssVMCacheEntry := &vmssVirtualMachinesEntry{
resourceGroup: resourceGroupName,
vmssName: vmssName,
instanceID: to.String(vm.InstanceID),
virtualMachine: &vm,
lastUpdate: time.Now().UTC(),
}
// set cache entry to nil when the VM is under deleting.
if vm.VirtualMachineScaleSetVMProperties != nil &&
strings.EqualFold(to.String(vm.VirtualMachineScaleSetVMProperties.ProvisioningState), string(compute.ProvisioningStateDeleting)) {
klog.V(4).Infof("VMSS virtualMachine %q is under deleting, setting its cache to nil", computerName)
vmssVMCacheEntry.virtualMachine = nil
}
localCache.Store(computerName, vmssVMCacheEntry)
delete(oldCache, computerName)
}
// add old missing cache data with nil entries to prevent aggressive
// ARM calls during cache invalidation
for name, vmEntry := range oldCache {
// if the nil cache entry has existed for vmssVirtualMachinesCacheTTL in the cache
// then it should not be added back to the cache
if vmEntry.virtualMachine == nil && time.Since(vmEntry.lastUpdate) > vmssVirtualMachinesCacheTTL {
klog.V(5).Infof("ignoring expired entries from old cache for %s", name)
continue
}
lastUpdate := time.Now().UTC()
if vmEntry.virtualMachine == nil {
// if this is already a nil entry then keep the time the nil
// entry was first created, so we can cleanup unwanted entries
lastUpdate = vmEntry.lastUpdate
}
klog.V(5).Infof("adding old entries to new cache for %s", name)
localCache.Store(name, &vmssVirtualMachinesEntry{
resourceGroup: vmEntry.resourceGroup,
vmssName: vmEntry.vmssName,
instanceID: vmEntry.instanceID,
virtualMachine: nil,
lastUpdate: lastUpdate,
})
}
return localCache, nil
}
return azcache.NewTimedcache(vmssVirtualMachinesCacheTTL, getter)
}
func (ss *ScaleSet) deleteCacheForNode(nodeName string) error {
node, err := ss.getNodeIdentityByNodeName(nodeName, azcache.CacheReadTypeUnsafe)
if err != nil {
klog.Errorf("deleteCacheForNode(%s) failed with error: %v", nodeName, err)
return err
}
cacheKey, timedcache, err := ss.getVMSSVMCache(node.resourceGroup, node.vmssName)
if err != nil {
klog.Errorf("deleteCacheForNode(%s) failed with error: %v", nodeName, err)
return err
}
vmcache, err := timedcache.Get(cacheKey, azcache.CacheReadTypeUnsafe)
if err != nil {
klog.Errorf("deleteCacheForNode(%s) failed with error: %v", nodeName, err)
return err
}
virtualMachines := vmcache.(*sync.Map)
virtualMachines.Delete(nodeName)
if err := ss.gcVMSSVMCache(); err != nil {
klog.Errorf("deleteCacheForNode(%s) failed to gc stale vmss caches: %v", nodeName, err)
}
return nil
}
func (ss *ScaleSet) newAvailabilitySetNodesCache() (*azcache.TimedCache, error) {
getter := func(key string) (interface{}, error) {
vmNames := sets.NewString()
resourceGroups, err := ss.GetResourceGroups()
if err != nil {
return nil, err
}
vmList := make([]compute.VirtualMachine, 0)
for _, resourceGroup := range resourceGroups.List() {
vms, err := ss.Cloud.ListVirtualMachines(resourceGroup)
if err != nil {
return nil, fmt.Errorf("newAvailabilitySetNodesCache: failed to list vms in the resource group %s: %w", resourceGroup, err)
}
for _, vm := range vms {
if vm.Name != nil {
vmNames.Insert(to.String(vm.Name))
vmList = append(vmList, vm)
}
}
}
// store all the node names in the cluster when the cache data was created.
nodeNames, err := ss.GetNodeNames()
if err != nil {
return nil, err
}
localCache := availabilitySetNodeEntry{
vmNames: vmNames,
nodeNames: nodeNames,
vms: vmList,
}
return localCache, nil
}
if ss.Config.AvailabilitySetNodesCacheTTLInSeconds == 0 {
ss.Config.AvailabilitySetNodesCacheTTLInSeconds = consts.AvailabilitySetNodesCacheTTLDefaultInSeconds
}
return azcache.NewTimedcache(time.Duration(ss.Config.AvailabilitySetNodesCacheTTLInSeconds)*time.Second, getter)
}
func (ss *ScaleSet) isNodeManagedByAvailabilitySet(nodeName string, crt azcache.AzureCacheReadType) (bool, error) {
// Assume all nodes are managed by VMSS when DisableAvailabilitySetNodes is enabled.
if ss.DisableAvailabilitySetNodes {
klog.V(2).Infof("Assuming node %q is managed by VMSS since DisableAvailabilitySetNodes is set to true", nodeName)
return false, nil
}
cached, err := ss.availabilitySetNodesCache.Get(consts.AvailabilitySetNodesKey, crt)
if err != nil {
return false, err
}
cachedNodes := cached.(availabilitySetNodeEntry).nodeNames
// if the node is not in the cache, assume the node has joined after the last cache refresh and attempt to refresh the cache.
if !cachedNodes.Has(nodeName) {
klog.V(2).Infof("Node %s has joined the cluster since the last VM cache refresh, refreshing the cache", nodeName)
cached, err = ss.availabilitySetNodesCache.Get(consts.AvailabilitySetNodesKey, azcache.CacheReadTypeForceRefresh)
if err != nil {
return false, err
}
}
cachedVMs := cached.(availabilitySetNodeEntry).vmNames
return cachedVMs.Has(nodeName), nil
}

View File

@ -0,0 +1,360 @@
/*
Copyright 2020 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 provider
import (
"fmt"
"net/http"
"regexp"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2020-12-01/compute"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-02-01/network"
"github.com/Azure/go-autorest/autorest/to"
"k8s.io/apimachinery/pkg/types"
cloudprovider "k8s.io/cloud-provider"
"k8s.io/klog/v2"
azcache "sigs.k8s.io/cloud-provider-azure/pkg/cache"
"sigs.k8s.io/cloud-provider-azure/pkg/consts"
"sigs.k8s.io/cloud-provider-azure/pkg/retry"
)
var (
vmCacheTTLDefaultInSeconds = 60
loadBalancerCacheTTLDefaultInSeconds = 120
nsgCacheTTLDefaultInSeconds = 120
routeTableCacheTTLDefaultInSeconds = 120
azureNodeProviderIDRE = regexp.MustCompile(`^azure:///subscriptions/(?:.*)/resourceGroups/(?:.*)/providers/Microsoft.Compute/(?:.*)`)
azureResourceGroupNameRE = regexp.MustCompile(`.*/subscriptions/(?:.*)/resourceGroups/(.+)/providers/(?:.*)`)
)
// checkExistsFromError inspects an error and returns a true if err is nil,
// false if error is an autorest.Error with StatusCode=404 and will return the
// error back if error is another status code or another type of error.
func checkResourceExistsFromError(err *retry.Error) (bool, *retry.Error) {
if err == nil {
return true, nil
}
if err.HTTPStatusCode == http.StatusNotFound {
return false, nil
}
return false, err
}
/// getVirtualMachine calls 'VirtualMachinesClient.Get' with a timed cache
/// The service side has throttling control that delays responses if there are multiple requests onto certain vm
/// resource request in short period.
func (az *Cloud) getVirtualMachine(nodeName types.NodeName, crt azcache.AzureCacheReadType) (vm compute.VirtualMachine, err error) {
vmName := string(nodeName)
cachedVM, err := az.vmCache.Get(vmName, crt)
if err != nil {
return vm, err
}
if cachedVM == nil {
klog.Warningf("Unable to find node %s: %v", nodeName, cloudprovider.InstanceNotFound)
return vm, cloudprovider.InstanceNotFound
}
return *(cachedVM.(*compute.VirtualMachine)), nil
}
func (az *Cloud) getRouteTable(crt azcache.AzureCacheReadType) (routeTable network.RouteTable, exists bool, err error) {
if len(az.RouteTableName) == 0 {
return routeTable, false, fmt.Errorf("Route table name is not configured")
}
cachedRt, err := az.rtCache.Get(az.RouteTableName, crt)
if err != nil {
return routeTable, false, err
}
if cachedRt == nil {
return routeTable, false, nil
}
return *(cachedRt.(*network.RouteTable)), true, nil
}
func (az *Cloud) getPublicIPAddress(pipResourceGroup string, pipName string) (network.PublicIPAddress, bool, error) {
resourceGroup := az.ResourceGroup
if pipResourceGroup != "" {
resourceGroup = pipResourceGroup
}
ctx, cancel := getContextWithCancel()
defer cancel()
pip, err := az.PublicIPAddressesClient.Get(ctx, resourceGroup, pipName, "")
exists, rerr := checkResourceExistsFromError(err)
if rerr != nil {
return pip, false, rerr.Error()
}
if !exists {
klog.V(2).Infof("Public IP %q not found", pipName)
return pip, false, nil
}
return pip, exists, nil
}
func (az *Cloud) getSubnet(virtualNetworkName string, subnetName string) (network.Subnet, bool, error) {
var rg string
if len(az.VnetResourceGroup) > 0 {
rg = az.VnetResourceGroup
} else {
rg = az.ResourceGroup
}
ctx, cancel := getContextWithCancel()
defer cancel()
subnet, err := az.SubnetsClient.Get(ctx, rg, virtualNetworkName, subnetName, "")
exists, rerr := checkResourceExistsFromError(err)
if rerr != nil {
return subnet, false, rerr.Error()
}
if !exists {
klog.V(2).Infof("Subnet %q not found", subnetName)
return subnet, false, nil
}
return subnet, exists, nil
}
func (az *Cloud) getAzureLoadBalancer(name string, crt azcache.AzureCacheReadType) (lb network.LoadBalancer, exists bool, err error) {
cachedLB, err := az.lbCache.Get(name, crt)
if err != nil {
return lb, false, err
}
if cachedLB == nil {
return lb, false, nil
}
return *(cachedLB.(*network.LoadBalancer)), true, nil
}
func (az *Cloud) getSecurityGroup(crt azcache.AzureCacheReadType) (network.SecurityGroup, error) {
nsg := network.SecurityGroup{}
if az.SecurityGroupName == "" {
return nsg, fmt.Errorf("securityGroupName is not configured")
}
securityGroup, err := az.nsgCache.Get(az.SecurityGroupName, crt)
if err != nil {
return nsg, err
}
if securityGroup == nil {
return nsg, fmt.Errorf("nsg %q not found", az.SecurityGroupName)
}
return *(securityGroup.(*network.SecurityGroup)), nil
}
func (az *Cloud) newVMCache() (*azcache.TimedCache, error) {
getter := func(key string) (interface{}, error) {
// Currently InstanceView request are used by azure_zones, while the calls come after non-InstanceView
// request. If we first send an InstanceView request and then a non InstanceView request, the second
// request will still hit throttling. This is what happens now for cloud controller manager: In this
// case we do get instance view every time to fulfill the azure_zones requirement without hitting
// throttling.
// Consider adding separate parameter for controlling 'InstanceView' once node update issue #56276 is fixed
ctx, cancel := getContextWithCancel()
defer cancel()
resourceGroup, err := az.GetNodeResourceGroup(key)
if err != nil {
return nil, err
}
vm, verr := az.VirtualMachinesClient.Get(ctx, resourceGroup, key, compute.InstanceView)
exists, rerr := checkResourceExistsFromError(verr)
if rerr != nil {
return nil, rerr.Error()
}
if !exists {
klog.V(2).Infof("Virtual machine %q not found", key)
return nil, nil
}
if vm.VirtualMachineProperties != nil &&
strings.EqualFold(to.String(vm.VirtualMachineProperties.ProvisioningState), string(compute.ProvisioningStateDeleting)) {
klog.V(2).Infof("Virtual machine %q is under deleting", key)
return nil, nil
}
return &vm, nil
}
if az.VMCacheTTLInSeconds == 0 {
az.VMCacheTTLInSeconds = vmCacheTTLDefaultInSeconds
}
return azcache.NewTimedcache(time.Duration(az.VMCacheTTLInSeconds)*time.Second, getter)
}
func (az *Cloud) newLBCache() (*azcache.TimedCache, error) {
getter := func(key string) (interface{}, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
lb, err := az.LoadBalancerClient.Get(ctx, az.getLoadBalancerResourceGroup(), key, "")
exists, rerr := checkResourceExistsFromError(err)
if rerr != nil {
return nil, rerr.Error()
}
if !exists {
klog.V(2).Infof("Load balancer %q not found", key)
return nil, nil
}
return &lb, nil
}
if az.LoadBalancerCacheTTLInSeconds == 0 {
az.LoadBalancerCacheTTLInSeconds = loadBalancerCacheTTLDefaultInSeconds
}
return azcache.NewTimedcache(time.Duration(az.LoadBalancerCacheTTLInSeconds)*time.Second, getter)
}
func (az *Cloud) newNSGCache() (*azcache.TimedCache, error) {
getter := func(key string) (interface{}, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
nsg, err := az.SecurityGroupsClient.Get(ctx, az.SecurityGroupResourceGroup, key, "")
exists, rerr := checkResourceExistsFromError(err)
if rerr != nil {
return nil, rerr.Error()
}
if !exists {
klog.V(2).Infof("Security group %q not found", key)
return nil, nil
}
return &nsg, nil
}
if az.NsgCacheTTLInSeconds == 0 {
az.NsgCacheTTLInSeconds = nsgCacheTTLDefaultInSeconds
}
return azcache.NewTimedcache(time.Duration(az.NsgCacheTTLInSeconds)*time.Second, getter)
}
func (az *Cloud) newRouteTableCache() (*azcache.TimedCache, error) {
getter := func(key string) (interface{}, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
rt, err := az.RouteTablesClient.Get(ctx, az.RouteTableResourceGroup, key, "")
exists, rerr := checkResourceExistsFromError(err)
if rerr != nil {
return nil, rerr.Error()
}
if !exists {
klog.V(2).Infof("Route table %q not found", key)
return nil, nil
}
return &rt, nil
}
if az.RouteTableCacheTTLInSeconds == 0 {
az.RouteTableCacheTTLInSeconds = routeTableCacheTTLDefaultInSeconds
}
return azcache.NewTimedcache(time.Duration(az.RouteTableCacheTTLInSeconds)*time.Second, getter)
}
func (az *Cloud) useStandardLoadBalancer() bool {
return strings.EqualFold(az.LoadBalancerSku, consts.LoadBalancerSkuStandard)
}
func (az *Cloud) excludeMasterNodesFromStandardLB() bool {
return az.ExcludeMasterFromStandardLB != nil && *az.ExcludeMasterFromStandardLB
}
func (az *Cloud) disableLoadBalancerOutboundSNAT() bool {
if !az.useStandardLoadBalancer() || az.DisableOutboundSNAT == nil {
return false
}
return *az.DisableOutboundSNAT
}
// IsNodeUnmanaged returns true if the node is not managed by Azure cloud provider.
// Those nodes includes on-prem or VMs from other clouds. They will not be added to load balancer
// backends. Azure routes and managed disks are also not supported for them.
func (az *Cloud) IsNodeUnmanaged(nodeName string) (bool, error) {
unmanagedNodes, err := az.GetUnmanagedNodes()
if err != nil {
return false, err
}
return unmanagedNodes.Has(nodeName), nil
}
// IsNodeUnmanagedByProviderID returns true if the node is not managed by Azure cloud provider.
// All managed node's providerIDs are in format 'azure:///subscriptions/<id>/resourceGroups/<rg>/providers/Microsoft.Compute/.*'
func (az *Cloud) IsNodeUnmanagedByProviderID(providerID string) bool {
return !azureNodeProviderIDRE.Match([]byte(providerID))
}
// convertResourceGroupNameToLower converts the resource group name in the resource ID to be lowered.
func convertResourceGroupNameToLower(resourceID string) (string, error) {
matches := azureResourceGroupNameRE.FindStringSubmatch(resourceID)
if len(matches) != 2 {
return "", fmt.Errorf("%q isn't in Azure resource ID format %q", resourceID, azureResourceGroupNameRE.String())
}
resourceGroup := matches[1]
return strings.Replace(resourceID, resourceGroup, strings.ToLower(resourceGroup), 1), nil
}
// isBackendPoolOnSameLB checks whether newBackendPoolID is on the same load balancer as existingBackendPools.
// Since both public and internal LBs are supported, lbName and lbName-internal are treated as same.
// If not same, the lbName for existingBackendPools would also be returned.
func isBackendPoolOnSameLB(newBackendPoolID string, existingBackendPools []string) (bool, string, error) {
matches := backendPoolIDRE.FindStringSubmatch(newBackendPoolID)
if len(matches) != 2 {
return false, "", fmt.Errorf("new backendPoolID %q is in wrong format", newBackendPoolID)
}
newLBName := matches[1]
newLBNameTrimmed := strings.TrimSuffix(newLBName, consts.InternalLoadBalancerNameSuffix)
for _, backendPool := range existingBackendPools {
matches := backendPoolIDRE.FindStringSubmatch(backendPool)
if len(matches) != 2 {
return false, "", fmt.Errorf("existing backendPoolID %q is in wrong format", backendPool)
}
lbName := matches[1]
if !strings.EqualFold(strings.TrimSuffix(lbName, consts.InternalLoadBalancerNameSuffix), newLBNameTrimmed) {
return false, lbName, nil
}
}
return true, "", nil
}

Some files were not shown because too many files have changed in this diff Show More