Repository: ansible-collections/community.vmware

Issue:
vmware_guest_disk: add support for NVME and SATA disk controller type
<!--- Verify first that your feature was not already discussed on GitHub -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
<!--- Describe the new feature/improvement briefly below -->

##### ISSUE TYPE
- Feature Idea

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->
vmware_guest_disk

##### ADDITIONAL INFORMATION
<!--- Describe how the feature would be used, why it is needed and what it would solve -->
We want to configure disks attached to other controller types using vmware_guest_disk module.

<!--- Paste example playbooks or commands between quotes below -->
```yaml

```

<!--- HINT: You can also paste gist.github.com links for larger files -->



Discussion:
Hi @Akasurde, I want to make some changes on "vmware_guest_disk" module to support NVME and SATA controllers. What do you think? Thanks.
> I want to make some changes on "vmware_guest_disk" module to support NVME and SATA controllers. What do you think?

@Akasurde didn't object, so I'd say he agreed implicitly. Go ahead! Personally, I think it would be great if you would implement this.



@Tomorrow9 Now that #201 is merged, I think you should close this issue. Or is there anything left to be implemented?
@mariolenz I think this module should handle SATA and NVMe disks as well. And vmware_guest can not remove disks, it can add and reconfigure disks.
> I think this module should handle SATA and NVMe disks as well. And vmware_guest can not remove disks, it can add and reconfigure disks.

Sorry, my mistake... I've just realized that this issue is about vmware_guest **_disk** but the PR implemented SATA and NVME disk controller types for **vmware_guest**. I've overlooked that they are both about SATA and NVME, but address different modules.

You're absolutely right, there's no reason to close this issue. Sorry again :-/
No need to be sorry:), there are some functions in these two modules duplicated, I also had the same concern, but considering new VM creation in "vmware_guest" module also need other types of disk controllers, so add this support in it firstly. Thanks.

Resolving patch:
diff --git a/plugins/modules/vmware_guest_disk.py b/plugins/modules/vmware_guest_disk.py
--- a/plugins/modules/vmware_guest_disk.py
+++ b/plugins/modules/vmware_guest_disk.py
@@ -68,86 +68,136 @@
      type: bool
    disk:
      description:
-     - A list of disks to add.
+     - A list of disks to add or remove.
      - The virtual disk related information is provided using this list.
      - All values and parameters are case sensitive.
-     - 'Valid attributes are:'
-     - ' - C(size[_tb,_gb,_mb,_kb]) (integer): Disk storage size in specified unit.'
-     - '   If C(size) specified then unit must be specified. There is no space allowed in between size number and unit.'
-     - '   Only first occurrence in disk element will be considered, even if there are multiple size* parameters available.'
-     - ' - C(type) (string): Valid values are:'
-     - '     - C(thin) thin disk'
-     - '     - C(eagerzeroedthick) eagerzeroedthick disk'
-     - '     - C(thick) thick disk'
-     - '     Default: C(thick) thick disk, no eagerzero.'
-     - ' - C(disk_mode) (string): Type of disk mode. Valid values are:'
-     - '     - C(persistent) Changes are immediately and permanently written to the virtual disk. This is default.'
-     - '     - C(independent_persistent) Same as persistent, but not affected by snapshots.'
-     - '     - C(independent_nonpersistent) Changes to virtual disk are made to a redo log and discarded at power off, but not affected by snapshots.'
-     - ' - C(sharing) (bool): The sharing mode of the virtual disk. The default value is no sharing.'
-     - '   Setting C(sharing) means that multiple virtual machines can write to the virtual disk.'
-     - '   Sharing can only be set if C(type) is C(eagerzeroedthick).'
-     - ' - C(datastore) (string): Name of datastore or datastore cluster to be used for the disk.'
-     - ' - C(autoselect_datastore) (bool): Select the less used datastore. Specify only if C(datastore) is not specified.'
-     - ' - C(scsi_controller) (integer): SCSI controller number. Valid value range from 0 to 3.'
-     - '   Only 4 SCSI controllers are allowed per VM.'
-     - '   Care should be taken while specifying C(scsi_controller) is 0 and C(unit_number) as 0 as this disk may contain OS.'
-     - ' - C(unit_number) (integer): Disk Unit Number. Valid value range from 0 to 15. Only 15 disks are allowed per SCSI Controller.'
-     - ' - C(scsi_type) (string): Type of SCSI controller. This value is required only for the first occurrence of SCSI Controller.'
-     - '   This value is ignored, if SCSI Controller is already present or C(state) is C(absent).'
-     - '   Valid values are C(buslogic), C(lsilogic), C(lsilogicsas) and C(paravirtual).'
-     - '   C(paravirtual) is default value for this parameter.'
-     - ' - C(destroy) (bool): If C(state) is C(absent), make sure the disk file is deleted from the datastore (default C(yes)).'
-     - '   Added in version 2.10.'
-     - ' - C(filename) (string): Existing disk image to be used. Filename must already exist on the datastore.'
-     - '   Specify filename string in C([datastore_name] path/to/file.vmdk) format. Added in version 2.10.'
-     - ' - C(state) (string): State of disk. This is either "absent" or "present".'
-     - '   If C(state) is set to C(absent), disk will be removed permanently from virtual machine configuration and from VMware storage.'
-     - '   If C(state) is set to C(present), disk will be added if not present at given SCSI Controller and Unit Number.'
-     - '   If C(state) is set to C(present) and disk exists with different size, disk size is increased.'
-     - '   Reducing disk size is not allowed.'
      suboptions:
-       iolimit:
+       size:
+         description:
+           - Disk storage size.
+           - If size specified then unit must be specified. There is no space allowed in between size number and unit.
+           - Only first occurrence in disk element will be considered, even if there are multiple size* parameters available.
+         type: str
+       size_kb:
+         description: Disk storage size in kb.
+         type: int
+       size_mb:
+         description: Disk storage size in mb.
+         type: int
+       size_gb:
+         description: Disk storage size in gb.
+         type: int
+       size_tb:
+         description: Disk storage size in tb.
+         type: int
+       type:
+         description: The type of disk, if not specified then use C(thick) type for new disk, no eagerzero.
+         type: str
+         choices: ['thin', 'eagerzeroedthick', 'thick']
+       disk_mode:
+         description:
+           - Type of disk mode. If not specified then use C(persistent) mode for new disk.
+           - If set to 'persistent' mode, changes are immediately and permanently written to the virtual disk.
+           - If set to 'independent_persistent' mode, same as persistent, but not affected by snapshots.
+           - If set to 'independent_nonpersistent' mode, changes to virtual disk are made to a redo log and discarded
+             at power off, but not affected by snapshots.
+         type: str
+         choices: ['persistent', 'independent_persistent', 'independent_nonpersistent']
+       sharing:
+         description:
+           - The sharing mode of the virtual disk.
+           - Setting sharing means that multiple virtual machines can write to the virtual disk.
+           - Sharing can only be set if C(type) is set to C(eagerzeroedthick).
+         type: bool
+         default: False
+       datastore:
+         description: Name of datastore or datastore cluster to be used for the disk.
+         type: str
+       autoselect_datastore:
+         description: Select the less used datastore. Specify only if C(datastore) is not specified.
+         type: bool
+       scsi_controller:
+         description:
+           - SCSI controller number. Only 4 SCSI controllers are allowed per VM.
+           - Care should be taken while specifying 'scsi_controller' is 0 and 'unit_number' as 0 as this disk may contain OS.
+         type: int
+         choices: [0, 1, 2, 3]
+       unit_number:
+         description:
+           - Disk Unit Number.
+           - Valid value range from 0 to 15, except 7 for SCSI Controller.
+           - Valid value range from 0 to 29 for SATA controller.
+           - Valid value range from 0 to 14 for NVME controller.
+         type: int
+         required: True
+       scsi_type:
+         description:
+           - Type of SCSI controller. This value is required only for the first occurrence of SCSI Controller.
+           - This value is ignored, if SCSI Controller is already present or C(state) is C(absent).
+         type: str
+         choices: ['buslogic', 'lsilogic', 'lsilogicsas', 'paravirtual']
+       destroy:
+         description: If C(state) is C(absent), make sure the disk file is deleted from the datastore. Added in version 2.10.
+         type: bool
+         default: True
+       filename:
+         description:
+           - Existing disk image to be used. Filename must already exist on the datastore.
+           - Specify filename string in C([datastore_name] path/to/file.vmdk) format. Added in version 2.10.
+         type: str
+       state:
+         description:
+           - State of disk.
+           - If set to 'absent', disk will be removed permanently from virtual machine configuration and from VMware storage.
+           - If set to 'present', disk will be added if not present at given Controller and Unit Number.
+           - or disk exists with different size, disk size is increased, reducing disk size is not allowed.
+         type: str
+         choices: ['present', 'absent']
+         default: 'present'
+       controller_type:
          description:
