From 955e960f45bb1e0876a3b9eabaaa367f4ac70ad2 Mon Sep 17 00:00:00 2001 From: Diane Wang <41371902+Tomorrow9@users.noreply.github.com> Date: Fri, 11 Jan 2019 22:07:14 -0800 Subject: [PATCH] VMware: add new module vmware_guest_video (#50590) --- .../cloud/vmware/vmware_guest_video.py | 326 ++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 lib/ansible/modules/cloud/vmware/vmware_guest_video.py diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest_video.py b/lib/ansible/modules/cloud/vmware/vmware_guest_video.py new file mode 100644 index 00000000000..5a1a602aff6 --- /dev/null +++ b/lib/ansible/modules/cloud/vmware/vmware_guest_video.py @@ -0,0 +1,326 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2018, Ansible Project +# Copyright: (c) 2018, Diane Wang +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community' +} + +DOCUMENTATION = ''' +--- +module: vmware_guest_video +short_description: Modify video card configurations of specified virtual machine in given vCenter infrastructure +description: + - This module is used to reconfigure video card settings of given virtual machine. + - All parameters and VMware object names are case sensitive. +version_added: '2.8' +author: + - Diane Wang (@Tomorrow9) +notes: + - Tested on vSphere 6.0, 6.5 and 6.7 +requirements: + - "python >= 2.6" + - PyVmomi +options: + name: + description: + - Name of the virtual machine. + - This is a required parameter, if parameter C(uuid) is not supplied. + uuid: + description: + - UUID of the instance to gather facts if known, this is VMware's unique identifier. + - This is a required parameter, if parameter C(name) is not supplied. + folder: + description: + - Destination folder, absolute or relative path to find an existing guest. + - This is a required parameter, only if multiple VMs are found with same name. + - The folder should include the datacenter. ESXi server's datacenter is ha-datacenter. + - 'Examples:' + - ' folder: /ha-datacenter/vm' + - ' folder: ha-datacenter/vm' + - ' folder: /datacenter1/vm' + - ' folder: datacenter1/vm' + - ' folder: /datacenter1/vm/folder1' + - ' folder: datacenter1/vm/folder1' + - ' folder: /folder1/datacenter1/vm' + - ' folder: folder1/datacenter1/vm' + - ' folder: /folder1/datacenter1/vm/folder2' + datacenter: + default: ha-datacenter + description: + - The datacenter name to which virtual machine belongs to. + - This parameter is case sensitive. + gather_video_facts: + description: + - If set to True, return settings of the video card, other attributes are ignored. + - If set to False, will do reconfiguration and return video card settings. + type: bool + default: 'no' + use_auto_detect: + description: + - 'If set to True, applies common video settings to the guest operating system, attributes C(display_number) and C(video_memory_mb) are ignored.' + - 'If set to False, the number of display and the total video memory will be reconfigured using C(display_number) and C(video_memory_mb).' + type: bool + display_number: + description: + - The number of display. Valid value from 1 to 10. The maximum display number is 4 on vCenter 6.0, 6.5 web UI. + video_memory_mb: + description: + - 'Valid total MB of video memory range of virtual machine is from 1.172 MB to 256 MB on ESXi 6.7U1, + from 1.172 MB to 128 MB on ESXi 6.7 and previous versions.' + - For specific guest OS, supported minimum and maximum video memory are different, please be careful on setting this. + enable_3D: + description: + - Enable 3D for guest operating systems on which VMware supports 3D. + type: bool + renderer_3D: + description: + - 'If set to C(automatic), selects the appropriate option (software or hardware) for this virtual machine automatically.' + - 'If set to C(software), uses normal CPU processing for 3D calculations.' + - 'If set to C(hardware), requires graphics hardware (GPU) for faster 3D calculations.' + choices: [ automatic, software, hardware ] + memory_3D_mb: + description: + - The value of 3D Memory must be power of 2 and valid value is from 32 MB to 2048 MB. +extends_documentation_fragment: vmware.documentation +''' + +EXAMPLES = ''' +- name: Change video card settings of virtual machine + vmware_guest_video: + hostname: "{{ vcenter_hostname }}" + username: "{{ vcenter_username }}" + password: "{{ vcenter_password }}" + datacenter: "{{ datacenter_name }}" + validate_certs: no + name: test-vm + gather_video_facts: false + use_auto_detect: false + display_number: 2 + video_memory_mb: 8.0 + enable_3D: true + renderer_3D: automatic + memory_3D_mb: 512 + delegate_to: localhost + register: video_facts +''' + +RETURN = """ +video_status: + description: metadata about the virtual machine's video card after managing them + returned: always + type: dict + sample: { + "auto_detect": false, + "display_number": 2, + "enable_3D_support": true, + "memory_3D": 524288, + "renderer_3D": "automatic", + "video_memory": 8192 + } +""" + +try: + from pyVmomi import vim +except ImportError: + pass + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible.module_utils.vmware import PyVmomi, vmware_argument_spec, wait_for_task + + +class PyVmomiHelper(PyVmomi): + def __init__(self, module): + super(PyVmomiHelper, self).__init__(module) + self.change_detected = False + self.config_spec = vim.vm.ConfigSpec() + self.config_spec.deviceChange = [] + self.video_card_facts = None + + @staticmethod + def is_power_of_2(num): + return num != 0 and ((num & (num - 1)) == 0) + + def gather_video_card_facts(self, vm_obj): + """ + Gather facts about VM's video card settings + Args: + vm_obj: Managed object of virtual machine + Returns: Video Card device and a list of dict video card configuration + """ + video_facts = dict() + video_card = None + for device in vm_obj.config.hardware.device: + if isinstance(device, vim.vm.device.VirtualVideoCard): + video_card = device + video_facts = dict( + auto_detect=device.useAutoDetect, + display_number=device.numDisplays, + video_memory=device.videoRamSizeInKB, + enable_3D_support=device.enable3DSupport, + renderer_3D=device.use3dRenderer, + memory_3D=device.graphicsMemorySizeInKB, + ) + break + return video_card, video_facts + + def get_video_card_spec(self, vm_obj): + """ + Get device changes of virtual machine + Args: + vm_obj: Managed object of virtual machine + Returns: virtual device spec + """ + video_card, video_card_facts = self.gather_video_card_facts(vm_obj) + self.video_card_facts = video_card_facts + if video_card is None: + self.module.fail_json(msg='Not get video card device of specified virtual machine.') + video_spec = vim.vm.device.VirtualDeviceSpec() + video_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit + video_spec.device = video_card + auto_detect = False + enabled_3d = False + + if self.params['gather_video_facts']: + return None + if self.params['use_auto_detect'] is not None: + if video_card_facts['auto_detect'] and self.params['use_auto_detect']: + auto_detect = True + elif not video_card_facts['auto_detect'] and self.params['use_auto_detect']: + video_spec.device.useAutoDetect = True + self.change_detected = True + auto_detect = True + elif video_card_facts['auto_detect'] and not self.params['use_auto_detect']: + video_spec.device.useAutoDetect = False + self.change_detected = True + else: + if video_card_facts['auto_detect']: + auto_detect = True + # useAutoDetect set to False then display number and video memory config can be changed + if not auto_detect: + if self.params['display_number'] is not None: + if self.params['display_number'] < 1: + self.module.fail_json(msg="display_number attribute valid value: 1-10.") + if self.params['display_number'] != video_card_facts['display_number']: + video_spec.device.numDisplays = self.params['display_number'] + self.change_detected = True + + if self.params['video_memory_mb'] is not None: + if self.params['video_memory_mb'] < 1.172: + self.module.fail_json(msg="video_memory_mb attribute valid value: ESXi 6.7U1(1.172-256 MB)," + "ESXi 6.7/6.5/6.0(1.172-128 MB).") + if int(self.params['video_memory_mb'] * 1024) != video_card_facts['video_memory']: + video_spec.device.videoRamSizeInKB = int(self.params['video_memory_mb'] * 1024) + self.change_detected = True + else: + if self.params['display_number'] is not None or self.params['video_memory_mb'] is not None: + self.module.fail_json(msg="display_number and video_memory_mb can not be changed if use_auto_detect is true.") + # useAutoDetect value not control 3D config + if self.params['enable_3D'] is not None: + if self.params['enable_3D'] != video_card_facts['enable_3D_support']: + video_spec.device.enable3DSupport = self.params['enable_3D'] + self.change_detected = True + if self.params['enable_3D']: + enabled_3d = True + else: + if video_card_facts['enable_3D_support']: + enabled_3d = True + else: + if video_card_facts['enable_3D_support']: + enabled_3d = True + # 3D is enabled then 3D memory and renderer method can be set + if enabled_3d: + if self.params['renderer_3D'] is not None: + renderer = self.params['renderer_3D'].lower() + if renderer not in ['automatic', 'software', 'hardware']: + self.module.fail_json(msg="renderer_3D attribute valid value: automatic, software, hardware.") + if renderer != video_card_facts['renderer_3D']: + video_spec.device.use3dRenderer = renderer + self.change_detected = True + if self.params['memory_3D_mb'] is not None: + memory_3d = self.params['memory_3D_mb'] + if not self.is_power_of_2(memory_3d): + self.module.fail_json(msg="memory_3D_mb attribute should be an integer value and power of 2(32-2048).") + else: + if memory_3d < 32 or memory_3d > 2048: + self.module.fail_json(msg="memory_3D_mb attribute should be an integer value and power of 2(32-2048).") + if memory_3d * 1024 != video_card_facts['memory_3D']: + video_spec.device.graphicsMemorySizeInKB = memory_3d * 1024 + self.change_detected = True + else: + if self.params['renderer_3D'] is not None or self.params['memory_3D_mb'] is not None: + self.module.fail_json(msg='3D renderer or 3D memory can not be configured if 3D is not enabled.') + if not self.change_detected: + return None + return video_spec + + def reconfigure_vm_video(self, vm_obj): + """ + Reconfigure video card settings of virtual machine + Args: + vm_obj: Managed object of virtual machine + Returns: Reconfigure results + """ + video_card_spec = self.get_video_card_spec(vm_obj) + if video_card_spec is None: + return {'changed': False, 'failed': False, 'instance': self.video_card_facts} + self.config_spec.deviceChange.append(video_card_spec) + try: + task = vm_obj.ReconfigVM_Task(spec=self.config_spec) + wait_for_task(task) + except vim.fault.InvalidDeviceSpec as invalid_device_spec: + self.module.fail_json(msg="Failed to configure video card on given virtual machine due to invalid" + " device spec : %s" % (to_native(invalid_device_spec.msg)), + details="Please check ESXi server logs for more details.") + except vim.fault.RestrictedVersion as e: + self.module.fail_json(msg="Failed to reconfigure virtual machine due to" + " product versioning restrictions: %s" % to_native(e.msg)) + if task.info.state == 'error': + return {'changed': self.change_detected, 'failed': True, 'msg': task.info.error.msg} + video_card_facts = self.gather_video_card_facts(vm_obj)[1] + return {'changed': self.change_detected, 'failed': False, 'instance': video_card_facts} + + +def main(): + argument_spec = vmware_argument_spec() + argument_spec.update( + name=dict(type='str'), + uuid=dict(type='str'), + folder=dict(type='str'), + datacenter=dict(type='str', default='ha-datacenter'), + gather_video_facts=dict(type='bool', default=False), + use_auto_detect=dict(type='bool'), + display_number=dict(type='int'), + video_memory_mb=dict(type='float'), + enable_3D=dict(type='bool'), + renderer_3D=dict(type='str', choices=['automatic', 'software', 'hardware']), + memory_3D_mb=dict(type='int'), + ) + module = AnsibleModule(argument_spec=argument_spec, + required_one_of=[['name', 'uuid']]) + pyv = PyVmomiHelper(module) + vm = pyv.get_vm() + if not vm: + module.fail_json(msg='Unable to find the specified virtual machine : %s' % (module.params.get('uuid') or module.params.get('name'))) + + vm_facts = pyv.gather_facts(vm) + vm_power_state = vm_facts['hw_power_status'].lower() + if vm_power_state != 'poweredoff': + module.fail_json(msg='VM state should be poweredoff to reconfigure video card settings.') + result = pyv.reconfigure_vm_video(vm_obj=vm) + if result['failed']: + module.fail_json(**result) + else: + module.exit_json(**result) + + +if __name__ == '__main__': + main()