#!/usr/bin/python # Copyright: (c) 2020, Dell Technologies # Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) """Ansible module for managing nfs export on Unity""" from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = r""" --- module: nfs version_added: '1.1.0' short_description: Manage NFS export on Unity storage system description: - Managing NFS export on Unity storage system includes- Create new NFS export, Modify NFS export attributes, Display NFS export details, Delete NFS export. extends_documentation_fragment: - dellemc.unity.unity author: - Vivek Soni (@v-soni11) options: nfs_export_name: description: - Name of the nfs export. - Mandatory for create operation. - Specify either I(nfs_export_name) or I(nfs_export_id) (but not both) for any operation. type: str nfs_export_id: description: - ID of the nfs export. - This is a unique ID generated by Unity storage system. type: str filesystem_name: description: - Name of the filesystem for which NFS export will be created. - Either filesystem or snapshot is required for creation of the NFS. - If I(filesystem_name) is specified, then I(nas_server) is required to uniquely identify the filesystem. - If filesystem parameter is provided, then snapshot cannot be specified. type: str filesystem_id: description: - ID of the filesystem. - This is a unique ID generated by Unity storage system. type: str snapshot_name: description: - Name of the snapshot for which NFS export will be created. - Either filesystem or snapshot is required for creation of the NFS export. - If snapshot parameter is provided, then filesystem cannot be specified. type: str snapshot_id: description: - ID of the snapshot. - This is a unique ID generated by Unity storage system. type: str nas_server_name: description: - Name of the NAS server on which filesystem will be hosted. type: str nas_server_id: description: - ID of the NAS server on which filesystem will be hosted. type: str path: description: - Local path to export relative to the NAS server root. - With NFS, each export of a file_system or file_snap must have a unique local path. - Mandatory while creating NFS export. type: str description: description: - Description of the NFS export. - Optional parameter when creating a NFS export. - To modify description, pass the new value in I(description) field. - To remove description, pass the empty value in I(description) field. type: str host_state: description: - Define whether the hosts can access the NFS export. - Required when adding or removing access of hosts from the export. type: str choices: ['present-in-export', 'absent-in-export'] anonymous_uid: description: - Specifies the user ID of the anonymous account. - If not specified at the time of creation, it will be set to 4294967294. type: int anonymous_gid: description: - Specifies the group ID of the anonymous account. - If not specified at the time of creation, it will be set to 4294967294. type: int state: description: - State variable to determine whether NFS export will exist or not. required: true type: str choices: ['absent', 'present'] default_access: description: - Default access level for all hosts that can access the NFS export. - For hosts that need different access than the default, they can be configured by adding to the list. - If I(default_access) is not mentioned during creation, then NFS export will be created with C(NO_ACCESS). type: str choices: ['NO_ACCESS', 'READ_ONLY', 'READ_WRITE', 'ROOT', 'READ_ONLY_ROOT'] min_security: description: - NFS enforced security type for users accessing a NFS export. - If not specified at the time of creation, it will be set to C(SYS). type: str choices: ['SYS', 'KERBEROS', 'KERBEROS_WITH_INTEGRITY', 'KERBEROS_WITH_ENCRYPTION'] adv_host_mgmt_enabled: description: - If C(false), allows you to specify hosts without first having to register them. - Mandatory while adding access hosts. type: bool no_access_hosts: description: - Hosts with no access to the NFS export. - List of dictionaries. Each dictionary will have any of the keys from I(host_name), I(host_id), I(subnet), I(netgroup), I(domain) and I(ip_address). - If I(adv_host_mgmt_enabled) is C(true) then the accepted keys are I(host_name), I(host_id) and I(ip_address). - If I(adv_host_mgmt_enabled) is C(false) then the accepted keys are I(host_name), I(subnet), I(netgroup), I(domain) and I(ip_address). type: list elements: dict suboptions: host_name: description: - Name of the host. type: str host_id: description: - ID of the host. type: str ip_address: description: - IP address of the host. type: str subnet: description: - Subnet can be an 'IP address/netmask' or 'IP address/prefix length'. type: str netgroup: description: - Netgroup that is defined in NIS or the local netgroup file. type: str domain: description: - DNS domain, where all NFS clients in the domain are included in the host list. type: str read_only_hosts: description: - Hosts with read-only access to the NFS export. - List of dictionaries. Each dictionary will have any of the keys from I(host_name), I(host_id), I(subnet), I(netgroup), I(domain) and I(ip_address). - If I(adv_host_mgmt_enabled) is C(true) then the accepted keys are I(host_name), I(host_id) and I(ip_address). - If I(adv_host_mgmt_enabled) is C(false) then the accepted keys are I(host_name), I(subnet), I(netgroup), I(domain) and I(ip_address). type: list elements: dict suboptions: host_name: description: - Name of the host. type: str host_id: description: - ID of the host. type: str ip_address: description: - IP address of the host. type: str subnet: description: - Subnet can be an 'IP address/netmask' or 'IP address/prefix length'. type: str netgroup: description: - Netgroup that is defined in NIS or the local netgroup file. type: str domain: description: - DNS domain, where all NFS clients in the domain are included in the host list. type: str read_only_root_hosts: description: - Hosts with read-only for root user access to the NFS export. - List of dictionaries. Each dictionary will have any of the keys from I(host_name), I(host_id), I(subnet), I(netgroup), I(domain) and I(ip_address). - If I(adv_host_mgmt_enabled) is C(true) then the accepted keys are I(host_name), I(host_id) and I(ip_address). - If I(adv_host_mgmt_enabled) is C(false) then the accepted keys are I(host_name), I(subnet), I(netgroup), I(domain) and I(ip_address). type: list elements: dict suboptions: host_name: description: - Name of the host. type: str host_id: description: - ID of the host. type: str ip_address: description: - IP address of the host. type: str subnet: description: - Subnet can be an 'IP address/netmask' or 'IP address/prefix length'. type: str netgroup: description: - Netgroup that is defined in NIS or the local netgroup file. type: str domain: description: - DNS domain, where all NFS clients in the domain are included in the host list. type: str read_write_hosts: description: - Hosts with read and write access to the NFS export. - List of dictionaries. Each dictionary will have any of the keys from I(host_name), I(host_id), I(subnet), I(netgroup), I(domain) and I(ip_address). - If I(adv_host_mgmt_enabled) is C(true) then the accepted keys are I(host_name), I(host_id) and I(ip_address). - If I(adv_host_mgmt_enabled) is C(false) then the accepted keys are I(host_name), I(subnet), I(netgroup), I(domain) and I(ip_address). type: list elements: dict suboptions: host_name: description: - Name of the host. type: str host_id: description: - ID of the host. type: str ip_address: description: - IP address of the host. type: str subnet: description: - Subnet can be an 'IP address/netmask' or 'IP address/prefix length'. type: str netgroup: description: - Netgroup that is defined in NIS or the local netgroup file. type: str domain: description: - DNS domain, where all NFS clients in the domain are included in the host list. type: str read_write_root_hosts: description: - Hosts with read and write for root user access to the NFS export. - List of dictionaries. Each dictionary will have any of the keys from I(host_name), I(host_id), I(subnet), I(netgroup), I(domain) and I(ip_address). - If I(adv_host_mgmt_enabled) is C(true) then the accepted keys are I(host_name), I(host_id) and I(ip_address). - If I(adv_host_mgmt_enabled) is C(false) then the accepted keys are I(host_name), I(subnet), I(netgroup), I(domain) and I(ip_address). type: list elements: dict suboptions: host_name: description: - Name of the host. type: str host_id: description: - ID of the host. type: str ip_address: description: - IP address of the host. type: str subnet: description: - Subnet can be an 'IP address/netmask' or 'IP address/prefix length'. type: str netgroup: description: - Netgroup that is defined in NIS or the local netgroup file. type: str domain: description: - DNS domain, where all NFS clients in the domain are included in the host list. type: str notes: - The I(check_mode) is not supported. """ EXAMPLES = r""" - name: Create nfs export from filesystem dellemc.unity.nfs: unispherehost: "{{unispherehost}}" username: "{{username}}" password: "{{password}}" validate_certs: "{{validate_certs}}" nfs_export_name: "ansible_nfs_from_fs" path: '/' filesystem_id: "fs_377" state: "present" - name: Create nfs export from snapshot dellemc.unity.nfs: unispherehost: "{{unispherehost}}" username: "{{username}}" password: "{{password}}" validate_certs: "{{validate_certs}}" nfs_export_name: "ansible_nfs_from_snap" path: '/' snapshot_name: "ansible_fs_snap" state: "present" - name: Modify nfs export dellemc.unity.nfs: unispherehost: "{{unispherehost}}" username: "{{username}}" password: "{{password}}" validate_certs: "{{validate_certs}}" nfs_export_name: "ansible_nfs_from_fs" nas_server_id: "nas_3" description: "" default_access: "READ_ONLY_ROOT" anonymous_gid: 4294967290 anonymous_uid: 4294967290 state: "present" - name: Add host in nfs export with adv_host_mgmt_enabled as true dellemc.unity.nfs: unispherehost: "{{unispherehost}}" username: "{{username}}" password: "{{password}}" validate_certs: "{{validate_certs}}" nfs_export_name: "ansible_nfs_from_fs" filesystem_id: "fs_377" adv_host_mgmt_enabled: true no_access_hosts: - host_id: "Host_1" read_only_hosts: - host_id: "Host_2" read_only_root_hosts: - host_name: "host_name1" read_write_hosts: - host_name: "host_name2" read_write_root_hosts: - ip_address: "1.1.1.1" host_state: "present-in-export" state: "present" - name: Remove host in nfs export with adv_host_mgmt_enabled as true dellemc.unity.nfs: unispherehost: "{{unispherehost}}" username: "{{username}}" password: "{{password}}" validate_certs: "{{validate_certs}}" nfs_export_name: "ansible_nfs_from_fs" filesystem_id: "fs_377" adv_host_mgmt_enabled: true no_access_hosts: - host_id: "Host_1" read_only_hosts: - host_id: "Host_2" read_only_root_hosts: - host_name: "host_name1" read_write_hosts: - host_name: "host_name2" read_write_root_hosts: - ip_address: "1.1.1.1" host_state: "absent-in-export" state: "present" - name: Add host in nfs export with adv_host_mgmt_enabled as false dellemc.unity.nfs: unispherehost: "{{unispherehost}}" username: "{{username}}" password: "{{password}}" validate_certs: "{{validate_certs}}" nfs_export_name: "ansible_nfs_from_fs" filesystem_id: "fs_377" adv_host_mgmt_enabled: false no_access_hosts: - domain: "google.com" read_only_hosts: - netgroup: "netgroup_admin" read_only_root_hosts: - host_name: "host5" read_write_hosts: - subnet: "168.159.57.4/255.255.255.0" read_write_root_hosts: - ip_address: "10.255.2.4" host_state: "present-in-export" state: "present" - name: Remove host in nfs export with adv_host_mgmt_enabled as false dellemc.unity.nfs: unispherehost: "{{unispherehost}}" username: "{{username}}" password: "{{password}}" validate_certs: "{{validate_certs}}" nfs_export_name: "ansible_nfs_from_fs" filesystem_id: "fs_377" adv_host_mgmt_enabled: false no_access_hosts: - domain: "google.com" read_only_hosts: - netgroup: "netgroup_admin" read_only_root_hosts: - host_name: "host5" read_write_hosts: - subnet: "168.159.57.4/255.255.255.0" read_write_root_hosts: - ip_address: "10.255.2.4" host_state: "absent-in-export" state: "present" - name: Get nfs details dellemc.unity.nfs: unispherehost: "{{unispherehost}}" username: "{{username}}" password: "{{password}}" validate_certs: "{{validate_certs}}" nfs_export_id: "NFSShare_291" state: "present" - name: Delete nfs export by nfs name dellemc.unity.nfs: unispherehost: "{{unispherehost}}" username: "{{username}}" password: "{{password}}" validate_certs: "{{validate_certs}}" nfs_export_name: "ansible_nfs_name" nas_server_name: "ansible_nas_name" state: "absent" """ RETURN = r""" changed: description: Whether or not the resource has changed. returned: always type: bool sample: "false" nfs_share_details: description: Details of the nfs export. returned: When nfs export exists. type: dict contains: anonymous_uid: description: User ID of the anonymous account type: int anonymous_gid: description: Group ID of the anonymous account type: int default_access: description: Default access level for all hosts that can access export type: str description: description: Description about the nfs export type: str id: description: ID of the nfs export type: str min_security: description: NFS enforced security type for users accessing an export type: str name: description: Name of the nfs export type: str no_access_hosts_string: description: Hosts with no access to the nfs export type: str read_only_hosts_string: description: Hosts with read-only access to the nfs export type: str read_only_root_hosts_string: description: Hosts with read-only for root user access to the nfs export type: str read_write_hosts_string: description: Hosts with read and write access to the nfs export type: str read_write_root_hosts_string: description: Hosts with read and write for root user access to export type: str type: description: NFS export type. i.e. filesystem or snapshot type: str export_paths: description: Export paths that can be used to mount and access export type: list filesystem: description: Details of the filesystem on which nfs export is present type: dict contains: UnityFileSystem: description: filesystem details type: dict contains: id: description: ID of the filesystem type: str name: description: Name of the filesystem type: str nas_server: description: Details of the nas server type: dict contains: UnityNasServer: description: NAS server details type: dict contains: id: description: ID of the nas server type: str name: description: Name of the nas server type: str sample: { 'anonymous_gid': 4294967294, 'anonymous_uid': 4294967294, 'creation_time': '2022-03-09 15:05:34.720000+00:00', 'default_access': 'NFSShareDefaultAccessEnum.NO_ACCESS', 'description': '', 'export_option': 1, 'export_paths': [ '**.***.**.**:/dummy-share-123' ], 'filesystem': { 'UnityFileSystem': { 'id': 'fs_id_1', 'name': 'fs_name_1' } }, 'host_accesses': None, 'id': 'NFSShare_14393', 'is_read_only': None, 'min_security': 'NFSShareSecurityEnum.SYS', 'modification_time': '2022-04-25 08:12:28.179000+00:00', 'name': 'dummy-share-123', 'nfs_owner_username': None, 'no_access_hosts': None, 'no_access_hosts_string': 'host1,**.***.*.*', 'path': '/', 'read_only_hosts': None, 'read_only_hosts_string': '', 'read_only_root_access_hosts': None, 'read_only_root_hosts_string': '', 'read_write_hosts': None, 'read_write_hosts_string': '', 'read_write_root_hosts_string': '', 'role': 'NFSShareRoleEnum.PRODUCTION', 'root_access_hosts': None, 'snap': None, 'type': 'NFSTypeEnum.NFS_SHARE', 'existed': True, 'nas_server': { 'UnityNasServer': { 'id': 'nas_id_1', 'name': 'dummy_nas_server' } } } """ import re import traceback try: from ipaddress import ip_network, IPv4Network, IPv6Network HAS_IPADDRESS, IP_ADDRESS_IMP_ERR = True, None except ImportError: HAS_IPADDRESS, IP_ADDRESS_IMP_ERR = False, traceback.format_exc() from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible_collections.dellemc.unity.plugins.module_utils.storage.dell \ import utils LOG = utils.get_logger('nfs') DEFAULT_ACCESS_LIST = ['NO_ACCESS', 'READ_ONLY', 'READ_WRITE', 'ROOT', 'READ_ONLY_ROOT'] MIN_SECURITY_LIST = ['SYS', 'KERBEROS', 'KERBEROS_WITH_INTEGRITY', 'KERBEROS_WITH_ENCRYPTION'] HOST_DICT = dict(type='list', required=False, elements='dict', options=dict(host_name=dict(), host_id=dict(), ip_address=dict(), subnet=dict(), netgroup=dict(), domain=dict())) HOST_STATE_LIST = ['present-in-export', 'absent-in-export'] STATE_LIST = ['present', 'absent'] application_type = "Ansible/1.6.0" class NFS(object): """Class with nfs export operations""" def __init__(self): """ Define all parameters required by this module""" self.module_params = utils.get_unity_management_host_parameters() self.module_params.update(get_nfs_parameters()) mutually_exclusive = [['nfs_export_id', 'nas_server_id'], ['nfs_export_id', 'nas_server_name'], ['filesystem_id', 'filesystem_name', 'snapshot_id', 'snapshot_name'], ['nas_server_id', 'nas_server_name']] required_one_of = [['nfs_export_id', 'nfs_export_name']] """ initialize the ansible module """ self.module = AnsibleModule( argument_spec=self.module_params, supports_check_mode=False, mutually_exclusive=mutually_exclusive, required_one_of=required_one_of) utils.ensure_required_libs(self.module) if not HAS_IPADDRESS: self.module.fail_json(msg=missing_required_lib("ipaddress"), exception=IP_ADDRESS_IMP_ERR) self.unity = utils.get_unity_unisphere_connection(self.module.params, application_type) self.cli = self.unity._cli self.is_given_nfs_for_fs = None if self.module.params['filesystem_name'] or \ self.module.params['filesystem_id']: self.is_given_nfs_for_fs = True elif self.module.params['snapshot_name'] or \ self.module.params['snapshot_id']: self.is_given_nfs_for_fs = False # Contain hosts input & output parameters self.host_param_mapping = { 'no_access_hosts': 'no_access_hosts_string', 'read_only_hosts': 'read_only_hosts_string', 'read_only_root_hosts': 'read_only_root_hosts_string', 'read_write_hosts': 'read_write_hosts_string', 'read_write_root_hosts': 'read_write_root_hosts_string' } # Default_access mapping. keys are giving by user & values are # accepted by SDK self.default_access = {'READ_ONLY_ROOT': 'RO_ROOT'} LOG.info('Got the unity instance for provisioning on Unity') def validate_host_access_data(self, host_dict): """ Validate host access data :param host_dict: Host access data :return None """ fqdn_pat = re.compile(r'(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}' r'[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)') if host_dict.get('host_name'): version = get_ip_version(host_dict.get('host_name')) if version in (4, 6): msg = "IP4/IP6: %s given in host_name instead " \ "of name" % host_dict.get('host_name') LOG.error(msg) self.module.fail_json(msg=msg) if host_dict.get('ip_address'): ip_or_fqdn = host_dict.get('ip_address') version = get_ip_version(ip_or_fqdn) # validate its FQDN or not if version == 0 and not fqdn_pat.match(ip_or_fqdn): msg = "%s is not a valid FQDN" % ip_or_fqdn LOG.error(msg) self.module.fail_json(msg=msg) if host_dict.get('subnet'): subnet = host_dict.get('subnet') subnet_info = subnet.split("/") if len(subnet_info) != 2: msg = "Subnet should be in format 'IP address/netmask' or 'IP address/prefix length'" LOG.error(msg) self.module.fail_json(msg=msg) def validate_adv_host_mgmt_enabled_check(self, host_dict): """ Validate adv_host_mgmt_enabled check :param host_dict: Host access data :return None """ host_dict_keys_set = set(host_dict.keys()) adv_host_mgmt_enabled_true_set = {'host_name', 'host_id', 'ip_address'} adv_host_mgmt_enabled_false_set = {'host_name', 'subnet', 'domain', 'netgroup', 'ip_address'} adv_host_mgmt_enabled_true_diff = host_dict_keys_set - adv_host_mgmt_enabled_true_set adv_host_mgmt_enabled_false_diff = host_dict_keys_set - adv_host_mgmt_enabled_false_set if self.module.params['adv_host_mgmt_enabled'] and adv_host_mgmt_enabled_true_diff != set(): msg = "If 'adv_host_mgmt_enabled' is true then host access should only have %s" % adv_host_mgmt_enabled_true_set LOG.error(msg) self.module.fail_json(msg=msg) elif not self.module.params['adv_host_mgmt_enabled'] and adv_host_mgmt_enabled_false_diff != set(): msg = "If 'adv_host_mgmt_enabled' is false then host access should only have %s" % adv_host_mgmt_enabled_false_set LOG.error(msg) self.module.fail_json(msg=msg) def validate_host_access_input_params(self): """ Validate host access params :return None """ for param in list(self.host_param_mapping.keys()): if self.module.params[param] and (not self.module.params[ 'host_state'] or self.module.params[ 'adv_host_mgmt_enabled'] is None): msg = "'host_state' and 'adv_host_mgmt_enabled' is required along with: %s" % param LOG.error(msg) self.module.fail_json(msg=msg) elif self.module.params[param]: for host_dict in self.module.params[param]: host_dict = {k: v for k, v in host_dict.items() if v} self.validate_adv_host_mgmt_enabled_check(host_dict) self.validate_host_access_data(host_dict) def validate_module_attributes(self): """ Validate module attributes :return None """ param_list = ['nfs_export_name', 'nfs_export_id', 'filesystem_name', 'filesystem_id', 'nas_server_id', 'snapshot_name', 'snapshot_id', 'path'] for param in param_list: if self.module.params[param] and \ len(self.module.params[param].strip()) == 0: msg = "Please provide valid value for: %s" % param LOG.error(msg) self.module.fail_json(msg=msg) def validate_input(self): """ Validate input parameters """ if self.module.params['nfs_export_name'] and \ not self.module.params['snapshot_name'] and \ not self.module.params['snapshot_id']: if ((self.module.params['filesystem_name']) and (not self.module.params['nas_server_id'] and not self.module.params['nas_server_name'])): msg = "Please provide nas server id or name along with " \ "filesystem name and nfs name" LOG.error(msg) self.module.fail_json(msg=msg) if ((not self.module.params['nas_server_id']) and (not self.module.params['nas_server_name']) and (not self.module.params['filesystem_id'])): msg = "Please provide either nas server id/name or " \ "filesystem id" LOG.error(msg) self.module.fail_json(msg=msg) self.validate_module_attributes() self.validate_host_access_input_params() def get_nfs_id_or_name(self): """ Provide nfs_export_id or nfs_export_name user given value :return: value provided by user in nfs_export_id/nfs_export_name :rtype: str """ if self.module.params['nfs_export_id']: return self.module.params['nfs_export_id'] return self.module.params['nfs_export_name'] def get_nas_from_given_input(self): """ Get nas server object :return: nas server object :rtype: UnityNasServer """ LOG.info("Getting nas server details") if not self.module.params['nas_server_id'] and not \ self.module.params['nas_server_name']: return None id_or_name = self.module.params['nas_server_id'] if \ self.module.params['nas_server_id'] else self.module.params[ 'nas_server_name'] try: nas = self.unity.get_nas_server( _id=self.module.params['nas_server_id'], name=self.module.params['nas_server_name']) except utils.UnityResourceNotFoundError as e: # In case of incorrect name msg = "Given nas server not found error: %s" % str(e) LOG.error(msg) self.module.fail_json(msg=msg) except utils.HTTPClientError as e: if e.http_status == 401: msg = "Failed to get nas server: %s due to incorrect " \ "username/password error: %s" % (id_or_name, str(e)) else: msg = "Failed to get nas server: %s error: %s" % ( id_or_name, str(e)) LOG.error(msg) self.module.fail_json(msg=msg) except Exception as e: msg = "Failed to get nas server: %s error: %s" % ( id_or_name, str(e)) LOG.error(msg) self.module.fail_json(msg=msg) if nas and not nas.existed: # In case of incorrect id, sdk return nas object whose attribute # existed=false, instead of raising UnityResourceNotFoundError msg = "Please check nas details it does not exists" LOG.error(msg) self.module.fail_json(msg=msg) LOG.info("Got nas server details") return nas def get_nfs_share(self, id=None, name=None): """ Get the nfs export :return: nfs_export object if nfs exists else None :rtype: UnityNfsShare or None """ try: if not id and not name: msg = "Please give nfs id/name" LOG.error(msg) self.module.fail_json(msg=msg) id_or_name = id if id else name LOG.info("Getting nfs export: %s", id_or_name) if id: # Get nfs details from nfs ID if self.is_given_nfs_for_fs: nfs = self.unity.get_nfs_share( _id=id, filesystem=self.fs_obj) elif self.is_given_nfs_for_fs is False: # nfs from snap nfs = self.unity.get_nfs_share(_id=id, snap=self.snap_obj) else: nfs = self.unity.get_nfs_share(_id=id) else: # Get nfs details from nfs name if self.is_given_nfs_for_fs: nfs = self.unity.get_nfs_share( name=name, filesystem=self.fs_obj) elif self.is_given_nfs_for_fs is False: # nfs from snap nfs = self.unity.get_nfs_share( name=name, snap=self.snap_obj) else: nfs = self.unity.get_nfs_share(name=name) if isinstance(nfs, utils.UnityNfsShareList): # This block will be executed, when we are trying to get nfs # details using nfs name & nas server. nfs_list = nfs LOG.info("Multiple nfs export with same name: %s " "found", id_or_name) if self.nas_obj: for n in nfs_list: if n.filesystem.nas_server == self.nas_obj: return n msg = "Multiple nfs share with same name: %s found. " \ "Given nas server is not correct. Please check" else: msg = "Multiple nfs share with same name: %s found. " \ "Please give nas server" else: # nfs is instance of UnityNfsShare class if nfs and nfs.existed: if self.nas_obj and nfs.filesystem.nas_server != \ self.nas_obj: msg = "nfs found but nas details given is incorrect" LOG.error(msg) self.module.fail_json(msg=msg) LOG.info("Successfully got nfs share for: %s", id_or_name) return nfs elif nfs and not nfs.existed: # in case of incorrect id, sdk returns nfs object whose # attribute existed=False msg = "Please check incorrect nfs id is given" else: msg = "Failed to get nfs share: %s" % id_or_name except utils.UnityResourceNotFoundError as e: msg = "NFS share: %(id_or_name)s not found " \ "error: %(err)s" % {'id_or_name': id_or_name, 'err': str(e)} LOG.info(str(msg)) return None except utils.HTTPClientError as e: if e.http_status == 401: msg = "Failed to get nfs share: %s due to incorrect " \ "username/password error: %s" % (id_or_name, str(e)) else: msg = "Failed to get nfs share: %s error: %s" % (id_or_name, str(e)) except utils.StoropsConnectTimeoutError as e: msg = "Failed to get nfs share: %s check unispherehost IP: %s " \ "error: %s" % (id_or_name, self.module.params['nfs_export_id'], str(e)) except Exception as e: msg = "Failed to get nfs share: %s error: %s" % (id_or_name, str(e)) LOG.error(msg) self.module.fail_json(msg=msg) def delete_nfs_share(self, nfs_obj): """ Delete nfs share :param nfs: NFS share obj :type nfs: UnityNfsShare :return: None """ try: LOG.info("Deleting nfs share: %s", self.get_nfs_id_or_name()) nfs_obj.delete() LOG.info("Deleted nfs share") except Exception as e: msg = "Failed to delete nfs share, error: %s" % str(e) LOG.error(msg) self.module.fail_json(msg=msg) def get_filesystem(self): """ Get filesystem obj :return: filesystem obj :rtype: UnityFileSystem """ if self.module.params['filesystem_id']: id_or_name = self.module.params['filesystem_id'] elif self.module.params['filesystem_name']: id_or_name = self.module.params['filesystem_name'] else: msg = "Please provide filesystem ID/name, to get filesystem" LOG.error(msg) self.module.fail_json(msg=msg) try: if self.module.params['filesystem_name']: if not self.nas_obj: err_msg = "NAS Server is required to get the filesystem" LOG.error(err_msg) self.module.fail_json(msg=err_msg) LOG.info("Getting filesystem by name: %s", id_or_name) fs_obj = self.unity.get_filesystem( name=self.module.params['filesystem_name'], nas_server=self.nas_obj) elif self.module.params['filesystem_id']: LOG.info("Getting filesystem by ID: %s", id_or_name) fs_obj = self.unity.get_filesystem( _id=self.module.params['filesystem_id']) except utils.UnityResourceNotFoundError as e: msg = "Filesystem: %s not found error: %s" % ( id_or_name, str(e)) LOG.error(msg) self.module.fail_json(msg=msg) except utils.HTTPClientError as e: if e.http_status == 401: msg = "Failed to get filesystem due to incorrect " \ "username/password error: %s" % str(e) else: msg = "Failed to get filesystem error: %s" % str(e) LOG.error(msg) except Exception as e: msg = "Failed to get filesystem: %s error: %s" % ( id_or_name, str(e)) LOG.error(msg) self.module.fail_json(msg=msg) if fs_obj and fs_obj.existed: LOG.info("Got the filesystem: %s", id_or_name) return fs_obj else: msg = "Filesystem: %s does not exists" % id_or_name LOG.error(msg) self.module.fail_json(msg=msg) def get_snapshot(self): """ Get snapshot obj :return: Snapshot obj :rtype: UnitySnap """ if self.module.params['snapshot_id']: id_or_name = self.module.params['snapshot_id'] elif self.module.params['snapshot_name']: id_or_name = self.module.params['snapshot_name'] else: msg = "Please provide snapshot ID/name, to get snapshot" LOG.error(msg) self.module.fail_json(msg=msg) LOG.info("Getting snapshot: %s", id_or_name) try: if id_or_name: snap_obj = self.unity.get_snap( _id=self.module.params['snapshot_id'], name=self.module.params['snapshot_name']) else: msg = "Failed to get the snapshot. Please provide snapshot " \ "details" LOG.error(msg) self.module.fail_json(msg=msg) except utils.UnityResourceNotFoundError as e: msg = "Failed to get snapshot: %s error: %s" % (id_or_name, str(e)) LOG.error(msg) self.module.fail_json(msg=msg) except utils.HTTPClientError as e: if e.http_status == 401: msg = "Failed to get snapshot due to incorrect " \ "username/password error: %s" % str(e) else: msg = "Failed to get snapshot error: %s" % str(e) LOG.error(msg) except Exception as e: msg = "Failed to get snapshot: %s error: %s" % (id_or_name, str(e)) LOG.error(msg) self.module.fail_json(msg=msg) if snap_obj and snap_obj.existed: LOG.info("Successfully got the snapshot: %s", id_or_name) return snap_obj else: msg = "Snapshot: %s does not exists" % id_or_name LOG.error(msg) self.module.fail_json(msg=msg) def get_host_obj(self, host_id=None, host_name=None, ip_address=None): """ Get host object :param host_id: ID of the host :param host_name: Name of the host :param ip_address: Network address of the host :return: Host object :rtype: object """ try: host_obj = None host = None if host_id: host = host_id host_obj = self.unity.get_host(_id=host_id) elif host_name: host = host_name host_obj = self.unity.get_host(name=host_name) elif ip_address: host = ip_address host_obj = self.unity.get_host(address=ip_address) if host_obj and host_obj.existed: LOG.info("Successfully got host: %s", host_obj.name) return host_obj else: msg = f'Host : {host} does not exists' LOG.error(msg) self.module.fail_json(msg=msg) except Exception as e: msg = f'Failed to get host {host}, error: {e}' LOG.error(msg) self.module.fail_json(msg=msg) def get_host_access_string_value(self, host_dict): """ Form host access string :host_dict Host access type info :return Host access data in string """ if host_dict.get("host_id"): return self.get_host_obj(host_id=(host_dict.get("host_id"))).name + ',' elif host_dict.get("host_name"): return host_dict.get( "host_name") + ',' elif host_dict.get("ip_address"): return host_dict.get( "ip_address") + ',' elif host_dict.get("subnet"): return host_dict.get( "subnet") + ',' elif host_dict.get("domain"): return "*." + host_dict.get( "domain") + ',' elif host_dict.get("netgroup"): return "@" + host_dict.get( "netgroup") + ',' def get_host_obj_value(self, host_dict): """ Form host access value using host object :host_dict Host access type info :return Host object """ if host_dict.get("host_id"): return self.get_host_obj(host_id=host_dict.get("host_id")) elif host_dict.get("host_name"): return self.get_host_obj(host_name=host_dict.get("host_name")) elif host_dict.get("ip_address"): return self.get_host_obj(ip_address=host_dict.get("ip_address")) def format_host_dict_for_adv_mgmt(self): """ Form host access for advance management :return: Formatted Host access type info :rtype: dict """ result_host = {} for param in list(self.host_param_mapping.keys()): if self.module.params[param]: result_host[param] = [] for host_dict in self.module.params[param]: result_host[param].append(self.get_host_obj_value(host_dict)) if 'read_only_root_hosts' in result_host: result_host['read_only_root_access_hosts'] = result_host.pop('read_only_root_hosts') if 'read_write_root_hosts' in result_host: result_host['root_access_hosts'] = result_host.pop('read_write_root_hosts') return result_host def format_host_dict_for_non_adv_mgmt(self): """ Form host access for non advance management option :return: Formatted Host access type info :rtype: dict """ result_host = {} for param in list(self.host_param_mapping.keys()): if self.module.params[param]: result_host[param] = '' for host_dict in self.module.params[param]: result_host[param] += self.get_host_access_string_value(host_dict) if result_host != {}: # Since we are supporting HOST STRING parameters instead of HOST # parameters, so lets change given input HOST parameter name to # HOST STRING parameter name and strip trailing ',' result_host = {self.host_param_mapping[k]: v[:-1] for k, v in result_host.items()} return result_host def get_host_dict_from_pb(self): """ Traverse all given hosts params and provides with host dict, which has respective host str param name with its value required by SDK :return: dict with key named as respective host str param name & value required by SDK :rtype: dict """ LOG.info("Getting host parameters") result_host = {} if self.module.params['host_state']: if not self.module.params['adv_host_mgmt_enabled']: result_host = self.format_host_dict_for_non_adv_mgmt() else: result_host = self.format_host_dict_for_adv_mgmt() return result_host def get_adv_param_from_pb(self): """ Provide all the advance parameters named as required by SDK :return: all given advanced parameters :rtype: dict """ param = {} LOG.info("Getting all given advance parameter") host_dict = self.get_host_dict_from_pb() if host_dict: param.update(host_dict) fields = ('description', 'anonymous_uid', 'anonymous_gid') for field in fields: if self.module.params[field] is not None: param[field] = self.module.params[field] if self.module.params['min_security'] and self.module.params[ 'min_security'] in utils.NFSShareSecurityEnum.__members__: LOG.info("Getting min_security object from NFSShareSecurityEnum") param['min_security'] = utils.NFSShareSecurityEnum[ self.module.params['min_security']] if self.module.params['default_access']: param['default_access'] = self.get_default_access() LOG.info("Successfully got advance parameter: %s", param) return param def get_default_access(self): LOG.info("Getting default_access object from " "NFSShareDefaultAccessEnum") default_access = self.default_access.get( self.module.params['default_access'], self.module.params['default_access']) try: return utils.NFSShareDefaultAccessEnum[default_access] except KeyError as e: msg = "default_access: %s not found error: %s" % ( default_access, str(e)) LOG.error(msg) self.module.fail_json(msg) def correct_payload_as_per_sdk(self, payload, nfs_details=None): """ Correct payload keys as required by SDK :param payload: Payload used for create/modify operation :type payload: dict :param nfs_details: NFS details :type nfs_details: dict :return: Payload required by SDK :rtype: dict """ ouput_host_param = self.host_param_mapping.values() if set(payload.keys()) & set(ouput_host_param): if not nfs_details or (nfs_details and nfs_details['export_option'] != 1): payload['export_option'] = 1 if 'read_write_root_hosts_string' in payload: # SDK have param named 'root_access_hosts_string' instead of # 'read_write_root_hosts_string' payload['root_access_hosts_string'] = payload.pop( 'read_write_root_hosts_string') return payload def create_nfs_share_from_filesystem(self): """ Create nfs share from given filesystem :return: nfs_share object :rtype: UnityNfsShare """ name = self.module.params['nfs_export_name'] path = self.module.params['path'] if not name or not path: msg = "Please provide name and path both for create" LOG.error(msg) self.module.fail_json(msg=msg) param = self.get_adv_param_from_pb() if 'default_access' in param: # create nfs from FILESYSTEM take 'share_access' as param in SDK param['share_access'] = param.pop('default_access') LOG.info("Param name: 'share_access' is used instead of " "'default_access' in SDK so changed") param = self.correct_payload_as_per_sdk(param) LOG.info("Creating nfs share from filesystem with param: %s", param) try: nfs_obj = utils.UnityNfsShare.create( cli=self.cli, name=name, fs=self.fs_obj, path=path, **param) LOG.info("Successfully created nfs share: %s", nfs_obj) return nfs_obj except utils.UnityNfsShareNameExistedError as e: LOG.error(str(e)) self.module.fail_json(msg=str(e)) except Exception as e: msg = "Failed to create nfs share: %s error: %s" % (name, str(e)) LOG.error(msg) self.module.fail_json(msg=msg) def create_nfs_share_from_snapshot(self): """ Create nfs share from given snapshot :return: nfs_share object :rtype: UnityNfsShare """ name = self.module.params['nfs_export_name'] path = self.module.params['path'] if not name or not path: msg = "Please provide name and path both for create" LOG.error(msg) self.module.fail_json(msg=msg) param = self.get_adv_param_from_pb() param = self.correct_payload_as_per_sdk(param) LOG.info("Creating nfs share from snap with param: %s", param) try: nfs_obj = utils.UnityNfsShare.create_from_snap( cli=self.cli, name=name, snap=self.snap_obj, path=path, **param) LOG.info("Successfully created nfs share: %s", nfs_obj) return nfs_obj except utils.UnityNfsShareNameExistedError as e: LOG.error(str(e)) self.module.fail_json(msg=str(e)) except Exception as e: msg = "Failed to create nfs share: %s error: %s" % (name, str(e)) LOG.error(msg) self.module.fail_json(msg=msg) def create_nfs_share(self): """ Create nfs share from either filesystem/snapshot :return: nfs_share object :rtype: UnityNfsShare """ if self.is_given_nfs_for_fs: # Share to be created from filesystem return self.create_nfs_share_from_filesystem() elif self.is_given_nfs_for_fs is False: # Share to be created from snapshot return self.create_nfs_share_from_snapshot() else: msg = "Please provide filesystem or filesystem snapshot to create NFS export" LOG.error(msg) self.module.fail_json(msg=msg) def convert_host_str_to_list(self, host_str): """ Convert host_str which have comma separated hosts to host_list with ip4/ip6 host obj if IP4/IP6 like string found :param host_str: hosts str separated by comma :return: hosts list, which may contains IP4/IP6 object if given in host_str :rytpe: list """ if not host_str: LOG.debug("Empty host_str given") return [] host_list = [] try: for h in host_str.split(","): version = get_ip_version(h) if version == 4: h = u'{0}'.format(h) h = IPv4Network(h, strict=False) elif version == 6: h = u'{0}'.format(h) h = IPv6Network(h, strict=False) host_list.append(h) except Exception as e: msg = "Error while converting host_str: %s to list error: %s" % ( host_str, str(e)) LOG.error(msg) self.module.fail_json(msg=msg) return host_list def add_host_dict_for_adv(self, existing_host_dict, new_host_dict): """ Compares & adds up new hosts with the existing ones and provide the final consolidated hosts for advance host management :param existing_host_dict: All hosts params details which are associated with existing nfs which to be modified :type existing_host_dict: dict :param new_host_dict: All hosts param details which are to be added :type new_host_dict: dict :return: consolidated hosts params details which contains newly added hosts along with the existing ones :rtype: dict """ modify_host_dict = {} for host_access_key in existing_host_dict: LOG.debug("Checking for param: %s", host_access_key) new_host_obj_list = new_host_dict[host_access_key] if new_host_obj_list and not existing_host_dict[host_access_key]: # Existing nfs host is empty so lets directly add # new_host_str as it is LOG.debug("Existing nfs host key: %s is empty, so lets add new host given value as it is", host_access_key) modify_host_dict[host_access_key] = new_host_obj_list continue existing_host_obj_list = [self.get_host_obj(host_id=existing_host_dict['UnityHost']['id']) for existing_host_dict in existing_host_dict[host_access_key]['UnityHostList']] if not new_host_obj_list: LOG.debug("Nothing to add as no host given") continue existing_set = set(host.id for host in existing_host_obj_list) actual_to_add = [new_host for new_host in new_host_obj_list if new_host.id not in existing_set] if not actual_to_add: LOG.debug("All host given to be added is already added") continue # Lets extends actual_to_add list, which is new with existing actual_to_add.extend(existing_host_obj_list) modify_host_dict[host_access_key] = actual_to_add return modify_host_dict def add_host_dict_for_non_adv(self, existing_host_dict, new_host_dict): """ Compares & adds up new hosts with the existing ones and provide the final consolidated hosts for non advance host management :param existing_host_dict: All hosts params details which are associated with existing nfs which to be modified :type existing_host_dict: dict :param new_host_dict: All hosts param details which are to be added :type new_host_dict: dict :return: consolidated hosts params details which contains newly added hosts along with the existing ones :rtype: dict """ modify_host_dict = {} for host_access_key in existing_host_dict: LOG.debug("Checking add host for param: %s", host_access_key) existing_host_str = existing_host_dict[host_access_key] existing_host_list = self.convert_host_str_to_list( existing_host_str) new_host_str = new_host_dict[host_access_key] new_host_list = self.convert_host_str_to_list( new_host_str) if not new_host_list: LOG.debug("Nothing to add as no host given") continue if new_host_list and not existing_host_list: # Existing nfs host is empty so lets directly add # new_host_str as it is LOG.debug("Existing nfs host key: %s is empty, so lets add new host given value as it is", host_access_key) modify_host_dict[host_access_key] = new_host_str continue actual_to_add = list(set(new_host_list) - set(existing_host_list)) if not actual_to_add: LOG.debug("All host given to be added is already added") continue # Lets extends actual_to_add list, which is new with existing actual_to_add.extend(existing_host_list) # Since SDK takes host_str as ',' separated instead of list, so # lets convert str to list # Note: explicity str() needed here to convert IP4/IP6 object modify_host_dict[host_access_key] = ",".join(str(v) for v in actual_to_add) return modify_host_dict def remove_host_dict_for_adv(self, existing_host_dict, new_host_dict): """ Compares & remove new hosts from the existing ones and provide the remaining hosts for advance host management :param existing_host_dict: All hosts params details which are associated with existing nfs which to be modified :type existing_host_dict: dict :param new_host_dict: All hosts param details which are to be removed :type new_host_dict: dict :return: existing hosts params details from which given new hosts are removed :rtype: dict """ modify_host_dict = {} for host_access_key in existing_host_dict: LOG.debug("Checking host for param: %s", host_access_key) if not existing_host_dict[host_access_key]: # existing list is already empty, so nothing to remove LOG.debug("Existing list is already empty, so nothing to remove") continue existing_host_obj_list = [self.get_host_obj(host_id=existing_host_dict['UnityHost']['id']) for existing_host_dict in existing_host_dict[host_access_key]['UnityHostList']] new_host_obj_list = new_host_dict[host_access_key] if new_host_obj_list == []: LOG.debug("Nothing to remove as no host given") continue unique_new_host_list = [new_host.id for new_host in new_host_obj_list] if len(new_host_obj_list) > len(set(unique_new_host_list)): msg = f'Duplicate host given: {unique_new_host_list} in host param: {host_access_key}' LOG.error(msg) self.module.fail_json(msg=msg) unique_existing_host_list = [host.id for host in existing_host_obj_list] actual_to_remove = list(set(unique_new_host_list) & set( unique_existing_host_list)) if not actual_to_remove: continue final_host_list = [existing_host for existing_host in existing_host_obj_list if existing_host.id not in unique_new_host_list] modify_host_dict[host_access_key] = final_host_list return modify_host_dict def remove_host_dict_for_non_adv(self, existing_host_dict, new_host_dict): """ Compares & remove new hosts from the existing ones and provide the remaining hosts for non advance host management :param existing_host_dict: All hosts params details which are associated with existing nfs which to be modified :type existing_host_dict: dict :param new_host_dict: All hosts param details which are to be removed :type new_host_dict: dict :return: existing hosts params details from which given new hosts are removed :rtype: dict """ modify_host_dict = {} for host_access_key in existing_host_dict: LOG.debug("Checking remove host for param: %s", host_access_key) existing_host_str = existing_host_dict[host_access_key] existing_host_list = self.convert_host_str_to_list( existing_host_str) new_host_str = new_host_dict[host_access_key] new_host_list = self.convert_host_str_to_list( new_host_str) if not new_host_list: LOG.debug("Nothing to remove as no host given") continue if len(new_host_list) > len(set(new_host_list)): msg = "Duplicate host given: %s in host param: %s" % ( new_host_list, host_access_key) LOG.error(msg) self.module.fail_json(msg=msg) if new_host_list and not existing_host_list: # existing list is already empty, so nothing to remove LOG.debug("Existing list is already empty, so nothing to remove") continue actual_to_remove = list(set(new_host_list) & set( existing_host_list)) if not actual_to_remove: continue final_host_list = list(set(existing_host_list) - set( actual_to_remove)) # Since SDK takes host_str as ',' separated instead of list, so # lets convert str to list # Note: explicity str() needed here to convert IP4/IP6 object modify_host_dict[host_access_key] = ",".join(str(v) for v in final_host_list) return modify_host_dict def add_host(self, existing_host_dict, new_host_dict): """ Compares & adds up new hosts with the existing ones and provide the final consolidated hosts :param existing_host_dict: All hosts params details which are associated with existing nfs which to be modified :type existing_host_dict: dict :param new_host_dict: All hosts param details which are to be added :type new_host_dict: dict :return: consolidated hosts params details which contains newly added hosts along with the existing ones :rtype: dict """ if self.module.params['adv_host_mgmt_enabled']: modify_host_dict = self.add_host_dict_for_adv(existing_host_dict, new_host_dict) else: modify_host_dict = self.add_host_dict_for_non_adv(existing_host_dict, new_host_dict) return modify_host_dict def remove_host(self, existing_host_dict, new_host_dict): """ Compares & remove new hosts from the existing ones and provide the remaining hosts :param existing_host_dict: All hosts params details which are associated with existing nfs which to be modified :type existing_host_dict: dict :param new_host_dict: All hosts param details which are to be removed :type new_host_dict: dict :return: existing hosts params details from which given new hosts are removed :rtype: dict """ if self.module.params['adv_host_mgmt_enabled']: modify_host_dict = self.remove_host_dict_for_adv(existing_host_dict, new_host_dict) else: modify_host_dict = self.remove_host_dict_for_non_adv(existing_host_dict, new_host_dict) return modify_host_dict def modify_nfs_share(self, nfs_obj): """ Modify given nfs share :param nfs_obj: NFS share obj :type nfs_obj: UnityNfsShare :return: tuple(bool, nfs_obj) - bool: indicates whether nfs_obj is modified or not - nfs_obj: same nfs_obj if not modified else modified nfs_obj :rtype: tuple """ modify_param = {} LOG.info("Modifying nfs share") nfs_details = nfs_obj._get_properties() fields = ('description', 'anonymous_uid', 'anonymous_gid') for field in fields: if self.module.params[field] is not None and \ self.module.params[field] != nfs_details[field]: modify_param[field] = self.module.params[field] if self.module.params['min_security'] and self.module.params[ 'min_security'] != nfs_obj.min_security.name: modify_param['min_security'] = utils.NFSShareSecurityEnum[ self.module.params['min_security']] if self.module.params['default_access']: default_access = self.get_default_access() if default_access != nfs_obj.default_access: modify_param['default_access'] = default_access new_host_dict = self.get_host_dict_from_pb() if new_host_dict: try: if is_nfs_have_host_with_host_obj(nfs_details) and not self.module.params['adv_host_mgmt_enabled']: msg = "Modification of nfs host is restricted using adv_host_mgmt_enabled as false since nfs " \ "already have host added using host obj" LOG.error(msg) self.module.fail_json(msg=msg) elif is_nfs_have_host_with_host_string(nfs_details) and self.module.params['adv_host_mgmt_enabled']: msg = "Modification of nfs host is restricted using adv_host_mgmt_enabled as true since nfs " \ "already have host added without host obj" LOG.error(msg) self.module.fail_json(msg=msg) LOG.info("Extracting same given param from nfs") existing_host_dict = {k: nfs_details[k] for k in new_host_dict} except KeyError as e: msg = "Failed to extract key-value from current nfs: %s" % \ str(e) LOG.error(msg) self.module.fail_json(msg=msg) if self.module.params['host_state'] == HOST_STATE_LIST[0]: # present-in-export LOG.info("Getting host to be added") modify_host_dict = self.add_host(existing_host_dict, new_host_dict) else: # absent-in-export LOG.info("Getting host to be removed") modify_host_dict = self.remove_host(existing_host_dict, new_host_dict) if modify_host_dict: modify_param.update(modify_host_dict) if not modify_param: LOG.info("Existing nfs attribute value is same as given input, " "so returning same nfs object - idempotency case") return False, nfs_obj modify_param = self.correct_payload_as_per_sdk( modify_param, nfs_details) try: resp = nfs_obj.modify(**modify_param) resp.raise_if_err() except Exception as e: msg = "Failed to modify nfs error: %s" % str(e) LOG.error(msg) self.module.fail_json(msg=msg) return True, self.get_nfs_share(id=nfs_obj.id) def perform_module_operation(self): """ Perform different actions on nfs based on user parameter chosen in playbook """ changed = False nfs_share_details = {} self.validate_input() self.nas_obj = None if self.module.params['nas_server_id'] or self.module.params[ 'nas_server_name']: self.nas_obj = self.get_nas_from_given_input() self.fs_obj = None self.snap_obj = None if self.is_given_nfs_for_fs: self.fs_obj = self.get_filesystem() elif self.is_given_nfs_for_fs is False: self.snap_obj = self.get_snapshot() # Get nfs Share nfs_obj = self.get_nfs_share( id=self.module.params['nfs_export_id'], name=self.module.params['nfs_export_name'] ) # Delete nfs Share if self.module.params['state'] == STATE_LIST[1]: if nfs_obj: # delete_nfs_share() does not return any value # In case of successful delete, lets nfs_obj set None # to avoid fetching and displaying attribute nfs_obj = self.delete_nfs_share(nfs_obj) changed = True elif not nfs_obj: # create nfs_obj = self.create_nfs_share() changed = True else: # modify changed, nfs_obj = self.modify_nfs_share(nfs_obj) # Get display attributes if self.module.params['state'] and nfs_obj: nfs_share_details = get_nfs_share_display_attrs(nfs_obj) result = {"changed": changed, "nfs_share_details": nfs_share_details} self.module.exit_json(**result) def get_nfs_share_display_attrs(nfs_obj): """ Provide nfs share attributes for display :param nfs: NFS share obj :type nfs: UnityNfsShare :return: nfs_share_details :rtype: dict """ LOG.info("Getting nfs share details from nfs share object") nfs_share_details = nfs_obj._get_properties() # Adding filesystem_name to nfs_share_details LOG.info("Updating filesystem details") nfs_share_details['filesystem']['UnityFileSystem']['name'] = \ nfs_obj.filesystem.name if 'id' not in nfs_share_details['filesystem']['UnityFileSystem']: nfs_share_details['filesystem']['UnityFileSystem']['id'] = \ nfs_obj.filesystem.id # Adding nas server details LOG.info("Updating nas server details") nas_details = nfs_obj.filesystem._get_properties()['nas_server'] nas_details['UnityNasServer']['name'] = \ nfs_obj.filesystem.nas_server.name nfs_share_details['nas_server'] = nas_details # Adding snap.id & snap.name if nfs_obj is for snap if is_nfs_obj_for_snap(nfs_obj): LOG.info("Updating snap details") nfs_share_details['snap']['UnitySnap']['id'] = nfs_obj.snap.id nfs_share_details['snap']['UnitySnap']['name'] = nfs_obj.snap.name LOG.info("Successfully updated nfs share details") return nfs_share_details def is_nfs_have_host_with_host_obj(nfs_details): """ Check whether nfs host is already added using host obj :param nfs_details: nfs details :return: True if nfs have host already added with host obj else False :rtype: bool """ host_obj_params = ('no_access_hosts', 'read_only_hosts', 'read_only_root_access_hosts', 'read_write_hosts', 'root_access_hosts') for host_obj_param in host_obj_params: if nfs_details.get(host_obj_param): return True return False def is_nfs_have_host_with_host_string(nfs_details): """ Check whether nfs host is already added using host by string method :param nfs_details: nfs details :return: True if nfs have host already added with host string method else False :rtype: bool """ host_obj_params = ( 'no_access_hosts_string', 'read_only_hosts_string', 'read_only_root_hosts_string', 'read_write_hosts_string', 'read_write_root_hosts_string' ) for host_obj_param in host_obj_params: if nfs_details.get(host_obj_param): return True return False def get_ip_version(val): try: val = u'{0}'.format(val) ip = ip_network(val, strict=False) return ip.version except ValueError: return 0 def is_nfs_obj_for_fs(nfs_obj): """ Check whether the nfs_obj if for filesystem :param nfs_obj: NFS share object :return: True if nfs_obj is of filesystem type :rtype: bool """ if nfs_obj.type == utils.NFSTypeEnum.NFS_SHARE: return True return False def is_nfs_obj_for_snap(nfs_obj): """ Check whether the nfs_obj if for snapshot :param nfs_obj: NFS share object :return: True if nfs_obj is of snapshot type :rtype: bool """ if nfs_obj.type == utils.NFSTypeEnum.NFS_SNAPSHOT: return True return False def get_nfs_parameters(): """ Provides parameters required for the NFS share module on Unity """ return dict( nfs_export_name=dict(required=False, type='str'), nfs_export_id=dict(required=False, type='str'), filesystem_id=dict(required=False, type='str'), filesystem_name=dict(required=False, type='str'), snapshot_id=dict(required=False, type='str'), snapshot_name=dict(required=False, type='str'), nas_server_id=dict(required=False, type='str'), nas_server_name=dict(required=False, type='str'), path=dict(required=False, type='str', no_log=True), description=dict(required=False, type='str'), default_access=dict(required=False, type='str', choices=DEFAULT_ACCESS_LIST), min_security=dict(required=False, type='str', choices=MIN_SECURITY_LIST), adv_host_mgmt_enabled=dict(required=False, type='bool', default=None), no_access_hosts=HOST_DICT, read_only_hosts=HOST_DICT, read_only_root_hosts=HOST_DICT, read_write_hosts=HOST_DICT, read_write_root_hosts=HOST_DICT, host_state=dict(required=False, type='str', choices=HOST_STATE_LIST), anonymous_uid=dict(required=False, type='int'), anonymous_gid=dict(required=False, type='int'), state=dict(required=True, type='str', choices=STATE_LIST) ) def main(): """ Create UnityNFS object and perform action on it based on user input from playbook""" obj = NFS() obj.perform_module_operation() if __name__ == '__main__': main()