Repository: ansible-collections/community.vmware

Issue:
Error message with bad destination host in vmotion module

##### SUMMARY
The error message we get when we provide a wrong ESXi node name as `destination_host` attribute to `vmware_vmotion` module is not helpful. I assume that we should have a clean exception handler with a correct error message.

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
-  module `vmware_vmotion`

##### ANSIBLE VERSION
```paste below
ansible 2.10.7
  config file = /home/xenlo/Projects/619/etc/ansible.cfg
  configured module search path = ['/home/xenlo/Projects/619/library']
  ansible python module location = /home/xenlo/.virtualenvs/619/lib/python3.6/site-packages/ansible
  executable location = /home/xenlo/.virtualenvs/619/bin/ansible
  python version = 3.6.12 (default, Dec 02 2020, 09:44:23) [GCC]
```

##### CONFIGURATION
```paste below
(619) ahgff@bootstrap:~/Projects/619> ansible-config dump --only-changed  | sed 's/ahgff/xenlo/'
ANSIBLE_SSH_ARGS(/home/xenlo/Projects/619/etc/ansible.cfg) = -C -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=60 -o ConnectionAttempts=2
CACHE_PLUGIN(/home/xenlo/Projects/619/etc/ansible.cfg) = jsonfile
CACHE_PLUGIN_CONNECTION(/home/xenlo/Projects/619/etc/ansible.cfg) = $HOME/.ansible/facts/
CACHE_PLUGIN_TIMEOUT(/home/xenlo/Projects/619/etc/ansible.cfg) = 3600
DEFAULT_ACTION_PLUGIN_PATH(env: ANSIBLE_ACTION_PLUGINS) = ['/home/xenlo/Projects/619/plugins/action', '/home/ahgff/.virtualenvs/619/lib/python3.6/site-packages/ara/plugins/action']
DEFAULT_CALLBACK_PLUGIN_PATH(env: ANSIBLE_CALLBACK_PLUGINS) = ['/home/xenlo/.virtualenvs/619/lib/python3.6/site-packages/ara/plugins/callback']
DEFAULT_CALLBACK_WHITELIST(/home/xenlo/Projects/619/etc/ansible.cfg) = ['profile_tasks']
DEFAULT_FILTER_PLUGIN_PATH(env: ANSIBLE_FILTER_PLUGINS) = ['/home/xenlo/Projects/619/plugins/filter']
DEFAULT_GATHERING(/home/xenlo/Projects/619/etc/ansible.cfg) = smart
DEFAULT_HOST_LIST(/home/xenlo/Projects/619/etc/ansible.cfg) = ['/home/ahgff/Projects/619/inventories/dev', '/home/ahgff/Projects/619/inventories/setupenv']
DEFAULT_LOG_PATH(/home/xenlo/Projects/619/etc/ansible.cfg) = /home/ahgff/.ansible/log/ansible.log
DEFAULT_MODULE_PATH(env: ANSIBLE_LIBRARY) = ['/home/xenlo/Projects/619/library']
DEFAULT_ROLES_PATH(env: ANSIBLE_ROLES_PATH) = ['/home/xenlo/Projects/619/roles.galaxy', '/home/ahgff/Projects/619/roles']
DEFAULT_STDOUT_CALLBACK(/home/xenlo/Projects/619/etc/ansible.cfg) = yaml
GALAXY_ROLE_SKELETON(env: ANSIBLE_GALAXY_ROLE_SKELETON) = /home/xenlo/Projects/619/etc/skel/default
HOST_KEY_CHECKING(/home/xenlo/Projects/619/etc/ansible.cfg) = False
```

##### OS / ENVIRONMENT
Target infra is vSphere 6.7

##### STEPS TO REPRODUCE
Play a `vmware_vmotion`task by providing an non-existing ESXi node name as `destination_host` (In my case I was providing the short dns name when the vcenter knows the hosts with the FQDN name).

```yaml
- name: Ensure that my VM is running on the right node and disk images are stored on SAN
  community.vmware.vmware_vmotion:
    hostname: "{{ hostvars['vcenter'].fqdn }}"
    username: "Administrator@{{ hostvars['vcenter'].dns_domain }}"
    password: "{{ vcenter_pass }}"
    validate_certs: false
    vm_name: "my-vm"
    destination_datastore: "{{ vcenter_datastore }}"
    destination_host: "this-is-NOT-an-esxi-node"
  delegate_to: localhost
```
##### EXPECTED RESULTS
I would expect having a error telling me that the "`this-is-NOT-an-esxi-node`" destination host does not exist in the cluster.

