Add image type request filter

This enables the scheduler, if configured, to limit placement results
to only computes that support the disk_format of the image used
for the request.

Change-Id: I41511365eb2b76c4cad804445766638a92b68378
This commit is contained in:
Dan Smith 2019-04-29 15:03:47 -07:00
parent 8eb53f5b5c
commit 57978de4a8
7 changed files with 123 additions and 0 deletions

View File

@ -177,6 +177,12 @@ be the same as not finding any suitable hosts.
Note that if you enable this flag, you can disable the (less efficient) Note that if you enable this flag, you can disable the (less efficient)
AvailabilityZoneFilter in the scheduler. AvailabilityZoneFilter in the scheduler.
"""),
cfg.BoolOpt("query_placement_for_image_type_support",
default=False,
help="""
This setting causes the scheduler to ask placement only for compute
hosts that support the ``disk_format`` of the image used in the request.
"""), """),
] ]

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import os_traits
from oslo_log import log as logging from oslo_log import log as logging
import nova.conf import nova.conf
@ -84,9 +85,38 @@ def map_az_to_placement_aggregate(ctxt, request_spec):
[agg.uuid for agg in aggregates]) [agg.uuid for agg in aggregates])
def require_image_type_support(ctxt, request_spec):
"""Request type-specific trait on candidates.
This will modify the request_spec to request hosts that support the
disk_format of the image provided.
"""
if not CONF.scheduler.query_placement_for_image_type_support:
return
if request_spec.is_bfv:
# We are booting from volume, and thus compute node image
# disk_format support does not matter.
return
disk_format = request_spec.image.disk_format
trait_name = 'COMPUTE_IMAGE_TYPE_%s' % disk_format.upper()
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
# NOTE(danms): We are using the transient flavor in the request spec
# to add the trait that we need. We make sure that we reset the dirty-ness
# of this field to avoid persisting it.
request_spec.flavor.extra_specs['trait:%s' % trait_name] = 'required'
request_spec.obj_reset_changes(fields=['flavor'], recursive=True)
ALL_REQUEST_FILTERS = [ ALL_REQUEST_FILTERS = [
require_tenant_aggregate, require_tenant_aggregate,
map_az_to_placement_aggregate, map_az_to_placement_aggregate,
require_image_type_support,
] ]

View File