-           - Section specifies the shares and limit for storage I/O resource.
+           - This parameter is added for managing disks attaching other types of controllers, e.g., SATA or NVMe.
+           - If either C(controller_type) or C(scsi_type) is not specified, then use C(paravirtual) type.
+         type: str
+         choices: ['buslogic', 'lsilogic', 'lsilogicsas', 'paravirtual', 'sata', 'nvme']
+       controller_number:
+         description: This parameter is used with C(controller_type) for specifying controller bus number.
+         type: int
+         choices: [0, 1, 2, 3]
+       iolimit:
+         description: Section specifies the shares and limit for storage I/O resource.
          suboptions:
            limit:
-             description:
-               - Section specifies values for limit where the utilization of a virtual machine will not exceed, even if there are available resources.
+             description: Section specifies values for limit where the utilization of a virtual machine will not exceed, even if there are available resources.
+             type: int
            shares:
-             description:
-               - Specifies different types of shares user can add for the given disk.
+             description: Specifies different types of shares user can add for the given disk.
              suboptions:
                level:
-                 description:
-                   - Specifies different level for the shares section.
-                   - Valid values are low, normal, high, custom.
+                 description: Specifies different level for the shares section.
+                 type: str
+                 choices: ['low', 'normal', 'high', 'custom']
                level_value:
-                 description:
-                   - Custom value when C(level) is set as C(custom).
+                 description: Custom value when C(level) is set as C(custom).
                  type: int
-             type: list
-             elements: dict
+             type: dict
+         type: dict
        shares:
-         description:
-           - section for iolimit section tells about what are all different types of shares user can add for disk.
+         description: Section for iolimit section tells about what are all different types of shares user can add for disk.
          suboptions:
            level:
-             description:
-               - tells about different level for the shares section, valid values are low,normal,high,custom.
+             description: Tells about different level for the shares section.
              type: str
+             choices: ['low', 'normal', 'high', 'custom']
            level_value:
-             description:
-               - custom value when level is set as custom.
+             description: Custom value when C(level) is set as C(custom).
              type: int
-         type: list
-         elements: dict
+         type: dict
      default: []
      type: list
      elements: dict
 extends_documentation_fragment:
 - community.vmware.vmware.documentation
-
 '''
 
 EXAMPLES = r'''
@@ -271,6 +321,33 @@
         destroy: no
   delegate_to: localhost
   register: disk_facts
+
+- name: Add disks to virtual machine using UUID to SATA and NVMe controller
+  community.vmware.vmware_guest_disk:
+    hostname: "{{ vcenter_hostname }}"
+    username: "{{ vcenter_username }}"
+    password: "{{ vcenter_password }}"
+    datacenter: "{{ datacenter_name }}"
+    validate_certs: no
+    uuid: 421e4592-c069-924d-ce20-7e7533fab926
+    disk:
+      - size_mb: 256
+        type: thin
+        datastore: datacluster0
+        state: present
+        controller_type: sata
+        controller_number: 1
+        unit_number: 1
+        disk_mode: 'persistent'
+      - size_gb: 1
+        state: present
+        autoselect_datastore: True
+        controller_type: nvme
+        controller_number: 2
+        unit_number: 3
+        disk_mode: 'independent_persistent'
+  delegate_to: localhost
+  register: disk_facts
 '''
 
 RETURN = r'''
@@ -304,9 +381,11 @@
 except ImportError:
     pass
 
+from random import randint
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils._text import to_native
-from ansible_collections.community.vmware.plugins.module_utils.vmware import PyVmomi, vmware_argument_spec, wait_for_task, find_obj, get_all_objs
+from ansible_collections.community.vmware.plugins.module_utils.vmware import PyVmomi, vmware_argument_spec,\
+    wait_for_task, find_obj, get_all_objs, get_parent_datacenter
 
 
 class PyVmomiHelper(PyVmomi):
@@ -318,6 +397,8 @@ def __init__(self, module):
                                      paravirtual=vim.vm.device.ParaVirtualSCSIController,
                                      buslogic=vim.vm.device.VirtualBusLogicController,
                                      lsilogicsas=vim.vm.device.VirtualLsiLogicSASController)
+        self.ctl_device_type = self.scsi_device_type.copy()
+        self.ctl_device_type.update({'sata': vim.vm.device.VirtualAHCIController, 'nvme': vim.vm.device.VirtualNVMEController})
         self.config_spec = vim.vm.ConfigSpec()
         self.config_spec.deviceChange = []
 
@@ -334,24 +415,52 @@ def create_scsi_controller(self, scsi_type, scsi_bus_number):
         scsi_ctl = vim.vm.device.VirtualDeviceSpec()
         scsi_ctl.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
         scsi_ctl.device = self.scsi_device_type[scsi_type]()
-        scsi_ctl.device.unitNumber = 3
+        scsi_ctl.device.deviceInfo = vim.Description()
         scsi_ctl.device.busNumber = scsi_bus_number
-        scsi_ctl.device.hotAddRemove = True
         scsi_ctl.device.sharedBus = 'noSharing'
+        scsi_ctl.device.hotAddRemove = True
+        scsi_ctl.device.key = -randint(1000, 9999)
         scsi_ctl.device.scsiCtlrUnitNumber = 7
 
         return scsi_ctl
 
+    def create_sata_controller(self, sata_bus_number):
+        sata_ctl = vim.vm.device.VirtualDeviceSpec()
+        sata_ctl.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
+        sata_ctl.device = vim.vm.device.VirtualAHCIController()
+        sata_ctl.device.deviceInfo = vim.Description()
+        sata_ctl.device.busNumber = sata_bus_number
+        sata_ctl.device.key = -randint(15000, 19999)
+
+        return sata_ctl
+
+    def create_nvme_controller(self, nvme_bus_number):
+        nvme_ctl = vim.vm.device.VirtualDeviceSpec()
+        nvme_ctl.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
+        nvme_ctl.device = vim.vm.device.VirtualNVMEController()
+        nvme_ctl.device.deviceInfo = vim.Description()
+        nvme_ctl.device.busNumber = nvme_bus_number
+        nvme_ctl.device.key = -randint(31000, 39999)
+
+        return nvme_ctl
+
+    def find_disk_by_key(self, disk_key, disk_unit_number):
+        found_disk = None
+        for device in self.vm.config.hardware.device:
+            if isinstance(device, vim.vm.device.VirtualDisk) and device.key == disk_key:
+                if device.unitNumber == disk_unit_number:
+                    found_disk = device
+                    break
+
+        return found_disk
+
     @staticmethod
