Skip to content

Commit

Permalink
Merge pull request juju#7779 from axw/gce-release-storage
Browse files Browse the repository at this point in the history
provider/gce: implement Release/ImportVolume

## Description of change

Implement ReleaseVolumes and ImportVolume, to
support releasing GCE volumes from one controller,
and importing them into another.

## QA steps

1. juju bootstrap google
2. juju deploy cs:~axwalk/storagetest --storage fs=gce,1G
(wait for storage to be attached)
3. juju remove-storage --force --no-destroy fs/0
(wait for storage to disappear from juju storage output)
4. juju destroy-controller
navigate to GCE console, check that the disk is still there, but has the controller/model labels cleared

5. juju bootstrap google
6. juju import-filesystem gce \<disk-name\> fs
7. juju destroy-controller -y --destroy-all-models --destroy-storage
(confirm disk is deleted)

## Documentation changes

None.

## Bug reference

None.
  • Loading branch information
jujubot authored Aug 25, 2017
2 parents b5a8898 + 6cf7900 commit 7a575b9
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 4 deletions.
71 changes: 67 additions & 4 deletions provider/gce/disks.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ func (v *volumeSource) DestroyVolumes(volNames []string) ([]error, error) {
return v.foreachVolume(volNames, v.destroyOneVolume), nil
}

func (v *volumeSource) ReleaseVolumes(volNames []string) ([]error, error) {
return v.foreachVolume(volNames, v.releaseOneVolume), nil
}

func (v *volumeSource) foreachVolume(volNames []string, f func(string) error) []error {
var wg sync.WaitGroup
wg.Add(len(volNames))
Expand All @@ -263,10 +267,6 @@ func (v *volumeSource) foreachVolume(volNames []string, f func(string) error) []
return results
}

func (v *volumeSource) ReleaseVolumes(volNames []string) ([]error, error) {
return nil, errors.NotImplementedf("ReleaseVolumes")
}

