Merge bdms of run instances parameters with image mappings

Implement AWS feature which allows a user to adjust bdm specified in an
image. The feature is used with boot operation by specifying changed
device attributes with the device name.

Change-Id: Ifb9c215527cd84a60f21ab73fee672928b604300
This commit is contained in:
Feodor Tersin 2015-07-17 12:46:18 +03:00
parent 476e899552
commit ffc445c964
3 changed files with 268 additions and 3 deletions

View File

@ -887,6 +887,10 @@ def _parse_image_parameters(context, image_id, kernel_id, ramdisk_id):
def _parse_block_device_mapping(context, block_device_mapping):
# TODO(ft): check block_device_mapping structure
# TODO(ft): leave only the last bdm if several bdms are occured with
# the same device name. If names differ in /dev/ prefix occurence,
# raise InvalidBlockDeviceMapping with the message
# The device '<short_name>' is used in more than one block-device mapping
# TODO(ft): support virtual devices
# TODO(ft): support no_device
bdms = []
@ -927,18 +931,67 @@ def _parse_block_device_mapping(context, block_device_mapping):
def _build_block_device_mapping(context, block_device_mapping, os_image):
mappings = _parse_block_device_mapping(context, block_device_mapping)
properties = ec2utils.deserialize_os_image_properties(os_image)
image_bdms = ec2utils.get_os_image_mappings(properties)
root_device_name = (
ec2utils.block_device_properties_root_device_name(properties))
short_root_device_name = ec2utils.block_device_strip_dev(root_device_name)
image_bdm_dict = {}
for bdm in image_bdms:
if bdm.get('device_name'):
key = ec2utils.block_device_strip_dev(bdm['device_name'])
if key == short_root_device_name:
bdm.setdefault('boot_index', 0)
elif bdm.get('boot_index') == 0:
key = short_root_device_name
bdm.setdefault('device_name', root_device_name)
else:
continue
image_bdm_dict[key] = bdm
result = []
# NOTE(ft): only the last device definition in the list is considered
# by AWS. So get only the last definitions, despite Nova can do the same.
# Because this is not contracted in Nova.
for bdm in mappings:
_populate_parsed_bdm_parameter(bdm, short_root_device_name)
short_device_name = ec2utils.block_device_strip_dev(bdm['device_name'])
if short_device_name not in image_bdm_dict:
_populate_parsed_bdm_parameter(bdm, short_root_device_name)
else:
image_bdm = image_bdm_dict[short_device_name]
if bdm['device_name'] != image_bdm['device_name']:
raise exception.InvalidBlockDeviceMapping(
_("The device '%s' is used in more than one "
"block-device mapping") % short_device_name)
if (image_bdm.get('boot_index') == 0 and 'snapshot_id' in bdm and
bdm['snapshot_id'] != image_bdm.get('snapshot_id')):
raise exception.InvalidBlockDeviceMapping(
_('snapshotId cannot be modified on root device'))
if ('volume_size' in bdm and 'volume_size' in image_bdm and
bdm['volume_size'] < image_bdm['volume_size']):
raise exception.InvalidBlockDeviceMapping(
_("Volume of size %(bdm_size)dGB is smaller than expected "
"size %(image_bdm_size)dGB for '(device_name)s'") %
{'bdm_size': bdm['volume_size'],
'image_bdm_size': image_bdm['volume_size'],
'device_name': bdm['device_name']})
if bdm.get('snapshot_id'):
if 'snapshot_id' not in image_bdm:
raise exception.InvalidBlockDeviceMapping(
_('snapshotId can only be modified on EBS devices'))
_populate_parsed_bdm_parameter(bdm, short_root_device_name)
else:
image_bdm.update(bdm)
bdm = image_bdm
source_type = bdm.get('source_type')
if source_type:
uuid = bdm.pop('_'.join([source_type, 'id']), None)
if uuid:
bdm['uuid'] = uuid
result.append(bdm)
return result

View File

@ -239,6 +239,10 @@ class InvalidSnapshotIDMalformed(EC2InvalidException):
msg_fmg = _('The snapshot %(id)s ID is not valid')
class InvalidBlockDeviceMapping(EC2InvalidException):
pass
class IncorrectState(EC2IncorrectStateException):
msg_fmt = _("The resource is in incorrect state for the request - reason: "
"'%(reason)s'")

