github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/constraints/constraints.go (about)

     1  // Copyright 2013, 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package constraints
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"math"
    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/core/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 is an alias for Cores.
    26  	cpuCores     = "cpu-cores"
    27  	Cores        = "cores"
    28  	CpuPower     = "cpu-power"
    29  	Mem          = "mem"
    30  	RootDisk     = "root-disk"
    31  	Tags         = "tags"
    32  	InstanceType = "instance-type"
    33  	Spaces       = "spaces"
    34  	VirtType     = "virt-type"
    35  	Zones        = "zones"
    36  )
    37  
    38  // Value describes a user's requirements of the hardware on which units
    39  // of an application will run. Constraints are used to choose an existing machine
    40  // onto which a unit will be deployed, or to provision a new machine if no
    41  // existing one satisfies the requirements.
    42  type Value struct {
    43  
    44  	// Arch, if not nil or empty, indicates that a machine must run the named
    45  	// architecture.
    46  	Arch *string `json:"arch,omitempty" yaml:"arch,omitempty"`
    47  
    48  	// Container, if not nil, indicates that a machine must be the specified container type.
    49  	Container *instance.ContainerType `json:"container,omitempty" yaml:"container,omitempty"`
    50  
    51  	// CpuCores, if not nil, indicates that a machine must have at least that
    52  	// number of effective cores available.
    53  	CpuCores *uint64 `json:"cores,omitempty" yaml:"cores,omitempty"`
    54  
    55  	// CpuPower, if not nil, indicates that a machine must have at least that
    56  	// amount of CPU power available, where 100 CpuPower is considered to be
    57  	// equivalent to 1 Amazon ECU (or, roughly, a single 2007-era Xeon).
    58  	CpuPower *uint64 `json:"cpu-power,omitempty" yaml:"cpu-power,omitempty"`
    59  
    60  	// Mem, if not nil, indicates that a machine must have at least that many
    61  	// megabytes of RAM.
    62  	Mem *uint64 `json:"mem,omitempty" yaml:"mem,omitempty"`
    63  
    64  	// RootDisk, if not nil, indicates that a machine must have at least
    65  	// that many megabytes of disk space available in the root disk. In
    66  	// providers where the root disk is configurable at instance startup
    67  	// time, an instance with the specified amount of disk space in the OS
    68  	// disk might be requested.
    69  	RootDisk *uint64 `json:"root-disk,omitempty" yaml:"root-disk,omitempty"`
    70  
    71  	// Tags, if not nil, indicates tags that the machine must have applied to it.
    72  	// An empty list is treated the same as a nil (unspecified) list, except an
    73  	// empty list will override any default tags, where a nil list will not.
    74  	Tags *[]string `json:"tags,omitempty" yaml:"tags,omitempty"`
    75  
    76  	// InstanceType, if not nil, indicates that the specified cloud instance type
    77  	// be used. Only valid for clouds which support instance types.
    78  	InstanceType *string `json:"instance-type,omitempty" yaml:"instance-type,omitempty"`
    79  
    80  	// Spaces, if not nil, holds a list of juju network spaces that
    81  	// should be available (or not) on the machine. Positive and
    82  	// negative values are accepted, and the difference is the latter
    83  	// have a "^" prefix to the name.
    84  	Spaces *[]string `json:"spaces,omitempty" yaml:"spaces,omitempty"`
    85  
    86  	// VirtType, if not nil or empty, indicates that a machine must run the named
    87  	// virtual type. Only valid for clouds with multi-hypervisor support.
    88  	VirtType *string `json:"virt-type,omitempty" yaml:"virt-type,omitempty"`
    89  
    90  	// Zones, if not nil, holds a list of availability zones limiting where
    91  	// the machine can be located.
    92  	Zones *[]string `json:"zones,omitempty" yaml:"zones,omitempty"`
    93  }
    94  
    95  var rawAliases = map[string]string{
    96  	cpuCores: Cores,
    97  }
    98  
    99  // resolveAlias returns the canonical representation of the given key, if it'a
   100  // an alias listed in aliases, otherwise it returns the original key.
   101  func resolveAlias(key string) string {
   102  	if canonical, ok := rawAliases[key]; ok {
   103  		return canonical
   104  	}
   105  	return key
   106  }
   107  
   108  // IsEmpty returns if the given constraints value has no constraints set
   109  func IsEmpty(v *Value) bool {
   110  	return v.String() == ""
   111  }
   112  
   113  // HasArch returns true if the constraints.Value specifies an architecture.
   114  func (v *Value) HasArch() bool {
   115  	return v.Arch != nil && *v.Arch != ""
   116  }
   117  
   118  // HasMem returns true if the constraints.Value specifies a minimum amount
   119  // of memory.
   120  func (v *Value) HasMem() bool {
   121  	return v.Mem != nil && *v.Mem > 0
   122  }
   123  
   124  // HasCpuPower returns true if the constraints.Value specifies a minimum amount
   125  // of CPU power.
   126  func (v *Value) HasCpuPower() bool {
   127  	return v.CpuPower != nil && *v.CpuPower > 0
   128  }
   129  
   130  // HasCpuCores returns true if the constraints.Value specifies a minimum number
   131  // of CPU cores.
   132  func (v *Value) HasCpuCores() bool {
   133  	return v.CpuCores != nil && *v.CpuCores > 0
   134  }
   135  
   136  // HasInstanceType returns true if the constraints.Value specifies an instance type.
   137  func (v *Value) HasInstanceType() bool {
   138  	return v.InstanceType != nil && *v.InstanceType != ""
   139  }
   140  
   141  // extractItems returns the list of entries in the given field which
   142  // are either positive (included) or negative (!included; with prefix
   143  // "^").
   144  func (v *Value) extractItems(field []string, included bool) []string {
   145  	var items []string
   146  	for _, name := range field {
   147  		prefixed := strings.HasPrefix(name, "^")
   148  		if prefixed && !included {
   149  			// has prefix and we want negatives.
   150  			items = append(items, strings.TrimPrefix(name, "^"))
   151  		} else if !prefixed && included {
   152  			// no prefix and we want positives.
   153  			items = append(items, name)
   154  		}
   155  	}
   156  	return items
   157  }
   158  
   159  // IncludeSpaces returns a list of spaces to include when starting a
   160  // machine, if specified.
   161  func (v *Value) IncludeSpaces() []string {
   162  	if v.Spaces == nil {
   163  		return nil
   164  	}
   165  	return v.extractItems(*v.Spaces, true)
   166  }
   167  
   168  // ExcludeSpaces returns a list of spaces to exclude when starting a
   169  // machine, if specified. They are given in the spaces constraint with
   170  // a "^" prefix to the name, which is stripped before returning.
   171  func (v *Value) ExcludeSpaces() []string {
   172  	if v.Spaces == nil {
   173  		return nil
   174  	}
   175  	return v.extractItems(*v.Spaces, false)
   176  }
   177  
   178  // HasSpaces returns whether any spaces constraints were specified.
   179  func (v *Value) HasSpaces() bool {
   180  	return v.Spaces != nil && len(*v.Spaces) > 0
   181  }
   182  
   183  // HasVirtType returns true if the constraints.Value specifies an virtual type.
   184  func (v *Value) HasVirtType() bool {
   185  	return v.VirtType != nil && *v.VirtType != ""
   186  }
   187  
   188  // HasZones returns whether any zone constraints were specified.
   189  func (v *Value) HasZones() bool {
   190  	return v.Zones != nil && len(*v.Zones) > 0
   191  }
   192  
   193  // String expresses a constraints.Value in the language in which it was specified.
   194  func (v Value) String() string {
   195  	var strs []string
   196  	if v.Arch != nil {
   197  		strs = append(strs, "arch="+*v.Arch)
   198  	}
   199  	if v.Container != nil {
   200  		strs = append(strs, "container="+string(*v.Container))
   201  	}
   202  	if v.CpuCores != nil {
   203  		strs = append(strs, "cores="+uintStr(*v.CpuCores))
   204  	}
   205  	if v.CpuPower != nil {
   206  		strs = append(strs, "cpu-power="+uintStr(*v.CpuPower))
   207  	}
   208  	if v.InstanceType != nil {
   209  		strs = append(strs, "instance-type="+(*v.InstanceType))
   210  	}
   211  	if v.Mem != nil {
   212  		s := uintStr(*v.Mem)
   213  		if s != "" {
   214  			s += "M"
   215  		}
   216  		strs = append(strs, "mem="+s)
   217  	}
   218  	if v.RootDisk != nil {
   219  		s := uintStr(*v.RootDisk)
   220  		if s != "" {
   221  			s += "M"
   222  		}
   223  		strs = append(strs, "root-disk="+s)
   224  	}
   225  	if v.Tags != nil {
   226  		s := strings.Join(*v.Tags, ",")
   227  		strs = append(strs, "tags="+s)
   228  	}
   229  	if v.Spaces != nil {
   230  		s := strings.Join(*v.Spaces, ",")
   231  		strs = append(strs, "spaces="+s)
   232  	}
   233  	if v.VirtType != nil {
   234  		strs = append(strs, "virt-type="+(*v.VirtType))
   235  	}
   236  	if v.Zones != nil {
   237  		s := strings.Join(*v.Zones, ",")
   238  		strs = append(strs, "zones="+s)
   239  	}
   240  	return strings.Join(strs, " ")
   241  }
   242  
   243  // GoString allows printing a constraints.Value nicely with the fmt
   244  // package, especially when nested inside other types.
   245  func (v Value) GoString() string {
   246  	var values []string
   247  	if v.Arch != nil {
   248  		values = append(values, fmt.Sprintf("Arch: %q", *v.Arch))
   249  	}
   250  	if v.CpuCores != nil {
   251  		values = append(values, fmt.Sprintf("Cores: %v", *v.CpuCores))
   252  	}
   253  	if v.CpuPower != nil {
   254  		values = append(values, fmt.Sprintf("CpuPower: %v", *v.CpuPower))
   255  	}
   256  	if v.Mem != nil {
   257  		values = append(values, fmt.Sprintf("Mem: %v", *v.Mem))
   258  	}
   259  	if v.RootDisk != nil {
   260  		values = append(values, fmt.Sprintf("RootDisk: %v", *v.RootDisk))
   261  	}
   262  	if v.InstanceType != nil {
   263  		values = append(values, fmt.Sprintf("InstanceType: %q", *v.InstanceType))
   264  	}
   265  	if v.Container != nil {
   266  		values = append(values, fmt.Sprintf("Container: %q", *v.Container))
   267  	}
   268  	if v.Tags != nil && *v.Tags != nil {
   269  		values = append(values, fmt.Sprintf("Tags: %q", *v.Tags))
   270  	} else if v.Tags != nil {
   271  		values = append(values, "Tags: (*[]string)(nil)")
   272  	}
   273  	if v.Spaces != nil && *v.Spaces != nil {
   274  		values = append(values, fmt.Sprintf("Spaces: %q", *v.Spaces))
   275  	} else if v.Spaces != nil {
   276  		values = append(values, "Spaces: (*[]string)(nil)")
   277  	}
   278  	if v.VirtType != nil {
   279  		values = append(values, fmt.Sprintf("VirtType: %q", *v.VirtType))
   280  	}
   281  	if v.Zones != nil && *v.Zones != nil {
   282  		values = append(values, fmt.Sprintf("Zones: %q", *v.Zones))
   283  	} else if v.Zones != nil {
   284  		values = append(values, "Zones: (*[]string)(nil)")
   285  	}
   286  	return fmt.Sprintf("{%s}", strings.Join(values, ", "))
   287  }
   288  
   289  func uintStr(i uint64) string {
   290  	if i == 0 {
   291  		return ""
   292  	}
   293  	return fmt.Sprintf("%d", i)
   294  }
   295  
   296  // Parse constructs a constraints.Value from the supplied arguments,
   297  // each of which must contain only spaces and name=value pairs. If any
   298  // name is specified more than once, an error is returned.
   299  func Parse(args ...string) (Value, error) {
   300  	v, _, err := ParseWithAliases(args...)
   301  	return v, err
   302  }
   303  
   304  // ParseWithAliases constructs a constraints.Value from the supplied arguments, each
   305  // of which must contain only spaces and name=value pairs. If any name is
   306  // specified more than once, an error is returned.  The aliases map returned
   307  // contains a map of aliases used, and their canonical values.
   308  func ParseWithAliases(args ...string) (cons Value, aliases map[string]string, err error) {
   309  	aliases = make(map[string]string)
   310  	for _, arg := range args {
   311  		raws := strings.Split(strings.TrimSpace(arg), " ")
   312  		for _, raw := range raws {
   313  			if raw == "" {
   314  				continue
   315  			}
   316  			name, val, err := splitRaw(raw)
   317  			if err != nil {
   318  				return Value{}, nil, errors.Trace(err)
   319  			}
   320  			if canonical, ok := rawAliases[name]; ok {
   321  				aliases[name] = canonical
   322  				name = canonical
   323  			}
   324  			if err := cons.setRaw(name, val); err != nil {
   325  				return Value{}, aliases, errors.Trace(err)
   326  			}
   327  		}
   328  	}
   329  	return cons, aliases, nil
   330  }
   331  
   332  // Merge returns the effective constraints after merging any given
   333  // existing values.
   334  func Merge(values ...Value) (Value, error) {
   335  	var args []string
   336  	for _, value := range values {
   337  		args = append(args, value.String())
   338  	}
   339  	return Parse(args...)
   340  }
   341  
   342  // MustParse constructs a constraints.Value from the supplied arguments,
   343  // as Parse, but panics on failure.
   344  func MustParse(args ...string) Value {
   345  	v, err := Parse(args...)
   346  	if err != nil {
   347  		panic(err)
   348  	}
   349  	return v
   350  }
   351  
   352  // Constraints implements gnuflag.Value for a Constraints.
   353  type ConstraintsValue struct {
   354  	Target *Value
   355  }
   356  
   357  func (v ConstraintsValue) Set(s string) error {
   358  	cons, err := Parse(s)
   359  	if err != nil {
   360  		return err
   361  	}
   362  	*v.Target = cons
   363  	return nil
   364  }
   365  
   366  func (v ConstraintsValue) String() string {
   367  	return v.Target.String()
   368  }
   369  
   370  // attributesWithValues returns the non-zero attribute tags and their values from the constraint.
   371  func (v *Value) attributesWithValues() map[string]interface{} {
   372  	// These can never fail, so we ignore the error for the sake of keeping our
   373  	// API clean.  I'm sorry (but not that sorry).
   374  	b, _ := json.Marshal(v)
   375  	result := map[string]interface{}{}
   376  	_ = json.Unmarshal(b, &result)
   377  	return result
   378  }
   379  
   380  func fromAttributes(attr map[string]interface{}) Value {
   381  	b, _ := json.Marshal(attr)
   382  	var result Value
   383  	_ = json.Unmarshal(b, &result)
   384  	return result
   385  }
   386  
   387  // hasAny returns any attrTags for which the constraint has a non-nil value.
   388  func (v *Value) hasAny(attrTags ...string) []string {
   389  	attributes := v.attributesWithValues()
   390  	var result []string
   391  	for _, tag := range attrTags {
   392  		_, ok := attributes[resolveAlias(tag)]
   393  		if ok {
   394  			result = append(result, tag)
   395  		}
   396  	}
   397  	return result
   398  }
   399  
   400  // without returns a copy of the constraint without values for
   401  // the specified attributes.
   402  func (v *Value) without(attrTags ...string) Value {
   403  	attributes := v.attributesWithValues()
   404  	for _, tag := range attrTags {
   405  		delete(attributes, resolveAlias(tag))
   406  	}
   407  	return fromAttributes(attributes)
   408  }
   409  
   410  func splitRaw(s string) (name, val string, err error) {
   411  	eq := strings.Index(s, "=")
   412  	if eq <= 0 {
   413  		return "", "", errors.Errorf("malformed constraint %q", s)
   414  	}
   415  	return s[:eq], s[eq+1:], nil
   416  }
   417  
   418  // setRaw interprets a name=value string and sets the supplied value.
   419  func (v *Value) setRaw(name, str string) error {
   420  	var err error
   421  	switch resolveAlias(name) {
   422  	case Arch:
   423  		err = v.setArch(str)
   424  	case Container:
   425  		err = v.setContainer(str)
   426  	case Cores:
   427  		err = v.setCpuCores(str)
   428  	case CpuPower:
   429  		err = v.setCpuPower(str)
   430  	case Mem:
   431  		err = v.setMem(str)
   432  	case RootDisk:
   433  		err = v.setRootDisk(str)
   434  	case Tags:
   435  		err = v.setTags(str)
   436  	case InstanceType:
   437  		err = v.setInstanceType(str)
   438  	case Spaces:
   439  		err = v.setSpaces(str)
   440  	case VirtType:
   441  		err = v.setVirtType(str)
   442  	case Zones:
   443  		err = v.setZones(str)
   444  	default:
   445  		return errors.Errorf("unknown constraint %q", name)
   446  	}
   447  	if err != nil {
   448  		return errors.Annotatef(err, "bad %q constraint", name)
   449  	}
   450  	return nil
   451  }
   452  
   453  // UnmarshalYAML is required to unmarshal a constraints.Value object
   454  // to ensure the container attribute is correctly handled when it is empty.
   455  // Because ContainerType is an alias for string, Go's reflect logic used in the
   456  // YAML decode determines that *string and *ContainerType are not assignable so
   457  // the container value of "" in the YAML is ignored.
   458  func (v *Value) UnmarshalYAML(unmarshal func(interface{}) error) error {
   459  	values := map[interface{}]interface{}{}
   460  	err := unmarshal(&values)
   461  	if err != nil {
   462  		return errors.Trace(err)
   463  	}
   464  	canonicals := map[string]string{}
   465  	for k, val := range values {
   466  		vstr := fmt.Sprintf("%v", val)
   467  		key, ok := k.(string)
   468  		if !ok {
   469  			return errors.Errorf("unexpected non-string key: %#v", k)
   470  		}
   471  		canonical := resolveAlias(key)
   472  		if v, ok := canonicals[canonical]; ok {
   473  			// duplicate entry
   474  			return errors.Errorf("constraint %q duplicates constraint %q", key, v)
   475  		}
   476  		canonicals[canonical] = key
   477  		switch canonical {
   478  		case Arch:
   479  			v.Arch = &vstr
   480  		case Container:
   481  			ctype := instance.ContainerType(vstr)
   482  			v.Container = &ctype
   483  		case InstanceType:
   484  			v.InstanceType = &vstr
   485  		case Cores:
   486  			v.CpuCores, err = parseUint64(vstr)
   487  		case CpuPower:
   488  			v.CpuPower, err = parseUint64(vstr)
   489  		case Mem:
   490  			v.Mem, err = parseUint64(vstr)
   491  		case RootDisk:
   492  			v.RootDisk, err = parseUint64(vstr)
   493  		case Tags:
   494  			v.Tags, err = parseYamlStrings("tags", val)
   495  		case Spaces:
   496  			var spaces *[]string
   497  			spaces, err = parseYamlStrings("spaces", val)
   498  			if err != nil {
   499  				return errors.Trace(err)
   500  			}
   501  			err = v.validateSpaces(spaces)
   502  			if err == nil {
   503  				v.Spaces = spaces
   504  			}
   505  		case VirtType:
   506  			v.VirtType = &vstr
   507  		case Zones:
   508  			v.Zones, err = parseYamlStrings("zones", val)
   509  		default:
   510  			return errors.Errorf("unknown constraint value: %v", k)
   511  		}
   512  		if err != nil {
   513  			return errors.Trace(err)
   514  		}
   515  	}
   516  	return nil
   517  }
   518  
   519  func (v *Value) setContainer(str string) error {
   520  	if v.Container != nil {
   521  		return errors.Errorf("already set")
   522  	}
   523  	if str == "" {
   524  		ctype := instance.ContainerType("")
   525  		v.Container = &ctype
   526  	} else {
   527  		ctype, err := instance.ParseContainerTypeOrNone(str)
   528  		if err != nil {
   529  			return err
   530  		}
   531  		v.Container = &ctype
   532  	}
   533  	return nil
   534  }
   535  
   536  // HasContainer returns true if the constraints.Value specifies a container.
   537  func (v *Value) HasContainer() bool {
   538  	return v.Container != nil && *v.Container != "" && *v.Container != instance.NONE
   539  }
   540  
   541  func (v *Value) setArch(str string) error {
   542  	if v.Arch != nil {
   543  		return errors.Errorf("already set")
   544  	}
   545  	if str != "" && !arch.IsSupportedArch(str) {
   546  		return errors.Errorf("%q not recognized", str)
   547  	}
   548  	v.Arch = &str
   549  	return nil
   550  }
   551  
   552  func (v *Value) setCpuCores(str string) (err error) {
   553  	if v.CpuCores != nil {
   554  		return errors.Errorf("already set")
   555  	}
   556  	v.CpuCores, err = parseUint64(str)
   557  	return
   558  }
   559  
   560  func (v *Value) setCpuPower(str string) (err error) {
   561  	if v.CpuPower != nil {
   562  		return errors.Errorf("already set")
   563  	}
   564  	v.CpuPower, err = parseUint64(str)
   565  	return
   566  }
   567  
   568  func (v *Value) setInstanceType(str string) error {
   569  	if v.InstanceType != nil {
   570  		return errors.Errorf("already set")
   571  	}
   572  	v.InstanceType = &str
   573  	return nil
   574  }
   575  
   576  func (v *Value) setMem(str string) (err error) {
   577  	if v.Mem != nil {
   578  		return errors.Errorf("already set")
   579  	}
   580  	v.Mem, err = parseSize(str)
   581  	return
   582  }
   583  
   584  func (v *Value) setRootDisk(str string) (err error) {
   585  	if v.RootDisk != nil {
   586  		return errors.Errorf("already set")
   587  	}
   588  	v.RootDisk, err = parseSize(str)
   589  	return
   590  }
   591  
   592  func (v *Value) setTags(str string) error {
   593  	if v.Tags != nil {
   594  		return errors.Errorf("already set")
   595  	}
   596  	v.Tags = parseCommaDelimited(str)
   597  	return nil
   598  }
   599  
   600  func (v *Value) setSpaces(str string) error {
   601  	if v.Spaces != nil {
   602  		return errors.Errorf("already set")
   603  	}
   604  	spaces := parseCommaDelimited(str)
   605  	if err := v.validateSpaces(spaces); err != nil {
   606  		return err
   607  	}
   608  	v.Spaces = spaces
   609  	return nil
   610  }
   611  
   612  func (v *Value) validateSpaces(spaces *[]string) error {
   613  	if spaces == nil {
   614  		return nil
   615  	}
   616  	for _, name := range *spaces {
   617  		space := strings.TrimPrefix(name, "^")
   618  		if !names.IsValidSpace(space) {
   619  			return errors.Errorf("%q is not a valid space name", space)
   620  		}
   621  	}
   622  	return nil
   623  }
   624  
   625  func (v *Value) setVirtType(str string) error {
   626  	if v.VirtType != nil {
   627  		return errors.Errorf("already set")
   628  	}
   629  	v.VirtType = &str
   630  	return nil
   631  }
   632  
   633  func (v *Value) setZones(str string) error {
   634  	if v.Zones != nil {
   635  		return errors.Errorf("already set")
   636  	}
   637  	v.Zones = parseCommaDelimited(str)
   638  	return nil
   639  }
   640  
   641  func parseUint64(str string) (*uint64, error) {
   642  	var value uint64
   643  	if str != "" {
   644  		val, err := strconv.ParseUint(str, 10, 64)
   645  		if err != nil {
   646  			return nil, errors.Errorf("must be a non-negative integer")
   647  		}
   648  		value = val
   649  	}
   650  	return &value, nil
   651  }
   652  
   653  func parseSize(str string) (*uint64, error) {
   654  	var value uint64
   655  	if str != "" {
   656  		mult := 1.0
   657  		if m, ok := mbSuffixes[str[len(str)-1:]]; ok {
   658  			str = str[:len(str)-1]
   659  			mult = m
   660  		}
   661  		val, err := strconv.ParseFloat(str, 64)
   662  		if err != nil || val < 0 {
   663  			return nil, errors.Errorf("must be a non-negative float with optional M/G/T/P suffix")
   664  		}
   665  		val *= mult
   666  		value = uint64(math.Ceil(val))
   667  	}
   668  	return &value, nil
   669  }
   670  
   671  // parseCommaDelimited returns the items in the value s. We expect the
   672  // items to be comma delimited strings.
   673  func parseCommaDelimited(s string) *[]string {
   674  	if s == "" {
   675  		return &[]string{}
   676  	}
   677  	t := strings.Split(s, ",")
   678  	return &t
   679  }
   680  
   681  func parseYamlStrings(entityName string, val interface{}) (*[]string, error) {
   682  	ifcs, ok := val.([]interface{})
   683  	if !ok {
   684  		return nil, errors.Errorf("unexpected type passed to %s: %T", entityName, val)
   685  	}
   686  	items := make([]string, len(ifcs))
   687  	for n, ifc := range ifcs {
   688  		s, ok := ifc.(string)
   689  		if !ok {
   690  			return nil, errors.Errorf("unexpected type passed as in %s: %T", entityName, ifc)
   691  		}
   692  		items[n] = s
   693  	}
   694  	return &items, nil
   695  }
   696  
   697  var mbSuffixes = map[string]float64{
   698  	"M": 1,
   699  	"G": 1024,
   700  	"T": 1024 * 1024,
   701  	"P": 1024 * 1024 * 1024,
   702  }