-    def create_scsi_disk(scsi_ctl_key, disk_index, disk_mode, disk_filename, sharing):
+    def create_disk(ctl_key, disk):
         """
         Create Virtual Device Spec for virtual disk
         Args:
-            scsi_ctl_key: Unique SCSI Controller Key
-            disk_index: Disk unit number at which disk needs to be attached
-            disk_mode: Disk mode
-            sharing: Disk sharing mode
-            disk_filename: Path to the disk file on the datastore
+            ctl_key: Unique SCSI Controller Key
+            disk: The disk configurations dict
 
         Returns: Virtual Device Spec for virtual disk
 
@@ -360,15 +469,14 @@ def create_scsi_disk(scsi_ctl_key, disk_index, disk_mode, disk_filename, sharing
         disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
         disk_spec.device = vim.vm.device.VirtualDisk()
         disk_spec.device.backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo()
-        disk_spec.device.backing.diskMode = disk_mode
-        disk_spec.device.backing.sharing = sharing
-        disk_spec.device.controllerKey = scsi_ctl_key
-        disk_spec.device.unitNumber = disk_index
-
-        if disk_filename is not None:
-            disk_spec.device.backing.fileName = disk_filename
-        else:
-            disk_spec.fileOperation = vim.vm.device.VirtualDeviceSpec.FileOperation.create
+        disk_spec.device.backing.diskMode = disk['disk_mode']
+        disk_spec.device.backing.sharing = disk['sharing']
+        disk_spec.device.controllerKey = ctl_key
+        disk_spec.device.unitNumber = disk['disk_unit_number']
+        if disk['disk_type'] == 'thin':
+            disk_spec.device.backing.thinProvisioned = True
+        elif disk['disk_type'] == 'eagerzeroedthick':
+            disk_spec.device.backing.eagerlyScrub = True
 
         return disk_spec
 
@@ -388,7 +496,7 @@ def reconfigure_vm(self, config_spec, device_type):
             task = self.vm.ReconfigVM_Task(spec=config_spec)
             changed, results = wait_for_task(task)
         except vim.fault.InvalidDeviceSpec as invalid_device_spec:
-            self.module.fail_json(msg="Failed to manage %s on given virtual machine due to invalid"
+            self.module.fail_json(msg="Failed to manage '%s' on given virtual machine due to invalid"
                                       " device spec : %s" % (device_type, to_native(invalid_device_spec.msg)),
                                   details="Please check ESXi server logs for more details.")
         except vim.fault.RestrictedVersion as e:
@@ -448,107 +556,131 @@ def ensure_disks(self, vm_obj=None):
         """
         # Set vm object
         self.vm = vm_obj
+        vm_files_datastore = self.vm.config.files.vmPathName.split(' ')[0].strip('[]')
         # Sanitize user input
         disk_data = self.sanitize_disk_inputs()
-        # Create stateful information about SCSI devices
-        current_scsi_info = dict()
+        ctl_changed = False
+        disk_change_list = list()
         results = dict(changed=False, disk_data=None, disk_changes=dict())
+        new_added_disk_ctl = list()
 
-        # Deal with SCSI Controller
-        for device in vm_obj.config.hardware.device:
-            if isinstance(device, tuple(self.scsi_device_type.values())):
-                # Found SCSI device
-                if device.busNumber not in current_scsi_info:
-                    device_bus_number = 1000 + device.busNumber
-                    current_scsi_info[device_bus_number] = dict(disks=dict())
-
-        scsi_changed = False
+        # Deal with controller
         for disk in disk_data:
-            scsi_controller = disk['scsi_controller'] + 1000
-            if scsi_controller not in current_scsi_info and disk['state'] == 'present':
-                scsi_ctl = self.create_scsi_controller(disk['scsi_type'], disk['scsi_controller'])
-                current_scsi_info[scsi_controller] = dict(disks=dict())
-                self.config_spec.deviceChange.append(scsi_ctl)
-                scsi_changed = True
-        if scsi_changed:
-            self.reconfigure_vm(self.config_spec, 'SCSI Controller')
+            ctl_found = False
+            # check if disk controller is in the new adding queue
+            for new_ctl in new_added_disk_ctl:
+                if new_ctl['controller_type'] == disk['controller_type'] and new_ctl['controller_number'] == disk['controller_number']:
+                    ctl_found = True
+                    break
+            # check if disk controller already exists
+            if not ctl_found:
+                for device in self.vm.config.hardware.device:
+                    if isinstance(device, self.ctl_device_type[disk['controller_type']]):
+                        if device.busNumber == disk['controller_number']:
+                            ctl_found = True
+                            break
+            # create disk controller when not found and disk state is present
+            if not ctl_found and disk['state'] == 'present':
+                # Create new controller
+                if disk['controller_type'] in self.scsi_device_type.keys():
+                    ctl_spec = self.create_scsi_controller(disk['controller_type'], disk['controller_number'])
+                elif disk['controller_type'] == 'sata':
+                    ctl_spec = self.create_sata_controller(disk['controller_number'])
+                elif disk['controller_type'] == 'nvme':
+                    ctl_spec = self.create_nvme_controller(disk['controller_number'])
+                new_added_disk_ctl.append({'controller_type': disk['controller_type'], 'controller_number': disk['controller_number']})
+                ctl_changed = True
+                self.config_spec.deviceChange.append(ctl_spec)
+            elif not ctl_found and disk['state'] == 'absent':
+                self.module.fail_json(msg="Not found 'controller_type': '%s', 'controller_number': '%s', so can not"
+                                          " remove this disk, please make sure 'controller_type' and"
+                                          " 'controller_number' are correct." % (disk['controller_type'], disk['controller_number']))
+        if ctl_changed:
+            self.reconfigure_vm(self.config_spec, 'Disk Controller')
             self.config_spec = vim.vm.ConfigSpec()
             self.config_spec.deviceChange = []
 
         # Deal with Disks
-        for device in vm_obj.config.hardware.device:
-            if isinstance(device, vim.vm.device.VirtualDisk):
-                # Found Virtual Disk device
-                if device.controllerKey not in current_scsi_info:
-                    current_scsi_info[device.controllerKey] = dict(disks=dict())
-                current_scsi_info[device.controllerKey]['disks'][device.unitNumber] = device
-
-        disk_change_list = []
         for disk in disk_data:
+            disk_found = False
             disk_change = False
-            scsi_controller = disk['scsi_controller'] + 1000  # VMware auto assign 1000 + SCSI Controller
-            if disk['disk_unit_number'] not in current_scsi_info[scsi_controller]['disks'] and disk['state'] == 'present':
-                # Add new disk
-                disk_spec = self.create_scsi_disk(scsi_controller, disk['disk_unit_number'], disk['disk_mode'], disk['filename'], disk['sharing'])
-                if disk['filename'] is None:
-                    disk_spec.device.capacityInKB = disk['size']
-                if disk['disk_type'] == 'thin':
-                    disk_spec.device.backing.thinProvisioned = True
-                elif disk['disk_type'] == 'eagerzeroedthick':
-                    disk_spec.device.backing.eagerlyScrub = True
-                # get Storage DRS recommended datastore from the datastore cluster
-                if disk['datastore_cluster'] is not None:
-                    datastore_name = self.get_recommended_datastore(datastore_cluster_obj=disk['datastore_cluster'], disk_spec_obj=disk_spec)
-                    disk['datastore'] = find_obj(self.content, [vim.Datastore], datastore_name)
-                if disk['filename'] is not None:
-                    disk_spec.device.backing.fileName = disk['filename']
-                disk_spec.device.backing.datastore = disk['datastore']
-                disk_spec.device.backing.sharing = disk['sharing']
-                disk_spec = self.get_ioandshares_diskconfig(disk_spec, disk)
-                self.config_spec.deviceChange.append(disk_spec)
-                disk_change = True
-                current_scsi_info[scsi_controller]['disks'][disk['disk_unit_number']] = disk_spec.device
-                results['disk_changes'][disk['disk_index']] = "Disk created."
-            elif disk['disk_unit_number'] in current_scsi_info[scsi_controller]['disks']:
-                if disk['state'] == 'present':
-                    disk_spec = vim.vm.device.VirtualDeviceSpec()
-                    # set the operation to edit so that it knows to keep other settings
-                    disk_spec.device = current_scsi_info[scsi_controller]['disks'][disk['disk_unit_number']]
-                    # Edit and no resizing allowed
-                    if disk['size'] < disk_spec.device.capacityInKB:
-                        self.module.fail_json(msg="Given disk size at disk index [%s] is smaller than found (%d < %d)."
-                                                  "Reducing disks is not allowed." % (disk['disk_index'],
-                                                                                      disk['size'],
-                                                                                      disk_spec.device.capacityInKB))
-                    if disk['size'] != disk_spec.device.capacityInKB:
-                        disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
+            ctl_found = False
+            for device in self.vm.config.hardware.device:
+                if isinstance(device, self.ctl_device_type[disk['controller_type']]) and device.busNumber == disk['controller_number']:
+                    for disk_key in device.device:
+                        disk_device = self.find_disk_by_key(disk_key, disk['disk_unit_number'])
+                        if disk_device is not None:
+                            disk_found = True
+                            if disk['state'] == 'present':
+                                disk_spec = vim.vm.device.VirtualDeviceSpec()
+                                # set the operation to edit so that it knows to keep other settings
+                                disk_spec.device = disk_device
+                                # Edit and no resizing allowed
+                                if disk['size'] < disk_spec.device.capacityInKB:
+                                    self.module.fail_json(msg="Given disk size at disk index [%s] is smaller than found"
+                                                              " (%d < %d). Reducing disks is not allowed."
+                                                              % (disk['disk_index'], disk['size'],
+                                                                 disk_spec.device.capacityInKB))
+                                if disk['size'] != disk_spec.device.capacityInKB:
+                                    disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
+                                    disk_spec = self.get_ioandshares_diskconfig(disk_spec, disk)
+                                    disk_spec.device.capacityInKB = disk['size']
+                                    self.config_spec.deviceChange.append(disk_spec)
+                                    disk_change = True
+                                    disk_change_list.append(disk_change)
+                                    results['disk_changes'][disk['disk_index']] = "Disk reconfigured."
+                            elif disk['state'] == 'absent':
+                                # Disk already exists, deleting
+                                disk_spec = vim.vm.device.VirtualDeviceSpec()
+                                disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.remove
+                                if disk['destroy'] is True:
+                                    disk_spec.fileOperation = vim.vm.device.VirtualDeviceSpec.FileOperation.destroy
+                                disk_spec.device = disk_device
+                                self.config_spec.deviceChange.append(disk_spec)
+                                disk_change = True
+                                disk_change_list.append(disk_change)
+                                results['disk_changes'][disk['disk_index']] = "Disk deleted."
+                            break
+                    if disk_found:
+                        break
+                    if not disk_found and disk['state'] == 'present':
+                        # Add new disk
+                        disk_spec = self.create_disk(device.key, disk)
+                        # get Storage DRS recommended datastore from the datastore cluster
+                        if disk['filename'] is None:
+                            if disk['datastore_cluster'] is not None:
+                                datastore_name = self.get_recommended_datastore(datastore_cluster_obj=disk['datastore_cluster'], disk_spec_obj=disk_spec)
+                                disk['datastore'] = find_obj(self.content, [vim.Datastore], datastore_name)
+                            disk_spec.fileOperation = vim.vm.device.VirtualDeviceSpec.FileOperation.create
+                            disk_spec.device.capacityInKB = disk['size']
+                            # Set backing filename when datastore is configured and not the same as VM datastore
+                            # If datastore is not configured or backing filename is not set, default is VM datastore
+                            if disk['datastore'] is not None and disk['datastore'].name != vm_files_datastore:
+                                disk_spec.device.backing.datastore = disk['datastore']
+                                disk_spec.device.backing.fileName = "[%s] %s/%s_%s_%s_%s.vmdk" % (disk['datastore'].name,
+                                                                                                  self.vm.name,
+                                                                                                  self.vm.name,
+                                                                                                  device.key,
+                                                                                                  str(disk['disk_unit_number']),
+                                                                                                  str(randint(1, 10000)))
+                        elif disk['filename'] is not None:
+                            disk_spec.device.backing.fileName = disk['filename']
                         disk_spec = self.get_ioandshares_diskconfig(disk_spec, disk)
