Repository: ansible/ansible

Issue:
vmware_guest: valid request fails with "Access to resource settings is restricted"
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and devel branch are affected too.
Always add information AFTER of these html comments. -->

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
 - Bug Report

##### COMPONENT NAME
vmware_guest

##### ANSIBLE VERSION
<!--- Paste, BELOW THIS COMMENT, verbatim output from "ansible --version" between quotes below -->
```
  config file = None
  configured module search path = [u'/home/app021/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python2.7/dist-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.14 (default, Sep 23 2017, 22:06:14) [GCC 7.2.0]
```

##### CONFIGURATION
<!--- If using Ansible 2.4 or above, paste, BELOW THIS COMMENT, the results of "ansible-config dump --only-changed"
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).-->
Default.

##### OS / ENVIRONMENT
<!--- Mention, BELOW THIS COMMENT, the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.-->
Ubuntu Server 17.10
ESXi 6.0.0/vCenter Server 6.0.0

##### SUMMARY
When ESXi/vCenter is configured in particular way, valid change in vm configuration fails. 
Same change works with Ansible 2.4.x.
##### STEPS TO REPRODUCE
<!--- For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used. -->
ESXi must allow direct access, and resource allocation must be controlled by vCenter (so it is not possible to change it on ESXi, must be changed on vCenter)
Run vmware_guest module to change vm properties other than resource allocation (cpu/mem in this case). Use ESXi address/credentials to connect, **NOT** vCenter ones. Example playbook listed below.
It also fails same way when module is called directly with ansible args:
python /usr/local/lib/python2.7/dist-packages/ansible/modules/cloud/vmware/vmware_guest.py ansible_args.json
(Example Ansible args also pasted below)
<!--- Paste example playbooks or commands between quotes below -->
```yaml
- hosts: localhost
  gather_facts: false
  tasks:
    - vmware_guest:
        username: root
        password: apassword
        hostname: <esxi address>
        name: "a-vm-name"
        hardware:
          memory_mb: 4096
          num_cpus: 6
        state: present
```

```
{
    "ANSIBLE_MODULE_ARGS": {
        "datacenter": "ha-datacenter",
        "hardware": {
            "memory_mb": "4096",
            "num_cpus": "4"
        },
        "hostname": "<esxi address>",
        "name": "a-vm-name",
        "password": "apassword",
        "state": "present",
        "username": "root"
    }
}
```
<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
Configuration is changed properly. It works this way with Ansible 2.4
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
Fails with:
<!--- Paste verbatim command output between quotes below -->
```
 "Access to resource settings on the host is restricted to the server that is managing it: '<vcenter address>'."
```
This is caused by adding empty `vim.ResourceAllocationInfo()` and `vim.ResourceAllocationInfo()` to `vim.vm.ConfigSpec()` used for requesting change in cpu/mem, even though there is no resource configuration specified in vmware_guest call. 
It is empty request but it seems to trigger permission check anyway, failing whole request.
I have altered the code by removing empty requests and it works.


Discussion:
Files identified in the description:
* [lib/ansible/modules/cloud/vmware/vmware_guest.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/vmware/vmware_guest.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @Akasurde @akasurde @dav1x @jctanner @nerzhul @pdellaert
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
@piotrplazienski Thanks for reporting this issue. Would you like to raise a PR for with your fix ? 

needs_info
Yes, I will be raising PR to fix this, just need to setup all dev and test environment for ansible and familiarize myself with it, also with development guidelines.
cc @warthog9
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
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
@@ -672,8 +672,9 @@ def configure_resource_alloc_info(self, vm_obj):
         :param vm_obj: VM object in case of reconfigure, None in case of deploy
         :return: None
         """
-        self.configspec.memoryAllocation = vim.ResourceAllocationInfo()
-        self.configspec.cpuAllocation = vim.ResourceAllocationInfo()
+        rai_change_detected = False
+        memory_allocation = vim.ResourceAllocationInfo()
+        cpu_allocation = vim.ResourceAllocationInfo()
 
         if 'hardware' in self.params:
             if 'mem_limit' in self.params['hardware']:
@@ -682,9 +683,9 @@ def configure_resource_alloc_info(self, vm_obj):
                     mem_limit = int(self.params['hardware'].get('mem_limit'))
                 except ValueError as e:
                     self.module.fail_json(msg="hardware.mem_limit attribute should be an integer value.")
-                self.configspec.memoryAllocation.limit = mem_limit
-                if vm_obj is None or self.configspec.memoryAllocation.limit != vm_obj.config.memoryAllocation.limit:
-                    self.change_detected = True
+                memory_allocation.limit = mem_limit
+                if vm_obj is None or memory_allocation.limit != vm_obj.config.memoryAllocation.limit:
+                    rai_change_detected = True
 
             if 'mem_reservation' in self.params['hardware']:
                 mem_reservation = None
@@ -693,10 +694,10 @@ def configure_resource_alloc_info(self, vm_obj):
                 except ValueError as e:
                     self.module.fail_json(msg="hardware.mem_reservation should be an integer value.")
 
-                self.configspec.memoryAllocation.reservation = mem_reservation
+                memory_allocation.reservation = mem_reservation
                 if vm_obj is None or \
-                        self.configspec.memoryAllocation.reservation != vm_obj.config.memoryAllocation.reservation:
-                    self.change_detected = True
+                        memory_allocation.reservation != vm_obj.config.memoryAllocation.reservation:
+                    rai_change_detected = True
 
             if 'cpu_limit' in self.params['hardware']:
                 cpu_limit = None
@@ -704,9 +705,9 @@ def configure_resource_alloc_info(self, vm_obj):
                     cpu_limit = int(self.params['hardware'].get('cpu_limit'))
                 except ValueError as e:
                     self.module.fail_json(msg="hardware.cpu_limit attribute should be an integer value.")
-                self.configspec.cpuAllocation.limit = cpu_limit
-                if vm_obj is None or self.configspec.cpuAllocation.limit != vm_obj.config.cpuAllocation.limit:
-                    self.change_detected = True
+                cpu_allocation.limit = cpu_limit
+                if vm_obj is None or cpu_allocation.limit != vm_obj.config.cpuAllocation.limit:
+                    rai_change_detected = True
 
             if 'cpu_reservation' in self.params['hardware']:
                 cpu_reservation = None
@@ -714,10 +715,15 @@ def configure_resource_alloc_info(self, vm_obj):
                     cpu_reservation = int(self.params['hardware'].get('cpu_reservation'))
                 except ValueError as e:
                     self.module.fail_json(msg="hardware.cpu_reservation should be an integer value.")
-                self.configspec.cpuAllocation.reservation = cpu_reservation
+                cpu_allocation.reservation = cpu_reservation
                 if vm_obj is None or \
-                        self.configspec.cpuAllocation.reservation != vm_obj.config.cpuAllocation.reservation:
-                    self.change_detected = True
+                        cpu_allocation.reservation != vm_obj.config.cpuAllocation.reservation:
+                    rai_change_detected = True
+
+        if rai_change_detected:
+            self.configspec.memoryAllocation = memory_allocation
+            self.configspec.cpuAllocation = cpu_allocation
+            self.change_detected = True
 
     def configure_cpu_and_memory(self, vm_obj, vm_creation=False):
         # set cpu/memory/etc


Test patch: