diff --git a/nova/conf/scheduler.py b/nova/conf/scheduler.py index d76693d6ade0..7e368424ca3a 100644 --- a/nova/conf/scheduler.py +++ b/nova/conf/scheduler.py @@ -198,7 +198,17 @@ flavor/image. If an aggregate is configured with a property with key and/or image metadata must also contain ``trait:$TRAIT_NAME=required`` to be eligible to be scheduled to hosts in that aggregate. More technical details at https://docs.openstack.org/nova/latest/reference/isolate-aggregates.html -""") +"""), + cfg.BoolOpt("image_metadata_prefilter", + default=False, + help=""" +This setting causes the scheduler to transform well known image metadata +properties into placement required traits to filter host based on image +metadata. This feature requires host support and is currently supported by the +following compute drivers: + +- ``libvirt.LibvirtDriver`` (since Ussuri (21.0.0)) +"""), ] filter_scheduler_group = cfg.OptGroup(name="filter_scheduler", diff --git a/nova/scheduler/request_filter.py b/nova/scheduler/request_filter.py index d5d6501293b8..0b783ae64c20 100644 --- a/nova/scheduler/request_filter.py +++ b/nova/scheduler/request_filter.py @@ -193,6 +193,47 @@ def require_image_type_support(ctxt, request_spec): return True +@trace_request_filter +def transform_image_metadata(ctxt, request_spec): + """Transform image metadata to required traits. + + This will modify the request_spec to request hosts that support + virtualisation capabilities based on the image metadata properties. + """ + if not CONF.scheduler.image_metadata_prefilter: + return False + + prefix_map = { + 'hw_cdrom_bus': 'COMPUTE_STORAGE_BUS', + 'hw_disk_bus': 'COMPUTE_STORAGE_BUS', + 'hw_video_model': 'COMPUTE_GRAPHICS_MODEL', + 'hw_vif_model': 'COMPUTE_NET_VIF_MODEL', + } + + trait_names = [] + + for key, prefix in prefix_map.items(): + if key in request_spec.image.properties: + value = request_spec.image.properties.get(key).replace( + '-', '_').upper() + trait_name = f'{prefix}_{value}' + if not hasattr(os_traits, trait_name): + LOG.error(('Computed trait name %r is not valid; ' + 'is os-traits up to date?'), trait_name) + return False + + trait_names.append(trait_name) + + for trait_name in trait_names: + LOG.debug( + 'transform_image_metadata request filter added required ' + 'trait %s', trait_name + ) + request_spec.root_required.add(trait_name) + + return True + + @trace_request_filter def compute_status_filter(ctxt, request_spec): """Pre-filter compute node resource providers using COMPUTE_STATUS_DISABLED @@ -215,6 +256,7 @@ ALL_REQUEST_FILTERS = [ require_image_type_support, compute_status_filter, isolate_aggregates, + transform_image_metadata, ] diff --git a/nova/tests/unit/scheduler/test_request_filter.py b/nova/tests/unit/scheduler/test_request_filter.py index 556dcb219a30..a7f590da2fcd 100644 --- a/nova/tests/unit/scheduler/test_request_filter.py +++ b/nova/tests/unit/scheduler/test_request_filter.py @@ -12,11 +12,13 @@ import mock import os_traits as ot + from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils import timeutils from nova import context as nova_context from nova import exception +from nova.network import model as network_model from nova import objects from nova.scheduler import request_filter from nova import test @@ -400,3 +402,36 @@ class TestRequestFilter(test.NoDBTestCase): log_lines = [c[0][0] for c in mock_log.debug.call_args_list] self.assertIn('added forbidden trait', log_lines[0]) self.assertIn('took %.1f seconds', log_lines[1]) + + @mock.patch.object(request_filter, 'LOG', new=mock.Mock()) + def test_transform_image_metadata(self): + self.flags(image_metadata_prefilter=True, group='scheduler') + properties = objects.ImageMetaProps( + hw_disk_bus=objects.fields.DiskBus.SATA, + hw_cdrom_bus=objects.fields.DiskBus.IDE, + hw_video_model=objects.fields.VideoModel.QXL, + hw_vif_model=network_model.VIF_MODEL_VIRTIO + ) + reqspec = objects.RequestSpec( + image=objects.ImageMeta(properties=properties), + flavor=objects.Flavor(extra_specs={}), + ) + self.assertTrue( + request_filter.transform_image_metadata(None, reqspec) + ) + expected = { + 'COMPUTE_GRAPHICS_MODEL_QXL', + 'COMPUTE_NET_VIF_MODEL_VIRTIO', + 'COMPUTE_STORAGE_BUS_IDE', + 'COMPUTE_STORAGE_BUS_SATA', + } + self.assertEqual(expected, reqspec.root_required) + + def test_transform_image_metadata__disabled(self): + self.flags(image_metadata_prefilter=False, group='scheduler') + reqspec = objects.RequestSpec(flavor=objects.Flavor(extra_specs={})) + # Assert that we completely skip the filter if disabled + self.assertFalse( + request_filter.transform_image_metadata(self.context, reqspec) + ) + self.assertEqual(set(), reqspec.root_required) diff --git a/releasenotes/notes/image-metadata-prefiltering-2921c1d38951f7a9.yaml b/releasenotes/notes/image-metadata-prefiltering-2921c1d38951f7a9.yaml new file mode 100644 index 000000000000..95ffc9cfa6ba --- /dev/null +++ b/releasenotes/notes/image-metadata-prefiltering-2921c1d38951f7a9.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + A new image metadata prefilter has been added to allow translation of + hypervisor-specific device model requests to standard traits. When this + feature is enabled, nova is able to utilize placement to select hosts that + are capable of emulating the requested devices, avoiding hosts that + could not support the request. This feature is currently supported by the + libvirt driver and can be enabled by configuring the + ``[scheduler]/image_metadata_prefilter`` to ``True`` in the controller + ``nova.conf``.