~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/constraints/constraints.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2013, 2014 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package constraints
 
5
 
 
6
import (
 
7
        "fmt"
 
8
        "math"
 
9
        "reflect"
 
10
        "strconv"
 
11
        "strings"
 
12
 
 
13
        "github.com/juju/errors"
 
14
        "github.com/juju/utils/arch"
 
15
        "gopkg.in/juju/names.v2"
 
16
 
 
17
        "github.com/juju/juju/instance"
 
18
)
 
19
 
 
20
// The following constants list the supported constraint attribute names, as defined
 
21
// by the fields in the Value struct.
 
22
const (
 
23
        Arch         = "arch"
 
24
        Container    = "container"
 
25
        CpuCores     = "cpu-cores"
 
26
        CpuPower     = "cpu-power"
 
27
        Mem          = "mem"
 
28
        RootDisk     = "root-disk"
 
29
        Tags         = "tags"
 
30
        InstanceType = "instance-type"
 
31
        Spaces       = "spaces"
 
32
        VirtType     = "virt-type"
 
33
)
 
34
 
 
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.
 
39
type Value struct {
 
40
 
 
41
        // Arch, if not nil or empty, indicates that a machine must run the named
 
42
        // architecture.
 
43
        Arch *string `json:"arch,omitempty" yaml:"arch,omitempty"`
 
44
 
 
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"`
 
47
 
 
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"`
 
51
 
 
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"`
 
56
 
 
57
        // Mem, if not nil, indicates that a machine must have at least that many
 
58
        // megabytes of RAM.
 
59
        Mem *uint64 `json:"mem,omitempty" yaml:"mem,omitempty"`
 
60
 
 
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"`
 
67
 
 
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"`
 
72
 
 
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"`
 
76
 
 
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"`
 
82
 
 
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"`
 
86
}
 
87
 
 
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
 
91
 
 
92
func init() {
 
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++ {
 
98
                field := typ.Field(i)
 
99
                if tag := field.Tag.Get("json"); tag != "" {
 
100
                        if i := strings.Index(tag, ","); i >= 0 {
 
101
                                tag = tag[0:i]
 
102
                        }
 
103
                        if tag == "-" {
 
104
                                continue
 
105
                        }
 
106
                        if tag != "" {
 
107
                                fieldNames[tag] = field.Name
 
108
                        }
 
109
                }
 
110
        }
 
111
}
 
112
 
 
113
// IsEmpty returns if the given constraints value has no constraints set
 
114
func IsEmpty(v *Value) bool {
 
115
        return v.String() == ""
 
116
}
 
117
 
 
118
// HasArch returns true if the constraints.Value specifies an architecture.
 
119
func (v *Value) HasArch() bool {
 
120
        return v.Arch != nil && *v.Arch != ""
 
121
}
 
122
 
 
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 != ""
 
126
}
 
127
 
 
128
// extractItems returns the list of entries in the given field which
 
129
// are either positive (included) or negative (!included; with prefix
 
130
// "^").
 
131
func (v *Value) extractItems(field []string, included bool) []string {
 
132
        var items []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)
 
141
                }
 
142
        }
 
143
        return items
 
144
}
 
145
 
 
146
// IncludeSpaces returns a list of spaces to include when starting a
 
147
// machine, if specified.
 
148
func (v *Value) IncludeSpaces() []string {
 
149
        if v.Spaces == nil {
 
150
                return nil
 
151
        }
 
152
        return v.extractItems(*v.Spaces, true)
 
153
}
 
154
 
 
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 {
 
159
        if v.Spaces == nil {
 
160
                return nil
 
161
        }
 
162
        return v.extractItems(*v.Spaces, false)
 
163
}
 
164
 
 
165
// HaveSpaces returns whether any spaces constraints were specified.
 
166
func (v *Value) HaveSpaces() bool {
 
167
        return v.Spaces != nil && len(*v.Spaces) > 0
 
168
}
 
169
 
 
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 != ""
 
173
}
 
174
 
 
175
// String expresses a constraints.Value in the language in which it was specified.
 
