resync: Hyper-V: Adds vNUMA implementation

(cherry-picked from commit 2195e4d68486ed70e55d0b5f038b13bd35e3271c)

Change-Id: Ic4e03e641356ea54206ce8fdaecb2837824d2747
This commit is contained in:
Claudiu Belu 2017-01-19 15:22:29 +02:00
parent eeb97176c4
commit 1ea0ea8b1b
4 changed files with 143 additions and 143 deletions

View File

@ -122,18 +122,6 @@ class HostOps(object):
LOG.debug('Windows version: %s ', version)
return version
def _get_host_numa_topology(self):
numa_nodes = self._hostutils.get_numa_nodes()
cells = []
for numa_node in numa_nodes:
numa_node['pinned_cpus'] = set([])
numa_node['mempages'] = []
numa_node['siblings'] = []
cell = objects.NUMACell(**numa_node)
cells.append(cell)
return objects.NUMATopology(cells=cells)
def _get_remotefx_gpu_info(self):
total_video_ram = 0
available_video_ram = 0
@ -150,6 +138,18 @@ class HostOps(object):
'used_video_ram': total_video_ram - available_video_ram,
'gpu_info': jsonutils.dumps(gpus)}
def _get_host_numa_topology(self):
numa_nodes = self._hostutils.get_numa_nodes()
cells = []
for numa_node in numa_nodes:
# Hyper-V does not support CPU pinning / mempages.
# initializing the rest of the fields.
numa_node.update(pinned_cpus=set(), mempages=[], siblings=[])
cell = objects.NUMACell(**numa_node)
cells.append(cell)
return objects.NUMATopology(cells=cells)
def get_available_resource(self):
"""Retrieve resource info.
@ -192,17 +192,12 @@ class HostOps(object):
(obj_fields.Architecture.X86_64,
obj_fields.HVType.HYPERV,
obj_fields.VMMode.HVM)],
'numa_topology': self._get_host_numa_topology()._to_json(),
}
gpu_info = self._get_remotefx_gpu_info()
dic.update(gpu_info)
numa_topology = self._get_host_numa_topology()
if numa_topology:
dic['numa_topology'] = numa_topology._to_json()
else:
dic['numa_topology'] = None
return dic
def host_power_action(self, action):

View File

@ -367,6 +367,9 @@ class VMOps(object):
self._get_instance_vnuma_config(instance, image_meta))
if memory_per_numa_node:
LOG.debug("Instance requires vNUMA topology. Host's NUMA spanning "
"has to be disabled in order for the instance to "
"benefit from it.", instance=instance)
if CONF.hyperv.dynamic_memory_ratio > 1.0:
LOG.warning(_LW(
"Instance vNUMA topology requested, but dynamic memory "
@ -424,6 +427,48 @@ class VMOps(object):
self._configure_secure_vm(context, instance, image_meta,
secure_boot_enabled)
def _get_instance_vnuma_config(self, instance, image_meta):
"""Returns the appropriate NUMA configuration for Hyper-V instances,
given the desired instance NUMA topology.
:param instance: instance containing the flavor and it's extra_specs,
where the NUMA topology is defined.
:param image_meta: image's metadata, containing properties related to
the instance's NUMA topology.
:returns: memory amount and number of vCPUs per NUMA node or
(None, None), if instance NUMA topology was not requested.
:raises exception.InstanceUnacceptable:
If the given instance NUMA topology is not possible on Hyper-V.
"""
image_meta = objects.ImageMeta.from_dict(image_meta)
instance_topology = hardware.numa_get_constraints(instance.flavor,
image_meta)
if not instance_topology:
# instance NUMA topology was not requested.
return None, None
memory_per_numa_node = instance_topology.cells[0].memory
cpus_per_numa_node = len(instance_topology.cells[0].cpuset)
# validate that the requested NUMA topology is not asymetric.
# e.g.: it should be like: (X cpus, X cpus, Y cpus), where X == Y.
# same with memory.
for cell in instance_topology.cells:
if len(cell.cpuset) != cpus_per_numa_node:
reason = _("Hyper-V does not support NUMA topologies with "
"uneven number of processors. (%(a)s != %(b)s)") % {
'a': len(cell.cpuset), 'b': cpus_per_numa_node}
raise exception.InstanceUnacceptable(reason=reason,
instance_id=instance.uuid)
if cell.memory != memory_per_numa_node:
reason = _("Hyper-V does not support NUMA topologies with "
"uneven amounts of memory. (%(a)s != %(b)s)") % {
'a': cell.memory, 'b': memory_per_numa_node}
raise exception.InstanceUnacceptable(reason=reason,
instance_id=instance.uuid)
return memory_per_numa_node, cpus_per_numa_node
def _configure_remotefx(self, instance, vm_gen):
extra_specs = instance.flavor.extra_specs
remotefx_max_resolution = extra_specs.get(
@ -621,48 +666,6 @@ class VMOps(object):
return configdrive_path
def _get_instance_vnuma_config(self, instance, image_meta):
"""Returns the appropriate NUMA configuration for Hyper-V instances,
given the desired instance NUMA topology.
:param instance: instance containing the flavor and it's extra_specs,
where the NUMA topology is defined.
:param image_meta: image's metadata, containing properties related to
the instance's NUMA topology.
:returns: memory amount and number of vCPUs per NUMA node or
(None, None), if instance NUMA topology was not requested.
:raises exception.InstanceUnacceptable:
If the given instance NUMA topology is not possible on Hyper-V.
"""
image_meta = objects.ImageMeta.from_dict(image_meta)
instance_topology = hardware.numa_get_constraints(instance.flavor,
image_meta)
if not instance_topology:
# instance NUMA topology was not requested.
return None, None
memory_per_numa_node = instance_topology.cells[0].memory
cpus_per_numa_node = len(instance_topology.cells[0].cpuset)
cpus_pinned = instance_topology.cells[0].cpu_pinning is not None
if cpus_pinned:
raise exception.InstanceUnacceptable(
reason="Hyper-V cannot guarantee the CPU pinning.",
instance_id=instance.uuid)
# validate that the requested NUMA topology is not asymetric.
# e.g.: it should be like: (X cpus, X cpus, Y cpus), where X == Y.
# same with memory.
for cell in instance_topology.cells:
if (len(cell.cpuset) != cpus_per_numa_node or
cell.memory != memory_per_numa_node):
raise exception.InstanceUnacceptable(
reason="Hyper-V cannot guarantee the given instance NUMA "
"topology.", instance_id=instance.uuid)
return memory_per_numa_node, cpus_per_numa_node
def attach_config_drive(self, instance, configdrive_path, vm_gen):
configdrive_ext = configdrive_path[(configdrive_path.rfind('.') + 1):]
# Do the attach here and if there is a certain file format that isn't

View File

@ -117,24 +117,6 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
self.assertEqual(6003, response_lower)
self.assertEqual(10001, response_higher)
@mock.patch.object(hostops.objects, 'NUMACell')
@mock.patch.object(hostops.objects, 'NUMATopology')
def test_get_host_numa_topology(self, mock_NUMATopology, mock_NUMACell):
numa_node = {'id': mock.sentinel.id, 'memory': mock.sentinel.memory,
'memory_usage': mock.sentinel.memory_usage,
'cpuset': mock.sentinel.cpuset,
'cpu_usage': mock.sentinel.cpu_usage}
self._hostops._hostutils.get_numa_nodes.return_value = [
dict(numa_node)]
result = self._hostops._get_host_numa_topology()
self.assertEqual(mock_NUMATopology.return_value, result)
mock_NUMACell.assert_called_once_with(
pinned_cpus=set([]), mempages=[], siblings=[], **numa_node)
mock_NUMATopology.assert_called_once_with(
cells=[mock_NUMACell.return_value])
def test_get_remotefx_gpu_info(self):
self.flags(enable_remotefx=True, group='hyperv')
fake_gpus = [{'total_video_ram': '2048',
@ -157,6 +139,24 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
self.assertEqual(0, ret_val['used_video_ram'])
self._hostops._hostutils.get_remotefx_gpu_info.assert_not_called()
@mock.patch.object(hostops.objects, 'NUMACell')
@mock.patch.object(hostops.objects, 'NUMATopology')
def test_get_host_numa_topology(self, mock_NUMATopology, mock_NUMACell):
numa_node = {'id': mock.sentinel.id, 'memory': mock.sentinel.memory,
'memory_usage': mock.sentinel.memory_usage,
'cpuset': mock.sentinel.cpuset,
'cpu_usage': mock.sentinel.cpu_usage}
self._hostops._hostutils.get_numa_nodes.return_value = [
numa_node.copy()]
result = self._hostops._get_host_numa_topology()
self.assertEqual(mock_NUMATopology.return_value, result)
mock_NUMACell.assert_called_once_with(
pinned_cpus=set([]), mempages=[], siblings=[], **numa_node)
mock_NUMATopology.assert_called_once_with(
cells=[mock_NUMACell.return_value])
@mock.patch.object(hostops.HostOps, '_get_host_numa_topology')
@mock.patch.object(hostops.HostOps, '_get_remotefx_gpu_info')
@mock.patch.object(hostops.HostOps, '_get_cpu_info')
@ -178,11 +178,11 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
mock_cpu_info = self._get_mock_cpu_info()
mock_get_cpu_info.return_value = mock_cpu_info
mock_get_hypervisor_version.return_value = mock.sentinel.VERSION
mock_get_numa_topology.return_value._to_json.return_value = (
mock.sentinel.numa_topology_json)
mock_gpu_info = self._get_mock_gpu_info()
mock_get_gpu_info.return_value = mock_gpu_info
mock_get_numa_topology.return_value._to_json.return_value = (
mock.sentinel.numa_topology_json)
self._hostops._hostutils.get_supported_vm_types.return_value = [
constants.IMAGE_PROP_VM_GEN_1]

View File

@ -576,6 +576,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
@mock.patch('hyperv.nova.vif.get_vif_driver')
@mock.patch.object(vmops.VMOps, '_requires_secure_boot')
@mock.patch.object(vmops.VMOps, '_requires_certificate')
@mock.patch.object(vmops.VMOps, '_get_instance_vnuma_config')
@mock.patch.object(vmops.volumeops.VolumeOps, 'attach_volumes')
@mock.patch.object(vmops.VMOps, '_set_instance_disk_qos_specs')
@mock.patch.object(vmops.VMOps, '_attach_root_device')
@ -583,21 +584,21 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
@mock.patch.object(vmops.VMOps, '_create_vm_com_port_pipes')
@mock.patch.object(vmops.VMOps, '_attach_ephemerals')
@mock.patch.object(vmops.VMOps, '_configure_remotefx')
@mock.patch.object(vmops.VMOps, '_get_instance_vnuma_config')
def _test_create_instance(self, mock_get_instance_vnuma_config,
mock_configure_remotefx,
def _test_create_instance(self, mock_configure_remotefx,
mock_attach_ephemerals,
mock_create_pipes,
mock_get_port_settings,
mock_attach_root_device,
mock_set_qos_specs,
mock_attach_volumes,
mock_get_vnuma_config,
mock_requires_certificate,
mock_requires_secure_boot,
mock_get_vif_driver,
mock_configure_secure_vm,
enable_instance_metrics,
vm_gen=constants.VM_GEN_1, vnuma_enabled=False,
vm_gen=constants.VM_GEN_1,
vnuma_enabled=False,
requires_sec_boot=True):
mock_vif_driver = mock_get_vif_driver()
self.flags(dynamic_memory_ratio=2.0, group='hyperv')
@ -613,13 +614,13 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
mock_requires_secure_boot.return_value = True
if vnuma_enabled:
mock_get_instance_vnuma_config.return_value = (
mock_get_vnuma_config.return_value = (
mock.sentinel.mem_per_numa, mock.sentinel.cpus_per_numa)
cpus_per_numa = mock.sentinel.numa_cpus
cpus_per_numa = mock.sentinel.cpus_per_numa
mem_per_numa = mock.sentinel.mem_per_numa
dynamic_memory_ratio = 1.0
else:
mock_get_instance_vnuma_config.return_value = (None, None)
mock_get_vnuma_config.return_value = (None, None)
mem_per_numa, cpus_per_numa = (None, None)
dynamic_memory_ratio = CONF.hyperv.dynamic_memory_ratio
@ -634,6 +635,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
vm_gen=vm_gen,
image_meta=mock.sentinel.image_meta)
mock_get_vnuma_config.assert_called_once_with(mock_instance,
mock.sentinel.image_meta)
self._vmops._vmutils.create_vm.assert_called_once_with(
mock_instance.name, vnuma_enabled, vm_gen,
instance_path, [mock_instance.uuid])
@ -686,6 +689,61 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
self._test_create_instance(enable_instance_metrics=False,
vm_gen=constants.VM_GEN_2)
def test_create_instance_vnuma_enabled(self):
self._test_create_instance(enable_instance_metrics=False,
vnuma_enabled=True)
@mock.patch.object(vmops.hardware, 'numa_get_constraints')
@mock.patch.object(vmops.objects.ImageMeta, 'from_dict')
def _check_get_instance_vnuma_config_exception(self, mock_from_dict,
mock_get_numa, numa_cells):
flavor = {'extra_specs': {}}
mock_instance = mock.MagicMock(flavor=flavor)
image_meta = mock.MagicMock(properties={})
mock_get_numa.return_value.cells = numa_cells
self.assertRaises(exception.InstanceUnacceptable,
self._vmops._get_instance_vnuma_config,
mock_instance, image_meta)
def test_get_instance_vnuma_config_bad_cpuset(self):
cell1 = mock.MagicMock(cpuset=set([0]), memory=1024)
cell2 = mock.MagicMock(cpuset=set([1, 2]), memory=1024)
self._check_get_instance_vnuma_config_exception(
numa_cells=[cell1, cell2])
def test_get_instance_vnuma_config_bad_memory(self):
cell1 = mock.MagicMock(cpuset=set([0]), memory=1024)
cell2 = mock.MagicMock(cpuset=set([1]), memory=2048)
self._check_get_instance_vnuma_config_exception(
numa_cells=[cell1, cell2])
@mock.patch.object(vmops.hardware, 'numa_get_constraints')
@mock.patch.object(vmops.objects.ImageMeta, 'from_dict')
def _check_get_instance_vnuma_config(
self, mock_from_dict, mock_get_numa, numa_topology=None,
expected_mem_per_numa=None, expected_cpus_per_numa=None):
mock_instance = mock.MagicMock()
image_meta = mock.MagicMock()
mock_get_numa.return_value = numa_topology
result_memory_per_numa, result_cpus_per_numa = (
self._vmops._get_instance_vnuma_config(mock_instance, image_meta))
self.assertEqual(expected_cpus_per_numa, result_cpus_per_numa)
self.assertEqual(expected_mem_per_numa, result_memory_per_numa)
def test_get_instance_vnuma_config(self):
cell1 = mock.MagicMock(cpuset=set([0]), memory=2048, cpu_pinning=None)
cell2 = mock.MagicMock(cpuset=set([1]), memory=2048, cpu_pinning=None)
mock_topology = mock.MagicMock(cells=[cell1, cell2])
self._check_get_instance_vnuma_config(numa_topology=mock_topology,
expected_cpus_per_numa=1,
expected_mem_per_numa=2048)
def test_get_instance_vnuma_config_no_topology(self):
self._check_get_instance_vnuma_config()
@mock.patch.object(vmops.volumeops.VolumeOps, 'attach_volume')
def test_attach_root_device_volume(self, mock_attach_volume):
mock_instance = fake_instance.fake_instance_obj(self.context)
@ -1762,62 +1820,6 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
}
self.assertEqual(expected_specs, ret_val)
@mock.patch.object(vmops.hardware, 'numa_get_constraints')
@mock.patch.object(vmops.objects.ImageMeta, 'from_dict')
def _check_get_instance_vnuma_config_exception(self, mock_from_dict,
mock_get_numa, numa_cells):
flavor = {'extra_specs': {}}
mock_instance = mock.MagicMock(flavor=flavor)
image_meta = mock.MagicMock(properties={})
mock_get_numa.return_value.cells = numa_cells
self.assertRaises(exception.InstanceUnacceptable,
self._vmops._get_instance_vnuma_config,
mock_instance, image_meta)
def test_get_instance_vnuma_config_bad_cpuset(self):
cell1 = mock.MagicMock(cpuset=set([0]), memory=1024, cpu_pinning=None)
cell2 = mock.MagicMock(cpuset=set([1, 2]), memory=1024,
cpu_pinning=None)
self._check_get_instance_vnuma_config_exception(
numa_cells=[cell1, cell2])
def test_get_instance_vnuma_config_bad_memory(self):
cell1 = mock.MagicMock(cpuset=set([0]), memory=1024, cpu_pinning=None)
cell2 = mock.MagicMock(cpuset=set([1]), memory=2048, cpu_pinning=None)
self._check_get_instance_vnuma_config_exception(
numa_cells=[cell1, cell2])
def test_get_instance_vnuma_config_cpu_pinning_requested(self):
cell = mock.MagicMock(cpu_pinning={})
self._check_get_instance_vnuma_config_exception(numa_cells=[cell])
@mock.patch.object(vmops.hardware, 'numa_get_constraints')
@mock.patch.object(vmops.objects.ImageMeta, 'from_dict')
def _check_get_instance_vnuma_config(
self, mock_from_dict, mock_get_numa, numa_topology=None,
expected_mem_per_numa=None, expected_cpus_per_numa=None):
mock_instance = mock.MagicMock()
image_meta = mock.MagicMock()
mock_get_numa.return_value = numa_topology
result_memory_per_numa, result_cpus_per_numa = (
self._vmops._get_instance_vnuma_config(mock_instance, image_meta))
self.assertEqual(expected_cpus_per_numa, result_cpus_per_numa)
self.assertEqual(expected_mem_per_numa, result_memory_per_numa)
def test_get_instance_vnuma_config(self):
cell1 = mock.MagicMock(cpuset=set([0]), memory=2048, cpu_pinning=None)
cell2 = mock.MagicMock(cpuset=set([1]), memory=2048, cpu_pinning=None)
mock_topology = mock.MagicMock(cells=[cell1, cell2])
self._check_get_instance_vnuma_config(numa_topology=mock_topology,
expected_cpus_per_numa=1,
expected_mem_per_numa=2048)
def test_get_instance_vnuma_config_no_topology(self):
self._check_get_instance_vnuma_config()
@mock.patch.object(vmops.VMOps, '_get_vif_driver')
def test_unplug_vifs(self, mock_get_vif_driver):
mock_instance = fake_instance.fake_instance_obj(self.context)