~rogpeppe/juju-core/azure

« back to all changes in this revision

Viewing changes to constraints/constraints.go

  • Committer: Gustavo Niemeyer
  • Date: 2011-09-26 14:48:45 UTC
  • mto: This revision was merged to the branch mainline in revision 34.
  • Revision ID: gustavo@niemeyer.net-20110926144845-atwp3u6blqngmhel
Bootstrapping store package.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
// Copyright 2013 Canonical Ltd.
2
 
// Licensed under the AGPLv3, see LICENCE file for details.
3
 
 
4
 
package constraints
5
 
 
6
 
import (
7
 
        "fmt"
8
 
        "math"
9
 
        "strconv"
10
 
        "strings"
11
 
 
12
 
        "launchpad.net/juju-core/instance"
13
 
)
14
 
 
15
 
// Value describes a user's requirements of the hardware on which units
16
 
// of a service will run. Constraints are used to choose an existing machine
17
 
// onto which a unit will be deployed, or to provision a new machine if no
18
 
// existing one satisfies the requirements.
19
 
type Value struct {
20
 
 
21
 
        // Arch, if not nil or empty, indicates that a machine must run the named
22
 
        // architecture.
23
 
        Arch *string `json:"arch,omitempty" yaml:"arch,omitempty"`
24
 
 
25
 
        // Container, if not nil, indicates that a machine must be the specified container type.
26
 
        Container *instance.ContainerType `json:"container,omitempty" yaml:"container,omitempty"`
27
 
 
28
 
        // CpuCores, if not nil, indicates that a machine must have at least that
29
 
        // number of effective cores available.
30
 
        CpuCores *uint64 `json:"cpu-cores,omitempty" yaml:"cpu-cores,omitempty"`
31
 
 
32
 
        // CpuPower, if not nil, indicates that a machine must have at least that
33
 
        // amount of CPU power available, where 100 CpuPower is considered to be
34
 
        // equivalent to 1 Amazon ECU (or, roughly, a single 2007-era Xeon).
35
 
        CpuPower *uint64 `json:"cpu-power,omitempty" yaml:"cpu-power,omitempty"`
36
 
 
37
 
        // Mem, if not nil, indicates that a machine must have at least that many
38
 
        // megabytes of RAM.
39
 
        Mem *uint64 `json:"mem,omitempty" yaml:"mem,omitempty"`
40
 
}
41
 
 
42
 
// String expresses a constraints.Value in the language in which it was specified.
43
 
func (v Value) String() string {
44
 
        var strs []string
45
 
        if v.Arch != nil {
46
 
                strs = append(strs, "arch="+*v.Arch)
47
 
        }
48
 
        if v.Container != nil {
49
 
                strs = append(strs, "container="+string(*v.Container))
50
 
        }
51
 
        if v.CpuCores != nil {
52
 
                strs = append(strs, "cpu-cores="+uintStr(*v.CpuCores))
53
 
        }
54
 
        if v.CpuPower != nil {
55
 
                strs = append(strs, "cpu-power="+uintStr(*v.CpuPower))
56
 
        }
57
 
        if v.Mem != nil {
58
 
                s := uintStr(*v.Mem)
59
 
                if s != "" {
60
 
                        s += "M"
61
 
                }
62
 
                strs = append(strs, "mem="+s)
63
 
        }
64
 
        return strings.Join(strs, " ")
65
 
}
66
 
 
67
 
// WithFallbacks returns a copy of v with nil values taken from v0.
68
 
func (v Value) WithFallbacks(v0 Value) Value {
69
 
        v1 := v0
70
 
        if v.Arch != nil {
71
 
                v1.Arch = v.Arch
72
 
        }
73
 
        if v.Container != nil {
74
 
                v1.Container = v.Container
75
 
        }
76
 
        if v.CpuCores != nil {
77
 
                v1.CpuCores = v.CpuCores
78
 
        }
79
 
        if v.CpuPower != nil {
80
 
                v1.CpuPower = v.CpuPower
81
 
        }
82
 
        if v.Mem != nil {
83
 
                v1.Mem = v.Mem
84
 
        }
85
 
        return v1
86
 
}
87
 
 
88
 
