Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
213 lines (191 sloc) 6.45 KB
// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package instances
import (
"fmt"
"sort"
"github.com/juju/juju/constraints"
)
// InstanceType holds all relevant attributes of the various instance types.
type InstanceType struct {
Id string
Name string
Arches []string
CpuCores uint64
Mem uint64
Cost uint64
RootDisk uint64
// These attributes are not supported by all clouds.
VirtType *string // The type of virtualisation used by the hypervisor, must match the image.
CpuPower *uint64
Tags []string
Deprecated bool
}
// InstanceTypesWithCostMetadata holds an array of InstanceType and metadata
// about their cost.
type InstanceTypesWithCostMetadata struct {
// InstanceTypes holds the array of InstanceTypes affected by this cost scheme.
InstanceTypes []InstanceType
// CostUnit holds the unit in which the InstanceType.Cost is expressed.
CostUnit string
// CostCurrency holds the currency in which InstanceType.Cost is expressed.
CostCurrency string
// CostDivisor indicates a number that must be applied to InstanceType.Cost to obtain
// a number that is in CostUnit.
// If 0 it means that InstanceType.Cost is already expressed in CostUnit.
CostDivisor uint64
}
func CpuPower(power uint64) *uint64 {
return &power
}
// match returns true if itype can satisfy the supplied constraints. If so,
// it also returns a copy of itype with any arches that do not match the
// constraints filtered out.
func (itype InstanceType) match(cons constraints.Value) (InstanceType, bool) {
nothing := InstanceType{}
if cons.Arch != nil {
itype.Arches = filterArches(itype.Arches, []string{*cons.Arch})
}
if itype.Deprecated && !cons.HasInstanceType() {
return nothing, false
}
if cons.HasInstanceType() && itype.Name != *cons.InstanceType {
return nothing, false
}
if len(itype.Arches) == 0 {
return nothing, false
}
if cons.CpuCores != nil && itype.CpuCores < *cons.CpuCores {
return nothing, false
}
if cons.CpuPower != nil && itype.CpuPower != nil && *itype.CpuPower < *cons.CpuPower {
return nothing, false
}
if cons.Mem != nil && itype.Mem < *cons.Mem {
return nothing, false
}
if cons.RootDisk != nil && itype.RootDisk > 0 && itype.RootDisk < *cons.RootDisk {
return nothing, false
}
if cons.Tags != nil && len(*cons.Tags) > 0 && !tagsMatch(*cons.Tags, itype.Tags) {
return nothing, false
}
if cons.HasVirtType() && (itype.VirtType == nil || *itype.VirtType != *cons.VirtType) {
return nothing, false
}
return itype, true
}
// filterArches returns every element of src that also exists in filter.
func filterArches(src, filter []string) (dst []string) {
for _, arch := range src {
for _, match := range filter {
if arch == match {
dst = append(dst, arch)
break
}
}
}
return dst
}
// minMemoryHeuristic is the assumed minimum amount of memory (in MB) we prefer in order to run a server (1GB)
const minMemoryHeuristic = 1024
// matchingTypesForConstraint returns instance types from allTypes which match cons.
func matchingTypesForConstraint(allTypes []InstanceType, cons constraints.Value) []InstanceType {
var matchingTypes []InstanceType
for _, itype := range allTypes {
itype, ok := itype.match(cons)
if !ok {
continue
}
matchingTypes = append(matchingTypes, itype)
}
return matchingTypes
}
// MatchingInstanceTypes returns all instance types matching constraints and available
// in region, sorted by increasing region-specific cost (if known).
func MatchingInstanceTypes(allInstanceTypes []InstanceType, region string, cons constraints.Value) ([]InstanceType, error) {
var itypes []InstanceType
// Rules used to select instance types:
// - non memory constraints like cores etc are always honoured
// - if no mem constraint specified and instance-type not specified,
// try opinionated default with enough mem to run a server.
// - if no matches and no mem constraint specified, try again and
// return any matching instance with the largest memory
origCons := cons
if !cons.HasInstanceType() && cons.Mem == nil {
minMem := uint64(minMemoryHeuristic)
cons.Mem = &minMem
}
itypes = matchingTypesForConstraint(allInstanceTypes, cons)
// No matches using opinionated default, so if no mem constraint specified,
// look for matching instance with largest memory.
if len(itypes) == 0 && cons.Mem != origCons.Mem {
itypes = matchingTypesForConstraint(allInstanceTypes, origCons)
if len(itypes) > 0 {
sort.Sort(byMemory(itypes))
itypes = []InstanceType{itypes[len(itypes)-1]}
}
}
// If we have matching instance types, we can return those, sorted by cost.
if len(itypes) > 0 {
sort.Sort(byCost(itypes))
return itypes, nil
}
// No luck, so report the error.
return nil, fmt.Errorf("no instance types in %s matching constraints %q", region, origCons)
}
// tagsMatch returns if the tags in wanted all exist in have.
// Note that duplicates of tags are disregarded in both lists
func tagsMatch(wanted, have []string) bool {
machineTags := map[string]struct{}{}
for _, tag := range have {
machineTags[tag] = struct{}{}
}
for _, tag := range wanted {
if _, ok := machineTags[tag]; !ok {
return false
}
}
return true
}
// byCost is used to sort a slice of instance types by Cost.
type byCost []InstanceType
func (bc byCost) Len() int { return len(bc) }
func (bc byCost) Less(i, j int) bool {
inst0, inst1 := &bc[i], &bc[j]
if inst0.Cost != inst1.Cost {
return inst0.Cost < inst1.Cost
}
if inst0.Mem != inst1.Mem {
return inst0.Mem < inst1.Mem
}
if inst0.CpuPower != nil &&
inst1.CpuPower != nil &&
*inst0.CpuPower != *inst1.CpuPower {
return *inst0.CpuPower < *inst1.CpuPower
}
if inst0.CpuCores != inst1.CpuCores {
return inst0.CpuCores < inst1.CpuCores
}
if inst0.RootDisk != inst1.RootDisk {
return inst0.RootDisk < inst1.RootDisk
}
// we intentionally don't compare tags, since we can't know how tags compare against each other
return false
}
func (bc byCost) Swap(i, j int) {
bc[i], bc[j] = bc[j], bc[i]
}
//byMemory is used to sort a slice of instance types by the amount of RAM they have.
type byMemory []InstanceType
func (s byMemory) Len() int { return len(s) }
func (s byMemory) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byMemory) Less(i, j int) bool {
inst0, inst1 := &s[i], &s[j]
if inst0.Mem != inst1.Mem {
return s[i].Mem < s[j].Mem
}
// Memory is equal, so use cost as a tie breaker.
// Result is in descending order of cost so instance with lowest cost is used.
return inst0.Cost > inst1.Cost
}