2016-05-29 03:54:26 +00:00
|
|
|
/*
|
|
|
|
Copyright 2016 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 azure
|
|
|
|
|
|
|
|
import (
|
2017-05-31 21:19:38 +00:00
|
|
|
"errors"
|
2016-05-29 03:54:26 +00:00
|
|
|
"fmt"
|
2017-07-13 11:55:32 +00:00
|
|
|
"hash/crc32"
|
2017-05-31 21:19:38 +00:00
|
|
|
"regexp"
|
2017-11-15 01:39:55 +00:00
|
|
|
"sort"
|
2017-07-13 11:55:32 +00:00
|
|
|
"strconv"
|
2016-05-29 03:54:26 +00:00
|
|
|
"strings"
|
|
|
|
|
2017-06-22 17:25:57 +00:00
|
|
|
"k8s.io/api/core/v1"
|
2016-05-29 03:54:26 +00:00
|
|
|
"k8s.io/kubernetes/pkg/cloudprovider"
|
|
|
|
|
|
|
|
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
|
|
|
"github.com/Azure/azure-sdk-for-go/arm/network"
|
2017-12-13 06:17:37 +00:00
|
|
|
"github.com/Azure/go-autorest/autorest/to"
|
2017-06-05 23:06:50 +00:00
|
|
|
"github.com/golang/glog"
|
2017-01-11 14:09:48 +00:00
|
|
|
"k8s.io/apimachinery/pkg/types"
|
2017-12-13 06:17:37 +00:00
|
|
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
2017-11-15 01:39:55 +00:00
|
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
2017-11-29 08:11:58 +00:00
|
|
|
"k8s.io/apimachinery/pkg/util/uuid"
|
2016-05-29 03:54:26 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
loadBalancerMinimumPriority = 500
|
|
|
|
loadBalancerMaximumPriority = 4096
|
|
|
|
|
2016-10-07 06:59:14 +00:00
|
|
|
machineIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s"
|
|
|
|
availabilitySetIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/availabilitySets/%s"
|
2016-05-29 03:54:26 +00:00
|
|
|
frontendIPConfigIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/frontendIPConfigurations/%s"
|
|
|
|
backendPoolIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/backendAddressPools/%s"
|
|
|
|
loadBalancerProbeIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/probes/%s"
|
2017-11-15 01:39:55 +00:00
|
|
|
|
|
|
|
// InternalLoadBalancerNameSuffix is load balancer posfix
|
|
|
|
InternalLoadBalancerNameSuffix = "-internal"
|
|
|
|
|
|
|
|
// nodeLabelRole specifies the role of a node
|
|
|
|
nodeLabelRole = "kubernetes.io/role"
|
2017-11-29 08:11:58 +00:00
|
|
|
|
|
|
|
storageAccountNameMaxLength = 24
|
2016-05-29 03:54:26 +00:00
|
|
|
)
|
|
|
|
|
2017-12-13 06:17:37 +00:00
|
|
|
var errNotInVMSet = errors.New("vm is not in the vmset")
|
2017-08-30 04:20:58 +00:00
|
|
|
var providerIDRE = regexp.MustCompile(`^` + CloudProviderName + `://(?:.*)/Microsoft.Compute/virtualMachines/(.+)$`)
|
2017-05-31 21:19:38 +00:00
|
|
|
|
2016-05-29 03:54:26 +00:00
|
|
|
// returns the full identifier of a machine
|
|
|
|
func (az *Cloud) getMachineID(machineName string) string {
|
|
|
|
return fmt.Sprintf(
|
2016-10-07 06:59:14 +00:00
|
|
|
machineIDTemplate,
|
2016-05-29 03:54:26 +00:00
|
|
|
az.SubscriptionID,
|
|
|
|
az.ResourceGroup,
|
|
|
|
machineName)
|
|
|
|
}
|
|
|
|
|
2016-10-07 06:59:14 +00:00
|
|
|
// returns the full identifier of an availabilitySet
|
|
|
|
func (az *Cloud) getAvailabilitySetID(availabilitySetName string) string {
|
|
|
|
return fmt.Sprintf(
|
|
|
|
availabilitySetIDTemplate,
|
|
|
|
az.SubscriptionID,
|
|
|
|
az.ResourceGroup,
|
|
|
|
availabilitySetName)
|
|
|
|
}
|
|
|
|
|
2016-05-29 03:54:26 +00:00
|
|
|
// returns the full identifier of a loadbalancer frontendipconfiguration.
|
|
|
|
func (az *Cloud) getFrontendIPConfigID(lbName, backendPoolName string) string {
|
|
|
|
return fmt.Sprintf(
|
|
|
|
frontendIPConfigIDTemplate,
|
|
|
|
az.SubscriptionID,
|
|
|
|
az.ResourceGroup,
|
|
|
|
lbName,
|
|
|
|
backendPoolName)
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns the full identifier of a loadbalancer backendpool.
|
|
|
|
func (az *Cloud) getBackendPoolID(lbName, backendPoolName string) string {
|
|
|
|
return fmt.Sprintf(
|
|
|
|
backendPoolIDTemplate,
|
|
|
|
az.SubscriptionID,
|
|
|
|
az.ResourceGroup,
|
|
|
|
lbName,
|
|
|
|
backendPoolName)
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns the full identifier of a loadbalancer probe.
|
|
|
|
func (az *Cloud) getLoadBalancerProbeID(lbName, lbRuleName string) string {
|
|
|
|
return fmt.Sprintf(
|
|
|
|
loadBalancerProbeIDTemplate,
|
|
|
|
az.SubscriptionID,
|
|
|
|
az.ResourceGroup,
|
|
|
|
lbName,
|
|
|
|
lbRuleName)
|
|
|
|
}
|
|
|
|
|
2017-12-13 06:17:37 +00:00
|
|
|
func (az *Cloud) mapLoadBalancerNameToVMSet(lbName string, clusterName string) (vmSetName string) {
|
|
|
|
vmSetName = strings.TrimSuffix(lbName, InternalLoadBalancerNameSuffix)
|
2018-02-12 07:56:57 +00:00
|
|
|
if strings.EqualFold(clusterName, vmSetName) {
|
2017-12-13 06:17:37 +00:00
|
|
|
vmSetName = az.vmSet.GetPrimaryVMSetName()
|
2017-11-15 01:39:55 +00:00
|
|
|
}
|
|
|
|
|
2017-12-13 06:17:37 +00:00
|
|
|
return vmSetName
|
2017-11-15 01:39:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// For a load balancer, all frontend ip should reference either a subnet or publicIpAddress.
|
|
|
|
// Thus Azure do not allow mixed type (public and internal) load balancer.
|
|
|
|
// So we'd have a separate name for internal load balancer.
|
|
|
|
// This would be the name for Azure LoadBalancer resource.
|
2017-12-13 06:17:37 +00:00
|
|
|
func (az *Cloud) getLoadBalancerName(clusterName string, vmSetName string, isInternal bool) string {
|
|
|
|
lbNamePrefix := vmSetName
|
|
|
|
if strings.EqualFold(vmSetName, az.vmSet.GetPrimaryVMSetName()) {
|
2017-11-15 01:39:55 +00:00
|
|
|
lbNamePrefix = clusterName
|
|
|
|
}
|
|
|
|
if isInternal {
|
|
|
|
return fmt.Sprintf("%s%s", lbNamePrefix, InternalLoadBalancerNameSuffix)
|
|
|
|
}
|
|
|
|
return lbNamePrefix
|
|
|
|
}
|
|
|
|
|
|
|
|
// isMasterNode returns returns true is the node has a master role label.
|
|
|
|
// The master role is determined by looking for:
|
|
|
|
// * a kubernetes.io/role="master" label
|
|
|
|
func isMasterNode(node *v1.Node) bool {
|
2017-11-15 20:52:59 +00:00
|
|
|
if val, ok := node.Labels[nodeLabelRole]; ok && val == "master" {
|
|
|
|
return true
|
2017-11-15 01:39:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-05-29 03:54:26 +00:00
|
|
|
// returns the deepest child's identifier from a full identifier string.
|
|
|
|
func getLastSegment(ID string) (string, error) {
|
|
|
|
parts := strings.Split(ID, "/")
|
|
|
|
name := parts[len(parts)-1]
|
|
|
|
if len(name) == 0 {
|
|
|
|
return "", fmt.Errorf("resource name was missing from identifier")
|
|
|
|
}
|
|
|
|
|
|
|
|
return name, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns the equivalent LoadBalancerRule, SecurityRule and LoadBalancerProbe
|
|
|
|
// protocol types for the given Kubernetes protocol type.
|
2017-05-08 22:02:41 +00:00
|
|
|
func getProtocolsFromKubernetesProtocol(protocol v1.Protocol) (*network.TransportProtocol, *network.SecurityRuleProtocol, *network.ProbeProtocol, error) {
|
|
|
|
var transportProto network.TransportProtocol
|
|
|
|
var securityProto network.SecurityRuleProtocol
|
|
|
|
var probeProto network.ProbeProtocol
|
|
|
|
|
2016-05-29 03:54:26 +00:00
|
|
|
switch protocol {
|
2016-11-18 20:58:42 +00:00
|
|
|
case v1.ProtocolTCP:
|
2017-05-08 22:02:41 +00:00
|
|
|
transportProto = network.TransportProtocolTCP
|
2017-06-09 05:20:01 +00:00
|
|
|
securityProto = network.SecurityRuleProtocolTCP
|
2017-05-08 22:02:41 +00:00
|
|
|
probeProto = network.ProbeProtocolTCP
|
2017-05-08 21:49:45 +00:00
|
|
|
return &transportProto, &securityProto, &probeProto, nil
|
2017-05-08 22:02:41 +00:00
|
|
|
case v1.ProtocolUDP:
|
|
|
|
transportProto = network.TransportProtocolUDP
|
2017-06-09 05:20:01 +00:00
|
|
|
securityProto = network.SecurityRuleProtocolUDP
|
2017-05-08 21:49:45 +00:00
|
|
|
return &transportProto, &securityProto, nil, nil
|
2016-05-29 03:54:26 +00:00
|
|
|
default:
|
2017-05-08 22:02:41 +00:00
|
|
|
return &transportProto, &securityProto, &probeProto, fmt.Errorf("Only TCP and UDP are supported for Azure LoadBalancers")
|
2016-05-29 03:54:26 +00:00
|
|
|
}
|
2017-05-08 22:02:41 +00:00
|
|
|
|
2016-05-29 03:54:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This returns the full identifier of the primary NIC for the given VM.
|
|
|
|
func getPrimaryInterfaceID(machine compute.VirtualMachine) (string, error) {
|
2016-12-07 07:16:20 +00:00
|
|
|
if len(*machine.NetworkProfile.NetworkInterfaces) == 1 {
|
|
|
|
return *(*machine.NetworkProfile.NetworkInterfaces)[0].ID, nil
|
2016-05-29 03:54:26 +00:00
|
|
|
}
|
|
|
|
|
2016-12-07 07:16:20 +00:00
|
|
|
for _, ref := range *machine.NetworkProfile.NetworkInterfaces {
|
|
|
|
if *ref.Primary {
|
2016-05-29 03:54:26 +00:00
|
|
|
return *ref.ID, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", fmt.Errorf("failed to find a primary nic for the vm. vmname=%q", *machine.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getPrimaryIPConfig(nic network.Interface) (*network.InterfaceIPConfiguration, error) {
|
2018-01-23 08:57:45 +00:00
|
|
|
if nic.IPConfigurations == nil {
|
|
|
|
return nil, fmt.Errorf("nic.IPConfigurations for nic (nicname=%q) is nil", *nic.Name)
|
|
|
|
}
|
|
|
|
|
2016-12-07 07:16:20 +00:00
|
|
|
if len(*nic.IPConfigurations) == 1 {
|
|
|
|
return &((*nic.IPConfigurations)[0]), nil
|
2016-05-29 03:54:26 +00:00
|
|
|
}
|
|
|
|
|
2016-12-07 07:16:20 +00:00
|
|
|
for _, ref := range *nic.IPConfigurations {
|
|
|
|
if *ref.Primary {
|
2016-11-15 20:59:40 +00:00
|
|
|
return &ref, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-23 08:57:45 +00:00
|
|
|
return nil, fmt.Errorf("failed to determine the primary ipconfig. nicname=%q", *nic.Name)
|
2016-05-29 03:54:26 +00:00
|
|
|
}
|
|
|
|
|
2017-11-15 01:39:55 +00:00
|
|
|
func isInternalLoadBalancer(lb *network.LoadBalancer) bool {
|
|
|
|
return strings.HasSuffix(*lb.Name, InternalLoadBalancerNameSuffix)
|
2016-05-29 03:54:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func getBackendPoolName(clusterName string) string {
|
|
|
|
return clusterName
|
|
|
|
}
|
|
|
|
|
2017-08-30 04:00:21 +00:00
|
|
|
func getLoadBalancerRuleName(service *v1.Service, port v1.ServicePort, subnetName *string) string {
|
|
|
|
if subnetName == nil {
|
|
|
|
return fmt.Sprintf("%s-%s-%d", getRulePrefix(service), port.Protocol, port.Port)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%s-%s-%s-%d", getRulePrefix(service), *subnetName, port.Protocol, port.Port)
|
2017-05-08 21:49:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func getSecurityRuleName(service *v1.Service, port v1.ServicePort, sourceAddrPrefix string) string {
|
2017-11-17 01:05:51 +00:00
|
|
|
if useSharedSecurityRule(service) {
|
|
|
|
safePrefix := strings.Replace(sourceAddrPrefix, "/", "_", -1)
|
|
|
|
return fmt.Sprintf("shared-%s-%d-%s", port.Protocol, port.Port, safePrefix)
|
|
|
|
}
|
2017-05-08 21:49:45 +00:00
|
|
|
safePrefix := strings.Replace(sourceAddrPrefix, "/", "_", -1)
|
|
|
|
return fmt.Sprintf("%s-%s-%d-%s", getRulePrefix(service), port.Protocol, port.Port, safePrefix)
|
2016-05-29 03:54:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This returns a human-readable version of the Service used to tag some resources.
|
|
|
|
// This is only used for human-readable convenience, and not to filter.
|
2016-11-18 20:58:42 +00:00
|
|
|
func getServiceName(service *v1.Service) string {
|
2016-05-29 03:54:26 +00:00
|
|
|
return fmt.Sprintf("%s/%s", service.Namespace, service.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This returns a prefix for loadbalancer/security rules.
|
2016-11-18 20:58:42 +00:00
|
|
|
func getRulePrefix(service *v1.Service) string {
|
2016-05-29 03:54:26 +00:00
|
|
|
return cloudprovider.GetLoadBalancerName(service)
|
|
|
|
}
|
|
|
|
|
2017-05-08 03:42:40 +00:00
|
|
|
func getPublicIPName(clusterName string, service *v1.Service) string {
|
|
|
|
return fmt.Sprintf("%s-%s", clusterName, cloudprovider.GetLoadBalancerName(service))
|
|
|
|
}
|
|
|
|
|
2016-11-18 20:58:42 +00:00
|
|
|
func serviceOwnsRule(service *v1.Service, rule string) bool {
|
2016-05-29 03:54:26 +00:00
|
|
|
prefix := getRulePrefix(service)
|
|
|
|
return strings.HasPrefix(strings.ToUpper(rule), strings.ToUpper(prefix))
|
|
|
|
}
|
|
|
|
|
2017-09-06 04:51:58 +00:00
|
|
|
func serviceOwnsFrontendIP(fip network.FrontendIPConfiguration, service *v1.Service) bool {
|
2017-08-30 04:00:21 +00:00
|
|
|
baseName := cloudprovider.GetLoadBalancerName(service)
|
|
|
|
return strings.HasPrefix(*fip.Name, baseName)
|
|
|
|
}
|
|
|
|
|
|
|
|
func getFrontendIPConfigName(service *v1.Service, subnetName *string) string {
|
|
|
|
baseName := cloudprovider.GetLoadBalancerName(service)
|
|
|
|
if subnetName != nil {
|
|
|
|
return fmt.Sprintf("%s-%s", baseName, *subnetName)
|
|
|
|
}
|
|
|
|
return baseName
|
2016-05-29 03:54:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This returns the next available rule priority level for a given set of security rules.
|
|
|
|
func getNextAvailablePriority(rules []network.SecurityRule) (int32, error) {
|
|
|
|
var smallest int32 = loadBalancerMinimumPriority
|
|
|
|
var spread int32 = 1
|
|
|
|
|
|
|
|
outer:
|
|
|
|
for smallest < loadBalancerMaximumPriority {
|
|
|
|
for _, rule := range rules {
|
2016-12-07 07:16:20 +00:00
|
|
|
if *rule.Priority == smallest {
|
2016-05-29 03:54:26 +00:00
|
|
|
smallest += spread
|
|
|
|
continue outer
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// no one else had it
|
|
|
|
return smallest, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1, fmt.Errorf("SecurityGroup priorities are exhausted")
|
|
|
|
}
|
|
|
|
|
2016-07-16 06:10:29 +00:00
|
|
|
func (az *Cloud) getIPForMachine(nodeName types.NodeName) (string, error) {
|
2017-12-13 06:17:37 +00:00
|
|
|
return az.vmSet.GetIPByNodeName(string(nodeName), "")
|
2017-05-31 21:19:38 +00:00
|
|
|
}
|
2017-07-13 11:55:32 +00:00
|
|
|
|
|
|
|
var polyTable = crc32.MakeTable(crc32.Koopman)
|
|
|
|
|
|
|
|
//MakeCRC32 : convert string to CRC32 format
|
|
|
|
func MakeCRC32(str string) string {
|
|
|
|
crc := crc32.New(polyTable)
|
|
|
|
crc.Write([]byte(str))
|
|
|
|
hash := crc.Sum32()
|
|
|
|
return strconv.FormatUint(uint64(hash), 10)
|
|
|
|
}
|
|
|
|
|
|
|
|
//ExtractVMData : extract dataDisks, storageProfile from a map struct
|
|
|
|
func ExtractVMData(vmData map[string]interface{}) (dataDisks []interface{},
|
|
|
|
storageProfile map[string]interface{},
|
|
|
|
hardwareProfile map[string]interface{}, err error) {
|
|
|
|
props, ok := vmData["properties"].(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return nil, nil, nil, fmt.Errorf("convert vmData(properties) to map error")
|
|
|
|
}
|
|
|
|
|
|
|
|
storageProfile, ok = props["storageProfile"].(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return nil, nil, nil, fmt.Errorf("convert vmData(storageProfile) to map error")
|
|
|
|
}
|
|
|
|
|
|
|
|
hardwareProfile, ok = props["hardwareProfile"].(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return nil, nil, nil, fmt.Errorf("convert vmData(hardwareProfile) to map error")
|
|
|
|
}
|
|
|
|
|
|
|
|
dataDisks, ok = storageProfile["dataDisks"].([]interface{})
|
|
|
|
if !ok {
|
|
|
|
return nil, nil, nil, fmt.Errorf("convert vmData(dataDisks) to map error")
|
|
|
|
}
|
|
|
|
return dataDisks, storageProfile, hardwareProfile, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//ExtractDiskData : extract provisioningState, diskState from a map struct
|
|
|
|
func ExtractDiskData(diskData interface{}) (provisioningState string, diskState string, err error) {
|
|
|
|
fragment, ok := diskData.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return "", "", fmt.Errorf("convert diskData to map error")
|
|
|
|
}
|
|
|
|
|
|
|
|
properties, ok := fragment["properties"].(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return "", "", fmt.Errorf("convert diskData(properties) to map error")
|
|
|
|
}
|
|
|
|
|
|
|
|
provisioningState, ok = properties["provisioningState"].(string) // if there is a disk, provisioningState property will be there
|
|
|
|
if ref, ok := properties["diskState"]; ok {
|
|
|
|
diskState = ref.(string)
|
|
|
|
}
|
|
|
|
return provisioningState, diskState, nil
|
|
|
|
}
|
2017-12-13 06:17:37 +00:00
|
|
|
|
|
|
|
// availabilitySet implements VMSet interface for Azure availability sets.
|
|
|
|
type availabilitySet struct {
|
|
|
|
*Cloud
|
|
|
|
}
|
|
|
|
|
|
|
|
// newStandardSet creates a new availabilitySet.
|
|
|
|
func newAvailabilitySet(az *Cloud) VMSet {
|
|
|
|
return &availabilitySet{
|
|
|
|
Cloud: az,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetInstanceIDByNodeName gets the cloud provider ID by node name.
|
|
|
|
// It must return ("", cloudprovider.InstanceNotFound) if the instance does
|
|
|
|
// not exist or is no longer running.
|
|
|
|
func (as *availabilitySet) GetInstanceIDByNodeName(name string) (string, error) {
|
|
|
|
var machine compute.VirtualMachine
|
|
|
|
var err error
|
|
|
|
|
2018-01-03 07:14:20 +00:00
|
|
|
machine, err = as.getVirtualMachine(types.NodeName(name))
|
2017-12-13 06:17:37 +00:00
|
|
|
if err != nil {
|
|
|
|
if as.CloudProviderBackoff {
|
|
|
|
glog.V(2).Infof("InstanceID(%s) backing off", name)
|
2018-01-03 07:14:20 +00:00
|
|
|
machine, err = as.GetVirtualMachineWithRetry(types.NodeName(name))
|
2017-12-13 06:17:37 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.V(2).Infof("InstanceID(%s) abort backoff", name)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return *machine.ID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetNodeNameByProviderID gets the node name by provider ID.
|
|
|
|
func (as *availabilitySet) GetNodeNameByProviderID(providerID string) (types.NodeName, error) {
|
|
|
|
// NodeName is part of providerID for standard instances.
|
|
|
|
matches := providerIDRE.FindStringSubmatch(providerID)
|
|
|
|
if len(matches) != 2 {
|
|
|
|
return "", errors.New("error splitting providerID")
|
|
|
|
}
|
|
|
|
|
|
|
|
return types.NodeName(matches[1]), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetInstanceTypeByNodeName gets the instance type by node name.
|
|
|
|
func (as *availabilitySet) GetInstanceTypeByNodeName(name string) (string, error) {
|
2018-01-03 07:14:20 +00:00
|
|
|
machine, err := as.getVirtualMachine(types.NodeName(name))
|
2017-12-13 06:17:37 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("error: as.GetInstanceTypeByNodeName(%s), as.getVirtualMachine(%s) err=%v", name, name, err)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(machine.HardwareProfile.VMSize), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetZoneByNodeName gets zone from instance view.
|
|
|
|
func (as *availabilitySet) GetZoneByNodeName(name string) (cloudprovider.Zone, error) {
|
2018-01-03 07:14:20 +00:00
|
|
|
vm, err := as.getVirtualMachine(types.NodeName(name))
|
2017-12-13 06:17:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return cloudprovider.Zone{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
failureDomain := strconv.Itoa(int(*vm.VirtualMachineProperties.InstanceView.PlatformFaultDomain))
|
|
|
|
zone := cloudprovider.Zone{
|
|
|
|
FailureDomain: failureDomain,
|
|
|
|
Region: *(vm.Location),
|
|
|
|
}
|
|
|
|
return zone, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPrimaryVMSetName returns the VM set name depending on the configured vmType.
|
|
|
|
// It returns config.PrimaryScaleSetName for vmss and config.PrimaryAvailabilitySetName for standard vmType.
|
|
|
|
func (as *availabilitySet) GetPrimaryVMSetName() string {
|
|
|
|
return as.Config.PrimaryAvailabilitySetName
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetIPByNodeName gets machine IP by node name.
|
|
|
|
func (as *availabilitySet) GetIPByNodeName(name, vmSetName string) (string, error) {
|
|
|
|
nic, err := as.GetPrimaryInterface(name, vmSetName)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
ipConfig, err := getPrimaryIPConfig(nic)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("error: as.GetIPByNodeName(%s), getPrimaryIPConfig(%v), err=%v", name, nic, err)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
targetIP := *ipConfig.PrivateIPAddress
|
|
|
|
return targetIP, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getAgentPoolAvailabiliySets lists the virtual machines for for the resource group and then builds
|
|
|
|
// a list of availability sets that match the nodes available to k8s.
|
|
|
|
func (as *availabilitySet) getAgentPoolAvailabiliySets(nodes []*v1.Node) (agentPoolAvailabilitySets *[]string, err error) {
|
|
|
|
vms, err := as.VirtualMachineClientListWithRetry()
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("as.getNodeAvailabilitySet - VirtualMachineClientListWithRetry failed, err=%v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
vmNameToAvailabilitySetID := make(map[string]string, len(vms))
|
|
|
|
for vmx := range vms {
|
|
|
|
vm := vms[vmx]
|
|
|
|
if vm.AvailabilitySet != nil {
|
|
|
|
vmNameToAvailabilitySetID[*vm.Name] = *vm.AvailabilitySet.ID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
availabilitySetIDs := sets.NewString()
|
|
|
|
agentPoolAvailabilitySets = &[]string{}
|
|
|
|
for nx := range nodes {
|
|
|
|
nodeName := (*nodes[nx]).Name
|
|
|
|
if isMasterNode(nodes[nx]) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
asID, ok := vmNameToAvailabilitySetID[nodeName]
|
|
|
|
if !ok {
|
|
|
|
glog.Errorf("as.getNodeAvailabilitySet - Node(%s) has no availability sets", nodeName)
|
|
|
|
return nil, fmt.Errorf("Node (%s) - has no availability sets", nodeName)
|
|
|
|
}
|
|
|
|
if availabilitySetIDs.Has(asID) {
|
|
|
|
// already added in the list
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
asName, err := getLastSegment(asID)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("as.getNodeAvailabilitySet - Node (%s)- getLastSegment(%s), err=%v", nodeName, asID, err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// AvailabilitySet ID is currently upper cased in a indeterministic way
|
|
|
|
// We want to keep it lower case, before the ID get fixed
|
|
|
|
asName = strings.ToLower(asName)
|
|
|
|
|
|
|
|
*agentPoolAvailabilitySets = append(*agentPoolAvailabilitySets, asName)
|
|
|
|
}
|
|
|
|
|
|
|
|
return agentPoolAvailabilitySets, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetVMSetNames selects all possible availability sets or scale sets
|
|
|
|
// (depending vmType configured) for service load balancer, if the service has
|
|
|
|
// no loadbalancer mode annotaion returns the primary VMSet. If service annotation
|
|
|
|
// for loadbalancer exists then return the eligible VMSet.
|
|
|
|
func (as *availabilitySet) GetVMSetNames(service *v1.Service, nodes []*v1.Node) (availabilitySetNames *[]string, err error) {
|
|
|
|
hasMode, isAuto, serviceAvailabilitySetNames := getServiceLoadBalancerMode(service)
|
|
|
|
if !hasMode {
|
|
|
|
// no mode specified in service annotation default to PrimaryAvailabilitySetName
|
|
|
|
availabilitySetNames = &[]string{as.Config.PrimaryAvailabilitySetName}
|
|
|
|
return availabilitySetNames, nil
|
|
|
|
}
|
|
|
|
availabilitySetNames, err = as.getAgentPoolAvailabiliySets(nodes)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("as.GetVMSetNames - getAgentPoolAvailabiliySets failed err=(%v)", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(*availabilitySetNames) == 0 {
|
|
|
|
glog.Errorf("as.GetVMSetNames - No availability sets found for nodes in the cluster, node count(%d)", len(nodes))
|
|
|
|
return nil, fmt.Errorf("No availability sets found for nodes, node count(%d)", len(nodes))
|
|
|
|
}
|
|
|
|
// sort the list to have deterministic selection
|
|
|
|
sort.Strings(*availabilitySetNames)
|
|
|
|
if !isAuto {
|
|
|
|
if serviceAvailabilitySetNames == nil || len(serviceAvailabilitySetNames) == 0 {
|
|
|
|
return nil, fmt.Errorf("service annotation for LoadBalancerMode is empty, it should have __auto__ or availability sets value")
|
|
|
|
}
|
|
|
|
// validate availability set exists
|
|
|
|
var found bool
|
|
|
|
for sasx := range serviceAvailabilitySetNames {
|
|
|
|
for asx := range *availabilitySetNames {
|
|
|
|
if strings.EqualFold((*availabilitySetNames)[asx], serviceAvailabilitySetNames[sasx]) {
|
|
|
|
found = true
|
|
|
|
serviceAvailabilitySetNames[sasx] = (*availabilitySetNames)[asx]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
glog.Errorf("as.GetVMSetNames - Availability set (%s) in service annotation not found", serviceAvailabilitySetNames[sasx])
|
|
|
|
return nil, fmt.Errorf("availability set (%s) - not found", serviceAvailabilitySetNames[sasx])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
availabilitySetNames = &serviceAvailabilitySetNames
|
|
|
|
}
|
|
|
|
|
|
|
|
return availabilitySetNames, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPrimaryInterface gets machine primary network interface by node name and vmSet.
|
|
|
|
func (as *availabilitySet) GetPrimaryInterface(nodeName, vmSetName string) (network.Interface, error) {
|
|
|
|
var machine compute.VirtualMachine
|
|
|
|
|
2018-01-03 07:16:16 +00:00
|
|
|
machine, err := as.GetVirtualMachineWithRetry(types.NodeName(nodeName))
|
2017-12-13 06:17:37 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.V(2).Infof("GetPrimaryInterface(%s, %s) abort backoff", nodeName, vmSetName)
|
|
|
|
return network.Interface{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
primaryNicID, err := getPrimaryInterfaceID(machine)
|
|
|
|
if err != nil {
|
|
|
|
return network.Interface{}, err
|
|
|
|
}
|
|
|
|
nicName, err := getLastSegment(primaryNicID)
|
|
|
|
if err != nil {
|
|
|
|
return network.Interface{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check availability set
|
|
|
|
if vmSetName != "" {
|
|
|
|
expectedAvailabilitySetName := as.getAvailabilitySetID(vmSetName)
|
|
|
|
if machine.AvailabilitySet == nil || !strings.EqualFold(*machine.AvailabilitySet.ID, expectedAvailabilitySetName) {
|
|
|
|
glog.V(3).Infof(
|
|
|
|
"GetPrimaryInterface: nic (%s) is not in the availabilitySet(%s)", nicName, vmSetName)
|
|
|
|
return network.Interface{}, errNotInVMSet
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nic, err := as.InterfacesClient.Get(as.ResourceGroup, nicName, "")
|
|
|
|
if err != nil {
|
|
|
|
return network.Interface{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nic, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensureHostInPool ensures the given VM's Primary NIC's Primary IP Configuration is
|
|
|
|
// participating in the specified LoadBalancer Backend Pool.
|
|
|
|
func (as *availabilitySet) ensureHostInPool(serviceName string, nodeName types.NodeName, backendPoolID string, vmSetName string) error {
|
|
|
|
vmName := mapNodeNameToVMName(nodeName)
|
|
|
|
nic, err := as.GetPrimaryInterface(vmName, vmSetName)
|
|
|
|
if err != nil {
|
|
|
|
if err == errNotInVMSet {
|
|
|
|
glog.V(3).Infof("ensureHostInPool skips node %s because it is not in the vmSet %s", nodeName, vmSetName)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
glog.Errorf("error: az.ensureHostInPool(%s), az.vmSet.GetPrimaryInterface.Get(%s, %s), err=%v", nodeName, vmName, vmSetName, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var primaryIPConfig *network.InterfaceIPConfiguration
|
|
|
|
primaryIPConfig, err = getPrimaryIPConfig(nic)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
foundPool := false
|
|
|
|
newBackendPools := []network.BackendAddressPool{}
|
|
|
|
if primaryIPConfig.LoadBalancerBackendAddressPools != nil {
|
|
|
|
newBackendPools = *primaryIPConfig.LoadBalancerBackendAddressPools
|
|
|
|
}
|
|
|
|
for _, existingPool := range newBackendPools {
|
|
|
|
if strings.EqualFold(backendPoolID, *existingPool.ID) {
|
|
|
|
foundPool = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !foundPool {
|
|
|
|
newBackendPools = append(newBackendPools,
|
|
|
|
network.BackendAddressPool{
|
|
|
|
ID: to.StringPtr(backendPoolID),
|
|
|
|
})
|
|
|
|
|
|
|
|
primaryIPConfig.LoadBalancerBackendAddressPools = &newBackendPools
|
|
|
|
|
|
|
|
nicName := *nic.Name
|
|
|
|
glog.V(3).Infof("nicupdate(%s): nic(%s) - updating", serviceName, nicName)
|
|
|
|
respChan, errChan := as.InterfacesClient.CreateOrUpdate(as.ResourceGroup, *nic.Name, nic, nil)
|
|
|
|
resp := <-respChan
|
|
|
|
err := <-errChan
|
|
|
|
glog.V(10).Infof("InterfacesClient.CreateOrUpdate(%q): end", *nic.Name)
|
|
|
|
if as.CloudProviderBackoff && shouldRetryAPIRequest(resp.Response, err) {
|
|
|
|
glog.V(2).Infof("nicupdate(%s) backing off: nic(%s) - updating, err=%v", serviceName, nicName, err)
|
|
|
|
retryErr := as.CreateOrUpdateInterfaceWithRetry(nic)
|
|
|
|
if retryErr != nil {
|
|
|
|
err = retryErr
|
|
|
|
glog.V(2).Infof("nicupdate(%s) abort backoff: nic(%s) - updating", serviceName, nicName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// EnsureHostsInPool ensures the given Node's primary IP configurations are
|
|
|
|
// participating in the specified LoadBalancer Backend Pool.
|
|
|
|
func (as *availabilitySet) EnsureHostsInPool(serviceName string, nodes []*v1.Node, backendPoolID string, vmSetName string) error {
|
|
|
|
hostUpdates := make([]func() error, len(nodes))
|
|
|
|
for i, node := range nodes {
|
|
|
|
localNodeName := node.Name
|
|
|
|
f := func() error {
|
|
|
|
err := as.ensureHostInPool(serviceName, types.NodeName(localNodeName), backendPoolID, vmSetName)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("ensure(%s): backendPoolID(%s) - failed to ensure host in pool: %q", serviceName, backendPoolID, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
hostUpdates[i] = f
|
|
|
|
}
|
|
|
|
|
|
|
|
errs := utilerrors.AggregateGoroutines(hostUpdates...)
|
|
|
|
if errs != nil {
|
|
|
|
return utilerrors.Flatten(errs)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// EnsureBackendPoolDeleted ensures the loadBalancer backendAddressPools deleted from the specified vmSet.
|
|
|
|
func (as *availabilitySet) EnsureBackendPoolDeleted(poolID, vmSetName string) error {
|
|
|
|
// Do nothing for availability set.
|
|
|
|
return nil
|
|
|
|
}
|
2017-11-29 08:11:58 +00:00
|
|
|
|
|
|
|
// get a storage account by UUID
|
|
|
|
func generateStorageAccountName(accountNamePrefix string) string {
|
|
|
|
uniqueID := strings.Replace(string(uuid.NewUUID()), "-", "", -1)
|
|
|
|
accountName := strings.ToLower(accountNamePrefix + uniqueID)
|
|
|
|
if len(accountName) > storageAccountNameMaxLength {
|
|
|
|
return accountName[:storageAccountNameMaxLength-1]
|
|
|
|
}
|
|
|
|
return accountName
|
|
|
|
}
|