diff --git a/CHANGELOG.md b/CHANGELOG.md index b139613ae0..5997f95854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -296,6 +296,7 @@ Ansible Changes By Release - vmware * vcenter_license * vmware_guest_find + * vmware_guest_tools_wait - windows * win_defrag * win_domain_group diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest_tools_wait.py b/lib/ansible/modules/cloud/vmware/vmware_guest_tools_wait.py new file mode 100644 index 0000000000..96f6431e9d --- /dev/null +++ b/lib/ansible/modules/cloud/vmware/vmware_guest_tools_wait.py @@ -0,0 +1,194 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 Philippe Dellaert +# 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.0', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: vmware_guest_tools_wait +short_description: Wait for VMware tools to become available +description: + - Wait for VMware tools to become available on the VM and return facts. +version_added: '2.4' +author: + - Philippe Dellaert (@pdellaert) +notes: + - Tested on vSphere 6.5 +requirements: + - python >= 2.6 + - PyVmomi +options: + name: + description: + - Name of the VM for which to wait until the tools become available. + - This is required if uuid is not supplied. + name_match: + description: + - If multiple VMs match the name, use the first or last found. + default: 'first' + choices: ['first', 'last'] + folder: + description: + - Destination folder, absolute or relative path to find an existing guest. + - This is required if C(name) is supplied. + - The folder should include the datacenter. ESX's datacenter is C(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' + default: /vm + uuid: + description: + - UUID of the VM for which to wait until the tools become available, if known. This is VMware's unique identifier. + - This is required if C(name) is not supplied. +extends_documentation_fragment: vmware.documentation +''' + +EXAMPLES = ''' +- name: Wait for VMware tools to become available by UUID + vmware_guest_tools_wait: + hostname: 192.168.1.209 + username: administrator@vsphere.local + password: vmware + validate_certs: no + uuid: 421e4592-c069-924d-ce20-7e7533fab926 + delegate_to: localhost + register: facts + +- name: Wait for VMware tools to become available by name + vmware_guest_tools_wait: + hostname: 192.168.1.209 + username: administrator@vsphere.local + password: vmware + validate_certs: no + name: test-vm + folder: /datacenter1/vm + delegate_to: localhost + register: facts +''' + +RETURN = """ +instance: + description: metadata about the virtual machine + returned: always + type: dict + sample: None +""" + +import time + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible.module_utils.vmware import connect_to_api, gather_vm_facts, vmware_argument_spec, find_vm_by_id + +HAS_PYVMOMI = False +try: + import pyVmomi + from pyVmomi import vim + + HAS_PYVMOMI = True +except ImportError: + pass + + +class PyVmomiHelper(object): + def __init__(self, module): + if not HAS_PYVMOMI: + module.fail_json(msg='pyvmomi module required') + + self.module = module + self.params = module.params + self.content = connect_to_api(self.module) + + def getvm(self, name=None, uuid=None, folder=None): + vm = None + match_first = False + if uuid: + vm = find_vm_by_id(self.content, vm_id=uuid, vm_id_type="uuid") + elif folder and name: + if self.params['name_match'] == 'first': + match_first = True + vm = find_vm_by_id(self.content, vm_id=name, vm_id_type="inventory_path", folder=folder, match_first=match_first) + return vm + + def gather_facts(self, vm): + return gather_vm_facts(self.content, vm) + + def wait_for_tools(self, vm, poll=100, sleep=5): + tools_running = False + vm_facts = {} + poll_num = 0 + vm_uuid = vm.config.uuid + while not tools_running and poll_num <= poll: + newvm = self.getvm(uuid=vm_uuid) + vm_facts = self.gather_facts(newvm) + if vm_facts['guest_tools_status'] == 'guestToolsRunning': + tools_running = True + else: + time.sleep(sleep) + poll_num += 1 + + if not tools_running: + return {'failed': True, 'msg': 'VMware tools either not present or not running after {0} seconds'.format((poll * sleep))} + + changed = False + if poll_num > 0: + changed = True + return {'changed': changed, 'failed': False, 'instance': vm_facts} + + +def main(): + argument_spec = vmware_argument_spec() + argument_spec.update( + name=dict(type='str'), + name_match=dict(type='str', default='first'), + folder=dict(type='str', default='/vm'), + uuid=dict(type='str'), + ) + module = AnsibleModule( + argument_spec=argument_spec, + required_one_of=[['name', 'uuid']], + required_together=['name', 'folder'] + ) + + # FindByInventoryPath() does not require an absolute path + # so we should leave the input folder path unmodified + module.params['folder'] = module.params['folder'].rstrip('/') + + pyv = PyVmomiHelper(module) + # Check if the VM exists before continuing + vm = pyv.getvm(name=module.params['name'], + folder=module.params['folder'], + uuid=module.params['uuid']) + + if not vm: + vm_id = module.params.get('name') or module.params.get('uuid') + module.fail_json(msg="Unable to wait for tools for non-existing VM {0:s}".format(vm_id)) + + try: + result = pyv.wait_for_tools(vm) + except Exception as e: + module.fail_json(msg="Waiting for tools failed with exception: {0:s}".format(to_native(e))) + + if result['failed']: + module.fail_json(**result) + else: + module.exit_json(**result) + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/vmware_guest_tools_wait/aliases b/test/integration/targets/vmware_guest_tools_wait/aliases new file mode 100644 index 0000000000..1c56b8da49 --- /dev/null +++ b/test/integration/targets/vmware_guest_tools_wait/aliases @@ -0,0 +1,2 @@ +posix/ci/cloud/vcenter +cloud/vcenter diff --git a/test/integration/targets/vmware_guest_tools_wait/tasks/main.yml b/test/integration/targets/vmware_guest_tools_wait/tasks/main.yml new file mode 100644 index 0000000000..05b46c89e0 --- /dev/null +++ b/test/integration/targets/vmware_guest_tools_wait/tasks/main.yml @@ -0,0 +1,97 @@ +# Test code for the vmware_guest_tools_wait module. + +# Copyright (c) 2017 Philippe Dellaert +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: make sure pyvmomi is installed + pip: + name: pyvmomi + state: latest + +- name: store the vcenter container ip + set_fact: + vcsim: "{{ lookup('env', 'vcenter_host') }}" + +- debug: var=vcsim + +- 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 + uri: + url: "{{ 'http://' + vcsim + ':5000/spawn?datacenter=1&cluster=1&folder=0' }}" + register: vcsim_instance + +- name: Wait for vcsim server to come up online + wait_for: + host: "{{ vcsim }}" + port: 443 + state: started + +- name: get a list of virtual machines from vcsim + uri: + url: "{{ 'http://' + vcsim + ':5000/govc_find?filter=VM' }}" + register: vms + +- set_fact: vm1="{{ vms['json'][0] }}" + +- name: Power on VM1 + vmware_guest: + validate_certs: False + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance['json']['username'] }}" + password: "{{ vcsim_instance['json']['password'] }}" + name: "{{ vm1|basename }}" + datacenter: "{{ (vm1|basename).split('_')[0] }}" + state: poweredon + folder: "{{ vm1|dirname }}" + +# FixMe: govcsim does not support VMware tools status reporting +## Testcase 0001: Wait for VMware tools to become available by name +#- name: Waiting until VMware tools becomes available +# vmware_guest_tools_wait: +# validate_certs: False +# hostname: "{{ vcsim }}" +# username: "{{ vcsim_instance['json']['username'] }}" +# password: "{{ vcsim_instance['json']['password'] }}" +# name: "{{ vm1 | basename }}" +# folder: "{{ vm1 | dirname }}" +# register: guest_facts_0001 +# +#- debug: msg="{{ guest_facts_0001 }}" +# +#- assert: +# that: +# - "guest_facts_0001['instance']['hw_name'] == vm1 | basename" +# - "guest_facts_0001['instance']['hw_product_uuid'] is defined" +# - "guest_facts_0001['instance']['guest_tools_status'] == 'guestToolsRunning'" +# +#- set_fact: vm1_uuid="{{ guest_facts_0001['instance']['hw_product_uuid'] }}" +# +#- debug: var=vm1_uuid +# +# Testcase 0002: Wait for VMware tools to become available by UUID +#- name: Waiting until VMware tools becomes available +# vmware_guest_tools_wait: +# validate_certs: False +# hostname: "{{ vcsim }}" +# username: "{{ vcsim_instance['json']['username'] }}" +# password: "{{ vcsim_instance['json']['password'] }}" +# uuid: "{{ vm1_uuid }}" +# register: guest_facts_0002 +# +#- debug: msg="{{ guest_facts_0002 }}" +# +#- assert: +# that: +# - "guest_facts_0002['instance']['hw_name'] == vm1 | basename" +# - "guest_facts_0002['instance']['hw_product_uuid'] is defined" +# - "guest_facts_0002['instance']['hw_product_uuid'] == vm1_uuid" +# - "guest_facts_0002['instance']['guest_tools_status'] == 'guestToolsRunning'"