##### ACTUAL RESULTS
I get the following python error which is not really helpfull to understand the problem.
```
AttributeError: 'NoneType' object has no attribute 'parent'
```

Here is the output I get in one of my playbook:
```
TASK [set_vmware_cluster : Ensure that VSP and vCenter are running on the right node and disk images are stored on SAN] ****************************************************************************************************
An exception occurred during task execution. To see the full traceback, use -vvv.
failed: [node01] (item=vcenter) => changed=false 
  ansible_loop_var: vmotion_host
  module_stderr: |-
    Traceback (most recent call last):
      File "/home/xenlo/.ansible/tmp/ansible-tmp-1618828944.0270574-24089-200331838688996/AnsiballZ_vmware_vmotion.py", line 102, in <module>
        _ansiballz_main()
      File "/home/xenlo/.ansible/tmp/ansible-tmp-1618828944.0270574-24089-200331838688996/AnsiballZ_vmware_vmotion.py", line 94, in _ansiballz_main
        invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
      File "/home/xenlo/.ansible/tmp/ansible-tmp-1618828944.0270574-24089-200331838688996/AnsiballZ_vmware_vmotion.py", line 40, in invoke_module
        runpy.run_module(mod_name='ansible_collections.community.vmware.plugins.modules.vmware_vmotion', init_globals=None, run_name='__main__', alter_sys=True)
      File "/usr/lib64/python3.6/runpy.py", line 205, in run_module
        return _run_module_code(code, init_globals, run_name, mod_spec)
      File "/usr/lib64/python3.6/runpy.py", line 96, in _run_module_code
        mod_name, mod_spec, pkg_name, script_name)
      File "/usr/lib64/python3.6/runpy.py", line 85, in _run_code
        exec(code, run_globals)
      File "/tmp/ansible_community.vmware.vmware_vmotion_payload_xsxp1s95/ansible_community.vmware.vmware_vmotion_payload.zip/ansible_collections/community/vmware/plugins/modules/vmware_vmotion.py", line 375, in <module>
      File "/tmp/ansible_community.vmware.vmware_vmotion_payload_xsxp1s95/ansible_community.vmware.vmware_vmotion_payload.zip/ansible_collections/community/vmware/plugins/modules/vmware_vmotion.py", line 371, in main
      File "/tmp/ansible_community.vmware.vmware_vmotion_payload_xsxp1s95/ansible_community.vmware.vmware_vmotion_payload.zip/ansible_collections/community/vmware/plugins/modules/vmware_vmotion.py", line 200, in __init__
    AttributeError: 'NoneType' object has no attribute 'parent'
  module_stdout: ''
  msg: |-
    MODULE FAILURE
    See stdout/stderr for the exact error
  rc: 1
  vmotion_host: vcenter
```



Discussion:
The error was generated on [line 189 in vmware_vmotion.py](https://github.com/ansible-collections/community.vmware/blob/main/plugins/modules/vmware_vmotion.py#L189) but in fact the None value comes from the [assigned `host_object` with find_hostsystem_by_name`](https://github.com/ansible-collections/community.vmware/blob/main/plugins/modules/vmware_vmotion.py#L167).

I assume that either in the `find_hostsystem_by_name` function or just after the definition to `host_object` we should have a test `if not None` otherwise through a helpful error like "destination_host %s not found in the cluster!".
cc @Akasurde @Tomorrow9 @bedecarroll @goneri @lparkes @oboukili @pgbidkar @warthog9
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Hi @xenlo, It looks like you've already done the investigation :-). Would you like to push a PR that improve the error message?

Resolving patch:
diff --git a/plugins/modules/vmware_vmotion.py b/plugins/modules/vmware_vmotion.py
--- a/plugins/modules/vmware_vmotion.py
+++ b/plugins/modules/vmware_vmotion.py
@@ -166,6 +166,8 @@ def __init__(self, module):
         if dest_host_name is not None:
             self.host_object = find_hostsystem_by_name(content=self.content,
                                                        hostname=dest_host_name)
+            if self.host_object is None:
+                self.module.fail_json(msg="Unable to find destination host %s" % dest_host_name)
 
         # Get Destination Datastore if specified by user
         dest_datastore = self.params.get('destination_datastore', None)
@@ -189,7 +191,7 @@ def __init__(self, module):
             self.resourcepool_object = self.host_object.parent.resourcePool
         # Fail if resourcePool object is not found
         if self.resourcepool_object is None:
-            self.module.fail_json(msg="Unable to destination resource pool object which is required")
+            self.module.fail_json(msg="Unable to find destination resource pool object which is required")
 
         # Check if datastore is required, this check is required if destination
         # and source host system does not share same datastore.


Test patch: