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:
parent
476e899552
commit
ffc445c964
|
@ -887,6 +887,10 @@ def _parse_image_parameters(context, image_id, kernel_id, ramdisk_id):
|
||||||
|
|
||||||
def _parse_block_device_mapping(context, block_device_mapping):
|
def _parse_block_device_mapping(context, block_device_mapping):
|
||||||
# TODO(ft): check block_device_mapping structure
|
# 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 virtual devices
|
||||||
# TODO(ft): support no_device
|
# TODO(ft): support no_device
|
||||||
bdms = []
|
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):
|
def _build_block_device_mapping(context, block_device_mapping, os_image):
|
||||||
mappings = _parse_block_device_mapping(context, block_device_mapping)
|
mappings = _parse_block_device_mapping(context, block_device_mapping)
|
||||||
properties = ec2utils.deserialize_os_image_properties(os_image)
|
properties = ec2utils.deserialize_os_image_properties(os_image)
|
||||||
|
image_bdms = ec2utils.get_os_image_mappings(properties)
|
||||||
root_device_name = (
|
root_device_name = (
|
||||||
ec2utils.block_device_properties_root_device_name(properties))
|
ec2utils.block_device_properties_root_device_name(properties))
|
||||||
short_root_device_name = ec2utils.block_device_strip_dev(root_device_name)
|
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 = []
|
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:
|
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')
|
source_type = bdm.get('source_type')
|
||||||
if source_type:
|
if source_type:
|
||||||
uuid = bdm.pop('_'.join([source_type, 'id']), None)
|
uuid = bdm.pop('_'.join([source_type, 'id']), None)
|
||||||
if uuid:
|
if uuid:
|
||||||
bdm['uuid'] = uuid
|
bdm['uuid'] = uuid
|
||||||
|
|
||||||
result.append(bdm)
|
result.append(bdm)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -239,6 +239,10 @@ class InvalidSnapshotIDMalformed(EC2InvalidException):
|
||||||
msg_fmg = _('The snapshot %(id)s ID is not valid')
|
msg_fmg = _('The snapshot %(id)s ID is not valid')
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidBlockDeviceMapping(EC2InvalidException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class IncorrectState(EC2IncorrectStateException):
|
class IncorrectState(EC2IncorrectStateException):
|
||||||
msg_fmt = _("The resource is in incorrect state for the request - reason: "
|
msg_fmt = _("The resource is in incorrect state for the request - reason: "
|
||||||
"'%(reason)s'")
|
"'%(reason)s'")
|
||||||
|
|
|
@ -1465,8 +1465,10 @@ class InstancePrivateTestCase(test_base.BaseTestCase):
|
||||||
def test_build_block_device_mapping(self, db_api):
|
def test_build_block_device_mapping(self, db_api):
|
||||||
fake_context = mock.Mock(service_catalog=[{'type': 'fake'}])
|
fake_context = mock.Mock(service_catalog=[{'type': 'fake'}])
|
||||||
db_api.get_item_by_id.side_effect = tools.get_db_api_get_item_by_id(
|
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 = [
|
bdms = [
|
||||||
{'device_name': '/dev/sda1',
|
{'device_name': '/dev/sda1',
|
||||||
'ebs': {'snapshot_id': fakes.ID_EC2_SNAPSHOT_1}},
|
'ebs': {'snapshot_id': fakes.ID_EC2_SNAPSHOT_1}},
|
||||||
|
@ -1496,11 +1498,217 @@ class InstancePrivateTestCase(test_base.BaseTestCase):
|
||||||
'delete_on_termination': True,
|
'delete_on_termination': True,
|
||||||
'boot_index': -1},
|
'boot_index': -1},
|
||||||
]
|
]
|
||||||
|
|
||||||
result = instance_api._build_block_device_mapping(
|
result = instance_api._build_block_device_mapping(
|
||||||
fake_context, bdms, fakes.OSImage(fakes.OS_IMAGE_1))
|
fake_context, bdms, fakes.OSImage(fakes.OS_IMAGE_1))
|
||||||
self.assertEqual(expected, result)
|
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('cinderclient.client.Client')
|
||||||
@mock.patch('novaclient.client.Client')
|
@mock.patch('novaclient.client.Client')
|
||||||
@mock.patch('ec2api.db.api.IMPL')
|
@mock.patch('ec2api.db.api.IMPL')
|
||||||
|
|
Loading…
Reference in New Issue