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 }