Merge pull request #87122 from feiskyer/more-arm-clients

Add Azure network and VM clients with backoff retry
This commit is contained in:
Kubernetes Prow Robot
2020-01-15 09:41:33 -08:00
committed by GitHub
75 changed files with 6481 additions and 1502 deletions

View File

@@ -66,6 +66,14 @@ go_library(
"//staging/src/k8s.io/component-base/featuregate:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/auth:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/interfaceclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/publicipclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/routetableclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/subnetclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/vmclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/vmssclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/vmssvmclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/metrics:go_default_library",

View File

@@ -46,6 +46,14 @@ import (
"k8s.io/klog"
"k8s.io/legacy-cloud-providers/azure/auth"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/interfaceclient"
"k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient"
"k8s.io/legacy-cloud-providers/azure/clients/publicipclient"
"k8s.io/legacy-cloud-providers/azure/clients/routeclient"
"k8s.io/legacy-cloud-providers/azure/clients/routetableclient"
"k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient"
"k8s.io/legacy-cloud-providers/azure/clients/subnetclient"
"k8s.io/legacy-cloud-providers/azure/clients/vmclient"
"k8s.io/legacy-cloud-providers/azure/clients/vmssclient"
"k8s.io/legacy-cloud-providers/azure/clients/vmssvmclient"
"k8s.io/legacy-cloud-providers/azure/retry"
@@ -476,24 +484,27 @@ func (az *Cloud) InitializeCloudFromConfig(config *Config, fromSecret bool) erro
Jitter: config.CloudProviderBackoffJitter,
}
}
az.DisksClient = newAzDisksClient(azClientConfig.WithRateLimiter(config.DiskRateLimit))
az.SnapshotsClient = newSnapshotsClient(azClientConfig.WithRateLimiter(config.SnapshotRateLimit))
az.RoutesClient = newAzRoutesClient(azClientConfig.WithRateLimiter(config.RouteRateLimit))
az.SubnetsClient = newAzSubnetsClient(azClientConfig.WithRateLimiter(config.SubnetsRateLimit))
az.InterfacesClient = newAzInterfacesClient(azClientConfig.WithRateLimiter(config.InterfaceRateLimit))
az.RouteTablesClient = newAzRouteTablesClient(azClientConfig.WithRateLimiter(config.RouteTableRateLimit))
az.LoadBalancerClient = newAzLoadBalancersClient(azClientConfig.WithRateLimiter(config.LoadBalancerRateLimit))
az.SecurityGroupsClient = newAzSecurityGroupsClient(azClientConfig.WithRateLimiter(config.SecurityGroupRateLimit))
az.StorageAccountClient = newAzStorageAccountClient(azClientConfig.WithRateLimiter(config.StorageAccountRateLimit))
az.VirtualMachinesClient = newAzVirtualMachinesClient(azClientConfig.WithRateLimiter(config.VirtualMachineRateLimit))
az.PublicIPAddressesClient = newAzPublicIPAddressesClient(azClientConfig.WithRateLimiter(config.PublicIPAddressRateLimit))
az.VirtualMachineSizesClient = newAzVirtualMachineSizesClient(azClientConfig.WithRateLimiter(config.VirtualMachineSizeRateLimit))
az.RoutesClient = routeclient.New(azClientConfig.WithRateLimiter(config.RouteRateLimit))
az.SubnetsClient = subnetclient.New(azClientConfig.WithRateLimiter(config.SubnetsRateLimit))
az.InterfacesClient = interfaceclient.New(azClientConfig.WithRateLimiter(config.InterfaceRateLimit))
az.RouteTablesClient = routetableclient.New(azClientConfig.WithRateLimiter(config.RouteTableRateLimit))
az.LoadBalancerClient = loadbalancerclient.New(azClientConfig.WithRateLimiter(config.LoadBalancerRateLimit))
az.SecurityGroupsClient = securitygroupclient.New(azClientConfig.WithRateLimiter(config.SecurityGroupRateLimit))
az.VirtualMachinesClient = vmclient.New(azClientConfig.WithRateLimiter(config.VirtualMachineRateLimit))
az.PublicIPAddressesClient = publicipclient.New(azClientConfig.WithRateLimiter(config.PublicIPAddressRateLimit))
az.VirtualMachineScaleSetsClient = vmssclient.New(azClientConfig.WithRateLimiter(config.VirtualMachineScaleSetRateLimit))
// Error "not an active Virtual Machine Scale Set VM" is not retriable for VMSS VM.
vmssVMClientConfig := azClientConfig.WithRateLimiter(config.VirtualMachineScaleSetRateLimit)
vmssVMClientConfig.Backoff = vmssVMClientConfig.Backoff.WithNonRetriableErrors([]string{vmssVMNotActiveErrorMessage})
az.VirtualMachineScaleSetVMsClient = vmssvmclient.New(vmssVMClientConfig)
// TODO(feiskyer): refactor the following clients to use armclient
az.DisksClient = newAzDisksClient(azClientConfig.WithRateLimiter(config.DiskRateLimit))
az.SnapshotsClient = newSnapshotsClient(azClientConfig.WithRateLimiter(config.SnapshotRateLimit))
az.StorageAccountClient = newAzStorageAccountClient(azClientConfig.WithRateLimiter(config.StorageAccountRateLimit))
az.VirtualMachineSizesClient = newAzVirtualMachineSizesClient(azClientConfig.WithRateLimiter(config.VirtualMachineSizeRateLimit))
// TODO(feiskyer): refactor azureFileClient to Interface.
az.FileClient = &azureFileClient{env: *env}

View File

