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