Merge "Add validation of block_device_mapping_v2"
This commit is contained in:
commit
d85361bd9f
@ -311,7 +311,10 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
|
||||
),
|
||||
BLOCK_DEVICE_MAPPING_BOOT_INDEX: properties.Schema(
|
||||
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(
|
||||
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
|
||||
# for block device mapping.
|
||||
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:
|
||||
device_name = mapping[self.BLOCK_DEVICE_MAPPING_DEVICE_NAME]
|
||||
if device_name == 'vda':
|
||||
bootable_vol = True
|
||||
bootable = True
|
||||
|
||||
volume_id = mapping.get(self.BLOCK_DEVICE_MAPPING_VOLUME_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
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
bdm_v2 = self.properties[self.BLOCK_DEVICE_MAPPING_V2] or []
|
||||
if bdm and bdm_v2:
|
||||
raise exception.ResourcePropertyConflict(
|
||||
self.BLOCK_DEVICE_MAPPING, self.BLOCK_DEVICE_MAPPING_V2)
|
||||
|
||||
bootable_devs = [image]
|
||||
for mapping in bdm_v2:
|
||||
volume_id = mapping.get(self.BLOCK_DEVICE_MAPPING_VOLUME_ID)
|
||||
snapshot_id = mapping.get(self.BLOCK_DEVICE_MAPPING_SNAPSHOT_ID)
|
||||
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)
|
||||
ephemeral = (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,
|
||||
image_id is not None)):
|
||||
bootable_vol = True
|
||||
|
||||
return bootable_vol
|
||||
# boot_index is not specified, set boot_index=0 when
|
||||
# build_block_device_mapping for volume, snapshot, image
|
||||
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):
|
||||
try:
|
||||
@ -1443,15 +1460,10 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin,
|
||||
'with user_data_format of SOFTWARE_CONFIG')
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
bootable_vol = self._validate_block_device_mapping()
|
||||
self._validate_block_device_mapping()
|
||||
|
||||
# make sure the image exists if specified.
|
||||
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]
|
||||
if image:
|
||||
self._validate_image_flavor(image, flavor)
|
||||
|
@ -2942,96 +2942,84 @@ class ServersTest(common.HeatTestCase):
|
||||
'block_device_mapping, block_device_mapping_v2.')
|
||||
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')
|
||||
def test_validate_conflict_block_device_mapping_v2_props(self,
|
||||
mock_create):
|
||||
stack_name = 'val_blkdev2'
|
||||
(tmpl, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
bdm_v2 = [{'volume_id': '1', 'snapshot_id': 2}]
|
||||
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.stub_VolumeConstraint_validate()
|
||||
error_msg = ('Cannot define the following properties at '
|
||||
'the same time: volume_id, snapshot_id')
|
||||
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')
|
||||
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'
|
||||
(tmpl, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
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, '
|
||||
'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')
|
||||
def test_validate_bdm_v2_properties_success(self, mock_create):
|
||||
stack_name = 'v2_properties'
|
||||
(tmpl, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
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())
|
||||
stack_name = 'bdm_v2_success'
|
||||
bdm_v2 = [{'volume_id': '1', 'boot_index': -1}]
|
||||
self._test_validate_bdm_v2(stack_name, bdm_v2)
|
||||
|
||||
@mock.patch.object(nova.NovaClientPlugin, '_create')
|
||||
def test_validate_bdm_v2_with_unresolved_volume(self, mock_create):
|
||||
stack_name = 'v2_properties'
|
||||
(tmpl, stack) = self._setup_test_stack(stack_name)
|
||||
del tmpl['Resources']['WebServer']['Properties']['image']
|
||||
|
||||
stack_name = 'bdm_v2_with_unresolved_vol'
|
||||
# empty string indicates that volume is unresolved
|
||||
bdm_v2 = [{'volume_id': ''}]
|
||||
wsp = tmpl.t['Resources']['WebServer']['Properties']
|
||||
wsp['block_device_mapping_v2'] = bdm_v2
|
||||
self._test_validate_bdm_v2(stack_name, bdm_v2, with_image=False)
|
||||
|
||||
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')
|
||||
def test_validate_bdm_v2_multiple_bootable_source(self, mock_create):
|
||||
stack_name = 'v2_multiple_bootable'
|
||||
# with two bootable sources: volume_id and image
|
||||
bdm_v2 = [{'volume_id': '1', 'boot_index': 0}]
|
||||
msg = ('Multiple bootable sources for instance')
|
||||
self._test_validate_bdm_v2(stack_name, bdm_v2,
|
||||
raise_exc=exception.StackValidationFailed,
|
||||
error_msg=msg)
|
||||
|
||||
@mock.patch.object(nova.NovaClientPlugin, '_create')
|
||||
def test_validate_bdm_v2_properties_no_bootable_vol(self, mock_create):
|
||||
stack_name = 'v2_properties'
|
||||
(tmpl, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
stack_name = 'bdm_v2_no_bootable'
|
||||
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 '
|
||||
'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):
|
||||
stack_name = 'srv_val_metadata'
|
||||
|
Loading…
Reference in New Issue
Block a user