176
func (v Value) String() string {
 
177
        var strs []string
 
178
        if v.Arch != nil {
 
179
                strs = append(strs, "arch="+*v.Arch)
 
180
        }
 
181
        if v.Container != nil {
 
182
                strs = append(strs, "container="+string(*v.Container))
 
183
        }
 
184
        if v.CpuCores != nil {
 
185
                strs = append(strs, "cpu-cores="+uintStr(*v.CpuCores))
 
186
        }
 
187
        if v.CpuPower != nil {
 
188
                strs = append(strs, "cpu-power="+uintStr(*v.CpuPower))
 
189
        }
 
190
        if v.InstanceType != nil {
 
191
                strs = append(strs, "instance-type="+string(*v.InstanceType))
 
192
        }
 
193
        if v.Mem != nil {
 
194
                s := uintStr(*v.Mem)
 
195
                if s != "" {
 
196
                        s += "M"
 
197
                }
 
198
                strs = append(strs, "mem="+s)
 
199
        }
 
200
        if v.RootDisk != nil {
 
201
                s := uintStr(*v.RootDisk)
 
202
                if s != "" {
 
203
                        s += "M"
 
204
                }
 
205
                strs = append(strs, "root-disk="+s)
 
206
        }
 
207
        if v.Tags != nil {
 
208
                s := strings.Join(*v.Tags, ",")
 
209
                strs = append(strs, "tags="+s)
 
210
        }
 
211
        if v.Spaces != nil {
 
212
                s := strings.Join(*v.Spaces, ",")
 
213
                strs = append(strs, "spaces="+s)
 
214
        }
 
215
        if v.VirtType != nil {
 
216
                strs = append(strs, "virt-type="+string(*v.VirtType))
 
217
        }
 
218
        return strings.Join(strs, " ")
 
219
}
 
220
 
 
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 {
 
224
        var values []string
 
225
        if v.Arch != nil {
 
226
                values = append(values, fmt.Sprintf("Arch: %q", *v.Arch))
 
227
        }
 
228
        if v.CpuCores != nil {
 
229
                values = append(values, fmt.Sprintf("CpuCores: %v", *v.CpuCores))
 
230
        }
 
231
        if v.CpuPower != nil {
 
232
                values = append(values, fmt.Sprintf("CpuPower: %v", *v.CpuPower))
 
233
        }
 
234
        if v.Mem != nil {
 
235
                values = append(values, fmt.Sprintf("Mem: %v", *v.Mem))
 
236
        }
 
237
        if v.RootDisk != nil {
 
238
                values = append(values, fmt.Sprintf("RootDisk: %v", *v.RootDisk))
 
239
        }
 
240
        if v.InstanceType != nil {
 
241
                values = append(values, fmt.Sprintf("InstanceType: %q", *v.InstanceType))
 
242
        }
 
243
        if v.Container != nil {
 
244
                values = append(values, fmt.Sprintf("Container: %q", *v.Container))
 
245
        }
 
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)")
 
250
        }
 
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)")
 
255
        }
 
256
        if v.VirtType != nil {
 
257
                values = append(values, fmt.Sprintf("VirtType: %q", *v.VirtType))
 
258
        }
 
259
        return fmt.Sprintf("{%s}", strings.Join(values, ", "))
 
260
}
 
261
 
 
262
func uintStr(i uint64) string {
 
263
        if i == 0 {
 
264
                return ""
 
265
        }
 
266
        return fmt.Sprintf("%d", i)
 
267
}
 
268
 
 
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) {
 
273
        cons := Value{}
 
274
        for _, arg := range args {
 
275
                raws := strings.Split(strings.TrimSpace(arg), " ")
 
276
                for _, raw := range raws {
 
277
                        if raw == "" {
 
278
                                continue
 
279
                        }
 
280
                        if err := cons.setRaw(raw); err != nil {
 
281
                                return Value{}, err
 
282
                        }
 
283
                }
 
284
        }
 
285
        return cons, nil
 
286
}
 
287
 
 
288
// Merge returns the effective constraints after merging any given
 
289
// existing values.
 
290
func Merge(values ...Value) (Value, error) {
 
291
        var args []string
 
292
        for _, value := range values {
 
293
                args = append(args, value.String())
 
294
        }
 
295
        return Parse(args...)
 
296
}
 
297
 
 
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...)
 
302
        if err != nil {
 
303
                panic(err)
 
304
        }
 
305
        return v
 
306
}
 
307
 
 
308
// Constraints implements gnuflag.Value for a Constraints.
 
309
type ConstraintsValue struct {
 
310
        Target *Value
 
311
}
 
312
 
 
313
func (v ConstraintsValue) Set(s string) error {
 
314
        cons, err := Parse(s)
 
315
        if err != nil {
 
316
                return err
 
317
        }
 
318
        *v.Target = cons
 
319
        return nil
 
320
}
 
321
 
 
322
func (v ConstraintsValue) String() string {
 
323
        return v.Target.String()
 
324
}
 
325
 
 
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()
 
330
}
 
331
 
 
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)
 
337
                if !val.IsNil() {
 
338
                        result[fieldTag] = val.Elem().Interface()
 
339
                }
 
340
        }
 
341
        return result
 
342
}
 
343
 
 
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]
 
