launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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  	"launchpad.net/juju-core/instance"
    13  	"launchpad.net/juju-core/provider/common"
    14  	"launchpad.net/juju-core/worker/firewaller"
    15  )
    16  
    17  type azureInstance struct {
    18  	// An instance contains an Azure Service (instance==service).
    19  	gwacl.HostedServiceDescriptor
    20  	environ *azureEnviron
    21  }
    22  
    23  // azureInstance implements Instance.
    24  var _ instance.Instance = (*azureInstance)(nil)
    25  
    26  // Id is specified in the Instance interface.
    27  func (azInstance *azureInstance) Id() instance.Id {
    28  	return instance.Id(azInstance.ServiceName)
    29  }
    30  
    31  // Status is specified in the Instance interface.
    32  func (azInstance *azureInstance) Status() string {
    33  	return azInstance.HostedServiceDescriptor.Status
    34  }
    35  
    36  var AZURE_DOMAIN_NAME = "cloudapp.net"
    37  
    38  // Refresh is specified in the Instance interface.
    39  func (azInstance *azureInstance) Refresh() error {
    40  	// TODO(axw) 2013-12-16 #1261324
    41  	// Cache Addresses/netInfo, refresh here.
    42  	return nil
    43  }
    44  
    45  // Addresses is specified in the Instance interface.
    46  func (azInstance *azureInstance) Addresses() ([]instance.Address, error) {
    47  	addrs := []instance.Address{}
    48  	ip, netname, err := azInstance.netInfo()
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  	if ip != "" {
    53  		addrs = append(addrs, instance.Address{
    54  			ip,
    55  			instance.Ipv4Address,
    56  			netname,
    57  			instance.NetworkCloudLocal})
    58  	}
    59  
    60  	name, err := azInstance.DNSName()
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	host := instance.Address{name, instance.HostName, "", instance.NetworkPublic}
    65  	addrs = append(addrs, host)
    66  	return addrs, nil
    67  }
    68  
    69  func (azInstance *azureInstance) netInfo() (ip, netname string, err error) {
    70  	err = azInstance.apiCall(false, func(c *azureManagementContext) error {
    71  		d, err := c.GetDeployment(&gwacl.GetDeploymentRequest{
    72  			ServiceName:    azInstance.ServiceName,
    73  			DeploymentName: azInstance.ServiceName,
    74  		})
    75  		if err != nil {
    76  			return err
    77  		}
    78  		switch len(d.RoleInstanceList) {
    79  		case 0:
    80  			// nothing to do, this can happen if the instances aren't finished deploying
    81  			return nil
    82  		case 1:
    83  			// success
    84  			ip = d.RoleInstanceList[0].IPAddress
    85  			netname = d.VirtualNetworkName
    86  			return nil
    87  		default:
    88  			return fmt.Errorf("Too many instances, expected one, got %d", len(d.RoleInstanceList))
    89  		}
    90  	})
    91  	if err != nil {
    92  		return "", "", err
    93  	}
    94  	return ip, netname, nil
    95  }
    96  
    97  // DNSName is specified in the Instance interface.
    98  func (azInstance *azureInstance) DNSName() (string, error) {
    99  	// For deployments in the Production slot, the instance's DNS name
   100  	// is its service name, in the cloudapp.net domain.
   101  	// (For Staging deployments it's all much weirder: they get random
   102  	// names assigned, which somehow don't seem to resolve from the
   103  	// outside.)
   104  	name := fmt.Sprintf("%s.%s", azInstance.ServiceName, AZURE_DOMAIN_NAME)
   105  	return name, nil
   106  }
   107  
   108  // WaitDNSName is specified in the Instance interface.
   109  func (azInstance *azureInstance) WaitDNSName() (string, error) {
   110  	return common.WaitDNSName(azInstance)
   111  }
   112  
   113  // OpenPorts is specified in the Instance interface.
   114  func (azInstance *azureInstance) OpenPorts(machineId string, ports []instance.Port) error {
   115  	return azInstance.apiCall(true, func(context *azureManagementContext) error {
   116  		return azInstance.openEndpoints(context, ports)
   117  	})
   118  }
   119  
   120  // apiCall wraps a call to the azure API to ensure it is properly disposed, optionally locking
   121  // the environment
   122  func (azInstance *azureInstance) apiCall(lock bool, f func(*azureManagementContext) error) error {
   123  	env := azInstance.environ
   124  
   125  	context, err := env.getManagementAPI()
   126  	if err != nil {
   127  		return err
   128  	}
   129  	defer env.releaseManagementAPI(context)
   130  
   131  	if lock {
   132  		env.Lock()
   133  		defer env.Unlock()
   134  	}
   135  	return f(context)
   136  }
   137  
   138  // openEndpoints opens the endpoints in the Azure deployment. The caller is
   139  // responsible for locking and unlocking the environ and releasing the
   140  // management context.
   141  func (azInstance *azureInstance) openEndpoints(context *azureManagementContext, ports []instance.Port) error {
   142  	deployments, err := context.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{
   143  		ServiceName: azInstance.ServiceName,
   144  	})
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	for _, deployment := range deployments {
   150  		for _, role := range deployment.RoleList {
   151  			request := &gwacl.AddRoleEndpointsRequest{
   152  				ServiceName:    azInstance.ServiceName,
   153  				DeploymentName: deployment.Name,
   154  				RoleName:       role.RoleName,
   155  			}
   156  			for _, port := range ports {
   157  				request.InputEndpoints = append(
   158  					request.InputEndpoints, gwacl.InputEndpoint{
   159  						LocalPort: port.Number,
   160  						Name:      fmt.Sprintf("%s%d", port.Protocol, port.Number),
   161  						Port:      port.Number,
   162  						Protocol:  port.Protocol,
   163  					})
   164  			}
   165  			err := context.AddRoleEndpoints(request)
   166  			if err != nil {
   167  				return err
   168  			}
   169  		}
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  // ClosePorts is specified in the Instance interface.
   176  func (azInstance *azureInstance) ClosePorts(machineId string, ports []instance.Port) error {
   177  	return azInstance.apiCall(true, func(context *azureManagementContext) error {
   178  		return azInstance.closeEndpoints(context, ports)
   179  	})
   180  }
   181  
   182  // closeEndpoints closes the endpoints in the Azure deployment. The caller is
   183  // responsible for locking and unlocking the environ and releasing the
   184  // management context.
   185  func (azInstance *azureInstance) closeEndpoints(context *azureManagementContext, ports []instance.Port) error {
   186  	deployments, err := context.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{
   187  		ServiceName: azInstance.ServiceName,
   188  	})
   189  	if err != nil {
   190  		return err
   191  	}
   192  
   193  	for _, deployment := range deployments {
   194  		for _, role := range deployment.RoleList {
   195  			request := &gwacl.RemoveRoleEndpointsRequest{
   196  				ServiceName:    azInstance.ServiceName,
   197  				DeploymentName: deployment.Name,
   198  				RoleName:       role.RoleName,
   199  			}
   200  			for _, port := range ports {
   201  				request.InputEndpoints = append(
   202  					request.InputEndpoints, gwacl.InputEndpoint{
   203  						LocalPort: port.Number,
   204  						Name:      fmt.Sprintf("%s%d", port.Protocol, port.Number),
   205  						Port:      port.Number,
   206  						Protocol:  port.Protocol,
   207  					})
   208  			}
   209  			err := context.RemoveRoleEndpoints(request)
   210  			if err != nil {
   211  				return err
   212  			}
   213  		}
   214  	}
   215  
   216  	return nil
   217  }
   218  
   219  // convertAndFilterEndpoints converts a slice of gwacl.InputEndpoint into a slice of instance.Port.
   220  func convertEndpointsToPorts(endpoints []gwacl.InputEndpoint) []instance.Port {
   221  	ports := []instance.Port{}
   222  	for _, endpoint := range endpoints {
   223  		ports = append(ports, instance.Port{
   224  			Protocol: strings.ToLower(endpoint.Protocol),
   225  			Number:   endpoint.Port,
   226  		})
   227  	}
   228  	return ports
   229  }
   230  
   231  // convertAndFilterEndpoints converts a slice of gwacl.InputEndpoint into a slice of instance.Port
   232  // and filters out the initial endpoints that every instance should have opened (ssh port, etc.).
   233  func convertAndFilterEndpoints(endpoints []gwacl.InputEndpoint, env *azureEnviron) []instance.Port {
   234  	return firewaller.Diff(
   235  		convertEndpointsToPorts(endpoints),
   236  		convertEndpointsToPorts(env.getInitialEndpoints()))
   237  }
   238  
   239  // Ports is specified in the Instance interface.
   240  func (azInstance *azureInstance) Ports(machineId string) (ports []instance.Port, err error) {
   241  	err = azInstance.apiCall(false, func(context *azureManagementContext) error {
   242  		ports, err = azInstance.listPorts(context)
   243  		return err
   244  	})
   245  	if ports != nil {
   246  		instance.SortPorts(ports)
   247  	}
   248  	return ports, err
   249  }
   250  
   251  // listPorts returns the slice of ports (instance.Port) that this machine
   252  // has opened. The returned list does not contain the "initial ports"
   253  // (i.e. the ports every instance shoud have opened). The caller is
   254  // responsible for locking and unlocking the environ and releasing the
   255  // management context.
   256  func (azInstance *azureInstance) listPorts(context *azureManagementContext) ([]instance.Port, error) {
   257  	deployments, err := context.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{
   258  		ServiceName: azInstance.ServiceName,
   259  	})
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  
   264  	env := azInstance.environ
   265  	switch {
   266  	// Only zero or one deployment is a valid state (instance==service).
   267  	case len(deployments) > 1:
   268  		return nil, fmt.Errorf("more than one Azure deployment inside the service named %q", azInstance.ServiceName)
   269  	case len(deployments) == 1:
   270  		deployment := deployments[0]
   271  		switch {
   272  		// Only zero or one role is a valid state (instance==service).
   273  		case len(deployment.RoleList) > 1:
   274  			return nil, fmt.Errorf("more than one Azure role inside the deployment named %q", deployment.Name)
   275  		case len(deployment.RoleList) == 1:
   276  			role := deployment.RoleList[0]
   277  
   278  			endpoints, err := context.ListRoleEndpoints(&gwacl.ListRoleEndpointsRequest{
   279  				ServiceName:    azInstance.ServiceName,
   280  				DeploymentName: deployment.Name,
   281  				RoleName:       role.RoleName,
   282  			})
   283  			if err != nil {
   284  				return nil, err
   285  			}
   286  			ports := convertAndFilterEndpoints(endpoints, env)
   287  			return ports, nil
   288  		}
   289  		return nil, nil
   290  	}
   291  	return nil, nil
   292  }