Adding support for multiple hypervisor versions

This is to prevent instances with newer tools from being provisioned
on older hypervisors.
Added comparison of hypervisor version from the compute-node and image
metadata in the image properties filter.
In host scheduler image_prop_filter, the 'hypervisor_version_requires'
image properties, if available, is compared to the host_state's
hypervisor_version, if available.
hypervisor_version_requires should be added on the image as metadata and
should have a operator and version value.
Examples : ">=6.0", ">6.0, <6.2", ""!=6.1"

Partially Implements blueprint xen-support-for-hypervisor-versions
Cell scheduler changes coming up in another commit.
DocImpact

Change-Id: I6fbf232adf9ac78ebb8ac2985739f163f4517d14
This commit is contained in:
Aditi Raveesh
2013-10-07 14:47:42 +05:30
parent 61333de39c
commit 3ad58c3050
6 changed files with 142 additions and 45 deletions

View File

@@ -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

View File

@@ -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))

View File

@@ -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),
]

View File

@@ -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',

View File

@@ -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")

View File

@@ -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():