From 849912c01b4824ba958b69334630624a909cea06 Mon Sep 17 00:00:00 2001 From: Christian Kotte Date: Sun, 14 Oct 2018 14:43:36 +0200 Subject: [PATCH] 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 --- .../cloud/vmware/vmware_host_datastore.py | 140 +++++++++++------- 1 file changed, 89 insertions(+), 51 deletions(-) diff --git a/lib/ansible/modules/cloud/vmware/vmware_host_datastore.py b/lib/ansible/modules/cloud/vmware/vmware_host_datastore.py index 092d70c19d9..d8c2cac42dc 100644 --- a/lib/ansible/modules/cloud/vmware/vmware_host_datastore.py +++ b/lib/ansible/modules/cloud/vmware/vmware_host_datastore.py @@ -19,14 +19,17 @@ module: vmware_host_datastore short_description: Manage a datastore on ESXi host description: - 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. - All parameters and VMware object names are case sensitive. version_added: '2.5' author: - Ludovic Rivallain (@lrivallain) +- Christian Kotte (@ckotte) notes: - 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: - python >= 2.6 - PyVmomi @@ -41,21 +44,22 @@ options: required: true datastore_type: description: - - Type of the datastore to configure (nfs/vmfs). + - Type of the datastore to configure (nfs/nfs41/vmfs). required: true - choices: [ 'nfs', 'vmfs' ] + choices: [ 'nfs', 'nfs41', 'vmfs' ] nfs_server: description: - 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: description: - 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: description: - 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 type: bool vmfs_device_name: @@ -112,6 +116,24 @@ EXAMPLES = r''' - { 'name': 'NasDS_vol01', 'server': 'nas01', 'path': '/mnt/vol01', '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 vmware_host_datastore: hostname: '{{ vcenter_hostname }}' @@ -189,61 +211,73 @@ class VMwareHostDatastore(PyVmomi): ds = find_datastore_by_name(self.content, self.datastore_name) if not ds: 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) - try: - self.esxi.configManager.datastoreSystem.RemoveDatastore(ds) - except (vim.fault.NotFound, vim.fault.HostConfigFault, vim.fault.ResourceInUse) as fault: - self.module.fail_json(msg="%s: %s" % (error_message_umount, to_native(fault.msg))) - except Exception as e: - self.module.fail_json(msg="%s: %s" % (error_message_umount, to_native(e))) + if self.module.check_mode is False: + error_message_umount = "Cannot umount datastore %s from host %s" % (self.datastore_name, self.esxi_hostname) + try: + self.esxi.configManager.datastoreSystem.RemoveDatastore(ds) + except (vim.fault.NotFound, vim.fault.HostConfigFault, vim.fault.ResourceInUse) as fault: + self.module.fail_json(msg="%s: %s" % (error_message_umount, to_native(fault.msg))) + 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)) 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() if self.datastore_type == 'vmfs': self.mount_vmfs_datastore_host() def mount_nfs_datastore_host(self): - mnt_specs = vim.host.NasVolume.Specification() - mnt_specs.remoteHost = self.nfs_server - mnt_specs.remotePath = self.nfs_path - mnt_specs.localPath = self.datastore_name - if self.nfs_ro: - mnt_specs.accessMode = "readOnly" - else: - mnt_specs.accessMode = "readWrite" - error_message_mount = "Cannot mount datastore %s on host %s" % (self.datastore_name, self.esxi_hostname) - 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))) + if self.module.check_mode is False: + mnt_specs = vim.host.NasVolume.Specification() + # NFS v3 + if self.datastore_type == 'nfs': + mnt_specs.type = "NFS" + mnt_specs.remoteHost = self.nfs_server + # NFS v4.1 + if self.datastore_type == 'nfs41': + mnt_specs.type = "NFS41" + # remoteHost needs to be set to a non-empty string, but the value is not used + mnt_specs.remoteHost = "something" + mnt_specs.remoteHostNames = [self.nfs_server] + mnt_specs.remotePath = self.nfs_path + mnt_specs.localPath = self.datastore_name + if self.nfs_ro: + mnt_specs.accessMode = "readOnly" + else: + mnt_specs.accessMode = "readWrite" + error_message_mount = "Cannot mount datastore %s on host %s" % (self.datastore_name, self.esxi_hostname) + 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)) def mount_vmfs_datastore_host(self): - ds_path = "/vmfs/devices/disks/" + str(self.vmfs_device_name) - host_ds_system = self.esxi.configManager.datastoreSystem - ds_system = vim.host.DatastoreSystem - error_message_mount = "Cannot mount datastore %s on host %s" % (self.datastore_name, self.esxi_hostname) - try: - vmfs_ds_options = ds_system.QueryVmfsDatastoreCreateOptions(host_ds_system, - ds_path, - self.vmfs_version) - vmfs_ds_options[0].spec.vmfs.volumeName = self.datastore_name - ds = ds_system.CreateVmfsDatastore(host_ds_system, - vmfs_ds_options[0].spec) - except (vim.fault.NotFound, vim.fault.DuplicateName, - vim.fault.HostConfigFault, vmodl.fault.InvalidArgument) 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))) + if self.module.check_mode is False: + ds_path = "/vmfs/devices/disks/" + str(self.vmfs_device_name) + host_ds_system = self.esxi.configManager.datastoreSystem + ds_system = vim.host.DatastoreSystem + error_message_mount = "Cannot mount datastore %s on host %s" % (self.datastore_name, self.esxi_hostname) + try: + vmfs_ds_options = ds_system.QueryVmfsDatastoreCreateOptions(host_ds_system, + ds_path, + self.vmfs_version) + vmfs_ds_options[0].spec.vmfs.volumeName = self.datastore_name + ds = ds_system.CreateVmfsDatastore(host_ds_system, + vmfs_ds_options[0].spec) + except (vim.fault.NotFound, vim.fault.DuplicateName, + vim.fault.HostConfigFault, vmodl.fault.InvalidArgument) 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)) @@ -252,7 +286,7 @@ def main(): argument_spec.update( datacenter_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_path=dict(type='str'), nfs_ro=dict(type='bool', default=False), @@ -276,6 +310,10 @@ def main(): msg = "Missing nfs_server with datastore_type = nfs" 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']: msg = "Missing vmfs_device_name with datastore_type = vmfs" module.fail_json(msg=msg)