Merge "Add validation of block_device_mapping_v2"

This commit is contained in:
Jenkins 2017-07-21 05:32:29 +00:00 committed by Gerrit Code Review
commit d85361bd9f
2 changed files with 80 additions and 80 deletions

View File

@ -311,7 +311,10 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
), ),
BLOCK_DEVICE_MAPPING_BOOT_INDEX: properties.Schema( BLOCK_DEVICE_MAPPING_BOOT_INDEX: properties.Schema(
properties.Schema.INTEGER, properties.Schema.INTEGER,
_('Integer used for ordering the boot disks.'), _('Integer used for ordering the boot disks. If '
'it is not specified, value "0" will be set '
'for bootable sources (volume, snapshot, image); '
'value "-1" will be set for non-bootable sources.'),
), ),
BLOCK_DEVICE_MAPPING_VOLUME_SIZE: properties.Schema( BLOCK_DEVICE_MAPPING_VOLUME_SIZE: properties.Schema(
properties.Schema.INTEGER, properties.Schema.INTEGER,
@ -1341,11 +1344,16 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
# either volume_id or snapshot_id needs to be specified, but not both # either volume_id or snapshot_id needs to be specified, but not both
# for block device mapping. # for block device mapping.
bdm = self.properties[self.BLOCK_DEVICE_MAPPING] or [] bdm = self.properties[self.BLOCK_DEVICE_MAPPING] or []
bootable_vol = False bdm_v2 = self.properties[self.BLOCK_DEVICE_MAPPING_V2] or []
image = self.properties[self.IMAGE]
if bdm and bdm_v2:
raise exception.ResourcePropertyConflict(
self.BLOCK_DEVICE_MAPPING, self.BLOCK_DEVICE_MAPPING_V2)
bootable = image is not None
for mapping in bdm: for mapping in bdm:
device_name = mapping[self.BLOCK_DEVICE_MAPPING_DEVICE_NAME] device_name = mapping[self.BLOCK_DEVICE_MAPPING_DEVICE_NAME]
if device_name == 'vda': if device_name == 'vda':
bootable_vol = True bootable = True
volume_id = mapping.get(self.BLOCK_DEVICE_MAPPING_VOLUME_ID) volume_id = mapping.get(self.BLOCK_DEVICE_MAPPING_VOLUME_ID)
snapshot_id = mapping.get(self.BLOCK_DEVICE_MAPPING_SNAPSHOT_ID) snapshot_id = mapping.get(self.BLOCK_DEVICE_MAPPING_SNAPSHOT_ID)
@ -1358,15 +1366,12 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
' device mapping %s') % device_name ' device mapping %s') % device_name
raise exception.StackValidationFailed(message=msg) raise exception.StackValidationFailed(message=msg)
bdm_v2 = self.properties[self.BLOCK_DEVICE_MAPPING_V2] or [] bootable_devs = [image]
if bdm and bdm_v2:
raise exception.ResourcePropertyConflict(
self.BLOCK_DEVICE_MAPPING, self.BLOCK_DEVICE_MAPPING_V2)
for mapping in bdm_v2: for mapping in bdm_v2:
volume_id = mapping.get(self.BLOCK_DEVICE_MAPPING_VOLUME_ID) volume_id = mapping.get(self.BLOCK_DEVICE_MAPPING_VOLUME_ID)
snapshot_id = mapping.get(self.BLOCK_DEVICE_MAPPING_SNAPSHOT_ID) snapshot_id = mapping.get(self.BLOCK_DEVICE_MAPPING_SNAPSHOT_ID)
image_id = mapping.get(self.BLOCK_DEVICE_MAPPING_IMAGE) image_id = mapping.get(self.BLOCK_DEVICE_MAPPING_IMAGE)
boot_index = mapping.get(self.BLOCK_DEVICE_MAPPING_BOOT_INDEX)
swap_size = mapping.get(self.BLOCK_DEVICE_MAPPING_SWAP_SIZE) swap_size = mapping.get(self.BLOCK_DEVICE_MAPPING_SWAP_SIZE)
ephemeral = (mapping.get( ephemeral = (mapping.get(
self.BLOCK_DEVICE_MAPPING_EPHEMERAL_SIZE) or mapping.get( self.BLOCK_DEVICE_MAPPING_EPHEMERAL_SIZE) or mapping.get(
@ -1393,9 +1398,21 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
if any((volume_id is not None, snapshot_id is not None, if any((volume_id is not None, snapshot_id is not None,
image_id is not None)): image_id is not None)):
bootable_vol = True # boot_index is not specified, set boot_index=0 when
# build_block_device_mapping for volume, snapshot, image
return bootable_vol if boot_index is None or boot_index == 0:
bootable = True
bootable_devs.append(volume_id)
bootable_devs.append(snapshot_id)
bootable_devs.append(image_id)
if not bootable:
msg = _('Neither image nor bootable volume is specified for '
'instance %s') % self.name
raise exception.StackValidationFailed(message=msg)
if bdm_v2 and len(list(
dev for dev in bootable_devs if dev is not None)) != 1:
msg = _('Multiple bootable sources for instance %s.') % self.name
raise exception.StackValidationFailed(message=msg)
def _validate_image_flavor(self, image, flavor): def _validate_image_flavor(self, image, flavor):
try: try:
@ -1443,15 +1460,10 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
'with user_data_format of SOFTWARE_CONFIG') 'with user_data_format of SOFTWARE_CONFIG')
raise exception.StackValidationFailed(message=msg) raise exception.StackValidationFailed(message=msg)
bootable_vol = self._validate_block_device_mapping() self._validate_block_device_mapping()
# make sure the image exists if specified. # make sure the image exists if specified.
image = self.properties[self.IMAGE] image = self.properties[self.IMAGE]
if image is None and not bootable_vol:
msg = _('Neither image nor bootable volume is specified for'
' instance %s') % self.name
raise exception.StackValidationFailed(message=msg)
flavor = self.properties[self.FLAVOR] flavor = self.properties[self.FLAVOR]
if image: if image:
self._validate_image_flavor(image, flavor) self._validate_image_flavor(image, flavor)

View File

@ -2942,96 +2942,84 @@ class ServersTest(common.HeatTestCase):
'block_device_mapping, block_device_mapping_v2.') 'block_device_mapping, block_device_mapping_v2.')
self.assertEqual(msg, six.text_type(exc)) self.assertEqual(msg, six.text_type(exc))
def _test_validate_bdm_v2(self, stack_name, bdm_v2, with_image=True,
error_msg=None, raise_exc=None):
tmpl, stack = self._setup_test_stack(stack_name)
if not with_image:
del tmpl['Resources']['WebServer']['Properties']['image']
wsp = tmpl.t['Resources']['WebServer']['Properties']
wsp['block_device_mapping_v2'] = bdm_v2
resource_defns = tmpl.resource_definitions(stack)
server = servers.Server('server_create_image_err',
resource_defns['WebServer'], stack)
self.patchobject(nova.NovaClientPlugin, 'get_flavor',
return_value=self.mock_flavor)
self.patchobject(glance.GlanceClientPlugin, 'get_image',
return_value=self.mock_image)
self.stub_VolumeConstraint_validate()
if raise_exc:
ex = self.assertRaises(raise_exc, server.validate)
self.assertIn(error_msg, six.text_type(ex))
else:
self.assertIsNone(server.validate())
@mock.patch.object(nova.NovaClientPlugin, '_create') @mock.patch.object(nova.NovaClientPlugin, '_create')
def test_validate_conflict_block_device_mapping_v2_props(self, def test_validate_conflict_block_device_mapping_v2_props(self,
mock_create): mock_create):
stack_name = 'val_blkdev2' stack_name = 'val_blkdev2'
(tmpl, stack) = self._setup_test_stack(stack_name)
bdm_v2 = [{'volume_id': '1', 'snapshot_id': 2}] bdm_v2 = [{'volume_id': '1', 'snapshot_id': 2}]
wsp = tmpl.t['Resources']['WebServer']['Properties'] error_msg = ('Cannot define the following properties at '
wsp['block_device_mapping_v2'] = bdm_v2 'the same time: volume_id, snapshot_id')
resource_defns = tmpl.resource_definitions(stack)
server = servers.Server('server_create_image_err',
resource_defns['WebServer'], stack)
self.stub_VolumeConstraint_validate()
self.stub_SnapshotConstraint_validate() self.stub_SnapshotConstraint_validate()
self.assertRaises(exception.ResourcePropertyConflict, server.validate) self._test_validate_bdm_v2(
stack_name, bdm_v2,
raise_exc=exception.ResourcePropertyConflict,
error_msg=error_msg)
@mock.patch.object(nova.NovaClientPlugin, '_create') @mock.patch.object(nova.NovaClientPlugin, '_create')
def test_validate_without_bootable_source_in_bdm_v2(self, mock_create): def test_validate_bdm_v2_with_empty_mapping(self, mock_create):
stack_name = 'val_blkdev2' stack_name = 'val_blkdev2'
(tmpl, stack) = self._setup_test_stack(stack_name)
bdm_v2 = [{}] bdm_v2 = [{}]
wsp = tmpl.t['Resources']['WebServer']['Properties']
wsp['block_device_mapping_v2'] = bdm_v2
resource_defns = tmpl.resource_definitions(stack)
server = servers.Server('server_create_image_err',
resource_defns['WebServer'], stack)
exc = self.assertRaises(exception.StackValidationFailed,
server.validate)
msg = ('Either volume_id, snapshot_id, image_id, swap_size, ' msg = ('Either volume_id, snapshot_id, image_id, swap_size, '
'ephemeral_size or ephemeral_format must be specified.') 'ephemeral_size or ephemeral_format must be specified.')
self.assertEqual(msg, six.text_type(exc)) self._test_validate_bdm_v2(stack_name, bdm_v2,
raise_exc=exception.StackValidationFailed,
error_msg=msg)
@mock.patch.object(nova.NovaClientPlugin, '_create') @mock.patch.object(nova.NovaClientPlugin, '_create')
def test_validate_bdm_v2_properties_success(self, mock_create): def test_validate_bdm_v2_properties_success(self, mock_create):
stack_name = 'v2_properties' stack_name = 'bdm_v2_success'
(tmpl, stack) = self._setup_test_stack(stack_name) bdm_v2 = [{'volume_id': '1', 'boot_index': -1}]
self._test_validate_bdm_v2(stack_name, bdm_v2)
bdm_v2 = [{'volume_id': '1'}]
wsp = tmpl.t['Resources']['WebServer']['Properties']
wsp['block_device_mapping_v2'] = bdm_v2
resource_defns = tmpl.resource_definitions(stack)
server = servers.Server('server_create_image_err',
resource_defns['WebServer'], stack)
self.patchobject(nova.NovaClientPlugin, 'get_flavor',
return_value=self.mock_flavor)
self.patchobject(glance.GlanceClientPlugin, 'get_image',
return_value=self.mock_image)
self.stub_VolumeConstraint_validate()
self.assertIsNone(server.validate())
@mock.patch.object(nova.NovaClientPlugin, '_create') @mock.patch.object(nova.NovaClientPlugin, '_create')
def test_validate_bdm_v2_with_unresolved_volume(self, mock_create): def test_validate_bdm_v2_with_unresolved_volume(self, mock_create):
stack_name = 'v2_properties' stack_name = 'bdm_v2_with_unresolved_vol'
(tmpl, stack) = self._setup_test_stack(stack_name)
del tmpl['Resources']['WebServer']['Properties']['image']
# empty string indicates that volume is unresolved # empty string indicates that volume is unresolved
bdm_v2 = [{'volume_id': ''}] bdm_v2 = [{'volume_id': ''}]
wsp = tmpl.t['Resources']['WebServer']['Properties'] self._test_validate_bdm_v2(stack_name, bdm_v2, with_image=False)
wsp['block_device_mapping_v2'] = bdm_v2
resource_defns = tmpl.resource_definitions(stack) @mock.patch.object(nova.NovaClientPlugin, '_create')
server = servers.Server('server_create_image_err', def test_validate_bdm_v2_multiple_bootable_source(self, mock_create):
resource_defns['WebServer'], stack) stack_name = 'v2_multiple_bootable'
self.patchobject(nova.NovaClientPlugin, 'get_flavor', # with two bootable sources: volume_id and image
return_value=self.mock_flavor) bdm_v2 = [{'volume_id': '1', 'boot_index': 0}]
self.patchobject(glance.GlanceClientPlugin, 'get_image', msg = ('Multiple bootable sources for instance')
return_value=self.mock_image) self._test_validate_bdm_v2(stack_name, bdm_v2,
self.stub_VolumeConstraint_validate() raise_exc=exception.StackValidationFailed,
self.assertIsNone(server.validate()) error_msg=msg)
@mock.patch.object(nova.NovaClientPlugin, '_create') @mock.patch.object(nova.NovaClientPlugin, '_create')
def test_validate_bdm_v2_properties_no_bootable_vol(self, mock_create): def test_validate_bdm_v2_properties_no_bootable_vol(self, mock_create):
stack_name = 'v2_properties' stack_name = 'bdm_v2_no_bootable'
(tmpl, stack) = self._setup_test_stack(stack_name)
bdm_v2 = [{'swap_size': 10}] bdm_v2 = [{'swap_size': 10}]
wsp = tmpl.t['Resources']['WebServer']['Properties']
wsp['block_device_mapping_v2'] = bdm_v2
wsp.pop('image')
resource_defns = tmpl.resource_definitions(stack)
server = servers.Server('server_create_image_err',
resource_defns['WebServer'], stack)
exc = self.assertRaises(exception.StackValidationFailed,
server.validate)
msg = ('Neither image nor bootable volume is specified for instance ' msg = ('Neither image nor bootable volume is specified for instance '
'server_create_image_err') 'server_create_image_err')
self.assertEqual(msg, six.text_type(exc)) self._test_validate_bdm_v2(stack_name, bdm_v2,
raise_exc=exception.StackValidationFailed,
error_msg=msg,
with_image=False)
def test_validate_metadata_too_many(self): def test_validate_metadata_too_many(self):
stack_name = 'srv_val_metadata' stack_name = 'srv_val_metadata'