Skip to content

Commit

Permalink
Initial storage pools, backported from feature branch
Browse files Browse the repository at this point in the history
  • Loading branch information
wallyworld committed Feb 11, 2015
1 parent cefb405 commit 28b686f
Show file tree
Hide file tree
Showing 34 changed files with 799 additions and 114 deletions.
86 changes: 74 additions & 12 deletions apiserver/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import (
"github.com/juju/juju/state/presence"
statestorage "github.com/juju/juju/state/storage"
"github.com/juju/juju/storage"
"github.com/juju/juju/storage/pool"
"github.com/juju/juju/storage/provider"
"github.com/juju/juju/testcharms"
coretesting "github.com/juju/juju/testing"
"github.com/juju/juju/testing/factory"
Expand Down Expand Up @@ -1467,6 +1469,53 @@ func (s *clientSuite) testClientServiceDeployWithStorage(c *gc.C, expectConstrai
}
}

func (s *clientSuite) TestClientServiceDeployWithInvalidStoragePool(c *gc.C) {
s.PatchEnvironment(osenv.JujuFeatureFlagEnvKey, "storage")
featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
s.makeMockCharmStore()
curl, _ := addCharm(c, "storage-block")
storageConstraints := map[string]storage.Constraints{
"data": storage.Constraints{
Pool: "foo",
Count: 1,
Size: 1024,
},
}

var cons constraints.Value
err := s.APIState.Client().ServiceDeployWithNetworks(
curl.String(), "service", 1, "", cons, "", nil,
storageConstraints,
)
c.Assert(err, gc.ErrorMatches, `.*reading pool "foo": settings not found`)
}

func (s *clientSuite) TestClientServiceDeployWithUnsupportedStoragePool(c *gc.C) {
s.PatchEnvironment(osenv.JujuFeatureFlagEnvKey, "storage")
featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
pm := pool.NewPoolManager(state.NewStateSettings(s.State))
_, err := pm.Create("foo", provider.LoopProviderType, map[string]interface{}{})
c.Assert(err, jc.ErrorIsNil)
s.makeMockCharmStore()
curl, _ := addCharm(c, "storage-block")
storageConstraints := map[string]storage.Constraints{
"data": storage.Constraints{
Pool: "foo",
Count: 1,
Size: 1024,
},
}

var cons constraints.Value
err = s.APIState.Client().ServiceDeployWithNetworks(
curl.String(), "service", 1, "", cons, "", nil,
storageConstraints,
)
c.Assert(
err, gc.ErrorMatches,
`.*pool "foo" uses storage provider "loop" which is not supported for environments of type "dummy"`)
}

func (s *clientSuite) setupServiceDeploy(c *gc.C, args string) (*charm.URL, charm.Charm, constraints.Value) {
s.makeMockCharmStore()
curl, bundle := addCharm(c, "dummy")
Expand Down Expand Up @@ -2882,34 +2931,47 @@ func (s *clientSuite) TestClientAddMachinesWithPlacement(c *gc.C) {
func (s *clientSuite) TestClientAddMachinesWithDisks(c *gc.C) {
s.PatchEnvironment(osenv.JujuFeatureFlagEnvKey, "storage")
featureflag.SetFlagsFromEnvironment(osenv.JujuFeatureFlagEnvKey)
pm := pool.NewPoolManager(state.NewStateSettings(s.State))
_, err := pm.Create("loop", provider.LoopProviderType, map[string]interface{}{})

apiParams := make([]params.AddMachineParams, 4)
apiParams := make([]params.AddMachineParams, 5)
for i := range apiParams {
apiParams[i] = params.AddMachineParams{
Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
}
}
apiParams[0].Disks = []storage.Constraints{{Size: 1, Count: 2}, {Size: 2, Count: 1}}
apiParams[1].Disks = []storage.Constraints{{Size: 1, Count: 2, Pool: "three"}}
apiParams[2].Disks = []storage.Constraints{{Size: 0, Count: 0}}
apiParams[3].Disks = []storage.Constraints{{Size: 0, Count: 1}}
apiParams[2].Disks = []storage.Constraints{{Size: 1, Count: 2, Pool: "loop"}}
apiParams[3].Disks = []storage.Constraints{{Size: 0, Count: 0}}
apiParams[4].Disks = []storage.Constraints{{Size: 0, Count: 1}}
machines, err := s.APIState.Client().AddMachines(apiParams)
c.Assert(err, jc.ErrorIsNil)
c.Assert(machines, gc.HasLen, 4)
c.Assert(machines, gc.HasLen, 5)
c.Assert(machines[0].Machine, gc.Equals, "0")
c.Assert(machines[1].Error, gc.ErrorMatches, "cannot add a new machine: validating volume params: storage pools not implemented")
c.Assert(machines[2].Error, gc.ErrorMatches, "invalid volume params: count not specified")
c.Assert(machines[3].Error, gc.ErrorMatches, "cannot add a new machine: validating volume params: invalid size 0")
c.Assert(machines[1].Error, gc.ErrorMatches, "cannot add a new machine: validating volume params: invalid storage pool: .*")
c.Assert(machines[2].Machine, gc.Equals, "2")
c.Assert(machines[3].Error, gc.ErrorMatches, "invalid volume params: count not specified")
c.Assert(machines[4].Error, gc.ErrorMatches, "cannot add a new machine: validating volume params: invalid size 0")

m, err := s.BackingState.Machine(machines[0].Machine)
expectParams := []state.VolumeParams{
{Size: 1}, {Size: 1}, {Size: 2},
}
s.assertVolumeParams(c, machines[0].Machine, expectParams)

expectParams = []state.VolumeParams{
{Size: 1, Pool: "loop"}, {Size: 1, Pool: "loop"},
}
s.assertVolumeParams(c, machines[2].Machine, expectParams)
}

func (s *clientSuite) assertVolumeParams(c *gc.C, machineId string, expectParams []state.VolumeParams) {
m, err := s.BackingState.Machine(machineId)
c.Assert(err, jc.ErrorIsNil)
volumeAttachments, err := s.BackingState.MachineVolumeAttachments(m.MachineTag())
c.Assert(err, jc.ErrorIsNil)
c.Assert(volumeAttachments, gc.HasLen, 3)
c.Assert(volumeAttachments, gc.HasLen, len(expectParams))

expectParams := []state.VolumeParams{
{Size: 1}, {Size: 1}, {Size: 2},
}
foundParams := make([]state.VolumeParams, len(volumeAttachments))
for i, attachment := range volumeAttachments {
volume, err := s.BackingState.Volume(attachment.Volume())
Expand Down
12 changes: 10 additions & 2 deletions apiserver/provisioner/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,11 +539,19 @@ func (p *ProvisionerAPI) machineVolumeParams(m *state.Machine) ([]params.VolumeP
continue
}
// Not provisioned yet, so ask the cloud provisioner do it.
var providerType storage.ProviderType
var options map[string]interface{}
if stateVolumeParams.Pool != "" {
providerType, options, err = poolConfig(p.st, stateVolumeParams.Pool)
if err != nil {
return nil, errors.Errorf("cannot get options for pool %q", stateVolumeParams.Pool)
}
}
volumeParams = append(volumeParams, params.VolumeParams{
volumeTag.String(),
stateVolumeParams.Size,
"", // TODO(axw) storage provider
nil, // TODO(axw) pool attributes
string(providerType),
options,
m.Tag().String(),
})
}
Expand Down
26 changes: 24 additions & 2 deletions apiserver/provisioner/provisioner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import (
"github.com/juju/juju/state"
"github.com/juju/juju/state/multiwatcher"
statetesting "github.com/juju/juju/state/testing"
"github.com/juju/juju/storage"
"github.com/juju/juju/storage/pool"
"github.com/juju/juju/storage/provider"
coretesting "github.com/juju/juju/testing"
)

Expand Down Expand Up @@ -724,6 +727,14 @@ func (s *withoutStateServerSuite) TestDistributionGroupMachineAgentAuth(c *gc.C)
}

func (s *withoutStateServerSuite) TestProvisioningInfo(c *gc.C) {
pm := pool.NewPoolManager(state.NewStateSettings(s.State))
_, err := pm.Create("loop-pool", provider.LoopProviderType, map[string]interface{}{"foo": "bar"})
c.Assert(err, jc.ErrorIsNil)
storage.RegisterDefaultPool("dummy", storage.StorageKindBlock, "loop-pool")
defer func() {
storage.RegisterDefaultPool("dummy", storage.StorageKindBlock, "")
}()

cons := constraints.MustParse("cpu-cores=123 mem=8G networks=^net3,^net4")
template := state.MachineTemplate{
Series: "quantal",
Expand All @@ -733,7 +744,7 @@ func (s *withoutStateServerSuite) TestProvisioningInfo(c *gc.C) {
RequestedNetworks: []string{"net1", "net2"},
Volumes: []state.MachineVolumeParams{
{Volume: state.VolumeParams{Size: 1000}},
{Volume: state.VolumeParams{Size: 2000}},
{Volume: state.VolumeParams{Size: 2000, Pool: "loop-pool"}},
},
}
placementMachine, err := s.State.AddOneMachine(template)
Expand Down Expand Up @@ -770,6 +781,8 @@ func (s *withoutStateServerSuite) TestProvisioningInfo(c *gc.C) {
VolumeTag: "disk-1",
Size: 2000,
MachineTag: placementMachine.Tag().String(),
Provider: "loop",
Attributes: map[string]interface{}{"foo": "bar"},
}},
}},
{Error: apiservertesting.NotFoundError("machine 42")},
Expand Down Expand Up @@ -931,9 +944,17 @@ func (s *withoutStateServerSuite) TestSetProvisioned(c *gc.C) {
}

func (s *withoutStateServerSuite) TestSetInstanceInfo(c *gc.C) {
pm := pool.NewPoolManager(state.NewStateSettings(s.State))
_, err := pm.Create("loop-pool", provider.LoopProviderType, map[string]interface{}{"foo": "bar"})
c.Assert(err, jc.ErrorIsNil)
storage.RegisterDefaultPool("dummy", storage.StorageKindBlock, "loop-pool")
defer func() {
storage.RegisterDefaultPool("dummy", storage.StorageKindBlock, "")
}()

// Provision machine 0 first.
hwChars := instance.MustParseHardware("arch=i386", "mem=4G")
err := s.machines[0].SetInstanceInfo("i-am", "fake_nonce", &hwChars, nil, nil, nil, nil)
err = s.machines[0].SetInstanceInfo("i-am", "fake_nonce", &hwChars, nil, nil, nil, nil)
c.Assert(err, jc.ErrorIsNil)

volumesMachine, err := s.State.AddOneMachine(state.MachineTemplate{
Expand All @@ -943,6 +964,7 @@ func (s *withoutStateServerSuite) TestSetInstanceInfo(c *gc.C) {
Volume: state.VolumeParams{Size: 1000},
}},
})
c.Assert(err, jc.ErrorIsNil)

networks := []params.Network{{
Tag: "network-net1",
Expand Down
12 changes: 12 additions & 0 deletions cmd/jujud/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/juju/juju/state/multiwatcher"
"github.com/juju/juju/state/storage"
"github.com/juju/juju/state/toolstorage"
"github.com/juju/juju/storage/pool"
"github.com/juju/juju/utils/ssh"
"github.com/juju/juju/version"
"github.com/juju/juju/worker/peergrouper"
Expand Down Expand Up @@ -232,6 +233,11 @@ func (c *BootstrapCommand) Run(_ *cmd.Context) error {
}
}

// Populate the storage pools.
if err := c.populateDefaultStoragePools(st); err != nil {
return err
}

// bootstrap machine always gets the vote
return m.SetHasVote(true)
}
Expand Down Expand Up @@ -284,6 +290,12 @@ func (c *BootstrapCommand) startMongo(addrs []network.Address, agentConfig agent
})
}