@ -394,6 +394,7 @@ class ProviderUsageBaseTestCase(test.TestCase, InstanceHelperMixin):
# nova.virt.fake.FakeDriver.capabilities # nova.virt.fake.FakeDriver.capabilities
expected_fake_driver_capability_traits = set([ expected_fake_driver_capability_traits = set([
six.u(trait) for trait in [ six.u(trait) for trait in [
os_traits.COMPUTE_IMAGE_TYPE_RAW,
os_traits.COMPUTE_NET_ATTACH_INTERFACE, os_traits.COMPUTE_NET_ATTACH_INTERFACE,
os_traits.COMPUTE_NET_ATTACH_INTERFACE_WITH_TAG, os_traits.COMPUTE_NET_ATTACH_INTERFACE_WITH_TAG,
os_traits.COMPUTE_VOLUME_ATTACH_WITH_TAG, os_traits.COMPUTE_VOLUME_ATTACH_WITH_TAG,

View File

@ -193,6 +193,28 @@ class ServersTest(ServersTestBase):
self._run_periodics() self._run_periodics()
self.assertEqual([1], list(self._get_node_build_failures().values())) self.assertEqual([1], list(self._get_node_build_failures().values()))
def test_create_server_with_image_type_filter(self):
self.flags(query_placement_for_image_type_support=True,
group='scheduler')
raw_image = '155d900f-4e14-4e4c-a73d-069cbf4541e6'
vhd_image = 'a440c04b-79fa-479c-bed1-0b816eaec379'
server = self._build_minimal_create_server_request(
image_uuid=vhd_image)
server = self.api.post_server({'server': server})
server = self.api.get_server(server['id'])
errored_server = self._wait_for_state_change(server, server['status'])
self.assertEqual('ERROR', errored_server['status'])
self.assertIn('No valid host', errored_server['fault']['message'])
server = self._build_minimal_create_server_request(
image_uuid=raw_image)
server = self.api.post_server({'server': server})
server = self.api.get_server(server['id'])
created_server = self._wait_for_state_change(server, server['status'])
self.assertEqual('ACTIVE', created_server['status'])
def _test_create_server_with_error_with_retries(self): def _test_create_server_with_error_with_retries(self):
# Create a server which will enter error state. # Create a server which will enter error state.
@ -4474,6 +4496,15 @@ class VolumeBackedServerTest(integrated_helpers.ProviderUsageBaseTestCase):
expected_usage, expected_usage,
self.admin_api.get_hypervisor_stats()['local_gb_used']) self.admin_api.get_hypervisor_stats()['local_gb_used'])
def test_volume_backed_image_type_filter(self):
# Enable the image type support filter and ensure that a
# non-image-having volume-backed server can still boot
self.flags(query_placement_for_image_type_support=True,
group='scheduler')
server = self._create_volume_backed_server()
created_server = self.api.get_server(server['id'])
self.assertEqual('ACTIVE', created_server['status'])
def test_volume_backed_no_disk_allocation(self): def test_volume_backed_no_disk_allocation(self):
server = self._create_volume_backed_server() server = self._create_volume_backed_server()
allocs = self._get_allocations_by_server_uuid(server['id']) allocs = self._get_allocations_by_server_uuid(server['id'])

View File

@ -152,3 +152,45 @@ class TestRequestFilter(test.NoDBTestCase):
mock.call(self.context, mock.call(self.context,
key='availability_zone', key='availability_zone',
value='myaz')]) value='myaz')])
def test_require_image_type_support_disabled(self):
self.flags(query_placement_for_image_type_support=False,
group='scheduler')
reqspec = objects.RequestSpec(flavor=objects.Flavor(extra_specs={}),
is_bfv=False)
# Assert that we completely skip the filter if disabled
request_filter.require_image_type_support(self.context, reqspec)
self.assertEqual({}, reqspec.flavor.extra_specs)
def test_require_image_type_support_volume_backed(self):
self.flags(query_placement_for_image_type_support=True,
group='scheduler')
reqspec = objects.RequestSpec(flavor=objects.Flavor(extra_specs={}),
is_bfv=True)
# Assert that we completely skip the filter if no image
request_filter.require_image_type_support(self.context, reqspec)
self.assertEqual({}, reqspec.flavor.extra_specs)
def test_require_image_type_support_unknown(self):
self.flags(query_placement_for_image_type_support=True,
group='scheduler')
reqspec = objects.RequestSpec(
image=objects.ImageMeta(disk_format='danformat'),
flavor=objects.Flavor(extra_specs={}),
is_bfv=False)
# Assert that we completely skip the filter if no matching trait
request_filter.require_image_type_support(self.context, reqspec)
self.assertEqual({}, reqspec.flavor.extra_specs)
def test_require_image_type_support_adds_trait(self):
self.flags(query_placement_for_image_type_support=True,
group='scheduler')
reqspec = objects.RequestSpec(
image=objects.ImageMeta(disk_format='raw'),
flavor=objects.Flavor(extra_specs={}),
is_bfv=False)
# Assert that we add the trait to the flavor as required
request_filter.require_image_type_support(self.context, reqspec)
self.assertEqual({'trait:COMPUTE_IMAGE_TYPE_RAW': 'required'},
reqspec.flavor.extra_specs)
self.assertEqual(set(), reqspec.flavor.obj_what_changed())

View File

@ -135,6 +135,10 @@ class FakeDriver(driver.ComputeDriver):
"supports_extend_volume": True, "supports_extend_volume": True,
"supports_multiattach": True, "supports_multiattach": True,
"supports_trusted_certs": True, "supports_trusted_certs": True,
# Supported image types
"supports_image_type_raw": True,
"supports_image_type_vhd": False,
} }
# Since we don't have a real hypervisor, pretend we have lots of # Since we don't have a real hypervisor, pretend we have lots of

View File

@ -0,0 +1,9 @@
---
features:
- |
The scheduler can now use placement to more efficiently query for hosts that support
the disk_format of the image used in a given request. The
``[scheduler]/query_placement_for_image_type_support`` config
option enables this behavior, but must not be turned on until all
computes have been upgraded to this version and thus are exposing image
type support traits.