diff --git a/hyperv/nova/constants.py b/hyperv/nova/constants.py index dc8bd937..9a77ad87 100644 --- a/hyperv/nova/constants.py +++ b/hyperv/nova/constants.py @@ -74,6 +74,8 @@ REMOTEFX_MAX_RES_1280x1024 = "1280x1024" REMOTEFX_MAX_RES_1600x1200 = "1600x1200" REMOTEFX_MAX_RES_1920x1200 = "1920x1200" REMOTEFX_MAX_RES_2560x1600 = "2560x1600" +REMOTEFX_MAX_RES_3840x2160 = "3840x2160" + FLAVOR_REMOTE_FX_EXTRA_SPEC_KEY = "hyperv:remotefx" diff --git a/hyperv/nova/vmops.py b/hyperv/nova/vmops.py index 72659db7..6362555e 100644 --- a/hyperv/nova/vmops.py +++ b/hyperv/nova/vmops.py @@ -393,13 +393,7 @@ class VMOps(object): remote_fx_config = flavor_extra_specs.get( constants.FLAVOR_REMOTE_FX_EXTRA_SPEC_KEY) if remote_fx_config: - if vm_gen == constants.VM_GEN_2: - reason = _("RemoteFX is not supported on generation 2 virtual " - "machines.") - raise exception.InstanceUnacceptable(instance_id=instance.uuid, - reason=reason) - else: - self._configure_remotefx(instance, remote_fx_config) + self._configure_remotefx(instance, vm_gen, remote_fx_config) self._vmutils.create_scsi_controller(instance_name) @@ -570,7 +564,7 @@ class VMOps(object): return memory_per_numa_node, cpus_per_numa_node - def _configure_remotefx(self, instance, config): + def _configure_remotefx(self, instance, vm_gen, config): if not CONF.hyperv.enable_remotefx: reason = _("enable_remotefx configuration option needs to be set " "to True in order to use RemoteFX") @@ -584,16 +578,25 @@ class VMOps(object): raise exception.InstanceUnacceptable(instance_id=instance.uuid, reason=reason) + if not self._vmutils.vm_gen_supports_remotefx(vm_gen): + reason = _("RemoteFX is not supported on generation %s virtual " + "machines on this version of Windows.") % vm_gen + raise exception.InstanceUnacceptable(instance_id=instance.uuid, + reason=reason) + instance_name = instance.name LOG.debug('Configuring RemoteFX for instance: %s', instance_name) - - (remotefx_max_resolution, remotefx_monitor_count) = config.split(',') - remotefx_monitor_count = int(remotefx_monitor_count) + remotefx_args = config.split(',') + remotefx_max_resolution = remotefx_args[0] + remotefx_monitor_count = int(remotefx_args[1]) + remotefx_vram = remotefx_args[2] if len(remotefx_args) == 3 else None + 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) + remotefx_max_resolution, + vram_bytes) def attach_config_drive(self, instance, configdrive_path, vm_gen): configdrive_ext = configdrive_path[(configdrive_path.rfind('.') + 1):] diff --git a/hyperv/tests/unit/test_vmops.py b/hyperv/tests/unit/test_vmops.py index 6b0700b0..97c3e4f2 100644 --- a/hyperv/tests/unit/test_vmops.py +++ b/hyperv/tests/unit/test_vmops.py @@ -553,6 +553,7 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): if remotefx is True: mock_configure_remotefx.assert_called_once_with( mock_instance, + vm_gen, flavor.extra_specs['hyperv:remotefx']) self._vmops._vmutils.create_vm.assert_called_once_with( @@ -1440,34 +1441,45 @@ class VMOpsTestCase(test_base.HyperVBaseTestCase): def test_get_instance_vnuma_config_no_topology(self): self._check_get_instance_vnuma_config() - def _test_configure_remotefx(self, fail=False): - self.flags(enable_remotefx=True, group='hyperv') + def _test_configure_remotefx(self, fail=True, enable_remotefx=True): + self.flags(enable_remotefx=enable_remotefx, group='hyperv') mock_instance = fake_instance.fake_instance_obj(self.context) fake_resolution = "1920x1200" fake_monitor_count = 3 - fake_config = "%s,%s" % (fake_resolution, fake_monitor_count) + fake_vram_mb = 64 + fake_config = "%s,%s,%s" % ( + fake_resolution, fake_monitor_count, fake_vram_mb) - self._vmops._vmutils.enable_remotefx_video_adapter = mock.MagicMock() enable_remotefx = self._vmops._vmutils.enable_remotefx_video_adapter - self._vmops._hostutils.check_server_feature = mock.MagicMock() if fail: - self._vmops._hostutils.check_server_feature.return_value = False self.assertRaises(exception.InstanceUnacceptable, self._vmops._configure_remotefx, - mock_instance, fake_config) + mock_instance, mock.sentinel.vm_gen, fake_config) else: - self._vmops._configure_remotefx(mock_instance, fake_config) + self._vmops._configure_remotefx( + mock_instance, mock.sentinel.vm_gen, fake_config) enable_remotefx.assert_called_once_with(mock_instance.name, fake_monitor_count, - fake_resolution) + fake_resolution, + fake_vram_mb * units.Mi) - def test_configure_remotefx_exception(self): - self._test_configure_remotefx(fail=True) + def test_configure_remotefx_disabled(self): + self._test_configure_remotefx(enable_remotefx=False) + + def test_configure_remotefx_no_server_feature(self): + self._vmops._hostutils.check_server_feature.return_value = False + self._test_configure_remotefx() + self._vmops._hostutils.check_server_feature.assert_called_once_with( + self._vmops._hostutils.FEATURE_RDS_VIRTUALIZATION) + + def test_configure_remotefx_unsupported_vm_gen(self): + self._vmops._vmutils.vm_gen_supports_remotefx.return_value = False + self._test_configure_remotefx() def test_configure_remotefx(self): - self._test_configure_remotefx() + self._test_configure_remotefx(fail=False) @mock.patch.object(vmops.VMOps, '_get_vm_state') def _test_check_hotplug_is_available(self, mock_get_vm_state, vm_gen,