From 57978de4a8ef43cd46e95e81e5d1874fdf4c69e4 Mon Sep 17 00:00:00 2001 From: Dan Smith Date: Mon, 29 Apr 2019 15:03:47 -0700 Subject: [PATCH] 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 --- nova/conf/scheduler.py | 6 +++ nova/scheduler/request_filter.py | 30 +++++++++++++ nova/tests/functional/integrated_helpers.py | 1 + nova/tests/functional/test_servers.py | 31 ++++++++++++++ .../unit/scheduler/test_request_filter.py | 42 +++++++++++++++++++ nova/virt/fake.py | 4 ++ ..._type_request_filter-7577ded9834330b6.yaml | 9 ++++ 7 files changed, 123 insertions(+) create mode 100644 releasenotes/notes/image_type_request_filter-7577ded9834330b6.yaml diff --git a/nova/conf/scheduler.py b/nova/conf/scheduler.py index fb51a80f0fb4..5e1d93ed5a02 100644 --- a/nova/conf/scheduler.py +++ b/nova/conf/scheduler.py @@ -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. """), ] diff --git a/nova/scheduler/request_filter.py b/nova/scheduler/request_filter.py index 315251628012..79d67cb21926 100644 --- a/nova/scheduler/request_filter.py +++ b/nova/scheduler/request_filter.py @@ -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, ] diff --git a/nova/tests/functional/integrated_helpers.py b/nova/tests/functional/integrated_helpers.py index 7ccf5f1ef7e1..e009aed43c20 100644 --- a/nova/tests/functional/integrated_helpers.py +++ b/nova/tests/functional/integrated_helpers.py @@ -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, diff --git a/nova/tests/functional/test_servers.py b/nova/tests/functional/test_servers.py index 13123bc4fb3a..963b11ece6cd 100644 --- a/nova/tests/functional/test_servers.py +++ b/nova/tests/functional/test_servers.py @@ -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']) diff --git a/nova/tests/unit/scheduler/test_request_filter.py b/nova/tests/unit/scheduler/test_request_filter.py index 519feb79e867..5cf26ae98931 100644 --- a/nova/tests/unit/scheduler/test_request_filter.py +++ b/nova/tests/unit/scheduler/test_request_filter.py @@ -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()) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 0ac43ce8eeee..61de840b4928 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -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 diff --git a/releasenotes/notes/image_type_request_filter-7577ded9834330b6.yaml b/releasenotes/notes/image_type_request_filter-7577ded9834330b6.yaml new file mode 100644 index 000000000000..6ffafdf07075 --- /dev/null +++ b/releasenotes/notes/image_type_request_filter-7577ded9834330b6.yaml @@ -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. \ No newline at end of file