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)
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
# under the License.
import os_traits
from oslo_log import log as logging
import nova.conf
@ -84,9 +85,38 @@ def map_az_to_placement_aggregate(ctxt, request_spec):
[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 = [
require_tenant_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
expected_fake_driver_capability_traits = set([
six.u(trait) for trait in [
os_traits.COMPUTE_IMAGE_TYPE_RAW,
os_traits.COMPUTE_NET_ATTACH_INTERFACE,
os_traits.COMPUTE_NET_ATTACH_INTERFACE_WITH_TAG,
os_traits.COMPUTE_VOLUME_ATTACH_WITH_TAG,

View File

@ -193,6 +193,28 @@ class ServersTest(ServersTestBase):
self._run_periodics()
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):
# Create a server which will enter error state.
@ -4474,6 +4496,15 @@ class VolumeBackedServerTest(integrated_helpers.ProviderUsageBaseTestCase):
expected_usage,
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):
server = self._create_volume_backed_server()
allocs = self._get_allocations_by_server_uuid(server['id'])

View File

@ -152,3 +152,45 @@ class TestRequestFilter(test.NoDBTestCase):
mock.call(self.context,
key='availability_zone',
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_multiattach": 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

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.