Repository: ansible/ansible

Issue:
vmware_guest: Clone from template results in fault.NicSettingMismatch.summary
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
vmware_guest

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
v2.3

##### SUMMARY
<!--- Explain the problem briefly -->
Spending a long time understanding what is going on, but the fact that it takes about 10 minutes before it fails (duing VM creation) makes it hard to troubleshoot.

The template has exactly one NIC, and my definition also has exactly one NIC, so it should be obvious that there is no real mismatch in number of NICs. But looking at other reports on the net seems to indicate a problem related to identity customizations (related to domain or DNS config).
vmware_guest: Clone from template results in fault.NicSettingMismatch.summary
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
vmware_guest

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
v2.3

##### SUMMARY
<!--- Explain the problem briefly -->
Spending a long time understanding what is going on, but the fact that it takes about 10 minutes before it fails (duing VM creation) makes it hard to troubleshoot.

The template has exactly one NIC, and my definition also has exactly one NIC, so it should be obvious that there is no real mismatch in number of NICs. But looking at other reports on the net seems to indicate a problem related to identity customizations (related to domain or DNS config).


Discussion:
cc @jctanner
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@nerzhul Have you seen this before ? I cannot wrap my head around this one :-/
did saw this error before, it's a VMWare API error ?
https://www.vmware.com/support/developer/vc-sdk/visdk25pubs/ReferenceGuide/vim.fault.NicSettingMismatch.html
"The number of network adapter settings in the customization specification does not match the number of network adapters present in the virtual machine."
What I noticed is that when this happens I end up with a VM with 2 NICs (the original from the template and a new one with my config). So this is definitely a regression in the logic, and I don't have to look at anything else anymore :-)

The reason this worked before was that the previous code removed all NICs and then added what was requested. (suboptimal, I agree, but it worked ;-))

I lost a few hours because of this though...
Yes old behaviour removed all NIC but it's not acceptable when using idempotence with running VM
Seems this is still an issue.
I have a template with no nics and this error still occurs.

Or was this related to something else and not the nic adapter count?


@joshmullis one of my student reproduced your bug today and i debugged with him to find the error.
VMware API is quite stupid, it clone the machine, every parameter is accepted by VMware, VM is okay but API returns an error whereas clone seems to be okay... it will be quite compilated to solve this issue
cool!  Well I found that the issue goes away if I specify an IP and Netmask (even though they are blank)

```
 networks:
   - name: "{{ vm_network }}"
      type: vmxnet3
      ip: ""
      netmask: ""
```


I tried the above but did not help, Now started getting the error for IP, I am exactly facing the same issue
@joshmullis I tried your solution but I get a failure:
`"msg": "A specified parameter was not correct: spec.nicSettingMap.adapter.ip"`

I'm cloning a template with no NICs, creating a NIC with `networks` parameter. If I try to use the `customize` parameter in any way I get:
`"msg": "fault.NicSettingMismatch.summary"`

**EDIT**: The error is actually generated by the `network` parameter. If I only specify the attributes `name` and `mac` it will create the NIC but it will throw the error `"msg": "fault.NicSettingMismatch.summary"`.

Question: how do I create a NIC that uses a DHCP server?
@randome I am also using a template without a nic.  Below is the code I'm using.  Also, this code allows me to use DHCP (since I'm specifying a blank IP and Netmask)

My environment:
 - ansible 2.3.0.0
 - vcenter 5.5



main.yml
```
- hosts: localhost
  vars_files:
    - vars.yml

  vars_prompt:
    - name: vsphere_password
      prompt: "Enter vsphere password"
      private: yes

  tasks:

    #######################
    ## Create/Manage VMs ##
    #######################

    - name: Create/Modify VMs
      with_items: "{{ vm_names }}"
      when: vm_lifecycle == "create"
      vmware_guest:
        state: poweredon
        hostname: "{{ vsphere_hostname }}"
        username: "{{ vsphere_username }}"
        password: "{{ vsphere_password }}"
        validate_certs: no
        datacenter: "{{ vsphere_datacenter }}"
        cluster: "{{ vsphere_cluster }}"
        resource_pool: "{{ vsphere_resource_pool | default(omit) }}"
        name: "{{ item }}"
        template: "{{ vm_template }}"
        folder: "{{ vm_folder }}"
        wait_for_ip_address: yes

        # "ip:" and "netmask:" must be here to fulfill api requirement, but please leave blank for DHCP
        networks:
        - name: "{{ vm_network }}"
          type: vmxnet3
          ip: ""
          netmask: ""

        disk:
        - autoselect_datastore: true
          datastore: "{{ vm_datastore_filter }}"
          size_gb: 50
          type: thin

        # Uncomment to have a 2nd disk setup
        #disk:
        #- autoselect_datastore: true
        #  datastore: "{{ vm_datastore_filter }}"
        #  size_gb: "{{ disk2_size }}"
        #  type: thin

        hardware:
          memory_mb: "{{ vm_mem }}"
          num_cpus: "{{ vm_cpus }}"

        customization:
          hostname: "{{ item }}"
          domain: "{{ vm_domain }}"





    #################
    ## Destroy VMs ##
    #################

    - name: Poweroff VMs
      with_items: "{{ vm_names }}"
      when: vm_lifecycle == "destroy"
      vmware_guest:
        state: poweredoff
        hostname: "{{ vsphere_hostname }}"
        username: "{{ vsphere_username }}"
        password: "{{ vsphere_password }}"
        validate_certs: no
        datacenter: "{{ vsphere_datacenter }}"
        cluster: "{{ vsphere_cluster }}"
        resource_pool: "{{ vsphere_resource_pool | default(omit) }}"
        name: "{{ item }}"
        folder: "{{ vm_folder }}"

    - name: Delete VMs
      with_items: "{{ vm_names }}"
      when: vm_lifecycle == "destroy"
      vmware_guest:
        state: absent
        hostname: "{{ vsphere_hostname }}"
        username: "{{ vsphere_username }}"
        password: "{{ vsphere_password }}"
        validate_certs: no
        datacenter: "{{ vsphere_datacenter }}"
        cluster: "{{ vsphere_cluster }}"
        resource_pool: "{{ vsphere_resource_pool | default(omit) }}"
        name: "{{ item }}"
        folder: "{{ vm_folder }}"
```



vars.yml
```
## VM Preferences ##
vsphere_username: "<username>"
vm_template: "<template_name>"
vm_network: "<vm_network_name>"
vm_folder: "<folder_path>"
disk2_size: ""
vm_cpus: "2"
vm_mem: "4096"
vm_names:
  - my-app001
#  - my-app002
#  - my-app003


vsphere_hostname: "<vcenter_hostname>"
vsphere_datacenter: "<datacenter_name>"
vsphere_cluster: "<cluster_name>"
vm_datastore_filter: "<datastore_partial_name_for_search>"
vm_domain: "<domain_suffix_for_vm_naming>"
```
@randome Be sure to pass the "vm_lifecycle" variable when running, or remove from the main.yml

```
ansible-playbook -e vm_lifecycle=create main.yml
ansible-playbook -e vm_lifecycle=destroy main.yml
```
@joshmullis Thank you for your reply! I've tried your playbook, unfortunately, if I leave the `ip` blank then the playbook fails with message: `"A specified parameter was not correct: spec.nicSettingMap.adapter.ip"`.

I wonder if the reason is that my vcenter is v6.0 :thinking: 
@radome I can confirm that it doesn't work with vcenter 6.5 either, I'm getting the same error.
I opened a PR at https://github.com/ansible/ansible/pull/24283 that resolves this error (falls back to a DHCP nic)
cc @jctanner
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@nerzhul Have you seen this before ? I cannot wrap my head around this one :-/
did saw this error before, it's a VMWare API error ?
https://www.vmware.com/support/developer/vc-sdk/visdk25pubs/ReferenceGuide/vim.fault.NicSettingMismatch.html
"The number of network adapter settings in the customization specification does not match the number of network adapters present in the virtual machine."
What I noticed is that when this happens I end up with a VM with 2 NICs (the original from the template and a new one with my config). So this is definitely a regression in the logic, and I don't have to look at anything else anymore :-)

The reason this worked before was that the previous code removed all NICs and then added what was requested. (suboptimal, I agree, but it worked ;-))

I lost a few hours because of this though...
Yes old behaviour removed all NIC but it's not acceptable when using idempotence with running VM
Seems this is still an issue.
I have a template with no nics and this error still occurs.

Or was this related to something else and not the nic adapter count?


@joshmullis one of my student reproduced your bug today and i debugged with him to find the error.
VMware API is quite stupid, it clone the machine, every parameter is accepted by VMware, VM is okay but API returns an error whereas clone seems to be okay... it will be quite compilated to solve this issue
cool!  Well I found that the issue goes away if I specify an IP and Netmask (even though they are blank)

```
 networks:
   - name: "{{ vm_network }}"
      type: vmxnet3
      ip: ""
      netmask: ""
```


I tried the above but did not help, Now started getting the error for IP, I am exactly facing the same issue
@joshmullis I tried your solution but I get a failure:
`"msg": "A specified parameter was not correct: spec.nicSettingMap.adapter.ip"`

I'm cloning a template with no NICs, creating a NIC with `networks` parameter. If I try to use the `customize` parameter in any way I get:
`"msg": "fault.NicSettingMismatch.summary"`

**EDIT**: The error is actually generated by the `network` parameter. If I only specify the attributes `name` and `mac` it will create the NIC but it will throw the error `"msg": "fault.NicSettingMismatch.summary"`.

Question: how do I create a NIC that uses a DHCP server?
@randome I am also using a template without a nic.  Below is the code I'm using.  Also, this code allows me to use DHCP (since I'm specifying a blank IP and Netmask)

My environment:
 - ansible 2.3.0.0
 - vcenter 5.5



main.yml
```
- hosts: localhost
  vars_files:
    - vars.yml

  vars_prompt:
    - name: vsphere_password
      prompt: "Enter vsphere password"
      private: yes

  tasks:

    #######################
    ## Create/Manage VMs ##
    #######################

    - name: Create/Modify VMs
      with_items: "{{ vm_names }}"
      when: vm_lifecycle == "create"
      vmware_guest:
        state: poweredon
        hostname: "{{ vsphere_hostname }}"
        username: "{{ vsphere_username }}"
        password: "{{ vsphere_password }}"
        validate_certs: no
        datacenter: "{{ vsphere_datacenter }}"
        cluster: "{{ vsphere_cluster }}"
        resource_pool: "{{ vsphere_resource_pool | default(omit) }}"
        name: "{{ item }}"
        template: "{{ vm_template }}"
        folder: "{{ vm_folder }}"
        wait_for_ip_address: yes

        # "ip:" and "netmask:" must be here to fulfill api requirement, but please leave blank for DHCP
        networks:
        - name: "{{ vm_network }}"
          type: vmxnet3
          ip: ""
          netmask: ""

        disk:
        - autoselect_datastore: true
          datastore: "{{ vm_datastore_filter }}"
          size_gb: 50
          type: thin

        # Uncomment to have a 2nd disk setup
        #disk:
        #- autoselect_datastore: true
        #  datastore: "{{ vm_datastore_filter }}"
        #  size_gb: "{{ disk2_size }}"
        #  type: thin

        hardware:
          memory_mb: "{{ vm_mem }}"
          num_cpus: "{{ vm_cpus }}"

        customization:
          hostname: "{{ item }}"
          domain: "{{ vm_domain }}"





    #################
    ## Destroy VMs ##
    #################

    - name: Poweroff VMs
      with_items: "{{ vm_names }}"
      when: vm_lifecycle == "destroy"
      vmware_guest:
        state: poweredoff
        hostname: "{{ vsphere_hostname }}"
        username: "{{ vsphere_username }}"
        password: "{{ vsphere_password }}"
        validate_certs: no
        datacenter: "{{ vsphere_datacenter }}"
        cluster: "{{ vsphere_cluster }}"
        resource_pool: "{{ vsphere_resource_pool | default(omit) }}"
        name: "{{ item }}"
        folder: "{{ vm_folder }}"

    - name: Delete VMs
      with_items: "{{ vm_names }}"
      when: vm_lifecycle == "destroy"
      vmware_guest:
        state: absent
        hostname: "{{ vsphere_hostname }}"
        username: "{{ vsphere_username }}"
        password: "{{ vsphere_password }}"
        validate_certs: no
        datacenter: "{{ vsphere_datacenter }}"
        cluster: "{{ vsphere_cluster }}"
        resource_pool: "{{ vsphere_resource_pool | default(omit) }}"
        name: "{{ item }}"
        folder: "{{ vm_folder }}"
```



vars.yml
```
## VM Preferences ##
vsphere_username: "<username>"
vm_template: "<template_name>"
vm_network: "<vm_network_name>"
vm_folder: "<folder_path>"
disk2_size: ""
vm_cpus: "2"
vm_mem: "4096"
vm_names:
  - my-app001
#  - my-app002
#  - my-app003


vsphere_hostname: "<vcenter_hostname>"
vsphere_datacenter: "<datacenter_name>"
vsphere_cluster: "<cluster_name>"
vm_datastore_filter: "<datastore_partial_name_for_search>"
vm_domain: "<domain_suffix_for_vm_naming>"
```
@randome Be sure to pass the "vm_lifecycle" variable when running, or remove from the main.yml

```
ansible-playbook -e vm_lifecycle=create main.yml
ansible-playbook -e vm_lifecycle=destroy main.yml
```
@joshmullis Thank you for your reply! I've tried your playbook, unfortunately, if I leave the `ip` blank then the playbook fails with message: `"A specified parameter was not correct: spec.nicSettingMap.adapter.ip"`.

I wonder if the reason is that my vcenter is v6.0 :thinking: 
@radome I can confirm that it doesn't work with vcenter 6.5 either, I'm getting the same error.
I opened a PR at https://github.com/ansible/ansible/pull/24283 that resolves this error (falls back to a DHCP nic)

Resolving patch:
diff --git a/lib/ansible/module_utils/vmware.py b/lib/ansible/module_utils/vmware.py
--- a/lib/ansible/module_utils/vmware.py
+++ b/lib/ansible/module_utils/vmware.py
@@ -52,6 +52,30 @@ def wait_for_task(task):
             time.sleep(15)
 
 
+def find_obj(content, vimtype, name, first=True):
+    container = content.viewManager.CreateContainerView(container=content.rootFolder, recursive=True, type=vimtype)
+    obj_list = container.view
+    container.Destroy()
+
+    # Backward compatible with former get_obj() function
+    if name is None:
+        if obj_list:
+            return obj_list[0]
+        return None
+
+    # Select the first match
+    if first is True:
+        for obj in obj_list:
+            if obj.name == name:
+                return obj
+
+        # If no object found, return None
+        return None
+
+    # Return all matching objects if needed
+    return [obj for obj in obj_list if obj.name == name]
+
+
 def find_dvspg_by_name(dv_switch, portgroup_name):
 
     portgroups = dv_switch.portgroup
diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest.py b/lib/ansible/modules/cloud/vmware/vmware_guest.py
--- a/lib/ansible/modules/cloud/vmware/vmware_guest.py
+++ b/lib/ansible/modules/cloud/vmware/vmware_guest.py
@@ -23,281 +23,289 @@
                     'supported_by': 'community'}
 
 
