Merge "Adds RemoteFX support to the Hyper-V driver"

This commit is contained in:
Jenkins
2016-06-07 14:58:54 +00:00
committed by Gerrit Code Review
7 changed files with 276 additions and 5 deletions

View File

@@ -411,6 +411,49 @@ Related options:
""")
enable_remotefx_opt = cfg.BoolOpt('enable_remotefx',
default=False,
help="""
Enable RemoteFX feature
This requires at least one DirectX 11 capable graphics adapter for
Windows / Hyper-V Server 2012 R2 or newer and RDS-Virtualization
feature has to be enabled.
Possible values:
* False: Disables the feature (Default).
* True: Enables the feature.
Instances with RemoteFX can be requested with the following flavor
extra specs:
**os:resolution**. Guest VM screen resolution size. Acceptable values::
1024x768, 1280x1024, 1600x1200, 1920x1200, 2560x1600, 3840x2160
``3840x2160`` is only available on Windows / Hyper-V Server 2016.
**os:monitors**. Guest VM number of monitors. Acceptable values::
[1, 4] - Windows / Hyper-V Server 2012 R2
[1, 8] - Windows / Hyper-V Server 2016
**os:vram**. Guest VM VRAM amount. Only available on
Windows / Hyper-V Server 2016. Acceptable values::
64, 128, 256, 512, 1024
Services which consume this:
* nova-compute
Related options:
* None
""")
ALL_OPTS = [dynamic_memory_ratio_opt,
enable_instance_metrics_collection_opt,
instances_path_share_opt,
@@ -425,7 +468,8 @@ ALL_OPTS = [dynamic_memory_ratio_opt,
config_drive_cdrom_opt,
config_drive_inject_password_opt,
volume_attach_retry_count_opt,
volume_attach_retry_interval_opt]
volume_attach_retry_interval_opt,
enable_remotefx_opt]
def register_opts(conf):

View File

@@ -78,6 +78,11 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
'threads': self.FAKE_NUM_CPUS,
'sockets': self.FAKE_NUM_CPUS}}
def _get_mock_gpu_info(self):
return {'remotefx_total_video_ram': 4096,
'remotefx_available_video_ram': 2048,
'remotefx_gpu_info': mock.sentinel.FAKE_GPU_INFO}
def test_get_memory_info(self):
self._hostops._hostutils.get_memory_info.return_value = (2 * units.Ki,
1 * units.Ki)
@@ -104,6 +109,29 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
self.assertEqual(6003, response_lower)
self.assertEqual(10001, response_higher)
def test_get_remotefx_gpu_info(self):
self.flags(enable_remotefx=True, group='hyperv')
fake_gpus = [{'total_video_ram': '2048',
'available_video_ram': '1024'},
{'total_video_ram': '1024',
'available_video_ram': '1024'}]
self._hostops._hostutils.get_remotefx_gpu_info.return_value = fake_gpus
ret_val = self._hostops._get_remotefx_gpu_info()
self.assertEqual(3072, ret_val['total_video_ram'])
self.assertEqual(1024, ret_val['used_video_ram'])
def test_get_remotefx_gpu_info_disabled(self):
self.flags(enable_remotefx=False, group='hyperv')
ret_val = self._hostops._get_remotefx_gpu_info()
self.assertEqual(0, ret_val['total_video_ram'])
self.assertEqual(0, ret_val['used_video_ram'])
self._hostops._hostutils.get_remotefx_gpu_info.assert_not_called()
@mock.patch.object(hostops.HostOps, '_get_remotefx_gpu_info')
@mock.patch.object(hostops.HostOps, '_get_cpu_info')
@mock.patch.object(hostops.HostOps, '_get_memory_info')
@mock.patch.object(hostops.HostOps, '_get_hypervisor_version')
@@ -112,7 +140,8 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
def test_get_available_resource(self, mock_node,
mock_get_local_hdd_info_gb,
mock_get_hypervisor_version,
mock_get_memory_info, mock_get_cpu_info):
mock_get_memory_info, mock_get_cpu_info,
mock_get_gpu_info):
mock_get_local_hdd_info_gb.return_value = (mock.sentinel.LOCAL_GB,
mock.sentinel.LOCAL_GB_FREE,
mock.sentinel.LOCAL_GB_USED)
@@ -123,6 +152,9 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
mock_get_cpu_info.return_value = mock_cpu_info
mock_get_hypervisor_version.return_value = mock.sentinel.VERSION
mock_gpu_info = self._get_mock_gpu_info()
mock_get_gpu_info.return_value = mock_gpu_info
response = self._hostops.get_available_resource()
mock_get_memory_info.assert_called_once_with()
@@ -141,6 +173,9 @@ class HostOpsTestCase(test_base.HyperVBaseTestCase):
'vcpus_used': 0,
'hypervisor_type': 'hyperv',
'numa_topology': None,
'remotefx_available_video_ram': 2048,
'remotefx_gpu_info': mock.sentinel.FAKE_GPU_INFO,
'remotefx_total_video_ram': 4096,
}
self.assertEqual(expected, response)

View File

@@ -26,7 +26,9 @@ from oslo_utils import units
from nova.compute import vm_states
from nova import exception
from nova import objects
from nova.objects import flavor as flavor_obj
from nova.tests.unit import fake_instance
from nova.tests.unit.objects import test_flavor
from nova.tests.unit.objects import test_virtual_interface
from nova.tests.unit.virt.hyperv import test_base
from nova.virt import hardware
@@ -413,9 +415,11 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
'.attach_volumes')
@mock.patch.object(vmops.VMOps, '_attach_drive')
@mock.patch.object(vmops.VMOps, '_create_vm_com_port_pipes')
def _test_create_instance(self, mock_create_pipes,
mock_attach_drive, mock_attach_volumes,
fake_root_path, fake_ephemeral_path,
@mock.patch.object(vmops.VMOps, '_configure_remotefx')
def _test_create_instance(self, mock_configure_remotefx,
mock_create_pipes, mock_attach_drive,
mock_attach_volumes, fake_root_path,
fake_ephemeral_path,
enable_instance_metrics,
vm_gen=constants.VM_GEN_1):
mock_vif_driver = mock.MagicMock()
@@ -427,6 +431,9 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
mock_instance = fake_instance.fake_instance_obj(self.context)
instance_path = os.path.join(CONF.instances_path, mock_instance.name)
flavor = flavor_obj.Flavor(**test_flavor.fake_flavor)
mock_instance.flavor = flavor
self._vmops.create_instance(instance=mock_instance,
network_info=[fake_network_info],
block_device_info=mock.sentinel.DEV_INFO,
@@ -438,6 +445,8 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
mock_instance.vcpus, CONF.hyperv.limit_cpu_features,
CONF.hyperv.dynamic_memory_ratio, vm_gen, instance_path,
[mock_instance.uuid])
mock_configure_remotefx.assert_called_once_with(mock_instance, vm_gen)
expected = []
ctrl_type = vmops.VM_GENERATIONS_CONTROLLER_TYPES[vm_gen]
ctrl_disk_addr = 0
@@ -1052,6 +1061,64 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase):
mock.call(mock.sentinel.FAKE_DVD_PATH2,
mock.sentinel.FAKE_DEST_PATH))
def _setup_remotefx_mocks(self):
mock_instance = fake_instance.fake_instance_obj(self.context)
mock_instance.flavor.extra_specs = {
'os:resolution': os_win_const.REMOTEFX_MAX_RES_1920x1200,
'os:monitors': '2',
'os:vram': '256'}
return mock_instance
def test_configure_remotefx_not_required(self):
self.flags(enable_remotefx=False, group='hyperv')
mock_instance = fake_instance.fake_instance_obj(self.context)
self._vmops._configure_remotefx(mock_instance, mock.sentinel.VM_GEN)
def test_configure_remotefx_exception_enable_config(self):
self.flags(enable_remotefx=False, group='hyperv')
mock_instance = self._setup_remotefx_mocks()
self.assertRaises(exception.InstanceUnacceptable,
self._vmops._configure_remotefx,
mock_instance, mock.sentinel.VM_GEN)
def test_configure_remotefx_exception_server_feature(self):
self.flags(enable_remotefx=True, group='hyperv')
mock_instance = self._setup_remotefx_mocks()
self._vmops._hostutils.check_server_feature.return_value = False
self.assertRaises(exception.InstanceUnacceptable,
self._vmops._configure_remotefx,
mock_instance, mock.sentinel.VM_GEN)
def test_configure_remotefx_exception_vm_gen(self):
self.flags(enable_remotefx=True, group='hyperv')
mock_instance = self._setup_remotefx_mocks()
self._vmops._hostutils.check_server_feature.return_value = True
self._vmops._vmutils.vm_gen_supports_remotefx.return_value = False
self.assertRaises(exception.InstanceUnacceptable,
self._vmops._configure_remotefx,
mock_instance, mock.sentinel.VM_GEN)
def test_configure_remotefx(self):
self.flags(enable_remotefx=True, group='hyperv')
mock_instance = self._setup_remotefx_mocks()
self._vmops._hostutils.check_server_feature.return_value = True
self._vmops._vmutils.vm_gen_supports_remotefx.return_value = True
extra_specs = mock_instance.flavor.extra_specs
self._vmops._configure_remotefx(mock_instance,
constants.VM_GEN_1)
mock_enable_remotefx = (
self._vmops._vmutils.enable_remotefx_video_adapter)
mock_enable_remotefx.assert_called_once_with(
mock_instance.name, int(extra_specs['os:monitors']),
extra_specs['os:resolution'],
int(extra_specs['os:vram']) * units.Mi)
@mock.patch.object(vmops.VMOps, '_get_vm_state')
def test_check_hotplug_available_vm_disabled(self, mock_get_vm_state):
fake_vm = fake_instance.fake_instance_obj(self.context)

View File

@@ -76,3 +76,7 @@ SERIAL_PORT_TYPE_RW = 'rw'
# The default serial console port number used for
# logging and interactive sessions.
DEFAULT_SERIAL_CONSOLE_PORT = 1
FLAVOR_ESPEC_REMOTEFX_RES = 'os:resolution'
FLAVOR_ESPEC_REMOTEFX_MONITORS = 'os:monitors'
FLAVOR_ESPEC_REMOTEFX_VRAM = 'os:vram'

View File

@@ -105,6 +105,22 @@ class HostOps(object):
LOG.debug('Windows version: %s ', version)
return version
def _get_remotefx_gpu_info(self):
total_video_ram = 0
available_video_ram = 0
if CONF.hyperv.enable_remotefx:
gpus = self._hostutils.get_remotefx_gpu_info()
for gpu in gpus:
total_video_ram += int(gpu['total_video_ram'])
available_video_ram += int(gpu['available_video_ram'])
else:
gpus = []
return {'total_video_ram': total_video_ram,
'used_video_ram': total_video_ram - available_video_ram,
'gpu_info': jsonutils.dumps(gpus)}
def get_available_resource(self):
"""Retrieve resource info.
@@ -146,6 +162,8 @@ class HostOps(object):
'numa_topology': None,
}
gpu_info = self._get_remotefx_gpu_info()
dic.update(gpu_info)
return dic
def host_power_action(self, action):

