����JFIF��������� Mr.X
  
  __  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

deexcl@216.73.217.71: ~ $
# 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) <ansible.team@dell.com>

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()

Filemanager

Name Type Size Permission Actions
__pycache__ Folder 0755
cifsserver.py File 23.98 KB 0644
consistencygroup.py File 59.71 KB 0644
filesystem.py File 76.11 KB 0644
filesystem_snapshot.py File 30.21 KB 0644
host.py File 39.13 KB 0644
info.py File 54.49 KB 0644
interface.py File 18.75 KB 0644
nasserver.py File 47.26 KB 0644
nfs.py File 70.05 KB 0644
nfsserver.py File 19.93 KB 0644
smbshare.py File 34.9 KB 0644
snapshot.py File 27.47 KB 0644
snapshotschedule.py File 38.27 KB 0644
storagepool.py File 34.52 KB 0644
tree_quota.py File 26.39 KB 0644
user_quota.py File 39.42 KB 0644
volume.py File 47.02 KB 0644