From 88766d33a39e9b628f251b49d83c52e163dd0a58 Mon Sep 17 00:00:00 2001 From: zhangbailin Date: Sat, 30 Jun 2018 20:00:17 +0800 Subject: [PATCH] Add compute version 36 to support ``volume_type`` Add support specifying ``volume_type`` to storage backend when booting instance in compute version 36. bp: boot-instance-specific-storage-backend Change-Id: Icc301230fe7c8e3ebbbcc7f4a807e562db7f93e3 --- nova/objects/service.py | 5 +- nova/tests/unit/virt/test_block_device.py | 193 ++++++++++++++++++++-- nova/virt/block_device.py | 11 +- 3 files changed, 190 insertions(+), 19 deletions(-) diff --git a/nova/objects/service.py b/nova/objects/service.py index 7903939aab6d..f761dd331cf4 100644 --- a/nova/objects/service.py +++ b/nova/objects/service.py @@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__) # NOTE(danms): This is the global service version counter -SERVICE_VERSION = 35 +SERVICE_VERSION = 36 # NOTE(danms): This is our SERVICE_VERSION history. The idea is that any @@ -146,6 +146,9 @@ SERVICE_VERSION_HISTORY = ( # Version 35: Indicates that nova-compute supports live migration with # ports bound early on the destination host using VIFMigrateData. {'compute_rpc': '5.0'}, + # Version 36: Indicates that nova-compute supports specifying volume + # type when booting a volume-backed server. + {'compute_rpc': '5.0'}, ) diff --git a/nova/tests/unit/virt/test_block_device.py b/nova/tests/unit/virt/test_block_device.py index 6ae0f5db0b46..d4085bc71d84 100644 --- a/nova/tests/unit/virt/test_block_device.py +++ b/nova/tests/unit/virt/test_block_device.py @@ -112,7 +112,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): 'disk_bus': 'scsi', 'device_type': 'disk', 'guest_format': 'ext4', - 'boot_index': 0} + 'boot_index': 0, + 'volume_type': None} volume_legacy_driver_bdm = { 'mount_device': '/dev/sda1', @@ -131,7 +132,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): 'connection_info': '{"fake": "connection_info"}', 'snapshot_id': 'fake-snapshot-id-1', 'volume_id': 'fake-volume-id-2', - 'boot_index': -1}) + 'boot_index': -1, + 'volume_type': None}) volsnapshot_driver_bdm = { 'mount_device': '/dev/sda2', @@ -140,7 +142,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): 'disk_bus': 'scsi', 'device_type': 'disk', 'guest_format': None, - 'boot_index': -1} + 'boot_index': -1, + 'volume_type': None} volsnapshot_legacy_driver_bdm = { 'mount_device': '/dev/sda2', @@ -159,7 +162,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): 'connection_info': '{"fake": "connection_info"}', 'image_id': 'fake-image-id-1', 'volume_id': 'fake-volume-id-2', - 'boot_index': -1}) + 'boot_index': -1, + 'volume_type': None}) volimage_driver_bdm = { 'mount_device': '/dev/sda2', @@ -168,7 +172,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): 'disk_bus': 'scsi', 'device_type': 'disk', 'guest_format': None, - 'boot_index': -1} + 'boot_index': -1, + 'volume_type': None} volimage_legacy_driver_bdm = { 'mount_device': '/dev/sda2', @@ -187,7 +192,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): 'connection_info': '{"fake": "connection_info"}', 'snapshot_id': 'fake-snapshot-id-1', 'volume_id': 'fake-volume-id-2', - 'boot_index': -1}) + 'boot_index': -1, + 'volume_type': None}) volblank_driver_bdm = { 'mount_device': '/dev/sda2', @@ -196,7 +202,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): 'disk_bus': 'scsi', 'device_type': 'disk', 'guest_format': None, - 'boot_index': -1} + 'boot_index': -1, + 'volume_type': None} volblank_legacy_driver_bdm = { 'mount_device': '/dev/sda2', @@ -851,7 +858,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): self.volume_api.get_snapshot(self.context, 'fake-snapshot-id-1').AndReturn(snapshot) self.volume_api.create(self.context, 3, '', '', snapshot, - availability_zone=None).AndReturn(volume) + availability_zone=None, + volume_type=None).AndReturn(volume) wait_func(self.context, 'fake-volume-id-2').AndReturn(None) instance, expected_conn_info = self._test_volume_attach( test_bdm, no_volume_snapshot, volume) @@ -883,7 +891,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): self.volume_api.get_snapshot(self.context, 'fake-snapshot-id-1').AndReturn(snapshot) self.volume_api.create(self.context, 3, '', '', snapshot, - availability_zone='test-az').AndReturn(volume) + availability_zone='test-az', + volume_type=None).AndReturn(volume) wait_func(self.context, 'fake-volume-id-2').AndReturn(None) instance, expected_conn_info = self._test_volume_attach( test_bdm, no_volume_snapshot, volume, @@ -930,7 +939,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): vol_get_snap.assert_called_once_with( self.context, 'fake-snapshot-id-1') vol_create.assert_called_once_with( - self.context, 3, '', '', snapshot, availability_zone=None) + self.context, 3, '', '', snapshot, availability_zone=None, + volume_type=None) vol_delete.assert_called_once_with(self.context, volume['id']) def test_snapshot_attach_volume(self): @@ -970,7 +980,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): wait_func = self.mox.CreateMockAnything() self.volume_api.create(self.context, 1, '', '', image_id=image['id'], - availability_zone=None).AndReturn(volume) + availability_zone=None, + volume_type=None).AndReturn(volume) wait_func(self.context, 'fake-volume-id-2').AndReturn(None) instance, expected_conn_info = self._test_volume_attach( test_bdm, no_volume_image, volume) @@ -999,7 +1010,8 @@ class TestDriverBlockDevice(test.NoDBTestCase): wait_func = self.mox.CreateMockAnything() self.volume_api.create(self.context, 1, '', '', image_id=image['id'], - availability_zone='test-az').AndReturn(volume) + availability_zone='test-az', + volume_type=None).AndReturn(volume) wait_func(self.context, 'fake-volume-id-2').AndReturn(None) instance, expected_conn_info = self._test_volume_attach( test_bdm, no_volume_image, volume, @@ -1042,7 +1054,7 @@ class TestDriverBlockDevice(test.NoDBTestCase): vol_create.assert_called_once_with( self.context, 1, '', '', image_id=image['id'], - availability_zone=None) + availability_zone=None, volume_type=None) vol_delete.assert_called_once_with(self.context, volume['id']) def test_image_attach_volume(self): @@ -1097,7 +1109,7 @@ class TestDriverBlockDevice(test.NoDBTestCase): vol_create.assert_called_once_with( self.context, test_bdm.volume_size, '%s-blank-vol' % uuids.uuid, - '', availability_zone=None) + '', volume_type=None, availability_zone=None) vol_delete.assert_called_once_with( self.context, volume['id']) @@ -1123,7 +1135,7 @@ class TestDriverBlockDevice(test.NoDBTestCase): vol_create.assert_called_once_with( self.context, test_bdm.volume_size, '%s-blank-vol' % uuids.uuid, - '', availability_zone=None) + '', volume_type=None, availability_zone=None) vol_attach.assert_called_once_with(self.context, instance, self.volume_api, self.virt_driver) @@ -1154,7 +1166,7 @@ class TestDriverBlockDevice(test.NoDBTestCase): vol_create.assert_called_once_with( self.context, test_bdm.volume_size, '%s-blank-vol' % uuids.uuid, - '', availability_zone='test-az') + '', volume_type=None, availability_zone='test-az') vol_attach.assert_called_once_with(self.context, instance, self.volume_api, self.virt_driver) @@ -1319,6 +1331,155 @@ class TestDriverBlockDevice(test.NoDBTestCase): self.assertEqual(set(['uuid', 'is_volume', 'B', 'C', 'E']), E(bdm)._proxy_as_attr) + def _test_boot_from_volume_source_blank_volume_type( + self, bdm, expected_volume_type): + self.flags(cross_az_attach=False, group='cinder') + test_bdm = self.driver_classes['volblank'](bdm) + updates = {'uuid': uuids.uuid, 'availability_zone': 'test-az'} + instance = fake_instance.fake_instance_obj(mock.sentinel.ctx, + **updates) + volume_class = self.driver_classes['volume'] + volume = {'id': 'fake-volume-id-2', + 'display_name': '%s-blank-vol' % uuids.uuid} + + with mock.patch.object(self.volume_api, 'create', + return_value=volume) as vol_create: + with mock.patch.object(volume_class, 'attach') as vol_attach: + test_bdm.attach(self.context, instance, self.volume_api, + self.virt_driver) + + vol_create.assert_called_once_with( + self.context, test_bdm.volume_size, + '%s-blank-vol' % uuids.uuid, '', + volume_type=expected_volume_type, + availability_zone='test-az') + vol_attach.assert_called_once_with( + self.context, instance, self.volume_api, self.virt_driver) + self.assertEqual('fake-volume-id-2', test_bdm.volume_id) + + def test_boot_from_volume_source_blank_with_unset_volume_type(self): + """Tests the scenario that the BlockDeviceMapping.volume_type field + is unset for RPC compatibility to an older compute. + """ + no_blank_volume = self.volblank_bdm_dict.copy() + no_blank_volume['volume_id'] = None + bdm = fake_block_device.fake_bdm_object(self.context, no_blank_volume) + delattr(bdm, 'volume_type') + self.assertNotIn('volume_type', bdm) + self._test_boot_from_volume_source_blank_volume_type(bdm, None) + + def test_boot_from_volume_source_blank_with_volume_type(self): + # Tests that the blank volume created specifies the volume type. + no_blank_volume = self.volblank_bdm_dict.copy() + no_blank_volume['volume_id'] = None + no_blank_volume['volume_type'] = 'fake-lvm-1' + bdm = fake_block_device.fake_bdm_object(self.context, no_blank_volume) + self._test_boot_from_volume_source_blank_volume_type(bdm, 'fake-lvm-1') + + def _test_boot_from_volume_source_image_volume_type( + self, bdm, expected_volume_type): + self.flags(cross_az_attach=False, group='cinder') + test_bdm = self.driver_classes['volimage'](bdm) + + updates = {'uuid': uuids.uuid, 'availability_zone': 'test-az'} + instance = fake_instance.fake_instance_obj(mock.sentinel.ctx, + **updates) + volume_class = self.driver_classes['volume'] + image = {'id': 'fake-image-id-1'} + volume = {'id': 'fake-volume-id-2', + 'display_name': 'fake-image-vol'} + + with mock.patch.object(self.volume_api, 'create', + return_value=volume) as vol_create: + with mock.patch.object(volume_class, 'attach') as vol_attach: + test_bdm.attach(self.context, instance, self.volume_api, + self.virt_driver) + + vol_create.assert_called_once_with( + self.context, test_bdm.volume_size, + '', '', image_id=image['id'], + volume_type=expected_volume_type, + availability_zone='test-az') + vol_attach.assert_called_once_with( + self.context, instance, self.volume_api, self.virt_driver) + self.assertEqual('fake-volume-id-2', test_bdm.volume_id) + + def test_boot_from_volume_source_image_with_unset_volume_type(self): + """Tests the scenario that the BlockDeviceMapping.volume_type field + is unset for RPC compatibility to an older compute. + """ + no_volume_image = self.volimage_bdm_dict.copy() + no_volume_image['volume_id'] = None + bdm = fake_block_device.fake_bdm_object(self.context, no_volume_image) + delattr(bdm, 'volume_type') + self.assertNotIn('volume_type', bdm) + self._test_boot_from_volume_source_image_volume_type(bdm, None) + + def test_boot_from_volume_source_image_with_volume_type(self): + # Tests that the volume created from the image specifies the volume + # type. + no_volume_image = self.volimage_bdm_dict.copy() + no_volume_image['volume_id'] = None + no_volume_image['volume_type'] = 'fake-lvm-1' + bdm = fake_block_device.fake_bdm_object(self.context, no_volume_image) + self._test_boot_from_volume_source_image_volume_type(bdm, 'fake-lvm-1') + + def _test_boot_from_volume_source_snapshot_volume_type( + self, bdm, expected_volume_type): + self.flags(cross_az_attach=False, group='cinder') + test_bdm = self.driver_classes['volsnapshot'](bdm) + + snapshot = {'id': 'fake-snapshot-id-1', + 'attach_status': 'detached'} + + updates = {'uuid': uuids.uuid, 'availability_zone': 'test-az'} + instance = fake_instance.fake_instance_obj(mock.sentinel.ctx, + **updates) + volume_class = self.driver_classes['volume'] + volume = {'id': 'fake-volume-id-2', + 'display_name': 'fake-snapshot-vol'} + + with test.nested( + mock.patch.object(self.volume_api, 'create', return_value=volume), + mock.patch.object(self.volume_api, 'get_snapshot', + return_value=snapshot), + mock.patch.object(volume_class, 'attach') + ) as ( + vol_create, vol_get_snap, vol_attach + ): + test_bdm.attach(self.context, instance, self.volume_api, + self.virt_driver) + + vol_create.assert_called_once_with( + self.context, test_bdm.volume_size, '', '', snapshot, + volume_type=expected_volume_type, availability_zone='test-az') + vol_attach.assert_called_once_with( + self.context, instance, self.volume_api, self.virt_driver) + self.assertEqual('fake-volume-id-2', test_bdm.volume_id) + + def test_boot_from_volume_source_snapshot_with_unset_volume_type(self): + """Tests the scenario that the BlockDeviceMapping.volume_type field + is unset for RPC compatibility to an older compute. + """ + no_volume_snapshot = self.volsnapshot_bdm_dict.copy() + no_volume_snapshot['volume_id'] = None + bdm = fake_block_device.fake_bdm_object( + self.context, no_volume_snapshot) + delattr(bdm, 'volume_type') + self.assertNotIn('volume_type', bdm) + self._test_boot_from_volume_source_snapshot_volume_type(bdm, None) + + def test_boot_from_volume_source_snapshot_with_volume_type(self): + # Tests that the volume created from the snapshot specifies the volume + # type. + no_volume_snapshot = self.volsnapshot_bdm_dict.copy() + no_volume_snapshot['volume_id'] = None + no_volume_snapshot['volume_type'] = 'fake-lvm-1' + bdm = fake_block_device.fake_bdm_object( + self.context, no_volume_snapshot) + self._test_boot_from_volume_source_snapshot_volume_type( + bdm, 'fake-lvm-1') + class TestDriverBlockDeviceNewFlow(TestDriverBlockDevice): """Virt block_device tests for the Cinder 3.44 volume attach flow diff --git a/nova/virt/block_device.py b/nova/virt/block_device.py index f224398d2251..8abbdb40f2b0 100644 --- a/nova/virt/block_device.py +++ b/nova/virt/block_device.py @@ -265,7 +265,7 @@ class DriverVolumeBlockDevice(DriverBlockDevice): _valid_source = 'volume' _valid_destination = 'volume' - _proxy_as_attr_inherited = set(['volume_size', 'volume_id']) + _proxy_as_attr_inherited = set(['volume_size', 'volume_id', 'volume_type']) _update_on_save = {'disk_bus': None, 'device_name': 'mount_device', 'device_type': None} @@ -286,6 +286,10 @@ class DriverVolumeBlockDevice(DriverBlockDevice): self._bdm_obj.connection_info) except TypeError: self['connection_info'] = None + # volume_type might not be set on the internal bdm object so default + # to None if not set + self['volume_type'] = ( + self.volume_type if 'volume_type' in self._bdm_obj else None) def _preserve_multipath_id(self, connection_info): if self['connection_info'] and 'data' in self['connection_info']: @@ -710,7 +714,8 @@ class DriverVolSnapshotBlockDevice(DriverVolumeBlockDevice): snapshot = volume_api.get_snapshot(context, self.snapshot_id) vol = volume_api.create(context, self.volume_size, '', '', - snapshot, availability_zone=av_zone) + snapshot, volume_type=self.volume_type, + availability_zone=av_zone) if wait_func: self._call_wait_func(context, wait_func, volume_api, vol['id']) @@ -735,6 +740,7 @@ class DriverVolImageBlockDevice(DriverVolumeBlockDevice): av_zone = _get_volume_create_az_value(instance) vol = volume_api.create(context, self.volume_size, '', '', image_id=self.image_id, + volume_type=self.volume_type, availability_zone=av_zone) if wait_func: self._call_wait_func(context, wait_func, volume_api, vol['id']) @@ -759,6 +765,7 @@ class DriverVolBlankBlockDevice(DriverVolumeBlockDevice): vol_name = instance.uuid + '-blank-vol' av_zone = _get_volume_create_az_value(instance) vol = volume_api.create(context, self.volume_size, vol_name, '', + volume_type=self.volume_type, availability_zone=av_zone) if wait_func: self._call_wait_func(context, wait_func, volume_api, vol['id'])