From 60e37c54cc65d82aa61387a9eaa7a6a4620a37b3 Mon Sep 17 00:00:00 2001 From: Diane Wang <41371902+Tomorrow9@users.noreply.github.com> Date: Sun, 3 Feb 2019 21:08:35 -0800 Subject: [PATCH] VMware: add support for customize existing VM directly (#51215) * add support for customize existing VM * modify space issue * move customize existing vm after reconfigure * delete one debug line --- .../modules/cloud/vmware/vmware_guest.py | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest.py b/lib/ansible/modules/cloud/vmware/vmware_guest.py index 2c1a78ab1d..ebb26f6d2b 100644 --- a/lib/ansible/modules/cloud/vmware/vmware_guest.py +++ b/lib/ansible/modules/cloud/vmware/vmware_guest.py @@ -297,13 +297,15 @@ options: version_added: '2.3' customization: description: - - Parameters for OS customization when cloning from the template or the virtual machine. + - Parameters for OS customization when cloning from the template or the virtual machine, or apply to the existing virtual machine directly. - Not all operating systems are supported for customization with respective vCenter version, please check VMware documentation for respective OS customization. - For supported customization operating system matrix, (see U(http://partnerweb.vmware.com/programs/guestOS/guest-os-customization-matrix.pdf)) - All parameters and VMware object names are case sensitive. - Linux based OSes requires Perl package to be installed for OS customizations. - 'Common parameters (Linux/Windows):' + - ' - C(existing_vm) (bool): If set to C(True), do OS customization on the specified virtual machine directly. + If set to C(False) or not specified, do OS customization when cloning from the template or the virtual machine. version_added: 2.8' - ' - C(dns_servers) (list): List of DNS servers to configure.' - ' - C(dns_suffix) (list): List of domain suffixes, also known as DNS search path (default: C(domain) parameter).' - ' - C(domain) (string): DNS domain name to use.' @@ -563,6 +565,7 @@ instance: import re import time +import string HAS_PYVMOMI = False try: @@ -1584,7 +1587,9 @@ class PyVmomiHelper(PyVmomi): # Setting hostName, orgName and fullName is mandatory, so we set some default when missing ident.userData.computerName = vim.vm.customization.FixedName() - ident.userData.computerName.name = str(self.params['customization'].get('hostname', self.params['name'].split('.')[0])) + # computer name will be truncated to 15 characters if using VM name + default_name = self.params['name'].translate(None, string.punctuation) + ident.userData.computerName.name = str(self.params['customization'].get('hostname', default_name[0:15])) ident.userData.fullName = str(self.params['customization'].get('fullname', 'Administrator')) ident.userData.orgName = str(self.params['customization'].get('orgname', 'ACME')) @@ -2146,7 +2151,7 @@ class PyVmomiHelper(PyVmomi): network_changes = True break - if len(self.params['customization']) > 0 or network_changes or self.params.get('customization_spec'): + if len(self.params['customization']) > 0 or network_changes or self.params.get('customization_spec') is not None: self.customize_vm(vm_obj=vm_obj) clonespec = None @@ -2394,9 +2399,51 @@ class PyVmomiHelper(PyVmomi): self.change_detected = True + # add customize existing VM after VM re-configure + if 'existing_vm' in self.params['customization'] and self.params['customization']['existing_vm']: + if self.current_vm_obj.config.template: + self.module.fail_json(msg="VM is template, not support guest OS customization.") + if self.current_vm_obj.runtime.powerState != vim.VirtualMachinePowerState.poweredOff: + self.module.fail_json(msg="VM is not in poweroff state, can not do guest OS customization.") + cus_result = self.customize_exist_vm() + if cus_result['failed']: + return cus_result + vm_facts = self.gather_facts(self.current_vm_obj) return {'changed': self.change_applied, 'failed': False, 'instance': vm_facts} + def customize_exist_vm(self): + task = None + # Find if we need network customizations (find keys in dictionary that requires customizations) + network_changes = False + for nw in self.params['networks']: + for key in nw: + # We don't need customizations for these keys + if key not in ('device_type', 'mac', 'name', 'vlan', 'type', 'start_connected'): + network_changes = True + break + if len(self.params['customization']) > 1 or network_changes or self.params.get('customization_spec'): + self.customize_vm(vm_obj=self.current_vm_obj) + try: + task = self.current_vm_obj.CustomizeVM_Task(self.customspec) + except vim.fault.CustomizationFault as e: + self.module.fail_json(msg="Failed to customization virtual machine due to CustomizationFault: %s" % to_native(e.msg)) + except vim.fault.RuntimeFault as e: + self.module.fail_json(msg="failed to customization virtual machine due to RuntimeFault: %s" % to_native(e.msg)) + except Exception as e: + self.module.fail_json(msg="failed to customization virtual machine due to fault: %s" % to_native(e.msg)) + self.wait_for_task(task) + if task.info.state == 'error': + return {'changed': self.change_applied, 'failed': True, 'msg': task.info.error.msg, 'op': 'customize_exist'} + + if self.params['wait_for_customization']: + set_vm_power_state(self.content, self.current_vm_obj, 'poweredon', force=False) + is_customization_ok = self.wait_for_customization(self.current_vm_obj) + if not is_customization_ok: + return {'changed': self.change_applied, 'failed': True, 'op': 'wait_for_customize_exist'} + + return {'changed': self.change_applied, 'failed': False} + def wait_for_task(self, task, poll_interval=1): """ Wait for a VMware task to complete. Terminal states are 'error' and 'success'. @@ -2430,9 +2477,8 @@ class PyVmomiHelper(PyVmomi): return facts - def get_vm_events(self, eventTypeIdList): - newvm = self.get_vm() - byEntity = vim.event.EventFilterSpec.ByEntity(entity=newvm, recursion="self") + def get_vm_events(self, vm, eventTypeIdList): + byEntity = vim.event.EventFilterSpec.ByEntity(entity=vm, recursion="self") filterSpec = vim.event.EventFilterSpec(entity=byEntity, eventTypeId=eventTypeIdList) eventManager = self.content.eventManager return eventManager.QueryEvent(filterSpec) @@ -2440,11 +2486,11 @@ class PyVmomiHelper(PyVmomi): def wait_for_customization(self, vm, poll=10000, sleep=10): thispoll = 0 while thispoll <= poll: - eventStarted = self.get_vm_events(['CustomizationStartedEvent']) + eventStarted = self.get_vm_events(vm, ['CustomizationStartedEvent']) if len(eventStarted): thispoll = 0 while thispoll <= poll: - eventsFinishedResult = self.get_vm_events(['CustomizationSucceeded', 'CustomizationFailed']) + eventsFinishedResult = self.get_vm_events(vm, ['CustomizationSucceeded', 'CustomizationFailed']) if len(eventsFinishedResult): if not isinstance(eventsFinishedResult[0], vim.event.CustomizationSucceeded): self.module.fail_json(msg='Customization failed with error {0}:\n{1}'.format(