github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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  	"sort"
     9  
    10  	"github.com/juju/errors"
    11  	"launchpad.net/gwacl"
    12  
    13  	"github.com/juju/juju/constraints"
    14  	"github.com/juju/juju/environs/imagemetadata"
    15  	"github.com/juju/juju/environs/instances"
    16  	"github.com/juju/juju/environs/simplestreams"
    17  )
    18  
    19  // preferredTypes is a list of machine types, in order of preference so that
    20  // the first type that matches a set of hardware constraints is also the best
    21  // (cheapest) fit for those constraints.  Or if your constraint is a maximum
    22  // price you're willing to pay, your best match is the last type that falls
    23  // within your threshold price.
    24  type preferredTypes []*gwacl.RoleSize
    25  
    26  // preferredTypes implements sort.Interface.
    27  var _ sort.Interface = (*preferredTypes)(nil)
    28  
    29  // newPreferredTypes creates a preferredTypes based on the given slice of
    30  // RoleSize objects.  It will hold pointers to the elements of that slice.
    31  func newPreferredTypes(availableTypes []gwacl.RoleSize) preferredTypes {
    32  	types := make(preferredTypes, len(availableTypes))
    33  	for index := range availableTypes {
    34  		types[index] = &availableTypes[index]
    35  	}
    36  	sort.Sort(&types)
    37  	return types
    38  }
    39  
    40  // Len is specified in sort.Interface.
    41  func (types *preferredTypes) Len() int {
    42  	return len(*types)
    43  }
    44  
    45  // Less is specified in sort.Interface.
    46  func (types *preferredTypes) Less(i, j int) bool {
    47  	// All we care about for now is cost.  If at some point Azure offers
    48  	// different tradeoffs for the same price, we may need a tie-breaker.
    49  	return (*types)[i].Cost < (*types)[j].Cost
    50  }
    51  
    52  // Swap is specified in sort.Interface.
    53  func (types *preferredTypes) Swap(i, j int) {
    54  	firstPtr := &(*types)[i]
    55  	secondPtr := &(*types)[j]
    56  	*secondPtr, *firstPtr = *firstPtr, *secondPtr
    57  }
    58  
    59  // suffices returns whether the given value is high enough to meet the
    60  // required value, if any.  If "required" is nil, there is no requirement
    61  // and so any given value will do.
    62  //
    63  // This is a method only to limit namespace pollution.  It ignores the receiver.
    64  func (*preferredTypes) suffices(given uint64, required *uint64) bool {
    65  	return required == nil || given >= *required
    66  }
    67  
    68  // satisfies returns whether the given machine type is enough to satisfy
    69  // the given constraints.  (It doesn't matter if it's overkill; all that
    70  // matters here is whether the machine type is good enough.)
    71  //
    72  // This is a method only to limit namespace pollution.  It ignores the receiver.
    73  func (types *preferredTypes) satisfies(machineType *gwacl.RoleSize, constraint constraints.Value) bool {
    74  	// gwacl does not model CPU power yet, although Azure does have the
    75  	// option of a shared core (for ExtraSmall instances).  For now we
    76  	// just pretend that's a full-fledged core.
    77  	return types.suffices(machineType.CpuCores, constraint.CpuCores) &&
    78  		types.suffices(machineType.Mem, constraint.Mem)
    79  }
    80  
    81  const defaultMem = 1 * gwacl.GB
    82  
    83  // If you specify no constraints at all, you're going to get the smallest
    84  // instance type available.  In practice that one's a bit small.  So unless
    85  // the constraints are deliberately set lower, this gives you a set of
    86  // baseline constraints that are just slightly more ambitious than that.
    87  func defaultToBaselineSpec(constraint constraints.Value) constraints.Value {
    88  	result := constraint
    89  	if result.Mem == nil {
    90  		var value uint64 = defaultMem
    91  		result.Mem = &value
    92  	}
    93  	return result
    94  }
    95  
    96  // selectMachineType returns the Azure machine type that best matches the
    97  // supplied instanceConstraint.
    98  func selectMachineType(availableTypes []gwacl.RoleSize, constraint constraints.Value) (*gwacl.RoleSize, error) {
    99  	types := newPreferredTypes(availableTypes)
   100  	for _, machineType := range types {
   101  		if types.satisfies(machineType, constraint) {
   102  			return machineType, nil
   103  		}
   104  	}
   105  	return nil, fmt.Errorf("no machine type matches constraints %v", constraint)
   106  }
   107  
   108  // getEndpoint returns the simplestreams endpoint to use for the given Azure
   109  // location (e.g. West Europe or China North).
   110  func getEndpoint(location string) string {
   111  	// Simplestreams uses the management-API endpoint for the image, not
   112  	// the base managent API URL.
   113  	return gwacl.GetEndpoint(location).ManagementAPI()
   114  }
   115  
   116  // As long as this code only supports the default simplestreams
   117  // database, which is always signed, there is no point in accepting
   118  // unsigned metadata.
   119  //
   120  // For tests, however, unsigned data is more convenient.  They can override
   121  // this setting.
   122  var signedImageDataOnly = true
   123  
   124  // findMatchingImages queries simplestreams for OS images that match the given
   125  // requirements.
   126  //
   127  // If it finds no matching images, that's an error.
   128  func findMatchingImages(e *azureEnviron, location, series string, arches []string) ([]*imagemetadata.ImageMetadata, error) {
   129  	endpoint := getEndpoint(location)
   130  	constraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   131  		CloudSpec: simplestreams.CloudSpec{location, endpoint},
   132  		Series:    []string{series},
   133  		Arches:    arches,
   134  		Stream:    e.Config().ImageStream(),
   135  	})
   136  	sources, err := imagemetadata.GetMetadataSources(e)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	indexPath := simplestreams.DefaultIndexPath
   141  	images, _, err := imagemetadata.Fetch(sources, indexPath, constraint, signedImageDataOnly)
   142  	if len(images) == 0 || errors.IsNotFound(err) {
   143  		return nil, fmt.Errorf("no OS images found for location %q, series %q, architectures %q (and endpoint: %q)", location, series, arches, endpoint)
   144  	} else if err != nil {
   145  		return nil, err
   146  	}
   147  	return images, nil
   148  }
   149  
   150  // newInstanceType creates an InstanceType based on a gwacl.RoleSize.
   151  func newInstanceType(roleSize gwacl.RoleSize) instances.InstanceType {
   152  	vtype := "Hyper-V"
   153  	// Actually Azure has shared and dedicated CPUs, but gwacl doesn't
   154  	// model that distinction yet.
   155  	var cpuPower uint64 = 100
   156  
   157  	return instances.InstanceType{
   158  		Id:       roleSize.Name,
   159  		Name:     roleSize.Name,
   160  		CpuCores: roleSize.CpuCores,
   161  		Mem:      roleSize.Mem,
   162  		RootDisk: roleSize.OSDiskSpaceVirt,
   163  		Cost:     roleSize.Cost,
   164  		VirtType: &vtype,
   165  		CpuPower: &cpuPower,
   166  		// tags are not currently supported by azure
   167  	}
   168  }
   169  
   170  // listInstanceTypes describes the available instance types based on a
   171  // description in gwacl's terms.
   172  func listInstanceTypes(env *azureEnviron, roleSizes []gwacl.RoleSize) ([]instances.InstanceType, error) {
   173  	arches, err := env.SupportedArchitectures()
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	types := make([]instances.InstanceType, len(roleSizes))
   178  	for index, roleSize := range roleSizes {
   179  		types[index] = newInstanceType(roleSize)
   180  		types[index].Arches = arches
   181  	}
   182  	return types, nil
   183  }
   184  
   185  // findInstanceSpec returns the InstanceSpec that best satisfies the supplied
   186  // InstanceConstraint.
   187  func findInstanceSpec(env *azureEnviron, constraint *instances.InstanceConstraint) (*instances.InstanceSpec, error) {
   188  	constraint.Constraints = defaultToBaselineSpec(constraint.Constraints)
   189  	imageData, err := findMatchingImages(env, constraint.Region, constraint.Series, constraint.Arches)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	images := instances.ImageMetadataToImages(imageData)
   194  	instanceTypes, err := listInstanceTypes(env, gwacl.RoleSizes)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  	return instances.FindInstanceSpec(images, constraint, instanceTypes)
   199  }