From 5ea7db9b7195132df4d8efea0a8e41e4f994b23c Mon Sep 17 00:00:00 2001 From: Jim Fehlig Date: Fri, 17 Aug 2012 15:26:01 -0600 Subject: Introduce ImagePropertiesFilter scheduler filter Commit d39137fa added functionality to the ComputeFilter to filter on architecture, hypervisor_type, and vm_mode. The existing ArchFilter already filtered on architecture. This patch merges the ComputeFilter functionality introduced in d39137fa with the ArchFilter to create the ImagePropertiesFilter. The ImagePropertiesFilter uses image properties specified in the request_spec to filter hosts. This patch also adds the ImagePropertiesFilter to the list of default filters specified by the scheduler_default_filters FLAG. Fixes LP Bug #1037339 DocImpact Change-Id: Ifa6fccf2db266b0fe3457d58fc81fdb50ffcbc0f --- doc/source/devref/filter_scheduler.rst | 27 +++-- etc/nova/nova.conf.sample | 2 +- nova/scheduler/filters/arch_filter.py | 44 -------- nova/scheduler/filters/compute_filter.py | 57 +--------- nova/scheduler/filters/image_props_filter.py | 86 +++++++++++++++ nova/scheduler/host_manager.py | 3 +- nova/tests/scheduler/test_host_filters.py | 151 +++++++-------------------- 7 files changed, 146 insertions(+), 224 deletions(-) delete mode 100644 nova/scheduler/filters/arch_filter.py create mode 100644 nova/scheduler/filters/image_props_filter.py diff --git a/doc/source/devref/filter_scheduler.rst b/doc/source/devref/filter_scheduler.rst index d6ceb08ef..807fdceec 100644 --- a/doc/source/devref/filter_scheduler.rst +++ b/doc/source/devref/filter_scheduler.rst @@ -27,8 +27,9 @@ There are some standard filter classes to use (:mod:`nova.scheduler.filters`): * |AllHostsFilter| - frankly speaking, this filter does no operation. It passes all the available hosts. -* |ArchFilter| - filters hosts based on architecture. It passes hosts - that can support the architecture specified in the instance properties. +* |ImagePropertiesFilter| - filters hosts based on properties defined + on the instance's image. It passes hosts that can support the specified + image properties contained in the instance. * |AvailabilityZoneFilter| - filters hosts by availability zone. It passes hosts matching the availability zone specfied in the instance properties. * |ComputeCapabilityFilter| - checks that the capabilities provided by the @@ -86,10 +87,17 @@ scheduler with availability zones support and can configure availability zones on each compute host. This classes method `host_passes` returns `True` if availability zone mentioned in request is the same on the current compute host. -The |ArchFilter| filters hosts based on the architecture specified in the -instance properties. E.g., an instance might require a host that supports -the arm architecture. The |ArchFilter| will only pass hosts that can -support the architecture requested by the instance. +The |ImagePropertiesFilter| filters hosts based on the architecture, +hypervisor type, and virtual machine mode specified in the +instance. E.g., an instance might require a host that supports the arm +architecture on a qemu compute host. The |ImagePropertiesFilter| will only +pass hosts that can statisfy this request. These instance +properties are populated from properties define on the instance's image. +E.g. an image can be decorated with these properties using +`glance image-update img-uuid --property architecture=arm --property +hypervisor_type=qemu` +Only hosts that statify these requirements will pass the +|ImagePropertiesFilter|. |ComputeCapabilitesFilter| checks if the host satisfies any 'extra specs' specfied on the instance type. The 'extra specs' can contain key/value pairs, @@ -160,11 +168,12 @@ The default values for these settings in nova.conf are: :: --scheduler_available_filters=nova.scheduler.filters.standard_filters - --scheduler_default_filters=RamFilter,ComputeFilter,AvailabilityZoneFilter,ComputeCapabilityFilter + --scheduler_default_filters=RamFilter,ComputeFilter,AvailabilityZoneFilter,ComputeCapabilityFilter,ImagePropertiesFilter With this configuration, all filters in `nova.scheduler.filters` would be available, and by default the |RamFilter|, |ComputeFilter|, -|AvailabilityZoneFilter|, and |ComputeCapabilityFilter| would be used. +|AvailabilityZoneFilter|, |ComputeCapabilityFilter|, and +|ImagePropertiesFilter| would be used. If you want to create **your own filter** you just need to inherit from |BaseHostFilter| and implement one method: @@ -278,7 +287,7 @@ P.S.: you can find more examples of using Filter Scheduler and standard filters in :mod:`nova.tests.scheduler`. .. |AllHostsFilter| replace:: :class:`AllHostsFilter ` -.. |ArchFilter| replace:: :class:`ArchFilter ` +.. |ImagePropertiesFilter| replace:: :class:`ImagePropertiesFilter ` .. |AvailabilityZoneFilter| replace:: :class:`AvailabilityZoneFilter ` .. |BaseHostFilter| replace:: :class:`BaseHostFilter ` .. |ComputeCapabilitiesFilter| replace:: :class:`ComputeCapabilitiesFilter ` diff --git a/etc/nova/nova.conf.sample b/etc/nova/nova.conf.sample index 5fba9a816..8eabdf9fd 100644 --- a/etc/nova/nova.conf.sample +++ b/etc/nova/nova.conf.sample @@ -1211,7 +1211,7 @@ #### "nova.scheduler.filters.standard_filters" maps to all #### filters included with nova. -# scheduler_default_filters=AvailabilityZoneFilter,RamFilter,ComputeFilter,ComputeCapabilitiesFilter +# scheduler_default_filters=AvailabilityZoneFilter,RamFilter,ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter #### (ListOpt) Which filter class names to use for filtering hosts when not #### specified in the request. diff --git a/nova/scheduler/filters/arch_filter.py b/nova/scheduler/filters/arch_filter.py deleted file mode 100644 index 625ce2909..000000000 --- a/nova/scheduler/filters/arch_filter.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2011-2012 OpenStack, LLC -# Copyright (c) 2012 Canonical Ltd -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -from nova.openstack.common import log as logging -from nova.scheduler import filters -from nova import utils - - -LOG = logging.getLogger(__name__) - - -class ArchFilter(filters.BaseHostFilter): - """Filter out hosts that can not support the guest architecture. - Note: This is supported for libvirt only now. - """ - - def host_passes(self, host_state, filter_properties): - spec = filter_properties.get('request_spec', {}) - props = spec.get('instance_properties', {}) - - cpu_info = host_state.capabilities.get('cpu_info') - permitted_instances = cpu_info.get('permitted_instance_types', None) - - instance_arch = utils.sys_platform_translate(props.get('architecture')) - - if permitted_instances and instance_arch in permitted_instances: - return True - - LOG.warn(_('%(host_state)s fails permitted_instance_types'), locals()) - return False diff --git a/nova/scheduler/filters/compute_filter.py b/nova/scheduler/filters/compute_filter.py index c0ee98762..2d7c898d6 100644 --- a/nova/scheduler/filters/compute_filter.py +++ b/nova/scheduler/filters/compute_filter.py @@ -22,59 +22,10 @@ LOG = logging.getLogger(__name__) class ComputeFilter(filters.BaseHostFilter): - """Filter on active Compute nodes that satisfy the instance properties""" - - def _instance_supported(self, capabilities, instance_meta): - """Check if the instance is supported by the hypervisor. - - The instance may specify an architecture, hypervisor, and - vm_mode, e.g. (x86_64, kvm, hvm). - """ - inst_arch = instance_meta.get('image_architecture', None) - inst_h_type = instance_meta.get('image_hypervisor_type', None) - inst_vm_mode = instance_meta.get('image_vm_mode', None) - inst_props_req = (inst_arch, inst_h_type, inst_vm_mode) - - # Supported if no compute-related instance properties are specified - if not any(inst_props_req): - return True - - supp_instances = capabilities.get('supported_instances', None) - # Not supported if an instance property is requested but nothing - # advertised by the host. - if not supp_instances: - LOG.debug(_("Instance contains properties %(instance_meta)s, " - "but no corresponding capabilities are advertised " - "by the compute node"), locals()) - return False - - def _compare_props(props, other_props): - for i in props: - if i and i not in other_props: - return False - return True - - for supp_inst in supp_instances: - if _compare_props(inst_props_req, supp_inst): - LOG.debug(_("Instance properties %(instance_meta)s " - "are satisfied by compute host capabilities " - "%(capabilities)s"), locals()) - return True - - LOG.debug(_("Instance contains properties %(instance_meta)s " - "that are not provided by the compute node " - "capabilities %(capabilities)s"), locals()) - return False + """Filter on active Compute nodes""" def host_passes(self, host_state, filter_properties): - """Check if host passes instance compute properties. - - Returns True for active compute nodes that satisfy - the compute properties specified in the instance. - """ - spec = filter_properties.get('request_spec', {}) - instance_props = spec.get('instance_properties', {}) - instance_meta = instance_props.get('system_metadata', {}) + """Returns True for only active compute nodes""" instance_type = filter_properties.get('instance_type') if host_state.topic != 'compute' or not instance_type: return True @@ -89,8 +40,4 @@ class ComputeFilter(filters.BaseHostFilter): LOG.debug(_("%(host_state)s is disabled via capabilities"), locals()) return False - if not self._instance_supported(capabilities, instance_meta): - LOG.debug(_("%(host_state)s does not support requested " - "instance_properties"), locals()) - return False return True diff --git a/nova/scheduler/filters/image_props_filter.py b/nova/scheduler/filters/image_props_filter.py new file mode 100644 index 000000000..5eb710882 --- /dev/null +++ b/nova/scheduler/filters/image_props_filter.py @@ -0,0 +1,86 @@ +# Copyright (c) 2011-2012 OpenStack, LLC +# Copyright (c) 2012 Canonical Ltd +# Copyright (c) 2012 SUSE LINUX Products GmbH +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.openstack.common import log as logging +from nova.scheduler import filters +from nova import utils + + +LOG = logging.getLogger(__name__) + + +class ImagePropertiesFilter(filters.BaseHostFilter): + """Filter compute nodes that satisfy instance image properties. + + The ImagePropertiesFilter filters compute nodes that satisfy + any architecture, hpervisor type, or virtual machine mode properties + specified on the instance's image properties. Image properties are + contained in the image dictionary in the request_spec. + """ + + def _instance_supported(self, capabilities, image_props): + img_arch = image_props.get('architecture', None) + img_h_type = image_props.get('hypervisor_type', None) + img_vm_mode = image_props.get('vm_mode', None) + checked_img_props = (img_arch, img_h_type, img_vm_mode) + + # Supported if no compute-related instance properties are specified + if not any(checked_img_props): + return True + + supp_instances = capabilities.get('supported_instances', None) + # Not supported if an instance property is requested but nothing + # advertised by the host. + if not supp_instances: + LOG.debug(_("Instance contains properties %(image_props)s, " + "but no corresponding capabilities are advertised " + "by the compute node"), locals()) + return False + + def _compare_props(props, other_props): + for i in props: + if i and i not in other_props: + return False + return True + + for supp_inst in supp_instances: + if _compare_props(checked_img_props, supp_inst): + LOG.debug(_("Instance properties %(image_props)s " + "are satisfied by compute host capabilities " + "%(capabilities)s"), locals()) + return True + + LOG.debug(_("Instance contains properties %(image_props)s " + "that are not provided by the compute node " + "capabilities %(capabilities)s"), locals()) + return False + + def host_passes(self, host_state, filter_properties): + """Check if host passes specified image properties. + + Returns True for compute nodes that satisfy image properties + contained in the request_spec. + """ + spec = filter_properties.get('request_spec', {}) + image_props = spec.get('image', {}).get('properties', {}) + capabilities = host_state.capabilities + + if not self._instance_supported(capabilities, image_props): + LOG.debug(_("%(host_state)s does not support requested " + "instance_properties"), locals()) + return False + return True diff --git a/nova/scheduler/host_manager.py b/nova/scheduler/host_manager.py index 0403dffdf..f459df165 100644 --- a/nova/scheduler/host_manager.py +++ b/nova/scheduler/host_manager.py @@ -47,7 +47,8 @@ host_manager_opts = [ 'AvailabilityZoneFilter', 'RamFilter', 'ComputeFilter', - 'ComputeCapabilitiesFilter' + 'ComputeCapabilitiesFilter', + 'ImagePropertiesFilter' ], help='Which filter class names to use for filtering hosts ' 'when not specified in the request.'), diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py index d6f083576..e49cf925a 100644 --- a/nova/tests/scheduler/test_host_filters.py +++ b/nova/tests/scheduler/test_host_filters.py @@ -394,104 +394,81 @@ class HostFiltersTestCase(test.TestCase): 'service': service}) self.assertTrue(filt_cls.host_passes(host, filter_properties)) - def test_compute_filter_passes_same_inst_props(self): + def test_image_properties_filter_passes_same_inst_props(self): self._stub_service_is_up(True) - filt_cls = self.class_map['ComputeFilter']() - inst_meta = {'system_metadata': {'image_architecture': 'x86_64', - 'image_hypervisor_type': 'kvm', - 'image_vm_mode': 'hvm'}} - req_spec = {'instance_properties': inst_meta} - filter_properties = {'instance_type': {'memory_mb': 1024}, - 'request_spec': req_spec} + filt_cls = self.class_map['ImagePropertiesFilter']() + img_props = {'properties': {'_architecture': 'x86_64', + 'hypervisor_type': 'kvm', + 'vm_mode': 'hvm'}} + filter_properties = {'request_spec': {'image': img_props}} capabilities = {'enabled': True, 'supported_instances': [ ('x86_64', 'kvm', 'hvm')]} - service = {'disabled': False} host = fakes.FakeHostState('host1', 'compute', - {'free_ram_mb': 1024, 'capabilities': capabilities, - 'service': service}) + {'capabilities': capabilities}) self.assertTrue(filt_cls.host_passes(host, filter_properties)) - def test_compute_filter_fails_different_inst_props(self): + def test_image_properties_filter_fails_different_inst_props(self): self._stub_service_is_up(True) - filt_cls = self.class_map['ComputeFilter']() - inst_meta = {'system_metadata': {'image_architecture': 'arm', - 'image_hypervisor_type': 'qemu', - 'image_vm_mode': 'hvm'}} - req_spec = {'instance_properties': inst_meta} - filter_properties = {'instance_type': {'memory_mb': 1024}, - 'request_spec': req_spec} + filt_cls = self.class_map['ImagePropertiesFilter']() + img_props = {'properties': {'architecture': 'arm', + 'hypervisor_type': 'qemu', + 'vm_mode': 'hvm'}} + filter_properties = {'request_spec': {'image': img_props}} capabilities = {'enabled': True, 'supported_instances': [ ('x86_64', 'kvm', 'hvm')]} - service = {'disabled': False} host = fakes.FakeHostState('host1', 'compute', - {'free_ram_mb': 1024, 'capabilities': capabilities, - 'service': service}) + {'capabilities': capabilities}) self.assertFalse(filt_cls.host_passes(host, filter_properties)) - def test_compute_filter_passes_partial_inst_props(self): + def test_image_properties_filter_passes_partial_inst_props(self): self._stub_service_is_up(True) - filt_cls = self.class_map['ComputeFilter']() - inst_meta = {'system_metadata': {'image_architecture': 'x86_64', - 'image_vm_mode': 'hvm'}} - req_spec = {'instance_properties': inst_meta} - filter_properties = {'instance_type': {'memory_mb': 1024}, - 'request_spec': req_spec} + filt_cls = self.class_map['ImagePropertiesFilter']() + img_props = {'properties': {'architecture': 'x86_64', + 'vm_mode': 'hvm'}} + filter_properties = {'request_spec': {'image': img_props}} capabilities = {'enabled': True, 'supported_instances': [ ('x86_64', 'kvm', 'hvm')]} - service = {'disabled': False} host = fakes.FakeHostState('host1', 'compute', - {'free_ram_mb': 1024, 'capabilities': capabilities, - 'service': service}) + {'capabilities': capabilities}) self.assertTrue(filt_cls.host_passes(host, filter_properties)) - def test_compute_filter_fails_partial_inst_props(self): + def test_image_properties_filter_fails_partial_inst_props(self): self._stub_service_is_up(True) - filt_cls = self.class_map['ComputeFilter']() - inst_meta = {'system_metadata': {'image_architecture': 'x86_64', - 'image_vm_mode': 'hvm'}} - req_spec = {'instance_properties': inst_meta} - filter_properties = {'instance_type': {'memory_mb': 1024}, - 'request_spec': req_spec} + filt_cls = self.class_map['ImagePropertiesFilter']() + img_props = {'properties': {'architecture': 'x86_64', + 'vm_mode': 'hvm'}} + filter_properties = {'request_spec': {'image': img_props}} capabilities = {'enabled': True, 'supported_instances': [ ('x86_64', 'xen', 'xen')]} - service = {'disabled': False} host = fakes.FakeHostState('host1', 'compute', - {'free_ram_mb': 1024, 'capabilities': capabilities, - 'service': service}) + {'capabilities': capabilities}) self.assertFalse(filt_cls.host_passes(host, filter_properties)) - def test_compute_filter_passes_without_inst_props(self): + def test_image_properties_filter_passes_without_inst_props(self): self._stub_service_is_up(True) - filt_cls = self.class_map['ComputeFilter']() - filter_properties = {'instance_type': {'memory_mb': 1024}, - 'request_spec': {}} + filt_cls = self.class_map['ImagePropertiesFilter']() + filter_properties = {'request_spec': {}} capabilities = {'enabled': True, 'supported_instances': [ ('x86_64', 'kvm', 'hvm')]} - service = {'disabled': False} host = fakes.FakeHostState('host1', 'compute', - {'free_ram_mb': 1024, 'capabilities': capabilities, - 'service': service}) + {'capabilities': capabilities}) self.assertTrue(filt_cls.host_passes(host, filter_properties)) - def test_compute_filter_fails_without_host_props(self): + def test_image_properties_filter_fails_without_host_props(self): self._stub_service_is_up(True) - filt_cls = self.class_map['ComputeFilter']() - inst_meta = {'system_metadata': {'image_architecture': 'x86_64', - 'image_hypervisor_type': 'kvm', - 'image_vm_mode': 'hvm'}} - req_spec = {'instance_properties': inst_meta} - filter_properties = {'instance_type': {'memory_mb': 1024}, - 'request_spec': req_spec} + filt_cls = self.class_map['ImagePropertiesFilter']() + img_props = {'properties': {'architecture': 'x86_64', + 'hypervisor_type': 'kvm', + 'vm_mode': 'hvm'}} + filter_properties = {'request_spec': {'image': img_props}} capabilities = {'enabled': True} - service = {'disabled': False} host = fakes.FakeHostState('host1', 'compute', - {'free_ram_mb': 1024, 'capabilities': capabilities, - 'service': service}) + {'capabilities': capabilities}) self.assertFalse(filt_cls.host_passes(host, filter_properties)) def _do_test_compute_filter_extra_specs(self, ecaps, especs, passes): @@ -1214,60 +1191,6 @@ class HostFiltersTestCase(test.TestCase): host = fakes.FakeHostState('host1', 'compute', {'service': service}) self.assertFalse(filt_cls.host_passes(host, request)) - def test_arch_filter_same(self): - permitted_instances = ['x86_64'] - filt_cls = self.class_map['ArchFilter']() - filter_properties = { - 'request_spec': { - 'instance_properties': {'architecture': 'x86_64'} - } - } - capabilities = {'enabled': True, - 'cpu_info': { - 'permitted_instance_types': permitted_instances - } - } - service = {'disabled': False} - host = fakes.FakeHostState('host1', 'compute', - {'capabilities': capabilities, 'service': service}) - self.assertTrue(filt_cls.host_passes(host, filter_properties)) - - def test_arch_filter_different(self): - permitted_instances = ['arm'] - filt_cls = self.class_map['ArchFilter']() - filter_properties = { - 'request_spec': { - 'instance_properties': {'architecture': 'x86_64'} - } - } - capabilities = {'enabled': True, - 'cpu_info': { - 'permitted_instance_types': permitted_instances - } - } - service = {'disabled': False} - host = fakes.FakeHostState('host1', 'compute', - {'capabilities': capabilities, 'service': service}) - self.assertFalse(filt_cls.host_passes(host, filter_properties)) - - def test_arch_filter_without_permitted_instances(self): - permitted_instances = [] - filt_cls = self.class_map['ArchFilter']() - filter_properties = { - 'request_spec': { - 'instance_properties': {'architecture': 'x86_64'} - } - } - capabilities = {'enabled': True, - 'cpu_info': { - 'permitted_instance_types': permitted_instances - } - } - service = {'disabled': False} - host = fakes.FakeHostState('host1', 'compute', - {'capabilities': capabilities, 'service': service}) - self.assertFalse(filt_cls.host_passes(host, filter_properties)) - def test_retry_filter_disabled(self): """Test case where retry/re-scheduling is disabled""" filt_cls = self.class_map['RetryFilter']() -- cgit