diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest.py b/lib/ansible/modules/cloud/vmware/vmware_guest.py index aebc34a4622..3879db6d85e 100644 --- a/lib/ansible/modules/cloud/vmware/vmware_guest.py +++ b/lib/ansible/modules/cloud/vmware/vmware_guest.py @@ -844,6 +844,7 @@ class PyVmomiHelper(PyVmomi): super(PyVmomiHelper, self).__init__(module) self.device_helper = PyVmomiDeviceHelper(self.module) self.configspec = None + self.relospec = None self.change_detected = False # a change was detected and needs to be applied through reconfiguration self.change_applied = False # a change was applied meaning at least one task succeeded self.customspec = None @@ -1397,6 +1398,7 @@ class PyVmomiHelper(PyVmomi): nic.device.backing.opaqueNetworkType = 'nsx.LogicalSwitch' nic.device.backing.opaqueNetworkId = network_id nic.device.deviceInfo.summary = 'nsx.LogicalSwitch: %s' % network_id + nic_change_detected = True else: # vSwitch if not isinstance(nic.device.backing, vim.vm.device.VirtualEthernetCard.NetworkBackingInfo): @@ -1413,7 +1415,13 @@ class PyVmomiHelper(PyVmomi): nic_change_detected = True if nic_change_detected: - self.configspec.deviceChange.append(nic) + # Change to fix the issue found while configuring opaque network + # VMs cloned from a template with opaque network will get disconnected + # Replacing deprecated config parameter with relocation Spec + if isinstance(self.cache.get_network(network_name), vim.OpaqueNetwork): + self.relospec.deviceChange.append(nic) + else: + self.configspec.deviceChange.append(nic) self.change_detected = True def configure_vapp_properties(self, vm_obj): @@ -2143,6 +2151,9 @@ class PyVmomiHelper(PyVmomi): self.configspec = vim.vm.ConfigSpec() self.configspec.deviceChange = [] + # create the relocation spec + self.relospec = vim.vm.RelocateSpec() + self.relospec.deviceChange = [] self.configure_guestid(vm_obj=vm_obj, vm_creation=True) self.configure_cpu_and_memory(vm_obj=vm_obj, vm_creation=True) self.configure_hardware_params(vm_obj=vm_obj) @@ -2167,13 +2178,10 @@ class PyVmomiHelper(PyVmomi): clone_method = None try: if self.params['template']: - # create the relocation spec - relospec = vim.vm.RelocateSpec() - # Only select specific host when ESXi hostname is provided if self.params['esxi_hostname']: - relospec.host = self.select_host() - relospec.datastore = datastore + self.relospec.host = self.select_host() + self.relospec.datastore = datastore # Convert disk present in template if is set if self.params['convert']: @@ -2189,22 +2197,22 @@ class PyVmomiHelper(PyVmomi): disk_locator.diskBackingInfo.diskMode = "persistent" disk_locator.diskId = device.key disk_locator.datastore = datastore - relospec.disk.append(disk_locator) + self.relospec.disk.append(disk_locator) # https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.vm.RelocateSpec.html # > pool: For a clone operation from a template to a virtual machine, this argument is required. - relospec.pool = resource_pool + self.relospec.pool = resource_pool linked_clone = self.params.get('linked_clone') snapshot_src = self.params.get('snapshot_src', None) if linked_clone: if snapshot_src is not None: - relospec.diskMoveType = vim.vm.RelocateSpec.DiskMoveOptions.createNewChildDiskBacking + self.relospec.diskMoveType = vim.vm.RelocateSpec.DiskMoveOptions.createNewChildDiskBacking else: self.module.fail_json(msg="Parameter 'linked_src' and 'snapshot_src' are" " required together for linked clone operation.") - clonespec = vim.vm.CloneSpec(template=self.params['is_template'], location=relospec) + clonespec = vim.vm.CloneSpec(template=self.params['is_template'], location=self.relospec) if self.customspec: clonespec.customization = self.customspec @@ -2314,7 +2322,9 @@ class PyVmomiHelper(PyVmomi): def reconfigure_vm(self): self.configspec = vim.vm.ConfigSpec() self.configspec.deviceChange = [] - + # create the relocation spec + self.relospec = vim.vm.RelocateSpec() + self.relospec.deviceChange = [] self.configure_guestid(vm_obj=self.current_vm_obj) self.configure_cpu_and_memory(vm_obj=self.current_vm_obj) self.configure_hardware_params(vm_obj=self.current_vm_obj) @@ -2329,12 +2339,11 @@ class PyVmomiHelper(PyVmomi): self.configspec.annotation = str(self.params['annotation']) self.change_detected = True - relospec = vim.vm.RelocateSpec() if self.params['resource_pool']: - relospec.pool = self.get_resource_pool() + self.relospec.pool = self.get_resource_pool() - if relospec.pool != self.current_vm_obj.resourcePool: - task = self.current_vm_obj.RelocateVM_Task(spec=relospec) + if self.relospec.pool != self.current_vm_obj.resourcePool: + task = self.current_vm_obj.RelocateVM_Task(spec=self.relospec) self.wait_for_task(task) if task.info.state == 'error': return {'changed': self.change_applied, 'failed': True, 'msg': task.info.error.msg, 'op': 'relocate'} diff --git a/test/integration/targets/vmware_guest/tasks/clone_customize_guest_test.yml b/test/integration/targets/vmware_guest/tasks/clone_customize_guest_test.yml new file mode 100644 index 00000000000..3fcc1a767de --- /dev/null +++ b/test/integration/targets/vmware_guest/tasks/clone_customize_guest_test.yml @@ -0,0 +1,81 @@ +# Test code for the vmware_guest module. +# Copyright: (c) 2019, Pavan Bidkar +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Wait for Flask controller to come up online + wait_for: + host: "{{ vcsim }}" + port: 5000 + state: started + +- name: kill vcsim + uri: + url: http://{{ vcsim }}:5000/killall + +- name: start vcsim with no folders + uri: + url: http://{{ vcsim }}:5000/spawn?datacenter=1&cluster=1&folder=0 + register: vcsim_instance + +- name: Wait for Flask controller to come up online + wait_for: + host: "{{ vcsim }}" + port: 443 + state: started + +- name: get a list of VMS from vcsim + uri: + url: http://{{ vcsim }}:5000/govc_find?filter=VM + register: vmlist + +- debug: + var: vcsim_instance + +- debug: + var: vmlist + +- name: clone vm from template and customize GOS + vmware_guest: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + name: "{{ 'net_customize_' + item|basename }}" + template: "{{ item|basename }}" + datacenter: "{{ (item|basename).split('_')[0] }}" + state: poweredoff + folder: "{{ item|dirname }}" + convert: thin + with_items: "{{ vmlist['json'] }}" + register: clone_customize + +- debug: + var: clone_customize + +- name: assert that changes were made + assert: + that: + - "clone_customize.results|map(attribute='changed')|unique|list == [true]" + +- name: clone vm from template and customize GOS again + vmware_guest: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + name: "{{ 'net_customize_' + item|basename }}" + template: "{{ item|basename }}" + datacenter: "{{ (item|basename).split('_')[0] }}" + state: poweredoff + folder: "{{ item|dirname }}" + convert: thin + with_items: "{{ vmlist['json'] }}" + register: clone_customize_again + +- debug: + var: clone_customize_again + +- name: assert that changes were not made + assert: + that: + - "clone_customize_again.results|map(attribute='changed')|unique|list == [false]" diff --git a/test/integration/targets/vmware_guest/tasks/main.yml b/test/integration/targets/vmware_guest/tasks/main.yml index 451233b156b..7b05c1bf7d4 100644 --- a/test/integration/targets/vmware_guest/tasks/main.yml +++ b/test/integration/targets/vmware_guest/tasks/main.yml @@ -28,3 +28,4 @@ - include: linked_clone_d1_c1_f0.yml - include: boot_firmware_d1_c1_f0.yml - include: clone_with_convert.yml +- include: clone_customize_guest_test.yml