File: //usr/lib/python3.9/site-packages/ansible_collections/dellemc/unity/plugins/modules/nfs.py
# 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) <[email protected]>
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()