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