-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
storage: add Directive, Specification
We introduce two new structures: Directive and Specification. Directive contains user- specified requirements, while Specification contains both user-specified and charm-specified requirements. There is an associated ParseDirective function that will be used by "juju deploy" and "juju add-unit" later.
- Loading branch information
Showing
6 changed files
with
359 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
// Copyright 2014 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package storage | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"strconv" | ||
|
||
"github.com/juju/errors" | ||
"github.com/juju/utils" | ||
) | ||
|
||
const ( | ||
// ProviderSource identifies the environment's cloud provider | ||
// storage service(s). | ||
ProviderSource = "provider" | ||
|
||
storageNameSnippet = "(?:[a-z][a-z0-9]*(?:-[a-z0-9]+)*)" | ||
storageSourceSnippet = "(?:[a-z][a-z0-9]*)" | ||
storageCountSnippet = "-?[0-9]+" | ||
storageSizeSnippet = "-?[0-9]+(?:\\.[0-9]+)?[MGTP]?" | ||
storageOptionsSnippet = ".*" | ||
) | ||
|
||
// ErrStorageSourceMissing is an error that is returned from ParseDirective | ||
// if the source is unspecified. | ||
var ErrStorageSourceMissing = fmt.Errorf("storage source missing") | ||
|
||
var storageRE = regexp.MustCompile( | ||
"^" + | ||
"(?:(" + storageNameSnippet + ")=)?" + | ||
"(?:(" + storageSourceSnippet + "):)?" + | ||
"(?:(" + storageCountSnippet + ")x)?" + | ||
"(" + storageSizeSnippet + ")?" + | ||
"(" + storageOptionsSnippet + ")?" + | ||
"$", | ||
) | ||
|
||
// Directive is a storage creation directive. | ||
type Directive struct { | ||
// Name is the name of the storage. | ||
// | ||
// Name is required. | ||
Name string | ||
|
||
// Source is the storage source (provider, ceph, ...). | ||
// | ||
// Source is required. | ||
Source string | ||
|
||
// Count is the number of instances of the store to create/attach. | ||
// | ||
// Count is optional. Count will default to 1 if a size is | ||
// specified, otherwise it will default to 0. | ||
Count int | ||
|
||
// Size is the size of the storage in MiB. | ||
// | ||
// Size's optionality depends on the storage source. For some | ||
// types of storage (e.g. an NFS share), it is not meaningful | ||
// to specify a size; for others (e.g. EBS), it is necessary. | ||
Size uint64 | ||
|
||
// Options is source-specific options for storage creation. | ||
Options string | ||
} | ||
|
||
// ParseDirective attempts to parse the string and create a | ||
// corresponding Directive structure. | ||
// | ||
// If a storage source is not specified, ParseDirective will | ||
// return ErrStorageSourceMissing. | ||
// | ||
// The acceptable format for storage directives is: | ||
// NAME=SOURCE:[[COUNTx]SIZE][,OPTIONS] | ||
// where | ||
// NAME is an identifier for storage instances; multiple | ||
// instances may share the same storage name. NAME can be a | ||
// string starting with a letter of the alphabet, followed | ||
// by zero or more alpha-numeric characters. | ||
// | ||
// SOURCE identifies the storage source. SOURCE can be a | ||
// string starting with a letter of the alphabet, followed | ||
// by zero or more alpha-numeric characters optionally | ||
// separated by hyphens. | ||
// | ||
// COUNT is a decimal number indicating how many instances | ||
// of the storage to create. If count is unspecified and a | ||
// size is specified, 1 is assumed. | ||
// | ||
// SIZE is a floating point number and optional multiplier from | ||
// the set (M, G, T, P), which are all treated as powers of 1024. | ||
// | ||
// OPTIONS is the string remaining the colon (if any) that will | ||
// be passed onto the storage source unmodified. | ||
func ParseDirective(s string) (*Directive, error) { | ||
match := storageRE.FindStringSubmatch(s) | ||
if match == nil { | ||
return nil, errors.Errorf("failed to parse storage %q", s) | ||
} | ||
if match[1] == "" { | ||
return nil, errors.New("storage name missing") | ||
} | ||
if match[2] == "" { | ||
return nil, ErrStorageSourceMissing | ||
} | ||
|
||
var size uint64 | ||
var count int | ||
var err error | ||
if match[4] != "" { | ||
size, err = utils.ParseSize(match[4]) | ||
if err != nil { | ||
return nil, errors.Annotate(err, "failed to parse size") | ||
} | ||
} | ||
options := match[5] | ||
|
||
if size > 0 { | ||
// Don't bother parsing count unless we have a size too. | ||
if count, err = parseStorageCount(match[3]); err != nil { | ||
return nil, err | ||
} | ||
|
||
// Size was specified, so options must be preceded by a ",". | ||
if options != "" { | ||
if options[0] != ',' { | ||
return nil, errors.Errorf( | ||
"invalid trailing data %q: options must be preceded by ',' when size is specified", | ||
options, | ||
) | ||
} | ||
options = options[1:] | ||
} | ||
} | ||
|
||
storage := Directive{ | ||
Name: match[1], | ||
Source: match[2], | ||
Count: count, | ||
Size: size, | ||
Options: options, | ||
} | ||
return &storage, nil | ||
} | ||
|
||
func parseStorageCount(count string) (int, error) { | ||
if count == "" { | ||
return 1, nil | ||
} | ||
n, err := strconv.Atoi(count) | ||
if err != nil { | ||
return -1, err | ||
} | ||
if n <= 0 { | ||
return -1, errors.New("count must be a positive integer") | ||
} | ||
return n, nil | ||
} | ||
|
||
// MustParseDirective attempts to parse the string and create a | ||
// corresponding Directive structure, panicking if an error occurs. | ||
func MustParseDirective(s string) *Directive { | ||
storage, err := ParseDirective(s) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return storage | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Copyright 2014 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package storage_test | ||
|
||
import ( | ||
gc "gopkg.in/check.v1" | ||
|
||
"github.com/juju/juju/storage" | ||
) | ||
|
||
type DirectiveSuite struct{} | ||
|
||
var _ = gc.Suite(&DirectiveSuite{}) | ||
|
||
func (s *DirectiveSuite) TestParseDirective(c *gc.C) { | ||
parseStorageTests := []struct { | ||
arg string | ||
expectSource string | ||
expectName string | ||
expectCount int | ||
expectSize uint64 | ||
expectPersistent bool | ||
expectOptions string | ||
err string | ||
}{{ | ||
arg: "", | ||
err: `storage name missing`, | ||
}, { | ||
arg: ":", | ||
err: `storage name missing`, | ||
}, { | ||
arg: "1M", | ||
err: "storage name missing", | ||
}, { | ||
arg: "ebs:1M", | ||
err: "storage name missing", | ||
}, { | ||
arg: "name=1M", | ||
err: "storage source missing", | ||
}, { | ||
arg: "name=source:1M", | ||
expectName: "name", | ||
expectSource: "source", | ||
expectCount: 1, | ||
expectSize: 1, | ||
}, { | ||
arg: "n-a-m-e=source:1M", | ||
expectName: "n-a-m-e", | ||
expectSource: "source", | ||
expectCount: 1, | ||
expectSize: 1, | ||
}, { | ||
arg: "name=source:1Msomejunk", | ||
err: `invalid trailing data "somejunk": options must be preceded by ',' when size is specified`, | ||
}, { | ||
arg: "name=source:anyoldjunk", | ||
expectName: "name", | ||
expectSource: "source", | ||
expectCount: 0, | ||
expectSize: 0, | ||
expectOptions: "anyoldjunk", | ||
}, { | ||
arg: "name=source:1M,", | ||
expectName: "name", | ||
expectSource: "source", | ||
expectCount: 1, | ||
expectSize: 1, | ||
}, { | ||
arg: "name=source:1M,whatever options that please me", | ||
expectName: "name", | ||
expectSource: "source", | ||
expectCount: 1, | ||
expectSize: 1, | ||
expectOptions: "whatever options that please me", | ||
}, { | ||
arg: "n=s:1G", | ||
expectName: "n", | ||
expectSource: "s", | ||
expectCount: 1, | ||
expectSize: 1024, | ||
}, { | ||
arg: "n=s:0.5T", | ||
expectName: "n", | ||
expectSource: "s", | ||
expectCount: 1, | ||
expectSize: 1024 * 512, | ||
}, { | ||
arg: "n=s:3x0.125P", | ||
expectName: "n", | ||
expectSource: "s", | ||
expectCount: 3, | ||
expectSize: 1024 * 1024 * 128, | ||
}, { | ||
arg: "n=s:0x100M", | ||
err: "count must be a positive integer", | ||
}, { | ||
arg: "n=s:-1x100M", | ||
err: "count must be a positive integer", | ||
}, { | ||
arg: "n=s:-100M", | ||
err: `failed to parse size: expected a non-negative number with optional multiplier suffix \(M/G/T/P\), got "-100M"`, | ||
}} | ||
|
||
for i, t := range parseStorageTests { | ||
c.Logf("test %d: %q", i, t.arg) | ||
p, err := storage.ParseDirective(t.arg) | ||
if t.err != "" { | ||
c.Check(err, gc.ErrorMatches, t.err) | ||
c.Check(p, gc.IsNil) | ||
} else { | ||
if !c.Check(err, gc.IsNil) { | ||
continue | ||
} | ||
c.Check(p, gc.DeepEquals, &storage.Directive{ | ||
Name: t.expectName, | ||
Source: t.expectSource, | ||
Count: t.expectCount, | ||
Size: t.expectSize, | ||
Options: t.expectOptions, | ||
}) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright 2014 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package storage_test | ||
|
||
import ( | ||
stdtesting "testing" | ||
|
||
gc "gopkg.in/check.v1" | ||
) | ||
|
||
func TestPackage(t *stdtesting.T) { | ||
gc.TestingT(t) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Copyright 2014 Canonical Ltd. | ||
// Licensed under the AGPLv3, see LICENCE file for details. | ||
|
||
package storage | ||
|
||
import "sort" | ||
|
||
// SortBlockDevices sorts block devices by device name. | ||
func SortBlockDevices(devices []BlockDevice) { | ||
sort.Sort(byDeviceName(devices)) | ||
} | ||
|
||
type byDeviceName []BlockDevice | ||
|
||
func (b byDeviceName) Len() int { | ||
return len(b) | ||
} | ||
|
||
func (b byDeviceName) Swap(i, j int) { | ||
b[i], b[j] = b[j], b[i] | ||
} | ||
|
||
func (b byDeviceName) Less(i, j int) bool { | ||
return b[i].DeviceName < b[j].DeviceName | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package storage | ||
|
||
// Specification is a fully specified set of requirements for storage, | ||
// derived from a Directive and a charm's storage metadata. | ||
type Specification struct { | ||
// Name is the name of the storage. | ||
Name string | ||
|
||
// Source is the storage source (provider, ceph, ...). | ||
Source string | ||
|
||
// Size is the size of the storage in MiB. | ||
Size uint64 | ||
|
||
// Options is source-specific options for storage creation. | ||
Options string | ||
|
||
// ReadOnly indicates that the storage should be made read-only if | ||
// possible. | ||
ReadOnly bool | ||
|
||
// Persistent indicates that the storage should be made persistent, | ||
// beyond the lifetime of the entity it is attached to, if possible. | ||
Persistent bool | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters