Merge "Allow block devices without device_name"

This commit is contained in:
Jenkins 2013-09-05 14:09:53 +00:00 committed by Gerrit Code Review
commit a433ff739e
10 changed files with 502 additions and 33 deletions

View File

@ -599,9 +599,12 @@ class API(base.Base):
def _check_and_transform_bdm(self, base_options, min_count, max_count,
block_device_mapping, legacy_bdm):
if legacy_bdm:
# NOTE (ndipanov): Assume root dev name is 'vda' if not supplied.
# It's needed for legacy conversion to work.
root_device_name = (base_options.get('root_device_name') or 'vda')
block_device_mapping = block_device.from_legacy_mapping(
block_device_mapping, base_options.get('image_ref', ''),
base_options.get('root_device_name'))
root_device_name)
if min_count > 1 or max_count > 1:
if any(map(lambda bdm: bdm['source_type'] == 'volume',

View File

@ -40,6 +40,7 @@ import uuid
from eventlet import greenthread
from oslo.config import cfg
from nova import block_device
from nova.cells import rpcapi as cells_rpcapi
from nova.cloudpipe import pipelib
from nova import compute
@ -1004,6 +1005,10 @@ class ComputeManager(manager.SchedulerDependentManager):
bdms = self.conductor_api.block_device_mapping_get_all_by_instance(
context, instance, legacy=False)
# Verify that all the BDMs have a device_name set and assign a default
# one to the ones missing it with the help of the driver.
self._default_block_device_names(context, instance, image_meta, bdms)
# b64 decode the files to inject:
injected_files_orig = injected_files
injected_files = self._decode_files(injected_files)
@ -1266,6 +1271,92 @@ class ComputeManager(manager.SchedulerDependentManager):
requested_networks, macs, security_groups, is_vpn,
dhcp_options)
def _default_root_device_name(self, instance, image_meta, root_bdm):
try:
return self.driver.default_root_device_name(instance,
image_meta,
root_bdm)
except NotImplementedError:
return compute_utils.get_next_device_name(instance, [])
def _default_device_names_for_instance(self, instance,
root_device_name,
update_function,
*block_device_lists):
try:
self.driver.default_device_names_for_instance(instance,
root_device_name,
*block_device_lists)
except NotImplementedError:
compute_utils.default_device_names_for_instance(
instance, root_device_name,
update_function, *block_device_lists)
def _default_block_device_names(self, context, instance,
image_meta, block_devices):
"""Verify that all the devices have the device_name set. If not,
provide a default name.
It also ensures that there is a root_device_name and is set to the
first block device in the boot sequence (boot_index=0).
"""
try:
root_bdm = (bdm for bdm in block_devices
if bdm['boot_index'] == 0).next()
except StopIteration:
return
# Get the root_device_name from the root BDM or the instance
root_device_name = None
update_instance = False
update_root_bdm = False
if root_bdm['device_name']:
root_device_name = root_bdm['device_name']
instance['root_device_name'] = root_device_name
update_instance = True
elif instance['root_device_name']:
root_device_name = instance['root_device_name']
root_bdm['device_name'] = root_device_name
update_root_bdm = True
else:
root_device_name = self._default_root_device_name(instance,
image_meta,
root_bdm)
instance['root_device_name'] = root_device_name
root_bdm['device_name'] = root_device_name
update_instance = update_root_bdm = True
if update_instance:
self._instance_update(context, instance['uuid'],
root_device_name=root_device_name)
if update_root_bdm:
self.conductor_api.block_device_mapping_update(
context, root_bdm['id'], {'device_name': root_device_name})
def _is_mapping(bdm):
return (bdm['source_type'] in ('image', 'volume', 'snapshot') and
driver_block_device.is_implemented(bdm))
ephemerals = filter(block_device.new_format_is_ephemeral,
block_devices)
swap = filter(block_device.new_format_is_swap,
block_devices)
block_device_mapping = filter(_is_mapping, block_devices)
def _update_bdm(bdm_for_update):
self.conductor_api.block_device_mapping_update(
context, instance['uuid'], bdm_for_update['id'],
bdm_for_update['device_name'])
self._default_device_names_for_instance(instance,
root_device_name,
_update_bdm,
ephemerals,
swap,
block_device_mapping)
def _prep_block_device(self, context, instance, bdms):
"""Set up the block device for an instance with error logging."""
try:

View File

@ -16,6 +16,7 @@
"""Compute-related Utilities and helpers."""
import itertools
import re
import string
import traceback
@ -122,12 +123,78 @@ def pack_action_event_finish(context, instance_uuid, event_name, exc_val=None,
def get_device_name_for_instance(context, instance, bdms, device):
"""Validates (or generates) a device name for instance.
This method is a wrapper for get_next_device_name that gets the list
of used devices and the root device from a block device mapping.
"""
mappings = block_device.instance_block_mapping(instance, bdms)
return get_next_device_name(instance, mappings.values(),
mappings['root'], device)
def default_device_names_for_instance(instance, root_device_name,
update_function, *block_device_lists):
"""Generate missing device names for an instance."""
def _device_names(iterables):
return [bdm['device_name']
for bdm in itertools.chain(*iterables) if bdm['device_name']]
dev_list = _device_names(block_device_lists)
if root_device_name not in dev_list:
dev_list.append(root_device_name)
inst_type = flavors.extract_flavor(instance)
is_libvirt = driver.compute_driver_matches('libvirt.LibvirtDriver')
libvirt_default_ephemerals = []
bdm_named_lists = zip(('ephemerals', 'swap', 'block_device_mapping'),
block_device_lists)
for name, bdm_list in bdm_named_lists:
# Libvirt will create a default ephemeral if instance allows
# and was not overridden.
if (is_libvirt and name == 'ephemerals' and not bdm_list and
instance['ephemeral_gb'] > 0):
default_eph = get_next_device_name(instance,
[root_device_name],
root_device_name)
if default_eph not in dev_list:
dev_list.append(default_eph)
libvirt_default_ephemerals.append(default_eph)
# Libvirt will create a default swap if it's in instance type
# and it was not supplied or overridden
if (is_libvirt and name == 'swap' and not bdm_list and
inst_type['swap'] > 0):
ephemerals = (_device_names(bdm_named_lists[0][1]) or
libvirt_default_ephemerals)
default_swap = get_next_device_name(instance,
[root_device_name] + ephemerals, root_device_name)
if default_swap not in dev_list:
dev_list.append(default_swap)
for bdm in bdm_list:
dev = bdm.get('device_name')
if not dev:
dev = get_next_device_name(instance, dev_list,
root_device_name)
bdm['device_name'] = dev
if update_function:
update_function(bdm)
dev_list.append(dev)
def get_next_device_name(instance, device_name_list,
root_device_name=None, device=None):
"""Validates (or generates) a device name for instance.
If device is not set, it will generate a unique device appropriate
for the instance. It uses the block device mapping table to find
valid device names. If the device name is valid but applicable to
a different backend (for example /dev/vdc is specified but the
backend uses /dev/xvdc), the device name will be converted to the
appropriate format.
for the instance. It uses the root_device_name (if provided) and
the list of used devices to find valid device names. If the device
name is valid but applicable to a different backend (for example
/dev/vdc is specified but the backend uses /dev/xvdc), the device
name will be converted to the appropriate format.
"""
req_prefix = None
req_letter = None
@ -138,23 +205,29 @@ def get_device_name_for_instance(context, instance, bdms, device):
except (TypeError, AttributeError, ValueError):
raise exception.InvalidDevicePath(path=device)
mappings = block_device.instance_block_mapping(instance, bdms)
if not root_device_name:
root_device_name = block_device.DEFAULT_ROOT_DEV_NAME
try:
prefix = block_device.match_device(mappings['root'])[0]
prefix = block_device.match_device(root_device_name)[0]
except (TypeError, AttributeError, ValueError):
raise exception.InvalidDevicePath(path=mappings['root'])
raise exception.InvalidDevicePath(path=root_device_name)
# NOTE(vish): remove this when xenapi is setting default_root_device
if driver.compute_driver_matches('xenapi.XenAPIDriver'):
prefix = '/dev/xvd'
# NOTE(xqueralt): This can be removed when we have libvirt
# defaulting it's own device names.
if driver.compute_driver_matches('libvirt.LibvirtDriver'):
prefix = '/dev/vd'
if req_prefix != prefix:
LOG.debug(_("Using %(prefix)s instead of %(req_prefix)s"),
{'prefix': prefix, 'req_prefix': req_prefix})
used_letters = set()
for device_path in mappings.itervalues():
for device_path in device_name_list:
letter = block_device.strip_prefix(device_path)
# NOTE(vish): delete numbers in case we have something like
# /dev/sda1
@ -177,8 +250,7 @@ def get_device_name_for_instance(context, instance, bdms, device):
if req_letter in used_letters:
raise exception.DevicePathInUse(path=device)
device_name = prefix + req_letter
return device_name
return prefix + req_letter
def _get_unused_letter(used_letters):

View File

@ -3451,32 +3451,35 @@ def block_device_mapping_update(context, bdm_id, values, legacy=True):
def block_device_mapping_update_or_create(context, values, legacy=True):
_scrub_empty_str_values(values, ['volume_size'])
values = _from_legacy_values(values, legacy, allow_updates=True)
session = get_session()
with session.begin():
result = _block_device_mapping_get_query(context, session=session).\
filter_by(instance_uuid=values['instance_uuid']).\
filter_by(device_name=values['device_name']).\
first()
if not result:
values = _from_legacy_values(values, legacy)
bdm_ref = models.BlockDeviceMapping()
bdm_ref.update(values)
bdm_ref.save(session=session)
result = bdm_ref
else:
values = _from_legacy_values(values, legacy, allow_updates=True)
result.update(values)
result = None
# NOTE(xqueralt): Only update a BDM when device_name was provided. We
# allow empty device names so they will be set later by the manager.
if values['device_name']:
query = _block_device_mapping_get_query(context, session=session)
result = query.filter_by(instance_uuid=values['instance_uuid'],
device_name=values['device_name']).first()
# NOTE(xqueralt): prevent from having multiple swap devices for the
# same instance. So delete the existing ones.
if result:
result.update(values)
else:
# Either the device_name doesn't exist in the database yet, or no
# device_name was provided. Both cases mean creating a new BDM.
result = models.BlockDeviceMapping(**values)
result.save(session=session)
# NOTE(xqueralt): Prevent from having multiple swap devices for the
# same instance. This will delete all the existing ones.
if block_device.new_format_is_swap(values):
query = (_block_device_mapping_get_query(context, session=session).
filter_by(instance_uuid=values['instance_uuid']).
filter_by(source_type='blank').
filter(models.BlockDeviceMapping.device_name !=
values['device_name']).
filter_by(guest_format='swap'))
query = _block_device_mapping_get_query(context, session=session)
query = query.filter_by(instance_uuid=values['instance_uuid'],
source_type='blank', guest_format='swap')
query = query.filter(models.BlockDeviceMapping.id != result.id)
query.soft_delete()
return result

View File

@ -5512,6 +5512,81 @@ class ComputeTestCase(BaseTestCase):
instance.refresh()
self.assertEqual(vm_states.ACTIVE, instance['vm_state'])
def _get_instance_and_bdm_for_dev_defaults_tests(self):
instance = self._create_fake_instance(
params={'root_device_name': '/dev/vda'})
block_device_mapping = [
{'id': 3, 'instance_uuid': 'fake-instance',
'device_name': '/dev/vda',
'source_type': 'volume',
'destination_type': 'volume',
'image_id': 'fake-image-id-1',
'boot_index': 0}]
return instance, block_device_mapping
def test_default_block_device_names_empty_instance_root_dev(self):
instance, bdms = self._get_instance_and_bdm_for_dev_defaults_tests()
instance['root_device_name'] = None
self.mox.StubOutWithMock(self.compute, '_instance_update')
self.mox.StubOutWithMock(self.compute,
'_default_device_names_for_instance')
self.compute._instance_update(self.context, instance['uuid'],
root_device_name='/dev/vda')
self.compute._default_device_names_for_instance(instance,
'/dev/vda',
mox.IgnoreArg(),
[], [], bdms)
self.mox.ReplayAll()
self.compute._default_block_device_names(self.context,
instance,
{}, bdms)
def test_default_block_device_names_empty_root_device(self):
instance, bdms = self._get_instance_and_bdm_for_dev_defaults_tests()
bdms[0]['device_name'] = None
self.mox.StubOutWithMock(self.compute.conductor_api,
'block_device_mapping_update')
self.mox.StubOutWithMock(self.compute,
'_default_device_names_for_instance')
self.compute.conductor_api.block_device_mapping_update(
self.context, bdms[0]['id'], {'device_name': '/dev/vda'})
self.compute._default_device_names_for_instance(instance,
'/dev/vda',
mox.IgnoreArg(),
[], [], bdms)
self.mox.ReplayAll()
self.compute._default_block_device_names(self.context,
instance,
{}, bdms)
def test_default_block_device_names_no_root_device(self):
instance, bdms = self._get_instance_and_bdm_for_dev_defaults_tests()
instance['root_device_name'] = None
bdms[0]['device_name'] = None
self.mox.StubOutWithMock(self.compute, '_instance_update')
self.mox.StubOutWithMock(self.compute.conductor_api,
'block_device_mapping_update')
self.mox.StubOutWithMock(self.compute,
'_default_root_device_name')
self.mox.StubOutWithMock(self.compute,
'_default_device_names_for_instance')
self.compute._default_root_device_name(instance, mox.IgnoreArg(),
bdms[0]).AndReturn('/dev/vda')
self.compute._instance_update(self.context, instance['uuid'],
root_device_name='/dev/vda')
self.compute.conductor_api.block_device_mapping_update(
self.context, bdms[0]['id'], {'device_name': '/dev/vda'})
self.compute._default_device_names_for_instance(instance,
'/dev/vda',
mox.IgnoreArg(),
[], [], bdms)
self.mox.ReplayAll()
self.compute._default_block_device_names(self.context,
instance,
{}, bdms)
class ComputeAPITestCase(BaseTestCase):
def setUp(self):

View File

@ -18,6 +18,7 @@
"""Tests For miscellaneous util methods used with compute."""
import copy
import string
from oslo.config import cfg
@ -37,6 +38,8 @@ from nova.tests import fake_instance_actions
from nova.tests import fake_network
from nova.tests import fake_notifier
import nova.tests.image.fake
from nova.tests import matchers
from nova.virt import driver
CONF = cfg.CONF
CONF.import_opt('compute_manager', 'nova.service')
@ -226,6 +229,178 @@ class ComputeValidateDeviceTestCase(test.TestCase):
self.assertEqual(device, '/dev/xvdd')
class DefaultDeviceNamesForInstanceTestCase(test.TestCase):
def setUp(self):
super(DefaultDeviceNamesForInstanceTestCase, self).setUp()
self.ephemerals = [
{'id': 1, 'instance_uuid': 'fake-instance',
'device_name': '/dev/vdb',
'source_type': 'blank',
'destination_type': 'local',
'delete_on_termination': True,
'guest_format': None,
'boot_index': -1}]
self.swap = [
{'id': 2, 'instance_uuid': 'fake-instance',
'device_name': '/dev/vdc',
'source_type': 'blank',
'destination_type': 'local',
'delete_on_termination': True,
'guest_format': 'swap',
'boot_index': -1}]
self.block_device_mapping = [
{'id': 3, 'instance_uuid': 'fake-instance',
'device_name': '/dev/vda',
'source_type': 'volume',
'destination_type': 'volume',
'volume_id': 'fake-volume-id-1',
'boot_index': 0},
{'id': 4, 'instance_uuid': 'fake-instance',
'device_name': '/dev/vdd',
'source_type': 'snapshot',
'destination_type': 'volume',
'snapshot_id': 'fake-snapshot-id-1',
'boot_index': -1}]
self.instance_type = {'swap': 4}
self.instance = {'uuid': 'fake_instance', 'ephemeral_gb': 2}
self.is_libvirt = False
self.root_device_name = '/dev/vda'
self.update_called = False
def fake_extract_flavor(instance):
return self.instance_type
def fake_driver_matches(driver_string):
if driver_string == 'libvirt.LibvirtDriver':
return self.is_libvirt
return False
self.stubs.Set(flavors, 'extract_flavor', fake_extract_flavor)
self.stubs.Set(driver, 'compute_driver_matches', fake_driver_matches)
def _test_default_device_names(self, update_function, *block_device_lists):
compute_utils.default_device_names_for_instance(self.instance,
self.root_device_name,
update_function,
*block_device_lists)
def test_only_block_device_mapping(self):
# Test no-op
original_bdm = copy.deepcopy(self.block_device_mapping)
self._test_default_device_names(None, [], [],
self.block_device_mapping)
self.assertThat(original_bdm,
matchers.DictListMatches(self.block_device_mapping))
# Asser it defaults the missing one as expected
self.block_device_mapping[1]['device_name'] = None
self._test_default_device_names(None, [], [],
self.block_device_mapping)
self.assertEquals(self.block_device_mapping[1]['device_name'],
'/dev/vdb')
def test_with_ephemerals(self):
# Test ephemeral gets assigned
self.ephemerals[0]['device_name'] = None
self._test_default_device_names(None, self.ephemerals, [],
self.block_device_mapping)
self.assertEquals(self.ephemerals[0]['device_name'], '/dev/vdb')
self.block_device_mapping[1]['device_name'] = None
self._test_default_device_names(None, self.ephemerals, [],
self.block_device_mapping)
self.assertEquals(self.block_device_mapping[1]['device_name'],
'/dev/vdc')
def test_with_swap(self):
# Test swap only
self.swap[0]['device_name'] = None
self._test_default_device_names(None, [], self.swap, [])
self.assertEquals(self.swap[0]['device_name'], '/dev/vdb')
# Test swap and block_device_mapping
self.swap[0]['device_name'] = None
self.block_device_mapping[1]['device_name'] = None
self._test_default_device_names(None, [], self.swap,
self.block_device_mapping)
self.assertEquals(self.swap[0]['device_name'], '/dev/vdb')
self.assertEquals(self.block_device_mapping[1]['device_name'],
'/dev/vdc')
def test_all_together(self):
# Test swap missing
self.swap[0]['device_name'] = None
self._test_default_device_names(None, self.ephemerals,
self.swap, self.block_device_mapping)
self.assertEquals(self.swap[0]['device_name'], '/dev/vdc')
# Test swap and eph missing
self.swap[0]['device_name'] = None
self.ephemerals[0]['device_name'] = None
self._test_default_device_names(None, self.ephemerals,
self.swap, self.block_device_mapping)
self.assertEquals(self.ephemerals[0]['device_name'], '/dev/vdb')
self.assertEquals(self.swap[0]['device_name'], '/dev/vdc')
# Test all missing
self.swap[0]['device_name'] = None
self.ephemerals[0]['device_name'] = None
self.block_device_mapping[1]['device_name'] = None
self._test_default_device_names(None, self.ephemerals,
self.swap, self.block_device_mapping)
self.assertEquals(self.ephemerals[0]['device_name'], '/dev/vdb')
self.assertEquals(self.swap[0]['device_name'], '/dev/vdc')
self.assertEquals(self.block_device_mapping[1]['device_name'],
'/dev/vdd')
def test_libvirt_default_eph(self):
self.block_device_mapping[1]['device_name'] = None
self._test_default_device_names(None, [], [],
self.block_device_mapping)
self.assertEquals(self.block_device_mapping[1]['device_name'],
'/dev/vdb')
# Test that default eph will be taken into account on libvirt
self.is_libvirt = True
self.instance_type['swap'] = 0
self.block_device_mapping[1]['device_name'] = None
self._test_default_device_names(None, [], [],
self.block_device_mapping)
self.assertEquals(self.block_device_mapping[1]['device_name'],
'/dev/vdc')
def test_libvirt_default_swap(self):
# Test that default swap will be taken into account on libvirt
self.is_libvirt = True
self.instance['ephemeral_gb'] = 0
self.block_device_mapping[1]['device_name'] = None
self._test_default_device_names(None, [], [],
self.block_device_mapping)
self.assertEquals(self.block_device_mapping[1]['device_name'],
'/dev/vdc')
def test_libvirt_default_swap_ephemeral(self):
self.is_libvirt = True
self.block_device_mapping[1]['device_name'] = None
self._test_default_device_names(None, [], [],
self.block_device_mapping)
self.assertEquals(self.block_device_mapping[1]['device_name'],
'/dev/vdd')
def test_update_fn_gets_called(self):
def _update(bdm):
self.update_called = True
self.block_device_mapping[1]['device_name'] = None
compute_utils.default_device_names_for_instance(
self.instance, self.root_device_name, _update, [], [],
self.block_device_mapping)
self.assertTrue(self.update_called)
class UsageInfoTestCase(test.TestCase):
def setUp(self):

View File

@ -4226,6 +4226,24 @@ class BlockDeviceMappingTestCase(test.TestCase):
self.assertEqual(bdm_real['device_name'], 'fake_name')
self.assertEqual(bdm_real['destination_type'], 'camelot')
# check create without device_name
bdm1 = dict(values)
bdm1['device_name'] = None
db.block_device_mapping_update_or_create(self.ctxt, bdm1, legacy=False)
bdm_real = db.block_device_mapping_get_all_by_instance(self.ctxt, uuid)
self.assertEqual(len(bdm_real), 2)
bdm_real = bdm_real[1]
self.assertEqual(bdm_real['device_name'], None)
# check create multiple devices without device_name
bdm2 = dict(values)
bdm2['device_name'] = None
db.block_device_mapping_update_or_create(self.ctxt, bdm2, legacy=False)
bdm_real = db.block_device_mapping_get_all_by_instance(self.ctxt, uuid)
self.assertEqual(len(bdm_real), 3)
bdm_real = bdm_real[2]
self.assertEqual(bdm_real['device_name'], None)
def test_block_device_mapping_update_or_create_multiple_ephemeral(self):
uuid = self.instance['uuid']
values = {

View File

@ -468,3 +468,11 @@ class TestDriverBlockDevice(test.TestCase):
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([]))
def test_is_implemented(self):
for bdm in (self.image_bdm, self.volume_bdm, self.swap_bdm,
self.ephemeral_bdm, self.snapshot_bdm):
self.assertTrue(driver_block_device.is_implemented(bdm))
local_image = self.image_bdm.copy()
local_image['destination_type'] = 'local'
self.assertFalse(driver_block_device.is_implemented(local_image))

View File

@ -327,3 +327,18 @@ def get_swap(transformed_list):
return transformed_list.pop()
except IndexError:
return None
_IMPLEMENTED_CLASSES = (DriverSwapBlockDevice, DriverEphemeralBlockDevice,
DriverVolumeBlockDevice, DriverSnapshotBlockDevice,
DriverImageBlockDevice)
def is_implemented(bdm):
for cls in _IMPLEMENTED_CLASSES:
try:
cls(bdm)
return True
except _NotTransformable:
pass
return False

View File

@ -1068,6 +1068,15 @@ class ComputeDriver(object):
"""
raise NotImplementedError()
def default_root_device_name(self, instance, image_meta, root_bdm):
"""Provide a default root device name for the driver."""
raise NotImplementedError()
def default_device_names_for_instance(self, instance, root_device_name,
*block_device_lists):
"""Default the missing device names in the block device mapping."""
raise NotImplementedError()
def load_compute_driver(virtapi, compute_driver=None):
"""Load a compute driver module.