diff --git a/heat/engine/resources/volume.py b/heat/engine/resources/volume.py index 5b2ca708af..ef09f8b331 100644 --- a/heat/engine/resources/volume.py +++ b/heat/engine/resources/volume.py @@ -102,7 +102,7 @@ class Volume(resource.Resource): if vol.status == 'in-use': logger.warn('cant delete volume when in-use') - raise exception.Error("Volume in use") + raise exception.Error('Volume in use') self.cinder().volumes.delete(self.resource_id) except clients.cinderclient.exceptions.NotFound: @@ -123,17 +123,18 @@ class VolumeAttachment(resource.Resource): 'Required': True}, 'VolumeId': {'Type': 'String', 'Required': True}, - 'Device': {'Type': "String", + 'Device': {'Type': 'String', 'Required': True, 'AllowedPattern': '/dev/vd[b-z]'}} - def __init__(self, name, json_snippet, stack): - super(VolumeAttachment, self).__init__(name, json_snippet, stack) + _instance_property = 'InstanceId' + _volume_property = 'VolumeId' + _device_property = 'Device' def handle_create(self): - server_id = self.properties['InstanceId'] - volume_id = self.properties['VolumeId'] - dev = self.properties['Device'] + server_id = self.properties[self._instance_property] + volume_id = self.properties[self._volume_property] + dev = self.properties[self._device_property] inst = self.stack.clients.attach_volume_to_instance(server_id, volume_id, dev) @@ -143,8 +144,8 @@ class VolumeAttachment(resource.Resource): return self.UPDATE_REPLACE def handle_delete(self): - server_id = self.properties['InstanceId'] - volume_id = self.properties['VolumeId'] + server_id = self.properties[self._instance_property] + volume_id = self.properties[self._volume_property] self.stack.clients.detach_volume_from_instance(server_id, volume_id) @@ -200,9 +201,24 @@ class CinderVolume(Volume): return unicode(getattr(vol, key)) +class CinderVolumeAttachment(VolumeAttachment): + + properties_schema = {'instance_uuid': {'Type': 'String', + 'Required': True}, + 'volume_id': {'Type': 'String', + 'Required': True}, + 'mountpoint': {'Type': 'String', + 'Required': True}} + + _instance_property = 'instance_uuid' + _volume_property = 'volume_id' + _device_property = 'mountpoint' + + def resource_mapping(): return { 'AWS::EC2::Volume': Volume, 'AWS::EC2::VolumeAttachment': VolumeAttachment, 'OS::Cinder::Volume': CinderVolume, + 'OS::Cinder::VolumeAttachment': CinderVolumeAttachment, } diff --git a/heat/tests/test_volume.py b/heat/tests/test_volume.py index 3d4266c032..be5f98bbfe 100644 --- a/heat/tests/test_volume.py +++ b/heat/tests/test_volume.py @@ -225,7 +225,6 @@ class VolumeTest(HeatTestCase): # create script clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc) - #clients.OpenStackClients.cinder().MultipleTimes().AndReturn(self.fc) eventlet.sleep(1).MultipleTimes().AndReturn(None) self.fc.volumes.create_server_volume( device=u'/dev/vdc', @@ -505,6 +504,61 @@ class VolumeTest(HeatTestCase): 'The Referenced Attribute (DataVolume unknown) is incorrect.', str(error)) + def test_cinder_attachment(self): + fv = FakeVolume('creating', 'available') + fva = FakeVolume('attaching', 'in-use') + stack_name = 'test_volume_attach_stack' + + # volume create + clients.OpenStackClients.cinder().MultipleTimes().AndReturn( + self.cinder_fc) + self.cinder_fc.volumes.create( + size=u'1', availability_zone='nova', + display_description='%s.DataVolume' % stack_name, + display_name='%s.DataVolume' % stack_name).AndReturn(fv) + + # create script + clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc) + eventlet.sleep(1).MultipleTimes().AndReturn(None) + self.fc.volumes.create_server_volume( + device=u'/dev/vdc', + server_id=u'WikiDatabase', + volume_id=u'vol-123').AndReturn(fva) + + self.cinder_fc.volumes.get('vol-123').AndReturn(fva) + + # delete script + fva = FakeVolume('in-use', 'available') + self.fc.volumes.delete_server_volume('WikiDatabase', + 'vol-123').AndReturn(None) + self.cinder_fc.volumes.get('vol-123').AndReturn(fva) + + self.m.ReplayAll() + + t = template_format.parse(volume_template) + t['Resources']['DataVolume']['Properties']['AvailabilityZone'] = 'nova' + t['Resources']['MountPoint']['Properties'] = { + 'instance_uuid': {'Ref': 'WikiDatabase'}, + 'volume_id': {'Ref': 'DataVolume'}, + 'mountpoint': '/dev/vdc' + } + stack = parse_stack(t, stack_name=stack_name) + + scheduler.TaskRunner(stack['DataVolume'].create)() + self.assertEqual(fv.status, 'available') + resource = vol.CinderVolumeAttachment('MountPoint', + t['Resources']['MountPoint'], + stack) + self.assertEqual(resource.validate(), None) + scheduler.TaskRunner(resource.create)() + self.assertEqual(resource.state, vol.VolumeAttachment.CREATE_COMPLETE) + + self.assertEqual(resource.handle_update({}), vol.Volume.UPDATE_REPLACE) + + self.assertEqual(resource.delete(), None) + + self.m.VerifyAll() + class FakeVolume: status = 'attaching'