View File

@@ -287,6 +287,8 @@ class VMOps(object):
instance_path,
[instance.uuid])
self._configure_remotefx(instance, vm_gen)
self._vmutils.create_scsi_controller(instance_name)
controller_type = VM_GENERATIONS_CONTROLLER_TYPES[vm_gen]
@@ -327,6 +329,45 @@ class VMOps(object):
if CONF.hyperv.enable_instance_metrics_collection:
self._metricsutils.enable_vm_metrics_collection(instance_name)
def _configure_remotefx(self, instance, vm_gen):
extra_specs = instance.flavor.extra_specs
remotefx_max_resolution = extra_specs.get(
constants.FLAVOR_ESPEC_REMOTEFX_RES)
if not remotefx_max_resolution:
# RemoteFX not required.
return
if not CONF.hyperv.enable_remotefx:
raise exception.InstanceUnacceptable(
_("enable_remotefx configuration option needs to be set to "
"True in order to use RemoteFX."))
if not self._hostutils.check_server_feature(
self._hostutils.FEATURE_RDS_VIRTUALIZATION):
raise exception.InstanceUnacceptable(
_("The RDS-Virtualization feature must be installed in order "
"to use RemoteFX."))
if not self._vmutils.vm_gen_supports_remotefx(vm_gen):
raise exception.InstanceUnacceptable(
_("RemoteFX is not supported on generation %s virtual "
"machines on this version of Windows.") % vm_gen)
instance_name = instance.name
LOG.debug('Configuring RemoteFX for instance: %s', instance_name)
remotefx_monitor_count = int(extra_specs.get(
constants.FLAVOR_ESPEC_REMOTEFX_MONITORS) or 1)
remotefx_vram = extra_specs.get(
constants.FLAVOR_ESPEC_REMOTEFX_VRAM)
vram_bytes = int(remotefx_vram) * units.Mi if remotefx_vram else None
self._vmutils.enable_remotefx_video_adapter(
instance_name,
remotefx_monitor_count,
remotefx_max_resolution,
vram_bytes)
def _attach_drive(self, instance_name, path, drive_addr, ctrl_disk_addr,
controller_type, drive_type=constants.DISK):
if controller_type == constants.CTRL_TYPE_SCSI:

