mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-10 20:42:26 +00:00
implement azure cloudprovider
This commit is contained in:
parent
6ad9dc659f
commit
2ebffb431d
@ -81,12 +81,15 @@ type LoadBalancer interface {
|
||||
// GetLoadBalancer returns whether the specified load balancer exists, and
|
||||
// if so, what its status is.
|
||||
// Implementations must treat the *api.Service parameter as read-only and not modify it.
|
||||
// Parameter 'clusterName' is the name of the cluster as presented to kube-controller-manager
|
||||
GetLoadBalancer(clusterName string, service *api.Service) (status *api.LoadBalancerStatus, exists bool, err error)
|
||||
// EnsureLoadBalancer creates a new load balancer 'name', or updates the existing one. Returns the status of the balancer
|
||||
// Implementations must treat the *api.Service parameter as read-only and not modify it.
|
||||
// Parameter 'clusterName' is the name of the cluster as presented to kube-controller-manager
|
||||
EnsureLoadBalancer(clusterName string, service *api.Service, hosts []string) (*api.LoadBalancerStatus, error)
|
||||
// UpdateLoadBalancer updates hosts under the specified load balancer.
|
||||
// Implementations must treat the *api.Service parameter as read-only and not modify it.
|
||||
// Parameter 'clusterName' is the name of the cluster as presented to kube-controller-manager
|
||||
UpdateLoadBalancer(clusterName string, service *api.Service, hosts []string) error
|
||||
// EnsureLoadBalancerDeleted deletes the specified load balancer if it
|
||||
// exists, returning nil if the load balancer specified either didn't exist or
|
||||
@ -95,6 +98,7 @@ type LoadBalancer interface {
|
||||
// have multiple underlying components, meaning a Get could say that the LB
|
||||
// doesn't exist even if some part of it is still laying around.
|
||||
// Implementations must treat the *api.Service parameter as read-only and not modify it.
|
||||
// Parameter 'clusterName' is the name of the cluster as presented to kube-controller-manager
|
||||
EnsureLoadBalancerDeleted(clusterName string, service *api.Service) error
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@ import (
|
||||
)
|
||||
|
||||
const TestClusterId = "clusterid.test"
|
||||
const TestClusterName = "testCluster"
|
||||
|
||||
func TestReadAWSCloudConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
@ -1182,7 +1183,7 @@ func TestDescribeLoadBalancerOnDelete(t *testing.T) {
|
||||
c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices)
|
||||
awsServices.elb.expectDescribeLoadBalancers("aid")
|
||||
|
||||
c.EnsureLoadBalancerDeleted(&api.Service{ObjectMeta: api.ObjectMeta{Name: "myservice", UID: "id"}})
|
||||
c.EnsureLoadBalancerDeleted(TestClusterName, &api.Service{ObjectMeta: api.ObjectMeta{Name: "myservice", UID: "id"}})
|
||||
}
|
||||
|
||||
func TestDescribeLoadBalancerOnUpdate(t *testing.T) {
|
||||
@ -1190,7 +1191,7 @@ func TestDescribeLoadBalancerOnUpdate(t *testing.T) {
|
||||
c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices)
|
||||
awsServices.elb.expectDescribeLoadBalancers("aid")
|
||||
|
||||
c.UpdateLoadBalancer(&api.Service{ObjectMeta: api.ObjectMeta{Name: "myservice", UID: "id"}}, []string{})
|
||||
c.UpdateLoadBalancer(TestClusterName, &api.Service{ObjectMeta: api.ObjectMeta{Name: "myservice", UID: "id"}}, []string{})
|
||||
}
|
||||
|
||||
func TestDescribeLoadBalancerOnGet(t *testing.T) {
|
||||
@ -1198,7 +1199,7 @@ func TestDescribeLoadBalancerOnGet(t *testing.T) {
|
||||
c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices)
|
||||
awsServices.elb.expectDescribeLoadBalancers("aid")
|
||||
|
||||
c.GetLoadBalancer(&api.Service{ObjectMeta: api.ObjectMeta{Name: "myservice", UID: "id"}})
|
||||
c.GetLoadBalancer(TestClusterName, &api.Service{ObjectMeta: api.ObjectMeta{Name: "myservice", UID: "id"}})
|
||||
}
|
||||
|
||||
func TestDescribeLoadBalancerOnEnsure(t *testing.T) {
|
||||
@ -1206,7 +1207,7 @@ func TestDescribeLoadBalancerOnEnsure(t *testing.T) {
|
||||
c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices)
|
||||
awsServices.elb.expectDescribeLoadBalancers("aid")
|
||||
|
||||
c.EnsureLoadBalancer(&api.Service{ObjectMeta: api.ObjectMeta{Name: "myservice", UID: "id"}}, []string{})
|
||||
c.EnsureLoadBalancer(TestClusterName, &api.Service{ObjectMeta: api.ObjectMeta{Name: "myservice", UID: "id"}}, []string{})
|
||||
}
|
||||
|
||||
func TestBuildListener(t *testing.T) {
|
||||
|
3
pkg/cloudprovider/providers/azure/OWNERS
Normal file
3
pkg/cloudprovider/providers/azure/OWNERS
Normal file
@ -0,0 +1,3 @@
|
||||
assignees:
|
||||
- colemickens
|
||||
- brendandburns
|
173
pkg/cloudprovider/providers/azure/azure.go
Normal file
173
pkg/cloudprovider/providers/azure/azure.go
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
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 (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/network"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
// CloudProviderName is the value used for the --cloud-provider flag
|
||||
const CloudProviderName = "azure"
|
||||
|
||||
// Config holds the configuration parsed from the --cloud-config flag
|
||||
type Config struct {
|
||||
Cloud string `json:"cloud" yaml:"cloud"`
|
||||
TenantID string `json:"tenantId" yaml:"tenantId"`
|
||||
SubscriptionID string `json:"subscriptionId" yaml:"subscriptionId"`
|
||||
ResourceGroup string `json:"resourceGroup" yaml:"resourceGroup"`
|
||||
Location string `json:"location" yaml:"location"`
|
||||
VnetName string `json:"vnetName" yaml:"vnetName"`
|
||||
SubnetName string `json:"subnetName" yaml:"subnetName"`
|
||||
SecurityGroupName string `json:"securityGroupName" yaml:"securityGroupName"`
|
||||
RouteTableName string `json:"routeTableName" yaml:"routeTableName"`
|
||||
|
||||
AADClientID string `json:"aadClientId" yaml:"aadClientId"`
|
||||
AADClientSecret string `json:"aadClientSecret" yaml:"aadClientSecret"`
|
||||
AADTenantID string `json:"aadTenantId" yaml:"aadTenantId"`
|
||||
}
|
||||
|
||||
// Cloud holds the config and clients
|
||||
type Cloud struct {
|
||||
Config
|
||||
Environment azure.Environment
|
||||
RoutesClient network.RoutesClient
|
||||
SubnetsClient network.SubnetsClient
|
||||
InterfacesClient network.InterfacesClient
|
||||
RouteTablesClient network.RouteTablesClient
|
||||
LoadBalancerClient network.LoadBalancersClient
|
||||
PublicIPAddressesClient network.PublicIPAddressesClient
|
||||
SecurityGroupsClient network.SecurityGroupsClient
|
||||
VirtualMachinesClient compute.VirtualMachinesClient
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloudprovider.RegisterCloudProvider(CloudProviderName, NewCloud)
|
||||
}
|
||||
|
||||
// NewCloud returns a Cloud with initialized clients
|
||||
func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) {
|
||||
var az Cloud
|
||||
|
||||
configContents, err := ioutil.ReadAll(configReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = yaml.Unmarshal(configContents, &az)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if az.Cloud == "" {
|
||||
az.Environment = azure.PublicCloud
|
||||
} else {
|
||||
az.Environment, err = azure.EnvironmentFromName(az.Cloud)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
oauthConfig, err := az.Environment.OAuthConfigForTenant(az.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
servicePrincipalToken, err := azure.NewServicePrincipalToken(
|
||||
*oauthConfig,
|
||||
az.AADClientID,
|
||||
az.AADClientSecret,
|
||||
az.Environment.ServiceManagementEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
az.SubnetsClient = network.NewSubnetsClient(az.SubscriptionID)
|
||||
az.SubnetsClient.BaseURI = az.Environment.ResourceManagerEndpoint
|
||||
az.SubnetsClient.Authorizer = servicePrincipalToken
|
||||
|
||||
az.RouteTablesClient = network.NewRouteTablesClient(az.SubscriptionID)
|
||||
az.RouteTablesClient.BaseURI = az.Environment.ResourceManagerEndpoint
|
||||
az.RouteTablesClient.Authorizer = servicePrincipalToken
|
||||
|
||||
az.RoutesClient = network.NewRoutesClient(az.SubscriptionID)
|
||||
az.RoutesClient.BaseURI = az.Environment.ResourceManagerEndpoint
|
||||
az.RoutesClient.Authorizer = servicePrincipalToken
|
||||
|
||||
az.InterfacesClient = network.NewInterfacesClient(az.SubscriptionID)
|
||||
az.InterfacesClient.BaseURI = az.Environment.ResourceManagerEndpoint
|
||||
az.InterfacesClient.Authorizer = servicePrincipalToken
|
||||
|
||||
az.LoadBalancerClient = network.NewLoadBalancersClient(az.SubscriptionID)
|
||||
az.LoadBalancerClient.BaseURI = az.Environment.ResourceManagerEndpoint
|
||||
az.LoadBalancerClient.Authorizer = servicePrincipalToken
|
||||
|
||||
az.VirtualMachinesClient = compute.NewVirtualMachinesClient(az.SubscriptionID)
|
||||
az.VirtualMachinesClient.BaseURI = az.Environment.ResourceManagerEndpoint
|
||||
az.VirtualMachinesClient.Authorizer = servicePrincipalToken
|
||||
|
||||
az.PublicIPAddressesClient = network.NewPublicIPAddressesClient(az.SubscriptionID)
|
||||
az.PublicIPAddressesClient.BaseURI = az.Environment.ResourceManagerEndpoint
|
||||
az.PublicIPAddressesClient.Authorizer = servicePrincipalToken
|
||||
|
||||
az.SecurityGroupsClient = network.NewSecurityGroupsClient(az.SubscriptionID)
|
||||
az.SecurityGroupsClient.BaseURI = az.Environment.ResourceManagerEndpoint
|
||||
az.SecurityGroupsClient.Authorizer = servicePrincipalToken
|
||||
|
||||
return &az, nil
|
||||
}
|
||||
|
||||
// LoadBalancer returns a balancer interface. Also returns true if the interface is supported, false otherwise.
|
||||
func (az *Cloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
|
||||
return az, true
|
||||
}
|
||||
|
||||
// Instances returns an instances interface. Also returns true if the interface is supported, false otherwise.
|
||||
func (az *Cloud) Instances() (cloudprovider.Instances, bool) {
|
||||
return az, true
|
||||
}
|
||||
|
||||
// Zones returns a zones interface. Also returns true if the interface is supported, false otherwise.
|
||||
func (az *Cloud) Zones() (cloudprovider.Zones, bool) {
|
||||
return az, true
|
||||
}
|
||||
|
||||
// Clusters returns a clusters interface. Also returns true if the interface is supported, false otherwise.
|
||||
func (az *Cloud) Clusters() (cloudprovider.Clusters, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Routes returns a routes interface along with whether the interface is supported.
|
||||
func (az *Cloud) Routes() (cloudprovider.Routes, bool) {
|
||||
return az, true
|
||||
}
|
||||
|
||||
// ScrubDNS provides an opportunity for cloud-provider-specific code to process DNS settings for pods.
|
||||
func (az *Cloud) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) {
|
||||
return nameservers, searches
|
||||
}
|
||||
|
||||
// ProviderName returns the cloud provider ID.
|
||||
func (az *Cloud) ProviderName() string {
|
||||
return CloudProviderName
|
||||
}
|
146
pkg/cloudprovider/providers/azure/azure_instances.go
Normal file
146
pkg/cloudprovider/providers/azure/azure_instances.go
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
)
|
||||
|
||||
// NodeAddresses returns the addresses of the specified instance.
|
||||
func (az *Cloud) NodeAddresses(name string) ([]api.NodeAddress, error) {
|
||||
ip, err := az.getIPForMachine(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []api.NodeAddress{
|
||||
{Type: api.NodeInternalIP, Address: ip},
|
||||
{Type: api.NodeHostName, Address: name},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExternalID returns the cloud provider ID of the specified instance (deprecated).
|
||||
func (az *Cloud) ExternalID(name string) (string, error) {
|
||||
return az.InstanceID(name)
|
||||
}
|
||||
|
||||
// InstanceID returns the cloud provider ID of the specified instance.
|
||||
// Note that if the instance does not exist or is no longer running, we must return ("", cloudprovider.InstanceNotFound)
|
||||
func (az *Cloud) InstanceID(name string) (string, error) {
|
||||
machine, exists, err := az.getVirtualMachine(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if !exists {
|
||||
return "", cloudprovider.InstanceNotFound
|
||||
}
|
||||
return *machine.ID, nil
|
||||
}
|
||||
|
||||
// InstanceType returns the type of the specified instance.
|
||||
// Note that if the instance does not exist or is no longer running, we must return ("", cloudprovider.InstanceNotFound)
|
||||
// (Implementer Note): This is used by kubelet. Kubelet will label the node. Real log from kubelet:
|
||||
// Adding node label from cloud provider: beta.kubernetes.io/instance-type=[value]
|
||||
func (az *Cloud) InstanceType(name string) (string, error) {
|
||||
machine, exists, err := az.getVirtualMachine(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if !exists {
|
||||
return "", cloudprovider.InstanceNotFound
|
||||
}
|
||||
return string(machine.Properties.HardwareProfile.VMSize), nil
|
||||
}
|
||||
|
||||
// List lists instances that match 'filter' which is a regular expression which must match the entire instance name (fqdn)
|
||||
func (az *Cloud) List(filter string) ([]string, error) {
|
||||
allNodes, err := az.listAllNodesInResourceGroup()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filteredNodes, err := filterNodes(allNodes, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodeNames := make([]string, len(filteredNodes))
|
||||
for i, v := range filteredNodes {
|
||||
nodeNames[i] = *v.Name
|
||||
}
|
||||
|
||||
return nodeNames, nil
|
||||
}
|
||||
|
||||
// AddSSHKeyToAllInstances adds an SSH public key as a legal identity for all instances
|
||||
// expected format for the key is standard ssh-keygen format: <protocol> <blob>
|
||||
func (az *Cloud) AddSSHKeyToAllInstances(user string, keyData []byte) error {
|
||||
return fmt.Errorf("not supported")
|
||||
}
|
||||
|
||||
// CurrentNodeName returns the name of the node we are currently running on
|
||||
// On most clouds (e.g. GCE) this is the hostname, so we provide the hostname
|
||||
func (az *Cloud) CurrentNodeName(hostname string) (string, error) {
|
||||
return hostname, nil
|
||||
}
|
||||
|
||||
func (az *Cloud) listAllNodesInResourceGroup() ([]compute.VirtualMachine, error) {
|
||||
allNodes := []compute.VirtualMachine{}
|
||||
|
||||
result, err := az.VirtualMachinesClient.List(az.ResourceGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
morePages := (result.Value != nil && len(*result.Value) > 1)
|
||||
|
||||
for morePages {
|
||||
allNodes = append(allNodes, *result.Value...)
|
||||
|
||||
result, err = az.VirtualMachinesClient.ListAllNextResults(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
morePages = (result.Value != nil && len(*result.Value) > 1)
|
||||
}
|
||||
|
||||
return allNodes, nil
|
||||
|
||||
}
|
||||
|
||||
func filterNodes(nodes []compute.VirtualMachine, filter string) ([]compute.VirtualMachine, error) {
|
||||
filteredNodes := []compute.VirtualMachine{}
|
||||
|
||||
re, err := regexp.Compile(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
// search tags
|
||||
if re.MatchString(*node.Name) {
|
||||
filteredNodes = append(filteredNodes, node)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredNodes, nil
|
||||
}
|
611
pkg/cloudprovider/providers/azure/azure_loadbalancer.go
Normal file
611
pkg/cloudprovider/providers/azure/azure_loadbalancer.go
Normal file
@ -0,0 +1,611 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
utilerrors "k8s.io/kubernetes/pkg/util/errors"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/network"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// GetLoadBalancer returns whether the specified load balancer exists, and
|
||||
// if so, what its status is.
|
||||
func (az *Cloud) GetLoadBalancer(clusterName string, service *api.Service) (status *api.LoadBalancerStatus, exists bool, err error) {
|
||||
lbName := getLoadBalancerName(clusterName)
|
||||
pipName := getPublicIPName(clusterName, service)
|
||||
serviceName := getServiceName(service)
|
||||
|
||||
_, existsLb, err := az.getAzureLoadBalancer(lbName)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !existsLb {
|
||||
glog.V(5).Infof("get(%s): lb(%s) - doesn't exist", serviceName, pipName)
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
pip, existsPip, err := az.getPublicIPAddress(pipName)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !existsPip {
|
||||
glog.V(5).Infof("get(%s): pip(%s) - doesn't exist", serviceName, pipName)
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
return &api.LoadBalancerStatus{
|
||||
Ingress: []api.LoadBalancerIngress{{IP: *pip.Properties.IPAddress}},
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
// EnsureLoadBalancer creates a new load balancer 'name', or updates the existing one. Returns the status of the balancer
|
||||
func (az *Cloud) EnsureLoadBalancer(clusterName string, service *api.Service, hosts []string) (*api.LoadBalancerStatus, error) {
|
||||
lbName := getLoadBalancerName(clusterName)
|
||||
pipName := getPublicIPName(clusterName, service)
|
||||
serviceName := getServiceName(service)
|
||||
glog.V(2).Infof("ensure(%s): START clusterName=%q lbName=%q", serviceName, clusterName, lbName)
|
||||
|
||||
pip, err := az.ensurePublicIPExists(serviceName, pipName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sg, err := az.SecurityGroupsClient.Get(az.ResourceGroup, az.SecurityGroupName, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sg, sgNeedsUpdate, err := az.reconcileSecurityGroup(sg, clusterName, service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sgNeedsUpdate {
|
||||
glog.V(3).Infof("ensure(%s): sg(%s) - updating", serviceName, *sg.Name)
|
||||
_, err := az.SecurityGroupsClient.CreateOrUpdate(az.ResourceGroup, *sg.Name, sg, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
lb, existsLb, err := az.getAzureLoadBalancer(lbName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existsLb {
|
||||
lb = network.LoadBalancer{
|
||||
Name: &lbName,
|
||||
Location: &az.Location,
|
||||
Properties: &network.LoadBalancerPropertiesFormat{},
|
||||
}
|
||||
}
|
||||
|
||||
lb, lbNeedsUpdate, err := az.reconcileLoadBalancer(lb, pip, clusterName, service, hosts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existsLb || lbNeedsUpdate {
|
||||
glog.V(3).Infof("ensure(%s): lb(%s) - updating", serviceName, lbName)
|
||||
_, err = az.LoadBalancerClient.CreateOrUpdate(az.ResourceGroup, *lb.Name, lb, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the machines to the backend pool if they're not already
|
||||
lbBackendName := getBackendPoolName(clusterName)
|
||||
lbBackendPoolID := az.getBackendPoolID(lbName, lbBackendName)
|
||||
hostUpdates := make([]func() error, len(hosts))
|
||||
for i, host := range hosts {
|
||||
localHost := host
|
||||
f := func() error {
|
||||
err := az.ensureHostInPool(serviceName, localHost, lbBackendPoolID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ensure(%s): lb(%s) - failed to ensure host in pool: %q", serviceName, lbName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
hostUpdates[i] = f
|
||||
}
|
||||
|
||||
errs := utilerrors.AggregateGoroutines(hostUpdates...)
|
||||
if errs != nil {
|
||||
return nil, utilerrors.Flatten(errs)
|
||||
}
|
||||
|
||||
glog.V(2).Infof("ensure(%s): FINISH - %s", service.Name, *pip.Properties.IPAddress)
|
||||
return &api.LoadBalancerStatus{
|
||||
Ingress: []api.LoadBalancerIngress{{IP: *pip.Properties.IPAddress}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateLoadBalancer updates hosts under the specified load balancer.
|
||||
func (az *Cloud) UpdateLoadBalancer(clusterName string, service *api.Service, hosts []string) error {
|
||||
_, err := az.EnsureLoadBalancer(clusterName, service, hosts)
|
||||
return err
|
||||
}
|
||||
|
||||
// EnsureLoadBalancerDeleted deletes the specified load balancer if it
|
||||
// exists, returning nil if the load balancer specified either didn't exist or
|
||||
// was successfully deleted.
|
||||
// This construction is useful because many cloud providers' load balancers
|
||||
// have multiple underlying components, meaning a Get could say that the LB
|
||||
// doesn't exist even if some part of it is still laying around.
|
||||
func (az *Cloud) EnsureLoadBalancerDeleted(clusterName string, service *api.Service) error {
|
||||
lbName := getLoadBalancerName(clusterName)
|
||||
pipName := getPublicIPName(clusterName, service)
|
||||
serviceName := getServiceName(service)
|
||||
|
||||
glog.V(2).Infof("delete(%s): START clusterName=%q lbName=%q", serviceName, clusterName, lbName)
|
||||
|
||||
// reconcile logic is capable of fully reconcile, so we can use this to delete
|
||||
service.Spec.Ports = []api.ServicePort{}
|
||||
|
||||
lb, existsLb, err := az.getAzureLoadBalancer(lbName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existsLb {
|
||||
lb, lbNeedsUpdate, reconcileErr := az.reconcileLoadBalancer(lb, nil, clusterName, service, []string{})
|
||||
if reconcileErr != nil {
|
||||
return reconcileErr
|
||||
}
|
||||
if lbNeedsUpdate {
|
||||
if len(*lb.Properties.FrontendIPConfigurations) > 0 {
|
||||
glog.V(3).Infof("delete(%s): lb(%s) - updating", serviceName, lbName)
|
||||
_, err = az.LoadBalancerClient.CreateOrUpdate(az.ResourceGroup, *lb.Name, lb, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
glog.V(3).Infof("delete(%s): lb(%s) - deleting; no remaining frontendipconfigs", serviceName, lbName)
|
||||
|
||||
_, err = az.LoadBalancerClient.Delete(az.ResourceGroup, lbName, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sg, existsSg, err := az.getSecurityGroup()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existsSg {
|
||||
reconciledSg, sgNeedsUpdate, reconcileErr := az.reconcileSecurityGroup(sg, clusterName, service)
|
||||
if reconcileErr != nil {
|
||||
return reconcileErr
|
||||
}
|
||||
if sgNeedsUpdate {
|
||||
glog.V(3).Infof("delete(%s): sg(%s) - updating", serviceName, az.SecurityGroupName)
|
||||
_, err := az.SecurityGroupsClient.CreateOrUpdate(az.ResourceGroup, *reconciledSg.Name, reconciledSg, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = az.ensurePublicIPDeleted(serviceName, pipName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.V(2).Infof("delete(%s): FINISH", serviceName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (az *Cloud) ensurePublicIPExists(serviceName, pipName string) (*network.PublicIPAddress, error) {
|
||||
pip, existsPip, err := az.getPublicIPAddress(pipName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existsPip {
|
||||
return &pip, nil
|
||||
}
|
||||
|
||||
pip.Name = to.StringPtr(pipName)
|
||||
pip.Location = to.StringPtr(az.Location)
|
||||
pip.Properties = &network.PublicIPAddressPropertiesFormat{
|
||||
PublicIPAllocationMethod: network.Static,
|
||||
}
|
||||
pip.Tags = &map[string]*string{"service": &serviceName}
|
||||
|
||||
glog.V(3).Infof("ensure(%s): pip(%s) - creating", serviceName, *pip.Name)
|
||||
_, err = az.PublicIPAddressesClient.CreateOrUpdate(az.ResourceGroup, *pip.Name, pip, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pip, err = az.PublicIPAddressesClient.Get(az.ResourceGroup, *pip.Name, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pip, nil
|
||||
|
||||
}
|
||||
|
||||
func (az *Cloud) ensurePublicIPDeleted(serviceName, pipName string) error {
|
||||
_, deleteErr := az.PublicIPAddressesClient.Delete(az.ResourceGroup, pipName, nil)
|
||||
_, realErr := checkResourceExistsFromError(deleteErr)
|
||||
if realErr != nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This ensures load balancer exists and the frontend ip config is setup.
|
||||
// This also reconciles the Service's Ports with the LoadBalancer config.
|
||||
// This entails adding rules/probes for expected Ports and removing stale rules/ports.
|
||||
func (az *Cloud) reconcileLoadBalancer(lb network.LoadBalancer, pip *network.PublicIPAddress, clusterName string, service *api.Service, hosts []string) (network.LoadBalancer, bool, error) {
|
||||
lbName := getLoadBalancerName(clusterName)
|
||||
serviceName := getServiceName(service)
|
||||
lbFrontendIPConfigName := getFrontendIPConfigName(service)
|
||||
lbFrontendIPConfigID := az.getFrontendIPConfigID(lbName, lbFrontendIPConfigName)
|
||||
lbBackendPoolName := getBackendPoolName(clusterName)
|
||||
lbBackendPoolID := az.getBackendPoolID(lbName, lbBackendPoolName)
|
||||
|
||||
wantLb := len(service.Spec.Ports) > 0
|
||||
dirtyLb := false
|
||||
|
||||
// Ensure LoadBalancer's Backend Pool Configuration
|
||||
if wantLb {
|
||||
if lb.Properties.BackendAddressPools == nil ||
|
||||
len(*lb.Properties.BackendAddressPools) == 0 {
|
||||
lb.Properties.BackendAddressPools = &[]network.BackendAddressPool{
|
||||
{
|
||||
Name: to.StringPtr(lbBackendPoolName),
|
||||
},
|
||||
}
|
||||
glog.V(10).Infof("reconcile(%s)(%t): lb backendpool - adding", serviceName, wantLb)
|
||||
dirtyLb = true
|
||||
} else if len(*lb.Properties.BackendAddressPools) != 1 ||
|
||||
!strings.EqualFold(*(*lb.Properties.BackendAddressPools)[0].Name, lbBackendPoolName) {
|
||||
return lb, false, fmt.Errorf("loadbalancer is misconfigured with a different backend pool")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure LoadBalancer's Frontend IP Configurations
|
||||
dirtyConfigs := false
|
||||
newConfigs := []network.FrontendIPConfiguration{}
|
||||
if lb.Properties.FrontendIPConfigurations != nil {
|
||||
newConfigs = *lb.Properties.FrontendIPConfigurations
|
||||
}
|
||||
if !wantLb {
|
||||
for i := len(newConfigs) - 1; i >= 0; i-- {
|
||||
config := newConfigs[i]
|
||||
if strings.EqualFold(*config.Name, lbFrontendIPConfigName) {
|
||||
glog.V(3).Infof("reconcile(%s)(%t): lb frontendconfig(%s) - dropping", serviceName, wantLb, lbFrontendIPConfigName)
|
||||
newConfigs = append(newConfigs[:i], newConfigs[i+1:]...)
|
||||
dirtyConfigs = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foundConfig := false
|
||||
for _, config := range newConfigs {
|
||||
if strings.EqualFold(*config.Name, lbFrontendIPConfigName) {
|
||||
foundConfig = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundConfig {
|
||||
newConfigs = append(newConfigs,
|
||||
network.FrontendIPConfiguration{
|
||||
Name: to.StringPtr(lbFrontendIPConfigName),
|
||||
Properties: &network.FrontendIPConfigurationPropertiesFormat{
|
||||
PublicIPAddress: &network.PublicIPAddress{
|
||||
ID: pip.ID,
|
||||
},
|
||||
},
|
||||
})
|
||||
glog.V(10).Infof("reconcile(%s)(%t): lb frontendconfig(%s) - adding", serviceName, wantLb, lbFrontendIPConfigName)
|
||||
dirtyConfigs = true
|
||||
}
|
||||
}
|
||||
if dirtyConfigs {
|
||||
dirtyLb = true
|
||||
lb.Properties.FrontendIPConfigurations = &newConfigs
|
||||
}
|
||||
|
||||
// update probes/rules
|
||||
expectedProbes := make([]network.Probe, len(service.Spec.Ports))
|
||||
expectedRules := make([]network.LoadBalancingRule, len(service.Spec.Ports))
|
||||
for i, port := range service.Spec.Ports {
|
||||
lbRuleName := getRuleName(service, port)
|
||||
|
||||
transportProto, _, probeProto, err := getProtocolsFromKubernetesProtocol(port.Protocol)
|
||||
if err != nil {
|
||||
return lb, false, err
|
||||
}
|
||||
|
||||
expectedProbes[i] = network.Probe{
|
||||
Name: &lbRuleName,
|
||||
Properties: &network.ProbePropertiesFormat{
|
||||
Protocol: probeProto,
|
||||
Port: to.Int32Ptr(port.NodePort),
|
||||
IntervalInSeconds: to.Int32Ptr(5),
|
||||
NumberOfProbes: to.Int32Ptr(2),
|
||||
},
|
||||
}
|
||||
|
||||
expectedRules[i] = network.LoadBalancingRule{
|
||||
Name: &lbRuleName,
|
||||
Properties: &network.LoadBalancingRulePropertiesFormat{
|
||||
Protocol: transportProto,
|
||||
FrontendIPConfiguration: &network.SubResource{
|
||||
ID: to.StringPtr(lbFrontendIPConfigID),
|
||||
},
|
||||
BackendAddressPool: &network.SubResource{
|
||||
ID: to.StringPtr(lbBackendPoolID),
|
||||
},
|
||||
Probe: &network.SubResource{
|
||||
ID: to.StringPtr(az.getLoadBalancerProbeID(lbName, lbRuleName)),
|
||||
},
|
||||
FrontendPort: to.Int32Ptr(port.Port),
|
||||
BackendPort: to.Int32Ptr(port.NodePort),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// remove unwated probes
|
||||
dirtyProbes := false
|
||||
var updatedProbes []network.Probe
|
||||
if lb.Properties.Probes != nil {
|
||||
updatedProbes = *lb.Properties.Probes
|
||||
}
|
||||
for i := len(updatedProbes) - 1; i >= 0; i-- {
|
||||
existingProbe := updatedProbes[i]
|
||||
if serviceOwnsRule(service, *existingProbe.Name) {
|
||||
glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - considering evicting", serviceName, wantLb, *existingProbe.Name)
|
||||
keepProbe := false
|
||||
if findProbe(expectedProbes, existingProbe) {
|
||||
glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - keeping", serviceName, wantLb, *existingProbe.Name)
|
||||
keepProbe = true
|
||||
}
|
||||
if !keepProbe {
|
||||
updatedProbes = append(updatedProbes[:i], updatedProbes[i+1:]...)
|
||||
glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - dropping", serviceName, wantLb, *existingProbe.Name)
|
||||
dirtyProbes = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// add missing, wanted probes
|
||||
for _, expectedProbe := range expectedProbes {
|
||||
foundProbe := false
|
||||
if findProbe(updatedProbes, expectedProbe) {
|
||||
glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - already exists", serviceName, wantLb, *expectedProbe.Name)
|
||||
foundProbe = true
|
||||
}
|
||||
if !foundProbe {
|
||||
glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - adding", serviceName, wantLb, *expectedProbe.Name)
|
||||
updatedProbes = append(updatedProbes, expectedProbe)
|
||||
dirtyProbes = true
|
||||
}
|
||||
}
|
||||
if dirtyProbes {
|
||||
dirtyLb = true
|
||||
lb.Properties.Probes = &updatedProbes
|
||||
}
|
||||
|
||||
// update rules
|
||||
dirtyRules := false
|
||||
var updatedRules []network.LoadBalancingRule
|
||||
if lb.Properties.LoadBalancingRules != nil {
|
||||
updatedRules = *lb.Properties.LoadBalancingRules
|
||||
}
|
||||
// update rules: remove unwanted
|
||||
for i := len(updatedRules) - 1; i >= 0; i-- {
|
||||
existingRule := updatedRules[i]
|
||||
if serviceOwnsRule(service, *existingRule.Name) {
|
||||
keepRule := false
|
||||
glog.V(10).Infof("reconcile(%s)(%t): lb rule(%s) - considering evicting", serviceName, wantLb, *existingRule.Name)
|
||||
if findRule(expectedRules, existingRule) {
|
||||
glog.V(10).Infof("reconcile(%s)(%t): lb rule(%s) - keeping", serviceName, wantLb, *existingRule.Name)
|
||||
keepRule = true
|
||||
}
|
||||
if !keepRule {
|
||||
glog.V(3).Infof("reconcile(%s)(%t): lb rule(%s) - dropping", serviceName, wantLb, *existingRule.Name)
|
||||
updatedRules = append(updatedRules[:i], updatedRules[i+1:]...)
|
||||
dirtyRules = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// update rules: add needed
|
||||
for _, expectedRule := range expectedRules {
|
||||
foundRule := false
|
||||
if findRule(updatedRules, expectedRule) {
|
||||
glog.V(10).Infof("reconcile(%s)(%t): lb rule(%s) - already exists", serviceName, wantLb, *expectedRule.Name)
|
||||
foundRule = true
|
||||
}
|
||||
if !foundRule {
|
||||
glog.V(10).Infof("reconcile(%s)(%t): lb rule(%s) adding", serviceName, wantLb, *expectedRule.Name)
|
||||
updatedRules = append(updatedRules, expectedRule)
|
||||
dirtyRules = true
|
||||
}
|
||||
}
|
||||
if dirtyRules {
|
||||
dirtyLb = true
|
||||
lb.Properties.LoadBalancingRules = &updatedRules
|
||||
}
|
||||
|
||||
return lb, dirtyLb, nil
|
||||
}
|
||||
|
||||
// This reconciles the Network Security Group similar to how the LB is reconciled.
|
||||
// This entails adding required, missing SecurityRules and removing stale rules.
|
||||
func (az *Cloud) reconcileSecurityGroup(sg network.SecurityGroup, clusterName string, service *api.Service) (network.SecurityGroup, bool, error) {
|
||||
serviceName := getServiceName(service)
|
||||
wantLb := len(service.Spec.Ports) > 0
|
||||
expectedSecurityRules := make([]network.SecurityRule, len(service.Spec.Ports))
|
||||
for i, port := range service.Spec.Ports {
|
||||
securityRuleName := getRuleName(service, port)
|
||||
_, securityProto, _, err := getProtocolsFromKubernetesProtocol(port.Protocol)
|
||||
if err != nil {
|
||||
return sg, false, err
|
||||
}
|
||||
|
||||
expectedSecurityRules[i] = network.SecurityRule{
|
||||
Name: to.StringPtr(securityRuleName),
|
||||
Properties: &network.SecurityRulePropertiesFormat{
|
||||
Protocol: securityProto,
|
||||
SourcePortRange: to.StringPtr("*"),
|
||||
DestinationPortRange: to.StringPtr(strconv.Itoa(int(port.NodePort))),
|
||||
SourceAddressPrefix: to.StringPtr("Internet"),
|
||||
DestinationAddressPrefix: to.StringPtr("*"),
|
||||
Access: network.Allow,
|
||||
Direction: network.Inbound,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// update security rules
|
||||
dirtySg := false
|
||||
var updatedRules []network.SecurityRule
|
||||
if sg.Properties.SecurityRules != nil {
|
||||
updatedRules = *sg.Properties.SecurityRules
|
||||
}
|
||||
// update security rules: remove unwanted
|
||||
for i := len(updatedRules) - 1; i >= 0; i-- {
|
||||
existingRule := updatedRules[i]
|
||||
if serviceOwnsRule(service, *existingRule.Name) {
|
||||
glog.V(10).Infof("reconcile(%s)(%t): sg rule(%s) - considering evicting", serviceName, wantLb, *existingRule.Name)
|
||||
keepRule := false
|
||||
if findSecurityRule(expectedSecurityRules, existingRule) {
|
||||
glog.V(10).Infof("reconcile(%s)(%t): sg rule(%s) - keeping", serviceName, wantLb, *existingRule.Name)
|
||||
keepRule = true
|
||||
}
|
||||
if !keepRule {
|
||||
glog.V(10).Infof("reconcile(%s)(%t): sg rule(%s) - dropping", serviceName, wantLb, *existingRule.Name)
|
||||
updatedRules = append(updatedRules[:i], updatedRules[i+1:]...)
|
||||
dirtySg = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// update security rules: add needed
|
||||
for _, expectedRule := range expectedSecurityRules {
|
||||
foundRule := false
|
||||
if findSecurityRule(updatedRules, expectedRule) {
|
||||
glog.V(10).Infof("reconcile(%s)(%t): sg rule(%s) - already exists", serviceName, wantLb, *expectedRule.Name)
|
||||
foundRule = true
|
||||
}
|
||||
if !foundRule {
|
||||
glog.V(10).Infof("reconcile(%s)(%t): sg rule(%s) - adding", serviceName, wantLb, *expectedRule.Name)
|
||||
|
||||
nextAvailablePriority, err := getNextAvailablePriority(updatedRules)
|
||||
if err != nil {
|
||||
return sg, false, err
|
||||
}
|
||||
|
||||
expectedRule.Properties.Priority = to.Int32Ptr(nextAvailablePriority)
|
||||
updatedRules = append(updatedRules, expectedRule)
|
||||
dirtySg = true
|
||||
}
|
||||
}
|
||||
if dirtySg {
|
||||
sg.Properties.SecurityRules = &updatedRules
|
||||
}
|
||||
return sg, dirtySg, nil
|
||||
}
|
||||
|
||||
func findProbe(probes []network.Probe, probe network.Probe) bool {
|
||||
for _, existingProbe := range probes {
|
||||
if strings.EqualFold(*existingProbe.Name, *probe.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func findRule(rules []network.LoadBalancingRule, rule network.LoadBalancingRule) bool {
|
||||
for _, existingRule := range rules {
|
||||
if strings.EqualFold(*existingRule.Name, *rule.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func findSecurityRule(rules []network.SecurityRule, rule network.SecurityRule) bool {
|
||||
for _, existingRule := range rules {
|
||||
if strings.EqualFold(*existingRule.Name, *rule.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// This ensures the given VM's Primary NIC's Primary IP Configuration is
|
||||
// participating in the specified LoadBalancer Backend Pool.
|
||||
func (az *Cloud) ensureHostInPool(serviceName, machineName string, backendPoolID string) error {
|
||||
machine, err := az.VirtualMachinesClient.Get(az.ResourceGroup, machineName, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
primaryNicID, err := getPrimaryInterfaceID(machine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nicName, err := getLastSegment(primaryNicID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nic, err := az.InterfacesClient.Get(az.ResourceGroup, nicName, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var primaryIPConfig *network.InterfaceIPConfiguration
|
||||
primaryIPConfig, err = getPrimaryIPConfig(nic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
foundPool := false
|
||||
newBackendPools := []network.BackendAddressPool{}
|
||||
if primaryIPConfig.Properties.LoadBalancerBackendAddressPools != nil {
|
||||
newBackendPools = *primaryIPConfig.Properties.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.Properties.LoadBalancerBackendAddressPools = &newBackendPools
|
||||
|
||||
glog.V(3).Infof("nicupdate(%s): nic(%s) - updating", serviceName, nicName)
|
||||
_, err := az.InterfacesClient.CreateOrUpdate(az.ResourceGroup, *nic.Name, nic, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
161
pkg/cloudprovider/providers/azure/azure_routes.go
Normal file
161
pkg/cloudprovider/providers/azure/azure_routes.go
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/network"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// ListRoutes lists all managed routes that belong to the specified clusterName
|
||||
func (az *Cloud) ListRoutes(clusterName string) (routes []*cloudprovider.Route, err error) {
|
||||
glog.V(10).Infof("list: START clusterName=%q", clusterName)
|
||||
routeTable, existsRouteTable, err := az.getRouteTable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existsRouteTable {
|
||||
return []*cloudprovider.Route{}, nil
|
||||
}
|
||||
|
||||
var kubeRoutes []*cloudprovider.Route
|
||||
if routeTable.Properties.Routes != nil {
|
||||
kubeRoutes = make([]*cloudprovider.Route, len(*routeTable.Properties.Routes))
|
||||
for i, route := range *routeTable.Properties.Routes {
|
||||
instance := getInstanceName(*route.Name)
|
||||
cidr := *route.Properties.AddressPrefix
|
||||
glog.V(10).Infof("list: * instance=%q, cidr=%q", instance, cidr)
|
||||
|
||||
kubeRoutes[i] = &cloudprovider.Route{
|
||||
Name: *route.Name,
|
||||
TargetInstance: instance,
|
||||
DestinationCIDR: cidr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(10).Info("list: FINISH")
|
||||
return kubeRoutes, nil
|
||||
}
|
||||
|
||||
// CreateRoute creates the described managed route
|
||||
// route.Name will be ignored, although the cloud-provider may use nameHint
|
||||
// to create a more user-meaningful name.
|
||||
func (az *Cloud) CreateRoute(clusterName string, nameHint string, kubeRoute *cloudprovider.Route) error {
|
||||
glog.V(2).Infof("create: creating route. clusterName=%q instance=%q cidr=%q", clusterName, kubeRoute.TargetInstance, kubeRoute.DestinationCIDR)
|
||||
|
||||
routeTable, existsRouteTable, err := az.getRouteTable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !existsRouteTable {
|
||||
routeTable = network.RouteTable{
|
||||
Name: to.StringPtr(az.RouteTableName),
|
||||
Location: to.StringPtr(az.Location),
|
||||
Properties: &network.RouteTablePropertiesFormat{},
|
||||
}
|
||||
|
||||
glog.V(3).Infof("create: creating routetable. routeTableName=%q", az.RouteTableName)
|
||||
_, err = az.RouteTablesClient.CreateOrUpdate(az.ResourceGroup, az.RouteTableName, routeTable, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
routeTable, err = az.RouteTablesClient.Get(az.ResourceGroup, az.RouteTableName, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// ensure the subnet is properly configured
|
||||
subnet, err := az.SubnetsClient.Get(az.ResourceGroup, az.VnetName, az.SubnetName, "")
|
||||
if err != nil {
|
||||
// 404 is fatal here
|
||||
return err
|
||||
}
|
||||
if subnet.Properties.RouteTable != nil {
|
||||
if *subnet.Properties.RouteTable.ID != *routeTable.ID {
|
||||
return fmt.Errorf("The subnet has a route table, but it was unrecognized. Refusing to modify it. active_routetable=%q expected_routetable=%q", *subnet.Properties.RouteTable.ID, *routeTable.ID)
|
||||
}
|
||||
} else {
|
||||
subnet.Properties.RouteTable = &network.RouteTable{
|
||||
ID: routeTable.ID,
|
||||
}
|
||||
glog.V(3).Info("create: updating subnet")
|
||||
_, err := az.SubnetsClient.CreateOrUpdate(az.ResourceGroup, az.VnetName, az.SubnetName, subnet, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
targetIP, err := az.getIPForMachine(kubeRoute.TargetInstance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
routeName := getRouteName(kubeRoute.TargetInstance)
|
||||
route := network.Route{
|
||||
Name: to.StringPtr(routeName),
|
||||
Properties: &network.RoutePropertiesFormat{
|
||||
AddressPrefix: to.StringPtr(kubeRoute.DestinationCIDR),
|
||||
NextHopType: network.RouteNextHopTypeVirtualAppliance,
|
||||
NextHopIPAddress: to.StringPtr(targetIP),
|
||||
},
|
||||
}
|
||||
|
||||
glog.V(3).Infof("create: creating route: instance=%q cidr=%q", kubeRoute.TargetInstance, kubeRoute.DestinationCIDR)
|
||||
_, err = az.RoutesClient.CreateOrUpdate(az.ResourceGroup, az.RouteTableName, *route.Name, route, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.V(2).Infof("create: route created. clusterName=%q instance=%q cidr=%q", clusterName, kubeRoute.TargetInstance, kubeRoute.DestinationCIDR)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRoute deletes the specified managed route
|
||||
// Route should be as returned by ListRoutes
|
||||
func (az *Cloud) DeleteRoute(clusterName string, kubeRoute *cloudprovider.Route) error {
|
||||
glog.V(2).Infof("delete: deleting route. clusterName=%q instance=%q cidr=%q", clusterName, kubeRoute.TargetInstance, kubeRoute.DestinationCIDR)
|
||||
|
||||
routeName := getRouteName(kubeRoute.TargetInstance)
|
||||
_, err := az.RoutesClient.Delete(az.ResourceGroup, az.RouteTableName, routeName, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.V(2).Infof("delete: route deleted. clusterName=%q instance=%q cidr=%q", clusterName, kubeRoute.TargetInstance, kubeRoute.DestinationCIDR)
|
||||
return nil
|
||||
}
|
||||
|
||||
// This must be kept in sync with getInstanceName.
|
||||
// These two functions enable stashing the instance name in the route
|
||||
// and then retrieving it later when listing. This is needed because
|
||||
// Azure does not let you put tags/descriptions on the Route itself.
|
||||
func getRouteName(instanceName string) string {
|
||||
return fmt.Sprintf("%s", instanceName)
|
||||
}
|
||||
|
||||
// Used with getRouteName. See comment on getRouteName.
|
||||
func getInstanceName(routeName string) string {
|
||||
return fmt.Sprintf("%s", routeName)
|
||||
}
|
517
pkg/cloudprovider/providers/azure/azure_test.go
Normal file
517
pkg/cloudprovider/providers/azure/azure_test.go
Normal file
@ -0,0 +1,517 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/network"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
)
|
||||
|
||||
var testClusterName = "testCluster"
|
||||
|
||||
// Test additional of a new service/port.
|
||||
func TestReconcileLoadBalancerAddPort(t *testing.T) {
|
||||
az := getTestCloud()
|
||||
svc := getTestService("servicea", 80)
|
||||
pip := getTestPublicIP()
|
||||
lb := getTestLoadBalancer()
|
||||
hosts := []string{}
|
||||
|
||||
lb, updated, err := az.reconcileLoadBalancer(lb, &pip, testClusterName, &svc, hosts)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %q", err)
|
||||
}
|
||||
|
||||
if !updated {
|
||||
t.Error("Expected the loadbalancer to need an update")
|
||||
}
|
||||
|
||||
// ensure we got a frontend ip configuration
|
||||
if len(*lb.Properties.FrontendIPConfigurations) != 1 {
|
||||
t.Error("Expected the loadbalancer to have a frontend ip configuration")
|
||||
}
|
||||
|
||||
validateLoadBalancer(t, lb, svc)
|
||||
}
|
||||
|
||||
// Test removing all services results in removing the frontend ip configuration
|
||||
func TestReconcileLoadBalancerRemoveAllPortsRemovesFrontendConfig(t *testing.T) {
|
||||
az := getTestCloud()
|
||||
svc := getTestService("servicea", 80)
|
||||
lb := getTestLoadBalancer()
|
||||
pip := getTestPublicIP()
|
||||
hosts := []string{}
|
||||
|
||||
lb, updated, err := az.reconcileLoadBalancer(lb, &pip, testClusterName, &svc, hosts)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %q", err)
|
||||
}
|
||||
|
||||
svcUpdated := getTestService("servicea")
|
||||
lb, updated, err = az.reconcileLoadBalancer(lb, nil, testClusterName, &svcUpdated, hosts)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %q", err)
|
||||
}
|
||||
|
||||
if !updated {
|
||||
t.Error("Expected the loadbalancer to need an update")
|
||||
}
|
||||
|
||||
// ensure we abandonded the frontend ip configuration
|
||||
if len(*lb.Properties.FrontendIPConfigurations) != 0 {
|
||||
t.Error("Expected the loadbalancer to have no frontend ip configuration")
|
||||
}
|
||||
|
||||
validateLoadBalancer(t, lb, svcUpdated)
|
||||
}
|
||||
|
||||
// Test removal of a port from an existing service.
|
||||
func TestReconcileLoadBalancerRemovesPort(t *testing.T) {
|
||||
az := getTestCloud()
|
||||
svc := getTestService("servicea", 80, 443)
|
||||
pip := getTestPublicIP()
|
||||
hosts := []string{}
|
||||
|
||||
existingLoadBalancer := getTestLoadBalancer(svc)
|
||||
|
||||
svcUpdated := getTestService("servicea", 80)
|
||||
updatedLoadBalancer, _, err := az.reconcileLoadBalancer(existingLoadBalancer, &pip, testClusterName, &svcUpdated, hosts)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %q", err)
|
||||
}
|
||||
|
||||
validateLoadBalancer(t, updatedLoadBalancer, svcUpdated)
|
||||
}
|
||||
|
||||
// Test reconciliation of multiple services on same port
|
||||
func TestReconcileLoadBalancerMultipleServices(t *testing.T) {
|
||||
az := getTestCloud()
|
||||
svc1 := getTestService("servicea", 80, 443)
|
||||
svc2 := getTestService("serviceb", 80)
|
||||
pip := getTestPublicIP()
|
||||
hosts := []string{}
|
||||
|
||||
existingLoadBalancer := getTestLoadBalancer()
|
||||
|
||||
updatedLoadBalancer, _, err := az.reconcileLoadBalancer(existingLoadBalancer, &pip, testClusterName, &svc1, hosts)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %q", err)
|
||||
}
|
||||
|
||||
updatedLoadBalancer, _, err = az.reconcileLoadBalancer(updatedLoadBalancer, &pip, testClusterName, &svc2, hosts)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %q", err)
|
||||
}
|
||||
|
||||
validateLoadBalancer(t, updatedLoadBalancer, svc1, svc2)
|
||||
}
|
||||
|
||||
func TestReconcileSecurityGroupNewServiceAddsPort(t *testing.T) {
|
||||
az := getTestCloud()
|
||||
svc1 := getTestService("serviceea", 80)
|
||||
|
||||
sg := getTestSecurityGroup()
|
||||
|
||||
sg, _, err := az.reconcileSecurityGroup(sg, testClusterName, &svc1)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %q", err)
|
||||
}
|
||||
|
||||
validateSecurityGroup(t, sg, svc1)
|
||||
}
|
||||
|
||||
func TestReconcileSecurityGroupRemoveServiceRemovesPort(t *testing.T) {
|
||||
az := getTestCloud()
|
||||
svc := getTestService("servicea", 80, 443)
|
||||
|
||||
sg := getTestSecurityGroup(svc)
|
||||
|
||||
svcUpdated := getTestService("servicea", 80)
|
||||
sg, _, err := az.reconcileSecurityGroup(sg, testClusterName, &svcUpdated)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %q", err)
|
||||
}
|
||||
|
||||
validateSecurityGroup(t, sg, svcUpdated)
|
||||
}
|
||||
|
||||
func getTestCloud() *Cloud {
|
||||
return &Cloud{
|
||||
Config: Config{
|
||||
TenantID: "tenant",
|
||||
SubscriptionID: "subscription",
|
||||
ResourceGroup: "rg",
|
||||
Location: "westus",
|
||||
VnetName: "vnet",
|
||||
SubnetName: "subnet",
|
||||
SecurityGroupName: "nsg",
|
||||
RouteTableName: "rt",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getBackendPort(port int32) int32 {
|
||||
return port + 10000
|
||||
}
|
||||
|
||||
func getTestPublicIP() network.PublicIPAddress {
|
||||
pip := network.PublicIPAddress{}
|
||||
pip.ID = to.StringPtr("/this/is/a/public/ip/address/id")
|
||||
return pip
|
||||
}
|
||||
|
||||
func getTestService(identifier string, requestedPorts ...int32) api.Service {
|
||||
ports := []api.ServicePort{}
|
||||
for _, port := range requestedPorts {
|
||||
ports = append(ports, api.ServicePort{
|
||||
Name: fmt.Sprintf("port-%d", port),
|
||||
Protocol: api.ProtocolTCP,
|
||||
Port: port,
|
||||
NodePort: getBackendPort(port),
|
||||
})
|
||||
}
|
||||
|
||||
svc := api.Service{
|
||||
Spec: api.ServiceSpec{
|
||||
Type: api.ServiceTypeLoadBalancer,
|
||||
Ports: ports,
|
||||
},
|
||||
}
|
||||
svc.Name = identifier
|
||||
svc.Namespace = "default"
|
||||
svc.UID = types.UID(identifier)
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
func getTestLoadBalancer(services ...api.Service) network.LoadBalancer {
|
||||
rules := []network.LoadBalancingRule{}
|
||||
probes := []network.Probe{}
|
||||
|
||||
for _, service := range services {
|
||||
for _, port := range service.Spec.Ports {
|
||||
ruleName := getRuleName(&service, port)
|
||||
rules = append(rules, network.LoadBalancingRule{
|
||||
Name: to.StringPtr(ruleName),
|
||||
Properties: &network.LoadBalancingRulePropertiesFormat{
|
||||
FrontendPort: to.Int32Ptr(port.Port),
|
||||
BackendPort: to.Int32Ptr(port.NodePort),
|
||||
},
|
||||
})
|
||||
probes = append(probes, network.Probe{
|
||||
Name: to.StringPtr(ruleName),
|
||||
Properties: &network.ProbePropertiesFormat{
|
||||
Port: to.Int32Ptr(port.NodePort),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
lb := network.LoadBalancer{
|
||||
Properties: &network.LoadBalancerPropertiesFormat{
|
||||
LoadBalancingRules: &rules,
|
||||
Probes: &probes,
|
||||
},
|
||||
}
|
||||
|
||||
return lb
|
||||
}
|
||||
|
||||
func getTestSecurityGroup(services ...api.Service) network.SecurityGroup {
|
||||
rules := []network.SecurityRule{}
|
||||
|
||||
for _, service := range services {
|
||||
for _, port := range service.Spec.Ports {
|
||||
ruleName := getRuleName(&service, port)
|
||||
rules = append(rules, network.SecurityRule{
|
||||
Name: to.StringPtr(ruleName),
|
||||
Properties: &network.SecurityRulePropertiesFormat{
|
||||
DestinationPortRange: to.StringPtr(fmt.Sprintf("%d", port.NodePort)),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sg := network.SecurityGroup{
|
||||
Properties: &network.SecurityGroupPropertiesFormat{
|
||||
SecurityRules: &rules,
|
||||
},
|
||||
}
|
||||
|
||||
return sg
|
||||
}
|
||||
|
||||
func validateLoadBalancer(t *testing.T, loadBalancer network.LoadBalancer, services ...api.Service) {
|
||||
expectedRuleCount := 0
|
||||
for _, svc := range services {
|
||||
for _, wantedRule := range svc.Spec.Ports {
|
||||
expectedRuleCount++
|
||||
wantedRuleName := getRuleName(&svc, wantedRule)
|
||||
foundRule := false
|
||||
for _, actualRule := range *loadBalancer.Properties.LoadBalancingRules {
|
||||
if strings.EqualFold(*actualRule.Name, wantedRuleName) &&
|
||||
*actualRule.Properties.FrontendPort == wantedRule.Port &&
|
||||
*actualRule.Properties.BackendPort == wantedRule.NodePort {
|
||||
foundRule = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundRule {
|
||||
t.Errorf("Expected rule but didn't find it: %q", wantedRuleName)
|
||||
}
|
||||
|
||||
foundProbe := false
|
||||
for _, actualProbe := range *loadBalancer.Properties.Probes {
|
||||
if strings.EqualFold(*actualProbe.Name, wantedRuleName) &&
|
||||
*actualProbe.Properties.Port == wantedRule.NodePort {
|
||||
foundProbe = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundProbe {
|
||||
t.Errorf("Expected probe but didn't find it: %q", wantedRuleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lenRules := len(*loadBalancer.Properties.LoadBalancingRules)
|
||||
if lenRules != expectedRuleCount {
|
||||
t.Errorf("Expected the loadbalancer to have %d rules. Found %d.", expectedRuleCount, lenRules)
|
||||
}
|
||||
lenProbes := len(*loadBalancer.Properties.Probes)
|
||||
if lenProbes != expectedRuleCount {
|
||||
t.Errorf("Expected the loadbalancer to have %d probes. Found %d.", expectedRuleCount, lenProbes)
|
||||
}
|
||||
}
|
||||
|
||||
func validateSecurityGroup(t *testing.T, securityGroup network.SecurityGroup, services ...api.Service) {
|
||||
expectedRuleCount := 0
|
||||
for _, svc := range services {
|
||||
for _, wantedRule := range svc.Spec.Ports {
|
||||
expectedRuleCount++
|
||||
wantedRuleName := getRuleName(&svc, wantedRule)
|
||||
foundRule := false
|
||||
for _, actualRule := range *securityGroup.Properties.SecurityRules {
|
||||
if strings.EqualFold(*actualRule.Name, wantedRuleName) &&
|
||||
*actualRule.Properties.DestinationPortRange == fmt.Sprintf("%d", wantedRule.NodePort) {
|
||||
foundRule = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundRule {
|
||||
t.Errorf("Expected rule but didn't find it: %q", wantedRuleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lenRules := len(*securityGroup.Properties.SecurityRules)
|
||||
if lenRules != expectedRuleCount {
|
||||
t.Errorf("Expected the loadbalancer to have %d rules. Found %d.", expectedRuleCount, lenRules)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityRulePriorityPicksNextAvailablePriority(t *testing.T) {
|
||||
rules := []network.SecurityRule{}
|
||||
|
||||
var expectedPriority int32 = loadBalancerMinimumPriority + 50
|
||||
|
||||
var i int32
|
||||
for i = loadBalancerMinimumPriority; i < expectedPriority; i++ {
|
||||
rules = append(rules, network.SecurityRule{
|
||||
Properties: &network.SecurityRulePropertiesFormat{
|
||||
Priority: to.Int32Ptr(i),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
priority, err := getNextAvailablePriority(rules)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpectected error: %q", err)
|
||||
}
|
||||
|
||||
if priority != expectedPriority {
|
||||
t.Errorf("Expected priority %d. Got priority %d.", expectedPriority, priority)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityRulePriorityFailsIfExhausted(t *testing.T) {
|
||||
rules := []network.SecurityRule{}
|
||||
|
||||
var i int32
|
||||
for i = loadBalancerMinimumPriority; i < loadBalancerMaximumPriority; i++ {
|
||||
rules = append(rules, network.SecurityRule{
|
||||
Properties: &network.SecurityRulePropertiesFormat{
|
||||
Priority: to.Int32Ptr(i),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
_, err := getNextAvailablePriority(rules)
|
||||
if err == nil {
|
||||
t.Error("Expectected an error. There are no priority levels left.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtocolTranslationTCP(t *testing.T) {
|
||||
proto := api.ProtocolTCP
|
||||
transportProto, securityGroupProto, probeProto, err := getProtocolsFromKubernetesProtocol(proto)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if transportProto != network.TransportProtocolTCP {
|
||||
t.Errorf("Expected TCP LoadBalancer Rule Protocol. Got %v", transportProto)
|
||||
}
|
||||
if securityGroupProto != network.TCP {
|
||||
t.Errorf("Expected TCP SecurityGroup Protocol. Got %v", transportProto)
|
||||
}
|
||||
if probeProto != network.ProbeProtocolTCP {
|
||||
t.Errorf("Expected TCP LoadBalancer Probe Protocol. Got %v", transportProto)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtocolTranslationUDP(t *testing.T) {
|
||||
proto := api.ProtocolUDP
|
||||
_, _, _, err := getProtocolsFromKubernetesProtocol(proto)
|
||||
if err == nil {
|
||||
t.Error("Expected an error. UDP is unsupported.")
|
||||
}
|
||||
}
|
||||
|
||||
// Test Configuration deserialization (json)
|
||||
func TestNewCloudFromJSON(t *testing.T) {
|
||||
config := `{
|
||||
"tenantId": "--tenant-id--",
|
||||
"subscriptionId": "--subscription-id--",
|
||||
"aadClientId": "--aad-client-id--",
|
||||
"aadClientSecret": "--aad-client-secret--",
|
||||
"resourceGroup": "--resource-group--",
|
||||
"location": "--location--",
|
||||
"subnetName": "--subnet-name--",
|
||||
"securityGroupName": "--security-group-name--",
|
||||
"vnetName": "--vnet-name--",
|
||||
"routeTableName": "--route-table-name--"
|
||||
}`
|
||||
validateConfig(t, config)
|
||||
}
|
||||
|
||||
// Test Configuration deserialization (yaml)
|
||||
func TestNewCloudFromYAML(t *testing.T) {
|
||||
config := `
|
||||
tenantId: --tenant-id--
|
||||
subscriptionId: --subscription-id--
|
||||
aadClientId: --aad-client-id--
|
||||
aadClientSecret: --aad-client-secret--
|
||||
resourceGroup: --resource-group--
|
||||
location: --location--
|
||||
subnetName: --subnet-name--
|
||||
securityGroupName: --security-group-name--
|
||||
vnetName: --vnet-name--
|
||||
routeTableName: --route-table-name--
|
||||
`
|
||||
validateConfig(t, config)
|
||||
}
|
||||
|
||||
func validateConfig(t *testing.T, config string) {
|
||||
configReader := strings.NewReader(config)
|
||||
cloud, err := NewCloud(configReader)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
azureCloud, ok := cloud.(*Cloud)
|
||||
if !ok {
|
||||
t.Error("NewCloud returned incorrect type")
|
||||
}
|
||||
|
||||
if azureCloud.TenantID != "--tenant-id--" {
|
||||
t.Errorf("got incorrect value for TenantID")
|
||||
}
|
||||
if azureCloud.SubscriptionID != "--subscription-id--" {
|
||||
t.Errorf("got incorrect value for SubscriptionID")
|
||||
}
|
||||
if azureCloud.AADClientID != "--aad-client-id--" {
|
||||
t.Errorf("got incorrect value for AADClientID")
|
||||
}
|
||||
if azureCloud.AADClientSecret != "--aad-client-secret--" {
|
||||
t.Errorf("got incorrect value for AADClientSecret")
|
||||
}
|
||||
if azureCloud.ResourceGroup != "--resource-group--" {
|
||||
t.Errorf("got incorrect value for ResourceGroup")
|
||||
}
|
||||
if azureCloud.Location != "--location--" {
|
||||
t.Errorf("got incorrect value for Location")
|
||||
}
|
||||
if azureCloud.SubnetName != "--subnet-name--" {
|
||||
t.Errorf("got incorrect value for SubnetName")
|
||||
}
|
||||
if azureCloud.SecurityGroupName != "--security-group-name--" {
|
||||
t.Errorf("got incorrect value for SecurityGroupName")
|
||||
}
|
||||
if azureCloud.VnetName != "--vnet-name--" {
|
||||
t.Errorf("got incorrect value for VnetName")
|
||||
}
|
||||
if azureCloud.RouteTableName != "--route-table-name--" {
|
||||
t.Errorf("got incorrect value for RouteTableName")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeInstanceInfo(t *testing.T) {
|
||||
response := `{"ID":"_azdev","UD":"0","FD":"99"}`
|
||||
|
||||
faultDomain, err := readFaultDomain(strings.NewReader(response))
|
||||
if err != nil {
|
||||
t.Error("Unexpected error in ReadFaultDomain")
|
||||
}
|
||||
|
||||
if faultDomain == nil {
|
||||
t.Error("Fault domain was unexpectedly nil")
|
||||
}
|
||||
|
||||
if *faultDomain != "99" {
|
||||
t.Error("got incorrect fault domain")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterNodes(t *testing.T) {
|
||||
nodes := []compute.VirtualMachine{
|
||||
{Name: to.StringPtr("test")},
|
||||
{Name: to.StringPtr("test2")},
|
||||
{Name: to.StringPtr("3test")},
|
||||
}
|
||||
|
||||
filteredNodes, err := filterNodes(nodes, "^test$")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpeted error when filtering: %q", err)
|
||||
}
|
||||
|
||||
if len(filteredNodes) != 1 {
|
||||
t.Error("Got too many nodes after filtering")
|
||||
}
|
||||
|
||||
if *filteredNodes[0].Name != "test" {
|
||||
t.Error("Get the wrong node after filtering")
|
||||
}
|
||||
}
|
236
pkg/cloudprovider/providers/azure/azure_util.go
Normal file
236
pkg/cloudprovider/providers/azure/azure_util.go
Normal file
@ -0,0 +1,236 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/network"
|
||||
)
|
||||
|
||||
const (
|
||||
loadBalancerMinimumPriority = 500
|
||||
loadBalancerMaximumPriority = 4096
|
||||
|
||||
machineResourceIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachines/%s"
|
||||
frontendIPConfigIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/frontendIPConfigurations/%s"
|
||||
backendPoolIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/backendAddressPools/%s"
|
||||
loadBalancerRuleIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/loadBalancingRules/%s"
|
||||
loadBalancerProbeIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/loadBalancers/%s/probes/%s"
|
||||
securityRuleIDTemplate = "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/networkSecurityGroups/%s/securityRules/%s"
|
||||
)
|
||||
|
||||
// returns the full identifier of a machine
|
||||
func (az *Cloud) getMachineID(machineName string) string {
|
||||
return fmt.Sprintf(
|
||||
machineResourceIDTemplate,
|
||||
az.SubscriptionID,
|
||||
az.ResourceGroup,
|
||||
machineName)
|
||||
}
|
||||
|
||||
// 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 rule.
|
||||
func (az *Cloud) getLoadBalancerRuleID(lbName, lbRuleName string) string {
|
||||
return fmt.Sprintf(
|
||||
loadBalancerRuleIDTemplate,
|
||||
az.SubscriptionID,
|
||||
az.ResourceGroup,
|
||||
lbName,
|
||||
lbRuleName)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// returns the full identifier of a network security group security rule.
|
||||
func (az *Cloud) getSecurityRuleID(securityRuleName string) string {
|
||||
return fmt.Sprintf(
|
||||
securityRuleIDTemplate,
|
||||
az.SubscriptionID,
|
||||
az.ResourceGroup,
|
||||
az.SecurityGroupName,
|
||||
securityRuleName)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func getProtocolsFromKubernetesProtocol(protocol api.Protocol) (network.TransportProtocol, network.SecurityRuleProtocol, network.ProbeProtocol, error) {
|
||||
switch protocol {
|
||||
case api.ProtocolTCP:
|
||||
return network.TransportProtocolTCP, network.TCP, network.ProbeProtocolTCP, nil
|
||||
default:
|
||||
return "", "", "", fmt.Errorf("Only TCP is supported for Azure LoadBalancers")
|
||||
}
|
||||
}
|
||||
|
||||
// This returns the full identifier of the primary NIC for the given VM.
|
||||
func getPrimaryInterfaceID(machine compute.VirtualMachine) (string, error) {
|
||||
if len(*machine.Properties.NetworkProfile.NetworkInterfaces) == 1 {
|
||||
return *(*machine.Properties.NetworkProfile.NetworkInterfaces)[0].ID, nil
|
||||
}
|
||||
|
||||
for _, ref := range *machine.Properties.NetworkProfile.NetworkInterfaces {
|
||||
if *ref.Properties.Primary {
|
||||
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) {
|
||||
if len(*nic.Properties.IPConfigurations) == 1 {
|
||||
return &((*nic.Properties.IPConfigurations)[0]), nil
|
||||
}
|
||||
|
||||
// we're here because we either have multiple ipconfigs and can't determine the primary:
|
||||
// https://github.com/Azure/azure-rest-api-specs/issues/305
|
||||
// or somehow we had zero ipconfigs
|
||||
return nil, fmt.Errorf("failed to determine the determine primary ipconfig. nicname=%q", *nic.Name)
|
||||
}
|
||||
|
||||
func getLoadBalancerName(clusterName string) string {
|
||||
return clusterName
|
||||
}
|
||||
|
||||
func getBackendPoolName(clusterName string) string {
|
||||
return clusterName
|
||||
}
|
||||
|
||||
func getRuleName(service *api.Service, port api.ServicePort) string {
|
||||
return fmt.Sprintf("%s-%s-%d-%d", getRulePrefix(service), port.Protocol, port.Port, port.NodePort)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func getServiceName(service *api.Service) string {
|
||||
return fmt.Sprintf("%s/%s", service.Namespace, service.Name)
|
||||
}
|
||||
|
||||
// This returns a prefix for loadbalancer/security rules.
|
||||
func getRulePrefix(service *api.Service) string {
|
||||
return cloudprovider.GetLoadBalancerName(service)
|
||||
}
|
||||
|
||||
func serviceOwnsRule(service *api.Service, rule string) bool {
|
||||
prefix := getRulePrefix(service)
|
||||
return strings.HasPrefix(strings.ToUpper(rule), strings.ToUpper(prefix))
|
||||
}
|
||||
|
||||
func getFrontendIPConfigName(service *api.Service) string {
|
||||
return cloudprovider.GetLoadBalancerName(service)
|
||||
}
|
||||
|
||||
func getPublicIPName(clusterName string, service *api.Service) string {
|
||||
return fmt.Sprintf("%s-%s", clusterName, cloudprovider.GetLoadBalancerName(service))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if *rule.Properties.Priority == smallest {
|
||||
smallest += spread
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
// no one else had it
|
||||
return smallest, nil
|
||||
}
|
||||
|
||||
return -1, fmt.Errorf("SecurityGroup priorities are exhausted")
|
||||
}
|
||||
|
||||
func (az *Cloud) getIPForMachine(machineName string) (string, error) {
|
||||
machine, exists, err := az.getVirtualMachine(machineName)
|
||||
if !exists {
|
||||
return "", cloudprovider.InstanceNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nicID, err := getPrimaryInterfaceID(machine)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nicName, err := getLastSegment(nicID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nic, err := az.InterfacesClient.Get(az.ResourceGroup, nicName, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ipConfig, err := getPrimaryIPConfig(nic)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
targetIP := *ipConfig.Properties.PrivateIPAddress
|
||||
return targetIP, nil
|
||||
}
|
124
pkg/cloudprovider/providers/azure/azure_wrap.go
Normal file
124
pkg/cloudprovider/providers/azure/azure_wrap.go
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
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 (
|
||||
"net/http"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||
"github.com/Azure/azure-sdk-for-go/arm/network"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
)
|
||||
|
||||
// checkExistsFromError inspects an error and returns a true if err is nil,
|
||||
// false if error is an autorest.Error with StatusCode=404 and will return the
|
||||
// error back if error is another status code or another type of error.
|
||||
func checkResourceExistsFromError(err error) (bool, error) {
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
v, ok := err.(autorest.DetailedError)
|
||||
if ok && v.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, v
|
||||
}
|
||||
|
||||
func (az *Cloud) getVirtualMachine(machineName string) (vm compute.VirtualMachine, exists bool, err error) {
|
||||
var realErr error
|
||||
|
||||
vm, err = az.VirtualMachinesClient.Get(az.ResourceGroup, machineName, "")
|
||||
|
||||
exists, realErr = checkResourceExistsFromError(err)
|
||||
if realErr != nil {
|
||||
return vm, false, realErr
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return vm, false, nil
|
||||
}
|
||||
|
||||
return vm, exists, err
|
||||
}
|
||||
|
||||
func (az *Cloud) getRouteTable() (routeTable network.RouteTable, exists bool, err error) {
|
||||
var realErr error
|
||||
|
||||
routeTable, err = az.RouteTablesClient.Get(az.ResourceGroup, az.RouteTableName, "")
|
||||
|
||||
exists, realErr = checkResourceExistsFromError(err)
|
||||
if realErr != nil {
|
||||
return routeTable, false, realErr
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return routeTable, false, nil
|
||||
}
|
||||
|
||||
return routeTable, exists, err
|
||||
}
|
||||
|
||||
func (az *Cloud) getSecurityGroup() (sg network.SecurityGroup, exists bool, err error) {
|
||||
var realErr error
|
||||
|
||||
sg, err = az.SecurityGroupsClient.Get(az.ResourceGroup, az.SecurityGroupName, "")
|
||||
|
||||
exists, realErr = checkResourceExistsFromError(err)
|
||||
if realErr != nil {
|
||||
return sg, false, realErr
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return sg, false, nil
|
||||
}
|
||||
|
||||
return sg, exists, err
|
||||
}
|
||||
|
||||
func (az *Cloud) getAzureLoadBalancer(name string) (lb network.LoadBalancer, exists bool, err error) {
|
||||
var realErr error
|
||||
|
||||
lb, err = az.LoadBalancerClient.Get(az.ResourceGroup, name, "")
|
||||
|
||||
exists, realErr = checkResourceExistsFromError(err)
|
||||
if realErr != nil {
|
||||
return lb, false, realErr
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return lb, false, nil
|
||||
}
|
||||
|
||||
return lb, exists, err
|
||||
}
|
||||
|
||||
func (az *Cloud) getPublicIPAddress(name string) (pip network.PublicIPAddress, exists bool, err error) {
|
||||
var realErr error
|
||||
|
||||
pip, err = az.PublicIPAddressesClient.Get(az.ResourceGroup, name, "")
|
||||
|
||||
exists, realErr = checkResourceExistsFromError(err)
|
||||
if realErr != nil {
|
||||
return pip, false, realErr
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return pip, false, nil
|
||||
}
|
||||
|
||||
return pip, exists, err
|
||||
}
|
78
pkg/cloudprovider/providers/azure/azure_zones.go
Normal file
78
pkg/cloudprovider/providers/azure/azure_zones.go
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
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 (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
)
|
||||
|
||||
const instanceInfoURL = "http://169.254.169.254/metadata/v1/InstanceInfo"
|
||||
|
||||
var faultMutex = &sync.Mutex{}
|
||||
var faultDomain *string
|
||||
|
||||
type instanceInfo struct {
|
||||
ID string `json:"ID"`
|
||||
UpdateDomain string `json:"UD"`
|
||||
FaultDomain string `json:"FD"`
|
||||
}
|
||||
|
||||
// GetZone returns the Zone containing the current failure zone and locality region that the program is running in
|
||||
func (az *Cloud) GetZone() (cloudprovider.Zone, error) {
|
||||
faultMutex.Lock()
|
||||
if faultDomain == nil {
|
||||
var err error
|
||||
faultDomain, err = fetchFaultDomain()
|
||||
if err != nil {
|
||||
return cloudprovider.Zone{}, err
|
||||
}
|
||||
}
|
||||
zone := cloudprovider.Zone{
|
||||
FailureDomain: *faultDomain,
|
||||
Region: az.Location,
|
||||
}
|
||||
faultMutex.Unlock()
|
||||
return zone, nil
|
||||
}
|
||||
|
||||
func fetchFaultDomain() (*string, error) {
|
||||
resp, err := http.Get(instanceInfoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return readFaultDomain(resp.Body)
|
||||
}
|
||||
|
||||
func readFaultDomain(reader io.Reader) (*string, error) {
|
||||
var instanceInfo instanceInfo
|
||||
body, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(body, &instanceInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &instanceInfo.FaultDomain, nil
|
||||
}
|
@ -121,7 +121,7 @@ func (f *FakeCloud) Routes() (cloudprovider.Routes, bool) {
|
||||
}
|
||||
|
||||
// GetLoadBalancer is a stub implementation of LoadBalancer.GetLoadBalancer.
|
||||
func (f *FakeCloud) GetLoadBalancer(service *api.Service) (*api.LoadBalancerStatus, bool, error) {
|
||||
func (f *FakeCloud) GetLoadBalancer(clusterName string, service *api.Service) (*api.LoadBalancerStatus, bool, error) {
|
||||
status := &api.LoadBalancerStatus{}
|
||||
status.Ingress = []api.LoadBalancerIngress{{IP: f.ExternalIP.String()}}
|
||||
|
||||
@ -130,7 +130,7 @@ func (f *FakeCloud) GetLoadBalancer(service *api.Service) (*api.LoadBalancerStat
|
||||
|
||||
// EnsureLoadBalancer is a test-spy implementation of LoadBalancer.EnsureLoadBalancer.
|
||||
// It adds an entry "create" into the internal method call record.
|
||||
func (f *FakeCloud) EnsureLoadBalancer(service *api.Service, hosts []string) (*api.LoadBalancerStatus, error) {
|
||||
func (f *FakeCloud) EnsureLoadBalancer(clusterName string, service *api.Service, hosts []string) (*api.LoadBalancerStatus, error) {
|
||||
f.addCall("create")
|
||||
if f.Balancers == nil {
|
||||
f.Balancers = make(map[string]FakeBalancer)
|
||||
@ -155,7 +155,7 @@ func (f *FakeCloud) EnsureLoadBalancer(service *api.Service, hosts []string) (*a
|
||||
|
||||
// UpdateLoadBalancer is a test-spy implementation of LoadBalancer.UpdateLoadBalancer.
|
||||
// It adds an entry "update" into the internal method call record.
|
||||
func (f *FakeCloud) UpdateLoadBalancer(service *api.Service, hosts []string) error {
|
||||
func (f *FakeCloud) UpdateLoadBalancer(clusterName string, service *api.Service, hosts []string) error {
|
||||
f.addCall("update")
|
||||
f.UpdateCalls = append(f.UpdateCalls, FakeUpdateBalancerCall{service, hosts})
|
||||
return f.Err
|
||||
@ -163,7 +163,7 @@ func (f *FakeCloud) UpdateLoadBalancer(service *api.Service, hosts []string) err
|
||||
|
||||
// EnsureLoadBalancerDeleted is a test-spy implementation of LoadBalancer.EnsureLoadBalancerDeleted.
|
||||
// It adds an entry "delete" into the internal method call record.
|
||||
func (f *FakeCloud) EnsureLoadBalancerDeleted(service *api.Service) error {
|
||||
func (f *FakeCloud) EnsureLoadBalancerDeleted(clusterName string, service *api.Service) error {
|
||||
f.addCall("delete")
|
||||
return f.Err
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
const volumeAvailableStatus = "available"
|
||||
const volumeInUseStatus = "in-use"
|
||||
const volumeCreateTimeoutSeconds = 30
|
||||
const testClusterName = "testCluster"
|
||||
|
||||
func WaitForVolumeStatus(t *testing.T, os *OpenStack, volumeName string, status string, timeoutSeconds int) {
|
||||
timeout := timeoutSeconds
|
||||
@ -216,7 +217,7 @@ func TestLoadBalancer(t *testing.T) {
|
||||
t.Fatalf("LoadBalancer() returned false - perhaps your stack doesn't support Neutron?")
|
||||
}
|
||||
|
||||
_, exists, err := lb.GetLoadBalancer(&api.Service{ObjectMeta: api.ObjectMeta{Name: "noexist"}})
|
||||
_, exists, err := lb.GetLoadBalancer(testClusterName, &api.Service{ObjectMeta: api.ObjectMeta{Name: "noexist"}})
|
||||
if err != nil {
|
||||
t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err)
|
||||
}
|
||||
@ -242,7 +243,7 @@ func TestLoadBalancerV2(t *testing.T) {
|
||||
t.Fatalf("LoadBalancer() returned false - perhaps your stack doesn't support Neutron?")
|
||||
}
|
||||
|
||||
_, exists, err := lbaas.GetLoadBalancer(&api.Service{ObjectMeta: api.ObjectMeta{Name: "noexist"}})
|
||||
_, exists, err := lbaas.GetLoadBalancer(testClusterName, &api.Service{ObjectMeta: api.ObjectMeta{Name: "noexist"}})
|
||||
if err != nil {
|
||||
t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err)
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package cloudprovider
|
||||
import (
|
||||
// Cloud providers
|
||||
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
|
||||
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/azure"
|
||||
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
|
||||
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/mesos"
|
||||
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/openstack"
|
||||
|
Loading…
Reference in New Issue
Block a user