diff --git a/doc/source/user/flavors.rst b/doc/source/user/flavors.rst index a084538d8370..7c3456d13195 100644 --- a/doc/source/user/flavors.rst +++ b/doc/source/user/flavors.rst @@ -305,11 +305,15 @@ Hardware video RAM image. Used in conjunction with the ``hw_video_ram`` image property. ``hw_video_ram`` must be less than or equal to ``hw_video:ram_max_mb``. - This is currently only supported by the libvirt driver. + This is currently supported by the libvirt and the vmware drivers. See https://libvirt.org/formatdomain.html#elementsVideo for more information on how this is used to set the ``vram`` attribute with the libvirt driver. + See https://pubs.vmware.com/vi-sdk/visdk250/ReferenceGuide/vim.vm.device.VirtualVideoCard.html + for more information on how this is used to set the ``videoRamSizeInKB`` attribute with + the vmware driver. + Watchdog behavior For the libvirt driver, you can enable and set the behavior of a virtual hardware watchdog device for each flavor. Watchdog devices keep an eye on the diff --git a/nova/tests/unit/virt/vmwareapi/test_vmops.py b/nova/tests/unit/virt/vmwareapi/test_vmops.py index 5c45b9db77de..ccca577d7c15 100644 --- a/nova/tests/unit/virt/vmwareapi/test_vmops.py +++ b/nova/tests/unit/virt/vmwareapi/test_vmops.py @@ -2192,6 +2192,104 @@ class VMwareVMOpsTestCase(test.NoDBTestCase): flavor_extra_specs = self._vmops._get_extra_specs(flavor, None) self._validate_extra_specs(expected, flavor_extra_specs) + """ + The test covers the negative failure scenario, where `hw_video_ram`, + coming from the image is bigger than the maximum allowed video ram from + the flavor. + """ + def test_video_ram(self): + meta_dict = {'id': self._image_id, 'properties': {'hw_video_ram': 120}} + image_meta, flavor = self._get_image_and_flavor_for_test_video( + meta_dict) + + self.assertRaises(exception.RequestedVRamTooHigh, + self._vmops._get_extra_specs, + flavor, + image_meta) + + """ + Testing VM provisioning result in the case where `hw_video_ram`, + coming from the image is not specified. This is a success scenario, + in the case where `hw_video_ram` property is not set. + """ + def test_video_ram_if_none(self): + meta_dict = {'id': self._image_id, 'properties': {}} + image_meta, flavor = self._get_image_and_flavor_for_test_video( + meta_dict) + + extra_specs = self._vmops._get_extra_specs(flavor, image_meta) + self.assertIsNone(extra_specs.hw_video_ram) + + """ + Testing VM provisioning result in the case where `hw_video:ram_max_mb`, + coming from the flavor is not specified. This is a success scenario, + in the case where `hw_video_ram` property is not set. + """ + def test_max_video_ram_none(self): + meta_dict = {'id': self._image_id, 'properties': {'hw_video_ram': 120}} + image_meta = objects.ImageMeta.from_dict(meta_dict) + flavor_extra_specs = {'quota:cpu_limit': 7, + 'quota:cpu_reservation': 6} + flavor = objects.Flavor(name='my-flavor', + memory_mb=6, + vcpus=28, + root_gb=496, + ephemeral_gb=8128, + swap=33550336, + extra_specs=flavor_extra_specs) + + self.assertRaises(exception.RequestedVRamTooHigh, + self._vmops._get_extra_specs, + flavor, + image_meta) + + """ + Testing VM provisioning result in the case where `hw_video_ram`, + coming from the image is less than the maximum allowed video ram from + the flavor. This is a success scenario, in the case where `hw_video_ram` + property is set in the extra spec. + """ + def test_success_video_ram(self): + expected_video_ram = 90 + meta_dict = {'id': self._image_id, 'properties': { + 'hw_video_ram': expected_video_ram}} + image_meta, flavor = self._get_image_and_flavor_for_test_video( + meta_dict) + + extra_specs = self._vmops._get_extra_specs(flavor, image_meta) + self.assertEqual(self._calculate_expected_fake_video_ram( + expected_video_ram), extra_specs.hw_video_ram) + + """ + Testing VM provisioning result in the case where `hw_video_ram`, + coming from the image is equal to 0. This is a success scenario, in the + case where `hw_video_ram` property is not set in the extra spec. + """ + def test_zero_video_ram(self): + meta_dict = {'id': self._image_id, 'properties': {'hw_video_ram': 0}} + image_meta, flavor = self._get_image_and_flavor_for_test_video( + meta_dict) + + extra_specs = self._vmops._get_extra_specs(flavor, image_meta) + self.assertIsNone(extra_specs.hw_video_ram) + + def _calculate_expected_fake_video_ram(self, amount): + return amount * units.Mi / units.Ki + + def _get_image_and_flavor_for_test_video(self, meta_dict): + image_meta = objects.ImageMeta.from_dict(meta_dict) + flavor_extra_specs = {'quota:cpu_limit': 7, + 'quota:cpu_reservation': 6, + 'hw_video:ram_max_mb': 100} + flavor = objects.Flavor(name='my-flavor', + memory_mb=6, + vcpus=28, + root_gb=496, + ephemeral_gb=8128, + swap=33550336, + extra_specs=flavor_extra_specs) + return image_meta, flavor + def test_extra_specs_cpu_limit(self): flavor_extra_specs = {'quota:cpu_limit': 7} cpu_limits = vm_util.Limits(limit=7) diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py index f0bf503b9c38..322c8b133cf2 100644 --- a/nova/virt/vmwareapi/vm_util.py +++ b/nova/virt/vmwareapi/vm_util.py @@ -92,7 +92,7 @@ class ExtraSpecs(object): def __init__(self, cpu_limits=None, hw_version=None, storage_policy=None, cores_per_socket=None, memory_limits=None, disk_io_limits=None, - vif_limits=None, firmware=None): + vif_limits=None, firmware=None, hw_video_ram=None): """ExtraSpecs object holds extra_specs for the instance.""" self.cpu_limits = cpu_limits or Limits() self.memory_limits = memory_limits or Limits() @@ -102,6 +102,7 @@ class ExtraSpecs(object): self.storage_policy = storage_policy self.cores_per_socket = cores_per_socket self.firmware = firmware + self.hw_video_ram = hw_video_ram def vm_refs_cache_reset(): @@ -257,6 +258,11 @@ def get_vm_create_spec(client_factory, instance, data_store_name, if serial_port_spec: devices.append(serial_port_spec) + virtual_device_config_spec = create_video_card_spec(client_factory, + extra_specs) + if virtual_device_config_spec: + devices.append(virtual_device_config_spec) + config_spec.deviceChange = devices # add vm-uuid and iface-id.x values for Neutron @@ -298,6 +304,18 @@ def get_vm_create_spec(client_factory, instance, data_store_name, return config_spec +def create_video_card_spec(client_factory, extra_specs): + if extra_specs.hw_video_ram: + video_card = client_factory.create('ns0:VirtualMachineVideoCard') + video_card.videoRamSizeInKB = extra_specs.hw_video_ram + video_card.key = -1 + virtual_device_config_spec = client_factory.create( + 'ns0:VirtualDeviceConfigSpec') + virtual_device_config_spec.operation = "add" + virtual_device_config_spec.device = video_card + return virtual_device_config_spec + + def create_serial_port_spec(client_factory): """Creates config spec for serial port.""" if not CONF.vmware.serial_port_service_uri: diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index 7f69dd0e8bc6..5cac6ba2a8d0 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -327,6 +327,16 @@ class VMwareVMOps(object): extra_specs.firmware = 'bios' hw_version = flavor.extra_specs.get('vmware:hw_version') extra_specs.hw_version = hw_version + + video_ram = image_meta.properties.get('hw_video_ram', 0) + max_vram = int(flavor.extra_specs.get('hw_video:ram_max_mb', 0)) + + if video_ram > max_vram: + raise exception.RequestedVRamTooHigh(req_vram=video_ram, + max_vram=max_vram) + if video_ram and max_vram: + extra_specs.hw_video_ram = video_ram * units.Mi / units.Ki + if CONF.vmware.pbm_enabled: storage_policy = flavor.extra_specs.get('vmware:storage_policy', CONF.vmware.pbm_default_policy) diff --git a/releasenotes/notes/vmware-add-max-ram-validation-f27f94d4a04aef3a.yaml b/releasenotes/notes/vmware-add-max-ram-validation-f27f94d4a04aef3a.yaml new file mode 100644 index 000000000000..c0fcd39b307c --- /dev/null +++ b/releasenotes/notes/vmware-add-max-ram-validation-f27f94d4a04aef3a.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + For the VMware vCenter driver, added support for the configured video ram ``hw_video_ram`` from the image, + which will be checked against the maximum allowed video ram ``hw_video:ram_max_mb`` + from the flavor. + If the selected video ram from the image is less than or equal to the maximum allowed ram, + the ``videoRamSizeInKB`` will be set. + If the selected ram is more than the maximum allowed one, then server creation will fail for the given + image and flavor. + If the maximum allowed video ram is not set in the flavor we do not set ``videoRamSizeInKB`` in the VM.