func uintStr(i uint64) string {
89
 
        if i == 0 {
90
 
                return ""
91
 
        }
92
 
        return fmt.Sprintf("%d", i)
93
 
}
94
 
 
95
 
// Parse constructs a constraints.Value from the supplied arguments,
96
 
// each of which must contain only spaces and name=value pairs. If any
97
 
// name is specified more than once, an error is returned.
98
 
func Parse(args ...string) (Value, error) {
99
 
        cons := Value{}
100
 
        for _, arg := range args {
101
 
                raws := strings.Split(strings.TrimSpace(arg), " ")
102
 
                for _, raw := range raws {
103
 
                        if raw == "" {
104
 
                                continue
105
 
                        }
106
 
                        if err := cons.setRaw(raw); err != nil {
107
 
                                return Value{}, err
108
 
                        }
109
 
                }
110
 
        }
111
 
        return cons, nil
112
 
}
113
 
 
114
 
// MustParse constructs a constraints.Value from the supplied arguments,
115
 
// as Parse, but panics on failure.
116
 
func MustParse(args ...string) Value {
117
 
        v, err := Parse(args...)
118
 
        if err != nil {
119
 
                panic(err)
120
 
        }
121
 
        return v
122
 
}
123
 
 
124
 
// Constraints implements gnuflag.Value for a Constraints.
125
 
type ConstraintsValue struct {
126
 
        Target *Value
127
 
}
128
 
 
129
 
func (v ConstraintsValue) Set(s string) error {
130
 
        cons, err := Parse(s)
131
 
        if err != nil {
132
 
                return err
133
 
        }
134
 
        *v.Target = cons
135
 
        return nil
136
 
}
137
 
 
138
 
func (v ConstraintsValue) String() string {
139
 
        return v.Target.String()
140
 
}
141
 
 
142
 
// setRaw interprets a name=value string and sets the supplied value.
143
 
func (v *Value) setRaw(raw string) error {
144
 
        eq := strings.Index(raw, "=")
145
 
        if eq <= 0 {
146
 
                return fmt.Errorf("malformed constraint %q", raw)
147
 
        }
148
 
        name, str := raw[:eq], raw[eq+1:]
149
 
        var err error
150
 
        switch name {
151
 
        case "arch":
152
 
                err = v.setArch(str)
153
 
        case "container":
154
 
                err = v.setContainer(str)
155
 
        case "cpu-cores":
156
 
                err = v.setCpuCores(str)
157
 
        case "cpu-power":
158
 
                err = v.setCpuPower(str)
159
 
        case "mem":
160
 
                err = v.setMem(str)
161
 
        default:
162
 
                return fmt.Errorf("unknown constraint %q", name)
163
 
        }
164
 
        if err != nil {
165
 
                return fmt.Errorf("bad %q constraint: %v", name, err)
166
 
        }
167
 
        return nil
168
 
}
169
 
 
170
 
// SetYAML is required to unmarshall a constraints.Value object
171
 
// to ensure the container attribute is correctly handled when it is empty.
172
 
// Because ContainerType is an alias for string, Go's reflect logic used in the
173
 
// YAML decode determines that *string and *ContainerType are not assignable so
174
 
// the container value of "" in the YAML is ignored.
175
 
