github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/environs/instances/instancetype.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package instances
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  
    10  	"github.com/juju/juju/constraints"
    11  )
    12  
    13  // InstanceType holds all relevant attributes of the various instance types.
    14  type InstanceType struct {
    15  	Id       string
    16  	Name     string
    17  	Arches   []string
    18  	CpuCores uint64
    19  	Mem      uint64
    20  	Cost     uint64
    21  	RootDisk uint64
    22  	// These attributes are not supported by all clouds.
    23  	VirtType *string // The type of virtualisation used by the hypervisor, must match the image.
    24  	CpuPower *uint64
    25  	Tags     []string
    26  }
    27  
    28  func CpuPower(power uint64) *uint64 {
    29  	return &power
    30  }
    31  
    32  // match returns true if itype can satisfy the supplied constraints. If so,
    33  // it also returns a copy of itype with any arches that do not match the
    34  // constraints filtered out.
    35  func (itype InstanceType) match(cons constraints.Value) (InstanceType, bool) {
    36  	nothing := InstanceType{}
    37  	if cons.Arch != nil {
    38  		itype.Arches = filterArches(itype.Arches, []string{*cons.Arch})
    39  	}
    40  	if cons.HasInstanceType() && itype.Name != *cons.InstanceType {
    41  		return nothing, false
    42  	}
    43  	if len(itype.Arches) == 0 {
    44  		return nothing, false
    45  	}
    46  	if cons.CpuCores != nil && itype.CpuCores < *cons.CpuCores {
    47  		return nothing, false
    48  	}
    49  	if cons.CpuPower != nil && itype.CpuPower != nil && *itype.CpuPower < *cons.CpuPower {
    50  		return nothing, false
    51  	}
    52  	if cons.Mem != nil && itype.Mem < *cons.Mem {
    53  		return nothing, false
    54  	}
    55  	if cons.RootDisk != nil && itype.RootDisk > 0 && itype.RootDisk < *cons.RootDisk {
    56  		return nothing, false
    57  	}
    58  	if cons.Tags != nil && len(*cons.Tags) > 0 && !tagsMatch(*cons.Tags, itype.Tags) {
    59  		return nothing, false
    60  	}
    61  	return itype, true
    62  }
    63  
    64  // filterArches returns every element of src that also exists in filter.
    65  func filterArches(src, filter []string) (dst []string) {
    66  	for _, arch := range src {
    67  		for _, match := range filter {
    68  			if arch == match {
    69  				dst = append(dst, arch)
    70  				break
    71  			}
    72  		}
    73  	}
    74  	return dst
    75  }
    76  
    77  // minMemoryHeuristic is the assumed minimum amount of memory (in MB) we prefer in order to run a server (1GB)
    78  const minMemoryHeuristic = 1024
    79  
    80  // matchingTypesForConstraint returns instance types from allTypes which match cons.
    81  func matchingTypesForConstraint(allTypes []InstanceType, cons constraints.Value) []InstanceType {
    82  	var matchingTypes []InstanceType
    83  	for _, itype := range allTypes {
    84  		itype, ok := itype.match(cons)
    85  		if !ok {
    86  			continue
    87  		}
    88  		matchingTypes = append(matchingTypes, itype)
    89  	}
    90  	return matchingTypes
    91  }
    92  
    93  // MatchingInstanceTypes returns all instance types matching constraints and available
    94  // in region, sorted by increasing region-specific cost (if known).
    95  func MatchingInstanceTypes(allInstanceTypes []InstanceType, region string, cons constraints.Value) ([]InstanceType, error) {
    96  	var itypes []InstanceType
    97  
    98  	// Rules used to select instance types:
    99  	// - non memory constraints like cpu-cores etc are always honoured
   100  	// - if no mem constraint specified and instance-type not specified,
   101  	//   try opinionated default with enough mem to run a server.
   102  	// - if no matches and no mem constraint specified, try again and
   103  	//   return any matching instance with the largest memory
   104  	origCons := cons
   105  	if !cons.HasInstanceType() && cons.Mem == nil {
   106  		minMem := uint64(minMemoryHeuristic)
   107  		cons.Mem = &minMem
   108  	}
   109  	itypes = matchingTypesForConstraint(allInstanceTypes, cons)
   110  
   111  	// No matches using opinionated default, so if no mem constraint specified,
   112  	// look for matching instance with largest memory.
   113  	if len(itypes) == 0 && cons.Mem != origCons.Mem {
   114  		itypes = matchingTypesForConstraint(allInstanceTypes, origCons)
   115  		if len(itypes) > 0 {
   116  			sort.Sort(byMemory(itypes))
   117  			itypes = []InstanceType{itypes[len(itypes)-1]}
   118  		}
   119  	}
   120  	// If we have matching instance types, we can return those, sorted by cost.
   121  	if len(itypes) > 0 {
   122  		sort.Sort(byCost(itypes))
   123  		return itypes, nil
   124  	}
   125  
   126  	// No luck, so report the error.
   127  	return nil, fmt.Errorf("no instance types in %s matching constraints %q", region, origCons)
   128  }
   129  
   130  // tagsMatch returns if the tags in wanted all exist in have.
   131  // Note that duplicates of tags are disregarded in both lists
   132  func tagsMatch(wanted, have []string) bool {
   133  	machineTags := map[string]struct{}{}
   134  	for _, tag := range have {
   135  		machineTags[tag] = struct{}{}
   136  	}
   137  	for _, tag := range wanted {
   138  		if _, ok := machineTags[tag]; !ok {
   139  			return false
   140  		}
   141  	}
   142  	return true
   143  }
   144  
   145  // byCost is used to sort a slice of instance types by Cost.
   146  type byCost []InstanceType
   147  
   148  func (bc byCost) Len() int { return len(bc) }
   149  
   150  func (bc byCost) Less(i, j int) bool {
   151  	inst0, inst1 := &bc[i], &bc[j]
   152  	if inst0.Cost != inst1.Cost {
   153  		return inst0.Cost < inst1.Cost
   154  	}
   155  	if inst0.Mem != inst1.Mem {
   156  		return inst0.Mem < inst1.Mem
   157  	}
   158  	if inst0.CpuPower != nil &&
   159  		inst1.CpuPower != nil &&
   160  		*inst0.CpuPower != *inst1.CpuPower {
   161  		return *inst0.CpuPower < *inst1.CpuPower
   162  	}
   163  	if inst0.CpuCores != inst1.CpuCores {
   164  		return inst0.CpuCores < inst1.CpuCores
   165  	}
   166  	if inst0.RootDisk != inst1.RootDisk {
   167  		return inst0.RootDisk < inst1.RootDisk
   168  	}
   169  	// we intentionally don't compare tags, since we can't know how tags compare against each other
   170  	return false
   171  }
   172  
   173  func (bc byCost) Swap(i, j int) {
   174  	bc[i], bc[j] = bc[j], bc[i]
   175  }
   176  
   177  //byMemory is used to sort a slice of instance types by the amount of RAM they have.
   178  type byMemory []InstanceType
   179  
   180  func (s byMemory) Len() int      { return len(s) }
   181  func (s byMemory) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   182  func (s byMemory) Less(i, j int) bool {
   183  	inst0, inst1 := &s[i], &s[j]
   184  	if inst0.Mem != inst1.Mem {
   185  		return s[i].Mem < s[j].Mem
   186  	}
   187  	// Memory is equal, so use cost as a tie breaker.
   188  	// Result is in descending order of cost so instance with lowest cost is used.
   189  	return inst0.Cost > inst1.Cost
   190  }