View File

@@ -0,0 +1,62 @@
---
features: |
- Hyper-V RemoteFX feature.
Microsoft RemoteFX enhances the visual experience in RDP connections,
including providing access to virtualized instances of a physical GPU to
multiple guests running on Hyper-V.
In order to use RemoteFX in Hyper-V 2012 R2, one or more DirectX 11
capable display adapters must be present and the RDS-Virtualization
server feature must be installed.
To enable this feature, the following config option must be set in
the Hyper-V compute node's 'nova.conf' file::
[hyperv]
enable_remotefx = True
To create instances with RemoteFX capabilities, the following flavor
extra specs must be used:
**os:resolution**. Guest VM screen resolution size. Acceptable values::
1024x768, 1280x1024, 1600x1200, 1920x1200, 2560x1600, 3840x2160
'3840x2160' is only available on Windows / Hyper-V Server 2016.
**os:monitors**. Guest VM number of monitors. Acceptable values::
[1, 4] - Windows / Hyper-V Server 2012 R2
[1, 8] - Windows / Hyper-V Server 2016
**os:vram**. Guest VM VRAM amount. Only available on
Windows / Hyper-V Server 2016. Acceptable values::
64, 128, 256, 512, 1024
There are a few considerations that needs to be kept in mind:
* Not all guests support RemoteFX capabilities.
* Windows / Hyper-V Server 2012 R2 does not support Generation 2 VMs
with RemoteFX capabilities.
* Per resolution, there is a maximum amount of monitors that can be
added. The limits are as follows::
For Windows / Hyper-V Server 2012 R2::
1024x768: 4
1280x1024: 4
1600x1200: 3
1920x1200: 2
2560x1600: 1
For Windows / Hyper-V Server 2016::
1024x768: 8
1280x1024: 8
1600x1200: 4
1920x1200: 4
2560x1600: 2
3840x2160: 1