From 134b11ccd145f982b7173bc9a5d0f1bc3e8eef8c Mon Sep 17 00:00:00 2001 From: Thomas Herve Date: Wed, 8 May 2013 14:16:37 +0200 Subject: [PATCH] Implement OS::Cinder::Volume It creates a new resource type allowing to pass cinder specific arguments in the template. Change-Id: I1e8512892fdc7f141f91580bfca4e370b0740a56 --- heat/engine/resources/volume.py | 65 +++++++++++++++++-- heat/tests/test_volume.py | 110 +++++++++++++++++++++++++++++--- 2 files changed, 158 insertions(+), 17 deletions(-) diff --git a/heat/engine/resources/volume.py b/heat/engine/resources/volume.py index 32564531..9a2adff8 100644 --- a/heat/engine/resources/volume.py +++ b/heat/engine/resources/volume.py @@ -34,23 +34,36 @@ class Volume(resource.Resource): 'SnapshotId': {'Type': 'String'}, 'Tags': {'Type': 'List'}} + _restore_property = 'SnapshotId' + + def _display_name(self): + return self.physical_resource_name() + + def _display_description(self): + return self.physical_resource_name() + + def _create_arguments(self): + return {'size': self.properties['Size'], + 'availability_zone': self.properties['AvailabilityZone']} + def handle_create(self): - backup_id = self.properties.get('SnapshotId') + backup_id = self.properties.get(self._restore_property) cinder = self.cinder() if backup_id is not None: if volume_backups is None: - raise exception.Error('SnapshotId not supported') + raise exception.Error( + '%s not supported' % self._restore_property) vol_id = cinder.restores.restore(backup_id)['volume_id'] vol = cinder.volumes.get(vol_id) vol.update( - display_name=self.physical_resource_name(), - display_description=self.physical_resource_name()) + display_name=self._display_name(), + display_description=self._display_description()) else: vol = cinder.volumes.create( - self.properties['Size'], - display_name=self.physical_resource_name(), - display_description=self.physical_resource_name()) + display_name=self._display_name(), + display_description=self._display_description(), + **self._create_arguments()) while vol.status == 'creating': eventlet.sleep(1) @@ -119,8 +132,46 @@ class VolumeAttachment(resource.Resource): self.stack.clients.detach_volume_from_instance(server_id, volume_id) +class CinderVolume(Volume): + + properties_schema = {'availability_zone': {'Type': 'String', + 'Required': True}, + 'size': {'Type': 'Number'}, + 'snapshot_id': {'Type': 'String'}, + 'backup_id': {'Type': 'String'}, + 'name': {'Type': 'String'}, + 'description': {'Type': 'String'}, + 'volume_type': {'Type': 'String'}, + 'metadata': {'Type': 'Map'}, + 'imageRef': {'Type': 'String'}, + 'source_volid': {'Type': 'String'}} + + _restore_property = 'backup_id' + + def _display_name(self): + name = self.properties['name'] + if name: + return name + return super(CinderVolume, self)._display_name() + + def _display_description(self): + return self.properties['description'] + + def _create_arguments(self): + arguments = { + 'size': self.properties['size'], + 'availability_zone': self.properties['availability_zone'] + } + optionals = ['snapshot_id', 'volume_type', 'imageRef', 'source_volid', + 'metadata'] + arguments.update((prop, self.properties[prop]) for prop in optionals + if self.properties[prop]) + return arguments + + def resource_mapping(): return { 'AWS::EC2::Volume': Volume, 'AWS::EC2::VolumeAttachment': VolumeAttachment, + 'OS::Cinder::Volume': CinderVolume, } diff --git a/heat/tests/test_volume.py b/heat/tests/test_volume.py index 2804eb30..141a21aa 100644 --- a/heat/tests/test_volume.py +++ b/heat/tests/test_volume.py @@ -85,9 +85,9 @@ class VolumeTest(HeatTestCase): setup_dummy_db() def create_volume(self, t, stack, resource_name): - resource = vol.Volume(resource_name, - t['Resources'][resource_name], - stack) + data = t['Resources'][resource_name] + data['Properties']['AvailabilityZone'] = 'nova' + resource = vol.Volume(resource_name, data, stack) self.assertEqual(resource.validate(), None) scheduler.TaskRunner(resource.create)() self.assertEqual(resource.state, vol.Volume.CREATE_COMPLETE) @@ -110,7 +110,8 @@ class VolumeTest(HeatTestCase): clients.OpenStackClients.cinder().MultipleTimes().AndReturn( self.cinder_fc) self.cinder_fc.volumes.create( - u'1', display_description='%s.DataVolume' % stack_name, + size=u'1', availability_zone='nova', + display_description='%s.DataVolume' % stack_name, display_name='%s.DataVolume' % stack_name).AndReturn(fv) # delete script @@ -150,7 +151,8 @@ class VolumeTest(HeatTestCase): # create script clients.OpenStackClients.cinder().AndReturn(self.cinder_fc) self.cinder_fc.volumes.create( - u'1', display_description='%s.DataVolume' % stack_name, + size=u'1', availability_zone='nova', + display_description='%s.DataVolume' % stack_name, display_name='%s.DataVolume' % stack_name).AndReturn(fv) eventlet.sleep(1).AndReturn(None) @@ -158,6 +160,7 @@ class VolumeTest(HeatTestCase): self.m.ReplayAll() t = template_format.parse(volume_template) + t['Resources']['DataVolume']['Properties']['AvailabilityZone'] = 'nova' stack = parse_stack(t, stack_name=stack_name) resource = vol.Volume('DataVolume', @@ -177,7 +180,8 @@ class VolumeTest(HeatTestCase): clients.OpenStackClients.cinder().MultipleTimes().AndReturn( self.cinder_fc) self.cinder_fc.volumes.create( - u'1', display_description='%s.DataVolume' % stack_name, + size=u'1', availability_zone='nova', + display_description='%s.DataVolume' % stack_name, display_name='%s.DataVolume' % stack_name).AndReturn(fv) # create script @@ -194,6 +198,7 @@ class VolumeTest(HeatTestCase): self.m.ReplayAll() t = template_format.parse(volume_template) + t['Resources']['DataVolume']['Properties']['AvailabilityZone'] = 'nova' stack = parse_stack(t, stack_name=stack_name) scheduler.TaskRunner(stack['DataVolume'].create)() @@ -215,7 +220,8 @@ class VolumeTest(HeatTestCase): clients.OpenStackClients.cinder().MultipleTimes().AndReturn( self.cinder_fc) self.cinder_fc.volumes.create( - u'1', display_description='%s.DataVolume' % stack_name, + size=u'1', availability_zone='nova', + display_description='%s.DataVolume' % stack_name, display_name='%s.DataVolume' % stack_name).AndReturn(fv) # create script @@ -238,6 +244,7 @@ class VolumeTest(HeatTestCase): self.m.ReplayAll() t = template_format.parse(volume_template) + t['Resources']['DataVolume']['Properties']['AvailabilityZone'] = 'nova' stack = parse_stack(t, stack_name=stack_name) scheduler.TaskRunner(stack['DataVolume'].create)() @@ -260,7 +267,8 @@ class VolumeTest(HeatTestCase): clients.OpenStackClients.cinder().MultipleTimes().AndReturn( self.cinder_fc) self.cinder_fc.volumes.create( - u'1', display_description='%s.DataVolume' % stack_name, + size=u'1', availability_zone='nova', + display_description='%s.DataVolume' % stack_name, display_name='%s.DataVolume' % stack_name).AndReturn(fv) eventlet.sleep(1).AndReturn(None) @@ -292,7 +300,8 @@ class VolumeTest(HeatTestCase): clients.OpenStackClients.cinder().MultipleTimes().AndReturn( self.cinder_fc) self.cinder_fc.volumes.create( - u'1', display_description='%s.DataVolume' % stack_name, + size=u'1', availability_zone='nova', + display_description='%s.DataVolume' % stack_name, display_name='%s.DataVolume' % stack_name).AndReturn(fv) eventlet.sleep(1).AndReturn(None) @@ -321,7 +330,8 @@ class VolumeTest(HeatTestCase): clients.OpenStackClients.cinder().MultipleTimes().AndReturn( self.cinder_fc) self.cinder_fc.volumes.create( - u'1', display_description='%s.DataVolume' % stack_name, + size=u'1', availability_zone='nova', + display_description='%s.DataVolume' % stack_name, display_name='%s.DataVolume' % stack_name).AndReturn(fv) eventlet.sleep(1).AndReturn(None) @@ -329,6 +339,7 @@ class VolumeTest(HeatTestCase): t = template_format.parse(volume_template) t['Resources']['DataVolume']['DeletionPolicy'] = 'Snapshot' + t['Resources']['DataVolume']['Properties']['AvailabilityZone'] = 'nova' stack = parse_stack(t, stack_name=stack_name) resource = vol.Volume('DataVolume', t['Resources']['DataVolume'], @@ -370,6 +381,85 @@ class VolumeTest(HeatTestCase): self.m.VerifyAll() + def test_cinder_create(self): + fv = FakeVolume('creating', 'available') + stack_name = 'test_volume_stack' + + clients.OpenStackClients.cinder().MultipleTimes().AndReturn( + self.cinder_fc) + self.cinder_fc.volumes.create( + size=u'1', availability_zone='nova', + display_description='CustomDescription', + display_name='CustomName', + imageRef='Image1', + snapshot_id='snap-123', + metadata={'key': 'value'}, + source_volid='vol-012', + volume_type='lvm').AndReturn(fv) + + eventlet.sleep(1).AndReturn(None) + + self.m.ReplayAll() + + t = template_format.parse(volume_template) + t['Resources']['DataVolume']['Properties'] = { + 'size': '1', + 'availability_zone': 'nova', + 'name': 'CustomName', + 'description': 'CustomDescription', + 'volume_type': 'lvm', + 'metadata': {'key': 'value'}, + # Note that specifying all these arguments doesn't work in + # practice, as they are conflicting, but we just want to check they + # are sent to the backend. + 'imageRef': 'Image1', + 'snapshot_id': 'snap-123', + 'source_volid': 'vol-012', + } + stack = parse_stack(t, stack_name=stack_name) + + resource = vol.CinderVolume('DataVolume', + t['Resources']['DataVolume'], + stack) + self.assertEqual(resource.validate(), None) + scheduler.TaskRunner(resource.create)() + self.assertEqual(resource.state, vol.Volume.CREATE_COMPLETE) + self.assertEqual(fv.status, 'available') + + self.m.VerifyAll() + + def test_cinder_default(self): + fv = FakeVolume('creating', 'available') + stack_name = 'test_volume_stack' + + clients.OpenStackClients.cinder().MultipleTimes().AndReturn( + self.cinder_fc) + self.cinder_fc.volumes.create( + size=u'1', availability_zone='nova', + display_description=None, + display_name='%s.DataVolume' % stack_name).AndReturn(fv) + + eventlet.sleep(1).AndReturn(None) + + self.m.ReplayAll() + + t = template_format.parse(volume_template) + t['Resources']['DataVolume']['Properties'] = { + 'size': '1', + 'availability_zone': 'nova', + } + stack = parse_stack(t, stack_name=stack_name) + + resource = vol.CinderVolume('DataVolume', + t['Resources']['DataVolume'], + stack) + self.assertEqual(resource.validate(), None) + scheduler.TaskRunner(resource.create)() + self.assertEqual(resource.state, vol.Volume.CREATE_COMPLETE) + self.assertEqual(fv.status, 'available') + + self.m.VerifyAll() + class FakeVolume: status = 'attaching' -- 2.45.2