func (v *Value) SetYAML(tag string, value interface{}) bool {
176
 
        values := value.(map[interface{}]interface{})
177
 
        for k, val := range values {
178
 
                vstr := fmt.Sprintf("%v", val)
179
 
                var err error
180
 
                switch k {
181
 
                case "arch":
182
 
                        v.Arch = &vstr
183
 
                case "container":
184
 
                        ctype := instance.ContainerType(vstr)
185
 
                        v.Container = &ctype
186
 
                case "cpu-cores":
187
 
                        v.CpuCores, err = parseUint64(vstr)
188
 
                case "cpu-power":
189
 
                        v.CpuPower, err = parseUint64(vstr)
190
 
                case "mem":
191
 
                        v.Mem, err = parseUint64(vstr)
192
 
                default:
193
 
                        return false
194
 
                }
195
 
                if err != nil {
196
 
                        return false
197
 
                }
198
 
        }
199
 
        return true
200
 
}
201
 
 
202
 
func (v *Value) setContainer(str string) error {
203
 
        if v.Container != nil {
204
 
                return fmt.Errorf("already set")
205
 
        }
206
 
        if str == "" {
207
 
                ctype := instance.ContainerType("")
208
 
                v.Container = &ctype
209
 
        } else {
210
 
                ctype, err := instance.ParseSupportedContainerTypeOrNone(str)
211
 
                if err != nil {
212
 
                        return err
213
 
                }
214
 
                v.Container = &ctype
215
 
        }
216
 
        return nil
217
 
}
218
 
 
219
 
// HasContainer returns true if the constraints.Value specifies a container.
220
 
func (v *Value) HasContainer() bool {
221
 
        return v.Container != nil && *v.Container != "" && *v.Container != instance.NONE
222
 
}
223
 
 
224
 
func (v *Value) setArch(str string) error {
225
 
        if v.Arch != nil {
226
 
                return fmt.Errorf("already set")
227
 
        }
228
 
        switch str {
229
 
        case "":
230
 
        case "amd64", "i386", "arm":
231
 
        default:
232
 
                return fmt.Errorf("%q not recognized", str)
233
 
        }
234
 
        v.Arch = &str
235
 
        return nil
236
 
}
237
 
 
238
 
func (v *Value) setCpuCores(str string) (err error) {
239
 
        if v.CpuCores != nil {
240
 
                return fmt.Errorf("already set")
241
 
        }
242
 
        v.CpuCores, err = parseUint64(str)
243
 
        return
244
 
}
245
 
 
246
 
func (v *Value) setCpuPower(str string) (err error) {
247
 
        if v.CpuPower != nil {
248
 
                return fmt.Errorf("already set")
249
 
        }
250
 
        v.CpuPower, err = parseUint64(str)
251
 
        return
252
 
}
253
 
 
254
 
func (v *Value) setMem(str string) error {
255
 
        if v.Mem != nil {
256
 
                return fmt.Errorf("already set")
257
 
        }
258
 
        var value uint64
259
 
        if str != "" {
260
 
                mult := 1.0
261
 
                if m, ok := mbSuffixes[str[len(str)-1:]]; ok {
262
 
                        str = str[:len(str)-1]
263
 
                        mult = m
264
 
                }
265
 
                val, err := strconv.ParseFloat(str, 64)
266
 
                if err != nil || val < 0 {
267
 
                        return fmt.Errorf("must be a non-negative float with optional M/G/T/P suffix")
268
 
                }
269
 
                val *= mult
270
 
                value = uint64(math.Ceil(val))
271
 
        }
272
 
        v.Mem = &value
273
 
        return nil
274
 
}
275
 
 
276
 
func parseUint64(str string) (*uint64, error) {
277
 
        var value uint64
278
 
        if str != "" {
279
 
                if val, err := strconv.ParseUint(str, 10, 64); err != nil {
280
 
                        return nil, fmt.Errorf("must be a non-negative integer")
281
 
                } else {
282
 
                        value = uint64(val)
283
 
                }
284
 
        }
285
 
        return &value, nil
286
 
}
287
 
 
288
 
var mbSuffixes = map[string]float64{
289
 
        "M": 1,
290
 
        "G": 1024,
291
 
        "T": 1024 * 1024,
292
 
        "P": 1024 * 1024 * 1024,
293
 
}