Refactor how BDMs are handled when booting
This patch refactors how block devices are handled when booting VMs. Also it introduces new block device mapping format in the run_instance manager method of the compute service. The refactoring is done by creating a simple hierarchy of DriverBlockDevice classes that are meant to: 1) Provide a common place to keep information about the data that is kept for each block device type 2) Encapsulate the logic of "attaching" the device from the compute service point of view (driver will still need to do the actual work). Currently we have special subclasses for ephemeral, swap, volume and snapshot devices. These classes also make it possible to easily provide the drivers that will require old block device mapping format with it by overriding the legacy() method. Another advantage that this refactoring brings is that adding additional block device types, like Image block devices (coming soon in a subsequent patch) that can have destination type of either a hypervisor image or a volume will be just a matter of extending the base class and redefining attach to do the work of creating the volume if needed. blueprint: improve-block-device-handling Change-Id: I5b9c3e2d959c602fa22f49db681da918ae0adcea
This commit is contained in:
		@@ -370,6 +370,21 @@ def is_swap_or_ephemeral(device_name):
 | 
				
			|||||||
            (device_name == 'swap' or is_ephemeral(device_name)))
 | 
					            (device_name == 'swap' or is_ephemeral(device_name)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def new_format_is_swap(bdm):
 | 
				
			||||||
 | 
					    if (bdm.get('source_type') == 'blank' and
 | 
				
			||||||
 | 
					            bdm.get('destination_type') == 'local' and
 | 
				
			||||||
 | 
					            bdm.get('guest_format') == 'swap'):
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def new_format_is_ephemeral(bdm):
 | 
				
			||||||
 | 
					    if (bdm.get('source_type') == 'blank' and not
 | 
				
			||||||
 | 
					            new_format_is_swap(bdm)):
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def mappings_prepend_dev(mappings):
 | 
					def mappings_prepend_dev(mappings):
 | 
				
			||||||
    """Prepend '/dev/' to 'device' entry of swap/ephemeral virtual type."""
 | 
					    """Prepend '/dev/' to 'device' entry of swap/ephemeral virtual type."""
 | 
				
			||||||
    for m in mappings:
 | 
					    for m in mappings:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,7 +40,6 @@ import uuid
 | 
				
			|||||||
from eventlet import greenthread
 | 
					from eventlet import greenthread
 | 
				
			||||||
from oslo.config import cfg
 | 
					from oslo.config import cfg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from nova import block_device
 | 
					 | 
				
			||||||
from nova.cells import rpcapi as cells_rpcapi
 | 
					from nova.cells import rpcapi as cells_rpcapi
 | 
				
			||||||
from nova.cloudpipe import pipelib
 | 
					from nova.cloudpipe import pipelib
 | 
				
			||||||
from nova import compute
 | 
					from nova import compute
 | 
				
			||||||
@@ -76,6 +75,7 @@ from nova import paths
 | 
				
			|||||||
from nova import quota
 | 
					from nova import quota
 | 
				
			||||||
from nova import safe_utils
 | 
					from nova import safe_utils
 | 
				
			||||||
from nova import utils
 | 
					from nova import utils
 | 
				
			||||||
 | 
					from nova.virt import block_device as driver_block_device
 | 
				
			||||||
from nova.virt import driver
 | 
					from nova.virt import driver
 | 
				
			||||||
from nova.virt import event as virtevent
 | 
					from nova.virt import event as virtevent
 | 
				
			||||||
from nova.virt import storage_users
 | 
					from nova.virt import storage_users
 | 
				
			||||||
@@ -906,70 +906,6 @@ class ComputeManager(manager.SchedulerDependentManager):
 | 
				
			|||||||
                                         seconds=int(time.time() - start),
 | 
					                                         seconds=int(time.time() - start),
 | 
				
			||||||
                                         attempts=attempts)
 | 
					                                         attempts=attempts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _setup_block_device_mapping(self, context, instance, bdms):
 | 
					 | 
				
			||||||
        """setup volumes for block device mapping."""
 | 
					 | 
				
			||||||
        block_device_mapping = []
 | 
					 | 
				
			||||||
        swap = None
 | 
					 | 
				
			||||||
        ephemerals = []
 | 
					 | 
				
			||||||
        for bdm in bdms:
 | 
					 | 
				
			||||||
            LOG.debug(_('Setting up bdm %s'), bdm, instance=instance)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if bdm['no_device']:
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            if bdm['virtual_name']:
 | 
					 | 
				
			||||||
                virtual_name = bdm['virtual_name']
 | 
					 | 
				
			||||||
                device_name = bdm['device_name']
 | 
					 | 
				
			||||||
                assert block_device.is_swap_or_ephemeral(virtual_name)
 | 
					 | 
				
			||||||
                if virtual_name == 'swap':
 | 
					 | 
				
			||||||
                    swap = {'device_name': device_name,
 | 
					 | 
				
			||||||
                            'swap_size': bdm['volume_size']}
 | 
					 | 
				
			||||||
                elif block_device.is_ephemeral(virtual_name):
 | 
					 | 
				
			||||||
                    eph = {'num': block_device.ephemeral_num(virtual_name),
 | 
					 | 
				
			||||||
                           'virtual_name': virtual_name,
 | 
					 | 
				
			||||||
                           'device_name': device_name,
 | 
					 | 
				
			||||||
                           'size': bdm['volume_size']}
 | 
					 | 
				
			||||||
                    ephemerals.append(eph)
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if ((bdm['snapshot_id'] is not None) and
 | 
					 | 
				
			||||||
                    (bdm['volume_id'] is None)):
 | 
					 | 
				
			||||||
                # TODO(yamahata): default name and description
 | 
					 | 
				
			||||||
                snapshot = self.volume_api.get_snapshot(context,
 | 
					 | 
				
			||||||
                                                        bdm['snapshot_id'])
 | 
					 | 
				
			||||||
                vol = self.volume_api.create(context, bdm['volume_size'],
 | 
					 | 
				
			||||||
                                             '', '', snapshot)
 | 
					 | 
				
			||||||
                self._await_block_device_map_created(context, vol['id'])
 | 
					 | 
				
			||||||
                self.conductor_api.block_device_mapping_update(
 | 
					 | 
				
			||||||
                    context, bdm['id'], {'volume_id': vol['id']})
 | 
					 | 
				
			||||||
                bdm['volume_id'] = vol['id']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if bdm['volume_id'] is not None:
 | 
					 | 
				
			||||||
                volume = self.volume_api.get(context, bdm['volume_id'])
 | 
					 | 
				
			||||||
                self.volume_api.check_attach(context, volume,
 | 
					 | 
				
			||||||
                                             instance=instance)
 | 
					 | 
				
			||||||
                cinfo = self._attach_volume_boot(context,
 | 
					 | 
				
			||||||
                                                 instance,
 | 
					 | 
				
			||||||
                                                 volume,
 | 
					 | 
				
			||||||
                                                 bdm['device_name'])
 | 
					 | 
				
			||||||
                if 'serial' not in cinfo:
 | 
					 | 
				
			||||||
                    cinfo['serial'] = bdm['volume_id']
 | 
					 | 
				
			||||||
                self.conductor_api.block_device_mapping_update(
 | 
					 | 
				
			||||||
                        context, bdm['id'],
 | 
					 | 
				
			||||||
                        {'connection_info': jsonutils.dumps(cinfo)})
 | 
					 | 
				
			||||||
                bdmap = {'connection_info': cinfo,
 | 
					 | 
				
			||||||
                         'mount_device': bdm['device_name'],
 | 
					 | 
				
			||||||
                         'delete_on_termination': bdm['delete_on_termination']}
 | 
					 | 
				
			||||||
                block_device_mapping.append(bdmap)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        block_device_info = {
 | 
					 | 
				
			||||||
            'root_device_name': instance['root_device_name'],
 | 
					 | 
				
			||||||
            'swap': swap,
 | 
					 | 
				
			||||||
            'ephemerals': ephemerals,
 | 
					 | 
				
			||||||
            'block_device_mapping': block_device_mapping
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return block_device_info
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _decode_files(self, injected_files):
 | 
					    def _decode_files(self, injected_files):
 | 
				
			||||||
        """Base64 decode the list of files to inject."""
 | 
					        """Base64 decode the list of files to inject."""
 | 
				
			||||||
        if not injected_files:
 | 
					        if not injected_files:
 | 
				
			||||||
@@ -1068,7 +1004,7 @@ class ComputeManager(manager.SchedulerDependentManager):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        network_info = None
 | 
					        network_info = None
 | 
				
			||||||
        bdms = self.conductor_api.block_device_mapping_get_all_by_instance(
 | 
					        bdms = self.conductor_api.block_device_mapping_get_all_by_instance(
 | 
				
			||||||
            context, instance)
 | 
					            context, instance, legacy=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # b64 decode the files to inject:
 | 
					        # b64 decode the files to inject:
 | 
				
			||||||
        injected_files_orig = injected_files
 | 
					        injected_files_orig = injected_files
 | 
				
			||||||
@@ -1333,7 +1269,29 @@ class ComputeManager(manager.SchedulerDependentManager):
 | 
				
			|||||||
    def _prep_block_device(self, context, instance, bdms):
 | 
					    def _prep_block_device(self, context, instance, bdms):
 | 
				
			||||||
        """Set up the block device for an instance with error logging."""
 | 
					        """Set up the block device for an instance with error logging."""
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            return self._setup_block_device_mapping(context, instance, bdms)
 | 
					            block_device_info = {
 | 
				
			||||||
 | 
					                'root_device_name': instance['root_device_name'],
 | 
				
			||||||
 | 
					                'swap': driver_block_device.get_swap(
 | 
				
			||||||
 | 
					                    driver_block_device.legacy_block_devices(
 | 
				
			||||||
 | 
					                        driver_block_device.convert_swap(bdms))),
 | 
				
			||||||
 | 
					                'ephemerals': driver_block_device.legacy_block_devices(
 | 
				
			||||||
 | 
					                    driver_block_device.convert_ephemerals(bdms)),
 | 
				
			||||||
 | 
					                'block_device_mapping':
 | 
				
			||||||
 | 
					                    (driver_block_device.legacy_block_devices(
 | 
				
			||||||
 | 
					                            driver_block_device.attach_block_devices(
 | 
				
			||||||
 | 
					                                driver_block_device.convert_volumes(bdms),
 | 
				
			||||||
 | 
					                                context, instance, self.volume_api,
 | 
				
			||||||
 | 
					                                self.driver, self.conductor_api)) +
 | 
				
			||||||
 | 
					                     driver_block_device.legacy_block_devices(
 | 
				
			||||||
 | 
					                            driver_block_device.attach_block_devices(
 | 
				
			||||||
 | 
					                                driver_block_device.convert_snapshots(bdms),
 | 
				
			||||||
 | 
					                                context, instance, self.volume_api,
 | 
				
			||||||
 | 
					                                self.driver, self.conductor_api,
 | 
				
			||||||
 | 
					                                self._await_block_device_map_created)))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return block_device_info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        except Exception:
 | 
					        except Exception:
 | 
				
			||||||
            with excutils.save_and_reraise_exception():
 | 
					            with excutils.save_and_reraise_exception():
 | 
				
			||||||
                LOG.exception(_('Instance failed block device setup'),
 | 
					                LOG.exception(_('Instance failed block device setup'),
 | 
				
			||||||
@@ -3382,25 +3340,6 @@ class ComputeManager(manager.SchedulerDependentManager):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return console_info['port'] == port
 | 
					        return console_info['port'] == port
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _attach_volume_boot(self, context, instance, volume, mountpoint):
 | 
					 | 
				
			||||||
        """Attach a volume to an instance at boot time. So actual attach
 | 
					 | 
				
			||||||
        is done by instance creation.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        instance_id = instance['id']
 | 
					 | 
				
			||||||
        instance_uuid = instance['uuid']
 | 
					 | 
				
			||||||
        volume_id = volume['id']
 | 
					 | 
				
			||||||
        context = context.elevated()
 | 
					 | 
				
			||||||
        LOG.audit(_('Booting with volume %(volume_id)s at %(mountpoint)s'),
 | 
					 | 
				
			||||||
                  {'volume_id': volume_id, 'mountpoint': mountpoint},
 | 
					 | 
				
			||||||
                  context=context, instance=instance)
 | 
					 | 
				
			||||||
        connector = self.driver.get_volume_connector(instance)
 | 
					 | 
				
			||||||
        connection_info = self.volume_api.initialize_connection(context,
 | 
					 | 
				
			||||||
                                                                volume_id,
 | 
					 | 
				
			||||||
                                                                connector)
 | 
					 | 
				
			||||||
        self.volume_api.attach(context, volume_id, instance_uuid, mountpoint)
 | 
					 | 
				
			||||||
        return connection_info
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
 | 
					    @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
 | 
				
			||||||
    @reverts_task_state
 | 
					    @reverts_task_state
 | 
				
			||||||
    @wrap_instance_fault
 | 
					    @wrap_instance_fault
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -419,17 +419,19 @@ class ComputeVolumeTestCase(BaseTestCase):
 | 
				
			|||||||
        self.assertEqual(attempts, 3)
 | 
					        self.assertEqual(attempts, 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_boot_volume_serial(self):
 | 
					    def test_boot_volume_serial(self):
 | 
				
			||||||
        block_device_mapping = [{
 | 
					        block_device_mapping = [
 | 
				
			||||||
 | 
					        block_device.BlockDeviceDict({
 | 
				
			||||||
            'id': 1,
 | 
					            'id': 1,
 | 
				
			||||||
            'no_device': None,
 | 
					            'no_device': None,
 | 
				
			||||||
            'virtual_name': None,
 | 
					            'source_type': 'volume',
 | 
				
			||||||
 | 
					            'destination_type': 'volume',
 | 
				
			||||||
            'snapshot_id': None,
 | 
					            'snapshot_id': None,
 | 
				
			||||||
            'volume_id': self.volume_id,
 | 
					            'volume_id': self.volume_id,
 | 
				
			||||||
            'device_name': '/dev/vdb',
 | 
					            'device_name': '/dev/vdb',
 | 
				
			||||||
            'delete_on_termination': False,
 | 
					            'delete_on_termination': False,
 | 
				
			||||||
        }]
 | 
					        })]
 | 
				
			||||||
        self.compute._setup_block_device_mapping(self.context, self.instance,
 | 
					        self.compute._prep_block_device(self.context, self.instance,
 | 
				
			||||||
                                                 block_device_mapping)
 | 
					                                        block_device_mapping)
 | 
				
			||||||
        self.assertEqual(self.cinfo.get('serial'), self.volume_id)
 | 
					        self.assertEqual(self.cinfo.get('serial'), self.volume_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_boot_volume_metadata(self):
 | 
					    def test_boot_volume_metadata(self):
 | 
				
			||||||
@@ -1204,7 +1206,7 @@ class ComputeTestCase(BaseTestCase):
 | 
				
			|||||||
        def fake(*args, **kwargs):
 | 
					        def fake(*args, **kwargs):
 | 
				
			||||||
            raise test.TestingException()
 | 
					            raise test.TestingException()
 | 
				
			||||||
        self.stubs.Set(nova.compute.manager.ComputeManager,
 | 
					        self.stubs.Set(nova.compute.manager.ComputeManager,
 | 
				
			||||||
                       '_setup_block_device_mapping', fake)
 | 
					                       '_prep_block_device', fake)
 | 
				
			||||||
        instance = self._create_instance()
 | 
					        instance = self._create_instance()
 | 
				
			||||||
        self.assertRaises(test.TestingException, self.compute.run_instance,
 | 
					        self.assertRaises(test.TestingException, self.compute.run_instance,
 | 
				
			||||||
                          self.context, instance=instance)
 | 
					                          self.context, instance=instance)
 | 
				
			||||||
@@ -2771,8 +2773,8 @@ class ComputeTestCase(BaseTestCase):
 | 
				
			|||||||
        # When a spawn fails the network must be deallocated.
 | 
					        # When a spawn fails the network must be deallocated.
 | 
				
			||||||
        instance = jsonutils.to_primitive(self._create_fake_instance())
 | 
					        instance = jsonutils.to_primitive(self._create_fake_instance())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.mox.StubOutWithMock(self.compute, "_setup_block_device_mapping")
 | 
					        self.mox.StubOutWithMock(self.compute, "_prep_block_device")
 | 
				
			||||||
        self.compute._setup_block_device_mapping(
 | 
					        self.compute._prep_block_device(
 | 
				
			||||||
                mox.IgnoreArg(), mox.IgnoreArg(),
 | 
					                mox.IgnoreArg(), mox.IgnoreArg(),
 | 
				
			||||||
                mox.IgnoreArg()).AndRaise(rpc.common.RemoteError('', '', ''))
 | 
					                mox.IgnoreArg()).AndRaise(rpc.common.RemoteError('', '', ''))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -8981,6 +8983,7 @@ class EvacuateHostTestCase(BaseTestCase):
 | 
				
			|||||||
    def test_rebuild_on_host_with_volumes(self):
 | 
					    def test_rebuild_on_host_with_volumes(self):
 | 
				
			||||||
        """Confirm evacuate scenario reconnects volumes."""
 | 
					        """Confirm evacuate scenario reconnects volumes."""
 | 
				
			||||||
        values = {'instance_uuid': self.inst_ref['uuid'],
 | 
					        values = {'instance_uuid': self.inst_ref['uuid'],
 | 
				
			||||||
 | 
					                  'source_type': 'volume',
 | 
				
			||||||
                  'device_name': '/dev/vdc',
 | 
					                  'device_name': '/dev/vdc',
 | 
				
			||||||
                  'delete_on_termination': False,
 | 
					                  'delete_on_termination': False,
 | 
				
			||||||
                  'volume_id': 'fake_volume_id'}
 | 
					                  'volume_id': 'fake_volume_id'}
 | 
				
			||||||
@@ -9007,10 +9010,10 @@ class EvacuateHostTestCase(BaseTestCase):
 | 
				
			|||||||
        self.mox.StubOutWithMock(self.compute.volume_api, 'detach')
 | 
					        self.mox.StubOutWithMock(self.compute.volume_api, 'detach')
 | 
				
			||||||
        self.compute.volume_api.detach(mox.IsA(self.context), mox.IgnoreArg())
 | 
					        self.compute.volume_api.detach(mox.IsA(self.context), mox.IgnoreArg())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.mox.StubOutWithMock(self.compute, '_setup_block_device_mapping')
 | 
					        self.mox.StubOutWithMock(self.compute, '_prep_block_device')
 | 
				
			||||||
        self.compute._setup_block_device_mapping(mox.IsA(self.context),
 | 
					        self.compute._prep_block_device(mox.IsA(self.context),
 | 
				
			||||||
                                                 mox.IsA(self.inst_ref),
 | 
					                                        mox.IsA(self.inst_ref),
 | 
				
			||||||
                                                 mox.IgnoreArg())
 | 
					                                        mox.IgnoreArg())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.stubs.Set(self.compute.driver, 'instance_on_disk', lambda x: True)
 | 
					        self.stubs.Set(self.compute.driver, 'instance_on_disk', lambda x: True)
 | 
				
			||||||
        self.mox.ReplayAll()
 | 
					        self.mox.ReplayAll()
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										331
									
								
								nova/tests/virt/test_block_device.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								nova/tests/virt/test_block_device.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,331 @@
 | 
				
			|||||||
 | 
					# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# All Rights Reserved.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
				
			||||||
 | 
					#    not use this file except in compliance with the License. You may obtain
 | 
				
			||||||
 | 
					#    a copy of the License at
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#         http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#    Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
				
			||||||
 | 
					#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
				
			||||||
 | 
					#    License for the specific language governing permissions and limitations
 | 
				
			||||||
 | 
					#    under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from nova import block_device
 | 
				
			||||||
 | 
					from nova.conductor import api as conductor_api
 | 
				
			||||||
 | 
					from nova import context
 | 
				
			||||||
 | 
					from nova.openstack.common import jsonutils
 | 
				
			||||||
 | 
					from nova import test
 | 
				
			||||||
 | 
					from nova.tests import matchers
 | 
				
			||||||
 | 
					from nova.virt import block_device as driver_block_device
 | 
				
			||||||
 | 
					from nova.virt import driver
 | 
				
			||||||
 | 
					from nova.volume import cinder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestDriverBlockDevice(test.TestCase):
 | 
				
			||||||
 | 
					    driver_classes = {
 | 
				
			||||||
 | 
					        'swap': driver_block_device.DriverSwapBlockDevice,
 | 
				
			||||||
 | 
					        'ephemeral': driver_block_device.DriverEphemeralBlockDevice,
 | 
				
			||||||
 | 
					        'volume': driver_block_device.DriverVolumeBlockDevice,
 | 
				
			||||||
 | 
					        'snapshot': driver_block_device.DriverSnapshotBlockDevice
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    swap_bdm = block_device.BlockDeviceDict(
 | 
				
			||||||
 | 
					        {'id': 1, 'instance_uuid': 'fake-instance',
 | 
				
			||||||
 | 
					         'device_name': '/dev/sdb1',
 | 
				
			||||||
 | 
					         'source_type': 'blank',
 | 
				
			||||||
 | 
					         'destination_type': 'local',
 | 
				
			||||||
 | 
					         'delete_on_termination': True,
 | 
				
			||||||
 | 
					         'guest_format': 'swap',
 | 
				
			||||||
 | 
					         'disk_bus': 'scsi',
 | 
				
			||||||
 | 
					         'volume_size': 2,
 | 
				
			||||||
 | 
					         'boot_index': -1})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    swap_driver_bdm = {
 | 
				
			||||||
 | 
					        'device_name': '/dev/sdb1',
 | 
				
			||||||
 | 
					        'swap_size': 2,
 | 
				
			||||||
 | 
					        'disk_bus': 'scsi'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    swap_legacy_driver_bdm = {
 | 
				
			||||||
 | 
					        'device_name': '/dev/sdb1',
 | 
				
			||||||
 | 
					        'swap_size': 2}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ephemeral_bdm = block_device.BlockDeviceDict(
 | 
				
			||||||
 | 
					        {'id': 2, 'instance_uuid': 'fake-instance',
 | 
				
			||||||
 | 
					         'device_name': '/dev/sdc1',
 | 
				
			||||||
 | 
					         'source_type': 'blank',
 | 
				
			||||||
 | 
					         'destination_type': 'local',
 | 
				
			||||||
 | 
					         'disk_bus': 'scsi',
 | 
				
			||||||
 | 
					         'device_type': 'disk',
 | 
				
			||||||
 | 
					         'volume_size': 4,
 | 
				
			||||||
 | 
					         'guest_format': 'ext4',
 | 
				
			||||||
 | 
					         'delete_on_termination': True,
 | 
				
			||||||
 | 
					         'boot_index': -1})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ephemeral_driver_bdm = {
 | 
				
			||||||
 | 
					        'device_name': '/dev/sdc1',
 | 
				
			||||||
 | 
					        'size': 4,
 | 
				
			||||||
 | 
					        'device_type': 'disk',
 | 
				
			||||||
 | 
					        'guest_format': 'ext4',
 | 
				
			||||||
 | 
					        'disk_bus': 'scsi'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ephemeral_legacy_driver_bdm = {
 | 
				
			||||||
 | 
					        'device_name': '/dev/sdc1',
 | 
				
			||||||
 | 
					        'size': 4,
 | 
				
			||||||
 | 
					        'virtual_name': 'ephemeral0',
 | 
				
			||||||
 | 
					        'num': 0}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    volume_bdm = block_device.BlockDeviceDict(
 | 
				
			||||||
 | 
					        {'id': 3, 'instance_uuid': 'fake-instance',
 | 
				
			||||||
 | 
					         'device_name': '/dev/sda1',
 | 
				
			||||||
 | 
					         'source_type': 'volume',
 | 
				
			||||||
 | 
					         'disk_bus': 'scsi',
 | 
				
			||||||
 | 
					         'device_type': 'disk',
 | 
				
			||||||
 | 
					         'volume_size': 8,
 | 
				
			||||||
 | 
					         'destination_type': 'volume',
 | 
				
			||||||
 | 
					         'volume_id': 'fake-volume-id-1',
 | 
				
			||||||
 | 
					         'guest_format': 'ext4',
 | 
				
			||||||
 | 
					         'connection_info': '{"fake": "connection_info"}',
 | 
				
			||||||
 | 
					         'delete_on_termination': False,
 | 
				
			||||||
 | 
					         'boot_index': 0})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    volume_driver_bdm = {
 | 
				
			||||||
 | 
					        'mount_device': '/dev/sda1',
 | 
				
			||||||
 | 
					        'connection_info': {"fake": "connection_info"},
 | 
				
			||||||
 | 
					        'delete_on_termination': False,
 | 
				
			||||||
 | 
					        'disk_bus': 'scsi',
 | 
				
			||||||
 | 
					        'device_type': 'disk',
 | 
				
			||||||
 | 
					        'guest_format': 'ext4',
 | 
				
			||||||
 | 
					        'boot_index': 0}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    volume_legacy_driver_bdm = {
 | 
				
			||||||
 | 
					        'mount_device': '/dev/sda1',
 | 
				
			||||||
 | 
					        'connection_info': {"fake": "connection_info"},
 | 
				
			||||||
 | 
					        'delete_on_termination': False}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    snapshot_bdm = block_device.BlockDeviceDict(
 | 
				
			||||||
 | 
					        {'id': 4, 'instance_uuid': 'fake-instance',
 | 
				
			||||||
 | 
					         'device_name': '/dev/sda2',
 | 
				
			||||||
 | 
					         'delete_on_termination': True,
 | 
				
			||||||
 | 
					         'volume_size': 3,
 | 
				
			||||||
 | 
					         'disk_bus': 'scsi',
 | 
				
			||||||
 | 
					         'device_type': 'disk',
 | 
				
			||||||
 | 
					         'source_type': 'snapshot',
 | 
				
			||||||
 | 
					         'destination_type': 'volume',
 | 
				
			||||||
 | 
					         'connection_info': '{"fake": "connection_info"}',
 | 
				
			||||||
 | 
					         'snapshot_id': 'fake-snapshot-id-1',
 | 
				
			||||||
 | 
					         'volume_id': 'fake-volume-id-2',
 | 
				
			||||||
 | 
					         'boot_index': -1})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    snapshot_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}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    snapshot_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)
 | 
				
			||||||
 | 
					        self.virt_driver = self.mox.CreateMock(driver.ComputeDriver)
 | 
				
			||||||
 | 
					        self.db_api = self.mox.CreateMock(conductor_api.API)
 | 
				
			||||||
 | 
					        self.context = context.RequestContext('fake_user',
 | 
				
			||||||
 | 
					                                              'fake_project')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_driver_block_device_base_class(self):
 | 
				
			||||||
 | 
					        self.base_class_transform_called = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class DummyBlockDevice(driver_block_device.DriverBlockDevice):
 | 
				
			||||||
 | 
					            _fields = set(['foo', 'bar'])
 | 
				
			||||||
 | 
					            _legacy_fields = set(['foo', 'baz'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            def _transform(inst, bdm):
 | 
				
			||||||
 | 
					                self.base_class_transform_called = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dummy_device = DummyBlockDevice({'foo': 'foo_val', 'id': 42})
 | 
				
			||||||
 | 
					        self.assertTrue(self.base_class_transform_called)
 | 
				
			||||||
 | 
					        self.assertThat(dummy_device, matchers.DictMatches(
 | 
				
			||||||
 | 
					            {'foo': None, 'bar': None}))
 | 
				
			||||||
 | 
					        self.assertEquals(dummy_device.id, 42)
 | 
				
			||||||
 | 
					        self.assertThat(dummy_device.legacy(), matchers.DictMatches(
 | 
				
			||||||
 | 
					            {'foo': None, 'baz': None}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(driver_block_device._NotTransformable,
 | 
				
			||||||
 | 
					                          DummyBlockDevice, {'no_device': True})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _test_driver_device(self, name):
 | 
				
			||||||
 | 
					        test_bdm = self.driver_classes[name](
 | 
				
			||||||
 | 
					            getattr(self, "%s_bdm" % name))
 | 
				
			||||||
 | 
					        self.assertThat(test_bdm, matchers.DictMatches(
 | 
				
			||||||
 | 
					            getattr(self, "%s_driver_bdm" % name)))
 | 
				
			||||||
 | 
					        self.assertThat(test_bdm.legacy(),
 | 
				
			||||||
 | 
					                        matchers.DictMatches(
 | 
				
			||||||
 | 
					                            getattr(self, "%s_legacy_driver_bdm" % name)))
 | 
				
			||||||
 | 
					        # Make sure that all others raise _invalidType
 | 
				
			||||||
 | 
					        for other_name, cls in self.driver_classes.iteritems():
 | 
				
			||||||
 | 
					            if other_name == name:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            self.assertRaises(driver_block_device._InvalidType,
 | 
				
			||||||
 | 
					                              cls,
 | 
				
			||||||
 | 
					                              getattr(self, '%s_bdm' % name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_driver_swap_block_device(self):
 | 
				
			||||||
 | 
					        self._test_driver_device("swap")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_driver_ephemeral_block_device(self):
 | 
				
			||||||
 | 
					        self._test_driver_device("ephemeral")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_driver_volume_block_device(self):
 | 
				
			||||||
 | 
					        self._test_driver_device("volume")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        test_bdm = self.driver_classes['volume'](
 | 
				
			||||||
 | 
					            self.volume_bdm)
 | 
				
			||||||
 | 
					        self.assertEquals(test_bdm.id, 3)
 | 
				
			||||||
 | 
					        self.assertEquals(test_bdm.volume_id, 'fake-volume-id-1')
 | 
				
			||||||
 | 
					        self.assertEquals(test_bdm.volume_size, 8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_driver_snapshot_block_device(self):
 | 
				
			||||||
 | 
					        self._test_driver_device("snapshot")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        test_bdm = self.driver_classes['snapshot'](
 | 
				
			||||||
 | 
					            self.snapshot_bdm)
 | 
				
			||||||
 | 
					        self.assertEquals(test_bdm.id, 4)
 | 
				
			||||||
 | 
					        self.assertEquals(test_bdm.snapshot_id, 'fake-snapshot-id-1')
 | 
				
			||||||
 | 
					        self.assertEquals(test_bdm.volume_id, 'fake-volume-id-2')
 | 
				
			||||||
 | 
					        self.assertEquals(test_bdm.volume_size, 3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_volume_attach(self):
 | 
				
			||||||
 | 
					        test_bdm = self.driver_classes['volume'](
 | 
				
			||||||
 | 
					            self.volume_bdm)
 | 
				
			||||||
 | 
					        elevated_context = self.context.elevated()
 | 
				
			||||||
 | 
					        self.stubs.Set(self.context, 'elevated',
 | 
				
			||||||
 | 
					                       lambda: elevated_context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        instance = {'id': 'fake_id', 'uuid': 'fake_uuid'}
 | 
				
			||||||
 | 
					        volume = {'id': 'fake-volume-id-1'}
 | 
				
			||||||
 | 
					        connector = {'ip': 'fake_ip', 'host': 'fake_host'}
 | 
				
			||||||
 | 
					        connection_info = {'data': 'fake_data'}
 | 
				
			||||||
 | 
					        expected_conn_info = {'data': 'fake_data',
 | 
				
			||||||
 | 
					                              'serial': 'fake-volume-id-1'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.volume_api.get(self.context,
 | 
				
			||||||
 | 
					                            'fake-volume-id-1').AndReturn(volume)
 | 
				
			||||||
 | 
					        self.volume_api.check_attach(self.context, volume,
 | 
				
			||||||
 | 
					                                instance=instance).AndReturn(None)
 | 
				
			||||||
 | 
					        self.virt_driver.get_volume_connector(instance).AndReturn(connector)
 | 
				
			||||||
 | 
					        self.volume_api.initialize_connection(
 | 
				
			||||||
 | 
					            elevated_context, volume['id'],
 | 
				
			||||||
 | 
					            connector).AndReturn(connection_info)
 | 
				
			||||||
 | 
					        self.volume_api.attach(elevated_context, 'fake-volume-id-1',
 | 
				
			||||||
 | 
					                          'fake_uuid', '/dev/sda1').AndReturn(None)
 | 
				
			||||||
 | 
					        self.db_api.block_device_mapping_update(elevated_context, 3,
 | 
				
			||||||
 | 
					                {'connection_info': jsonutils.dumps(expected_conn_info)})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.mox.ReplayAll()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        test_bdm.attach(self.context, instance,
 | 
				
			||||||
 | 
					                        self.volume_api, self.virt_driver, self.db_api)
 | 
				
			||||||
 | 
					        self.assertThat(test_bdm['connection_info'],
 | 
				
			||||||
 | 
					                        matchers.DictMatches(expected_conn_info))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_snapshot_attach_no_volume(self):
 | 
				
			||||||
 | 
					        no_volume_snapshot = self.snapshot_bdm.copy()
 | 
				
			||||||
 | 
					        no_volume_snapshot['volume_id'] = None
 | 
				
			||||||
 | 
					        test_bdm = self.driver_classes['snapshot'](no_volume_snapshot)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        instance = {'id': 'fake_id', 'uuid': 'fake_uuid'}
 | 
				
			||||||
 | 
					        snapshot = {'id': 'fake-snapshot-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.get_snapshot(self.context,
 | 
				
			||||||
 | 
					                                     'fake-snapshot-id-1').AndReturn(snapshot)
 | 
				
			||||||
 | 
					        self.volume_api.create(self.context, 3,
 | 
				
			||||||
 | 
					                               '', '', snapshot).AndReturn(volume)
 | 
				
			||||||
 | 
					        wait_func(self.context, 'fake-volume-id-2').AndReturn(None)
 | 
				
			||||||
 | 
					        self.db_api.block_device_mapping_update(
 | 
				
			||||||
 | 
					            self.context, 4, {'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_snapshot_attach_volume(self):
 | 
				
			||||||
 | 
					        test_bdm = self.driver_classes['snapshot'](
 | 
				
			||||||
 | 
					            self.snapshot_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'],
 | 
				
			||||||
 | 
					            [self.volume_bdm, self.ephemeral_bdm])
 | 
				
			||||||
 | 
					        self.assertEquals(converted, [self.volume_driver_bdm])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_legacy_block_devices(self):
 | 
				
			||||||
 | 
					        test_snapshot = self.driver_classes['snapshot'](
 | 
				
			||||||
 | 
					            self.snapshot_bdm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        block_device_mapping = [test_snapshot, test_snapshot]
 | 
				
			||||||
 | 
					        legacy_bdm = driver_block_device.legacy_block_devices(
 | 
				
			||||||
 | 
					            block_device_mapping)
 | 
				
			||||||
 | 
					        self.assertEquals(legacy_bdm, [self.snapshot_legacy_driver_bdm,
 | 
				
			||||||
 | 
					                                       self.snapshot_legacy_driver_bdm])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test that the ephemerals work as expected
 | 
				
			||||||
 | 
					        test_ephemerals = [self.driver_classes['ephemeral'](
 | 
				
			||||||
 | 
					            self.ephemeral_bdm) for _ in xrange(2)]
 | 
				
			||||||
 | 
					        expected = [self.ephemeral_legacy_driver_bdm.copy()
 | 
				
			||||||
 | 
					                             for _ in xrange(2)]
 | 
				
			||||||
 | 
					        expected[0]['virtual_name'] = 'ephemeral0'
 | 
				
			||||||
 | 
					        expected[0]['num'] = 0
 | 
				
			||||||
 | 
					        expected[1]['virtual_name'] = 'ephemeral1'
 | 
				
			||||||
 | 
					        expected[1]['num'] = 1
 | 
				
			||||||
 | 
					        legacy_ephemerals = driver_block_device.legacy_block_devices(
 | 
				
			||||||
 | 
					            test_ephemerals)
 | 
				
			||||||
 | 
					        self.assertEquals(expected, legacy_ephemerals)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_get_swap(self):
 | 
				
			||||||
 | 
					        swap = [self.swap_driver_bdm]
 | 
				
			||||||
 | 
					        legacy_swap = [self.swap_legacy_driver_bdm]
 | 
				
			||||||
 | 
					        no_swap = [self.volume_driver_bdm]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEquals(swap[0], driver_block_device.get_swap(swap))
 | 
				
			||||||
 | 
					        self.assertEquals(legacy_swap[0],
 | 
				
			||||||
 | 
					                          driver_block_device.get_swap(legacy_swap))
 | 
				
			||||||
 | 
					        self.assertEquals(no_swap, driver_block_device.get_swap(no_swap))
 | 
				
			||||||
 | 
					        self.assertEquals(None, driver_block_device.get_swap([]))
 | 
				
			||||||
							
								
								
									
										284
									
								
								nova/virt/block_device.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								nova/virt/block_device.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,284 @@
 | 
				
			|||||||
 | 
					# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# All Rights Reserved.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
				
			||||||
 | 
					#    not use this file except in compliance with the License. You may obtain
 | 
				
			||||||
 | 
					#    a copy of the License at
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#         http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#    Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
				
			||||||
 | 
					#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
				
			||||||
 | 
					#    License for the specific language governing permissions and limitations
 | 
				
			||||||
 | 
					#    under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import functools
 | 
				
			||||||
 | 
					import operator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from nova import block_device
 | 
				
			||||||
 | 
					from nova.openstack.common.gettextutils import _
 | 
				
			||||||
 | 
					from nova.openstack.common import jsonutils
 | 
				
			||||||
 | 
					from nova.openstack.common import log as logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LOG = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _NotTransformable(Exception):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _InvalidType(_NotTransformable):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _NoLegacy(Exception):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DriverBlockDevice(dict):
 | 
				
			||||||
 | 
					    def __init__(self, bdm):
 | 
				
			||||||
 | 
					        if bdm.get('no_device'):
 | 
				
			||||||
 | 
					            raise _NotTransformable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # NOTE (ndipanov): Always save the id of the bdm
 | 
				
			||||||
 | 
					        #                  so we can use it for db updates.
 | 
				
			||||||
 | 
					        self.id = bdm.get('id')
 | 
				
			||||||
 | 
					        self.update(dict((field, None)
 | 
				
			||||||
 | 
					                    for field in self._fields))
 | 
				
			||||||
 | 
					        self._transform(bdm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _transform(self, bdm):
 | 
				
			||||||
 | 
					        """Transform bdm to the format that is passed to drivers."""
 | 
				
			||||||
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def legacy(self):
 | 
				
			||||||
 | 
					        """Basic legacy transformation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Basic method will just drop the fields that are not in
 | 
				
			||||||
 | 
					        _legacy_fields set. Override this in subclass if needed.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return dict((key, self.get(key)) for key in self._legacy_fields)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def attach(self, **kwargs):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Make the device available to be used by VMs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        To be overriden in subclasses with the connecting logic for
 | 
				
			||||||
 | 
					        the type of device the subclass represents.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        raise NotImplementedError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DriverSwapBlockDevice(DriverBlockDevice):
 | 
				
			||||||
 | 
					    _fields = set(['device_name', 'swap_size', 'disk_bus'])
 | 
				
			||||||
 | 
					    _legacy_fields = _fields - set(['disk_bus'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _transform(self, bdm):
 | 
				
			||||||
 | 
					        if not block_device.new_format_is_swap(bdm):
 | 
				
			||||||
 | 
					            raise _InvalidType
 | 
				
			||||||
 | 
					        self.update({
 | 
				
			||||||
 | 
					            'device_name': bdm.get('device_name'),
 | 
				
			||||||
 | 
					            'swap_size': bdm.get('volume_size', 0),
 | 
				
			||||||
 | 
					            'disk_bus': bdm.get('disk_bus')
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DriverEphemeralBlockDevice(DriverBlockDevice):
 | 
				
			||||||
 | 
					    _new_only_fields = set(['disk_bus', 'device_type', 'guest_format'])
 | 
				
			||||||
 | 
					    _fields = set(['device_name', 'size']) | _new_only_fields
 | 
				
			||||||
 | 
					    _legacy_fields = (_fields - _new_only_fields |
 | 
				
			||||||
 | 
					                      set(['num', 'virtual_name']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _transform(self, bdm):
 | 
				
			||||||
 | 
					        if not block_device.new_format_is_ephemeral(bdm):
 | 
				
			||||||
 | 
					            raise _InvalidType
 | 
				
			||||||
 | 
					        self.update({
 | 
				
			||||||
 | 
					            'device_name': bdm.get('device_name'),
 | 
				
			||||||
 | 
					            'size': bdm.get('volume_size', 0),
 | 
				
			||||||
 | 
					            'disk_bus': bdm.get('disk_bus'),
 | 
				
			||||||
 | 
					            'device_type': bdm.get('device_type'),
 | 
				
			||||||
 | 
					            'guest_format': bdm.get('guest_format')
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def legacy(self, num=0):
 | 
				
			||||||
 | 
					        legacy_bdm = super(DriverEphemeralBlockDevice, self).legacy()
 | 
				
			||||||
 | 
					        legacy_bdm['num'] = num
 | 
				
			||||||
 | 
					        legacy_bdm['virtual_name'] = 'ephemeral' + str(num)
 | 
				
			||||||
 | 
					        return legacy_bdm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DriverVolumeBlockDevice(DriverBlockDevice):
 | 
				
			||||||
 | 
					    _legacy_fields = set(['connection_info', 'mount_device',
 | 
				
			||||||
 | 
					                          'delete_on_termination'])
 | 
				
			||||||
 | 
					    _new_fields = set(['guest_format', 'device_type',
 | 
				
			||||||
 | 
					                       'disk_bus', 'boot_index'])
 | 
				
			||||||
 | 
					    _fields = _legacy_fields | _new_fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _transform(self, bdm):
 | 
				
			||||||
 | 
					        if not bdm.get('source_type') == 'volume':
 | 
				
			||||||
 | 
					            raise _InvalidType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # NOTE (ndipanov): Save it as an attribute as we will
 | 
				
			||||||
 | 
					        #                  need it for attach()
 | 
				
			||||||
 | 
					        self.volume_size = bdm.get('volume_size')
 | 
				
			||||||
 | 
					        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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def attach(self, context, instance, volume_api, virt_driver, db_api=None):
 | 
				
			||||||
 | 
					        volume = volume_api.get(context, self.volume_id)
 | 
				
			||||||
 | 
					        volume_api.check_attach(context, volume, instance=instance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Attach a volume to an instance at boot time. So actual attach
 | 
				
			||||||
 | 
					        # is done by instance creation.
 | 
				
			||||||
 | 
					        instance_id = instance['id']
 | 
				
			||||||
 | 
					        instance_uuid = instance['uuid']
 | 
				
			||||||
 | 
					        volume_id = volume['id']
 | 
				
			||||||
 | 
					        context = context.elevated()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        LOG.audit(_('Booting with volume %(volume_id)s at %(mountpoint)s'),
 | 
				
			||||||
 | 
					                  {'volume_id': volume_id,
 | 
				
			||||||
 | 
					                   'mountpoint': self['mount_device']},
 | 
				
			||||||
 | 
					                  context=context, instance=instance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        connector = virt_driver.get_volume_connector(instance)
 | 
				
			||||||
 | 
					        connection_info = volume_api.initialize_connection(context,
 | 
				
			||||||
 | 
					                                                           volume_id,
 | 
				
			||||||
 | 
					                                                           connector)
 | 
				
			||||||
 | 
					        volume_api.attach(context, volume_id,
 | 
				
			||||||
 | 
					                          instance_uuid, self['mount_device'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 'serial' not in connection_info:
 | 
				
			||||||
 | 
					            connection_info['serial'] = self.volume_id
 | 
				
			||||||
 | 
					        self['connection_info'] = connection_info
 | 
				
			||||||
 | 
					        if db_api:
 | 
				
			||||||
 | 
					            db_api.block_device_mapping_update(
 | 
				
			||||||
 | 
					                context, self.id,
 | 
				
			||||||
 | 
					                {'connection_info': jsonutils.dumps(connection_info)})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def attach(self, context, instance, volume_api, virt_driver,
 | 
				
			||||||
 | 
					               db_api=None, wait_func=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not self.volume_id:
 | 
				
			||||||
 | 
					            snapshot = volume_api.get_snapshot(context,
 | 
				
			||||||
 | 
					                                               self.snapshot_id)
 | 
				
			||||||
 | 
					            vol = volume_api.create(context, self.volume_size,
 | 
				
			||||||
 | 
					                                    '', '', snapshot)
 | 
				
			||||||
 | 
					            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']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Call the volume attach now
 | 
				
			||||||
 | 
					        super(DriverSnapshotBlockDevice, self).attach(context, instance,
 | 
				
			||||||
 | 
					                                                      volume_api, virt_driver,
 | 
				
			||||||
 | 
					                                                      db_api)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _convert_block_devices(device_type, block_device_mapping):
 | 
				
			||||||
 | 
					    def _is_transformable(bdm):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            device_type(bdm)
 | 
				
			||||||
 | 
					        except _NotTransformable:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return [device_type(bdm)
 | 
				
			||||||
 | 
					            for bdm in block_device_mapping
 | 
				
			||||||
 | 
					            if _is_transformable(bdm)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					convert_swap = functools.partial(_convert_block_devices,
 | 
				
			||||||
 | 
					                                 DriverSwapBlockDevice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					convert_ephemerals = functools.partial(_convert_block_devices,
 | 
				
			||||||
 | 
					                                      DriverEphemeralBlockDevice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					convert_volumes = functools.partial(_convert_block_devices,
 | 
				
			||||||
 | 
					                                   DriverVolumeBlockDevice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					convert_snapshots = functools.partial(_convert_block_devices,
 | 
				
			||||||
 | 
					                                     DriverSnapshotBlockDevice)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def attach_block_devices(block_device_mapping, *attach_args, **attach_kwargs):
 | 
				
			||||||
 | 
					    map(operator.methodcaller('attach', *attach_args, **attach_kwargs),
 | 
				
			||||||
 | 
					        block_device_mapping)
 | 
				
			||||||
 | 
					    return block_device_mapping
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def legacy_block_devices(block_device_mapping):
 | 
				
			||||||
 | 
					    def _has_legacy(bdm):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            bdm.legacy()
 | 
				
			||||||
 | 
					        except _NoLegacy:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bdms = [bdm.legacy()
 | 
				
			||||||
 | 
					            for bdm in block_device_mapping
 | 
				
			||||||
 | 
					            if _has_legacy(bdm)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Re-enumerate ephemeral devices
 | 
				
			||||||
 | 
					    if all(isinstance(bdm, DriverEphemeralBlockDevice)
 | 
				
			||||||
 | 
					           for bdm in block_device_mapping):
 | 
				
			||||||
 | 
					        for i, dev in enumerate(bdms):
 | 
				
			||||||
 | 
					            dev['virtual_name'] = dev['virtual_name'][:-1] + str(i)
 | 
				
			||||||
 | 
					            dev['num'] = i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return bdms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_swap(transformed_list):
 | 
				
			||||||
 | 
					    """Get the swap device out of the list context.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The block_device_info needs swap to be a single device,
 | 
				
			||||||
 | 
					    not a list - otherwise this is a no-op.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if not all(isinstance(device, DriverSwapBlockDevice) or
 | 
				
			||||||
 | 
					               'swap_size' in device
 | 
				
			||||||
 | 
					                for device in transformed_list):
 | 
				
			||||||
 | 
					        return transformed_list
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return transformed_list.pop()
 | 
				
			||||||
 | 
					    except IndexError:
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
		Reference in New Issue
	
	Block a user