na_ontap_qtree / na_ontap_gather_facts: qtree new params and modify operation / new subsets (#55825)

* qtree new parameters and modify action

* fixing pylint offenses

* fixing shippable fails

* added igroup_info gather_subset

* added qos_policy_info / qos_adaptive_policy_info gather_subsets

* pylint fixes

* fixing shippable test failure

* requiring option flexvol_name for na_ontap_qtree module
This commit is contained in:
vicmunoz 2019-05-07 20:32:27 +02:00 committed by Jake Jackson
parent ba9fee6c37
commit 6f0ac90ec3
2 changed files with 216 additions and 85 deletions

View file

@ -32,12 +32,12 @@ options:
description:
- When supplied, this argument will restrict the facts collected
to a given subset. Possible values for this argument include
"aggregate_info", "cluster_node_info", "lun_info", "net_ifgrp_info",
"aggregate_info", "cluster_node_info", "igroup_info", "lun_info", "net_ifgrp_info",
"net_interface_info", "net_port_info", "nvme_info", "nvme_interface_info",
"nvme_namespace_info", "nvme_subsystem_info", "ontap_version",
"security_key_manager_key_info", "security_login_account_info",
"storage_failover_info", "volume_info", "vserver_info",
"vserver_login_banner_info", "vserver_motd_info"
"qos_adaptive_policy_info", "qos_policy_info", "security_key_manager_key_info",
"security_login_account_info", "storage_failover_info", "volume_info",
"vserver_info", "vserver_login_banner_info", "vserver_motd_info"
Can specify a list of values to include a larger subset. Values can also be used
with an initial C(M(!)) to specify that a specific subset should
not be collected.
@ -103,7 +103,10 @@ ontap_facts:
"vserver_login_banner_info": {...},
"vserver_motd_info": {...},
"vserver_info": {...},
"ontap_version": {...}
"ontap_version": {...},
"igroup_info": {...},
"qos_policy_info": {...},
"qos_adaptive_policy_info": {...}
}'
'''
@ -128,6 +131,7 @@ HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPGatherFacts(object):
'''Class with gather facts methods'''
def __init__(self, module):
self.module = module
@ -278,6 +282,37 @@ class NetAppONTAPGatherFacts(object):
},
'min_version': '0',
},
'igroup_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'igroup-get-iter',
'attribute': 'initiator-group-info',
'field': ('vserver', 'initiator-group-name'),
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'qos_policy_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'qos-policy-group-get-iter',
'attribute': 'qos-policy-group-info',
'field': 'policy-group',
'query': {'max-records': '1024'},
},
'min_version': '0',
},
# supported in ONTAP 9.3 and onwards
'qos_adaptive_policy_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'qos-adaptive-policy-group-get-iter',
'attribute': 'qos-adaptive-policy-group-info',
'field': 'policy-group',
'query': {'max-records': '1024'},
},
'min_version': '130',
},
# supported in ONTAP 9.4 and onwards
'nvme_info': {
'method': self.get_generic_get_iter,
@ -327,34 +362,41 @@ class NetAppONTAPGatherFacts(object):
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def ontapi(self):
'''Method to get ontapi version'''
api = 'system-get-ontapi-version'
api_call = netapp_utils.zapi.NaElement(api)
try:
results = self.server.invoke_successfully(api_call, enable_tunneling=False)
ontapi_version = results.get_child_content('minor-version')
return ontapi_version if ontapi_version is not None else '0'
except netapp_utils.zapi.NaApiError as e:
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error calling API %s: %s" %
(api, to_native(e)), exception=traceback.format_exc())
(api, to_native(error)), exception=traceback.format_exc())
def call_api(self, call, query=None):
'''Main method to run an API call'''
api_call = netapp_utils.zapi.NaElement(call)
result = None
if query:
for k, v in query.items():
# Can v be nested?
api_call.add_new_child(k, v)
for key, val in query.items():
# Can val be nested?
api_call.add_new_child(key, val)
try:
result = self.server.invoke_successfully(api_call, enable_tunneling=False)
return result
except netapp_utils.zapi.NaApiError as e:
except netapp_utils.zapi.NaApiError as error:
if call in ['security-key-manager-key-get-iter']:
return result
else:
self.module.fail_json(msg="Error calling API %s: %s" % (call, to_native(e)), exception=traceback.format_exc())
self.module.fail_json(msg="Error calling API %s: %s"
% (call, to_native(error)), exception=traceback.format_exc())
def get_ifgrp_info(self):
'''Method to get network port ifgroups info'''
try:
net_port_info = self.netapp_info['net_port_info']
except KeyError:
@ -372,14 +414,22 @@ class NetAppONTAPGatherFacts(object):
query = dict()
query['node'], query['ifgrp-name'] = ifgrp.split(':')
tmp = self.get_generic_get_iter('net-port-ifgrp-get', field=('node', 'ifgrp-name'), attribute='net-ifgrp-info', query=query, children='attributes')
tmp = self.get_generic_get_iter('net-port-ifgrp-get', field=('node', 'ifgrp-name'),
attribute='net-ifgrp-info', query=query)
net_ifgrp_info = net_ifgrp_info.copy()
net_ifgrp_info.update(tmp)
return net_ifgrp_info
def get_generic_get_iter(self, call, attribute=None, field=None, query=None, children='attributes-list'):
def get_generic_get_iter(self, call, attribute=None, field=None, query=None):
'''Method to run a generic get-iter call'''
generic_call = self.call_api(call, query)
if call == 'net-port-ifgrp-get':
children = 'attributes'
else:
children = 'attributes-list'
if generic_call is None:
return None
@ -394,25 +444,27 @@ class NetAppONTAPGatherFacts(object):
return None
for child in attributes_list.get_children():
d = xmltodict.parse(child.to_string(), xml_attribs=False)
dic = xmltodict.parse(child.to_string(), xml_attribs=False)
if attribute is not None:
d = d[attribute]
dic = dic[attribute]
if isinstance(field, str):
unique_key = _finditem(d, field)
unique_key = _finditem(dic, field)
out = out.copy()
out.update({unique_key: convert_keys(json.loads(json.dumps(d)))})
out.update({unique_key: convert_keys(json.loads(json.dumps(dic)))})
elif isinstance(field, tuple):
unique_key = ':'.join([_finditem(d, el) for el in field])
unique_key = ':'.join([_finditem(dic, el) for el in field])
out = out.copy()
out.update({unique_key: convert_keys(json.loads(json.dumps(d)))})
out.update({unique_key: convert_keys(json.loads(json.dumps(dic)))})
else:
out.append(convert_keys(json.loads(json.dumps(d))))
out.append(convert_keys(json.loads(json.dumps(dic))))
return out
def get_all(self, gather_subset):
'''Method to get all subsets'''
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_gather_facts", cserver)
@ -430,6 +482,8 @@ class NetAppONTAPGatherFacts(object):
return self.netapp_info
def get_subset(self, gather_subset, version):
'''Method to get a single subset'''
runable_subsets = set()
exclude_subsets = set()
usable_subsets = [key for key in self.fact_subsets.keys() if version >= self.fact_subsets[key]['min_version']]
@ -471,9 +525,9 @@ def __finditem(obj, key):
if key in obj:
return obj[key]
for dummy, v in obj.items():
if isinstance(v, dict):
item = __finditem(v, key)
for dummy, val in obj.items():
if isinstance(val, dict):
item = __finditem(val, key)
if item is not None:
return item
return None
@ -487,18 +541,22 @@ def _finditem(obj, key):
raise KeyError(key)
def convert_keys(d):
def convert_keys(d_param):
'''Method to convert hyphen to underscore'''
out = {}
if isinstance(d, dict):
for k, v in d.items():
v = convert_keys(v)
out[k.replace('-', '_')] = v
if isinstance(d_param, dict):
for key, val in d_param.items():
val = convert_keys(val)
out[key.replace('-', '_')] = val
else:
return d
return d_param
return out
def main():
'''Execute action'''
argument_spec = netapp_utils.na_ontap_host_argument_spec()
argument_spec.update(dict(
state=dict(default='info', choices=['info']),
@ -520,10 +578,10 @@ def main():
gather_subset = module.params['gather_subset']
if gather_subset is None:
gather_subset = ['all']
v = NetAppONTAPGatherFacts(module)
g = v.get_all(gather_subset)
gf_obj = NetAppONTAPGatherFacts(module)
gf_all = gf_obj.get_all(gather_subset)
result = {'state': state, 'changed': False}
module.exit_json(ansible_facts={'ontap_facts': g}, **result)
module.exit_json(ansible_facts={'ontap_facts': gf_all}, **result)
if __name__ == '__main__':

View file

@ -2,11 +2,9 @@
# (c) 2018-2019, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'certified'}
@ -46,12 +44,35 @@ options:
flexvol_name:
description:
- The name of the FlexVol the qtree should exist on. Required when C(state=present).
required: true
vserver:
description:
- The name of the vserver to use.
required: true
export_policy:
description:
- The name of the export policy to apply.
version_added: '2.9'
security_style:
description:
- The security style for the qtree.
choices: ['unix', 'ntfs', 'mixed']
version_added: '2.9'
oplocks:
description:
- Whether the oplocks should be enabled or not for the qtree.
choices: ['enabled', 'disabled']
version_added: '2.9'
unix_permissions:
description:
- File permissions bits of the qtree.
version_added: '2.9'
'''
EXAMPLES = """
@ -81,26 +102,31 @@ RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapQTree(object):
'''Class with qtree operations'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=[
'present', 'absent'], default='present'),
state=dict(required=False,
choices=['present', 'absent'],
default='present'),
name=dict(required=True, type='str'),
from_name=dict(required=False, type='str'),
flexvol_name=dict(type='str'),
vserver=dict(required=True, type='str'),
export_policy=dict(required=False, type='str'),
security_style=dict(required=False, choices=['unix', 'ntfs', 'mixed']),
oplocks=dict(required=False, choices=['enabled', 'disabled']),
unix_permissions=dict(required=False, type='str'),
))
self.module = AnsibleModule(
@ -110,81 +136,93 @@ class NetAppOntapQTree(object):
],
supports_check_mode=True
)
p = self.module.params
# set up state variables
self.state = p['state']
self.name = p['name']
self.from_name = p['from_name']
self.flexvol_name = p['flexvol_name']
self.vserver = p['vserver']
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=self.vserver)
module=self.module, vserver=self.parameters['vserver'])
def get_qtree(self, name=None):
"""
Checks if the qtree exists.
:param:
name : qtree name
:return:
True if qtree found
Details about the qtree
False if qtree is not found
:rtype: bool
"""
if name is None:
name = self.name
name = self.parameters['name']
qtree_list_iter = netapp_utils.zapi.NaElement('qtree-list-iter')
query_details = netapp_utils.zapi.NaElement.create_node_with_children(
'qtree-info', **{'vserver': self.vserver,
'volume': self.flexvol_name,
'qtree-info', **{'vserver': self.parameters['vserver'],
'volume': self.parameters['flexvol_name'],
'qtree': name})
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
qtree_list_iter.add_child_elem(query)
result = self.server.invoke_successfully(qtree_list_iter,
enable_tunneling=True)
return_q = False
if (result.get_child_by_name('num-records') and
int(result.get_child_content('num-records')) >= 1):
return True
else:
return False
return_q = {'export_policy': result['attributes-list']['qtree-info']['export-policy'],
'unix_permissions': result['attributes-list']['qtree-info']['mode'],
'oplocks': result['attributes-list']['qtree-info']['oplocks'],
'security_style': result['attributes-list']['qtree-info']['security-style']}
return return_q
def create_qtree(self):
"""
Create a qtree
"""
options = {'qtree': self.parameters['name'], 'volume': self.parameters['flexvol_name']}
if self.parameters.get('export_policy'):
options['export-policy'] = self.parameters['export_policy']
if self.parameters.get('security_style'):
options['security-style'] = self.parameters['security_style']
if self.parameters.get('oplocks'):
options['oplocks'] = self.parameters['oplocks']
if self.parameters.get('unix_permissions'):
options['mode'] = self.parameters['unix_permissions']
qtree_create = netapp_utils.zapi.NaElement.create_node_with_children(
'qtree-create', **{'volume': self.flexvol_name,
'qtree': self.name})
'qtree-create', **options)
try:
self.server.invoke_successfully(qtree_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error provisioning qtree %s: %s" % (self.name, to_native(e)),
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error provisioning qtree %s: %s"
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_qtree(self):
path = '/vol/%s/%s' % (self.flexvol_name, self.name)
"""
Delete a qtree
"""
path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])
qtree_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'qtree-delete', **{'qtree': path})
try:
self.server.invoke_successfully(qtree_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error deleting qtree %s: %s" % (path, to_native(e)),
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error deleting qtree %s: %s" % (path, to_native(error)),
exception=traceback.format_exc())
def rename_qtree(self):
path = '/vol/%s/%s' % (self.flexvol_name, self.from_name)
new_path = '/vol/%s/%s' % (self.flexvol_name, self.name)
"""
Rename a qtree
"""
path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['from_name'])
new_path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])
qtree_rename = netapp_utils.zapi.NaElement.create_node_with_children(
'qtree-rename', **{'qtree': path,
'new-qtree-name': new_path})
@ -192,25 +230,57 @@ class NetAppOntapQTree(object):
try:
self.server.invoke_successfully(qtree_rename,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error renaming qtree %s: %s" % (self.from_name, to_native(e)),
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error renaming qtree %s: %s"
% (self.parameters['from_name'], to_native(error)),
exception=traceback.format_exc())
def modify_qtree(self):
"""
Modify a qtree
"""
options = {'qtree': self.parameters['name'], 'volume': self.parameters['flexvol_name']}
if self.parameters.get('export_policy'):
options['export-policy'] = self.parameters['export_policy']
if self.parameters.get('security_style'):
options['security-style'] = self.parameters['security_style']
if self.parameters.get('oplocks'):
options['oplocks'] = self.parameters['oplocks']
if self.parameters.get('unix_permissions'):
options['mode'] = self.parameters['unix_permissions']
qtree_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'qtree-modify', **options)
try:
self.server.invoke_successfully(qtree_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying qtree %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def apply(self):
changed = False
qtree_exists = False
rename_qtree = False
'''Call create/delete/modify/rename operations'''
# changed = False
# rename_qtree = False
# modified_qtree = None
changed, rename_qtree, modified_qtree = False, False, None
netapp_utils.ems_log_event("na_ontap_qtree", self.server)
qtree_detail = self.get_qtree()
if qtree_detail:
qtree_exists = True
if self.state == 'absent': # delete
# delete or modify qtree
if self.parameters['state'] == 'absent': # delete
changed = True
elif self.state == 'present':
else:
modified_qtree = self.na_helper.get_modified_attributes(
qtree_detail, self.parameters)
if modified_qtree is not None:
changed = True
elif self.parameters['state'] == 'present':
# create or rename qtree
if self.from_name:
if self.get_qtree(self.from_name) is None:
self.module.fail_json(msg="Error renaming qtree %s: does not exists" % self.from_name)
if self.parameters.get('from_name'):
if self.get_qtree(self.parameters['from_name']) is None:
self.module.fail_json(
msg="Error renaming qtree %s: does not exists"
% self.parameters['from_name'])
else:
changed = True
rename_qtree = True
@ -220,20 +290,23 @@ class NetAppOntapQTree(object):
if self.module.check_mode:
pass
else:
if self.state == 'present':
if self.parameters['state'] == 'present':
if rename_qtree:
self.rename_qtree()
elif modified_qtree:
self.modify_qtree()
else:
self.create_qtree()
elif self.state == 'absent':
elif self.parameters['state'] == 'absent':
self.delete_qtree()
self.module.exit_json(changed=changed)
def main():
v = NetAppOntapQTree()
v.apply()
'''Apply qtree operations from playbook'''
qtree_obj = NetAppOntapQTree()
qtree_obj.apply()
if __name__ == '__main__':