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)
|
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.
|
||||||
"""),
|
"""),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -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,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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'])
|
||||||
|
@ -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())
|
||||||
|
@ -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
|
||||||
|
@ -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…
x
Reference in New Issue
Block a user