-                        disk_spec.device.capacityInKB = disk['size']
                         self.config_spec.deviceChange.append(disk_spec)
                         disk_change = True
-                        results['disk_changes'][disk['disk_index']] = "Disk size increased."
-                    else:
-                        results['disk_changes'][disk['disk_index']] = "Disk already exists."
-
-                elif disk['state'] == 'absent':
-                    # Disk already exists, deleting
-                    disk_spec = vim.vm.device.VirtualDeviceSpec()
-                    disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.remove
-                    if disk['destroy'] is True:
-                        disk_spec.fileOperation = vim.vm.device.VirtualDeviceSpec.FileOperation.destroy
-                    disk_spec.device = current_scsi_info[scsi_controller]['disks'][disk['disk_unit_number']]
-                    self.config_spec.deviceChange.append(disk_spec)
-                    disk_change = True
-                    results['disk_changes'][disk['disk_index']] = "Disk deleted."
-
+                        disk_change_list.append(disk_change)
+                        results['disk_changes'][disk['disk_index']] = "Disk created."
+                        break
+                    if not disk_found and disk['state'] == 'absent':
+                        self.module.fail_json(msg="Not found disk with 'controller_type': '%s',"
+                                                  " 'controller_number': '%s', 'unit_number': '%s' to remove."
+                                                  % (disk['controller_type'], disk['controller_number'], disk['disk_unit_number']))
             if disk_change:
                 # Adding multiple disks in a single attempt raises weird errors
                 # So adding single disk at a time.
                 self.reconfigure_vm(self.config_spec, 'disks')
                 self.config_spec = vim.vm.ConfigSpec()
                 self.config_spec.deviceChange = []
-            disk_change_list.append(disk_change)
-
         if any(disk_change_list):
             results['changed'] = True
         results['disk_data'] = self.gather_disk_facts(vm_obj=self.vm)
