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