Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright 2015 Canonical Ltd. | |
| // Licensed under the AGPLv3, see LICENCE file for details. | |
| package state_test | |
| import ( | |
| "github.com/juju/errors" | |
| jc "github.com/juju/testing/checkers" | |
| gc "gopkg.in/check.v1" | |
| "gopkg.in/juju/names.v2" | |
| "github.com/juju/juju/constraints" | |
| "github.com/juju/juju/instance" | |
| "github.com/juju/juju/provider/dummy" | |
| "github.com/juju/juju/state" | |
| "github.com/juju/juju/state/testing" | |
| "github.com/juju/juju/storage/poolmanager" | |
| "github.com/juju/juju/storage/provider" | |
| ) | |
| type VolumeStateSuite struct { | |
| StorageStateSuiteBase | |
| } | |
| var _ = gc.Suite(&VolumeStateSuite{}) | |
| func (s *VolumeStateSuite) TestAddMachine(c *gc.C) { | |
| _, unit, _ := s.setupSingleStorage(c, "block", "loop-pool") | |
| err := s.State.AssignUnit(unit, state.AssignCleanEmpty) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.assertMachineVolume(c, unit) | |
| } | |
| func (s *VolumeStateSuite) TestAssignToMachine(c *gc.C) { | |
| _, unit, _ := s.setupSingleStorage(c, "block", "loop-pool") | |
| machine, err := s.State.AddMachine("quantal", state.JobHostUnits) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = unit.AssignToMachine(machine) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.assertMachineVolume(c, unit) | |
| } | |
| func (s *VolumeStateSuite) assertMachineVolume(c *gc.C, unit *state.Unit) { | |
| assignedMachineId, err := unit.AssignedMachineId() | |
| c.Assert(err, jc.ErrorIsNil) | |
| storageAttachments, err := s.State.UnitStorageAttachments(unit.UnitTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(storageAttachments, gc.HasLen, 1) | |
| storageInstance, err := s.State.StorageInstance(storageAttachments[0].StorageInstance()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volume := s.storageInstanceVolume(c, storageInstance.StorageTag()) | |
| c.Assert(volume.VolumeTag(), gc.Equals, names.NewVolumeTag("0/0")) | |
| volumeStorageTag, err := volume.StorageInstance() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(volumeStorageTag, gc.Equals, storageInstance.StorageTag()) | |
| _, err = volume.Info() | |
| c.Assert(err, jc.Satisfies, errors.IsNotProvisioned) | |
| _, ok := volume.Params() | |
| c.Assert(ok, jc.IsTrue) | |
| machine, err := s.State.Machine(assignedMachineId) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volumeAttachments, err := s.State.MachineVolumeAttachments(machine.MachineTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(volumeAttachments, gc.HasLen, 1) | |
| c.Assert(volumeAttachments[0].Volume(), gc.Equals, volume.VolumeTag()) | |
| c.Assert(volumeAttachments[0].Machine(), gc.Equals, machine.MachineTag()) | |
| _, err = volumeAttachments[0].Info() | |
| c.Assert(err, jc.Satisfies, errors.IsNotProvisioned) | |
| _, ok = volumeAttachments[0].Params() | |
| c.Assert(ok, jc.IsTrue) | |
| _, err = s.State.VolumeAttachment(machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| assertMachineStorageRefs(c, s.State, machine.MachineTag()) | |
| } | |
| func (s *VolumeStateSuite) TestAddServiceInvalidPool(c *gc.C) { | |
| ch := s.AddTestingCharm(c, "storage-block") | |
| storage := map[string]state.StorageConstraints{ | |
| "data": makeStorageCons("invalid-pool", 1024, 1), | |
| } | |
| _, err := s.State.AddApplication(state.AddApplicationArgs{Name: "storage-block", Charm: ch, Storage: storage}) | |
| c.Assert(err, gc.ErrorMatches, `.* pool "invalid-pool" not found`) | |
| } | |
| func (s *VolumeStateSuite) TestAddServiceNoUserDefaultPool(c *gc.C) { | |
| ch := s.AddTestingCharm(c, "storage-block") | |
| storage := map[string]state.StorageConstraints{ | |
| "data": makeStorageCons("", 1024, 1), | |
| } | |
| app, err := s.State.AddApplication(state.AddApplicationArgs{Name: "storage-block", Charm: ch, Storage: storage}) | |
| c.Assert(err, jc.ErrorIsNil) | |
| cons, err := app.StorageConstraints() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(cons, jc.DeepEquals, map[string]state.StorageConstraints{ | |
| "data": state.StorageConstraints{ | |
| Pool: "loop", | |
| Size: 1024, | |
| Count: 1, | |
| }, | |
| "allecto": state.StorageConstraints{ | |
| Pool: "loop", | |
| Size: 1024, | |
| Count: 0, | |
| }, | |
| }) | |
| } | |
| func (s *VolumeStateSuite) TestAddServiceDefaultPool(c *gc.C) { | |
| // Register a default pool. | |
| pm := poolmanager.New(state.NewStateSettings(s.State), dummy.StorageProviders()) | |
| _, err := pm.Create("default-block", provider.LoopProviderType, map[string]interface{}{}) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.UpdateModelConfig(map[string]interface{}{ | |
| "storage-default-block-source": "default-block", | |
| }, nil, nil) | |
| c.Assert(err, jc.ErrorIsNil) | |
| ch := s.AddTestingCharm(c, "storage-block") | |
| storage := map[string]state.StorageConstraints{ | |
| "data": makeStorageCons("", 1024, 1), | |
| } | |
| app := s.AddTestingServiceWithStorage(c, "storage-block", ch, storage) | |
| cons, err := app.StorageConstraints() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(cons, jc.DeepEquals, map[string]state.StorageConstraints{ | |
| "data": state.StorageConstraints{ | |
| Pool: "default-block", | |
| Size: 1024, | |
| Count: 1, | |
| }, | |
| "allecto": state.StorageConstraints{ | |
| Pool: "loop", | |
| Size: 1024, | |
| Count: 0, | |
| }, | |
| }) | |
| } | |
| func (s *VolumeStateSuite) TestSetVolumeInfo(c *gc.C) { | |
| _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") | |
| err := s.State.AssignUnit(u, state.AssignCleanEmpty) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volume := s.storageInstanceVolume(c, storageTag) | |
| volumeTag := volume.VolumeTag() | |
| s.assertVolumeUnprovisioned(c, volumeTag) | |
| volumeInfoSet := state.VolumeInfo{Size: 123, Persistent: true, VolumeId: "vol-ume"} | |
| err = s.State.SetVolumeInfo(volume.VolumeTag(), volumeInfoSet) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volumeInfoSet.Pool = "loop-pool" // taken from params | |
| s.assertVolumeInfo(c, volumeTag, volumeInfoSet) | |
| } | |
| func (s *VolumeStateSuite) TestSetVolumeInfoNoVolumeId(c *gc.C) { | |
| _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") | |
| err := s.State.AssignUnit(u, state.AssignCleanEmpty) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volume := s.storageInstanceVolume(c, storageTag) | |
| volumeTag := volume.VolumeTag() | |
| s.assertVolumeUnprovisioned(c, volumeTag) | |
| volumeInfoSet := state.VolumeInfo{Size: 123, Persistent: true} | |
| err = s.State.SetVolumeInfo(volume.VolumeTag(), volumeInfoSet) | |
| c.Assert(err, gc.ErrorMatches, `cannot set info for volume "0/0": volume ID not set`) | |
| } | |
| func (s *VolumeStateSuite) TestSetVolumeInfoNoStorageAssigned(c *gc.C) { | |
| oneJob := []state.MachineJob{state.JobHostUnits} | |
| cons := constraints.MustParse("mem=4G") | |
| hc := instance.MustParseHardware("mem=2G") | |
| volumeParams := state.VolumeParams{ | |
| Pool: "loop-pool", | |
| Size: 123, | |
| } | |
| machineTemplate := state.MachineTemplate{ | |
| Series: "precise", | |
| Constraints: cons, | |
| HardwareCharacteristics: hc, | |
| InstanceId: "inst-id", | |
| Nonce: "nonce", | |
| Jobs: oneJob, | |
| Volumes: []state.MachineVolumeParams{{ | |
| Volume: volumeParams, | |
| }}, | |
| } | |
| machines, err := s.State.AddMachines(machineTemplate) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(machines, gc.HasLen, 1) | |
| m, err := s.State.Machine(machines[0].Id()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volumeAttachments, err := s.State.MachineVolumeAttachments(m.MachineTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(volumeAttachments, gc.HasLen, 1) | |
| volumeTag := volumeAttachments[0].Volume() | |
| volume := s.volume(c, volumeTag) | |
| _, err = volume.StorageInstance() | |
| c.Assert(err, jc.Satisfies, errors.IsNotAssigned) | |
| s.assertVolumeUnprovisioned(c, volumeTag) | |
| volumeInfoSet := state.VolumeInfo{Size: 123, VolumeId: "vol-ume"} | |
| err = s.State.SetVolumeInfo(volume.VolumeTag(), volumeInfoSet) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volumeInfoSet.Pool = "loop-pool" // taken from params | |
| s.assertVolumeInfo(c, volumeTag, volumeInfoSet) | |
| } | |
| func (s *VolumeStateSuite) TestSetVolumeInfoImmutable(c *gc.C) { | |
| _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") | |
| err := s.State.AssignUnit(u, state.AssignCleanEmpty) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volume := s.storageInstanceVolume(c, storageTag) | |
| volumeTag := volume.VolumeTag() | |
| volumeInfoSet := state.VolumeInfo{Size: 123, VolumeId: "vol-ume"} | |
| err = s.State.SetVolumeInfo(volume.VolumeTag(), volumeInfoSet) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // The first call to SetVolumeInfo takes the pool name from | |
| // the params; the second does not, but it must not change | |
| // either. Callers are expected to get the existing info and | |
| // update it, leaving immutable values intact. | |
| err = s.State.SetVolumeInfo(volume.VolumeTag(), volumeInfoSet) | |
| c.Assert(err, gc.ErrorMatches, `cannot set info for volume "0/0": cannot change pool from "loop-pool" to ""`) | |
| volumeInfoSet.Pool = "loop-pool" | |
| s.assertVolumeInfo(c, volumeTag, volumeInfoSet) | |
| } | |
| func (s *VolumeStateSuite) TestWatchVolumeAttachment(c *gc.C) { | |
| _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") | |
| err := s.State.AssignUnit(u, state.AssignCleanEmpty) | |
| c.Assert(err, jc.ErrorIsNil) | |
| assignedMachineId, err := u.AssignedMachineId() | |
| c.Assert(err, jc.ErrorIsNil) | |
| machineTag := names.NewMachineTag(assignedMachineId) | |
| volume := s.storageInstanceVolume(c, storageTag) | |
| volumeTag := volume.VolumeTag() | |
| w := s.State.WatchVolumeAttachment(machineTag, volumeTag) | |
| defer testing.AssertStop(c, w) | |
| wc := testing.NewNotifyWatcherC(c, s.State, w) | |
| wc.AssertOneChange() | |
| machine, err := s.State.Machine(assignedMachineId) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = machine.SetProvisioned("inst-id", "fake_nonce", nil) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // volume attachment will NOT react to volume changes | |
| err = s.State.SetVolumeInfo(volumeTag, state.VolumeInfo{VolumeId: "vol-123"}) | |
| c.Assert(err, jc.ErrorIsNil) | |
| wc.AssertNoChange() | |
| err = s.State.SetVolumeAttachmentInfo( | |
| machineTag, volumeTag, state.VolumeAttachmentInfo{ | |
| DeviceName: "xvdf1", | |
| }, | |
| ) | |
| c.Assert(err, jc.ErrorIsNil) | |
| wc.AssertOneChange() | |
| } | |
| func (s *VolumeStateSuite) TestWatchModelVolumes(c *gc.C) { | |
| app := s.setupMixedScopeStorageApplication(c, "block") | |
| addUnit := func() { | |
| u, err := app.AddUnit() | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.AssignUnit(u, state.AssignCleanEmpty) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| addUnit() | |
| w := s.State.WatchModelVolumes() | |
| defer testing.AssertStop(c, w) | |
| wc := testing.NewStringsWatcherC(c, s.State, w) | |
| wc.AssertChangeInSingleEvent("0", "1") // initial | |
| wc.AssertNoChange() | |
| addUnit() | |
| wc.AssertChangeInSingleEvent("4", "5") | |
| wc.AssertNoChange() | |
| volume, err := s.State.Volume(names.NewVolumeTag("0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| storageTag, err := volume.StorageInstance() | |
| c.Assert(err, jc.ErrorIsNil) | |
| removeStorageInstance(c, s.State, storageTag) | |
| err = s.State.DestroyVolume(names.NewVolumeTag("0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| wc.AssertChangeInSingleEvent("0") // dying | |
| wc.AssertNoChange() | |
| err = s.State.DetachVolume(names.NewMachineTag("0"), names.NewVolumeTag("0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.RemoveVolumeAttachment(names.NewMachineTag("0"), names.NewVolumeTag("0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.RemoveVolume(names.NewVolumeTag("0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| wc.AssertChangeInSingleEvent("0") // removed | |
| wc.AssertNoChange() | |
| } | |
| func (s *VolumeStateSuite) TestWatchEnvironVolumeAttachments(c *gc.C) { | |
| app := s.setupMixedScopeStorageApplication(c, "block") | |
| addUnit := func() { | |
| u, err := app.AddUnit() | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.AssignUnit(u, state.AssignCleanEmpty) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| addUnit() | |
| w := s.State.WatchEnvironVolumeAttachments() | |
| defer testing.AssertStop(c, w) | |
| wc := testing.NewStringsWatcherC(c, s.State, w) | |
| wc.AssertChangeInSingleEvent("0:0", "0:1") // initial | |
| wc.AssertNoChange() | |
| addUnit() | |
| wc.AssertChangeInSingleEvent("1:4", "1:5") | |
| wc.AssertNoChange() | |
| err := s.State.DetachVolume(names.NewMachineTag("0"), names.NewVolumeTag("0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| wc.AssertChangeInSingleEvent("0:0") // dying | |
| wc.AssertNoChange() | |
| err = s.State.RemoveVolumeAttachment(names.NewMachineTag("0"), names.NewVolumeTag("0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| wc.AssertChangeInSingleEvent("0:0") // removed | |
| wc.AssertNoChange() | |
| } | |
| func (s *VolumeStateSuite) TestWatchMachineVolumes(c *gc.C) { | |
| app := s.setupMixedScopeStorageApplication(c, "block", "machinescoped", "modelscoped") | |
| addUnit := func() { | |
| u, err := app.AddUnit() | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.AssignUnit(u, state.AssignCleanEmpty) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| addUnit() | |
| w := s.State.WatchMachineVolumes(names.NewMachineTag("0")) | |
| defer testing.AssertStop(c, w) | |
| wc := testing.NewStringsWatcherC(c, s.State, w) | |
| wc.AssertChangeInSingleEvent("0/0", "0/1") // initial | |
| wc.AssertNoChange() | |
| addUnit() | |
| // no change, since we're only interested in the one machine. | |
| wc.AssertNoChange() | |
| volume, err := s.State.Volume(names.NewVolumeTag("0/0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| storageTag, err := volume.StorageInstance() | |
| c.Assert(err, jc.ErrorIsNil) | |
| removeStorageInstance(c, s.State, storageTag) | |
| err = s.State.DestroyVolume(volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| wc.AssertChangeInSingleEvent("0/0") // dying | |
| wc.AssertNoChange() | |
| err = s.State.DestroyVolume(names.NewVolumeTag("0/0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.RemoveVolumeAttachment(names.NewMachineTag("0"), names.NewVolumeTag("0/0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.RemoveVolume(names.NewVolumeTag("0/0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| wc.AssertChangeInSingleEvent("0/0") // removed | |
| wc.AssertNoChange() | |
| } | |
| func (s *VolumeStateSuite) TestWatchMachineVolumeAttachments(c *gc.C) { | |
| app := s.setupMixedScopeStorageApplication(c, "block", "machinescoped", "modelscoped") | |
| addUnit := func(to *state.Machine) (u *state.Unit, m *state.Machine) { | |
| var err error | |
| u, err = app.AddUnit() | |
| c.Assert(err, jc.ErrorIsNil) | |
| if to != nil { | |
| err = u.AssignToMachine(to) | |
| c.Assert(err, jc.ErrorIsNil) | |
| return u, to | |
| } | |
| err = s.State.AssignUnit(u, state.AssignCleanEmpty) | |
| c.Assert(err, jc.ErrorIsNil) | |
| mid, err := u.AssignedMachineId() | |
| c.Assert(err, jc.ErrorIsNil) | |
| m, err = s.State.Machine(mid) | |
| c.Assert(err, jc.ErrorIsNil) | |
| return u, m | |
| } | |
| _, m0 := addUnit(nil) | |
| w := s.State.WatchMachineVolumeAttachments(names.NewMachineTag("0")) | |
| defer testing.AssertStop(c, w) | |
| wc := testing.NewStringsWatcherC(c, s.State, w) | |
| wc.AssertChangeInSingleEvent("0:0/0", "0:0/1") // initial | |
| wc.AssertNoChange() | |
| addUnit(nil) | |
| // no change, since we're only interested in the one machine. | |
| wc.AssertNoChange() | |
| err := s.State.DetachVolume(names.NewMachineTag("0"), names.NewVolumeTag("2")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // no change, since we're only interested in attachments of | |
| // machine-scoped volumes. | |
| wc.AssertNoChange() | |
| removeVolumeStorageInstance(c, s.State, names.NewVolumeTag("0/0")) | |
| err = s.State.DestroyVolume(names.NewVolumeTag("0/0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| wc.AssertChangeInSingleEvent("0:0/0") // dying | |
| wc.AssertNoChange() | |
| err = s.State.RemoveVolumeAttachment(names.NewMachineTag("0"), names.NewVolumeTag("0/0")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| wc.AssertChangeInSingleEvent("0:0/0") // removed | |
| wc.AssertNoChange() | |
| addUnit(m0) | |
| wc.AssertChangeInSingleEvent("0:0/8", "0:0/9") // added | |
| } | |
| func (s *VolumeStateSuite) TestParseVolumeAttachmentId(c *gc.C) { | |
| assertValid := func(id string, m names.MachineTag, v names.VolumeTag) { | |
| machineTag, volumeTag, err := state.ParseVolumeAttachmentId(id) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(machineTag, gc.Equals, m) | |
| c.Assert(volumeTag, gc.Equals, v) | |
| } | |
| assertValid("0:0", names.NewMachineTag("0"), names.NewVolumeTag("0")) | |
| assertValid("0:0/1", names.NewMachineTag("0"), names.NewVolumeTag("0/1")) | |
| assertValid("0/lxd/0:1", names.NewMachineTag("0/lxd/0"), names.NewVolumeTag("1")) | |
| } | |
| func (s *VolumeStateSuite) TestParseVolumeAttachmentIdError(c *gc.C) { | |
| assertError := func(id, expect string) { | |
| _, _, err := state.ParseVolumeAttachmentId(id) | |
| c.Assert(err, gc.ErrorMatches, expect) | |
| } | |
| assertError("", `invalid volume attachment ID ""`) | |
| assertError("0", `invalid volume attachment ID "0"`) | |
| assertError("0:foo", `invalid volume attachment ID "0:foo"`) | |
| assertError("bar:0", `invalid volume attachment ID "bar:0"`) | |
| } | |
| func (s *VolumeStateSuite) TestAllVolumes(c *gc.C) { | |
| _, expected, _ := s.assertCreateVolumes(c) | |
| volumes, err := s.State.AllVolumes() | |
| c.Assert(err, jc.ErrorIsNil) | |
| tags := make([]names.VolumeTag, len(volumes)) | |
| for i, v := range volumes { | |
| tags[i] = v.VolumeTag() | |
| } | |
| c.Assert(tags, jc.SameContents, expected) | |
| } | |
| func (s *VolumeStateSuite) assertCreateVolumes(c *gc.C) (_ *state.Machine, all, persistent []names.VolumeTag) { | |
| machine, err := s.State.AddOneMachine(state.MachineTemplate{ | |
| Series: "quantal", | |
| Jobs: []state.MachineJob{state.JobHostUnits}, | |
| Volumes: []state.MachineVolumeParams{{ | |
| Volume: state.VolumeParams{Pool: "persistent-block", Size: 1024}, | |
| }, { | |
| Volume: state.VolumeParams{Pool: "loop-pool", Size: 2048}, | |
| }, { | |
| Volume: state.VolumeParams{Pool: "static", Size: 2048}, | |
| }}, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| assertMachineStorageRefs(c, s.State, machine.MachineTag()) | |
| volume1 := s.volume(c, names.NewVolumeTag("0")) | |
| volume2 := s.volume(c, names.NewVolumeTag("0/1")) | |
| volume3 := s.volume(c, names.NewVolumeTag("2")) | |
| volumeInfoSet := state.VolumeInfo{Size: 123, Persistent: true, VolumeId: "vol-1"} | |
| err = s.State.SetVolumeInfo(volume1.VolumeTag(), volumeInfoSet) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volumeInfoSet = state.VolumeInfo{Size: 456, Persistent: false, VolumeId: "vol-2"} | |
| err = s.State.SetVolumeInfo(volume2.VolumeTag(), volumeInfoSet) | |
| c.Assert(err, jc.ErrorIsNil) | |
| all = []names.VolumeTag{ | |
| volume1.VolumeTag(), | |
| volume2.VolumeTag(), | |
| volume3.VolumeTag(), | |
| } | |
| persistent = []names.VolumeTag{ | |
| volume1.VolumeTag(), | |
| } | |
| return machine, all, persistent | |
| } | |
| func (s *VolumeStateSuite) TestRemoveStorageInstanceDestroysAndUnassignsVolume(c *gc.C) { | |
| _, u, storageTag := s.setupSingleStorage(c, "block", "modelscoped") | |
| err := s.State.AssignUnit(u, state.AssignCleanEmpty) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volume := s.storageInstanceVolume(c, storageTag) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = u.Destroy() | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.DestroyStorageInstance(storageTag) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.DetachStorage(storageTag, u.UnitTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // The storage instance and attachment are dying, but not yet | |
| // removed from state. The volume should still be assigned. | |
| s.storageInstanceVolume(c, storageTag) | |
| err = s.State.RemoveStorageAttachment(storageTag, u.UnitTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // The storage instance is now gone; the volume should no longer | |
| // be assigned to the storage. | |
| _, err = s.State.StorageInstanceVolume(storageTag) | |
| c.Assert(err, gc.ErrorMatches, `volume for storage instance "data/0" not found`) | |
| // The volume should still exist, but it should be dying. | |
| v := s.volume(c, volume.VolumeTag()) | |
| c.Assert(v.Life(), gc.Equals, state.Dying) | |
| } | |
| func (s *VolumeStateSuite) TestSetVolumeAttachmentInfoVolumeNotProvisioned(c *gc.C) { | |
| _, u, storageTag := s.setupSingleStorage(c, "block", "loop-pool") | |
| err := s.State.AssignUnit(u, state.AssignCleanEmpty) | |
| c.Assert(err, jc.ErrorIsNil) | |
| assignedMachineId, err := u.AssignedMachineId() | |
| c.Assert(err, jc.ErrorIsNil) | |
| machineTag := names.NewMachineTag(assignedMachineId) | |
| volume := s.storageInstanceVolume(c, storageTag) | |
| volumeTag := volume.VolumeTag() | |
| err = s.State.SetVolumeAttachmentInfo( | |
| machineTag, volumeTag, state.VolumeAttachmentInfo{ | |
| DeviceName: "xvdf1", | |
| }, | |
| ) | |
| c.Assert(err, gc.ErrorMatches, `cannot set info for volume attachment 0/0:0: volume "0/0" not provisioned`) | |
| } | |
| func (s *VolumeStateSuite) TestDestroyVolume(c *gc.C) { | |
| volume, _ := s.setupMachineScopedVolumeAttachment(c) | |
| assertDestroy := func() { | |
| err := s.State.DestroyVolume(volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volume = s.volume(c, volume.VolumeTag()) | |
| c.Assert(volume.Life(), gc.Equals, state.Dying) | |
| } | |
| defer state.SetBeforeHooks(c, s.State, assertDestroy).Check() | |
| assertDestroy() | |
| } | |
| func (s *VolumeStateSuite) TestDestroyVolumeNotFound(c *gc.C) { | |
| err := s.State.DestroyVolume(names.NewVolumeTag("0")) | |
| c.Assert(err, gc.ErrorMatches, `destroying volume 0: volume "0" not found`) | |
| c.Assert(err, jc.Satisfies, errors.IsNotFound) | |
| } | |
| func (s *VolumeStateSuite) TestDestroyVolumeStorageAssigned(c *gc.C) { | |
| volume, _, u := s.setupStorageVolumeAttachment(c) | |
| storageTag, err := volume.StorageInstance() | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.DestroyVolume(volume.VolumeTag()) | |
| c.Assert(err, gc.ErrorMatches, "destroying volume 0: volume is assigned to storage data/0") | |
| err = u.Destroy() | |
| c.Assert(err, jc.ErrorIsNil) | |
| removeStorageInstance(c, s.State, storageTag) | |
| err = s.State.DestroyVolume(volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (s *VolumeStateSuite) TestDestroyVolumeNoAttachments(c *gc.C) { | |
| volume, machine := s.setupModelScopedVolumeAttachment(c) | |
| err := s.State.DetachVolume(machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| defer state.SetBeforeHooks(c, s.State, func() { | |
| err := s.State.RemoveVolumeAttachment(machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| }).Check() | |
| err = s.State.DestroyVolume(volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volume = s.volume(c, volume.VolumeTag()) | |
| // There are no more attachments, so the volume should | |
| // have been progressed directly to Dead. | |
| c.Assert(volume.Life(), gc.Equals, state.Dead) | |
| } | |
| func (s *VolumeStateSuite) TestRemoveVolume(c *gc.C) { | |
| volume, machine := s.setupMachineScopedVolumeAttachment(c) | |
| err := s.State.DestroyVolume(volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.RemoveVolumeAttachment(machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| assertRemove := func() { | |
| err = s.State.RemoveVolume(volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| _, err = s.State.Volume(volume.VolumeTag()) | |
| c.Assert(err, jc.Satisfies, errors.IsNotFound) | |
| } | |
| defer state.SetBeforeHooks(c, s.State, assertRemove).Check() | |
| assertRemove() | |
| } | |
| func (s *VolumeStateSuite) TestRemoveVolumeNotFound(c *gc.C) { | |
| err := s.State.RemoveVolume(names.NewVolumeTag("42")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (s *VolumeStateSuite) TestRemoveVolumeNotDead(c *gc.C) { | |
| volume, _ := s.setupMachineScopedVolumeAttachment(c) | |
| err := s.State.RemoveVolume(volume.VolumeTag()) | |
| c.Assert(err, gc.ErrorMatches, "removing volume 0/0: volume is not dead") | |
| err = s.State.DestroyVolume(volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.RemoveVolume(volume.VolumeTag()) | |
| c.Assert(err, gc.ErrorMatches, "removing volume 0/0: volume is not dead") | |
| } | |
| func (s *VolumeStateSuite) TestDetachVolume(c *gc.C) { | |
| volume, machine := s.setupModelScopedVolumeAttachment(c) | |
| assertDetach := func() { | |
| err := s.State.DetachVolume(machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| attachment := s.volumeAttachment(c, machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(attachment.Life(), gc.Equals, state.Dying) | |
| } | |
| defer state.SetBeforeHooks(c, s.State, assertDetach).Check() | |
| assertDetach() | |
| } | |
| func (s *VolumeStateSuite) TestRemoveLastVolumeAttachment(c *gc.C) { | |
| volume, machine := s.setupModelScopedVolumeAttachment(c) | |
| err := s.State.DetachVolume(machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.DestroyVolume(volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volume = s.volume(c, volume.VolumeTag()) | |
| c.Assert(volume.Life(), gc.Equals, state.Dying) | |
| err = s.State.RemoveVolumeAttachment(machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // The volume was Dying when the last attachment was | |
| // removed, so the volume should now be Dead. | |
| volume = s.volume(c, volume.VolumeTag()) | |
| c.Assert(volume.Life(), gc.Equals, state.Dead) | |
| } | |
| func (s *VolumeStateSuite) TestRemoveLastVolumeAttachmentConcurrently(c *gc.C) { | |
| volume, machine := s.setupModelScopedVolumeAttachment(c) | |
| err := s.State.DetachVolume(machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| defer state.SetBeforeHooks(c, s.State, func() { | |
| err := s.State.DestroyVolume(volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volume := s.volume(c, volume.VolumeTag()) | |
| c.Assert(volume.Life(), gc.Equals, state.Dying) | |
| }).Check() | |
| err = s.State.RemoveVolumeAttachment(machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // Last attachment was removed, and the volume was (concurrently) | |
| // destroyed, so the volume should be Dead. | |
| volume = s.volume(c, volume.VolumeTag()) | |
| c.Assert(volume.Life(), gc.Equals, state.Dead) | |
| } | |
| func (s *VolumeStateSuite) TestRemoveVolumeAttachmentNotFound(c *gc.C) { | |
| err := s.State.RemoveVolumeAttachment(names.NewMachineTag("42"), names.NewVolumeTag("42")) | |
| c.Assert(err, jc.Satisfies, errors.IsNotFound) | |
| c.Assert(err, gc.ErrorMatches, `removing attachment of volume 42 from machine 42: volume "42" on machine "42" not found`) | |
| } | |
| func (s *VolumeStateSuite) TestRemoveVolumeAttachmentConcurrently(c *gc.C) { | |
| volume, machine := s.setupMachineScopedVolumeAttachment(c) | |
| err := s.State.DestroyVolume(volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| remove := func() { | |
| err := s.State.RemoveVolumeAttachment(machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| assertMachineStorageRefs(c, s.State, machine.MachineTag()) | |
| } | |
| defer state.SetBeforeHooks(c, s.State, remove).Check() | |
| remove() | |
| } | |
| func (s *VolumeStateSuite) TestRemoveVolumeAttachmentAlive(c *gc.C) { | |
| volume, machine := s.setupMachineScopedVolumeAttachment(c) | |
| err := s.State.RemoveVolumeAttachment(machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(err, gc.ErrorMatches, "removing attachment of volume 0/0 from machine 0: volume attachment is not dying") | |
| } | |
| func (s *VolumeStateSuite) TestRemoveMachineRemovesVolumes(c *gc.C) { | |
| machine, err := s.State.AddOneMachine(state.MachineTemplate{ | |
| Series: "quantal", | |
| Jobs: []state.MachineJob{state.JobHostUnits}, | |
| Volumes: []state.MachineVolumeParams{{ | |
| Volume: state.VolumeParams{Pool: "persistent-block", Size: 1024}, // unprovisioned | |
| }, { | |
| Volume: state.VolumeParams{Pool: "loop-pool", Size: 2048}, // provisioned | |
| }, { | |
| Volume: state.VolumeParams{Pool: "loop-pool", Size: 2048}, // unprovisioned | |
| }, { | |
| Volume: state.VolumeParams{Pool: "loop-pool", Size: 2048}, // provisioned, non-persistent | |
| }, { | |
| Volume: state.VolumeParams{Pool: "static", Size: 2048}, // provisioned | |
| }, { | |
| Volume: state.VolumeParams{Pool: "static", Size: 2048}, // unprovisioned | |
| }}, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volumeInfoSet := state.VolumeInfo{Size: 123, Persistent: true, VolumeId: "vol-1"} | |
| err = s.State.SetVolumeInfo(names.NewVolumeTag("0/1"), volumeInfoSet) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volumeInfoSet = state.VolumeInfo{Size: 456, Persistent: false, VolumeId: "vol-2"} | |
| err = s.State.SetVolumeInfo(names.NewVolumeTag("0/3"), volumeInfoSet) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volumeInfoSet = state.VolumeInfo{Size: 789, Persistent: false, VolumeId: "vol-3"} | |
| err = s.State.SetVolumeInfo(names.NewVolumeTag("4"), volumeInfoSet) | |
| c.Assert(err, jc.ErrorIsNil) | |
| allVolumes, err := s.State.AllVolumes() | |
| c.Assert(err, jc.ErrorIsNil) | |
| persistentVolumes := make([]state.Volume, 0, len(allVolumes)) | |
| for _, v := range allVolumes { | |
| info, err := v.Info() | |
| if err == nil && info.Persistent { | |
| persistentVolumes = append(persistentVolumes, v) | |
| } | |
| } | |
| c.Assert(len(allVolumes), jc.GreaterThan, len(persistentVolumes)) | |
| c.Assert(machine.Destroy(), jc.ErrorIsNil) | |
| // Cannot advance to Dead while there are detachable dynamic volumes. | |
| err = machine.EnsureDead() | |
| c.Assert(err, jc.Satisfies, state.IsHasAttachmentsError) | |
| c.Assert(err, gc.ErrorMatches, "machine 0 has attachments \\[volume-0\\]") | |
| s.obliterateVolumeAttachment(c, machine.MachineTag(), names.NewVolumeTag("0")) | |
| c.Assert(machine.EnsureDead(), jc.ErrorIsNil) | |
| c.Assert(machine.Remove(), jc.ErrorIsNil) | |
| // Machine is gone: non-detachable storage should be done too. | |
| allVolumes, err = s.State.AllVolumes() | |
| c.Assert(err, jc.ErrorIsNil) | |
| // We should only have the persistent volume remaining. | |
| c.Assert(allVolumes, gc.HasLen, 1) | |
| c.Assert(allVolumes[0].Tag().String(), gc.Equals, "volume-0") | |
| attachments, err := s.State.MachineVolumeAttachments(machine.MachineTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(attachments, gc.HasLen, 0) | |
| } | |
| func (s *VolumeStateSuite) TestEnsureMachineDeadAddVolumeConcurrently(c *gc.C) { | |
| machine, err := s.State.AddOneMachine(state.MachineTemplate{ | |
| Series: "quantal", | |
| Jobs: []state.MachineJob{state.JobHostUnits}, | |
| Volumes: []state.MachineVolumeParams{{ | |
| Volume: state.VolumeParams{Pool: "static", Size: 1024}, | |
| }}, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| addVolume := func() { | |
| _, u, _ := s.setupSingleStorage(c, "block", "modelscoped") | |
| err := u.AssignToMachine(machine) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.obliterateUnit(c, u.UnitTag()) | |
| } | |
| defer state.SetBeforeHooks(c, s.State, addVolume).Check() | |
| // The static volume the machine was provisioned with does not matter, | |
| // but the volume added concurrently does. | |
| err = machine.EnsureDead() | |
| c.Assert(err, gc.ErrorMatches, `machine 0 has attachments \[volume-1\]`) | |
| } | |
| func (s *VolumeStateSuite) TestEnsureMachineDeadRemoveVolumeConcurrently(c *gc.C) { | |
| machine, err := s.State.AddOneMachine(state.MachineTemplate{ | |
| Series: "quantal", | |
| Jobs: []state.MachineJob{state.JobHostUnits}, | |
| Volumes: []state.MachineVolumeParams{{ | |
| Volume: state.VolumeParams{Pool: "static", Size: 1024}, | |
| }}, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| removeVolume := func() { | |
| s.obliterateVolume(c, names.NewVolumeTag("0")) | |
| } | |
| defer state.SetBeforeHooks(c, s.State, removeVolume).Check() | |
| // Removing a volume concurrently does not cause a transaction failure. | |
| err = machine.EnsureDead() | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (s *VolumeStateSuite) TestVolumeMachineScoped(c *gc.C) { | |
| machine, err := s.State.AddOneMachine(state.MachineTemplate{ | |
| Series: "quantal", | |
| Jobs: []state.MachineJob{state.JobHostUnits}, | |
| Volumes: []state.MachineVolumeParams{{ | |
| Volume: state.VolumeParams{Pool: "loop", Size: 1024}, | |
| }}, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volume := s.volume(c, names.NewVolumeTag("0/0")) | |
| c.Assert(volume.Life(), gc.Equals, state.Alive) | |
| err = s.State.DestroyVolume(volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.State.RemoveVolumeAttachment(machine.MachineTag(), volume.VolumeTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volume = s.volume(c, volume.VolumeTag()) | |
| c.Assert(volume.Life(), gc.Equals, state.Dead) | |
| // Remove the machine: this should remove the volume. | |
| err = machine.Destroy() | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = machine.EnsureDead() | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = machine.Remove() | |
| c.Assert(err, jc.ErrorIsNil) | |
| volume, err = s.State.Volume(volume.VolumeTag()) | |
| c.Assert(err, jc.Satisfies, errors.IsNotFound) | |
| } | |
| func (s *VolumeStateSuite) TestVolumeBindingStorage(c *gc.C) { | |
| // Volumes created assigned to a storage instance are bound | |
| // to the machine/model, and not the storage. i.e. storage | |
| // is persistent by default. | |
| volume, _, u := s.setupStorageVolumeAttachment(c) | |
| storageTag, err := volume.StorageInstance() | |
| c.Assert(err, jc.ErrorIsNil) | |
| // The volume should transition to Dying when the storage is removed. | |
| // We must destroy the unit before we can remove the storage. | |
| err = u.Destroy() | |
| c.Assert(err, jc.ErrorIsNil) | |
| removeStorageInstance(c, s.State, storageTag) | |
| volume = s.volume(c, volume.VolumeTag()) | |
| c.Assert(volume.Life(), gc.Equals, state.Dying) | |
| } | |
| func (s *VolumeStateSuite) setupStorageVolumeAttachment(c *gc.C) (state.Volume, *state.Machine, *state.Unit) { | |
| _, u, storageTag := s.setupSingleStorage(c, "block", "modelscoped") | |
| err := s.State.AssignUnit(u, state.AssignCleanEmpty) | |
| c.Assert(err, jc.ErrorIsNil) | |
| assignedMachineId, err := u.AssignedMachineId() | |
| c.Assert(err, jc.ErrorIsNil) | |
| return s.storageInstanceVolume(c, storageTag), s.machine(c, assignedMachineId), u | |
| } | |
| func (s *VolumeStateSuite) setupModelScopedVolumeAttachment(c *gc.C) (state.Volume, *state.Machine) { | |
| return s.setupVolumeAttachment(c, "modelscoped") | |
| } | |
| func (s *VolumeStateSuite) setupMachineScopedVolumeAttachment(c *gc.C) (state.Volume, *state.Machine) { | |
| return s.setupVolumeAttachment(c, "loop") | |
| } | |
| func (s *VolumeStateSuite) setupVolumeAttachment(c *gc.C, pool string) (state.Volume, *state.Machine) { | |
| machine, err := s.State.AddOneMachine(state.MachineTemplate{ | |
| Series: "quantal", | |
| Jobs: []state.MachineJob{state.JobHostUnits}, | |
| Volumes: []state.MachineVolumeParams{{ | |
| Volume: state.VolumeParams{Pool: pool, Size: 1024}, | |
| }}, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| volumeAttachments, err := s.State.MachineVolumeAttachments(machine.MachineTag()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(volumeAttachments, gc.HasLen, 1) | |
| volume, err := s.State.Volume(volumeAttachments[0].Volume()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| return volume, machine | |
| } | |
| func removeVolumeStorageInstance(c *gc.C, st *state.State, volumeTag names.VolumeTag) { | |
| volume, err := st.Volume(volumeTag) | |
| c.Assert(err, jc.ErrorIsNil) | |
| storageTag, err := volume.StorageInstance() | |
| c.Assert(err, jc.ErrorIsNil) | |
| removeStorageInstance(c, st, storageTag) | |
| } | |
| func removeStorageInstance(c *gc.C, st *state.State, storageTag names.StorageTag) { | |
| err := st.DestroyStorageInstance(storageTag) | |
| c.Assert(err, jc.ErrorIsNil) | |
| attachments, err := st.StorageAttachments(storageTag) | |
| c.Assert(err, jc.ErrorIsNil) | |
| for _, a := range attachments { | |
| err = st.DetachStorage(storageTag, a.Unit()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = st.RemoveStorageAttachment(storageTag, a.Unit()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| _, err = st.StorageInstance(storageTag) | |
| c.Assert(err, jc.Satisfies, errors.IsNotFound) | |
| } |