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