VMware: Improve module vmware_host_datastore (#45652)

* Don't execute mount or umount in check mode
* The datastore was mounted or unmounted when you execute the playbook in
check mode.
* Test if running in check mode before mounting or unmounting the
datastore.
* Add support for NFS v4.1 datastores
This commit is contained in:
Christian Kotte 2018-10-14 14:43:36 +02:00 committed by Abhijeet Kasurde
parent 23b60d843e
commit 849912c01b

View file

@ -19,14 +19,17 @@ module: vmware_host_datastore
short_description: Manage a datastore on ESXi host short_description: Manage a datastore on ESXi host
description: description:
- This module can be used to mount/umount datastore on ESXi host. - This module can be used to mount/umount datastore on ESXi host.
- This module only support NFS/VMFS type of datastores. - This module only supports NFS (NFS v3 or NFS v4.1) and VMFS datastores.
- For VMFS datastore, available device must already be connected on ESXi host. - For VMFS datastore, available device must already be connected on ESXi host.
- All parameters and VMware object names are case sensitive. - All parameters and VMware object names are case sensitive.
version_added: '2.5' version_added: '2.5'
author: author:
- Ludovic Rivallain (@lrivallain) <ludovic.rivallain@gmail.com> - Ludovic Rivallain (@lrivallain) <ludovic.rivallain@gmail.com>
- Christian Kotte (@ckotte) <christian.kotte@gmx.de>
notes: notes:
- Tested on vSphere 6.0 and 6.5 - Tested on vSphere 6.0 and 6.5
- NFS v4.1 tested on vSphere 6.5
- Kerberos authentication with NFS v4.1 isn't implemented
requirements: requirements:
- python >= 2.6 - python >= 2.6
- PyVmomi - PyVmomi
@ -41,21 +44,22 @@ options:
required: true required: true
datastore_type: datastore_type:
description: description:
- Type of the datastore to configure (nfs/vmfs). - Type of the datastore to configure (nfs/nfs41/vmfs).
required: true required: true
choices: [ 'nfs', 'vmfs' ] choices: [ 'nfs', 'nfs41', 'vmfs' ]
nfs_server: nfs_server:
description: description:
- NFS host serving nfs datastore. - NFS host serving nfs datastore.
- Required if datastore type is set to C(nfs) and state is set to C(present), else unused. - Required if datastore type is set to C(nfs)/C(nfs41) and state is set to C(present), else unused.
- Two or more servers can be defined if datastore type is set to C(nfs41)
nfs_path: nfs_path:
description: description:
- Resource path on NFS host. - Resource path on NFS host.
- Required if datastore type is set to C(nfs) and state is set to C(present), else unused. - Required if datastore type is set to C(nfs)/C(nfs41) and state is set to C(present), else unused.
nfs_ro: nfs_ro:
description: description:
- ReadOnly or ReadWrite mount. - ReadOnly or ReadWrite mount.
- Unused if datastore type is not set to C(nfs) and state is not set to C(present). - Unused if datastore type is not set to C(nfs)/C(nfs41) and state is not set to C(present).
default: False default: False
type: bool type: bool
vmfs_device_name: vmfs_device_name:
@ -112,6 +116,24 @@ EXAMPLES = r'''
- { 'name': 'NasDS_vol01', 'server': 'nas01', 'path': '/mnt/vol01', 'type': 'nfs'} - { 'name': 'NasDS_vol01', 'server': 'nas01', 'path': '/mnt/vol01', 'type': 'nfs'}
- { 'name': 'NasDS_vol02', 'server': 'nas01', 'path': '/mnt/vol02', 'type': 'nfs'} - { 'name': 'NasDS_vol02', 'server': 'nas01', 'path': '/mnt/vol02', 'type': 'nfs'}
- name: Mount NFS v4.1 datastores to ESXi
vmware_host_datastore:
hostname: '{{ vcenter_hostname }}'
username: '{{ vcenter_username }}'
password: '{{ vcenter_password }}'
datacenter_name: '{{ datacenter }}'
datastore_name: '{{ item.name }}'
datastore_type: '{{ item.type }}'
nfs_server: '{{ item.server }}'
nfs_path: '{{ item.path }}'
nfs_ro: no
esxi_hostname: '{{ inventory_hostname }}'
state: present
delegate_to: localhost
with_items:
- { 'name': 'NasDS_vol03', 'server': 'nas01,nas02', 'path': '/mnt/vol01', 'type': 'nfs41'}
- { 'name': 'NasDS_vol04', 'server': 'nas01,nas02', 'path': '/mnt/vol02', 'type': 'nfs41'}
- name: Remove/Umount Datastores from ESXi - name: Remove/Umount Datastores from ESXi
vmware_host_datastore: vmware_host_datastore:
hostname: '{{ vcenter_hostname }}' hostname: '{{ vcenter_hostname }}'
@ -189,61 +211,73 @@ class VMwareHostDatastore(PyVmomi):
ds = find_datastore_by_name(self.content, self.datastore_name) ds = find_datastore_by_name(self.content, self.datastore_name)
if not ds: if not ds:
self.module.fail_json(msg="No datastore found with name %s" % self.datastore_name) self.module.fail_json(msg="No datastore found with name %s" % self.datastore_name)
error_message_umount = "Cannot umount datastore %s from host %s" % (self.datastore_name, self.esxi_hostname) if self.module.check_mode is False:
try: error_message_umount = "Cannot umount datastore %s from host %s" % (self.datastore_name, self.esxi_hostname)
self.esxi.configManager.datastoreSystem.RemoveDatastore(ds) try:
except (vim.fault.NotFound, vim.fault.HostConfigFault, vim.fault.ResourceInUse) as fault: self.esxi.configManager.datastoreSystem.RemoveDatastore(ds)
self.module.fail_json(msg="%s: %s" % (error_message_umount, to_native(fault.msg))) except (vim.fault.NotFound, vim.fault.HostConfigFault, vim.fault.ResourceInUse) as fault:
except Exception as e: self.module.fail_json(msg="%s: %s" % (error_message_umount, to_native(fault.msg)))
self.module.fail_json(msg="%s: %s" % (error_message_umount, to_native(e))) except Exception as e:
self.module.fail_json(msg="%s: %s" % (error_message_umount, to_native(e)))
self.module.exit_json(changed=True, result="Datastore %s on host %s" % (self.datastore_name, self.esxi_hostname)) self.module.exit_json(changed=True, result="Datastore %s on host %s" % (self.datastore_name, self.esxi_hostname))
def mount_datastore_host(self): def mount_datastore_host(self):
if self.datastore_type == 'nfs': if self.datastore_type == 'nfs' or self.datastore_type == 'nfs41':
self.mount_nfs_datastore_host() self.mount_nfs_datastore_host()
if self.datastore_type == 'vmfs': if self.datastore_type == 'vmfs':
self.mount_vmfs_datastore_host() self.mount_vmfs_datastore_host()
def mount_nfs_datastore_host(self): def mount_nfs_datastore_host(self):
mnt_specs = vim.host.NasVolume.Specification() if self.module.check_mode is False:
mnt_specs.remoteHost = self.nfs_server mnt_specs = vim.host.NasVolume.Specification()
mnt_specs.remotePath = self.nfs_path # NFS v3
mnt_specs.localPath = self.datastore_name if self.datastore_type == 'nfs':
if self.nfs_ro: mnt_specs.type = "NFS"
mnt_specs.accessMode = "readOnly" mnt_specs.remoteHost = self.nfs_server
else: # NFS v4.1
mnt_specs.accessMode = "readWrite" if self.datastore_type == 'nfs41':
error_message_mount = "Cannot mount datastore %s on host %s" % (self.datastore_name, self.esxi_hostname) mnt_specs.type = "NFS41"
try: # remoteHost needs to be set to a non-empty string, but the value is not used
ds = self.esxi.configManager.datastoreSystem.CreateNasDatastore(mnt_specs) mnt_specs.remoteHost = "something"
if not ds: mnt_specs.remoteHostNames = [self.nfs_server]
self.module.fail_json(msg=error_message_mount) mnt_specs.remotePath = self.nfs_path
except (vim.fault.NotFound, vim.fault.DuplicateName, mnt_specs.localPath = self.datastore_name
vim.fault.AlreadyExists, vim.fault.HostConfigFault, if self.nfs_ro:
vmodl.fault.InvalidArgument, vim.fault.NoVirtualNic, mnt_specs.accessMode = "readOnly"
vim.fault.NoGateway) as fault: else:
self.module.fail_json(msg="%s: %s" % (error_message_mount, to_native(fault.msg))) mnt_specs.accessMode = "readWrite"
except Exception as e: error_message_mount = "Cannot mount datastore %s on host %s" % (self.datastore_name, self.esxi_hostname)
self.module.fail_json(msg="%s : %s" % (error_message_mount, to_native(e))) try:
ds = self.esxi.configManager.datastoreSystem.CreateNasDatastore(mnt_specs)
if not ds:
self.module.fail_json(msg=error_message_mount)
except (vim.fault.NotFound, vim.fault.DuplicateName,
vim.fault.AlreadyExists, vim.fault.HostConfigFault,
vmodl.fault.InvalidArgument, vim.fault.NoVirtualNic,
vim.fault.NoGateway) as fault:
self.module.fail_json(msg="%s: %s" % (error_message_mount, to_native(fault.msg)))
except Exception as e:
self.module.fail_json(msg="%s : %s" % (error_message_mount, to_native(e)))
self.module.exit_json(changed=True, result="Datastore %s on host %s" % (self.datastore_name, self.esxi_hostname)) self.module.exit_json(changed=True, result="Datastore %s on host %s" % (self.datastore_name, self.esxi_hostname))
def mount_vmfs_datastore_host(self): def mount_vmfs_datastore_host(self):
ds_path = "/vmfs/devices/disks/" + str(self.vmfs_device_name) if self.module.check_mode is False:
host_ds_system = self.esxi.configManager.datastoreSystem ds_path = "/vmfs/devices/disks/" + str(self.vmfs_device_name)
ds_system = vim.host.DatastoreSystem host_ds_system = self.esxi.configManager.datastoreSystem
error_message_mount = "Cannot mount datastore %s on host %s" % (self.datastore_name, self.esxi_hostname) ds_system = vim.host.DatastoreSystem
try: error_message_mount = "Cannot mount datastore %s on host %s" % (self.datastore_name, self.esxi_hostname)
vmfs_ds_options = ds_system.QueryVmfsDatastoreCreateOptions(host_ds_system, try:
ds_path, vmfs_ds_options = ds_system.QueryVmfsDatastoreCreateOptions(host_ds_system,
self.vmfs_version) ds_path,
vmfs_ds_options[0].spec.vmfs.volumeName = self.datastore_name self.vmfs_version)
ds = ds_system.CreateVmfsDatastore(host_ds_system, vmfs_ds_options[0].spec.vmfs.volumeName = self.datastore_name
vmfs_ds_options[0].spec) ds = ds_system.CreateVmfsDatastore(host_ds_system,
except (vim.fault.NotFound, vim.fault.DuplicateName, vmfs_ds_options[0].spec)
vim.fault.HostConfigFault, vmodl.fault.InvalidArgument) as fault: except (vim.fault.NotFound, vim.fault.DuplicateName,
self.module.fail_json(msg="%s : %s" % (error_message_mount, to_native(fault.msg))) vim.fault.HostConfigFault, vmodl.fault.InvalidArgument) as fault:
except Exception as e: self.module.fail_json(msg="%s : %s" % (error_message_mount, to_native(fault.msg)))
self.module.fail_json(msg="%s : %s" % (error_message_mount, to_native(e))) except Exception as e:
self.module.fail_json(msg="%s : %s" % (error_message_mount, to_native(e)))
self.module.exit_json(changed=True, result="Datastore %s on host %s" % (self.datastore_name, self.esxi_hostname)) self.module.exit_json(changed=True, result="Datastore %s on host %s" % (self.datastore_name, self.esxi_hostname))
@ -252,7 +286,7 @@ def main():
argument_spec.update( argument_spec.update(
datacenter_name=dict(type='str', required=True), datacenter_name=dict(type='str', required=True),
datastore_name=dict(type='str', required=True), datastore_name=dict(type='str', required=True),
datastore_type=dict(type='str', choices=['nfs', 'vmfs']), datastore_type=dict(type='str', choices=['nfs', 'nfs41', 'vmfs']),
nfs_server=dict(type='str'), nfs_server=dict(type='str'),
nfs_path=dict(type='str'), nfs_path=dict(type='str'),
nfs_ro=dict(type='bool', default=False), nfs_ro=dict(type='bool', default=False),
@ -276,6 +310,10 @@ def main():
msg = "Missing nfs_server with datastore_type = nfs" msg = "Missing nfs_server with datastore_type = nfs"
module.fail_json(msg=msg) module.fail_json(msg=msg)
if module.params['datastore_type'] == 'nfs41' and not module.params['nfs_server']:
msg = "Missing nfs_server with datastore_type = nfs41"
module.fail_json(msg=msg)
if module.params['datastore_type'] == 'vmfs' and not module.params['vmfs_device_name']: if module.params['datastore_type'] == 'vmfs' and not module.params['vmfs_device_name']:
msg = "Missing vmfs_device_name with datastore_type = vmfs" msg = "Missing vmfs_device_name with datastore_type = vmfs"
module.fail_json(msg=msg) module.fail_json(msg=msg)