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