Skip to content

Commit 6cf7900

Browse files
committed
provider/gce: implement Release/ImportVolume
Implement ReleaseVolumes and ImportVolume, to support releasing GCE volumes from one controller, and importing them into another.
1 parent ae8a29b commit 6cf7900

File tree

2 files changed

+128
-4
lines changed

2 files changed

+128
-4
lines changed

provider/gce/disks.go

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ func (v *volumeSource) DestroyVolumes(volNames []string) ([]error, error) {
249249
return v.foreachVolume(volNames, v.destroyOneVolume), nil
250250
}
251251

252+
func (v *volumeSource) ReleaseVolumes(volNames []string) ([]error, error) {
253+
return v.foreachVolume(volNames, v.releaseOneVolume), nil
254+
}
255+
252256
func (v *volumeSource) foreachVolume(volNames []string, f func(string) error) []error {
253257
var wg sync.WaitGroup
254258
wg.Add(len(volNames))
@@ -263,10 +267,6 @@ func (v *volumeSource) foreachVolume(volNames []string, f func(string) error) []
263267
return results
264268
}
265269

266-
func (v *volumeSource) ReleaseVolumes(volNames []string) ([]error, error) {
267-
return nil, errors.NotImplementedf("ReleaseVolumes")
268-
}
269-
270270
func parseVolumeId(volName string) (string, string, error) {
271271
idRest := strings.SplitN(volName, "--", 2)
272272
if len(idRest) != 2 {
@@ -293,6 +293,37 @@ func (v *volumeSource) destroyOneVolume(volName string) error {
293293
return nil
294294
}
295295

296+
func (v *volumeSource) releaseOneVolume(volName string) error {
297+
zone, _, err := parseVolumeId(volName)
298+
if err != nil {
299+
return errors.Annotatef(err, "invalid volume id %q", volName)
300+
}
301+
disk, err := v.gce.Disk(zone, volName)
302+
if err != nil {
303+
return errors.Trace(err)
304+
}
305+
switch disk.Status {
306+
case google.StatusReady, google.StatusFailed:
307+
default:
308+
return errors.Errorf(
309+
"cannot release volume %q with status %q",
310+
volName, disk.Status,
311+
)
312+
}
313+
if len(disk.AttachedInstances) > 0 {
314+
return errors.Errorf(
315+
"cannot release volume %q, attached to instances %q",
316+
volName, disk.AttachedInstances,
317+
)
318+
}
319+
delete(disk.Labels, tags.JujuController)
320+
delete(disk.Labels, tags.JujuModel)
321+
if err := v.gce.SetDiskLabels(zone, volName, disk.LabelFingerprint, disk.Labels); err != nil {
322+
return errors.Annotatef(err, "cannot remove labels from volume %q", volName)
323+
}
324+
return nil
325+
}
326+
296327
func (v *volumeSource) ListVolumes() ([]string, error) {
297328
var volumes []string
298329
disks, err := v.gce.Disks()
@@ -311,6 +342,38 @@ func (v *volumeSource) ListVolumes() ([]string, error) {
311342
return volumes, nil
312343
}
313344

345+
// ImportVolume is specified on the storage.VolumeImporter interface.
346+
func (v *volumeSource) ImportVolume(volName string, tags map[string]string) (storage.VolumeInfo, error) {
347+
zone, _, err := parseVolumeId(volName)
348+
if err != nil {
349+
return storage.VolumeInfo{}, errors.Annotatef(err, "cannot get volume %q", volName)
350+
}
351+
disk, err := v.gce.Disk(zone, volName)
352+
if err != nil {
353+
return storage.VolumeInfo{}, errors.Annotatef(err, "cannot get volume %q", volName)
354+
}
355+
if disk.Status != google.StatusReady {
356+
return storage.VolumeInfo{}, errors.Errorf(
357+
"cannot import volume %q with status %q",
358+
volName, disk.Status,
359+
)
360+
}
361+
if disk.Labels == nil {
362+
disk.Labels = make(map[string]string)
363+
}
364+
for k, v := range resourceTagsToDiskLabels(tags) {
365+
disk.Labels[k] = v
366+
}
367+
if err := v.gce.SetDiskLabels(zone, volName, disk.LabelFingerprint, disk.Labels); err != nil {
368+
return storage.VolumeInfo{}, errors.Annotatef(err, "cannot update labels on volume %q", volName)
369+
}
370+
return storage.VolumeInfo{
371+
VolumeId: disk.Name,
372+
Size: disk.Size,
373+
Persistent: true,
374+
}, nil
375+
}
376+
314377
func (v *volumeSource) DescribeVolumes(volNames []string) ([]storage.DescribeVolumesResult, error) {
315378
results := make([]storage.DescribeVolumesResult, len(volNames))
316379
for i, vol := range volNames {

provider/gce/disks_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,67 @@ func (s *volumeSourceSuite) TestDestroyVolumes(c *gc.C) {
179179
c.Assert(call[0].ID, gc.Equals, "a--volume-name")
180180
}
181181

182+
func (s *volumeSourceSuite) TestReleaseVolumes(c *gc.C) {
183+
s.FakeConn.GoogleDisk = s.BaseDisk
184+
185+
errs, err := s.source.ReleaseVolumes([]string{s.BaseDisk.Name})
186+
c.Check(err, jc.ErrorIsNil)
187+
c.Check(errs, gc.HasLen, 1)
188+
c.Assert(errs[0], jc.ErrorIsNil)
189+
190+
called, calls := s.FakeConn.WasCalled("SetDiskLabels")
191+
c.Check(called, jc.IsTrue)
192+
c.Assert(calls, gc.HasLen, 1)
193+
c.Assert(calls[0].ZoneName, gc.Equals, "home-zone")
194+
c.Assert(calls[0].ID, gc.Equals, s.BaseDisk.Name)
195+
c.Assert(calls[0].Labels, jc.DeepEquals, map[string]string{
196+
"yodel": "eh",
197+
// Note, no controller/model labels
198+
})
199+
}
200+
201+
func (s *volumeSourceSuite) TestImportVolume(c *gc.C) {
202+
s.FakeConn.GoogleDisk = s.BaseDisk
203+
204+
c.Assert(s.source, gc.Implements, new(storage.VolumeImporter))
205+
volumeInfo, err := s.source.(storage.VolumeImporter).ImportVolume(
206+
s.BaseDisk.Name, map[string]string{
207+
"juju-model-uuid": "foo",
208+
"juju-controller-uuid": "bar",
209+
},
210+
)
211+
c.Check(err, jc.ErrorIsNil)
212+
c.Assert(volumeInfo, jc.DeepEquals, storage.VolumeInfo{
213+
VolumeId: s.BaseDisk.Name,
214+
Size: 1024,
215+
Persistent: true,
216+
})
217+
218+
called, calls := s.FakeConn.WasCalled("SetDiskLabels")
219+
c.Check(called, jc.IsTrue)
220+
c.Assert(calls, gc.HasLen, 1)
221+
c.Assert(calls[0].ZoneName, gc.Equals, "home-zone")
222+
c.Assert(calls[0].ID, gc.Equals, s.BaseDisk.Name)
223+
c.Assert(calls[0].Labels, jc.DeepEquals, map[string]string{
224+
"juju-model-uuid": "foo",
225+
"juju-controller-uuid": "bar",
226+
"yodel": "eh", // other existing tags left alone
227+
})
228+
}
229+
230+
func (s *volumeSourceSuite) TestImportVolumeNotReady(c *gc.C) {
231+
s.FakeConn.GoogleDisk = s.BaseDisk
232+
s.FakeConn.GoogleDisk.Status = "floop"
233+
234+
_, err := s.source.(storage.VolumeImporter).ImportVolume(
235+
s.BaseDisk.Name, map[string]string{},
236+
)
237+
c.Check(err, gc.ErrorMatches, `cannot import volume "`+s.BaseDisk.Name+`" with status "floop"`)
238+
239+
called, _ := s.FakeConn.WasCalled("SetDiskLabels")
240+
c.Check(called, jc.IsFalse)
241+
}
242+
182243
func (s *volumeSourceSuite) TestListVolumes(c *gc.C) {
183244
s.FakeConn.GoogleDisks = []*google.Disk{s.BaseDisk}
184245
vols, err := s.source.ListVolumes()

0 commit comments

Comments
 (0)