diff --git a/nova/scheduler/filters/compute_filter.py b/nova/scheduler/filters/compute_filter.py index 2d7c898d69..c0ee98762e 100644 --- a/nova/scheduler/filters/compute_filter.py +++ b/nova/scheduler/filters/compute_filter.py @@ -22,10 +22,59 @@ LOG = logging.getLogger(__name__) class ComputeFilter(filters.BaseHostFilter): - """Filter on active Compute nodes""" + """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 def host_passes(self, host_state, filter_properties): - """Returns True for only active compute nodes""" + """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', {}) instance_type = filter_properties.get('instance_type') if host_state.topic != 'compute' or not instance_type: return True @@ -40,4 +89,8 @@ 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/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py index ca5ca5366c..227fbfa829 100644 --- a/nova/tests/scheduler/test_host_filters.py +++ b/nova/tests/scheduler/test_host_filters.py @@ -394,6 +394,106 @@ class HostFiltersTestCase(test.TestCase): 'service': service}) self.assertTrue(filt_cls.host_passes(host, filter_properties)) + def test_compute_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} + 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}) + self.assertTrue(filt_cls.host_passes(host, filter_properties)) + + def test_compute_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} + 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}) + self.assertFalse(filt_cls.host_passes(host, filter_properties)) + + def test_compute_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} + 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}) + self.assertTrue(filt_cls.host_passes(host, filter_properties)) + + def test_compute_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} + 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}) + self.assertFalse(filt_cls.host_passes(host, filter_properties)) + + def test_compute_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': {}} + 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}) + self.assertTrue(filt_cls.host_passes(host, filter_properties)) + + def test_compute_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} + capabilities = {'enabled': True} + service = {'disabled': False} + host = fakes.FakeHostState('host1', 'compute', + {'free_ram_mb': 1024, 'capabilities': capabilities, + 'service': service}) + self.assertFalse(filt_cls.host_passes(host, filter_properties)) + def test_compute_filter_passes_extra_specs_noop(self): self._stub_service_is_up(True) filt_cls = self.class_map['ComputeCapabilitiesFilter']()