github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/provider/azure/instancetype.go (about)

     1  // Copyright 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  	"github.com/juju/errors"
    11  	"github.com/juju/utils/set"
    12  	"launchpad.net/gwacl"
    13  
    14  	"github.com/juju/juju/constraints"
    15  	"github.com/juju/juju/environs"
    16  	"github.com/juju/juju/environs/imagemetadata"
    17  	"github.com/juju/juju/environs/instances"
    18  	"github.com/juju/juju/environs/simplestreams"
    19  )
    20  
    21  const defaultMem = 1 * gwacl.GB
    22  
    23  var (
    24  	roleSizeCost          = gwacl.RoleSizeCost
    25  	getAvailableRoleSizes = (*azureEnviron).getAvailableRoleSizes
    26  	getVirtualNetwork     = (*azureEnviron).getVirtualNetwork
    27  )
    28  
    29  // If you specify no constraints at all, you're going to get the smallest
    30  // instance type available.  In practice that one's a bit small.  So unless
    31  // the constraints are deliberately set lower, this gives you a set of
    32  // baseline constraints that are just slightly more ambitious than that.
    33  func defaultToBaselineSpec(constraint constraints.Value) constraints.Value {
    34  	result := constraint
    35  	if !result.HasInstanceType() && result.Mem == nil {
    36  		var value uint64 = defaultMem
    37  		result.Mem = &value
    38  	}
    39  	return result
    40  }
    41  
    42  // selectMachineType returns the Azure machine type that best matches the
    43  // supplied instanceConstraint.
    44  func selectMachineType(env *azureEnviron, cons constraints.Value) (*instances.InstanceType, error) {
    45  	instanceTypes, err := listInstanceTypes(env)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	region := env.getSnapshot().ecfg.location()
    50  	instanceTypes, err = instances.MatchingInstanceTypes(instanceTypes, region, cons)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	return &instanceTypes[0], nil
    55  }
    56  
    57  // getEndpoint returns the simplestreams endpoint to use for the given Azure
    58  // location (e.g. West Europe or China North).
    59  func getEndpoint(location string) string {
    60  	// Simplestreams uses the management-API endpoint for the image, not
    61  	// the base managent API URL.
    62  	return gwacl.GetEndpoint(location).ManagementAPI()
    63  }
    64  
    65  // As long as this code only supports the default simplestreams
    66  // database, which is always signed, there is no point in accepting
    67  // unsigned metadata.
    68  //
    69  // For tests, however, unsigned data is more convenient.  They can override
    70  // this setting.
    71  var signedImageDataOnly = true
    72  
    73  // findMatchingImages queries simplestreams for OS images that match the given
    74  // requirements.
    75  //
    76  // If it finds no matching images, that's an error.
    77  func findMatchingImages(e *azureEnviron, location, series string, arches []string) ([]*imagemetadata.ImageMetadata, error) {
    78  	endpoint := getEndpoint(location)
    79  	constraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
    80  		CloudSpec: simplestreams.CloudSpec{location, endpoint},
    81  		Series:    []string{series},
    82  		Arches:    arches,
    83  		Stream:    e.Config().ImageStream(),
    84  	})
    85  	sources, err := environs.ImageMetadataSources(e)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	images, _, err := imagemetadata.Fetch(sources, constraint, signedImageDataOnly)
    90  	if len(images) == 0 || errors.IsNotFound(err) {
    91  		return nil, fmt.Errorf("no OS images found for location %q, series %q, architectures %q (and endpoint: %q)", location, series, arches, endpoint)
    92  	} else if err != nil {
    93  		return nil, err
    94  	}
    95  	return images, nil
    96  }
    97  
    98  // newInstanceType creates an InstanceType based on a gwacl.RoleSize.
    99  func newInstanceType(roleSize gwacl.RoleSize, region string) (instances.InstanceType, error) {
   100  	cost, err := roleSizeCost(region, roleSize.Name)
   101  	if err != nil {
   102  		return instances.InstanceType{}, err
   103  	}
   104  
   105  	vtype := "Hyper-V"
   106  	return instances.InstanceType{
   107  		Id:       roleSize.Name,
   108  		Name:     roleSize.Name,
   109  		CpuCores: roleSize.CpuCores,
   110  		Mem:      roleSize.Mem,
   111  		RootDisk: roleSize.OSDiskSpace,
   112  		Cost:     cost,
   113  		VirtType: &vtype,
   114  		// tags are not currently supported by azure
   115  	}, nil
   116  }
   117  
   118  // listInstanceTypes describes the available instance types based on a
   119  // description in gwacl's terms.
   120  func listInstanceTypes(env *azureEnviron) ([]instances.InstanceType, error) {
   121  	// Not all locations are made equal: some role sizes are only available
   122  	// in some locations.
   123  	availableRoleSizes, err := getAvailableRoleSizes(env)
   124  	if err != nil {
   125  		return nil, errors.Trace(err)
   126  	}
   127  
   128  	// If the virtual network is tied to an affinity group, then the
   129  	// role sizes that are useable are limited.
   130  	vnet, err := getVirtualNetwork(env)
   131  	if err != nil && errors.IsNotFound(err) {
   132  		// Virtual network doesn't exist yet; we'll create it with a
   133  		// location, so we're not limited.
   134  	} else if err != nil {
   135  		return nil, errors.Annotate(err, "cannot get virtual network details to filter instance types")
   136  	}
   137  	limitedTypes := make(set.Strings)
   138  
   139  	region := env.getSnapshot().ecfg.location()
   140  	arches, err := env.SupportedArchitectures()
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	types := make([]instances.InstanceType, 0, len(gwacl.RoleSizes))
   145  	for _, roleSize := range gwacl.RoleSizes {
   146  		if !availableRoleSizes.Contains(roleSize.Name) {
   147  			logger.Debugf("role size %q is unavailable in location %q", roleSize.Name, region)
   148  			continue
   149  		}
   150  		// TODO(axw) 2014-06-23 #1324666
   151  		// Support basic instance types. We need to not default
   152  		// to them as they do not support load balancing.
   153  		if strings.HasPrefix(roleSize.Name, "Basic_") {
   154  			logger.Debugf("role size %q is unsupported", roleSize.Name)
   155  			continue
   156  		}
   157  		if vnet != nil && vnet.AffinityGroup != "" && isLimitedRoleSize(roleSize.Name) {
   158  			limitedTypes.Add(roleSize.Name)
   159  			continue
   160  		}
   161  		instanceType, err := newInstanceType(roleSize, region)
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  		types = append(types, instanceType)
   166  	}
   167  	for i := range types {
   168  		types[i].Arches = arches
   169  	}
   170  	if len(limitedTypes) > 0 {
   171  		logger.Warningf(
   172  			"virtual network %q has an affinity group: disabling instance types %s",
   173  			vnet.Name, limitedTypes.SortedValues(),
   174  		)
   175  	}
   176  	return types, nil
   177  }
   178  
   179  // isLimitedRoleSize reports whether the named role size is limited to some
   180  // physical hosts only.
   181  func isLimitedRoleSize(name string) bool {
   182  	switch name {
   183  	case "ExtraSmall", "Small", "Medium", "Large", "ExtraLarge":
   184  		// At the time of writing, only the original role sizes are not limited.
   185  		return false
   186  	case "A5", "A6", "A7", "A8", "A9":
   187  		// We never used to filter out A5-A9 role sizes, so leave them in
   188  		// case users have been relying on them. It is *possible* that A-series
   189  		// role sizes are available, but we cannot automatically use them as
   190  		// they *may* not be.
   191  		return false
   192  	}
   193  	return true
   194  }
   195  
   196  // findInstanceSpec returns the InstanceSpec that best satisfies the supplied
   197  // InstanceConstraint.
   198  func findInstanceSpec(env *azureEnviron, constraint *instances.InstanceConstraint) (*instances.InstanceSpec, error) {
   199  	constraint.Constraints = defaultToBaselineSpec(constraint.Constraints)
   200  	imageData, err := findMatchingImages(env, constraint.Region, constraint.Series, constraint.Arches)
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  	images := instances.ImageMetadataToImages(imageData)
   205  	instanceTypes, err := listInstanceTypes(env)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  	return instances.FindInstanceSpec(images, constraint, instanceTypes)
   210  }