forked from juju/juju
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinstancetype.go
239 lines (213 loc) · 7.38 KB
/
instancetype.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package instances
import (
"fmt"
"sort"
"strings"
"github.com/juju/juju/core/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
}
// ByName is used to sort a slice by name by best effort. As we have different separators for different providers
// A possible sort could be:
// We sort by using a lexical sort for the type, which is before the delimiter,
// and if they are the same, we sort by using the cost
type ByName []InstanceType
func (bt ByName) Len() int { return len(bt) }
func (bt ByName) Swap(i, j int) { bt[i], bt[j] = bt[j], bt[i] }
func (bt ByName) Less(i, j int) bool {
inst0, inst1 := &bt[i], &bt[j]
baseInst0 := strings.FieldsFunc(inst0.Name, splitDelimiters)
baseInst1 := strings.FieldsFunc(inst1.Name, splitDelimiters)
if baseInst0[0] != baseInst1[0] {
return baseInst0[0] < baseInst1[0]
}
// Name is equal, so use cost as a tie breaker.
// Result is in ascending order of cost so instance with lowest cost is first.
return inst0.Cost < inst1.Cost
}
func splitDelimiters(r rune) bool {
return r == ',' || r == '-' || r == '.'
}