From e3b6a07f6e65d8307139cfc70da10333ecdc1f40 Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Wed, 20 Mar 2019 06:56:23 -0700 Subject: [PATCH] Adds Gather Subset Feature to na_ontap_gather_facts (#53993) * Revert "changes to clusteR" This reverts commit 33ee1b71e4bc8435fb315762a871f8c4cb6c5f80. * Revert "changes to clusteR" This reverts commit 33ee1b71e4bc8435fb315762a871f8c4cb6c5f80. * Revert "Revert "changes to clusteR"" This reverts commit f1104a37b42886aebb4d2b2ab27c91c96d97858a. * Revert "Revert "changes to clusteR"" This reverts commit f1104a37b42886aebb4d2b2ab27c91c96d97858a. * Revert "documentation changes" This reverts commit 02c369d0414fdff492d90865c903bdade3174261. * Revert "changes to clusteR" This reverts commit 33ee1b71e4bc8435fb315762a871f8c4cb6c5f80. * Revert "changes to clusteR" This reverts commit 33ee1b71e4bc8435fb315762a871f8c4cb6c5f80. * Revert "Revert "changes to clusteR"" This reverts commit f1104a37b42886aebb4d2b2ab27c91c96d97858a. * Revert "Revert "changes to clusteR"" This reverts commit f1104a37b42886aebb4d2b2ab27c91c96d97858a. * Revert "documentation changes" This reverts commit 02c369d0414fdff492d90865c903bdade3174261. * Revert "Revert "documentation changes"" This reverts commit fffeb974104f2da047435021e0dce94311e945b1. * Gather facts * Revert "Gather facts" This reverts commit 67bfd54f6780818854b5e5d99db52afef89ac4f2. * Revert "Revert "Gather facts"" This reverts commit 199c27c520bdfce1fc2dfa685dc953cb56381de2. * Revert "Gather facts" This reverts commit fc6d21418123d03af3cedd1903175277b1612856. * Revert "changes to clusteR" This reverts commit 33ee1b71e4bc8435fb315762a871f8c4cb6c5f80. * Revert "changes to clusteR" This reverts commit 33ee1b71e4bc8435fb315762a871f8c4cb6c5f80. * Revert "Revert "changes to clusteR"" This reverts commit f1104a37b42886aebb4d2b2ab27c91c96d97858a. * Revert "Revert "changes to clusteR"" This reverts commit f1104a37b42886aebb4d2b2ab27c91c96d97858a. * Revert "documentation changes" This reverts commit 02c369d0414fdff492d90865c903bdade3174261. * Gather facts * Revert "Gather facts" This reverts commit 67bfd54f6780818854b5e5d99db52afef89ac4f2. * Revert "Revert "Gather facts"" This reverts commit 199c27c520bdfce1fc2dfa685dc953cb56381de2. * Revert "Gather facts" This reverts commit fc6d21418123d03af3cedd1903175277b1612856. * revert * Revert "Revert "documentation changes"" This reverts commit fffeb974104f2da047435021e0dce94311e945b1. * fix conflicts --- .../storage/netapp/na_ontap_gather_facts.py | 390 +++++++++++++----- .../netapp/test_na_ontap_gather_facts.py | 241 +++++++++-- 2 files changed, 493 insertions(+), 138 deletions(-) diff --git a/lib/ansible/modules/storage/netapp/na_ontap_gather_facts.py b/lib/ansible/modules/storage/netapp/na_ontap_gather_facts.py index 782ff59def..1448ab722c 100644 --- a/lib/ansible/modules/storage/netapp/na_ontap_gather_facts.py +++ b/lib/ansible/modules/storage/netapp/na_ontap_gather_facts.py @@ -1,6 +1,7 @@ #!/usr/bin/python # (c) 2018 Piotr Olczak +# (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 @@ -26,8 +27,24 @@ options: description: - Returns "info" default: "info" - required: false choices: ['info'] + gather_subset: + 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", + "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" + 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. + - nvme is supported with ONTAP 9.4 onwards. + - use "help" to get a list of supported facts for your system. + default: "all" + version_added: 2.8 ''' EXAMPLES = ''' @@ -37,9 +54,33 @@ EXAMPLES = ''' hostname: "na-vsim" username: "admin" password: "admins_password" - - debug: var: ontap_facts +- name: Limit Fact Gathering to Aggregate Information + na_ontap_gather_facts: + state: info + hostname: "na-vsim" + username: "admin" + password: "admins_password" + gather_subset: "aggregate_info" +- name: Limit Fact Gathering to Volume and Lun Information + na_ontap_gather_facts: + state: info + hostname: "na-vsim" + username: "admin" + password: "admins_password" + gather_subset: + - volume_info + - lun_info +- name: Gather all facts except for volume and lun information + na_ontap_gather_facts: + state: info + hostname: "na-vsim" + username: "admin" + password: "admins_password" + gather_subset: + - "!volume_info" + - "!lun_info" ''' RETURN = ''' @@ -92,20 +133,199 @@ class NetAppONTAPGatherFacts(object): self.module = module self.netapp_info = dict() + # thanks to coreywan (https://github.com/ansible/ansible/pull/47016) + # for starting this + # min_version identifies the ontapi version which supports this ZAPI + # use 0 if it is supported since 9.1 + self.fact_subsets = { + 'net_interface_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'net-interface-get-iter', + 'attribute': 'net-interface-info', + 'field': 'interface-name', + 'query': {'max-records': '1024'}, + }, + 'min_version': '0', + }, + 'net_port_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'net-port-get-iter', + 'attribute': 'net-port-info', + 'field': ('node', 'port'), + 'query': {'max-records': '1024'}, + }, + 'min_version': '0', + }, + 'cluster_node_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'cluster-node-get-iter', + 'attribute': 'cluster-node-info', + 'field': 'node-name', + 'query': {'max-records': '1024'}, + }, + 'min_version': '0', + }, + 'security_login_account_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'security-login-get-iter', + 'attribute': 'security-login-account-info', + 'field': ('user-name', 'application', 'authentication-method'), + 'query': {'max-records': '1024'}, + }, + 'min_version': '0', + }, + 'aggregate_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'aggr-get-iter', + 'attribute': 'aggr-attributes', + 'field': 'aggregate-name', + 'query': {'max-records': '1024'}, + }, + 'min_version': '0', + }, + 'volume_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'volume-get-iter', + 'attribute': 'volume-attributes', + 'field': ('name', 'owning-vserver-name'), + 'query': {'max-records': '1024'}, + }, + 'min_version': '0', + }, + 'lun_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'lun-get-iter', + 'attribute': 'lun-info', + 'field': 'path', + 'query': {'max-records': '1024'}, + }, + 'min_version': '0', + }, + 'storage_failover_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'cf-get-iter', + 'attribute': 'storage-failover-info', + 'field': 'node', + 'query': {'max-records': '1024'}, + }, + 'min_version': '0', + }, + 'vserver_motd_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'vserver-motd-get-iter', + 'attribute': 'vserver-motd-info', + 'field': 'vserver', + 'query': {'max-records': '1024'}, + }, + 'min_version': '0', + }, + 'vserver_login_banner_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'vserver-login-banner-get-iter', + 'attribute': 'vserver-login-banner-info', + 'field': 'vserver', + 'query': {'max-records': '1024'}, + }, + 'min_version': '0', + }, + 'security_key_manager_key_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'security-key-manager-key-get-iter', + 'attribute': 'security-key-manager-key-info', + 'field': ('node', 'key-id'), + 'query': {'max-records': '1024'}, + }, + 'min_version': '0', + }, + 'vserver_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'vserver-get-iter', + 'attribute': 'vserver-info', + 'field': 'vserver-name', + 'query': {'max-records': '1024'}, + }, + 'min_version': '0', + }, + 'net_ifgrp_info': { + 'method': self.get_ifgrp_info, + 'kwargs': {}, + 'min_version': '0', + }, + 'ontap_version': { + 'method': self.ontapi, + 'kwargs': {}, + 'min_version': '0', + }, + # supported in ONTAP 9.4 and onwards + 'nvme_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'nvme-get-iter', + 'attribute': 'nvme-target-service-info', + 'field': 'vserver', + 'query': {'max-records': '1024'}, + }, + 'min_version': '140', + }, + 'nvme_interface_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'nvme-interface-get-iter', + 'attribute': 'nvme-interface-info', + 'field': 'vserver', + 'query': {'max-records': '1024'}, + }, + 'min_version': '140', + }, + 'nvme_subsystem_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'nvme-subsystem-get-iter', + 'attribute': 'nvme-subsystem-info', + 'field': 'subsystem', + 'query': {'max-records': '1024'}, + }, + 'min_version': '140', + }, + 'nvme_namespace_info': { + 'method': self.get_generic_get_iter, + 'kwargs': { + 'call': 'nvme-namespace-get-iter', + 'attribute': 'nvme-namespace-info', + 'field': 'path', + 'query': {'max-records': '1024'}, + }, + 'min_version': '140', + }, + } + 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 ontapi(self): - api_call = netapp_utils.zapi.NaElement('system-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: self.module.fail_json(msg="Error calling API %s: %s" % - (api_call.to_string(), to_native(e)), exception=traceback.format_exc()) + (api, to_native(e)), exception=traceback.format_exc()) def call_api(self, call, query=None): api_call = netapp_utils.zapi.NaElement(call) @@ -125,7 +345,11 @@ class NetAppONTAPGatherFacts(object): 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'] + try: + net_port_info = self.netapp_info['net_port_info'] + except KeyError: + net_port_info_calls = self.fact_subsets['net_port_info'] + net_port_info = net_port_info_calls['method'](**net_port_info_calls['kwargs']) interfaces = net_port_info.keys() ifgrps = [] @@ -178,121 +402,59 @@ class NetAppONTAPGatherFacts(object): return out - def get_all(self): + def get_all(self, gather_subset): 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) self.netapp_info['ontap_version'] = self.ontapi() - if self.netapp_info['ontap_version'] >= '140': - self.netapp_info['nvme_info'] = self.get_generic_get_iter( - 'nvme-get-iter', - attribute='nvme-target-service-info', - field='vserver', - query={'max-records': '1024'} - ) - self.netapp_info['nvme_interface_info'] = self.get_generic_get_iter( - 'nvme-interface-get-iter', - attribute='nvme-interface-info', - field='vserver', - query={'max-records': '1024'} - ) - self.netapp_info['nvme_subsystem_info'] = self.get_generic_get_iter( - 'nvme-subsystem-get-iter', - attribute='nvme-subsystem-info', - field='subsystem', - query={'max-records': '1024'} - ) - self.netapp_info['nvme_namespace_info'] = self.get_generic_get_iter( - 'nvme-namespace-get-iter', - attribute='nvme-namespace-info', - field='path', - query={'max-records': '1024'} - ) - - 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', 'owning-vserver-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'} - ) + run_subset = self.get_subset(gather_subset, self.netapp_info['ontap_version']) + if 'help' in gather_subset: + self.netapp_info['help'] = sorted(run_subset) + else: + for subset in run_subset: + call = self.fact_subsets[subset] + self.netapp_info[subset] = call['method'](**call['kwargs']) return self.netapp_info + def get_subset(self, gather_subset, version): + runable_subsets = set() + exclude_subsets = set() + usable_subsets = [key for key in self.fact_subsets.keys() if version >= self.fact_subsets[key]['min_version']] + if 'help' in gather_subset: + return usable_subsets + for subset in gather_subset: + if subset == 'all': + runable_subsets.update(usable_subsets) + return runable_subsets + if subset.startswith('!'): + subset = subset[1:] + if subset == 'all': + return set() + exclude = True + else: + exclude = False + + if subset not in usable_subsets: + if subset not in self.fact_subsets.keys(): + self.module.fail_json(msg='Bad subset: %s' % subset) + self.module.fail_json(msg='Remote system at version %s does not support %s' % + (version, subset)) + + if exclude: + exclude_subsets.add(subset) + else: + runable_subsets.add(subset) + + if not runable_subsets: + runable_subsets.update(usable_subsets) + + runable_subsets.difference_update(exclude_subsets) + + return runable_subsets + # https://stackoverflow.com/questions/14962485/finding-a-key-recursively-in-a-dictionary def __finditem(obj, key): @@ -330,6 +492,7 @@ def main(): argument_spec = netapp_utils.na_ontap_host_argument_spec() argument_spec.update(dict( state=dict(default='info', choices=['info']), + gather_subset=dict(default=['all'], type='list'), )) module = AnsibleModule( @@ -344,8 +507,11 @@ def main(): module.fail_json(msg="json missing") state = module.params['state'] + gather_subset = module.params['gather_subset'] + if gather_subset is None: + gather_subset = ['all'] v = NetAppONTAPGatherFacts(module) - g = v.get_all() + g = v.get_all(gather_subset) result = {'state': state, 'changed': False} module.exit_json(ansible_facts={'ontap_facts': g}, **result) diff --git a/test/units/modules/storage/netapp/test_na_ontap_gather_facts.py b/test/units/modules/storage/netapp/test_na_ontap_gather_facts.py index e99217e497..94e722266c 100644 --- a/test/units/modules/storage/netapp/test_na_ontap_gather_facts.py +++ b/test/units/modules/storage/netapp/test_na_ontap_gather_facts.py @@ -1,4 +1,4 @@ -# (c) 2018, NetApp, Inc +# (c) 2018-2019, NetApp, Inc # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ''' unit tests for ONTAP Ansible module na_ontap_gather_facts ''' @@ -14,8 +14,10 @@ from ansible.module_utils import basic from ansible.module_utils._text import to_bytes import ansible.module_utils.netapp as netapp_utils +from ansible.modules.storage.netapp.na_ontap_gather_facts import main as gather_facts_main +from ansible.modules.storage.netapp.na_ontap_gather_facts import __finditem as gather_facts_finditem from ansible.modules.storage.netapp.na_ontap_gather_facts \ - import NetAppONTAPGatherFacts as my_module # module under test + import NetAppONTAPGatherFacts as gather_facts_module # module under test if not netapp_utils.has_netapp_lib(): pytestmark = pytest.mark.skip('skipping as missing required netapp_lib') @@ -53,10 +55,9 @@ def fail_json(*args, **kwargs): # pylint: disable=unused-argument class MockONTAPConnection(object): ''' mock server connection to ONTAP host ''' - def __init__(self, kind=None, parm1=None): + def __init__(self, kind=None): ''' save arguments ''' self.type = kind - self.parm1 = parm1 self.xml_in = None self.xml_out = None @@ -65,20 +66,39 @@ class MockONTAPConnection(object): self.xml_in = xml print(xml.to_string()) if self.type == 'vserver': - xml = self.build_vserver_info(self.parm1) + xml = self.build_vserver_info() + elif self.type == 'net_port': + xml = self.build_net_port_info() + elif self.type == 'zapi_error': + error = netapp_utils.zapi.NaApiError('test', 'error') + raise error self.xml_out = xml - print(xml.to_string()) return xml @staticmethod - def build_vserver_info(vserver): + def build_vserver_info(): ''' build xml data for vserser-info ''' xml = netapp_utils.zapi.NaElement('xml') attributes = netapp_utils.zapi.NaElement('attributes-list') attributes.add_node_with_children('vserver-info', - **{'vserver-name': vserver}) + **{'vserver-name': 'test_vserver'}) xml.add_child_elem(attributes) - # print(xml.to_string()) + return xml + + @staticmethod + def build_net_port_info(): + ''' build xml data for net-port-info ''' + xml = netapp_utils.zapi.NaElement('xml') + attributes_list = netapp_utils.zapi.NaElement('attributes-list') + num_net_port_info = 2 + for i in range(num_net_port_info): + net_port_info = netapp_utils.zapi.NaElement('net-port-info') + net_port_info.add_new_child('node', 'node_' + str(i)) + net_port_info.add_new_child('port', 'port_' + str(i)) + net_port_info.add_new_child('broadcast_domain', 'test_domain_' + str(i)) + net_port_info.add_new_child('ipspace', 'ipspace' + str(i)) + attributes_list.add_child_elem(net_port_info) + xml.add_child_elem(attributes_list) return xml @@ -93,33 +113,202 @@ class TestMyModule(unittest.TestCase): self.addCleanup(self.mock_module_helper.stop) self.server = MockONTAPConnection() + def mock_args(self): + return { + 'hostname': 'hostname', + 'username': 'username', + 'password': 'password', + } + + def get_gather_facts_mock_object(self, kind=None): + """ + Helper method to return an na_ontap_gather_facts object + """ + module = basic.AnsibleModule( + argument_spec=netapp_utils.na_ontap_host_argument_spec(), + supports_check_mode=True + ) + obj = gather_facts_module(module) + obj.netapp_info = dict() + if kind is None: + obj.server = MockONTAPConnection() + else: + obj.server = MockONTAPConnection(kind) + return obj + def test_module_fail_when_required_args_missing(self): ''' required arguments are reported as errors ''' with pytest.raises(AnsibleFailJson) as exc: set_module_args({}) - module = basic.AnsibleModule( - argument_spec=netapp_utils.na_ontap_host_argument_spec(), - supports_check_mode=True - ) - my_module(module) + self.get_gather_facts_mock_object() print('Info: %s' % exc.value.args[0]['msg']) @patch('ansible.module_utils.netapp.ems_log_event') def test_ensure_command_called(self, mock_ems_log): ''' calling get_all will raise a KeyError exception ''' - set_module_args({ - 'hostname': 'hostname', - 'username': 'username', - 'password': 'password', - }) - module = basic.AnsibleModule( - argument_spec=netapp_utils.na_ontap_host_argument_spec(), - supports_check_mode=True - ) - my_obj = my_module(module) - my_obj.server = MockONTAPConnection('vserver', 'SVMadmin') + set_module_args(self.mock_args()) + my_obj = self.get_gather_facts_mock_object('vserver') with pytest.raises(KeyError) as exc: - my_obj.get_all() + my_obj.get_all(['net_interface_info']) if sys.version_info >= (2, 7): msg = 'net-interface-info' assert exc.value.args[0] == msg + + @patch('ansible.module_utils.netapp.ems_log_event') + def test_get_generic_get_iter(self, mock_ems_log): + '''calling get_generic_get_iter will return expected dict''' + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('net_port') + result = obj.get_generic_get_iter( + 'net-port-get-iter', + attribute='net-port-info', + field=('node', 'port'), + query={'max-records': '1024'} + ) + assert result.get('node_0:port_0') + assert result.get('node_1:port_1') + + @patch('ansible.modules.storage.netapp.na_ontap_gather_facts.NetAppONTAPGatherFacts.get_all') + def test_main(self, get_all): + '''test main method.''' + set_module_args(self.mock_args()) + get_all.side_effect = [ + {'test_get_all': + {'vserver_login_banner_info': 'test_vserver_login_banner_info', 'vserver_info': 'test_vserver_info'}} + ] + with pytest.raises(AnsibleExitJson) as exc: + gather_facts_main() + assert exc.value.args[0]['state'] == 'info' + + @patch('ansible.modules.storage.netapp.na_ontap_gather_facts.NetAppONTAPGatherFacts.get_generic_get_iter') + def test_get_ifgrp_info(self, get_generic_get_iter): + '''test get_ifgrp_info with empty ifgrp_info''' + set_module_args(self.mock_args()) + get_generic_get_iter.side_effect = [ + {} + ] + obj = self.get_gather_facts_mock_object() + obj.netapp_info['net_port_info'] = {} + result = obj.get_ifgrp_info() + assert result == {} + + def test_ontapi_error(self): + '''test ontapi will raise zapi error''' + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('zapi_error') + with pytest.raises(AnsibleFailJson) as exc: + obj.ontapi() + assert exc.value.args[0]['msg'] == 'Error calling API system-get-ontapi-version: NetApp API failed. Reason - test:error' + + def test_call_api_error(self): + '''test call_api will raise zapi error''' + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('zapi_error') + with pytest.raises(AnsibleFailJson) as exc: + obj.call_api('nvme-get-iter') + assert exc.value.args[0]['msg'] == 'Error calling API nvme-get-iter: NetApp API failed. Reason - test:error' + + def test_find_item(self): + '''test __find_item return expected key value''' + obj = {"A": 1, "B": {"C": {"D": 2}}} + key = "D" + result = gather_facts_finditem(obj, key) + assert result == 2 + + def test_subset_return_all_complete(self): + ''' Check all returns all of the entries if version is high enough ''' + version = '140' # change this if new ZAPIs are supported + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('vserver') + subset = obj.get_subset(['all'], version) + assert set(obj.fact_subsets.keys()) == subset + + def test_subset_return_all_partial(self): + ''' Check all returns a subset of the entries if version is low enough ''' + version = '120' # low enough so that some ZAPIs are not supported + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('vserver') + subset = obj.get_subset(['all'], version) + all_keys = obj.fact_subsets.keys() + assert set(all_keys) > subset + supported_keys = filter(lambda key: obj.fact_subsets[key]['min_version'] <= version, all_keys) + assert set(supported_keys) == subset + + def test_subset_return_one(self): + ''' Check single entry returns one ''' + version = '120' # low enough so that some ZAPIs are not supported + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('vserver') + subset = obj.get_subset(['net_interface_info'], version) + assert len(subset) == 1 + + def test_subset_return_multiple(self): + ''' Check that more than one entry returns the same number ''' + version = '120' # low enough so that some ZAPIs are not supported + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('vserver') + subset_entries = ['net_interface_info', 'net_port_info'] + subset = obj.get_subset(subset_entries, version) + assert len(subset) == len(subset_entries) + + def test_subset_return_bad(self): + ''' Check that a bad subset entry will error out ''' + version = '120' # low enough so that some ZAPIs are not supported + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('vserver') + with pytest.raises(AnsibleFailJson) as exc: + subset = obj.get_subset(['net_interface_info', 'my_invalid_subset'], version) + print('Info: %s' % exc.value.args[0]['msg']) + assert exc.value.args[0]['msg'] == 'Bad subset: my_invalid_subset' + + def test_subset_return_unsupported(self): + ''' Check that a new subset entry will error out on an older system ''' + version = '120' # low enough so that some ZAPIs are not supported + key = 'nvme_info' # only supported starting at 140 + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('vserver') + with pytest.raises(AnsibleFailJson) as exc: + subset = obj.get_subset(['net_interface_info', key], version) + print('Info: %s' % exc.value.args[0]['msg']) + msg = 'Remote system at version %s does not support %s' % (version, key) + assert exc.value.args[0]['msg'] == msg + + def test_subset_return_none(self): + ''' Check usable subset can be empty ''' + version = '!' # lower then 0, so that no ZAPI is supported + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('vserver') + subset = obj.get_subset(['all'], version) + assert len(subset) == 0 + + def test_subset_return_all_expect_one(self): + ''' Check !x returns all of the entries except x if version is high enough ''' + version = '140' # change this if new ZAPIs are supported + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('vserver') + subset = obj.get_subset(['!net_interface_info'], version) + assert len(obj.fact_subsets.keys()) == len(subset) + 1 + subset.add('net_interface_info') + assert set(obj.fact_subsets.keys()) == subset + + def test_subset_return_all_expect_three(self): + ''' Check !x,!y,!z returns all of the entries except x, y, z if version is high enough ''' + version = '140' # change this if new ZAPIs are supported + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('vserver') + subset = obj.get_subset(['!net_interface_info', '!nvme_info', '!ontap_version'], version) + assert len(obj.fact_subsets.keys()) == len(subset) + 3 + subset.update(['net_interface_info', 'nvme_info', 'ontap_version']) + assert set(obj.fact_subsets.keys()) == subset + + def test_subset_return_none_with_exclusion(self): + ''' Check usable subset can be empty with !x ''' + version = '!' # lower then 0, so that no ZAPI is supported + key = 'net_interface_info' + set_module_args(self.mock_args()) + obj = self.get_gather_facts_mock_object('vserver') + with pytest.raises(AnsibleFailJson) as exc: + subset = obj.get_subset(['!' + key], version) + print('Info: %s' % exc.value.args[0]['msg']) + msg = 'Remote system at version %s does not support %s' % (version, key) + assert exc.value.args[0]['msg'] == msg