diff --git a/nova/scheduler/filters/image_props_filter.py b/nova/scheduler/filters/image_props_filter.py index bb3fd8cdb..76e8795fc 100644 --- a/nova/scheduler/filters/image_props_filter.py +++ b/nova/scheduler/filters/image_props_filter.py @@ -14,10 +14,12 @@ # 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 distutils import versionpredicate from nova.openstack.common.gettextutils import _ from nova.openstack.common import log as logging from nova.scheduler import filters +from nova import utils LOG = logging.getLogger(__name__) @@ -36,7 +38,8 @@ class ImagePropertiesFilter(filters.BaseHostFilter): # a request run_filter_once_per_request = True - def _instance_supported(self, host_state, image_props): + def _instance_supported(self, host_state, image_props, + hypervisor_version): 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) @@ -53,7 +56,7 @@ class ImagePropertiesFilter(filters.BaseHostFilter): LOG.debug(_("Instance contains properties %(image_props)s, " "but no corresponding supported_instances are " "advertised by the compute node"), - {'image_props': image_props}) + {'image_props': image_props}) return False def _compare_props(props, other_props): @@ -62,20 +65,34 @@ class ImagePropertiesFilter(filters.BaseHostFilter): return False return True + def _compare_product_version(hyper_version, image_props): + version_required = image_props.get('hypervisor_version_requires') + if not(hypervisor_version and version_required): + return True + img_prop_predicate = versionpredicate.VersionPredicate( + 'image_prop (%s)' % version_required) + hyper_ver_str = utils.convert_version_to_str(hyper_version) + return img_prop_predicate.satisfied_by(hyper_ver_str) + 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 supported_instances" - "%(supp_instances)s"), - {'image_props': image_props, - 'supp_instances': supp_instances}) - return True + if _compare_product_version(hypervisor_version, image_props): + LOG.debug(_("Instance properties %(image_props)s " + "are satisfied by compute host hypervisor " + "version %(hypervisor_version) and " + "supported instances %(supp_instances)s"), + {'image_props': image_props, + 'supp_instances': supp_instances, + 'hypervisor_version': hypervisor_version}) + return True LOG.debug(_("Instance contains properties %(image_props)s " "that are not provided by the compute node " - "supported_instances %(supp_instances)s"), + "supported_instances %(supp_instances)s or " + "hypervisor version %(hypervisor_version)s do not match"), {'image_props': image_props, - 'supp_instances': supp_instances}) + 'supp_instances': supp_instances, + 'hypervisor_version': hypervisor_version}) return False def host_passes(self, host_state, filter_properties): @@ -87,7 +104,8 @@ class ImagePropertiesFilter(filters.BaseHostFilter): spec = filter_properties.get('request_spec', {}) image_props = spec.get('image', {}).get('properties', {}) - if not self._instance_supported(host_state, image_props): + if not self._instance_supported(host_state, image_props, + host_state.hypervisor_version): LOG.debug(_("%(host_state)s does not support requested " "instance_properties"), {'host_state': host_state}) return False diff --git a/nova/scheduler/host_manager.py b/nova/scheduler/host_manager.py index eaeecd6d8..566d8e7ce 100644 --- a/nova/scheduler/host_manager.py +++ b/nova/scheduler/host_manager.py @@ -187,6 +187,7 @@ class HostState(object): # overwrite any values, or get overwritten themselves. Store in self so # filters can schedule with them. self.stats = self._statmap(compute.get('stats', [])) + self.hypervisor_version = compute['hypervisor_version'] # Track number of instances on host self.num_instances = int(self.stats.get('num_instances', 0)) diff --git a/nova/tests/scheduler/fakes.py b/nova/tests/scheduler/fakes.py index 0469c9258..8ed23d897 100644 --- a/nova/tests/scheduler/fakes.py +++ b/nova/tests/scheduler/fakes.py @@ -29,22 +29,26 @@ COMPUTE_NODES = [ disk_available_least=512, free_ram_mb=512, vcpus_used=1, free_disk_mb=512, local_gb_used=0, updated_at=None, service=dict(host='host1', disabled=False), - hypervisor_hostname='node1', host_ip='127.0.0.1'), + hypervisor_hostname='node1', host_ip='127.0.0.1', + hypervisor_version=0), dict(id=2, local_gb=2048, memory_mb=2048, vcpus=2, disk_available_least=1024, free_ram_mb=1024, vcpus_used=2, free_disk_mb=1024, local_gb_used=0, updated_at=None, service=dict(host='host2', disabled=True), - hypervisor_hostname='node2', host_ip='127.0.0.1'), + hypervisor_hostname='node2', host_ip='127.0.0.1', + hypervisor_version=0), dict(id=3, local_gb=4096, memory_mb=4096, vcpus=4, disk_available_least=3072, free_ram_mb=3072, vcpus_used=1, free_disk_mb=3072, local_gb_used=0, updated_at=None, service=dict(host='host3', disabled=False), - hypervisor_hostname='node3', host_ip='127.0.0.1'), + hypervisor_hostname='node3', host_ip='127.0.0.1', + hypervisor_version=0), dict(id=4, local_gb=8192, memory_mb=8192, vcpus=8, disk_available_least=8192, free_ram_mb=8192, vcpus_used=0, free_disk_mb=8192, local_gb_used=0, updated_at=None, service=dict(host='host4', disabled=False), - hypervisor_hostname='node4', host_ip='127.0.0.1'), + hypervisor_hostname='node4', host_ip='127.0.0.1', + hypervisor_version=0), # Broken entry dict(id=5, local_gb=1024, memory_mb=1024, vcpus=1, service=None), ] diff --git a/nova/tests/scheduler/test_host_filters.py b/nova/tests/scheduler/test_host_filters.py index 3bacb9990..ea82ab041 100644 --- a/nova/tests/scheduler/test_host_filters.py +++ b/nova/tests/scheduler/test_host_filters.py @@ -31,6 +31,7 @@ from nova.scheduler.filters import trusted_filter from nova import servicegroup from nova import test from nova.tests.scheduler import fakes +from nova import utils CONF = cfg.CONF CONF.import_opt('my_ip', 'nova.netconf') @@ -652,17 +653,19 @@ class HostFiltersTestCase(test.NoDBTestCase): {'free_ram_mb': 1024, 'service': service}) self.assertFalse(filt_cls.host_passes(host, filter_properties)) - def test_image_properties_filter_passes_same_inst_props(self): + def test_image_properties_filter_passes_same_inst_props_and_version(self): self._stub_service_is_up(True) filt_cls = self.class_map['ImagePropertiesFilter']() img_props = {'properties': {'_architecture': 'x86_64', 'hypervisor_type': 'kvm', - 'vm_mode': 'hvm'}} + 'vm_mode': 'hvm', + 'hypervisor_version_requires': '>=6.0,<6.2' + }} filter_properties = {'request_spec': {'image': img_props}} - capabilities = {'supported_instances': [ - ('x86_64', 'kvm', 'hvm')]} - host = fakes.FakeHostState('host1', 'node1', - capabilities) + hypervisor_version = utils.convert_version_to_int('6.0.0') + capabilities = {'supported_instances': [('x86_64', 'kvm', 'hvm')], + 'hypervisor_version': hypervisor_version} + host = fakes.FakeHostState('host1', 'node1', capabilities) self.assertTrue(filt_cls.host_passes(host, filter_properties)) def test_image_properties_filter_fails_different_inst_props(self): @@ -672,10 +675,25 @@ class HostFiltersTestCase(test.NoDBTestCase): 'hypervisor_type': 'qemu', 'vm_mode': 'hvm'}} filter_properties = {'request_spec': {'image': img_props}} - capabilities = {'supported_instances': [ - ('x86_64', 'kvm', 'hvm')]} - host = fakes.FakeHostState('host1', 'node1', - capabilities) + hypervisor_version = utils.convert_version_to_int('6.0.0') + capabilities = {'supported_instances': [('x86_64', 'kvm', 'hvm')], + 'hypervisor_version': hypervisor_version} + host = fakes.FakeHostState('host1', 'node1', capabilities) + self.assertFalse(filt_cls.host_passes(host, filter_properties)) + + def test_image_properties_filter_fails_different_hyper_version(self): + self._stub_service_is_up(True) + filt_cls = self.class_map['ImagePropertiesFilter']() + img_props = {'properties': {'architecture': 'x86_64', + 'hypervisor_type': 'kvm', + 'vm_mode': 'hvm', + 'hypervisor_version_requires': '>=6.2'}} + filter_properties = {'request_spec': {'image': img_props}} + hypervisor_version = utils.convert_version_to_int('6.0.0') + capabilities = {'enabled': True, + 'supported_instances': [('x86_64', 'kvm', 'hvm')], + 'hypervisor_version': hypervisor_version} + host = fakes.FakeHostState('host1', 'node1', capabilities) self.assertFalse(filt_cls.host_passes(host, filter_properties)) def test_image_properties_filter_passes_partial_inst_props(self): @@ -684,10 +702,10 @@ class HostFiltersTestCase(test.NoDBTestCase): img_props = {'properties': {'architecture': 'x86_64', 'vm_mode': 'hvm'}} filter_properties = {'request_spec': {'image': img_props}} - capabilities = {'supported_instances': [ - ('x86_64', 'kvm', 'hvm')]} - host = fakes.FakeHostState('host1', 'node1', - capabilities) + hypervisor_version = utils.convert_version_to_int('6.0.0') + capabilities = {'supported_instances': [('x86_64', 'kvm', 'hvm')], + 'hypervisor_version': hypervisor_version} + host = fakes.FakeHostState('host1', 'node1', capabilities) self.assertTrue(filt_cls.host_passes(host, filter_properties)) def test_image_properties_filter_fails_partial_inst_props(self): @@ -696,20 +714,20 @@ class HostFiltersTestCase(test.NoDBTestCase): img_props = {'properties': {'architecture': 'x86_64', 'vm_mode': 'hvm'}} filter_properties = {'request_spec': {'image': img_props}} - capabilities = {'supported_instances': [ - ('x86_64', 'xen', 'xen')]} - host = fakes.FakeHostState('host1', 'node1', - capabilities) + hypervisor_version = utils.convert_version_to_int('6.0.0') + capabilities = {'supported_instances': [('x86_64', 'xen', 'xen')], + 'hypervisor_version': hypervisor_version} + host = fakes.FakeHostState('host1', 'node1', capabilities) self.assertFalse(filt_cls.host_passes(host, filter_properties)) def test_image_properties_filter_passes_without_inst_props(self): self._stub_service_is_up(True) filt_cls = self.class_map['ImagePropertiesFilter']() filter_properties = {'request_spec': {}} - capabilities = {'supported_instances': [ - ('x86_64', 'kvm', 'hvm')]} - host = fakes.FakeHostState('host1', 'node1', - capabilities) + hypervisor_version = utils.convert_version_to_int('6.0.0') + capabilities = {'supported_instances': [('x86_64', 'kvm', 'hvm')], + 'hypervisor_version': hypervisor_version} + host = fakes.FakeHostState('host1', 'node1', capabilities) self.assertTrue(filt_cls.host_passes(host, filter_properties)) def test_image_properties_filter_fails_without_host_props(self): @@ -719,7 +737,37 @@ class HostFiltersTestCase(test.NoDBTestCase): 'hypervisor_type': 'kvm', 'vm_mode': 'hvm'}} filter_properties = {'request_spec': {'image': img_props}} - host = fakes.FakeHostState('host1', 'node1', {}) + hypervisor_version = utils.convert_version_to_int('6.0.0') + capabilities = {'enabled': True, + 'hypervisor_version': hypervisor_version} + host = fakes.FakeHostState('host1', 'node1', capabilities) + self.assertFalse(filt_cls.host_passes(host, filter_properties)) + + def test_image_properties_filter_passes_without_hyper_version(self): + self._stub_service_is_up(True) + filt_cls = self.class_map['ImagePropertiesFilter']() + img_props = {'properties': {'architecture': 'x86_64', + 'hypervisor_type': 'kvm', + 'vm_mode': 'hvm', + 'hypervisor_version_requires': '>=6.0'}} + filter_properties = {'request_spec': {'image': img_props}} + capabilities = {'enabled': True, + 'supported_instances': [('x86_64', 'kvm', 'hvm')]} + host = fakes.FakeHostState('host1', 'node1', capabilities) + self.assertTrue(filt_cls.host_passes(host, filter_properties)) + + def test_image_properties_filter_fails_with_unsupported_hyper_ver(self): + self._stub_service_is_up(True) + filt_cls = self.class_map['ImagePropertiesFilter']() + img_props = {'properties': {'architecture': 'x86_64', + 'hypervisor_type': 'kvm', + 'vm_mode': 'hvm', + 'hypervisor_version_requires': '>=6.0'}} + filter_properties = {'request_spec': {'image': img_props}} + capabilities = {'enabled': True, + 'supported_instances': [('x86_64', 'kvm', 'hvm')], + 'hypervisor_version': 5000} + host = fakes.FakeHostState('host1', 'node1', capabilities) self.assertFalse(filt_cls.host_passes(host, filter_properties)) def _do_test_compute_filter_extra_specs(self, ecaps, especs, passes): @@ -786,8 +834,7 @@ class HostFiltersTestCase(test.NoDBTestCase): filter_properties = {'context': self.context, 'instance_type': {'memory_mb': 1024}} - host = fakes.FakeHostState('host1', 'node1', - capabilities) + host = fakes.FakeHostState('host1', 'node1', capabilities) self.assertTrue(filt_cls.host_passes(host, filter_properties)) def _create_aggregate_with_host(self, name='fake_aggregate', diff --git a/nova/tests/scheduler/test_host_manager.py b/nova/tests/scheduler/test_host_manager.py index c307b2d27..13afca95a 100644 --- a/nova/tests/scheduler/test_host_manager.py +++ b/nova/tests/scheduler/test_host_manager.py @@ -25,6 +25,7 @@ from nova.scheduler import host_manager from nova import test from nova.tests import matchers from nova.tests.scheduler import fakes +from nova import utils class FakeFilterClass1(filters.BaseHostFilter): @@ -435,12 +436,14 @@ class HostStateTestCase(test.NoDBTestCase): dict(key='num_os_type_windoze', value='1'), dict(key='io_workload', value='42'), ] + hyper_ver_int = utils.convert_version_to_int('6.0.0') compute = dict(stats=stats, memory_mb=1, free_disk_gb=0, local_gb=0, local_gb_used=0, free_ram_mb=0, vcpus=0, vcpus_used=0, updated_at=None, host_ip='127.0.0.1', - hypervisor_type='htype', hypervisor_version='1.1', + hypervisor_type='htype', hypervisor_hostname='hostname', cpu_info='cpu_info', - supported_instances='{}') + supported_instances='{}', + hypervisor_version=hyper_ver_int) host = host_manager.HostState("fakehost", "fakenode") host.update_from_compute_node(compute) @@ -459,10 +462,10 @@ class HostStateTestCase(test.NoDBTestCase): self.assertEqual('127.0.0.1', host.host_ip) self.assertEqual('htype', host.hypervisor_type) - self.assertEqual('1.1', host.hypervisor_version) self.assertEqual('hostname', host.hypervisor_hostname) self.assertEqual('cpu_info', host.cpu_info) self.assertEqual({}, host.supported_instances) + self.assertEqual(hyper_ver_int, host.hypervisor_version) def test_stat_consumption_from_compute_node_non_pci(self): stats = [ @@ -477,13 +480,16 @@ class HostStateTestCase(test.NoDBTestCase): dict(key='num_os_type_windoze', value='1'), dict(key='io_workload', value='42'), ] + hyper_ver_int = utils.convert_version_to_int('6.0.0') compute = dict(stats=stats, memory_mb=0, free_disk_gb=0, local_gb=0, local_gb_used=0, free_ram_mb=0, vcpus=0, vcpus_used=0, - updated_at=None, host_ip='127.0.0.1') + updated_at=None, host_ip='127.0.0.1', + hypervisor_version=hyper_ver_int) host = host_manager.HostState("fakehost", "fakenode") host.update_from_compute_node(compute) self.assertEqual(None, host.pci_stats) + self.assertEqual(hyper_ver_int, host.hypervisor_version) def test_stat_consumption_from_instance(self): host = host_manager.HostState("fakehost", "fakenode") diff --git a/nova/utils.py b/nova/utils.py index 2ec51868b..b6440b5ff 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -1027,7 +1027,28 @@ def is_none_string(val): def convert_version_to_int(version): - return version[0] * 1000000 + version[1] * 1000 + version[2] + try: + if type(version) == str: + version = convert_version_to_tuple(version) + if type(version) == tuple: + return reduce(lambda x, y: (x * 1000) + y, version) + except Exception: + raise exception.NovaException(message="Hypervisor version invalid.") + + +def convert_version_to_str(version_int): + version_numbers = [] + factor = 1000 + while version_int != 0: + version_number = version_int - (version_int // factor * factor) + version_numbers.insert(0, str(version_number)) + version_int = version_int / factor + + return reduce(lambda x, y: "%s.%s" % (x, y), version_numbers) + + +def convert_version_to_tuple(version_str): + return tuple(int(part) for part in version_str.split('.')) def is_neutron():