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