-DOCUMENTATION = '''
+DOCUMENTATION = r'''
 ---
 module: vmware_guest
-short_description: Manages virtual machines in vcenter
+short_description: Manages virtual machines in vCenter
 description:
-    - Create new virtual machines (from templates or not)
-    - Power on/power off/restart a virtual machine
-    - Modify, rename or remove a virtual machine
-version_added: 2.2
+- Create new virtual machines (from templates or not).
+- Power on/power off/restart a virtual machine.
+- Modify, rename or remove a virtual machine.
+version_added: '2.2'
 author:
-    - James Tanner (@jctanner) <tanner.jc@gmail.com>
-    - Loic Blot (@nerzhul) <loic.blot@unix-experience.fr>
+- James Tanner (@jctanner) <tanner.jc@gmail.com>
+- Loic Blot (@nerzhul) <loic.blot@unix-experience.fr>
 notes:
-    - Tested on vSphere 5.5 and 6.0
+- Tested on vSphere 5.5 and 6.0
 requirements:
-    - "python >= 2.6"
-    - PyVmomi
+- python >= 2.6
+- PyVmomi
 options:
-   state:
-        description:
-            - What state should the virtual machine be in?
-            - If C(state) is set to C(present) and VM exists, ensure the VM configuration conforms to task arguments
-        required: True
-        choices: ['present', 'absent', 'poweredon', 'poweredoff', 'restarted', 'suspended', 'shutdownguest', 'rebootguest']
-   name:
-        description:
-            - Name of the VM to work with
-        required: True
-   name_match:
-        description:
-            - If multiple VMs matching the name, use the first or last found
-        default: 'first'
-        choices: ['first', 'last']
-   uuid:
-        description:
-            - UUID of the instance to manage if known, this is VMware's unique identifier.
-            - This is required if name is not supplied.
-   template:
-        description:
-            - Template used to create VM.
-            - If this value is not set, VM is created without using a template.
-            - If the VM exists already this setting will be ignored.
-   is_template:
-        description:
-            - Flag the instance as a template
-        default: False
-        version_added: "2.3"
-   folder:
-        description:
-            - Destination folder, absolute path to find an existing guest or create the new guest
-   hardware:
-        description:
-            - "Manage some VM hardware attributes."
-            - "Valid attributes are: memory_mb, num_cpus and scsi"
-            - "scsi: Valid values are buslogic, lsilogic, lsilogicsas and paravirtual (default)"
-   guest_id:
-        description:
-            - "Set the guest ID (Debian, RHEL, Windows...)"
-            - "This field is required when creating a VM"
-            - >
-              Valid values are referenced here:
-              https://www.vmware.com/support/developer/converter-sdk/conv55_apireference/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html
-        version_added: "2.3"
-   disk:
-        description:
-            - "A list of disks to add"
-            - "Valid attributes are: size_[tb,gb,mb,kb], type, datastore and autoselect_datastore"
-            - "type: Valid value is thin (default: None)"
-            - "datastore: Datastore to use for the disk. If autoselect_datastore is True, filter datastore selection."
-            - "autoselect_datastore (bool): select the less used datastore."
-   resource_pool:
-        description:
-            - Affect machine to the given resource pool
-            - Resource pool should be child of the selected host parent
-        default: None
-        version_added: "2.3"
-   wait_for_ip_address:
-        description:
-            - Wait until vCenter detects an IP address for the VM
-            - This requires vmware-tools (vmtoolsd) to properly work after creation
-        default: False
-   snapshot_src:
-        description:
-            - Name of an existing snapshot to use to create a clone of a VM.
-        default: None
-        version_added: "2.4"
-   linked_clone:
-        description:
-            - Whether to create a Linked Clone from the snapshot specified
-        default: False
-        version_added: "2.4"
-   force:
-        description:
-            - Ignore warnings and complete the actions
-   datacenter:
-        description:
-            - Destination datacenter for the deploy operation
-        default: ha-datacenter
-   cluster:
-        description:
-            - The cluster name where the VM will run.
-        version_added: "2.3"
-   esxi_hostname:
-        description:
-            - The esxi hostname where the VM will run.
-   annotation:
-        description:
-            - A note or annotation to include in the VM
-        version_added: "2.3"
-   customvalues:
-        description:
-            - Define a list of customvalues to set on VM.
-            - "A customvalue object takes 2 fields 'key' and 'value'."
-        version_added: "2.3"
-   networks:
-        description:
-          - Network to use should include C(name) or C(vlan) entry
-          - Add an optional C(ip) and C(netmask) for network configuration
-          - Add an optional C(gateway) entry to configure a gateway
-          - Add an optional C(mac) entry to customize mac address
-          - Add an optional C(dns_servers) or C(domain) entry per interface (Windows)
-          - Add an optional C(device_type) to configure the virtual NIC (pcnet32, vmxnet2, vmxnet3, e1000, e1000e)
-        version_added: "2.3"
-   customization:
-        description:
-          - "Parameters to customize template"
-          - "Common parameters (Linux/Windows):"
-          - "  C(dns_servers) (list): List of DNS servers to configure"
-          - "  C(dns_suffix) (list): List of domain suffixes, aka DNS search path (default: C(domain) parameter)"
-          - "  C(domain) (string): DNS domain name to use"
-          - "  C(hostname) (string): Computer hostname (default: C(name) parameter)"
-          - "Parameters related to windows customization:"
-          - "  C(autologon) (bool): Auto logon after VM customization (default: False)"
-          - "  C(autologoncount) (int): Number of autologon after reboot (default: 1)"
-          - "  C(domainadmin) (string): User used to join in AD domain (mandatory with joindomain)"
-          - "  C(domainadminpassword) (string): Password used to join in AD domain (mandatory with joindomain)"
-          - "  C(fullname) (string): Server owner name (default: Administrator)"
-          - "  C(joindomain) (string): AD domain to join (Not compatible with C(joinworkgroup))"
-          - "  C(joinworkgroup) (string): Workgroup to join (Not compatible with C(joindomain), default: WORKGROUP)"
-          - "  C(orgname) (string): Organisation name (default: ACME)"
-          - "  C(password) (string): Local administrator password (mandatory)"
-          - "  C(productid) (string): Product ID"
-          - "  C(runonce) (list): List of commands to run at first user logon"
-          - "  C(timezone) (int): Timezone (default: 85) See U(https://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx)"
-        version_added: "2.3"
+  state:
+    description:
+    - What state should the virtual machine be in?
+    - If C(state) is set to C(present) and VM exists, ensure the VM configuration conforms to task arguments.
+    required: yes
+    choices: [ 'present', 'absent', 'poweredon', 'poweredoff', 'restarted', 'suspended', 'shutdownguest', 'rebootguest' ]
+  name:
+    description:
+    - Name of the VM to work with.
+    - VM names in vCenter are not necessarily unique, which may be problematic, see C(name_match).
+    required: yes
+  name_match:
+    description:
+    - If multiple VMs matching the name, use the first or last found.
+    default: 'first'
+    choices: [ 'first', 'last' ]
+  uuid:
+    description:
+    - UUID of the instance to manage if known, this is VMware's unique identifier.
+    - This is required if name is not supplied.
+  template:
+    description:
+    - Template used to create VM.
+    - If this value is not set, VM is created without using a template.
+    - If the VM exists already this setting will be ignored.
+  is_template:
+    description:
+    - Flag the instance as a template.
+    default: 'no'
+    type: bool
+    version_added: '2.3'
+  folder:
+    description:
+    - Destination folder, absolute path to find an existing guest or create the new guest.
+    default: /
+  hardware:
+    description:
+    - Manage some VM hardware attributes.
+    - 'Valid attributes are:'
+    - ' - C(memory_mb) (integer): Amount of memory in MB.'
+    - ' - C(num_cpus) (integer): Number of CPUs.'
+    - ' - C(scsi) (string): Valid values are C(buslogic), C(lsilogic), C(lsilogicsas) and C(paravirtual) (default).'
+  guest_id:
+    description:
+    - Set the guest ID (Debian, RHEL, Windows...).
+    - This field is required when creating a VM.
+    - >
+         Valid values are referenced here:
+         https://www.vmware.com/support/developer/converter-sdk/conv55_apireference/vim.vm.GuestOsDescriptor.GuestOsIdentifier.html
+    version_added: '2.3'
+  disk:
+    description:
+    - A list of disks to add.
+    - 'Valid attributes are:'
+    - ' - C(size_[tb,gb,mb,kb]) (integer): Disk storage size in specified unit.'
+    - ' - C(type) (string): Valid value is C(thin) (default: None).'
+    - ' - C(datastore) (string): Datastore to use for the disk. If C(autoselect_datastore) is enabled, filter datastore selection.'
+    - ' - C(autoselect_datastore) (bool): select the less used datastore.'
+  resource_pool:
+    description:
+    - Affect machine to the given resource pool.
+    - Resource pool should be child of the selected host parent.
+    version_added: '2.3'
+  wait_for_ip_address:
+    description:
+    - Wait until vCenter detects an IP address for the VM.
+    - This requires vmware-tools (vmtoolsd) to properly work after creation.
+    default: 'no'
+    type: bool
+  snapshot_src:
+    description:
+    - Name of an existing snapshot to use to create a clone of a VM.
+    version_added: '2.4'
+  linked_clone:
+    description:
+    - Whether to create a Linked Clone from the snapshot specified.
+    default: 'no'
+    type: bool
+    version_added: '2.4'
+  force:
+    description:
+    - Ignore warnings and complete the actions.
+    default: 'no'
+    type: bool
+  datacenter:
+    description:
+    - Destination datacenter for the deploy operation.
+    default: ha-datacenter
+  cluster:
+    description:
+    - The cluster name where the VM will run.
+    version_added: '2.3'
+  esxi_hostname:
+    description:
+    - The ESXi hostname where the VM will run.
+  annotation:
+    description:
+    - A note or annotation to include in the VM.
+    version_added: '2.3'
+  customvalues:
+    description:
+    - Define a list of customvalues to set on VM.
+    - A customvalue object takes 2 fields C(key) and C(value).
+    version_added: '2.3'
+  networks:
+    description:
+    - A list of networks (in the order of the NICs).
+    - 'One of the below parameters is required per entry:'
+    - ' - C(name) (string): Name of the portgroup for this interface.'
+    - ' - C(vlan) (integer): VLAN number for this interface.'
+    - 'Optional parameters per entry (used for virtual hardware):'
+    - ' - C(device_type) (string): Virtual network device (one of C(e1000), C(e1000e), C(pcnet32), C(vmxnet2), C(vmxnet3) (default), C(sriov)).'
+    - ' - C(mac) (string): Customize mac address.'
+    - 'Optional parameters per entry (used for OS customization):'
+    - ' - C(type) (string): Type of IP assignment (either C(dhcp) or C(static)).'
+    - ' - C(ip) (string): Static IP address (implies C(type: static)).'
+    - ' - C(netmask) (string): Static netmask required for C(ip).'
+    - ' - C(gateway) (string): Static gateway.'
+    - ' - C(dns_servers) (string): DNS servers for this network interface (Windows).'
+    - ' - C(domain) (string): Domain name for this network interface (Windows).'
+    version_added: '2.3'
+  customization:
+    description:
+    - Parameters for OS customization when cloning from template.
+    - 'Common parameters (Linux/Windows):'
+    - ' - C(dns_servers) (list): List of DNS servers to configure.'
+    - ' - C(dns_suffix) (list): List of domain suffixes, aka DNS search path (default: C(domain) parameter).'
+    - ' - C(domain) (string): DNS domain name to use.'
+    - ' - C(hostname) (string): Computer hostname (default: shorted C(name) parameter).'
+    - 'Parameters related to Windows customization:'
+    - ' - C(autologon) (bool): Auto logon after VM customization (default: False).'
+    - ' - C(autologoncount) (int): Number of autologon after reboot (default: 1).'
+    - ' - C(domainadmin) (string): User used to join in AD domain (mandatory with C(joindomain)).'
+    - ' - C(domainadminpassword) (string): Password used to join in AD domain (mandatory with C(joindomain)).'
+    - ' - C(fullname) (string): Server owner name (default: Administrator).'
+    - ' - C(joindomain) (string): AD domain to join (Not compatible with C(joinworkgroup)).'
+    - ' - C(joinworkgroup) (string): Workgroup to join (Not compatible with C(joindomain), default: WORKGROUP).'
+    - ' - C(orgname) (string): Organisation name (default: ACME).'
+    - ' - C(password) (string): Local administrator password.'
+    - ' - C(productid) (string): Product ID.'
+    - ' - C(runonce) (list): List of commands to run at first user logon.'
+    - ' - C(timezone) (int): Timezone (See U(https://msdn.microsoft.com/en-us/library/ms912391.aspx)).'
+    version_added: '2.3'
 extends_documentation_fragment: vmware.documentation
 '''
 
-EXAMPLES = '''
-# Create a VM from a template
-  - name: create the VM
-    vmware_guest:
-      hostname: 192.0.2.44
-      username: administrator@vsphere.local
-      password: vmware
-      validate_certs: no
-      esxi_hostname: 192.0.2.117
-      datacenter: datacenter1
-      folder: testvms
-      name: testvm_2
-      state: poweredon
-      guest_id: centos64guest
-      disk:
-      - size_gb: 10
-        type: thin
-        datastore: g73_datastore
-      hardware:
-        memory_mb: 512
-        num_cpus: 1
-        scsi: paravirtual
-      networks:
-      - name: VM Network
-        ip: 192.168.1.100
-        netmask: 255.255.255.0
-        mac: 'aa:bb:dd:aa:00:14'
-      template: template_el7
-      wait_for_ip_address: yes
-    delegate_to: localhost
-    register: deploy
-
-# Clone a VM from Template and customize
-  - name: Clone template and customize
-    vmware_guest:
-      hostname: 192.168.1.209
-      username: administrator@vsphere.local
-      password: vmware
-      validate_certs: no
-      datacenter: datacenter1
-      cluster: cluster
-      name: testvm-2
-      template: template_windows
-      networks:
-      - name: VM Network
-        ip: 192.168.1.100
-        netmask: 255.255.255.0
-        gateway: 192.168.1.1
-        mac: 'aa:bb:dd:aa:00:14'
-        domain: my_domain
-        dns_servers:
-        - 192.168.1.1
-        - 192.168.1.2
-      customization:
-        autologon: True
-        dns_servers:
-        - 192.168.1.1
-        - 192.168.1.2
-        domain: my_domain
-        password: new_vm_password
-        runonce:
-        - powershell.exe -ExecutionPolicy Unrestricted -File C:\Windows\Temp\Enable-WinRM.ps1 -ForceNewSSLCert
-    delegate_to: localhost
-
-# Create a VM template
-  - name: create a VM template
-    vmware_guest:
-      hostname: 192.0.2.88
-      username: administrator@vsphere.local
-      password: vmware
-      validate_certs: no
-      datacenter: datacenter1
-      cluster: vmware_cluster_esx
-      resource_pool: highperformance_pool
-      folder: testvms
-      name: testvm_6
-      is_template: yes
-      guest_id: debian6_64Guest
-      disk:
-      - size_gb: 10
-        type: thin
-        datastore: g73_datastore
-      hardware:
-        memory_mb: 512
-        num_cpus: 1
-        scsi: lsilogic
-      wait_for_ip_address: yes
-    delegate_to: localhost
-    register: deploy
-
-# Rename a VM (requires the VM's uuid)
-  - vmware_guest:
-      hostname: 192.168.1.209
-      username: administrator@vsphere.local
-      password: vmware
-      uuid: 421e4592-c069-924d-ce20-7e7533fab926
-      name: new_name
-      state: present
-    delegate_to: localhost
-
-# Remove a VM by uuid
-  - vmware_guest:
-      hostname: 192.168.1.209
-      username: administrator@vsphere.local
-      password: vmware
-      uuid: 421e4592-c069-924d-ce20-7e7533fab926
-      state: absent
-    delegate_to: localhost
+EXAMPLES = r'''
+- name: Create a VM from a template
+  vmware_guest:
+    hostname: 192.0.2.44
+    username: administrator@vsphere.local
+    password: vmware
+    validate_certs: no
+    folder: /testvms
+    name: testvm_2
+    state: poweredon
+    template: template_el7
+    disk:
+    - size_gb: 10
+      type: thin
+      datastore: g73_datastore
+    hardware:
+      memory_mb: 512
+      num_cpus: 1
+      scsi: paravirtual
+    networks:
+    - name: VM Network
+      mac: aa:bb:dd:aa:00:14
+    wait_for_ip_address: yes
+  delegate_to: localhost
+  register: deploy
+
+- name: Clone a VM from Template and customize
+  vmware_guest:
+    hostname: 192.168.1.209
+    username: administrator@vsphere.local
+    password: vmware
+    validate_certs: no
+    datacenter: datacenter1
+    cluster: cluster
+    name: testvm-2
+    template: template_windows
+    networks:
+    - name: VM Network
+      ip: 192.168.1.100
+      netmask: 255.255.255.0
+      gateway: 192.168.1.1
+      mac: aa:bb:dd:aa:00:14
+      domain: my_domain
+      dns_servers:
+      - 192.168.1.1
+      - 192.168.1.2
+    - vlan: 1234
+      type: dhcp
+    customization:
+      autologon: yes
+      dns_servers:
+      - 192.168.1.1
+      - 192.168.1.2
+      domain: my_domain
+      password: new_vm_password
+      runonce:
+      - powershell.exe -ExecutionPolicy Unrestricted -File C:\Windows\Temp\ConfigureRemotingForAnsible.ps1 -ForceNewSSLCert -EnableCredSSP
+  delegate_to: localhost
+
+- name: Create a VM template
+  vmware_guest:
+    hostname: 192.0.2.88
+    username: administrator@vsphere.local
+    password: vmware
+    validate_certs: no
+    datacenter: datacenter1
+    cluster: vmware_cluster_esx
+    resource_pool: highperformance_pool
+    folder: /testvms
+    name: testvm_6
+    is_template: yes
+    guest_id: debian6_64Guest
+    disk:
+    - size_gb: 10
+      type: thin
+      datastore: g73_datastore
+    hardware:
+      memory_mb: 512
+      num_cpus: 1
+      scsi: lsilogic
+  delegate_to: localhost
+  register: deploy
+
+- name: Rename a VM (requires the VM's uuid)
+  vmware_guest:
+    hostname: 192.168.1.209
+    username: administrator@vsphere.local
+    password: vmware
+    uuid: 421e4592-c069-924d-ce20-7e7533fab926
+    name: new_name
+    state: present
+  delegate_to: localhost
+
+- name: Remove a VM by uuid
+  vmware_guest:
+    hostname: 192.168.1.209
+    username: administrator@vsphere.local
+    password: vmware
+    uuid: 421e4592-c069-924d-ce20-7e7533fab926
+    state: absent
+  delegate_to: localhost
 '''
 