// populateDefaultStoragePools creates the default storage pools.
func (c *BootstrapCommand) populateDefaultStoragePools(st *state.State) error {
settings := state.NewStateSettings(st)
return pool.AddDefaultStoragePools(settings, c.CurrentConfig())
}

// populateTools stores uploaded tools in provider storage
// and updates the tools metadata.
func (c *BootstrapCommand) populateTools(st *state.State, env environs.Environ) error {
Expand Down
25 changes: 25 additions & 0 deletions cmd/jujud/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import (
"github.com/juju/juju/state/multiwatcher"
statestorage "github.com/juju/juju/state/storage"
statetesting "github.com/juju/juju/state/testing"
"github.com/juju/juju/storage/pool"
"github.com/juju/juju/testing"
"github.com/juju/juju/tools"
"github.com/juju/juju/version"
Expand Down Expand Up @@ -692,3 +693,27 @@ func (m b64yaml) encode() string {
}
return base64.StdEncoding.EncodeToString(data)
}

func (s *BootstrapSuite) TestDefaultStoragePools(c *gc.C) {
_, cmd, err := s.initBootstrapCommand(c, nil, "--env-config", s.b64yamlEnvcfg, "--instance-id", string(s.instanceId))
c.Assert(err, jc.ErrorIsNil)
err = cmd.Run(nil)
c.Assert(err, jc.ErrorIsNil)

st, err := state.Open(&mongo.MongoInfo{
Info: mongo.Info{
Addrs: []string{gitjujutesting.MgoServer.Addr()},
CACert: testing.CACert,
},
Password: testPasswordHash(),
}, mongo.DefaultDialOpts(), environs.NewStatePolicy())
c.Assert(err, jc.ErrorIsNil)
defer st.Close()

settings := state.NewStateSettings(st)
pm := pool.NewPoolManager(settings)
for _, p := range []string{"ebs", "ebs-ssd"} {
_, err = pm.Get(p)
c.Assert(err, jc.ErrorIsNil)
}
}
14 changes: 14 additions & 0 deletions provider/dummy/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package dummy

import (
"github.com/juju/juju/storage"
"github.com/juju/juju/storage/provider"
)

func init() {
// TODO(wallyworld) - common provider registration
storage.RegisterEnvironStorageProviders("dummy", provider.LoopProviderType)
}
19 changes: 16 additions & 3 deletions provider/ec2/disks.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
package ec2

import (
"fmt"
"strconv"

"github.com/juju/errors"
"gopkg.in/amz.v2/ec2"

"github.com/juju/juju/environs"
providerstorage "github.com/juju/juju/provider/ec2/storage"
"github.com/juju/juju/storage"
)

const (
ebsStorageSource = "ebs"

// minRootDiskSizeMiB is the minimum/default size (in mebibytes) for ec2 root disks.
minRootDiskSizeMiB uint64 = 8 * 1024

Expand Down Expand Up @@ -89,7 +91,18 @@ func getBlockDeviceMappings(
mapping := ec2.BlockDeviceMapping{
VolumeSize: int64(mibToGib(params.Size)),
DeviceName: requestDeviceName,
// TODO(axw) VolumeType, IOPS and DeleteOnTermination
// TODO(axw) DeleteOnTermination
}
// Translate user values for storage provider parameters.
options := providerstorage.TranslateUserEBSOptions(params.Attributes)
if v, ok := options[providerstorage.VolumeType]; ok && v != "" {
mapping.VolumeType = fmt.Sprintf("%v", v)
}
if v, ok := options[providerstorage.IOPS]; ok && v != "" {
mapping.IOPS, err = strconv.ParseInt(fmt.Sprintf("%v", v), 10, 64)
if err != nil {
return nil, nil, nil, errors.Annotatef(err, "invalid iops value %v, expected integer", v)
}
}
volume := storage.Volume{
Tag: params.Tag,
Expand Down
7 changes: 6 additions & 1 deletion provider/ec2/disks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ func (*DisksSuite) TestGetBlockDeviceMappings(c *gc.C) {
mapping, volumes, volumeAttachments, err := ec2.GetBlockDeviceMappings(
"pv", &environs.StartInstanceParams{Volumes: []storage.VolumeParams{
{Tag: volume0, Size: 1234},
{Tag: volume1, Size: 4321},
{Tag: volume1,
Size: 4321,
Attributes: map[string]interface{}{"volume-type": "standard", "iops": 1234},
},
}},
)
c.Assert(err, jc.ErrorIsNil)
Expand All @@ -112,6 +115,8 @@ func (*DisksSuite) TestGetBlockDeviceMappings(c *gc.C) {
}, {
VolumeSize: 5,
DeviceName: "/dev/sdf2",
VolumeType: "standard",
IOPS: 1234,
}})
c.Assert(volumes, gc.DeepEquals, []storage.Volume{
{Tag: volume0, Size: 2048},
Expand Down
Loading

0 comments on commit 28b686f

Please sign in to comment.