github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/azure/instance.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package azure
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"github.com/Azure/azure-sdk-for-go/arm/network"
    12  	"github.com/Azure/go-autorest/autorest"
    13  	"github.com/Azure/go-autorest/autorest/to"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/juju/instance"
    17  	jujunetwork "github.com/juju/juju/network"
    18  	"github.com/juju/juju/status"
    19  	"gopkg.in/juju/names.v2"
    20  )
    21  
    22  type azureInstance struct {
    23  	vmName            string
    24  	provisioningState string
    25  	env               *azureEnviron
    26  	networkInterfaces []network.Interface
    27  	publicIPAddresses []network.PublicIPAddress
    28  }
    29  
    30  // Id is specified in the Instance interface.
    31  func (inst *azureInstance) Id() instance.Id {
    32  	// Note: we use Name and not Id, since all VM operations are in
    33  	// terms of the VM name (qualified by resource group). The ID is
    34  	// an internal detail.
    35  	return instance.Id(inst.vmName)
    36  }
    37  
    38  // Status is specified in the Instance interface.
    39  func (inst *azureInstance) Status() instance.InstanceStatus {
    40  	instanceStatus := status.Empty
    41  	message := inst.provisioningState
    42  	switch inst.provisioningState {
    43  	case "Succeeded":
    44  		// TODO(axw) once a VM has been started, we should
    45  		// start using its power state to show if it's
    46  		// really running or not. This is just a nice to
    47  		// have, since we should not expect a VM to ever
    48  		// be stopped.
    49  		instanceStatus = status.Running
    50  		message = ""
    51  	case "Canceled", "Failed":
    52  		// TODO(axw) if the provisioning state is "Failed", then we
    53  		// should use the error message from the deployment description
    54  		// as the Message. The error details are not currently exposed
    55  		// in the Azure SDK. See:
    56  		//     https://github.com/Azure/azure-sdk-for-go/issues/399
    57  		instanceStatus = status.ProvisioningError
    58  	case "Running":
    59  		message = ""
    60  		fallthrough
    61  	default:
    62  		instanceStatus = status.Provisioning
    63  	}
    64  	return instance.InstanceStatus{
    65  		Status:  instanceStatus,
    66  		Message: message,
    67  	}
    68  }
    69  
    70  // setInstanceAddresses queries Azure for the NICs and public IPs associated
    71  // with the given set of instances. This assumes that the instances'
    72  // VirtualMachines are up-to-date, and that there are no concurrent accesses
    73  // to the instances.
    74  func setInstanceAddresses(
    75  	callAPI callAPIFunc,
    76  	resourceGroup string,
    77  	nicClient network.InterfacesClient,
    78  	pipClient network.PublicIPAddressesClient,
    79  	instances []*azureInstance,
    80  ) (err error) {
    81  	instanceNics, err := instanceNetworkInterfaces(
    82  		callAPI, resourceGroup, nicClient,
    83  	)
    84  	if err != nil {
    85  		return errors.Annotate(err, "listing network interfaces")
    86  	}
    87  	instancePips, err := instancePublicIPAddresses(
    88  		callAPI, resourceGroup, pipClient,
    89  	)
    90  	if err != nil {
    91  		return errors.Annotate(err, "listing public IP addresses")
    92  	}
    93  	for _, inst := range instances {
    94  		inst.networkInterfaces = instanceNics[inst.Id()]
    95  		inst.publicIPAddresses = instancePips[inst.Id()]
    96  	}
    97  	return nil
    98  }
    99  
   100  // instanceNetworkInterfaces lists all network interfaces in the resource
   101  // group, and returns a mapping from instance ID to the network interfaces
   102  // associated with that instance.
   103  func instanceNetworkInterfaces(
   104  	callAPI callAPIFunc,
   105  	resourceGroup string,
   106  	nicClient network.InterfacesClient,
   107  ) (map[instance.Id][]network.Interface, error) {
   108  	var nicsResult network.InterfaceListResult
   109  	if err := callAPI(func() (autorest.Response, error) {
   110  		var err error
   111  		nicsResult, err = nicClient.List(resourceGroup)
   112  		return nicsResult.Response, err
   113  	}); err != nil {
   114  		return nil, errors.Annotate(err, "listing network interfaces")
   115  	}
   116  	if nicsResult.Value == nil || len(*nicsResult.Value) == 0 {
   117  		return nil, nil
   118  	}
   119  	instanceNics := make(map[instance.Id][]network.Interface)
   120  	for _, nic := range *nicsResult.Value {
   121  		instanceId := instance.Id(toTags(nic.Tags)[jujuMachineNameTag])
   122  		instanceNics[instanceId] = append(instanceNics[instanceId], nic)
   123  	}
   124  	return instanceNics, nil
   125  }
   126  
   127  // interfacePublicIPAddresses lists all public IP addresses in the resource
   128  // group, and returns a mapping from instance ID to the public IP addresses
   129  // associated with that instance.
   130  func instancePublicIPAddresses(
   131  	callAPI callAPIFunc,
   132  	resourceGroup string,
   133  	pipClient network.PublicIPAddressesClient,
   134  ) (map[instance.Id][]network.PublicIPAddress, error) {
   135  	var pipsResult network.PublicIPAddressListResult
   136  	if err := callAPI(func() (autorest.Response, error) {
   137  		var err error
   138  		pipsResult, err = pipClient.List(resourceGroup)
   139  		return pipsResult.Response, err
   140  	}); err != nil {
   141  		return nil, errors.Annotate(err, "listing public IP addresses")
   142  	}
   143  	if pipsResult.Value == nil || len(*pipsResult.Value) == 0 {
   144  		return nil, nil
   145  	}
   146  	instancePips := make(map[instance.Id][]network.PublicIPAddress)
   147  	for _, pip := range *pipsResult.Value {
   148  		instanceId := instance.Id(toTags(pip.Tags)[jujuMachineNameTag])
   149  		instancePips[instanceId] = append(instancePips[instanceId], pip)
   150  	}
   151  	return instancePips, nil
   152  }
   153  
   154  // Addresses is specified in the Instance interface.
   155  func (inst *azureInstance) Addresses() ([]jujunetwork.Address, error) {
   156  	addresses := make([]jujunetwork.Address, 0, len(inst.networkInterfaces)+len(inst.publicIPAddresses))
   157  	for _, nic := range inst.networkInterfaces {
   158  		if nic.Properties.IPConfigurations == nil {
   159  			continue
   160  		}
   161  		for _, ipConfiguration := range *nic.Properties.IPConfigurations {
   162  			privateIpAddress := ipConfiguration.Properties.PrivateIPAddress
   163  			if privateIpAddress == nil {
   164  				continue
   165  			}
   166  			addresses = append(addresses, jujunetwork.NewScopedAddress(
   167  				to.String(privateIpAddress),
   168  				jujunetwork.ScopeCloudLocal,
   169  			))
   170  		}
   171  	}
   172  	for _, pip := range inst.publicIPAddresses {
   173  		if pip.Properties.IPAddress == nil {
   174  			continue
   175  		}
   176  		addresses = append(addresses, jujunetwork.NewScopedAddress(
   177  			to.String(pip.Properties.IPAddress),
   178  			jujunetwork.ScopePublic,
   179  		))
   180  	}
   181  	return addresses, nil
   182  }
   183  
   184  // primaryNetworkAddress returns the instance's primary jujunetwork.Address for
   185  // the internal virtual network. This address is used to identify the machine in
   186  // network security rules.
   187  func (inst *azureInstance) primaryNetworkAddress() (jujunetwork.Address, error) {
   188  	for _, nic := range inst.networkInterfaces {
   189  		if nic.Properties.IPConfigurations == nil {
   190  			continue
   191  		}
   192  		for _, ipConfiguration := range *nic.Properties.IPConfigurations {
   193  			if ipConfiguration.Properties.Subnet == nil {
   194  				continue
   195  			}
   196  			if !to.Bool(ipConfiguration.Properties.Primary) {
   197  				continue
   198  			}
   199  			privateIpAddress := ipConfiguration.Properties.PrivateIPAddress
   200  			if privateIpAddress == nil {
   201  				continue
   202  			}
   203  			return jujunetwork.NewScopedAddress(
   204  				to.String(privateIpAddress),
   205  				jujunetwork.ScopeCloudLocal,
   206  			), nil
   207  		}
   208  	}
   209  	return jujunetwork.Address{}, errors.NotFoundf("internal network address")
   210  }
   211  
   212  // OpenPorts is specified in the Instance interface.
   213  func (inst *azureInstance) OpenPorts(machineId string, ports []jujunetwork.PortRange) error {
   214  	nsgClient := network.SecurityGroupsClient{inst.env.network}
   215  	securityRuleClient := network.SecurityRulesClient{inst.env.network}
   216  	primaryNetworkAddress, err := inst.primaryNetworkAddress()
   217  	if err != nil {
   218  		return errors.Trace(err)
   219  	}
   220  
   221  	securityGroupName := internalSecurityGroupName
   222  	var nsg network.SecurityGroup
   223  	if err := inst.env.callAPI(func() (autorest.Response, error) {
   224  		var err error
   225  		nsg, err = nsgClient.Get(inst.env.resourceGroup, securityGroupName, "")
   226  		return nsg.Response, err
   227  	}); err != nil {
   228  		return errors.Annotate(err, "querying network security group")
   229  	}
   230  
   231  	var securityRules []network.SecurityRule
   232  	if nsg.Properties.SecurityRules != nil {
   233  		securityRules = *nsg.Properties.SecurityRules
   234  	} else {
   235  		nsg.Properties.SecurityRules = &securityRules
   236  	}
   237  
   238  	// Create rules one at a time; this is necessary to avoid trampling
   239  	// on changes made by the provisioner. We still record rules in the
   240  	// NSG in memory, so we can easily tell which priorities are available.
   241  	vmName := resourceName(names.NewMachineTag(machineId))
   242  	prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName))
   243  	for _, ports := range ports {
   244  		ruleName := securityRuleName(prefix, ports)
   245  
   246  		// Check if the rule already exists; OpenPorts must be idempotent.
   247  		var found bool
   248  		for _, rule := range securityRules {
   249  			if to.String(rule.Name) == ruleName {
   250  				found = true
   251  				break
   252  			}
   253  		}
   254  		if found {
   255  			logger.Debugf("security rule %q already exists", ruleName)
   256  			continue
   257  		}
   258  		logger.Debugf("creating security rule %q", ruleName)
   259  
   260  		priority, err := nextSecurityRulePriority(nsg, securityRuleInternalMax+1, securityRuleMax)
   261  		if err != nil {
   262  			return errors.Annotatef(err, "getting security rule priority for %s", ports)
   263  		}
   264  
   265  		var protocol network.SecurityRuleProtocol
   266  		switch ports.Protocol {
   267  		case "tcp":
   268  			protocol = network.TCP
   269  		case "udp":
   270  			protocol = network.UDP
   271  		default:
   272  			return errors.Errorf("invalid protocol %q", ports.Protocol)
   273  		}
   274  
   275  		var portRange string
   276  		if ports.FromPort != ports.ToPort {
   277  			portRange = fmt.Sprintf("%d-%d", ports.FromPort, ports.ToPort)
   278  		} else {
   279  			portRange = fmt.Sprint(ports.FromPort)
   280  		}
   281  
   282  		rule := network.SecurityRule{
   283  			Properties: &network.SecurityRulePropertiesFormat{
   284  				Description:              to.StringPtr(ports.String()),
   285  				Protocol:                 protocol,
   286  				SourcePortRange:          to.StringPtr("*"),
   287  				DestinationPortRange:     to.StringPtr(portRange),
   288  				SourceAddressPrefix:      to.StringPtr("*"),
   289  				DestinationAddressPrefix: to.StringPtr(primaryNetworkAddress.Value),
   290  				Access:    network.Allow,
   291  				Priority:  to.Int32Ptr(priority),
   292  				Direction: network.Inbound,
   293  			},
   294  		}
   295  		if err := inst.env.callAPI(func() (autorest.Response, error) {
   296  			return securityRuleClient.CreateOrUpdate(
   297  				inst.env.resourceGroup, securityGroupName, ruleName, rule,
   298  				nil, // abort channel
   299  			)
   300  		}); err != nil {
   301  			return errors.Annotatef(err, "creating security rule for %s", ports)
   302  		}
   303  		securityRules = append(securityRules, rule)
   304  	}
   305  	return nil
   306  }
   307  
   308  // ClosePorts is specified in the Instance interface.
   309  func (inst *azureInstance) ClosePorts(machineId string, ports []jujunetwork.PortRange) error {
   310  	securityRuleClient := network.SecurityRulesClient{inst.env.network}
   311  	securityGroupName := internalSecurityGroupName
   312  
   313  	// Delete rules one at a time; this is necessary to avoid trampling
   314  	// on changes made by the provisioner.
   315  	vmName := resourceName(names.NewMachineTag(machineId))
   316  	prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName))
   317  	for _, ports := range ports {
   318  		ruleName := securityRuleName(prefix, ports)
   319  		logger.Debugf("deleting security rule %q", ruleName)
   320  		var result autorest.Response
   321  		if err := inst.env.callAPI(func() (autorest.Response, error) {
   322  			var err error
   323  			result, err = securityRuleClient.Delete(
   324  				inst.env.resourceGroup, securityGroupName, ruleName,
   325  				nil, // abort channel
   326  			)
   327  			return result, err
   328  		}); err != nil {
   329  			if result.Response == nil || result.StatusCode != http.StatusNotFound {
   330  				return errors.Annotatef(err, "deleting security rule %q", ruleName)
   331  			}
   332  		}
   333  	}
   334  	return nil
   335  }
   336  
   337  // Ports is specified in the Instance interface.
   338  func (inst *azureInstance) Ports(machineId string) (ports []jujunetwork.PortRange, err error) {
   339  	nsgClient := network.SecurityGroupsClient{inst.env.network}
   340  	securityGroupName := internalSecurityGroupName
   341  	var nsg network.SecurityGroup
   342  	if err := inst.env.callAPI(func() (autorest.Response, error) {
   343  		var err error
   344  		nsg, err = nsgClient.Get(inst.env.resourceGroup, securityGroupName, "")
   345  		return nsg.Response, err
   346  	}); err != nil {
   347  		return nil, errors.Annotate(err, "querying network security group")
   348  	}
   349  	if nsg.Properties.SecurityRules == nil {
   350  		return nil, nil
   351  	}
   352  
   353  	vmName := resourceName(names.NewMachineTag(machineId))
   354  	prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName))
   355  	for _, rule := range *nsg.Properties.SecurityRules {
   356  		if rule.Properties.Direction != network.Inbound {
   357  			continue
   358  		}
   359  		if rule.Properties.Access != network.Allow {
   360  			continue
   361  		}
   362  		if to.Int32(rule.Properties.Priority) <= securityRuleInternalMax {
   363  			continue
   364  		}
   365  		if !strings.HasPrefix(to.String(rule.Name), prefix) {
   366  			continue
   367  		}
   368  
   369  		var portRange jujunetwork.PortRange
   370  		if *rule.Properties.DestinationPortRange == "*" {
   371  			portRange.FromPort = 0
   372  			portRange.ToPort = 65535
   373  		} else {
   374  			portRange, err = jujunetwork.ParsePortRange(
   375  				*rule.Properties.DestinationPortRange,
   376  			)
   377  			if err != nil {
   378  				return nil, errors.Annotatef(
   379  					err, "parsing port range for security rule %q",
   380  					to.String(rule.Name),
   381  				)
   382  			}
   383  		}
   384  
   385  		var protocols []string
   386  		switch rule.Properties.Protocol {
   387  		case network.TCP:
   388  			protocols = []string{"tcp"}
   389  		case network.UDP:
   390  			protocols = []string{"udp"}
   391  		default:
   392  			protocols = []string{"tcp", "udp"}
   393  		}
   394  		for _, protocol := range protocols {
   395  			portRange.Protocol = protocol
   396  			ports = append(ports, portRange)
   397  		}
   398  	}
   399  	return ports, nil
   400  }
   401  
   402  // deleteInstanceNetworkSecurityRules deletes network security rules in the
   403  // internal network security group that correspond to the specified machine.
   404  //
   405  // This is expected to delete *all* security rules related to the instance,
   406  // i.e. both the ones opened by OpenPorts above, and the ones opened for API
   407  // access.
   408  func deleteInstanceNetworkSecurityRules(
   409  	resourceGroup string, id instance.Id,
   410  	nsgClient network.SecurityGroupsClient,
   411  	securityRuleClient network.SecurityRulesClient,
   412  	callAPI callAPIFunc,
   413  ) error {
   414  	var nsg network.SecurityGroup
   415  	if err := callAPI(func() (autorest.Response, error) {
   416  		var err error
   417  		nsg, err = nsgClient.Get(resourceGroup, internalSecurityGroupName, "")
   418  		return nsg.Response, err
   419  	}); err != nil {
   420  		return errors.Annotate(err, "querying network security group")
   421  	}
   422  	if nsg.Properties.SecurityRules == nil {
   423  		return nil
   424  	}
   425  	prefix := instanceNetworkSecurityRulePrefix(id)
   426  	for _, rule := range *nsg.Properties.SecurityRules {
   427  		ruleName := to.String(rule.Name)
   428  		if !strings.HasPrefix(ruleName, prefix) {
   429  			continue
   430  		}
   431  		var result autorest.Response
   432  		err := callAPI(func() (autorest.Response, error) {
   433  			var err error
   434  			result, err = securityRuleClient.Delete(
   435  				resourceGroup,
   436  				internalSecurityGroupName,
   437  				ruleName,
   438  				nil, // abort channel
   439  			)
   440  			return result, err
   441  		})
   442  		if err != nil {
   443  			if result.Response == nil || result.StatusCode != http.StatusNotFound {
   444  				return errors.Annotatef(err, "deleting security rule %q", ruleName)
   445  			}
   446  		}
   447  	}
   448  	return nil
   449  }
   450  
   451  // instanceNetworkSecurityRulePrefix returns the unique prefix for network
   452  // security rule names that relate to the instance with the given ID.
   453  func instanceNetworkSecurityRulePrefix(id instance.Id) string {
   454  	return string(id) + "-"
   455  }
   456  
   457  // securityRuleName returns the security rule name for the given port range,
   458  // and prefix returned by instanceNetworkSecurityRulePrefix.
   459  func securityRuleName(prefix string, ports jujunetwork.PortRange) string {
   460  	ruleName := fmt.Sprintf("%s%s-%d", prefix, ports.Protocol, ports.FromPort)
   461  	if ports.FromPort != ports.ToPort {
   462  		ruleName += fmt.Sprintf("-%d", ports.ToPort)
   463  	}
   464  	return ruleName
   465  }