func parseVolumeId(volName string) (string, string, error) {
idRest := strings.SplitN(volName, "--", 2)
if len(idRest) != 2 {
Expand All @@ -293,6 +293,37 @@ func (v *volumeSource) destroyOneVolume(volName string) error {
return nil
}

func (v *volumeSource) releaseOneVolume(volName string) error {
zone, _, err := parseVolumeId(volName)
if err != nil {
return errors.Annotatef(err, "invalid volume id %q", volName)
}
disk, err := v.gce.Disk(zone, volName)
if err != nil {
return errors.Trace(err)
}
switch disk.Status {
case google.StatusReady, google.StatusFailed:
default:
return errors.Errorf(
"cannot release volume %q with status %q",
volName, disk.Status,
)
}
if len(disk.AttachedInstances) > 0 {
return errors.Errorf(
"cannot release volume %q, attached to instances %q",
volName, disk.AttachedInstances,
)
}
delete(disk.Labels, tags.JujuController)
delete(disk.Labels, tags.JujuModel)
if err := v.gce.SetDiskLabels(zone, volName, disk.LabelFingerprint, disk.Labels); err != nil {
return errors.Annotatef(err, "cannot remove labels from volume %q", volName)
}
return nil
}

func (v *volumeSource) ListVolumes() ([]string, error) {
var volumes []string
disks, err := v.gce.Disks()
Expand All @@ -311,6 +342,38 @@ func (v *volumeSource) ListVolumes() ([]string, error) {
return volumes, nil
}

// ImportVolume is specified on the storage.VolumeImporter interface.
func (v *volumeSource) ImportVolume(volName string, tags map[string]string) (storage.VolumeInfo, error) {
zone, _, err := parseVolumeId(volName)
if err != nil {
return storage.VolumeInfo{}, errors.Annotatef(err, "cannot get volume %q", volName)
}
disk, err := v.gce.Disk(zone, volName)
if err != nil {
return storage.VolumeInfo{}, errors.Annotatef(err, "cannot get volume %q", volName)
}
if disk.Status != google.StatusReady {
return storage.VolumeInfo{}, errors.Errorf(
"cannot import volume %q with status %q",
volName, disk.Status,
)
}
if disk.Labels == nil {
disk.Labels = make(map[string]string)
}
for k, v := range resourceTagsToDiskLabels(tags) {
disk.Labels[k] = v
}
if err := v.gce.SetDiskLabels(zone, volName, disk.LabelFingerprint, disk.Labels); err != nil {
return storage.VolumeInfo{}, errors.Annotatef(err, "cannot update labels on volume %q", volName)
}
return storage.VolumeInfo{
VolumeId: disk.Name,
Size: disk.Size,
Persistent: true,
}, nil
}

func (v *volumeSource) DescribeVolumes(volNames []string) ([]storage.DescribeVolumesResult, error) {
results := make([]storage.DescribeVolumesResult, len(volNames))
for i, vol := range volNames {
Expand Down
61 changes: 61 additions & 0 deletions provider/gce/disks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,67 @@ func (s *volumeSourceSuite) TestDestroyVolumes(c *gc.C) {
c.Assert(call[0].ID, gc.Equals, "a--volume-name")
}

func (s *volumeSourceSuite) TestReleaseVolumes(c *gc.C) {
s.FakeConn.GoogleDisk = s.BaseDisk

errs, err := s.source.ReleaseVolumes([]string{s.BaseDisk.Name})
c.Check(err, jc.ErrorIsNil)
c.Check(errs, gc.HasLen, 1)
c.Assert(errs[0], jc.ErrorIsNil)

called, calls := s.FakeConn.WasCalled("SetDiskLabels")
c.Check(called, jc.IsTrue)
c.Assert(calls, gc.HasLen, 1)
c.Assert(calls[0].ZoneName, gc.Equals, "home-zone")
c.Assert(calls[0].ID, gc.Equals, s.BaseDisk.Name)
c.Assert(calls[0].Labels, jc.DeepEquals, map[string]string{
"yodel": "eh",
// Note, no controller/model labels
})
}

func (s *volumeSourceSuite) TestImportVolume(c *gc.C) {
s.FakeConn.GoogleDisk = s.BaseDisk

c.Assert(s.source, gc.Implements, new(storage.VolumeImporter))
volumeInfo, err := s.source.(storage.VolumeImporter).ImportVolume(
s.BaseDisk.Name, map[string]string{
"juju-model-uuid": "foo",
"juju-controller-uuid": "bar",
},
)
c.Check(err, jc.ErrorIsNil)
c.Assert(volumeInfo, jc.DeepEquals, storage.VolumeInfo{
VolumeId: s.BaseDisk.Name,
Size: 1024,
Persistent: true,
})

called, calls := s.FakeConn.WasCalled("SetDiskLabels")
c.Check(called, jc.IsTrue)
c.Assert(calls, gc.HasLen, 1)
c.Assert(calls[0].ZoneName, gc.Equals, "home-zone")
c.Assert(calls[0].ID, gc.Equals, s.BaseDisk.Name)
c.Assert(calls[0].Labels, jc.DeepEquals, map[string]string{
"juju-model-uuid": "foo",
"juju-controller-uuid": "bar",
"yodel": "eh", // other existing tags left alone
})
}

func (s *volumeSourceSuite) TestImportVolumeNotReady(c *gc.C) {
s.FakeConn.GoogleDisk = s.BaseDisk
s.FakeConn.GoogleDisk.Status = "floop"

_, err := s.source.(storage.VolumeImporter).ImportVolume(
s.BaseDisk.Name, map[string]string{},
)
c.Check(err, gc.ErrorMatches, `cannot import volume "`+s.BaseDisk.Name+`" with status "floop"`)

called, _ := s.FakeConn.WasCalled("SetDiskLabels")
c.Check(called, jc.IsFalse)
}

func (s *volumeSourceSuite) TestListVolumes(c *gc.C) {
s.FakeConn.GoogleDisks = []*google.Disk{s.BaseDisk}
vols, err := s.source.ListVolumes()
Expand Down

0 comments on commit 7a575b9

Please sign in to comment.