github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/provider/azure/instance.go (about)

     1  // Copyright 2011, 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package azure
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"launchpad.net/gwacl"
    11  
    12  	"github.com/juju/juju/instance"
    13  	"github.com/juju/juju/network"
    14  )
    15  
    16  const AzureDomainName = "cloudapp.net"
    17  
    18  type azureInstance struct {
    19  	environ              *azureEnviron
    20  	hostedService        *gwacl.HostedServiceDescriptor
    21  	roleInstance         *gwacl.RoleInstance
    22  	instanceId           instance.Id
    23  	deploymentName       string
    24  	roleName             string
    25  	maskStateServerPorts bool
    26  }
    27  
    28  // azureInstance implements Instance.
    29  var _ instance.Instance = (*azureInstance)(nil)
    30  
    31  // Id is specified in the Instance interface.
    32  func (azInstance *azureInstance) Id() instance.Id {
    33  	return azInstance.instanceId
    34  }
    35  
    36  // supportsLoadBalancing returns true iff the instance is
    37  // not a legacy instance where endpoints may have been
    38  // created without load balancing set names associated.
    39  func (azInstance *azureInstance) supportsLoadBalancing() bool {
    40  	v1Name := deploymentNameV1(azInstance.hostedService.ServiceName)
    41  	return azInstance.deploymentName != v1Name
    42  }
    43  
    44  // Status is specified in the Instance interface.
    45  func (azInstance *azureInstance) Status() string {
    46  	if azInstance.roleInstance == nil {
    47  		return ""
    48  	}
    49  	return azInstance.roleInstance.InstanceStatus
    50  }
    51  
    52  func (azInstance *azureInstance) serviceName() string {
    53  	return azInstance.hostedService.ServiceName
    54  }
    55  
    56  // Addresses is specified in the Instance interface.
    57  func (azInstance *azureInstance) Addresses() ([]network.Address, error) {
    58  	var addrs []network.Address
    59  	if ip := azInstance.ipAddress(); ip != "" {
    60  		addrs = append(addrs, network.Address{
    61  			Value:       ip,
    62  			Type:        network.IPv4Address,
    63  			NetworkName: azInstance.environ.getVirtualNetworkName(),
    64  			Scope:       network.ScopeCloudLocal,
    65  		})
    66  	}
    67  	name := fmt.Sprintf("%s.%s", azInstance.serviceName(), AzureDomainName)
    68  	host := network.Address{
    69  		Value:       name,
    70  		Type:        network.HostName,
    71  		NetworkName: "",
    72  		Scope:       network.ScopePublic,
    73  	}
    74  	addrs = append(addrs, host)
    75  	return addrs, nil
    76  }
    77  
    78  func (azInstance *azureInstance) ipAddress() string {
    79  	if azInstance.roleInstance == nil {
    80  		// RoleInstance hasn't finished deploying.
    81  		return ""
    82  	}
    83  	return azInstance.roleInstance.IPAddress
    84  }
    85  
    86  // OpenPorts is specified in the Instance interface.
    87  func (azInstance *azureInstance) OpenPorts(machineId string, portRange []network.PortRange) error {
    88  	return azInstance.apiCall(true, func(api *gwacl.ManagementAPI) error {
    89  		return azInstance.openEndpoints(api, portRange)
    90  	})
    91  }
    92  
    93  // apiCall wraps a call to the azure API, optionally locking the environment.
    94  func (azInstance *azureInstance) apiCall(lock bool, f func(*gwacl.ManagementAPI) error) error {
    95  	env := azInstance.environ
    96  	api := env.getSnapshot().api
    97  	if lock {
    98  		env.Lock()
    99  		defer env.Unlock()
   100  	}
   101  	return f(api)
   102  }
   103  
   104  // openEndpoints opens the endpoints in the Azure deployment.
   105  func (azInstance *azureInstance) openEndpoints(api *gwacl.ManagementAPI, portRanges []network.PortRange) error {
   106  	request := &gwacl.AddRoleEndpointsRequest{
   107  		ServiceName:    azInstance.serviceName(),
   108  		DeploymentName: azInstance.deploymentName,
   109  		RoleName:       azInstance.roleName,
   110  	}
   111  	for _, portRange := range portRanges {
   112  		name := fmt.Sprintf("%s%d-%d", portRange.Protocol, portRange.FromPort, portRange.ToPort)
   113  		for port := portRange.FromPort; port <= portRange.ToPort; port++ {
   114  			endpoint := gwacl.InputEndpoint{
   115  				LocalPort: port,
   116  				Name:      fmt.Sprintf("%s_range_%d", name, port),
   117  				Port:      port,
   118  				Protocol:  portRange.Protocol,
   119  			}
   120  			if azInstance.supportsLoadBalancing() {
   121  				probePort := port
   122  				if strings.ToUpper(endpoint.Protocol) == "UDP" {
   123  					// Load balancing needs a TCP port to probe, or an HTTP
   124  					// server port & path to query. For UDP, we just use the
   125  					// machine's SSH agent port to test machine liveness.
   126  					//
   127  					// It probably doesn't make sense to load balance most UDP
   128  					// protocols transparently, but that's an application level
   129  					// concern.
   130  					probePort = 22
   131  				}
   132  				endpoint.LoadBalancedEndpointSetName = name
   133  				endpoint.LoadBalancerProbe = &gwacl.LoadBalancerProbe{
   134  					Port:     probePort,
   135  					Protocol: "TCP",
   136  				}
   137  			}
   138  			request.InputEndpoints = append(request.InputEndpoints, endpoint)
   139  		}
   140  	}
   141  	return api.AddRoleEndpoints(request)
   142  }
   143  
   144  // ClosePorts is specified in the Instance interface.
   145  func (azInstance *azureInstance) ClosePorts(machineId string, ports []network.PortRange) error {
   146  	return azInstance.apiCall(true, func(api *gwacl.ManagementAPI) error {
   147  		return azInstance.closeEndpoints(api, ports)
   148  	})
   149  }
   150  
   151  // closeEndpoints closes the endpoints in the Azure deployment.
   152  func (azInstance *azureInstance) closeEndpoints(api *gwacl.ManagementAPI, portRanges []network.PortRange) error {
   153  	request := &gwacl.RemoveRoleEndpointsRequest{
   154  		ServiceName:    azInstance.serviceName(),
   155  		DeploymentName: azInstance.deploymentName,
   156  		RoleName:       azInstance.roleName,
   157  	}
   158  	for _, portRange := range portRanges {
   159  		name := fmt.Sprintf("%s%d-%d", portRange.Protocol, portRange.FromPort, portRange.ToPort)
   160  		for port := portRange.FromPort; port <= portRange.ToPort; port++ {
   161  			request.InputEndpoints = append(request.InputEndpoints, gwacl.InputEndpoint{
   162  				LocalPort:                   port,
   163  				Name:                        fmt.Sprintf("%s_%d", name, port),
   164  				Port:                        port,
   165  				Protocol:                    portRange.Protocol,
   166  				LoadBalancedEndpointSetName: name,
   167  			})
   168  		}
   169  	}
   170  	return api.RemoveRoleEndpoints(request)
   171  }
   172  
   173  // convertEndpointsToPorts converts a slice of gwacl.InputEndpoint into a slice of network.PortRange.
   174  func convertEndpointsToPortRanges(endpoints []gwacl.InputEndpoint) []network.PortRange {
   175  	// group ports by prefix on the endpoint name
   176  	portSets := make(map[string][]network.Port)
   177  	otherPorts := []network.Port{}
   178  	for _, endpoint := range endpoints {
   179  		port := network.Port{
   180  			Protocol: strings.ToLower(endpoint.Protocol),
   181  			Number:   endpoint.Port,
   182  		}
   183  		if strings.Contains(endpoint.Name, "_range_") {
   184  			prefix := strings.Split(endpoint.Name, "_range_")[0]
   185  			portSets[prefix] = append(portSets[prefix], port)
   186  		} else {
   187  			otherPorts = append(otherPorts, port)
   188  		}
   189  	}
   190  
   191  	portRanges := []network.PortRange{}
   192  
   193  	// convert port sets into port ranges
   194  	for _, ports := range portSets {
   195  		portRanges = append(portRanges, network.CollapsePorts(ports)...)
   196  	}
   197  
   198  	portRanges = append(portRanges, network.CollapsePorts(otherPorts)...)
   199  	network.SortPortRanges(portRanges)
   200  	return portRanges
   201  }
   202  
   203  // convertAndFilterEndpoints converts a slice of gwacl.InputEndpoint into a slice of network.PortRange
   204  // and filters out the initial endpoints that every instance should have opened (ssh port, etc.).
   205  func convertAndFilterEndpoints(endpoints []gwacl.InputEndpoint, env *azureEnviron, stateServer bool) []network.PortRange {
   206  	return portRangeDiff(
   207  		convertEndpointsToPortRanges(endpoints),
   208  		convertEndpointsToPortRanges(env.getInitialEndpoints(stateServer)),
   209  	)
   210  }
   211  
   212  // portRangeDiff returns all port ranges that are in a but not in b.
   213  func portRangeDiff(A, B []network.PortRange) (missing []network.PortRange) {
   214  next:
   215  	for _, a := range A {
   216  		for _, b := range B {
   217  			if a == b {
   218  				continue next
   219  			}
   220  		}
   221  		missing = append(missing, a)
   222  	}
   223  	return
   224  }
   225  
   226  // Ports is specified in the Instance interface.
   227  func (azInstance *azureInstance) Ports(machineId string) (ports []network.PortRange, err error) {
   228  	err = azInstance.apiCall(false, func(api *gwacl.ManagementAPI) error {
   229  		ports, err = azInstance.listPorts(api)
   230  		return err
   231  	})
   232  	if ports != nil {
   233  		network.SortPortRanges(ports)
   234  	}
   235  	return ports, err
   236  }
   237  
   238  // listPorts returns the slice of port ranges (network.PortRange)
   239  // that this machine has opened. The returned list does not contain
   240  // the "initial port ranges" (i.e. the port ranges every instance
   241  // shoud have opened).
   242  func (azInstance *azureInstance) listPorts(api *gwacl.ManagementAPI) ([]network.PortRange, error) {
   243  	endpoints, err := api.ListRoleEndpoints(&gwacl.ListRoleEndpointsRequest{
   244  		ServiceName:    azInstance.serviceName(),
   245  		DeploymentName: azInstance.deploymentName,
   246  		RoleName:       azInstance.roleName,
   247  	})
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  	ports := convertAndFilterEndpoints(endpoints, azInstance.environ, azInstance.maskStateServerPorts)
   252  	return ports, nil
   253  }