github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest"
    12  	"github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/to"
    13  	"github.com/Azure/azure-sdk-for-go/arm/compute"
    14  	"github.com/Azure/azure-sdk-for-go/arm/network"
    15  
    16  	"github.com/juju/errors"
    17  	"github.com/juju/juju/instance"
    18  	jujunetwork "github.com/juju/juju/network"
    19  	"github.com/juju/juju/status"
    20  	"github.com/juju/names"
    21  )
    22  
    23  type azureInstance struct {
    24  	compute.VirtualMachine
    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(to.String(inst.VirtualMachine.Name))
    36  }
    37  
    38  // Status is specified in the Instance interface.
    39  func (inst *azureInstance) Status() instance.InstanceStatus {
    40  	// NOTE(axw) ideally we would use the power state, but that is only
    41  	// available when using the "instance view". Instance view is only
    42  	// delivered when explicitly requested, and you can only request it
    43  	// when querying a single VM. This means the results of AllInstances
    44  	// or Instances would have the instance view missing.
    45  	return instance.InstanceStatus{
    46  		Status:  status.StatusEmpty,
    47  		Message: to.String(inst.Properties.ProvisioningState),
    48  	}
    49  
    50  }
    51  
    52  // setInstanceAddresses queries Azure for the NICs and public IPs associated
    53  // with the given set of instances. This assumes that the instances'
    54  // VirtualMachines are up-to-date, and that there are no concurrent accesses
    55  // to the instances.
    56  func setInstanceAddresses(
    57  	pipClient network.PublicIPAddressesClient,
    58  	resourceGroup string,
    59  	instances []*azureInstance,
    60  	nicsResult network.InterfaceListResult,
    61  ) (err error) {
    62  
    63  	instanceNics := make(map[instance.Id][]network.Interface)
    64  	instancePips := make(map[instance.Id][]network.PublicIPAddress)
    65  	for _, inst := range instances {
    66  		instanceNics[inst.Id()] = nil
    67  		instancePips[inst.Id()] = nil
    68  	}
    69  
    70  	// When setAddresses returns without error, update each
    71  	// instance's network interfaces and public IP addresses.
    72  	setInstanceFields := func(inst *azureInstance) {
    73  		inst.networkInterfaces = instanceNics[inst.Id()]
    74  		inst.publicIPAddresses = instancePips[inst.Id()]
    75  	}
    76  	defer func() {
    77  		if err != nil {
    78  			return
    79  		}
    80  		for _, inst := range instances {
    81  			setInstanceFields(inst)
    82  		}
    83  	}()
    84  
    85  	// We do not rely on references because of how StopInstances works.
    86  	// In order to not leak resources we must not delete the virtual
    87  	// machine until after all of its dependencies are deleted.
    88  	//
    89  	// NICs and PIPs cannot be deleted until they have no references.
    90  	// Thus, we cannot delete a PIP until there is no reference to it
    91  	// in any NICs, and likewise we cannot delete a NIC until there
    92  	// is no reference to it in any virtual machine.
    93  
    94  	if nicsResult.Value != nil {
    95  		for _, nic := range *nicsResult.Value {
    96  			instanceId := instance.Id(toTags(nic.Tags)[jujuMachineNameTag])
    97  			if _, ok := instanceNics[instanceId]; !ok {
    98  				continue
    99  			}
   100  			instanceNics[instanceId] = append(instanceNics[instanceId], nic)
   101  		}
   102  	}
   103  
   104  	pipsResult, err := pipClient.List(resourceGroup)
   105  	if err != nil {
   106  		return errors.Annotate(err, "listing public IP addresses")
   107  	}
   108  	if pipsResult.Value != nil {
   109  		for _, pip := range *pipsResult.Value {
   110  			instanceId := instance.Id(toTags(pip.Tags)[jujuMachineNameTag])
   111  			if _, ok := instanceNics[instanceId]; !ok {
   112  				continue
   113  			}
   114  			instancePips[instanceId] = append(instancePips[instanceId], pip)
   115  		}
   116  	}
   117  
   118  	// Fields will be assigned to instances by the deferred call.
   119  	return nil
   120  }
   121  
   122  // Addresses is specified in the Instance interface.
   123  func (inst *azureInstance) Addresses() ([]jujunetwork.Address, error) {
   124  	addresses := make([]jujunetwork.Address, 0, len(inst.networkInterfaces)+len(inst.publicIPAddresses))
   125  	for _, nic := range inst.networkInterfaces {
   126  		if nic.Properties.IPConfigurations == nil {
   127  			continue
   128  		}
   129  		for _, ipConfiguration := range *nic.Properties.IPConfigurations {
   130  			privateIpAddress := ipConfiguration.Properties.PrivateIPAddress
   131  			if privateIpAddress == nil {
   132  				continue
   133  			}
   134  			addresses = append(addresses, jujunetwork.NewScopedAddress(
   135  				to.String(privateIpAddress),
   136  				jujunetwork.ScopeCloudLocal,
   137  			))
   138  		}
   139  	}
   140  	for _, pip := range inst.publicIPAddresses {
   141  		if pip.Properties.IPAddress == nil {
   142  			continue
   143  		}
   144  		addresses = append(addresses, jujunetwork.NewScopedAddress(
   145  			to.String(pip.Properties.IPAddress),
   146  			jujunetwork.ScopePublic,
   147  		))
   148  	}
   149  	return addresses, nil
   150  }
   151  
   152  // internalNetworkAddress returns the instance's jujunetwork.Address for the
   153  // internal virtual network. This address is used to identify the machine in
   154  // network security rules.
   155  func (inst *azureInstance) internalNetworkAddress() (jujunetwork.Address, error) {
   156  	inst.env.mu.Lock()
   157  	subscriptionId := inst.env.config.subscriptionId
   158  	resourceGroup := inst.env.resourceGroup
   159  	inst.env.mu.Unlock()
   160  	internalSubnetId := internalSubnetId(resourceGroup, subscriptionId)
   161  
   162  	for _, nic := range inst.networkInterfaces {
   163  		if nic.Properties.IPConfigurations == nil {
   164  			continue
   165  		}
   166  		for _, ipConfiguration := range *nic.Properties.IPConfigurations {
   167  			if ipConfiguration.Properties.Subnet == nil {
   168  				continue
   169  			}
   170  			if strings.ToLower(to.String(ipConfiguration.Properties.Subnet.ID)) != strings.ToLower(internalSubnetId) {
   171  				continue
   172  			}
   173  			privateIpAddress := ipConfiguration.Properties.PrivateIPAddress
   174  			if privateIpAddress == nil {
   175  				continue
   176  			}
   177  			return jujunetwork.NewScopedAddress(
   178  				to.String(privateIpAddress),
   179  				jujunetwork.ScopeCloudLocal,
   180  			), nil
   181  		}
   182  	}
   183  	return jujunetwork.Address{}, errors.NotFoundf("internal network address")
   184  }
   185  
   186  // OpenPorts is specified in the Instance interface.
   187  func (inst *azureInstance) OpenPorts(machineId string, ports []jujunetwork.PortRange) error {
   188  	inst.env.mu.Lock()
   189  	nsgClient := network.SecurityGroupsClient{inst.env.network}
   190  	securityRuleClient := network.SecurityRulesClient{inst.env.network}
   191  	inst.env.mu.Unlock()
   192  	internalNetworkAddress, err := inst.internalNetworkAddress()
   193  	if err != nil {
   194  		return errors.Trace(err)
   195  	}
   196  
   197  	securityGroupName := internalSecurityGroupName
   198  	var nsg network.SecurityGroup
   199  	if err := inst.env.callAPI(func() (autorest.Response, error) {
   200  		var err error
   201  		nsg, err = nsgClient.Get(inst.env.resourceGroup, securityGroupName)
   202  		return nsg.Response, err
   203  	}); err != nil {
   204  		return errors.Annotate(err, "querying network security group")
   205  	}
   206  
   207  	var securityRules []network.SecurityRule
   208  	if nsg.Properties.SecurityRules != nil {
   209  		securityRules = *nsg.Properties.SecurityRules
   210  	} else {
   211  		nsg.Properties.SecurityRules = &securityRules
   212  	}
   213  
   214  	// Create rules one at a time; this is necessary to avoid trampling
   215  	// on changes made by the provisioner. We still record rules in the
   216  	// NSG in memory, so we can easily tell which priorities are available.
   217  	vmName := resourceName(names.NewMachineTag(machineId))
   218  	prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName))
   219  	for _, ports := range ports {
   220  		ruleName := securityRuleName(prefix, ports)
   221  
   222  		// Check if the rule already exists; OpenPorts must be idempotent.
   223  		var found bool
   224  		for _, rule := range securityRules {
   225  			if to.String(rule.Name) == ruleName {
   226  				found = true
   227  				break
   228  			}
   229  		}
   230  		if found {
   231  			logger.Debugf("security rule %q already exists", ruleName)
   232  			continue
   233  		}
   234  		logger.Debugf("creating security rule %q", ruleName)
   235  
   236  		priority, err := nextSecurityRulePriority(nsg, securityRuleInternalMax+1, securityRuleMax)
   237  		if err != nil {
   238  			return errors.Annotatef(err, "getting security rule priority for %s", ports)
   239  		}
   240  
   241  		var protocol network.SecurityRuleProtocol
   242  		switch ports.Protocol {
   243  		case "tcp":
   244  			protocol = network.TCP
   245  		case "udp":
   246  			protocol = network.UDP
   247  		default:
   248  			return errors.Errorf("invalid protocol %q", ports.Protocol)
   249  		}
   250  
   251  		var portRange string
   252  		if ports.FromPort != ports.ToPort {
   253  			portRange = fmt.Sprintf("%d-%d", ports.FromPort, ports.ToPort)
   254  		} else {
   255  			portRange = fmt.Sprint(ports.FromPort)
   256  		}
   257  
   258  		rule := network.SecurityRule{
   259  			Properties: &network.SecurityRulePropertiesFormat{
   260  				Description:              to.StringPtr(ports.String()),
   261  				Protocol:                 protocol,
   262  				SourcePortRange:          to.StringPtr("*"),
   263  				DestinationPortRange:     to.StringPtr(portRange),
   264  				SourceAddressPrefix:      to.StringPtr("*"),
   265  				DestinationAddressPrefix: to.StringPtr(internalNetworkAddress.Value),
   266  				Access:    network.Allow,
   267  				Priority:  to.IntPtr(priority),
   268  				Direction: network.Inbound,
   269  			},
   270  		}
   271  		if err := inst.env.callAPI(func() (autorest.Response, error) {
   272  			result, err := securityRuleClient.CreateOrUpdate(
   273  				inst.env.resourceGroup, securityGroupName, ruleName, rule,
   274  			)
   275  			return result.Response, err
   276  		}); err != nil {
   277  			return errors.Annotatef(err, "creating security rule for %s", ports)
   278  		}
   279  		securityRules = append(securityRules, rule)
   280  	}
   281  	return nil
   282  }
   283  
   284  // ClosePorts is specified in the Instance interface.
   285  func (inst *azureInstance) ClosePorts(machineId string, ports []jujunetwork.PortRange) error {
   286  	inst.env.mu.Lock()
   287  	securityRuleClient := network.SecurityRulesClient{inst.env.network}
   288  	inst.env.mu.Unlock()
   289  	securityGroupName := internalSecurityGroupName
   290  
   291  	// Delete rules one at a time; this is necessary to avoid trampling
   292  	// on changes made by the provisioner.
   293  	vmName := resourceName(names.NewMachineTag(machineId))
   294  	prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName))
   295  	for _, ports := range ports {
   296  		ruleName := securityRuleName(prefix, ports)
   297  		logger.Debugf("deleting security rule %q", ruleName)
   298  		var result autorest.Response
   299  		if err := inst.env.callAPI(func() (autorest.Response, error) {
   300  			var err error
   301  			result, err = securityRuleClient.Delete(
   302  				inst.env.resourceGroup, securityGroupName, ruleName,
   303  			)
   304  			return result, err
   305  		}); err != nil {
   306  			if result.Response == nil || result.StatusCode != http.StatusNotFound {
   307  				return errors.Annotatef(err, "deleting security rule %q", ruleName)
   308  			}
   309  		}
   310  	}
   311  	return nil
   312  }
   313  
   314  // Ports is specified in the Instance interface.
   315  func (inst *azureInstance) Ports(machineId string) (ports []jujunetwork.PortRange, err error) {
   316  	inst.env.mu.Lock()
   317  	nsgClient := network.SecurityGroupsClient{inst.env.network}
   318  	inst.env.mu.Unlock()
   319  
   320  	securityGroupName := internalSecurityGroupName
   321  	var nsg network.SecurityGroup
   322  	if err := inst.env.callAPI(func() (autorest.Response, error) {
   323  		var err error
   324  		nsg, err = nsgClient.Get(inst.env.resourceGroup, securityGroupName)
   325  		return nsg.Response, err
   326  	}); err != nil {
   327  		return nil, errors.Annotate(err, "querying network security group")
   328  	}
   329  	if nsg.Properties.SecurityRules == nil {
   330  		return nil, nil
   331  	}
   332  
   333  	vmName := resourceName(names.NewMachineTag(machineId))
   334  	prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName))
   335  	for _, rule := range *nsg.Properties.SecurityRules {
   336  		if rule.Properties.Direction != network.Inbound {
   337  			continue
   338  		}
   339  		if rule.Properties.Access != network.Allow {
   340  			continue
   341  		}
   342  		if to.Int(rule.Properties.Priority) <= securityRuleInternalMax {
   343  			continue
   344  		}
   345  		if !strings.HasPrefix(to.String(rule.Name), prefix) {
   346  			continue
   347  		}
   348  
   349  		var portRange jujunetwork.PortRange
   350  		if *rule.Properties.DestinationPortRange == "*" {
   351  			portRange.FromPort = 0
   352  			portRange.ToPort = 65535
   353  		} else {
   354  			portRange, err = jujunetwork.ParsePortRange(
   355  				*rule.Properties.DestinationPortRange,
   356  			)
   357  			if err != nil {
   358  				return nil, errors.Annotatef(
   359  					err, "parsing port range for security rule %q",
   360  					to.String(rule.Name),
   361  				)
   362  			}
   363  		}
   364  
   365  		var protocols []string
   366  		switch rule.Properties.Protocol {
   367  		case network.TCP:
   368  			protocols = []string{"tcp"}
   369  		case network.UDP:
   370  			protocols = []string{"udp"}
   371  		default:
   372  			protocols = []string{"tcp", "udp"}
   373  		}
   374  		for _, protocol := range protocols {
   375  			portRange.Protocol = protocol
   376  			ports = append(ports, portRange)
   377  		}
   378  	}
   379  	return ports, nil
   380  }
   381  
   382  // deleteInstanceNetworkSecurityRules deletes network security rules in the
   383  // internal network security group that correspond to the specified machine.
   384  //
   385  // This is expected to delete *all* security rules related to the instance,
   386  // i.e. both the ones opened by OpenPorts above, and the ones opened for API
   387  // access.
   388  func deleteInstanceNetworkSecurityRules(
   389  	resourceGroup string, id instance.Id,
   390  	nsgClient network.SecurityGroupsClient,
   391  	securityRuleClient network.SecurityRulesClient,
   392  	callAPI callAPIFunc,
   393  ) error {
   394  	var nsg network.SecurityGroup
   395  	if err := callAPI(func() (autorest.Response, error) {
   396  		var err error
   397  		nsg, err = nsgClient.Get(resourceGroup, internalSecurityGroupName)
   398  		return nsg.Response, err
   399  	}); err != nil {
   400  		return errors.Annotate(err, "querying network security group")
   401  	}
   402  	if nsg.Properties.SecurityRules == nil {
   403  		return nil
   404  	}
   405  	prefix := instanceNetworkSecurityRulePrefix(id)
   406  	for _, rule := range *nsg.Properties.SecurityRules {
   407  		ruleName := to.String(rule.Name)
   408  		if !strings.HasPrefix(ruleName, prefix) {
   409  			continue
   410  		}
   411  		result, err := securityRuleClient.Delete(
   412  			resourceGroup,
   413  			internalSecurityGroupName,
   414  			ruleName,
   415  		)
   416  		if err != nil {
   417  			if result.Response == nil || result.StatusCode != http.StatusNotFound {
   418  				return errors.Annotatef(err, "deleting security rule %q", ruleName)
   419  			}
   420  		}
   421  	}
   422  	return nil
   423  }
   424  
   425  // instanceNetworkSecurityRulePrefix returns the unique prefix for network
   426  // security rule names that relate to the instance with the given ID.
   427  func instanceNetworkSecurityRulePrefix(id instance.Id) string {
   428  	return string(id) + "-"
   429  }
   430  
   431  // securityRuleName returns the security rule name for the given port range,
   432  // and prefix returned by instanceNetworkSecurityRulePrefix.
   433  func securityRuleName(prefix string, ports jujunetwork.PortRange) string {
   434  	ruleName := fmt.Sprintf("%s%s-%d", prefix, ports.Protocol, ports.FromPort)
   435  	if ports.FromPort != ports.ToPort {
   436  		ruleName += fmt.Sprintf("-%d", ports.ToPort)
   437  	}
   438  	return ruleName
   439  }