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 }