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(
|
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)
|
||||||
|
@ -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'
|
||||||
|
Loading…
Reference in New Issue
Block a user