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:
parent
8eb53f5b5c
commit
57978de4a8
@ -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.
|
||||
"""),
|
||||
]
|
||||
|
||||
|
@ -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,
|
||||
]
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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'])
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user