Commit 2ebffb43 authored by Cole Mickens's avatar Cole Mickens

implement azure cloudprovider

parent 6ad9dc65
...@@ -81,12 +81,15 @@ type LoadBalancer interface { ...@@ -81,12 +81,15 @@ type LoadBalancer interface {
// GetLoadBalancer returns whether the specified load balancer exists, and // GetLoadBalancer returns whether the specified load balancer exists, and
// if so, what its status is. // if so, what its status is.
// Implementations must treat the *api.Service parameter as read-only and not modify it. // 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) 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 // 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. // 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) EnsureLoadBalancer(clusterName string, service *api.Service, hosts []string) (*api.LoadBalancerStatus, error)
// UpdateLoadBalancer updates hosts under the specified load balancer. // UpdateLoadBalancer updates hosts under the specified load balancer.
// Implementations must treat the *api.Service parameter as read-only and not modify it. // 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 UpdateLoadBalancer(clusterName string, service *api.Service, hosts []string) error
// EnsureLoadBalancerDeleted deletes the specified load balancer if it // EnsureLoadBalancerDeleted deletes the specified load balancer if it
// exists, returning nil if the load balancer specified either didn't exist or // exists, returning nil if the load balancer specified either didn't exist or
...@@ -95,6 +98,7 @@ type LoadBalancer interface { ...@@ -95,6 +98,7 @@ type LoadBalancer interface {
// have multiple underlying components, meaning a Get could say that the LB // 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. // 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. // 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 EnsureLoadBalancerDeleted(clusterName string, service *api.Service) error
} }
......
...@@ -37,6 +37,7 @@ import ( ...@@ -37,6 +37,7 @@ import (
) )
const TestClusterId = "clusterid.test" const TestClusterId = "clusterid.test"
const TestClusterName = "testCluster"
func TestReadAWSCloudConfig(t *testing.T) { func TestReadAWSCloudConfig(t *testing.T) {
tests := []struct { tests := []struct {
...@@ -1182,7 +1183,7 @@ func TestDescribeLoadBalancerOnDelete(t *testing.T) { ...@@ -1182,7 +1183,7 @@ func TestDescribeLoadBalancerOnDelete(t *testing.T) {
c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices)
awsServices.elb.expectDescribeLoadBalancers("aid") 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) { func TestDescribeLoadBalancerOnUpdate(t *testing.T) {
...@@ -1190,7 +1191,7 @@ func TestDescribeLoadBalancerOnUpdate(t *testing.T) { ...@@ -1190,7 +1191,7 @@ func TestDescribeLoadBalancerOnUpdate(t *testing.T) {
c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices)
awsServices.elb.expectDescribeLoadBalancers("aid") 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) { func TestDescribeLoadBalancerOnGet(t *testing.T) {
...@@ -1198,7 +1199,7 @@ func TestDescribeLoadBalancerOnGet(t *testing.T) { ...@@ -1198,7 +1199,7 @@ func TestDescribeLoadBalancerOnGet(t *testing.T) {
c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices)
awsServices.elb.expectDescribeLoadBalancers("aid") 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) { func TestDescribeLoadBalancerOnEnsure(t *testing.T) {
...@@ -1206,7 +1207,7 @@ func TestDescribeLoadBalancerOnEnsure(t *testing.T) { ...@@ -1206,7 +1207,7 @@ func TestDescribeLoadBalancerOnEnsure(t *testing.T) {
c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices) c, _ := newAWSCloud(strings.NewReader("[global]"), awsServices)
awsServices.elb.expectDescribeLoadBalancers("aid") 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) { func TestBuildListener(t *testing.T) {
......
assignees:
- colemickens
- brendandburns
/*
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
}
/*
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
}
/*
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)
}
/*
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
}
/*
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
}
/*
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) { ...@@ -121,7 +121,7 @@ func (f *FakeCloud) Routes() (cloudprovider.Routes, bool) {
} }
// GetLoadBalancer is a stub implementation of LoadBalancer.GetLoadBalancer. // 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 := &api.LoadBalancerStatus{}
status.Ingress = []api.LoadBalancerIngress{{IP: f.ExternalIP.String()}} status.Ingress = []api.LoadBalancerIngress{{IP: f.ExternalIP.String()}}
...@@ -130,7 +130,7 @@ func (f *FakeCloud) GetLoadBalancer(service *api.Service) (*api.LoadBalancerStat ...@@ -130,7 +130,7 @@ func (f *FakeCloud) GetLoadBalancer(service *api.Service) (*api.LoadBalancerStat
// EnsureLoadBalancer is a test-spy implementation of LoadBalancer.EnsureLoadBalancer. // EnsureLoadBalancer is a test-spy implementation of LoadBalancer.EnsureLoadBalancer.
// It adds an entry "create" into the internal method call record. // 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") f.addCall("create")
if f.Balancers == nil { if f.Balancers == nil {
f.Balancers = make(map[string]FakeBalancer) f.Balancers = make(map[string]FakeBalancer)
...@@ -155,7 +155,7 @@ func (f *FakeCloud) EnsureLoadBalancer(service *api.Service, hosts []string) (*a ...@@ -155,7 +155,7 @@ func (f *FakeCloud) EnsureLoadBalancer(service *api.Service, hosts []string) (*a
// UpdateLoadBalancer is a test-spy implementation of LoadBalancer.UpdateLoadBalancer. // UpdateLoadBalancer is a test-spy implementation of LoadBalancer.UpdateLoadBalancer.
// It adds an entry "update" into the internal method call record. // 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.addCall("update")
f.UpdateCalls = append(f.UpdateCalls, FakeUpdateBalancerCall{service, hosts}) f.UpdateCalls = append(f.UpdateCalls, FakeUpdateBalancerCall{service, hosts})
return f.Err return f.Err
...@@ -163,7 +163,7 @@ func (f *FakeCloud) UpdateLoadBalancer(service *api.Service, hosts []string) err ...@@ -163,7 +163,7 @@ func (f *FakeCloud) UpdateLoadBalancer(service *api.Service, hosts []string) err
// EnsureLoadBalancerDeleted is a test-spy implementation of LoadBalancer.EnsureLoadBalancerDeleted. // EnsureLoadBalancerDeleted is a test-spy implementation of LoadBalancer.EnsureLoadBalancerDeleted.
// It adds an entry "delete" into the internal method call record. // 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") f.addCall("delete")
return f.Err return f.Err
} }
......
...@@ -31,6 +31,7 @@ import ( ...@@ -31,6 +31,7 @@ import (
const volumeAvailableStatus = "available" const volumeAvailableStatus = "available"
const volumeInUseStatus = "in-use" const volumeInUseStatus = "in-use"
const volumeCreateTimeoutSeconds = 30 const volumeCreateTimeoutSeconds = 30
const testClusterName = "testCluster"
func WaitForVolumeStatus(t *testing.T, os *OpenStack, volumeName string, status string, timeoutSeconds int) { func WaitForVolumeStatus(t *testing.T, os *OpenStack, volumeName string, status string, timeoutSeconds int) {
timeout := timeoutSeconds timeout := timeoutSeconds
...@@ -216,7 +217,7 @@ func TestLoadBalancer(t *testing.T) { ...@@ -216,7 +217,7 @@ func TestLoadBalancer(t *testing.T) {
t.Fatalf("LoadBalancer() returned false - perhaps your stack doesn't support Neutron?") 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 { if err != nil {
t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err) t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err)
} }
...@@ -242,7 +243,7 @@ func TestLoadBalancerV2(t *testing.T) { ...@@ -242,7 +243,7 @@ func TestLoadBalancerV2(t *testing.T) {
t.Fatalf("LoadBalancer() returned false - perhaps your stack doesn't support Neutron?") 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 { if err != nil {
t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err) t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err)
} }
......
...@@ -19,6 +19,7 @@ package cloudprovider ...@@ -19,6 +19,7 @@ package cloudprovider
import ( import (
// Cloud providers // Cloud providers
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/aws" _ "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/gce"
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/mesos" _ "k8s.io/kubernetes/pkg/cloudprovider/providers/mesos"
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/openstack" _ "k8s.io/kubernetes/pkg/cloudprovider/providers/openstack"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment