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