-RETURN = """
+RETURN = r'''
 instance:
     description: metadata about the new virtualmachine
     returned: always
     type: dict
     sample: None
-"""
+'''
 
 import os
 import time
 
-# import module snippets
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.pycompat24 import get_exception
 from ansible.module_utils.six import iteritems
 from ansible.module_utils.urls import fetch_url
-from ansible.module_utils.vmware import get_all_objs, connect_to_api, gather_vm_facts
+from ansible.module_utils.vmware import connect_to_api, find_obj, gather_vm_facts, get_all_objs
 
 try:
     import json
@@ -395,8 +403,7 @@ def create_nic(self, device_type, device_label, device_infos):
         elif device_type == 'sriov':
             nic.device = vim.vm.device.VirtualSriovEthernetCard()
         else:
-            self.module.fail_json(msg="Invalid device_type '%s' for network %s" %
-                                      (device_type, device_infos['name']))
+            self.module.fail_json(msg='Invalid device_type "%s" for network "%s"' % (device_type, device_infos['name']))
 
         nic.device.wakeOnLanEnabled = True
         nic.device.deviceInfo = vim.Description()
@@ -425,19 +432,19 @@ def __init__(self, content):
 
     def get_network(self, network):
         if network not in self.networks:
-            self.networks[network] = get_obj(self.content, [vim.Network], network)
+            self.networks[network] = find_obj(self.content, [vim.Network], network)
 
         return self.networks[network]
 
     def get_cluster(self, cluster):
         if cluster not in self.clusters:
-            self.clusters[cluster] = get_obj(self.content, [vim.ClusterComputeResource], cluster)
+            self.clusters[cluster] = find_obj(self.content, [vim.ClusterComputeResource], cluster)
 
         return self.clusters[cluster]
 
     def get_esx_host(self, host):
         if host not in self.esx_hosts:
-            self.esx_hosts[host] = get_obj(self.content, [vim.HostSystem], host)
+            self.esx_hosts[host] = find_obj(self.content, [vim.HostSystem], host)
 
         return self.esx_hosts[host]
 
@@ -458,9 +465,6 @@ def __init__(self, module):
         self.current_vm_obj = None
         self.cache = PyVmomiCache(self.content)
 
-    def should_deploy_from_template(self):
-        return self.params.get('template') is not None
-
     def getvm(self, name=None, uuid=None, folder=None):
 
         # https://www.vmware.com/support/developer/vc-sdk/visdk2xpubs/ReferenceGuide/vim.SearchIndex.html
@@ -584,7 +588,7 @@ def remove_vm(self, vm):
 
     def configure_guestid(self, vm_obj, vm_creation=False):
         # guest_id is not required when using templates
-        if self.should_deploy_from_template() and self.params.get('guest_id') is None:
+        if self.params['template'] and not self.params['guest_id']:
             return
 
         # guest_id is only mandatory on VM creation
@@ -603,7 +607,7 @@ def configure_cpu_and_memory(self, vm_obj, vm_creation=False):
                 if vm_obj is None or self.configspec.numCPUs != vm_obj.config.hardware.numCPU:
                     self.change_detected = True
             # num_cpu is mandatory for VM creation
-            elif vm_creation and not self.should_deploy_from_template():
+            elif vm_creation and not self.params['template']:
                 self.module.fail_json(msg="hardware.num_cpus attribute is mandatory for VM creation")
 
             if 'memory_mb' in self.params['hardware']:
@@ -611,10 +615,9 @@ def configure_cpu_and_memory(self, vm_obj, vm_creation=False):
                 if vm_obj is None or self.configspec.memoryMB != vm_obj.config.hardware.memoryMB:
                     self.change_detected = True
             # memory_mb is mandatory for VM creation
-            elif vm_creation and not self.should_deploy_from_template():
+            elif vm_creation and not self.params['template']:
                 self.module.fail_json(msg="hardware.memory_mb attribute is mandatory for VM creation")
 
-
     def get_vm_network_interfaces(self, vm=None):
         if vm is None:
             return []
@@ -639,11 +642,11 @@ def configure_network(self, vm_obj):
         network_devices = list()
         for network in self.params['networks']:
             if 'ip' in network or 'netmask' in network:
-                if 'ip' not in network or not 'netmask' in network:
+                if 'ip' not in network or 'netmask' not in network:
                     self.module.fail_json(msg="Both 'ip' and 'netmask' are required together.")
 
             if 'name' in network:
-                if get_obj(self.content, [vim.Network], network['name']) is None:
+                if find_obj(self.content, [vim.Network], network['name']) is None:
                     self.module.fail_json(msg="Network '%(name)s' does not exists" % network)
 
             elif 'vlan' in network:
@@ -677,7 +680,7 @@ def configure_network(self, vm_obj):
                                                 network_devices[key])
 
             nic_change_detected = False
-            if key < len(current_net_devices) and (vm_obj or self.should_deploy_from_template()):
+            if key < len(current_net_devices) and (vm_obj or self.params['template']):
                 nic.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
                 # Changing mac address has no effect when editing interface
                 if 'mac' in network_devices[key] and nic.device.macAddress != current_net_devices[key].macAddress:
@@ -692,7 +695,7 @@ def configure_network(self, vm_obj):
 
             if hasattr(self.cache.get_network(network_devices[key]['name']), 'portKeys'):
                 # VDS switch
-                pg_obj = get_obj(self.content, [vim.dvs.DistributedVirtualPortgroup], network_devices[key]['name'])
+                pg_obj = find_obj(self.content, [vim.dvs.DistributedVirtualPortgroup], network_devices[key]['name'])
 
                 if (nic.device.backing and
                         (nic.device.backing.port.portgroupKey != pg_obj.key or
@@ -747,57 +750,80 @@ def customize_vm(self, vm_obj):
         # Network settings
         adaptermaps = []
         for network in self.params['networks']:
+
+            guest_map = vim.vm.customization.AdapterMapping()
+            guest_map.adapter = vim.vm.customization.IPSettings()
+
             if 'ip' in network and 'netmask' in network:
-                guest_map = vim.vm.customization.AdapterMapping()
-                guest_map.adapter = vim.vm.customization.IPSettings()
+                if 'type' in network and network['type'] != 'static':
+                    self.module.fail_json(msg='Static IP information provided for network "%(name)s", but "type" is set to "%(type)s".' % network)
                 guest_map.adapter.ip = vim.vm.customization.FixedIp()
                 guest_map.adapter.ip.ipAddress = str(network['ip'])
                 guest_map.adapter.subnetMask = str(network['netmask'])
+            elif 'type' in network and network['type'] == 'static':
+                self.module.fail_json(msg='Network "%(name)s" was set to type "%(type)s", but "ip" and "netmask" are missing.' % network)
+            elif 'type' in network and network['type'] == 'dhcp':
+                guest_map.adapter.ip = vim.vm.customization.DhcpIpGenerator()
+            else:
+                self.module.fail_json(msg='Network "%(name)s" was set to unknown type "%(type)s".' % network)
 
-                if 'gateway' in network:
-                    guest_map.adapter.gateway = network['gateway']
+            if 'gateway' in network:
+                guest_map.adapter.gateway = network['gateway']
 
-                # On Windows, DNS domain and DNS servers can be set by network interface
-                # https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.IPSettings.html
-                if 'domain' in network:
-                    guest_map.adapter.dnsDomain = network['domain']
-                elif self.params['customization'].get('domain'):
-                    guest_map.adapter.dnsDomain = self.params['customization']['domain']
-                if 'dns_servers' in network:
-                    guest_map.adapter.dnsServerList = network['dns_servers']
-                elif self.params['customization'].get('dns_servers'):
-                    guest_map.adapter.dnsServerList = self.params['customization']['dns_servers']
+            # On Windows, DNS domain and DNS servers can be set by network interface
+            # https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.IPSettings.html
+            if 'domain' in network:
+                guest_map.adapter.dnsDomain = network['domain']
+            elif 'domain' in self.params['customization']:
+                guest_map.adapter.dnsDomain = self.params['customization']['domain']
 
-                adaptermaps.append(guest_map)
+            if 'dns_servers' in network:
+                guest_map.adapter.dnsServerList = network['dns_servers']
+            elif 'dns_servers' in self.params['customization']:
+                guest_map.adapter.dnsServerList = self.params['customization']['dns_servers']
+
+            adaptermaps.append(guest_map)
 
         # Global DNS settings
         globalip = vim.vm.customization.GlobalIPSettings()
         if 'dns_servers' in self.params['customization']:
-            globalip.dnsServerList = self.params['customization'].get('dns_servers')
+            globalip.dnsServerList = self.params['customization']['dns_servers']
+
         # TODO: Maybe list the different domains from the interfaces here by default ?
-        if 'dns_suffix' in self.params['customization'] or 'domain' in self.params['customization']:
-            globalip.dnsSuffixList = self.params['customization'].get('dns_suffix', self.params['customization']['domain'])
+        if 'dns_suffix' in self.params['customization']:
+            globalip.dnsSuffixList = self.params['customization']['dns_suffix']
+        elif 'domain' in self.params['customization']:
+            globalip.dnsSuffixList = self.params['customization']['domain']
 
         if self.params['guest_id']:
             guest_id = self.params['guest_id']
         else:
             guest_id = vm_obj.summary.config.guestId
 
-        # If I install a Windows use Sysprep
+        # For windows guest OS, use SysPrep
         # https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.Sysprep.html#field_detail
         if 'win' in guest_id:
             ident = vim.vm.customization.Sysprep()
 
             ident.userData = vim.vm.customization.UserData()
+
+            # 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']))
+            ident.userData.computerName.name = str(self.params['customization'].get('hostname', self.params['name'].split('.')[0]))
             ident.userData.fullName = str(self.params['customization'].get('fullname', 'Administrator'))
             ident.userData.orgName = str(self.params['customization'].get('orgname', 'ACME'))
 
+            if 'productid' in self.params['customization']:
+                ident.userData.productId = str(self.params['customization']['productid'])
+
             ident.guiUnattended = vim.vm.customization.GuiUnattended()
-            ident.guiUnattended.autoLogon = self.params['customization'].get('autologon', False)
-            ident.guiUnattended.autoLogonCount = self.params['customization'].get('autologoncount', 1)
-            ident.guiUnattended.timeZone = self.params['customization'].get('timezone', 85)
+
+            if 'autologon' in self.params['customization']:
+                ident.guiUnattended.autoLogon = self.params['customization']['autologon']
+                ident.guiUnattended.autoLogonCount = self.params['customization'].get('autologoncount', 1)
+
+            if 'timezone' in self.params['customization']:
+                ident.guiUnattended.timeZone = self.params['customization']['timezone']
 
             ident.identification = vim.vm.customization.Identification()
 
@@ -805,38 +831,38 @@ def customize_vm(self, vm_obj):
                 ident.guiUnattended.password = vim.vm.customization.Password()
                 ident.guiUnattended.password.value = str(self.params['customization']['password'])
                 ident.guiUnattended.password.plainText = True
-            else:
-                self.module.fail_json(msg="The 'customization' section requires a 'password' entry, which cannot be empty.")
-
-            if 'productid' in self.params['customization']:
-                ident.userData.orgName = str(self.params['customization']['productid'])
 
             if 'joindomain' in self.params['customization']:
                 if 'domainadmin' not in self.params['customization'] or 'domainadminpassword' not in self.params['customization']:
                     self.module.fail_json(msg="'domainadmin' and 'domainadminpassword' entries are mandatory in 'customization' section to use "
                                               "joindomain feature")
 
-                ident.identification.domainAdmin = str(self.params['customization'].get('domainadmin'))
-                ident.identification.joinDomain = str(self.params['customization'].get('joindomain'))
+                ident.identification.domainAdmin = str(self.params['customization']['domainadmin'])
+                ident.identification.joinDomain = str(self.params['customization']['joindomain'])
                 ident.identification.domainAdminPassword = vim.vm.customization.Password()
-                ident.identification.domainAdminPassword.value = str(self.params['customization'].get('domainadminpassword'))
+                ident.identification.domainAdminPassword.value = str(self.params['customization']['domainadminpassword'])
                 ident.identification.domainAdminPassword.plainText = True
 
             elif 'joinworkgroup' in self.params['customization']:
-                ident.identification.joinWorkgroup = str(self.params['customization'].get('joinworkgroup'))
+                ident.identification.joinWorkgroup = str(self.params['customization']['joinworkgroup'])
 
             if 'runonce' in self.params['customization']:
                 ident.guiRunOnce = vim.vm.customization.GuiRunOnce()
                 ident.guiRunOnce.commandList = self.params['customization']['runonce']
+
         else:
-            # Else use LinuxPrep
+            # FIXME: We have no clue whether this non-Windows OS is actually Linux, hence it might fail !
+
+            # For Linux guest OS, use LinuxPrep
             # https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.customization.LinuxPrep.html
             ident = vim.vm.customization.LinuxPrep()
+
             # TODO: Maybe add domain from interface if missing ?
             if 'domain' in self.params['customization']:
-                ident.domain = str(self.params['customization'].get('domain'))
+                ident.domain = str(self.params['customization']['domain'])
+
             ident.hostName = vim.vm.customization.FixedName()
-            ident.hostName.name = str(self.params['customization'].get('hostname', self.params['name']))
+            ident.hostName.name = str(self.params['customization'].get('hostname', self.params['name'].split('.')[0]))
 
         self.customspec = vim.vm.customization.Specification()
         self.customspec.nicSettingMap = adaptermaps
@@ -941,7 +967,7 @@ def configure_disks(self, vm_obj):
             # VMWare doesn't allow to reduce disk sizes
             if kb < diskspec.device.capacityInKB:
                 self.module.fail_json(
-                    msg="Given disk size is lesser than found (%d < %d). Reducing disks is not allowed." %
+                    msg="Given disk size is smaller than found (%d < %d). Reducing disks is not allowed." %
                         (kb, diskspec.device.capacityInKB))
 
             if kb != diskspec.device.capacityInKB or disk_modified:
@@ -955,14 +981,16 @@ def select_host(self):
         if self.params['cluster']:
             cluster = self.cache.get_cluster(self.params['cluster'])
             if not cluster:
-                self.module.fail_json(msg="Failed to find a cluster named %(cluster)s" % self.params)
+                self.module.fail_json(msg='Failed to find cluster "%(cluster)s"' % self.params)
             hostsystems = [x for x in cluster.host]
+            if not hostsystems:
+                self.module.fail_json(msg='No hosts found in cluster "%(cluster)s. Maybe you lack the right privileges ?"' % self.params)
             # TODO: add a policy to select host
             hostsystem = hostsystems[0]
         else:
             hostsystem = self.cache.get_esx_host(self.params['esxi_hostname'])
             if not hostsystem:
-                self.module.fail_json(msg="Failed to find a host named %(esxi_hostname)s" % self.params)
+                self.module.fail_json(msg='Failed to find ESX host "%(esxi_hostname)s"' % self.params)
 
         return hostsystem
 
@@ -991,11 +1019,10 @@ def select_datastore(self, vm_obj=None):
 
             elif 'datastore' in self.params['disk'][0]:
                 datastore_name = self.params['disk'][0]['datastore']
-                datastore = get_obj(self.content, [vim.Datastore], datastore_name)
+                datastore = find_obj(self.content, [vim.Datastore], datastore_name)
             else:
-                self.module.fail_json(msg="Either datastore or autoselect_datastore "
-                                          "should be provided to select datastore")
-        if not datastore and self.should_deploy_from_template():
+                self.module.fail_json(msg="Either datastore or autoselect_datastore should be provided to select datastore")
+        if not datastore and self.params['template']:
             # use the template's existing DS
             disks = [x for x in vm_obj.config.hardware.device if isinstance(x, vim.vm.device.VirtualDisk)]
             datastore = disks[0].backing.datastore
@@ -1017,7 +1044,13 @@ def obj_has_parent(self, obj, parent):
             if current_parent is None:
                 return False
 
-    def select_resource_pool(self, host):
+    def select_resource_pool_by_name(self, resource_pool_name):
+        resource_pool = find_obj(self.content, [vim.ResourcePool], resource_pool_name)
+        if resource_pool is None:
+            self.module.fail_json(msg='Could not find resource_pool "%s"' % resource_pool_name)
+        return resource_pool
+
+    def select_resource_pool_by_host(self, host):
         resource_pools = get_all_objs(self.content, [vim.ResourcePool])
         for rp in resource_pools.items():
             if not rp[0]:
@@ -1060,9 +1093,9 @@ def deploy_vm(self):
         #   - multiple templates by the same name
         #   - static IPs
 
-        #datacenters = get_all_objs(self.content, [vim.Datacenter])
-        datacenter = get_obj(self.content, [vim.Datacenter], self.params['datacenter'])
-        if not datacenter:
+        # datacenters = get_all_objs(self.content, [vim.Datacenter])
+        datacenter = find_obj(self.content, [vim.Datacenter], self.params['datacenter'])
+        if datacenter is None:
             self.module.fail_json(msg='No datacenter named %(datacenter)s was found' % self.params)
 
         destfolder = None
@@ -1074,19 +1107,22 @@ def deploy_vm(self):
             self.module.fail_json(msg='No folder matched the path: %(folder)s' % self.params)
         destfolder = f_obj
 
-        hostsystem = self.select_host()
-
-        if self.should_deploy_from_template():
+        if self.params['template']:
             # FIXME: need to search for this in the same way as guests to ensure accuracy
-            vm_obj = get_obj(self.content, [vim.VirtualMachine], self.params['template'])
-            if not vm_obj:
+            vm_obj = find_obj(self.content, [vim.VirtualMachine], self.params['template'])
+            if vm_obj is None:
                 self.module.fail_json(msg="Could not find a template named %(template)s" % self.params)
         else:
             vm_obj = None
 
+        if self.params['resource_pool']:
+            resource_pool = self.select_resource_pool_by_name(self.params['resource_pool'])
+
+            if resource_pool is None:
+                self.module.fail_json(msg='Unable to find resource pool "%(resource_pool)s"' % self.params)
+
         # set the destination datastore for VM & disks
         (datastore, datastore_name) = self.select_datastore(vm_obj)
-        resource_pool = self.select_resource_pool(hostsystem)
 
         self.configspec = vim.vm.ConfigSpec(cpuHotAddEnabled=True, memoryHotAddEnabled=True)
         self.configspec.deviceChange = []
@@ -1095,18 +1131,30 @@ def deploy_vm(self):
         self.configure_disks(vm_obj=vm_obj)
         self.configure_network(vm_obj=vm_obj)
 
-        if len(self.params['customization']) > 0 or len(self.params['networks']) > 0:
+        # 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'):
+                    network_changes = True
+                    break
+
+        if len(self.params['customization']) > 0 or network_changes is True:
             self.customize_vm(vm_obj=vm_obj)
 
         try:
-            if self.should_deploy_from_template():
+            if self.params['template']:
                 # create the relocation spec
                 relospec = vim.vm.RelocateSpec()
-                # Only provide specific host when using ESXi host directly
+
+                # Only select specific host when ESXi hostname is provided
                 if self.params['esxi_hostname']:
-                    relospec.host = hostsystem
+                    relospec.host = self.select_host()
                 relospec.datastore = datastore
-                relospec.pool = resource_pool
+
+                if self.params['resource_pool']:
+                    relospec.pool = resource_pool
 
                 if self.params['snapshot_src'] is not None and self.params['linked_clone']:
                     relospec.diskMoveType = vim.vm.RelocateSpec.DiskMoveOptions.createNewChildDiskBacking
@@ -1116,11 +1164,9 @@ def deploy_vm(self):
                     clonespec.customization = self.customspec
 
                 if self.params['snapshot_src'] is not None:
-                    snapshot = self.get_snapshots_by_name_recursively(snapshots=vm_obj.snapshot.rootSnapshotList,
-                                                                      snapname=self.params['snapshot_src'])
+                    snapshot = self.get_snapshots_by_name_recursively(snapshots=vm_obj.snapshot.rootSnapshotList, snapname=self.params['snapshot_src'])
                     if len(snapshot) != 1:
-                        self.module.fail_json(msg='virtual machine "{0}" does not contain snapshot named "{1}"'.format(
-                            self.params['template'], self.params['snapshot_src']))
+                        self.module.fail_json(msg='virtual machine "%(template)s" does not contain snapshot named "%(snapshot_src)s"' % self.params)
 
                     clonespec.snapshot = snapshot[0].snapshot
 
@@ -1189,15 +1235,19 @@ def reconfigure_vm(self):
             self.configspec.annotation = str(self.params['annotation'])
             self.change_detected = True
 
+        change_applied = False
+
         relospec = vim.vm.RelocateSpec()
-        hostsystem = self.select_host()
-        relospec.pool = self.select_resource_pool(hostsystem)
+        if self.params['resource_pool']:
+            relospec.pool = self.select_resource_pool_by_name(self.params['resource_pool'])
 
-        change_applied = False
-        if relospec.pool != self.current_vm_obj.resourcePool:
-            task = self.current_vm_obj.RelocateVM_Task(spec=relospec)
-            self.wait_for_task(task)
-            change_applied = True
+            if relospec.pool is None:
+                self.module.fail_json(msg='Unable to find resource pool "%(resource_pool)s"' % self.params)
+
+            elif relospec.pool != self.current_vm_obj.resourcePool:
+                task = self.current_vm_obj.RelocateVM_Task(spec=relospec)
+                self.wait_for_task(task)
+                change_applied = True
 
         # Only send VMWare task if we see a modification
         if self.change_detected:
@@ -1232,7 +1282,7 @@ def wait_for_task(task):
         # https://www.vmware.com/support/developer/vc-sdk/visdk25pubs/ReferenceGuide/vim.Task.html
         # https://www.vmware.com/support/developer/vc-sdk/visdk25pubs/ReferenceGuide/vim.TaskInfo.html
         # https://github.com/virtdevninja/pyvmomi-community-samples/blob/master/samples/tools/tasks.py
-        while task.info.state not in ['success', 'error']:
+        while task.info.state not in ['error', 'success']:
             time.sleep(1)
 
     def wait_for_vm_ip(self, vm, poll=100, sleep=5):
@@ -1251,61 +1301,20 @@ def wait_for_vm_ip(self, vm, poll=100, sleep=5):
         return facts
 
 
-def get_obj(content, vimtype, name):
-    """
-    Return an object by name, if name is None the
-    first found object is returned
-    """
-    obj = None
-    container = content.viewManager.CreateContainerView(
-        content.rootFolder, vimtype, True)
-    for c in container.view:
-        if name:
-            if c.name == name:
-                obj = c
-                break
-        else:
-            obj = c
-            break
-
-    container.Destroy()
-    return obj
-
-
 def main():
     module = AnsibleModule(
         argument_spec=dict(
-            hostname=dict(
-                type='str',
-                default=os.environ.get('VMWARE_HOST')
-            ),
-            username=dict(
-                type='str',
-                default=os.environ.get('VMWARE_USER')
-            ),
-            password=dict(
-                type='str', no_log=True,
-                default=os.environ.get('VMWARE_PASSWORD')
-            ),
-            state=dict(
-                required=False,
-                choices=[
-                    'poweredon',
-                    'poweredoff',
-                    'present',
-                    'absent',
-                    'restarted',
-                    'suspended',
-                    'shutdownguest',
-                    'rebootguest'
-                ],
-                default='present'),
+            hostname=dict(type='str', default=os.environ.get('VMWARE_HOST')),
+            username=dict(type='str', default=os.environ.get('VMWARE_USER')),
+            password=dict(type='str', default=os.environ.get('VMWARE_PASSWORD'), no_log=True),
+            state=dict(type='str', default='present',
+                       choices=['absent', 'poweredoff', 'poweredon', 'present', 'rebootguest', 'restarted', 'shutdownguest', 'suspended']),
             validate_certs=dict(type='bool', default=True),
-            template_src=dict(type='str', aliases=['template']),
+            template=dict(type='str', aliases=['template_src']),
             is_template=dict(type='bool', default=False),
             annotation=dict(type='str', aliases=['notes']),
             customvalues=dict(type='list', default=[]),
-            name=dict(required=True, type='str'),
+            name=dict(type='str', required=True),
             name_match=dict(type='str', default='first'),
             uuid=dict(type='str'),
             folder=dict(type='str', default='/vm'),
@@ -1317,19 +1326,15 @@ def main():
             esxi_hostname=dict(type='str'),
             cluster=dict(type='str'),
             wait_for_ip_address=dict(type='bool', default=False),
-            snapshot_src=dict(type='str', default=None),
+            snapshot_src=dict(type='str'),
             linked_clone=dict(type='bool', default=False),
             networks=dict(type='list', default=[]),
             resource_pool=dict(type='str'),
-            customization=dict(type='dict', no_log=True, default={}),
+            customization=dict(type='dict', default={}, no_log=True),
         ),
         supports_check_mode=True,
         mutually_exclusive=[
-            ['esxi_hostname', 'cluster'],
-        ],
-        required_together=[
-            ['state', 'force'],
-            ['template'],
+            ['cluster', 'esxi_hostname'],
         ],
     )
 
@@ -1341,10 +1346,9 @@ def main():
     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'])
+    vm = pyv.getvm(name=module.params['name'], folder=module.params['folder'], uuid=module.params['uuid'])
 
     # VM already exists
     if vm:
@@ -1372,9 +1376,6 @@ def main():
             # Create it ...
             result = pyv.deploy_vm()
 
-    if 'failed' not in result:
-        result['failed'] = False
-
     if result['failed']:
         module.fail_json(**result)
     else:


Test patch:
diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt
--- a/test/sanity/pep8/legacy-files.txt
+++ b/test/sanity/pep8/legacy-files.txt
@@ -299,7 +299,6 @@ lib/ansible/modules/cloud/vmware/vca_nat.py
 lib/ansible/modules/cloud/vmware/vca_vapp.py
 lib/ansible/modules/cloud/vmware/vmware_cluster.py
 lib/ansible/modules/cloud/vmware/vmware_dvswitch.py
-lib/ansible/modules/cloud/vmware/vmware_guest.py
 lib/ansible/modules/cloud/vmware/vmware_local_user_manager.py
 lib/ansible/modules/cloud/vmware/vmware_migrate_vmk.py
 lib/ansible/modules/cloud/vmware/vmware_target_canonical_facts.py