1
// Copyright 2013, 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
13
"github.com/juju/errors"
14
"github.com/juju/utils/arch"
15
"gopkg.in/juju/names.v2"
17
"github.com/juju/juju/instance"
20
// The following constants list the supported constraint attribute names, as defined
21
// by the fields in the Value struct.
24
Container = "container"
25
CpuCores = "cpu-cores"
26
CpuPower = "cpu-power"
28
RootDisk = "root-disk"
30
InstanceType = "instance-type"
32
VirtType = "virt-type"
35
// Value describes a user's requirements of the hardware on which units
36
// of a service will run. Constraints are used to choose an existing machine
37
// onto which a unit will be deployed, or to provision a new machine if no
38
// existing one satisfies the requirements.
41
// Arch, if not nil or empty, indicates that a machine must run the named
43
Arch *string `json:"arch,omitempty" yaml:"arch,omitempty"`
45
// Container, if not nil, indicates that a machine must be the specified container type.
46
Container *instance.ContainerType `json:"container,omitempty" yaml:"container,omitempty"`
48
// CpuCores, if not nil, indicates that a machine must have at least that
49
// number of effective cores available.
50
CpuCores *uint64 `json:"cpu-cores,omitempty" yaml:"cpu-cores,omitempty"`
52
// CpuPower, if not nil, indicates that a machine must have at least that
53
// amount of CPU power available, where 100 CpuPower is considered to be
54
// equivalent to 1 Amazon ECU (or, roughly, a single 2007-era Xeon).
55
CpuPower *uint64 `json:"cpu-power,omitempty" yaml:"cpu-power,omitempty"`
57
// Mem, if not nil, indicates that a machine must have at least that many
59
Mem *uint64 `json:"mem,omitempty" yaml:"mem,omitempty"`
61
// RootDisk, if not nil, indicates that a machine must have at least
62
// that many megabytes of disk space available in the root disk. In
63
// providers where the root disk is configurable at instance startup
64
// time, an instance with the specified amount of disk space in the OS
65
// disk might be requested.
66
RootDisk *uint64 `json:"root-disk,omitempty" yaml:"root-disk,omitempty"`
68
// Tags, if not nil, indicates tags that the machine must have applied to it.
69
// An empty list is treated the same as a nil (unspecified) list, except an
70
// empty list will override any default tags, where a nil list will not.
71
Tags *[]string `json:"tags,omitempty" yaml:"tags,omitempty"`
73
// InstanceType, if not nil, indicates that the specified cloud instance type
74
// be used. Only valid for clouds which support instance types.
75
InstanceType *string `json:"instance-type,omitempty" yaml:"instance-type,omitempty"`
77
// Spaces, if not nil, holds a list of juju network spaces that
78
// should be available (or not) on the machine. Positive and
79
// negative values are accepted, and the difference is the latter
80
// have a "^" prefix to the name.
81
Spaces *[]string `json:"spaces,omitempty" yaml:"spaces,omitempty"`
83
// VirtType, if not nil or empty, indicates that a machine must run the named
84
// virtual type. Only valid for clouds with multi-hypervisor support.
85
VirtType *string `json:"virt-type,omitempty" yaml:"virt-type,omitempty"`
88
// fieldNames records a mapping from the constraint tag to struct field name.
89
// eg "root-disk" maps to RootDisk.
90
var fieldNames map[string]string
93
// Create the fieldNames map by inspecting the json tags for each of
94
// the Value struct fields.
95
fieldNames = make(map[string]string)
96
typ := reflect.TypeOf(Value{})
97
for i := 0; i < typ.NumField(); i++ {
99
if tag := field.Tag.Get("json"); tag != "" {
100
if i := strings.Index(tag, ","); i >= 0 {
107
fieldNames[tag] = field.Name
113
// IsEmpty returns if the given constraints value has no constraints set
114
func IsEmpty(v *Value) bool {
115
return v.String() == ""
118
// HasArch returns true if the constraints.Value specifies an architecture.
119
func (v *Value) HasArch() bool {
120
return v.Arch != nil && *v.Arch != ""
123
// HasInstanceType returns true if the constraints.Value specifies an instance type.
124
func (v *Value) HasInstanceType() bool {
125
return v.InstanceType != nil && *v.InstanceType != ""
128
// extractItems returns the list of entries in the given field which
129
// are either positive (included) or negative (!included; with prefix
131
func (v *Value) extractItems(field []string, included bool) []string {
133
for _, name := range field {
134
prefixed := strings.HasPrefix(name, "^")
135
if prefixed && !included {
136
// has prefix and we want negatives.
137
items = append(items, strings.TrimPrefix(name, "^"))
138
} else if !prefixed && included {
139
// no prefix and we want positives.
140
items = append(items, name)
146
// IncludeSpaces returns a list of spaces to include when starting a
147
// machine, if specified.
148
func (v *Value) IncludeSpaces() []string {
152
return v.extractItems(*v.Spaces, true)
155
// ExcludeSpaces returns a list of spaces to exclude when starting a
156
// machine, if specified. They are given in the spaces constraint with
157
// a "^" prefix to the name, which is stripped before returning.
158
func (v *Value) ExcludeSpaces() []string {
162
return v.extractItems(*v.Spaces, false)
165
// HaveSpaces returns whether any spaces constraints were specified.
166
func (v *Value) HaveSpaces() bool {
167
return v.Spaces != nil && len(*v.Spaces) > 0
170
// HasVirtType returns true if the constraints.Value specifies an virtual type.
171
func (v *Value) HasVirtType() bool {
172
return v.VirtType != nil && *v.VirtType != ""
175
// String expresses a constraints.Value in the language in which it was specified.
176
func (v Value) String() string {
179
strs = append(strs, "arch="+*v.Arch)
181
if v.Container != nil {
182
strs = append(strs, "container="+string(*v.Container))
184
if v.CpuCores != nil {
185
strs = append(strs, "cpu-cores="+uintStr(*v.CpuCores))
187
if v.CpuPower != nil {
188
strs = append(strs, "cpu-power="+uintStr(*v.CpuPower))
190
if v.InstanceType != nil {
191
strs = append(strs, "instance-type="+string(*v.InstanceType))
198
strs = append(strs, "mem="+s)
200
if v.RootDisk != nil {
201
s := uintStr(*v.RootDisk)
205
strs = append(strs, "root-disk="+s)
208
s := strings.Join(*v.Tags, ",")
209
strs = append(strs, "tags="+s)
212
s := strings.Join(*v.Spaces, ",")
213
strs = append(strs, "spaces="+s)
215
if v.VirtType != nil {
216
strs = append(strs, "virt-type="+string(*v.VirtType))
218
return strings.Join(strs, " ")
221
// GoString allows printing a constraints.Value nicely with the fmt
222
// package, especially when nested inside other types.
223
func (v Value) GoString() string {
226
values = append(values, fmt.Sprintf("Arch: %q", *v.Arch))
228
if v.CpuCores != nil {
229
values = append(values, fmt.Sprintf("CpuCores: %v", *v.CpuCores))
231
if v.CpuPower != nil {
232
values = append(values, fmt.Sprintf("CpuPower: %v", *v.CpuPower))
235
values = append(values, fmt.Sprintf("Mem: %v", *v.Mem))
237
if v.RootDisk != nil {
238
values = append(values, fmt.Sprintf("RootDisk: %v", *v.RootDisk))
240
if v.InstanceType != nil {
241
values = append(values, fmt.Sprintf("InstanceType: %q", *v.InstanceType))
243
if v.Container != nil {
244
values = append(values, fmt.Sprintf("Container: %q", *v.Container))
246
if v.Tags != nil && *v.Tags != nil {
247
values = append(values, fmt.Sprintf("Tags: %q", *v.Tags))
248
} else if v.Tags != nil {
249
values = append(values, "Tags: (*[]string)(nil)")
251
if v.Spaces != nil && *v.Spaces != nil {
252
values = append(values, fmt.Sprintf("Spaces: %q", *v.Spaces))
253
} else if v.Spaces != nil {
254
values = append(values, "Spaces: (*[]string)(nil)")
256
if v.VirtType != nil {
257
values = append(values, fmt.Sprintf("VirtType: %q", *v.VirtType))
259
return fmt.Sprintf("{%s}", strings.Join(values, ", "))
262
func uintStr(i uint64) string {
266
return fmt.Sprintf("%d", i)
269
// Parse constructs a constraints.Value from the supplied arguments,
270
// each of which must contain only spaces and name=value pairs. If any
271
// name is specified more than once, an error is returned.
272
func Parse(args ...string) (Value, error) {
274
for _, arg := range args {
275
raws := strings.Split(strings.TrimSpace(arg), " ")
276
for _, raw := range raws {
280
if err := cons.setRaw(raw); err != nil {
288
// Merge returns the effective constraints after merging any given
290
func Merge(values ...Value) (Value, error) {
292
for _, value := range values {
293
args = append(args, value.String())
295
return Parse(args...)
298
// MustParse constructs a constraints.Value from the supplied arguments,
299
// as Parse, but panics on failure.
300
func MustParse(args ...string) Value {
301
v, err := Parse(args...)
308
// Constraints implements gnuflag.Value for a Constraints.
309
type ConstraintsValue struct {
313
func (v ConstraintsValue) Set(s string) error {
314
cons, err := Parse(s)
322
func (v ConstraintsValue) String() string {
323
return v.Target.String()
326
func (v *Value) fieldFromTag(tagName string) (reflect.Value, bool) {
327
fieldName := fieldNames[tagName]
328
val := reflect.ValueOf(v).Elem().FieldByName(fieldName)
329
return val, val.IsValid()
332
// attributesWithValues returns the non-zero attribute tags and their values from the constraint.
333
func (v *Value) attributesWithValues() (result map[string]interface{}) {
334
result = make(map[string]interface{})
335
for fieldTag, fieldName := range fieldNames {
336
val := reflect.ValueOf(v).Elem().FieldByName(fieldName)
338
result[fieldTag] = val.Elem().Interface()
344
// hasAny returns any attrTags for which the constraint has a non-nil value.
345
func (v *Value) hasAny(attrTags ...string) []string {
346
attrValues := v.attributesWithValues()
347
var result []string = []string{}
348
for _, tag := range attrTags {
349
_, ok := attrValues[tag]
351
result = append(result, tag)
357
// without returns a copy of the constraint without values for
358
// the specified attributes.
359
func (v *Value) without(attrTags ...string) (Value, error) {
361
for _, tag := range attrTags {
362
val, ok := result.fieldFromTag(tag)
364
return Value{}, errors.Errorf("unknown constraint %q", tag)
366
val.Set(reflect.Zero(val.Type()))
371
// setRaw interprets a name=value string and sets the supplied value.
372
func (v *Value) setRaw(raw string) error {
373
eq := strings.Index(raw, "=")
375
return errors.Errorf("malformed constraint %q", raw)
377
name, str := raw[:eq], raw[eq+1:]
383
err = v.setContainer(str)
385
err = v.setCpuCores(str)
387
err = v.setCpuPower(str)
391
err = v.setRootDisk(str)
395
err = v.setInstanceType(str)
397
err = v.setSpaces(str)
399
err = v.setVirtType(str)
401
return errors.Errorf("unknown constraint %q", name)
404
return errors.Annotatef(err, "bad %q constraint", name)
409
// UnmarshalYAML is required to unmarshal a constraints.Value object
410
// to ensure the container attribute is correctly handled when it is empty.
411
// Because ContainerType is an alias for string, Go's reflect logic used in the
412
// YAML decode determines that *string and *ContainerType are not assignable so
413
// the container value of "" in the YAML is ignored.
414
func (v *Value) UnmarshalYAML(unmarshal func(interface{}) error) error {
415
values := map[interface{}]interface{}{}
416
err := unmarshal(&values)
418
return errors.Trace(err)
420
for k, val := range values {
421
vstr := fmt.Sprintf("%v", val)
426
ctype := instance.ContainerType(vstr)
429
v.InstanceType = &vstr
431
v.CpuCores, err = parseUint64(vstr)
433
v.CpuPower, err = parseUint64(vstr)
435
v.Mem, err = parseUint64(vstr)
437
v.RootDisk, err = parseUint64(vstr)
439
v.Tags, err = parseYamlStrings("tags", val)
442
spaces, err = parseYamlStrings("spaces", val)
444
return errors.Trace(err)
446
err = v.validateSpaces(spaces)
453
return errors.Errorf("unknown constraint value: %v", k)
456
return errors.Trace(err)
462
func (v *Value) setContainer(str string) error {
463
if v.Container != nil {
464
return errors.Errorf("already set")
467
ctype := instance.ContainerType("")
470
ctype, err := instance.ParseContainerTypeOrNone(str)
479
// HasContainer returns true if the constraints.Value specifies a container.
480
func (v *Value) HasContainer() bool {
481
return v.Container != nil && *v.Container != "" && *v.Container != instance.NONE
484
func (v *Value) setArch(str string) error {
486
return errors.Errorf("already set")
488
if str != "" && !arch.IsSupportedArch(str) {
489
return errors.Errorf("%q not recognized", str)
495
func (v *Value) setCpuCores(str string) (err error) {
496
if v.CpuCores != nil {
497
return errors.Errorf("already set")
499
v.CpuCores, err = parseUint64(str)
503
func (v *Value) setCpuPower(str string) (err error) {
504
if v.CpuPower != nil {
505
return errors.Errorf("already set")
507
v.CpuPower, err = parseUint64(str)
511
func (v *Value) setInstanceType(str string) error {
512
if v.InstanceType != nil {
513
return errors.Errorf("already set")
515
v.InstanceType = &str
519
func (v *Value) setMem(str string) (err error) {
521
return errors.Errorf("already set")
523
v.Mem, err = parseSize(str)
527
func (v *Value) setRootDisk(str string) (err error) {
528
if v.RootDisk != nil {
529
return errors.Errorf("already set")
531
v.RootDisk, err = parseSize(str)
535
func (v *Value) setTags(str string) error {
537
return errors.Errorf("already set")
539
v.Tags = parseCommaDelimited(str)
543
func (v *Value) setSpaces(str string) error {
545
return errors.Errorf("already set")
547
spaces := parseCommaDelimited(str)
548
if err := v.validateSpaces(spaces); err != nil {
555
func (v *Value) validateSpaces(spaces *[]string) error {
559
for _, name := range *spaces {
560
space := strings.TrimPrefix(name, "^")
561
if !names.IsValidSpace(space) {
562
return errors.Errorf("%q is not a valid space name", space)
568
func (v *Value) setVirtType(str string) error {
569
if v.VirtType != nil {
570
return errors.Errorf("already set")
576
func parseUint64(str string) (*uint64, error) {
579
if val, err := strconv.ParseUint(str, 10, 64); err != nil {
580
return nil, errors.Errorf("must be a non-negative integer")
588
func parseSize(str string) (*uint64, error) {
592
if m, ok := mbSuffixes[str[len(str)-1:]]; ok {
593
str = str[:len(str)-1]
596
val, err := strconv.ParseFloat(str, 64)
597
if err != nil || val < 0 {
598
return nil, errors.Errorf("must be a non-negative float with optional M/G/T/P suffix")
601
value = uint64(math.Ceil(val))
606
// parseCommaDelimited returns the items in the value s. We expect the
607
// items to be comma delimited strings.
608
func parseCommaDelimited(s string) *[]string {
612
t := strings.Split(s, ",")
616
func parseYamlStrings(entityName string, val interface{}) (*[]string, error) {
617
ifcs, ok := val.([]interface{})
619
return nil, errors.Errorf("unexpected type passed to %s: %T", entityName, val)
621
items := make([]string, len(ifcs))
622
for n, ifc := range ifcs {
623
s, ok := ifc.(string)
625
return nil, errors.Errorf("unexpected type passed as in %s: %T", entityName, ifc)
632
var mbSuffixes = map[string]float64{
636
"P": 1024 * 1024 * 1024,