github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/storage/constraints.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package storage
     5  
     6  import (
     7  	"regexp"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/utils"
    14  )
    15  
    16  var logger = loggo.GetLogger("juju.storage")
    17  
    18  // Constraints describes a set of storage constraints.
    19  type Constraints struct {
    20  	// Pool is the name of the storage pool (ebs, ceph, custompool, ...)
    21  	// that must provide the storage, or "" if the default pool should be
    22  	// used.
    23  	Pool string
    24  
    25  	// Size is the minimum size of the storage in MiB.
    26  	Size uint64
    27  
    28  	// Count is the number of instances of the storage to create.
    29  	Count uint64
    30  }
    31  
    32  var (
    33  	poolRE  = regexp.MustCompile("^[a-zA-Z]+[-?a-zA-Z0-9]*$")
    34  	countRE = regexp.MustCompile("^-?[0-9]+$")
    35  	sizeRE  = regexp.MustCompile("^-?[0-9]+(?:\\.[0-9]+)?[MGTPEZY](?:i?B)?$")
    36  )
    37  
    38  // ParseConstraints parses the specified string and creates a
    39  // Constraints structure.
    40  //
    41  // The acceptable format for storage constraints is a comma separated
    42  // sequence of: POOL, COUNT, and SIZE, where
    43  //
    44  //    POOL identifies the storage pool. POOL can be a string
    45  //    starting with a letter, followed by zero or more digits
    46  //    or letters optionally separated by hyphens.
    47  //
    48  //    COUNT is a positive integer indicating how many instances
    49  //    of the storage to create. If unspecified, and SIZE is
    50  //    specified, COUNT defaults to 1.
    51  //
    52  //    SIZE describes the minimum size of the storage instances to
    53  //    create. SIZE is a floating point number and multiplier from
    54  //    the set (M, G, T, P, E, Z, Y), which are all treated as
    55  //    powers of 1024.
    56  func ParseConstraints(s string) (Constraints, error) {
    57  	var cons Constraints
    58  	fields := strings.Split(s, ",")
    59  	for _, field := range fields {
    60  		if field == "" {
    61  			continue
    62  		}
    63  		if IsValidPoolName(field) {
    64  			if cons.Pool != "" {
    65  				logger.Debugf("pool name is already set to %q, ignoring %q", cons.Pool, field)
    66  			} else {
    67  				cons.Pool = field
    68  			}
    69  			continue
    70  		}
    71  		if count, ok, err := parseCount(field); ok {
    72  			if err != nil {
    73  				return cons, errors.Annotate(err, "cannot parse count")
    74  			}
    75  			cons.Count = count
    76  			continue
    77  		}
    78  		if size, ok, err := parseSize(field); ok {
    79  			if err != nil {
    80  				return cons, errors.Annotate(err, "cannot parse size")
    81  			}
    82  			cons.Size = size
    83  			continue
    84  		}
    85  		logger.Debugf("ignoring unknown storage constraint %q", field)
    86  	}
    87  	if cons.Count == 0 && cons.Size == 0 && cons.Pool == "" {
    88  		return Constraints{}, errors.New("storage constraints require at least one field to be specified")
    89  	}
    90  	if cons.Count == 0 {
    91  		cons.Count = 1
    92  	}
    93  	return cons, nil
    94  }
    95  
    96  // IsValidPoolName checks if given string is a valid pool name.
    97  func IsValidPoolName(s string) bool {
    98  	return poolRE.MatchString(s)
    99  }
   100  
   101  // ParseConstraintsMap parses string representation of
   102  // storage constraints into a map keyed on storage names
   103  // with constraints as values.
   104  //
   105  // Storage constraints may be specified as
   106  //     <name>=<constraints>
   107  // or as
   108  //     <name>
   109  // where latter is equivalent to <name>=1.
   110  //
   111  // Duplicate storage names cause an error to be returned.
   112  // Constraints presence can be enforced.
   113  func ParseConstraintsMap(args []string, mustHaveConstraints bool) (map[string]Constraints, error) {
   114  	results := make(map[string]Constraints, len(args))
   115  	for _, kv := range args {
   116  		parts := strings.SplitN(kv, "=", -1)
   117  		name := parts[0]
   118  		if len(parts) > 2 || len(name) == 0 {
   119  			return nil, errors.Errorf(`expected "name=constraints" or "name", got %q`, kv)
   120  		}
   121  
   122  		if mustHaveConstraints && len(parts) == 1 {
   123  			return nil, errors.Errorf(`expected "name=constraints" where "constraints" must be specified, got %q`, kv)
   124  		}
   125  
   126  		if _, exists := results[name]; exists {
   127  			return nil, errors.Errorf("storage %q specified more than once", name)
   128  		}
   129  		consString := "1"
   130  		if len(parts) > 1 {
   131  			consString = parts[1]
   132  		}
   133  		cons, err := ParseConstraints(consString)
   134  		if err != nil {
   135  			return nil, errors.Annotatef(err, "cannot parse constraints for storage %q", name)
   136  		}
   137  
   138  		results[name] = cons
   139  	}
   140  	return results, nil
   141  }
   142  
   143  func parseCount(s string) (uint64, bool, error) {
   144  	if !countRE.MatchString(s) {
   145  		return 0, false, nil
   146  	}
   147  	var n uint64
   148  	var err error
   149  	if s[0] == '-' {
   150  		goto bad
   151  	}
   152  	n, err = strconv.ParseUint(s, 10, 64)
   153  	if err != nil {
   154  		return 0, false, nil
   155  	}
   156  	if n > 0 {
   157  		return n, true, nil
   158  	}
   159  bad:
   160  	return 0, true, errors.Errorf("count must be greater than zero, got %q", s)
   161  }
   162  
   163  func parseSize(s string) (uint64, bool, error) {
   164  	if !sizeRE.MatchString(s) {
   165  		return 0, false, nil
   166  	}
   167  	size, err := utils.ParseSize(s)
   168  	if err != nil {
   169  		return 0, true, err
   170  	}
   171  	return size, true, nil
   172  }