@@ -86,33 +86,8 @@ func (az *Cloud) GetVirtualMachineWithRetry(name types.NodeName, crt cacheReadTy
return machine, err
}
// ListVirtualMachinesWithRetry invokes az.VirtualMachinesClient.List with exponential backoff retry
func (az *Cloud) ListVirtualMachinesWithRetry(resourceGroup string) ([]compute.VirtualMachine, error) {
allNodes := []compute.VirtualMachine{}
err := wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, error) {
var retryErr *retry.Error
ctx, cancel := getContextWithCancel()
defer cancel()
allNodes, retryErr = az.VirtualMachinesClient.List(ctx, resourceGroup)
if retryErr != nil {
klog.Errorf("VirtualMachinesClient.List(%v) - backoff: failure, will retry,err=%v",
resourceGroup,
retryErr)
return false, retryErr.Error()
}
klog.V(2).Infof("VirtualMachinesClient.List(%v) - backoff: success", resourceGroup)
return true, nil
})
if err != nil {
return nil, err
}
return allNodes, nil
}
// ListVirtualMachines invokes az.VirtualMachinesClient.List with exponential backoff retry
func (az *Cloud) ListVirtualMachines(resourceGroup string) ([]compute.VirtualMachine, error) {
if az.Config.shouldOmitCloudProviderBackoff() {
ctx, cancel := getContextWithCancel()
defer cancel()
@@ -125,9 +100,6 @@ func (az *Cloud) ListVirtualMachines(resourceGroup string) ([]compute.VirtualMac
return allNodes, nil
}
return az.ListVirtualMachinesWithRetry(resourceGroup)
}
// getPrivateIPsForMachine is wrapper for optional backoff getting private ips
// list of a node by name
func (az *Cloud) getPrivateIPsForMachine(nodeName types.NodeName) ([]string, error) {
@@ -183,7 +155,6 @@ func (az *Cloud) GetIPForMachineWithRetry(name types.NodeName) (string, string,
// CreateOrUpdateSecurityGroup invokes az.SecurityGroupsClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) CreateOrUpdateSecurityGroup(service *v1.Service, sg network.SecurityGroup) error {
if az.Config.shouldOmitCloudProviderBackoff() {
ctx, cancel := getContextWithCancel()
defer cancel()
@@ -197,53 +168,21 @@ func (az *Cloud) CreateOrUpdateSecurityGroup(service *v1.Service, sg network.Sec
// 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()), operationCancledErrorMessage) {
klog.V(3).Infof("SecurityGroup cache for %s is cleanup because CreateOrUpdateSecurityGroup is canceld by another operation", *sg.Name)
az.nsgCache.Delete(*sg.Name)
}
return rerr.Error()
}
return az.CreateOrUpdateSGWithRetry(service, sg)
}
// CreateOrUpdateSGWithRetry invokes az.SecurityGroupsClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) CreateOrUpdateSGWithRetry(service *v1.Service, sg network.SecurityGroup) error {
return wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, 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 true, nil
}
// Invalidate the cache and abort backoff because ETAG precondition mismatch.
if rerr.HTTPStatusCode == http.StatusPreconditionFailed {
az.nsgCache.Delete(*sg.Name)
return true, rerr.Error()
}
// Invalidate the cache and abort backoff because another new operation has canceled the current request.
if strings.Contains(strings.ToLower(rerr.Error().Error()), operationCancledErrorMessage) {
az.nsgCache.Delete(*sg.Name)
return true, rerr.Error()
}
return !rerr.Retriable, rerr.Error()
})
}
// CreateOrUpdateLB invokes az.LoadBalancerClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) CreateOrUpdateLB(service *v1.Service, lb network.LoadBalancer) error {
if az.Config.shouldOmitCloudProviderBackoff() {
ctx, cancel := getContextWithCancel()
defer cancel()
@@ -258,50 +197,20 @@ func (az *Cloud) CreateOrUpdateLB(service *v1.Service, lb network.LoadBalancer)
// 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", *lb.Name)
az.lbCache.Delete(*lb.Name)
}
// Invalidate the cache because another new operation has canceled the current request.
if strings.Contains(strings.ToLower(rerr.Error().Error()), operationCancledErrorMessage) {
klog.V(3).Infof("LoadBalancer cache for %s is cleanup because CreateOrUpdate is canceled by another operation", *lb.Name)
az.lbCache.Delete(*lb.Name)
}
return rerr.Error()
}
return az.createOrUpdateLBWithRetry(service, lb)
}
// createOrUpdateLBWithRetry invokes az.LoadBalancerClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) createOrUpdateLBWithRetry(service *v1.Service, lb network.LoadBalancer) error {
return wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
rgName := az.getLoadBalancerResourceGroup()
rerr := az.LoadBalancerClient.CreateOrUpdate(ctx, rgName, *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 true, nil
}
// Invalidate the cache and abort backoff because ETAG precondition mismatch.
if rerr.HTTPStatusCode == http.StatusPreconditionFailed {
az.lbCache.Delete(*lb.Name)
return true, rerr.Error()
}
// Invalidate the cache and abort backoff because another new operation has canceled the current request.
if strings.Contains(strings.ToLower(rerr.Error().Error()), operationCancledErrorMessage) {
az.lbCache.Delete(*lb.Name)
return true, rerr.Error()
}
return !rerr.Retriable, rerr.Error()
})
}
// ListLB invokes az.LoadBalancerClient.List with exponential backoff retry
func (az *Cloud) ListLB(service *v1.Service) ([]network.LoadBalancer, error) {
if az.Config.shouldOmitCloudProviderBackoff() {
ctx, cancel := getContextWithCancel()
defer cancel()
@@ -316,40 +225,8 @@ func (az *Cloud) ListLB(service *v1.Service) ([]network.LoadBalancer, error) {
return allLBs, nil
}
return az.listLBWithRetry(service)
}
// listLBWithRetry invokes az.LoadBalancerClient.List with exponential backoff retry
func (az *Cloud) listLBWithRetry(service *v1.Service) ([]network.LoadBalancer, error) {
var retryErr *retry.Error
var allLBs []network.LoadBalancer
err := wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
rgName := az.getLoadBalancerResourceGroup()
allLBs, retryErr = az.LoadBalancerClient.List(ctx, rgName)
if retryErr != nil {
az.Event(service, v1.EventTypeWarning, "ListLoadBalancers", retryErr.Error().Error())
klog.Errorf("LoadBalancerClient.List(%v) - backoff: failure, will retry,err=%v",
rgName,
retryErr)
return false, retryErr.Error()
}
klog.V(2).Infof("LoadBalancerClient.List(%v) - backoff: success", az.ResourceGroup)
return true, nil
})
if err != nil {
return nil, err
}
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) {
if az.Config.shouldOmitCloudProviderBackoff() {
ctx, cancel := getContextWithCancel()
defer cancel()
@@ -359,43 +236,13 @@ func (az *Cloud) ListPIP(service *v1.Service, pipResourceGroup string) ([]networ
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
}
return az.listPIPWithRetry(service, pipResourceGroup)
}
// listPIPWithRetry list the PIP resources in the given resource group
func (az *Cloud) listPIPWithRetry(service *v1.Service, pipResourceGroup string) ([]network.PublicIPAddress, error) {
var allPIPs []network.PublicIPAddress
err := wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
var retryErr *retry.Error
allPIPs, retryErr = az.PublicIPAddressesClient.List(ctx, pipResourceGroup)
if retryErr != nil {
az.Event(service, v1.EventTypeWarning, "ListPublicIPs", retryErr.Error().Error())
klog.Errorf("PublicIPAddressesClient.List(%v) - backoff: failure, will retry,err=%v",
pipResourceGroup,
retryErr)
return false, retryErr.Error()
}
klog.V(2).Infof("PublicIPAddressesClient.List(%v) - backoff: success", pipResourceGroup)
return true, nil
})
if err != nil {
return nil, err
}
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 {
if az.Config.shouldOmitCloudProviderBackoff() {
ctx, cancel := getContextWithCancel()
defer cancel()
@@ -410,30 +257,8 @@ func (az *Cloud) CreateOrUpdatePIP(service *v1.Service, pipResourceGroup string,
return nil
}
return az.createOrUpdatePIPWithRetry(service, pipResourceGroup, pip)
}
// createOrUpdatePIPWithRetry invokes az.PublicIPAddressesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) createOrUpdatePIPWithRetry(service *v1.Service, pipResourceGroup string, pip network.PublicIPAddress) error {
return wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
rerr := az.PublicIPAddressesClient.CreateOrUpdate(ctx, pipResourceGroup, *pip.Name, pip)
klog.V(10).Infof("PublicIPAddressesClient.CreateOrUpdate(%s, %s): end", pipResourceGroup, *pip.Name)
if rerr != nil {
klog.Errorf("PublicIPAddressesClient.CreateOrUpdate(%s, %s) failed: %s", pipResourceGroup, *pip.Name, rerr.Error().Error())
az.Event(service, v1.EventTypeWarning, "CreateOrUpdatePublicIPAddress", rerr.Error().Error())
return !rerr.Retriable, rerr.Error()
}
return true, nil
})
}
// CreateOrUpdateInterface invokes az.PublicIPAddressesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) CreateOrUpdateInterface(service *v1.Service, nic network.Interface) error {
if az.Config.shouldOmitCloudProviderBackoff() {
ctx, cancel := getContextWithCancel()
defer cancel()
@@ -448,30 +273,8 @@ func (az *Cloud) CreateOrUpdateInterface(service *v1.Service, nic network.Interf
return nil
}
return az.createOrUpdateInterfaceWithRetry(service, nic)
}
// createOrUpdateInterfaceWithRetry invokes az.PublicIPAddressesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) createOrUpdateInterfaceWithRetry(service *v1.Service, nic network.Interface) error {
return wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, 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) faild: %s", *nic.Name, rerr.Error().Error())
az.Event(service, v1.EventTypeWarning, "CreateOrUpdateInterface", rerr.Error().Error())
return !rerr.Retriable, rerr.Error()
}
return true, nil
})
}
// DeletePublicIP invokes az.PublicIPAddressesClient.Delete with exponential backoff retry
func (az *Cloud) DeletePublicIP(service *v1.Service, pipResourceGroup string, pipName string) error {
if az.Config.shouldOmitCloudProviderBackoff() {
ctx, cancel := getContextWithCancel()
defer cancel()
@@ -485,29 +288,8 @@ func (az *Cloud) DeletePublicIP(service *v1.Service, pipResourceGroup string, pi
return nil
}
return az.deletePublicIPWithRetry(service, pipResourceGroup, pipName)
}
// deletePublicIPWithRetry invokes az.PublicIPAddressesClient.Delete with exponential backoff retry
func (az *Cloud) deletePublicIPWithRetry(service *v1.Service, pipResourceGroup string, pipName string) error {
return wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, 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())
return !rerr.Retriable, rerr.Error()
}
return true, nil
})
}
// DeleteLB invokes az.LoadBalancerClient.Delete with exponential backoff retry
func (az *Cloud) DeleteLB(service *v1.Service, lbName string) error {
if az.Config.shouldOmitCloudProviderBackoff() {
ctx, cancel := getContextWithCancel()
defer cancel()
@@ -524,32 +306,8 @@ func (az *Cloud) DeleteLB(service *v1.Service, lbName string) error {
return rerr.Error()
}
return az.deleteLBWithRetry(service, lbName)
}
// deleteLBWithRetry invokes az.LoadBalancerClient.Delete with exponential backoff retry
func (az *Cloud) deleteLBWithRetry(service *v1.Service, lbName string) error {
return wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
rgName := az.getLoadBalancerResourceGroup()
rerr := az.LoadBalancerClient.Delete(ctx, rgName, lbName)
if rerr == nil {
// Invalidate the cache right after deleting
az.lbCache.Delete(lbName)
return true, nil
}
klog.Errorf("LoadBalancerClient.Delete(%s) failed: %s", lbName, rerr.Error().Error())
az.Event(service, v1.EventTypeWarning, "CreateOrUpdateInterface", rerr.Error().Error())
return !rerr.Retriable, rerr.Error()
})
}
// CreateOrUpdateRouteTable invokes az.RouteTablesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) CreateOrUpdateRouteTable(routeTable network.RouteTable) error {
if az.Config.shouldOmitCloudProviderBackoff() {
ctx, cancel := getContextWithCancel()
defer cancel()
@@ -562,49 +320,20 @@ func (az *Cloud) CreateOrUpdateRouteTable(routeTable network.RouteTable) error {
// 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()), operationCancledErrorMessage) {
klog.V(3).Infof("Route table cache for %s is cleanup because CreateOrUpdateRouteTable is canceld by another operation", *routeTable.Name)
az.rtCache.Delete(*routeTable.Name)
}
klog.Errorf("RouteTablesClient.CreateOrUpdate(%s) failed: %v", az.RouteTableName, rerr.Error())
return rerr.Error()
}
return az.createOrUpdateRouteTableWithRetry(routeTable)
}
// createOrUpdateRouteTableWithRetry invokes az.RouteTablesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) createOrUpdateRouteTableWithRetry(routeTable network.RouteTable) error {
return wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, error) {
ctx, cancel := getContextWithCancel()
defer cancel()
rerr := az.RouteTablesClient.CreateOrUpdate(ctx, az.RouteTableResourceGroup, az.RouteTableName, routeTable, to.String(routeTable.Etag))
if rerr == nil {
az.rtCache.Delete(*routeTable.Name)
return true, nil
}
// Invalidate the cache and abort backoff because ETAG precondition mismatch.
if rerr.HTTPStatusCode == http.StatusPreconditionFailed {
az.rtCache.Delete(*routeTable.Name)
return true, rerr.Error()
}
// Invalidate the cache and abort backoff because another new operation has canceled the current request.
if strings.Contains(strings.ToLower(rerr.Error().Error()), operationCancledErrorMessage) {
az.rtCache.Delete(*routeTable.Name)
return true, rerr.Error()
}
klog.Errorf("RouteTablesClient.CreateOrUpdate(%s) failed: %v", az.RouteTableName, rerr.Error())
return !rerr.Retriable, rerr.Error()
})
}
// CreateOrUpdateRoute invokes az.RoutesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) CreateOrUpdateRoute(route network.Route) error {
if az.Config.shouldOmitCloudProviderBackoff() {
ctx, cancel := getContextWithCancel()
defer cancel()
@@ -616,50 +345,19 @@ func (az *Cloud) CreateOrUpdateRoute(route network.Route) error {
}
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()), operationCancledErrorMessage) {
klog.V(3).Infof("Route cache for %s is cleanup because CreateOrUpdateRouteTable is canceld by another operation", *route.Name)
az.rtCache.Delete(az.RouteTableName)
}
return rerr.Error()
}
return az.createOrUpdateRouteWithRetry(route)
}
// createOrUpdateRouteWithRetry invokes az.RoutesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) createOrUpdateRouteWithRetry(route network.Route) error {
return wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, 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 true, nil
}
// Invalidate the cache and abort backoff because ETAG precondition mismatch.
if rerr.HTTPStatusCode == http.StatusPreconditionFailed {
az.rtCache.Delete(az.RouteTableName)
return true, rerr.Error()
}
// Invalidate the cache and abort backoff because another new operation has canceled the current request.
if strings.Contains(strings.ToLower(rerr.Error().Error()), operationCancledErrorMessage) {
az.rtCache.Delete(az.RouteTableName)
return true, rerr.Error()
}
return !rerr.Retriable, rerr.Error()
})
}
// DeleteRouteWithName invokes az.RoutesClient.CreateOrUpdate with exponential backoff retry
func (az *Cloud) DeleteRouteWithName(routeName string) error {
if az.Config.shouldOmitCloudProviderBackoff() {
ctx, cancel := getContextWithCancel()
defer cancel()
@@ -673,26 +371,6 @@ func (az *Cloud) DeleteRouteWithName(routeName string) error {
return rerr.Error()
}
return az.deleteRouteWithRetry(routeName)
}
// deleteRouteWithRetry invokes az.RoutesClient.Delete with exponential backoff retry
func (az *Cloud) deleteRouteWithRetry(routeName string) error {
return wait.ExponentialBackoff(az.RequestBackoff(), func() (bool, 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 true, nil
}
klog.Errorf("RoutesClient.Delete(%s, %s) failed: %v", az.RouteTableName, routeName, rerr.Error())
return !rerr.Retriable, rerr.Error()
})
}
// CreateOrUpdateVMSS invokes az.VirtualMachineScaleSetsClient.Update().
func (az *Cloud) CreateOrUpdateVMSS(resourceGroupName string, VMScaleSetName string, parameters compute.VirtualMachineScaleSet) *retry.Error {
ctx, cancel := getContextWithCancel()

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,14 @@ filegroup(
srcs = [
":package-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:all-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/interfaceclient:all-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient:all-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/publicipclient:all-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient:all-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/routetableclient:all-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient:all-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/subnetclient:all-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/vmclient:all-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/vmssclient:all-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/vmssvmclient:all-srcs",
],

View File

@@ -185,6 +185,17 @@ func (c *Client) PreparePutRequest(ctx context.Context, decorators ...autorest.P
return c.prepareRequest(ctx, decorators...)
}
// PreparePatchRequest prepares patch request
func (c *Client) PreparePatchRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) {
decorators = append(
[]autorest.PrepareDecorator{
autorest.AsContentType("application/json; charset=utf-8"),
autorest.AsPatch(),
autorest.WithBaseURL(c.baseURI)},
decorators...)
return c.prepareRequest(ctx, decorators...)
}
// PreparePostRequest prepares post request
func (c *Client) PreparePostRequest(ctx context.Context, decorators ...autorest.PrepareDecorator) (*http.Request, error) {
decorators = append(
@@ -300,13 +311,32 @@ func (c *Client) GetResource(ctx context.Context, resourceID, expand string) (*h
return c.Send(ctx, request)
}
// GetResourceWithDecorators get a resource with decorators by resource ID
func (c *Client) GetResourceWithDecorators(ctx context.Context, resourceID string, decorators []autorest.PrepareDecorator) (*http.Response, *retry.Error) {
getDecorators := []autorest.PrepareDecorator{
autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}),
}
getDecorators = append(getDecorators, decorators...)
request, err := c.PrepareGetRequest(ctx, getDecorators...)
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "get.prepare", resourceID, err)
return nil, retry.NewError(false, err)
}
return c.Send(ctx, request)
}
// PutResource puts a resource by resource ID
func (c *Client) PutResource(ctx context.Context, resourceID string, parameters interface{}) (*http.Response, *retry.Error) {
decorators := []autorest.PrepareDecorator{
putDecorators := []autorest.PrepareDecorator{
autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}),
autorest.WithJSON(parameters),
}
return c.PutResourceWithDecorators(ctx, resourceID, parameters, putDecorators)
}
// PutResourceWithDecorators puts a resource by resource ID
func (c *Client) PutResourceWithDecorators(ctx context.Context, resourceID string, parameters interface{}, decorators []autorest.PrepareDecorator) (*http.Response, *retry.Error) {
request, err := c.PreparePutRequest(ctx, decorators...)
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "put.prepare", resourceID, err)
@@ -340,6 +370,46 @@ func (c *Client) PutResource(ctx context.Context, resourceID string, parameters
return response, nil
}
// PatchResource patches a resource by resource ID
func (c *Client) PatchResource(ctx context.Context, resourceID string, parameters interface{}) (*http.Response, *retry.Error) {
decorators := []autorest.PrepareDecorator{
autorest.WithPathParameters("{resourceID}", map[string]interface{}{"resourceID": resourceID}),
autorest.WithJSON(parameters),
}
request, err := c.PreparePatchRequest(ctx, decorators...)
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "patch.prepare", resourceID, err)
return nil, retry.NewError(false, err)
}
future, resp, clientErr := c.SendAsync(ctx, request)
defer c.CloseResponse(ctx, resp)
if clientErr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "patch.send", resourceID, clientErr.Error())
return nil, clientErr
}
response, err := c.WaitForAsyncOperationResult(ctx, future, "armclient.PatchResource")
if err != nil {
if response != nil {
klog.V(5).Infof("Received error in WaitForAsyncOperationResult: '%s', response code %d", err.Error(), response.StatusCode)
} else {
klog.V(5).Infof("Received error in WaitForAsyncOperationResult: '%s', no response", err.Error())
}
retriableErr := retry.GetError(response, err)
if !retriableErr.Retriable &&
strings.Contains(strings.ToUpper(err.Error()), strings.ToUpper("InternalServerError")) {
klog.V(5).Infof("Received InternalServerError in WaitForAsyncOperationResult: '%s', setting error retriable", err.Error())
retriableErr.Retriable = true
}
return nil, retriableErr
}
return response, nil
}
// PutResourceAsync puts a resource by resource ID in async mode
func (c *Client) PutResourceAsync(ctx context.Context, resourceID string, parameters interface{}) (*azure.Future, *retry.Error) {
decorators := []autorest.PrepareDecorator{

View File

@@ -61,6 +61,12 @@ type Interface interface {
// PutResource puts a resource by resource ID
PutResource(ctx context.Context, resourceID string, parameters interface{}) (*http.Response, *retry.Error)
// PutResourceWithDecorators puts a resource with decorators by resource ID
PutResourceWithDecorators(ctx context.Context, resourceID string, parameters interface{}, decorators []autorest.PrepareDecorator) (*http.Response, *retry.Error)
// PatchResource patches a resource by resource ID
PatchResource(ctx context.Context, resourceID string, parameters interface{}) (*http.Response, *retry.Error)
// PutResourceAsync puts a resource by resource ID in async mode
PutResourceAsync(ctx context.Context, resourceID string, parameters interface{}) (*azure.Future, *retry.Error)
@@ -70,6 +76,9 @@ type Interface interface {
// GetResource get a resource by resource ID
GetResource(ctx context.Context, resourceID, expand string) (*http.Response, *retry.Error)
//GetResourceWithDecorators get a resource with decorators by resource ID
GetResourceWithDecorators(ctx context.Context, resourceID string, decorators []autorest.PrepareDecorator) (*http.Response, *retry.Error)
// PostResource posts a resource by resource ID
PostResource(ctx context.Context, resourceID, action string, parameters interface{}) (*http.Response, *retry.Error)

View File

@@ -227,6 +227,36 @@ func (mr *MockInterfaceMockRecorder) PutResource(ctx, resourceID, parameters int
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutResource", reflect.TypeOf((*MockInterface)(nil).PutResource), ctx, resourceID, parameters)
}
// PutResourceWithDecorators mocks base method
func (m *MockInterface) PutResourceWithDecorators(ctx context.Context, resourceID string, parameters interface{}, decorators []autorest.PrepareDecorator) (*http.Response, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PutResourceWithDecorators", ctx, resourceID, parameters, decorators)
ret0, _ := ret[0].(*http.Response)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// PutResourceWithDecorators indicates an expected call of PutResourceWithDecorators
func (mr *MockInterfaceMockRecorder) PutResourceWithDecorators(ctx, resourceID, parameters, decorators interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PutResourceWithDecorators", reflect.TypeOf((*MockInterface)(nil).PutResourceWithDecorators), ctx, resourceID, parameters, decorators)
}
// PatchResource mocks base method
func (m *MockInterface) PatchResource(ctx context.Context, resourceID string, parameters interface{}) (*http.Response, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "PatchResource", ctx, resourceID, parameters)
ret0, _ := ret[0].(*http.Response)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// PatchResource indicates an expected call of PatchResource
func (mr *MockInterfaceMockRecorder) PatchResource(ctx, resourceID, parameters interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PatchResource", reflect.TypeOf((*MockInterface)(nil).PatchResource), ctx, resourceID, parameters)
}
// PutResourceAsync mocks base method
func (m *MockInterface) PutResourceAsync(ctx context.Context, resourceID string, parameters interface{}) (*azure.Future, *retry.Error) {
m.ctrl.T.Helper()
@@ -272,6 +302,21 @@ func (mr *MockInterfaceMockRecorder) GetResource(ctx, resourceID, expand interfa
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResource", reflect.TypeOf((*MockInterface)(nil).GetResource), ctx, resourceID, expand)
}
// GetResourceWithDecorators mocks base method
func (m *MockInterface) GetResourceWithDecorators(ctx context.Context, resourceID string, decorators []autorest.PrepareDecorator) (*http.Response, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetResourceWithDecorators", ctx, resourceID, decorators)
ret0, _ := ret[0].(*http.Response)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// GetResourceWithDecorators indicates an expected call of GetResourceWithDecorators
func (mr *MockInterfaceMockRecorder) GetResourceWithDecorators(ctx, resourceID, decorators interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResourceWithDecorators", reflect.TypeOf((*MockInterface)(nil).GetResourceWithDecorators), ctx, resourceID, decorators)
}
// PostResource mocks base method
func (m *MockInterface) PostResource(ctx context.Context, resourceID, action string, parameters interface{}) (*http.Response, *retry.Error) {
m.ctrl.T.Helper()

View File

@@ -0,0 +1,57 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"azure_interfaceclient.go",
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/interfaceclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/interfaceclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/metrics:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["azure_interfaceclient_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/interfaceclient/mockinterfaceclient:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,276 @@
// +build !providerless
/*
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 interfaceclient
import (
"context"
"fmt"
"net/http"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/metrics"
"k8s.io/legacy-cloud-providers/azure/retry"
)
var _ Interface = &Client{}
// Client implements network interface client.
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 network interface client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := autorest.NewBearerAuthorizer(config.ServicePrincipalToken)
armClient := armclient.New(authorizer, baseURI, "", APIVersion, config.Location, config.Backoff)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
klog.V(2).Infof("Azure InterfacesClient (read ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPS,
config.RateLimitConfig.CloudProviderRateLimitBucket)
klog.V(2).Infof("Azure InterfacesClient (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 network.Interface.
func (c *Client) Get(ctx context.Context, resourceGroupName string, networkInterfaceName string, expand string) (network.Interface, *retry.Error) {
mc := metrics.NewMetricContext("interfaces", "get", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return network.Interface{}, retry.GetRateLimitError(false, "NicGet")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("NicGet", "client throttled", c.RetryAfterReader)
return network.Interface{}, rerr
}
result, rerr := c.getNetworkInterface(ctx, resourceGroupName, networkInterfaceName, expand)
mc.Observe(rerr.Error())
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
}
// getNetworkInterface gets a network.Interface.
func (c *Client) getNetworkInterface(ctx context.Context, resourceGroupName string, networkInterfaceName string, expand string) (network.Interface, *retry.Error) {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/networkInterfaces",
networkInterfaceName,
)
result := network.Interface{}
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", "nic.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", "nic.get.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}
// GetVirtualMachineScaleSetNetworkInterface gets a network.Interface of VMSS VM.
func (c *Client) GetVirtualMachineScaleSetNetworkInterface(ctx context.Context, resourceGroupName string, virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string, expand string) (network.Interface, *retry.Error) {
mc := metrics.NewMetricContext("interfaces", "get_vmss_nic", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return network.Interface{}, retry.GetRateLimitError(false, "NicGetVirtualMachineScaleSetNetworkInterface")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("NicGetVirtualMachineScaleSetNetworkInterface", "client throttled", c.RetryAfterReader)
return network.Interface{}, rerr
}
result, rerr := c.getVMSSNetworkInterface(ctx, resourceGroupName, virtualMachineScaleSetName, virtualmachineIndex, networkInterfaceName, expand)
mc.Observe(rerr.Error())
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
}
// getVMSSNetworkInterface gets a network.Interface of VMSS VM.
func (c *Client) getVMSSNetworkInterface(ctx context.Context, resourceGroupName string, virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string, expand string) (network.Interface, *retry.Error) {
resourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachineScaleSets/%s/virtualMachines/%s/networkInterfaces/%s",
autorest.Encode("path", c.subscriptionID),
autorest.Encode("path", resourceGroupName),
autorest.Encode("path", virtualMachineScaleSetName),
autorest.Encode("path", virtualmachineIndex),
autorest.Encode("path", networkInterfaceName),
)
result := network.Interface{}
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", "vmssnic.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", "vmssnic.get.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}
// CreateOrUpdate creates or updates a network.Interface.
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, networkInterfaceName string, parameters network.Interface) *retry.Error {
mc := metrics.NewMetricContext("interfaces", "create_or_update", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "NicCreateOrUpdate")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("NicCreateOrUpdate", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.createOrUpdateInterface(ctx, resourceGroupName, networkInterfaceName, parameters)
mc.Observe(rerr.Error())
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
}
// createOrUpdateInterface creates or updates a network.Interface.
func (c *Client) createOrUpdateInterface(ctx context.Context, resourceGroupName string, networkInterfaceName string, parameters network.Interface) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Network/networkInterfaces",
networkInterfaceName,
)
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", "nic.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", "nic.put.respond", resourceID, rerr.Error())
return rerr
}
}
return nil
}
func (c *Client) createOrUpdateResponder(resp *http.Response) (*network.Interface, *retry.Error) {
result := &network.Interface{}
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,152 @@
// +build !providerless
/*
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 interfaceclient
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/http"
"testing"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/to"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient"
)
func TestGetNotFound(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic1"
response := &http.Response{
StatusCode: http.StatusNotFound,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
nicClient := getTestInterfaceClient(armClient)
expected := network.Interface{Response: autorest.Response{}}
result, rerr := nicClient.Get(context.TODO(), "rg", "nic1", "")
assert.Equal(t, expected, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusNotFound, rerr.HTTPStatusCode)
}
func TestGetInternalError(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/nic1"
response := &http.Response{
StatusCode: http.StatusInternalServerError,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
nicClient := getTestInterfaceClient(armClient)
expected := network.Interface{Response: autorest.Response{}}
result, rerr := nicClient.Get(context.TODO(), "rg", "nic1", "")
assert.Equal(t, expected, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusInternalServerError, rerr.HTTPStatusCode)
}
func TestGetVirtualMachineScaleSetNetworkInterface(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Compute/virtualMachineScaleSets/vmss/virtualMachines/0/networkInterfaces/nic1"
testInterface := getTestVMSSInterface("nic1")
networkInterface, err := testInterface.MarshalJSON()
assert.Nil(t, err)
response := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(networkInterface)),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResourceWithDecorators(gomock.Any(), resourceID, gomock.Any()).Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
nicClient := getTestInterfaceClient(armClient)
expected := getTestVMSSInterface("nic1")
expected.Response = autorest.Response{Response: response}
result, rerr := nicClient.GetVirtualMachineScaleSetNetworkInterface(context.TODO(), "rg", "vmss", "0", "nic1", "")
assert.Equal(t, expected, result)
assert.Nil(t, rerr)
}
func TestCreateOrUpdate(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
testInterface := getTestInterface("nic1")
armClient := mockarmclient.NewMockInterface(ctrl)
response := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}
armClient.EXPECT().PutResource(gomock.Any(), to.String(testInterface.ID), testInterface).Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
nicClient := getTestInterfaceClient(armClient)
rerr := nicClient.CreateOrUpdate(context.TODO(), "rg", "nic1", testInterface)
assert.Nil(t, rerr)
}
func getTestInterface(name string) network.Interface {
resourceID := fmt.Sprintf("/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/networkInterfaces/%s", name)
return network.Interface{
ID: to.StringPtr(resourceID),
Name: to.StringPtr(name),
Location: to.StringPtr("eastus"),
}
}
func getTestVMSSInterface(name string) network.Interface {
resourceID := fmt.Sprintf("/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Compute/virtualMachineScaleSets/vmss/virtualMachines/0/networkInterfaces/%s", name)
return network.Interface{
ID: to.StringPtr(resourceID),
Location: to.StringPtr("eastus"),
InterfacePropertiesFormat: &network.InterfacePropertiesFormat{
Primary: to.BoolPtr(true),
},
}
}
func getTestInterfaceClient(armClient armclient.Interface) *Client {
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(&azclients.RateLimitConfig{})
return &Client{
armClient: armClient,
subscriptionID: "subscriptionID",
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
}
}

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 interfaceclient implements the client for network interfaces.
package interfaceclient // import "k8s.io/legacy-cloud-providers/azure/clients/interfaceclient"

View File

@@ -0,0 +1,48 @@
// +build !providerless
/*
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 interfaceclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
"k8s.io/legacy-cloud-providers/azure/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2019-06-01"
// ComputeAPIVersion is the API version for compute. It is required to get VMSS network interface.
ComputeAPIVersion = "2017-03-30"
)
// Interface is the client interface for NetworkInterface.
// Don't forget to run the following command to generate the mock client:
// mockgen -source=$GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/interfaceclient/interface.go -package=mockinterfaceclient Interface > $GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/interfaceclient/mockinterfaceclient/interface.go
type Interface interface {
// Get gets a network.Interface.
Get(ctx context.Context, resourceGroupName string, networkInterfaceName string, expand string) (result network.Interface, rerr *retry.Error)
// GetVirtualMachineScaleSetNetworkInterface gets a network.Interface of VMSS VM.
GetVirtualMachineScaleSetNetworkInterface(ctx context.Context, resourceGroupName string, virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string, expand string) (result network.Interface, rerr *retry.Error)
// CreateOrUpdate creates or updates a network.Interface.
CreateOrUpdate(ctx context.Context, resourceGroupName string, networkInterfaceName string, parameters network.Interface) *retry.Error
}

View File

@@ -0,0 +1,31 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/interfaceclient/mockinterfaceclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/interfaceclient/mockinterfaceclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 mockinterfaceclient implements the mock client for network interfaces.
package mockinterfaceclient // import "k8s.io/legacy-cloud-providers/azure/clients/interfaceclient/mockinterfaceclient"

View File

@@ -0,0 +1,95 @@
// +build !providerless
/*
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 mockinterfaceclient
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
gomock "github.com/golang/mock/gomock"
retry "k8s.io/legacy-cloud-providers/azure/retry"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// Get mocks base method
func (m *MockInterface) Get(ctx context.Context, resourceGroupName, networkInterfaceName, expand string) (network.Interface, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", ctx, resourceGroupName, networkInterfaceName, expand)
ret0, _ := ret[0].(network.Interface)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// Get indicates an expected call of Get
func (mr *MockInterfaceMockRecorder) Get(ctx, resourceGroupName, networkInterfaceName, expand interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), ctx, resourceGroupName, networkInterfaceName, expand)
}
// GetVirtualMachineScaleSetNetworkInterface mocks base method
func (m *MockInterface) GetVirtualMachineScaleSetNetworkInterface(ctx context.Context, resourceGroupName, virtualMachineScaleSetName, virtualmachineIndex, networkInterfaceName, expand string) (network.Interface, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetVirtualMachineScaleSetNetworkInterface", ctx, resourceGroupName, virtualMachineScaleSetName, virtualmachineIndex, networkInterfaceName, expand)
ret0, _ := ret[0].(network.Interface)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// GetVirtualMachineScaleSetNetworkInterface indicates an expected call of GetVirtualMachineScaleSetNetworkInterface
func (mr *MockInterfaceMockRecorder) GetVirtualMachineScaleSetNetworkInterface(ctx, resourceGroupName, virtualMachineScaleSetName, virtualmachineIndex, networkInterfaceName, expand interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVirtualMachineScaleSetNetworkInterface", reflect.TypeOf((*MockInterface)(nil).GetVirtualMachineScaleSetNetworkInterface), ctx, resourceGroupName, virtualMachineScaleSetName, virtualmachineIndex, networkInterfaceName, expand)
}
// CreateOrUpdate mocks base method
func (m *MockInterface) CreateOrUpdate(ctx context.Context, resourceGroupName, networkInterfaceName string, parameters network.Interface) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroupName, networkInterfaceName, parameters)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// CreateOrUpdate indicates an expected call of CreateOrUpdate
func (mr *MockInterfaceMockRecorder) CreateOrUpdate(ctx, resourceGroupName, networkInterfaceName, parameters interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockInterface)(nil).CreateOrUpdate), ctx, resourceGroupName, networkInterfaceName, parameters)
}

View File

@@ -0,0 +1,58 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"azure_loadbalancerclient.go",
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/metrics:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["azure_loadbalancerclient_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient/mockloadbalancerclient:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,412 @@
// +build !providerless
/*
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"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-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"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/metrics"
"k8s.io/legacy-cloud-providers/azure/retry"
)
var _ Interface = &Client{}
// Client implements LoadBalancer 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 LoadBalancer client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := autorest.NewBearerAuthorizer(config.ServicePrincipalToken)
armClient := armclient.New(authorizer, baseURI, "", APIVersion, config.Location, config.Backoff)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(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,
}
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.Error())
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.Error())
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 page.NotDone() {
result = append(result, *page.Response().Value...)
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.Error())
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.Error())
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
}

View File

@@ -0,0 +1,139 @@
// +build !providerless
/*
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 (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"testing"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/to"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient"
)
func TestGetNotFound(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/lb1"
response := &http.Response{
StatusCode: http.StatusNotFound,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
lbClient := getTestLoadBalancerClient(armClient)
expected := network.LoadBalancer{Response: autorest.Response{}}
result, rerr := lbClient.Get(context.TODO(), "rg", "lb1", "")
assert.Equal(t, expected, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusNotFound, rerr.HTTPStatusCode)
}
func TestGetInternalError(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/lb1"
response := &http.Response{
StatusCode: http.StatusInternalServerError,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
lbClient := getTestLoadBalancerClient(armClient)
expected := network.LoadBalancer{Response: autorest.Response{}}
result, rerr := lbClient.Get(context.TODO(), "rg", "lb1", "")
assert.Equal(t, expected, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusInternalServerError, rerr.HTTPStatusCode)
}
func TestList(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/loadBalancers"
armClient := mockarmclient.NewMockInterface(ctrl)
lbList := []network.LoadBalancer{getTestLoadBalancer("lb1"), getTestLoadBalancer("lb2"), getTestLoadBalancer("lb3")}
responseBody, err := json.Marshal(network.LoadBalancerListResult{Value: &lbList})
assert.Nil(t, err)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(
&http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(responseBody)),
}, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
lbClient := getTestLoadBalancerClient(armClient)
result, rerr := lbClient.List(context.TODO(), "rg")
assert.Nil(t, rerr)
assert.Equal(t, 3, len(result))
}
func TestCreateOrUpdate(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
lb := getTestLoadBalancer("lb1")
armClient := mockarmclient.NewMockInterface(ctrl)
response := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}
armClient.EXPECT().PutResourceWithDecorators(gomock.Any(), to.String(lb.ID), lb, gomock.Any()).Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
lbClient := getTestLoadBalancerClient(armClient)
rerr := lbClient.CreateOrUpdate(context.TODO(), "rg", "lb1", lb, "")
assert.Nil(t, rerr)
}
func getTestLoadBalancer(name string) network.LoadBalancer {
return network.LoadBalancer{
ID: to.StringPtr(fmt.Sprintf("/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/loadBalancers/%s", name)),
Name: to.StringPtr(name),
Location: to.StringPtr("eastus"),
}
}
func getTestLoadBalancerClient(armClient armclient.Interface) *Client {
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(&azclients.RateLimitConfig{})
return &Client{
armClient: armClient,
subscriptionID: "subscriptionID",
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
}
}

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 "k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient"

View File

@@ -0,0 +1,48 @@
// +build !providerless
/*
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/2019-06-01/network"
"k8s.io/legacy-cloud-providers/azure/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2019-06-01"
)
// Interface is the client interface for LoadBalancer.
// Don't forget to run the following command to generate the mock client:
// mockgen -source=$GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient/interface.go -package=mockloadbalancerclient Interface > $GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient/mockloadbalancerclient/interface.go
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
// Delete deletes a LoadBalancer by name.
Delete(ctx context.Context, resourceGroupName string, loadBalancerName string) *retry.Error
}

View File

@@ -0,0 +1,31 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient/mockloadbalancerclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient/mockloadbalancerclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 "k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient/mockloadbalancerclient"

View File

@@ -0,0 +1,109 @@
// +build !providerless
/*
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
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
gomock "github.com/golang/mock/gomock"
retry "k8s.io/legacy-cloud-providers/azure/retry"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// 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)
}
// 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,58 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"azure_publicipclient.go",
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/publicipclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/publicipclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/metrics:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["azure_publicipclient_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/publicipclient/mockpublicipclient:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,478 @@
// +build !providerless
/*
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"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-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"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/metrics"
"k8s.io/legacy-cloud-providers/azure/retry"
)
var _ Interface = &Client{}
// Client implements PublicIPAddress 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 PublicIPAddress client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := autorest.NewBearerAuthorizer(config.ServicePrincipalToken)
armClient := armclient.New(authorizer, baseURI, "", APIVersion, config.Location, config.Backoff)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(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,
}
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.Error())
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.Error())
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{}
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.Error())
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 page.NotDone() {
result = append(result, *page.Response().Value...)
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.Error())
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.Error())
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
}

View File

@@ -0,0 +1,152 @@
// +build !providerless
/*
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 (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"testing"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/to"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient"
)
func TestGetNotFound(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip1"
response := &http.Response{
StatusCode: http.StatusNotFound,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
pipClient := getTestPublicIPAddressClient(armClient)
expected := network.PublicIPAddress{Response: autorest.Response{}}
result, rerr := pipClient.Get(context.TODO(), "rg", "pip1", "")
assert.Equal(t, expected, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusNotFound, rerr.HTTPStatusCode)
}
func TestGetInternalError(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/pip1"
response := &http.Response{
StatusCode: http.StatusInternalServerError,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
pipClient := getTestPublicIPAddressClient(armClient)
expected := network.PublicIPAddress{Response: autorest.Response{}}
result, rerr := pipClient.Get(context.TODO(), "rg", "pip1", "")
assert.Equal(t, expected, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusInternalServerError, rerr.HTTPStatusCode)
}
func TestList(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses"
armClient := mockarmclient.NewMockInterface(ctrl)
pipList := []network.PublicIPAddress{getTestPublicIPAddress("pip1"), getTestPublicIPAddress("pip2"), getTestPublicIPAddress("pip3")}
responseBody, err := json.Marshal(network.PublicIPAddressListResult{Value: &pipList})
assert.Nil(t, err)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(
&http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(responseBody)),
}, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
pipClient := getTestPublicIPAddressClient(armClient)
result, rerr := pipClient.List(context.TODO(), "rg")
assert.Nil(t, rerr)
assert.Equal(t, 3, len(result))
}
func TestCreateOrUpdate(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
pip := getTestPublicIPAddress("pip1")
armClient := mockarmclient.NewMockInterface(ctrl)
response := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}
armClient.EXPECT().PutResource(gomock.Any(), to.String(pip.ID), pip).Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
pipClient := getTestPublicIPAddressClient(armClient)
rerr := pipClient.CreateOrUpdate(context.TODO(), "rg", "pip1", pip)
assert.Nil(t, rerr)
}
func TestDelete(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
pip := getTestPublicIPAddress("pip1")
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().DeleteResource(gomock.Any(), to.String(pip.ID), "").Return(nil).Times(1)
pipClient := getTestPublicIPAddressClient(armClient)
rerr := pipClient.Delete(context.TODO(), "rg", "pip1")
assert.Nil(t, rerr)
}
func getTestPublicIPAddress(name string) network.PublicIPAddress {
return network.PublicIPAddress{
ID: to.StringPtr(fmt.Sprintf("/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/publicIPAddresses/%s", name)),
Name: to.StringPtr(name),
Location: to.StringPtr("eastus"),
}
}
func getTestPublicIPAddressClient(armClient armclient.Interface) *Client {
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(&azclients.RateLimitConfig{})
return &Client{
armClient: armClient,
subscriptionID: "subscriptionID",
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
}
}

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package publicipclient implements the client for PublicIPAddress.
package publicipclient // import "k8s.io/legacy-cloud-providers/azure/clients/publicipclient"

View File

@@ -0,0 +1,54 @@
// +build !providerless
/*
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/2019-06-01/network"
"k8s.io/legacy-cloud-providers/azure/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2019-06-01"
// ComputeAPIVersion is the API version for compute. It is required to get VMSS public IP.
ComputeAPIVersion = "2017-03-30"
)
// Interface is the client interface for PublicIPAddress.
// Don't forget to run the following command to generate the mock client:
// mockgen -source=$GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/publicipclient/interface.go -package=mockpublicipclient Interface > $GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/publicipclient/mockpublicipclient/interface.go
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)
// 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,31 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/publicipclient/mockpublicipclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/publicipclient/mockpublicipclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 "k8s.io/legacy-cloud-providers/azure/clients/publicipclient/mockpublicipclient"

View File

@@ -0,0 +1,124 @@
// +build !providerless
/*
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
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
gomock "github.com/golang/mock/gomock"
retry "k8s.io/legacy-cloud-providers/azure/retry"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// 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)
}
// 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,56 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"azure_routeclient.go",
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/routeclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/routeclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/metrics:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["azure_routeclient_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient/mockrouteclient:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,198 @@
// +build !providerless
/*
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"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/metrics"
"k8s.io/legacy-cloud-providers/azure/retry"
)
var _ Interface = &Client{}
// Client implements Route 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 Route client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := autorest.NewBearerAuthorizer(config.ServicePrincipalToken)
armClient := armclient.New(authorizer, baseURI, "", APIVersion, config.Location, config.Backoff)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(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,
}
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.Error())
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.Error())
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,85 @@
// +build !providerless
/*
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 (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/http"
"testing"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
"github.com/Azure/go-autorest/autorest/to"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient"
)
func TestCreateOrUpdate(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
r := getTestRoute("r1")
armClient := mockarmclient.NewMockInterface(ctrl)
response := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}
armClient.EXPECT().PutResourceWithDecorators(gomock.Any(), to.String(r.ID), r, gomock.Any()).Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
rtClient := getTestRouteClient(armClient)
rerr := rtClient.CreateOrUpdate(context.TODO(), "rg", "rt", "r1", r, "")
assert.Nil(t, rerr)
}
func TestDelete(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
r := getTestRoute("r1")
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().DeleteResource(gomock.Any(), to.String(r.ID), "").Return(nil).Times(1)
rtClient := getTestRouteClient(armClient)
rerr := rtClient.Delete(context.TODO(), "rg", "rt", "r1")
assert.Nil(t, rerr)
}
func getTestRoute(name string) network.Route {
return network.Route{
ID: to.StringPtr(fmt.Sprintf("/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/routeTables/rt/routes/%s", name)),
Name: to.StringPtr(name),
}
}
func getTestRouteClient(armClient armclient.Interface) *Client {
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(&azclients.RateLimitConfig{})
return &Client{
armClient: armClient,
subscriptionID: "subscriptionID",
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
}
}

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 "k8s.io/legacy-cloud-providers/azure/clients/routeclient"

View File

@@ -0,0 +1,42 @@
// +build !providerless
/*
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/2019-06-01/network"
"k8s.io/legacy-cloud-providers/azure/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2019-06-01"
)
// Interface is the client interface for Route.
// Don't forget to run the following command to generate the mock client:
// mockgen -source=$GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient/interface.go -package=mockrouteclient Interface > $GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/routeclient/mockrouteclient/interface.go
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,31 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/routeclient/mockrouteclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/routeclient/mockrouteclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 "k8s.io/legacy-cloud-providers/azure/clients/routeclient/mockrouteclient"

View File

@@ -0,0 +1,79 @@
// +build !providerless
/*
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
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
gomock "github.com/golang/mock/gomock"
retry "k8s.io/legacy-cloud-providers/azure/retry"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// 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,57 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"azure_routetableclient.go",
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/routetableclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/routetableclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/metrics:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["azure_routetableclient_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/routetableclient/mockroutetableclient:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,212 @@
// +build !providerless
/*
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"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"k8s.io/client-go/util/flowcontrol"
"k8s.io/klog"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/metrics"
"k8s.io/legacy-cloud-providers/azure/retry"
)
var _ Interface = &Client{}
// Client implements RouteTable 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 RouteTable client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := autorest.NewBearerAuthorizer(config.ServicePrincipalToken)
armClient := armclient.New(authorizer, baseURI, "", APIVersion, config.Location, config.Backoff)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(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,
}
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.Error())
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.Error())
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,116 @@
// +build !providerless
/*
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 (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/http"
"testing"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/to"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient"
)
func TestGetNotFound(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/routeTables/rt1"
response := &http.Response{
StatusCode: http.StatusNotFound,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
rtClient := getTestRouteTableClient(armClient)
expected := network.RouteTable{Response: autorest.Response{}}
result, rerr := rtClient.Get(context.TODO(), "rg", "rt1", "")
assert.Equal(t, expected, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusNotFound, rerr.HTTPStatusCode)
}
func TestGetInternalError(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/routeTables/rt1"
response := &http.Response{
StatusCode: http.StatusInternalServerError,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
rtClient := getTestRouteTableClient(armClient)
expected := network.RouteTable{Response: autorest.Response{}}
result, rerr := rtClient.Get(context.TODO(), "rg", "rt1", "")
assert.Equal(t, expected, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusInternalServerError, rerr.HTTPStatusCode)
}
func TestCreateOrUpdate(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
lb := getTestRouteTable("rt1")
armClient := mockarmclient.NewMockInterface(ctrl)
response := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}
armClient.EXPECT().PutResourceWithDecorators(gomock.Any(), to.String(lb.ID), lb, gomock.Any()).Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
rtClient := getTestRouteTableClient(armClient)
rerr := rtClient.CreateOrUpdate(context.TODO(), "rg", "rt1", lb, "")
assert.Nil(t, rerr)
}
func getTestRouteTable(name string) network.RouteTable {
return network.RouteTable{
ID: to.StringPtr(fmt.Sprintf("/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/routeTables/%s", name)),
Name: to.StringPtr(name),
Location: to.StringPtr("eastus"),
}
}
func getTestRouteTableClient(armClient armclient.Interface) *Client {
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(&azclients.RateLimitConfig{})
return &Client{
armClient: armClient,
subscriptionID: "subscriptionID",
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
}
}

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 "k8s.io/legacy-cloud-providers/azure/clients/routetableclient"

View File

@@ -0,0 +1,42 @@
// +build !providerless
/*
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/2019-06-01/network"
"k8s.io/legacy-cloud-providers/azure/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2019-06-01"
)
// Interface is the client interface for RouteTable.
// Don't forget to run the following command to generate the mock client:
// mockgen -source=$GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/routetableclient/interface.go -package=mockroutetableclient Interface > $GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/routetableclient/mockroutetableclient/interface.go
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,31 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/routetableclient/mockroutetableclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/routetableclient/mockroutetableclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 "k8s.io/legacy-cloud-providers/azure/clients/routetableclient/mockroutetableclient"

View File

@@ -0,0 +1,80 @@
// +build !providerless
/*
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
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
gomock "github.com/golang/mock/gomock"
retry "k8s.io/legacy-cloud-providers/azure/retry"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// 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,58 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"azure_securitygroupclient.go",
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/metrics:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["azure_securitygroupclient_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient/mocksecuritygroupclient:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,412 @@
// +build !providerless
/*
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"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-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"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/metrics"
"k8s.io/legacy-cloud-providers/azure/retry"
)
var _ Interface = &Client{}
// Client implements SecurityGroup 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 SecurityGroup client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := autorest.NewBearerAuthorizer(config.ServicePrincipalToken)
armClient := armclient.New(authorizer, baseURI, "", APIVersion, config.Location, config.Backoff)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(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,
}
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.Error())
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.Error())
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 page.NotDone() {
result = append(result, *page.Response().Value...)
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.Error())
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.Error())
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 PublicIPAddress 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,152 @@
// +build !providerless
/*
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 (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"testing"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/to"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient"
)
func TestGetNotFound(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/networkSecurityGroups/nsg1"
response := &http.Response{
StatusCode: http.StatusNotFound,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
nsgClient := getTestSecurityGroupClient(armClient)
expected := network.SecurityGroup{Response: autorest.Response{}}
result, rerr := nsgClient.Get(context.TODO(), "rg", "nsg1", "")
assert.Equal(t, expected, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusNotFound, rerr.HTTPStatusCode)
}
func TestGetInternalError(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/networkSecurityGroups/nsg1"
response := &http.Response{
StatusCode: http.StatusInternalServerError,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
nsgClient := getTestSecurityGroupClient(armClient)
expected := network.SecurityGroup{Response: autorest.Response{}}
result, rerr := nsgClient.Get(context.TODO(), "rg", "nsg1", "")
assert.Equal(t, expected, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusInternalServerError, rerr.HTTPStatusCode)
}
func TestList(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/networkSecurityGroups"
armClient := mockarmclient.NewMockInterface(ctrl)
nsgList := []network.SecurityGroup{getTestSecurityGroup("nsg1"), getTestSecurityGroup("nsg2"), getTestSecurityGroup("nsg3")}
responseBody, err := json.Marshal(network.SecurityGroupListResult{Value: &nsgList})
assert.Nil(t, err)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(
&http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(responseBody)),
}, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
nsgClient := getTestSecurityGroupClient(armClient)
result, rerr := nsgClient.List(context.TODO(), "rg")
assert.Nil(t, rerr)
assert.Equal(t, 3, len(result))
}
func TestCreateOrUpdate(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
nsg := getTestSecurityGroup("nsg1")
armClient := mockarmclient.NewMockInterface(ctrl)
response := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}
armClient.EXPECT().PutResourceWithDecorators(gomock.Any(), to.String(nsg.ID), nsg, gomock.Any()).Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
nsgClient := getTestSecurityGroupClient(armClient)
rerr := nsgClient.CreateOrUpdate(context.TODO(), "rg", "nsg1", nsg, "")
assert.Nil(t, rerr)
}
func TestDelete(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
r := getTestSecurityGroup("nsg1")
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().DeleteResource(gomock.Any(), to.String(r.ID), "").Return(nil).Times(1)
rtClient := getTestSecurityGroupClient(armClient)
rerr := rtClient.Delete(context.TODO(), "rg", "nsg1")
assert.Nil(t, rerr)
}
func getTestSecurityGroup(name string) network.SecurityGroup {
return network.SecurityGroup{
ID: to.StringPtr(fmt.Sprintf("/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/networkSecurityGroups/%s", name)),
Name: to.StringPtr(name),
Location: to.StringPtr("eastus"),
}
}
func getTestSecurityGroupClient(armClient armclient.Interface) *Client {
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(&azclients.RateLimitConfig{})
return &Client{
armClient: armClient,
subscriptionID: "subscriptionID",
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
}
}

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 "k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient"

View File

@@ -0,0 +1,48 @@
// +build !providerless
/*
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/2019-06-01/network"
"k8s.io/legacy-cloud-providers/azure/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2019-06-01"
)
// Interface is the client interface for SecurityGroups.
// Don't forget to run the following command to generate the mock client:
// mockgen -source=$GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient/interface.go -package=mocksecuritygroupclient Interface > $GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient/mocksecuritygroupclient/interface.go
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,31 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient/mocksecuritygroupclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient/mocksecuritygroupclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 "k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient/mocksecuritygroupclient"

View File

@@ -0,0 +1,109 @@
// +build !providerless
/*
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
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
gomock "github.com/golang/mock/gomock"
retry "k8s.io/legacy-cloud-providers/azure/retry"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// 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,58 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"azure_subnetclient.go",
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/subnetclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/subnetclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/metrics:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["azure_subnetclient_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/subnetclient/mocksubnetclient:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,412 @@
// +build !providerless
/*
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"
"time"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-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"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/metrics"
"k8s.io/legacy-cloud-providers/azure/retry"
)
var _ Interface = &Client{}
// Client implements Subnet 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 Subnet client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := autorest.NewBearerAuthorizer(config.ServicePrincipalToken)
armClient := armclient.New(authorizer, baseURI, "", APIVersion, config.Location, config.Backoff)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(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,
}
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.Error())
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.Error())
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 page.NotDone() {
result = append(result, *page.Response().Value...)
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.Error())
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.Error())
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,151 @@
// +build !providerless
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package subnetclient
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"testing"
"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/to"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient"
)
func TestGetNotFound(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet/subnets/subnet1"
response := &http.Response{
StatusCode: http.StatusNotFound,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
subnetClient := getTestSubnetClient(armClient)
expected := network.Subnet{Response: autorest.Response{}}
result, rerr := subnetClient.Get(context.TODO(), "rg", "vnet", "subnet1", "")
assert.Equal(t, expected, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusNotFound, rerr.HTTPStatusCode)
}
func TestGetInternalError(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet/subnets/subnet1"
response := &http.Response{
StatusCode: http.StatusInternalServerError,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
subnetClient := getTestSubnetClient(armClient)
expected := network.Subnet{Response: autorest.Response{}}
result, rerr := subnetClient.Get(context.TODO(), "rg", "vnet", "subnet1", "")
assert.Equal(t, expected, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusInternalServerError, rerr.HTTPStatusCode)
}
func TestList(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet/subnets"
armClient := mockarmclient.NewMockInterface(ctrl)
subnetList := []network.Subnet{getTestSubnet("subnet1"), getTestSubnet("subnet2"), getTestSubnet("subnet3")}
responseBody, err := json.Marshal(network.SubnetListResult{Value: &subnetList})
assert.Nil(t, err)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(
&http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(responseBody)),
}, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
subnetClient := getTestSubnetClient(armClient)
result, rerr := subnetClient.List(context.TODO(), "rg", "vnet")
assert.Nil(t, rerr)
assert.Equal(t, 3, len(result))
}
func TestCreateOrUpdate(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
subnet := getTestSubnet("subnet1")
armClient := mockarmclient.NewMockInterface(ctrl)
response := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}
armClient.EXPECT().PutResource(gomock.Any(), to.String(subnet.ID), subnet).Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
subnetClient := getTestSubnetClient(armClient)
rerr := subnetClient.CreateOrUpdate(context.TODO(), "rg", "vnet", "subnet1", subnet)
assert.Nil(t, rerr)
}
func TestDelete(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
r := getTestSubnet("subnet1")
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().DeleteResource(gomock.Any(), to.String(r.ID), "").Return(nil).Times(1)
subnetClient := getTestSubnetClient(armClient)
rerr := subnetClient.Delete(context.TODO(), "rg", "vnet", "subnet1")
assert.Nil(t, rerr)
}
func getTestSubnet(name string) network.Subnet {
return network.Subnet{
ID: to.StringPtr(fmt.Sprintf("/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/vnet/subnets/%s", name)),
Name: to.StringPtr(name),
}
}
func getTestSubnetClient(armClient armclient.Interface) *Client {
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(&azclients.RateLimitConfig{})
return &Client{
armClient: armClient,
subscriptionID: "subscriptionID",
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
}
}

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 "k8s.io/legacy-cloud-providers/azure/clients/subnetclient"

View File

@@ -0,0 +1,48 @@
// +build !providerless
/*
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/2019-06-01/network"
"k8s.io/legacy-cloud-providers/azure/retry"
)
const (
// APIVersion is the API version for network.
APIVersion = "2019-06-01"
)
// Interface is the client interface for Subnet.
// Don't forget to run the following command to generate the mock client:
// mockgen -source=$GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/subnetclient/interface.go -package=mocksubnetclient Interface > $GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/subnetclient/mocksubnetclient/interface.go
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,31 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/subnetclient/mocksubnetclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/subnetclient/mocksubnetclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 "k8s.io/legacy-cloud-providers/azure/clients/subnetclient/mocksubnetclient"

View File

@@ -0,0 +1,109 @@
// +build !providerless
/*
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
import (
context "context"
reflect "reflect"
network "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
gomock "github.com/golang/mock/gomock"
retry "k8s.io/legacy-cloud-providers/azure/retry"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// 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,58 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"azure_vmclient.go",
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/vmclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/vmclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/client-go/util/flowcontrol:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/metrics:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["azure_vmclient_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient:go_default_library",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/legacy-cloud-providers/azure/clients/vmclient/mockvmclient:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,436 @@
// +build !providerless
/*
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 vmclient
import (
"context"
"fmt"
"net/http"
"time"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-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"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/metrics"
"k8s.io/legacy-cloud-providers/azure/retry"
)
var _ Interface = &Client{}
// Client implements VirtualMachine 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 VirtualMachine client with ratelimiting.
func New(config *azclients.ClientConfig) *Client {
baseURI := config.ResourceManagerEndpoint
authorizer := autorest.NewBearerAuthorizer(config.ServicePrincipalToken)
armClient := armclient.New(authorizer, baseURI, "", APIVersion, config.Location, config.Backoff)
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
klog.V(2).Infof("Azure VirtualMachine client (read ops) using rate limit config: QPS=%g, bucket=%d",
config.RateLimitConfig.CloudProviderRateLimitQPS,
config.RateLimitConfig.CloudProviderRateLimitBucket)
klog.V(2).Infof("Azure VirtualMachine client (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 VirtualMachine.
func (c *Client) Get(ctx context.Context, resourceGroupName string, VMName string, expand compute.InstanceViewTypes) (compute.VirtualMachine, *retry.Error) {
mc := metrics.NewMetricContext("vm", "get", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return compute.VirtualMachine{}, retry.GetRateLimitError(false, "VMGet")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("VMGet", "client throttled", c.RetryAfterReader)
return compute.VirtualMachine{}, rerr
}
result, rerr := c.getVM(ctx, resourceGroupName, VMName, expand)
mc.Observe(rerr.Error())
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
}
// getVM gets a VirtualMachine.
func (c *Client) getVM(ctx context.Context, resourceGroupName string, VMName string, expand compute.InstanceViewTypes) (compute.VirtualMachine, *retry.Error) {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Compute/virtualMachines",
VMName,
)
result := compute.VirtualMachine{}
response, rerr := c.armClient.GetResource(ctx, resourceID, string(expand))
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vm.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", "vm.get.respond", resourceID, err)
return result, retry.GetError(response, err)
}
result.Response = autorest.Response{Response: response}
return result, nil
}
// List gets a list of VirtualMachine in the resourceGroupName.
func (c *Client) List(ctx context.Context, resourceGroupName string) ([]compute.VirtualMachine, *retry.Error) {
mc := metrics.NewMetricContext("vm", "list", resourceGroupName, c.subscriptionID, "")
// Report errors if the client is rate limited.
if !c.rateLimiterReader.TryAccept() {
mc.RateLimitedCount()
return nil, retry.GetRateLimitError(false, "VMList")
}
// Report errors if the client is throttled.
if c.RetryAfterReader.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("VMList", "client throttled", c.RetryAfterReader)
return nil, rerr
}
result, rerr := c.listVM(ctx, resourceGroupName)
mc.Observe(rerr.Error())
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
}
// listVM gets a list of VirtualMachines in the resourceGroupName.
func (c *Client) listVM(ctx context.Context, resourceGroupName string) ([]compute.VirtualMachine, *retry.Error) {
resourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines",
autorest.Encode("path", c.subscriptionID),
autorest.Encode("path", resourceGroupName),
)
result := make([]compute.VirtualMachine, 0)
page := &VirtualMachineListResultPage{}
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", "vm.list.request", resourceID, rerr.Error())
return result, rerr
}
var err error
page.vmlr, err = c.listResponder(resp)
if err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vm.list.respond", resourceID, err)
return result, retry.GetError(resp, err)
}
for page.NotDone() {
result = append(result, *page.Response().Value...)
if err = page.NextWithContext(ctx); err != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vm.list.next", resourceID, err)
return result, retry.GetError(page.Response().Response.Response, err)
}
}
return result, nil
}
// Update updates a VirtualMachine.
func (c *Client) Update(ctx context.Context, resourceGroupName string, VMName string, parameters compute.VirtualMachineUpdate, source string) *retry.Error {
mc := metrics.NewMetricContext("vm", "update", resourceGroupName, c.subscriptionID, source)
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "VMUpdate")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("VMUpdate", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.updateVM(ctx, resourceGroupName, VMName, parameters, source)
mc.Observe(rerr.Error())
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
}
// updateVM updates a VirtualMachine.
func (c *Client) updateVM(ctx context.Context, resourceGroupName string, VMName string, parameters compute.VirtualMachineUpdate, source string) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Compute/virtualMachines",
VMName,
)
response, rerr := c.armClient.PatchResource(ctx, resourceID, parameters)
defer c.armClient.CloseResponse(ctx, response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vm.put.request", resourceID, rerr.Error())
return rerr
}
if response != nil && response.StatusCode != http.StatusNoContent {
_, rerr = c.updateResponder(response)
if rerr != nil {
klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vm.put.respond", resourceID, rerr.Error())
return rerr
}
}
return nil
}
func (c *Client) updateResponder(resp *http.Response) (*compute.VirtualMachine, *retry.Error) {
result := &compute.VirtualMachine{}
err := autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return result, retry.GetError(resp, err)
}
func (c *Client) listResponder(resp *http.Response) (result compute.VirtualMachineListResult, err error) {
err = autorest.Respond(
resp,
autorest.ByIgnoring(),
azure.WithErrorUnlessStatusCode(http.StatusOK),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing(),
)
result.Response = autorest.Response{Response: resp}
return
}
// vmListResultPreparer prepares a request to retrieve the next set of results.
// It returns nil if no more results exist.
func (c *Client) vmListResultPreparer(ctx context.Context, vmlr compute.VirtualMachineListResult) (*http.Request, error) {
if vmlr.NextLink == nil || len(to.String(vmlr.NextLink)) < 1 {
return nil, nil
}
decorators := []autorest.PrepareDecorator{
autorest.WithBaseURL(to.String(vmlr.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.VirtualMachineListResult) (result compute.VirtualMachineListResult, err error) {
req, err := c.vmListResultPreparer(ctx, lastResults)
if err != nil {
return result, autorest.NewErrorWithError(err, "vmclient", "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(), "vmclient", "listNextResults", resp, "Failure sending next results request")
}
result, err = c.listResponder(resp)
if err != nil {
err = autorest.NewErrorWithError(err, "vmclient", "listNextResults", resp, "Failure responding to next results request")
}
return
}
// VirtualMachineListResultPage contains a page of VirtualMachine values.
type VirtualMachineListResultPage struct {
fn func(context.Context, compute.VirtualMachineListResult) (compute.VirtualMachineListResult, error)
vmlr compute.VirtualMachineListResult
}
// 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 *VirtualMachineListResultPage) NextWithContext(ctx context.Context) (err error) {
next, err := page.fn(ctx, page.vmlr)
if err != nil {
return err
}
page.vmlr = 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 *VirtualMachineListResultPage) Next() error {
return page.NextWithContext(context.Background())
}
// NotDone returns true if the page enumeration should be started or is not yet complete.
func (page VirtualMachineListResultPage) NotDone() bool {
return !page.vmlr.IsEmpty()
}
// Response returns the raw server response from the last page request.
func (page VirtualMachineListResultPage) Response() compute.VirtualMachineListResult {
return page.vmlr
}
// Values returns the slice of values for the current page or nil if there are no values.
func (page VirtualMachineListResultPage) Values() []compute.VirtualMachine {
if page.vmlr.IsEmpty() {
return nil
}
return *page.vmlr.Value
}
// CreateOrUpdate creates or updates a VirtualMachine.
func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, VMName string, parameters compute.VirtualMachine, source string) *retry.Error {
mc := metrics.NewMetricContext("vm", "create_or_update", resourceGroupName, c.subscriptionID, source)
// Report errors if the client is rate limited.
if !c.rateLimiterWriter.TryAccept() {
mc.RateLimitedCount()
return retry.GetRateLimitError(true, "VMCreateOrUpdate")
}
// Report errors if the client is throttled.
if c.RetryAfterWriter.After(time.Now()) {
mc.ThrottledCount()
rerr := retry.GetThrottlingError("VMCreateOrUpdate", "client throttled", c.RetryAfterWriter)
return rerr
}
rerr := c.createOrUpdateVM(ctx, resourceGroupName, VMName, parameters, source)
mc.Observe(rerr.Error())
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
}
// createOrUpdateVM creates or updates a VirtualMachine.
func (c *Client) createOrUpdateVM(ctx context.Context, resourceGroupName string, VMName string, parameters compute.VirtualMachine, source string) *retry.Error {
resourceID := armclient.GetResourceID(
c.subscriptionID,
resourceGroupName,
"Microsoft.Compute/virtualMachines",
VMName,
)
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", "vm.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", "vm.put.respond", resourceID, rerr.Error())
return rerr
}
}
return nil
}
func (c *Client) createOrUpdateResponder(resp *http.Response) (*compute.VirtualMachine, *retry.Error) {
result := &compute.VirtualMachine{}
err := autorest.Respond(
resp,
azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
autorest.ByUnmarshallingJSON(&result),
autorest.ByClosing())
result.Response = autorest.Response{Response: resp}
return result, retry.GetError(resp, err)
}

View File

@@ -0,0 +1,159 @@
// +build !providerless
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package vmclient
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"testing"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/to"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
azclients "k8s.io/legacy-cloud-providers/azure/clients"
"k8s.io/legacy-cloud-providers/azure/clients/armclient"
"k8s.io/legacy-cloud-providers/azure/clients/armclient/mockarmclient"
)
func TestGetNotFound(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/vm1"
response := &http.Response{
StatusCode: http.StatusNotFound,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "InstanceView").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
vmClient := getTestVMClient(armClient)
expectedVM := compute.VirtualMachine{Response: autorest.Response{}}
result, rerr := vmClient.Get(context.TODO(), "rg", "vm1", "InstanceView")
assert.Equal(t, expectedVM, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusNotFound, rerr.HTTPStatusCode)
}
func TestGetInternalError(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/vm1"
response := &http.Response{
StatusCode: http.StatusInternalServerError,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{}"))),
}
armClient := mockarmclient.NewMockInterface(ctrl)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "InstanceView").Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
vmClient := getTestVMClient(armClient)
expectedVM := compute.VirtualMachine{Response: autorest.Response{}}
result, rerr := vmClient.Get(context.TODO(), "rg", "vm1", "InstanceView")
assert.Equal(t, expectedVM, result)
assert.NotNil(t, rerr)
assert.Equal(t, http.StatusInternalServerError, rerr.HTTPStatusCode)
}
func TestList(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines"
armClient := mockarmclient.NewMockInterface(ctrl)
vmList := []compute.VirtualMachine{getTestVM("vm1"), getTestVM("vm1"), getTestVM("vm1")}
responseBody, err := json.Marshal(compute.VirtualMachineListResult{Value: &vmList})
assert.Nil(t, err)
armClient.EXPECT().GetResource(gomock.Any(), resourceID, "").Return(
&http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(responseBody)),
}, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
vmClient := getTestVMClient(armClient)
result, rerr := vmClient.List(context.TODO(), "rg")
assert.Nil(t, rerr)
assert.Equal(t, 3, len(result))
}
func TestUpdate(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
resourceID := "/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/vm1"
testVM := compute.VirtualMachineUpdate{}
armClient := mockarmclient.NewMockInterface(ctrl)
response := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}
armClient.EXPECT().PatchResource(gomock.Any(), resourceID, testVM).Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
vmClient := getTestVMClient(armClient)
rerr := vmClient.Update(context.TODO(), "rg", "vm1", testVM, "test")
assert.Nil(t, rerr)
}
func TestCreateOrUpdate(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
testVM := getTestVM("vm1")
armClient := mockarmclient.NewMockInterface(ctrl)
response := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}
armClient.EXPECT().PutResource(gomock.Any(), to.String(testVM.ID), testVM).Return(response, nil).Times(1)
armClient.EXPECT().CloseResponse(gomock.Any(), gomock.Any()).Times(1)
vmClient := getTestVMClient(armClient)
rerr := vmClient.CreateOrUpdate(context.TODO(), "rg", "vm1", testVM, "test")
assert.Nil(t, rerr)
}
func getTestVM(vmName string) compute.VirtualMachine {
resourceID := fmt.Sprintf("/subscriptions/subscriptionID/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/%s", vmName)
return compute.VirtualMachine{
ID: to.StringPtr(resourceID),
Name: to.StringPtr(vmName),
Location: to.StringPtr("eastus"),
}
}
func getTestVMClient(armClient armclient.Interface) *Client {
rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(&azclients.RateLimitConfig{})
return &Client{
armClient: armClient,
subscriptionID: "subscriptionID",
rateLimiterReader: rateLimiterReader,
rateLimiterWriter: rateLimiterWriter,
}
}

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 vmclient implements the client for VirtualMachines.
package vmclient // import "k8s.io/legacy-cloud-providers/azure/clients/vmclient"

View File

@@ -0,0 +1,48 @@
// +build !providerless
/*
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 vmclient
import (
"context"
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute"
"k8s.io/legacy-cloud-providers/azure/retry"
)
const (
// APIVersion is the API version for VirtualMachine.
APIVersion = "2019-07-01"
)
// Interface is the client interface for VirtualMachines.
// Don't forget to run the following command to generate the mock client:
// mockgen -source=$GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/vmclient/interface.go -package=mockvmclient Interface > $GOPATH/src/k8s.io/kubernetes/staging/src/k8s.io/legacy-cloud-providers/azure/clients/vmclient/mockvmclient/interface.go
type Interface interface {
// Get gets a VirtualMachine.
Get(ctx context.Context, resourceGroupName string, VMName string, expand compute.InstanceViewTypes) (compute.VirtualMachine, *retry.Error)
// List gets a list of VirtualMachines in the resourceGroupName.
List(ctx context.Context, resourceGroupName string) ([]compute.VirtualMachine, *retry.Error)
// CreateOrUpdate creates or updates a VirtualMachine.
CreateOrUpdate(ctx context.Context, resourceGroupName string, VMName string, parameters compute.VirtualMachine, source string) *retry.Error
// Update updates a VirtualMachine.
Update(ctx context.Context, resourceGroupName string, VMName string, parameters compute.VirtualMachineUpdate, source string) *retry.Error
}

View File

@@ -0,0 +1,31 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"interface.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/legacy-cloud-providers/azure/clients/vmclient/mockvmclient",
importpath = "k8s.io/legacy-cloud-providers/azure/clients/vmclient/mockvmclient",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/legacy-cloud-providers/azure/retry:go_default_library",
"//vendor/github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute:go_default_library",
"//vendor/github.com/golang/mock/gomock:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,20 @@
// +build !providerless
/*
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 mockvmclient implements the mock client for VirtualMachines.
package mockvmclient // import "k8s.io/legacy-cloud-providers/azure/clients/vmclient/mockvmclient"

View File

@@ -0,0 +1,109 @@
// +build !providerless
/*
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 mockvmclient
import (
context "context"
reflect "reflect"
compute "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-07-01/compute"
gomock "github.com/golang/mock/gomock"
retry "k8s.io/legacy-cloud-providers/azure/retry"
)
// MockInterface is a mock of Interface interface
type MockInterface struct {
ctrl *gomock.Controller
recorder *MockInterfaceMockRecorder
}
// MockInterfaceMockRecorder is the mock recorder for MockInterface
type MockInterfaceMockRecorder struct {
mock *MockInterface
}
// NewMockInterface creates a new mock instance
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
mock := &MockInterface{ctrl: ctrl}
mock.recorder = &MockInterfaceMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
return m.recorder
}
// Get mocks base method
func (m *MockInterface) Get(ctx context.Context, resourceGroupName, VMName string, expand compute.InstanceViewTypes) (compute.VirtualMachine, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", ctx, resourceGroupName, VMName, expand)
ret0, _ := ret[0].(compute.VirtualMachine)
ret1, _ := ret[1].(*retry.Error)
return ret0, ret1
}
// Get indicates an expected call of Get
func (mr *MockInterfaceMockRecorder) Get(ctx, resourceGroupName, VMName, expand interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), ctx, resourceGroupName, VMName, expand)
}
// List mocks base method
func (m *MockInterface) List(ctx context.Context, resourceGroupName string) ([]compute.VirtualMachine, *retry.Error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, resourceGroupName)
ret0, _ := ret[0].([]compute.VirtualMachine)
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, VMName string, parameters compute.VirtualMachine, source string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, resourceGroupName, VMName, parameters, source)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// CreateOrUpdate indicates an expected call of CreateOrUpdate
func (mr *MockInterfaceMockRecorder) CreateOrUpdate(ctx, resourceGroupName, VMName, parameters, source interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockInterface)(nil).CreateOrUpdate), ctx, resourceGroupName, VMName, parameters, source)
}
// Update mocks base method
func (m *MockInterface) Update(ctx context.Context, resourceGroupName, VMName string, parameters compute.VirtualMachineUpdate, source string) *retry.Error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, resourceGroupName, VMName, parameters, source)
ret0, _ := ret[0].(*retry.Error)
return ret0
}
// Update indicates an expected call of Update
func (mr *MockInterfaceMockRecorder) Update(ctx, resourceGroupName, VMName, parameters, source interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockInterface)(nil).Update), ctx, resourceGroupName, VMName, parameters, source)
}

View File

@@ -3,7 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"azure_vmssclientvm.go",
"azure_vmssvmclient.go",
"doc.go",
"interface.go",
],

8
vendor/modules.txt vendored
View File

@@ -1841,6 +1841,14 @@ k8s.io/legacy-cloud-providers/azure
k8s.io/legacy-cloud-providers/azure/auth
k8s.io/legacy-cloud-providers/azure/clients
k8s.io/legacy-cloud-providers/azure/clients/armclient
k8s.io/legacy-cloud-providers/azure/clients/interfaceclient
k8s.io/legacy-cloud-providers/azure/clients/loadbalancerclient
k8s.io/legacy-cloud-providers/azure/clients/publicipclient
k8s.io/legacy-cloud-providers/azure/clients/routeclient
k8s.io/legacy-cloud-providers/azure/clients/routetableclient
k8s.io/legacy-cloud-providers/azure/clients/securitygroupclient
k8s.io/legacy-cloud-providers/azure/clients/subnetclient
k8s.io/legacy-cloud-providers/azure/clients/vmclient
k8s.io/legacy-cloud-providers/azure/clients/vmssclient
k8s.io/legacy-cloud-providers/azure/clients/vmssvmclient
k8s.io/legacy-cloud-providers/azure/metrics