Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ONTAP information gathering module #43888

Merged
merged 5 commits into from
Aug 28, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
296 changes: 296 additions & 0 deletions lib/ansible/modules/storage/netapp/na_ontap_gather_facts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
#!/usr/bin/python

# (c) 2018 Piotr Olczak <piotr.olczak@redhat.com>
# 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': 'community'}

DOCUMENTATION = '''
module: na_ontap_gather_facts
author: Piotr Olczak (polczak@redhat.com)
extends_documentation_fragment:
- netapp.na_ontap
short_description: NetApp information gatherer
description:
- This module allows you to gather various information about ONTAP configuration
version_added: "2.7"
requirements:
- netapp_lib
options:
state:
description:
- Returns "info"
default: "info"
required: false
choices: ['info']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we would have liked to only use present and absent for state.

But since it's optional, no big deal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll keep it like this.

'''

EXAMPLES = '''
- name: Get NetApp info (Password Authentication)
na_ontap_gather_facts:
state: info
hostname: "na-vsim"
username: "admin"
password: "admins_password"

- debug:
var: ontap_facts
'''

RETURN = '''
ontap_facts:
description: Returns various information about NetApp cluster configuration
returned: always
type: dict
sample: '{
"ontap_facts": {
"aggregate_info": {...},
"cluster_node_info": {...},
"net_ifgrp_info": {...},
"net_interface_info": {...},
"net_port_info": {...},
"security_key_manager_key_info": {...},
"security_login_account_info": {...},
"volume_info": {...},
"lun_info": {...},
"storage_failover_info": {...},
"vserver_login_banner_info": {...},
"vserver_motd_info": {...},
"vserver_info": {...}
}'
'''

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

try:
import xmltodict
HAS_XMLTODICT = True
except ImportError:
HAS_XMLTODICT = False

try:
import json
HAS_JSON = True
except ImportError:
HAS_JSON = False

HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()


class NetAppGatherFacts(object):

def __init__(self, module):
self.module = module
self.netapp_info = dict()

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)

def call_api(self, call, query=None):
api_call = netapp_utils.zapi.NaElement(call)
result = None

if query:
for k, v in query.items():
# Can v be nested?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alas, yes.

api_call.add_new_child(k, v)
try:
result = self.server.invoke_successfully(api_call, enable_tunneling=False)
return result
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error calling API %s: %s" %
(call, to_native(e)), exception=traceback.format_exc())

def get_ifgrp_info(self):
net_port_info = self.netapp_info['net_port_info']
interfaces = net_port_info.keys()

ifgrps = []
for ifn in interfaces:
if net_port_info[ifn]['port_type'] == 'if_group':
ifgrps.append(ifn)

net_ifgrp_info = dict()
for ifgrp in ifgrps:
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')
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'):
generic_call = self.call_api(call, query)

if field is None:
out = []
else:
out = {}

attributes_list = generic_call.get_child_by_name(children)

if attributes_list is None:
return None

for child in attributes_list.get_children():
d = xmltodict.parse(child.to_string(), xml_attribs=False)

if attribute is not None:
d = d[attribute]

if isinstance(field, str):
unique_key = _finditem(d, field)
out = out.copy()
out.update({unique_key: convert_keys(json.loads(json.dumps(d)))})
elif isinstance(field, tuple):
unique_key = ':'.join([_finditem(d, el) for el in field])
out = out.copy()
out.update({unique_key: convert_keys(json.loads(json.dumps(d)))})
else:
out.append(convert_keys(json.loads(json.dumps(d))))

return out

def get_all(self):
self.netapp_info['net_interface_info'] = self.get_generic_get_iter(
'net-interface-get-iter',
attribute='net-interface-info',
field='interface-name',
query={'max-records': '1024'}
)
self.netapp_info['net_port_info'] = self.get_generic_get_iter(
'net-port-get-iter',
attribute='net-port-info',
field=('node', 'port'),
query={'max-records': '1024'}
)
self.netapp_info['cluster_node_info'] = self.get_generic_get_iter(
'cluster-node-get-iter',
attribute='cluster-node-info',
field='node-name',
query={'max-records': '1024'}
)
self.netapp_info['security_login_account_info'] = self.get_generic_get_iter(
'security-login-get-iter',
attribute='security-login-account-info',
field=('user-name', 'application', 'authentication-method'),
query={'max-records': '1024'}
)
self.netapp_info['aggregate_info'] = self.get_generic_get_iter(
'aggr-get-iter',
attribute='aggr-attributes',
field='aggregate-name',
query={'max-records': '1024'}
)
self.netapp_info['volume_info'] = self.get_generic_get_iter(
'volume-get-iter',
attribute='volume-attributes',
field=('name', 'node', 'aggr-name'),
query={'max-records': '1024'}
)
self.netapp_info['lun_info'] = self.get_generic_get_iter(
'lun-get-iter',
attribute='lun-info',
field='path',
query={'max-records': '1024'}
)
self.netapp_info['storage_failover_info'] = self.get_generic_get_iter(
'cf-get-iter',
attribute='storage-failover-info',
field='node',
query={'max-records': '1024'}
)

self.netapp_info['net_ifgrp_info'] = self.get_ifgrp_info()

self.netapp_info['vserver_motd_info'] = self.get_generic_get_iter(
'vserver-motd-get-iter',
attribute='vserver-motd-info',
field='vserver',
query={'max-records': '1024'}
)

self.netapp_info['vserver_login_banner_info'] = self.get_generic_get_iter(
'vserver-login-banner-get-iter',
attribute='vserver-login-banner-info',
field='vserver',
query={'max-records': '1024'}
)

self.netapp_info['security_key_manager_key_info'] = self.get_generic_get_iter(
'security-key-manager-key-get-iter',
attribute='security-key-manager-key-info',
field=('node', 'key-id'),
query={'max-records': '1024'}
)
self.netapp_info['vserver_info'] = self.get_generic_get_iter(
'vserver-get-iter',
attribute='vserver-info',
field='vserver-name',
query={'max-records': '1024'}
)

return self.netapp_info


# https://stackoverflow.com/questions/14962485/finding-a-key-recursively-in-a-dictionary
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)
if item is not None:
return item
return None


def convert_keys(d):
out = {}
if isinstance(d, dict):
for k, v in d.items():
v = convert_keys(v)
out[k.replace('-', '_')] = v
else:
return d
return out


def main():
argument_spec = netapp_utils.na_ontap_host_argument_spec()
argument_spec.update(dict(
state=dict(default='info', choices=['info']),
))

module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)

if not HAS_XMLTODICT:
module.fail_json(msg="xmltodict missing")

if not HAS_JSON:
module.fail_json(msg="json missing")

state = module.params['state']
v = NetAppGatherFacts(module)
g = v.get_all()
result = {'state': state, 'changed': False}
module.exit_json(ansible_facts={'ontap_facts': g}, **result)


if __name__ == '__main__':
main()