350
                if ok {
 
351
                        result = append(result, tag)
 
352
                }
 
353
        }
 
354
        return result
 
355
}
 
356
 
 
357
// without returns a copy of the constraint without values for
 
358
// the specified attributes.
 
359
func (v *Value) without(attrTags ...string) (Value, error) {
 
360
        result := *v
 
361
        for _, tag := range attrTags {
 
362
                val, ok := result.fieldFromTag(tag)
 
363
                if !ok {
 
364
                        return Value{}, errors.Errorf("unknown constraint %q", tag)
 
365
                }
 
366
                val.Set(reflect.Zero(val.Type()))
 
367
        }
 
368
        return result, nil
 
369
}
 
370
 
 
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, "=")
 
374
        if eq <= 0 {
 
375
                return errors.Errorf("malformed constraint %q", raw)
 
376
        }
 
377
        name, str := raw[:eq], raw[eq+1:]
 
378
        var err error
 
379
        switch name {
 
380
        case Arch:
 
381
                err = v.setArch(str)
 
382
        case Container:
 
383
                err = v.setContainer(str)
 
384
        case CpuCores:
 
385
                err = v.setCpuCores(str)
 
386
        case CpuPower:
 
387
                err = v.setCpuPower(str)
 
388
        case Mem:
 
389
                err = v.setMem(str)
 
390
        case RootDisk:
 
391
                err = v.setRootDisk(str)
 
392
        case Tags:
 
393
                err = v.setTags(str)
 
394
        case InstanceType:
 
395
                err = v.setInstanceType(str)
 
396
        case Spaces:
 
397
                err = v.setSpaces(str)
 
398
        case VirtType:
 
399
                err = v.setVirtType(str)
 
400
        default:
 
401
                return errors.Errorf("unknown constraint %q", name)
 
402
        }
 
403
        if err != nil {
 
404
                return errors.Annotatef(err, "bad %q constraint", name)
 
405
        }
 
406
        return nil
 
407
}
 
408
 
 
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)
 
417
        if err != nil {
 
418
                return errors.Trace(err)
 
419
        }
 
420
        for k, val := range values {
 
421
                vstr := fmt.Sprintf("%v", val)
 
422
                switch k {
 
423
                case Arch:
 
424
                        v.Arch = &vstr
 
425
                case Container:
 
426
                        ctype := instance.ContainerType(vstr)
 
427
                        v.Container = &ctype
 
428
                case InstanceType:
 
429
                        v.InstanceType = &vstr
 
430
                case CpuCores:
 
431
                        v.CpuCores, err = parseUint64(vstr)
 
432
                case CpuPower:
 
433
                        v.CpuPower, err = parseUint64(vstr)
 
434
                case Mem:
 
435
                        v.Mem, err = parseUint64(vstr)
 
436
                case RootDisk:
 
437
                        v.RootDisk, err = parseUint64(vstr)
 
438
                case Tags:
 
439
                        v.Tags, err = parseYamlStrings("tags", val)
 
440
                case Spaces:
 
441
                        var spaces *[]string
 
442
                        spaces, err = parseYamlStrings("spaces", val)
 
443
                        if err != nil {
 
444
                                return errors.Trace(err)
 
445
                        }
 
446
                        err = v.validateSpaces(spaces)
 
447
                        if err == nil {
 
448
                                v.Spaces = spaces
 
449
                        }
 
450
                case VirtType:
 
451
                        v.VirtType = &vstr
 
452
                default:
 
453
                        return errors.Errorf("unknown constraint value: %v", k)
 
454
                }
 
455
                if err != nil {
 
456
                        return errors.Trace(err)
 
457
                }
 
458
        }
 
459
        return nil
 
460
}
 
461
 
 
462
func (v *Value) setContainer(str string) error {
 
463
        if v.Container != nil {
 
464
                return errors.Errorf("already set")
 
465
        }
 
466
        if str == "" {
 
467
                ctype := instance.ContainerType("")
 
468
                v.Container = &ctype
 
469
        } else {
 
470
                ctype, err := instance.ParseContainerTypeOrNone(str)
 
471
                if err != nil {
 
472
                        return err
 
473
                }
 
474
                v.Container = &ctype
 
475
        }
 
476
        return nil
 
477
}
 
478
 
 
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
 
482
}
 
483
 
 
484
func (v *Value) setArch(str string) error {
 
485
        if v.Arch != nil {
 
486
                return errors.Errorf("already set")
 
487
        }
 
488
        if str != "" && !arch.IsSupportedArch(str) {
 
489
                return errors.Errorf("%q not recognized", str)
 
490
        }
 
491
        v.Arch = &str
 
492
        return nil
 
493
}
 
