Skip to content

Commit

Permalink
Adding support for multiple hypervisor versions
Browse files Browse the repository at this point in the history
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
  • Loading branch information
aditiraveesh committed Oct 16, 2013
1 parent 6affe67 commit a52259e
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 48 deletions.
40 changes: 29 additions & 11 deletions nova/scheduler/filters/image_props_filter.py
Expand Up @@ -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__)
Expand All @@ -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)
Expand All @@ -53,7 +56,7 @@ def _instance_supported(self, host_state, image_props):
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):
Expand All @@ -62,20 +65,34 @@ def _compare_props(props, other_props):
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):
Expand All @@ -87,7 +104,8 @@ def host_passes(self, host_state, filter_properties):
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
Expand Down
1 change: 1 addition & 0 deletions nova/scheduler/host_manager.py
Expand Up @@ -187,6 +187,7 @@ def update_from_compute_node(self, compute):
# 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))
Expand Down
12 changes: 8 additions & 4 deletions nova/tests/scheduler/fakes.py
Expand Up @@ -29,22 +29,26 @@
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),
]
Expand Down
97 changes: 72 additions & 25 deletions nova/tests/scheduler/test_host_filters.py
Expand Up @@ -31,6 +31,7 @@
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')
Expand Down Expand Up @@ -652,17 +653,19 @@ def test_compute_filter_fails_on_service_down(self):
{'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):
Expand All @@ -672,10 +675,25 @@ def test_image_properties_filter_fails_different_inst_props(self):
'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):
Expand All @@ -684,10 +702,10 @@ def test_image_properties_filter_passes_partial_inst_props(self):
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):
Expand All @@ -696,20 +714,20 @@ def test_image_properties_filter_fails_partial_inst_props(self):
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):
Expand All @@ -719,7 +737,37 @@ def test_image_properties_filter_fails_without_host_props(self):
'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):
Expand Down Expand Up @@ -786,8 +834,7 @@ def test_aggregate_filter_passes_no_extra_specs(self):

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',
Expand Down
14 changes: 10 additions & 4 deletions nova/tests/scheduler/test_host_manager.py
Expand Up @@ -25,6 +25,7 @@
from nova import test
from nova.tests import matchers
from nova.tests.scheduler import fakes
from nova import utils


class FakeFilterClass1(filters.BaseHostFilter):
Expand Down Expand Up @@ -435,12 +436,14 @@ def test_stat_consumption_from_compute_node(self):
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)
Expand All @@ -459,10 +462,10 @@ def test_stat_consumption_from_compute_node(self):

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 = [
Expand All @@ -477,13 +480,16 @@ def test_stat_consumption_from_compute_node_non_pci(self):
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")
Expand Down
16 changes: 16 additions & 0 deletions nova/tests/test_utils.py
Expand Up @@ -920,3 +920,19 @@ def test_non_inheritable_image_properties(self):

# Verify that the foo1 key has not been inherited
self.assertNotIn("foo1", image)


class VersionTestCase(test.TestCase):
def test_convert_version_to_int(self):
self.assertEqual(utils.convert_version_to_int('6.2.0'), 6002000)
self.assertEqual(utils.convert_version_to_int((6, 4, 3)), 6004003)
self.assertEqual(utils.convert_version_to_int((5, )), 5)
self.assertRaises(exception.NovaException,
utils.convert_version_to_int, '5a.6b')

def test_convert_version_to_string(self):
self.assertEqual(utils.convert_version_to_str(6007000), '6.7.0')
self.assertEqual(utils.convert_version_to_str(4), '4')

def test_convert_version_to_tuple(self):
self.assertEqual(utils.convert_version_to_tuple('6.7.0'), (6, 7, 0))
23 changes: 22 additions & 1 deletion nova/utils.py
Expand Up @@ -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():
Expand Down
4 changes: 1 addition & 3 deletions nova/virt/xenapi/driver.py
Expand Up @@ -745,9 +745,7 @@ def _get_product_version_and_brand(self):
product_version_str = software_version.get('platform_version',
'0.0.0')
product_brand = software_version.get('product_brand')

product_version = tuple(int(part) for part in
product_version_str.split('.'))
product_version = utils.convert_version_to_tuple(product_version_str)

return product_version, product_brand

Expand Down

0 comments on commit a52259e

Please sign in to comment.