@@ -562,8 +694,8 @@ def sanitize_disk_inputs(self):
         """
         disks_data = list()
         if not self.desired_disks:
-            self.module.exit_json(changed=False, msg="No disks provided for virtual"
-                                                     " machine '%s' for management." % self.vm.name)
+            self.module.exit_json(changed=False, msg="No disks provided for virtual machine '%s' for management."
+                                                     % self.vm.name)
 
         for disk_index, disk in enumerate(self.desired_disks):
             # Initialize default value for disk
@@ -575,33 +707,76 @@ def sanitize_disk_inputs(self):
                                 datastore=None,
                                 autoselect_datastore=True,
                                 disk_unit_number=0,
-                                scsi_controller=0,
+                                controller_number=0,
                                 disk_mode='persistent',
+                                disk_type='thick',
                                 sharing=False)
             # Check state
-            if 'state' in disk:
-                if disk['state'] not in ['absent', 'present']:
-                    self.module.fail_json(msg="Invalid state provided '%s' for disk index [%s]."
-                                              " State can be either - 'absent', 'present'" % (disk['state'],
-                                                                                              disk_index))
-                else:
-                    current_disk['state'] = disk['state']
+            if disk['state'] is not None:
+                current_disk['state'] = disk['state']
+
+            # Check controller type
+            if disk['scsi_type'] is not None and disk['controller_type'] is None:
+                current_disk['controller_type'] = disk['scsi_type']
+            elif disk['scsi_type'] is None and disk['controller_type'] is None:
+                current_disk['controller_type'] = 'paravirtual'
+            elif disk['controller_type'] is not None and disk['scsi_type'] is None:
+                current_disk['controller_type'] = disk['controller_type']
+            else:
+                self.module.fail_json(msg="Please specify either 'scsi_type' or 'controller_type' for disk index [%s]."
+                                          % disk_index)
+
+            # Check controller bus number
+            if disk['scsi_controller'] is not None and disk['controller_number'] is None and disk['controller_type'] is None:
+                temp_disk_controller = disk['scsi_controller']
+            elif disk['controller_number'] is not None and disk['scsi_controller'] is None and disk['scsi_type'] is None:
+                temp_disk_controller = disk['controller_number']
+            else:
+                self.module.fail_json(msg="Please specify 'scsi_controller' with 'scsi_type', or 'controller_number'"
+                                          " with 'controller_type' under disk parameter for disk index [%s], which is"
+                                          " required while creating or configuring disk." % disk_index)
+            try:
+                disk_controller = int(temp_disk_controller)
+            except ValueError:
+                self.module.fail_json(msg="Invalid controller bus number '%s' specified"
+                                          " for disk index [%s]" % (temp_disk_controller, disk_index))
+            current_disk['controller_number'] = disk_controller
+
+            try:
+                temp_disk_unit_number = int(disk['unit_number'])
+            except ValueError:
+                self.module.fail_json(msg="Invalid Disk unit number ID '%s' specified at index [%s]."
+                                          % (disk['unit_number'], disk_index))
+            if current_disk['controller_type'] in self.scsi_device_type.keys():
+                if temp_disk_unit_number not in range(0, 16):
+                    self.module.fail_json(msg="Invalid Disk unit number ID specified for disk [%s] at index [%s],"
+                                              " please specify value between 0 to 15 only (excluding 7)."
+                                              % (temp_disk_unit_number, disk_index))
+                if temp_disk_unit_number == 7:
+                    self.module.fail_json(msg="Invalid Disk unit number ID specified for disk at index [%s], please"
+                                              " specify value other than 7 as it is reserved for SCSI Controller."
+                                              % disk_index)
+            elif current_disk['controller_type'] == 'sata' and temp_disk_unit_number not in range(0, 30):
+                self.module.fail_json(msg="Invalid Disk unit number ID specified for SATA disk [%s] at index [%s],"
+                                          " please specify value between 0 to 29" % (temp_disk_unit_number, disk_index))
+            elif current_disk['controller_type'] == 'nvme' and temp_disk_unit_number not in range(0, 15):
+                self.module.fail_json(msg="Invalid Disk unit number ID specified for NVMe disk [%s] at index [%s],"
+                                          " please specify value between 0 to 14" % (temp_disk_unit_number, disk_index))
+            current_disk['disk_unit_number'] = temp_disk_unit_number
 
             # By default destroy file from datastore if 'destroy' parameter is not provided
             if current_disk['state'] == 'absent':
                 current_disk['destroy'] = disk.get('destroy', True)
             elif current_disk['state'] == 'present':
                 # Select datastore or datastore cluster
-                if 'datastore' in disk:
-                    if 'autoselect_datastore' in disk:
-                        self.module.fail_json(msg="Please specify either 'datastore' "
-                                                  "or 'autoselect_datastore' for disk index [%s]" % disk_index)
-
+                if disk['datastore'] is not None:
+                    if disk['autoselect_datastore'] is not None:
+                        self.module.fail_json(msg="Please specify either 'datastore' or 'autoselect_datastore' for"
+                                                  " disk index [%s]" % disk_index)
                     # Check if given value is datastore or datastore cluster
                     datastore_name = disk['datastore']
                     datastore_cluster = find_obj(self.content, [vim.StoragePod], datastore_name)
                     datastore = find_obj(self.content, [vim.Datastore], datastore_name)
-
                     if datastore is None and datastore_cluster is None:
                         self.module.fail_json(msg="Failed to find datastore or datastore cluster named '%s' "
                                                   "in given configuration." % disk['datastore'])
@@ -609,14 +784,19 @@ def sanitize_disk_inputs(self):
                         # If user specified datastore cluster, keep track of that for determining datastore later
                         current_disk['datastore_cluster'] = datastore_cluster
                     elif datastore:
+                        ds_datacenter = get_parent_datacenter(datastore)
+                        if ds_datacenter.name != self.module.params['datacenter']:
+                            self.module.fail_json(msg="Get datastore '%s' in datacenter '%s', not the configured"
+                                                      " datacenter '%s'" % (datastore.name, ds_datacenter.name,
+                                                                            self.module.params['datacenter']))
                         current_disk['datastore'] = datastore
                     current_disk['autoselect_datastore'] = False
-                elif 'autoselect_datastore' in disk:
+                elif disk['autoselect_datastore'] is not None:
                     # Find datastore which fits requirement
                     datastores = get_all_objs(self.content, [vim.Datastore])
                     if not datastores:
-                        self.module.fail_json(msg="Failed to gather information about"
-                                                  " available datastores in given datacenter.")
+                        self.module.fail_json(msg="Failed to gather information about available datastores in given"
+                                                  " datacenter '%s'." % self.module.params['datacenter'])
                     datastore = None
                     datastore_freespace = 0
                     for ds in datastores:
@@ -626,18 +806,13 @@ def sanitize_disk_inputs(self):
                             datastore_freespace = ds.summary.freeSpace
                     current_disk['datastore'] = datastore
 
-                if 'datastore' not in disk and 'autoselect_datastore' not in disk and 'filename' not in disk:
-                    self.module.fail_json(msg="Either 'datastore' or 'autoselect_datastore' is"
-                                              " required parameter while creating disk for "
-                                              "disk index [%s]." % disk_index)
-
-                if 'filename' in disk:
+                if disk['filename'] is not None:
                     current_disk['filename'] = disk['filename']
 
-                if [x for x in disk.keys() if x.startswith('size_') or x == 'size']:
+                if [x for x in disk.keys() if ((x.startswith('size_') or x == 'size') and disk[x] is not None)]:
                     # size, size_tb, size_gb, size_mb, size_kb
                     disk_size_parse_failed = False
-                    if 'size' in disk:
+                    if disk['size'] is not None:
                         size_regex = re.compile(r'(\d+(?:\.\d+)?)([tgmkTGMK][bB])')
                         disk_size_m = size_regex.match(disk['size'])
                         if disk_size_m:
@@ -657,7 +832,7 @@ def sanitize_disk_inputs(self):
                     else:
                         # Even multiple size_ parameter provided by user,
                         # consider first value only
-                        param = [x for x in disk.keys() if x.startswith('size_')][0]
+                        param = [x for x in disk.keys() if (x.startswith('size_') and disk[x] is not None)][0]
                         unit = param.split('_')[-1]
                         disk_size = disk[param]
                         if isinstance(disk_size, (float, int)):
@@ -685,79 +860,25 @@ def sanitize_disk_inputs(self):
                         current_disk['size'] = expected * (1024 ** disk_units[unit])
                     else:
                         self.module.fail_json(msg="%s is not a supported unit for disk size for disk index [%s]."
-                                                  " Supported units are ['%s']." % (unit,
-                                                                                    disk_index,
-                                                                                    "', '".join(disk_units.keys())))
-
+                                                  " Supported units are ['%s']." % (unit, disk_index, "', '".join(disk_units.keys())))
                 elif current_disk['filename'] is None:
                     # No size found but disk, fail
                     self.module.fail_json(msg="No size, size_kb, size_mb, size_gb or size_tb"
                                               " attribute found into disk index [%s] configuration." % disk_index)
-            # Check SCSI controller key
-            if 'scsi_controller' in disk:
-                try:
-                    temp_disk_controller = int(disk['scsi_controller'])
-                except ValueError:
-                    self.module.fail_json(msg="Invalid SCSI controller ID '%s' specified"
-                                              " at index [%s]" % (disk['scsi_controller'], disk_index))
-                if temp_disk_controller not in range(0, 4):
-                    # Only 4 SCSI controllers are allowed per VM
-                    self.module.fail_json(msg="Invalid SCSI controller ID specified [%s],"
-                                              " please specify value between 0 to 3 only." % temp_disk_controller)
-                current_disk['scsi_controller'] = temp_disk_controller
-            else:
-                self.module.fail_json(msg="Please specify 'scsi_controller' under disk parameter"
-                                          " at index [%s], which is required while creating disk." % disk_index)
-            # Check for disk unit number
-            if 'unit_number' in disk:
-                try:
-                    temp_disk_unit_number = int(disk['unit_number'])
-                except ValueError:
-                    self.module.fail_json(msg="Invalid Disk unit number ID '%s'"
-                                              " specified at index [%s]" % (disk['unit_number'], disk_index))
-                if temp_disk_unit_number not in range(0, 16):
-                    self.module.fail_json(msg="Invalid Disk unit number ID specified for disk [%s] at index [%s],"
-                                              " please specify value between 0 to 15"
-                                              " only (excluding 7)." % (temp_disk_unit_number, disk_index))
-
-                if temp_disk_unit_number == 7:
-                    self.module.fail_json(msg="Invalid Disk unit number ID specified for disk at index [%s],"
-                                              " please specify value other than 7 as it is reserved"
-                                              "for SCSI Controller" % disk_index)
-                current_disk['disk_unit_number'] = temp_disk_unit_number
 
-            else:
-                self.module.fail_json(msg="Please specify 'unit_number' under disk parameter"
-                                          " at index [%s], which is required while creating disk." % disk_index)
-
-            # Type of Disk
-            disk_type = disk.get('type', 'thick').lower()
-            if disk_type not in ['thin', 'thick', 'eagerzeroedthick']:
-                self.module.fail_json(msg="Invalid 'disk_type' specified for disk index [%s]. Please specify"
-                                          " 'disk_type' value from ['thin', 'thick', 'eagerzeroedthick']." % disk_index)
-            current_disk['disk_type'] = disk_type
-
-            # Mode of Disk
-            temp_disk_mode = disk.get('disk_mode', 'persistent').lower()
-            if temp_disk_mode not in ['persistent', 'independent_persistent', 'independent_nonpersistent']:
-                self.module.fail_json(msg="Invalid 'disk_mode' specified for disk index [%s]. Please specify"
-                                          " 'disk_mode' value from ['persistent', 'independent_persistent', 'independent_nonpersistent']." % disk_index)
-            current_disk['disk_mode'] = temp_disk_mode
-
-            # Sharing mode of disk
-            current_disk['sharing'] = self.get_sharing(disk, disk_type, disk_index)
-
-            # SCSI Controller Type
-            scsi_contrl_type = disk.get('scsi_type', 'paravirtual').lower()
-            if scsi_contrl_type not in self.scsi_device_type.keys():
-                self.module.fail_json(msg="Invalid 'scsi_type' specified for disk index [%s]. Please specify"
-                                          " 'scsi_type' value from ['%s']" % (disk_index,
-                                                                              "', '".join(self.scsi_device_type.keys())))
-            current_disk['scsi_type'] = scsi_contrl_type
-            if 'shares' in disk:
-                current_disk['shares'] = disk['shares']
-            if 'iolimit' in disk:
-                current_disk['iolimit'] = disk['iolimit']
+                # Type of Disk
+                if disk['type'] is not None:
+                    current_disk['disk_type'] = disk['type']
+                # Mode of Disk
+                if disk['disk_mode'] is not None:
+                    current_disk['disk_mode'] = disk['disk_mode']
+                # Sharing mode of disk
+                current_disk['sharing'] = self.get_sharing(disk, current_disk['disk_type'], disk_index)
+
+                if disk['shares'] is not None:
+                    current_disk['shares'] = disk['shares']
+                if disk['iolimit'] is not None:
+                    current_disk['iolimit'] = disk['iolimit']
             disks_data.append(current_disk)
         return disks_data
 
@@ -863,14 +984,55 @@ def main():
         moid=dict(type='str'),
         folder=dict(type='str'),
         datacenter=dict(type='str', required=True),
-        disk=dict(type='list', default=[], elements='dict'),
         use_instance_uuid=dict(type='bool', default=False),
+        disk=dict(
+            type='list',
+            default=[],
+            elements='dict',
+            options=dict(
+                size=dict(type='str'),
+                size_kb=dict(type='int'),
+                size_mb=dict(type='int'),
+                size_gb=dict(type='int'),
+                size_tb=dict(type='int'),
+                type=dict(type='str', choices=['thin', 'eagerzeroedthick', 'thick']),
+                disk_mode=dict(type='str', choices=['persistent', 'independent_persistent', 'independent_nonpersistent']),
+                sharing=dict(type='bool', default=False),
+                datastore=dict(type='str'),
+                autoselect_datastore=dict(type='bool'),
+                scsi_controller=dict(type='int', choices=[0, 1, 2, 3]),
+                unit_number=dict(type='int', required=True),
+                scsi_type=dict(type='str', choices=['buslogic', 'lsilogic', 'paravirtual', 'lsilogicsas']),
+                destroy=dict(type='bool', default=True),
+                filename=dict(type='str'),
+                state=dict(type='str', default='present', choices=['present', 'absent']),
+                controller_type=dict(type='str', choices=['buslogic', 'lsilogic', 'paravirtual', 'lsilogicsas', 'sata', 'nvme']),
+                controller_number=dict(type='int', choices=[0, 1, 2, 3]),
+                iolimit=dict(
+                    type='dict',
+                    options=dict(
+                        limit=dict(type='int'),
+                        shares=dict(
+                            type='dict',
+                            options=dict(
+                                level=dict(type='str', choices=['low', 'high', 'normal', 'custom']),
+                                level_value=dict(type='int'),
+                            ),
+                        ),
+                    )),
+                shares=dict(
+                    type='dict',
+                    options=dict(
+                        level=dict(type='str', choices=['low', 'high', 'normal', 'custom']),
+                        level_value=dict(type='int'),
+                    ),
+                ),
+            ),
+        ),
     )
     module = AnsibleModule(
         argument_spec=argument_spec,
-        required_one_of=[
-            ['name', 'uuid', 'moid']
-        ]
+        required_one_of=[['name', 'uuid', 'moid']],
     )
 
     if module.params['folder']:


Test patch:
diff --git a/tests/integration/targets/vmware_guest_disk/tasks/main.yml b/tests/integration/targets/vmware_guest_disk/tasks/main.yml
--- a/tests/integration/targets/vmware_guest_disk/tasks/main.yml
+++ b/tests/integration/targets/vmware_guest_disk/tasks/main.yml
@@ -16,14 +16,14 @@
     validate_certs: false
     name: "{{ virtual_machines[0].name }}"
     disk:
-        - datastore: "{{ rw_datastore }}"
-          disk_mode: "invalid_disk_mode"
-          scsi_controller: 0
-          scsi_type: 'paravirtual'
-          size_gb: 10
-          state: present
-          type: eagerzeroedthick
-          unit_number: 2
+      - datastore: "{{ rw_datastore }}"
+        disk_mode: "invalid_disk_mode"
+        scsi_controller: 0
+        scsi_type: 'paravirtual'
+        size_gb: 10
+        state: present
+        type: eagerzeroedthick
+        unit_number: 2
   register: test_create_disk1
   ignore_errors: true
 
@@ -33,7 +33,7 @@
 - name: assert that changes were not made
   assert:
     that:
-        - not(test_create_disk1 is changed)
+      - not(test_create_disk1 is changed)
 
 - name: create new disk(s) with valid disk mode
   vmware_guest_disk:
@@ -44,30 +44,30 @@
     validate_certs: false
     name: "{{ virtual_machines[0].name }}"
     disk:
-        - datastore: "{{ rw_datastore }}"
-          disk_mode: "independent_persistent"
-          scsi_controller: 0
-          scsi_type: 'paravirtual'
-          size_gb: 1
-          state: present
-          type: eagerzeroedthick
-          unit_number: 2
-        - datastore: "{{ rw_datastore }}"
-          disk_mode: "independent_nonpersistent"
-          scsi_controller: 0
-          scsi_type: 'paravirtual'
-          size_gb: 1
-          state: present
-          type: eagerzeroedthick
-          unit_number: 3
-        - datastore: "{{ rw_datastore }}"
-          disk_mode: "persistent"
-          scsi_controller: 0
-          scsi_type: 'paravirtual'
-          size_gb: 1
-          state: present
-          type: eagerzeroedthick
-          unit_number: 4
+      - datastore: "{{ rw_datastore }}"
+        disk_mode: "independent_persistent"
+        scsi_controller: 0
+        scsi_type: 'paravirtual'
+        size_gb: 1
+        state: present
+        type: eagerzeroedthick
+        unit_number: 2
+      - datastore: "{{ rw_datastore }}"
+        disk_mode: "independent_nonpersistent"
+        scsi_controller: 0
+        scsi_type: 'paravirtual'
+        size_gb: 1
+        state: present
+        type: eagerzeroedthick
+        unit_number: 3
+      - datastore: "{{ rw_datastore }}"
+        disk_mode: "persistent"
+        scsi_controller: 0
+        scsi_type: 'paravirtual'
+        size_gb: 1
+        state: present
+        type: eagerzeroedthick
+        unit_number: 4
   register: test_create_disk2
 
 - debug:
@@ -76,7 +76,7 @@
 - name: assert that changes were made
   assert:
     that:
-        - test_create_disk2 is changed
+      - test_create_disk2 is changed
 
 - name: create new disk with custom shares
   vmware_guest_disk:
@@ -87,16 +87,16 @@
     validate_certs: false
     name: "{{ virtual_machines[0].name }}"
     disk:
-        - size_gb: 1
-          type: eagerzeroedthick
-          datastore: "{{ rw_datastore }}"
-          disk_mode: "independent_nonpersistent"
-          scsi_controller: 1
-          state: present
-          unit_number: 4
-          shares:
-            level: custom
-            level_value: 1300
+      - size_gb: 1
+        type: eagerzeroedthick
+        datastore: "{{ rw_datastore }}"
+        disk_mode: "independent_nonpersistent"
+        scsi_controller: 1
+        state: present
+        unit_number: 4
+        shares:
+          level: custom
+          level_value: 1300
   register: test_custom_shares
 
 - debug:
@@ -105,7 +105,7 @@
 - name: assert that changes were made
   assert:
      that:
-         - test_custom_shares is changed
+       - test_custom_shares is changed
 
 - name: create new disk with custom IO limits and shares in IO Limits
   vmware_guest_disk:
@@ -116,18 +116,18 @@
     validate_certs: false
     name: "{{ virtual_machines[0].name }}"
     disk:
-        - size_gb: 1
-          type: eagerzeroedthick
-          datastore: "{{ rw_datastore }}"
-          disk_mode: "independent_nonpersistent"
-          scsi_controller: 2
-          state: present
-          unit_number: 4
-          iolimit:
-            limit: 1506
-            shares:
-              level: custom
-              level_value: 1305
+      - size_gb: 1
+        type: eagerzeroedthick
+        datastore: "{{ rw_datastore }}"
+        disk_mode: "independent_nonpersistent"
+        scsi_controller: 2
+        state: present
+        unit_number: 4
+        iolimit:
+          limit: 1506
+          shares:
+            level: custom
+            level_value: 1305
   register: test_custom_IoLimit_shares
 
 - debug:
@@ -136,71 +136,117 @@
 - name: assert that changes were made
   assert:
      that:
-         - test_custom_IoLimit_shares is changed
-
-- name: Update disk for custom IO limits in IO Limits
-  vmware_guest_disk:
-    hostname: "{{ vcenter_hostname }}"
-    username: "{{ vcenter_username }}"
-    password: "{{ vcenter_password }}"
-    datacenter: "{{ dc1 }}"
-    validate_certs: false
-    name: "{{ virtual_machines[0].name }}"
-    disk:
-        - size_gb: 2
-          type: eagerzeroedthick
-          datastore: "{{ rw_datastore }}"
-          disk_mode: "independent_nonpersistent"
-          scsi_controller: 2
-          state: present
-          unit_number: 4
-          iolimit:
-            limit: 1500
-            shares:
-              level: custom
-              level_value: 1305
-  register: test_custom_IoLimit
-
-- debug:
-    msg: "{{ test_custom_IoLimit }}"
-
-- name: assert that changes were made
-  assert:
-     that:
-         - test_custom_IoLimit is changed
-
-- name: Update disk for shares of IO limits
-  vmware_guest_disk:
-    hostname: "{{ vcenter_hostname }}"
-    username: "{{ vcenter_username }}"
-    password: "{{ vcenter_password }}"
-    datacenter: "{{ dc1 }}"
-    validate_certs: false
-    name: "{{ virtual_machines[0].name }}"
-    disk:
-        - size_gb: 3
-          type: eagerzeroedthick
-          datastore: "{{ rw_datastore }}"
-          disk_mode: "independent_nonpersistent"
-          scsi_controller: 2
-          state: present
-          unit_number: 4
-          iolimit:
-            limit: 1500
-            shares:
-              level: low
-              level_value: 650
-  register: test_shares_IoLimit
-
-- debug:
-    msg: "{{ test_shares_IoLimit }}"
+       - test_custom_IoLimit_shares is changed
+
+# TODO: vcsim does not support reconfiguration of disk mode, fails with types.InvalidDeviceSpec
+- when: vcsim is not defined
+  block:
+    - name: Update disk for custom IO limits in IO Limits
+      vmware_guest_disk:
+        hostname: "{{ vcenter_hostname }}"
+        username: "{{ vcenter_username }}"
+        password: "{{ vcenter_password }}"
+        datacenter: "{{ dc1 }}"
+        validate_certs: false
+        name: "{{ virtual_machines[0].name }}"
+        disk:
+          - size_gb: 2
+            scsi_controller: 2
+            state: present
+            unit_number: 4
+            iolimit:
+              limit: 1500
+              shares:
+                level: custom
+                level_value: 1305
+      register: test_custom_IoLimit
+
+    - debug:
+        msg: "{{ test_custom_IoLimit }}"
+
+    - name: assert that changes were made
+      assert:
+         that:
+           - test_custom_IoLimit is changed
+
+    - name: Update disk for shares of IO limits
+      vmware_guest_disk:
+        hostname: "{{ vcenter_hostname }}"
+        username: "{{ vcenter_username }}"
+        password: "{{ vcenter_password }}"
+        datacenter: "{{ dc1 }}"
+        validate_certs: false
+        name: "{{ virtual_machines[0].name }}"
+        disk:
+          - size_gb: 3
+            scsi_controller: 2
+            state: present
+            unit_number: 4
+            iolimit:
+              limit: 1500
+              shares:
+                level: low
+      register: test_shares_IoLimit
+
+    - debug:
+        msg: "{{ test_shares_IoLimit }}"
+
+    - name: assert that changes were made
+      assert:
+         that:
+           - test_shares_IoLimit is changed
+
+    - name: Update disk for shares and IoLimits of IO limits
+      vmware_guest_disk:
+        hostname: "{{ vcenter_hostname }}"
+        username: "{{ vcenter_username }}"
+        password: "{{ vcenter_password }}"
+        datacenter: "{{ dc1 }}"
+        validate_certs: false
+        name: "{{ virtual_machines[0].name }}"
+        disk:
+          - size_gb: 4
+            scsi_controller: 2
+            state: present
+            unit_number: 4
+            iolimit:
+              limit: 1507
+              shares:
+                level: high
+      register: test_shares_IoLimits
+
+    - debug:
+        msg: "{{ test_shares_IoLimits }}"
+
+    - name: assert that changes were made
+      assert:
+         that:
+           - test_shares_IoLimits is changed
+
+    - name: remove disks without destroy file
+      vmware_guest_disk:
+        hostname: "{{ vcenter_hostname }}"
+        username: "{{ vcenter_username }}"
+        password: "{{ vcenter_password }}"
+        datacenter: "{{ dc1 }}"
+        validate_certs: false
+        name: "{{ virtual_machines[0].name }}"
+        disk:
+          - state: "absent"
+            scsi_controller: 0
+            unit_number: 2
+            destroy: false
+      register: test_remove_without_destroy
+
+    - debug:
+        msg: "{{ test_remove_without_destroy }}"
+
+    - name: assert that changes were made
+      assert:
+        that:
+          - test_remove_without_destroy is changed
 
-- name: assert that changes were made
-  assert:
-     that:
-         - test_shares_IoLimit is changed
-
-- name: Update disk for shares and IoLimits of IO limits
+- name: re-create disk with valid disk mode
   vmware_guest_disk:
     hostname: "{{ vcenter_hostname }}"
     username: "{{ vcenter_username }}"
@@ -209,29 +255,25 @@
     validate_certs: false
     name: "{{ virtual_machines[0].name }}"
     disk:
-        - size_gb: 4
-          type: eagerzeroedthick
-          datastore: "{{ rw_datastore }}"
-          disk_mode: "independent_nonpersistent"
-          scsi_controller: 2
-          state: present
-          unit_number: 4
-          iolimit:
-            limit: 1507
-            shares:
-              level: high
-              level_value: 1200
-  register: test_shares_IoLimits
+      - datastore: "{{ rw_datastore }}"
+        disk_mode: "persistent"
+        scsi_controller: 0
+        scsi_type: 'paravirtual'
+        size_gb: 1
+        state: present
+        type: eagerzeroedthick
+        unit_number: 8
+  register: test_recreate_disk
 
 - debug:
-    msg: "{{ test_shares_IoLimits }}"
+    msg: "{{ test_recreate_disk }}"
 
 - name: assert that changes were made
   assert:
-     that:
-         - test_shares_IoLimits is changed
+    that:
+      - test_recreate_disk is changed
 
-- name: remove disks without destroy file
+- name: create new disk with sharing (multi-writer) mode
   vmware_guest_disk:
     hostname: "{{ vcenter_hostname }}"
     username: "{{ vcenter_username }}"
@@ -240,21 +282,26 @@
     validate_certs: false
     name: "{{ virtual_machines[0].name }}"
     disk:
-        - state: "absent"
-          scsi_controller: 0
-          unit_number: 4
-          destroy: false
-  register: test_remove_without_destroy
+      - datastore: "{{ rw_datastore }}"
+        disk_mode: "independent_persistent"
+        scsi_controller: 0
+        scsi_type: 'paravirtual'
+        size_gb: 1
+        state: present
+        type: eagerzeroedthick
+        sharing: true
+        unit_number: 6
+  register: test_create_disk_sharing
 
 - debug:
-    msg: "{{ test_remove_without_destroy }}"
+    msg: "{{ test_create_disk_sharing }}"
 
 - name: assert that changes were made
   assert:
     that:
-        - test_remove_without_destroy is changed
+      - test_create_disk_sharing is changed
 
-- name: re-create disk with valid disk mode
+- name: create new disk with invalid disk type for sharing (multi-writer) mode
   vmware_guest_disk:
     hostname: "{{ vcenter_hostname }}"
     username: "{{ vcenter_username }}"
@@ -263,25 +310,55 @@
     validate_certs: false
     name: "{{ virtual_machines[0].name }}"
     disk:
-        - datastore: "{{ rw_datastore }}"
-          disk_mode: "persistent"
-          scsi_controller: 0
-          scsi_type: 'paravirtual'
-          size_gb: 1
-          state: present
-          type: eagerzeroedthick
-          unit_number: 4
-  register: test_recreate_disk
+      - datastore: "{{ rw_datastore }}"
+        disk_mode: "independent_persistent"
+        scsi_controller: 0
+        scsi_type: 'paravirtual'
+        size_gb: 1
+        state: present
+        type: thin
+        unit_number: 5
+        sharing: true
+  register: test_create_disk_sharing_invalid
+  ignore_errors: true
 
 - debug:
-    msg: "{{ test_recreate_disk }}"
+    msg: "{{ test_create_disk_sharing_invalid }}"
 
-- name: assert that changes were made
+- name: assert that changes were not made
   assert:
     that:
-        - test_recreate_disk is changed
-
-- name: create new disk with sharing (multi-writer) mode
+      - not(test_create_disk_sharing_invalid is changed)
+
+- when: vcsim is not defined
+  block:
+    - name: remove disk with destroy file
+      vmware_guest_disk:
+        hostname: "{{ vcenter_hostname }}"
+        username: "{{ vcenter_username }}"
+        password: "{{ vcenter_password }}"
+        datacenter: "{{ dc1 }}"
+        validate_certs: false
+        name: "{{ virtual_machines[0].name }}"
+        disk:
+          - state: "absent"
+            scsi_controller: 0
+            unit_number: 3
+            destroy: true
+          - state: "absent"
+            scsi_controller: 0
+            unit_number: 4
+      register: test_remove_with_destroy
+
+    - debug:
+        msg: "{{ test_remove_with_destroy }}"
+
+    - name: assert that changes were made
+      assert:
+        that:
+          - test_remove_with_destroy is changed
+
+- name: create new disk with SATA controller
   vmware_guest_disk:
     hostname: "{{ vcenter_hostname }}"
     username: "{{ vcenter_username }}"
@@ -290,26 +367,25 @@
     validate_certs: false
     name: "{{ virtual_machines[0].name }}"
     disk:
-        - datastore: "{{ rw_datastore }}"
-          disk_mode: "independent_persistent"
-          scsi_controller: 0
-          scsi_type: 'paravirtual'
-          size_gb: 1
-          state: present
-          type: eagerzeroedthick
-          sharing: true
-          unit_number: 6
-  register: test_create_disk_sharing
+      - datastore: "{{ rw_datastore }}"
+        disk_mode: "independent_persistent"
+        controller_type: 'sata'
+        controller_number: 1
+        unit_number: 3
+        size_gb: 1
+        state: present
+        type: thin
+  register: test_create_sata_disk
 
 - debug:
-    msg: "{{ test_create_disk_sharing }}"
+    msg: "{{ test_create_sata_disk }}"
 
 - name: assert that changes were made
   assert:
     that:
-        - test_create_disk_sharing is changed
+      - test_create_sata_disk is changed
 
-- name: create new disk with invalid disk type for sharing (multi-writer) mode
+- name: create new disk with NVMe controller
   vmware_guest_disk:
     hostname: "{{ vcenter_hostname }}"
     username: "{{ vcenter_username }}"
@@ -318,27 +394,25 @@
     validate_certs: false
     name: "{{ virtual_machines[0].name }}"
     disk:
-        - datastore: "{{ rw_datastore }}"
-          disk_mode: "independent_persistent"
-          scsi_controller: 0
-          scsi_type: 'paravirtual'
-          size_gb: 1
-          state: present
-          type: thin
-          unit_number: 5
-          sharing: true
-  register: test_create_disk_sharing_invalid
-  ignore_errors: true
+      - datastore: "{{ rw_datastore }}"
+        disk_mode: "independent_persistent"
+        controller_type: 'nvme'
+        controller_number: 0
+        unit_number: 1
+        size: 1gb
+        state: present
+        type: thin
+  register: test_create_nvme_disk
 
 - debug:
-    msg: "{{ test_create_disk_sharing_invalid }}"
+    msg: "{{ test_create_nvme_disk }}"
 
-- name: assert that changes were not made
+- name: assert that changes were made
   assert:
     that:
-        - not(test_create_disk_sharing_invalid is changed)
+      - test_create_nvme_disk is changed
 
-- name: remove disk with destroy file
+- name: create 2 new disks on existing SATA controller and NVMe controller
   vmware_guest_disk:
     hostname: "{{ vcenter_hostname }}"
     username: "{{ vcenter_username }}"
@@ -347,19 +421,115 @@
     validate_certs: false
     name: "{{ virtual_machines[0].name }}"
     disk:
-        - state: "absent"
-          scsi_controller: 0
-          unit_number: 3
-          destroy: true
-        - state: "absent"
-          scsi_controller: 0
-          unit_number: 4
-  register: test_remove_with_destroy
+      - datastore: "{{ rw_datastore }}"
+        disk_mode: "independent_persistent"
+        controller_type: 'sata'
+        controller_number: 1
+        unit_number: 6
+        size_gb: 1
+        state: present
+      - datastore: "{{ rw_datastore }}"
+        disk_mode: "independent_persistent"
+        controller_type: 'nvme'
+        controller_number: 0
+        unit_number: 4
+        size: 1gb
+        state: present
+        type: thin
+  register: test_create_two_disks
 
 - debug:
-    msg: "{{ test_remove_with_destroy }}"
+    msg: "{{ test_create_two_disks }}"
 
 - name: assert that changes were made
   assert:
     that:
-        - test_remove_with_destroy is changed
+      - test_create_two_disks is changed
+
+- when: vcsim is not defined
+  block:
+    - name: re-configure SATA disk
+      vmware_guest_disk:
+        hostname: "{{ vcenter_hostname }}"
+        username: "{{ vcenter_username }}"
+        password: "{{ vcenter_password }}"
+        datacenter: "{{ dc1 }}"
+        validate_certs: false
+        name: "{{ virtual_machines[0].name }}"
+        disk:
+          - disk_mode: "independent_nonpersistent"
+            controller_type: 'sata'
+            controller_number: 1
+            unit_number: 3
+            size_gb: 2
+            state: present
+            shares:
+              level: custom
+              level_value: 1200
+      register: test_reconfig_sata_disk
+
+    - debug:
+        msg: "{{ test_reconfig_sata_disk }}"
+
+    - name: assert that changes were made
+      assert:
+        that:
+          - test_reconfig_sata_disk is changed
+
+    - name: re-configure NVMe disk
+      vmware_guest_disk:
+        hostname: "{{ vcenter_hostname }}"
+        username: "{{ vcenter_username }}"
+        password: "{{ vcenter_password }}"
+        datacenter: "{{ dc1 }}"
+        validate_certs: false
+        name: "{{ virtual_machines[0].name }}"
+        disk:
+          - controller_type: 'nvme'
+            controller_number: 0
+            unit_number: 1
+            size_gb: 2
+            state: present
+            iolimit:
+              limit: 1507
+              shares:
+                level: custom
+                level_value: 1000
+      register: test_reconfig_nvme_disk
+
+    - debug:
+        msg: "{{ test_reconfig_nvme_disk }}"
+
+    - name: assert that changes were made
+      assert:
+        that:
+          - test_reconfig_nvme_disk is changed
+
+    - name: remove SATA and NVMe disks
+      vmware_guest_disk:
+        hostname: "{{ vcenter_hostname }}"
+        username: "{{ vcenter_username }}"
+        password: "{{ vcenter_password }}"
+        datacenter: "{{ dc1 }}"
+        validate_certs: false
+        name: "{{ virtual_machines[0].name }}"
+        disk:
+          - state: "absent"
+            controller_type: 'sata'
+            controller_number: 1
+            unit_number: 3
+            destroy: false
+          - state: "absent"
+            controller_type: 'nvme'
+            controller_number: 0
+            unit_number: 1
+            destroy: true
+      register: test_remove_sata_nvme_disk
+
+    - debug:
+        msg: "{{ test_remove_sata_nvme_disk }}"
+
+    - name: assert that changes were made
+      assert:
+        that:
+          - test_remove_sata_nvme_disk is changed