Merge "Hyper-V PCI Passthrough"
This commit is contained in:
@@ -193,6 +193,7 @@ class HostOps(object):
|
||||
obj_fields.HVType.HYPERV,
|
||||
obj_fields.VMMode.HVM)],
|
||||
'numa_topology': self._get_host_numa_topology()._to_json(),
|
||||
'pci_passthrough_devices': self._get_pci_passthrough_devices(),
|
||||
}
|
||||
|
||||
gpu_info = self._get_remotefx_gpu_info()
|
||||
@@ -200,6 +201,31 @@ class HostOps(object):
|
||||
|
||||
return dic
|
||||
|
||||
def _get_pci_passthrough_devices(self):
|
||||
"""Get host PCI devices information.
|
||||
|
||||
Obtains PCI devices information and returns it as a JSON string.
|
||||
|
||||
:returns: a JSON string containing a list of the assignable PCI
|
||||
devices information.
|
||||
"""
|
||||
|
||||
pci_devices = self._hostutils.get_pci_passthrough_devices()
|
||||
|
||||
for pci_dev in pci_devices:
|
||||
# NOTE(claudiub): These fields are required by the PCI tracker.
|
||||
dev_label = 'label_%(vendor_id)s_%(product_id)s' % {
|
||||
'vendor_id': pci_dev['vendor_id'],
|
||||
'product_id': pci_dev['product_id']}
|
||||
|
||||
# TODO(claudiub): Find a way to associate the PCI devices with
|
||||
# the NUMA nodes they are in.
|
||||
pci_dev.update(dev_type=obj_fields.PciDeviceType.STANDARD,
|
||||
label=dev_label,
|
||||
numa_node=None)
|
||||
|
||||
return jsonutils.dumps(pci_devices)
|
||||
|
||||
def host_power_action(self, action):
|
||||
"""Reboots, shuts down or powers up the host."""
|
||||
if action in [constants.HOST_POWER_ACTION_SHUTDOWN,
|
||||
|
||||
@@ -379,6 +379,13 @@ class VMOps(object):
|
||||
dynamic_memory_ratio = CONF.hyperv.dynamic_memory_ratio
|
||||
vnuma_enabled = False
|
||||
|
||||
if instance.pci_requests.requests:
|
||||
# NOTE(claudiub): if the instance requires PCI devices, its
|
||||
# host shutdown action MUST be shutdown.
|
||||
host_shutdown_action = os_win_const.HOST_SHUTDOWN_ACTION_SHUTDOWN
|
||||
else:
|
||||
host_shutdown_action = None
|
||||
|
||||
self._vmutils.create_vm(instance_name,
|
||||
vnuma_enabled,
|
||||
vm_gen,
|
||||
@@ -391,7 +398,8 @@ class VMOps(object):
|
||||
instance.flavor.vcpus,
|
||||
cpus_per_numa_node,
|
||||
CONF.hyperv.limit_cpu_features,
|
||||
dynamic_memory_ratio)
|
||||
dynamic_memory_ratio,
|
||||
host_shutdown_action=host_shutdown_action)
|
||||
|
||||
self._configure_remotefx(instance, vm_gen)
|
||||
|
||||
@@ -423,6 +431,16 @@ class VMOps(object):
|
||||
self._configure_secure_vm(context, instance, image_meta,
|
||||
secure_boot_enabled)
|
||||
|
||||
self._attach_pci_devices(instance)
|
||||
|
||||
def _attach_pci_devices(self, instance):
|
||||
for pci_request in instance.pci_requests.requests:
|
||||
spec = pci_request.spec[0]
|
||||
for counter in range(pci_request.count):
|
||||
self._vmutils.add_pci_device(instance.name,
|
||||
spec['vendor_id'],
|
||||
spec['product_id'])
|
||||
|
||||
def _get_instance_vnuma_config(self, instance, image_meta):
|
||||
"""Returns the appropriate NUMA configuration for Hyper-V instances,
|
||||
given the desired instance NUMA topology.
|
||||
|
||||
@@ -19,6 +19,7 @@ import mock
|
||||
from nova import context as nova_context
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova.objects import fields as obj_fields
|
||||
from os_win import constants as os_win_const
|
||||
from oslo_config import cfg
|
||||
from oslo_serialization import jsonutils
|
||||
@@ -157,6 +158,7 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_NUMATopology.assert_called_once_with(
|
||||
cells=[mock_NUMACell.return_value])
|
||||
|
||||
@mock.patch.object(hostops.HostOps, '_get_pci_passthrough_devices')
|
||||
@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')
|
||||
@@ -168,7 +170,8 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_get_storage_info_gb,
|
||||
mock_get_hypervisor_version,
|
||||
mock_get_memory_info, mock_get_cpu_info,
|
||||
mock_get_gpu_info, mock_get_numa_topology):
|
||||
mock_get_gpu_info, mock_get_numa_topology,
|
||||
mock_get_pci_devices):
|
||||
mock_get_storage_info_gb.return_value = (mock.sentinel.LOCAL_GB,
|
||||
mock.sentinel.LOCAL_GB_FREE,
|
||||
mock.sentinel.LOCAL_GB_USED)
|
||||
@@ -180,6 +183,7 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
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_get_pci_devices.return_value = mock.sentinel.pcis
|
||||
|
||||
mock_gpu_info = self._get_mock_gpu_info()
|
||||
mock_get_gpu_info.return_value = mock_gpu_info
|
||||
@@ -189,6 +193,7 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_get_memory_info.assert_called_once_with()
|
||||
mock_get_cpu_info.assert_called_once_with()
|
||||
mock_get_hypervisor_version.assert_called_once_with()
|
||||
mock_get_pci_devices.assert_called_once_with()
|
||||
expected = {'supported_instances': [("i686", "hyperv", "hvm"),
|
||||
("x86_64", "hyperv", "hvm")],
|
||||
'hypervisor_hostname': mock_node(),
|
||||
@@ -205,9 +210,32 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
'remotefx_available_video_ram': 2048,
|
||||
'remotefx_gpu_info': mock.sentinel.FAKE_GPU_INFO,
|
||||
'remotefx_total_video_ram': 4096,
|
||||
'pci_passthrough_devices': mock.sentinel.pcis,
|
||||
}
|
||||
self.assertEqual(expected, response)
|
||||
|
||||
@mock.patch.object(hostops.jsonutils, 'dumps')
|
||||
def test_get_pci_passthrough_devices(self, mock_jsonutils_dumps):
|
||||
mock_pci_dev = {'vendor_id': 'fake_vendor_id',
|
||||
'product_id': 'fake_product_id',
|
||||
'dev_id': 'fake_dev_id',
|
||||
'address': 'fake_address'}
|
||||
mock_get_pcis = self._hostops._hostutils.get_pci_passthrough_devices
|
||||
mock_get_pcis.return_value = [mock_pci_dev]
|
||||
|
||||
expected_label = 'label_%(vendor_id)s_%(product_id)s' % {
|
||||
'vendor_id': mock_pci_dev['vendor_id'],
|
||||
'product_id': mock_pci_dev['product_id']}
|
||||
expected_pci_dev = mock_pci_dev.copy()
|
||||
expected_pci_dev.update(dev_type=obj_fields.PciDeviceType.STANDARD,
|
||||
label=expected_label,
|
||||
numa_node=None)
|
||||
|
||||
result = self._hostops._get_pci_passthrough_devices()
|
||||
|
||||
self.assertEqual(mock_jsonutils_dumps.return_value, result)
|
||||
mock_jsonutils_dumps.assert_called_once_with([expected_pci_dev])
|
||||
|
||||
def _test_host_power_action(self, action):
|
||||
self._hostops._hostutils.host_power_action = mock.Mock()
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from eventlet import timeout as etimeout
|
||||
import mock
|
||||
from nova.compute import vm_states
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova.objects import fields
|
||||
from nova.objects import flavor as flavor_obj
|
||||
from nova.tests.unit.objects import test_flavor
|
||||
@@ -565,6 +566,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
self.assertEqual([], events)
|
||||
mock_is_neutron.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(vmops.VMOps, '_attach_pci_devices')
|
||||
@mock.patch.object(vmops.VMOps, '_configure_secure_vm')
|
||||
@mock.patch.object(vmops.VMOps, '_requires_secure_boot')
|
||||
@mock.patch.object(vmops.VMOps, '_requires_certificate')
|
||||
@@ -587,9 +589,11 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
mock_requires_certificate,
|
||||
mock_requires_secure_boot,
|
||||
mock_configure_secure_vm,
|
||||
mock_attach_pci_devices,
|
||||
enable_instance_metrics,
|
||||
vm_gen=constants.VM_GEN_1,
|
||||
vnuma_enabled=False,
|
||||
pci_requests=None,
|
||||
requires_sec_boot=True):
|
||||
self.flags(dynamic_memory_ratio=2.0, group='hyperv')
|
||||
self.flags(enable_instance_metrics_collection=enable_instance_metrics,
|
||||
@@ -605,6 +609,11 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
|
||||
flavor = flavor_obj.Flavor(**test_flavor.fake_flavor)
|
||||
mock_instance.flavor = flavor
|
||||
instance_pci_requests = objects.InstancePCIRequests(
|
||||
requests=pci_requests or [], instance_uuid=mock_instance.uuid)
|
||||
mock_instance.pci_requests = instance_pci_requests
|
||||
host_shutdown_action = (os_win_const.HOST_SHUTDOWN_ACTION_SHUTDOWN
|
||||
if pci_requests else None)
|
||||
|
||||
if vnuma_enabled:
|
||||
mock_get_vnuma_config.return_value = (
|
||||
@@ -632,7 +641,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
self._vmops._vmutils.update_vm.assert_called_once_with(
|
||||
mock_instance.name, mock_instance.flavor.memory_mb, mem_per_numa,
|
||||
mock_instance.flavor.vcpus, cpus_per_numa,
|
||||
CONF.hyperv.limit_cpu_features, dynamic_memory_ratio)
|
||||
CONF.hyperv.limit_cpu_features, dynamic_memory_ratio,
|
||||
host_shutdown_action=host_shutdown_action)
|
||||
|
||||
mock_configure_remotefx.assert_called_once_with(mock_instance, vm_gen)
|
||||
mock_create_scsi_ctrl = self._vmops._vmutils.create_scsi_controller
|
||||
@@ -665,6 +675,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
msft_ca_required=mock_requires_certificate.return_value)
|
||||
mock_configure_secure_vm.assert_called_once_with(self.context,
|
||||
mock_instance, mock.sentinel.image_meta, requires_sec_boot)
|
||||
mock_attach_pci_devices.assert_called_once_with(mock_instance)
|
||||
|
||||
def test_create_instance(self):
|
||||
self._test_create_instance(enable_instance_metrics=True)
|
||||
@@ -680,6 +691,29 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
|
||||
self._test_create_instance(enable_instance_metrics=False,
|
||||
vnuma_enabled=True)
|
||||
|
||||
def test_create_instance_pci_requested(self):
|
||||
vendor_id = 'fake_vendor_id'
|
||||
product_id = 'fake_product_id'
|
||||
spec = {'vendor_id': vendor_id, 'product_id': product_id}
|
||||
request = objects.InstancePCIRequest(count=1, spec=[spec])
|
||||
self._test_create_instance(enable_instance_metrics=False,
|
||||
pci_requests=[request])
|
||||
|
||||
def test_attach_pci_devices(self):
|
||||
mock_instance = fake_instance.fake_instance_obj(self.context)
|
||||
vendor_id = 'fake_vendor_id'
|
||||
product_id = 'fake_product_id'
|
||||
spec = {'vendor_id': vendor_id, 'product_id': product_id}
|
||||
request = objects.InstancePCIRequest(count=2, spec=[spec])
|
||||
instance_pci_requests = objects.InstancePCIRequests(
|
||||
requests=[request], instance_uuid=mock_instance.uuid)
|
||||
mock_instance.pci_requests = instance_pci_requests
|
||||
|
||||
self._vmops._attach_pci_devices(mock_instance)
|
||||
|
||||
self._vmops._vmutils.add_pci_device.assert_has_calls(
|
||||
[mock.call(mock_instance.name, vendor_id, product_id)] * 2)
|
||||
|
||||
@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,
|
||||
|
||||
Reference in New Issue
Block a user