diff --git a/nova/block_device.py b/nova/block_device.py index 0b784760b156..9636e81f842b 100644 --- a/nova/block_device.py +++ b/nova/block_device.py @@ -203,6 +203,7 @@ class BlockDeviceDict(dict): for field in copy_over_fields if field in self) source_type = self.get('source_type') + destination_type = self.get('destination_type') no_device = self.get('no_device') if source_type == 'blank': if self['guest_format'] == 'swap': @@ -214,9 +215,11 @@ class BlockDeviceDict(dict): elif source_type in ('volume', 'snapshot') or no_device: legacy_block_device['virtual_name'] = None elif source_type == 'image': - # NOTE(ndipanov): Image bdms have no meaning in - # the legacy format - raise - raise exception.InvalidBDMForLegacy() + if destination_type != 'volume': + # NOTE(ndipanov): Image bdms with local destination + # have no meaning in the legacy format - raise + raise exception.InvalidBDMForLegacy() + legacy_block_device['virtual_name'] = None return legacy_block_device diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0801f31c63ad..0a1ffe7df58d 100755 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -895,7 +895,7 @@ class ComputeManager(manager.SchedulerDependentManager): while attempts < max_tries: volume = self.volume_api.get(context, vol_id) volume_status = volume['status'] - if volume_status != 'creating': + if volume_status not in ['creating', 'downloading']: if volume_status != 'available': LOG.warn(_("Volume id: %s finished being created but was" " not set as 'available'"), vol_id) @@ -1284,6 +1284,11 @@ class ComputeManager(manager.SchedulerDependentManager): driver_block_device.convert_snapshots(bdms), context, instance, self.volume_api, self.driver, self.conductor_api, + self._await_block_device_map_created) + + driver_block_device.attach_block_devices( + driver_block_device.convert_images(bdms), + context, instance, self.volume_api, + self.driver, self.conductor_api, self._await_block_device_map_created)) } diff --git a/nova/tests/test_block_device.py b/nova/tests/test_block_device.py index 274973f3da15..bb91805c41e2 100644 --- a/nova/tests/test_block_device.py +++ b/nova/tests/test_block_device.py @@ -224,6 +224,30 @@ class TestBlockDeviceDict(test.TestCase): 'device_name': '/dev/vdc'}, ] + self.new_mapping_source_image = [ + BDM({'id': 6, 'instance_uuid': 'fake-instance', + 'device_name': '/dev/sda3', + 'source_type': 'image', + 'destination_type': 'volume', + 'connection_info': "{'fake': 'connection_info'}", + 'volume_id': 'fake-volume-id-3', + 'boot_index': -1}), + BDM({'id': 7, 'instance_uuid': 'fake-instance', + 'device_name': '/dev/sda4', + 'source_type': 'image', + 'destination_type': 'local', + 'connection_info': "{'fake': 'connection_info'}", + 'image_id': 'fake-image-id-2', + 'boot_index': -1}), + ] + + self.legacy_mapping_source_image = [ + {'id': 6, 'instance_uuid': 'fake-instance', + 'device_name': '/dev/sda3', + 'connection_info': "{'fake': 'connection_info'}", + 'volume_id': 'fake-volume-id-3'}, + ] + def test_init(self): def fake_validate(obj, dct): pass @@ -366,3 +390,17 @@ class TestBlockDeviceDict(test.TestCase): for legacy, expected in zip(got_legacy, self.legacy_mapping): self.assertThat(expected, matchers.IsSubDictOf(legacy)) + + def test_legacy_source_image(self): + for legacy, new in zip(self.legacy_mapping_source_image, + self.new_mapping_source_image): + if new['destination_type'] == 'volume': + self.assertThat(legacy, matchers.IsSubDictOf(new.legacy())) + else: + self.assertRaises(exception.InvalidBDMForLegacy, new.legacy) + + def test_legacy_mapping_source_image(self): + got_legacy = block_device.legacy_mapping(self.new_mapping) + + for legacy, expected in zip(got_legacy, self.legacy_mapping): + self.assertThat(expected, matchers.IsSubDictOf(legacy)) diff --git a/nova/tests/virt/test_block_device.py b/nova/tests/virt/test_block_device.py index e4b4c3749bb3..233dcaff712a 100644 --- a/nova/tests/virt/test_block_device.py +++ b/nova/tests/virt/test_block_device.py @@ -30,7 +30,8 @@ class TestDriverBlockDevice(test.TestCase): 'swap': driver_block_device.DriverSwapBlockDevice, 'ephemeral': driver_block_device.DriverEphemeralBlockDevice, 'volume': driver_block_device.DriverVolumeBlockDevice, - 'snapshot': driver_block_device.DriverSnapshotBlockDevice + 'snapshot': driver_block_device.DriverSnapshotBlockDevice, + 'image': driver_block_device.DriverImageBlockDevice } swap_bdm = block_device.BlockDeviceDict( @@ -134,6 +135,34 @@ class TestDriverBlockDevice(test.TestCase): 'connection_info': {"fake": "connection_info"}, 'delete_on_termination': True} + image_bdm = block_device.BlockDeviceDict( + {'id': 5, 'instance_uuid': 'fake-instance', + 'device_name': '/dev/sda2', + 'delete_on_termination': True, + 'volume_size': 1, + 'disk_bus': 'scsi', + 'device_type': 'disk', + 'source_type': 'image', + 'destination_type': 'volume', + 'connection_info': '{"fake": "connection_info"}', + 'image_id': 'fake-image-id-1', + 'volume_id': 'fake-volume-id-2', + 'boot_index': -1}) + + image_driver_bdm = { + 'mount_device': '/dev/sda2', + 'connection_info': {"fake": "connection_info"}, + 'delete_on_termination': True, + 'disk_bus': 'scsi', + 'device_type': 'disk', + 'guest_format': None, + 'boot_index': -1} + + image_legacy_driver_bdm = { + 'mount_device': '/dev/sda2', + 'connection_info': {"fake": "connection_info"}, + 'delete_on_termination': True} + def setUp(self): super(TestDriverBlockDevice, self).setUp() self.volume_api = self.mox.CreateMock(cinder.API) @@ -204,6 +233,22 @@ class TestDriverBlockDevice(test.TestCase): self.assertEquals(test_bdm.volume_id, 'fake-volume-id-2') self.assertEquals(test_bdm.volume_size, 3) + def test_driver_image_block_device(self): + self._test_driver_device('image') + + test_bdm = self.driver_classes['image']( + self.image_bdm) + self.assertEquals(test_bdm.id, 5) + self.assertEquals(test_bdm.image_id, 'fake-image-id-1') + self.assertEquals(test_bdm.volume_size, 1) + + def test_driver_image_block_device_destination_local(self): + self._test_driver_device('image') + bdm = self.image_bdm.copy() + bdm['destination_type'] = 'local' + self.assertRaises(driver_block_device._InvalidType, + self.driver_classes['image'], bdm) + def test_volume_attach(self): test_bdm = self.driver_classes['volume']( self.volume_bdm) @@ -315,6 +360,56 @@ class TestDriverBlockDevice(test.TestCase): self.virt_driver, self.db_api) self.assertEquals(test_bdm.volume_id, 'fake-volume-id-2') + def test_image_attach_no_volume(self): + no_volume_image = self.image_bdm.copy() + no_volume_image['volume_id'] = None + test_bdm = self.driver_classes['image'](no_volume_image) + + instance = {'id': 'fake_id', 'uuid': 'fake_uuid'} + image = {'id': 'fake-image-id-1'} + volume = {'id': 'fake-volume-id-2'} + + wait_func = self.mox.CreateMockAnything() + volume_class = self.driver_classes['volume'] + self.mox.StubOutWithMock(volume_class, 'attach') + + self.volume_api.create(self.context, 1, + '', '', image_id=image['id']).AndReturn(volume) + wait_func(self.context, 'fake-volume-id-2').AndReturn(None) + self.db_api.block_device_mapping_update( + self.context, 5, {'volume_id': 'fake-volume-id-2'}).AndReturn(None) + volume_class.attach(self.context, instance, self.volume_api, + self.virt_driver, self.db_api).AndReturn(None) + + self.mox.ReplayAll() + + test_bdm.attach(self.context, instance, self.volume_api, + self.virt_driver, self.db_api, wait_func) + self.assertEquals(test_bdm.volume_id, 'fake-volume-id-2') + + def test_image_attach_volume(self): + test_bdm = self.driver_classes['image']( + self.image_bdm) + + instance = {'id': 'fake_id', 'uuid': 'fake_uuid'} + + volume_class = self.driver_classes['volume'] + self.mox.StubOutWithMock(volume_class, 'attach') + + # Make sure theses are not called + self.mox.StubOutWithMock(self.volume_api, 'get_snapshot') + self.mox.StubOutWithMock(self.volume_api, 'create') + self.mox.StubOutWithMock(self.db_api, + 'block_device_mapping_update') + + volume_class.attach(self.context, instance, self.volume_api, + self.virt_driver, self.db_api).AndReturn(None) + self.mox.ReplayAll() + + test_bdm.attach(self.context, instance, self.volume_api, + self.virt_driver, self.db_api) + self.assertEquals(test_bdm.volume_id, 'fake-volume-id-2') + def test_convert_block_devices(self): converted = driver_block_device._convert_block_devices( self.driver_classes['volume'], diff --git a/nova/virt/block_device.py b/nova/virt/block_device.py index 7ee2e1dd71ff..4173406cb6e6 100644 --- a/nova/virt/block_device.py +++ b/nova/virt/block_device.py @@ -116,8 +116,17 @@ class DriverVolumeBlockDevice(DriverBlockDevice): 'disk_bus', 'boot_index']) _fields = _legacy_fields | _new_fields + _valid_source = 'volume' + _valid_destination = 'volume' + + # Override in subclasses if volume should be created from + # another source. + _source_id_field = None + def _transform(self, bdm): - if not bdm.get('source_type') == 'volume': + if not bdm.get('source_type') == self._valid_source\ + or not bdm.get('destination_type') == \ + self._valid_destination: raise _InvalidType # NOTE (ndipanov): Save it as an attribute as we will @@ -125,6 +134,9 @@ class DriverVolumeBlockDevice(DriverBlockDevice): self.volume_size = bdm.get('volume_size') self.volume_id = bdm.get('volume_id') + if self._source_id_field: + setattr(self, self._source_id_field, + bdm.get(self._source_id_field, None)) self.update( dict((k, v) for k, v in bdm.iteritems() if k in self._new_fields | set(['delete_on_termination'])) @@ -187,26 +199,9 @@ class DriverVolumeBlockDevice(DriverBlockDevice): class DriverSnapshotBlockDevice(DriverVolumeBlockDevice): - def _transform(self, bdm): - if not bdm.get('source_type') == 'snapshot': - raise _InvalidType - # NOTE (ndipanov): Save these as attributes as we will - # need them for attach() - self.volume_size = bdm.get('volume_size') - self.snapshot_id = bdm.get('snapshot_id') - self.volume_id = bdm.get('volume_id') - - self.update( - dict((k, v) for k, v in bdm.iteritems() - if k in self._new_fields | set(['delete_on_termination'])) - ) - self['mount_device'] = bdm.get('device_name') - try: - self['connection_info'] = jsonutils.loads( - bdm.get('connection_info')) - except TypeError: - self['connection_info'] = None + _valid_source = 'snapshot' + _source_id_field = 'snapshot_id' def attach(self, context, instance, volume_api, virt_driver, db_api=None, wait_func=None): @@ -229,6 +224,28 @@ class DriverSnapshotBlockDevice(DriverVolumeBlockDevice): db_api) +class DriverImageBlockDevice(DriverVolumeBlockDevice): + + _valid_source = 'image' + _source_id_field = 'image_id' + + def attach(self, context, instance, volume_api, virt_driver, + db_api=None, wait_func=None): + if not self.volume_id: + vol = volume_api.create(context, self.volume_size, + '', '', image_id=self.image_id) + if wait_func: + wait_func(context, vol['id']) + if db_api: + db_api.block_device_mapping_update(context, self.id, + {'volume_id': vol['id']}) + self.volume_id = vol['id'] + + super(DriverImageBlockDevice, self).attach(context, instance, + volume_api, virt_driver, + db_api) + + def _convert_block_devices(device_type, block_device_mapping): def _is_transformable(bdm): try: @@ -257,6 +274,9 @@ convert_volumes = functools.partial(_convert_block_devices, convert_snapshots = functools.partial(_convert_block_devices, DriverSnapshotBlockDevice) +convert_images = functools.partial(_convert_block_devices, + DriverImageBlockDevice) + def attach_block_devices(block_device_mapping, *attach_args, **attach_kwargs): map(operator.methodcaller('attach', *attach_args, **attach_kwargs),