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