Add transform_image_metadata request filter
Add a table-driven prefilter to transform image metadata into required traits. This requires a new config option to make the filter optional. Change-Id: I257ff81e23cdae6f2b62ec3d071b8f8f32d97781 Implements: blueprint image-metadata-prefiltering Co-Authored-By: Stephen Finucane <sfinucan@redhat.com>
This commit is contained in:
		@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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``.
 | 
			
		||||
		Reference in New Issue
	
	Block a user