Skip to content

Commit 519b2d2

Browse files
committed
storage: update to match spec changes
IOPS and persistence will now be configured at the storage pool level. User can now only specify pool, count and size in --storage, all of which are optional.
1 parent 1d88340 commit 519b2d2

File tree

2 files changed

+96
-118
lines changed

2 files changed

+96
-118
lines changed

storage/constraints.go

Lines changed: 75 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ const (
2222

2323
// Constraints describes a set of storage constraints.
2424
type Constraints struct {
25-
// Source is the name of the storage source (ebs, ceph, ...) that
26-
// must provide the storage, or "" if any source may be used.
27-
Source string
25+
// Pool is the name of the storage pool (ebs, ceph, custompool, ...)
26+
// that must provide the storage, or "" if the default pool should be
27+
// used.
28+
Pool string
2829

2930
// Minimum is the minimum acceptable values for each constraint variable.
3031
Minimum ConstraintValues
@@ -41,108 +42,104 @@ type ConstraintValues struct {
4142

4243
// Count is the number of instances of the storage to create.
4344
Count uint64
44-
45-
// Persistent indicates that the storage should be persistent
46-
// beyond the lifetime of the machine that it is initially
47-
// attached to.
48-
Persistent bool
49-
50-
// IOPS is the number of IOPS (I/O Operations Per Second) that the
51-
// storage should be capable of.
52-
IOPS uint64
5345
}
5446

55-
const (
56-
countSnippet = "(?:(-?[0-9]+)x)"
57-
sizeSuffixSnippet = "(?:[MGTPEZY](?:i?B)?)"
58-
sizeSnippet = "(-?[0-9]+(?:\\.[0-9]+)?" + sizeSuffixSnippet + "?)"
47+
var (
48+
poolRE = regexp.MustCompile("^[a-zA-Z]+[-?a-zA-Z0-9]*$")
49+
countRE = regexp.MustCompile("^-?[0-9]+$")
50+
sizeRE = regexp.MustCompile("^-?[0-9]+(?:\\.[0-9]+)?[MGTPEZY](?:i?B)?$")
5951
)
6052

61-
var countSizeRE = regexp.MustCompile("^" + countSnippet + "?" + sizeSnippet + "$")
62-
6353
// ParseConstraints parses the specified string and creates a
6454
// Constraints structure.
6555
//
66-
// The acceptable format for storage constraints is:
67-
// [SOURCE:][[COUNTx]SIZE][,persistent][,iops:IOPS]
68-
// where
69-
// SOURCE identifies the storage source. SOURCE can be a
70-
// string starting with a letter of the alphabet, followed
71-
// by zero or more alpha-numeric characters optionally
72-
// separated by hyphens.
56+
// The acceptable format for storage constraints is a comma separated
57+
// sequence of: POOL, COUNT, and SIZE, where
58+
//
59+
// POOL identifies the storage pool. POOL can be a string
60+
// starting with a letter, followed by zero or more digits
61+
// or letters optionally separated by hyphens.
7362
//
7463
// COUNT is a positive integer indicating how many instances
7564
// of the storage to create. If unspecified, and SIZE is
7665
// specified, COUNT defaults to 1.
7766
//
7867
// SIZE describes the minimum size of the storage instances to
79-
// create. SIZE is a floating point number and optional multiplier
80-
// from the set (M, G, T, P, E, Z, Y), which are all treated as
68+
// create. SIZE is a floating point number and multiplier from
69+
// the set (M, G, T, P, E, Z, Y), which are all treated as
8170
// powers of 1024.
82-
//
83-
// IOPS is a positive integer describing the minimum number of
84-
// IOPS the storage should be capable of. If unspecified, then
85-
// there is no constraint.
8671
func ParseConstraints(s string) (Constraints, error) {
8772
var cons Constraints
88-
if i := strings.IndexRune(s, ':'); i >= 0 {
89-
cons.Source, s = s[:i], s[i+1:]
90-
}
91-
92-
var countSizeMatch []string
93-
if i := strings.IndexRune(s, ','); i >= 0 {
94-
countSizeMatch = countSizeRE.FindStringSubmatch(s[:i])
95-
if countSizeMatch != nil {
96-
s = s[i+1:]
73+
fields := strings.Split(s, ",")
74+
for _, field := range fields {
75+
if field == "" {
76+
continue
9777
}
98-
} else {
99-
countSizeMatch = countSizeRE.FindStringSubmatch(s)
100-
if countSizeMatch != nil {
101-
s = ""
102-
}
103-
}
104-
var err error
105-
if countSizeMatch != nil {
106-
if countSizeMatch[1] != "" {
107-
if countSizeMatch[1][0] != '-' {
108-
cons.Preferred.Count, err = strconv.ParseUint(countSizeMatch[1], 10, 64)
109-
if err != nil {
110-
return cons, errors.Annotatef(err, "cannot parse count %q", countSizeMatch[1])
111-
}
112-
}
113-
if cons.Preferred.Count == 0 {
114-
return cons, errors.Errorf("count must be greater than zero, got %q", countSizeMatch[1])
78+
if isValidPoolName(field) {
79+
if cons.Pool != "" {
80+
logger.Warningf("pool name is already set to %q, ignoring %q", cons.Pool, field)
81+
} else {
82+
cons.Pool = field
11583
}
116-
} else {
117-
// Size is specified, but count is not; default count to 1.
118-
cons.Preferred.Count = 1
84+
continue
11985
}
120-
cons.Preferred.Size, err = utils.ParseSize(countSizeMatch[2])
121-
if err != nil {
122-
return cons, errors.Annotate(err, "cannot parse size")
86+
if count, ok, err := parseCount(field); ok {
87+
if err != nil {
88+
return cons, errors.Annotate(err, "cannot parse count")
89+
}
90+
cons.Preferred.Count = count
91+
continue
12392
}
124-
}
125-
126-
// Remaining constraints may be in any order.
127-
for _, field := range strings.Split(s, ",") {
128-
field = strings.TrimSpace(field)
129-
switch {
130-
case field == "":
131-
case field == persistentConstraint:
132-
cons.Preferred.Persistent = true
133-
case strings.HasPrefix(strings.ToLower(field), iopsConstraintPrefix):
134-
value := field[len(iopsConstraintPrefix):]
135-
cons.Preferred.IOPS, err = strconv.ParseUint(value, 10, 64)
93+
if size, ok, err := parseSize(field); ok {
13694
if err != nil {
137-
return cons, errors.Annotatef(err, "cannot parse IOPS %q", value)
95+
return cons, errors.Annotate(err, "cannot parse size")
13896
}
139-
default:
140-
logger.Warningf("ignoring unknown storage constraint %q", field)
97+
cons.Preferred.Size = size
98+
continue
14199
}
100+
logger.Warningf("ignoring unknown storage constraint %q", field)
101+
}
102+
if cons.Preferred.Count == 0 && cons.Preferred.Size > 0 {
103+
cons.Preferred.Count = 1
142104
}
143105

144106
// Explicitly specified constraints are always required;
145107
// the minimum is the same as the preferred.
146108
cons.Minimum = cons.Preferred
147109
return cons, nil
148110
}
111+
112+
func isValidPoolName(s string) bool {
113+
return poolRE.MatchString(s)
114+
}
115+
116+
func parseCount(s string) (uint64, bool, error) {
117+
if !countRE.MatchString(s) {
118+
return 0, false, nil
119+
}
120+
var n uint64
121+
var err error
122+
if s[0] == '-' {
123+
goto bad
124+
}
125+
n, err = strconv.ParseUint(s, 10, 64)
126+
if err != nil {
127+
return 0, false, nil
128+
}
129+
if n > 0 {
130+
return n, true, nil
131+
}
132+
bad:
133+
return 0, true, errors.Errorf("count must be greater than zero, got %q", s)
134+
}
135+
136+
func parseSize(s string) (uint64, bool, error) {
137+
if !sizeRE.MatchString(s) {
138+
return 0, false, nil
139+
}
140+
size, err := utils.ParseSize(s)
141+
if err != nil {
142+
return 0, true, err
143+
}
144+
return size, true, nil
145+
}

storage/constraints_test.go

Lines changed: 21 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,19 @@ type ConstraintsSuite struct{}
1414

1515
var _ = gc.Suite(&ConstraintsSuite{})
1616

17-
func (s *ConstraintsSuite) TestParseConstraintsStorageSource(c *gc.C) {
18-
s.testParse(c, "provider:1M", storage.Constraints{
19-
Source: "provider",
17+
func (s *ConstraintsSuite) TestParseConstraintsStoragePool(c *gc.C) {
18+
s.testParse(c, "pool,1M", storage.Constraints{
19+
Pool: "pool",
2020
Preferred: storage.ConstraintValues{
2121
Count: 1,
2222
Size: 1,
2323
},
2424
})
25-
s.testParse(c, "provider0123:", storage.Constraints{
26-
Source: "provider0123",
25+
s.testParse(c, "pool,", storage.Constraints{
26+
Pool: "pool",
2727
})
28-
s.testParse(c, ":", storage.Constraints{})
28+
s.testParse(c, "", storage.Constraints{})
29+
s.testParse(c, ",", storage.Constraints{})
2930
s.testParse(c, "1M", storage.Constraints{
3031
Preferred: storage.ConstraintValues{
3132
Size: 1,
@@ -35,22 +36,22 @@ func (s *ConstraintsSuite) TestParseConstraintsStorageSource(c *gc.C) {
3536
}
3637

3738
func (s *ConstraintsSuite) TestParseConstraintsCountSize(c *gc.C) {
38-
s.testParse(c, "s:1G", storage.Constraints{
39-
Source: "s",
39+
s.testParse(c, "p,1G", storage.Constraints{
40+
Pool: "p",
4041
Preferred: storage.ConstraintValues{
4142
Count: 1,
4243
Size: 1024,
4344
},
4445
})
45-
s.testParse(c, "s:1x0.5T", storage.Constraints{
46-
Source: "s",
46+
s.testParse(c, "p,1,0.5T", storage.Constraints{
47+
Pool: "p",
4748
Preferred: storage.ConstraintValues{
4849
Count: 1,
4950
Size: 1024 * 512,
5051
},
5152
})
52-
s.testParse(c, "s:3x0.125P", storage.Constraints{
53-
Source: "s",
53+
s.testParse(c, "p,0.125P,3", storage.Constraints{
54+
Pool: "p",
5455
Preferred: storage.ConstraintValues{
5556
Count: 3,
5657
Size: 1024 * 1024 * 128,
@@ -59,46 +60,26 @@ func (s *ConstraintsSuite) TestParseConstraintsCountSize(c *gc.C) {
5960
}
6061

6162
func (s *ConstraintsSuite) TestParseConstraintsOptions(c *gc.C) {
62-
s.testParse(c, "s:1M,", storage.Constraints{
63-
Source: "s",
63+
s.testParse(c, "p,1M,", storage.Constraints{
64+
Pool: "p",
6465
Preferred: storage.ConstraintValues{
6566
Count: 1,
6667
Size: 1,
6768
},
6869
})
69-
s.testParse(c, "s:anyoldjunk", storage.Constraints{
70-
Source: "s",
71-
})
72-
s.testParse(c, "s:persistent", storage.Constraints{
73-
Source: "s",
74-
Preferred: storage.ConstraintValues{
75-
Persistent: true,
76-
},
77-
})
78-
s.testParse(c, "s:persistent,iops:10000", storage.Constraints{
79-
Source: "s",
80-
Preferred: storage.ConstraintValues{
81-
Persistent: true,
82-
IOPS: 10000,
83-
},
84-
})
85-
s.testParse(c, "s:iops:10000,persistent", storage.Constraints{
86-
Source: "s",
87-
Preferred: storage.ConstraintValues{
88-
Persistent: true,
89-
IOPS: 10000,
90-
},
70+
s.testParse(c, "p,anyoldjunk", storage.Constraints{
71+
Pool: "p",
9172
})
9273
}
9374

9475
func (s *ConstraintsSuite) TestParseConstraintsCountRange(c *gc.C) {
95-
s.testParseError(c, "s:0x100M", `count must be greater than zero, got "0"`)
96-
s.testParseError(c, "s:00x100M", `count must be greater than zero, got "00"`)
97-
s.testParseError(c, "s:-1x100M", `count must be greater than zero, got "-1"`)
76+
s.testParseError(c, "p,0,100M", `cannot parse count: count must be greater than zero, got "0"`)
77+
s.testParseError(c, "p,00,100M", `cannot parse count: count must be greater than zero, got "00"`)
78+
s.testParseError(c, "p,-1,100M", `cannot parse count: count must be greater than zero, got "-1"`)
9879
}
9980

10081
func (s *ConstraintsSuite) TestParseConstraintsSizeRange(c *gc.C) {
101-
s.testParseError(c, "s:-100M", `cannot parse size: expected a non-negative number, got "-100M"`)
82+
s.testParseError(c, "p,-100M", `cannot parse size: expected a non-negative number, got "-100M"`)
10283
}
10384

10485
func (*ConstraintsSuite) testParse(c *gc.C, s string, expect storage.Constraints) {

0 commit comments

Comments
 (0)