494
 
 
495
func (v *Value) setCpuCores(str string) (err error) {
 
496
        if v.CpuCores != nil {
 
497
                return errors.Errorf("already set")
 
498
        }
 
499
        v.CpuCores, err = parseUint64(str)
 
500
        return
 
501
}
 
502
 
 
503
func (v *Value) setCpuPower(str string) (err error) {
 
504
        if v.CpuPower != nil {
 
505
                return errors.Errorf("already set")
 
506
        }
 
507
        v.CpuPower, err = parseUint64(str)
 
508
        return
 
509
}
 
510
 
 
511
func (v *Value) setInstanceType(str string) error {
 
512
        if v.InstanceType != nil {
 
513
                return errors.Errorf("already set")
 
514
        }
 
515
        v.InstanceType = &str
 
516
        return nil
 
517
}
 
518
 
 
519
func (v *Value) setMem(str string) (err error) {
 
520
        if v.Mem != nil {
 
521
                return errors.Errorf("already set")
 
522
        }
 
523
        v.Mem, err = parseSize(str)
 
524
        return
 
525
}
 
526
 
 
527
func (v *Value) setRootDisk(str string) (err error) {
 
528
        if v.RootDisk != nil {
 
529
                return errors.Errorf("already set")
 
530
        }
 
531
        v.RootDisk, err = parseSize(str)
 
532
        return
 
533
}
 
534
 
 
535
func (v *Value) setTags(str string) error {
 
536
        if v.Tags != nil {
 
537
                return errors.Errorf("already set")
 
538
        }
 
539
        v.Tags = parseCommaDelimited(str)
 
540
        return nil
 
541
}
 
542
 
 
543
func (v *Value) setSpaces(str string) error {
 
544
        if v.Spaces != nil {
 
545
                return errors.Errorf("already set")
 
546
        }
 
547
        spaces := parseCommaDelimited(str)
 
548
        if err := v.validateSpaces(spaces); err != nil {
 
549
                return err
 
550
        }
 
551
        v.Spaces = spaces
 
552
        return nil
 
553
}
 
554
 
 
555
func (v *Value) validateSpaces(spaces *[]string) error {
 
556
        if spaces == nil {
 
557
                return nil
 
558
        }
 
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)
 
563
                }
 
564
        }
 
565
        return nil
 
566
}
 
567
 
 
568
func (v *Value) setVirtType(str string) error {
 
569
        if v.VirtType != nil {
 
570
                return errors.Errorf("already set")
 
571
        }
 
572
        v.VirtType = &str
 
573
        return nil
 
574
}
 
575
 
 
576
func parseUint64(str string) (*uint64, error) {
 
577
        var value uint64
 
578
        if str != "" {
 
579
                if val, err := strconv.ParseUint(str, 10, 64); err != nil {
 
580
                        return nil, errors.Errorf("must be a non-negative integer")
 
581
                } else {
 
582
                        value = uint64(val)
 
583
                }
 
584
        }
 
585
        return &value, nil
 
586
}
 
587
 
 
588
func parseSize(str string) (*uint64, error) {
 
589
        var value uint64
 
590
        if str != "" {
 
591
                mult := 1.0
 
592
                if m, ok := mbSuffixes[str[len(str)-1:]]; ok {
 
593
                        str = str[:len(str)-1]
 
594
                        mult = m
 
595
                }
 
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")
 
599
                }
 
600
                val *= mult
 
601
                value = uint64(math.Ceil(val))
 
602
        }
 
603
        return &value, nil
 
604
}
 
605
 
 
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 {
 
609
        if s == "" {
 
610
                return &[]string{}
 
611
        }
 
612
        t := strings.Split(s, ",")
 
613
        return &t
 
614
}
 
615
 
 
616
func parseYamlStrings(entityName string, val interface{}) (*[]string, error) {
 
617
        ifcs, ok := val.([]interface{})
 
618
        if !ok {
 
619
                return nil, errors.Errorf("unexpected type passed to %s: %T", entityName, val)
 
620
        }
 
621
        items := make([]string, len(ifcs))
 
622
        for n, ifc := range ifcs {
 
623
                s, ok := ifc.(string)
 
624
                if !ok {
 
625
                        return nil, errors.Errorf("unexpected type passed as in %s: %T", entityName, ifc)
 
626
                }
 
627
                items[n] = s
 
628
        }
 
629
        return &items, nil
 
630
}
 
631
 
 
632
var mbSuffixes = map[string]float64{
 
633
        "M": 1,
 
634
        "G": 1024,
 
635
        "T": 1024 * 1024,
 
636
        "P": 1024 * 1024 * 1024,
 
637
}