From 29bed12cdddd1ff693014aad7ee40b9a263e6e0d Mon Sep 17 00:00:00 2001 From: tchernomax Date: Wed, 13 Dec 2017 17:13:12 +0100 Subject: [PATCH] vmware_host: add reconnect and add_or_reconnect states (#30582) * vmware_host: add reconnect and add_or_reconnect states Add "reconnect" and "add_or_reconnect" choices for "state". * reconnect: reconnect an esxi to a vcenter (imply it is present). * add_or_reconnect: do the same but add the esxi if absent. Also: * tag the cluster_name as required (because it is). * tag esxi_username and esxi_password as not required because they aren't when the esxi isn't added. * vmware_host: add + prepare/document integration tests vmware_host module Add integration test for the add part of "add_or_reconnect" state. Prepare and document integration tests for the reconnect part of "add_or_reconnect" state and "reconnect" and "absent" states. Currently we can't test those states as ReconnectHost_Task (for "reconnect") and EnterMaintenanceMode_Task (for "absent") aren't implemented yet in vcsim (from vmware/govmomi) --- .../modules/cloud/vmware/vmware_host.py | 110 ++++++++++++++-- .../targets/vmware_host/tasks/main.yml | 122 ++++++++++++++++-- 2 files changed, 208 insertions(+), 24 deletions(-) diff --git a/lib/ansible/modules/cloud/vmware/vmware_host.py b/lib/ansible/modules/cloud/vmware/vmware_host.py index ff7d3d8894a..285ad1505a4 100644 --- a/lib/ansible/modules/cloud/vmware/vmware_host.py +++ b/lib/ansible/modules/cloud/vmware/vmware_host.py @@ -17,11 +17,12 @@ DOCUMENTATION = r''' module: vmware_host short_description: Add/remove ESXi host to/from vCenter description: -- This module can be used to add/remove an ESXi host to/from vCenter. +- This module can be used to add/remove/reconnect an ESXi host to/from vCenter. version_added: '2.0' author: - Joseph Callen (@jcpowermac) - Russell Teague (@mtnbikenc) +- Maxime de Roucy (@tchernomax) notes: - Tested on vSphere 5.5 requirements: @@ -35,6 +36,7 @@ options: cluster_name: description: - Name of the cluster to add the host. + - Required from version 2.5. required: yes esxi_hostname: description: @@ -43,16 +45,29 @@ options: esxi_username: description: - ESXi username. - required: yes + - Required for adding a host. + - Optional for reconnect. + - Unused for removing. + - No longer required from version 2.5. esxi_password: description: - ESXi password. - required: yes + - Required for adding a host. + - Optional for reconnect. + - Unused for removing. + - No longer required from version 2.5. state: description: - - Add or remove the host. - choices: [absent, present] + - "present: add the host if it's absent else do nothing." + - "absent: remove the host if it's present else do nothing." + - "add_or_reconnect: add the host if it's absent else reconnect it." + - "reconnect: reconnect the host if it's present else fail." default: present + choices: + - present + - absent + - add_or_reconnect + - reconnect extends_documentation_fragment: vmware.documentation ''' @@ -69,6 +84,30 @@ EXAMPLES = r''' esxi_password: '{{ esxi_password }}' state: present delegate_to: localhost + +- name: Reconnect ESXi Host (with username/password set) + vmware_host: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + datacenter_name: datacenter_name + cluster_name: cluster_name + esxi_hostname: '{{ esxi_hostname }}' + esxi_username: '{{ esxi_username }}' + esxi_password: '{{ esxi_password }}' + state: reconnect + delegate_to: localhost + +- name: Reconnect ESXi Host (with default username/password) + vmware_host: + hostname: '{{ vcenter_hostname }}' + username: '{{ vcenter_username }}' + password: '{{ vcenter_password }}' + datacenter_name: datacenter_name + cluster_name: cluster_name + esxi_hostname: '{{ esxi_hostname }}' + state: reconnect + delegate_to: localhost ''' RETURN = r''' @@ -99,7 +138,6 @@ class VMwareHost(object): self.esxi_username = module.params['esxi_username'] self.esxi_password = module.params['esxi_password'] self.state = module.params['state'] - self.dc = None self.cluster = None self.host = None self.content = connect_to_api(module) @@ -115,6 +153,13 @@ class VMwareHost(object): 'present': { 'present': self.state_exit_unchanged, 'absent': self.state_add_host, + }, + 'add_or_reconnect': { + 'present': self.state_reconnect_host, + 'absent': self.state_add_host, + }, + 'reconnect': { + 'present': self.state_reconnect_host, } } @@ -128,6 +173,10 @@ class VMwareHost(object): self.module.fail_json(msg=str(e)) def add_host_to_vcenter(self): + + if self.esxi_username is None or self.esxi_password is None: + self.module.fail_json(msg='esxi_username and esxi_password are required to add a host') + host_connect_spec = vim.host.ConnectSpec() host_connect_spec.hostName = self.esxi_hostname host_connect_spec.userName = self.esxi_username @@ -156,6 +205,32 @@ class VMwareHost(object): success, result = wait_for_task(task) return success, result + def reconnect_host_to_vcenter(self): + reconnecthost_args = {} + reconnecthost_args['reconnectSpec'] = vim.HostSystem.ReconnectSpec() + reconnecthost_args['reconnectSpec'].syncState = True + + if self.esxi_username is not None or self.esxi_password is not None: + reconnecthost_args['cnxSpec'] = vim.host.ConnectSpec() + reconnecthost_args['cnxSpec'].hostName = self.esxi_hostname + reconnecthost_args['cnxSpec'].userName = self.esxi_username + reconnecthost_args['cnxSpec'].password = self.esxi_password + reconnecthost_args['cnxSpec'].force = True + reconnecthost_args['cnxSpec'].sslThumbprint = "" + + try: + task = self.host.ReconnectHost_Task(**reconnecthost_args) + success, result = wait_for_task(task) + return success, result + except TaskError as add_task_error: + # See add_host_to_vcenter + ssl_verify_fault = add_task_error.args[0] + reconnecthost_args['cnxSpec'].sslThumbprint = ssl_verify_fault.thumbprint + + task = self.host.ReconnectHost_Task(**reconnecthost_args) + success, result = wait_for_task(task) + return success, result + def state_exit_unchanged(self): self.module.exit_json(changed=False) @@ -185,6 +260,14 @@ class VMwareHost(object): changed, result = self.add_host_to_vcenter() self.module.exit_json(changed=changed, result=str(result)) + def state_reconnect_host(self): + changed = True + result = None + + if not self.module.check_mode: + changed, result = self.reconnect_host_to_vcenter() + self.module.exit_json(changed=changed, result=str(result)) + def check_host_state(self): self.host, self.cluster = find_host_by_cluster_datacenter(self.module, self.content, self.datacenter_name, self.cluster_name, self.esxi_hostname) @@ -199,17 +282,20 @@ def main(): argument_spec = vmware_argument_spec() argument_spec.update( datacenter_name=dict(type='str', required=True), - cluster_name=dict(type='str'), + cluster_name=dict(type='str', required=True), esxi_hostname=dict(type='str', required=True), - esxi_username=dict(type='str', required=True), - esxi_password=dict(type='str', required=True, no_log=True), - state=dict(type='str', default='present', choices=['absent', 'present']) - ) + esxi_username=dict(type='str', required=False), + esxi_password=dict(type='str', required=False, no_log=True), + state=dict(default='present', + choices=['present', 'absent', 'add_or_reconnect', 'reconnect'], + type='str')) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, - ) + required_if=[ + ['state', 'present', ['esxi_username', 'esxi_password']], + ['state', 'add_or_reconnect', ['esxi_username', 'esxi_password']]]) if not HAS_PYVMOMI: module.fail_json(msg='pyvmomi is required for this module') diff --git a/test/integration/targets/vmware_host/tasks/main.yml b/test/integration/targets/vmware_host/tasks/main.yml index 89ac08b4e9f..714f6deb805 100644 --- a/test/integration/targets/vmware_host/tasks/main.yml +++ b/test/integration/targets/vmware_host/tasks/main.yml @@ -49,7 +49,6 @@ - debug: var=dc1 - - name: get a list of Cluster from vcsim uri: url: http://{{ vcsim }}:5000/govc_find?filter=CCR @@ -61,7 +60,7 @@ - debug: var=ccr1 -# Testcase 0001: Add Host +# Testcase: Add Host - name: add host vmware_host: hostname: "{{ vcsim }}" @@ -74,23 +73,20 @@ datacenter_name: "{{ dc1 }}" cluster_name: "{{ ccr1 }}" state: present - register: host_system_result_0001 + register: add_host_result - name: get a list of host system from vcsim after adding host system uri: url: http://{{ vcsim }}:5000/govc_find?filter=H - register: new_host_list - -- set_fact: - new_host: "{% for host in new_host_list.json %} {{ True if (host | basename) == 'test_host_system_0001' else False }} {% endfor %}" + register: host_list - name: ensure host system is present assert: that: - - host_system_result_0001.changed == true - - "'True' in new_host" + - add_host_result | changed + - "{% for host in host_list.json if ((host | basename) == 'test_host_system_0001') -%} True {%- else -%} False {%- endfor %}" -# Testcase 0002: Add Host again +# Testcase: Add Host again - name: add host again vmware_host: hostname: "{{ vcsim }}" @@ -103,9 +99,111 @@ datacenter_name: "{{ dc1 }}" cluster_name: "{{ ccr1 }}" state: present - register: host_system_result_0002 + register: readd_host_result + +- name: ensure precend task didn't changed anything + assert: + that: + - not (readd_host_result|changed) + +# Testcase: Add Host via add_or_reconnect state +- name: add host via add_or_reconnect + vmware_host: + hostname: "{{ vcsim }}" + username: "{{ vcsim_instance.json.username }}" + password: "{{ vcsim_instance.json.password }}" + validate_certs: no + esxi_hostname: test_host_system_0002 + esxi_username: "{{ vcsim_instance.json.username }}" + esxi_password: "{{ vcsim_instance.json.password }}" + datacenter_name: "{{ dc1 }}" + cluster_name: "{{ ccr1 }}" + state: add_or_reconnect + register: add_or_reconnect_host_result + +- name: get a list of host system from vcsim after adding host system + uri: + url: http://{{ vcsim }}:5000/govc_find?filter=H + register: host_list - name: ensure host system is present assert: that: - - host_system_result_0002.changed == false + - add_or_reconnect_host_result | changed + - "{% for host in host_list.json if ((host | basename) == 'test_host_system_0002') -%} True {%- else -%} False {%- endfor %}" + +## Testcase: Reconnect Host +# +# ReconnectHost_Task need to be implemented in vcsim for this test to work +# https://github.com/vmware/govmomi/tree/master/vcsim#supported-methods +# +#- name: reconnect host +# vmware_host: +# hostname: "{{ vcsim }}" +# username: "{{ vcsim_instance.json.username }}" +# password: "{{ vcsim_instance.json.password }}" +# validate_certs: no +# esxi_hostname: test_host_system_0001 +# datacenter_name: "{{ dc1 }}" +# cluster_name: "{{ ccr1 }}" +# state: reconnect +# register: reconnect_host_result +# +#- name: ensure host system has been reconnected +# assert: +# that: +# - reconnect_host_result | changed +# # it would be a good idea to check the events on the host to see the reconnect +# # https://github.com/vmware/govmomi/blob/master/govc/USAGE.md#events +# # "govc events ..." need to be callable from +# # test/utils/docker/vcenter-simulator/flask_control.py + +## Testcase: Remove Host +# +# EnterMaintenanceMode_Task need to be implemented in vcsim for this test to work +# https://github.com/vmware/govmomi/tree/master/vcsim#supported-methods +# +#- name: remove host +# vmware_host: +# hostname: "{{ vcsim }}" +# username: "{{ vcsim_instance.json.username }}" +# password: "{{ vcsim_instance.json.password }}" +# validate_certs: no +# esxi_hostname: test_host_system_0001 +# datacenter_name: "{{ dc1 }}" +# cluster_name: "{{ ccr1 }}" +# state: absent +# register: remove_host_result +# +#- name: get a list of host system from vcsim after removing host system +# uri: +# url: http://{{ vcsim }}:5000/govc_find?filter=H +# register: host_list +# +#- name: ensure host system is absent +# assert: +# that: +# - remove_host_result | changed +# - "{% for host in host_list.json if ((host | basename) == 'test_host_system_0001') -%} False {%- else -%} True {%- endfor %}" + +## Testcase: Remove Host again +# +# EnterMaintenanceMode_Task need to be implemented in vcsim for this test to work +# https://github.com/vmware/govmomi/tree/master/vcsim#supported-methods +# +#- name: remove host again +# vmware_host: +# hostname: "{{ vcsim }}" +# username: "{{ vcsim_instance.json.username }}" +# password: "{{ vcsim_instance.json.password }}" +# validate_certs: no +# esxi_hostname: test_host_system_0001 +# datacenter_name: "{{ dc1 }}" +# cluster_name: "{{ ccr1 }}" +# state: absent +# register: reremove_host_result +# +#- name: ensure precend task didn't changed anything +# assert: +# that: +# - not (reremove_host_result|changed)