diff --git a/heat/engine/resources/openstack/nova/server.py b/heat/engine/resources/openstack/nova/server.py index 8c95a47d4d..5c711128e4 100644 --- a/heat/engine/resources/openstack/nova/server.py +++ b/heat/engine/resources/openstack/nova/server.py @@ -90,6 +90,8 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin, BLOCK_DEVICE_MAPPING_BOOT_INDEX, BLOCK_DEVICE_MAPPING_VOLUME_SIZE, BLOCK_DEVICE_MAPPING_DELETE_ON_TERM, + BLOCK_DEVICE_MAPPING_EPHEMERAL_SIZE, + BLOCK_DEVICE_MAPPING_EPHEMERAL_FORMAT, ) = ( 'device_name', 'volume_id', @@ -102,6 +104,8 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin, 'boot_index', 'volume_size', 'delete_on_termination', + 'ephemeral_size', + 'ephemeral_format' ) _NETWORK_KEYS = ( @@ -249,6 +253,24 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin, properties.Schema.INTEGER, _('The size of the swap, in MB.') ), + BLOCK_DEVICE_MAPPING_EPHEMERAL_SIZE: properties.Schema( + properties.Schema.INTEGER, + _('The size of the local ephemeral block device, ' + 'in GB.'), + support_status=support.SupportStatus(version='8.0.0'), + constraints=[constraints.Range(min=1)] + ), + BLOCK_DEVICE_MAPPING_EPHEMERAL_FORMAT: properties.Schema( + properties.Schema.STRING, + _('The format of the local ephemeral block device. ' + 'If no format is specified, uses default value, ' + 'defined in nova configuration file.'), + constraints=[ + constraints.AllowedValues(['ext2', 'ext3', 'ext4', + 'xfs', 'ntfs']) + ], + support_status=support.SupportStatus(version='8.0.0') + ), BLOCK_DEVICE_MAPPING_DEVICE_TYPE: properties.Schema( properties.Schema.STRING, _('Device type: at the moment we can make distinction ' @@ -934,6 +956,22 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin, 'volume_size': mapping.get( cls.BLOCK_DEVICE_MAPPING_SWAP_SIZE), } + elif (mapping.get(cls.BLOCK_DEVICE_MAPPING_EPHEMERAL_SIZE) or + mapping.get(cls.BLOCK_DEVICE_MAPPING_EPHEMERAL_FORMAT)): + bmd_dict = { + 'source_type': 'blank', + 'destination_type': 'local', + 'boot_index': -1, + 'delete_on_termination': True + } + ephemeral_size = mapping.get( + cls.BLOCK_DEVICE_MAPPING_EPHEMERAL_SIZE) + if ephemeral_size: + bmd_dict.update({'volume_size': ephemeral_size}) + ephemeral_format = mapping.get( + cls.BLOCK_DEVICE_MAPPING_EPHEMERAL_FORMAT) + if ephemeral_format: + bmd_dict.update({'guest_format': ephemeral_format}) # NOTE(prazumovsky): In case of server doesn't take empty value of # device name, need to escape from such situation. @@ -1222,19 +1260,27 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin, snapshot_id = mapping.get(self.BLOCK_DEVICE_MAPPING_SNAPSHOT_ID) image_id = mapping.get(self.BLOCK_DEVICE_MAPPING_IMAGE) swap_size = mapping.get(self.BLOCK_DEVICE_MAPPING_SWAP_SIZE) + ephemeral = (mapping.get( + self.BLOCK_DEVICE_MAPPING_EPHEMERAL_SIZE) or mapping.get( + self.BLOCK_DEVICE_MAPPING_EPHEMERAL_FORMAT)) - property_tuple = (volume_id, snapshot_id, image_id, swap_size) + property_tuple = (volume_id, snapshot_id, image_id, swap_size, + ephemeral) - if property_tuple.count(None) < 3: + if property_tuple.count(None) < 4: raise exception.ResourcePropertyConflict( self.BLOCK_DEVICE_MAPPING_VOLUME_ID, self.BLOCK_DEVICE_MAPPING_SNAPSHOT_ID, self.BLOCK_DEVICE_MAPPING_IMAGE, - self.BLOCK_DEVICE_MAPPING_SWAP_SIZE) + self.BLOCK_DEVICE_MAPPING_SWAP_SIZE, + self.BLOCK_DEVICE_MAPPING_EPHEMERAL_SIZE, + self.BLOCK_DEVICE_MAPPING_EPHEMERAL_FORMAT + ) - if property_tuple.count(None) == 4: - msg = _('Either volume_id, snapshot_id, image_id or ' - 'swap_size must be specified.') + if property_tuple.count(None) == 5: + msg = _('Either volume_id, snapshot_id, image_id, swap_size, ' + 'ephemeral_size or ephemeral_format must be ' + 'specified.') raise exception.StackValidationFailed(message=msg) if any((volume_id is not None, snapshot_id is not None, diff --git a/heat/tests/openstack/nova/test_server.py b/heat/tests/openstack/nova/test_server.py index 389a0d293d..c430dae7a2 100644 --- a/heat/tests/openstack/nova/test_server.py +++ b/heat/tests/openstack/nova/test_server.py @@ -2727,6 +2727,18 @@ class ServersTest(common.HeatTestCase): {'device_name': ''} ])) + self.assertEqual([ + {'source_type': 'blank', + 'destination_type': 'local', + 'boot_index': -1, + 'delete_on_termination': True, + 'volume_size': 1, + 'guest_format': 'ext4'} + ], servers.Server._build_block_device_mapping_v2([ + {'ephemeral_size': 1, + 'ephemeral_format': 'ext4'} + ])) + def test_block_device_mapping_v2_image_resolve(self): (tmpl, stack) = self._setup_test_stack('mapping', test_templ=bdm_v2_template) @@ -2801,8 +2813,8 @@ class ServersTest(common.HeatTestCase): resource_defns['WebServer'], stack) exc = self.assertRaises(exception.StackValidationFailed, server.validate) - msg = ('Either volume_id, snapshot_id, image_id or swap_size must ' - 'be specified.') + 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)) @mock.patch.object(nova.NovaClientPlugin, '_create') diff --git a/releasenotes/notes/server-ephemeral-bdm-v2-55e0fe2afc5d8b63.yaml b/releasenotes/notes/server-ephemeral-bdm-v2-55e0fe2afc5d8b63.yaml new file mode 100644 index 0000000000..0bfb5170c2 --- /dev/null +++ b/releasenotes/notes/server-ephemeral-bdm-v2-55e0fe2afc5d8b63.yaml @@ -0,0 +1,8 @@ +--- +features: + - OS::Nova::Server now supports ephemeral_size and ephemeral_format + properties for block_device_mapping_v2 property. Property ephemeral_size is + integer, that require flavor with ephemeral disk size greater that 0. + Property ephemeral_format is string with allowed values ext2, ext3, ext4, + xfs and ntfs for Windows guests; it is optional and if has no value, uses + default, defined in nova config file.