View File

@ -1465,8 +1465,10 @@ class InstancePrivateTestCase(test_base.BaseTestCase):
def test_build_block_device_mapping(self, db_api):
fake_context = mock.Mock(service_catalog=[{'type': 'fake'}])
db_api.get_item_by_id.side_effect = tools.get_db_api_get_item_by_id(
fakes.DB_SNAPSHOT_1, fakes.DB_VOLUME_1)
fakes.DB_SNAPSHOT_1, fakes.DB_SNAPSHOT_2,
fakes.DB_VOLUME_1, fakes.DB_VOLUME_2)
# check bdm attributes' population
bdms = [
{'device_name': '/dev/sda1',
'ebs': {'snapshot_id': fakes.ID_EC2_SNAPSHOT_1}},
@ -1496,11 +1498,217 @@ class InstancePrivateTestCase(test_base.BaseTestCase):
'delete_on_termination': True,
'boot_index': -1},
]
result = instance_api._build_block_device_mapping(
fake_context, bdms, fakes.OSImage(fakes.OS_IMAGE_1))
self.assertEqual(expected, result)
fake_image_template = {
'id': fakes.random_os_id(),
'properties': {'root_device_name': '/dev/vda',
'bdm_v2': True,
'block_device_mapping': []}}
# check merging with image bdms
fake_image_template['properties']['block_device_mapping'] = [
{'boot_index': 0,
'device_name': '/dev/vda',
'source_type': 'snapshot',
'snapshot_id': fakes.ID_OS_SNAPSHOT_1,
'delete_on_termination': True},
{'device_name': 'vdb',
'source_type': 'snapshot',
'snapshot_id': fakes.random_os_id(),
'volume_size': 50},
{'device_name': '/dev/vdc',
'source_type': 'blank',
'volume_size': 10},
]
bdms = [
{'device_name': '/dev/vda',
'ebs': {'volume_size': 15}},
{'device_name': 'vdb',
'ebs': {'snapshot_id': fakes.ID_EC2_SNAPSHOT_2,
'delete_on_termination': False}},
{'device_name': '/dev/vdc',
'ebs': {'volume_size': 20}},
]
expected = [
{'device_name': '/dev/vda',
'source_type': 'snapshot',
'destination_type': 'volume',
'uuid': fakes.ID_OS_SNAPSHOT_1,
'delete_on_termination': True,
'volume_size': 15,
'boot_index': 0},
{'device_name': 'vdb',
'source_type': 'snapshot',
'destination_type': 'volume',
'uuid': fakes.ID_OS_SNAPSHOT_2,
'delete_on_termination': False,
'boot_index': -1},
{'device_name': '/dev/vdc',
'source_type': 'blank',
'destination_type': 'volume',
'volume_size': 20,
'delete_on_termination': False},
]
result = instance_api._build_block_device_mapping(
fake_context, bdms, fakes.OSImage(fake_image_template))
self.assertEqual(expected, result)
# check result order for adjusting some bdm of all
fake_image_template['properties']['block_device_mapping'] = [
{'device_name': '/dev/vdc',
'source_type': 'blank',
'volume_size': 10},
{'device_name': '/dev/vde',
'source_type': 'blank',
'volume_size': 10},
{'device_name': '/dev/vdf',
'source_type': 'blank',
'volume_size': 10},
{'boot_index': -1,
'source_type': 'blank',
'volume_size': 10},
]
bdms = [
{'device_name': '/dev/vdh',
'ebs': {'volume_size': 15}},
{'device_name': '/dev/vde',
'ebs': {'volume_size': 15}},
{'device_name': '/dev/vdb',
'ebs': {'volume_size': 15}},
]
expected = [
{'device_name': '/dev/vdh',
'source_type': 'blank',
'destination_type': 'volume',
'volume_size': 15,
'delete_on_termination': True,
'boot_index': -1},
{'device_name': '/dev/vde',
'source_type': 'blank',
'destination_type': 'volume',
'volume_size': 15,
'delete_on_termination': False},
{'device_name': '/dev/vdb',
'source_type': 'blank',
'destination_type': 'volume',
'volume_size': 15,
'delete_on_termination': True,
'boot_index': -1},
]
result = instance_api._build_block_device_mapping(
fake_context, bdms, fakes.OSImage(fake_image_template))
self.assertEqual(expected, result)
# check conflict of short and full device names
fake_image_template['properties']['block_device_mapping'] = [
{'device_name': '/dev/vdc',
'source_type': 'blank',
'volume_size': 10},
]
bdms = [
{'device_name': 'vdc',
'ebs': {'volume_size': 15}},
]
self.assertRaises(exception.InvalidBlockDeviceMapping,
instance_api._build_block_device_mapping,
fake_context, bdms,
fakes.OSImage(fake_image_template))
# opposit combination of the same case
fake_image_template['properties']['block_device_mapping'] = [
{'device_name': 'vdc',
'source_type': 'blank',
'volume_size': 10},
]
bdms = [
{'device_name': '/dev/vdc',
'ebs': {'volume_size': 15}},
]
self.assertRaises(exception.InvalidBlockDeviceMapping,
instance_api._build_block_device_mapping,
fake_context, bdms,
fakes.OSImage(fake_image_template))
# check fault on root device snapshot changing
fake_image_template['properties']['block_device_mapping'] = [
{'boot_index': 0,
'source_type': 'snapshot',
'snapshot_id': fakes.ID_EC2_SNAPSHOT_1},
]
bdms = [
{'device_name': '/dev/vda',
'ebs': {'snapshot_id': fakes.ID_EC2_SNAPSHOT_2}},
]
self.assertRaises(exception.InvalidBlockDeviceMapping,
instance_api._build_block_device_mapping,
fake_context, bdms,
fakes.OSImage(fake_image_template))
# same case for legacy bdm
fake_image_template['properties']['block_device_mapping'] = [
{'device_name': '/dev/vda',
'snapshot_id': fakes.ID_EC2_SNAPSHOT_1},
]
fake_image_template['properties']['bdm_v2'] = False
bdms = [
{'device_name': '/dev/vda',
'ebs': {'snapshot_id': fakes.ID_EC2_SNAPSHOT_2}},
]
self.assertRaises(exception.InvalidBlockDeviceMapping,
instance_api._build_block_device_mapping,
fake_context, bdms,
fakes.OSImage(fake_image_template))
# same case for legacy bdm with short names
fake_image_template['properties']['block_device_mapping'] = [
{'device_name': 'vda',
'snapshot_id': fakes.ID_EC2_SNAPSHOT_1},
]
fake_image_template['properties']['bdm_v2'] = False
bdms = [
{'device_name': 'vda',
'ebs': {'snapshot_id': fakes.ID_EC2_SNAPSHOT_2}},
]
self.assertRaises(exception.InvalidBlockDeviceMapping,
instance_api._build_block_device_mapping,
fake_context, bdms,
fakes.OSImage(fake_image_template))
fake_image_template['properties']['bdm_v2'] = True
# check fault on reduce volume size
fake_image_template['properties']['block_device_mapping'] = [
{'device_name': 'vdc',
'source_type': 'blank',
'volume_size': 15},
]
bdms = [
{'device_name': '/dev/vdc',
'ebs': {'volume_size': 10}},
]
self.assertRaises(exception.InvalidBlockDeviceMapping,
instance_api._build_block_device_mapping,
fake_context, bdms,
fakes.OSImage(fake_image_template))
# check fault on set snapshot id if bdm doesn't have one
fake_image_template['properties']['block_device_mapping'] = [
{'device_name': 'vdc',
'source_type': 'blank',
'volume_size': 10},
]
bdms = [
{'device_name': '/dev/vdc',
'ebs': {'snapshot_id': fakes.ID_EC2_SNAPSHOT_1}},
]
self.assertRaises(exception.InvalidBlockDeviceMapping,
instance_api._build_block_device_mapping,
fake_context, bdms,
fakes.OSImage(fake_image_template))
@mock.patch('cinderclient.client.Client')
@mock.patch('novaclient.client.Client')
@mock.patch('ec2api.db.api.IMPL')